Merge branch 'production' of https://github.com/SamuelTariku/FortuneBet-Backend into production
This commit is contained in:
commit
8b8ebee765
68
cmd/main.go
68
cmd/main.go
|
|
@ -18,7 +18,6 @@ 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"
|
||||||
|
|
||||||
|
|
@ -36,6 +35,8 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/currency"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/currency"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/institutions"
|
||||||
|
issuereporting "github.com/SamuelTariku/FortuneBet-Backend/internal/services/issue_reporting"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||||
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/odds"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
|
|
@ -43,12 +44,12 @@ import (
|
||||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||||
"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"
|
||||||
alea "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/Alea"
|
alea "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/Alea"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/veli"
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet/monitor"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet/monitor"
|
||||||
|
|
||||||
|
|
@ -56,7 +57,6 @@ 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
|
||||||
|
|
@ -87,7 +87,7 @@ func main() {
|
||||||
|
|
||||||
logger := customlogger.NewLogger(cfg.Env, cfg.LogLevel)
|
logger := customlogger.NewLogger(cfg.Env, cfg.LogLevel)
|
||||||
|
|
||||||
domain.MongoDBLogger, err = mongoLogger.InitLogger()
|
domain.MongoDBLogger, err = mongoLogger.InitLogger(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Logger initialization failed: %v", err)
|
log.Fatalf("Logger initialization failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -99,11 +99,11 @@ func main() {
|
||||||
v := customvalidator.NewCustomValidator(validator.New())
|
v := customvalidator.NewCustomValidator(validator.New())
|
||||||
|
|
||||||
// Initialize services
|
// Initialize services
|
||||||
|
settingSvc := settings.NewService(store)
|
||||||
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)
|
|
||||||
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)
|
||||||
|
|
@ -121,8 +121,9 @@ func main() {
|
||||||
branchSvc := branch.NewService(store)
|
branchSvc := branch.NewService(store)
|
||||||
companySvc := company.NewService(store)
|
companySvc := company.NewService(store)
|
||||||
leagueSvc := league.New(store)
|
leagueSvc := league.New(store)
|
||||||
|
ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger, *settingSvc, notificationSvc)
|
||||||
betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, logger, domain.MongoDBLogger)
|
betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, logger, domain.MongoDBLogger)
|
||||||
resultSvc := result.NewService(store, cfg, logger, *betSvc, *oddsSvc, eventSvc, leagueSvc)
|
resultSvc := result.NewService(store, cfg, logger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc)
|
||||||
referalRepo := repository.NewReferralRepository(store)
|
referalRepo := repository.NewReferralRepository(store)
|
||||||
vitualGameRepo := repository.NewVirtualGameRepository(store)
|
vitualGameRepo := repository.NewVirtualGameRepository(store)
|
||||||
recommendationRepo := repository.NewRecommendationRepository(store)
|
recommendationRepo := repository.NewRecommendationRepository(store)
|
||||||
|
|
@ -130,7 +131,7 @@ 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(vitualGameRepo, *walletSvc, cfg, logger)
|
aleaService := alea.NewAleaPlayService(vitualGameRepo, *walletSvc, cfg, logger)
|
||||||
veliService := veli.NewVeliPlayService(vitualGameRepo, *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)
|
||||||
|
|
||||||
|
|
@ -138,6 +139,7 @@ func main() {
|
||||||
wallet.TransferStore(store),
|
wallet.TransferStore(store),
|
||||||
*walletSvc,
|
*walletSvc,
|
||||||
user.UserStore(store),
|
user.UserStore(store),
|
||||||
|
cfg,
|
||||||
chapaClient,
|
chapaClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -162,15 +164,19 @@ func main() {
|
||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initialize report worker with CSV exporter
|
go httpserver.SetupReportCronJobs(context.Background(), reportSvc)
|
||||||
csvExporter := infrastructure.CSVExporter{
|
|
||||||
ExportPath: cfg.ReportExportPath, // Make sure to add this to your config
|
|
||||||
}
|
|
||||||
|
|
||||||
reportWorker := worker.NewReportWorker(
|
bankRepository := repository.NewBankRepository(store)
|
||||||
reportSvc,
|
instSvc := institutions.New(bankRepository)
|
||||||
csvExporter,
|
// Initialize report worker with CSV exporter
|
||||||
)
|
// csvExporter := infrastructure.CSVExporter{
|
||||||
|
// ExportPath: cfg.ReportExportPath, // Make sure to add this to your config
|
||||||
|
// }
|
||||||
|
|
||||||
|
// reportWorker := worker.NewReportWorker(
|
||||||
|
// reportSvc,
|
||||||
|
// csvExporter,
|
||||||
|
// )
|
||||||
|
|
||||||
// Start cron jobs for automated reporting
|
// Start cron jobs for automated reporting
|
||||||
|
|
||||||
|
|
@ -196,13 +202,39 @@ func main() {
|
||||||
|
|
||||||
httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc)
|
httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc)
|
||||||
httpserver.StartTicketCrons(*ticketSvc)
|
httpserver.StartTicketCrons(*ticketSvc)
|
||||||
go httpserver.SetupReportCronJob(reportWorker)
|
|
||||||
|
// Fetch companies and branches for live wallet metrics update
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
companies := []domain.GetCompany{
|
||||||
|
{ID: 1, Name: "Company A", WalletBalance: 1000.0},
|
||||||
|
}
|
||||||
|
|
||||||
|
branches := []domain.BranchWallet{
|
||||||
|
{ID: 10, Name: "Branch Z", CompanyID: 1, Balance: 500.0},
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationSvc.UpdateLiveWalletMetrics(ctx, companies, branches)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to update live metrics:", err)
|
||||||
|
} else {
|
||||||
|
log.Println("Live metrics broadcasted successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
issueReportingRepo := repository.NewReportedIssueRepository(store)
|
||||||
|
|
||||||
|
issueReportingSvc := issuereporting.New(issueReportingRepo)
|
||||||
|
|
||||||
|
// go httpserver.SetupReportCronJob(reportWorker)
|
||||||
|
|
||||||
// Initialize and start HTTP server
|
// Initialize and start HTTP server
|
||||||
app := httpserver.NewApp(
|
app := httpserver.NewApp(
|
||||||
|
issueReportingSvc,
|
||||||
|
instSvc,
|
||||||
currSvc,
|
currSvc,
|
||||||
cfg.Port,
|
cfg.Port,
|
||||||
v,
|
v,
|
||||||
|
settingSvc,
|
||||||
authSvc,
|
authSvc,
|
||||||
logger,
|
logger,
|
||||||
jwtutil.JwtConfig{
|
jwtutil.JwtConfig{
|
||||||
|
|
@ -225,10 +257,11 @@ func main() {
|
||||||
referalSvc,
|
referalSvc,
|
||||||
virtualGameSvc,
|
virtualGameSvc,
|
||||||
aleaService,
|
aleaService,
|
||||||
veliService,
|
// veliService,
|
||||||
recommendationSvc,
|
recommendationSvc,
|
||||||
resultSvc,
|
resultSvc,
|
||||||
cfg,
|
cfg,
|
||||||
|
domain.MongoDBLogger,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.Info("Starting server", "port", cfg.Port)
|
logger.Info("Starting server", "port", cfg.Port)
|
||||||
|
|
@ -236,4 +269,5 @@ func main() {
|
||||||
logger.Error("Failed to start server", "error", err)
|
logger.Error("Failed to start server", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
select {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
db.sql
Normal file
17
db.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
psql (16.8)
|
||||||
|
Type "help" for help.
|
||||||
|
|
||||||
|
gh=#
|
||||||
|
gh=#
|
||||||
|
gh=#
|
||||||
|
gh=#
|
||||||
|
gh=#
|
||||||
|
gh=#
|
||||||
|
gh=#
|
||||||
|
gh=#
|
||||||
|
gh=#
|
||||||
|
gh=#
|
||||||
|
gh=#
|
||||||
|
gh=#
|
||||||
|
gh=#
|
||||||
|
gh=#
|
||||||
|
|
@ -77,3 +77,6 @@ DROP TABLE IF EXISTS otps;
|
||||||
DROP TABLE IF EXISTS odds;
|
DROP TABLE IF EXISTS odds;
|
||||||
DROP TABLE IF EXISTS events;
|
DROP TABLE IF EXISTS events;
|
||||||
DROP TABLE IF EXISTS leagues;
|
DROP TABLE IF EXISTS leagues;
|
||||||
|
DROP TABLE IF EXISTS teams;
|
||||||
|
DROP TABLE IF EXISTS settings;
|
||||||
|
-- DELETE FROM wallet_transfer;
|
||||||
|
|
@ -55,6 +55,7 @@ CREATE TABLE IF NOT EXISTS bets (
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
is_shop_bet BOOLEAN NOT NULL,
|
is_shop_bet BOOLEAN NOT NULL,
|
||||||
|
outcomes_hash TEXT NOT NULL,
|
||||||
UNIQUE(cashout_id),
|
UNIQUE(cashout_id),
|
||||||
CHECK (
|
CHECK (
|
||||||
user_id IS NOT NULL
|
user_id IS NOT NULL
|
||||||
|
|
@ -111,6 +112,23 @@ CREATE TABLE IF NOT EXISTS ticket_outcomes (
|
||||||
status INT NOT NULL DEFAULT 0,
|
status INT NOT NULL DEFAULT 0,
|
||||||
expires TIMESTAMP NOT NULL
|
expires TIMESTAMP NOT NULL
|
||||||
);
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS banks (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
slug VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
swift VARCHAR(20) NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
acct_length INT NOT NULL,
|
||||||
|
country_id INT NOT NULL,
|
||||||
|
is_mobilemoney INT, -- nullable integer (0 or 1)
|
||||||
|
is_active INT NOT NULL, -- 0 or 1
|
||||||
|
is_rtgs INT NOT NULL, -- 0 or 1
|
||||||
|
active INT NOT NULL, -- 0 or 1
|
||||||
|
is_24hrs INT, -- nullable integer (0 or 1)
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
currency VARCHAR(10) NOT NULL,
|
||||||
|
bank_logo TEXT -- URL or base64 string
|
||||||
|
);
|
||||||
CREATE TABLE IF NOT EXISTS wallets (
|
CREATE TABLE IF NOT EXISTS wallets (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
balance BIGINT NOT NULL DEFAULT 0,
|
balance BIGINT NOT NULL DEFAULT 0,
|
||||||
|
|
@ -138,7 +156,7 @@ CREATE TABLE IF NOT EXISTS wallet_transfer (
|
||||||
sender_wallet_id BIGINT,
|
sender_wallet_id BIGINT,
|
||||||
cashier_id BIGINT,
|
cashier_id BIGINT,
|
||||||
verified BOOLEAN DEFAULT false,
|
verified BOOLEAN DEFAULT false,
|
||||||
reference_number VARCHAR(255),
|
reference_number VARCHAR(255) NOT NULL,
|
||||||
status VARCHAR(255),
|
status VARCHAR(255),
|
||||||
payment_method VARCHAR(255),
|
payment_method VARCHAR(255),
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
@ -249,10 +267,12 @@ CREATE TABLE companies (
|
||||||
CREATE TABLE leagues (
|
CREATE TABLE leagues (
|
||||||
id BIGINT PRIMARY KEY,
|
id BIGINT PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
|
img TEXT,
|
||||||
country_code TEXT,
|
country_code TEXT,
|
||||||
bet365_id INT,
|
bet365_id INT,
|
||||||
sport_id INT NOT NULL,
|
sport_id INT NOT NULL,
|
||||||
is_active BOOLEAN DEFAULT true
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
is_featured BOOLEAN DEFAULT false
|
||||||
);
|
);
|
||||||
CREATE TABLE teams (
|
CREATE TABLE teams (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
|
|
@ -261,6 +281,12 @@ CREATE TABLE teams (
|
||||||
bet365_id INT,
|
bet365_id INT,
|
||||||
logo_url TEXT
|
logo_url TEXT
|
||||||
);
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
-- Views
|
-- Views
|
||||||
CREATE VIEW companies_details AS
|
CREATE VIEW companies_details AS
|
||||||
SELECT companies.*,
|
SELECT companies.*,
|
||||||
|
|
@ -273,12 +299,12 @@ FROM companies
|
||||||
JOIN wallets ON wallets.id = companies.wallet_id
|
JOIN wallets ON wallets.id = companies.wallet_id
|
||||||
JOIN users ON users.id = companies.admin_id;
|
JOIN users ON users.id = companies.admin_id;
|
||||||
;
|
;
|
||||||
|
|
||||||
CREATE VIEW branch_details AS
|
CREATE VIEW branch_details AS
|
||||||
SELECT branches.*,
|
SELECT branches.*,
|
||||||
CONCAT(users.first_name, ' ', users.last_name) AS manager_name,
|
CONCAT(users.first_name, ' ', users.last_name) AS manager_name,
|
||||||
users.phone_number AS manager_phone_number,
|
users.phone_number AS manager_phone_number,
|
||||||
wallets.balance
|
wallets.balance,
|
||||||
|
wallets.is_active AS wallet_is_active
|
||||||
FROM branches
|
FROM branches
|
||||||
LEFT JOIN users ON branches.branch_manager_id = users.id
|
LEFT JOIN users ON branches.branch_manager_id = users.id
|
||||||
LEFT JOin wallets ON wallets.id = branches.wallet_id;
|
LEFT JOin wallets ON wallets.id = branches.wallet_id;
|
||||||
|
|
@ -299,42 +325,60 @@ SELECT tickets.*,
|
||||||
FROM tickets
|
FROM tickets
|
||||||
LEFT JOIN ticket_outcomes ON tickets.id = ticket_outcomes.ticket_id
|
LEFT JOIN ticket_outcomes ON tickets.id = ticket_outcomes.ticket_id
|
||||||
GROUP BY tickets.id;
|
GROUP BY tickets.id;
|
||||||
|
CREATE VIEW customer_wallet_details AS
|
||||||
|
SELECT cw.id,
|
||||||
|
cw.customer_id,
|
||||||
|
rw.id AS regular_id,
|
||||||
|
rw.balance AS regular_balance,
|
||||||
|
sw.id AS static_id,
|
||||||
|
sw.balance AS static_balance,
|
||||||
|
rw.is_active as regular_is_active,
|
||||||
|
sw.is_active as static_is_active,
|
||||||
|
rw.updated_at as regular_updated_at,
|
||||||
|
sw.updated_at as static_updated_at,
|
||||||
|
cw.created_at,
|
||||||
|
users.first_name,
|
||||||
|
users.last_name,
|
||||||
|
users.phone_number
|
||||||
|
FROM customer_wallets cw
|
||||||
|
JOIN wallets rw ON cw.regular_wallet_id = rw.id
|
||||||
|
JOIN wallets sw ON cw.static_wallet_id = sw.id
|
||||||
|
JOIN users ON users.id = cw.customer_id;
|
||||||
-- Foreign Keys
|
-- Foreign Keys
|
||||||
|
|
||||||
ALTER TABLE users
|
ALTER TABLE users
|
||||||
ADD CONSTRAINT unique_email UNIQUE (email),
|
ADD CONSTRAINT unique_email UNIQUE (email),
|
||||||
ADD CONSTRAINT unique_phone_number UNIQUE (phone_number);
|
ADD CONSTRAINT unique_phone_number UNIQUE (phone_number);
|
||||||
ALTER TABLE refresh_tokens
|
ALTER TABLE refresh_tokens
|
||||||
ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users(id);
|
ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users(id);
|
||||||
ALTER TABLE bets
|
ALTER TABLE bets
|
||||||
ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id),
|
ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id),
|
||||||
ADD CONSTRAINT fk_bets_branches FOREIGN KEY (branch_id) REFERENCES branches(id);
|
ADD CONSTRAINT fk_bets_branches FOREIGN KEY (branch_id) REFERENCES branches(id);
|
||||||
ALTER TABLE wallets
|
ALTER TABLE wallets
|
||||||
ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id),
|
ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id),
|
||||||
ADD COLUMN currency VARCHAR(3) NOT NULL DEFAULT 'ETB';
|
ADD COLUMN currency VARCHAR(3) NOT NULL DEFAULT 'ETB';
|
||||||
ALTER TABLE customer_wallets
|
ALTER TABLE customer_wallets
|
||||||
ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users(id),
|
ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users(id),
|
||||||
ADD CONSTRAINT fk_customer_wallets_regular_wallet FOREIGN KEY (regular_wallet_id) REFERENCES wallets(id),
|
ADD CONSTRAINT fk_customer_wallets_regular_wallet FOREIGN KEY (regular_wallet_id) REFERENCES wallets(id),
|
||||||
ADD CONSTRAINT fk_customer_wallets_static_wallet FOREIGN KEY (static_wallet_id) REFERENCES wallets(id);
|
ADD CONSTRAINT fk_customer_wallets_static_wallet FOREIGN KEY (static_wallet_id) REFERENCES wallets(id);
|
||||||
ALTER TABLE wallet_transfer
|
ALTER TABLE wallet_transfer
|
||||||
ADD CONSTRAINT fk_wallet_transfer_receiver_wallet FOREIGN KEY (receiver_wallet_id) REFERENCES wallets(id),
|
ADD CONSTRAINT fk_wallet_transfer_receiver_wallet FOREIGN KEY (receiver_wallet_id) REFERENCES wallets(id),
|
||||||
ADD CONSTRAINT fk_wallet_transfer_sender_wallet FOREIGN KEY (sender_wallet_id) REFERENCES wallets(id),
|
ADD CONSTRAINT fk_wallet_transfer_sender_wallet FOREIGN KEY (sender_wallet_id) REFERENCES wallets(id),
|
||||||
ADD CONSTRAINT fk_wallet_transfer_cashier FOREIGN KEY (cashier_id) REFERENCES users(id);
|
ADD CONSTRAINT fk_wallet_transfer_cashier FOREIGN KEY (cashier_id) REFERENCES users(id);
|
||||||
ALTER TABLE transactions
|
ALTER TABLE transactions
|
||||||
ADD CONSTRAINT fk_transactions_branches FOREIGN KEY (branch_id) REFERENCES branches(id),
|
ADD CONSTRAINT fk_transactions_branches FOREIGN KEY (branch_id) REFERENCES branches(id),
|
||||||
ADD CONSTRAINT fk_transactions_cashiers FOREIGN KEY (cashier_id) REFERENCES users(id),
|
ADD CONSTRAINT fk_transactions_cashiers FOREIGN KEY (cashier_id) REFERENCES users(id),
|
||||||
ADD CONSTRAINT fk_transactions_bets FOREIGN KEY (bet_id) REFERENCES bets(id);
|
ADD CONSTRAINT fk_transactions_bets FOREIGN KEY (bet_id) REFERENCES bets(id);
|
||||||
ALTER TABLE branches
|
ALTER TABLE branches
|
||||||
ADD CONSTRAINT fk_branches_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id),
|
ADD CONSTRAINT fk_branches_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id),
|
||||||
ADD CONSTRAINT fk_branches_manager FOREIGN KEY (branch_manager_id) REFERENCES users(id);
|
ADD CONSTRAINT fk_branches_manager FOREIGN KEY (branch_manager_id) REFERENCES users(id);
|
||||||
ALTER TABLE branch_operations
|
ALTER TABLE branch_operations
|
||||||
ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERENCES supported_operations(id) ON DELETE CASCADE,
|
ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERENCES supported_operations(id) ON DELETE CASCADE,
|
||||||
ADD CONSTRAINT fk_branch_operations_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_branch_operations_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE;
|
||||||
ALTER TABLE branch_cashiers
|
ALTER TABLE branch_cashiers
|
||||||
ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE;
|
||||||
ALTER TABLE companies
|
ALTER TABLE companies
|
||||||
ADD CONSTRAINT fk_companies_admin FOREIGN KEY (admin_id) REFERENCES users(id),
|
ADD CONSTRAINT fk_companies_admin FOREIGN KEY (admin_id) REFERENCES users(id),
|
||||||
ADD CONSTRAINT fk_companies_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_companies_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id) ON DELETE CASCADE;
|
||||||
----------------------------------------------seed data-------------------------------------------------------------
|
----------------------------------------------seed data-------------------------------------------------------------
|
||||||
-------------------------------------- DO NOT USE IN PRODUCTION-------------------------------------------------
|
-------------------------------------- DO NOT USE IN PRODUCTION-------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ CREATE TABLE virtual_game_transactions (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
session_id BIGINT NOT NULL REFERENCES virtual_game_sessions(id),
|
session_id BIGINT NOT NULL REFERENCES virtual_game_sessions(id),
|
||||||
user_id BIGINT NOT NULL REFERENCES users(id),
|
user_id BIGINT NOT NULL REFERENCES users(id),
|
||||||
|
company_id BIGINT,
|
||||||
|
provider VARCHAR(100),
|
||||||
|
game_id VARCHAR(100),
|
||||||
wallet_id BIGINT NOT NULL REFERENCES wallets(id),
|
wallet_id BIGINT NOT NULL REFERENCES wallets(id),
|
||||||
transaction_type VARCHAR(20) NOT NULL,
|
transaction_type VARCHAR(20) NOT NULL,
|
||||||
amount BIGINT NOT NULL,
|
amount BIGINT NOT NULL,
|
||||||
|
|
@ -40,6 +43,41 @@ CREATE TABLE virtual_game_transactions (
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE virtual_game_histories (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
session_id VARCHAR(100), -- nullable
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
company_id BIGINT,
|
||||||
|
provider VARCHAR(100),
|
||||||
|
wallet_id BIGINT, -- nullable
|
||||||
|
game_id BIGINT, -- nullable
|
||||||
|
transaction_type VARCHAR(20) NOT NULL, -- e.g., BET, WIN, CANCEL
|
||||||
|
amount BIGINT NOT NULL, -- in cents or smallest currency unit
|
||||||
|
currency VARCHAR(10) NOT NULL,
|
||||||
|
external_transaction_id VARCHAR(100) NOT NULL,
|
||||||
|
reference_transaction_id VARCHAR(100), -- nullable, for cancel/refund
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'COMPLETED', -- transaction status
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS favorite_games (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
game_id BIGINT NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Optional: Indexes for performance
|
||||||
|
CREATE INDEX idx_virtual_game_user_id ON virtual_game_histories(user_id);
|
||||||
|
CREATE INDEX idx_virtual_game_transaction_type ON virtual_game_histories(transaction_type);
|
||||||
|
CREATE INDEX idx_virtual_game_game_id ON virtual_game_histories(game_id);
|
||||||
|
CREATE INDEX idx_virtual_game_external_transaction_id ON virtual_game_histories(external_transaction_id);
|
||||||
|
|
||||||
CREATE INDEX idx_virtual_game_sessions_user_id ON virtual_game_sessions(user_id);
|
CREATE INDEX idx_virtual_game_sessions_user_id ON virtual_game_sessions(user_id);
|
||||||
CREATE INDEX idx_virtual_game_transactions_session_id ON virtual_game_transactions(session_id);
|
CREATE INDEX idx_virtual_game_transactions_session_id ON virtual_game_transactions(session_id);
|
||||||
CREATE INDEX idx_virtual_game_transactions_user_id ON virtual_game_transactions(user_id);
|
CREATE INDEX idx_virtual_game_transactions_user_id ON virtual_game_transactions(user_id);
|
||||||
|
|
||||||
|
ALTER TABLE favorite_games
|
||||||
|
ADD CONSTRAINT unique_user_game_favorite UNIQUE (user_id, game_id);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
-- CREATE TABLE virtual_games (
|
CREATE TABLE IF NOT EXISTS virtual_games (
|
||||||
-- id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
-- name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
-- provider VARCHAR(100) NOT NULL,
|
provider VARCHAR(100) NOT NULL,
|
||||||
-- category VARCHAR(100) NOT NULL,
|
category VARCHAR(100) NOT NULL,
|
||||||
-- min_bet DECIMAL(15,2) NOT NULL,
|
min_bet DECIMAL(15,2) NOT NULL,
|
||||||
-- max_bet DECIMAL(15,2) NOT NULL,
|
max_bet DECIMAL(15,2) NOT NULL,
|
||||||
-- volatility VARCHAR(50) NOT NULL,
|
volatility VARCHAR(50) NOT NULL,
|
||||||
-- rtp DECIMAL(5,2) NOT NULL,
|
rtp DECIMAL(5,2) NOT NULL,
|
||||||
-- is_featured BOOLEAN DEFAULT false,
|
is_featured BOOLEAN DEFAULT false,
|
||||||
-- popularity_score INTEGER DEFAULT 0,
|
popularity_score INTEGER DEFAULT 0,
|
||||||
-- thumbnail_url TEXT,
|
thumbnail_url TEXT,
|
||||||
-- created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
-- updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
-- );
|
);
|
||||||
|
|
||||||
CREATE TABLE user_game_interactions (
|
CREATE TABLE user_game_interactions (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
user_id BIGINT NOT NULL REFERENCES users(id),
|
user_id BIGINT NOT NULL REFERENCES users(id),
|
||||||
game_id BIGINT NOT NULL REFERENCES virtual_games(id),
|
game_id BIGINT NOT NULL REFERENCES virtual_games(id),
|
||||||
interaction_type VARCHAR(50) NOT NULL, -- 'view', 'play', 'bet', 'favorite'
|
interaction_type VARCHAR(50) NOT NULL,
|
||||||
amount DECIMAL(15,2),
|
-- 'view', 'play', 'bet', 'favorite'
|
||||||
|
amount DECIMAL(15, 2),
|
||||||
duration_seconds INTEGER,
|
duration_seconds INTEGER,
|
||||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_user_game_interactions_user ON user_game_interactions(user_id);
|
CREATE INDEX idx_user_game_interactions_user ON user_game_interactions(user_id);
|
||||||
CREATE INDEX idx_user_game_interactions_game ON user_game_interactions(game_id);
|
CREATE INDEX idx_user_game_interactions_game ON user_game_interactions(game_id);
|
||||||
17
db/migrations/000007_setting_data.up.sql
Normal file
17
db/migrations/000007_setting_data.up.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
-- Settings Initial Data
|
||||||
|
INSERT INTO settings (key, value)
|
||||||
|
VALUES ('max_number_of_outcomes', '30') ON CONFLICT (key) DO
|
||||||
|
UPDATE
|
||||||
|
SET value = EXCLUDED.value;
|
||||||
|
INSERT INTO settings (key, value)
|
||||||
|
VALUES ('bet_amount_limit', '100000') ON CONFLICT (key) DO
|
||||||
|
UPDATE
|
||||||
|
SET value = EXCLUDED.value;
|
||||||
|
INSERT INTO settings (key, value)
|
||||||
|
VALUES ('daily_ticket_limit', '50') ON CONFLICT (key) DO
|
||||||
|
UPDATE
|
||||||
|
SET value = EXCLUDED.value;
|
||||||
|
INSERT INTO settings (key, value)
|
||||||
|
VALUES ('total_winnings_limit', '1000000') ON CONFLICT (key) DO
|
||||||
|
UPDATE
|
||||||
|
SET value = EXCLUDED.value;
|
||||||
2
db/migrations/000008_issue_reporting.down.sql
Normal file
2
db/migrations/000008_issue_reporting.down.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
DROP TABLE IF EXISTS reported_issues;
|
||||||
|
|
||||||
12
db/migrations/000008_issue_reporting.up.sql
Normal file
12
db/migrations/000008_issue_reporting.up.sql
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS reported_issues (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
customer_id BIGINT NOT NULL,
|
||||||
|
subject TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
issue_type TEXT NOT NULL, -- e.g., "deposit", "withdrawal", "bet", "technical"
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending', -- pending, in_progress, resolved, rejected
|
||||||
|
metadata JSONB,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
|
@ -9,9 +9,10 @@ INSERT INTO bets (
|
||||||
user_id,
|
user_id,
|
||||||
is_shop_bet,
|
is_shop_bet,
|
||||||
cashout_id,
|
cashout_id,
|
||||||
company_id
|
company_id,
|
||||||
|
outcomes_hash
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
-- name: CreateBetOutcome :copyfrom
|
-- name: CreateBetOutcome :copyfrom
|
||||||
INSERT INTO bet_outcomes (
|
INSERT INTO bet_outcomes (
|
||||||
|
|
@ -48,16 +49,33 @@ VALUES (
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM bet_with_outcomes
|
FROM bet_with_outcomes
|
||||||
wHERE (
|
wHERE (
|
||||||
branch_id = $1
|
branch_id = sqlc.narg('branch_id')
|
||||||
OR $1 IS NULL
|
OR sqlc.narg('branch_id') IS NULL
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
company_id = $2
|
company_id = sqlc.narg('company_id')
|
||||||
OR $2 IS NULL
|
OR sqlc.narg('company_id') IS NULL
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
user_id = $3
|
user_id = sqlc.narg('user_id')
|
||||||
OR $3 IS NULL
|
OR sqlc.narg('user_id') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
is_shop_bet = sqlc.narg('is_shop_bet')
|
||||||
|
OR sqlc.narg('is_shop_bet') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
full_name ILIKE '%' || sqlc.narg('query') || '%'
|
||||||
|
OR phone_number ILIKE '%' || sqlc.narg('query') || '%'
|
||||||
|
OR sqlc.narg('query') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
created_at > sqlc.narg('created_before')
|
||||||
|
OR sqlc.narg('created_before') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
created_at < sqlc.narg('created_after')
|
||||||
|
OR sqlc.narg('created_after') IS NULL
|
||||||
);
|
);
|
||||||
-- name: GetBetByID :one
|
-- name: GetBetByID :one
|
||||||
SELECT *
|
SELECT *
|
||||||
|
|
@ -78,11 +96,22 @@ WHERE user_id = $1;
|
||||||
-- name: GetBetOutcomeByEventID :many
|
-- name: GetBetOutcomeByEventID :many
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM bet_outcomes
|
FROM bet_outcomes
|
||||||
WHERE event_id = $1;
|
WHERE (event_id = $1)
|
||||||
|
AND (
|
||||||
|
status = sqlc.narg('filter_status')
|
||||||
|
OR sqlc.narg('filter_status') IS NULL
|
||||||
|
OR status = sqlc.narg('filter_status_2')
|
||||||
|
OR sqlc.narg('filter_status_2') IS NULL
|
||||||
|
);
|
||||||
-- name: GetBetOutcomeByBetID :many
|
-- name: GetBetOutcomeByBetID :many
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM bet_outcomes
|
FROM bet_outcomes
|
||||||
WHERE bet_id = $1;
|
WHERE bet_id = $1;
|
||||||
|
-- name: GetBetCount :one
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM bets
|
||||||
|
where user_id = $1
|
||||||
|
AND outcomes_hash = $2;
|
||||||
-- name: UpdateCashOut :exec
|
-- name: UpdateCashOut :exec
|
||||||
UPDATE bets
|
UPDATE bets
|
||||||
SET cashed_out = $2,
|
SET cashed_out = $2,
|
||||||
|
|
@ -93,6 +122,16 @@ UPDATE bet_outcomes
|
||||||
SET status = $1
|
SET status = $1
|
||||||
WHERE id = $2
|
WHERE id = $2
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
-- name: UpdateBetOutcomeStatusByBetID :one
|
||||||
|
UPDATE bet_outcomes
|
||||||
|
SET status = $1
|
||||||
|
WHERE bet_id = $2
|
||||||
|
RETURNING *;
|
||||||
|
-- name: UpdateBetOutcomeStatusForEvent :many
|
||||||
|
UPDATE bet_outcomes
|
||||||
|
SEt status = $1
|
||||||
|
WHERE event_id = $2
|
||||||
|
RETURNING *;
|
||||||
-- name: UpdateStatus :exec
|
-- name: UpdateStatus :exec
|
||||||
UPDATE bets
|
UPDATE bets
|
||||||
SET status = $1,
|
SET status = $1,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,32 @@ VALUES ($1, $2)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
-- name: GetAllBranches :many
|
-- name: GetAllBranches :many
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM branch_details;
|
FROM branch_details
|
||||||
|
WHERE (
|
||||||
|
company_id = sqlc.narg('company_id')
|
||||||
|
OR sqlc.narg('company_id') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
is_active = sqlc.narg('is_active')
|
||||||
|
OR sqlc.narg('is_active') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
branch_manager_id = sqlc.narg('branch_manager_id')
|
||||||
|
OR sqlc.narg('branch_manager_id') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
name ILIKE '%' || sqlc.narg('query') || '%'
|
||||||
|
OR location ILIKE '%' || sqlc.narg('query') || '%'
|
||||||
|
OR sqlc.narg('query') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
created_at > sqlc.narg('created_before')
|
||||||
|
OR sqlc.narg('created_before') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
created_at < sqlc.narg('created_after')
|
||||||
|
OR sqlc.narg('created_after') IS NULL
|
||||||
|
);
|
||||||
-- name: GetBranchByID :one
|
-- name: GetBranchByID :one
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM branch_details
|
FROM branch_details
|
||||||
|
|
@ -61,7 +86,8 @@ SET name = COALESCE(sqlc.narg(name), name),
|
||||||
location = COALESCE(sqlc.narg(location), location),
|
location = COALESCE(sqlc.narg(location), location),
|
||||||
branch_manager_id = COALESCE(sqlc.narg(branch_manager_id), branch_manager_id),
|
branch_manager_id = COALESCE(sqlc.narg(branch_manager_id), branch_manager_id),
|
||||||
company_id = COALESCE(sqlc.narg(company_id), company_id),
|
company_id = COALESCE(sqlc.narg(company_id), company_id),
|
||||||
is_self_owned = COALESCE(sqlc.narg(is_self_owned), is_self_owned)
|
is_self_owned = COALESCE(sqlc.narg(is_self_owned), is_self_owned),
|
||||||
|
is_active = COALESCE(sqlc.narg(is_active), is_active)
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
-- name: DeleteBranch :exec
|
-- name: DeleteBranch :exec
|
||||||
|
|
|
||||||
60
db/query/institutions.sql
Normal file
60
db/query/institutions.sql
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
-- name: CreateBank :one
|
||||||
|
INSERT INTO banks (
|
||||||
|
slug,
|
||||||
|
swift,
|
||||||
|
name,
|
||||||
|
acct_length,
|
||||||
|
country_id,
|
||||||
|
is_mobilemoney,
|
||||||
|
is_active,
|
||||||
|
is_rtgs,
|
||||||
|
active,
|
||||||
|
is_24hrs,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
currency,
|
||||||
|
bank_logo
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, $11, $12
|
||||||
|
)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetBankByID :one
|
||||||
|
SELECT *
|
||||||
|
FROM banks
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: GetAllBanks :many
|
||||||
|
SELECT *
|
||||||
|
FROM banks
|
||||||
|
WHERE (
|
||||||
|
country_id = sqlc.narg('country_id')
|
||||||
|
OR sqlc.narg('country_id') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
is_active = sqlc.narg('is_active')
|
||||||
|
OR sqlc.narg('is_active') IS NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- name: UpdateBank :one
|
||||||
|
UPDATE banks
|
||||||
|
SET slug = COALESCE(sqlc.narg(slug), slug),
|
||||||
|
swift = COALESCE(sqlc.narg(swift), swift),
|
||||||
|
name = COALESCE(sqlc.narg(name), name),
|
||||||
|
acct_length = COALESCE(sqlc.narg(acct_length), acct_length),
|
||||||
|
country_id = COALESCE(sqlc.narg(country_id), country_id),
|
||||||
|
is_mobilemoney = COALESCE(sqlc.narg(is_mobilemoney), is_mobilemoney),
|
||||||
|
is_active = COALESCE(sqlc.narg(is_active), is_active),
|
||||||
|
is_rtgs = COALESCE(sqlc.narg(is_rtgs), is_rtgs),
|
||||||
|
active = COALESCE(sqlc.narg(active), active),
|
||||||
|
is_24hrs = COALESCE(sqlc.narg(is_24hrs), is_24hrs),
|
||||||
|
updated_at = CURRENT_TIMESTAMP,
|
||||||
|
currency = COALESCE(sqlc.narg(currency), currency),
|
||||||
|
bank_logo = COALESCE(sqlc.narg(bank_logo), bank_logo)
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: DeleteBank :exec
|
||||||
|
DELETE FROM banks
|
||||||
|
WHERE id = $1;
|
||||||
32
db/query/issue_reporting.sql
Normal file
32
db/query/issue_reporting.sql
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
-- name: CreateReportedIssue :one
|
||||||
|
INSERT INTO reported_issues (
|
||||||
|
customer_id, subject, description, issue_type, metadata
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5
|
||||||
|
)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: ListReportedIssues :many
|
||||||
|
SELECT * FROM reported_issues
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT $1 OFFSET $2;
|
||||||
|
|
||||||
|
-- name: ListReportedIssuesByCustomer :many
|
||||||
|
SELECT * FROM reported_issues
|
||||||
|
WHERE customer_id = $1
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT $2 OFFSET $3;
|
||||||
|
|
||||||
|
-- name: CountReportedIssues :one
|
||||||
|
SELECT COUNT(*) FROM reported_issues;
|
||||||
|
|
||||||
|
-- name: CountReportedIssuesByCustomer :one
|
||||||
|
SELECT COUNT(*) FROM reported_issues WHERE customer_id = $1;
|
||||||
|
|
||||||
|
-- name: UpdateReportedIssueStatus :exec
|
||||||
|
UPDATE reported_issues
|
||||||
|
SET status = $2, updated_at = NOW()
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: DeleteReportedIssue :exec
|
||||||
|
DELETE FROM reported_issues WHERE id = $1;
|
||||||
|
|
@ -5,14 +5,16 @@ INSERT INTO leagues (
|
||||||
country_code,
|
country_code,
|
||||||
bet365_id,
|
bet365_id,
|
||||||
sport_id,
|
sport_id,
|
||||||
is_active
|
is_active,
|
||||||
|
is_featured
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (id) DO
|
VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (id) DO
|
||||||
UPDATE
|
UPDATE
|
||||||
SET name = EXCLUDED.name,
|
SET name = EXCLUDED.name,
|
||||||
country_code = EXCLUDED.country_code,
|
country_code = EXCLUDED.country_code,
|
||||||
bet365_id = EXCLUDED.bet365_id,
|
bet365_id = EXCLUDED.bet365_id,
|
||||||
is_active = EXCLUDED.is_active,
|
is_active = EXCLUDED.is_active,
|
||||||
|
is_featured = EXCLUDED.is_featured,
|
||||||
sport_id = EXCLUDED.sport_id;
|
sport_id = EXCLUDED.sport_id;
|
||||||
-- name: GetAllLeagues :many
|
-- name: GetAllLeagues :many
|
||||||
SELECT id,
|
SELECT id,
|
||||||
|
|
@ -20,6 +22,7 @@ SELECT id,
|
||||||
country_code,
|
country_code,
|
||||||
bet365_id,
|
bet365_id,
|
||||||
is_active,
|
is_active,
|
||||||
|
is_featured,
|
||||||
sport_id
|
sport_id
|
||||||
FROM leagues
|
FROM leagues
|
||||||
WHERE (
|
WHERE (
|
||||||
|
|
@ -34,7 +37,21 @@ WHERE (
|
||||||
is_active = sqlc.narg('is_active')
|
is_active = sqlc.narg('is_active')
|
||||||
OR sqlc.narg('is_active') IS NULL
|
OR sqlc.narg('is_active') IS NULL
|
||||||
)
|
)
|
||||||
|
AND (
|
||||||
|
is_featured = sqlc.narg('is_featured')
|
||||||
|
OR sqlc.narg('is_featured') IS NULL
|
||||||
|
)
|
||||||
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
||||||
|
-- name: GetFeaturedLeagues :many
|
||||||
|
SELECT id,
|
||||||
|
name,
|
||||||
|
country_code,
|
||||||
|
bet365_id,
|
||||||
|
is_active,
|
||||||
|
is_featured,
|
||||||
|
sport_id
|
||||||
|
FROM leagues
|
||||||
|
WHERE is_featured = true;
|
||||||
-- name: CheckLeagueSupport :one
|
-- name: CheckLeagueSupport :one
|
||||||
SELECT EXISTS(
|
SELECT EXISTS(
|
||||||
SELECT 1
|
SELECT 1
|
||||||
|
|
@ -48,6 +65,7 @@ SET name = COALESCE(sqlc.narg('name'), name),
|
||||||
country_code = COALESCE(sqlc.narg('country_code'), country_code),
|
country_code = COALESCE(sqlc.narg('country_code'), country_code),
|
||||||
bet365_id = COALESCE(sqlc.narg('bet365_id'), bet365_id),
|
bet365_id = COALESCE(sqlc.narg('bet365_id'), bet365_id),
|
||||||
is_active = COALESCE(sqlc.narg('is_active'), is_active),
|
is_active = COALESCE(sqlc.narg('is_active'), is_active),
|
||||||
|
is_featured = COALESCE(sqlc.narg('is_featured'), is_featured),
|
||||||
sport_id = COALESCE(sqlc.narg('sport_id'), sport_id)
|
sport_id = COALESCE(sqlc.narg('sport_id'), sport_id)
|
||||||
WHERE id = $1;
|
WHERE id = $1;
|
||||||
-- name: UpdateLeagueByBet365ID :exec
|
-- name: UpdateLeagueByBet365ID :exec
|
||||||
|
|
@ -56,6 +74,7 @@ SET name = COALESCE(sqlc.narg('name'), name),
|
||||||
id = COALESCE(sqlc.narg('id'), id),
|
id = COALESCE(sqlc.narg('id'), id),
|
||||||
country_code = COALESCE(sqlc.narg('country_code'), country_code),
|
country_code = COALESCE(sqlc.narg('country_code'), country_code),
|
||||||
is_active = COALESCE(sqlc.narg('is_active'), is_active),
|
is_active = COALESCE(sqlc.narg('is_active'), is_active),
|
||||||
|
is_featured = COALESCE(sqlc.narg('is_featured'), is_featured),
|
||||||
sport_id = COALESCE(sqlc.narg('sport_id'), sport_id)
|
sport_id = COALESCE(sqlc.narg('sport_id'), sport_id)
|
||||||
WHERE bet365_id = $1;
|
WHERE bet365_id = $1;
|
||||||
-- name: SetLeagueActive :exec
|
-- name: SetLeagueActive :exec
|
||||||
|
|
|
||||||
44
db/query/report.sql
Normal file
44
db/query/report.sql
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
-- name: GetTotalBetsMadeInRange :one
|
||||||
|
SELECT COUNT(*) AS total_bets
|
||||||
|
FROM bets
|
||||||
|
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to');
|
||||||
|
-- name: GetTotalCashMadeInRange :one
|
||||||
|
SELECT COALESCE(SUM(amount), 0) AS total_cash_made
|
||||||
|
FROM bets
|
||||||
|
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to');
|
||||||
|
-- name: GetTotalCashOutInRange :one
|
||||||
|
SELECT COALESCE(SUM(amount), 0) AS total_cash_out
|
||||||
|
FROM bets
|
||||||
|
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
|
||||||
|
AND cashed_out = true;
|
||||||
|
-- name: GetTotalCashBacksInRange :one
|
||||||
|
SELECT COALESCE(SUM(amount), 0) AS total_cash_backs
|
||||||
|
FROM bets
|
||||||
|
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
|
||||||
|
AND status = 5;
|
||||||
|
-- name: GetCompanyWiseReport :many
|
||||||
|
SELECT
|
||||||
|
b.company_id,
|
||||||
|
c.name AS company_name,
|
||||||
|
COUNT(*) AS total_bets,
|
||||||
|
COALESCE(SUM(b.amount), 0) AS total_cash_made,
|
||||||
|
COALESCE(SUM(CASE WHEN b.cashed_out THEN b.amount ELSE 0 END), 0) AS total_cash_out,
|
||||||
|
COALESCE(SUM(CASE WHEN b.status = 5 THEN b.amount ELSE 0 END), 0) AS total_cash_backs
|
||||||
|
FROM bets b
|
||||||
|
JOIN companies c ON b.company_id = c.id
|
||||||
|
WHERE b.created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
|
||||||
|
GROUP BY b.company_id, c.name;
|
||||||
|
-- name: GetBranchWiseReport :many
|
||||||
|
SELECT
|
||||||
|
b.branch_id,
|
||||||
|
br.name AS branch_name,
|
||||||
|
br.company_id,
|
||||||
|
COUNT(*) AS total_bets,
|
||||||
|
COALESCE(SUM(b.amount), 0) AS total_cash_made,
|
||||||
|
COALESCE(SUM(CASE WHEN b.cashed_out THEN b.amount ELSE 0 END), 0) AS total_cash_out,
|
||||||
|
COALESCE(SUM(CASE WHEN b.status = 5 THEN b.amount ELSE 0 END), 0) AS total_cash_backs
|
||||||
|
FROM bets b
|
||||||
|
JOIN branches br ON b.branch_id = br.id
|
||||||
|
WHERE b.created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
|
||||||
|
GROUP BY b.branch_id, br.name, br.company_id;
|
||||||
|
|
||||||
13
db/query/settings.sql
Normal file
13
db/query/settings.sql
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
-- name: GetSettings :many
|
||||||
|
SELECT *
|
||||||
|
FROM settings;
|
||||||
|
-- name: GetSetting :one
|
||||||
|
SELECT *
|
||||||
|
FROM settings
|
||||||
|
WHERE key = $1;
|
||||||
|
-- name: SaveSetting :one
|
||||||
|
INSERT INTO settings (key, value, updated_at)
|
||||||
|
VALUES ($1, $2, CURRENT_TIMESTAMP) ON CONFLICT (key) DO
|
||||||
|
UPDATE
|
||||||
|
SET value = EXCLUDED.value
|
||||||
|
RETURNING *;
|
||||||
|
|
@ -59,3 +59,7 @@ where created_at < now() - interval '1 day';
|
||||||
-- name: DeleteTicketOutcome :exec
|
-- name: DeleteTicketOutcome :exec
|
||||||
Delete from ticket_outcomes
|
Delete from ticket_outcomes
|
||||||
where ticket_id = $1;
|
where ticket_id = $1;
|
||||||
|
-- name: GetAllTicketsInRange :one
|
||||||
|
SELECT COUNT(*) as total_tickets, COALESCE(SUM(amount), 0) as total_amount
|
||||||
|
FROM tickets
|
||||||
|
WHERE created_at BETWEEN $1 AND $2;
|
||||||
|
|
@ -39,3 +39,12 @@ UPDATE wallet_transfer
|
||||||
SET status = $1,
|
SET status = $1,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $2;
|
WHERE id = $2;
|
||||||
|
|
||||||
|
-- name: GetWalletTransactionsInRange :many
|
||||||
|
SELECT type, COUNT(*) as count, SUM(amount) as total_amount
|
||||||
|
FROM wallet_transfer
|
||||||
|
WHERE created_at BETWEEN $1 AND $2
|
||||||
|
GROUP BY type;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,30 +4,85 @@ INSERT INTO virtual_game_sessions (
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1, $2, $3, $4, $5, $6
|
$1, $2, $3, $4, $5, $6
|
||||||
) RETURNING id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at;
|
) RETURNING id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at;
|
||||||
|
|
||||||
-- name: GetVirtualGameSessionByToken :one
|
-- name: GetVirtualGameSessionByToken :one
|
||||||
SELECT id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at
|
SELECT id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at
|
||||||
FROM virtual_game_sessions
|
FROM virtual_game_sessions
|
||||||
WHERE session_token = $1;
|
WHERE session_token = $1;
|
||||||
|
|
||||||
-- name: UpdateVirtualGameSessionStatus :exec
|
-- name: UpdateVirtualGameSessionStatus :exec
|
||||||
UPDATE virtual_game_sessions
|
UPDATE virtual_game_sessions
|
||||||
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $1;
|
WHERE id = $1;
|
||||||
|
|
||||||
-- name: CreateVirtualGameTransaction :one
|
-- name: CreateVirtualGameTransaction :one
|
||||||
INSERT INTO virtual_game_transactions (
|
INSERT INTO virtual_game_transactions (
|
||||||
session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status
|
session_id, user_id, company_id, provider, wallet_id, transaction_type, amount, currency, external_transaction_id, status
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1, $2, $3, $4, $5, $6, $7, $8
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||||
) RETURNING id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at;
|
) RETURNING id, session_id, user_id, company_id, provider, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at;
|
||||||
|
-- name: CreateVirtualGameHistory :one
|
||||||
|
INSERT INTO virtual_game_histories (
|
||||||
|
session_id,
|
||||||
|
user_id,
|
||||||
|
company_id,
|
||||||
|
provider,
|
||||||
|
wallet_id,
|
||||||
|
game_id,
|
||||||
|
transaction_type,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
external_transaction_id,
|
||||||
|
reference_transaction_id,
|
||||||
|
status
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12
|
||||||
|
) RETURNING
|
||||||
|
id,
|
||||||
|
session_id,
|
||||||
|
user_id,
|
||||||
|
company_id,
|
||||||
|
provider,
|
||||||
|
wallet_id,
|
||||||
|
game_id,
|
||||||
|
transaction_type,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
external_transaction_id,
|
||||||
|
reference_transaction_id,
|
||||||
|
status,
|
||||||
|
created_at,
|
||||||
|
updated_at;
|
||||||
-- name: GetVirtualGameTransactionByExternalID :one
|
-- name: GetVirtualGameTransactionByExternalID :one
|
||||||
SELECT id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
SELECT id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
||||||
FROM virtual_game_transactions
|
FROM virtual_game_transactions
|
||||||
WHERE external_transaction_id = $1;
|
WHERE external_transaction_id = $1;
|
||||||
|
|
||||||
-- name: UpdateVirtualGameTransactionStatus :exec
|
-- name: UpdateVirtualGameTransactionStatus :exec
|
||||||
UPDATE virtual_game_transactions
|
UPDATE virtual_game_transactions
|
||||||
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $1;
|
WHERE id = $1;
|
||||||
|
-- name: GetVirtualGameSummaryInRange :many
|
||||||
|
SELECT
|
||||||
|
c.name AS company_name,
|
||||||
|
vg.name AS game_name,
|
||||||
|
COUNT(vgt.id) AS number_of_bets,
|
||||||
|
COALESCE(SUM(vgt.amount), 0) AS total_transaction_sum
|
||||||
|
FROM virtual_game_transactions vgt
|
||||||
|
JOIN virtual_game_sessions vgs ON vgt.session_id = vgs.id
|
||||||
|
JOIN virtual_games vg ON vgs.game_id = vg.id
|
||||||
|
JOIN companies c ON vgt.company_id = c.id
|
||||||
|
WHERE vgt.transaction_type = 'BET'
|
||||||
|
AND vgt.created_at BETWEEN $1 AND $2
|
||||||
|
GROUP BY c.name, vg.name;
|
||||||
|
-- name: AddFavoriteGame :exec
|
||||||
|
INSERT INTO favorite_games (
|
||||||
|
user_id,
|
||||||
|
game_id,
|
||||||
|
created_at
|
||||||
|
) VALUES ($1, $2, NOW())
|
||||||
|
ON CONFLICT (user_id, game_id) DO NOTHING;
|
||||||
|
-- name: RemoveFavoriteGame :exec
|
||||||
|
DELETE FROM favorite_games
|
||||||
|
WHERE user_id = $1 AND game_id = $2;
|
||||||
|
-- name: ListFavoriteGames :many
|
||||||
|
SELECT game_id
|
||||||
|
FROM favorite_games
|
||||||
|
WHERE user_id = $1;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,20 +26,13 @@ WHERE id = $1;
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM wallets
|
FROM wallets
|
||||||
WHERE user_id = $1;
|
WHERE user_id = $1;
|
||||||
|
-- name: GetAllCustomerWallet :many
|
||||||
|
SELECT *
|
||||||
|
FROM customer_wallet_details;
|
||||||
-- name: GetCustomerWallet :one
|
-- name: GetCustomerWallet :one
|
||||||
SELECT cw.id,
|
SELECT *
|
||||||
cw.customer_id,
|
FROM customer_wallet_details
|
||||||
rw.id AS regular_id,
|
WHERE customer_id = $1;
|
||||||
rw.balance AS regular_balance,
|
|
||||||
sw.id AS static_id,
|
|
||||||
sw.balance AS static_balance,
|
|
||||||
rw.updated_at as regular_updated_at,
|
|
||||||
sw.updated_at as static_updated_at,
|
|
||||||
cw.created_at
|
|
||||||
FROM customer_wallets cw
|
|
||||||
JOIN wallets rw ON cw.regular_wallet_id = rw.id
|
|
||||||
JOIN wallets sw ON cw.static_wallet_id = sw.id
|
|
||||||
WHERE cw.customer_id = $1;
|
|
||||||
-- name: GetAllBranchWallets :many
|
-- name: GetAllBranchWallets :many
|
||||||
SELECT wallets.id,
|
SELECT wallets.id,
|
||||||
wallets.balance,
|
wallets.balance,
|
||||||
|
|
@ -63,3 +56,13 @@ UPDATE wallets
|
||||||
SET is_active = $1,
|
SET is_active = $1,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $2;
|
WHERE id = $2;
|
||||||
|
-- name: GetCompanyByWalletID :one
|
||||||
|
SELECT id, name, admin_id, wallet_id
|
||||||
|
FROM companies
|
||||||
|
WHERE wallet_id = $1
|
||||||
|
LIMIT 1;
|
||||||
|
-- name: GetBranchByWalletID :one
|
||||||
|
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at
|
||||||
|
FROM branches
|
||||||
|
WHERE wallet_id = $1
|
||||||
|
LIMIT 1;
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
version: "3.8"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
|
|
@ -54,6 +56,18 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- app
|
- app
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
networks:
|
||||||
|
- app
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
app:
|
app:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
|
|
@ -64,14 +78,19 @@ services:
|
||||||
environment:
|
environment:
|
||||||
- DB_URL=postgresql://root:secret@postgres:5432/gh?sslmode=disable
|
- DB_URL=postgresql://root:secret@postgres:5432/gh?sslmode=disable
|
||||||
- MONGO_URI=mongodb://root:secret@mongo:27017
|
- MONGO_URI=mongodb://root:secret@mongo:27017
|
||||||
|
- REDIS_ADDR=redis:6379
|
||||||
depends_on:
|
depends_on:
|
||||||
migrate:
|
migrate:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
mongo:
|
mongo:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
networks:
|
networks:
|
||||||
- app
|
- app
|
||||||
command: ["/app/bin/web"]
|
command: ["/app/bin/web"]
|
||||||
|
volumes:
|
||||||
|
- "C:/Users/User/Desktop:/host-desktop"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
build:
|
build:
|
||||||
|
|
|
||||||
1986
docs/docs.go
1986
docs/docs.go
File diff suppressed because it is too large
Load Diff
1986
docs/swagger.json
1986
docs/swagger.json
File diff suppressed because it is too large
Load Diff
1340
docs/swagger.yaml
1340
docs/swagger.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -22,23 +22,25 @@ INSERT INTO bets (
|
||||||
user_id,
|
user_id,
|
||||||
is_shop_bet,
|
is_shop_bet,
|
||||||
cashout_id,
|
cashout_id,
|
||||||
company_id
|
company_id,
|
||||||
|
outcomes_hash
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||||
RETURNING id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet
|
RETURNING id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateBetParams struct {
|
type CreateBetParams struct {
|
||||||
Amount int64 `json:"amount"`
|
Amount int64 `json:"amount"`
|
||||||
TotalOdds float32 `json:"total_odds"`
|
TotalOdds float32 `json:"total_odds"`
|
||||||
Status int32 `json:"status"`
|
Status int32 `json:"status"`
|
||||||
FullName string `json:"full_name"`
|
FullName string `json:"full_name"`
|
||||||
PhoneNumber string `json:"phone_number"`
|
PhoneNumber string `json:"phone_number"`
|
||||||
BranchID pgtype.Int8 `json:"branch_id"`
|
BranchID pgtype.Int8 `json:"branch_id"`
|
||||||
UserID pgtype.Int8 `json:"user_id"`
|
UserID pgtype.Int8 `json:"user_id"`
|
||||||
IsShopBet bool `json:"is_shop_bet"`
|
IsShopBet bool `json:"is_shop_bet"`
|
||||||
CashoutID string `json:"cashout_id"`
|
CashoutID string `json:"cashout_id"`
|
||||||
CompanyID pgtype.Int8 `json:"company_id"`
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
|
OutcomesHash string `json:"outcomes_hash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, error) {
|
func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, error) {
|
||||||
|
|
@ -53,6 +55,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
|
||||||
arg.IsShopBet,
|
arg.IsShopBet,
|
||||||
arg.CashoutID,
|
arg.CashoutID,
|
||||||
arg.CompanyID,
|
arg.CompanyID,
|
||||||
|
arg.OutcomesHash,
|
||||||
)
|
)
|
||||||
var i Bet
|
var i Bet
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -70,6 +73,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.IsShopBet,
|
&i.IsShopBet,
|
||||||
|
&i.OutcomesHash,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +115,7 @@ func (q *Queries) DeleteBetOutcome(ctx context.Context, betID int64) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetAllBets = `-- name: GetAllBets :many
|
const GetAllBets = `-- name: GetAllBets :many
|
||||||
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
|
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash, outcomes
|
||||||
FROM bet_with_outcomes
|
FROM bet_with_outcomes
|
||||||
wHERE (
|
wHERE (
|
||||||
branch_id = $1
|
branch_id = $1
|
||||||
|
|
@ -125,16 +129,45 @@ wHERE (
|
||||||
user_id = $3
|
user_id = $3
|
||||||
OR $3 IS NULL
|
OR $3 IS NULL
|
||||||
)
|
)
|
||||||
|
AND (
|
||||||
|
is_shop_bet = $4
|
||||||
|
OR $4 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
full_name ILIKE '%' || $5 || '%'
|
||||||
|
OR phone_number ILIKE '%' || $5 || '%'
|
||||||
|
OR $5 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
created_at > $6
|
||||||
|
OR $6 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
created_at < $7
|
||||||
|
OR $7 IS NULL
|
||||||
|
)
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetAllBetsParams struct {
|
type GetAllBetsParams struct {
|
||||||
BranchID pgtype.Int8 `json:"branch_id"`
|
BranchID pgtype.Int8 `json:"branch_id"`
|
||||||
CompanyID pgtype.Int8 `json:"company_id"`
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
UserID pgtype.Int8 `json:"user_id"`
|
UserID pgtype.Int8 `json:"user_id"`
|
||||||
|
IsShopBet pgtype.Bool `json:"is_shop_bet"`
|
||||||
|
Query pgtype.Text `json:"query"`
|
||||||
|
CreatedBefore pgtype.Timestamp `json:"created_before"`
|
||||||
|
CreatedAfter pgtype.Timestamp `json:"created_after"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWithOutcome, error) {
|
func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWithOutcome, error) {
|
||||||
rows, err := q.db.Query(ctx, GetAllBets, arg.BranchID, arg.CompanyID, arg.UserID)
|
rows, err := q.db.Query(ctx, GetAllBets,
|
||||||
|
arg.BranchID,
|
||||||
|
arg.CompanyID,
|
||||||
|
arg.UserID,
|
||||||
|
arg.IsShopBet,
|
||||||
|
arg.Query,
|
||||||
|
arg.CreatedBefore,
|
||||||
|
arg.CreatedAfter,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -157,6 +190,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.IsShopBet,
|
&i.IsShopBet,
|
||||||
|
&i.OutcomesHash,
|
||||||
&i.Outcomes,
|
&i.Outcomes,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -170,7 +204,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetBetByBranchID = `-- name: GetBetByBranchID :many
|
const GetBetByBranchID = `-- name: GetBetByBranchID :many
|
||||||
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
|
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash, outcomes
|
||||||
FROM bet_with_outcomes
|
FROM bet_with_outcomes
|
||||||
WHERE branch_id = $1
|
WHERE branch_id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -199,6 +233,7 @@ func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.IsShopBet,
|
&i.IsShopBet,
|
||||||
|
&i.OutcomesHash,
|
||||||
&i.Outcomes,
|
&i.Outcomes,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -212,7 +247,7 @@ func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetBetByCashoutID = `-- name: GetBetByCashoutID :one
|
const GetBetByCashoutID = `-- name: GetBetByCashoutID :one
|
||||||
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
|
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash, outcomes
|
||||||
FROM bet_with_outcomes
|
FROM bet_with_outcomes
|
||||||
WHERE cashout_id = $1
|
WHERE cashout_id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -235,13 +270,14 @@ func (q *Queries) GetBetByCashoutID(ctx context.Context, cashoutID string) (BetW
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.IsShopBet,
|
&i.IsShopBet,
|
||||||
|
&i.OutcomesHash,
|
||||||
&i.Outcomes,
|
&i.Outcomes,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetBetByID = `-- name: GetBetByID :one
|
const GetBetByID = `-- name: GetBetByID :one
|
||||||
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
|
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash, outcomes
|
||||||
FROM bet_with_outcomes
|
FROM bet_with_outcomes
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -264,13 +300,14 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.IsShopBet,
|
&i.IsShopBet,
|
||||||
|
&i.OutcomesHash,
|
||||||
&i.Outcomes,
|
&i.Outcomes,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetBetByUserID = `-- name: GetBetByUserID :many
|
const GetBetByUserID = `-- name: GetBetByUserID :many
|
||||||
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes
|
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash, outcomes
|
||||||
FROM bet_with_outcomes
|
FROM bet_with_outcomes
|
||||||
WHERE user_id = $1
|
WHERE user_id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -299,6 +336,7 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]Bet
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.IsShopBet,
|
&i.IsShopBet,
|
||||||
|
&i.OutcomesHash,
|
||||||
&i.Outcomes,
|
&i.Outcomes,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -311,6 +349,25 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]Bet
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GetBetCount = `-- name: GetBetCount :one
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM bets
|
||||||
|
where user_id = $1
|
||||||
|
AND outcomes_hash = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetBetCountParams struct {
|
||||||
|
UserID pgtype.Int8 `json:"user_id"`
|
||||||
|
OutcomesHash string `json:"outcomes_hash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetBetCount(ctx context.Context, arg GetBetCountParams) (int64, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetBetCount, arg.UserID, arg.OutcomesHash)
|
||||||
|
var count int64
|
||||||
|
err := row.Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
const GetBetOutcomeByBetID = `-- name: GetBetOutcomeByBetID :many
|
const GetBetOutcomeByBetID = `-- name: GetBetOutcomeByBetID :many
|
||||||
SELECT id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires
|
SELECT id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires
|
||||||
FROM bet_outcomes
|
FROM bet_outcomes
|
||||||
|
|
@ -356,11 +413,23 @@ func (q *Queries) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]BetO
|
||||||
const GetBetOutcomeByEventID = `-- name: GetBetOutcomeByEventID :many
|
const GetBetOutcomeByEventID = `-- name: GetBetOutcomeByEventID :many
|
||||||
SELECT id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires
|
SELECT id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires
|
||||||
FROM bet_outcomes
|
FROM bet_outcomes
|
||||||
WHERE event_id = $1
|
WHERE (event_id = $1)
|
||||||
|
AND (
|
||||||
|
status = $2
|
||||||
|
OR $2 IS NULL
|
||||||
|
OR status = $3
|
||||||
|
OR $3 IS NULL
|
||||||
|
)
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]BetOutcome, error) {
|
type GetBetOutcomeByEventIDParams struct {
|
||||||
rows, err := q.db.Query(ctx, GetBetOutcomeByEventID, eventID)
|
EventID int64 `json:"event_id"`
|
||||||
|
FilterStatus pgtype.Int4 `json:"filter_status"`
|
||||||
|
FilterStatus2 pgtype.Int4 `json:"filter_status_2"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetBetOutcomeByEventID(ctx context.Context, arg GetBetOutcomeByEventIDParams) ([]BetOutcome, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetBetOutcomeByEventID, arg.EventID, arg.FilterStatus, arg.FilterStatus2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -430,6 +499,89 @@ func (q *Queries) UpdateBetOutcomeStatus(ctx context.Context, arg UpdateBetOutco
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const UpdateBetOutcomeStatusByBetID = `-- name: UpdateBetOutcomeStatusByBetID :one
|
||||||
|
UPDATE bet_outcomes
|
||||||
|
SET status = $1
|
||||||
|
WHERE bet_id = $2
|
||||||
|
RETURNING id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateBetOutcomeStatusByBetIDParams struct {
|
||||||
|
Status int32 `json:"status"`
|
||||||
|
BetID int64 `json:"bet_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateBetOutcomeStatusByBetID(ctx context.Context, arg UpdateBetOutcomeStatusByBetIDParams) (BetOutcome, error) {
|
||||||
|
row := q.db.QueryRow(ctx, UpdateBetOutcomeStatusByBetID, arg.Status, arg.BetID)
|
||||||
|
var i BetOutcome
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.BetID,
|
||||||
|
&i.SportID,
|
||||||
|
&i.EventID,
|
||||||
|
&i.OddID,
|
||||||
|
&i.HomeTeamName,
|
||||||
|
&i.AwayTeamName,
|
||||||
|
&i.MarketID,
|
||||||
|
&i.MarketName,
|
||||||
|
&i.Odd,
|
||||||
|
&i.OddName,
|
||||||
|
&i.OddHeader,
|
||||||
|
&i.OddHandicap,
|
||||||
|
&i.Status,
|
||||||
|
&i.Expires,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateBetOutcomeStatusForEvent = `-- name: UpdateBetOutcomeStatusForEvent :many
|
||||||
|
UPDATE bet_outcomes
|
||||||
|
SEt status = $1
|
||||||
|
WHERE event_id = $2
|
||||||
|
RETURNING id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateBetOutcomeStatusForEventParams struct {
|
||||||
|
Status int32 `json:"status"`
|
||||||
|
EventID int64 `json:"event_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateBetOutcomeStatusForEvent(ctx context.Context, arg UpdateBetOutcomeStatusForEventParams) ([]BetOutcome, error) {
|
||||||
|
rows, err := q.db.Query(ctx, UpdateBetOutcomeStatusForEvent, arg.Status, arg.EventID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []BetOutcome
|
||||||
|
for rows.Next() {
|
||||||
|
var i BetOutcome
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.BetID,
|
||||||
|
&i.SportID,
|
||||||
|
&i.EventID,
|
||||||
|
&i.OddID,
|
||||||
|
&i.HomeTeamName,
|
||||||
|
&i.AwayTeamName,
|
||||||
|
&i.MarketID,
|
||||||
|
&i.MarketName,
|
||||||
|
&i.Odd,
|
||||||
|
&i.OddName,
|
||||||
|
&i.OddHeader,
|
||||||
|
&i.OddHandicap,
|
||||||
|
&i.Status,
|
||||||
|
&i.Expires,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const UpdateCashOut = `-- name: UpdateCashOut :exec
|
const UpdateCashOut = `-- name: UpdateCashOut :exec
|
||||||
UPDATE bets
|
UPDATE bets
|
||||||
SET cashed_out = $2,
|
SET cashed_out = $2,
|
||||||
|
|
|
||||||
|
|
@ -155,12 +155,53 @@ func (q *Queries) DeleteBranchOperation(ctx context.Context, arg DeleteBranchOpe
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetAllBranches = `-- name: GetAllBranches :many
|
const GetAllBranches = `-- name: GetAllBranches :many
|
||||||
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance
|
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active
|
||||||
FROM branch_details
|
FROM branch_details
|
||||||
|
WHERE (
|
||||||
|
company_id = $1
|
||||||
|
OR $1 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
is_active = $2
|
||||||
|
OR $2 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
branch_manager_id = $3
|
||||||
|
OR $3 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
name ILIKE '%' || $4 || '%'
|
||||||
|
OR location ILIKE '%' || $4 || '%'
|
||||||
|
OR $4 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
created_at > $5
|
||||||
|
OR $5 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
created_at < $6
|
||||||
|
OR $6 IS NULL
|
||||||
|
)
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetAllBranches(ctx context.Context) ([]BranchDetail, error) {
|
type GetAllBranchesParams struct {
|
||||||
rows, err := q.db.Query(ctx, GetAllBranches)
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
|
IsActive pgtype.Bool `json:"is_active"`
|
||||||
|
BranchManagerID pgtype.Int8 `json:"branch_manager_id"`
|
||||||
|
Query pgtype.Text `json:"query"`
|
||||||
|
CreatedBefore pgtype.Timestamp `json:"created_before"`
|
||||||
|
CreatedAfter pgtype.Timestamp `json:"created_after"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetAllBranches(ctx context.Context, arg GetAllBranchesParams) ([]BranchDetail, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetAllBranches,
|
||||||
|
arg.CompanyID,
|
||||||
|
arg.IsActive,
|
||||||
|
arg.BranchManagerID,
|
||||||
|
arg.Query,
|
||||||
|
arg.CreatedBefore,
|
||||||
|
arg.CreatedAfter,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -182,6 +223,7 @@ func (q *Queries) GetAllBranches(ctx context.Context) ([]BranchDetail, error) {
|
||||||
&i.ManagerName,
|
&i.ManagerName,
|
||||||
&i.ManagerPhoneNumber,
|
&i.ManagerPhoneNumber,
|
||||||
&i.Balance,
|
&i.Balance,
|
||||||
|
&i.WalletIsActive,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +286,7 @@ func (q *Queries) GetBranchByCashier(ctx context.Context, userID int64) (Branch,
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetBranchByCompanyID = `-- name: GetBranchByCompanyID :many
|
const GetBranchByCompanyID = `-- name: GetBranchByCompanyID :many
|
||||||
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance
|
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active
|
||||||
FROM branch_details
|
FROM branch_details
|
||||||
WHERE company_id = $1
|
WHERE company_id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -272,6 +314,7 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]
|
||||||
&i.ManagerName,
|
&i.ManagerName,
|
||||||
&i.ManagerPhoneNumber,
|
&i.ManagerPhoneNumber,
|
||||||
&i.Balance,
|
&i.Balance,
|
||||||
|
&i.WalletIsActive,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -284,7 +327,7 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetBranchByID = `-- name: GetBranchByID :one
|
const GetBranchByID = `-- name: GetBranchByID :one
|
||||||
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance
|
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active
|
||||||
FROM branch_details
|
FROM branch_details
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -306,12 +349,13 @@ func (q *Queries) GetBranchByID(ctx context.Context, id int64) (BranchDetail, er
|
||||||
&i.ManagerName,
|
&i.ManagerName,
|
||||||
&i.ManagerPhoneNumber,
|
&i.ManagerPhoneNumber,
|
||||||
&i.Balance,
|
&i.Balance,
|
||||||
|
&i.WalletIsActive,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetBranchByManagerID = `-- name: GetBranchByManagerID :many
|
const GetBranchByManagerID = `-- name: GetBranchByManagerID :many
|
||||||
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance
|
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active
|
||||||
FROM branch_details
|
FROM branch_details
|
||||||
WHERE branch_manager_id = $1
|
WHERE branch_manager_id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -339,6 +383,7 @@ func (q *Queries) GetBranchByManagerID(ctx context.Context, branchManagerID int6
|
||||||
&i.ManagerName,
|
&i.ManagerName,
|
||||||
&i.ManagerPhoneNumber,
|
&i.ManagerPhoneNumber,
|
||||||
&i.Balance,
|
&i.Balance,
|
||||||
|
&i.WalletIsActive,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -398,7 +443,7 @@ func (q *Queries) GetBranchOperations(ctx context.Context, branchID int64) ([]Ge
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchBranchByName = `-- name: SearchBranchByName :many
|
const SearchBranchByName = `-- name: SearchBranchByName :many
|
||||||
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance
|
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active
|
||||||
FROM branch_details
|
FROM branch_details
|
||||||
WHERE name ILIKE '%' || $1 || '%'
|
WHERE name ILIKE '%' || $1 || '%'
|
||||||
`
|
`
|
||||||
|
|
@ -426,6 +471,7 @@ func (q *Queries) SearchBranchByName(ctx context.Context, dollar_1 pgtype.Text)
|
||||||
&i.ManagerName,
|
&i.ManagerName,
|
||||||
&i.ManagerPhoneNumber,
|
&i.ManagerPhoneNumber,
|
||||||
&i.Balance,
|
&i.Balance,
|
||||||
|
&i.WalletIsActive,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -443,7 +489,8 @@ SET name = COALESCE($2, name),
|
||||||
location = COALESCE($3, location),
|
location = COALESCE($3, location),
|
||||||
branch_manager_id = COALESCE($4, branch_manager_id),
|
branch_manager_id = COALESCE($4, branch_manager_id),
|
||||||
company_id = COALESCE($5, company_id),
|
company_id = COALESCE($5, company_id),
|
||||||
is_self_owned = COALESCE($6, is_self_owned)
|
is_self_owned = COALESCE($6, is_self_owned),
|
||||||
|
is_active = COALESCE($7, is_active)
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at
|
RETURNING id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at
|
||||||
`
|
`
|
||||||
|
|
@ -455,6 +502,7 @@ type UpdateBranchParams struct {
|
||||||
BranchManagerID pgtype.Int8 `json:"branch_manager_id"`
|
BranchManagerID pgtype.Int8 `json:"branch_manager_id"`
|
||||||
CompanyID pgtype.Int8 `json:"company_id"`
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
IsSelfOwned pgtype.Bool `json:"is_self_owned"`
|
IsSelfOwned pgtype.Bool `json:"is_self_owned"`
|
||||||
|
IsActive pgtype.Bool `json:"is_active"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateBranch(ctx context.Context, arg UpdateBranchParams) (Branch, error) {
|
func (q *Queries) UpdateBranch(ctx context.Context, arg UpdateBranchParams) (Branch, error) {
|
||||||
|
|
@ -465,6 +513,7 @@ func (q *Queries) UpdateBranch(ctx context.Context, arg UpdateBranchParams) (Bra
|
||||||
arg.BranchManagerID,
|
arg.BranchManagerID,
|
||||||
arg.CompanyID,
|
arg.CompanyID,
|
||||||
arg.IsSelfOwned,
|
arg.IsSelfOwned,
|
||||||
|
arg.IsActive,
|
||||||
)
|
)
|
||||||
var i Branch
|
var i Branch
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
|
||||||
251
gen/db/institutions.sql.go
Normal file
251
gen/db/institutions.sql.go
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.29.0
|
||||||
|
// source: institutions.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CreateBank = `-- name: CreateBank :one
|
||||||
|
INSERT INTO banks (
|
||||||
|
slug,
|
||||||
|
swift,
|
||||||
|
name,
|
||||||
|
acct_length,
|
||||||
|
country_id,
|
||||||
|
is_mobilemoney,
|
||||||
|
is_active,
|
||||||
|
is_rtgs,
|
||||||
|
active,
|
||||||
|
is_24hrs,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
currency,
|
||||||
|
bank_logo
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, $11, $12
|
||||||
|
)
|
||||||
|
RETURNING id, slug, swift, name, acct_length, country_id, is_mobilemoney, is_active, is_rtgs, active, is_24hrs, created_at, updated_at, currency, bank_logo
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateBankParams struct {
|
||||||
|
Slug string `json:"slug"`
|
||||||
|
Swift string `json:"swift"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
AcctLength int32 `json:"acct_length"`
|
||||||
|
CountryID int32 `json:"country_id"`
|
||||||
|
IsMobilemoney pgtype.Int4 `json:"is_mobilemoney"`
|
||||||
|
IsActive int32 `json:"is_active"`
|
||||||
|
IsRtgs int32 `json:"is_rtgs"`
|
||||||
|
Active int32 `json:"active"`
|
||||||
|
Is24hrs pgtype.Int4 `json:"is_24hrs"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
BankLogo pgtype.Text `json:"bank_logo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateBank(ctx context.Context, arg CreateBankParams) (Bank, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreateBank,
|
||||||
|
arg.Slug,
|
||||||
|
arg.Swift,
|
||||||
|
arg.Name,
|
||||||
|
arg.AcctLength,
|
||||||
|
arg.CountryID,
|
||||||
|
arg.IsMobilemoney,
|
||||||
|
arg.IsActive,
|
||||||
|
arg.IsRtgs,
|
||||||
|
arg.Active,
|
||||||
|
arg.Is24hrs,
|
||||||
|
arg.Currency,
|
||||||
|
arg.BankLogo,
|
||||||
|
)
|
||||||
|
var i Bank
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Slug,
|
||||||
|
&i.Swift,
|
||||||
|
&i.Name,
|
||||||
|
&i.AcctLength,
|
||||||
|
&i.CountryID,
|
||||||
|
&i.IsMobilemoney,
|
||||||
|
&i.IsActive,
|
||||||
|
&i.IsRtgs,
|
||||||
|
&i.Active,
|
||||||
|
&i.Is24hrs,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.Currency,
|
||||||
|
&i.BankLogo,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteBank = `-- name: DeleteBank :exec
|
||||||
|
DELETE FROM banks
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteBank(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, DeleteBank, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetAllBanks = `-- name: GetAllBanks :many
|
||||||
|
SELECT id, slug, swift, name, acct_length, country_id, is_mobilemoney, is_active, is_rtgs, active, is_24hrs, created_at, updated_at, currency, bank_logo
|
||||||
|
FROM banks
|
||||||
|
WHERE (
|
||||||
|
country_id = $1
|
||||||
|
OR $1 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
is_active = $2
|
||||||
|
OR $2 IS NULL
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetAllBanksParams struct {
|
||||||
|
CountryID pgtype.Int4 `json:"country_id"`
|
||||||
|
IsActive pgtype.Int4 `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetAllBanks(ctx context.Context, arg GetAllBanksParams) ([]Bank, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetAllBanks, arg.CountryID, arg.IsActive)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Bank
|
||||||
|
for rows.Next() {
|
||||||
|
var i Bank
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Slug,
|
||||||
|
&i.Swift,
|
||||||
|
&i.Name,
|
||||||
|
&i.AcctLength,
|
||||||
|
&i.CountryID,
|
||||||
|
&i.IsMobilemoney,
|
||||||
|
&i.IsActive,
|
||||||
|
&i.IsRtgs,
|
||||||
|
&i.Active,
|
||||||
|
&i.Is24hrs,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.Currency,
|
||||||
|
&i.BankLogo,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetBankByID = `-- name: GetBankByID :one
|
||||||
|
SELECT id, slug, swift, name, acct_length, country_id, is_mobilemoney, is_active, is_rtgs, active, is_24hrs, created_at, updated_at, currency, bank_logo
|
||||||
|
FROM banks
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetBankByID(ctx context.Context, id int64) (Bank, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetBankByID, id)
|
||||||
|
var i Bank
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Slug,
|
||||||
|
&i.Swift,
|
||||||
|
&i.Name,
|
||||||
|
&i.AcctLength,
|
||||||
|
&i.CountryID,
|
||||||
|
&i.IsMobilemoney,
|
||||||
|
&i.IsActive,
|
||||||
|
&i.IsRtgs,
|
||||||
|
&i.Active,
|
||||||
|
&i.Is24hrs,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.Currency,
|
||||||
|
&i.BankLogo,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateBank = `-- name: UpdateBank :one
|
||||||
|
UPDATE banks
|
||||||
|
SET slug = COALESCE($2, slug),
|
||||||
|
swift = COALESCE($3, swift),
|
||||||
|
name = COALESCE($4, name),
|
||||||
|
acct_length = COALESCE($5, acct_length),
|
||||||
|
country_id = COALESCE($6, country_id),
|
||||||
|
is_mobilemoney = COALESCE($7, is_mobilemoney),
|
||||||
|
is_active = COALESCE($8, is_active),
|
||||||
|
is_rtgs = COALESCE($9, is_rtgs),
|
||||||
|
active = COALESCE($10, active),
|
||||||
|
is_24hrs = COALESCE($11, is_24hrs),
|
||||||
|
updated_at = CURRENT_TIMESTAMP,
|
||||||
|
currency = COALESCE($12, currency),
|
||||||
|
bank_logo = COALESCE($13, bank_logo)
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING id, slug, swift, name, acct_length, country_id, is_mobilemoney, is_active, is_rtgs, active, is_24hrs, created_at, updated_at, currency, bank_logo
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateBankParams struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Slug pgtype.Text `json:"slug"`
|
||||||
|
Swift pgtype.Text `json:"swift"`
|
||||||
|
Name pgtype.Text `json:"name"`
|
||||||
|
AcctLength pgtype.Int4 `json:"acct_length"`
|
||||||
|
CountryID pgtype.Int4 `json:"country_id"`
|
||||||
|
IsMobilemoney pgtype.Int4 `json:"is_mobilemoney"`
|
||||||
|
IsActive pgtype.Int4 `json:"is_active"`
|
||||||
|
IsRtgs pgtype.Int4 `json:"is_rtgs"`
|
||||||
|
Active pgtype.Int4 `json:"active"`
|
||||||
|
Is24hrs pgtype.Int4 `json:"is_24hrs"`
|
||||||
|
Currency pgtype.Text `json:"currency"`
|
||||||
|
BankLogo pgtype.Text `json:"bank_logo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateBank(ctx context.Context, arg UpdateBankParams) (Bank, error) {
|
||||||
|
row := q.db.QueryRow(ctx, UpdateBank,
|
||||||
|
arg.ID,
|
||||||
|
arg.Slug,
|
||||||
|
arg.Swift,
|
||||||
|
arg.Name,
|
||||||
|
arg.AcctLength,
|
||||||
|
arg.CountryID,
|
||||||
|
arg.IsMobilemoney,
|
||||||
|
arg.IsActive,
|
||||||
|
arg.IsRtgs,
|
||||||
|
arg.Active,
|
||||||
|
arg.Is24hrs,
|
||||||
|
arg.Currency,
|
||||||
|
arg.BankLogo,
|
||||||
|
)
|
||||||
|
var i Bank
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Slug,
|
||||||
|
&i.Swift,
|
||||||
|
&i.Name,
|
||||||
|
&i.AcctLength,
|
||||||
|
&i.CountryID,
|
||||||
|
&i.IsMobilemoney,
|
||||||
|
&i.IsActive,
|
||||||
|
&i.IsRtgs,
|
||||||
|
&i.Active,
|
||||||
|
&i.Is24hrs,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.Currency,
|
||||||
|
&i.BankLogo,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
181
gen/db/issue_reporting.sql.go
Normal file
181
gen/db/issue_reporting.sql.go
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.29.0
|
||||||
|
// source: issue_reporting.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CountReportedIssues = `-- name: CountReportedIssues :one
|
||||||
|
SELECT COUNT(*) FROM reported_issues
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) CountReportedIssues(ctx context.Context) (int64, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CountReportedIssues)
|
||||||
|
var count int64
|
||||||
|
err := row.Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const CountReportedIssuesByCustomer = `-- name: CountReportedIssuesByCustomer :one
|
||||||
|
SELECT COUNT(*) FROM reported_issues WHERE customer_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) CountReportedIssuesByCustomer(ctx context.Context, customerID int64) (int64, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CountReportedIssuesByCustomer, customerID)
|
||||||
|
var count int64
|
||||||
|
err := row.Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateReportedIssue = `-- name: CreateReportedIssue :one
|
||||||
|
INSERT INTO reported_issues (
|
||||||
|
customer_id, subject, description, issue_type, metadata
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5
|
||||||
|
)
|
||||||
|
RETURNING id, customer_id, subject, description, issue_type, status, metadata, created_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateReportedIssueParams struct {
|
||||||
|
CustomerID int64 `json:"customer_id"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
IssueType string `json:"issue_type"`
|
||||||
|
Metadata []byte `json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateReportedIssue(ctx context.Context, arg CreateReportedIssueParams) (ReportedIssue, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreateReportedIssue,
|
||||||
|
arg.CustomerID,
|
||||||
|
arg.Subject,
|
||||||
|
arg.Description,
|
||||||
|
arg.IssueType,
|
||||||
|
arg.Metadata,
|
||||||
|
)
|
||||||
|
var i ReportedIssue
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CustomerID,
|
||||||
|
&i.Subject,
|
||||||
|
&i.Description,
|
||||||
|
&i.IssueType,
|
||||||
|
&i.Status,
|
||||||
|
&i.Metadata,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteReportedIssue = `-- name: DeleteReportedIssue :exec
|
||||||
|
DELETE FROM reported_issues WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteReportedIssue(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, DeleteReportedIssue, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListReportedIssues = `-- name: ListReportedIssues :many
|
||||||
|
SELECT id, customer_id, subject, description, issue_type, status, metadata, created_at, updated_at FROM reported_issues
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT $1 OFFSET $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type ListReportedIssuesParams struct {
|
||||||
|
Limit int32 `json:"limit"`
|
||||||
|
Offset int32 `json:"offset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ListReportedIssues(ctx context.Context, arg ListReportedIssuesParams) ([]ReportedIssue, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListReportedIssues, arg.Limit, arg.Offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []ReportedIssue
|
||||||
|
for rows.Next() {
|
||||||
|
var i ReportedIssue
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CustomerID,
|
||||||
|
&i.Subject,
|
||||||
|
&i.Description,
|
||||||
|
&i.IssueType,
|
||||||
|
&i.Status,
|
||||||
|
&i.Metadata,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListReportedIssuesByCustomer = `-- name: ListReportedIssuesByCustomer :many
|
||||||
|
SELECT id, customer_id, subject, description, issue_type, status, metadata, created_at, updated_at FROM reported_issues
|
||||||
|
WHERE customer_id = $1
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT $2 OFFSET $3
|
||||||
|
`
|
||||||
|
|
||||||
|
type ListReportedIssuesByCustomerParams struct {
|
||||||
|
CustomerID int64 `json:"customer_id"`
|
||||||
|
Limit int32 `json:"limit"`
|
||||||
|
Offset int32 `json:"offset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ListReportedIssuesByCustomer(ctx context.Context, arg ListReportedIssuesByCustomerParams) ([]ReportedIssue, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListReportedIssuesByCustomer, arg.CustomerID, arg.Limit, arg.Offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []ReportedIssue
|
||||||
|
for rows.Next() {
|
||||||
|
var i ReportedIssue
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CustomerID,
|
||||||
|
&i.Subject,
|
||||||
|
&i.Description,
|
||||||
|
&i.IssueType,
|
||||||
|
&i.Status,
|
||||||
|
&i.Metadata,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateReportedIssueStatus = `-- name: UpdateReportedIssueStatus :exec
|
||||||
|
UPDATE reported_issues
|
||||||
|
SET status = $2, updated_at = NOW()
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateReportedIssueStatusParams struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateReportedIssueStatus(ctx context.Context, arg UpdateReportedIssueStatusParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, UpdateReportedIssueStatus, arg.ID, arg.Status)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
@ -33,6 +33,7 @@ SELECT id,
|
||||||
country_code,
|
country_code,
|
||||||
bet365_id,
|
bet365_id,
|
||||||
is_active,
|
is_active,
|
||||||
|
is_featured,
|
||||||
sport_id
|
sport_id
|
||||||
FROM leagues
|
FROM leagues
|
||||||
WHERE (
|
WHERE (
|
||||||
|
|
@ -47,13 +48,18 @@ WHERE (
|
||||||
is_active = $3
|
is_active = $3
|
||||||
OR $3 IS NULL
|
OR $3 IS NULL
|
||||||
)
|
)
|
||||||
LIMIT $5 OFFSET $4
|
AND (
|
||||||
|
is_featured = $4
|
||||||
|
OR $4 IS NULL
|
||||||
|
)
|
||||||
|
LIMIT $6 OFFSET $5
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetAllLeaguesParams struct {
|
type GetAllLeaguesParams struct {
|
||||||
CountryCode pgtype.Text `json:"country_code"`
|
CountryCode pgtype.Text `json:"country_code"`
|
||||||
SportID pgtype.Int4 `json:"sport_id"`
|
SportID pgtype.Int4 `json:"sport_id"`
|
||||||
IsActive pgtype.Bool `json:"is_active"`
|
IsActive pgtype.Bool `json:"is_active"`
|
||||||
|
IsFeatured pgtype.Bool `json:"is_featured"`
|
||||||
Offset pgtype.Int4 `json:"offset"`
|
Offset pgtype.Int4 `json:"offset"`
|
||||||
Limit pgtype.Int4 `json:"limit"`
|
Limit pgtype.Int4 `json:"limit"`
|
||||||
}
|
}
|
||||||
|
|
@ -64,6 +70,7 @@ type GetAllLeaguesRow struct {
|
||||||
CountryCode pgtype.Text `json:"country_code"`
|
CountryCode pgtype.Text `json:"country_code"`
|
||||||
Bet365ID pgtype.Int4 `json:"bet365_id"`
|
Bet365ID pgtype.Int4 `json:"bet365_id"`
|
||||||
IsActive pgtype.Bool `json:"is_active"`
|
IsActive pgtype.Bool `json:"is_active"`
|
||||||
|
IsFeatured pgtype.Bool `json:"is_featured"`
|
||||||
SportID int32 `json:"sport_id"`
|
SportID int32 `json:"sport_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,6 +79,7 @@ func (q *Queries) GetAllLeagues(ctx context.Context, arg GetAllLeaguesParams) ([
|
||||||
arg.CountryCode,
|
arg.CountryCode,
|
||||||
arg.SportID,
|
arg.SportID,
|
||||||
arg.IsActive,
|
arg.IsActive,
|
||||||
|
arg.IsFeatured,
|
||||||
arg.Offset,
|
arg.Offset,
|
||||||
arg.Limit,
|
arg.Limit,
|
||||||
)
|
)
|
||||||
|
|
@ -88,6 +96,57 @@ func (q *Queries) GetAllLeagues(ctx context.Context, arg GetAllLeaguesParams) ([
|
||||||
&i.CountryCode,
|
&i.CountryCode,
|
||||||
&i.Bet365ID,
|
&i.Bet365ID,
|
||||||
&i.IsActive,
|
&i.IsActive,
|
||||||
|
&i.IsFeatured,
|
||||||
|
&i.SportID,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetFeaturedLeagues = `-- name: GetFeaturedLeagues :many
|
||||||
|
SELECT id,
|
||||||
|
name,
|
||||||
|
country_code,
|
||||||
|
bet365_id,
|
||||||
|
is_active,
|
||||||
|
is_featured,
|
||||||
|
sport_id
|
||||||
|
FROM leagues
|
||||||
|
WHERE is_featured = true
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetFeaturedLeaguesRow struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
CountryCode pgtype.Text `json:"country_code"`
|
||||||
|
Bet365ID pgtype.Int4 `json:"bet365_id"`
|
||||||
|
IsActive pgtype.Bool `json:"is_active"`
|
||||||
|
IsFeatured pgtype.Bool `json:"is_featured"`
|
||||||
|
SportID int32 `json:"sport_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetFeaturedLeagues(ctx context.Context) ([]GetFeaturedLeaguesRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetFeaturedLeagues)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetFeaturedLeaguesRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetFeaturedLeaguesRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.CountryCode,
|
||||||
|
&i.Bet365ID,
|
||||||
|
&i.IsActive,
|
||||||
|
&i.IsFeatured,
|
||||||
&i.SportID,
|
&i.SportID,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -107,14 +166,16 @@ INSERT INTO leagues (
|
||||||
country_code,
|
country_code,
|
||||||
bet365_id,
|
bet365_id,
|
||||||
sport_id,
|
sport_id,
|
||||||
is_active
|
is_active,
|
||||||
|
is_featured
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (id) DO
|
VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (id) DO
|
||||||
UPDATE
|
UPDATE
|
||||||
SET name = EXCLUDED.name,
|
SET name = EXCLUDED.name,
|
||||||
country_code = EXCLUDED.country_code,
|
country_code = EXCLUDED.country_code,
|
||||||
bet365_id = EXCLUDED.bet365_id,
|
bet365_id = EXCLUDED.bet365_id,
|
||||||
is_active = EXCLUDED.is_active,
|
is_active = EXCLUDED.is_active,
|
||||||
|
is_featured = EXCLUDED.is_featured,
|
||||||
sport_id = EXCLUDED.sport_id
|
sport_id = EXCLUDED.sport_id
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -125,6 +186,7 @@ type InsertLeagueParams struct {
|
||||||
Bet365ID pgtype.Int4 `json:"bet365_id"`
|
Bet365ID pgtype.Int4 `json:"bet365_id"`
|
||||||
SportID int32 `json:"sport_id"`
|
SportID int32 `json:"sport_id"`
|
||||||
IsActive pgtype.Bool `json:"is_active"`
|
IsActive pgtype.Bool `json:"is_active"`
|
||||||
|
IsFeatured pgtype.Bool `json:"is_featured"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) InsertLeague(ctx context.Context, arg InsertLeagueParams) error {
|
func (q *Queries) InsertLeague(ctx context.Context, arg InsertLeagueParams) error {
|
||||||
|
|
@ -135,6 +197,7 @@ func (q *Queries) InsertLeague(ctx context.Context, arg InsertLeagueParams) erro
|
||||||
arg.Bet365ID,
|
arg.Bet365ID,
|
||||||
arg.SportID,
|
arg.SportID,
|
||||||
arg.IsActive,
|
arg.IsActive,
|
||||||
|
arg.IsFeatured,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +224,8 @@ SET name = COALESCE($2, name),
|
||||||
country_code = COALESCE($3, country_code),
|
country_code = COALESCE($3, country_code),
|
||||||
bet365_id = COALESCE($4, bet365_id),
|
bet365_id = COALESCE($4, bet365_id),
|
||||||
is_active = COALESCE($5, is_active),
|
is_active = COALESCE($5, is_active),
|
||||||
sport_id = COALESCE($6, sport_id)
|
is_featured = COALESCE($6, is_featured),
|
||||||
|
sport_id = COALESCE($7, sport_id)
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -171,6 +235,7 @@ type UpdateLeagueParams struct {
|
||||||
CountryCode pgtype.Text `json:"country_code"`
|
CountryCode pgtype.Text `json:"country_code"`
|
||||||
Bet365ID pgtype.Int4 `json:"bet365_id"`
|
Bet365ID pgtype.Int4 `json:"bet365_id"`
|
||||||
IsActive pgtype.Bool `json:"is_active"`
|
IsActive pgtype.Bool `json:"is_active"`
|
||||||
|
IsFeatured pgtype.Bool `json:"is_featured"`
|
||||||
SportID pgtype.Int4 `json:"sport_id"`
|
SportID pgtype.Int4 `json:"sport_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,6 +246,7 @@ func (q *Queries) UpdateLeague(ctx context.Context, arg UpdateLeagueParams) erro
|
||||||
arg.CountryCode,
|
arg.CountryCode,
|
||||||
arg.Bet365ID,
|
arg.Bet365ID,
|
||||||
arg.IsActive,
|
arg.IsActive,
|
||||||
|
arg.IsFeatured,
|
||||||
arg.SportID,
|
arg.SportID,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
|
|
@ -192,7 +258,8 @@ SET name = COALESCE($2, name),
|
||||||
id = COALESCE($3, id),
|
id = COALESCE($3, id),
|
||||||
country_code = COALESCE($4, country_code),
|
country_code = COALESCE($4, country_code),
|
||||||
is_active = COALESCE($5, is_active),
|
is_active = COALESCE($5, is_active),
|
||||||
sport_id = COALESCE($6, sport_id)
|
is_featured = COALESCE($6, is_featured),
|
||||||
|
sport_id = COALESCE($7, sport_id)
|
||||||
WHERE bet365_id = $1
|
WHERE bet365_id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -202,6 +269,7 @@ type UpdateLeagueByBet365IDParams struct {
|
||||||
ID pgtype.Int8 `json:"id"`
|
ID pgtype.Int8 `json:"id"`
|
||||||
CountryCode pgtype.Text `json:"country_code"`
|
CountryCode pgtype.Text `json:"country_code"`
|
||||||
IsActive pgtype.Bool `json:"is_active"`
|
IsActive pgtype.Bool `json:"is_active"`
|
||||||
|
IsFeatured pgtype.Bool `json:"is_featured"`
|
||||||
SportID pgtype.Int4 `json:"sport_id"`
|
SportID pgtype.Int4 `json:"sport_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,6 +280,7 @@ func (q *Queries) UpdateLeagueByBet365ID(ctx context.Context, arg UpdateLeagueBy
|
||||||
arg.ID,
|
arg.ID,
|
||||||
arg.CountryCode,
|
arg.CountryCode,
|
||||||
arg.IsActive,
|
arg.IsActive,
|
||||||
|
arg.IsFeatured,
|
||||||
arg.SportID,
|
arg.SportID,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
157
gen/db/models.go
157
gen/db/models.go
|
|
@ -55,21 +55,40 @@ func (ns NullReferralstatus) Value() (driver.Value, error) {
|
||||||
return string(ns.Referralstatus), nil
|
return string(ns.Referralstatus), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Bank struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Slug string `json:"slug"`
|
||||||
|
Swift string `json:"swift"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
AcctLength int32 `json:"acct_length"`
|
||||||
|
CountryID int32 `json:"country_id"`
|
||||||
|
IsMobilemoney pgtype.Int4 `json:"is_mobilemoney"`
|
||||||
|
IsActive int32 `json:"is_active"`
|
||||||
|
IsRtgs int32 `json:"is_rtgs"`
|
||||||
|
Active int32 `json:"active"`
|
||||||
|
Is24hrs pgtype.Int4 `json:"is_24hrs"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
BankLogo pgtype.Text `json:"bank_logo"`
|
||||||
|
}
|
||||||
|
|
||||||
type Bet struct {
|
type Bet struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Amount int64 `json:"amount"`
|
Amount int64 `json:"amount"`
|
||||||
TotalOdds float32 `json:"total_odds"`
|
TotalOdds float32 `json:"total_odds"`
|
||||||
Status int32 `json:"status"`
|
Status int32 `json:"status"`
|
||||||
FullName string `json:"full_name"`
|
FullName string `json:"full_name"`
|
||||||
PhoneNumber string `json:"phone_number"`
|
PhoneNumber string `json:"phone_number"`
|
||||||
CompanyID pgtype.Int8 `json:"company_id"`
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
BranchID pgtype.Int8 `json:"branch_id"`
|
BranchID pgtype.Int8 `json:"branch_id"`
|
||||||
UserID pgtype.Int8 `json:"user_id"`
|
UserID pgtype.Int8 `json:"user_id"`
|
||||||
CashedOut bool `json:"cashed_out"`
|
CashedOut bool `json:"cashed_out"`
|
||||||
CashoutID string `json:"cashout_id"`
|
CashoutID string `json:"cashout_id"`
|
||||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||||
IsShopBet bool `json:"is_shop_bet"`
|
IsShopBet bool `json:"is_shop_bet"`
|
||||||
|
OutcomesHash string `json:"outcomes_hash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BetOutcome struct {
|
type BetOutcome struct {
|
||||||
|
|
@ -91,21 +110,22 @@ type BetOutcome struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type BetWithOutcome struct {
|
type BetWithOutcome struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Amount int64 `json:"amount"`
|
Amount int64 `json:"amount"`
|
||||||
TotalOdds float32 `json:"total_odds"`
|
TotalOdds float32 `json:"total_odds"`
|
||||||
Status int32 `json:"status"`
|
Status int32 `json:"status"`
|
||||||
FullName string `json:"full_name"`
|
FullName string `json:"full_name"`
|
||||||
PhoneNumber string `json:"phone_number"`
|
PhoneNumber string `json:"phone_number"`
|
||||||
CompanyID pgtype.Int8 `json:"company_id"`
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
BranchID pgtype.Int8 `json:"branch_id"`
|
BranchID pgtype.Int8 `json:"branch_id"`
|
||||||
UserID pgtype.Int8 `json:"user_id"`
|
UserID pgtype.Int8 `json:"user_id"`
|
||||||
CashedOut bool `json:"cashed_out"`
|
CashedOut bool `json:"cashed_out"`
|
||||||
CashoutID string `json:"cashout_id"`
|
CashoutID string `json:"cashout_id"`
|
||||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||||
IsShopBet bool `json:"is_shop_bet"`
|
IsShopBet bool `json:"is_shop_bet"`
|
||||||
Outcomes []BetOutcome `json:"outcomes"`
|
OutcomesHash string `json:"outcomes_hash"`
|
||||||
|
Outcomes []BetOutcome `json:"outcomes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Branch struct {
|
type Branch struct {
|
||||||
|
|
@ -141,6 +161,7 @@ type BranchDetail struct {
|
||||||
ManagerName interface{} `json:"manager_name"`
|
ManagerName interface{} `json:"manager_name"`
|
||||||
ManagerPhoneNumber pgtype.Text `json:"manager_phone_number"`
|
ManagerPhoneNumber pgtype.Text `json:"manager_phone_number"`
|
||||||
Balance pgtype.Int8 `json:"balance"`
|
Balance pgtype.Int8 `json:"balance"`
|
||||||
|
WalletIsActive pgtype.Bool `json:"wallet_is_active"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BranchOperation struct {
|
type BranchOperation struct {
|
||||||
|
|
@ -179,6 +200,23 @@ type CustomerWallet struct {
|
||||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CustomerWalletDetail struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
CustomerID int64 `json:"customer_id"`
|
||||||
|
RegularID int64 `json:"regular_id"`
|
||||||
|
RegularBalance int64 `json:"regular_balance"`
|
||||||
|
StaticID int64 `json:"static_id"`
|
||||||
|
StaticBalance int64 `json:"static_balance"`
|
||||||
|
RegularIsActive bool `json:"regular_is_active"`
|
||||||
|
StaticIsActive bool `json:"static_is_active"`
|
||||||
|
RegularUpdatedAt pgtype.Timestamp `json:"regular_updated_at"`
|
||||||
|
StaticUpdatedAt pgtype.Timestamp `json:"static_updated_at"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||||
|
}
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
SportID pgtype.Int4 `json:"sport_id"`
|
SportID pgtype.Int4 `json:"sport_id"`
|
||||||
|
|
@ -204,13 +242,31 @@ type Event struct {
|
||||||
Source pgtype.Text `json:"source"`
|
Source pgtype.Text `json:"source"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExchangeRate struct {
|
||||||
|
ID int32 `json:"id"`
|
||||||
|
FromCurrency string `json:"from_currency"`
|
||||||
|
ToCurrency string `json:"to_currency"`
|
||||||
|
Rate pgtype.Numeric `json:"rate"`
|
||||||
|
ValidUntil pgtype.Timestamp `json:"valid_until"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FavoriteGame struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
GameID int64 `json:"game_id"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type League struct {
|
type League struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Img pgtype.Text `json:"img"`
|
||||||
CountryCode pgtype.Text `json:"country_code"`
|
CountryCode pgtype.Text `json:"country_code"`
|
||||||
Bet365ID pgtype.Int4 `json:"bet365_id"`
|
Bet365ID pgtype.Int4 `json:"bet365_id"`
|
||||||
SportID int32 `json:"sport_id"`
|
SportID int32 `json:"sport_id"`
|
||||||
IsActive pgtype.Bool `json:"is_active"`
|
IsActive pgtype.Bool `json:"is_active"`
|
||||||
|
IsFeatured pgtype.Bool `json:"is_featured"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
|
|
@ -296,6 +352,18 @@ type RefreshToken struct {
|
||||||
Revoked bool `json:"revoked"`
|
Revoked bool `json:"revoked"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ReportedIssue struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
CustomerID int64 `json:"customer_id"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
IssueType string `json:"issue_type"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Metadata []byte `json:"metadata"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
BetOutcomeID int64 `json:"bet_outcome_id"`
|
BetOutcomeID int64 `json:"bet_outcome_id"`
|
||||||
|
|
@ -311,6 +379,13 @@ type Result struct {
|
||||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Setting struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type SupportedOperation struct {
|
type SupportedOperation struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
@ -434,6 +509,24 @@ type VirtualGame struct {
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VirtualGameHistory struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
SessionID pgtype.Text `json:"session_id"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
|
Provider pgtype.Text `json:"provider"`
|
||||||
|
WalletID pgtype.Int8 `json:"wallet_id"`
|
||||||
|
GameID pgtype.Int8 `json:"game_id"`
|
||||||
|
TransactionType string `json:"transaction_type"`
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
ExternalTransactionID string `json:"external_transaction_id"`
|
||||||
|
ReferenceTransactionID pgtype.Text `json:"reference_transaction_id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type VirtualGameSession struct {
|
type VirtualGameSession struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
|
|
@ -450,6 +543,9 @@ type VirtualGameTransaction struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
SessionID int64 `json:"session_id"`
|
SessionID int64 `json:"session_id"`
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
|
Provider pgtype.Text `json:"provider"`
|
||||||
|
GameID pgtype.Text `json:"game_id"`
|
||||||
WalletID int64 `json:"wallet_id"`
|
WalletID int64 `json:"wallet_id"`
|
||||||
TransactionType string `json:"transaction_type"`
|
TransactionType string `json:"transaction_type"`
|
||||||
Amount int64 `json:"amount"`
|
Amount int64 `json:"amount"`
|
||||||
|
|
@ -470,6 +566,7 @@ type Wallet struct {
|
||||||
IsActive bool `json:"is_active"`
|
IsActive bool `json:"is_active"`
|
||||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
BonusBalance pgtype.Numeric `json:"bonus_balance"`
|
BonusBalance pgtype.Numeric `json:"bonus_balance"`
|
||||||
CashBalance pgtype.Numeric `json:"cash_balance"`
|
CashBalance pgtype.Numeric `json:"cash_balance"`
|
||||||
}
|
}
|
||||||
|
|
@ -488,7 +585,7 @@ type WalletTransfer struct {
|
||||||
SenderWalletID pgtype.Int8 `json:"sender_wallet_id"`
|
SenderWalletID pgtype.Int8 `json:"sender_wallet_id"`
|
||||||
CashierID pgtype.Int8 `json:"cashier_id"`
|
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||||
Verified pgtype.Bool `json:"verified"`
|
Verified pgtype.Bool `json:"verified"`
|
||||||
ReferenceNumber pgtype.Text `json:"reference_number"`
|
ReferenceNumber string `json:"reference_number"`
|
||||||
Status pgtype.Text `json:"status"`
|
Status pgtype.Text `json:"status"`
|
||||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
|
|
||||||
199
gen/db/report.sql.go
Normal file
199
gen/db/report.sql.go
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.29.0
|
||||||
|
// source: report.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const GetBranchWiseReport = `-- name: GetBranchWiseReport :many
|
||||||
|
SELECT
|
||||||
|
b.branch_id,
|
||||||
|
br.name AS branch_name,
|
||||||
|
br.company_id,
|
||||||
|
COUNT(*) AS total_bets,
|
||||||
|
COALESCE(SUM(b.amount), 0) AS total_cash_made,
|
||||||
|
COALESCE(SUM(CASE WHEN b.cashed_out THEN b.amount ELSE 0 END), 0) AS total_cash_out,
|
||||||
|
COALESCE(SUM(CASE WHEN b.status = 5 THEN b.amount ELSE 0 END), 0) AS total_cash_backs
|
||||||
|
FROM bets b
|
||||||
|
JOIN branches br ON b.branch_id = br.id
|
||||||
|
WHERE b.created_at BETWEEN $1 AND $2
|
||||||
|
GROUP BY b.branch_id, br.name, br.company_id
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetBranchWiseReportParams struct {
|
||||||
|
From pgtype.Timestamp `json:"from"`
|
||||||
|
To pgtype.Timestamp `json:"to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetBranchWiseReportRow struct {
|
||||||
|
BranchID pgtype.Int8 `json:"branch_id"`
|
||||||
|
BranchName string `json:"branch_name"`
|
||||||
|
CompanyID int64 `json:"company_id"`
|
||||||
|
TotalBets int64 `json:"total_bets"`
|
||||||
|
TotalCashMade interface{} `json:"total_cash_made"`
|
||||||
|
TotalCashOut interface{} `json:"total_cash_out"`
|
||||||
|
TotalCashBacks interface{} `json:"total_cash_backs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetBranchWiseReport(ctx context.Context, arg GetBranchWiseReportParams) ([]GetBranchWiseReportRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetBranchWiseReport, arg.From, arg.To)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetBranchWiseReportRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetBranchWiseReportRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.BranchID,
|
||||||
|
&i.BranchName,
|
||||||
|
&i.CompanyID,
|
||||||
|
&i.TotalBets,
|
||||||
|
&i.TotalCashMade,
|
||||||
|
&i.TotalCashOut,
|
||||||
|
&i.TotalCashBacks,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetCompanyWiseReport = `-- name: GetCompanyWiseReport :many
|
||||||
|
SELECT
|
||||||
|
b.company_id,
|
||||||
|
c.name AS company_name,
|
||||||
|
COUNT(*) AS total_bets,
|
||||||
|
COALESCE(SUM(b.amount), 0) AS total_cash_made,
|
||||||
|
COALESCE(SUM(CASE WHEN b.cashed_out THEN b.amount ELSE 0 END), 0) AS total_cash_out,
|
||||||
|
COALESCE(SUM(CASE WHEN b.status = 5 THEN b.amount ELSE 0 END), 0) AS total_cash_backs
|
||||||
|
FROM bets b
|
||||||
|
JOIN companies c ON b.company_id = c.id
|
||||||
|
WHERE b.created_at BETWEEN $1 AND $2
|
||||||
|
GROUP BY b.company_id, c.name
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCompanyWiseReportParams struct {
|
||||||
|
From pgtype.Timestamp `json:"from"`
|
||||||
|
To pgtype.Timestamp `json:"to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetCompanyWiseReportRow struct {
|
||||||
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
|
CompanyName string `json:"company_name"`
|
||||||
|
TotalBets int64 `json:"total_bets"`
|
||||||
|
TotalCashMade interface{} `json:"total_cash_made"`
|
||||||
|
TotalCashOut interface{} `json:"total_cash_out"`
|
||||||
|
TotalCashBacks interface{} `json:"total_cash_backs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetCompanyWiseReport(ctx context.Context, arg GetCompanyWiseReportParams) ([]GetCompanyWiseReportRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetCompanyWiseReport, arg.From, arg.To)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetCompanyWiseReportRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetCompanyWiseReportRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.CompanyID,
|
||||||
|
&i.CompanyName,
|
||||||
|
&i.TotalBets,
|
||||||
|
&i.TotalCashMade,
|
||||||
|
&i.TotalCashOut,
|
||||||
|
&i.TotalCashBacks,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetTotalBetsMadeInRange = `-- name: GetTotalBetsMadeInRange :one
|
||||||
|
SELECT COUNT(*) AS total_bets
|
||||||
|
FROM bets
|
||||||
|
WHERE created_at BETWEEN $1 AND $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetTotalBetsMadeInRangeParams struct {
|
||||||
|
From pgtype.Timestamp `json:"from"`
|
||||||
|
To pgtype.Timestamp `json:"to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetTotalBetsMadeInRange(ctx context.Context, arg GetTotalBetsMadeInRangeParams) (int64, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetTotalBetsMadeInRange, arg.From, arg.To)
|
||||||
|
var total_bets int64
|
||||||
|
err := row.Scan(&total_bets)
|
||||||
|
return total_bets, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetTotalCashBacksInRange = `-- name: GetTotalCashBacksInRange :one
|
||||||
|
SELECT COALESCE(SUM(amount), 0) AS total_cash_backs
|
||||||
|
FROM bets
|
||||||
|
WHERE created_at BETWEEN $1 AND $2
|
||||||
|
AND status = 5
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetTotalCashBacksInRangeParams struct {
|
||||||
|
From pgtype.Timestamp `json:"from"`
|
||||||
|
To pgtype.Timestamp `json:"to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetTotalCashBacksInRange(ctx context.Context, arg GetTotalCashBacksInRangeParams) (interface{}, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetTotalCashBacksInRange, arg.From, arg.To)
|
||||||
|
var total_cash_backs interface{}
|
||||||
|
err := row.Scan(&total_cash_backs)
|
||||||
|
return total_cash_backs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetTotalCashMadeInRange = `-- name: GetTotalCashMadeInRange :one
|
||||||
|
SELECT COALESCE(SUM(amount), 0) AS total_cash_made
|
||||||
|
FROM bets
|
||||||
|
WHERE created_at BETWEEN $1 AND $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetTotalCashMadeInRangeParams struct {
|
||||||
|
From pgtype.Timestamp `json:"from"`
|
||||||
|
To pgtype.Timestamp `json:"to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetTotalCashMadeInRange(ctx context.Context, arg GetTotalCashMadeInRangeParams) (interface{}, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetTotalCashMadeInRange, arg.From, arg.To)
|
||||||
|
var total_cash_made interface{}
|
||||||
|
err := row.Scan(&total_cash_made)
|
||||||
|
return total_cash_made, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetTotalCashOutInRange = `-- name: GetTotalCashOutInRange :one
|
||||||
|
SELECT COALESCE(SUM(amount), 0) AS total_cash_out
|
||||||
|
FROM bets
|
||||||
|
WHERE created_at BETWEEN $1 AND $2
|
||||||
|
AND cashed_out = true
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetTotalCashOutInRangeParams struct {
|
||||||
|
From pgtype.Timestamp `json:"from"`
|
||||||
|
To pgtype.Timestamp `json:"to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetTotalCashOutInRange(ctx context.Context, arg GetTotalCashOutInRangeParams) (interface{}, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetTotalCashOutInRange, arg.From, arg.To)
|
||||||
|
var total_cash_out interface{}
|
||||||
|
err := row.Scan(&total_cash_out)
|
||||||
|
return total_cash_out, err
|
||||||
|
}
|
||||||
83
gen/db/settings.sql.go
Normal file
83
gen/db/settings.sql.go
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.29.0
|
||||||
|
// source: settings.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const GetSetting = `-- name: GetSetting :one
|
||||||
|
SELECT key, value, created_at, updated_at
|
||||||
|
FROM settings
|
||||||
|
WHERE key = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetSetting(ctx context.Context, key string) (Setting, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetSetting, key)
|
||||||
|
var i Setting
|
||||||
|
err := row.Scan(
|
||||||
|
&i.Key,
|
||||||
|
&i.Value,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetSettings = `-- name: GetSettings :many
|
||||||
|
SELECT key, value, created_at, updated_at
|
||||||
|
FROM settings
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetSettings(ctx context.Context) ([]Setting, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetSettings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Setting
|
||||||
|
for rows.Next() {
|
||||||
|
var i Setting
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.Key,
|
||||||
|
&i.Value,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const SaveSetting = `-- name: SaveSetting :one
|
||||||
|
INSERT INTO settings (key, value, updated_at)
|
||||||
|
VALUES ($1, $2, CURRENT_TIMESTAMP) ON CONFLICT (key) DO
|
||||||
|
UPDATE
|
||||||
|
SET value = EXCLUDED.value
|
||||||
|
RETURNING key, value, created_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type SaveSettingParams struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) SaveSetting(ctx context.Context, arg SaveSettingParams) (Setting, error) {
|
||||||
|
row := q.db.QueryRow(ctx, SaveSetting, arg.Key, arg.Value)
|
||||||
|
var i Setting
|
||||||
|
err := row.Scan(
|
||||||
|
&i.Key,
|
||||||
|
&i.Value,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
@ -128,6 +128,29 @@ func (q *Queries) GetAllTickets(ctx context.Context) ([]TicketWithOutcome, error
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GetAllTicketsInRange = `-- name: GetAllTicketsInRange :one
|
||||||
|
SELECT COUNT(*) as total_tickets, COALESCE(SUM(amount), 0) as total_amount
|
||||||
|
FROM tickets
|
||||||
|
WHERE created_at BETWEEN $1 AND $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetAllTicketsInRangeParams struct {
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
CreatedAt_2 pgtype.Timestamp `json:"created_at_2"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAllTicketsInRangeRow struct {
|
||||||
|
TotalTickets int64 `json:"total_tickets"`
|
||||||
|
TotalAmount interface{} `json:"total_amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetAllTicketsInRange(ctx context.Context, arg GetAllTicketsInRangeParams) (GetAllTicketsInRangeRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetAllTicketsInRange, arg.CreatedAt, arg.CreatedAt_2)
|
||||||
|
var i GetAllTicketsInRangeRow
|
||||||
|
err := row.Scan(&i.TotalTickets, &i.TotalAmount)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const GetTicketByID = `-- name: GetTicketByID :one
|
const GetTicketByID = `-- name: GetTicketByID :one
|
||||||
SELECT id, amount, total_odds, ip, created_at, updated_at, outcomes
|
SELECT id, amount, total_odds, ip, created_at, updated_at, outcomes
|
||||||
FROM ticket_with_outcomes
|
FROM ticket_with_outcomes
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ type CreateTransferParams struct {
|
||||||
SenderWalletID pgtype.Int8 `json:"sender_wallet_id"`
|
SenderWalletID pgtype.Int8 `json:"sender_wallet_id"`
|
||||||
CashierID pgtype.Int8 `json:"cashier_id"`
|
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||||
Verified pgtype.Bool `json:"verified"`
|
Verified pgtype.Bool `json:"verified"`
|
||||||
ReferenceNumber pgtype.Text `json:"reference_number"`
|
ReferenceNumber string `json:"reference_number"`
|
||||||
Status pgtype.Text `json:"status"`
|
Status pgtype.Text `json:"status"`
|
||||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||||
}
|
}
|
||||||
|
|
@ -139,7 +139,7 @@ FROM wallet_transfer
|
||||||
WHERE reference_number = $1
|
WHERE reference_number = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber pgtype.Text) (WalletTransfer, error) {
|
func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber string) (WalletTransfer, error) {
|
||||||
row := q.db.QueryRow(ctx, GetTransferByReference, referenceNumber)
|
row := q.db.QueryRow(ctx, GetTransferByReference, referenceNumber)
|
||||||
var i WalletTransfer
|
var i WalletTransfer
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -199,6 +199,44 @@ func (q *Queries) GetTransfersByWallet(ctx context.Context, receiverWalletID pgt
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GetWalletTransactionsInRange = `-- name: GetWalletTransactionsInRange :many
|
||||||
|
SELECT type, COUNT(*) as count, SUM(amount) as total_amount
|
||||||
|
FROM wallet_transfer
|
||||||
|
WHERE created_at BETWEEN $1 AND $2
|
||||||
|
GROUP BY type
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetWalletTransactionsInRangeParams struct {
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
CreatedAt_2 pgtype.Timestamp `json:"created_at_2"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetWalletTransactionsInRangeRow struct {
|
||||||
|
Type pgtype.Text `json:"type"`
|
||||||
|
Count int64 `json:"count"`
|
||||||
|
TotalAmount int64 `json:"total_amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetWalletTransactionsInRange(ctx context.Context, arg GetWalletTransactionsInRangeParams) ([]GetWalletTransactionsInRangeRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetWalletTransactionsInRange, arg.CreatedAt, arg.CreatedAt_2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetWalletTransactionsInRangeRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetWalletTransactionsInRangeRow
|
||||||
|
if err := rows.Scan(&i.Type, &i.Count, &i.TotalAmount); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const UpdateTransferStatus = `-- name: UpdateTransferStatus :exec
|
const UpdateTransferStatus = `-- name: UpdateTransferStatus :exec
|
||||||
UPDATE wallet_transfer
|
UPDATE wallet_transfer
|
||||||
SET status = $1,
|
SET status = $1,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,110 @@ import (
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const AddFavoriteGame = `-- name: AddFavoriteGame :exec
|
||||||
|
INSERT INTO favorite_games (
|
||||||
|
user_id,
|
||||||
|
game_id,
|
||||||
|
created_at
|
||||||
|
) VALUES ($1, $2, NOW())
|
||||||
|
ON CONFLICT (user_id, game_id) DO NOTHING
|
||||||
|
`
|
||||||
|
|
||||||
|
type AddFavoriteGameParams struct {
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
GameID int64 `json:"game_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) AddFavoriteGame(ctx context.Context, arg AddFavoriteGameParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, AddFavoriteGame, arg.UserID, arg.GameID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateVirtualGameHistory = `-- name: CreateVirtualGameHistory :one
|
||||||
|
INSERT INTO virtual_game_histories (
|
||||||
|
session_id,
|
||||||
|
user_id,
|
||||||
|
company_id,
|
||||||
|
provider,
|
||||||
|
wallet_id,
|
||||||
|
game_id,
|
||||||
|
transaction_type,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
external_transaction_id,
|
||||||
|
reference_transaction_id,
|
||||||
|
status
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12
|
||||||
|
) RETURNING
|
||||||
|
id,
|
||||||
|
session_id,
|
||||||
|
user_id,
|
||||||
|
company_id,
|
||||||
|
provider,
|
||||||
|
wallet_id,
|
||||||
|
game_id,
|
||||||
|
transaction_type,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
external_transaction_id,
|
||||||
|
reference_transaction_id,
|
||||||
|
status,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateVirtualGameHistoryParams struct {
|
||||||
|
SessionID pgtype.Text `json:"session_id"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
|
Provider pgtype.Text `json:"provider"`
|
||||||
|
WalletID pgtype.Int8 `json:"wallet_id"`
|
||||||
|
GameID pgtype.Int8 `json:"game_id"`
|
||||||
|
TransactionType string `json:"transaction_type"`
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
ExternalTransactionID string `json:"external_transaction_id"`
|
||||||
|
ReferenceTransactionID pgtype.Text `json:"reference_transaction_id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateVirtualGameHistory(ctx context.Context, arg CreateVirtualGameHistoryParams) (VirtualGameHistory, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreateVirtualGameHistory,
|
||||||
|
arg.SessionID,
|
||||||
|
arg.UserID,
|
||||||
|
arg.CompanyID,
|
||||||
|
arg.Provider,
|
||||||
|
arg.WalletID,
|
||||||
|
arg.GameID,
|
||||||
|
arg.TransactionType,
|
||||||
|
arg.Amount,
|
||||||
|
arg.Currency,
|
||||||
|
arg.ExternalTransactionID,
|
||||||
|
arg.ReferenceTransactionID,
|
||||||
|
arg.Status,
|
||||||
|
)
|
||||||
|
var i VirtualGameHistory
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SessionID,
|
||||||
|
&i.UserID,
|
||||||
|
&i.CompanyID,
|
||||||
|
&i.Provider,
|
||||||
|
&i.WalletID,
|
||||||
|
&i.GameID,
|
||||||
|
&i.TransactionType,
|
||||||
|
&i.Amount,
|
||||||
|
&i.Currency,
|
||||||
|
&i.ExternalTransactionID,
|
||||||
|
&i.ReferenceTransactionID,
|
||||||
|
&i.Status,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const CreateVirtualGameSession = `-- name: CreateVirtualGameSession :one
|
const CreateVirtualGameSession = `-- name: CreateVirtualGameSession :one
|
||||||
INSERT INTO virtual_game_sessions (
|
INSERT INTO virtual_game_sessions (
|
||||||
user_id, game_id, session_token, currency, status, expires_at
|
user_id, game_id, session_token, currency, status, expires_at
|
||||||
|
|
@ -54,27 +158,47 @@ func (q *Queries) CreateVirtualGameSession(ctx context.Context, arg CreateVirtua
|
||||||
|
|
||||||
const CreateVirtualGameTransaction = `-- name: CreateVirtualGameTransaction :one
|
const CreateVirtualGameTransaction = `-- name: CreateVirtualGameTransaction :one
|
||||||
INSERT INTO virtual_game_transactions (
|
INSERT INTO virtual_game_transactions (
|
||||||
session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status
|
session_id, user_id, company_id, provider, wallet_id, transaction_type, amount, currency, external_transaction_id, status
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1, $2, $3, $4, $5, $6, $7, $8
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||||
) RETURNING id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
) RETURNING id, session_id, user_id, company_id, provider, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateVirtualGameTransactionParams struct {
|
type CreateVirtualGameTransactionParams struct {
|
||||||
SessionID int64 `json:"session_id"`
|
SessionID int64 `json:"session_id"`
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
WalletID int64 `json:"wallet_id"`
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
TransactionType string `json:"transaction_type"`
|
Provider pgtype.Text `json:"provider"`
|
||||||
Amount int64 `json:"amount"`
|
WalletID int64 `json:"wallet_id"`
|
||||||
Currency string `json:"currency"`
|
TransactionType string `json:"transaction_type"`
|
||||||
ExternalTransactionID string `json:"external_transaction_id"`
|
Amount int64 `json:"amount"`
|
||||||
Status string `json:"status"`
|
Currency string `json:"currency"`
|
||||||
|
ExternalTransactionID string `json:"external_transaction_id"`
|
||||||
|
Status string `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateVirtualGameTransaction(ctx context.Context, arg CreateVirtualGameTransactionParams) (VirtualGameTransaction, error) {
|
type CreateVirtualGameTransactionRow struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
SessionID int64 `json:"session_id"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
CompanyID pgtype.Int8 `json:"company_id"`
|
||||||
|
Provider pgtype.Text `json:"provider"`
|
||||||
|
WalletID int64 `json:"wallet_id"`
|
||||||
|
TransactionType string `json:"transaction_type"`
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
ExternalTransactionID string `json:"external_transaction_id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateVirtualGameTransaction(ctx context.Context, arg CreateVirtualGameTransactionParams) (CreateVirtualGameTransactionRow, error) {
|
||||||
row := q.db.QueryRow(ctx, CreateVirtualGameTransaction,
|
row := q.db.QueryRow(ctx, CreateVirtualGameTransaction,
|
||||||
arg.SessionID,
|
arg.SessionID,
|
||||||
arg.UserID,
|
arg.UserID,
|
||||||
|
arg.CompanyID,
|
||||||
|
arg.Provider,
|
||||||
arg.WalletID,
|
arg.WalletID,
|
||||||
arg.TransactionType,
|
arg.TransactionType,
|
||||||
arg.Amount,
|
arg.Amount,
|
||||||
|
|
@ -82,11 +206,13 @@ func (q *Queries) CreateVirtualGameTransaction(ctx context.Context, arg CreateVi
|
||||||
arg.ExternalTransactionID,
|
arg.ExternalTransactionID,
|
||||||
arg.Status,
|
arg.Status,
|
||||||
)
|
)
|
||||||
var i VirtualGameTransaction
|
var i CreateVirtualGameTransactionRow
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.SessionID,
|
&i.SessionID,
|
||||||
&i.UserID,
|
&i.UserID,
|
||||||
|
&i.CompanyID,
|
||||||
|
&i.Provider,
|
||||||
&i.WalletID,
|
&i.WalletID,
|
||||||
&i.TransactionType,
|
&i.TransactionType,
|
||||||
&i.Amount,
|
&i.Amount,
|
||||||
|
|
@ -122,15 +248,81 @@ func (q *Queries) GetVirtualGameSessionByToken(ctx context.Context, sessionToken
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GetVirtualGameSummaryInRange = `-- name: GetVirtualGameSummaryInRange :many
|
||||||
|
SELECT
|
||||||
|
c.name AS company_name,
|
||||||
|
vg.name AS game_name,
|
||||||
|
COUNT(vgt.id) AS number_of_bets,
|
||||||
|
COALESCE(SUM(vgt.amount), 0) AS total_transaction_sum
|
||||||
|
FROM virtual_game_transactions vgt
|
||||||
|
JOIN virtual_game_sessions vgs ON vgt.session_id = vgs.id
|
||||||
|
JOIN virtual_games vg ON vgs.game_id = vg.id
|
||||||
|
JOIN companies c ON vgt.company_id = c.id
|
||||||
|
WHERE vgt.transaction_type = 'BET'
|
||||||
|
AND vgt.created_at BETWEEN $1 AND $2
|
||||||
|
GROUP BY c.name, vg.name
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetVirtualGameSummaryInRangeParams struct {
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
CreatedAt_2 pgtype.Timestamptz `json:"created_at_2"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetVirtualGameSummaryInRangeRow struct {
|
||||||
|
CompanyName string `json:"company_name"`
|
||||||
|
GameName string `json:"game_name"`
|
||||||
|
NumberOfBets int64 `json:"number_of_bets"`
|
||||||
|
TotalTransactionSum interface{} `json:"total_transaction_sum"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetVirtualGameSummaryInRange(ctx context.Context, arg GetVirtualGameSummaryInRangeParams) ([]GetVirtualGameSummaryInRangeRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetVirtualGameSummaryInRange, arg.CreatedAt, arg.CreatedAt_2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetVirtualGameSummaryInRangeRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetVirtualGameSummaryInRangeRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.CompanyName,
|
||||||
|
&i.GameName,
|
||||||
|
&i.NumberOfBets,
|
||||||
|
&i.TotalTransactionSum,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const GetVirtualGameTransactionByExternalID = `-- name: GetVirtualGameTransactionByExternalID :one
|
const GetVirtualGameTransactionByExternalID = `-- name: GetVirtualGameTransactionByExternalID :one
|
||||||
SELECT id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
SELECT id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
||||||
FROM virtual_game_transactions
|
FROM virtual_game_transactions
|
||||||
WHERE external_transaction_id = $1
|
WHERE external_transaction_id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetVirtualGameTransactionByExternalID(ctx context.Context, externalTransactionID string) (VirtualGameTransaction, error) {
|
type GetVirtualGameTransactionByExternalIDRow struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
SessionID int64 `json:"session_id"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
WalletID int64 `json:"wallet_id"`
|
||||||
|
TransactionType string `json:"transaction_type"`
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
ExternalTransactionID string `json:"external_transaction_id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetVirtualGameTransactionByExternalID(ctx context.Context, externalTransactionID string) (GetVirtualGameTransactionByExternalIDRow, error) {
|
||||||
row := q.db.QueryRow(ctx, GetVirtualGameTransactionByExternalID, externalTransactionID)
|
row := q.db.QueryRow(ctx, GetVirtualGameTransactionByExternalID, externalTransactionID)
|
||||||
var i VirtualGameTransaction
|
var i GetVirtualGameTransactionByExternalIDRow
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.SessionID,
|
&i.SessionID,
|
||||||
|
|
@ -147,6 +339,47 @@ func (q *Queries) GetVirtualGameTransactionByExternalID(ctx context.Context, ext
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ListFavoriteGames = `-- name: ListFavoriteGames :many
|
||||||
|
SELECT game_id
|
||||||
|
FROM favorite_games
|
||||||
|
WHERE user_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListFavoriteGames(ctx context.Context, userID int64) ([]int64, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListFavoriteGames, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []int64
|
||||||
|
for rows.Next() {
|
||||||
|
var game_id int64
|
||||||
|
if err := rows.Scan(&game_id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, game_id)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const RemoveFavoriteGame = `-- name: RemoveFavoriteGame :exec
|
||||||
|
DELETE FROM favorite_games
|
||||||
|
WHERE user_id = $1 AND game_id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type RemoveFavoriteGameParams struct {
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
GameID int64 `json:"game_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) RemoveFavoriteGame(ctx context.Context, arg RemoveFavoriteGameParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, RemoveFavoriteGame, arg.UserID, arg.GameID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const UpdateVirtualGameSessionStatus = `-- name: UpdateVirtualGameSessionStatus :exec
|
const UpdateVirtualGameSessionStatus = `-- name: UpdateVirtualGameSessionStatus :exec
|
||||||
UPDATE virtual_game_sessions
|
UPDATE virtual_game_sessions
|
||||||
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ INSERT INTO wallets (
|
||||||
user_id
|
user_id
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4)
|
VALUES ($1, $2, $3, $4)
|
||||||
RETURNING id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance
|
RETURNING id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateWalletParams struct {
|
type CreateWalletParams struct {
|
||||||
|
|
@ -77,6 +77,7 @@ func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wal
|
||||||
&i.IsActive,
|
&i.IsActive,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
&i.Currency,
|
||||||
&i.BonusBalance,
|
&i.BonusBalance,
|
||||||
&i.CashBalance,
|
&i.CashBalance,
|
||||||
)
|
)
|
||||||
|
|
@ -142,8 +143,48 @@ func (q *Queries) GetAllBranchWallets(ctx context.Context) ([]GetAllBranchWallet
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GetAllCustomerWallet = `-- name: GetAllCustomerWallet :many
|
||||||
|
SELECT id, customer_id, regular_id, regular_balance, static_id, static_balance, regular_is_active, static_is_active, regular_updated_at, static_updated_at, created_at, first_name, last_name, phone_number
|
||||||
|
FROM customer_wallet_details
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetAllCustomerWallet(ctx context.Context) ([]CustomerWalletDetail, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetAllCustomerWallet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []CustomerWalletDetail
|
||||||
|
for rows.Next() {
|
||||||
|
var i CustomerWalletDetail
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CustomerID,
|
||||||
|
&i.RegularID,
|
||||||
|
&i.RegularBalance,
|
||||||
|
&i.StaticID,
|
||||||
|
&i.StaticBalance,
|
||||||
|
&i.RegularIsActive,
|
||||||
|
&i.StaticIsActive,
|
||||||
|
&i.RegularUpdatedAt,
|
||||||
|
&i.StaticUpdatedAt,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.FirstName,
|
||||||
|
&i.LastName,
|
||||||
|
&i.PhoneNumber,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const GetAllWallets = `-- name: GetAllWallets :many
|
const GetAllWallets = `-- name: GetAllWallets :many
|
||||||
SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance
|
SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
|
||||||
FROM wallets
|
FROM wallets
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -166,6 +207,7 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) {
|
||||||
&i.IsActive,
|
&i.IsActive,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
&i.Currency,
|
||||||
&i.BonusBalance,
|
&i.BonusBalance,
|
||||||
&i.CashBalance,
|
&i.CashBalance,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
@ -179,37 +221,59 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) {
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetCustomerWallet = `-- name: GetCustomerWallet :one
|
const GetBranchByWalletID = `-- name: GetBranchByWalletID :one
|
||||||
SELECT cw.id,
|
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at
|
||||||
cw.customer_id,
|
FROM branches
|
||||||
rw.id AS regular_id,
|
WHERE wallet_id = $1
|
||||||
rw.balance AS regular_balance,
|
LIMIT 1
|
||||||
sw.id AS static_id,
|
|
||||||
sw.balance AS static_balance,
|
|
||||||
rw.updated_at as regular_updated_at,
|
|
||||||
sw.updated_at as static_updated_at,
|
|
||||||
cw.created_at
|
|
||||||
FROM customer_wallets cw
|
|
||||||
JOIN wallets rw ON cw.regular_wallet_id = rw.id
|
|
||||||
JOIN wallets sw ON cw.static_wallet_id = sw.id
|
|
||||||
WHERE cw.customer_id = $1
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetCustomerWalletRow struct {
|
func (q *Queries) GetBranchByWalletID(ctx context.Context, walletID int64) (Branch, error) {
|
||||||
ID int64 `json:"id"`
|
row := q.db.QueryRow(ctx, GetBranchByWalletID, walletID)
|
||||||
CustomerID int64 `json:"customer_id"`
|
var i Branch
|
||||||
RegularID int64 `json:"regular_id"`
|
err := row.Scan(
|
||||||
RegularBalance int64 `json:"regular_balance"`
|
&i.ID,
|
||||||
StaticID int64 `json:"static_id"`
|
&i.Name,
|
||||||
StaticBalance int64 `json:"static_balance"`
|
&i.Location,
|
||||||
RegularUpdatedAt pgtype.Timestamp `json:"regular_updated_at"`
|
&i.IsActive,
|
||||||
StaticUpdatedAt pgtype.Timestamp `json:"static_updated_at"`
|
&i.WalletID,
|
||||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
&i.BranchManagerID,
|
||||||
|
&i.CompanyID,
|
||||||
|
&i.IsSelfOwned,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (GetCustomerWalletRow, error) {
|
const GetCompanyByWalletID = `-- name: GetCompanyByWalletID :one
|
||||||
|
SELECT id, name, admin_id, wallet_id
|
||||||
|
FROM companies
|
||||||
|
WHERE wallet_id = $1
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetCompanyByWalletID(ctx context.Context, walletID int64) (Company, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetCompanyByWalletID, walletID)
|
||||||
|
var i Company
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.AdminID,
|
||||||
|
&i.WalletID,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetCustomerWallet = `-- name: GetCustomerWallet :one
|
||||||
|
SELECT id, customer_id, regular_id, regular_balance, static_id, static_balance, regular_is_active, static_is_active, regular_updated_at, static_updated_at, created_at, first_name, last_name, phone_number
|
||||||
|
FROM customer_wallet_details
|
||||||
|
WHERE customer_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (CustomerWalletDetail, error) {
|
||||||
row := q.db.QueryRow(ctx, GetCustomerWallet, customerID)
|
row := q.db.QueryRow(ctx, GetCustomerWallet, customerID)
|
||||||
var i GetCustomerWalletRow
|
var i CustomerWalletDetail
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.CustomerID,
|
&i.CustomerID,
|
||||||
|
|
@ -217,15 +281,20 @@ func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (GetC
|
||||||
&i.RegularBalance,
|
&i.RegularBalance,
|
||||||
&i.StaticID,
|
&i.StaticID,
|
||||||
&i.StaticBalance,
|
&i.StaticBalance,
|
||||||
|
&i.RegularIsActive,
|
||||||
|
&i.StaticIsActive,
|
||||||
&i.RegularUpdatedAt,
|
&i.RegularUpdatedAt,
|
||||||
&i.StaticUpdatedAt,
|
&i.StaticUpdatedAt,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
|
&i.FirstName,
|
||||||
|
&i.LastName,
|
||||||
|
&i.PhoneNumber,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetWalletByID = `-- name: GetWalletByID :one
|
const GetWalletByID = `-- name: GetWalletByID :one
|
||||||
SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance
|
SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
|
||||||
FROM wallets
|
FROM wallets
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -243,6 +312,7 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) {
|
||||||
&i.IsActive,
|
&i.IsActive,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
&i.Currency,
|
||||||
&i.BonusBalance,
|
&i.BonusBalance,
|
||||||
&i.CashBalance,
|
&i.CashBalance,
|
||||||
)
|
)
|
||||||
|
|
@ -250,7 +320,7 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetWalletByUserID = `-- name: GetWalletByUserID :many
|
const GetWalletByUserID = `-- name: GetWalletByUserID :many
|
||||||
SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance
|
SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
|
||||||
FROM wallets
|
FROM wallets
|
||||||
WHERE user_id = $1
|
WHERE user_id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -274,6 +344,7 @@ func (q *Queries) GetWalletByUserID(ctx context.Context, userID int64) ([]Wallet
|
||||||
&i.IsActive,
|
&i.IsActive,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
&i.Currency,
|
||||||
&i.BonusBalance,
|
&i.BonusBalance,
|
||||||
&i.CashBalance,
|
&i.CashBalance,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
|
||||||
14
go.mod
14
go.mod
|
|
@ -77,4 +77,16 @@ require (
|
||||||
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
|
require (
|
||||||
|
github.com/go-resty/resty/v2 v2.16.5
|
||||||
|
github.com/twilio/twilio-go v1.26.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/redis/go-redis/v9 v9.10.0 // indirect
|
||||||
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
)
|
||||||
|
|
|
||||||
24
go.sum
24
go.sum
|
|
@ -9,11 +9,14 @@ github.com/amanuelabay/afrosms-go v1.0.6/go.mod h1:5mzzZtWSCDdvQsA0OyYf5CtbdGpl9
|
||||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
|
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
|
@ -22,6 +25,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
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=
|
||||||
|
|
@ -49,11 +54,15 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
|
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||||
|
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||||
github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY=
|
github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY=
|
||||||
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
||||||
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
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=
|
||||||
|
|
@ -94,6 +103,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275 h1:IZycmTpoUtQK3PD60UYBwjaCUHUP7cML494ao9/O8+Q=
|
||||||
|
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275/go.mod h1:zt6UU74K6Z6oMOYJbJzYpYucqdcQwSMPBEdSvGiaUMw=
|
||||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
|
@ -114,8 +125,12 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6
|
||||||
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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
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/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs=
|
||||||
|
github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||||
github.com/resend/resend-go/v2 v2.20.0 h1:MrIrgV0aHhwRgmcRPw33Nexn6aGJvCvG2XwfFpAMBGM=
|
github.com/resend/resend-go/v2 v2.20.0 h1:MrIrgV0aHhwRgmcRPw33Nexn6aGJvCvG2XwfFpAMBGM=
|
||||||
github.com/resend/resend-go/v2 v2.20.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ=
|
github.com/resend/resend-go/v2 v2.20.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
|
@ -150,6 +165,8 @@ github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9J
|
||||||
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
|
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
|
||||||
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
||||||
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
||||||
|
github.com/twilio/twilio-go v1.26.3 h1:K2mYBzbhPVyWF+Jq5Sw53edBFvkgWo4sKTvgaO7461I=
|
||||||
|
github.com/twilio/twilio-go v1.26.3/go.mod h1:FpgNWMoD8CFnmukpKq9RNpUSGXC0BwnbeKZj2YHlIkw=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
|
|
@ -170,6 +187,7 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||||
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
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=
|
||||||
|
|
@ -198,6 +216,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
|
@ -213,8 +232,10 @@ golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|
@ -233,9 +254,12 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
|
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||||
|
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||||
|
|
|
||||||
|
|
@ -13,25 +13,28 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidDbUrl = errors.New("db url is invalid")
|
ErrInvalidDbUrl = errors.New("db url is invalid")
|
||||||
ErrInvalidPort = errors.New("port number is invalid")
|
ErrInvalidPort = errors.New("port number is invalid")
|
||||||
ErrRefreshExpiry = errors.New("refresh token expiry is invalid")
|
ErrRefreshExpiry = errors.New("refresh token expiry is invalid")
|
||||||
ErrAccessExpiry = errors.New("access token expiry is invalid")
|
ErrAccessExpiry = errors.New("access token expiry is invalid")
|
||||||
ErrInvalidJwtKey = errors.New("jwt key is invalid")
|
ErrInvalidJwtKey = errors.New("jwt key is invalid")
|
||||||
ErrLogLevel = errors.New("log level not set")
|
ErrLogLevel = errors.New("log level not set")
|
||||||
ErrInvalidLevel = errors.New("invalid log level")
|
ErrInvalidLevel = errors.New("invalid log level")
|
||||||
ErrInvalidEnv = errors.New("env not set or invalid")
|
ErrInvalidEnv = errors.New("env not set or invalid")
|
||||||
ErrInvalidSMSAPIKey = errors.New("SMS API key is invalid")
|
ErrInvalidSMSAPIKey = errors.New("SMS API key is invalid")
|
||||||
ErrMissingBetToken = errors.New("missing BET365_TOKEN in .env")
|
ErrMissingBetToken = errors.New("missing BET365_TOKEN in .env")
|
||||||
ErrInvalidPopOKClientID = errors.New("PopOK client ID is invalid")
|
ErrInvalidPopOKClientID = errors.New("PopOK client ID is invalid")
|
||||||
ErrInvalidPopOKSecretKey = errors.New("PopOK secret key is invalid")
|
ErrInvalidPopOKSecretKey = errors.New("PopOK secret key is invalid")
|
||||||
ErrInvalidPopOKBaseURL = errors.New("PopOK base URL is invalid")
|
ErrInvalidPopOKBaseURL = errors.New("PopOK base URL is invalid")
|
||||||
ErrInvalidPopOKCallbackURL = errors.New("PopOK callback URL is invalid")
|
ErrInvalidPopOKCallbackURL = errors.New("PopOK callback URL is invalid")
|
||||||
ErrInvalidVeliAPIURL = errors.New("Veli API URL is invalid")
|
ErrInvalidVeliAPIURL = errors.New("Veli API URL is invalid")
|
||||||
ErrInvalidVeliOperatorKey = errors.New("Veli operator key is invalid")
|
ErrInvalidVeliOperatorKey = errors.New("Veli operator key is invalid")
|
||||||
ErrInvalidVeliSecretKey = errors.New("Veli secret key is invalid")
|
ErrInvalidVeliSecretKey = errors.New("Veli secret key is invalid")
|
||||||
ErrMissingResendApiKey = errors.New("missing Resend Api key")
|
ErrMissingResendApiKey = errors.New("missing Resend Api key")
|
||||||
ErrMissingResendSenderEmail = errors.New("missing Resend sender name")
|
ErrMissingResendSenderEmail = errors.New("missing Resend sender name")
|
||||||
|
ErrMissingTwilioAccountSid = errors.New("missing twilio account sid")
|
||||||
|
ErrMissingTwilioAuthToken = errors.New("missing twilio auth token")
|
||||||
|
ErrMissingTwilioSenderPhoneNumber = errors.New("missing twilio sender phone number")
|
||||||
)
|
)
|
||||||
|
|
||||||
type AleaPlayConfig struct {
|
type AleaPlayConfig struct {
|
||||||
|
|
@ -44,15 +47,14 @@ type AleaPlayConfig struct {
|
||||||
SessionTimeout int `mapstructure:"session_timeout"` // In hours
|
SessionTimeout int `mapstructure:"session_timeout"` // In hours
|
||||||
}
|
}
|
||||||
|
|
||||||
type VeliGamesConfig struct {
|
type VeliConfig struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
APIKey string `mapstructure:"VELI_API_KEY"`
|
||||||
APIURL string `mapstructure:"api_url"`
|
BaseURL string `mapstructure:"VELI_BASE_URL"`
|
||||||
OperatorKey string `mapstructure:"operator_key"`
|
SecretKey string `mapstructure:"VELI_SECRET_KEY"`
|
||||||
SecretKey string `mapstructure:"secret_key"`
|
OperatorID string `mapstructure:"VELI_OPERATOR_ID"`
|
||||||
DefaultCurrency string `mapstructure:"default_currency"`
|
Currency string `mapstructure:"VELI_DEFAULT_CURRENCY"`
|
||||||
GameIDs struct {
|
WebhookURL string `mapstructure:"VELI_WEBHOOK_URL"`
|
||||||
Aviator string `mapstructure:"aviator"`
|
Enabled bool `mapstructure:"Enabled"`
|
||||||
} `mapstructure:"game_ids"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|
@ -60,6 +62,7 @@ type Config struct {
|
||||||
FIXER_BASE_URL string
|
FIXER_BASE_URL string
|
||||||
BASE_CURRENCY domain.IntCurrency
|
BASE_CURRENCY domain.IntCurrency
|
||||||
Port int
|
Port int
|
||||||
|
Service string
|
||||||
DbUrl string
|
DbUrl string
|
||||||
RefreshExpiry int
|
RefreshExpiry int
|
||||||
AccessExpiry int
|
AccessExpiry int
|
||||||
|
|
@ -81,10 +84,14 @@ type Config struct {
|
||||||
CHAPA_RETURN_URL string
|
CHAPA_RETURN_URL string
|
||||||
Bet365Token string
|
Bet365Token string
|
||||||
PopOK domain.PopOKConfig
|
PopOK domain.PopOKConfig
|
||||||
AleaPlay AleaPlayConfig `mapstructure:"alea_play"`
|
AleaPlay AleaPlayConfig `mapstructure:"alea_play"`
|
||||||
VeliGames VeliGamesConfig `mapstructure:"veli_games"`
|
VeliGames VeliConfig `mapstructure:"veli_games"`
|
||||||
ResendApiKey string
|
ResendApiKey string
|
||||||
ResendSenderEmail string
|
ResendSenderEmail string
|
||||||
|
TwilioAccountSid string
|
||||||
|
TwilioAuthToken string
|
||||||
|
TwilioSenderPhoneNumber string
|
||||||
|
RedisAddr string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig() (*Config, error) {
|
func NewConfig() (*Config, error) {
|
||||||
|
|
@ -109,6 +116,8 @@ func (c *Config) loadEnv() error {
|
||||||
|
|
||||||
c.ReportExportPath = os.Getenv("REPORT_EXPORT_PATH")
|
c.ReportExportPath = os.Getenv("REPORT_EXPORT_PATH")
|
||||||
|
|
||||||
|
c.RedisAddr = os.Getenv("REDIS_ADDR")
|
||||||
|
|
||||||
c.CHAPA_TRANSFER_TYPE = os.Getenv("CHAPA_TRANSFER_TYPE")
|
c.CHAPA_TRANSFER_TYPE = os.Getenv("CHAPA_TRANSFER_TYPE")
|
||||||
c.CHAPA_PAYMENT_TYPE = os.Getenv("CHAPA_PAYMENT_TYPE")
|
c.CHAPA_PAYMENT_TYPE = os.Getenv("CHAPA_PAYMENT_TYPE")
|
||||||
|
|
||||||
|
|
@ -236,26 +245,26 @@ func (c *Config) loadEnv() error {
|
||||||
if apiURL == "" {
|
if apiURL == "" {
|
||||||
apiURL = "https://api.velitech.games" // Default production URL
|
apiURL = "https://api.velitech.games" // Default production URL
|
||||||
}
|
}
|
||||||
c.VeliGames.APIURL = apiURL
|
c.VeliGames.BaseURL = apiURL
|
||||||
|
|
||||||
operatorKey := os.Getenv("VELI_OPERATOR_KEY")
|
operatorKey := os.Getenv("VELI_OPERATOR_KEY")
|
||||||
if operatorKey == "" && c.VeliGames.Enabled {
|
if operatorKey == "" && c.VeliGames.Enabled {
|
||||||
return ErrInvalidVeliOperatorKey
|
return ErrInvalidVeliOperatorKey
|
||||||
}
|
}
|
||||||
c.VeliGames.OperatorKey = operatorKey
|
// c.VeliGames.OperatorKey = operatorKey
|
||||||
|
|
||||||
secretKey := os.Getenv("VELI_SECRET_KEY")
|
secretKey := os.Getenv("VELI_SECRET_KEY")
|
||||||
if secretKey == "" && c.VeliGames.Enabled {
|
if secretKey == "" && c.VeliGames.Enabled {
|
||||||
return ErrInvalidVeliSecretKey
|
return ErrInvalidVeliSecretKey
|
||||||
}
|
}
|
||||||
c.VeliGames.SecretKey = secretKey
|
c.VeliGames.SecretKey = secretKey
|
||||||
c.VeliGames.GameIDs.Aviator = os.Getenv("VELI_GAME_ID_AVIATOR")
|
// c.VeliGames.GameIDs.Aviator = os.Getenv("VELI_GAME_ID_AVIATOR")
|
||||||
|
|
||||||
defaultCurrency := os.Getenv("VELI_DEFAULT_CURRENCY")
|
defaultCurrency := os.Getenv("VELI_DEFAULT_CURRENCY")
|
||||||
if defaultCurrency == "" {
|
if defaultCurrency == "" {
|
||||||
defaultCurrency = "USD" // Default currency
|
defaultCurrency = "USD" // Default currency
|
||||||
}
|
}
|
||||||
c.VeliGames.DefaultCurrency = defaultCurrency
|
// c.VeliGames.DefaultCurrency = defaultCurrency
|
||||||
|
|
||||||
c.LogLevel = lvl
|
c.LogLevel = lvl
|
||||||
|
|
||||||
|
|
@ -324,6 +333,24 @@ func (c *Config) loadEnv() error {
|
||||||
}
|
}
|
||||||
c.ResendSenderEmail = resendSenderEmail
|
c.ResendSenderEmail = resendSenderEmail
|
||||||
|
|
||||||
|
twilioAccountSid := os.Getenv("TWILIO_ACCOUNT_SID")
|
||||||
|
if twilioAccountSid == "" {
|
||||||
|
return ErrMissingTwilioAccountSid
|
||||||
|
}
|
||||||
|
c.TwilioAccountSid = twilioAccountSid
|
||||||
|
|
||||||
|
twilioAuthToken := os.Getenv("TWILIO_AUTH_TOKEN")
|
||||||
|
if twilioAuthToken == "" {
|
||||||
|
return ErrMissingTwilioAuthToken
|
||||||
|
}
|
||||||
|
c.TwilioAuthToken = twilioAuthToken
|
||||||
|
|
||||||
|
twilioSenderPhoneNumber := os.Getenv("TWILIO_SENDER_PHONE_NUMBER")
|
||||||
|
if twilioSenderPhoneNumber == "" {
|
||||||
|
return ErrMissingTwilioSenderPhoneNumber
|
||||||
|
}
|
||||||
|
c.TwilioSenderPhoneNumber = twilioSenderPhoneNumber
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,13 @@ type Bet struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type BetFilter struct {
|
type BetFilter struct {
|
||||||
BranchID ValidInt64 // Can Be Nullable
|
BranchID ValidInt64 // Can Be Nullable
|
||||||
CompanyID ValidInt64 // Can Be Nullable
|
CompanyID ValidInt64 // Can Be Nullable
|
||||||
UserID ValidInt64 // Can Be Nullable
|
UserID ValidInt64 // Can Be Nullable
|
||||||
|
IsShopBet ValidBool
|
||||||
|
Query ValidString
|
||||||
|
CreatedBefore ValidTime
|
||||||
|
CreatedAfter ValidTime
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetBet struct {
|
type GetBet struct {
|
||||||
|
|
@ -80,16 +84,17 @@ type GetBet struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateBet struct {
|
type CreateBet struct {
|
||||||
Amount Currency
|
Amount Currency
|
||||||
TotalOdds float32
|
TotalOdds float32
|
||||||
Status OutcomeStatus
|
Status OutcomeStatus
|
||||||
FullName string
|
FullName string
|
||||||
PhoneNumber string
|
PhoneNumber string
|
||||||
CompanyID ValidInt64 // Can Be Nullable
|
CompanyID ValidInt64 // Can Be Nullable
|
||||||
BranchID ValidInt64 // Can Be Nullable
|
BranchID ValidInt64 // Can Be Nullable
|
||||||
UserID ValidInt64 // Can Be Nullable
|
UserID ValidInt64 // Can Be Nullable
|
||||||
IsShopBet bool
|
IsShopBet bool
|
||||||
CashoutID string
|
CashoutID string
|
||||||
|
OutcomesHash string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateBetOutcomeReq struct {
|
type CreateBetOutcomeReq struct {
|
||||||
|
|
@ -173,4 +178,3 @@ func ConvertBet(bet GetBet) BetRes {
|
||||||
CreatedAt: bet.CreatedAt,
|
CreatedAt: bet.CreatedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,19 @@ type Branch struct {
|
||||||
WalletID int64
|
WalletID int64
|
||||||
BranchManagerID int64
|
BranchManagerID int64
|
||||||
CompanyID int64
|
CompanyID int64
|
||||||
IsSuspended bool
|
IsActive bool
|
||||||
IsSelfOwned bool
|
IsSelfOwned bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BranchFilter struct {
|
||||||
|
CompanyID ValidInt64
|
||||||
|
IsActive ValidBool
|
||||||
|
BranchManagerID ValidInt64
|
||||||
|
Query ValidString
|
||||||
|
CreatedBefore ValidTime
|
||||||
|
CreatedAfter ValidTime
|
||||||
|
}
|
||||||
|
|
||||||
type BranchDetail struct {
|
type BranchDetail struct {
|
||||||
ID int64
|
ID int64
|
||||||
Name string
|
Name string
|
||||||
|
|
@ -19,10 +28,11 @@ type BranchDetail struct {
|
||||||
Balance Currency
|
Balance Currency
|
||||||
BranchManagerID int64
|
BranchManagerID int64
|
||||||
CompanyID int64
|
CompanyID int64
|
||||||
IsSuspended bool
|
IsActive bool
|
||||||
IsSelfOwned bool
|
IsSelfOwned bool
|
||||||
ManagerName string
|
ManagerName string
|
||||||
ManagerPhoneNumber string
|
ManagerPhoneNumber string
|
||||||
|
WalletIsActive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type SupportedOperation struct {
|
type SupportedOperation struct {
|
||||||
|
|
@ -53,6 +63,7 @@ type UpdateBranch struct {
|
||||||
BranchManagerID *int64
|
BranchManagerID *int64
|
||||||
CompanyID *int64
|
CompanyID *int64
|
||||||
IsSelfOwned *bool
|
IsSelfOwned *bool
|
||||||
|
IsActive *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateSupportedOperation struct {
|
type CreateSupportedOperation struct {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ type PaymentStatus string
|
||||||
type WithdrawalStatus string
|
type WithdrawalStatus string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
WithdrawalStatusSuccessful WithdrawalStatus = "success"
|
||||||
WithdrawalStatusPending WithdrawalStatus = "pending"
|
WithdrawalStatusPending WithdrawalStatus = "pending"
|
||||||
WithdrawalStatusProcessing WithdrawalStatus = "processing"
|
WithdrawalStatusProcessing WithdrawalStatus = "processing"
|
||||||
WithdrawalStatusCompleted WithdrawalStatus = "completed"
|
WithdrawalStatusCompleted WithdrawalStatus = "completed"
|
||||||
|
|
@ -23,9 +24,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PaymentStatusPending PaymentStatus = "pending"
|
PaymentStatusSuccessful PaymentStatus = "success"
|
||||||
PaymentStatusCompleted PaymentStatus = "completed"
|
PaymentStatusPending PaymentStatus = "pending"
|
||||||
PaymentStatusFailed PaymentStatus = "failed"
|
PaymentStatusCompleted PaymentStatus = "completed"
|
||||||
|
PaymentStatusFailed PaymentStatus = "failed"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ChapaDepositRequest struct {
|
type ChapaDepositRequest struct {
|
||||||
|
|
@ -70,22 +72,23 @@ type ChapaVerificationResponse struct {
|
||||||
TxRef string `json:"tx_ref"`
|
TxRef string `json:"tx_ref"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Bank struct {
|
// type Bank struct {
|
||||||
ID int `json:"id"`
|
// ID int `json:"id"`
|
||||||
Slug string `json:"slug"`
|
// Slug string `json:"slug"`
|
||||||
Swift string `json:"swift"`
|
// Swift string `json:"swift"`
|
||||||
Name string `json:"name"`
|
// Name string `json:"name"`
|
||||||
AcctLength int `json:"acct_length"`
|
// AcctLength int `json:"acct_length"`
|
||||||
CountryID int `json:"country_id"`
|
// CountryID int `json:"country_id"`
|
||||||
IsMobileMoney int `json:"is_mobilemoney"` // nullable
|
// IsMobileMoney int `json:"is_mobilemoney"` // nullable
|
||||||
IsActive int `json:"is_active"`
|
// IsActive int `json:"is_active"`
|
||||||
IsRTGS int `json:"is_rtgs"`
|
// IsRTGS int `json:"is_rtgs"`
|
||||||
Active int `json:"active"`
|
// Active int `json:"active"`
|
||||||
Is24Hrs int `json:"is_24hrs"` // nullable
|
// Is24Hrs int `json:"is_24hrs"` // nullable
|
||||||
CreatedAt time.Time `json:"created_at"`
|
// CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
// UpdatedAt time.Time `json:"updated_at"`
|
||||||
Currency string `json:"currency"`
|
// Currency string `json:"currency"`
|
||||||
}
|
// BankLogo string `json:"bank_logo"` // URL or base64
|
||||||
|
// }
|
||||||
|
|
||||||
type BankResponse struct {
|
type BankResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
|
@ -142,11 +145,9 @@ type ChapaWithdrawalRequest struct {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
type ChapaWithdrawalResponse struct {
|
type ChapaWithdrawalResponse struct {
|
||||||
Status string `json:"status"`
|
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Data struct {
|
Status string `json:"status"`
|
||||||
Reference string `json:"reference"`
|
Data string `json:"data"` // Accepts string instead of struct
|
||||||
} `json:"data"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapaTransactionType struct {
|
type ChapaTransactionType struct {
|
||||||
|
|
|
||||||
|
|
@ -78,3 +78,6 @@ func CalculateWinnings(amount Currency, totalOdds float32) Currency {
|
||||||
return ToCurrency(possibleWin - incomeTax)
|
return ToCurrency(possibleWin - incomeTax)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PtrFloat64(v float64) *float64 { return &v }
|
||||||
|
func PtrInt64(v int64) *int64 { return &v }
|
||||||
|
|
|
||||||
21
internal/domain/institutions.go
Normal file
21
internal/domain/institutions.go
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Bank struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Slug string `json:"slug"`
|
||||||
|
Swift string `json:"swift"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
AcctLength int `json:"acct_length"`
|
||||||
|
CountryID int `json:"country_id"`
|
||||||
|
IsMobileMoney int `json:"is_mobilemoney"` // nullable
|
||||||
|
IsActive int `json:"is_active"`
|
||||||
|
IsRTGS int `json:"is_rtgs"`
|
||||||
|
Active int `json:"active"`
|
||||||
|
Is24Hrs int `json:"is_24hrs"` // nullable
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
BankLogo string `json:"bank_logo"` // URL or base64
|
||||||
|
}
|
||||||
15
internal/domain/issue_reporting.go
Normal file
15
internal/domain/issue_reporting.go
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type ReportedIssue struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
CustomerID int64 `json:"customer_id"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
IssueType string `json:"issue_type"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ type League struct {
|
||||||
Bet365ID int32 `json:"bet365_id" example:"1121"`
|
Bet365ID int32 `json:"bet365_id" example:"1121"`
|
||||||
IsActive bool `json:"is_active" example:"false"`
|
IsActive bool `json:"is_active" example:"false"`
|
||||||
SportID int32 `json:"sport_id" example:"1"`
|
SportID int32 `json:"sport_id" example:"1"`
|
||||||
|
IsFeatured bool `json:"is_featured" example:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateLeague struct {
|
type UpdateLeague struct {
|
||||||
|
|
@ -15,6 +16,7 @@ type UpdateLeague struct {
|
||||||
CountryCode ValidString `json:"cc" example:"uk"`
|
CountryCode ValidString `json:"cc" example:"uk"`
|
||||||
Bet365ID ValidInt32 `json:"bet365_id" example:"1121"`
|
Bet365ID ValidInt32 `json:"bet365_id" example:"1121"`
|
||||||
IsActive ValidBool `json:"is_active" example:"false"`
|
IsActive ValidBool `json:"is_active" example:"false"`
|
||||||
|
IsFeatured ValidBool `json:"is_featured" example:"false"`
|
||||||
SportID ValidInt32 `json:"sport_id" example:"1"`
|
SportID ValidInt32 `json:"sport_id" example:"1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,6 +24,69 @@ type LeagueFilter struct {
|
||||||
CountryCode ValidString
|
CountryCode ValidString
|
||||||
SportID ValidInt32
|
SportID ValidInt32
|
||||||
IsActive ValidBool
|
IsActive ValidBool
|
||||||
|
IsFeatured ValidBool
|
||||||
Limit ValidInt64
|
Limit ValidInt64
|
||||||
Offset ValidInt64
|
Offset ValidInt64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These leagues are automatically featured when the league is created
|
||||||
|
var FeaturedLeagues = []int64{
|
||||||
|
// Football
|
||||||
|
10044469, // Ethiopian Premier League
|
||||||
|
10041282, //Premier League
|
||||||
|
10083364, //La Liga
|
||||||
|
10041095, //German Bundesliga
|
||||||
|
10041100, //Ligue 1
|
||||||
|
10041809, //UEFA Champions League
|
||||||
|
10041957, //UEFA Europa League
|
||||||
|
10079560, //UEFA Conference League
|
||||||
|
10050282, //UEFA Nations League
|
||||||
|
10044685, //FIFA Club World Cup
|
||||||
|
10050346, //UEFA Super Cup
|
||||||
|
10081269, //CONCACAF Champions Cup
|
||||||
|
10070189, //CONCACAF Gold Cup
|
||||||
|
10076185, //UEFA Regions Cup
|
||||||
|
|
||||||
|
10067913, //Europe - World Cup Qualifying
|
||||||
|
10040162, //Asia - World Cup Qualifying
|
||||||
|
10067624, //South America - World Cup Qualifying
|
||||||
|
10073057, //North & Central America - World Cup Qualifying
|
||||||
|
|
||||||
|
10037075, //International Match
|
||||||
|
10077480, //Women’s International
|
||||||
|
10037109, //Europe Friendlies
|
||||||
|
10068837, //Euro U21
|
||||||
|
|
||||||
|
10041315, //Italian Serie A
|
||||||
|
10036538, //Spain Segunda
|
||||||
|
10047168, // US MLS
|
||||||
|
|
||||||
|
10043156, //England FA Cup
|
||||||
|
10042103, //France Cup
|
||||||
|
10041088, //Premier League 2
|
||||||
|
10084250, //Turkiye Super League
|
||||||
|
10041187, //Kenya Super League
|
||||||
|
10041391, //Netherlands Eredivisie
|
||||||
|
|
||||||
|
// Basketball
|
||||||
|
10041830, //NBA
|
||||||
|
10049984, //WNBA
|
||||||
|
10037165, //German Bundesliga
|
||||||
|
10036608, //Italian Lega 1
|
||||||
|
10040795, //EuroLeague
|
||||||
|
10041534, //Basketball Africa League
|
||||||
|
|
||||||
|
// Ice Hockey
|
||||||
|
10037477, //NHL
|
||||||
|
10037447, //AHL
|
||||||
|
10069385, //IIHF World Championship
|
||||||
|
|
||||||
|
// AMERICAN FOOTBALL
|
||||||
|
10037219, //NFL
|
||||||
|
|
||||||
|
// BASEBALL
|
||||||
|
10037485, // MLB
|
||||||
|
|
||||||
|
// VOLLEYBALL
|
||||||
|
10069666, //FIVB Nations League
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,19 +14,22 @@ type NotificationDeliveryStatus string
|
||||||
type DeliveryChannel string
|
type DeliveryChannel string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NotificationTypeCashOutSuccess NotificationType = "cash_out_success"
|
NotificationTypeCashOutSuccess NotificationType = "cash_out_success"
|
||||||
NotificationTypeDepositSuccess NotificationType = "deposit_success"
|
NotificationTypeDepositSuccess NotificationType = "deposit_success"
|
||||||
NotificationTypeBetPlaced NotificationType = "bet_placed"
|
NotificationTypeWithdrawSuccess NotificationType = "withdraw_success"
|
||||||
NotificationTypeDailyReport NotificationType = "daily_report"
|
NotificationTypeBetPlaced NotificationType = "bet_placed"
|
||||||
NotificationTypeHighLossOnBet NotificationType = "high_loss_on_bet"
|
NotificationTypeDailyReport NotificationType = "daily_report"
|
||||||
NotificationTypeBetOverload NotificationType = "bet_overload"
|
NotificationTypeHighLossOnBet NotificationType = "high_loss_on_bet"
|
||||||
NotificationTypeSignUpWelcome NotificationType = "signup_welcome"
|
NotificationTypeBetOverload NotificationType = "bet_overload"
|
||||||
NotificationTypeOTPSent NotificationType = "otp_sent"
|
NotificationTypeSignUpWelcome NotificationType = "signup_welcome"
|
||||||
NOTIFICATION_TYPE_WALLET NotificationType = "wallet_threshold"
|
NotificationTypeOTPSent NotificationType = "otp_sent"
|
||||||
NOTIFICATION_TYPE_TRANSFER NotificationType = "transfer_failed"
|
NOTIFICATION_TYPE_WALLET NotificationType = "wallet_threshold"
|
||||||
NOTIFICATION_TYPE_ADMIN_ALERT NotificationType = "admin_alert"
|
NOTIFICATION_TYPE_TRANSFER_FAIL NotificationType = "transfer_failed"
|
||||||
NOTIFICATION_RECEIVER_ADMIN NotificationRecieverSide = "admin"
|
NOTIFICATION_TYPE_TRANSFER_SUCCESS NotificationType = "transfer_success"
|
||||||
|
NOTIFICATION_TYPE_ADMIN_ALERT NotificationType = "admin_alert"
|
||||||
|
NOTIFICATION_TYPE_BET_RESULT NotificationType = "bet_result"
|
||||||
|
|
||||||
|
NOTIFICATION_RECEIVER_ADMIN NotificationRecieverSide = "admin"
|
||||||
NotificationRecieverSideAdmin NotificationRecieverSide = "admin"
|
NotificationRecieverSideAdmin NotificationRecieverSide = "admin"
|
||||||
NotificationRecieverSideCustomer NotificationRecieverSide = "customer"
|
NotificationRecieverSideCustomer NotificationRecieverSide = "customer"
|
||||||
NotificationRecieverSideCashier NotificationRecieverSide = "cashier"
|
NotificationRecieverSideCashier NotificationRecieverSide = "cashier"
|
||||||
|
|
@ -57,9 +60,9 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type NotificationPayload struct {
|
type NotificationPayload struct {
|
||||||
Headline string `json:"headline"`
|
Headline string `json:"headline"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
|
|
@ -91,3 +94,19 @@ func FromJSON(data []byte) (*Notification, error) {
|
||||||
}
|
}
|
||||||
return &n, nil
|
return &n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReceiverFromRole(role Role) NotificationRecieverSide {
|
||||||
|
|
||||||
|
switch role {
|
||||||
|
case RoleAdmin:
|
||||||
|
return NotificationRecieverSideAdmin
|
||||||
|
case RoleCashier:
|
||||||
|
return NotificationRecieverSideCashier
|
||||||
|
case RoleBranchManager:
|
||||||
|
return NotificationRecieverSideBranchManager
|
||||||
|
case RoleCustomer:
|
||||||
|
return NotificationRecieverSideCustomer
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@ const (
|
||||||
OtpMediumSms OtpMedium = "sms"
|
OtpMediumSms OtpMedium = "sms"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type OtpProvider string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TwilioSms OtpProvider = "twilio"
|
||||||
|
AfroMessage OtpProvider = "aformessage"
|
||||||
|
)
|
||||||
|
|
||||||
type Otp struct {
|
type Otp struct {
|
||||||
ID int64
|
ID int64
|
||||||
SentTo string
|
SentTo string
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,39 @@ const (
|
||||||
Monthly TimeFrame = "monthly"
|
Monthly TimeFrame = "monthly"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ReportFrequency string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReportDaily ReportFrequency = "daily"
|
||||||
|
ReportWeekly ReportFrequency = "weekly"
|
||||||
|
ReportMonthly ReportFrequency = "monthly"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReportRequest struct {
|
||||||
|
Frequency ReportFrequency
|
||||||
|
StartDate time.Time
|
||||||
|
EndDate time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportData struct {
|
||||||
|
TotalBets int64
|
||||||
|
TotalCashIn float64
|
||||||
|
TotalCashOut float64
|
||||||
|
CashBacks float64
|
||||||
|
Withdrawals float64
|
||||||
|
Deposits float64
|
||||||
|
TotalTickets int64
|
||||||
|
VirtualGameStats []VirtualGameStat
|
||||||
|
CompanyReports []CompanyReport
|
||||||
|
BranchReports []BranchReport
|
||||||
|
}
|
||||||
|
|
||||||
|
type VirtualGameStat struct {
|
||||||
|
GameName string
|
||||||
|
NumBets int64
|
||||||
|
TotalTransaction float64
|
||||||
|
}
|
||||||
|
|
||||||
type Report struct {
|
type Report struct {
|
||||||
ID string
|
ID string
|
||||||
TimeFrame TimeFrame
|
TimeFrame TimeFrame
|
||||||
|
|
@ -22,6 +55,22 @@ type Report struct {
|
||||||
GeneratedAt time.Time
|
GeneratedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LiveMetric struct {
|
||||||
|
TotalCashSportsbook float64
|
||||||
|
TotalCashSportGames float64
|
||||||
|
TotalLiveTickets int64
|
||||||
|
TotalUnsettledCash float64
|
||||||
|
TotalGames int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetricUpdates struct {
|
||||||
|
TotalCashSportsbookDelta *float64
|
||||||
|
TotalCashSportGamesDelta *float64
|
||||||
|
TotalLiveTicketsDelta *int64
|
||||||
|
TotalUnsettledCashDelta *float64
|
||||||
|
TotalGamesDelta *int64
|
||||||
|
}
|
||||||
|
|
||||||
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"`
|
||||||
|
|
@ -319,3 +368,41 @@ type CashierPerformance struct {
|
||||||
LastActivity time.Time `json:"last_activity"`
|
LastActivity time.Time `json:"last_activity"`
|
||||||
ActiveDays int `json:"active_days"`
|
ActiveDays int `json:"active_days"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CompanyWalletBalance struct {
|
||||||
|
CompanyID int64 `json:"company_id"`
|
||||||
|
CompanyName string `json:"company_name"`
|
||||||
|
Balance float64 `json:"balance"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BranchWalletBalance struct {
|
||||||
|
BranchID int64 `json:"branch_id"`
|
||||||
|
BranchName string `json:"branch_name"`
|
||||||
|
CompanyID int64 `json:"company_id"`
|
||||||
|
Balance float64 `json:"balance"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LiveWalletMetrics struct {
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
CompanyBalances []CompanyWalletBalance `json:"company_balances"`
|
||||||
|
BranchBalances []BranchWalletBalance `json:"branch_balances"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompanyReport struct {
|
||||||
|
CompanyID int64
|
||||||
|
CompanyName string
|
||||||
|
TotalBets int64
|
||||||
|
TotalCashIn float64
|
||||||
|
TotalCashOut float64
|
||||||
|
TotalCashBacks float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type BranchReport struct {
|
||||||
|
BranchID int64
|
||||||
|
BranchName string
|
||||||
|
CompanyID int64
|
||||||
|
TotalBets int64
|
||||||
|
TotalCashIn float64
|
||||||
|
TotalCashOut float64
|
||||||
|
TotalCashBacks float64
|
||||||
|
}
|
||||||
|
|
|
||||||
22
internal/domain/settings.go
Normal file
22
internal/domain/settings.go
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Setting struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type SettingRes struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SettingList struct {
|
||||||
|
MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"`
|
||||||
|
BetAmountLimit Currency `json:"bet_amount_limit"`
|
||||||
|
DailyTicketPerIP int64 `json:"daily_ticket_limit"`
|
||||||
|
TotalWinningLimit Currency `json:"total_winning_limit"`
|
||||||
|
}
|
||||||
|
|
@ -53,3 +53,31 @@ type CreateTicket struct {
|
||||||
TotalOdds float32
|
TotalOdds float32
|
||||||
IP string
|
IP string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateTicketOutcomeReq struct {
|
||||||
|
// TicketID int64 `json:"ticket_id" example:"1"`
|
||||||
|
EventID int64 `json:"event_id" example:"1"`
|
||||||
|
OddID int64 `json:"odd_id" example:"1"`
|
||||||
|
MarketID int64 `json:"market_id" example:"1"`
|
||||||
|
// HomeTeamName string `json:"home_team_name" example:"Manchester"`
|
||||||
|
// AwayTeamName string `json:"away_team_name" example:"Liverpool"`
|
||||||
|
// MarketName string `json:"market_name" example:"Fulltime Result"`
|
||||||
|
// Odd float32 `json:"odd" example:"1.5"`
|
||||||
|
// OddName string `json:"odd_name" example:"1"`
|
||||||
|
// Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateTicketReq struct {
|
||||||
|
Outcomes []CreateTicketOutcomeReq `json:"outcomes"`
|
||||||
|
Amount float32 `json:"amount" example:"100.0"`
|
||||||
|
}
|
||||||
|
type CreateTicketRes struct {
|
||||||
|
FastCode int64 `json:"fast_code" example:"1234"`
|
||||||
|
CreatedNumber int64 `json:"created_number" example:"3"`
|
||||||
|
}
|
||||||
|
type TicketRes struct {
|
||||||
|
ID int64 `json:"id" example:"1"`
|
||||||
|
Outcomes []TicketOutcome `json:"outcomes"`
|
||||||
|
Amount float32 `json:"amount" example:"100.0"`
|
||||||
|
TotalOdds float32 `json:"total_odds" example:"4.22"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,10 @@ const (
|
||||||
|
|
||||||
type PaymentMethod string
|
type PaymentMethod string
|
||||||
|
|
||||||
|
// Info on why the wallet was modified
|
||||||
|
// If its internal system modification then, its always direct
|
||||||
const (
|
const (
|
||||||
|
TRANSFER_DIRECT PaymentMethod = "direct"
|
||||||
TRANSFER_CASH PaymentMethod = "cash"
|
TRANSFER_CASH PaymentMethod = "cash"
|
||||||
TRANSFER_BANK PaymentMethod = "bank"
|
TRANSFER_BANK PaymentMethod = "bank"
|
||||||
TRANSFER_CHAPA PaymentMethod = "chapa"
|
TRANSFER_CHAPA PaymentMethod = "chapa"
|
||||||
|
|
@ -22,31 +25,36 @@ const (
|
||||||
TRANSFER_OTHER PaymentMethod = "other"
|
TRANSFER_OTHER PaymentMethod = "other"
|
||||||
)
|
)
|
||||||
|
|
||||||
// There is always a receiving wallet id
|
// Info for the payment providers
|
||||||
// There is a sender wallet id only if wallet transfer type
|
type PaymentDetails struct {
|
||||||
|
ReferenceNumber ValidString
|
||||||
|
BankNumber ValidString
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Transfer is logged for every modification of ALL wallets and wallet types
|
||||||
type Transfer struct {
|
type Transfer struct {
|
||||||
ID int64
|
ID int64 `json:"id"`
|
||||||
Amount Currency
|
Amount Currency `json:"amount"`
|
||||||
Verified bool
|
Verified bool `json:"verified"`
|
||||||
Type TransferType
|
Type TransferType `json:"type"`
|
||||||
PaymentMethod PaymentMethod
|
PaymentMethod PaymentMethod `json:"payment_method"`
|
||||||
ReceiverWalletID int64
|
ReceiverWalletID ValidInt64 `json:"receiver_wallet_id"`
|
||||||
SenderWalletID int64
|
SenderWalletID ValidInt64 `json:"sender_wallet_id"`
|
||||||
ReferenceNumber string
|
ReferenceNumber string `json:"reference_number"` // <-- needed
|
||||||
Status string
|
Status string `json:"status"`
|
||||||
CashierID ValidInt64
|
CashierID ValidInt64 `json:"cashier_id"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateTransfer struct {
|
type CreateTransfer struct {
|
||||||
Amount Currency
|
Amount Currency `json:"amount"`
|
||||||
Verified bool
|
Verified bool `json:"verified"`
|
||||||
ReferenceNumber string
|
Type TransferType `json:"type"`
|
||||||
Status string
|
PaymentMethod PaymentMethod `json:"payment_method"`
|
||||||
ReceiverWalletID int64
|
ReceiverWalletID ValidInt64 `json:"receiver_wallet_id"`
|
||||||
SenderWalletID int64
|
SenderWalletID ValidInt64 `json:"sender_wallet_id"`
|
||||||
CashierID ValidInt64
|
ReferenceNumber string `json:"reference_number"` // <-- needed
|
||||||
Type TransferType
|
Status string `json:"status"`
|
||||||
PaymentMethod PaymentMethod
|
CashierID ValidInt64 `json:"cashier_id"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
internal/domain/veli_games.go
Normal file
36
internal/domain/veli_games.go
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Game struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ReleaseDate string `json:"release_date"`
|
||||||
|
Developer string `json:"developer"`
|
||||||
|
Publisher string `json:"publisher"`
|
||||||
|
Genres []string `json:"genres"`
|
||||||
|
Platforms []string `json:"platforms"`
|
||||||
|
Price float64 `json:"price"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GameListResponse struct {
|
||||||
|
Data []Game `json:"data"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
PerPage int `json:"per_page"`
|
||||||
|
TotalPages int `json:"total_pages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GameCreateRequest struct {
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
Description string `json:"description" validate:"required"`
|
||||||
|
ReleaseDate string `json:"release_date" validate:"required"`
|
||||||
|
Developer string `json:"developer" validate:"required"`
|
||||||
|
Publisher string `json:"publisher" validate:"required"`
|
||||||
|
Genres []string `json:"genres" validate:"required"`
|
||||||
|
Platforms []string `json:"platforms" validate:"required"`
|
||||||
|
Price float64 `json:"price" validate:"required"`
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,30 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Provider string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PROVIDER_POPOK Provider = "PopOk"
|
||||||
|
PROVIDER_ALEA_PLAY Provider = "AleaPlay"
|
||||||
|
PROVIDER_VELI_GAMES Provider = "VeliGames"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FavoriteGame struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
GameID int64 `json:"game_id"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FavoriteGameRequest struct {
|
||||||
|
GameID int64 `json:"game_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FavoriteGameResponse struct {
|
||||||
|
GameID int64 `json:"game_id"`
|
||||||
|
GameName string `json:"game_name"`
|
||||||
|
}
|
||||||
|
|
||||||
type VirtualGame struct {
|
type VirtualGame struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
@ -38,10 +62,31 @@ type VirtualGameSession struct {
|
||||||
GameMode string `json:"game_mode"` // real, demo, tournament
|
GameMode string `json:"game_mode"` // real, demo, tournament
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VirtualGameHistory struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
SessionID string `json:"session_id,omitempty"` // Optional, if session tracking is used
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
CompanyID int64 `json:"company_id"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
WalletID *int64 `json:"wallet_id,omitempty"` // Optional if wallet detail is needed
|
||||||
|
GameID *int64 `json:"game_id,omitempty"` // Optional for game-level analysis
|
||||||
|
TransactionType string `json:"transaction_type"` // BET, WIN, CANCEL, etc.
|
||||||
|
Amount int64 `json:"amount"` // Stored in minor units (e.g. cents)
|
||||||
|
Currency string `json:"currency"` // e.g., ETB, USD
|
||||||
|
ExternalTransactionID string `json:"external_transaction_id"` // Provider transaction ID
|
||||||
|
ReferenceTransactionID string `json:"reference_transaction_id,omitempty"` // For CANCELs pointing to BETs
|
||||||
|
Status string `json:"status"` // COMPLETED, CANCELLED, FAILED, etc.
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type VirtualGameTransaction struct {
|
type VirtualGameTransaction struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
SessionID int64 `json:"session_id"`
|
SessionID int64 `json:"session_id"`
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
|
CompanyID int64 `json:"company_id"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
GameID string `json:"game_id"`
|
||||||
WalletID int64 `json:"wallet_id"`
|
WalletID int64 `json:"wallet_id"`
|
||||||
TransactionType string `json:"transaction_type"` // BET, WIN, REFUND, CASHOUT, etc.
|
TransactionType string `json:"transaction_type"` // BET, WIN, REFUND, CASHOUT, etc.
|
||||||
Amount int64 `json:"amount"` // Always in cents
|
Amount int64 `json:"amount"` // Always in cents
|
||||||
|
|
@ -143,6 +188,11 @@ type PopOKWinResponse struct {
|
||||||
Balance float64 `json:"balance"`
|
Balance float64 `json:"balance"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PopOKGenerateTokenRequest struct {
|
||||||
|
GameID string `json:"newGameId"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
type PopOKCancelRequest struct {
|
type PopOKCancelRequest struct {
|
||||||
ExternalToken string `json:"externalToken"`
|
ExternalToken string `json:"externalToken"`
|
||||||
PlayerID string `json:"playerId"`
|
PlayerID string `json:"playerId"`
|
||||||
|
|
@ -156,6 +206,10 @@ type PopOKCancelResponse struct {
|
||||||
Balance float64 `json:"balance"`
|
Balance float64 `json:"balance"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PopOKGenerateTokenResponse struct {
|
||||||
|
NewToken string `json:"newToken"`
|
||||||
|
}
|
||||||
|
|
||||||
type AleaPlayCallback struct {
|
type AleaPlayCallback struct {
|
||||||
EventID string `json:"event_id"`
|
EventID string `json:"event_id"`
|
||||||
TransactionID string `json:"transaction_id"`
|
TransactionID string `json:"transaction_id"`
|
||||||
|
|
@ -191,3 +245,27 @@ type GameSpecificData struct {
|
||||||
RiskLevel string `json:"risk_level,omitempty"` // For Mines
|
RiskLevel string `json:"risk_level,omitempty"` // For Mines
|
||||||
BucketIndex int `json:"bucket_index,omitempty"` // For Plinko
|
BucketIndex int `json:"bucket_index,omitempty"` // For Plinko
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PopOKGame struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
GameName string `json:"gameName"`
|
||||||
|
Bets []float64 `json:"bets"`
|
||||||
|
Thumbnail string `json:"thumbnail"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PopOKGameListResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data struct {
|
||||||
|
Slots []PopOKGame `json:"slots"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GameRecommendation struct {
|
||||||
|
GameID int `json:"game_id"`
|
||||||
|
GameName string `json:"game_name"`
|
||||||
|
Thumbnail string `json:"thumbnail"`
|
||||||
|
Bets []float64 `json:"bets"`
|
||||||
|
Reason string `json:"reason"` // e.g., "Based on your activity", "Popular", "Random pick"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,13 @@ type Wallet struct {
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WalletFilter struct {
|
||||||
|
IsActive ValidBool
|
||||||
|
Query ValidString
|
||||||
|
CreatedBefore ValidTime
|
||||||
|
CreatedAfter ValidTime
|
||||||
|
}
|
||||||
|
|
||||||
type CustomerWallet struct {
|
type CustomerWallet struct {
|
||||||
ID int64
|
ID int64
|
||||||
RegularID int64
|
RegularID int64
|
||||||
|
|
@ -28,9 +35,14 @@ type GetCustomerWallet struct {
|
||||||
StaticID int64
|
StaticID int64
|
||||||
StaticBalance Currency
|
StaticBalance Currency
|
||||||
CustomerID int64
|
CustomerID int64
|
||||||
|
RegularIsActive bool
|
||||||
|
StaticIsActive bool
|
||||||
RegularUpdatedAt time.Time
|
RegularUpdatedAt time.Time
|
||||||
StaticUpdatedAt time.Time
|
StaticUpdatedAt time.Time
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
PhoneNumber string
|
||||||
}
|
}
|
||||||
|
|
||||||
type BranchWallet struct {
|
type BranchWallet struct {
|
||||||
|
|
@ -58,3 +70,11 @@ type CreateCustomerWallet struct {
|
||||||
RegularWalletID int64
|
RegularWalletID int64
|
||||||
StaticWalletID int64
|
StaticWalletID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WalletType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CustomerWalletType WalletType = "customer_wallet"
|
||||||
|
BranchWalletType WalletType = "branch_wallet"
|
||||||
|
CompanyWalletType WalletType = "company_wallet"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,18 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitLogger() (*zap.Logger, error) {
|
func InitLogger(cfg *config.Config) (*zap.Logger, error) {
|
||||||
mongoCore, err := NewMongoCore(
|
mongoCore, err := NewMongoCore(
|
||||||
"mongodb://root:secret@localhost:27017/?authSource=admin",
|
os.Getenv("MONGODB_URL"),
|
||||||
"logdb",
|
"logdb",
|
||||||
"applogs",
|
"applogs",
|
||||||
zapcore.InfoLevel,
|
zapcore.InfoLevel,
|
||||||
|
cfg,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create MongoDB core: %w", err)
|
return nil, fmt.Errorf("failed to create MongoDB core: %w", err)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"maps"
|
"maps"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
|
@ -17,9 +18,10 @@ type MongoCore struct {
|
||||||
collection *mongo.Collection
|
collection *mongo.Collection
|
||||||
level zapcore.Level
|
level zapcore.Level
|
||||||
fields []zapcore.Field
|
fields []zapcore.Field
|
||||||
|
cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMongoCore(uri, dbName, collectionName string, level zapcore.Level) (zapcore.Core, error) {
|
func NewMongoCore(uri, dbName, collectionName string, level zapcore.Level, cfg *config.Config) (zapcore.Core, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|
@ -36,6 +38,7 @@ func NewMongoCore(uri, dbName, collectionName string, level zapcore.Level) (zapc
|
||||||
return &MongoCore{
|
return &MongoCore{
|
||||||
collection: coll,
|
collection: coll,
|
||||||
level: level,
|
level: level,
|
||||||
|
cfg: cfg,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,8 +76,8 @@ func (mc *MongoCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
||||||
"fields": logMap,
|
"fields": logMap,
|
||||||
"caller": entry.Caller.String(),
|
"caller": entry.Caller.String(),
|
||||||
"stacktrace": entry.Stack,
|
"stacktrace": entry.Stack,
|
||||||
"service": "fortunebet-backend",
|
"service": mc.cfg.Service,
|
||||||
"env": "dev",
|
"env": mc.cfg.Env,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,22 @@ func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]doma
|
||||||
Int64: filter.UserID.Value,
|
Int64: filter.UserID.Value,
|
||||||
Valid: filter.UserID.Valid,
|
Valid: filter.UserID.Valid,
|
||||||
},
|
},
|
||||||
|
IsShopBet: pgtype.Bool{
|
||||||
|
Bool: filter.IsShopBet.Value,
|
||||||
|
Valid: filter.IsShopBet.Valid,
|
||||||
|
},
|
||||||
|
Query: pgtype.Text{
|
||||||
|
String: filter.Query.Value,
|
||||||
|
Valid: filter.Query.Valid,
|
||||||
|
},
|
||||||
|
CreatedBefore: pgtype.Timestamp{
|
||||||
|
Time: filter.CreatedBefore.Value,
|
||||||
|
Valid: filter.CreatedBefore.Valid,
|
||||||
|
},
|
||||||
|
CreatedAfter: pgtype.Timestamp{
|
||||||
|
Time: filter.CreatedAfter.Value,
|
||||||
|
Valid: filter.CreatedAfter.Valid,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
domain.MongoDBLogger.Error("failed to get all bets",
|
domain.MongoDBLogger.Error("failed to get all bets",
|
||||||
|
|
@ -265,6 +281,19 @@ func (s *Store) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetB
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetBetCount(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
|
||||||
|
count, err := s.queries.GetBetCount(ctx, dbgen.GetBetCountParams{
|
||||||
|
UserID: pgtype.Int8{Int64: UserID},
|
||||||
|
OutcomesHash: outcomesHash,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error {
|
func (s *Store) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error {
|
||||||
err := s.queries.UpdateCashOut(ctx, dbgen.UpdateCashOutParams{
|
err := s.queries.UpdateCashOut(ctx, dbgen.UpdateCashOutParams{
|
||||||
ID: id,
|
ID: id,
|
||||||
|
|
@ -295,8 +324,19 @@ func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.Outcom
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error) {
|
func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) {
|
||||||
outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, eventID)
|
|
||||||
|
outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, dbgen.GetBetOutcomeByEventIDParams{
|
||||||
|
EventID: eventID,
|
||||||
|
FilterStatus: pgtype.Int4{
|
||||||
|
Int32: int32(domain.OUTCOME_STATUS_PENDING),
|
||||||
|
Valid: is_filtered,
|
||||||
|
},
|
||||||
|
FilterStatus2: pgtype.Int4{
|
||||||
|
Int32: int32(domain.OUTCOME_STATUS_ERROR),
|
||||||
|
Valid: is_filtered,
|
||||||
|
},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
domain.MongoDBLogger.Error("failed to get bet outcomes by event ID",
|
domain.MongoDBLogger.Error("failed to get bet outcomes by event ID",
|
||||||
zap.Int64("event_id", eventID),
|
zap.Int64("event_id", eventID),
|
||||||
|
|
@ -347,8 +387,44 @@ func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status dom
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) DeleteBet(ctx context.Context, id int64) error {
|
func (s *Store) UpdateBetOutcomeStatusByBetID(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) {
|
||||||
return s.queries.DeleteBet(ctx, id)
|
update, err := s.queries.UpdateBetOutcomeStatusByBetID(ctx, dbgen.UpdateBetOutcomeStatusByBetIDParams{
|
||||||
|
Status: int32(status),
|
||||||
|
BetID: id,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
domain.MongoDBLogger.Error("failed to update bet outcome status",
|
||||||
|
zap.Int64("id", id),
|
||||||
|
zap.Int32("status", int32(status)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return domain.BetOutcome{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := convertDBBetOutcomes(update)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error) {
|
||||||
|
outcomes, err := s.queries.UpdateBetOutcomeStatusForEvent(ctx, dbgen.UpdateBetOutcomeStatusForEventParams{
|
||||||
|
EventID: eventID,
|
||||||
|
Status: int32(status),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
domain.MongoDBLogger.Error("failed to update bet outcome status for event",
|
||||||
|
zap.Int64("eventID", eventID),
|
||||||
|
zap.Int32("status", int32(status)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes))
|
||||||
|
for _, outcome := range outcomes {
|
||||||
|
result = append(result, convertDBBetOutcomes(outcome))
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBetSummary returns aggregated bet statistics
|
// GetBetSummary returns aggregated bet statistics
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ func convertDBBranchDetail(dbBranch dbgen.BranchDetail) domain.BranchDetail {
|
||||||
ManagerName: dbBranch.ManagerName.(string),
|
ManagerName: dbBranch.ManagerName.(string),
|
||||||
ManagerPhoneNumber: dbBranch.ManagerPhoneNumber.String,
|
ManagerPhoneNumber: dbBranch.ManagerPhoneNumber.String,
|
||||||
Balance: domain.Currency(dbBranch.Balance.Int64),
|
Balance: domain.Currency(dbBranch.Balance.Int64),
|
||||||
|
IsActive: dbBranch.IsActive,
|
||||||
|
WalletIsActive: dbBranch.WalletIsActive.Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,6 +85,12 @@ func convertUpdateBranch(updateBranch domain.UpdateBranch) dbgen.UpdateBranchPar
|
||||||
Valid: true,
|
Valid: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if updateBranch.IsActive != nil {
|
||||||
|
newUpdateBranch.IsActive = pgtype.Bool{
|
||||||
|
Bool: *updateBranch.IsActive,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return newUpdateBranch
|
return newUpdateBranch
|
||||||
}
|
}
|
||||||
|
|
@ -128,8 +136,29 @@ func (s *Store) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]do
|
||||||
return branches, nil
|
return branches, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) GetAllBranches(ctx context.Context) ([]domain.BranchDetail, error) {
|
func (s *Store) GetAllBranches(ctx context.Context, filter domain.BranchFilter) ([]domain.BranchDetail, error) {
|
||||||
dbBranches, err := s.queries.GetAllBranches(ctx)
|
dbBranches, err := s.queries.GetAllBranches(ctx, dbgen.GetAllBranchesParams{
|
||||||
|
CompanyID: pgtype.Int8{
|
||||||
|
Int64: filter.CompanyID.Value,
|
||||||
|
Valid: filter.CompanyID.Valid,
|
||||||
|
},
|
||||||
|
BranchManagerID: pgtype.Int8{
|
||||||
|
Int64: filter.BranchManagerID.Value,
|
||||||
|
Valid: filter.BranchManagerID.Valid,
|
||||||
|
},
|
||||||
|
Query: pgtype.Text{
|
||||||
|
String: filter.Query.Value,
|
||||||
|
Valid: filter.Query.Valid,
|
||||||
|
},
|
||||||
|
CreatedBefore: pgtype.Timestamp{
|
||||||
|
Time: filter.CreatedBefore.Value,
|
||||||
|
Valid: filter.CreatedBefore.Valid,
|
||||||
|
},
|
||||||
|
CreatedAfter: pgtype.Timestamp{
|
||||||
|
Time: filter.CreatedAfter.Value,
|
||||||
|
Valid: filter.CreatedAfter.Valid,
|
||||||
|
},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
139
internal/repository/institutions.go
Normal file
139
internal/repository/institutions.go
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BankRepository interface {
|
||||||
|
CreateBank(ctx context.Context, bank *domain.Bank) error
|
||||||
|
GetBankByID(ctx context.Context, id int) (*domain.Bank, error)
|
||||||
|
GetAllBanks(ctx context.Context, countryID *int, isActive *int) ([]domain.Bank, error)
|
||||||
|
UpdateBank(ctx context.Context, bank *domain.Bank) error
|
||||||
|
DeleteBank(ctx context.Context, id int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type BankRepo struct {
|
||||||
|
store *Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBankRepository(store *Store) BankRepository {
|
||||||
|
return &BankRepo{store: store}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *BankRepo) CreateBank(ctx context.Context, bank *domain.Bank) error {
|
||||||
|
params := dbgen.CreateBankParams{
|
||||||
|
Slug: bank.Slug,
|
||||||
|
Swift: bank.Swift,
|
||||||
|
Name: bank.Name,
|
||||||
|
AcctLength: int32(bank.AcctLength),
|
||||||
|
CountryID: int32(bank.CountryID),
|
||||||
|
IsMobilemoney: pgtype.Int4{Int32: int32(bank.IsMobileMoney), Valid: true},
|
||||||
|
IsActive: int32(bank.IsActive),
|
||||||
|
IsRtgs: int32(bank.IsRTGS),
|
||||||
|
Active: int32(bank.Active),
|
||||||
|
Is24hrs: pgtype.Int4{Int32: int32(bank.Is24Hrs), Valid: true},
|
||||||
|
Currency: bank.Currency,
|
||||||
|
BankLogo: pgtype.Text{String: bank.BankLogo, Valid: true},
|
||||||
|
}
|
||||||
|
createdBank, err := r.store.queries.CreateBank(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Update the ID and timestamps on the passed struct
|
||||||
|
bank.ID = int(createdBank.ID)
|
||||||
|
bank.CreatedAt = createdBank.CreatedAt.Time
|
||||||
|
bank.UpdatedAt = createdBank.UpdatedAt.Time
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *BankRepo) GetBankByID(ctx context.Context, id int) (*domain.Bank, error) {
|
||||||
|
dbBank, err := r.store.queries.GetBankByID(ctx, int64(id))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mapDBBankToDomain(&dbBank), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *BankRepo) GetAllBanks(ctx context.Context, countryID *int, isActive *int) ([]domain.Bank, error) {
|
||||||
|
params := dbgen.GetAllBanksParams{
|
||||||
|
CountryID: pgtype.Int4{},
|
||||||
|
IsActive: pgtype.Int4{},
|
||||||
|
}
|
||||||
|
if countryID != nil {
|
||||||
|
params.CountryID = pgtype.Int4{Int32: int32(*countryID), Valid: true}
|
||||||
|
}
|
||||||
|
if isActive != nil {
|
||||||
|
params.IsActive = pgtype.Int4{Int32: int32(*isActive), Valid: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbBanks, err := r.store.queries.GetAllBanks(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
banks := make([]domain.Bank, len(dbBanks))
|
||||||
|
for i, b := range dbBanks {
|
||||||
|
banks[i] = *mapDBBankToDomain(&b)
|
||||||
|
}
|
||||||
|
return banks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *BankRepo) UpdateBank(ctx context.Context, bank *domain.Bank) error {
|
||||||
|
params := dbgen.UpdateBankParams{
|
||||||
|
ID: int64(bank.ID),
|
||||||
|
Slug: pgtype.Text{String: bank.Slug, Valid: true},
|
||||||
|
Swift: pgtype.Text{String: bank.Swift, Valid: true},
|
||||||
|
Name: pgtype.Text{String: bank.Name, Valid: true},
|
||||||
|
AcctLength: pgtype.Int4{Int32: int32(bank.AcctLength), Valid: true},
|
||||||
|
CountryID: pgtype.Int4{Int32: int32(bank.CountryID), Valid: true},
|
||||||
|
IsMobilemoney: pgtype.Int4{Int32: int32(bank.IsMobileMoney), Valid: true},
|
||||||
|
IsActive: pgtype.Int4{Int32: int32(bank.IsActive), Valid: true},
|
||||||
|
IsRtgs: pgtype.Int4{Int32: int32(bank.IsRTGS), Valid: true},
|
||||||
|
Active: pgtype.Int4{Int32: int32(bank.Active), Valid: true},
|
||||||
|
Is24hrs: pgtype.Int4{Int32: int32(bank.Is24Hrs), Valid: true},
|
||||||
|
Currency: pgtype.Text{String: bank.Currency, Valid: true},
|
||||||
|
BankLogo: pgtype.Text{String: bank.BankLogo, Valid: true},
|
||||||
|
}
|
||||||
|
updatedBank, err := r.store.queries.UpdateBank(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update timestamps in domain struct
|
||||||
|
bank.UpdatedAt = updatedBank.UpdatedAt.Time
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *BankRepo) DeleteBank(ctx context.Context, id int) error {
|
||||||
|
return r.store.queries.DeleteBank(ctx, int64(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to map DB struct to domain
|
||||||
|
func mapDBBankToDomain(dbBank *dbgen.Bank) *domain.Bank {
|
||||||
|
return &domain.Bank{
|
||||||
|
ID: int(dbBank.ID),
|
||||||
|
Slug: dbBank.Slug,
|
||||||
|
Swift: dbBank.Swift,
|
||||||
|
Name: dbBank.Name,
|
||||||
|
AcctLength: int(dbBank.AcctLength),
|
||||||
|
CountryID: int(dbBank.CountryID),
|
||||||
|
IsMobileMoney: int(dbBank.IsMobilemoney.Int32),
|
||||||
|
IsActive: int(dbBank.IsActive),
|
||||||
|
IsRTGS: int(dbBank.IsRtgs),
|
||||||
|
Active: int(dbBank.Active),
|
||||||
|
Is24Hrs: int(dbBank.Is24hrs.Int32),
|
||||||
|
CreatedAt: dbBank.CreatedAt.Time,
|
||||||
|
UpdatedAt: dbBank.UpdatedAt.Time,
|
||||||
|
Currency: dbBank.Currency,
|
||||||
|
BankLogo: dbBank.BankLogo.String,
|
||||||
|
}
|
||||||
|
}
|
||||||
65
internal/repository/issue_reporting.go
Normal file
65
internal/repository/issue_reporting.go
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReportedIssueRepository interface {
|
||||||
|
CreateReportedIssue(ctx context.Context, arg dbgen.CreateReportedIssueParams) (dbgen.ReportedIssue, error)
|
||||||
|
ListReportedIssues(ctx context.Context, limit, offset int32) ([]dbgen.ReportedIssue, error)
|
||||||
|
ListReportedIssuesByCustomer(ctx context.Context, customerID int64, limit, offset int32) ([]dbgen.ReportedIssue, error)
|
||||||
|
CountReportedIssues(ctx context.Context) (int64, error)
|
||||||
|
CountReportedIssuesByCustomer(ctx context.Context, customerID int64) (int64, error)
|
||||||
|
UpdateReportedIssueStatus(ctx context.Context, id int64, status string) error
|
||||||
|
DeleteReportedIssue(ctx context.Context, id int64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportedIssueRepo struct {
|
||||||
|
store *Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReportedIssueRepository(store *Store) ReportedIssueRepository {
|
||||||
|
return &ReportedIssueRepo{store: store}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReportedIssueRepo) CreateReportedIssue(ctx context.Context, arg dbgen.CreateReportedIssueParams) (dbgen.ReportedIssue, error) {
|
||||||
|
return s.store.queries.CreateReportedIssue(ctx, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReportedIssueRepo) ListReportedIssues(ctx context.Context, limit, offset int32) ([]dbgen.ReportedIssue, error) {
|
||||||
|
params := dbgen.ListReportedIssuesParams{
|
||||||
|
Limit: limit,
|
||||||
|
Offset: offset,
|
||||||
|
}
|
||||||
|
return s.store.queries.ListReportedIssues(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReportedIssueRepo) ListReportedIssuesByCustomer(ctx context.Context, customerID int64, limit, offset int32) ([]dbgen.ReportedIssue, error) {
|
||||||
|
params := dbgen.ListReportedIssuesByCustomerParams{
|
||||||
|
CustomerID: customerID,
|
||||||
|
Limit: limit,
|
||||||
|
Offset: offset,
|
||||||
|
}
|
||||||
|
return s.store.queries.ListReportedIssuesByCustomer(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReportedIssueRepo) CountReportedIssues(ctx context.Context) (int64, error) {
|
||||||
|
return s.store.queries.CountReportedIssues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReportedIssueRepo) CountReportedIssuesByCustomer(ctx context.Context, customerID int64) (int64, error) {
|
||||||
|
return s.store.queries.CountReportedIssuesByCustomer(ctx, customerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReportedIssueRepo) UpdateReportedIssueStatus(ctx context.Context, id int64, status string) error {
|
||||||
|
return s.store.queries.UpdateReportedIssueStatus(ctx, dbgen.UpdateReportedIssueStatusParams{
|
||||||
|
ID: id,
|
||||||
|
Status: status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReportedIssueRepo) DeleteReportedIssue(ctx context.Context, id int64) error {
|
||||||
|
return s.store.queries.DeleteReportedIssue(ctx, id)
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ func (s *Store) SaveLeague(ctx context.Context, l domain.League) error {
|
||||||
CountryCode: pgtype.Text{String: l.CountryCode, Valid: true},
|
CountryCode: pgtype.Text{String: l.CountryCode, Valid: true},
|
||||||
Bet365ID: pgtype.Int4{Int32: l.Bet365ID, Valid: true},
|
Bet365ID: pgtype.Int4{Int32: l.Bet365ID, Valid: true},
|
||||||
IsActive: pgtype.Bool{Bool: l.IsActive, Valid: true},
|
IsActive: pgtype.Bool{Bool: l.IsActive, Valid: true},
|
||||||
|
IsFeatured: pgtype.Bool{Bool: l.IsFeatured, Valid: true},
|
||||||
SportID: l.SportID,
|
SportID: l.SportID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -33,6 +34,10 @@ func (s *Store) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) (
|
||||||
Bool: filter.IsActive.Value,
|
Bool: filter.IsActive.Value,
|
||||||
Valid: filter.IsActive.Valid,
|
Valid: filter.IsActive.Valid,
|
||||||
},
|
},
|
||||||
|
IsFeatured: pgtype.Bool{
|
||||||
|
Bool: filter.IsFeatured.Value,
|
||||||
|
Valid: filter.IsFeatured.Valid,
|
||||||
|
},
|
||||||
Limit: pgtype.Int4{
|
Limit: pgtype.Int4{
|
||||||
Int32: int32(filter.Limit.Value),
|
Int32: int32(filter.Limit.Value),
|
||||||
Valid: filter.Limit.Valid,
|
Valid: filter.Limit.Valid,
|
||||||
|
|
@ -54,12 +59,35 @@ func (s *Store) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) (
|
||||||
CountryCode: league.CountryCode.String,
|
CountryCode: league.CountryCode.String,
|
||||||
Bet365ID: league.Bet365ID.Int32,
|
Bet365ID: league.Bet365ID.Int32,
|
||||||
IsActive: league.IsActive.Bool,
|
IsActive: league.IsActive.Bool,
|
||||||
|
IsFeatured: league.IsFeatured.Bool,
|
||||||
SportID: league.SportID,
|
SportID: league.SportID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return leagues, nil
|
return leagues, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetFeaturedLeagues(ctx context.Context) ([]domain.League, error) {
|
||||||
|
l, err := s.queries.GetFeaturedLeagues(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
leagues := make([]domain.League, len(l))
|
||||||
|
for i, league := range l {
|
||||||
|
leagues[i] = domain.League{
|
||||||
|
ID: league.ID,
|
||||||
|
Name: league.Name,
|
||||||
|
CountryCode: league.CountryCode.String,
|
||||||
|
Bet365ID: league.Bet365ID.Int32,
|
||||||
|
IsActive: league.IsActive.Bool,
|
||||||
|
|
||||||
|
SportID: league.SportID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return leagues, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) CheckLeagueSupport(ctx context.Context, leagueID int64) (bool, error) {
|
func (s *Store) CheckLeagueSupport(ctx context.Context, leagueID int64) (bool, error) {
|
||||||
return s.queries.CheckLeagueSupport(ctx, leagueID)
|
return s.queries.CheckLeagueSupport(ctx, leagueID)
|
||||||
}
|
}
|
||||||
|
|
@ -93,6 +121,10 @@ func (s *Store) UpdateLeague(ctx context.Context, league domain.UpdateLeague) er
|
||||||
Bool: league.IsActive.Value,
|
Bool: league.IsActive.Value,
|
||||||
Valid: league.IsActive.Valid,
|
Valid: league.IsActive.Valid,
|
||||||
},
|
},
|
||||||
|
IsFeatured: pgtype.Bool{
|
||||||
|
Bool: league.IsFeatured.Value,
|
||||||
|
Valid: league.IsActive.Valid,
|
||||||
|
},
|
||||||
SportID: pgtype.Int4{
|
SportID: pgtype.Int4{
|
||||||
Int32: league.SportID.Value,
|
Int32: league.SportID.Value,
|
||||||
Valid: league.SportID.Valid,
|
Valid: league.SportID.Valid,
|
||||||
|
|
|
||||||
|
|
@ -317,6 +317,40 @@ func (s *Store) CountUnreadNotifications(ctx context.Context, userID int64) (int
|
||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error) {
|
||||||
|
dbCompany, err := s.queries.GetCompanyByWalletID(ctx, walletID)
|
||||||
|
if err != nil {
|
||||||
|
return domain.Company{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.Company{
|
||||||
|
ID: dbCompany.ID,
|
||||||
|
Name: dbCompany.Name,
|
||||||
|
AdminID: dbCompany.AdminID,
|
||||||
|
WalletID: dbCompany.WalletID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error) {
|
||||||
|
dbBranch, err := s.queries.GetBranchByWalletID(ctx, walletID)
|
||||||
|
if err != nil {
|
||||||
|
return domain.Branch{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.Branch{
|
||||||
|
ID: dbBranch.ID,
|
||||||
|
Name: dbBranch.Name,
|
||||||
|
Location: dbBranch.Location,
|
||||||
|
IsActive: dbBranch.IsActive,
|
||||||
|
WalletID: dbBranch.WalletID,
|
||||||
|
BranchManagerID: dbBranch.BranchManagerID,
|
||||||
|
CompanyID: dbBranch.CompanyID,
|
||||||
|
IsSelfOwned: dbBranch.IsSelfOwned,
|
||||||
|
// Creat: dbBranch.CreatedAt.Time,
|
||||||
|
// UpdatedAt: dbBranch.UpdatedAt.Time,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// func (s *Store) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) {
|
// func (s *Store) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) {
|
||||||
// dbNotifications, err := s.queries.GetAllNotifications(ctx, dbgen.GetAllNotificationsParams{
|
// dbNotifications, err := s.queries.GetAllNotifications(ctx, dbgen.GetAllNotificationsParams{
|
||||||
// Limit: int32(limit),
|
// Limit: int32(limit),
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,28 @@ package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReportRepository interface {
|
type ReportRepository interface {
|
||||||
GenerateReport(timeFrame domain.TimeFrame, start, end time.Time) (*domain.Report, error)
|
GenerateReport(timeFrame domain.TimeFrame, start, end time.Time) (*domain.Report, error)
|
||||||
SaveReport(report *domain.Report) error
|
SaveReport(report *domain.Report) error
|
||||||
FindReportsByTimeFrame(timeFrame domain.TimeFrame, limit int) ([]*domain.Report, error)
|
FindReportsByTimeFrame(timeFrame domain.TimeFrame, limit int) ([]*domain.Report, error)
|
||||||
|
|
||||||
|
GetTotalCashOutInRange(ctx context.Context, from, to time.Time) (float64, error)
|
||||||
|
GetTotalCashMadeInRange(ctx context.Context, from, to time.Time) (float64, error)
|
||||||
|
GetTotalCashBacksInRange(ctx context.Context, from, to time.Time) (float64, error)
|
||||||
|
GetTotalBetsMadeInRange(ctx context.Context, from, to time.Time) (int64, error)
|
||||||
|
GetVirtualGameSummaryInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetVirtualGameSummaryInRangeRow, error)
|
||||||
|
GetAllTicketsInRange(ctx context.Context, from, to time.Time) (dbgen.GetAllTicketsInRangeRow, error)
|
||||||
|
GetWalletTransactionsInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetWalletTransactionsInRangeRow, error)
|
||||||
|
GetCompanyWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetCompanyWiseReportRow, error)
|
||||||
|
GetBranchWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetBranchWiseReportRow, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReportRepo struct {
|
type ReportRepo struct {
|
||||||
|
|
@ -105,3 +118,117 @@ func (r *ReportRepo) FindReportsByTimeFrame(timeFrame domain.TimeFrame, limit in
|
||||||
|
|
||||||
return reports, nil
|
return reports, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ReportRepo) GetTotalBetsMadeInRange(ctx context.Context, from, to time.Time) (int64, error) {
|
||||||
|
params := dbgen.GetTotalBetsMadeInRangeParams{
|
||||||
|
From: ToPgTimestamp(from),
|
||||||
|
To: ToPgTimestamp(to),
|
||||||
|
}
|
||||||
|
return r.store.queries.GetTotalBetsMadeInRange(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReportRepo) GetTotalCashBacksInRange(ctx context.Context, from, to time.Time) (float64, error) {
|
||||||
|
params := dbgen.GetTotalCashBacksInRangeParams{
|
||||||
|
From: ToPgTimestamp(from),
|
||||||
|
To: ToPgTimestamp(to),
|
||||||
|
}
|
||||||
|
value, err := r.store.queries.GetTotalCashBacksInRange(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return parseFloat(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReportRepo) GetTotalCashMadeInRange(ctx context.Context, from, to time.Time) (float64, error) {
|
||||||
|
params := dbgen.GetTotalCashMadeInRangeParams{
|
||||||
|
From: ToPgTimestamp(from),
|
||||||
|
To: ToPgTimestamp(to),
|
||||||
|
}
|
||||||
|
value, err := r.store.queries.GetTotalCashMadeInRange(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return parseFloat(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReportRepo) GetTotalCashOutInRange(ctx context.Context, from, to time.Time) (float64, error) {
|
||||||
|
params := dbgen.GetTotalCashOutInRangeParams{
|
||||||
|
From: ToPgTimestamp(from),
|
||||||
|
To: ToPgTimestamp(to),
|
||||||
|
}
|
||||||
|
value, err := r.store.queries.GetTotalCashOutInRange(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return parseFloat(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReportRepo) GetWalletTransactionsInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetWalletTransactionsInRangeRow, error) {
|
||||||
|
params := dbgen.GetWalletTransactionsInRangeParams{
|
||||||
|
CreatedAt: ToPgTimestamp(from),
|
||||||
|
CreatedAt_2: ToPgTimestamp(to),
|
||||||
|
}
|
||||||
|
return r.store.queries.GetWalletTransactionsInRange(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReportRepo) GetAllTicketsInRange(ctx context.Context, from, to time.Time) (dbgen.GetAllTicketsInRangeRow, error) {
|
||||||
|
params := dbgen.GetAllTicketsInRangeParams{
|
||||||
|
CreatedAt: ToPgTimestamp(from),
|
||||||
|
CreatedAt_2: ToPgTimestamp(to),
|
||||||
|
}
|
||||||
|
return r.store.queries.GetAllTicketsInRange(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReportRepo) GetVirtualGameSummaryInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetVirtualGameSummaryInRangeRow, error) {
|
||||||
|
params := dbgen.GetVirtualGameSummaryInRangeParams{
|
||||||
|
CreatedAt: ToPgTimestamptz(from),
|
||||||
|
CreatedAt_2: ToPgTimestamptz(to),
|
||||||
|
}
|
||||||
|
return r.store.queries.GetVirtualGameSummaryInRange(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToPgTimestamp(t time.Time) pgtype.Timestamp {
|
||||||
|
return pgtype.Timestamp{Time: t, Valid: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToPgTimestamptz(t time.Time) pgtype.Timestamptz {
|
||||||
|
return pgtype.Timestamptz{Time: t, Valid: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFloat(value interface{}) (float64, error) {
|
||||||
|
switch v := value.(type) {
|
||||||
|
case float64:
|
||||||
|
return v, nil
|
||||||
|
case int64:
|
||||||
|
return float64(v), nil
|
||||||
|
case pgtype.Numeric:
|
||||||
|
if !v.Valid {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
f, err := v.Float64Value()
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to convert pgtype.Numeric to float64: %w", err)
|
||||||
|
}
|
||||||
|
return f.Float64, nil
|
||||||
|
case nil:
|
||||||
|
return 0, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unexpected type %T for value: %+v", v, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReportRepo) GetCompanyWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetCompanyWiseReportRow, error) {
|
||||||
|
params := dbgen.GetCompanyWiseReportParams{
|
||||||
|
From: ToPgTimestamp(from),
|
||||||
|
To: ToPgTimestamp(to),
|
||||||
|
}
|
||||||
|
return r.store.queries.GetCompanyWiseReport(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReportRepo) GetBranchWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetBranchWiseReportRow, error) {
|
||||||
|
params := dbgen.GetBranchWiseReportParams{
|
||||||
|
From: ToPgTimestamp(from),
|
||||||
|
To: ToPgTimestamp(to),
|
||||||
|
}
|
||||||
|
return r.store.queries.GetBranchWiseReport(ctx, params)
|
||||||
|
}
|
||||||
|
|
|
||||||
127
internal/repository/settings.go
Normal file
127
internal/repository/settings.go
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBSettingList struct {
|
||||||
|
MaxNumberOfOutcomes domain.ValidInt64
|
||||||
|
BetAmountLimit domain.ValidInt64
|
||||||
|
DailyTicketPerIP domain.ValidInt64
|
||||||
|
TotalWinningLimit domain.ValidInt64
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDBSettingList(settings []dbgen.Setting) (domain.SettingList, error) {
|
||||||
|
var dbSettingList DBSettingList
|
||||||
|
var int64SettingsMap = map[string]*domain.ValidInt64{
|
||||||
|
"max_number_of_outcomes": &dbSettingList.MaxNumberOfOutcomes,
|
||||||
|
"bet_amount_limit": &dbSettingList.BetAmountLimit,
|
||||||
|
"daily_ticket_limit": &dbSettingList.DailyTicketPerIP,
|
||||||
|
"total_winnings_limit": &dbSettingList.TotalWinningLimit,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, setting := range settings {
|
||||||
|
is_setting_unknown := true
|
||||||
|
for key, dbSetting := range int64SettingsMap {
|
||||||
|
if setting.Key == key {
|
||||||
|
value, err := strconv.ParseInt(setting.Value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return domain.SettingList{}, err
|
||||||
|
}
|
||||||
|
*dbSetting = domain.ValidInt64{
|
||||||
|
Value: value,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
is_setting_unknown = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_setting_unknown {
|
||||||
|
domain.MongoDBLogger.Warn("unknown setting found on database", zap.String("setting", setting.Key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, dbSetting := range int64SettingsMap {
|
||||||
|
if !dbSetting.Valid {
|
||||||
|
fmt.Printf("setting value not found on database: %v \n", key)
|
||||||
|
domain.MongoDBLogger.Warn("setting value not found on database", zap.String("setting", key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.SettingList{
|
||||||
|
MaxNumberOfOutcomes: dbSettingList.MaxNumberOfOutcomes.Value,
|
||||||
|
BetAmountLimit: domain.Currency(dbSettingList.BetAmountLimit.Value),
|
||||||
|
DailyTicketPerIP: dbSettingList.DailyTicketPerIP.Value,
|
||||||
|
TotalWinningLimit: domain.Currency(dbSettingList.TotalWinningLimit.Value),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func (s *Store) GetSettingList(ctx context.Context) (domain.SettingList, error) {
|
||||||
|
settings, err := s.queries.GetSettings(ctx)
|
||||||
|
if err != nil {
|
||||||
|
domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetDBSettingList(settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetSettings(ctx context.Context) ([]domain.Setting, error) {
|
||||||
|
settings, err := s.queries.GetSettings(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []domain.Setting = make([]domain.Setting, 0, len(settings))
|
||||||
|
for _, setting := range settings {
|
||||||
|
result = append(result, domain.Setting{
|
||||||
|
Key: setting.Key,
|
||||||
|
Value: setting.Value,
|
||||||
|
UpdatedAt: setting.UpdatedAt.Time,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetSetting(ctx context.Context, key string) (domain.Setting, error) {
|
||||||
|
dbSetting, err := s.queries.GetSetting(ctx, key)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
result := domain.Setting{
|
||||||
|
Key: dbSetting.Key,
|
||||||
|
Value: dbSetting.Value,
|
||||||
|
UpdatedAt: dbSetting.UpdatedAt.Time,
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) SaveSetting(ctx context.Context, key, value string) (domain.Setting, error) {
|
||||||
|
dbSetting, err := s.queries.SaveSetting(ctx, dbgen.SaveSettingParams{
|
||||||
|
Key: key,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
domain.MongoDBLogger.Error("failed to update setting", zap.String("key", key), zap.String("value", value), zap.Error(err))
|
||||||
|
|
||||||
|
return domain.Setting{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
setting := domain.Setting{
|
||||||
|
Key: dbSetting.Key,
|
||||||
|
Value: dbSetting.Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
return setting, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -10,17 +10,27 @@ import (
|
||||||
|
|
||||||
func convertDBTransfer(transfer dbgen.WalletTransfer) domain.Transfer {
|
func convertDBTransfer(transfer dbgen.WalletTransfer) domain.Transfer {
|
||||||
return domain.Transfer{
|
return domain.Transfer{
|
||||||
ID: transfer.ID,
|
ID: transfer.ID,
|
||||||
Amount: domain.Currency(transfer.Amount.Int64),
|
Amount: domain.Currency(transfer.Amount.Int64),
|
||||||
Type: domain.TransferType(transfer.Type.String),
|
Type: domain.TransferType(transfer.Type.String),
|
||||||
Verified: transfer.Verified.Bool,
|
Verified: transfer.Verified.Bool,
|
||||||
ReceiverWalletID: transfer.ReceiverWalletID.Int64,
|
ReceiverWalletID: domain.ValidInt64{
|
||||||
SenderWalletID: transfer.SenderWalletID.Int64,
|
Value: transfer.ReceiverWalletID.Int64,
|
||||||
|
Valid: transfer.ReceiverWalletID.Valid,
|
||||||
|
},
|
||||||
|
SenderWalletID: domain.ValidInt64{
|
||||||
|
Value: transfer.SenderWalletID.Int64,
|
||||||
|
Valid: transfer.SenderWalletID.Valid,
|
||||||
|
},
|
||||||
CashierID: domain.ValidInt64{
|
CashierID: domain.ValidInt64{
|
||||||
Value: transfer.CashierID.Int64,
|
Value: transfer.CashierID.Int64,
|
||||||
Valid: transfer.CashierID.Valid,
|
Valid: transfer.CashierID.Valid,
|
||||||
},
|
},
|
||||||
PaymentMethod: domain.PaymentMethod(transfer.PaymentMethod.String),
|
PaymentMethod: domain.PaymentMethod(transfer.PaymentMethod.String),
|
||||||
|
ReferenceNumber: transfer.ReferenceNumber,
|
||||||
|
Status: transfer.Status.String,
|
||||||
|
CreatedAt: transfer.CreatedAt.Time,
|
||||||
|
UpdatedAt: transfer.UpdatedAt.Time,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,17 +39,19 @@ func convertCreateTransfer(transfer domain.CreateTransfer) dbgen.CreateTransferP
|
||||||
Amount: pgtype.Int8{Int64: int64(transfer.Amount), Valid: true},
|
Amount: pgtype.Int8{Int64: int64(transfer.Amount), Valid: true},
|
||||||
Type: pgtype.Text{String: string(transfer.Type), Valid: true},
|
Type: pgtype.Text{String: string(transfer.Type), Valid: true},
|
||||||
ReceiverWalletID: pgtype.Int8{
|
ReceiverWalletID: pgtype.Int8{
|
||||||
Int64: transfer.ReceiverWalletID,
|
Int64: transfer.ReceiverWalletID.Value,
|
||||||
Valid: true,
|
Valid: transfer.ReceiverWalletID.Valid,
|
||||||
},
|
},
|
||||||
SenderWalletID: pgtype.Int8{
|
SenderWalletID: pgtype.Int8{
|
||||||
Int64: transfer.SenderWalletID,
|
Int64: transfer.SenderWalletID.Value,
|
||||||
Valid: true,
|
Valid: transfer.SenderWalletID.Valid,
|
||||||
},
|
},
|
||||||
CashierID: pgtype.Int8{
|
CashierID: pgtype.Int8{
|
||||||
Int64: transfer.CashierID.Value,
|
Int64: transfer.CashierID.Value,
|
||||||
Valid: transfer.CashierID.Valid,
|
Valid: transfer.CashierID.Valid,
|
||||||
},
|
},
|
||||||
|
ReferenceNumber: string(transfer.ReferenceNumber),
|
||||||
|
|
||||||
PaymentMethod: pgtype.Text{String: string(transfer.PaymentMethod), Valid: true},
|
PaymentMethod: pgtype.Text{String: string(transfer.PaymentMethod), Valid: true},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -64,6 +76,7 @@ func (s *Store) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error)
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) {
|
func (s *Store) GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) {
|
||||||
transfers, err := s.queries.GetTransfersByWallet(ctx, pgtype.Int8{Int64: walletID, Valid: true})
|
transfers, err := s.queries.GetTransfersByWallet(ctx, pgtype.Int8{Int64: walletID, Valid: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -79,7 +92,7 @@ func (s *Store) GetTransfersByWallet(ctx context.Context, walletID int64) ([]dom
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) GetTransferByReference(ctx context.Context, reference string) (domain.Transfer, error) {
|
func (s *Store) GetTransferByReference(ctx context.Context, reference string) (domain.Transfer, error) {
|
||||||
transfer, err := s.queries.GetTransferByReference(ctx, pgtype.Text{String: reference, Valid: true})
|
transfer, err := s.queries.GetTransferByReference(ctx, reference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Transfer{}, nil
|
return domain.Transfer{}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,13 @@ type VirtualGameRepository interface {
|
||||||
GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error)
|
GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error)
|
||||||
UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) error
|
UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) error
|
||||||
// WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error
|
// WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error
|
||||||
|
AddFavoriteGame(ctx context.Context, userID, gameID int64) error
|
||||||
|
RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error
|
||||||
|
ListFavoriteGames(ctx context.Context, userID int64) ([]int64, error)
|
||||||
|
|
||||||
GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
||||||
|
GetUserGameHistory(ctx context.Context, userID int64) ([]domain.VirtualGameHistory, error)
|
||||||
|
CreateVirtualGameHistory(ctx context.Context, his *domain.VirtualGameHistory) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type VirtualGameRepo struct {
|
type VirtualGameRepo struct {
|
||||||
|
|
@ -36,6 +41,26 @@ func NewVirtualGameRepository(store *Store) VirtualGameRepository {
|
||||||
return &VirtualGameRepo{store: store}
|
return &VirtualGameRepo{store: store}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) AddFavoriteGame(ctx context.Context, userID, gameID int64) error {
|
||||||
|
params := dbgen.AddFavoriteGameParams{
|
||||||
|
UserID: userID,
|
||||||
|
GameID: gameID,
|
||||||
|
}
|
||||||
|
return r.store.queries.AddFavoriteGame(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error {
|
||||||
|
params := dbgen.RemoveFavoriteGameParams{
|
||||||
|
UserID: userID,
|
||||||
|
GameID: gameID,
|
||||||
|
}
|
||||||
|
return r.store.queries.RemoveFavoriteGame(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) ListFavoriteGames(ctx context.Context, userID int64) ([]int64, error) {
|
||||||
|
return r.store.queries.ListFavoriteGames(ctx, userID)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *VirtualGameRepo) CreateVirtualGameSession(ctx context.Context, session *domain.VirtualGameSession) error {
|
func (r *VirtualGameRepo) CreateVirtualGameSession(ctx context.Context, session *domain.VirtualGameSession) error {
|
||||||
params := dbgen.CreateVirtualGameSessionParams{
|
params := dbgen.CreateVirtualGameSessionParams{
|
||||||
UserID: session.UserID,
|
UserID: session.UserID,
|
||||||
|
|
@ -92,6 +117,21 @@ func (r *VirtualGameRepo) CreateVirtualGameTransaction(ctx context.Context, tx *
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) CreateVirtualGameHistory(ctx context.Context, his *domain.VirtualGameHistory) error {
|
||||||
|
params := dbgen.CreateVirtualGameHistoryParams{
|
||||||
|
SessionID: pgtype.Text{String: his.SessionID, Valid: true},
|
||||||
|
UserID: his.UserID,
|
||||||
|
// WalletID: pgtype.Int8{Int64: *his.WalletID, Valid: true},
|
||||||
|
TransactionType: his.TransactionType,
|
||||||
|
Amount: his.Amount,
|
||||||
|
Currency: his.Currency,
|
||||||
|
ExternalTransactionID: his.ExternalTransactionID,
|
||||||
|
Status: his.Status,
|
||||||
|
}
|
||||||
|
_, err := r.store.queries.CreateVirtualGameHistory(ctx, params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *VirtualGameRepo) GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error) {
|
func (r *VirtualGameRepo) GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error) {
|
||||||
dbTx, err := r.store.queries.GetVirtualGameTransactionByExternalID(ctx, externalID)
|
dbTx, err := r.store.queries.GetVirtualGameTransactionByExternalID(ctx, externalID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -153,6 +193,24 @@ func (r *VirtualGameRepo) GetGameCounts(ctx context.Context, filter domain.Repor
|
||||||
return total, active, inactive, nil
|
return total, active, inactive, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) GetUserGameHistory(ctx context.Context, userID int64) ([]domain.VirtualGameHistory, error) {
|
||||||
|
query := `SELECT game_id FROM virtual_game_histories WHERE user_id = $1 AND transaction_type = 'BET' ORDER BY created_at DESC LIMIT 100`
|
||||||
|
rows, err := r.store.conn.Query(ctx, query, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var history []domain.VirtualGameHistory
|
||||||
|
for rows.Next() {
|
||||||
|
var tx domain.VirtualGameHistory
|
||||||
|
if err := rows.Scan(&tx.GameID); err == nil {
|
||||||
|
history = append(history, tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return history, nil
|
||||||
|
}
|
||||||
|
|
||||||
// func (r *VirtualGameRepo) WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error {
|
// func (r *VirtualGameRepo) WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error {
|
||||||
// _, tx, err := r.store.BeginTx(ctx)
|
// _, tx, err := r.store.BeginTx(ctx)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ func convertCreateCustomerWallet(customerWallet domain.CreateCustomerWallet) dbg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertDBGetCustomerWallet(customerWallet dbgen.GetCustomerWalletRow) domain.GetCustomerWallet {
|
func convertDBGetCustomerWallet(customerWallet dbgen.CustomerWalletDetail) domain.GetCustomerWallet {
|
||||||
return domain.GetCustomerWallet{
|
return domain.GetCustomerWallet{
|
||||||
ID: customerWallet.ID,
|
ID: customerWallet.ID,
|
||||||
RegularID: customerWallet.RegularID,
|
RegularID: customerWallet.RegularID,
|
||||||
|
|
@ -55,9 +55,14 @@ func convertDBGetCustomerWallet(customerWallet dbgen.GetCustomerWalletRow) domai
|
||||||
StaticID: customerWallet.StaticID,
|
StaticID: customerWallet.StaticID,
|
||||||
StaticBalance: domain.Currency(customerWallet.StaticBalance),
|
StaticBalance: domain.Currency(customerWallet.StaticBalance),
|
||||||
CustomerID: customerWallet.CustomerID,
|
CustomerID: customerWallet.CustomerID,
|
||||||
|
RegularIsActive: customerWallet.RegularIsActive,
|
||||||
|
StaticIsActive: customerWallet.StaticIsActive,
|
||||||
RegularUpdatedAt: customerWallet.RegularUpdatedAt.Time,
|
RegularUpdatedAt: customerWallet.RegularUpdatedAt.Time,
|
||||||
StaticUpdatedAt: customerWallet.StaticUpdatedAt.Time,
|
StaticUpdatedAt: customerWallet.StaticUpdatedAt.Time,
|
||||||
CreatedAt: customerWallet.CreatedAt.Time,
|
CreatedAt: customerWallet.CreatedAt.Time,
|
||||||
|
FirstName: customerWallet.FirstName,
|
||||||
|
LastName: customerWallet.LastName,
|
||||||
|
PhoneNumber: customerWallet.PhoneNumber.String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,6 +120,19 @@ func (s *Store) GetWalletsByUser(ctx context.Context, userID int64) ([]domain.Wa
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetAllCustomerWallets(ctx context.Context) ([]domain.GetCustomerWallet, error) {
|
||||||
|
customerWallets, err := s.queries.GetAllCustomerWallet(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []domain.GetCustomerWallet = make([]domain.GetCustomerWallet, 0, len(customerWallets))
|
||||||
|
for _, wallet := range customerWallets {
|
||||||
|
result = append(result, convertDBGetCustomerWallet(wallet))
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error) {
|
func (s *Store) GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error) {
|
||||||
customerWallet, err := s.queries.GetCustomerWallet(ctx, customerID)
|
customerWallet, err := s.queries.GetCustomerWallet(ctx, customerID)
|
||||||
|
|
||||||
|
|
@ -257,3 +275,4 @@ func (s *Store) GetTotalWallets(ctx context.Context, filter domain.ReportFilter)
|
||||||
|
|
||||||
return total, nil
|
return total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,14 @@ type BetStore interface {
|
||||||
GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error)
|
GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error)
|
||||||
GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error)
|
GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error)
|
||||||
GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error)
|
GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error)
|
||||||
GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error)
|
GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error)
|
||||||
GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error)
|
GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error)
|
||||||
|
GetBetCount(ctx context.Context, userID int64, outcomesHash string) (int64, error)
|
||||||
UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error
|
UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error
|
||||||
UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error
|
UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error
|
||||||
UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error)
|
UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error)
|
||||||
DeleteBet(ctx context.Context, id int64) error
|
UpdateBetOutcomeStatusByBetID(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error)
|
||||||
|
UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error)
|
||||||
|
|
||||||
GetBetSummary(ctx context.Context, filter domain.ReportFilter) (
|
GetBetSummary(ctx context.Context, filter domain.ReportFilter) (
|
||||||
totalStakes domain.Currency,
|
totalStakes domain.Currency,
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,17 @@ package bet
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"math/big"
|
"math/big"
|
||||||
random "math/rand"
|
random "math/rand"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
|
@ -25,6 +29,12 @@ var (
|
||||||
ErrGenerateRandomOutcome = errors.New("Failed to generate any random outcome for events")
|
ErrGenerateRandomOutcome = errors.New("Failed to generate any random outcome for events")
|
||||||
ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending")
|
ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending")
|
||||||
ErrEventHasBeenRemoved = errors.New("Event has been removed")
|
ErrEventHasBeenRemoved = errors.New("Event has been removed")
|
||||||
|
|
||||||
|
ErrEventHasNotEnded = errors.New("Event has not ended yet")
|
||||||
|
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
|
||||||
|
ErrBranchIDRequired = errors.New("Branch ID required for this role")
|
||||||
|
ErrOutcomeLimit = errors.New("Too many outcomes on a single bet")
|
||||||
|
ErrTotalBalanceNotEnough = errors.New("Total Wallet balance is insufficient to create bet")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
|
|
@ -37,7 +47,15 @@ type Service struct {
|
||||||
mongoLogger *zap.Logger
|
mongoLogger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(betStore BetStore, eventSvc event.Service, prematchSvc odds.ServiceImpl, walletSvc wallet.Service, branchSvc branch.Service, logger *slog.Logger, mongoLogger *zap.Logger) *Service {
|
func NewService(
|
||||||
|
betStore BetStore,
|
||||||
|
eventSvc event.Service,
|
||||||
|
prematchSvc odds.ServiceImpl,
|
||||||
|
walletSvc wallet.Service,
|
||||||
|
branchSvc branch.Service,
|
||||||
|
logger *slog.Logger,
|
||||||
|
mongoLogger *zap.Logger,
|
||||||
|
) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
betStore: betStore,
|
betStore: betStore,
|
||||||
eventSvc: eventSvc,
|
eventSvc: eventSvc,
|
||||||
|
|
@ -49,13 +67,6 @@ func NewService(betStore BetStore, eventSvc event.Service, prematchSvc odds.Serv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
ErrEventHasNotEnded = errors.New("Event has not ended yet")
|
|
||||||
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
|
|
||||||
ErrBranchIDRequired = errors.New("Branch ID required for this role")
|
|
||||||
ErrOutcomeLimit = errors.New("Too many outcomes on a single bet")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Service) GenerateCashoutID() (string, error) {
|
func (s *Service) GenerateCashoutID() (string, error) {
|
||||||
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
const length int = 13
|
const length int = 13
|
||||||
|
|
@ -196,6 +207,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
||||||
var totalOdds float32 = 1
|
var totalOdds float32 = 1
|
||||||
|
|
||||||
for _, outcomeReq := range req.Outcomes {
|
for _, outcomeReq := range req.Outcomes {
|
||||||
|
fmt.Println("reqq: ", outcomeReq)
|
||||||
newOutcome, err := s.GenerateBetOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID)
|
newOutcome, err := s.GenerateBetOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mongoLogger.Error("failed to generate outcome",
|
s.mongoLogger.Error("failed to generate outcome",
|
||||||
|
|
@ -211,6 +223,23 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
||||||
outcomes = append(outcomes, newOutcome)
|
outcomes = append(outcomes, newOutcome)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outcomesHash, err := generateOutcomeHash(outcomes)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("failed to generate outcome hash",
|
||||||
|
zap.Int64("user_id", userID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := s.GetBetCount(ctx, userID, outcomesHash)
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
if count == 2 {
|
||||||
|
return domain.CreateBetRes{}, fmt.Errorf("bet already pleaced twice")
|
||||||
|
}
|
||||||
|
|
||||||
cashoutID, err := s.GenerateCashoutID()
|
cashoutID, err := s.GenerateCashoutID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mongoLogger.Error("failed to generate cashout ID",
|
s.mongoLogger.Error("failed to generate cashout ID",
|
||||||
|
|
@ -221,12 +250,13 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
||||||
}
|
}
|
||||||
|
|
||||||
newBet := domain.CreateBet{
|
newBet := domain.CreateBet{
|
||||||
Amount: domain.ToCurrency(req.Amount),
|
Amount: domain.ToCurrency(req.Amount),
|
||||||
TotalOdds: totalOdds,
|
TotalOdds: totalOdds,
|
||||||
Status: req.Status,
|
Status: req.Status,
|
||||||
FullName: req.FullName,
|
FullName: req.FullName,
|
||||||
PhoneNumber: req.PhoneNumber,
|
PhoneNumber: req.PhoneNumber,
|
||||||
CashoutID: cashoutID,
|
CashoutID: cashoutID,
|
||||||
|
OutcomesHash: outcomesHash,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch role {
|
switch role {
|
||||||
|
|
@ -241,7 +271,11 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
||||||
}
|
}
|
||||||
|
|
||||||
deductedAmount := req.Amount / 10
|
deductedAmount := req.Amount / 10
|
||||||
err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount))
|
_, err = s.walletSvc.DeductFromWallet(ctx,
|
||||||
|
branch.WalletID, domain.ToCurrency(deductedAmount), domain.BranchWalletType, domain.ValidInt64{
|
||||||
|
Value: userID,
|
||||||
|
Valid: true,
|
||||||
|
}, domain.TRANSFER_DIRECT)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mongoLogger.Error("failed to deduct from wallet",
|
s.mongoLogger.Error("failed to deduct from wallet",
|
||||||
zap.Int64("wallet_id", branch.WalletID),
|
zap.Int64("wallet_id", branch.WalletID),
|
||||||
|
|
@ -274,7 +308,10 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
||||||
}
|
}
|
||||||
|
|
||||||
deductedAmount := req.Amount / 10
|
deductedAmount := req.Amount / 10
|
||||||
err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount))
|
_, err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount), domain.BranchWalletType, domain.ValidInt64{
|
||||||
|
Value: userID,
|
||||||
|
Valid: true,
|
||||||
|
}, domain.TRANSFER_DIRECT)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mongoLogger.Error("wallet deduction failed",
|
s.mongoLogger.Error("wallet deduction failed",
|
||||||
zap.Int64("wallet_id", branch.WalletID),
|
zap.Int64("wallet_id", branch.WalletID),
|
||||||
|
|
@ -290,7 +327,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
||||||
newBet.IsShopBet = true
|
newBet.IsShopBet = true
|
||||||
|
|
||||||
case domain.RoleCustomer:
|
case domain.RoleCustomer:
|
||||||
wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID)
|
wallets, err := s.walletSvc.GetCustomerWallet(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mongoLogger.Error("failed to get customer wallets",
|
s.mongoLogger.Error("failed to get customer wallets",
|
||||||
zap.Int64("user_id", userID),
|
zap.Int64("user_id", userID),
|
||||||
|
|
@ -298,16 +335,52 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
||||||
)
|
)
|
||||||
return domain.CreateBetRes{}, err
|
return domain.CreateBetRes{}, err
|
||||||
}
|
}
|
||||||
|
if req.Amount < wallets.RegularBalance.Float32() {
|
||||||
|
_, err = s.walletSvc.DeductFromWallet(ctx, wallets.RegularID,
|
||||||
|
domain.ToCurrency(req.Amount), domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("wallet deduction failed for customer regular wallet",
|
||||||
|
zap.Int64("customer_id", wallets.CustomerID),
|
||||||
|
zap.Int64("customer_wallet_id", wallets.ID),
|
||||||
|
zap.Int64("regular wallet_id", wallets.RegularID),
|
||||||
|
zap.Float32("amount", req.Amount),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
combinedBalance := wallets.RegularBalance + wallets.StaticBalance
|
||||||
|
if req.Amount > combinedBalance.Float32() {
|
||||||
|
return domain.CreateBetRes{}, ErrTotalBalanceNotEnough
|
||||||
|
}
|
||||||
|
// Empty the regular balance
|
||||||
|
_, err = s.walletSvc.DeductFromWallet(ctx, wallets.RegularID,
|
||||||
|
wallets.RegularBalance, domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("wallet deduction failed for customer regular wallet",
|
||||||
|
zap.Int64("customer_id", wallets.CustomerID),
|
||||||
|
zap.Int64("customer_wallet_id", wallets.ID),
|
||||||
|
zap.Int64("regular wallet_id", wallets.RegularID),
|
||||||
|
zap.Float32("amount", req.Amount),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
// Empty remaining from static balance
|
||||||
|
remainingAmount := wallets.RegularBalance - domain.Currency(req.Amount)
|
||||||
|
_, err = s.walletSvc.DeductFromWallet(ctx, wallets.StaticID,
|
||||||
|
remainingAmount, domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("wallet deduction failed for customer static wallet",
|
||||||
|
zap.Int64("customer_id", wallets.CustomerID),
|
||||||
|
zap.Int64("customer_wallet_id", wallets.ID),
|
||||||
|
zap.Int64("static wallet_id", wallets.StaticID),
|
||||||
|
zap.Float32("amount", req.Amount),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
|
||||||
userWallet := wallets[0]
|
|
||||||
err = s.walletSvc.DeductFromWallet(ctx, userWallet.ID, domain.ToCurrency(req.Amount))
|
|
||||||
if err != nil {
|
|
||||||
s.mongoLogger.Error("wallet deduction failed for customer",
|
|
||||||
zap.Int64("wallet_id", userWallet.ID),
|
|
||||||
zap.Float32("amount", req.Amount),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return domain.CreateBetRes{}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newBet.UserID = domain.ValidInt64{Value: userID, Valid: true}
|
newBet.UserID = domain.ValidInt64{Value: userID, Valid: true}
|
||||||
|
|
@ -321,6 +394,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
||||||
return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type")
|
return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("Bet is: ", newBet)
|
||||||
bet, err := s.CreateBet(ctx, newBet)
|
bet, err := s.CreateBet(ctx, newBet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mongoLogger.Error("failed to create bet",
|
s.mongoLogger.Error("failed to create bet",
|
||||||
|
|
@ -636,6 +710,10 @@ func (s *Service) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.Ge
|
||||||
return s.betStore.GetBetByUserID(ctx, UserID)
|
return s.betStore.GetBetByUserID(ctx, UserID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetBetCount(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
|
||||||
|
return s.betStore.GetBetCount(ctx, UserID, outcomesHash)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error {
|
func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error {
|
||||||
return s.betStore.UpdateCashOut(ctx, id, cashedOut)
|
return s.betStore.UpdateCashOut(ctx, id, cashedOut)
|
||||||
}
|
}
|
||||||
|
|
@ -676,7 +754,8 @@ func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.Outc
|
||||||
amount = bet.Amount
|
amount = bet.Amount
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.walletSvc.AddToWallet(ctx, customerWallet.RegularID, amount)
|
_, err = s.walletSvc.AddToWallet(ctx, customerWallet.RegularID, amount, domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mongoLogger.Error("failed to add winnings to wallet",
|
s.mongoLogger.Error("failed to add winnings to wallet",
|
||||||
zap.Int64("wallet_id", customerWallet.RegularID),
|
zap.Int64("wallet_id", customerWallet.RegularID),
|
||||||
|
|
@ -782,6 +861,55 @@ func (s *Service) UpdateBetOutcomeStatus(ctx context.Context, id int64, status d
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) DeleteBet(ctx context.Context, id int64) error {
|
func (s *Service) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error) {
|
||||||
return s.betStore.DeleteBet(ctx, id)
|
outcomes, err := s.betStore.UpdateBetOutcomeStatusForEvent(ctx, eventID, status)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("failed to update bet outcome status",
|
||||||
|
zap.Int64("eventID", eventID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return outcomes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) SetBetToRemoved(ctx context.Context, id int64) error {
|
||||||
|
_, err := s.betStore.UpdateBetOutcomeStatusByBetID(ctx, id, domain.OUTCOME_STATUS_VOID)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("failed to update bet outcome to void", zap.Int64("id", id),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.betStore.UpdateStatus(ctx, id, domain.OUTCOME_STATUS_VOID)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("failed to update bet to void", zap.Int64("id", id),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateOutcomeHash(outcomes []domain.CreateBetOutcome) (string, error) {
|
||||||
|
// should always be in the same order for producing the same hash
|
||||||
|
sort.Slice(outcomes, func(i, j int) bool {
|
||||||
|
if outcomes[i].EventID != outcomes[j].EventID {
|
||||||
|
return outcomes[i].EventID < outcomes[j].EventID
|
||||||
|
}
|
||||||
|
if outcomes[i].MarketID != outcomes[j].MarketID {
|
||||||
|
return outcomes[i].MarketID < outcomes[j].MarketID
|
||||||
|
}
|
||||||
|
return outcomes[i].OddID < outcomes[j].OddID
|
||||||
|
})
|
||||||
|
|
||||||
|
var sb strings.Builder
|
||||||
|
for _, o := range outcomes {
|
||||||
|
sb.WriteString(fmt.Sprintf("%d-%d-%d;", o.EventID, o.MarketID, o.OddID))
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := sha256.Sum256([]byte(sb.String()))
|
||||||
|
return hex.EncodeToString(sum[:]), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ type BranchStore interface {
|
||||||
GetBranchByID(ctx context.Context, id int64) (domain.BranchDetail, error)
|
GetBranchByID(ctx context.Context, id int64) (domain.BranchDetail, error)
|
||||||
GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([]domain.BranchDetail, error)
|
GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([]domain.BranchDetail, error)
|
||||||
GetBranchByCompanyID(ctx context.Context, companyID int64) ([]domain.BranchDetail, error)
|
GetBranchByCompanyID(ctx context.Context, companyID int64) ([]domain.BranchDetail, error)
|
||||||
GetAllBranches(ctx context.Context) ([]domain.BranchDetail, error)
|
GetAllBranches(ctx context.Context, filter domain.BranchFilter) ([]domain.BranchDetail, error)
|
||||||
SearchBranchByName(ctx context.Context, name string) ([]domain.BranchDetail, error)
|
SearchBranchByName(ctx context.Context, name string) ([]domain.BranchDetail, error)
|
||||||
UpdateBranch(ctx context.Context, branch domain.UpdateBranch) (domain.Branch, error)
|
UpdateBranch(ctx context.Context, branch domain.UpdateBranch) (domain.Branch, error)
|
||||||
DeleteBranch(ctx context.Context, id int64) error
|
DeleteBranch(ctx context.Context, id int64) error
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package branch
|
package branch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -42,8 +42,8 @@ func (s *Service) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]
|
||||||
func (s *Service) GetBranchOperations(ctx context.Context, branchID int64) ([]domain.BranchOperation, error) {
|
func (s *Service) GetBranchOperations(ctx context.Context, branchID int64) ([]domain.BranchOperation, error) {
|
||||||
return s.branchStore.GetBranchOperations(ctx, branchID)
|
return s.branchStore.GetBranchOperations(ctx, branchID)
|
||||||
}
|
}
|
||||||
func (s *Service) GetAllBranches(ctx context.Context) ([]domain.BranchDetail, error) {
|
func (s *Service) GetAllBranches(ctx context.Context, filter domain.BranchFilter) ([]domain.BranchDetail, error) {
|
||||||
return s.branchStore.GetAllBranches(ctx)
|
return s.branchStore.GetAllBranches(ctx, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetBranchByCashier(ctx context.Context, userID int64) (domain.Branch, error) {
|
func (s *Service) GetBranchByCashier(ctx context.Context, userID int64) (domain.Branch, error) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -30,9 +31,9 @@ func NewClient(baseURL, secretKey string) *Client {
|
||||||
|
|
||||||
func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaDepositRequest) (domain.ChapaDepositResponse, error) {
|
func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaDepositRequest) (domain.ChapaDepositResponse, error) {
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"amount": req.Amount,
|
"amount": fmt.Sprintf("%.2f", float64(req.Amount)/100),
|
||||||
"currency": req.Currency,
|
"currency": req.Currency,
|
||||||
"email": req.Email,
|
// "email": req.Email,
|
||||||
"first_name": req.FirstName,
|
"first_name": req.FirstName,
|
||||||
"last_name": req.LastName,
|
"last_name": req.LastName,
|
||||||
"tx_ref": req.TxRef,
|
"tx_ref": req.TxRef,
|
||||||
|
|
@ -40,6 +41,8 @@ func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaDepositR
|
||||||
"return_url": req.ReturnURL,
|
"return_url": req.ReturnURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n\nChapa Payload: %+v\n\n", payload)
|
||||||
|
|
||||||
payloadBytes, err := json.Marshal(payload)
|
payloadBytes, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.ChapaDepositResponse{}, fmt.Errorf("failed to marshal payload: %w", err)
|
return domain.ChapaDepositResponse{}, fmt.Errorf("failed to marshal payload: %w", err)
|
||||||
|
|
@ -50,6 +53,8 @@ func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaDepositR
|
||||||
return domain.ChapaDepositResponse{}, fmt.Errorf("failed to create request: %w", err)
|
return domain.ChapaDepositResponse{}, fmt.Errorf("failed to create request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n\nBase URL is: %+v\n\n", c.baseURL)
|
||||||
|
|
||||||
httpReq.Header.Set("Authorization", "Bearer "+c.secretKey)
|
httpReq.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||||
httpReq.Header.Set("Content-Type", "application/json")
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
|
@ -59,6 +64,11 @@ func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaDepositR
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body) // <-- Add this
|
||||||
|
return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body)) // <-- Log it
|
||||||
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +87,7 @@ func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaDepositR
|
||||||
|
|
||||||
return domain.ChapaDepositResponse{
|
return domain.ChapaDepositResponse{
|
||||||
CheckoutURL: response.Data.CheckoutURL,
|
CheckoutURL: response.Data.CheckoutURL,
|
||||||
// Reference: req.TxRef,
|
Reference: req.TxRef,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,6 +175,51 @@ func (c *Client) ManualVerifyPayment(ctx context.Context, txRef string) (*domain
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) ManualVerifyTransfer(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
||||||
|
url := fmt.Sprintf("%s/transfers/verify/%s", c.baseURL, txRef)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var status domain.PaymentStatus
|
||||||
|
switch response.Status {
|
||||||
|
case "success":
|
||||||
|
status = domain.PaymentStatusCompleted
|
||||||
|
default:
|
||||||
|
status = domain.PaymentStatusFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
return &domain.ChapaVerificationResponse{
|
||||||
|
Status: string(status),
|
||||||
|
Amount: response.Amount,
|
||||||
|
Currency: response.Currency,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
|
func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/banks", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/banks", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -213,10 +268,6 @@ func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) InitiateTransfer(ctx context.Context, req domain.ChapaWithdrawalRequest) (bool, error) {
|
func (c *Client) InitiateTransfer(ctx context.Context, req domain.ChapaWithdrawalRequest) (bool, error) {
|
||||||
// base, err := url.Parse(c.baseURL)
|
|
||||||
// if err != nil {
|
|
||||||
// return false, fmt.Errorf("invalid base URL: %w", err)
|
|
||||||
// }
|
|
||||||
endpoint := c.baseURL + "/transfers"
|
endpoint := c.baseURL + "/transfers"
|
||||||
fmt.Printf("\n\nChapa withdrawal URL is %v\n\n", endpoint)
|
fmt.Printf("\n\nChapa withdrawal URL is %v\n\n", endpoint)
|
||||||
|
|
||||||
|
|
@ -230,7 +281,9 @@ func (c *Client) InitiateTransfer(ctx context.Context, req domain.ChapaWithdrawa
|
||||||
return false, fmt.Errorf("failed to create request: %w", err)
|
return false, fmt.Errorf("failed to create request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.setHeaders(httpReq)
|
// Set headers here
|
||||||
|
httpReq.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||||
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(httpReq)
|
resp, err := c.httpClient.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -239,7 +292,8 @@ func (c *Client) InitiateTransfer(ctx context.Context, req domain.ChapaWithdrawa
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return false, fmt.Errorf("chapa api returned status: %d", resp.StatusCode)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return false, fmt.Errorf("chapa api returned status: %d - %s", resp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
var response domain.ChapaWithdrawalResponse
|
var response domain.ChapaWithdrawalResponse
|
||||||
|
|
@ -247,7 +301,7 @@ func (c *Client) InitiateTransfer(ctx context.Context, req domain.ChapaWithdrawa
|
||||||
return false, fmt.Errorf("failed to decode response: %w", err)
|
return false, fmt.Errorf("failed to decode response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Status == string(domain.WithdrawalStatusProcessing), nil
|
return response.Status == string(domain.WithdrawalStatusSuccessful), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) VerifyTransfer(ctx context.Context, reference string) (*domain.ChapaVerificationResponse, error) {
|
func (c *Client) VerifyTransfer(ctx context.Context, reference string) (*domain.ChapaVerificationResponse, error) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"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"
|
||||||
|
|
@ -31,6 +32,7 @@ func NewService(
|
||||||
transferStore wallet.TransferStore,
|
transferStore wallet.TransferStore,
|
||||||
walletStore wallet.Service,
|
walletStore wallet.Service,
|
||||||
userStore user.UserStore,
|
userStore user.UserStore,
|
||||||
|
cfg *config.Config,
|
||||||
chapaClient *Client,
|
chapaClient *Client,
|
||||||
|
|
||||||
) *Service {
|
) *Service {
|
||||||
|
|
@ -38,6 +40,7 @@ func NewService(
|
||||||
transferStore: transferStore,
|
transferStore: transferStore,
|
||||||
walletStore: walletStore,
|
walletStore: walletStore,
|
||||||
userStore: userStore,
|
userStore: userStore,
|
||||||
|
cfg: cfg,
|
||||||
chapaClient: chapaClient,
|
chapaClient: chapaClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +61,9 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
||||||
var senderWallet domain.Wallet
|
var senderWallet domain.Wallet
|
||||||
|
|
||||||
// Generate unique reference
|
// Generate unique reference
|
||||||
reference := uuid.New().String()
|
// reference := uuid.New().String()
|
||||||
|
reference := fmt.Sprintf("chapa-deposit-%d-%s", userID, uuid.New().String())
|
||||||
|
|
||||||
senderWallets, err := s.walletStore.GetWalletsByUser(ctx, userID)
|
senderWallets, err := s.walletStore.GetWalletsByUser(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to get sender wallets: %w", err)
|
return "", fmt.Errorf("failed to get sender wallets: %w", err)
|
||||||
|
|
@ -83,25 +88,28 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
||||||
PaymentMethod: domain.TRANSFER_CHAPA,
|
PaymentMethod: domain.TRANSFER_CHAPA,
|
||||||
ReferenceNumber: reference,
|
ReferenceNumber: reference,
|
||||||
// ReceiverWalletID: 1,
|
// ReceiverWalletID: 1,
|
||||||
SenderWalletID: senderWallet.ID,
|
SenderWalletID: domain.ValidInt64{
|
||||||
Verified: false,
|
Value: senderWallet.ID,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
Verified: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
payload := domain.ChapaDepositRequest{
|
||||||
return "", fmt.Errorf("failed to save payment: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize payment with Chapa
|
|
||||||
response, err := s.chapaClient.InitializePayment(ctx, domain.ChapaDepositRequest{
|
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
Currency: "ETB",
|
Currency: "ETB",
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
FirstName: user.FirstName,
|
FirstName: user.FirstName,
|
||||||
LastName: user.LastName,
|
LastName: user.LastName,
|
||||||
TxRef: reference,
|
TxRef: reference,
|
||||||
CallbackURL: "https://fortunebet.com/api/v1/payments/callback",
|
CallbackURL: s.cfg.CHAPA_CALLBACK_URL,
|
||||||
ReturnURL: "https://fortunebet.com/api/v1/payment-success",
|
ReturnURL: s.cfg.CHAPA_RETURN_URL,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Initialize payment with Chapa
|
||||||
|
response, err := s.chapaClient.InitializePayment(ctx, payload)
|
||||||
|
|
||||||
|
fmt.Printf("\n\nChapa payload is: %+v\n\n", payload)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Update payment status to failed
|
// Update payment status to failed
|
||||||
|
|
@ -109,8 +117,17 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
||||||
return "", fmt.Errorf("failed to initialize payment: %w", err)
|
return "", fmt.Errorf("failed to initialize payment: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tempTransfer, err := s.transferStore.CreateTransfer(ctx, transfer)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to save payment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n\nTemp transfer is: %v\n\n", tempTransfer)
|
||||||
|
|
||||||
return response.CheckoutURL, nil
|
return response.CheckoutURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req domain.ChapaWithdrawalRequest) (*domain.Transfer, error) {
|
func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req domain.ChapaWithdrawalRequest) (*domain.Transfer, error) {
|
||||||
// Parse and validate amount
|
// Parse and validate amount
|
||||||
amount, err := strconv.ParseInt(req.Amount, 10, 64)
|
amount, err := strconv.ParseInt(req.Amount, 10, 64)
|
||||||
|
|
@ -150,14 +167,16 @@ func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req doma
|
||||||
reference := uuid.New().String()
|
reference := uuid.New().String()
|
||||||
|
|
||||||
createTransfer := domain.CreateTransfer{
|
createTransfer := domain.CreateTransfer{
|
||||||
Amount: domain.Currency(amount),
|
Amount: domain.Currency(amount),
|
||||||
Type: domain.WITHDRAW,
|
Type: domain.WITHDRAW,
|
||||||
ReceiverWalletID: 1,
|
SenderWalletID: domain.ValidInt64{
|
||||||
SenderWalletID: withdrawWallet.ID,
|
Value: withdrawWallet.ID,
|
||||||
Status: string(domain.PaymentStatusPending),
|
Valid: true,
|
||||||
Verified: false,
|
},
|
||||||
ReferenceNumber: reference,
|
Status: string(domain.PaymentStatusPending),
|
||||||
PaymentMethod: domain.TRANSFER_CHAPA,
|
Verified: false,
|
||||||
|
ReferenceNumber: reference,
|
||||||
|
PaymentMethod: domain.TRANSFER_CHAPA,
|
||||||
}
|
}
|
||||||
|
|
||||||
transfer, err := s.transferStore.CreateTransfer(ctx, createTransfer)
|
transfer, err := s.transferStore.CreateTransfer(ctx, createTransfer)
|
||||||
|
|
@ -177,12 +196,16 @@ func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req doma
|
||||||
}
|
}
|
||||||
|
|
||||||
success, err := s.chapaClient.InitiateTransfer(ctx, transferReq)
|
success, err := s.chapaClient.InitiateTransfer(ctx, transferReq)
|
||||||
if err != nil || !success {
|
if err != nil {
|
||||||
// Update withdrawal status to failed
|
|
||||||
_ = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusFailed))
|
_ = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusFailed))
|
||||||
return nil, fmt.Errorf("failed to initiate transfer: %w", err)
|
return nil, fmt.Errorf("failed to initiate transfer: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
_ = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusFailed))
|
||||||
|
return nil, errors.New("chapa rejected the transfer request")
|
||||||
|
}
|
||||||
|
|
||||||
// Update withdrawal status to processing
|
// Update withdrawal status to processing
|
||||||
if err := s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusProcessing)); err != nil {
|
if err := s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusProcessing)); err != nil {
|
||||||
return nil, fmt.Errorf("failed to update withdrawal status: %w", err)
|
return nil, fmt.Errorf("failed to update withdrawal status: %w", err)
|
||||||
|
|
@ -204,42 +227,68 @@ func (s *Service) GetSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
||||||
return banks, nil
|
return banks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ManualVerifTransaction(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
func (s *Service) ManuallyVerify(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
||||||
// First check if we already have a verified record
|
// Lookup transfer by reference
|
||||||
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
|
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
|
||||||
if err == nil && transfer.Verified {
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("transfer not found for reference %s: %w", txRef, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if transfer.Verified {
|
||||||
return &domain.ChapaVerificationResponse{
|
return &domain.ChapaVerificationResponse{
|
||||||
Status: string(domain.PaymentStatusCompleted),
|
Status: string(domain.PaymentStatusCompleted),
|
||||||
Amount: float64(transfer.Amount) / 100, // Convert from cents/kobo
|
Amount: float64(transfer.Amount) / 100,
|
||||||
Currency: "ETB",
|
Currency: "ETB",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not verified or not found, verify with Chapa
|
// Validate sender wallet
|
||||||
verification, err := s.chapaClient.VerifyPayment(ctx, txRef)
|
if !transfer.SenderWalletID.Valid {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("invalid sender wallet ID: %v", transfer.SenderWalletID)
|
||||||
return nil, fmt.Errorf("failed to verify payment: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update our records if payment is successful
|
var verification *domain.ChapaVerificationResponse
|
||||||
if verification.Status == domain.PaymentStatusCompleted {
|
|
||||||
err = s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true)
|
// Decide verification method based on type
|
||||||
|
switch strings.ToLower(string(transfer.Type)) {
|
||||||
|
case "deposit":
|
||||||
|
// Use Chapa Payment Verification
|
||||||
|
verification, err = s.chapaClient.ManualVerifyPayment(ctx, txRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to update verification status: %w", err)
|
return nil, fmt.Errorf("failed to verify deposit with Chapa: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Credit user's wallet
|
if verification.Status == string(domain.PaymentStatusSuccessful) {
|
||||||
err = s.walletStore.UpdateBalance(ctx, transfer.SenderWalletID, transfer.Amount)
|
// Mark verified
|
||||||
if err != nil {
|
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
||||||
return nil, fmt.Errorf("failed to update wallet balance: %w", err)
|
return nil, fmt.Errorf("failed to mark deposit transfer as verified: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Credit wallet
|
||||||
|
if _, err := s.walletStore.AddToWallet(ctx, transfer.SenderWalletID.Value, transfer.Amount, domain.ValidInt64{}, domain.TRANSFER_CHAPA, domain.PaymentDetails{}); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "withdraw":
|
||||||
|
// Use Chapa Transfer Verification
|
||||||
|
verification, err = s.chapaClient.ManualVerifyTransfer(ctx, txRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to verify withdrawal with Chapa: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if verification.Status == string(domain.PaymentStatusSuccessful) {
|
||||||
|
// Mark verified (withdraw doesn't affect balance)
|
||||||
|
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to mark withdrawal transfer as verified: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported transfer type: %s", transfer.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &domain.ChapaVerificationResponse{
|
return verification, nil
|
||||||
Status: string(verification.Status),
|
|
||||||
Amount: float64(verification.Amount),
|
|
||||||
Currency: verification.Currency,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) HandleVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaWebHookTransfer) error {
|
func (s *Service) HandleVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaWebHookTransfer) error {
|
||||||
|
|
@ -265,13 +314,18 @@ func (s *Service) HandleVerifyDepositWebhook(ctx context.Context, transfer domai
|
||||||
// verified = true
|
// verified = true
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if err := s.transferStore.UpdateTransferVerification(ctx, payment.ID, true); err != nil {
|
|
||||||
return fmt.Errorf("failed to update payment status: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If payment is completed, credit user's wallet
|
// If payment is completed, credit user's wallet
|
||||||
if transfer.Status == string(domain.PaymentStatusCompleted) {
|
if transfer.Status == string(domain.PaymentStatusSuccessful) {
|
||||||
if err := s.walletStore.AddToWallet(ctx, payment.SenderWalletID, payment.Amount); err != nil {
|
|
||||||
|
if err := s.transferStore.UpdateTransferVerification(ctx, payment.ID, true); err != nil {
|
||||||
|
return fmt.Errorf("failed to update payment status: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.walletStore.AddToWallet(ctx, payment.SenderWalletID.Value, payment.Amount, domain.ValidInt64{}, domain.TRANSFER_CHAPA, domain.PaymentDetails{
|
||||||
|
ReferenceNumber: domain.ValidString{
|
||||||
|
Value: transfer.Reference,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
return fmt.Errorf("failed to credit user wallet: %w", err)
|
return fmt.Errorf("failed to credit user wallet: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -302,13 +356,12 @@ func (s *Service) HandleVerifyWithdrawWebhook(ctx context.Context, payment domai
|
||||||
// verified = true
|
// verified = true
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
if payment.Status == string(domain.PaymentStatusSuccessful) {
|
||||||
return fmt.Errorf("failed to update payment status: %w", err)
|
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
||||||
}
|
return fmt.Errorf("failed to update payment status: %w", err)
|
||||||
|
} // If payment is completed, credit user's walle
|
||||||
// If payment is completed, credit user's wallet
|
} else {
|
||||||
if payment.Status == string(domain.PaymentStatusFailed) {
|
if _, err := s.walletStore.AddToWallet(ctx, transfer.SenderWalletID.Value, transfer.Amount, domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}); err != nil {
|
||||||
if err := s.walletStore.AddToWallet(ctx, transfer.SenderWalletID, transfer.Amount); err != nil {
|
|
||||||
return fmt.Errorf("failed to credit user wallet: %w", err)
|
return fmt.Errorf("failed to credit user wallet: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -202,8 +203,10 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, source string) {
|
func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, source string) {
|
||||||
sportIDs := []int{1, 18, 17, 3, 83, 15, 12, 19, 8, 16, 91}
|
// sportIDs := []int{1, 18, 17, 3, 83, 15, 12, 19, 8, 16, 91}
|
||||||
|
sportIDs := []int{1}
|
||||||
// TODO: Add the league skipping again when we have dynamic leagues
|
// TODO: Add the league skipping again when we have dynamic leagues
|
||||||
|
|
||||||
// b, err := os.OpenFile("logs/skipped_leagues.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
// b, err := os.OpenFile("logs/skipped_leagues.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// log.Printf("❌ Failed to open leagues file %v", err)
|
// log.Printf("❌ Failed to open leagues file %v", err)
|
||||||
|
|
@ -214,7 +217,6 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
|
||||||
var page int = 0
|
var page int = 0
|
||||||
var limit int = 200
|
var limit int = 200
|
||||||
var count int = 0
|
var count int = 0
|
||||||
log.Printf("Sport ID %d", sportID)
|
|
||||||
for page <= totalPages {
|
for page <= totalPages {
|
||||||
page = page + 1
|
page = page + 1
|
||||||
url := fmt.Sprintf(url, sportID, s.token, page)
|
url := fmt.Sprintf(url, sportID, s.token, page)
|
||||||
|
|
@ -252,11 +254,13 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
|
||||||
// doesn't make sense to save and check back to back, but for now it can be here
|
// doesn't make sense to save and check back to back, but for now it can be here
|
||||||
// no this its fine to keep it here
|
// no this its fine to keep it here
|
||||||
// but change the league id to bet365 id later
|
// but change the league id to bet365 id later
|
||||||
|
//Automatically feature the league if its in the list
|
||||||
err = s.store.SaveLeague(ctx, domain.League{
|
err = s.store.SaveLeague(ctx, domain.League{
|
||||||
ID: leagueID,
|
ID: leagueID,
|
||||||
Name: ev.League.Name,
|
Name: ev.League.Name,
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
SportID: convertInt32(ev.SportID),
|
IsFeatured: slices.Contains(domain.FeaturedLeagues, leagueID),
|
||||||
|
SportID: convertInt32(ev.SportID),
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
1
internal/services/institutions/port.go
Normal file
1
internal/services/institutions/port.go
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
package institutions
|
||||||
44
internal/services/institutions/service.go
Normal file
44
internal/services/institutions/service.go
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package institutions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
repo repository.BankRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(repo repository.BankRepository) *Service {
|
||||||
|
return &Service{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Create(ctx context.Context, bank *domain.Bank) error {
|
||||||
|
return s.repo.CreateBank(ctx, bank)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Update(ctx context.Context, bank *domain.Bank) error {
|
||||||
|
return s.repo.UpdateBank(ctx, bank)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetByID(ctx context.Context, id int64) (*domain.Bank, error) {
|
||||||
|
return s.repo.GetBankByID(ctx, int(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Delete(ctx context.Context, id int64) error {
|
||||||
|
return s.repo.DeleteBank(ctx, int(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) List(ctx context.Context) ([]*domain.Bank, error) {
|
||||||
|
banks, err := s.repo.GetAllBanks(ctx, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]*domain.Bank, len(banks))
|
||||||
|
for i := range banks {
|
||||||
|
result[i] = &banks[i]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
83
internal/services/issue_reporting/service.go
Normal file
83
internal/services/issue_reporting/service.go
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
package issuereporting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
repo repository.ReportedIssueRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(repo repository.ReportedIssueRepository) *Service {
|
||||||
|
return &Service{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreateReportedIssue(ctx context.Context, issue domain.ReportedIssue) (domain.ReportedIssue, error) {
|
||||||
|
params := dbgen.CreateReportedIssueParams{
|
||||||
|
// Map fields from domain.ReportedIssue to dbgen.CreateReportedIssueParams here.
|
||||||
|
// Example:
|
||||||
|
// Title: issue.Title,
|
||||||
|
// Description: issue.Description,
|
||||||
|
// CustomerID: issue.CustomerID,
|
||||||
|
// Status: issue.Status,
|
||||||
|
// Add other fields as necessary.
|
||||||
|
}
|
||||||
|
dbIssue, err := s.repo.CreateReportedIssue(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ReportedIssue{}, err
|
||||||
|
}
|
||||||
|
// Map dbgen.ReportedIssue to domain.ReportedIssue
|
||||||
|
reportedIssue := domain.ReportedIssue{
|
||||||
|
ID: dbIssue.ID,
|
||||||
|
Subject: dbIssue.Subject,
|
||||||
|
Description: dbIssue.Description,
|
||||||
|
CustomerID: dbIssue.CustomerID,
|
||||||
|
Status: dbIssue.Status,
|
||||||
|
CreatedAt: dbIssue.CreatedAt.Time,
|
||||||
|
UpdatedAt: dbIssue.UpdatedAt.Time,
|
||||||
|
// Add other fields as necessary
|
||||||
|
}
|
||||||
|
return reportedIssue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetIssuesForCustomer(ctx context.Context, customerID int64, limit, offset int) ([]domain.ReportedIssue, error) {
|
||||||
|
dbIssues, err := s.repo.ListReportedIssuesByCustomer(ctx, customerID, int32(limit), int32(offset))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reportedIssues := make([]domain.ReportedIssue, len(dbIssues))
|
||||||
|
for i, dbIssue := range dbIssues {
|
||||||
|
reportedIssues[i] = domain.ReportedIssue{
|
||||||
|
ID: dbIssue.ID,
|
||||||
|
Subject: dbIssue.Subject,
|
||||||
|
Description: dbIssue.Description,
|
||||||
|
CustomerID: dbIssue.CustomerID,
|
||||||
|
Status: dbIssue.Status,
|
||||||
|
CreatedAt: dbIssue.CreatedAt.Time,
|
||||||
|
UpdatedAt: dbIssue.UpdatedAt.Time,
|
||||||
|
// Add other fields as necessary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reportedIssues, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetAllIssues(ctx context.Context, limit, offset int) ([]dbgen.ReportedIssue, error) {
|
||||||
|
return s.repo.ListReportedIssues(ctx, int32(limit), int32(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateIssueStatus(ctx context.Context, issueID int64, status string) error {
|
||||||
|
validStatuses := map[string]bool{"pending": true, "in_progress": true, "resolved": true, "rejected": true}
|
||||||
|
if !validStatuses[status] {
|
||||||
|
return errors.New("invalid status")
|
||||||
|
}
|
||||||
|
return s.repo.UpdateReportedIssueStatus(ctx, issueID, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeleteIssue(ctx context.Context, issueID int64) error {
|
||||||
|
return s.repo.DeleteReportedIssue(ctx, issueID)
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
type Service interface {
|
type Service interface {
|
||||||
SaveLeague(ctx context.Context, l domain.League) error
|
SaveLeague(ctx context.Context, l domain.League) error
|
||||||
GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.League, error)
|
GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.League, error)
|
||||||
|
GetFeaturedLeagues(ctx context.Context) ([]domain.League, error)
|
||||||
SetLeagueActive(ctx context.Context, leagueId int64, isActive bool) error
|
SetLeagueActive(ctx context.Context, leagueId int64, isActive bool) error
|
||||||
UpdateLeague(ctx context.Context, league domain.UpdateLeague) error
|
UpdateLeague(ctx context.Context, league domain.UpdateLeague) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,10 @@ func (s *service) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter)
|
||||||
return s.store.GetAllLeagues(ctx, filter)
|
return s.store.GetAllLeagues(ctx, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *service) GetFeaturedLeagues(ctx context.Context) ([]domain.League, error) {
|
||||||
|
return s.store.GetFeaturedLeagues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *service) SetLeagueActive(ctx context.Context, leagueId int64, isActive bool) error {
|
func (s *service) SetLeagueActive(ctx context.Context, leagueId int64, isActive bool) error {
|
||||||
return s.store.SetLeagueActive(ctx, leagueId, isActive)
|
return s.store.SetLeagueActive(ctx, leagueId, isActive)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type NotificationStore interface {
|
type NotificationStore interface {
|
||||||
|
GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error)
|
||||||
|
GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error)
|
||||||
SendNotification(ctx context.Context, notification *domain.Notification) error
|
SendNotification(ctx context.Context, notification *domain.Notification) error
|
||||||
MarkAsRead(ctx context.Context, notificationID string, recipientID int64) error
|
MarkAsRead(ctx context.Context, notificationID string, recipientID int64) error
|
||||||
ListNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, error)
|
ListNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, error)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package notificationservice
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -11,23 +12,32 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
|
|
||||||
|
// "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws"
|
||||||
afro "github.com/amanuelabay/afrosms-go"
|
afro "github.com/amanuelabay/afrosms-go"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
repo repository.NotificationRepository
|
repo repository.NotificationRepository
|
||||||
Hub *ws.NotificationHub
|
Hub *ws.NotificationHub
|
||||||
connections sync.Map
|
notificationStore NotificationStore
|
||||||
notificationCh chan *domain.Notification
|
connections sync.Map
|
||||||
stopCh chan struct{}
|
notificationCh chan *domain.Notification
|
||||||
config *config.Config
|
stopCh chan struct{}
|
||||||
logger *slog.Logger
|
config *config.Config
|
||||||
|
logger *slog.Logger
|
||||||
|
redisClient *redis.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(repo repository.NotificationRepository, logger *slog.Logger, cfg *config.Config) *Service {
|
func New(repo repository.NotificationRepository, logger *slog.Logger, cfg *config.Config) *Service {
|
||||||
hub := ws.NewNotificationHub()
|
hub := ws.NewNotificationHub()
|
||||||
|
rdb := redis.NewClient(&redis.Options{
|
||||||
|
Addr: cfg.RedisAddr, // e.g., "redis:6379"
|
||||||
|
})
|
||||||
|
|
||||||
svc := &Service{
|
svc := &Service{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
Hub: hub,
|
Hub: hub,
|
||||||
|
|
@ -36,11 +46,13 @@ func New(repo repository.NotificationRepository, logger *slog.Logger, cfg *confi
|
||||||
notificationCh: make(chan *domain.Notification, 1000),
|
notificationCh: make(chan *domain.Notification, 1000),
|
||||||
stopCh: make(chan struct{}),
|
stopCh: make(chan struct{}),
|
||||||
config: cfg,
|
config: cfg,
|
||||||
|
redisClient: rdb,
|
||||||
}
|
}
|
||||||
|
|
||||||
go hub.Run()
|
go hub.Run()
|
||||||
go svc.startWorker()
|
go svc.startWorker()
|
||||||
go svc.startRetryWorker()
|
go svc.startRetryWorker()
|
||||||
|
go svc.RunRedisSubscriber(context.Background())
|
||||||
|
|
||||||
return svc
|
return svc
|
||||||
}
|
}
|
||||||
|
|
@ -255,7 +267,8 @@ func (s *Service) retryFailedNotifications() {
|
||||||
go func(notification *domain.Notification) {
|
go func(notification *domain.Notification) {
|
||||||
for attempt := 0; attempt < 3; attempt++ {
|
for attempt := 0; attempt < 3; attempt++ {
|
||||||
time.Sleep(time.Duration(attempt) * time.Second)
|
time.Sleep(time.Duration(attempt) * time.Second)
|
||||||
if notification.DeliveryChannel == domain.DeliveryChannelSMS {
|
switch notification.DeliveryChannel {
|
||||||
|
case domain.DeliveryChannelSMS:
|
||||||
if err := s.SendSMS(ctx, notification.RecipientID, notification.Payload.Message); err == nil {
|
if err := s.SendSMS(ctx, notification.RecipientID, notification.Payload.Message); err == nil {
|
||||||
notification.DeliveryStatus = domain.DeliveryStatusSent
|
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||||
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
|
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
|
||||||
|
|
@ -264,7 +277,7 @@ func (s *Service) retryFailedNotifications() {
|
||||||
s.logger.Info("[NotificationSvc.RetryFailedNotifications] Successfully retried notification", "id", notification.ID)
|
s.logger.Info("[NotificationSvc.RetryFailedNotifications] Successfully retried notification", "id", notification.ID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if notification.DeliveryChannel == domain.DeliveryChannelEmail {
|
case domain.DeliveryChannelEmail:
|
||||||
if err := s.SendEmail(ctx, notification.RecipientID, notification.Payload.Headline, notification.Payload.Message); err == nil {
|
if err := s.SendEmail(ctx, notification.RecipientID, notification.Payload.Headline, notification.Payload.Message); err == nil {
|
||||||
notification.DeliveryStatus = domain.DeliveryStatusSent
|
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||||
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
|
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
|
||||||
|
|
@ -287,3 +300,176 @@ func (s *Service) CountUnreadNotifications(ctx context.Context, recipient_id int
|
||||||
// func (s *Service) GetNotificationCounts(ctx context.Context, filter domain.ReportFilter) (total, read, unread int64, err error){
|
// func (s *Service) GetNotificationCounts(ctx context.Context, filter domain.ReportFilter) (total, read, unread int64, err error){
|
||||||
// return s.repo.Get(ctx, filter)
|
// return s.repo.Get(ctx, filter)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
func (s *Service) RunRedisSubscriber(ctx context.Context) {
|
||||||
|
pubsub := s.redisClient.Subscribe(ctx, "live_metrics")
|
||||||
|
defer pubsub.Close()
|
||||||
|
|
||||||
|
ch := pubsub.Channel()
|
||||||
|
for msg := range ch {
|
||||||
|
var parsed map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(msg.Payload), &parsed); err != nil {
|
||||||
|
s.logger.Error("invalid Redis message format", "payload", msg.Payload, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
eventType, _ := parsed["type"].(string)
|
||||||
|
payload := parsed["payload"]
|
||||||
|
recipientID, hasRecipient := parsed["recipient_id"]
|
||||||
|
recipientType, _ := parsed["recipient_type"].(string)
|
||||||
|
|
||||||
|
message := map[string]interface{}{
|
||||||
|
"type": eventType,
|
||||||
|
"payload": payload,
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasRecipient {
|
||||||
|
message["recipient_id"] = recipientID
|
||||||
|
message["recipient_type"] = recipientType
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Hub.Broadcast <- message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateLiveWalletMetrics(ctx context.Context, companies []domain.GetCompany, branches []domain.BranchWallet) error {
|
||||||
|
const key = "live_metrics"
|
||||||
|
|
||||||
|
companyBalances := make([]domain.CompanyWalletBalance, 0, len(companies))
|
||||||
|
for _, c := range companies {
|
||||||
|
companyBalances = append(companyBalances, domain.CompanyWalletBalance{
|
||||||
|
CompanyID: c.ID,
|
||||||
|
CompanyName: c.Name,
|
||||||
|
Balance: float64(c.WalletBalance.Float32()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
branchBalances := make([]domain.BranchWalletBalance, 0, len(branches))
|
||||||
|
for _, b := range branches {
|
||||||
|
branchBalances = append(branchBalances, domain.BranchWalletBalance{
|
||||||
|
BranchID: b.ID,
|
||||||
|
BranchName: b.Name,
|
||||||
|
CompanyID: b.CompanyID,
|
||||||
|
Balance: float64(b.Balance.Float32()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := domain.LiveWalletMetrics{
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
CompanyBalances: companyBalances,
|
||||||
|
BranchBalances: branchBalances,
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedData, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.redisClient.Set(ctx, key, updatedData, 0).Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.redisClient.Publish(ctx, key, updatedData).Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetLiveMetrics(ctx context.Context) (domain.LiveMetric, error) {
|
||||||
|
const key = "live_metrics"
|
||||||
|
var metric domain.LiveMetric
|
||||||
|
|
||||||
|
val, err := s.redisClient.Get(ctx, key).Result()
|
||||||
|
if err == redis.Nil {
|
||||||
|
// Key does not exist yet, return zero-valued struct
|
||||||
|
return domain.LiveMetric{}, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return domain.LiveMetric{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(val), &metric); err != nil {
|
||||||
|
return domain.LiveMetric{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return metric, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateLiveWalletMetricForWallet(ctx context.Context, wallet domain.Wallet) {
|
||||||
|
var (
|
||||||
|
payload domain.LiveWalletMetrics
|
||||||
|
event map[string]interface{}
|
||||||
|
key = "live_metrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Try company first
|
||||||
|
company, companyErr := s.notificationStore.GetCompanyByWalletID(ctx, wallet.ID)
|
||||||
|
if companyErr == nil {
|
||||||
|
payload = domain.LiveWalletMetrics{
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
CompanyBalances: []domain.CompanyWalletBalance{{
|
||||||
|
CompanyID: company.ID,
|
||||||
|
CompanyName: company.Name,
|
||||||
|
Balance: float64(wallet.Balance),
|
||||||
|
}},
|
||||||
|
BranchBalances: []domain.BranchWalletBalance{},
|
||||||
|
}
|
||||||
|
|
||||||
|
event = map[string]interface{}{
|
||||||
|
"type": "LIVE_WALLET_METRICS_UPDATE",
|
||||||
|
"recipient_id": company.ID,
|
||||||
|
"recipient_type": "company",
|
||||||
|
"payload": payload,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Try branch next
|
||||||
|
branch, branchErr := s.notificationStore.GetBranchByWalletID(ctx, wallet.ID)
|
||||||
|
if branchErr == nil {
|
||||||
|
payload = domain.LiveWalletMetrics{
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
CompanyBalances: []domain.CompanyWalletBalance{},
|
||||||
|
BranchBalances: []domain.BranchWalletBalance{{
|
||||||
|
BranchID: branch.ID,
|
||||||
|
BranchName: branch.Name,
|
||||||
|
CompanyID: branch.CompanyID,
|
||||||
|
Balance: float64(wallet.Balance),
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
event = map[string]interface{}{
|
||||||
|
"type": "LIVE_WALLET_METRICS_UPDATE",
|
||||||
|
"recipient_id": branch.ID,
|
||||||
|
"recipient_type": "branch",
|
||||||
|
"payload": payload,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Neither company nor branch matched this wallet
|
||||||
|
s.logger.Warn("wallet not linked to any company or branch", "walletID", wallet.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save latest metric to Redis
|
||||||
|
if jsonBytes, err := json.Marshal(payload); err == nil {
|
||||||
|
s.redisClient.Set(ctx, key, jsonBytes, 0)
|
||||||
|
} else {
|
||||||
|
s.logger.Error("failed to marshal wallet metrics payload", "walletID", wallet.ID, "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish via Redis
|
||||||
|
if jsonEvent, err := json.Marshal(event); err == nil {
|
||||||
|
s.redisClient.Publish(ctx, key, jsonEvent)
|
||||||
|
} else {
|
||||||
|
s.logger.Error("failed to marshal event payload", "walletID", wallet.ID, "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast over WebSocket
|
||||||
|
s.Hub.Broadcast <- event
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error) {
|
||||||
|
return s.notificationStore.GetCompanyByWalletID(ctx, walletID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error) {
|
||||||
|
return s.notificationStore.GetBranchByWalletID(ctx, walletID)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package odds
|
package odds
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo
|
||||||
|
|
||||||
walletID := wallets[0].ID
|
walletID := wallets[0].ID
|
||||||
currentBonus := float64(wallets[0].Balance)
|
currentBonus := float64(wallets[0].Balance)
|
||||||
err = s.walletSvc.AddToWallet(ctx, walletID, domain.Currency(int64((currentBonus+referral.RewardAmount)*100)))
|
_, err = s.walletSvc.AddToWallet(ctx, walletID, domain.ToCurrency(float32(currentBonus+referral.RewardAmount)), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to add referral reward to wallet", "walletID", walletID, "referrerID", referrerID, "error", err)
|
s.logger.Error("Failed to add referral reward to wallet", "walletID", walletID, "referrerID", referrerID, "error", err)
|
||||||
return err
|
return err
|
||||||
|
|
@ -162,7 +162,7 @@ func (s *Service) ProcessDepositBonus(ctx context.Context, userPhone string, amo
|
||||||
walletID := wallets[0].ID
|
walletID := wallets[0].ID
|
||||||
bonus := amount * (settings.CashbackPercentage / 100)
|
bonus := amount * (settings.CashbackPercentage / 100)
|
||||||
currentBonus := float64(wallets[0].Balance)
|
currentBonus := float64(wallets[0].Balance)
|
||||||
err = s.walletSvc.AddToWallet(ctx, walletID, domain.Currency(int64((currentBonus+bonus)*100)))
|
_, err = s.walletSvc.AddToWallet(ctx, walletID, domain.ToCurrency(float32(currentBonus+bonus)), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to add deposit bonus to wallet", "walletID", walletID, "userID", userID, "bonus", bonus, "error", err)
|
s.logger.Error("Failed to add deposit bonus to wallet", "walletID", walletID, "userID", userID, "bonus", bonus, "error", err)
|
||||||
return err
|
return err
|
||||||
|
|
@ -216,7 +216,7 @@ func (s *Service) ProcessBetReferral(ctx context.Context, userPhone string, betA
|
||||||
|
|
||||||
walletID := wallets[0].ID
|
walletID := wallets[0].ID
|
||||||
currentBalance := float64(wallets[0].Balance)
|
currentBalance := float64(wallets[0].Balance)
|
||||||
err = s.walletSvc.AddToWallet(ctx, walletID, domain.Currency(int64((currentBalance+bonus)*100)))
|
_, err = s.walletSvc.AddToWallet(ctx, walletID, domain.ToCurrency(float32(currentBalance+bonus)), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to add bet referral bonus to wallet", "walletID", walletID, "referrerID", referrerID, "bonus", bonus, "error", err)
|
s.logger.Error("Failed to add bet referral bonus to wallet", "walletID", walletID, "referrerID", referrerID, "bonus", bonus, "error", err)
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,14 @@ package report
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/csv"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
|
@ -454,32 +459,299 @@ 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) {
|
func (s *Service) GenerateReport(ctx context.Context, period string) error {
|
||||||
now := time.Now()
|
data, err := s.fetchReportData(ctx, period)
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, err
|
return fmt.Errorf("fetch data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.repo.SaveReport(report); err != nil {
|
filePath := fmt.Sprintf("/host-desktop/report_%s_%s.csv", period, time.Now().Format("2006-01-02_15-04"))
|
||||||
return nil, err
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create file: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
writer := csv.NewWriter(file)
|
||||||
|
defer writer.Flush()
|
||||||
|
|
||||||
|
// Summary section
|
||||||
|
writer.Write([]string{"Sports Betting Reports (Periodic)"})
|
||||||
|
writer.Write([]string{"Period", "Total Bets", "Total Cash Made", "Total Cash Out", "Total Cash Backs", "Total Deposits", "Total Withdrawals", "Total Tickets"})
|
||||||
|
writer.Write([]string{
|
||||||
|
period,
|
||||||
|
fmt.Sprintf("%d", data.TotalBets),
|
||||||
|
fmt.Sprintf("%.2f", data.TotalCashIn),
|
||||||
|
fmt.Sprintf("%.2f", data.TotalCashOut),
|
||||||
|
fmt.Sprintf("%.2f", data.CashBacks),
|
||||||
|
fmt.Sprintf("%.2f", data.Deposits),
|
||||||
|
fmt.Sprintf("%.2f", data.Withdrawals),
|
||||||
|
fmt.Sprintf("%d", data.TotalTickets),
|
||||||
|
})
|
||||||
|
|
||||||
|
writer.Write([]string{}) // Empty line for spacing
|
||||||
|
|
||||||
|
// Virtual Game Summary section
|
||||||
|
writer.Write([]string{"Virtual Game Reports (Periodic)"})
|
||||||
|
writer.Write([]string{"Game Name", "Number of Bets", "Total Transaction Sum"})
|
||||||
|
for _, row := range data.VirtualGameStats {
|
||||||
|
writer.Write([]string{
|
||||||
|
row.GameName,
|
||||||
|
fmt.Sprintf("%d", row.NumBets),
|
||||||
|
fmt.Sprintf("%.2f", row.TotalTransaction),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return report, nil
|
writer.Write([]string{}) // Empty line
|
||||||
|
writer.Write([]string{"Company Reports (Periodic)"})
|
||||||
|
writer.Write([]string{"Company ID", "Company Name", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||||
|
for _, cr := range data.CompanyReports {
|
||||||
|
writer.Write([]string{
|
||||||
|
fmt.Sprintf("%d", cr.CompanyID),
|
||||||
|
cr.CompanyName,
|
||||||
|
fmt.Sprintf("%d", cr.TotalBets),
|
||||||
|
fmt.Sprintf("%.2f", cr.TotalCashIn),
|
||||||
|
fmt.Sprintf("%.2f", cr.TotalCashOut),
|
||||||
|
fmt.Sprintf("%.2f", cr.TotalCashBacks),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write([]string{}) // Empty line
|
||||||
|
writer.Write([]string{"Branch Reports (Periodic)"})
|
||||||
|
writer.Write([]string{"Branch ID", "Branch Name", "Company ID", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||||
|
for _, br := range data.BranchReports {
|
||||||
|
writer.Write([]string{
|
||||||
|
fmt.Sprintf("%d", br.BranchID),
|
||||||
|
br.BranchName,
|
||||||
|
fmt.Sprintf("%d", br.CompanyID),
|
||||||
|
fmt.Sprintf("%d", br.TotalBets),
|
||||||
|
fmt.Sprintf("%.2f", br.TotalCashIn),
|
||||||
|
fmt.Sprintf("%.2f", br.TotalCashOut),
|
||||||
|
fmt.Sprintf("%.2f", br.TotalCashBacks),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalBets int64
|
||||||
|
var totalCashIn, totalCashOut, totalCashBacks float64
|
||||||
|
for _, cr := range data.CompanyReports {
|
||||||
|
totalBets += cr.TotalBets
|
||||||
|
totalCashIn += cr.TotalCashIn
|
||||||
|
totalCashOut += cr.TotalCashOut
|
||||||
|
totalCashBacks += cr.TotalCashBacks
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write([]string{})
|
||||||
|
writer.Write([]string{"Total Summary"})
|
||||||
|
writer.Write([]string{"Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||||
|
writer.Write([]string{
|
||||||
|
fmt.Sprintf("%d", totalBets),
|
||||||
|
fmt.Sprintf("%.2f", totalCashIn),
|
||||||
|
fmt.Sprintf("%.2f", totalCashOut),
|
||||||
|
fmt.Sprintf("%.2f", totalCashBacks),
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) fetchReportData(ctx context.Context, period string) (domain.ReportData, error) {
|
||||||
|
from, to := getTimeRange(period)
|
||||||
|
// companyID := int64(0)
|
||||||
|
|
||||||
|
// Basic metrics
|
||||||
|
totalBets, _ := s.repo.GetTotalBetsMadeInRange(ctx, from, to)
|
||||||
|
cashIn, _ := s.repo.GetTotalCashMadeInRange(ctx, from, to)
|
||||||
|
cashOut, _ := s.repo.GetTotalCashOutInRange(ctx, from, to)
|
||||||
|
cashBacks, _ := s.repo.GetTotalCashBacksInRange(ctx, from, to)
|
||||||
|
|
||||||
|
// Wallet Transactions
|
||||||
|
transactions, _ := s.repo.GetWalletTransactionsInRange(ctx, from, to)
|
||||||
|
var totalDeposits, totalWithdrawals float64
|
||||||
|
for _, tx := range transactions {
|
||||||
|
switch strings.ToLower(tx.Type.String) {
|
||||||
|
case "deposit":
|
||||||
|
totalDeposits += float64(tx.TotalAmount)
|
||||||
|
case "withdraw":
|
||||||
|
totalWithdrawals += float64(tx.TotalAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ticket Count
|
||||||
|
totalTickets, _ := s.repo.GetAllTicketsInRange(ctx, from, to)
|
||||||
|
|
||||||
|
// Virtual Game Summary
|
||||||
|
virtualGameStats, _ := s.repo.GetVirtualGameSummaryInRange(ctx, from, to)
|
||||||
|
|
||||||
|
// Convert []dbgen.GetVirtualGameSummaryInRangeRow to []domain.VirtualGameStat
|
||||||
|
var virtualGameStatsDomain []domain.VirtualGameStat
|
||||||
|
for _, row := range virtualGameStats {
|
||||||
|
var totalTransaction float64
|
||||||
|
switch v := row.TotalTransactionSum.(type) {
|
||||||
|
case string:
|
||||||
|
val, err := strconv.ParseFloat(v, 64)
|
||||||
|
if err == nil {
|
||||||
|
totalTransaction = val
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
totalTransaction = v
|
||||||
|
case int:
|
||||||
|
totalTransaction = float64(v)
|
||||||
|
default:
|
||||||
|
totalTransaction = 0
|
||||||
|
}
|
||||||
|
virtualGameStatsDomain = append(virtualGameStatsDomain, domain.VirtualGameStat{
|
||||||
|
GameName: row.GameName,
|
||||||
|
NumBets: row.NumberOfBets,
|
||||||
|
TotalTransaction: totalTransaction,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
companyRows, _ := s.repo.GetCompanyWiseReport(ctx, from, to)
|
||||||
|
var companyReports []domain.CompanyReport
|
||||||
|
for _, row := range companyRows {
|
||||||
|
var totalCashIn, totalCashOut, totalCashBacks float64
|
||||||
|
switch v := row.TotalCashMade.(type) {
|
||||||
|
case string:
|
||||||
|
val, err := strconv.ParseFloat(v, 64)
|
||||||
|
if err == nil {
|
||||||
|
totalCashIn = val
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
totalCashIn = v
|
||||||
|
case int:
|
||||||
|
totalCashIn = float64(v)
|
||||||
|
default:
|
||||||
|
totalCashIn = 0
|
||||||
|
}
|
||||||
|
switch v := row.TotalCashOut.(type) {
|
||||||
|
case string:
|
||||||
|
val, err := strconv.ParseFloat(v, 64)
|
||||||
|
if err == nil {
|
||||||
|
totalCashOut = val
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
totalCashOut = v
|
||||||
|
case int:
|
||||||
|
totalCashOut = float64(v)
|
||||||
|
default:
|
||||||
|
totalCashOut = 0
|
||||||
|
}
|
||||||
|
switch v := row.TotalCashBacks.(type) {
|
||||||
|
case string:
|
||||||
|
val, err := strconv.ParseFloat(v, 64)
|
||||||
|
if err == nil {
|
||||||
|
totalCashBacks = val
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
totalCashBacks = v
|
||||||
|
case int:
|
||||||
|
totalCashBacks = float64(v)
|
||||||
|
default:
|
||||||
|
totalCashBacks = 0
|
||||||
|
}
|
||||||
|
companyReports = append(companyReports, domain.CompanyReport{
|
||||||
|
CompanyID: row.CompanyID.Int64,
|
||||||
|
CompanyName: row.CompanyName,
|
||||||
|
TotalBets: row.TotalBets,
|
||||||
|
TotalCashIn: totalCashIn,
|
||||||
|
TotalCashOut: totalCashOut,
|
||||||
|
TotalCashBacks: totalCashBacks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
branchRows, _ := s.repo.GetBranchWiseReport(ctx, from, to)
|
||||||
|
var branchReports []domain.BranchReport
|
||||||
|
for _, row := range branchRows {
|
||||||
|
var totalCashIn, totalCashOut, totalCashBacks float64
|
||||||
|
switch v := row.TotalCashMade.(type) {
|
||||||
|
case string:
|
||||||
|
val, err := strconv.ParseFloat(v, 64)
|
||||||
|
if err == nil {
|
||||||
|
totalCashIn = val
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
totalCashIn = v
|
||||||
|
case int:
|
||||||
|
totalCashIn = float64(v)
|
||||||
|
default:
|
||||||
|
totalCashIn = 0
|
||||||
|
}
|
||||||
|
switch v := row.TotalCashOut.(type) {
|
||||||
|
case string:
|
||||||
|
val, err := strconv.ParseFloat(v, 64)
|
||||||
|
if err == nil {
|
||||||
|
totalCashOut = val
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
totalCashOut = v
|
||||||
|
case int:
|
||||||
|
totalCashOut = float64(v)
|
||||||
|
default:
|
||||||
|
totalCashOut = 0
|
||||||
|
}
|
||||||
|
switch v := row.TotalCashBacks.(type) {
|
||||||
|
case string:
|
||||||
|
val, err := strconv.ParseFloat(v, 64)
|
||||||
|
if err == nil {
|
||||||
|
totalCashBacks = val
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
totalCashBacks = v
|
||||||
|
case int:
|
||||||
|
totalCashBacks = float64(v)
|
||||||
|
default:
|
||||||
|
totalCashBacks = 0
|
||||||
|
}
|
||||||
|
branchReports = append(branchReports, domain.BranchReport{
|
||||||
|
BranchID: row.BranchID.Int64,
|
||||||
|
BranchName: row.BranchName,
|
||||||
|
CompanyID: row.CompanyID,
|
||||||
|
TotalBets: row.TotalBets,
|
||||||
|
TotalCashIn: totalCashIn,
|
||||||
|
TotalCashOut: totalCashOut,
|
||||||
|
TotalCashBacks: totalCashBacks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.ReportData{
|
||||||
|
TotalBets: totalBets,
|
||||||
|
TotalCashIn: cashIn,
|
||||||
|
TotalCashOut: cashOut,
|
||||||
|
CashBacks: cashBacks,
|
||||||
|
Deposits: totalDeposits,
|
||||||
|
Withdrawals: totalWithdrawals,
|
||||||
|
TotalTickets: totalTickets.TotalTickets,
|
||||||
|
VirtualGameStats: virtualGameStatsDomain,
|
||||||
|
CompanyReports: companyReports,
|
||||||
|
BranchReports: branchReports,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTimeRange(period string) (time.Time, time.Time) {
|
||||||
|
now := time.Now()
|
||||||
|
switch strings.ToLower(period) {
|
||||||
|
case "daily":
|
||||||
|
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||||
|
end := start.Add(5 * time.Minute)
|
||||||
|
return start, end
|
||||||
|
case "weekly":
|
||||||
|
weekday := int(now.Weekday())
|
||||||
|
if weekday == 0 {
|
||||||
|
weekday = 7
|
||||||
|
}
|
||||||
|
start := now.AddDate(0, 0, -weekday+1)
|
||||||
|
start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, now.Location())
|
||||||
|
end := start.AddDate(0, 0, 7)
|
||||||
|
return start, end
|
||||||
|
case "monthly":
|
||||||
|
start := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||||
|
end := start.AddDate(0, 1, 0)
|
||||||
|
return start, end
|
||||||
|
default:
|
||||||
|
// Default to daily
|
||||||
|
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||||
|
end := start.Add(24 * time.Hour)
|
||||||
|
return start, end
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -17,30 +16,33 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||||
|
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
repo *repository.Store
|
repo *repository.Store
|
||||||
config *config.Config
|
config *config.Config
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
client *http.Client
|
client *http.Client
|
||||||
betSvc bet.Service
|
betSvc bet.Service
|
||||||
oddSvc odds.ServiceImpl
|
oddSvc odds.ServiceImpl
|
||||||
eventSvc event.Service
|
eventSvc event.Service
|
||||||
leagueSvc league.Service
|
leagueSvc league.Service
|
||||||
|
notificationSvc *notificationservice.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(repo *repository.Store, cfg *config.Config, logger *slog.Logger, betSvc bet.Service, oddSvc odds.ServiceImpl, eventSvc event.Service, leagueSvc league.Service) *Service {
|
func NewService(repo *repository.Store, cfg *config.Config, logger *slog.Logger, betSvc bet.Service, oddSvc odds.ServiceImpl, eventSvc event.Service, leagueSvc league.Service, notificationSvc *notificationservice.Service) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
client: &http.Client{Timeout: 10 * time.Second},
|
client: &http.Client{Timeout: 10 * time.Second},
|
||||||
betSvc: betSvc,
|
betSvc: betSvc,
|
||||||
oddSvc: oddSvc,
|
oddSvc: oddSvc,
|
||||||
eventSvc: eventSvc,
|
eventSvc: eventSvc,
|
||||||
leagueSvc: leagueSvc,
|
leagueSvc: leagueSvc,
|
||||||
|
notificationSvc: notificationSvc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,6 +50,127 @@ var (
|
||||||
ErrEventIsNotActive = fmt.Errorf("event has been cancelled or postponed")
|
ErrEventIsNotActive = fmt.Errorf("event has been cancelled or postponed")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s *Service) UpdateResultForOutcomes(ctx context.Context, eventID int64, resultRes json.RawMessage, sportID int64) error {
|
||||||
|
// TODO: Optimize this since there could be many outcomes with the same event_id and market_id that could be updated the same time
|
||||||
|
outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID, true)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get pending bet outcomes", "error", err)
|
||||||
|
return fmt.Errorf("failed to get pending bet outcomes for event %d: %w", eventID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(outcomes) == 0 {
|
||||||
|
s.logger.Info("No bets have been placed on event", "event", eventID)
|
||||||
|
}
|
||||||
|
// if len(outcomes) == 0 {
|
||||||
|
// fmt.Printf("🕛 No bets have been placed on event %v (%d/%d) \n", eventID, i+1, len(events))
|
||||||
|
// } else {
|
||||||
|
// fmt.Printf("✅ %d bets have been placed on event %v (%d/%d) \n", len(outcomes), event.ID, i+1, len(events))
|
||||||
|
// }
|
||||||
|
|
||||||
|
for _, outcome := range outcomes {
|
||||||
|
if outcome.Expires.After(time.Now()) {
|
||||||
|
s.logger.Warn("Outcome is not expired yet", "event_id", outcome.EventID, "outcome_id", outcome.ID)
|
||||||
|
return fmt.Errorf("Outcome has not expired yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
parseResult, err := s.parseResult(ctx, resultRes, outcome, sportID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to parse result", "event_id", outcome.EventID, "outcome_id", outcome.ID, "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outcome, err = s.betSvc.UpdateBetOutcomeStatus(ctx, outcome.ID, parseResult.Status)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if outcome.Status == domain.OUTCOME_STATUS_ERROR || outcome.Status == domain.OUTCOME_STATUS_PENDING {
|
||||||
|
s.logger.Error("Outcome is pending or error", "event_id", outcome.EventID, "outcome_id", outcome.ID)
|
||||||
|
return fmt.Errorf("Error while updating outcome")
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := s.betSvc.CheckBetOutcomeForBet(ctx, outcome.BetID)
|
||||||
|
if err != nil {
|
||||||
|
if err != bet.ErrOutcomesNotCompleted {
|
||||||
|
s.logger.Error("Failed to check bet outcome for bet", "event_id", outcome.EventID, "error", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.logger.Info("Updating bet status", "bet_id", outcome.BetID, "status", status.String())
|
||||||
|
err = s.betSvc.UpdateStatus(ctx, outcome.BetID, status)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to update bet status", "event id", outcome.EventID, "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) RefundAllOutcomes(ctx context.Context, eventID int64) error {
|
||||||
|
outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID, false)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get pending bet outcomes", "error", err)
|
||||||
|
return fmt.Errorf("failed to get pending bet outcomes for event %d: %w", eventID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(outcomes) == 0 {
|
||||||
|
s.logger.Info("No bets have been placed on event", "event", eventID)
|
||||||
|
}
|
||||||
|
|
||||||
|
outcomes, err = s.betSvc.UpdateBetOutcomeStatusForEvent(ctx, eventID, domain.OUTCOME_STATUS_VOID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to update all outcomes for event")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all the unique bet_ids and how many outcomes have this bet_id
|
||||||
|
betIDSet := make(map[int64]int64)
|
||||||
|
for _, outcome := range outcomes {
|
||||||
|
betIDSet[outcome.BetID] += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for betID := range betIDSet {
|
||||||
|
status, err := s.betSvc.CheckBetOutcomeForBet(ctx, betID)
|
||||||
|
if err != nil {
|
||||||
|
if err != bet.ErrOutcomesNotCompleted {
|
||||||
|
s.logger.Error("Failed to check bet outcome for bet", "event_id", eventID, "error", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.betSvc.UpdateStatus(ctx, betID, status)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to update bet status", "event id", eventID, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
// for _, outcome := range outcomes {
|
||||||
|
// outcome, err = s.betSvc.UpdateBetOutcomeStatus(ctx, outcome.ID, domain.OUTCOME_STATUS_VOID)
|
||||||
|
// if err != nil {
|
||||||
|
// s.logger.Error("Failed to refund all outcome status", "bet_outcome_id", outcome.ID, "error", err)
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Check if all the bet outcomes have been set to refund for
|
||||||
|
// status, err := s.betSvc.CheckBetOutcomeForBet(ctx, outcome.BetID)
|
||||||
|
// if err != nil {
|
||||||
|
// if err != bet.ErrOutcomesNotCompleted {
|
||||||
|
// s.logger.Error("Failed to check bet outcome for bet", "event_id", outcome.EventID, "error", err)
|
||||||
|
// }
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// err = s.betSvc.UpdateStatus(ctx, outcome.BetID, domain.OUTCOME_STATUS_VOID)
|
||||||
|
|
||||||
|
// if err != nil {
|
||||||
|
// s.logger.Error("Failed to update bet status", "event id", outcome.EventID, "error", err)
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
||||||
// TODO: Optimize this because there could be many bet outcomes for the same odd
|
// TODO: Optimize this because there could be many bet outcomes for the same odd
|
||||||
// Take market id and match result as param and update all the bet outcomes at the same time
|
// Take market id and match result as param and update all the bet outcomes at the same time
|
||||||
|
|
@ -58,29 +181,15 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
fmt.Printf("⚠️ Expired Events: %d \n", len(events))
|
fmt.Printf("⚠️ Expired Events: %d \n", len(events))
|
||||||
removed := 0
|
removed := 0
|
||||||
errs := make([]error, 0, len(events))
|
|
||||||
for i, event := range events {
|
for _, event := range events {
|
||||||
|
|
||||||
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to parse event id")
|
s.logger.Error("Failed to parse", "eventID", event.ID, "err", err)
|
||||||
errs = append(errs, fmt.Errorf("failed to parse event id %s: %w", event.ID, err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to get pending bet outcomes", "error", err)
|
|
||||||
errs = append(errs, fmt.Errorf("failed to get pending bet outcomes for event %d: %w", eventID, err))
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(outcomes) == 0 {
|
|
||||||
fmt.Printf("🕛 No bets have been placed on event %v (%d/%d) \n", event.ID, i+1, len(events))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("✅ %d bets have been placed on event %v (%d/%d) \n", len(outcomes), event.ID, i+1, len(events))
|
|
||||||
}
|
|
||||||
|
|
||||||
isDeleted := true
|
|
||||||
result, err := s.fetchResult(ctx, eventID)
|
result, err := s.fetchResult(ctx, eventID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrEventIsNotActive {
|
if err == ErrEventIsNotActive {
|
||||||
|
|
@ -108,78 +217,41 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Figure out what to do with the events that have been cancelled or postponed, etc...
|
// TODO: Figure out what to do with the events that have been cancelled or postponed, etc...
|
||||||
if timeStatusParsed != int64(domain.TIME_STATUS_ENDED) {
|
// if timeStatusParsed != int64(domain.TIME_STATUS_ENDED) {
|
||||||
s.logger.Warn("Event is not ended yet", "event_id", eventID, "time_status", commonResp.TimeStatus)
|
// s.logger.Warn("Event is not ended yet", "event_id", eventID, "time_status", commonResp.TimeStatus)
|
||||||
fmt.Printf("⚠️ Event %v is not ended yet (%d/%d) \n", event.ID, i+1, len(events))
|
// fmt.Printf("⚠️ Event %v is not ended yet (%d/%d) \n", event.ID, i+1, len(events))
|
||||||
isDeleted = false
|
// isDeleted = false
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
// notification := &domain.Notification{
|
||||||
|
// RecipientID: recipientID,
|
||||||
|
// Type: domain.NOTIFICATION_TYPE_WALLET,
|
||||||
|
// Level: domain.NotificationLevelWarning,
|
||||||
|
// Reciever: domain.NotificationRecieverSideAdmin,
|
||||||
|
// DeliveryChannel: domain.DeliveryChannelInApp,
|
||||||
|
// Payload: domain.NotificationPayload{
|
||||||
|
// Headline: "Wallet Threshold Alert",
|
||||||
|
// Message: message,
|
||||||
|
// },
|
||||||
|
// Priority: 2, // Medium priority
|
||||||
|
// }
|
||||||
|
|
||||||
|
switch timeStatusParsed {
|
||||||
|
case int64(domain.TIME_STATUS_NOT_STARTED), int64(domain.TIME_STATUS_IN_PLAY):
|
||||||
continue
|
continue
|
||||||
}
|
|
||||||
|
|
||||||
for j, outcome := range outcomes {
|
case int64(domain.TIME_STATUS_TO_BE_FIXED):
|
||||||
fmt.Printf("⚙️ Processing 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
|
s.logger.Warn("Event needs to be rescheduled or corrected", "event_id", eventID)
|
||||||
outcome.MarketName,
|
// Admin users will be able to review the events
|
||||||
event.HomeTeam+" "+event.AwayTeam, event.ID,
|
|
||||||
j+1, len(outcomes))
|
|
||||||
|
|
||||||
if outcome.Expires.After(time.Now()) {
|
case int64(domain.TIME_STATUS_ENDED), int64(domain.TIME_STATUS_WALKOVER), int64(domain.TIME_STATUS_DECIDED_BY_FA):
|
||||||
isDeleted = false
|
err = s.UpdateResultForOutcomes(ctx, eventID, result.Results[0], sportID)
|
||||||
s.logger.Warn("Outcome is not expired yet", "event_id", event.ID, "outcome_id", outcome.ID)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
parseResult, err := s.parseResult(ctx, result.Results[0], outcome, sportID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
isDeleted = false
|
s.logger.Error("Error while updating result for event", "event_id", eventID)
|
||||||
s.logger.Error("Failed to parse result", "event_id", outcome.EventID, "outcome_id", outcome.ID, "error", err)
|
|
||||||
errs = append(errs, fmt.Errorf("failed to parse result for event %d: %w", outcome.EventID, err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
outcome, err = s.betSvc.UpdateBetOutcomeStatus(ctx, outcome.ID, parseResult.Status)
|
|
||||||
if err != nil {
|
|
||||||
isDeleted = false
|
|
||||||
s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if outcome.Status == domain.OUTCOME_STATUS_ERROR || outcome.Status == domain.OUTCOME_STATUS_PENDING {
|
|
||||||
fmt.Printf("❌ Error while updating 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
|
|
||||||
outcome.MarketName,
|
|
||||||
event.HomeTeam+" "+event.AwayTeam, event.ID,
|
|
||||||
j+1, len(outcomes))
|
|
||||||
|
|
||||||
s.logger.Error("Outcome is pending or error", "event_id", outcome.EventID, "outcome_id", outcome.ID)
|
|
||||||
isDeleted = false
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("✅ Successfully updated 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
|
s.logger.Info("Removing Event", "eventID", event.ID)
|
||||||
outcome.MarketName,
|
|
||||||
event.HomeTeam+" "+event.AwayTeam, event.ID,
|
|
||||||
j+1, len(outcomes))
|
|
||||||
|
|
||||||
status, err := s.betSvc.CheckBetOutcomeForBet(ctx, outcome.BetID)
|
|
||||||
if err != nil {
|
|
||||||
if err != bet.ErrOutcomesNotCompleted {
|
|
||||||
s.logger.Error("Failed to check bet outcome for bet", "event_id", outcome.EventID, "error", err)
|
|
||||||
}
|
|
||||||
isDeleted = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Printf("🧾 Updating bet %v - event %v (%d/%d) to %v\n", outcome.BetID, event.ID, j+1, len(outcomes), status.String())
|
|
||||||
err = s.betSvc.UpdateStatus(ctx, outcome.BetID, status)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to update bet status", "event id", outcome.EventID, "error", err)
|
|
||||||
isDeleted = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Printf("✅ Successfully updated 🎫 Bet %v - event %v(%v) (%d/%d) \n",
|
|
||||||
outcome.BetID,
|
|
||||||
event.HomeTeam+" "+event.AwayTeam, event.ID,
|
|
||||||
j+1, len(outcomes))
|
|
||||||
|
|
||||||
}
|
|
||||||
if isDeleted {
|
|
||||||
removed += 1
|
|
||||||
fmt.Printf("⚠️ Removing Event %v \n", event.ID)
|
|
||||||
err = s.repo.DeleteEvent(ctx, event.ID)
|
err = s.repo.DeleteEvent(ctx, event.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err)
|
s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err)
|
||||||
|
|
@ -190,17 +262,91 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
||||||
s.logger.Error("Failed to remove odds for event", "event_id", event.ID, "error", err)
|
s.logger.Error("Failed to remove odds for event", "event_id", event.ID, "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
removed += 1
|
||||||
|
case int64(domain.TIME_STATUS_ABANDONED), int64(domain.TIME_STATUS_CANCELLED), int64(domain.TIME_STATUS_REMOVED):
|
||||||
|
s.logger.Info("Event abandoned/cancelled/removed", "event_id", eventID, "status", timeStatusParsed)
|
||||||
|
err = s.RefundAllOutcomes(ctx, eventID)
|
||||||
|
|
||||||
|
s.logger.Info("Removing Event", "eventID", event.ID)
|
||||||
|
err = s.repo.DeleteEvent(ctx, event.ID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.repo.DeleteOddsForEvent(ctx, event.ID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to remove odds for event", "event_id", event.ID, "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
removed += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for j, outcome := range outcomes {
|
||||||
|
|
||||||
|
// fmt.Printf("⚙️ Processing 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
|
||||||
|
// outcome.MarketName,
|
||||||
|
// event.HomeTeam+" "+event.AwayTeam, event.ID,
|
||||||
|
// j+1, len(outcomes))
|
||||||
|
|
||||||
|
// if outcome.Expires.After(time.Now()) {
|
||||||
|
// isDeleted = false
|
||||||
|
// s.logger.Warn("Outcome is not expired yet", "event_id", event.ID, "outcome_id", outcome.ID)
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
// parseResult, err := s.parseResult(ctx, result.Results[0], outcome, sportID)
|
||||||
|
// if err != nil {
|
||||||
|
// isDeleted = false
|
||||||
|
// s.logger.Error("Failed to parse result", "event_id", outcome.EventID, "outcome_id", outcome.ID, "error", err)
|
||||||
|
// errs = append(errs, fmt.Errorf("failed to parse result for event %d: %w", outcome.EventID, err))
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
// outcome, err = s.betSvc.UpdateBetOutcomeStatus(ctx, outcome.ID, parseResult.Status)
|
||||||
|
// if err != nil {
|
||||||
|
// isDeleted = false
|
||||||
|
// s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err)
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// if outcome.Status == domain.OUTCOME_STATUS_ERROR || outcome.Status == domain.OUTCOME_STATUS_PENDING {
|
||||||
|
// fmt.Printf("❌ Error while updating 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
|
||||||
|
// outcome.MarketName,
|
||||||
|
// event.HomeTeam+" "+event.AwayTeam, event.ID,
|
||||||
|
// j+1, len(outcomes))
|
||||||
|
|
||||||
|
// s.logger.Error("Outcome is pending or error", "event_id", outcome.EventID, "outcome_id", outcome.ID)
|
||||||
|
// isDeleted = false
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fmt.Printf("✅ Successfully updated 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
|
||||||
|
// outcome.MarketName,
|
||||||
|
// event.HomeTeam+" "+event.AwayTeam, event.ID,
|
||||||
|
// j+1, len(outcomes))
|
||||||
|
|
||||||
|
// status, err := s.betSvc.CheckBetOutcomeForBet(ctx, outcome.BetID)
|
||||||
|
// if err != nil {
|
||||||
|
// if err != bet.ErrOutcomesNotCompleted {
|
||||||
|
// s.logger.Error("Failed to check bet outcome for bet", "event_id", outcome.EventID, "error", err)
|
||||||
|
// }
|
||||||
|
// isDeleted = false
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fmt.Printf("🧾 Updating bet %v - event %v (%d/%d) to %v\n", outcome.BetID, event.ID, j+1, len(outcomes), status.String())
|
||||||
|
// err = s.betSvc.UpdateStatus(ctx, outcome.BetID, status)
|
||||||
|
// if err != nil {
|
||||||
|
// s.logger.Error("Failed to update bet status", "event id", outcome.EventID, "error", err)
|
||||||
|
// isDeleted = false
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// fmt.Printf("✅ Successfully updated 🎫 Bet %v - event %v(%v) (%d/%d) \n",
|
||||||
|
// outcome.BetID,
|
||||||
|
// event.HomeTeam+" "+event.AwayTeam, event.ID,
|
||||||
|
// j+1, len(outcomes))
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
fmt.Printf("🗑️ Removed Events: %d \n", removed)
|
s.logger.Info("Total Number of Removed Events", "count", removed)
|
||||||
if len(errs) > 0 {
|
|
||||||
s.logger.Error("Errors occurred while processing results", "errors", errs)
|
|
||||||
for _, err := range errs {
|
|
||||||
fmt.Println("Error:", err)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("errors occurred while processing results: %v", errs)
|
|
||||||
}
|
|
||||||
s.logger.Info("Successfully processed results", "removed_events", removed, "total_events", len(events))
|
s.logger.Info("Successfully processed results", "removed_events", removed, "total_events", len(events))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -211,10 +357,9 @@ func (s *Service) CheckAndUpdateExpiredEvents(ctx context.Context) (int64, error
|
||||||
s.logger.Error("Failed to fetch events")
|
s.logger.Error("Failed to fetch events")
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
fmt.Printf("⚠️ Expired Events: %d \n", len(events))
|
|
||||||
updated := 0
|
updated := 0
|
||||||
for i, event := range events {
|
for _, event := range events {
|
||||||
fmt.Printf("⚙️ Processing event %v (%d/%d) \n", event.ID, i+1, len(events))
|
// fmt.Printf("⚙️ Processing event %v (%d/%d) \n", event.ID, i+1, len(events))
|
||||||
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to parse event id")
|
s.logger.Error("Failed to parse event id")
|
||||||
|
|
@ -232,7 +377,6 @@ func (s *Service) CheckAndUpdateExpiredEvents(ctx context.Context) (int64, error
|
||||||
}
|
}
|
||||||
if result.Success != 1 || len(result.Results) == 0 {
|
if result.Success != 1 || len(result.Results) == 0 {
|
||||||
s.logger.Error("Invalid API response", "event_id", eventID)
|
s.logger.Error("Invalid API response", "event_id", eventID)
|
||||||
fmt.Printf("⚠️ Invalid API response for event %v \n", result)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,12 +426,13 @@ func (s *Service) CheckAndUpdateExpiredEvents(ctx context.Context) (int64, error
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
updated++
|
updated++
|
||||||
fmt.Printf("✅ Successfully updated event %v to %v (%d/%d) \n", event.ID, eventStatus, i+1, len(events))
|
// fmt.Printf("✅ Successfully updated event %v to %v (%d/%d) \n", event.ID, eventStatus, i+1, len(events))
|
||||||
|
s.logger.Info("Updated Event Status", "event ID", event.ID, "status", eventStatus)
|
||||||
// Update the league because the league country code is only found on the result response
|
// Update the league because the league country code is only found on the result response
|
||||||
leagueID, err := strconv.ParseInt(commonResp.League.ID, 10, 64)
|
leagueID, err := strconv.ParseInt(commonResp.League.ID, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ Invalid league id, leagueID %v", commonResp.League.ID)
|
// log.Printf("❌ Invalid league id, leagueID %v", commonResp.League.ID)
|
||||||
|
s.logger.Error("Invalid League ID", "leagueID", commonResp.League.ID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,12 +449,11 @@ func (s *Service) CheckAndUpdateExpiredEvents(ctx context.Context) (int64, error
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ Error Updating League %v", commonResp.League.Name)
|
s.logger.Error("Error Updating League", "League Name", commonResp.League.Name, "err", err)
|
||||||
log.Printf("err:%v", err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.Printf("✅ Updated League %v with country code %v \n", leagueID, commonResp.League.CC)
|
// fmt.Printf("✅ Updated League %v with country code %v \n", leagueID, commonResp.League.CC)
|
||||||
|
s.logger.Info("Updated League with country code", "leagueID", leagueID, "code", commonResp.League.CC)
|
||||||
}
|
}
|
||||||
|
|
||||||
if updated == 0 {
|
if updated == 0 {
|
||||||
|
|
|
||||||
14
internal/services/settings/port.go
Normal file
14
internal/services/settings/port.go
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SettingStore interface {
|
||||||
|
GetSettingList(ctx context.Context) (domain.SettingList, error)
|
||||||
|
GetSettings(ctx context.Context) ([]domain.Setting, error)
|
||||||
|
GetSetting(ctx context.Context, key string) (domain.Setting, error)
|
||||||
|
SaveSetting(ctx context.Context, key, value string) (domain.Setting, error)
|
||||||
|
}
|
||||||
32
internal/services/settings/service.go
Normal file
32
internal/services/settings/service.go
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
settingStore SettingStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(settingStore SettingStore) *Service {
|
||||||
|
return &Service{
|
||||||
|
settingStore: settingStore,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetSettingList(ctx context.Context) (domain.SettingList, error) {
|
||||||
|
return s.settingStore.GetSettingList(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetSettings(ctx context.Context) ([]domain.Setting, error) {
|
||||||
|
return s.settingStore.GetSettings(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetSetting(ctx context.Context, key string) (domain.Setting, error) {
|
||||||
|
return s.settingStore.GetSetting(ctx, key)
|
||||||
|
}
|
||||||
|
func (s *Service) SaveSetting(ctx context.Context, key, value string) (domain.Setting, error) {
|
||||||
|
return s.settingStore.SaveSetting(ctx, key, value)
|
||||||
|
}
|
||||||
|
|
@ -2,24 +2,255 @@ package ticket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
|
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrGenerateRandomOutcome = errors.New("Failed to generate any random outcome for events")
|
||||||
|
// ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending")
|
||||||
|
ErrTicketHasExpired = errors.New("Ticket has expired")
|
||||||
|
ErrNoEventsAvailable = errors.New("Not enough events available with the given filters")
|
||||||
|
ErrEventHasBeenRemoved = errors.New("Event has been removed")
|
||||||
|
ErrTooManyOutcomesForTicket = errors.New("Too many odds/outcomes for a single ticket")
|
||||||
|
ErrTicketAmountTooHigh = errors.New("Cannot create a ticket with an amount above limit")
|
||||||
|
ErrTicketLimitForSingleUser = errors.New("Number of Ticket Limit reached")
|
||||||
|
ErrTicketWinningTooHigh = errors.New("Total Winnings over set limit")
|
||||||
|
|
||||||
|
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
ticketStore TicketStore
|
ticketStore TicketStore
|
||||||
|
eventSvc event.Service
|
||||||
|
prematchSvc odds.ServiceImpl
|
||||||
|
mongoLogger *zap.Logger
|
||||||
|
settingSvc settings.Service
|
||||||
|
notificationSvc *notificationservice.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(ticketStore TicketStore) *Service {
|
func NewService(
|
||||||
|
ticketStore TicketStore,
|
||||||
|
eventSvc event.Service,
|
||||||
|
prematchSvc odds.ServiceImpl,
|
||||||
|
mongoLogger *zap.Logger,
|
||||||
|
settingSvc settings.Service,
|
||||||
|
notificationSvc *notificationservice.Service,
|
||||||
|
) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
ticketStore: ticketStore,
|
ticketStore: ticketStore,
|
||||||
|
eventSvc: eventSvc,
|
||||||
|
prematchSvc: prematchSvc,
|
||||||
|
mongoLogger: mongoLogger,
|
||||||
|
settingSvc: settingSvc,
|
||||||
|
notificationSvc: notificationSvc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) CreateTicket(ctx context.Context, ticket domain.CreateTicket) (domain.Ticket, error) {
|
func (s *Service) GenerateTicketOutcome(ctx context.Context, settings domain.SettingList, eventID int64, marketID int64, oddID int64) (domain.CreateTicketOutcome, error) {
|
||||||
return s.ticketStore.CreateTicket(ctx, ticket)
|
eventIDStr := strconv.FormatInt(eventID, 10)
|
||||||
|
marketIDStr := strconv.FormatInt(marketID, 10)
|
||||||
|
oddIDStr := strconv.FormatInt(oddID, 10)
|
||||||
|
event, err := s.eventSvc.GetUpcomingEventByID(ctx, eventIDStr)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("failed to fetch upcoming event by ID",
|
||||||
|
zap.Int64("event_id", eventID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return domain.CreateTicketOutcome{}, ErrEventHasBeenRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checking to make sure the event hasn't already started
|
||||||
|
currentTime := time.Now()
|
||||||
|
if event.StartTime.Before(currentTime) {
|
||||||
|
s.mongoLogger.Error("event has already started",
|
||||||
|
zap.Int64("event_id", eventID),
|
||||||
|
zap.Time("event_start_time", event.StartTime),
|
||||||
|
zap.Time("current_time", currentTime),
|
||||||
|
)
|
||||||
|
return domain.CreateTicketOutcome{}, ErrTicketHasExpired
|
||||||
|
}
|
||||||
|
|
||||||
|
odds, err := s.prematchSvc.GetRawOddsByMarketID(ctx, marketIDStr, eventIDStr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("failed to get raw odds by market ID",
|
||||||
|
zap.Int64("event_id", eventID),
|
||||||
|
zap.Int64("market_id", marketID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
// return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil)
|
||||||
|
|
||||||
|
}
|
||||||
|
type rawOddType struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Odds string
|
||||||
|
Header string
|
||||||
|
Handicap string
|
||||||
|
}
|
||||||
|
var selectedOdd rawOddType
|
||||||
|
var isOddFound bool = false
|
||||||
|
for _, raw := range odds.RawOdds {
|
||||||
|
var rawOdd rawOddType
|
||||||
|
rawBytes, err := json.Marshal(raw)
|
||||||
|
err = json.Unmarshal(rawBytes, &rawOdd)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("failed to unmarshal raw ods",
|
||||||
|
zap.Int64("event_id", eventID),
|
||||||
|
zap.String("rawOddID", rawOdd.ID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if rawOdd.ID == oddIDStr {
|
||||||
|
selectedOdd = rawOdd
|
||||||
|
isOddFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isOddFound {
|
||||||
|
// return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil)
|
||||||
|
s.mongoLogger.Error("Invalid Odd ID",
|
||||||
|
zap.Int64("event_id", eventID),
|
||||||
|
zap.String("oddIDStr", oddIDStr),
|
||||||
|
)
|
||||||
|
return domain.CreateTicketOutcome{}, ErrRawOddInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("failed to parse selected odd value",
|
||||||
|
zap.String("odd", selectedOdd.Odds),
|
||||||
|
zap.Int64("odd_id", oddID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return domain.CreateTicketOutcome{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newOutcome := domain.CreateTicketOutcome{
|
||||||
|
EventID: eventID,
|
||||||
|
OddID: oddID,
|
||||||
|
MarketID: marketID,
|
||||||
|
HomeTeamName: event.HomeTeam,
|
||||||
|
AwayTeamName: event.AwayTeam,
|
||||||
|
MarketName: odds.MarketName,
|
||||||
|
Odd: float32(parsedOdd),
|
||||||
|
OddName: selectedOdd.Name,
|
||||||
|
OddHeader: selectedOdd.Header,
|
||||||
|
OddHandicap: selectedOdd.Handicap,
|
||||||
|
Expires: event.StartTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
// outcomes = append(outcomes, )
|
||||||
|
|
||||||
|
return newOutcome, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq, clientIP string) (domain.Ticket, int64, error) {
|
||||||
|
settingsList, err := s.settingSvc.GetSettingList(ctx)
|
||||||
|
|
||||||
|
// Check to see if the number of outcomes is above a set limit
|
||||||
|
if len(req.Outcomes) > int(settingsList.MaxNumberOfOutcomes) {
|
||||||
|
return domain.Ticket{}, 0, ErrTooManyOutcomesForTicket
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if the amount is above a set limit
|
||||||
|
if req.Amount > settingsList.BetAmountLimit.Float32() {
|
||||||
|
return domain.Ticket{}, 0, ErrTicketAmountTooHigh
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := s.CountTicketByIP(ctx, clientIP)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("failed to count number of ticket using ip",
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return domain.Ticket{}, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see how many tickets a single anonymous user has created
|
||||||
|
if count > settingsList.DailyTicketPerIP {
|
||||||
|
|
||||||
|
return domain.Ticket{}, 0, ErrTicketLimitForSingleUser
|
||||||
|
}
|
||||||
|
|
||||||
|
var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes))
|
||||||
|
var totalOdds float32 = 1
|
||||||
|
for _, outcomeReq := range req.Outcomes {
|
||||||
|
newOutcome, err := s.GenerateTicketOutcome(ctx, settingsList, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID)
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("failed to generate outcome",
|
||||||
|
zap.Int64("event_id", outcomeReq.EventID),
|
||||||
|
zap.Int64("market_id", outcomeReq.MarketID),
|
||||||
|
zap.Int64("odd_id", outcomeReq.OddID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return domain.Ticket{}, 0, err
|
||||||
|
}
|
||||||
|
totalOdds *= float32(newOutcome.Odd)
|
||||||
|
outcomes = append(outcomes, newOutcome)
|
||||||
|
}
|
||||||
|
totalWinnings := req.Amount * totalOdds
|
||||||
|
|
||||||
|
// Check to see if the total winning amount is over a set limit
|
||||||
|
if totalWinnings > settingsList.TotalWinningLimit.Float32() {
|
||||||
|
s.mongoLogger.Error("Total Winnings over limit",
|
||||||
|
zap.Float32("Total Odds", totalOdds),
|
||||||
|
zap.Float32("amount", req.Amount),
|
||||||
|
zap.Float32("limit", settingsList.TotalWinningLimit.Float32()))
|
||||||
|
return domain.Ticket{}, 0, ErrTicketWinningTooHigh
|
||||||
|
}
|
||||||
|
|
||||||
|
ticket, err := s.ticketStore.CreateTicket(ctx, domain.CreateTicket{
|
||||||
|
Amount: domain.ToCurrency(req.Amount),
|
||||||
|
TotalOdds: totalOdds,
|
||||||
|
IP: clientIP,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("Error Creating Ticket", zap.Float32("Total Odds", totalOdds), zap.Float32("amount", req.Amount))
|
||||||
|
return domain.Ticket{}, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the ticket id now that it has fetched from the database
|
||||||
|
for index := range outcomes {
|
||||||
|
outcomes[index].TicketID = ticket.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := s.CreateTicketOutcome(ctx, outcomes)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.mongoLogger.Error("Error Creating Ticket Outcomes", zap.Any("outcomes", outcomes))
|
||||||
|
return domain.Ticket{}, rows, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// updates := domain.MetricUpdates{
|
||||||
|
// TotalLiveTicketsDelta: domain.PtrInt64(1),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if err := s.notificationSvc.UpdateLiveMetrics(ctx, updates); err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
|
||||||
|
return ticket, rows, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (s *Service) CreateTicket(ctx context.Context, ticket domain.CreateTicket) (domain.Ticket, error) {
|
||||||
|
// return s.ticketStore.CreateTicket(ctx, ticket)
|
||||||
|
// }
|
||||||
|
|
||||||
func (s *Service) CreateTicketOutcome(ctx context.Context, outcomes []domain.CreateTicketOutcome) (int64, error) {
|
func (s *Service) CreateTicketOutcome(ctx context.Context, outcomes []domain.CreateTicketOutcome) (int64, error) {
|
||||||
return s.ticketStore.CreateTicketOutcome(ctx, outcomes)
|
return s.ticketStore.CreateTicketOutcome(ctx, outcomes)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,18 +10,29 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
|
||||||
afro "github.com/amanuelabay/afrosms-go"
|
afro "github.com/amanuelabay/afrosms-go"
|
||||||
"github.com/resend/resend-go/v2"
|
"github.com/resend/resend-go/v2"
|
||||||
|
"github.com/twilio/twilio-go"
|
||||||
|
twilioApi "github.com/twilio/twilio-go/rest/api/v2010"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium) error {
|
func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, provider domain.OtpProvider) error {
|
||||||
otpCode := helpers.GenerateOTP()
|
otpCode := helpers.GenerateOTP()
|
||||||
|
|
||||||
message := fmt.Sprintf("Welcome to Fortune bets, your OTP is %s please don't share with anyone.", otpCode)
|
message := fmt.Sprintf("Welcome to Fortune bets, your OTP is %s please don't share with anyone.", otpCode)
|
||||||
|
|
||||||
switch medium {
|
switch medium {
|
||||||
case domain.OtpMediumSms:
|
case domain.OtpMediumSms:
|
||||||
if err := s.SendSMSOTP(ctx, sentTo, message); err != nil {
|
switch provider {
|
||||||
return err
|
case "twilio":
|
||||||
|
if err := s.SendTwilioSMSOTP(ctx, sentTo, message, provider); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "afromessage":
|
||||||
|
if err := s.SendAfroMessageSMSOTP(ctx, sentTo, message, provider); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid sms provider: %s", provider)
|
||||||
}
|
}
|
||||||
case domain.OtpMediumEmail:
|
case domain.OtpMediumEmail:
|
||||||
if err := s.SendEmailOTP(ctx, sentTo, message); err != nil {
|
if err := s.SendEmailOTP(ctx, sentTo, message); err != nil {
|
||||||
|
|
@ -51,7 +62,7 @@ func hashPassword(plaintextPassword string) ([]byte, error) {
|
||||||
return hash, nil
|
return hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) SendSMSOTP(ctx context.Context, receiverPhone, message string) error {
|
func (s *Service) SendAfroMessageSMSOTP(ctx context.Context, receiverPhone, message string, provider domain.OtpProvider) error {
|
||||||
apiKey := s.config.AFRO_SMS_API_KEY
|
apiKey := s.config.AFRO_SMS_API_KEY
|
||||||
senderName := s.config.AFRO_SMS_SENDER_NAME
|
senderName := s.config.AFRO_SMS_SENDER_NAME
|
||||||
hostURL := s.config.ADRO_SMS_HOST_URL
|
hostURL := s.config.ADRO_SMS_HOST_URL
|
||||||
|
|
@ -79,6 +90,29 @@ func (s *Service) SendSMSOTP(ctx context.Context, receiverPhone, message string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) SendTwilioSMSOTP(ctx context.Context, receiverPhone, message string, provider domain.OtpProvider) error {
|
||||||
|
accountSid := s.config.TwilioAccountSid
|
||||||
|
authToken := s.config.TwilioAuthToken
|
||||||
|
senderPhone := s.config.TwilioSenderPhoneNumber
|
||||||
|
|
||||||
|
client := twilio.NewRestClientWithParams(twilio.ClientParams{
|
||||||
|
Username: accountSid,
|
||||||
|
Password: authToken,
|
||||||
|
})
|
||||||
|
|
||||||
|
params := &twilioApi.CreateMessageParams{}
|
||||||
|
params.SetTo(receiverPhone)
|
||||||
|
params.SetFrom(senderPhone)
|
||||||
|
params.SetBody(message)
|
||||||
|
|
||||||
|
_, err := client.Api.CreateMessage(params)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error sending SMS message: %s" + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) SendEmailOTP(ctx context.Context, receiverEmail, message string) error {
|
func (s *Service) SendEmailOTP(ctx context.Context, receiverEmail, message string) error {
|
||||||
apiKey := s.config.ResendApiKey
|
apiKey := s.config.ResendApiKey
|
||||||
client := resend.NewClient(apiKey)
|
client := resend.NewClient(apiKey)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) { // email,phone,error
|
func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) { // email,phone,error
|
||||||
return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email)
|
return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email)
|
||||||
}
|
}
|
||||||
func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string) error {
|
func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.OtpProvider) error {
|
||||||
var err error
|
var err error
|
||||||
// check if user exists
|
// check if user exists
|
||||||
switch medium {
|
switch medium {
|
||||||
|
|
@ -26,7 +26,7 @@ func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium,
|
||||||
}
|
}
|
||||||
|
|
||||||
// send otp based on the medium
|
// send otp based on the medium
|
||||||
return s.SendOtp(ctx, sentTo, domain.OtpRegister, medium)
|
return s.SendOtp(ctx, sentTo, domain.OtpRegister, medium, provider)
|
||||||
}
|
}
|
||||||
func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { // normal
|
func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { // normal
|
||||||
// get otp
|
// get otp
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string) error {
|
func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.OtpProvider) error {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
// check if user exists
|
// check if user exists
|
||||||
|
|
@ -23,7 +23,7 @@ func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, se
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.SendOtp(ctx, sentTo, domain.OtpReset, medium)
|
return s.SendOtp(ctx, sentTo, domain.OtpReset, medium, provider)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ func (s *AleaPlayService) processTransaction(ctx context.Context, tx *domain.Vir
|
||||||
}
|
}
|
||||||
tx.WalletID = wallets[0].ID
|
tx.WalletID = wallets[0].ID
|
||||||
|
|
||||||
if err := s.walletSvc.AddToWallet(ctx, tx.WalletID, domain.Currency(tx.Amount)); err != nil {
|
if _, err := s.walletSvc.AddToWallet(ctx, tx.WalletID, domain.Currency(tx.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}); err != nil {
|
||||||
return fmt.Errorf("wallet update failed: %w", err)
|
return fmt.Errorf("wallet update failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,13 @@ type VirtualGameService interface {
|
||||||
GetPlayerInfo(ctx context.Context, req *domain.PopOKPlayerInfoRequest) (*domain.PopOKPlayerInfoResponse, error)
|
GetPlayerInfo(ctx context.Context, req *domain.PopOKPlayerInfoRequest) (*domain.PopOKPlayerInfoResponse, error)
|
||||||
ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (*domain.PopOKWinResponse, error)
|
ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (*domain.PopOKWinResponse, error)
|
||||||
ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequest) (*domain.PopOKCancelResponse, error)
|
ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequest) (*domain.PopOKCancelResponse, error)
|
||||||
|
ProcessTournamentWin(ctx context.Context, req *domain.PopOKWinRequest) (*domain.PopOKWinResponse, error)
|
||||||
|
ProcessPromoWin(ctx context.Context, req *domain.PopOKWinRequest) (*domain.PopOKWinResponse, error)
|
||||||
|
|
||||||
GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
||||||
|
ListGames(ctx context.Context, currency string) ([]domain.PopOKGame, error)
|
||||||
|
RecommendGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error)
|
||||||
|
AddFavoriteGame(ctx context.Context, userID, gameID int64) error
|
||||||
|
RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error
|
||||||
|
ListFavoriteGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package virtualgameservice
|
package virtualgameservice
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
|
@ -8,7 +9,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"math/rand/v2"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
|
|
@ -43,14 +49,15 @@ func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameI
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionToken := fmt.Sprintf("%d-%s-%d", userID, gameID, time.Now().UnixNano())
|
sessionId := fmt.Sprintf("%d-%s-%d", userID, gameID, time.Now().UnixNano())
|
||||||
token, err := jwtutil.CreatePopOKJwt(
|
token, err := jwtutil.CreatePopOKJwt(
|
||||||
userID,
|
userID,
|
||||||
user.PhoneNumber,
|
user.CompanyID,
|
||||||
|
user.FirstName,
|
||||||
currency,
|
currency,
|
||||||
"en",
|
"en",
|
||||||
mode,
|
mode,
|
||||||
sessionToken,
|
sessionId,
|
||||||
s.config.PopOK.SecretKey,
|
s.config.PopOK.SecretKey,
|
||||||
24*time.Hour,
|
24*time.Hour,
|
||||||
)
|
)
|
||||||
|
|
@ -59,19 +66,33 @@ func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameI
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record game launch as a transaction (for history and recommendation purposes)
|
||||||
|
tx := &domain.VirtualGameHistory{
|
||||||
|
SessionID: sessionId, // Optional: populate if session tracking is implemented
|
||||||
|
UserID: userID,
|
||||||
|
CompanyID: user.CompanyID.Value,
|
||||||
|
Provider: string(domain.PROVIDER_POPOK),
|
||||||
|
GameID: toInt64Ptr(gameID),
|
||||||
|
TransactionType: "LAUNCH",
|
||||||
|
Amount: 0,
|
||||||
|
Currency: currency,
|
||||||
|
ExternalTransactionID: sessionId,
|
||||||
|
Status: "COMPLETED",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.repo.CreateVirtualGameHistory(ctx, tx); err != nil {
|
||||||
|
s.logger.Error("Failed to record game launch transaction", "error", err)
|
||||||
|
// Do not fail game launch on logging error — just log and continue
|
||||||
|
}
|
||||||
|
|
||||||
params := fmt.Sprintf(
|
params := fmt.Sprintf(
|
||||||
"partnerId=%s&gameId=%s&gameMode=%s&lang=en&platform=%s&externalToken=%s",
|
"partnerId=%s&gameId=%s&gameMode=%s&lang=en&platform=%s&externalToken=%s",
|
||||||
s.config.PopOK.ClientID, gameID, mode, s.config.PopOK.Platform, token,
|
s.config.PopOK.ClientID, gameID, mode, s.config.PopOK.Platform, token,
|
||||||
)
|
)
|
||||||
|
|
||||||
// params = fmt.Sprintf(
|
|
||||||
// "partnerId=%s&gameId=%sgameMode=%s&lang=en&platform=%s",
|
|
||||||
// "1", "1", "fun", "111",
|
|
||||||
// )
|
|
||||||
|
|
||||||
// signature := s.generateSignature(params)
|
|
||||||
return fmt.Sprintf("%s?%s", s.config.PopOK.BaseURL, params), nil
|
return fmt.Sprintf("%s?%s", s.config.PopOK.BaseURL, params), nil
|
||||||
// return fmt.Sprintf("%s?%s", s.config.PopOK.BaseURL, params), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) HandleCallback(ctx context.Context, callback *domain.PopOKCallback) error {
|
func (s *service) HandleCallback(ctx context.Context, callback *domain.PopOKCallback) error {
|
||||||
|
|
@ -117,7 +138,7 @@ func (s *service) HandleCallback(ctx context.Context, callback *domain.PopOKCall
|
||||||
return errors.New("unknown transaction type")
|
return errors.New("unknown transaction type")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.walletSvc.AddToWallet(ctx, walletID, domain.Currency(amount))
|
_, err = s.walletSvc.AddToWallet(ctx, walletID, domain.Currency(amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to update wallet", "walletID", walletID, "userID", session.UserID, "amount", amount, "error", err)
|
s.logger.Error("Failed to update wallet", "walletID", walletID, "userID", session.UserID, "amount", amount, "error", err)
|
||||||
return err
|
return err
|
||||||
|
|
@ -148,6 +169,8 @@ func (s *service) HandleCallback(ctx context.Context, callback *domain.PopOKCall
|
||||||
|
|
||||||
func (s *service) GetPlayerInfo(ctx context.Context, req *domain.PopOKPlayerInfoRequest) (*domain.PopOKPlayerInfoResponse, error) {
|
func (s *service) GetPlayerInfo(ctx context.Context, req *domain.PopOKPlayerInfoRequest) (*domain.PopOKPlayerInfoResponse, error) {
|
||||||
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
|
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
|
||||||
|
fmt.Printf("\n\nClaims: %+v\n\n", claims)
|
||||||
|
fmt.Printf("\n\nExternal token: %+v\n\n", req.ExternalToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to parse JWT", "error", err)
|
s.logger.Error("Failed to parse JWT", "error", err)
|
||||||
return nil, fmt.Errorf("invalid token")
|
return nil, fmt.Errorf("invalid token")
|
||||||
|
|
@ -184,15 +207,18 @@ func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) (
|
||||||
return &domain.PopOKBetResponse{}, fmt.Errorf("Failed to read user wallets")
|
return &domain.PopOKBetResponse{}, fmt.Errorf("Failed to read user wallets")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.walletSvc.DeductFromWallet(ctx, claims.UserID, domain.Currency(amountCents)); err != nil {
|
if _, err := s.walletSvc.DeductFromWallet(ctx, claims.UserID, domain.Currency(amountCents), domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT); err != nil {
|
||||||
return nil, fmt.Errorf("insufficient balance")
|
return nil, fmt.Errorf("insufficient balance")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create transaction record
|
// Create transaction record
|
||||||
tx := &domain.VirtualGameTransaction{
|
tx := &domain.VirtualGameTransaction{
|
||||||
UserID: claims.UserID,
|
UserID: claims.UserID,
|
||||||
|
CompanyID: claims.CompanyID.Value,
|
||||||
|
Provider: string(domain.PROVIDER_POPOK),
|
||||||
|
GameID: req.GameID,
|
||||||
TransactionType: "BET",
|
TransactionType: "BET",
|
||||||
Amount: -amountCents, // Negative for bets
|
Amount: amountCents, // Negative for bets
|
||||||
Currency: req.Currency,
|
Currency: req.Currency,
|
||||||
ExternalTransactionID: req.TransactionID,
|
ExternalTransactionID: req.TransactionID,
|
||||||
Status: "COMPLETED",
|
Status: "COMPLETED",
|
||||||
|
|
@ -219,6 +245,8 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
|
||||||
return nil, fmt.Errorf("invalid token")
|
return nil, fmt.Errorf("invalid token")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n\nClaims: %+v\n\n", claims)
|
||||||
|
|
||||||
// 2. Check for duplicate transaction (idempotency)
|
// 2. Check for duplicate transaction (idempotency)
|
||||||
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
|
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -245,7 +273,7 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
|
||||||
|
|
||||||
// 4. Credit to wallet
|
// 4. Credit to wallet
|
||||||
|
|
||||||
if err := s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(amountCents)); err != nil {
|
if _, err := s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(amountCents), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}); err != nil {
|
||||||
s.logger.Error("Failed to credit wallet", "userID", claims.UserID, "error", err)
|
s.logger.Error("Failed to credit wallet", "userID", claims.UserID, "error", err)
|
||||||
return nil, fmt.Errorf("wallet credit failed")
|
return nil, fmt.Errorf("wallet credit failed")
|
||||||
}
|
}
|
||||||
|
|
@ -257,6 +285,9 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
|
||||||
// 5. Create transaction record
|
// 5. Create transaction record
|
||||||
tx := &domain.VirtualGameTransaction{
|
tx := &domain.VirtualGameTransaction{
|
||||||
UserID: claims.UserID,
|
UserID: claims.UserID,
|
||||||
|
CompanyID: claims.CompanyID.Value,
|
||||||
|
Provider: string(domain.PROVIDER_POPOK),
|
||||||
|
GameID: req.GameID,
|
||||||
TransactionType: "WIN",
|
TransactionType: "WIN",
|
||||||
Amount: amountCents,
|
Amount: amountCents,
|
||||||
Currency: req.Currency,
|
Currency: req.Currency,
|
||||||
|
|
@ -277,14 +308,175 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequest) (*domain.PopOKCancelResponse, error) {
|
func (s *service) ProcessTournamentWin(ctx context.Context, req *domain.PopOKWinRequest) (*domain.PopOKWinResponse, error) {
|
||||||
// 1. Validate token and get user ID
|
// 1. Validate token and get user ID
|
||||||
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
|
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Invalid token in cancel request", "error", err)
|
s.logger.Error("Invalid token in tournament win request", "error", err)
|
||||||
return nil, fmt.Errorf("invalid token")
|
return nil, fmt.Errorf("invalid token")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Check for duplicate tournament win transaction
|
||||||
|
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to check existing tournament transaction", "error", err)
|
||||||
|
return nil, fmt.Errorf("transaction check failed")
|
||||||
|
}
|
||||||
|
if existingTx != nil && existingTx.TransactionType == "TOURNAMENT_WIN" {
|
||||||
|
s.logger.Warn("Duplicate tournament win", "transactionID", req.TransactionID)
|
||||||
|
wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
||||||
|
balance := 0.0
|
||||||
|
if len(wallets) > 0 {
|
||||||
|
balance = float64(wallets[0].Balance) / 100
|
||||||
|
}
|
||||||
|
return &domain.PopOKWinResponse{
|
||||||
|
TransactionID: req.TransactionID,
|
||||||
|
ExternalTrxID: fmt.Sprintf("%v", existingTx.ID),
|
||||||
|
Balance: balance,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Convert amount to cents
|
||||||
|
amountCents := int64(req.Amount * 100)
|
||||||
|
|
||||||
|
// 4. Credit user wallet
|
||||||
|
if _, err := s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(amountCents), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}); err != nil {
|
||||||
|
s.logger.Error("Failed to credit wallet for tournament", "userID", claims.UserID, "error", err)
|
||||||
|
return nil, fmt.Errorf("wallet credit failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Log tournament win transaction
|
||||||
|
tx := &domain.VirtualGameTransaction{
|
||||||
|
UserID: claims.UserID,
|
||||||
|
TransactionType: "TOURNAMENT_WIN",
|
||||||
|
Amount: amountCents,
|
||||||
|
Currency: req.Currency,
|
||||||
|
ExternalTransactionID: req.TransactionID,
|
||||||
|
Status: "COMPLETED",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.repo.CreateVirtualGameTransaction(ctx, tx); err != nil {
|
||||||
|
s.logger.Error("Failed to record tournament win transaction", "error", err)
|
||||||
|
return nil, fmt.Errorf("transaction recording failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Fetch updated balance
|
||||||
|
wallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to get wallet balance")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &domain.PopOKWinResponse{
|
||||||
|
TransactionID: req.TransactionID,
|
||||||
|
ExternalTrxID: fmt.Sprintf("%v", tx.ID),
|
||||||
|
Balance: float64(wallets[0].Balance) / 100,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) ProcessPromoWin(ctx context.Context, req *domain.PopOKWinRequest) (*domain.PopOKWinResponse, error) {
|
||||||
|
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Invalid token in promo win request", "error", err)
|
||||||
|
return nil, fmt.Errorf("invalid token")
|
||||||
|
}
|
||||||
|
|
||||||
|
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to check existing promo transaction", "error", err)
|
||||||
|
return nil, fmt.Errorf("transaction check failed")
|
||||||
|
}
|
||||||
|
if existingTx != nil && existingTx.TransactionType == "PROMO_WIN" {
|
||||||
|
s.logger.Warn("Duplicate promo win", "transactionID", req.TransactionID)
|
||||||
|
wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
||||||
|
balance := 0.0
|
||||||
|
if len(wallets) > 0 {
|
||||||
|
balance = float64(wallets[0].Balance) / 100
|
||||||
|
}
|
||||||
|
return &domain.PopOKWinResponse{
|
||||||
|
TransactionID: req.TransactionID,
|
||||||
|
ExternalTrxID: fmt.Sprintf("%v", existingTx.ID),
|
||||||
|
Balance: balance,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
amountCents := int64(req.Amount * 100)
|
||||||
|
|
||||||
|
if _, err := s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(amountCents), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}); err != nil {
|
||||||
|
s.logger.Error("Failed to credit wallet for promo", "userID", claims.UserID, "error", err)
|
||||||
|
return nil, fmt.Errorf("wallet credit failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := &domain.VirtualGameTransaction{
|
||||||
|
UserID: claims.UserID,
|
||||||
|
TransactionType: "PROMO_WIN",
|
||||||
|
Amount: amountCents,
|
||||||
|
Currency: req.Currency,
|
||||||
|
ExternalTransactionID: req.TransactionID,
|
||||||
|
Status: "COMPLETED",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.repo.CreateVirtualGameTransaction(ctx, tx); err != nil {
|
||||||
|
s.logger.Error("Failed to create promo win transaction", "error", err)
|
||||||
|
return nil, fmt.Errorf("transaction recording failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
wallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read wallets")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &domain.PopOKWinResponse{
|
||||||
|
TransactionID: req.TransactionID,
|
||||||
|
ExternalTrxID: fmt.Sprintf("%v", tx.ID),
|
||||||
|
Balance: float64(wallets[0].Balance) / 100,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (s *service) GenerateNewToken(ctx context.Context, req *domain.PopOKGenerateTokenRequest) (*domain.PopOKGenerateTokenResponse, error) {
|
||||||
|
// userID, err := strconv.ParseInt(req.PlayerID, 10, 64)
|
||||||
|
// if err != nil {
|
||||||
|
// s.logger.Error("Invalid player ID", "playerID", req.PlayerID, "error", err)
|
||||||
|
// return nil, fmt.Errorf("invalid player ID")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// user, err := s.store.GetUserByID(ctx, userID)
|
||||||
|
// if err != nil {
|
||||||
|
// s.logger.Error("Failed to find user for token refresh", "userID", userID, "error", err)
|
||||||
|
// return nil, fmt.Errorf("user not found")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// newSessionID := fmt.Sprintf("%d-%s-%d", userID, req.GameID, time.Now().UnixNano())
|
||||||
|
|
||||||
|
// token, err := jwtutil.CreatePopOKJwt(
|
||||||
|
// userID,
|
||||||
|
// user.FirstName,
|
||||||
|
// req.Currency,
|
||||||
|
// "en",
|
||||||
|
// req.Mode,
|
||||||
|
// newSessionID,
|
||||||
|
// s.config.PopOK.SecretKey,
|
||||||
|
// 24*time.Hour,
|
||||||
|
// )
|
||||||
|
// if err != nil {
|
||||||
|
// s.logger.Error("Failed to generate new token", "userID", userID, "error", err)
|
||||||
|
// return nil, fmt.Errorf("token generation failed")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return &domain.PopOKGenerateTokenResponse{
|
||||||
|
// NewToken: token,
|
||||||
|
// }, nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequest) (*domain.PopOKCancelResponse, error) {
|
||||||
|
// 1. Validate token and get user ID
|
||||||
|
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
|
||||||
|
// if err != nil {
|
||||||
|
// s.logger.Error("Invalid token in cancel request", "error", err)
|
||||||
|
// return nil, fmt.Errorf("invalid token")
|
||||||
|
// }
|
||||||
|
|
||||||
// 2. Find the original bet transaction
|
// 2. Find the original bet transaction
|
||||||
originalBet, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
|
originalBet, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -316,7 +508,7 @@ func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequ
|
||||||
// 5. Refund the bet amount (absolute value since bet amount is negative)
|
// 5. Refund the bet amount (absolute value since bet amount is negative)
|
||||||
refundAmount := -originalBet.Amount
|
refundAmount := -originalBet.Amount
|
||||||
|
|
||||||
if err := s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(refundAmount)); err != nil {
|
if _, err := s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(refundAmount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}); err != nil {
|
||||||
s.logger.Error("Failed to refund bet", "userID", claims.UserID, "error", err)
|
s.logger.Error("Failed to refund bet", "userID", claims.UserID, "error", err)
|
||||||
return nil, fmt.Errorf("refund failed")
|
return nil, fmt.Errorf("refund failed")
|
||||||
}
|
}
|
||||||
|
|
@ -399,3 +591,171 @@ func (s *service) verifySignature(callback *domain.PopOKCallback) bool {
|
||||||
func (s *service) GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) {
|
func (s *service) GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) {
|
||||||
return s.repo.GetGameCounts(ctx, filter)
|
return s.repo.GetGameCounts(ctx, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *service) ListGames(ctx context.Context, currency string) ([]domain.PopOKGame, error) {
|
||||||
|
now := time.Now().Format("02-01-2006 15:04:05") // dd-mm-yyyy hh:mm:ss
|
||||||
|
|
||||||
|
// Calculate hash: sha256(privateKey + time)
|
||||||
|
rawHash := s.config.PopOK.SecretKey + now
|
||||||
|
hash := fmt.Sprintf("%x", sha256.Sum256([]byte(rawHash)))
|
||||||
|
|
||||||
|
// Construct request payload
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"action": "gameList",
|
||||||
|
"platform": s.config.PopOK.Platform,
|
||||||
|
"partnerId": s.config.PopOK.ClientID,
|
||||||
|
"currency": currency,
|
||||||
|
"time": now,
|
||||||
|
"hash": hash,
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBytes, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to marshal game list request", "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", s.config.PopOK.BaseURL+"/serviceApi.php", bytes.NewReader(bodyBytes))
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to create game list request", "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to send game list request", "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
b, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("PopOK game list failed with status %d: %s", resp.StatusCode, string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
var gameList domain.PopOKGameListResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&gameList); err != nil {
|
||||||
|
s.logger.Error("Failed to decode game list response", "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if gameList.Code != 0 {
|
||||||
|
return nil, fmt.Errorf("PopOK error: %s", gameList.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gameList.Data.Slots, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) RecommendGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error) {
|
||||||
|
// Fetch all available games
|
||||||
|
games, err := s.ListGames(ctx, "ETB")
|
||||||
|
if err != nil || len(games) == 0 {
|
||||||
|
return nil, fmt.Errorf("could not fetch games")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has existing interaction
|
||||||
|
history, err := s.repo.GetUserGameHistory(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Warn("No previous game history", "userID", userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
recommendations := []domain.GameRecommendation{}
|
||||||
|
|
||||||
|
if len(history) > 0 {
|
||||||
|
// Score games based on interaction frequency
|
||||||
|
gameScores := map[int64]int{}
|
||||||
|
for _, h := range history {
|
||||||
|
if h.GameID != nil {
|
||||||
|
gameScores[*h.GameID]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by score descending
|
||||||
|
sort.SliceStable(games, func(i, j int) bool {
|
||||||
|
return gameScores[int64(games[i].ID)] > gameScores[int64(games[j].ID)]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Pick top 3
|
||||||
|
for _, g := range games[:min(3, len(games))] {
|
||||||
|
recommendations = append(recommendations, domain.GameRecommendation{
|
||||||
|
GameID: g.ID,
|
||||||
|
GameName: g.GameName,
|
||||||
|
Thumbnail: g.Thumbnail,
|
||||||
|
Bets: g.Bets,
|
||||||
|
Reason: "Based on your activity",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pick 3 random games for new users
|
||||||
|
rand.Shuffle(len(games), func(i, j int) {
|
||||||
|
games[i], games[j] = games[j], games[i]
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, g := range games[:min(3, len(games))] {
|
||||||
|
recommendations = append(recommendations, domain.GameRecommendation{
|
||||||
|
GameID: g.ID,
|
||||||
|
GameName: g.GameName,
|
||||||
|
Thumbnail: g.Thumbnail,
|
||||||
|
Bets: g.Bets,
|
||||||
|
Reason: "Random pick",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return recommendations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toInt64Ptr(s string) *int64 {
|
||||||
|
id, err := strconv.ParseInt(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) AddFavoriteGame(ctx context.Context, userID, gameID int64) error {
|
||||||
|
return s.repo.AddFavoriteGame(ctx, userID, gameID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error {
|
||||||
|
return s.repo.RemoveFavoriteGame(ctx, userID, gameID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) ListFavoriteGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error) {
|
||||||
|
gameIDs, err := s.repo.ListFavoriteGames(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to list favorite games", "userID", userID, "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(gameIDs) == 0 {
|
||||||
|
return []domain.GameRecommendation{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
allGames, err := s.ListGames(ctx, "ETB") // You can use dynamic currency if needed
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var favorites []domain.GameRecommendation
|
||||||
|
idMap := make(map[int64]bool)
|
||||||
|
for _, id := range gameIDs {
|
||||||
|
idMap[id] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, g := range allGames {
|
||||||
|
if idMap[int64(g.ID)] {
|
||||||
|
favorites = append(favorites, domain.GameRecommendation{
|
||||||
|
GameID: g.ID,
|
||||||
|
GameName: g.GameName,
|
||||||
|
Thumbnail: g.Thumbnail,
|
||||||
|
Bets: g.Bets,
|
||||||
|
Reason: "Marked as favorite",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return favorites, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
65
internal/services/virtualGame/veli/client.go
Normal file
65
internal/services/virtualGame/veli/client.go
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
package veli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VeliClient struct {
|
||||||
|
client *resty.Client
|
||||||
|
config *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVeliClient(cfg *config.Config) *VeliClient {
|
||||||
|
client := resty.New().
|
||||||
|
SetBaseURL(cfg.VeliGames.APIKey).
|
||||||
|
SetHeader("Accept", "application/json").
|
||||||
|
SetHeader("X-API-Key", cfg.VeliGames.APIKey).
|
||||||
|
SetTimeout(30 * time.Second)
|
||||||
|
|
||||||
|
return &VeliClient{
|
||||||
|
client: client,
|
||||||
|
config: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vc *VeliClient) Get(ctx context.Context, endpoint string, result interface{}) error {
|
||||||
|
resp, err := vc.client.R().
|
||||||
|
SetContext(ctx).
|
||||||
|
SetResult(result).
|
||||||
|
Get(endpoint)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("request failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.IsError() {
|
||||||
|
return fmt.Errorf("API error: %s", resp.Status())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vc *VeliClient) Post(ctx context.Context, endpoint string, body interface{}, result interface{}) error {
|
||||||
|
resp, err := vc.client.R().
|
||||||
|
SetContext(ctx).
|
||||||
|
SetBody(body).
|
||||||
|
SetResult(result).
|
||||||
|
Post(endpoint)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("request failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.IsError() {
|
||||||
|
return fmt.Errorf("API error: %s", resp.Status())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add other HTTP methods as needed (Put, Delete, etc.)
|
||||||
|
|
@ -1,158 +1,162 @@
|
||||||
package veli
|
package veli
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"context"
|
// "context"
|
||||||
"crypto/hmac"
|
// "fmt"
|
||||||
"crypto/sha256"
|
// "log/slog"
|
||||||
"encoding/hex"
|
// "time"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"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/repository"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
// "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
// )
|
||||||
)
|
|
||||||
|
|
||||||
type VeliPlayService struct {
|
// type Service struct {
|
||||||
repo repository.VirtualGameRepository
|
// client *VeliClient
|
||||||
walletSvc wallet.Service
|
// gameRepo repository.VeliGameRepository
|
||||||
config *config.VeliGamesConfig
|
// playerRepo repository.VeliPlayerRepository
|
||||||
logger *slog.Logger
|
// txRepo repository.VeliTransactionRepository
|
||||||
}
|
// walletSvc wallet.Service
|
||||||
|
// logger domain.Logger
|
||||||
|
// }
|
||||||
|
|
||||||
func NewVeliPlayService(
|
// func NewService(
|
||||||
repo repository.VirtualGameRepository,
|
// client *VeliClient,
|
||||||
walletSvc wallet.Service,
|
// gameRepo repository.VeliGameRepository,
|
||||||
cfg *config.Config,
|
// playerRepo repository.VeliPlayerRepository,
|
||||||
logger *slog.Logger,
|
// txRepo repository.VeliTransactionRepository,
|
||||||
) *VeliPlayService {
|
// walletSvc wallet.Service,
|
||||||
return &VeliPlayService{
|
// logger *slog.Logger,
|
||||||
repo: repo,
|
// ) *Service {
|
||||||
walletSvc: walletSvc,
|
// return &Service{
|
||||||
config: &cfg.VeliGames,
|
// client: client,
|
||||||
logger: logger,
|
// gameRepo: gameRepo,
|
||||||
}
|
// playerRepo: playerRepo,
|
||||||
}
|
// txRepo: txRepo,
|
||||||
|
// walletSvc: walletSvc,
|
||||||
|
// logger: logger,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
func (s *VeliPlayService) GenerateGameLaunchURL(ctx context.Context, userID int64, gameID, currency, mode string) (string, error) {
|
// func (s *Service) SyncGames(ctx context.Context) error {
|
||||||
session := &domain.VirtualGameSession{
|
// games, err := s.client.GetGameList(ctx)
|
||||||
UserID: userID,
|
// if err != nil {
|
||||||
GameID: gameID,
|
// return fmt.Errorf("failed to get game list: %w", err)
|
||||||
SessionToken: generateSessionToken(userID),
|
// }
|
||||||
Currency: currency,
|
|
||||||
Status: "ACTIVE",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
ExpiresAt: time.Now().Add(24 * time.Hour),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.repo.CreateVirtualGameSession(ctx, session); err != nil {
|
// for _, game := range games {
|
||||||
return "", fmt.Errorf("failed to create game session: %w", err)
|
// existing, err := s.gameRepo.GetGameByID(ctx, game.ID)
|
||||||
}
|
// if err != nil && err != domain.ErrGameNotFound {
|
||||||
|
// return fmt.Errorf("failed to check existing game: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
// Veli-specific parameters
|
// if existing == nil {
|
||||||
params := url.Values{
|
// // New game - create
|
||||||
"operator_key": []string{s.config.OperatorKey}, // Different from Alea's operator_id
|
// if err := s.gameRepo.CreateGame(ctx, game); err != nil {
|
||||||
"user_id": []string{fmt.Sprintf("%d", userID)},
|
// s.logger.Error("failed to create game", "game_id", game.ID, "error", err)
|
||||||
"game_id": []string{gameID},
|
// continue
|
||||||
"currency": []string{currency},
|
// }
|
||||||
"mode": []string{mode},
|
// } else {
|
||||||
"timestamp": []string{fmt.Sprintf("%d", time.Now().Unix())},
|
// // Existing game - update
|
||||||
}
|
// if err := s.gameRepo.UpdateGame(ctx, game); err != nil {
|
||||||
|
// s.logger.Error("failed to update game", "game_id", game.ID, "error", err)
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
signature := s.generateSignature(params.Encode())
|
// return nil
|
||||||
params.Add("signature", signature)
|
// }
|
||||||
|
|
||||||
return fmt.Sprintf("%s/launch?%s", s.config.APIURL, params.Encode()), nil
|
// func (s *Service) LaunchGame(ctx context.Context, playerID, gameID string) (string, error) {
|
||||||
}
|
// // Verify player exists
|
||||||
|
// player, err := s.playerRepo.GetPlayer(ctx, playerID)
|
||||||
|
// if err != nil {
|
||||||
|
// return "", fmt.Errorf("failed to get player: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
func (s *VeliPlayService) HandleCallback(ctx context.Context, callback *domain.VeliCallback) error {
|
// // Verify game exists
|
||||||
if !s.verifyCallbackSignature(callback) {
|
// game, err := s.gameRepo.GetGameByID(ctx, gameID)
|
||||||
return errors.New("invalid callback signature")
|
// if err != nil {
|
||||||
}
|
// return "", fmt.Errorf("failed to get game: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
// Veli uses round_id instead of transaction_id for idempotency
|
// // Call Veli API
|
||||||
existing, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, callback.RoundID)
|
// gameURL, err := s.client.LaunchGame(ctx, playerID, gameID)
|
||||||
if err != nil || existing != nil {
|
// if err != nil {
|
||||||
s.logger.Warn("duplicate round detected", "round_id", callback.RoundID)
|
// return "", fmt.Errorf("failed to launch game: %w", err)
|
||||||
return nil
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
session, err := s.repo.GetVirtualGameSessionByToken(ctx, callback.SessionID)
|
// // Create game session record
|
||||||
if err != nil {
|
// session := domain.GameSession{
|
||||||
return fmt.Errorf("failed to get game session: %w", err)
|
// SessionID: fmt.Sprintf("%s-%s-%d", playerID, gameID, time.Now().Unix()),
|
||||||
}
|
// PlayerID: playerID,
|
||||||
|
// GameID: gameID,
|
||||||
|
// LaunchTime: time.Now(),
|
||||||
|
// Status: "active",
|
||||||
|
// }
|
||||||
|
|
||||||
// Convert amount based on event type (BET, WIN, etc.)
|
// if err := s.gameRepo.CreateGameSession(ctx, session); err != nil {
|
||||||
amount := convertAmount(callback.Amount, callback.EventType)
|
// s.logger.Error("failed to create game session", "error", err)
|
||||||
|
// }
|
||||||
|
|
||||||
tx := &domain.VirtualGameTransaction{
|
// return gameURL, nil
|
||||||
SessionID: session.ID,
|
// }
|
||||||
UserID: session.UserID,
|
|
||||||
TransactionType: callback.EventType, // e.g., "bet_placed", "game_result"
|
|
||||||
Amount: amount,
|
|
||||||
Currency: callback.Currency,
|
|
||||||
ExternalTransactionID: callback.RoundID, // Veli uses round_id as the unique identifier
|
|
||||||
Status: "COMPLETED",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
GameSpecificData: domain.GameSpecificData{
|
|
||||||
Multiplier: callback.Multiplier, // Used for Aviator/Plinko
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.processTransaction(ctx, tx, session.UserID); err != nil {
|
// func (s *Service) PlaceBet(ctx context.Context, playerID, gameID string, amount float64) (*domain.VeliTransaction, error) {
|
||||||
return fmt.Errorf("failed to process transaction: %w", err)
|
// // 1. Verify player balance
|
||||||
}
|
// balance, err := s.walletRepo.GetBalance(ctx, playerID)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, fmt.Errorf("failed to get balance: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
return nil
|
// if balance < amount {
|
||||||
}
|
// return nil, domain.ErrInsufficientBalance
|
||||||
|
// }
|
||||||
|
|
||||||
func (s *VeliPlayService) generateSignature(data string) string {
|
// // 2. Create transaction record
|
||||||
h := hmac.New(sha256.New, []byte(s.config.SecretKey))
|
// tx := domain.VeliTransaction{
|
||||||
h.Write([]byte(data))
|
// TransactionID: generateTransactionID(),
|
||||||
return hex.EncodeToString(h.Sum(nil))
|
// PlayerID: playerID,
|
||||||
}
|
// GameID: gameID,
|
||||||
|
// Amount: amount,
|
||||||
|
// Type: "bet",
|
||||||
|
// Status: "pending",
|
||||||
|
// CreatedAt: time.Now(),
|
||||||
|
// }
|
||||||
|
|
||||||
func (s *VeliPlayService) verifyCallbackSignature(cb *domain.VeliCallback) bool {
|
// if err := s.txRepo.CreateTransaction(ctx, tx); err != nil {
|
||||||
signData := fmt.Sprintf("%s%s%s%.2f%s%d",
|
// return nil, fmt.Errorf("failed to create transaction: %w", err)
|
||||||
cb.RoundID, // Veli uses round_id instead of transaction_id
|
// }
|
||||||
cb.SessionID,
|
|
||||||
cb.EventType,
|
|
||||||
cb.Amount,
|
|
||||||
cb.Currency,
|
|
||||||
cb.Timestamp,
|
|
||||||
)
|
|
||||||
expectedSig := s.generateSignature(signData)
|
|
||||||
return expectedSig == cb.Signature
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertAmount(amount float64, eventType string) int64 {
|
// // 3. Call Veli API
|
||||||
cents := int64(amount * 100)
|
// if err := s.client.PlaceBet(ctx, tx.TransactionID, playerID, gameID, amount); err != nil {
|
||||||
if eventType == "bet_placed" {
|
// // Update transaction status
|
||||||
return -cents // Debit for bets
|
// tx.Status = "failed"
|
||||||
}
|
// _ = s.txRepo.UpdateTransaction(ctx, tx)
|
||||||
return cents // Credit for wins/results
|
// return nil, fmt.Errorf("failed to place bet: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func generateSessionToken(userID int64) string {
|
// // 4. Deduct from wallet
|
||||||
return fmt.Sprintf("veli-%d-%d", userID, time.Now().UnixNano())
|
// if err := s.walletRepo.DeductBalance(ctx, playerID, amount); err != nil {
|
||||||
}
|
// // Attempt to rollback
|
||||||
|
// _ = s.client.RollbackBet(ctx, tx.TransactionID)
|
||||||
|
// tx.Status = "failed"
|
||||||
|
// _ = s.txRepo.UpdateTransaction(ctx, tx)
|
||||||
|
// return nil, fmt.Errorf("failed to deduct balance: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
func (s *VeliPlayService) processTransaction(ctx context.Context, tx *domain.VirtualGameTransaction, userID int64) error {
|
// // 5. Update transaction status
|
||||||
wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID)
|
// tx.Status = "completed"
|
||||||
if err != nil || len(wallets) == 0 {
|
// if err := s.txRepo.UpdateTransaction(ctx, tx); err != nil {
|
||||||
return errors.New("no wallet available for user")
|
// s.logger.Error("failed to update transaction status", "error", err)
|
||||||
}
|
// }
|
||||||
tx.WalletID = wallets[0].ID
|
|
||||||
|
|
||||||
if err := s.walletSvc.AddToWallet(ctx, tx.WalletID, domain.Currency(tx.Amount)); err != nil {
|
// return &tx, nil
|
||||||
return fmt.Errorf("wallet update failed: %w", err)
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
return s.repo.CreateVirtualGameTransaction(ctx, tx)
|
// // Implement SettleBet, RollbackBet, GetBalance, etc. following similar patterns
|
||||||
}
|
|
||||||
|
// func generateTransactionID() string {
|
||||||
|
// return fmt.Sprintf("tx-%d", time.Now().UnixNano())
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -91,12 +91,11 @@ func (s *Service) checkWalletThresholds() {
|
||||||
|
|
||||||
// Initialize initial deposit if not set
|
// Initialize initial deposit if not set
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
if _, exists := s.initialDeposits[company.ID]; !exists {
|
initialDeposit, exists := s.initialDeposits[company.ID]
|
||||||
|
if !exists || wallet.Balance > initialDeposit {
|
||||||
s.initialDeposits[company.ID] = wallet.Balance
|
s.initialDeposits[company.ID] = wallet.Balance
|
||||||
s.mu.Unlock()
|
initialDeposit = wallet.Balance // update local variable
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
initialDeposit := s.initialDeposits[company.ID]
|
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
if initialDeposit == 0 {
|
if initialDeposit == 0 {
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type WalletStore interface {
|
type WalletStore interface {
|
||||||
|
// GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error)
|
||||||
|
// GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error)
|
||||||
CreateWallet(ctx context.Context, wallet domain.CreateWallet) (domain.Wallet, error)
|
CreateWallet(ctx context.Context, wallet domain.CreateWallet) (domain.Wallet, error)
|
||||||
CreateCustomerWallet(ctx context.Context, customerWallet domain.CreateCustomerWallet) (domain.CustomerWallet, error)
|
CreateCustomerWallet(ctx context.Context, customerWallet domain.CreateCustomerWallet) (domain.CustomerWallet, error)
|
||||||
GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error)
|
GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error)
|
||||||
GetAllWallets(ctx context.Context) ([]domain.Wallet, error)
|
GetAllWallets(ctx context.Context) ([]domain.Wallet, error)
|
||||||
GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wallet, error)
|
GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wallet, error)
|
||||||
|
GetAllCustomerWallets(ctx context.Context) ([]domain.GetCustomerWallet, error)
|
||||||
GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error)
|
GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error)
|
||||||
GetAllBranchWallets(ctx context.Context) ([]domain.BranchWallet, error)
|
GetAllBranchWallets(ctx context.Context) ([]domain.BranchWallet, error)
|
||||||
UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error
|
UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ type Service struct {
|
||||||
walletStore WalletStore
|
walletStore WalletStore
|
||||||
transferStore TransferStore
|
transferStore TransferStore
|
||||||
notificationStore notificationservice.NotificationStore
|
notificationStore notificationservice.NotificationStore
|
||||||
|
notificationSvc *notificationservice.Service
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user