gitignore fix + export report feature

This commit is contained in:
Yared Yemane 2025-06-13 11:10:05 +03:00
parent 2f2ba65abd
commit 26e85dbfe8
18 changed files with 39458 additions and 91 deletions

55
.env Normal file
View 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
View File

@ -1,8 +1,7 @@
bin # bin
coverage.out # coverage.out
coverage # coverage
.env # .env
tmp # tmp
build # build
*.log # *.log
db.sql

0
app.log Normal file
View File

BIN
bin/web Normal file

Binary file not shown.

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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

View File

@ -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"`
} }

View 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),
})
}

View 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
}

View File

@ -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)

View File

@ -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())

View 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
View 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

File diff suppressed because one or more lines are too long

BIN
tmp/main Normal file

Binary file not shown.