mirror of
https://github.com/FJurmanovic/wallet-go-api.git
synced 2026-02-06 14:18:12 +00:00
add cron job to sync currencies once in 24 hours
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
"wallet-api/pkg/controller"
|
||||
"wallet-api/pkg/job"
|
||||
"wallet-api/pkg/middleware"
|
||||
"wallet-api/pkg/utl/common"
|
||||
"wallet-api/pkg/utl/configs"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-co-op/gocron"
|
||||
"github.com/go-pg/pg/v10"
|
||||
"go.uber.org/dig"
|
||||
)
|
||||
@@ -22,6 +26,10 @@ Initializes web api controllers and its corresponding routes.
|
||||
*/
|
||||
func Routes(s *gin.Engine, db *pg.DB) {
|
||||
c := dig.New()
|
||||
|
||||
scheduler := gocron.NewScheduler(time.UTC)
|
||||
scheduler.SetMaxConcurrentJobs(3, 1)
|
||||
defer scheduler.StartAsync()
|
||||
ver := s.Group(configs.Prefix)
|
||||
|
||||
routeGroups := &common.RouteGroups{
|
||||
@@ -46,5 +54,13 @@ func Routes(s *gin.Engine, db *pg.DB) {
|
||||
c.Provide(func() *pg.DB {
|
||||
return db
|
||||
})
|
||||
c.Provide(func() *gocron.Scheduler {
|
||||
return scheduler
|
||||
})
|
||||
c.Provide(func() *log.Logger {
|
||||
return log.Default()
|
||||
})
|
||||
controller.InitializeControllers(c)
|
||||
job.InitializeJobs(c)
|
||||
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go.uber.org/dig"
|
||||
"strconv"
|
||||
"strings"
|
||||
"wallet-api/pkg/model"
|
||||
"wallet-api/pkg/service"
|
||||
"wallet-api/pkg/utl/common"
|
||||
|
||||
"go.uber.org/dig"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -21,17 +22,18 @@ Initializes Dependency Injection modules and registers controllers
|
||||
*dig.Container: Dig Container
|
||||
*/
|
||||
func InitializeControllers(c *dig.Container) {
|
||||
service.InitializeServices(c)
|
||||
controllerContainer := c.Scope("controller")
|
||||
service.InitializeServices(controllerContainer)
|
||||
|
||||
c.Invoke(NewApiController)
|
||||
c.Invoke(NewUserController)
|
||||
c.Invoke(NewWalletController)
|
||||
c.Invoke(NewWalletHeaderController)
|
||||
c.Invoke(NewTransactionController)
|
||||
c.Invoke(NewTransactionStatusController)
|
||||
c.Invoke(NewTransactionTypeController)
|
||||
c.Invoke(NewSubscriptionController)
|
||||
c.Invoke(NewSubscriptionTypeController)
|
||||
controllerContainer.Invoke(NewApiController)
|
||||
controllerContainer.Invoke(NewUserController)
|
||||
controllerContainer.Invoke(NewWalletController)
|
||||
controllerContainer.Invoke(NewWalletHeaderController)
|
||||
controllerContainer.Invoke(NewTransactionController)
|
||||
controllerContainer.Invoke(NewTransactionStatusController)
|
||||
controllerContainer.Invoke(NewTransactionTypeController)
|
||||
controllerContainer.Invoke(NewSubscriptionController)
|
||||
controllerContainer.Invoke(NewSubscriptionTypeController)
|
||||
|
||||
}
|
||||
|
||||
|
||||
52
pkg/job/currency.go
Normal file
52
pkg/job/currency.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"wallet-api/pkg/service"
|
||||
"wallet-api/pkg/utl/common"
|
||||
|
||||
"github.com/go-co-op/gocron"
|
||||
)
|
||||
|
||||
type CurrencyController struct {
|
||||
service *service.CurrencyService
|
||||
logger *log.Logger
|
||||
scheduler *gocron.Scheduler
|
||||
}
|
||||
|
||||
/*
|
||||
NewCurrencyJob
|
||||
|
||||
Initializes CurrencyJob.
|
||||
|
||||
Args:
|
||||
*services.CurrencyService: Currency service
|
||||
*gin.RouterGroup: Gin Router Group
|
||||
Returns:
|
||||
*CurrencyJob: Job for "Currency" route interactions
|
||||
*/
|
||||
func NewCurrencyJob(as *service.CurrencyService, scheduler *gocron.Scheduler, logger *log.Logger) *CurrencyController {
|
||||
currencyScheduler := scheduler.Tag("currency")
|
||||
|
||||
wc := &CurrencyController{
|
||||
service: as,
|
||||
logger: logger,
|
||||
scheduler: currencyScheduler,
|
||||
}
|
||||
|
||||
_, err := currencyScheduler.Every(1).Days().Do(wc.Sync)
|
||||
common.CheckError(err)
|
||||
currencyScheduler.StartAsync()
|
||||
|
||||
log.Println("CurrencyJob started")
|
||||
|
||||
return wc
|
||||
}
|
||||
|
||||
func (wc *CurrencyController) Sync() {
|
||||
wc.logger.Println("CurrencyJob: Syncing currencies")
|
||||
ctx := context.Background()
|
||||
wc.service.Sync(ctx)
|
||||
wc.logger.Println("CurrencyJob: Syncing currencies done")
|
||||
}
|
||||
33
pkg/job/job.go
Normal file
33
pkg/job/job.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"wallet-api/pkg/service"
|
||||
"wallet-api/pkg/utl/common"
|
||||
|
||||
"go.uber.org/dig"
|
||||
)
|
||||
|
||||
/*
|
||||
InitializeJobs
|
||||
|
||||
Initializes Dependency Injection modules and registers Jobs
|
||||
|
||||
Args:
|
||||
*dig.Container: Dig Container
|
||||
*/
|
||||
func InitializeJobs(c *dig.Container) {
|
||||
file, err := os.OpenFile("job.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
|
||||
common.CheckError(err)
|
||||
logger := log.New(file, "Job: ", log.Ldate|log.Ltime|log.Lshortfile)
|
||||
|
||||
jobContainer := c.Scope("job")
|
||||
jobContainer.Provide(func() *log.Logger {
|
||||
return logger
|
||||
})
|
||||
|
||||
service.InitializeServices(jobContainer)
|
||||
|
||||
jobContainer.Invoke(NewCurrencyJob)
|
||||
}
|
||||
@@ -27,7 +27,7 @@ func CreateTableTransactionStatus(db *pg.Tx) error {
|
||||
|
||||
for _, model := range models {
|
||||
err := db.Model(model).CreateTable(&orm.CreateTableOptions{
|
||||
IfNotExists: false,
|
||||
IfNotExists: true,
|
||||
FKConstraints: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
40
pkg/migrate/12_create_table_currencies.go
Normal file
40
pkg/migrate/12_create_table_currencies.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"wallet-api/pkg/model"
|
||||
|
||||
"github.com/go-pg/pg/v10"
|
||||
"github.com/go-pg/pg/v10/orm"
|
||||
)
|
||||
|
||||
/*
|
||||
CreateTableCurrencies
|
||||
|
||||
Creates Currencies table if it does not exist.
|
||||
|
||||
Args:
|
||||
*pg.DB: Postgres database client
|
||||
Returns:
|
||||
error: Returns if there is an error with table creation
|
||||
*/
|
||||
func CreateTableCurrencies(db *pg.Tx) error {
|
||||
models := []interface{}{
|
||||
(*model.Currency)(nil),
|
||||
}
|
||||
|
||||
for _, model := range models {
|
||||
err := db.Model(model).CreateTable(&orm.CreateTableOptions{
|
||||
IfNotExists: true,
|
||||
FKConstraints: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Error creating table \"currencies\": %s", err)
|
||||
return err
|
||||
} else {
|
||||
fmt.Println("Table \"currencies\" created successfully")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -27,7 +27,7 @@ func CreateTableWallets(db *pg.Tx) error {
|
||||
|
||||
for _, model := range models {
|
||||
err := db.Model(model).CreateTable(&orm.CreateTableOptions{
|
||||
IfNotExists: false,
|
||||
IfNotExists: true,
|
||||
FKConstraints: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -27,7 +27,7 @@ func CreateTableTransactionTypes(db *pg.Tx) error {
|
||||
|
||||
for _, model := range models {
|
||||
err := db.Model(model).CreateTable(&orm.CreateTableOptions{
|
||||
IfNotExists: false,
|
||||
IfNotExists: true,
|
||||
FKConstraints: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -27,7 +27,7 @@ func CreateTableTransactions(db *pg.Tx) error {
|
||||
|
||||
for _, model := range models {
|
||||
err := db.Model(model).CreateTable(&orm.CreateTableOptions{
|
||||
IfNotExists: false,
|
||||
IfNotExists: true,
|
||||
FKConstraints: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -27,7 +27,7 @@ func CreateTableSubscriptionTypes(db *pg.Tx) error {
|
||||
|
||||
for _, model := range models {
|
||||
err := db.Model(model).CreateTable(&orm.CreateTableOptions{
|
||||
IfNotExists: false,
|
||||
IfNotExists: true,
|
||||
FKConstraints: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -26,7 +26,7 @@ func CreateTableSubscriptions(db *pg.Tx) error {
|
||||
|
||||
for _, model := range models {
|
||||
err := db.Model(model).CreateTable(&orm.CreateTableOptions{
|
||||
IfNotExists: false,
|
||||
IfNotExists: true,
|
||||
FKConstraints: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -48,12 +48,19 @@ func Start(conn *pg.DB, version string) []error {
|
||||
CreateTableTransactions,
|
||||
},
|
||||
}
|
||||
migration005 := Migration{
|
||||
Version: "005",
|
||||
Migrations: []interface{}{
|
||||
CreateTableCurrencies,
|
||||
},
|
||||
}
|
||||
|
||||
migrationsMap := []Migration{
|
||||
migration001,
|
||||
migration002,
|
||||
migration003,
|
||||
migration004,
|
||||
migration005,
|
||||
}
|
||||
|
||||
var errors []error
|
||||
|
||||
63
pkg/model/currency.go
Normal file
63
pkg/model/currency.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
)
|
||||
|
||||
type Currency struct {
|
||||
tableName struct{} `pg:"currencies,alias:currencies"`
|
||||
BaseModel
|
||||
Rate float32 `json:"rate", pg:"rate,default:0"`
|
||||
Name string `json:"name", pg:"name,unique"`
|
||||
}
|
||||
|
||||
type CurrencyEdit struct {
|
||||
tableName struct{} `pg:"currencies,alias:currencies"`
|
||||
Id string `json:"id" form:"id"`
|
||||
Rate json.Number `json:"rate", form:"rate"`
|
||||
Name string `json:"name", form:"name"`
|
||||
}
|
||||
|
||||
type NewCurrencyBody struct {
|
||||
Rate json.Number `json:"rate", form:"rate"`
|
||||
Name string `json:"name", form:"name"`
|
||||
}
|
||||
|
||||
type ExchangeBody struct {
|
||||
Base string `json:"base"`
|
||||
Rates interface{} `json:"rates"`
|
||||
}
|
||||
|
||||
type Rate struct {
|
||||
Code string `json:"code"`
|
||||
Rate float64 `json:"rate"`
|
||||
}
|
||||
|
||||
func (body *CurrencyEdit) ToCurrency() *Currency {
|
||||
rate, _ := body.Rate.Float64()
|
||||
tm := new(Currency)
|
||||
tm.Id = body.Id
|
||||
tm.Rate = float32(math.Round(rate*100) / 100)
|
||||
tm.Name = body.Name
|
||||
|
||||
return tm
|
||||
}
|
||||
|
||||
func (body *NewCurrencyBody) ToCurrency() *Currency {
|
||||
rate, _ := body.Rate.Float64()
|
||||
tm := new(Currency)
|
||||
tm.Init()
|
||||
tm.Rate = float32(math.Round(rate*100) / 100)
|
||||
tm.Name = body.Name
|
||||
return tm
|
||||
}
|
||||
|
||||
func (body *ExchangeBody) Unmarshal(resp *[]interface{}) *ExchangeBody {
|
||||
body.Base = (*resp)[3].(string)
|
||||
// body.Rates = []Rate{}
|
||||
// for k, v := range (*resp)[5].(map[string]interface{}) {
|
||||
// body.Rates = append(body.Rates, Rate{Code: k, Rate: v.(float64)})
|
||||
// }
|
||||
return body
|
||||
}
|
||||
84
pkg/repository/currency.go
Normal file
84
pkg/repository/currency.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
"wallet-api/pkg/model"
|
||||
|
||||
"github.com/go-pg/pg/v10"
|
||||
)
|
||||
|
||||
type CurrencyRepository struct {
|
||||
db *pg.DB
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewCurrencyRepository(db *pg.DB, logger *log.Logger) *CurrencyRepository {
|
||||
return &CurrencyRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
GetFirst
|
||||
|
||||
Gets first row from Currency table.
|
||||
|
||||
Args:
|
||||
context.Context: Application context
|
||||
Returns:
|
||||
model.CurrencyModel: Currency object from database.
|
||||
*/
|
||||
func (as CurrencyRepository) Sync(ctx context.Context, rate *model.Rate, tx *pg.Tx) *model.Currency {
|
||||
currency := new(model.Currency)
|
||||
currency.Name = rate.Code
|
||||
|
||||
any := tx.Model(currency).Where("name = ?", rate.Code).First()
|
||||
currency.Rate = float32(rate.Rate)
|
||||
if any != nil {
|
||||
currency.Init()
|
||||
_, err := tx.Model(currency).Insert()
|
||||
if err != nil {
|
||||
as.logger.Println(err)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
currency.DateUpdated = time.Now()
|
||||
_, err := tx.Model(currency).WherePK().Update()
|
||||
if err != nil {
|
||||
as.logger.Println(err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return currency
|
||||
}
|
||||
|
||||
/*
|
||||
GetFirst
|
||||
|
||||
Gets first row from Currency table.
|
||||
|
||||
Args:
|
||||
context.Context: Application context
|
||||
Returns:
|
||||
model.CurrencyModel: Currency object from database.
|
||||
*/
|
||||
func (as CurrencyRepository) SyncBulk(ctx context.Context, rates *[]model.Rate) *[]model.Currency {
|
||||
tx, _ := as.db.BeginContext(ctx)
|
||||
defer tx.Rollback()
|
||||
|
||||
currencies := new([]model.Currency)
|
||||
|
||||
for _, r := range *rates {
|
||||
currency := as.Sync(ctx, &r, tx)
|
||||
if currency != nil {
|
||||
*currencies = append(*currencies, *currency)
|
||||
}
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
return currencies
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"go.uber.org/dig"
|
||||
"wallet-api/pkg/model"
|
||||
"wallet-api/pkg/utl/common"
|
||||
|
||||
"go.uber.org/dig"
|
||||
|
||||
"github.com/go-pg/pg/v10"
|
||||
)
|
||||
|
||||
@@ -16,7 +17,7 @@ Initializes Dependency Injection modules for repositories
|
||||
Args:
|
||||
*dig.Container: Dig Container
|
||||
*/
|
||||
func InitializeRepositories(c *dig.Container) {
|
||||
func InitializeRepositories(c *dig.Scope) {
|
||||
c.Provide(NewApiRepository)
|
||||
c.Provide(NewSubscriptionRepository)
|
||||
c.Provide(NewSubscriptionTypeRepository)
|
||||
@@ -25,6 +26,7 @@ func InitializeRepositories(c *dig.Container) {
|
||||
c.Provide(NewTransactionTypeRepository)
|
||||
c.Provide(NewUserRepository)
|
||||
c.Provide(NewWalletRepository)
|
||||
c.Provide(NewCurrencyRepository)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
50
pkg/service/currency.go
Normal file
50
pkg/service/currency.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"wallet-api/pkg/model"
|
||||
"wallet-api/pkg/repository"
|
||||
"wallet-api/pkg/utl/common"
|
||||
)
|
||||
|
||||
type CurrencyService struct {
|
||||
repository *repository.CurrencyRepository
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewCurrencyService(repository *repository.CurrencyRepository, logger *log.Logger) *CurrencyService {
|
||||
return &CurrencyService{
|
||||
repository: repository,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
GetFirst
|
||||
|
||||
Gets first row from Currency table.
|
||||
|
||||
Args:
|
||||
context.Context: Application context
|
||||
Returns:
|
||||
model.CurrencyModel: Currency object from database.
|
||||
*/
|
||||
func (as CurrencyService) Sync(ctx context.Context) {
|
||||
resp, err := common.Fetch[model.ExchangeBody]("GET", "https://api.exchangerate-api.com/v4/latest/euro")
|
||||
if err != nil {
|
||||
as.logger.Println(err)
|
||||
return
|
||||
}
|
||||
m := resp.Rates.(map[string]interface{})
|
||||
|
||||
rates := new([]model.Rate)
|
||||
|
||||
for k, v := range m {
|
||||
rate := new(model.Rate)
|
||||
rate.Code = k
|
||||
rate.Rate = v.(float64)
|
||||
*rates = append(*rates, *rate)
|
||||
}
|
||||
as.repository.SyncBulk(ctx, rates)
|
||||
}
|
||||
@@ -14,7 +14,7 @@ Initializes Dependency Injection modules for services
|
||||
Args:
|
||||
*dig.Container: Dig Container
|
||||
*/
|
||||
func InitializeServices(c *dig.Container) {
|
||||
func InitializeServices(c *dig.Scope) {
|
||||
repository.InitializeRepositories(c)
|
||||
|
||||
c.Provide(NewApiService)
|
||||
@@ -25,4 +25,5 @@ func InitializeServices(c *dig.Container) {
|
||||
c.Provide(NewTransactionTypeService)
|
||||
c.Provide(NewUserService)
|
||||
c.Provide(NewWalletService)
|
||||
c.Provide(NewCurrencyService)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type RouteGroups struct {
|
||||
@@ -61,3 +65,24 @@ func Find[T any](lst *[]T, callback func(item *T) bool) *T {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Fetch[T any](method string, url string) (*T, error) {
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := new(T)
|
||||
err = json.Unmarshal(body, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user