gitignore fix + export report feature
This commit is contained in:
parent
2f2ba65abd
commit
26e85dbfe8
55
.env
Normal file
55
.env
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# REPORT_EXPORT_PATH="C:\\ProgramData\\FortuneBet\\exported_reports" #prod env
|
||||||
|
REPORT_EXPORT_PATH ="./exported_reports" #dev env
|
||||||
|
|
||||||
|
RESEND_SENDER_EMAIL=email
|
||||||
|
RESEND_API_KEY=123
|
||||||
|
|
||||||
|
ENV=development
|
||||||
|
PORT=8080
|
||||||
|
DB_URL=postgresql://root:secret@localhost:5422/gh?sslmode=disable
|
||||||
|
REFRESH_EXPIRY=2592000
|
||||||
|
JWT_KEY=mysecretkey
|
||||||
|
ACCESS_EXPIRY=600
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
AFRO_SMS_API_KEY=1
|
||||||
|
AFRO_SMS_SENDER_NAME=
|
||||||
|
|
||||||
|
AFRO_SMS_RECEIVER_PHONE_NUMBER=
|
||||||
|
BET365_TOKEN=158046-hesJDP2Cay2M5G
|
||||||
|
POPOK_CLIENT_ID=1
|
||||||
|
POPOK_PLATFORM=111
|
||||||
|
POPOK_SECRET_KEY=XwFQ76Y59zBxGryh
|
||||||
|
# POPOK_BASE_URL=https://api.pokgaming.com/game/launch #Production
|
||||||
|
# POPOK_BASE_URL=https://games.pokgaming.com/launch #Production
|
||||||
|
# POPOK_BASE_URL=https://sandbox.pokgaming.com/game/launch #Staging
|
||||||
|
# POPOK_BASE_URL=https://test-api.pokgaming.com/launch #Staging
|
||||||
|
POPOK_BASE_URL=https://st.pokgaming.com/ #Staging
|
||||||
|
|
||||||
|
POPOK_CALLBACK_URL=1
|
||||||
|
|
||||||
|
# Chapa API Configuration
|
||||||
|
CHAPA_BASE_URL="https://api.chapa.co/v1"
|
||||||
|
CHAPA_ENCRYPTION_KEY=zLdYrjnBCknMvFikmP5jBfen
|
||||||
|
CHAPA_PUBLIC_KEY=CHAPUBK_TEST-HJR0qhQRPLTkauNy9Q8UrmskPTOR31aC
|
||||||
|
CHAPA_SECRET_KEY=CHASECK_TEST-q3jypwmFK6XJGYOK3aX4z9Kogd9KaHhF
|
||||||
|
CHAPA_CALLBACK_URL="https://fortunebet.com/api/v1/payments/callback" # Optional
|
||||||
|
CHAPA_RETURN_URL="https://fortunebet.com/api/v1/payment-success" # Optional
|
||||||
|
|
||||||
|
#Alea Play
|
||||||
|
ALEA_ENABLED=true
|
||||||
|
ALEA_BASE_URL=https://api.aleaplay.com
|
||||||
|
ALEA_OPERATOR_ID=operator_id
|
||||||
|
ALEA_SECRET_KEY=hmac_secret
|
||||||
|
ALEA_GAME_LIST_URL=https://api.aleaplay.com/games/list # Optional
|
||||||
|
ALEA_DEFAULT_CURRENCY=USD # Optional (default: USD)
|
||||||
|
ALEA_SESSION_TIMEOUT=24 # Optional (hours, default: 24)
|
||||||
|
ALEA_GAME_ID_AVIATOR=aviator_prod
|
||||||
|
|
||||||
|
|
||||||
|
# Veli Games
|
||||||
|
VELI_ENABLED=true
|
||||||
|
VELI_API_URL=https://api.velitech.games
|
||||||
|
VELI_OPERATOR_KEY=Veli123
|
||||||
|
VELI_SECRET_KEY=hmac_secret
|
||||||
|
VELI_GAME_ID_AVIATOR=veli_aviator_v1
|
||||||
|
VELI_DEFAULT_CURRENCY=USD
|
||||||
15
.gitignore
vendored
15
.gitignore
vendored
|
|
@ -1,8 +1,7 @@
|
||||||
bin
|
# bin
|
||||||
coverage.out
|
# coverage.out
|
||||||
coverage
|
# coverage
|
||||||
.env
|
# .env
|
||||||
tmp
|
# tmp
|
||||||
build
|
# build
|
||||||
*.log
|
# *.log
|
||||||
db.sql
|
|
||||||
125
cmd/main.go
125
cmd/main.go
|
|
@ -17,6 +17,7 @@ import (
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/infrastructure"
|
||||||
customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger"
|
customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/logger/mongoLogger"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/logger/mongoLogger"
|
||||||
|
|
||||||
|
|
@ -53,6 +54,7 @@ import (
|
||||||
httpserver "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server"
|
httpserver "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server"
|
||||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||||
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/worker"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @title FortuneBet API
|
// @title FortuneBet API
|
||||||
|
|
@ -69,16 +71,15 @@ import (
|
||||||
// @name Authorization
|
// @name Authorization
|
||||||
// @BasePath /
|
// @BasePath /
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
cfg, err := config.NewConfig()
|
cfg, err := config.NewConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error(" Config error:", "err", err)
|
slog.Error("Config error:", "err", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
db, _, err := repository.OpenDB(cfg.DbUrl)
|
db, _, err := repository.OpenDB(cfg.DbUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(" Database error:", err)
|
fmt.Println("Database error:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,55 +93,25 @@ func main() {
|
||||||
|
|
||||||
zap.ReplaceGlobals(domain.MongoDBLogger)
|
zap.ReplaceGlobals(domain.MongoDBLogger)
|
||||||
|
|
||||||
// client := mongoLogger.InitDB()
|
|
||||||
// defer func() {
|
|
||||||
// if err := client.Disconnect(context.Background()); err != nil {
|
|
||||||
// slog.Error("Failed to disconnect MongoDB", "error", err)
|
|
||||||
// }
|
|
||||||
// }()
|
|
||||||
|
|
||||||
// // 2. Create MongoDB logger handler
|
|
||||||
// handler, err := mongoLogger.NewMongoHandler("logs", "app_logs", slog.LevelDebug)
|
|
||||||
// if err != nil {
|
|
||||||
// slog.Error("Failed to create MongoDB logger", "error", err)
|
|
||||||
// os.Exit(1)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 3. Set as default logger
|
|
||||||
// tempLogger := slog.New(handler)
|
|
||||||
// slog.SetDefault(tempLogger)
|
|
||||||
|
|
||||||
// // 4. Log examples
|
|
||||||
// tempLogger.Info("Application started", "version", "1.0.0")
|
|
||||||
// slog.Warn("Low disk space", "available_gb", 12.5)
|
|
||||||
// slog.Error("Payment failed", "transaction_id", "tx123", "error", "insufficient funds")
|
|
||||||
|
|
||||||
store := repository.NewStore(db)
|
store := repository.NewStore(db)
|
||||||
v := customvalidator.NewCustomValidator(validator.New())
|
v := customvalidator.NewCustomValidator(validator.New())
|
||||||
|
|
||||||
|
// Initialize services
|
||||||
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
|
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
|
||||||
|
|
||||||
userSvc := user.NewService(store, store, cfg)
|
userSvc := user.NewService(store, store, cfg)
|
||||||
|
|
||||||
eventSvc := event.New(cfg.Bet365Token, store)
|
eventSvc := event.New(cfg.Bet365Token, store)
|
||||||
oddsSvc := odds.New(store, cfg, logger)
|
oddsSvc := odds.New(store, cfg, logger)
|
||||||
ticketSvc := ticket.NewService(store)
|
ticketSvc := ticket.NewService(store)
|
||||||
notificationRepo := repository.NewNotificationRepository(store)
|
notificationRepo := repository.NewNotificationRepository(store)
|
||||||
virtuaGamesRepo := repository.NewVirtualGameRepository(store)
|
virtuaGamesRepo := repository.NewVirtualGameRepository(store)
|
||||||
|
|
||||||
notificationSvc := notificationservice.New(notificationRepo, logger, cfg)
|
notificationSvc := notificationservice.New(notificationRepo, logger, cfg)
|
||||||
|
|
||||||
// var betStore bet.BetStore
|
var notificatioStore notificationservice.NotificationStore
|
||||||
// var walletStore wallet.WalletStore
|
|
||||||
// var transactionStore transaction.TransactionStore
|
|
||||||
// var branchStore branch.BranchStore
|
|
||||||
// var userStore user.UserStore
|
|
||||||
var notificationStore notificationservice.NotificationStore
|
|
||||||
|
|
||||||
walletSvc := wallet.NewService(
|
walletSvc := wallet.NewService(
|
||||||
wallet.WalletStore(store),
|
wallet.WalletStore(store),
|
||||||
wallet.TransferStore(store),
|
wallet.TransferStore(store),
|
||||||
notificationStore,
|
notificatioStore,
|
||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -156,18 +127,8 @@ func main() {
|
||||||
|
|
||||||
referalSvc := referralservice.New(referalRepo, *walletSvc, store, cfg, logger)
|
referalSvc := referralservice.New(referalRepo, *walletSvc, store, cfg, logger)
|
||||||
virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, cfg, logger)
|
virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, cfg, logger)
|
||||||
aleaService := alea.NewAleaPlayService(
|
aleaService := alea.NewAleaPlayService(vitualGameRepo, *walletSvc, cfg, logger)
|
||||||
vitualGameRepo,
|
veliService := veli.NewVeliPlayService(vitualGameRepo, *walletSvc, cfg, logger)
|
||||||
*walletSvc,
|
|
||||||
cfg,
|
|
||||||
logger,
|
|
||||||
)
|
|
||||||
veliService := veli.NewVeliPlayService(
|
|
||||||
vitualGameRepo,
|
|
||||||
*walletSvc,
|
|
||||||
cfg,
|
|
||||||
logger,
|
|
||||||
)
|
|
||||||
recommendationSvc := recommendation.NewService(recommendationRepo)
|
recommendationSvc := recommendation.NewService(recommendationRepo)
|
||||||
chapaClient := chapa.NewClient(cfg.CHAPA_BASE_URL, cfg.CHAPA_SECRET_KEY)
|
chapaClient := chapa.NewClient(cfg.CHAPA_BASE_URL, cfg.CHAPA_SECRET_KEY)
|
||||||
|
|
||||||
|
|
@ -178,27 +139,35 @@ func main() {
|
||||||
chapaClient,
|
chapaClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Initialize reporting components
|
||||||
|
reportRepo := repository.NewReportRepo(store)
|
||||||
|
|
||||||
reportSvc := report.NewService(
|
reportSvc := report.NewService(
|
||||||
bet.BetStore(store), // Must implement BetStore
|
bet.BetStore(store),
|
||||||
wallet.WalletStore(store), // Must implement WalletStore
|
wallet.WalletStore(store),
|
||||||
transaction.TransactionStore(store),
|
transaction.TransactionStore(store),
|
||||||
branch.BranchStore(store),
|
branch.BranchStore(store),
|
||||||
user.UserStore(store),
|
user.UserStore(store),
|
||||||
|
reportRepo,
|
||||||
company.CompanyStore(store),
|
company.CompanyStore(store),
|
||||||
virtuaGamesRepo,
|
virtuaGamesRepo,
|
||||||
notificationRepo,
|
notificationRepo,
|
||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
|
|
||||||
// reportSvc := report.NewService(
|
// Initialize report worker with CSV exporter
|
||||||
// betStore,
|
csvExporter := infrastructure.CSVExporter{
|
||||||
// walletStore,
|
ExportPath: cfg.ReportExportPath, // Make sure to add this to your config
|
||||||
// transactionStore,
|
}
|
||||||
// branchStore,
|
|
||||||
// userStore,
|
|
||||||
// logger,
|
|
||||||
// )
|
|
||||||
|
|
||||||
|
reportWorker := worker.NewReportWorker(
|
||||||
|
reportSvc,
|
||||||
|
csvExporter,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start cron jobs for automated reporting
|
||||||
|
|
||||||
|
// Initialize wallet monitoring
|
||||||
walletMonitorSvc := monitor.NewService(
|
walletMonitorSvc := monitor.NewService(
|
||||||
*walletSvc,
|
*walletSvc,
|
||||||
*branchSvc,
|
*branchSvc,
|
||||||
|
|
@ -208,16 +177,44 @@ func main() {
|
||||||
)
|
)
|
||||||
walletMonitorSvc.Start()
|
walletMonitorSvc.Start()
|
||||||
|
|
||||||
|
// Start other cron jobs
|
||||||
httpserver.StartDataFetchingCrons(eventSvc, oddsSvc, resultSvc)
|
httpserver.StartDataFetchingCrons(eventSvc, oddsSvc, resultSvc)
|
||||||
httpserver.StartTicketCrons(*ticketSvc)
|
httpserver.StartTicketCrons(*ticketSvc)
|
||||||
|
go httpserver.SetupReportCronJob(reportWorker)
|
||||||
|
|
||||||
|
// Initialize and start HTTP server
|
||||||
|
app := httpserver.NewApp(
|
||||||
|
cfg.Port,
|
||||||
|
v,
|
||||||
|
authSvc,
|
||||||
|
logger,
|
||||||
|
jwtutil.JwtConfig{
|
||||||
|
JwtAccessKey: cfg.JwtKey,
|
||||||
|
JwtAccessExpiry: cfg.AccessExpiry,
|
||||||
|
},
|
||||||
|
userSvc,
|
||||||
|
ticketSvc,
|
||||||
|
betSvc,
|
||||||
|
reportSvc, // Make sure httpserver.NewApp accepts this parameter
|
||||||
|
chapaSvc,
|
||||||
|
walletSvc,
|
||||||
|
transactionSvc,
|
||||||
|
branchSvc,
|
||||||
|
companySvc,
|
||||||
|
notificationSvc,
|
||||||
|
oddsSvc,
|
||||||
|
eventSvc,
|
||||||
|
leagueSvc,
|
||||||
|
referalSvc,
|
||||||
|
virtualGameSvc,
|
||||||
|
aleaService,
|
||||||
|
veliService,
|
||||||
|
recommendationSvc,
|
||||||
|
resultSvc,
|
||||||
|
cfg,
|
||||||
|
)
|
||||||
|
|
||||||
app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{
|
|
||||||
JwtAccessKey: cfg.JwtKey,
|
|
||||||
JwtAccessExpiry: cfg.AccessExpiry,
|
|
||||||
}, userSvc,
|
|
||||||
ticketSvc, betSvc, reportSvc, chapaSvc, walletSvc, transactionSvc, branchSvc, companySvc, notificationSvc, oddsSvc, eventSvc, leagueSvc, referalSvc, virtualGameSvc, aleaService, veliService, recommendationSvc, resultSvc, cfg)
|
|
||||||
logger.Info("Starting server", "port", cfg.Port)
|
logger.Info("Starting server", "port", cfg.Port)
|
||||||
|
|
||||||
if err := app.Run(); err != nil {
|
if err := app.Run(); err != nil {
|
||||||
logger.Error("Failed to start server", "error", err)
|
logger.Error("Failed to start server", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
||||||
3
go.mod
3
go.mod
|
|
@ -72,6 +72,9 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/go-co-op/gocron v1.37.0
|
||||||
github.com/resend/resend-go/v2 v2.20.0 // direct
|
github.com/resend/resend-go/v2 v2.20.0 // direct
|
||||||
go.uber.org/multierr v1.10.0 // indirect
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
|
|
||||||
13
go.sum
13
go.sum
|
|
@ -25,6 +25,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
|
||||||
|
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
|
||||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
|
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
|
||||||
|
|
@ -56,6 +58,7 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
|
@ -81,6 +84,8 @@ github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBF
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
|
@ -108,6 +113,7 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ
|
||||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||||
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/resend/resend-go/v2 v2.20.0 h1:MrIrgV0aHhwRgmcRPw33Nexn6aGJvCvG2XwfFpAMBGM=
|
github.com/resend/resend-go/v2 v2.20.0 h1:MrIrgV0aHhwRgmcRPw33Nexn6aGJvCvG2XwfFpAMBGM=
|
||||||
|
|
@ -117,6 +123,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
|
@ -132,6 +140,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/swaggo/fiber-swagger v1.3.0 h1:RMjIVDleQodNVdKuu7GRs25Eq8RVXK7MwY9f5jbobNg=
|
github.com/swaggo/fiber-swagger v1.3.0 h1:RMjIVDleQodNVdKuu7GRs25Eq8RVXK7MwY9f5jbobNg=
|
||||||
|
|
@ -165,6 +174,9 @@ github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
|
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
|
||||||
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||||
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
|
|
@ -237,6 +249,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ type Config struct {
|
||||||
JwtKey string
|
JwtKey string
|
||||||
LogLevel slog.Level
|
LogLevel slog.Level
|
||||||
Env string
|
Env string
|
||||||
|
ReportExportPath string `mapstructure:"REPORT_EXPORT_PATH"`
|
||||||
AFRO_SMS_API_KEY string
|
AFRO_SMS_API_KEY string
|
||||||
AFRO_SMS_SENDER_NAME string
|
AFRO_SMS_SENDER_NAME string
|
||||||
AFRO_SMS_RECEIVER_PHONE_NUMBER string
|
AFRO_SMS_RECEIVER_PHONE_NUMBER string
|
||||||
|
|
@ -101,6 +102,8 @@ func (c *Config) loadEnv() error {
|
||||||
}
|
}
|
||||||
c.Env = env
|
c.Env = env
|
||||||
|
|
||||||
|
c.ReportExportPath = os.Getenv("REPORT_EXPORT_PATH")
|
||||||
|
|
||||||
portStr := os.Getenv("PORT")
|
portStr := os.Getenv("PORT")
|
||||||
if portStr == "" {
|
if portStr == "" {
|
||||||
return ErrInvalidPort
|
return ErrInvalidPort
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,26 @@ package domain
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
type TimeFrame string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Daily TimeFrame = "daily"
|
||||||
|
Weekly TimeFrame = "weekly"
|
||||||
|
Monthly TimeFrame = "monthly"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Report struct {
|
||||||
|
ID string
|
||||||
|
TimeFrame TimeFrame
|
||||||
|
PeriodStart time.Time
|
||||||
|
PeriodEnd time.Time
|
||||||
|
TotalBets int
|
||||||
|
TotalCashIn float64
|
||||||
|
TotalCashOut float64
|
||||||
|
TotalCashBack float64
|
||||||
|
GeneratedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type DashboardSummary struct {
|
type DashboardSummary struct {
|
||||||
TotalStakes Currency `json:"total_stakes"`
|
TotalStakes Currency `json:"total_stakes"`
|
||||||
TotalBets int64 `json:"total_bets"`
|
TotalBets int64 `json:"total_bets"`
|
||||||
|
|
@ -280,22 +300,22 @@ type CompanyPerformance struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CashierPerformance struct {
|
type CashierPerformance struct {
|
||||||
CashierID int64 `json:"cashier_id"`
|
CashierID int64 `json:"cashier_id"`
|
||||||
CashierName string `json:"cashier_name"`
|
CashierName string `json:"cashier_name"`
|
||||||
BranchID int64 `json:"branch_id"`
|
BranchID int64 `json:"branch_id"`
|
||||||
BranchName string `json:"branch_name"`
|
BranchName string `json:"branch_name"`
|
||||||
CompanyID int64 `json:"company_id"`
|
CompanyID int64 `json:"company_id"`
|
||||||
TotalBets int64 `json:"total_bets"`
|
TotalBets int64 `json:"total_bets"`
|
||||||
TotalStakes Currency `json:"total_stakes"`
|
TotalStakes Currency `json:"total_stakes"`
|
||||||
TotalWins int64 `json:"total_wins"`
|
TotalWins int64 `json:"total_wins"`
|
||||||
TotalPayouts Currency `json:"total_payouts"`
|
TotalPayouts Currency `json:"total_payouts"`
|
||||||
Profit Currency `json:"profit"`
|
Profit Currency `json:"profit"`
|
||||||
WinRate float64 `json:"win_rate"`
|
WinRate float64 `json:"win_rate"`
|
||||||
AverageStake Currency `json:"average_stake"`
|
AverageStake Currency `json:"average_stake"`
|
||||||
Deposits Currency `json:"deposits"`
|
Deposits Currency `json:"deposits"`
|
||||||
Withdrawals Currency `json:"withdrawals"`
|
Withdrawals Currency `json:"withdrawals"`
|
||||||
NetTransactionAmount Currency `json:"net_transaction_amount"`
|
NetTransactionAmount Currency `json:"net_transaction_amount"`
|
||||||
FirstActivity time.Time `json:"first_activity"`
|
FirstActivity time.Time `json:"first_activity"`
|
||||||
LastActivity time.Time `json:"last_activity"`
|
LastActivity time.Time `json:"last_activity"`
|
||||||
ActiveDays int `json:"active_days"`
|
ActiveDays int `json:"active_days"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
48
internal/infrastructure/csv_exporter.go
Normal file
48
internal/infrastructure/csv_exporter.go
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
// infrastructure/csv_exporter.go
|
||||||
|
package infrastructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CSVExporter struct {
|
||||||
|
ExportPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *CSVExporter) Export(report *domain.Report) error {
|
||||||
|
filename := e.ExportPath + "/report_" + string(report.TimeFrame) + "_" +
|
||||||
|
time.Now().Format("20060102") + ".csv"
|
||||||
|
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
writer := csv.NewWriter(file)
|
||||||
|
defer writer.Flush()
|
||||||
|
|
||||||
|
// Write header
|
||||||
|
_ = writer.Write([]string{
|
||||||
|
"Time Frame", "Period Start", "Period End",
|
||||||
|
"Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Back",
|
||||||
|
"Generated At",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Write data
|
||||||
|
return writer.Write([]string{
|
||||||
|
string(report.TimeFrame),
|
||||||
|
report.PeriodStart.Format(time.RFC3339),
|
||||||
|
report.PeriodEnd.Format(time.RFC3339),
|
||||||
|
strconv.Itoa(report.TotalBets),
|
||||||
|
strconv.FormatFloat(report.TotalCashIn, 'f', 2, 64),
|
||||||
|
strconv.FormatFloat(report.TotalCashOut, 'f', 2, 64),
|
||||||
|
strconv.FormatFloat(report.TotalCashBack, 'f', 2, 64),
|
||||||
|
report.GeneratedAt.Format(time.RFC3339),
|
||||||
|
})
|
||||||
|
}
|
||||||
107
internal/repository/report.go
Normal file
107
internal/repository/report.go
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReportRepository interface {
|
||||||
|
GenerateReport(timeFrame domain.TimeFrame, start, end time.Time) (*domain.Report, error)
|
||||||
|
SaveReport(report *domain.Report) error
|
||||||
|
FindReportsByTimeFrame(timeFrame domain.TimeFrame, limit int) ([]*domain.Report, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportRepo struct {
|
||||||
|
store *Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReportRepo(store *Store) ReportRepository {
|
||||||
|
return &ReportRepo{store: store}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReportRepo) GenerateReport(timeFrame domain.TimeFrame, start, end time.Time) (*domain.Report, error) {
|
||||||
|
// Implement SQL queries to calculate metrics
|
||||||
|
var report domain.Report
|
||||||
|
|
||||||
|
// Total Bets
|
||||||
|
err := r.store.conn.QueryRow(
|
||||||
|
context.Background(),
|
||||||
|
`SELECT COUNT(*) FROM bets
|
||||||
|
WHERE created_at BETWEEN $1 AND $2`, start, end).Scan(&report.TotalBets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total Cash In
|
||||||
|
err = r.store.conn.QueryRow(
|
||||||
|
context.Background(),
|
||||||
|
`SELECT COALESCE(SUM(amount), 0) FROM transactions
|
||||||
|
WHERE type = 'stake' AND created_at BETWEEN $1 AND $2`, start, end).Scan(&report.TotalCashIn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similar queries for Cash Out and Cash Back...
|
||||||
|
|
||||||
|
report.TimeFrame = timeFrame
|
||||||
|
report.PeriodStart = start
|
||||||
|
report.PeriodEnd = end
|
||||||
|
report.GeneratedAt = time.Now()
|
||||||
|
|
||||||
|
return &report, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReportRepo) SaveReport(report *domain.Report) error {
|
||||||
|
_, err := r.store.conn.Exec(
|
||||||
|
context.Background(),
|
||||||
|
`INSERT INTO reports
|
||||||
|
(id, time_frame, period_start, period_end, total_bets, total_cash_in, total_cash_out, total_cash_back, generated_at)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||||
|
report.ID, report.TimeFrame, report.PeriodStart, report.PeriodEnd,
|
||||||
|
report.TotalBets, report.TotalCashIn, report.TotalCashOut, report.TotalCashBack, report.GeneratedAt)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReportRepo) FindReportsByTimeFrame(timeFrame domain.TimeFrame, limit int) ([]*domain.Report, error) {
|
||||||
|
rows, err := r.store.conn.Query(
|
||||||
|
context.Background(),
|
||||||
|
`SELECT id, time_frame, period_start, period_end, total_bets,
|
||||||
|
total_cash_in, total_cash_out, total_cash_back, generated_at
|
||||||
|
FROM reports
|
||||||
|
WHERE time_frame = $1
|
||||||
|
ORDER BY generated_at DESC
|
||||||
|
LIMIT $2`,
|
||||||
|
timeFrame, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var reports []*domain.Report
|
||||||
|
for rows.Next() {
|
||||||
|
var report domain.Report
|
||||||
|
err := rows.Scan(
|
||||||
|
&report.ID,
|
||||||
|
&report.TimeFrame,
|
||||||
|
&report.PeriodStart,
|
||||||
|
&report.PeriodEnd,
|
||||||
|
&report.TotalBets,
|
||||||
|
&report.TotalCashIn,
|
||||||
|
&report.TotalCashOut,
|
||||||
|
&report.TotalCashBack,
|
||||||
|
&report.GeneratedAt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reports = append(reports, &report)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return reports, nil
|
||||||
|
}
|
||||||
|
|
@ -5,15 +5,18 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"sort"
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||||
|
|
||||||
// notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
// notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||||
|
|
||||||
// virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
// virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||||
)
|
)
|
||||||
|
|
@ -29,6 +32,7 @@ type Service struct {
|
||||||
transactionStore transaction.TransactionStore
|
transactionStore transaction.TransactionStore
|
||||||
branchStore branch.BranchStore
|
branchStore branch.BranchStore
|
||||||
userStore user.UserStore
|
userStore user.UserStore
|
||||||
|
repo repository.ReportRepository
|
||||||
companyStore company.CompanyStore
|
companyStore company.CompanyStore
|
||||||
virtulaGamesStore repository.VirtualGameRepository
|
virtulaGamesStore repository.VirtualGameRepository
|
||||||
notificationStore repository.NotificationRepository
|
notificationStore repository.NotificationRepository
|
||||||
|
|
@ -41,6 +45,7 @@ func NewService(
|
||||||
transactionStore transaction.TransactionStore,
|
transactionStore transaction.TransactionStore,
|
||||||
branchStore branch.BranchStore,
|
branchStore branch.BranchStore,
|
||||||
userStore user.UserStore,
|
userStore user.UserStore,
|
||||||
|
repo repository.ReportRepository,
|
||||||
companyStore company.CompanyStore,
|
companyStore company.CompanyStore,
|
||||||
virtulaGamesStore repository.VirtualGameRepository,
|
virtulaGamesStore repository.VirtualGameRepository,
|
||||||
notificationStore repository.NotificationRepository,
|
notificationStore repository.NotificationRepository,
|
||||||
|
|
@ -52,6 +57,7 @@ func NewService(
|
||||||
transactionStore: transactionStore,
|
transactionStore: transactionStore,
|
||||||
branchStore: branchStore,
|
branchStore: branchStore,
|
||||||
userStore: userStore,
|
userStore: userStore,
|
||||||
|
repo: repo,
|
||||||
companyStore: companyStore,
|
companyStore: companyStore,
|
||||||
virtulaGamesStore: virtulaGamesStore,
|
virtulaGamesStore: virtulaGamesStore,
|
||||||
notificationStore: notificationStore,
|
notificationStore: notificationStore,
|
||||||
|
|
@ -448,6 +454,34 @@ func (s *Service) GetSportPerformance(ctx context.Context, filter domain.ReportF
|
||||||
return performances, nil
|
return performances, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) GenerateReport(timeFrame domain.TimeFrame) (*domain.Report, error) {
|
||||||
|
now := time.Now()
|
||||||
|
var start, end time.Time
|
||||||
|
|
||||||
|
switch timeFrame {
|
||||||
|
case domain.Daily:
|
||||||
|
start = now.AddDate(0, 0, -1)
|
||||||
|
end = now
|
||||||
|
case domain.Weekly:
|
||||||
|
start = now.AddDate(0, 0, -7)
|
||||||
|
end = now
|
||||||
|
case domain.Monthly:
|
||||||
|
start = now.AddDate(0, -1, 0)
|
||||||
|
end = now
|
||||||
|
}
|
||||||
|
|
||||||
|
report, err := s.repo.GenerateReport(timeFrame, start, end)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.repo.SaveReport(report); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return report, nil
|
||||||
|
}
|
||||||
|
|
||||||
// func (s *Service) GetCompanyPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CompanyPerformance, error) {
|
// func (s *Service) GetCompanyPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CompanyPerformance, error) {
|
||||||
// // Get company bet activity
|
// // Get company bet activity
|
||||||
// companyBets, err := s.betStore.GetCompanyBetActivity(ctx, filter)
|
// companyBets, err := s.betStore.GetCompanyBetActivity(ctx, filter)
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,43 @@ package httpserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
// "time"
|
// "time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
resultsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
resultsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/worker"
|
||||||
|
"github.com/go-co-op/gocron"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func SetupReportCronJob(reportWorker *worker.ReportWorker) {
|
||||||
|
s := gocron.NewScheduler(time.UTC)
|
||||||
|
|
||||||
|
// Daily at midnight
|
||||||
|
_, _ = s.Every(1).Day().At("00:00").Do(func() {
|
||||||
|
_ = reportWorker.GenerateAndExport(domain.Daily)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Weekly on Sunday at 00:05
|
||||||
|
_, _ = s.Every(1).Week().Sunday().At("00:05").Do(func() {
|
||||||
|
_ = reportWorker.GenerateAndExport(domain.Weekly)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Monthly on 1st at 00:10
|
||||||
|
_, _ = s.Every(1).Month(1).At("00:10").Do(func() {
|
||||||
|
_ = reportWorker.GenerateAndExport(domain.Monthly)
|
||||||
|
})
|
||||||
|
|
||||||
|
s.StartAsync()
|
||||||
|
}
|
||||||
|
|
||||||
func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.Service, resultService *resultsvc.Service) {
|
func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.Service, resultService *resultsvc.Service) {
|
||||||
c := cron.New(cron.WithSeconds())
|
c := cron.New(cron.WithSeconds())
|
||||||
|
|
||||||
|
|
|
||||||
29
internal/web_server/worker/report.go
Normal file
29
internal/web_server/worker/report.go
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
// worker/report_worker.go
|
||||||
|
package worker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/infrastructure"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReportWorker struct {
|
||||||
|
reportService *report.Service
|
||||||
|
exporter infrastructure.CSVExporter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReportWorker(service *report.Service, exporter infrastructure.CSVExporter) *ReportWorker {
|
||||||
|
return &ReportWorker{
|
||||||
|
reportService: service,
|
||||||
|
exporter: exporter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ReportWorker) GenerateAndExport(timeFrame domain.TimeFrame) error {
|
||||||
|
report, err := w.reportService.GenerateReport(timeFrame)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.exporter.Export(report)
|
||||||
|
}
|
||||||
0
logs/app.log
Normal file
0
logs/app.log
Normal file
39033
logs/failed_markets.log
Normal file
39033
logs/failed_markets.log
Normal file
File diff suppressed because it is too large
Load Diff
1
tmp/build-errors.log
Normal file
1
tmp/build-errors.log
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user