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/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/infrastructure"
|
||||
customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger"
|
||||
"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/currency"
|
||||
"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"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
|
|
@ -43,12 +44,12 @@ import (
|
|||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
|
||||
"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/transaction"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
||||
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/monitor"
|
||||
|
||||
|
|
@ -56,7 +57,6 @@ import (
|
|||
httpserver "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server"
|
||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/worker"
|
||||
)
|
||||
|
||||
// @title FortuneBet API
|
||||
|
|
@ -87,7 +87,7 @@ func main() {
|
|||
|
||||
logger := customlogger.NewLogger(cfg.Env, cfg.LogLevel)
|
||||
|
||||
domain.MongoDBLogger, err = mongoLogger.InitLogger()
|
||||
domain.MongoDBLogger, err = mongoLogger.InitLogger(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("Logger initialization failed: %v", err)
|
||||
}
|
||||
|
|
@ -99,11 +99,11 @@ func main() {
|
|||
v := customvalidator.NewCustomValidator(validator.New())
|
||||
|
||||
// Initialize services
|
||||
settingSvc := settings.NewService(store)
|
||||
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
|
||||
userSvc := user.NewService(store, store, cfg)
|
||||
eventSvc := event.New(cfg.Bet365Token, store)
|
||||
oddsSvc := odds.New(store, cfg, logger)
|
||||
ticketSvc := ticket.NewService(store)
|
||||
notificationRepo := repository.NewNotificationRepository(store)
|
||||
virtuaGamesRepo := repository.NewVirtualGameRepository(store)
|
||||
notificationSvc := notificationservice.New(notificationRepo, logger, cfg)
|
||||
|
|
@ -121,8 +121,9 @@ func main() {
|
|||
branchSvc := branch.NewService(store)
|
||||
companySvc := company.NewService(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)
|
||||
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)
|
||||
vitualGameRepo := repository.NewVirtualGameRepository(store)
|
||||
recommendationRepo := repository.NewRecommendationRepository(store)
|
||||
|
|
@ -130,7 +131,7 @@ func main() {
|
|||
referalSvc := referralservice.New(referalRepo, *walletSvc, store, cfg, logger)
|
||||
virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, 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)
|
||||
chapaClient := chapa.NewClient(cfg.CHAPA_BASE_URL, cfg.CHAPA_SECRET_KEY)
|
||||
|
||||
|
|
@ -138,6 +139,7 @@ func main() {
|
|||
wallet.TransferStore(store),
|
||||
*walletSvc,
|
||||
user.UserStore(store),
|
||||
cfg,
|
||||
chapaClient,
|
||||
)
|
||||
|
||||
|
|
@ -162,15 +164,19 @@ func main() {
|
|||
logger,
|
||||
)
|
||||
|
||||
// Initialize report worker with CSV exporter
|
||||
csvExporter := infrastructure.CSVExporter{
|
||||
ExportPath: cfg.ReportExportPath, // Make sure to add this to your config
|
||||
}
|
||||
go httpserver.SetupReportCronJobs(context.Background(), reportSvc)
|
||||
|
||||
reportWorker := worker.NewReportWorker(
|
||||
reportSvc,
|
||||
csvExporter,
|
||||
)
|
||||
bankRepository := repository.NewBankRepository(store)
|
||||
instSvc := institutions.New(bankRepository)
|
||||
// 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
|
||||
|
||||
|
|
@ -196,13 +202,39 @@ func main() {
|
|||
|
||||
httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc)
|
||||
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
|
||||
app := httpserver.NewApp(
|
||||
issueReportingSvc,
|
||||
instSvc,
|
||||
currSvc,
|
||||
cfg.Port,
|
||||
v,
|
||||
settingSvc,
|
||||
authSvc,
|
||||
logger,
|
||||
jwtutil.JwtConfig{
|
||||
|
|
@ -225,10 +257,11 @@ func main() {
|
|||
referalSvc,
|
||||
virtualGameSvc,
|
||||
aleaService,
|
||||
veliService,
|
||||
// veliService,
|
||||
recommendationSvc,
|
||||
resultSvc,
|
||||
cfg,
|
||||
domain.MongoDBLogger,
|
||||
)
|
||||
|
||||
logger.Info("Starting server", "port", cfg.Port)
|
||||
|
|
@ -236,4 +269,5 @@ func main() {
|
|||
logger.Error("Failed to start server", "error", err)
|
||||
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=#
|
||||
|
|
@ -76,4 +76,7 @@ DROP TABLE IF EXISTS refresh_tokens;
|
|||
DROP TABLE IF EXISTS otps;
|
||||
DROP TABLE IF EXISTS odds;
|
||||
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,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
is_shop_bet BOOLEAN NOT NULL,
|
||||
outcomes_hash TEXT NOT NULL,
|
||||
UNIQUE(cashout_id),
|
||||
CHECK (
|
||||
user_id IS NOT NULL
|
||||
|
|
@ -111,6 +112,23 @@ CREATE TABLE IF NOT EXISTS ticket_outcomes (
|
|||
status INT NOT NULL DEFAULT 0,
|
||||
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 (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
balance BIGINT NOT NULL DEFAULT 0,
|
||||
|
|
@ -138,8 +156,8 @@ CREATE TABLE IF NOT EXISTS wallet_transfer (
|
|||
sender_wallet_id BIGINT,
|
||||
cashier_id BIGINT,
|
||||
verified BOOLEAN DEFAULT false,
|
||||
reference_number VARCHAR(255),
|
||||
status VARCHAR(255),
|
||||
reference_number VARCHAR(255) NOT NULL,
|
||||
status VARCHAR(255),
|
||||
payment_method VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
|
|
@ -249,10 +267,12 @@ CREATE TABLE companies (
|
|||
CREATE TABLE leagues (
|
||||
id BIGINT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
img TEXT,
|
||||
country_code TEXT,
|
||||
bet365_id INT,
|
||||
sport_id INT NOT NULL,
|
||||
is_active BOOLEAN DEFAULT true
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
is_featured BOOLEAN DEFAULT false
|
||||
);
|
||||
CREATE TABLE teams (
|
||||
id TEXT PRIMARY KEY,
|
||||
|
|
@ -261,6 +281,12 @@ CREATE TABLE teams (
|
|||
bet365_id INT,
|
||||
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
|
||||
CREATE VIEW companies_details AS
|
||||
SELECT companies.*,
|
||||
|
|
@ -273,12 +299,12 @@ FROM companies
|
|||
JOIN wallets ON wallets.id = companies.wallet_id
|
||||
JOIN users ON users.id = companies.admin_id;
|
||||
;
|
||||
|
||||
CREATE VIEW branch_details AS
|
||||
SELECT branches.*,
|
||||
CONCAT(users.first_name, ' ', users.last_name) AS manager_name,
|
||||
users.phone_number AS manager_phone_number,
|
||||
wallets.balance
|
||||
wallets.balance,
|
||||
wallets.is_active AS wallet_is_active
|
||||
FROM branches
|
||||
LEFT JOIN users ON branches.branch_manager_id = users.id
|
||||
LEFT JOin wallets ON wallets.id = branches.wallet_id;
|
||||
|
|
@ -299,42 +325,60 @@ SELECT tickets.*,
|
|||
FROM tickets
|
||||
LEFT JOIN ticket_outcomes ON tickets.id = ticket_outcomes.ticket_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
|
||||
|
||||
ALTER TABLE users
|
||||
ADD CONSTRAINT unique_email UNIQUE (email),
|
||||
ADD CONSTRAINT unique_email UNIQUE (email),
|
||||
ADD CONSTRAINT unique_phone_number UNIQUE (phone_number);
|
||||
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
|
||||
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);
|
||||
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';
|
||||
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_static_wallet FOREIGN KEY (static_wallet_id) REFERENCES wallets(id);
|
||||
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_cashier FOREIGN KEY (cashier_id) REFERENCES users(id);
|
||||
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_bets FOREIGN KEY (bet_id) REFERENCES bets(id);
|
||||
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);
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
----------------------------------------------seed data-------------------------------------------------------------
|
||||
-------------------------------------- DO NOT USE IN PRODUCTION-------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ CREATE TABLE virtual_game_transactions (
|
|||
id BIGSERIAL PRIMARY KEY,
|
||||
session_id BIGINT NOT NULL REFERENCES virtual_game_sessions(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),
|
||||
transaction_type VARCHAR(20) NOT NULL,
|
||||
amount BIGINT NOT NULL,
|
||||
|
|
@ -40,6 +43,41 @@ CREATE TABLE virtual_game_transactions (
|
|||
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_transactions_session_id ON virtual_game_transactions(session_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 (
|
||||
-- id BIGSERIAL PRIMARY KEY,
|
||||
-- name VARCHAR(255) NOT NULL,
|
||||
-- provider VARCHAR(100) NOT NULL,
|
||||
-- category VARCHAR(100) NOT NULL,
|
||||
-- min_bet DECIMAL(15,2) NOT NULL,
|
||||
-- max_bet DECIMAL(15,2) NOT NULL,
|
||||
-- volatility VARCHAR(50) NOT NULL,
|
||||
-- rtp DECIMAL(5,2) NOT NULL,
|
||||
-- is_featured BOOLEAN DEFAULT false,
|
||||
-- popularity_score INTEGER DEFAULT 0,
|
||||
-- thumbnail_url TEXT,
|
||||
-- created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
-- updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
-- );
|
||||
CREATE TABLE IF NOT EXISTS virtual_games (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
provider VARCHAR(100) NOT NULL,
|
||||
category VARCHAR(100) NOT NULL,
|
||||
min_bet DECIMAL(15,2) NOT NULL,
|
||||
max_bet DECIMAL(15,2) NOT NULL,
|
||||
volatility VARCHAR(50) NOT NULL,
|
||||
rtp DECIMAL(5,2) NOT NULL,
|
||||
is_featured BOOLEAN DEFAULT false,
|
||||
popularity_score INTEGER DEFAULT 0,
|
||||
thumbnail_url TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE user_game_interactions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id),
|
||||
game_id BIGINT NOT NULL REFERENCES virtual_games(id),
|
||||
interaction_type VARCHAR(50) NOT NULL, -- 'view', 'play', 'bet', 'favorite'
|
||||
amount DECIMAL(15,2),
|
||||
interaction_type VARCHAR(50) NOT NULL,
|
||||
-- 'view', 'play', 'bet', 'favorite'
|
||||
amount DECIMAL(15, 2),
|
||||
duration_seconds INTEGER,
|
||||
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_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,
|
||||
is_shop_bet,
|
||||
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 *;
|
||||
-- name: CreateBetOutcome :copyfrom
|
||||
INSERT INTO bet_outcomes (
|
||||
|
|
@ -48,16 +49,33 @@ VALUES (
|
|||
SELECT *
|
||||
FROM bet_with_outcomes
|
||||
wHERE (
|
||||
branch_id = $1
|
||||
OR $1 IS NULL
|
||||
branch_id = sqlc.narg('branch_id')
|
||||
OR sqlc.narg('branch_id') IS NULL
|
||||
)
|
||||
AND (
|
||||
company_id = $2
|
||||
OR $2 IS NULL
|
||||
company_id = sqlc.narg('company_id')
|
||||
OR sqlc.narg('company_id') IS NULL
|
||||
)
|
||||
AND (
|
||||
user_id = $3
|
||||
OR $3 IS NULL
|
||||
user_id = sqlc.narg('user_id')
|
||||
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
|
||||
SELECT *
|
||||
|
|
@ -78,11 +96,22 @@ WHERE user_id = $1;
|
|||
-- name: GetBetOutcomeByEventID :many
|
||||
SELECT *
|
||||
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
|
||||
SELECT *
|
||||
FROM bet_outcomes
|
||||
WHERE bet_id = $1;
|
||||
-- name: GetBetCount :one
|
||||
SELECT COUNT(*)
|
||||
FROM bets
|
||||
where user_id = $1
|
||||
AND outcomes_hash = $2;
|
||||
-- name: UpdateCashOut :exec
|
||||
UPDATE bets
|
||||
SET cashed_out = $2,
|
||||
|
|
@ -93,6 +122,16 @@ UPDATE bet_outcomes
|
|||
SET status = $1
|
||||
WHERE id = $2
|
||||
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
|
||||
UPDATE bets
|
||||
SET status = $1,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,32 @@ VALUES ($1, $2)
|
|||
RETURNING *;
|
||||
-- name: GetAllBranches :many
|
||||
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
|
||||
SELECT *
|
||||
FROM branch_details
|
||||
|
|
@ -61,7 +86,8 @@ SET name = COALESCE(sqlc.narg(name), name),
|
|||
location = COALESCE(sqlc.narg(location), location),
|
||||
branch_manager_id = COALESCE(sqlc.narg(branch_manager_id), branch_manager_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
|
||||
RETURNING *;
|
||||
-- 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,
|
||||
bet365_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
|
||||
SET name = EXCLUDED.name,
|
||||
country_code = EXCLUDED.country_code,
|
||||
bet365_id = EXCLUDED.bet365_id,
|
||||
is_active = EXCLUDED.is_active,
|
||||
is_featured = EXCLUDED.is_featured,
|
||||
sport_id = EXCLUDED.sport_id;
|
||||
-- name: GetAllLeagues :many
|
||||
SELECT id,
|
||||
|
|
@ -20,6 +22,7 @@ SELECT id,
|
|||
country_code,
|
||||
bet365_id,
|
||||
is_active,
|
||||
is_featured,
|
||||
sport_id
|
||||
FROM leagues
|
||||
WHERE (
|
||||
|
|
@ -34,7 +37,21 @@ WHERE (
|
|||
is_active = sqlc.narg('is_active')
|
||||
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');
|
||||
-- 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
|
||||
SELECT EXISTS(
|
||||
SELECT 1
|
||||
|
|
@ -48,6 +65,7 @@ SET name = COALESCE(sqlc.narg('name'), name),
|
|||
country_code = COALESCE(sqlc.narg('country_code'), country_code),
|
||||
bet365_id = COALESCE(sqlc.narg('bet365_id'), bet365_id),
|
||||
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)
|
||||
WHERE id = $1;
|
||||
-- name: UpdateLeagueByBet365ID :exec
|
||||
|
|
@ -56,6 +74,7 @@ SET name = COALESCE(sqlc.narg('name'), name),
|
|||
id = COALESCE(sqlc.narg('id'), id),
|
||||
country_code = COALESCE(sqlc.narg('country_code'), country_code),
|
||||
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)
|
||||
WHERE bet365_id = $1;
|
||||
-- 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 *;
|
||||
|
|
@ -58,4 +58,8 @@ Delete from tickets
|
|||
where created_at < now() - interval '1 day';
|
||||
-- name: DeleteTicketOutcome :exec
|
||||
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;
|
||||
|
|
@ -38,4 +38,13 @@ WHERE id = $2;
|
|||
UPDATE wallet_transfer
|
||||
SET status = $1,
|
||||
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 (
|
||||
$1, $2, $3, $4, $5, $6
|
||||
) RETURNING id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at;
|
||||
|
||||
-- name: GetVirtualGameSessionByToken :one
|
||||
SELECT id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at
|
||||
FROM virtual_game_sessions
|
||||
WHERE session_token = $1;
|
||||
|
||||
-- name: UpdateVirtualGameSessionStatus :exec
|
||||
UPDATE virtual_game_sessions
|
||||
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: CreateVirtualGameTransaction :one
|
||||
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 (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8
|
||||
) RETURNING id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at;
|
||||
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||
) 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
|
||||
SELECT id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
||||
FROM virtual_game_transactions
|
||||
WHERE external_transaction_id = $1;
|
||||
|
||||
-- name: UpdateVirtualGameTransactionStatus :exec
|
||||
UPDATE virtual_game_transactions
|
||||
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
||||
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 *
|
||||
FROM wallets
|
||||
WHERE user_id = $1;
|
||||
-- name: GetAllCustomerWallet :many
|
||||
SELECT *
|
||||
FROM customer_wallet_details;
|
||||
-- name: GetCustomerWallet :one
|
||||
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.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;
|
||||
SELECT *
|
||||
FROM customer_wallet_details
|
||||
WHERE customer_id = $1;
|
||||
-- name: GetAllBranchWallets :many
|
||||
SELECT wallets.id,
|
||||
wallets.balance,
|
||||
|
|
@ -62,4 +55,14 @@ WHERE id = $2;
|
|||
UPDATE wallets
|
||||
SET is_active = $1,
|
||||
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:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
|
|
@ -54,6 +56,18 @@ services:
|
|||
networks:
|
||||
- app
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
networks:
|
||||
- app
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
|
|
@ -64,14 +78,19 @@ services:
|
|||
environment:
|
||||
- DB_URL=postgresql://root:secret@postgres:5432/gh?sslmode=disable
|
||||
- MONGO_URI=mongodb://root:secret@mongo:27017
|
||||
- REDIS_ADDR=redis:6379
|
||||
depends_on:
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
mongo:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- app
|
||||
command: ["/app/bin/web"]
|
||||
volumes:
|
||||
- "C:/Users/User/Desktop:/host-desktop"
|
||||
|
||||
test:
|
||||
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,
|
||||
is_shop_bet,
|
||||
cashout_id,
|
||||
company_id
|
||||
company_id,
|
||||
outcomes_hash
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
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
|
||||
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, outcomes_hash
|
||||
`
|
||||
|
||||
type CreateBetParams struct {
|
||||
Amount int64 `json:"amount"`
|
||||
TotalOdds float32 `json:"total_odds"`
|
||||
Status int32 `json:"status"`
|
||||
FullName string `json:"full_name"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
BranchID pgtype.Int8 `json:"branch_id"`
|
||||
UserID pgtype.Int8 `json:"user_id"`
|
||||
IsShopBet bool `json:"is_shop_bet"`
|
||||
CashoutID string `json:"cashout_id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
Amount int64 `json:"amount"`
|
||||
TotalOdds float32 `json:"total_odds"`
|
||||
Status int32 `json:"status"`
|
||||
FullName string `json:"full_name"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
BranchID pgtype.Int8 `json:"branch_id"`
|
||||
UserID pgtype.Int8 `json:"user_id"`
|
||||
IsShopBet bool `json:"is_shop_bet"`
|
||||
CashoutID string `json:"cashout_id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
OutcomesHash string `json:"outcomes_hash"`
|
||||
}
|
||||
|
||||
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.CashoutID,
|
||||
arg.CompanyID,
|
||||
arg.OutcomesHash,
|
||||
)
|
||||
var i Bet
|
||||
err := row.Scan(
|
||||
|
|
@ -70,6 +73,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.IsShopBet,
|
||||
&i.OutcomesHash,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -111,7 +115,7 @@ func (q *Queries) DeleteBetOutcome(ctx context.Context, betID int64) error {
|
|||
}
|
||||
|
||||
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
|
||||
wHERE (
|
||||
branch_id = $1
|
||||
|
|
@ -125,16 +129,45 @@ wHERE (
|
|||
user_id = $3
|
||||
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 {
|
||||
BranchID pgtype.Int8 `json:"branch_id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
UserID pgtype.Int8 `json:"user_id"`
|
||||
BranchID pgtype.Int8 `json:"branch_id"`
|
||||
CompanyID pgtype.Int8 `json:"company_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) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -157,6 +190,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.IsShopBet,
|
||||
&i.OutcomesHash,
|
||||
&i.Outcomes,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -170,7 +204,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi
|
|||
}
|
||||
|
||||
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
|
||||
WHERE branch_id = $1
|
||||
`
|
||||
|
|
@ -199,6 +233,7 @@ func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.IsShopBet,
|
||||
&i.OutcomesHash,
|
||||
&i.Outcomes,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -212,7 +247,7 @@ func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([
|
|||
}
|
||||
|
||||
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
|
||||
WHERE cashout_id = $1
|
||||
`
|
||||
|
|
@ -235,13 +270,14 @@ func (q *Queries) GetBetByCashoutID(ctx context.Context, cashoutID string) (BetW
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.IsShopBet,
|
||||
&i.OutcomesHash,
|
||||
&i.Outcomes,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
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
|
||||
WHERE id = $1
|
||||
`
|
||||
|
|
@ -264,13 +300,14 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.IsShopBet,
|
||||
&i.OutcomesHash,
|
||||
&i.Outcomes,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
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
|
||||
WHERE user_id = $1
|
||||
`
|
||||
|
|
@ -299,6 +336,7 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]Bet
|
|||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.IsShopBet,
|
||||
&i.OutcomesHash,
|
||||
&i.Outcomes,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -311,6 +349,25 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]Bet
|
|||
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
|
||||
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
|
||||
|
|
@ -356,11 +413,23 @@ func (q *Queries) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]BetO
|
|||
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
|
||||
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) {
|
||||
rows, err := q.db.Query(ctx, GetBetOutcomeByEventID, eventID)
|
||||
type GetBetOutcomeByEventIDParams struct {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -430,6 +499,89 @@ func (q *Queries) UpdateBetOutcomeStatus(ctx context.Context, arg UpdateBetOutco
|
|||
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
|
||||
UPDATE bets
|
||||
SET cashed_out = $2,
|
||||
|
|
|
|||
|
|
@ -155,12 +155,53 @@ func (q *Queries) DeleteBranchOperation(ctx context.Context, arg DeleteBranchOpe
|
|||
}
|
||||
|
||||
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
|
||||
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) {
|
||||
rows, err := q.db.Query(ctx, GetAllBranches)
|
||||
type GetAllBranchesParams struct {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -182,6 +223,7 @@ func (q *Queries) GetAllBranches(ctx context.Context) ([]BranchDetail, error) {
|
|||
&i.ManagerName,
|
||||
&i.ManagerPhoneNumber,
|
||||
&i.Balance,
|
||||
&i.WalletIsActive,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -244,7 +286,7 @@ func (q *Queries) GetBranchByCashier(ctx context.Context, userID int64) (Branch,
|
|||
}
|
||||
|
||||
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
|
||||
WHERE company_id = $1
|
||||
`
|
||||
|
|
@ -272,6 +314,7 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]
|
|||
&i.ManagerName,
|
||||
&i.ManagerPhoneNumber,
|
||||
&i.Balance,
|
||||
&i.WalletIsActive,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -284,7 +327,7 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]
|
|||
}
|
||||
|
||||
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
|
||||
WHERE id = $1
|
||||
`
|
||||
|
|
@ -306,12 +349,13 @@ func (q *Queries) GetBranchByID(ctx context.Context, id int64) (BranchDetail, er
|
|||
&i.ManagerName,
|
||||
&i.ManagerPhoneNumber,
|
||||
&i.Balance,
|
||||
&i.WalletIsActive,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
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
|
||||
WHERE branch_manager_id = $1
|
||||
`
|
||||
|
|
@ -339,6 +383,7 @@ func (q *Queries) GetBranchByManagerID(ctx context.Context, branchManagerID int6
|
|||
&i.ManagerName,
|
||||
&i.ManagerPhoneNumber,
|
||||
&i.Balance,
|
||||
&i.WalletIsActive,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -398,7 +443,7 @@ func (q *Queries) GetBranchOperations(ctx context.Context, branchID int64) ([]Ge
|
|||
}
|
||||
|
||||
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
|
||||
WHERE name ILIKE '%' || $1 || '%'
|
||||
`
|
||||
|
|
@ -426,6 +471,7 @@ func (q *Queries) SearchBranchByName(ctx context.Context, dollar_1 pgtype.Text)
|
|||
&i.ManagerName,
|
||||
&i.ManagerPhoneNumber,
|
||||
&i.Balance,
|
||||
&i.WalletIsActive,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -443,7 +489,8 @@ SET name = COALESCE($2, name),
|
|||
location = COALESCE($3, location),
|
||||
branch_manager_id = COALESCE($4, branch_manager_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
|
||||
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"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
IsSelfOwned pgtype.Bool `json:"is_self_owned"`
|
||||
IsActive pgtype.Bool `json:"is_active"`
|
||||
}
|
||||
|
||||
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.CompanyID,
|
||||
arg.IsSelfOwned,
|
||||
arg.IsActive,
|
||||
)
|
||||
var i Branch
|
||||
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,
|
||||
bet365_id,
|
||||
is_active,
|
||||
is_featured,
|
||||
sport_id
|
||||
FROM leagues
|
||||
WHERE (
|
||||
|
|
@ -47,13 +48,18 @@ WHERE (
|
|||
is_active = $3
|
||||
OR $3 IS NULL
|
||||
)
|
||||
LIMIT $5 OFFSET $4
|
||||
AND (
|
||||
is_featured = $4
|
||||
OR $4 IS NULL
|
||||
)
|
||||
LIMIT $6 OFFSET $5
|
||||
`
|
||||
|
||||
type GetAllLeaguesParams struct {
|
||||
CountryCode pgtype.Text `json:"country_code"`
|
||||
SportID pgtype.Int4 `json:"sport_id"`
|
||||
IsActive pgtype.Bool `json:"is_active"`
|
||||
IsFeatured pgtype.Bool `json:"is_featured"`
|
||||
Offset pgtype.Int4 `json:"offset"`
|
||||
Limit pgtype.Int4 `json:"limit"`
|
||||
}
|
||||
|
|
@ -64,6 +70,7 @@ type GetAllLeaguesRow struct {
|
|||
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"`
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +79,7 @@ func (q *Queries) GetAllLeagues(ctx context.Context, arg GetAllLeaguesParams) ([
|
|||
arg.CountryCode,
|
||||
arg.SportID,
|
||||
arg.IsActive,
|
||||
arg.IsFeatured,
|
||||
arg.Offset,
|
||||
arg.Limit,
|
||||
)
|
||||
|
|
@ -88,6 +96,57 @@ func (q *Queries) GetAllLeagues(ctx context.Context, arg GetAllLeaguesParams) ([
|
|||
&i.CountryCode,
|
||||
&i.Bet365ID,
|
||||
&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,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -107,14 +166,16 @@ INSERT INTO leagues (
|
|||
country_code,
|
||||
bet365_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
|
||||
SET name = EXCLUDED.name,
|
||||
country_code = EXCLUDED.country_code,
|
||||
bet365_id = EXCLUDED.bet365_id,
|
||||
is_active = EXCLUDED.is_active,
|
||||
is_featured = EXCLUDED.is_featured,
|
||||
sport_id = EXCLUDED.sport_id
|
||||
`
|
||||
|
||||
|
|
@ -125,6 +186,7 @@ type InsertLeagueParams struct {
|
|||
Bet365ID pgtype.Int4 `json:"bet365_id"`
|
||||
SportID int32 `json:"sport_id"`
|
||||
IsActive pgtype.Bool `json:"is_active"`
|
||||
IsFeatured pgtype.Bool `json:"is_featured"`
|
||||
}
|
||||
|
||||
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.SportID,
|
||||
arg.IsActive,
|
||||
arg.IsFeatured,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
|
@ -161,7 +224,8 @@ SET name = COALESCE($2, name),
|
|||
country_code = COALESCE($3, country_code),
|
||||
bet365_id = COALESCE($4, bet365_id),
|
||||
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
|
||||
`
|
||||
|
||||
|
|
@ -171,6 +235,7 @@ type UpdateLeagueParams struct {
|
|||
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 pgtype.Int4 `json:"sport_id"`
|
||||
}
|
||||
|
||||
|
|
@ -181,6 +246,7 @@ func (q *Queries) UpdateLeague(ctx context.Context, arg UpdateLeagueParams) erro
|
|||
arg.CountryCode,
|
||||
arg.Bet365ID,
|
||||
arg.IsActive,
|
||||
arg.IsFeatured,
|
||||
arg.SportID,
|
||||
)
|
||||
return err
|
||||
|
|
@ -192,7 +258,8 @@ SET name = COALESCE($2, name),
|
|||
id = COALESCE($3, id),
|
||||
country_code = COALESCE($4, country_code),
|
||||
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
|
||||
`
|
||||
|
||||
|
|
@ -202,6 +269,7 @@ type UpdateLeagueByBet365IDParams struct {
|
|||
ID pgtype.Int8 `json:"id"`
|
||||
CountryCode pgtype.Text `json:"country_code"`
|
||||
IsActive pgtype.Bool `json:"is_active"`
|
||||
IsFeatured pgtype.Bool `json:"is_featured"`
|
||||
SportID pgtype.Int4 `json:"sport_id"`
|
||||
}
|
||||
|
||||
|
|
@ -212,6 +280,7 @@ func (q *Queries) UpdateLeagueByBet365ID(ctx context.Context, arg UpdateLeagueBy
|
|||
arg.ID,
|
||||
arg.CountryCode,
|
||||
arg.IsActive,
|
||||
arg.IsFeatured,
|
||||
arg.SportID,
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
ID int64 `json:"id"`
|
||||
Amount int64 `json:"amount"`
|
||||
TotalOdds float32 `json:"total_odds"`
|
||||
Status int32 `json:"status"`
|
||||
FullName string `json:"full_name"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
BranchID pgtype.Int8 `json:"branch_id"`
|
||||
UserID pgtype.Int8 `json:"user_id"`
|
||||
CashedOut bool `json:"cashed_out"`
|
||||
CashoutID string `json:"cashout_id"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
IsShopBet bool `json:"is_shop_bet"`
|
||||
ID int64 `json:"id"`
|
||||
Amount int64 `json:"amount"`
|
||||
TotalOdds float32 `json:"total_odds"`
|
||||
Status int32 `json:"status"`
|
||||
FullName string `json:"full_name"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
BranchID pgtype.Int8 `json:"branch_id"`
|
||||
UserID pgtype.Int8 `json:"user_id"`
|
||||
CashedOut bool `json:"cashed_out"`
|
||||
CashoutID string `json:"cashout_id"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
IsShopBet bool `json:"is_shop_bet"`
|
||||
OutcomesHash string `json:"outcomes_hash"`
|
||||
}
|
||||
|
||||
type BetOutcome struct {
|
||||
|
|
@ -91,21 +110,22 @@ type BetOutcome struct {
|
|||
}
|
||||
|
||||
type BetWithOutcome struct {
|
||||
ID int64 `json:"id"`
|
||||
Amount int64 `json:"amount"`
|
||||
TotalOdds float32 `json:"total_odds"`
|
||||
Status int32 `json:"status"`
|
||||
FullName string `json:"full_name"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
BranchID pgtype.Int8 `json:"branch_id"`
|
||||
UserID pgtype.Int8 `json:"user_id"`
|
||||
CashedOut bool `json:"cashed_out"`
|
||||
CashoutID string `json:"cashout_id"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
IsShopBet bool `json:"is_shop_bet"`
|
||||
Outcomes []BetOutcome `json:"outcomes"`
|
||||
ID int64 `json:"id"`
|
||||
Amount int64 `json:"amount"`
|
||||
TotalOdds float32 `json:"total_odds"`
|
||||
Status int32 `json:"status"`
|
||||
FullName string `json:"full_name"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
BranchID pgtype.Int8 `json:"branch_id"`
|
||||
UserID pgtype.Int8 `json:"user_id"`
|
||||
CashedOut bool `json:"cashed_out"`
|
||||
CashoutID string `json:"cashout_id"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
IsShopBet bool `json:"is_shop_bet"`
|
||||
OutcomesHash string `json:"outcomes_hash"`
|
||||
Outcomes []BetOutcome `json:"outcomes"`
|
||||
}
|
||||
|
||||
type Branch struct {
|
||||
|
|
@ -141,6 +161,7 @@ type BranchDetail struct {
|
|||
ManagerName interface{} `json:"manager_name"`
|
||||
ManagerPhoneNumber pgtype.Text `json:"manager_phone_number"`
|
||||
Balance pgtype.Int8 `json:"balance"`
|
||||
WalletIsActive pgtype.Bool `json:"wallet_is_active"`
|
||||
}
|
||||
|
||||
type BranchOperation struct {
|
||||
|
|
@ -179,6 +200,23 @@ type CustomerWallet struct {
|
|||
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 {
|
||||
ID string `json:"id"`
|
||||
SportID pgtype.Int4 `json:"sport_id"`
|
||||
|
|
@ -204,13 +242,31 @@ type Event struct {
|
|||
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 {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Img pgtype.Text `json:"img"`
|
||||
CountryCode pgtype.Text `json:"country_code"`
|
||||
Bet365ID pgtype.Int4 `json:"bet365_id"`
|
||||
SportID int32 `json:"sport_id"`
|
||||
IsActive pgtype.Bool `json:"is_active"`
|
||||
IsFeatured pgtype.Bool `json:"is_featured"`
|
||||
}
|
||||
|
||||
type Notification struct {
|
||||
|
|
@ -296,6 +352,18 @@ type RefreshToken struct {
|
|||
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 {
|
||||
ID int64 `json:"id"`
|
||||
BetOutcomeID int64 `json:"bet_outcome_id"`
|
||||
|
|
@ -311,6 +379,13 @@ type Result struct {
|
|||
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 {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
|
@ -434,6 +509,24 @@ type VirtualGame struct {
|
|||
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 {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
|
|
@ -450,6 +543,9 @@ type VirtualGameTransaction 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"`
|
||||
GameID pgtype.Text `json:"game_id"`
|
||||
WalletID int64 `json:"wallet_id"`
|
||||
TransactionType string `json:"transaction_type"`
|
||||
Amount int64 `json:"amount"`
|
||||
|
|
@ -470,6 +566,7 @@ type Wallet struct {
|
|||
IsActive bool `json:"is_active"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
Currency string `json:"currency"`
|
||||
BonusBalance pgtype.Numeric `json:"bonus_balance"`
|
||||
CashBalance pgtype.Numeric `json:"cash_balance"`
|
||||
}
|
||||
|
|
@ -488,7 +585,7 @@ type WalletTransfer struct {
|
|||
SenderWalletID pgtype.Int8 `json:"sender_wallet_id"`
|
||||
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||
Verified pgtype.Bool `json:"verified"`
|
||||
ReferenceNumber pgtype.Text `json:"reference_number"`
|
||||
ReferenceNumber string `json:"reference_number"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
SELECT id, amount, total_odds, ip, created_at, updated_at, outcomes
|
||||
FROM ticket_with_outcomes
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ type CreateTransferParams struct {
|
|||
SenderWalletID pgtype.Int8 `json:"sender_wallet_id"`
|
||||
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||
Verified pgtype.Bool `json:"verified"`
|
||||
ReferenceNumber pgtype.Text `json:"reference_number"`
|
||||
ReferenceNumber string `json:"reference_number"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||
}
|
||||
|
|
@ -139,7 +139,7 @@ FROM wallet_transfer
|
|||
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)
|
||||
var i WalletTransfer
|
||||
err := row.Scan(
|
||||
|
|
@ -199,6 +199,44 @@ func (q *Queries) GetTransfersByWallet(ctx context.Context, receiverWalletID pgt
|
|||
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
|
||||
UPDATE wallet_transfer
|
||||
SET status = $1,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,110 @@ import (
|
|||
"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
|
||||
INSERT INTO virtual_game_sessions (
|
||||
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
|
||||
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 (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8
|
||||
) RETURNING id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||
) 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 {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
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,
|
||||
arg.SessionID,
|
||||
arg.UserID,
|
||||
arg.CompanyID,
|
||||
arg.Provider,
|
||||
arg.WalletID,
|
||||
arg.TransactionType,
|
||||
arg.Amount,
|
||||
|
|
@ -82,11 +206,13 @@ func (q *Queries) CreateVirtualGameTransaction(ctx context.Context, arg CreateVi
|
|||
arg.ExternalTransactionID,
|
||||
arg.Status,
|
||||
)
|
||||
var i VirtualGameTransaction
|
||||
var i CreateVirtualGameTransactionRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.SessionID,
|
||||
&i.UserID,
|
||||
&i.CompanyID,
|
||||
&i.Provider,
|
||||
&i.WalletID,
|
||||
&i.TransactionType,
|
||||
&i.Amount,
|
||||
|
|
@ -122,15 +248,81 @@ func (q *Queries) GetVirtualGameSessionByToken(ctx context.Context, sessionToken
|
|||
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
|
||||
SELECT id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
||||
FROM virtual_game_transactions
|
||||
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)
|
||||
var i VirtualGameTransaction
|
||||
var i GetVirtualGameTransactionByExternalIDRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.SessionID,
|
||||
|
|
@ -147,6 +339,47 @@ func (q *Queries) GetVirtualGameTransactionByExternalID(ctx context.Context, ext
|
|||
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
|
||||
UPDATE virtual_game_sessions
|
||||
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ INSERT INTO wallets (
|
|||
user_id
|
||||
)
|
||||
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 {
|
||||
|
|
@ -77,6 +77,7 @@ func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wal
|
|||
&i.IsActive,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Currency,
|
||||
&i.BonusBalance,
|
||||
&i.CashBalance,
|
||||
)
|
||||
|
|
@ -142,8 +143,48 @@ func (q *Queries) GetAllBranchWallets(ctx context.Context) ([]GetAllBranchWallet
|
|||
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
|
||||
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
|
||||
`
|
||||
|
||||
|
|
@ -166,6 +207,7 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) {
|
|||
&i.IsActive,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Currency,
|
||||
&i.BonusBalance,
|
||||
&i.CashBalance,
|
||||
); err != nil {
|
||||
|
|
@ -179,37 +221,59 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) {
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const GetCustomerWallet = `-- name: GetCustomerWallet :one
|
||||
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.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
|
||||
const GetBranchByWalletID = `-- 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
|
||||
`
|
||||
|
||||
type GetCustomerWalletRow 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"`
|
||||
RegularUpdatedAt pgtype.Timestamp `json:"regular_updated_at"`
|
||||
StaticUpdatedAt pgtype.Timestamp `json:"static_updated_at"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
func (q *Queries) GetBranchByWalletID(ctx context.Context, walletID int64) (Branch, error) {
|
||||
row := q.db.QueryRow(ctx, GetBranchByWalletID, walletID)
|
||||
var i Branch
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Location,
|
||||
&i.IsActive,
|
||||
&i.WalletID,
|
||||
&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)
|
||||
var i GetCustomerWalletRow
|
||||
var i CustomerWalletDetail
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CustomerID,
|
||||
|
|
@ -217,15 +281,20 @@ func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (GetC
|
|||
&i.RegularBalance,
|
||||
&i.StaticID,
|
||||
&i.StaticBalance,
|
||||
&i.RegularIsActive,
|
||||
&i.StaticIsActive,
|
||||
&i.RegularUpdatedAt,
|
||||
&i.StaticUpdatedAt,
|
||||
&i.CreatedAt,
|
||||
&i.FirstName,
|
||||
&i.LastName,
|
||||
&i.PhoneNumber,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
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
|
||||
WHERE id = $1
|
||||
`
|
||||
|
|
@ -243,6 +312,7 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) {
|
|||
&i.IsActive,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Currency,
|
||||
&i.BonusBalance,
|
||||
&i.CashBalance,
|
||||
)
|
||||
|
|
@ -250,7 +320,7 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) {
|
|||
}
|
||||
|
||||
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
|
||||
WHERE user_id = $1
|
||||
`
|
||||
|
|
@ -274,6 +344,7 @@ func (q *Queries) GetWalletByUserID(ctx context.Context, userID int64) ([]Wallet
|
|||
&i.IsActive,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Currency,
|
||||
&i.BonusBalance,
|
||||
&i.CashBalance,
|
||||
); err != nil {
|
||||
|
|
|
|||
14
go.mod
14
go.mod
|
|
@ -77,4 +77,16 @@ require (
|
|||
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.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
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/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
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/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/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
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/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
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.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
||||
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/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/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
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/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/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-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
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.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
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/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/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ=
|
||||
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.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
||||
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/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
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/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/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.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
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-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-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-20210805182204-aaa1db679c0d/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-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-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-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-20210809222454-d867a43fc93e/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.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
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-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.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.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
|
|
|
|||
|
|
@ -13,25 +13,28 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
ErrInvalidDbUrl = errors.New("db url is invalid")
|
||||
ErrInvalidPort = errors.New("port number is invalid")
|
||||
ErrRefreshExpiry = errors.New("refresh token expiry is invalid")
|
||||
ErrAccessExpiry = errors.New("access token expiry is invalid")
|
||||
ErrInvalidJwtKey = errors.New("jwt key is invalid")
|
||||
ErrLogLevel = errors.New("log level not set")
|
||||
ErrInvalidLevel = errors.New("invalid log level")
|
||||
ErrInvalidEnv = errors.New("env not set or invalid")
|
||||
ErrInvalidSMSAPIKey = errors.New("SMS API key is invalid")
|
||||
ErrMissingBetToken = errors.New("missing BET365_TOKEN in .env")
|
||||
ErrInvalidPopOKClientID = errors.New("PopOK client ID is invalid")
|
||||
ErrInvalidPopOKSecretKey = errors.New("PopOK secret key is invalid")
|
||||
ErrInvalidPopOKBaseURL = errors.New("PopOK base URL is invalid")
|
||||
ErrInvalidPopOKCallbackURL = errors.New("PopOK callback URL is invalid")
|
||||
ErrInvalidVeliAPIURL = errors.New("Veli API URL is invalid")
|
||||
ErrInvalidVeliOperatorKey = errors.New("Veli operator key is invalid")
|
||||
ErrInvalidVeliSecretKey = errors.New("Veli secret key is invalid")
|
||||
ErrMissingResendApiKey = errors.New("missing Resend Api key")
|
||||
ErrMissingResendSenderEmail = errors.New("missing Resend sender name")
|
||||
ErrInvalidDbUrl = errors.New("db url is invalid")
|
||||
ErrInvalidPort = errors.New("port number is invalid")
|
||||
ErrRefreshExpiry = errors.New("refresh token expiry is invalid")
|
||||
ErrAccessExpiry = errors.New("access token expiry is invalid")
|
||||
ErrInvalidJwtKey = errors.New("jwt key is invalid")
|
||||
ErrLogLevel = errors.New("log level not set")
|
||||
ErrInvalidLevel = errors.New("invalid log level")
|
||||
ErrInvalidEnv = errors.New("env not set or invalid")
|
||||
ErrInvalidSMSAPIKey = errors.New("SMS API key is invalid")
|
||||
ErrMissingBetToken = errors.New("missing BET365_TOKEN in .env")
|
||||
ErrInvalidPopOKClientID = errors.New("PopOK client ID is invalid")
|
||||
ErrInvalidPopOKSecretKey = errors.New("PopOK secret key is invalid")
|
||||
ErrInvalidPopOKBaseURL = errors.New("PopOK base URL is invalid")
|
||||
ErrInvalidPopOKCallbackURL = errors.New("PopOK callback URL is invalid")
|
||||
ErrInvalidVeliAPIURL = errors.New("Veli API URL is invalid")
|
||||
ErrInvalidVeliOperatorKey = errors.New("Veli operator key is invalid")
|
||||
ErrInvalidVeliSecretKey = errors.New("Veli secret key is invalid")
|
||||
ErrMissingResendApiKey = errors.New("missing Resend Api key")
|
||||
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 {
|
||||
|
|
@ -44,15 +47,14 @@ type AleaPlayConfig struct {
|
|||
SessionTimeout int `mapstructure:"session_timeout"` // In hours
|
||||
}
|
||||
|
||||
type VeliGamesConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
APIURL string `mapstructure:"api_url"`
|
||||
OperatorKey string `mapstructure:"operator_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
DefaultCurrency string `mapstructure:"default_currency"`
|
||||
GameIDs struct {
|
||||
Aviator string `mapstructure:"aviator"`
|
||||
} `mapstructure:"game_ids"`
|
||||
type VeliConfig struct {
|
||||
APIKey string `mapstructure:"VELI_API_KEY"`
|
||||
BaseURL string `mapstructure:"VELI_BASE_URL"`
|
||||
SecretKey string `mapstructure:"VELI_SECRET_KEY"`
|
||||
OperatorID string `mapstructure:"VELI_OPERATOR_ID"`
|
||||
Currency string `mapstructure:"VELI_DEFAULT_CURRENCY"`
|
||||
WebhookURL string `mapstructure:"VELI_WEBHOOK_URL"`
|
||||
Enabled bool `mapstructure:"Enabled"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
|
@ -60,6 +62,7 @@ type Config struct {
|
|||
FIXER_BASE_URL string
|
||||
BASE_CURRENCY domain.IntCurrency
|
||||
Port int
|
||||
Service string
|
||||
DbUrl string
|
||||
RefreshExpiry int
|
||||
AccessExpiry int
|
||||
|
|
@ -81,10 +84,14 @@ type Config struct {
|
|||
CHAPA_RETURN_URL string
|
||||
Bet365Token string
|
||||
PopOK domain.PopOKConfig
|
||||
AleaPlay AleaPlayConfig `mapstructure:"alea_play"`
|
||||
VeliGames VeliGamesConfig `mapstructure:"veli_games"`
|
||||
AleaPlay AleaPlayConfig `mapstructure:"alea_play"`
|
||||
VeliGames VeliConfig `mapstructure:"veli_games"`
|
||||
ResendApiKey string
|
||||
ResendSenderEmail string
|
||||
TwilioAccountSid string
|
||||
TwilioAuthToken string
|
||||
TwilioSenderPhoneNumber string
|
||||
RedisAddr string
|
||||
}
|
||||
|
||||
func NewConfig() (*Config, error) {
|
||||
|
|
@ -109,6 +116,8 @@ func (c *Config) loadEnv() error {
|
|||
|
||||
c.ReportExportPath = os.Getenv("REPORT_EXPORT_PATH")
|
||||
|
||||
c.RedisAddr = os.Getenv("REDIS_ADDR")
|
||||
|
||||
c.CHAPA_TRANSFER_TYPE = os.Getenv("CHAPA_TRANSFER_TYPE")
|
||||
c.CHAPA_PAYMENT_TYPE = os.Getenv("CHAPA_PAYMENT_TYPE")
|
||||
|
||||
|
|
@ -236,26 +245,26 @@ func (c *Config) loadEnv() error {
|
|||
if apiURL == "" {
|
||||
apiURL = "https://api.velitech.games" // Default production URL
|
||||
}
|
||||
c.VeliGames.APIURL = apiURL
|
||||
c.VeliGames.BaseURL = apiURL
|
||||
|
||||
operatorKey := os.Getenv("VELI_OPERATOR_KEY")
|
||||
if operatorKey == "" && c.VeliGames.Enabled {
|
||||
return ErrInvalidVeliOperatorKey
|
||||
}
|
||||
c.VeliGames.OperatorKey = operatorKey
|
||||
// c.VeliGames.OperatorKey = operatorKey
|
||||
|
||||
secretKey := os.Getenv("VELI_SECRET_KEY")
|
||||
if secretKey == "" && c.VeliGames.Enabled {
|
||||
return ErrInvalidVeliSecretKey
|
||||
}
|
||||
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")
|
||||
if defaultCurrency == "" {
|
||||
defaultCurrency = "USD" // Default currency
|
||||
}
|
||||
c.VeliGames.DefaultCurrency = defaultCurrency
|
||||
// c.VeliGames.DefaultCurrency = defaultCurrency
|
||||
|
||||
c.LogLevel = lvl
|
||||
|
||||
|
|
@ -324,6 +333,24 @@ func (c *Config) loadEnv() error {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,9 +57,13 @@ type Bet struct {
|
|||
}
|
||||
|
||||
type BetFilter struct {
|
||||
BranchID ValidInt64 // Can Be Nullable
|
||||
CompanyID ValidInt64 // Can Be Nullable
|
||||
UserID ValidInt64 // Can Be Nullable
|
||||
BranchID ValidInt64 // Can Be Nullable
|
||||
CompanyID ValidInt64 // Can Be Nullable
|
||||
UserID ValidInt64 // Can Be Nullable
|
||||
IsShopBet ValidBool
|
||||
Query ValidString
|
||||
CreatedBefore ValidTime
|
||||
CreatedAfter ValidTime
|
||||
}
|
||||
|
||||
type GetBet struct {
|
||||
|
|
@ -80,16 +84,17 @@ type GetBet struct {
|
|||
}
|
||||
|
||||
type CreateBet struct {
|
||||
Amount Currency
|
||||
TotalOdds float32
|
||||
Status OutcomeStatus
|
||||
FullName string
|
||||
PhoneNumber string
|
||||
CompanyID ValidInt64 // Can Be Nullable
|
||||
BranchID ValidInt64 // Can Be Nullable
|
||||
UserID ValidInt64 // Can Be Nullable
|
||||
IsShopBet bool
|
||||
CashoutID string
|
||||
Amount Currency
|
||||
TotalOdds float32
|
||||
Status OutcomeStatus
|
||||
FullName string
|
||||
PhoneNumber string
|
||||
CompanyID ValidInt64 // Can Be Nullable
|
||||
BranchID ValidInt64 // Can Be Nullable
|
||||
UserID ValidInt64 // Can Be Nullable
|
||||
IsShopBet bool
|
||||
CashoutID string
|
||||
OutcomesHash string
|
||||
}
|
||||
|
||||
type CreateBetOutcomeReq struct {
|
||||
|
|
@ -173,4 +178,3 @@ func ConvertBet(bet GetBet) BetRes {
|
|||
CreatedAt: bet.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,19 @@ type Branch struct {
|
|||
WalletID int64
|
||||
BranchManagerID int64
|
||||
CompanyID int64
|
||||
IsSuspended bool
|
||||
IsActive bool
|
||||
IsSelfOwned bool
|
||||
}
|
||||
|
||||
type BranchFilter struct {
|
||||
CompanyID ValidInt64
|
||||
IsActive ValidBool
|
||||
BranchManagerID ValidInt64
|
||||
Query ValidString
|
||||
CreatedBefore ValidTime
|
||||
CreatedAfter ValidTime
|
||||
}
|
||||
|
||||
type BranchDetail struct {
|
||||
ID int64
|
||||
Name string
|
||||
|
|
@ -19,10 +28,11 @@ type BranchDetail struct {
|
|||
Balance Currency
|
||||
BranchManagerID int64
|
||||
CompanyID int64
|
||||
IsSuspended bool
|
||||
IsActive bool
|
||||
IsSelfOwned bool
|
||||
ManagerName string
|
||||
ManagerPhoneNumber string
|
||||
WalletIsActive bool
|
||||
}
|
||||
|
||||
type SupportedOperation struct {
|
||||
|
|
@ -53,6 +63,7 @@ type UpdateBranch struct {
|
|||
BranchManagerID *int64
|
||||
CompanyID *int64
|
||||
IsSelfOwned *bool
|
||||
IsActive *bool
|
||||
}
|
||||
|
||||
type CreateSupportedOperation struct {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ type PaymentStatus string
|
|||
type WithdrawalStatus string
|
||||
|
||||
const (
|
||||
WithdrawalStatusSuccessful WithdrawalStatus = "success"
|
||||
WithdrawalStatusPending WithdrawalStatus = "pending"
|
||||
WithdrawalStatusProcessing WithdrawalStatus = "processing"
|
||||
WithdrawalStatusCompleted WithdrawalStatus = "completed"
|
||||
|
|
@ -23,9 +24,10 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
PaymentStatusPending PaymentStatus = "pending"
|
||||
PaymentStatusCompleted PaymentStatus = "completed"
|
||||
PaymentStatusFailed PaymentStatus = "failed"
|
||||
PaymentStatusSuccessful PaymentStatus = "success"
|
||||
PaymentStatusPending PaymentStatus = "pending"
|
||||
PaymentStatusCompleted PaymentStatus = "completed"
|
||||
PaymentStatusFailed PaymentStatus = "failed"
|
||||
)
|
||||
|
||||
type ChapaDepositRequest struct {
|
||||
|
|
@ -70,22 +72,23 @@ type ChapaVerificationResponse struct {
|
|||
TxRef string `json:"tx_ref"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
// 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
|
||||
// }
|
||||
|
||||
type BankResponse struct {
|
||||
Message string `json:"message"`
|
||||
|
|
@ -142,11 +145,9 @@ type ChapaWithdrawalRequest struct {
|
|||
// }
|
||||
|
||||
type ChapaWithdrawalResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
Reference string `json:"reference"`
|
||||
} `json:"data"`
|
||||
Status string `json:"status"`
|
||||
Data string `json:"data"` // Accepts string instead of struct
|
||||
}
|
||||
|
||||
type ChapaTransactionType struct {
|
||||
|
|
|
|||
|
|
@ -78,3 +78,6 @@ func CalculateWinnings(amount Currency, totalOdds float32) Currency {
|
|||
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"`
|
||||
IsActive bool `json:"is_active" example:"false"`
|
||||
SportID int32 `json:"sport_id" example:"1"`
|
||||
IsFeatured bool `json:"is_featured" example:"false"`
|
||||
}
|
||||
|
||||
type UpdateLeague struct {
|
||||
|
|
@ -15,6 +16,7 @@ type UpdateLeague struct {
|
|||
CountryCode ValidString `json:"cc" example:"uk"`
|
||||
Bet365ID ValidInt32 `json:"bet365_id" example:"1121"`
|
||||
IsActive ValidBool `json:"is_active" example:"false"`
|
||||
IsFeatured ValidBool `json:"is_featured" example:"false"`
|
||||
SportID ValidInt32 `json:"sport_id" example:"1"`
|
||||
}
|
||||
|
||||
|
|
@ -22,6 +24,69 @@ type LeagueFilter struct {
|
|||
CountryCode ValidString
|
||||
SportID ValidInt32
|
||||
IsActive ValidBool
|
||||
IsFeatured ValidBool
|
||||
Limit 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
|
||||
|
||||
const (
|
||||
NotificationTypeCashOutSuccess NotificationType = "cash_out_success"
|
||||
NotificationTypeDepositSuccess NotificationType = "deposit_success"
|
||||
NotificationTypeBetPlaced NotificationType = "bet_placed"
|
||||
NotificationTypeDailyReport NotificationType = "daily_report"
|
||||
NotificationTypeHighLossOnBet NotificationType = "high_loss_on_bet"
|
||||
NotificationTypeBetOverload NotificationType = "bet_overload"
|
||||
NotificationTypeSignUpWelcome NotificationType = "signup_welcome"
|
||||
NotificationTypeOTPSent NotificationType = "otp_sent"
|
||||
NOTIFICATION_TYPE_WALLET NotificationType = "wallet_threshold"
|
||||
NOTIFICATION_TYPE_TRANSFER NotificationType = "transfer_failed"
|
||||
NOTIFICATION_TYPE_ADMIN_ALERT NotificationType = "admin_alert"
|
||||
NOTIFICATION_RECEIVER_ADMIN NotificationRecieverSide = "admin"
|
||||
NotificationTypeCashOutSuccess NotificationType = "cash_out_success"
|
||||
NotificationTypeDepositSuccess NotificationType = "deposit_success"
|
||||
NotificationTypeWithdrawSuccess NotificationType = "withdraw_success"
|
||||
NotificationTypeBetPlaced NotificationType = "bet_placed"
|
||||
NotificationTypeDailyReport NotificationType = "daily_report"
|
||||
NotificationTypeHighLossOnBet NotificationType = "high_loss_on_bet"
|
||||
NotificationTypeBetOverload NotificationType = "bet_overload"
|
||||
NotificationTypeSignUpWelcome NotificationType = "signup_welcome"
|
||||
NotificationTypeOTPSent NotificationType = "otp_sent"
|
||||
NOTIFICATION_TYPE_WALLET NotificationType = "wallet_threshold"
|
||||
NOTIFICATION_TYPE_TRANSFER_FAIL NotificationType = "transfer_failed"
|
||||
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"
|
||||
NotificationRecieverSideCustomer NotificationRecieverSide = "customer"
|
||||
NotificationRecieverSideCashier NotificationRecieverSide = "cashier"
|
||||
|
|
@ -57,9 +60,9 @@ const (
|
|||
)
|
||||
|
||||
type NotificationPayload struct {
|
||||
Headline string `json:"headline"`
|
||||
Message string `json:"message"`
|
||||
Tags []string `json:"tags"`
|
||||
Headline string `json:"headline"`
|
||||
Message string `json:"message"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
type Notification struct {
|
||||
|
|
@ -91,3 +94,19 @@ func FromJSON(data []byte) (*Notification, error) {
|
|||
}
|
||||
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"
|
||||
)
|
||||
|
||||
type OtpProvider string
|
||||
|
||||
const (
|
||||
TwilioSms OtpProvider = "twilio"
|
||||
AfroMessage OtpProvider = "aformessage"
|
||||
)
|
||||
|
||||
type Otp struct {
|
||||
ID int64
|
||||
SentTo string
|
||||
|
|
|
|||
|
|
@ -10,6 +10,39 @@ const (
|
|||
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 {
|
||||
ID string
|
||||
TimeFrame TimeFrame
|
||||
|
|
@ -22,6 +55,22 @@ type Report struct {
|
|||
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 {
|
||||
TotalStakes Currency `json:"total_stakes"`
|
||||
TotalBets int64 `json:"total_bets"`
|
||||
|
|
@ -319,3 +368,41 @@ type CashierPerformance struct {
|
|||
LastActivity time.Time `json:"last_activity"`
|
||||
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
|
||||
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
|
||||
|
||||
// Info on why the wallet was modified
|
||||
// If its internal system modification then, its always direct
|
||||
const (
|
||||
TRANSFER_DIRECT PaymentMethod = "direct"
|
||||
TRANSFER_CASH PaymentMethod = "cash"
|
||||
TRANSFER_BANK PaymentMethod = "bank"
|
||||
TRANSFER_CHAPA PaymentMethod = "chapa"
|
||||
|
|
@ -22,31 +25,36 @@ const (
|
|||
TRANSFER_OTHER PaymentMethod = "other"
|
||||
)
|
||||
|
||||
// There is always a receiving wallet id
|
||||
// There is a sender wallet id only if wallet transfer type
|
||||
// Info for the payment providers
|
||||
type PaymentDetails struct {
|
||||
ReferenceNumber ValidString
|
||||
BankNumber ValidString
|
||||
}
|
||||
|
||||
// A Transfer is logged for every modification of ALL wallets and wallet types
|
||||
type Transfer struct {
|
||||
ID int64
|
||||
Amount Currency
|
||||
Verified bool
|
||||
Type TransferType
|
||||
PaymentMethod PaymentMethod
|
||||
ReceiverWalletID int64
|
||||
SenderWalletID int64
|
||||
ReferenceNumber string
|
||||
Status string
|
||||
CashierID ValidInt64
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
ID int64 `json:"id"`
|
||||
Amount Currency `json:"amount"`
|
||||
Verified bool `json:"verified"`
|
||||
Type TransferType `json:"type"`
|
||||
PaymentMethod PaymentMethod `json:"payment_method"`
|
||||
ReceiverWalletID ValidInt64 `json:"receiver_wallet_id"`
|
||||
SenderWalletID ValidInt64 `json:"sender_wallet_id"`
|
||||
ReferenceNumber string `json:"reference_number"` // <-- needed
|
||||
Status string `json:"status"`
|
||||
CashierID ValidInt64 `json:"cashier_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CreateTransfer struct {
|
||||
Amount Currency
|
||||
Verified bool
|
||||
ReferenceNumber string
|
||||
Status string
|
||||
ReceiverWalletID int64
|
||||
SenderWalletID int64
|
||||
CashierID ValidInt64
|
||||
Type TransferType
|
||||
PaymentMethod PaymentMethod
|
||||
Amount Currency `json:"amount"`
|
||||
Verified bool `json:"verified"`
|
||||
Type TransferType `json:"type"`
|
||||
PaymentMethod PaymentMethod `json:"payment_method"`
|
||||
ReceiverWalletID ValidInt64 `json:"receiver_wallet_id"`
|
||||
SenderWalletID ValidInt64 `json:"sender_wallet_id"`
|
||||
ReferenceNumber string `json:"reference_number"` // <-- needed
|
||||
Status string `json:"status"`
|
||||
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"
|
||||
)
|
||||
|
||||
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 {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
|
@ -38,10 +62,31 @@ type VirtualGameSession struct {
|
|||
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 {
|
||||
ID int64 `json:"id"`
|
||||
SessionID int64 `json:"session_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"`
|
||||
TransactionType string `json:"transaction_type"` // BET, WIN, REFUND, CASHOUT, etc.
|
||||
Amount int64 `json:"amount"` // Always in cents
|
||||
|
|
@ -143,6 +188,11 @@ type PopOKWinResponse struct {
|
|||
Balance float64 `json:"balance"`
|
||||
}
|
||||
|
||||
type PopOKGenerateTokenRequest struct {
|
||||
GameID string `json:"newGameId"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type PopOKCancelRequest struct {
|
||||
ExternalToken string `json:"externalToken"`
|
||||
PlayerID string `json:"playerId"`
|
||||
|
|
@ -156,6 +206,10 @@ type PopOKCancelResponse struct {
|
|||
Balance float64 `json:"balance"`
|
||||
}
|
||||
|
||||
type PopOKGenerateTokenResponse struct {
|
||||
NewToken string `json:"newToken"`
|
||||
}
|
||||
|
||||
type AleaPlayCallback struct {
|
||||
EventID string `json:"event_id"`
|
||||
TransactionID string `json:"transaction_id"`
|
||||
|
|
@ -191,3 +245,27 @@ type GameSpecificData struct {
|
|||
RiskLevel string `json:"risk_level,omitempty"` // For Mines
|
||||
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
|
||||
}
|
||||
|
||||
type WalletFilter struct {
|
||||
IsActive ValidBool
|
||||
Query ValidString
|
||||
CreatedBefore ValidTime
|
||||
CreatedAfter ValidTime
|
||||
}
|
||||
|
||||
type CustomerWallet struct {
|
||||
ID int64
|
||||
RegularID int64
|
||||
|
|
@ -28,9 +35,14 @@ type GetCustomerWallet struct {
|
|||
StaticID int64
|
||||
StaticBalance Currency
|
||||
CustomerID int64
|
||||
RegularIsActive bool
|
||||
StaticIsActive bool
|
||||
RegularUpdatedAt time.Time
|
||||
StaticUpdatedAt time.Time
|
||||
CreatedAt time.Time
|
||||
FirstName string
|
||||
LastName string
|
||||
PhoneNumber string
|
||||
}
|
||||
|
||||
type BranchWallet struct {
|
||||
|
|
@ -58,3 +70,11 @@ type CreateCustomerWallet struct {
|
|||
RegularWalletID 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"
|
||||
"os"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func InitLogger() (*zap.Logger, error) {
|
||||
func InitLogger(cfg *config.Config) (*zap.Logger, error) {
|
||||
mongoCore, err := NewMongoCore(
|
||||
"mongodb://root:secret@localhost:27017/?authSource=admin",
|
||||
os.Getenv("MONGODB_URL"),
|
||||
"logdb",
|
||||
"applogs",
|
||||
zapcore.InfoLevel,
|
||||
cfg,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create MongoDB core: %w", err)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"maps"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
|
|
@ -17,9 +18,10 @@ type MongoCore struct {
|
|||
collection *mongo.Collection
|
||||
level zapcore.Level
|
||||
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)
|
||||
defer cancel()
|
||||
|
||||
|
|
@ -36,6 +38,7 @@ func NewMongoCore(uri, dbName, collectionName string, level zapcore.Level) (zapc
|
|||
return &MongoCore{
|
||||
collection: coll,
|
||||
level: level,
|
||||
cfg: cfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -73,8 +76,8 @@ func (mc *MongoCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
|||
"fields": logMap,
|
||||
"caller": entry.Caller.String(),
|
||||
"stacktrace": entry.Stack,
|
||||
"service": "fortunebet-backend",
|
||||
"env": "dev",
|
||||
"service": mc.cfg.Service,
|
||||
"env": mc.cfg.Env,
|
||||
}
|
||||
|
||||
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,
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
err := s.queries.UpdateCashOut(ctx, dbgen.UpdateCashOutParams{
|
||||
ID: id,
|
||||
|
|
@ -295,8 +324,19 @@ func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.Outcom
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error) {
|
||||
outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, eventID)
|
||||
func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) {
|
||||
|
||||
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 {
|
||||
domain.MongoDBLogger.Error("failed to get bet outcomes by event ID",
|
||||
zap.Int64("event_id", eventID),
|
||||
|
|
@ -347,8 +387,44 @@ func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status dom
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (s *Store) DeleteBet(ctx context.Context, id int64) error {
|
||||
return s.queries.DeleteBet(ctx, id)
|
||||
func (s *Store) UpdateBetOutcomeStatusByBetID(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ func convertDBBranchDetail(dbBranch dbgen.BranchDetail) domain.BranchDetail {
|
|||
ManagerName: dbBranch.ManagerName.(string),
|
||||
ManagerPhoneNumber: dbBranch.ManagerPhoneNumber.String,
|
||||
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,
|
||||
}
|
||||
}
|
||||
if updateBranch.IsActive != nil {
|
||||
newUpdateBranch.IsActive = pgtype.Bool{
|
||||
Bool: *updateBranch.IsActive,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
return newUpdateBranch
|
||||
}
|
||||
|
|
@ -128,8 +136,29 @@ func (s *Store) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]do
|
|||
return branches, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetAllBranches(ctx context.Context) ([]domain.BranchDetail, error) {
|
||||
dbBranches, err := s.queries.GetAllBranches(ctx)
|
||||
func (s *Store) GetAllBranches(ctx context.Context, filter domain.BranchFilter) ([]domain.BranchDetail, error) {
|
||||
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 {
|
||||
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},
|
||||
Bet365ID: pgtype.Int4{Int32: l.Bet365ID, Valid: true},
|
||||
IsActive: pgtype.Bool{Bool: l.IsActive, Valid: true},
|
||||
IsFeatured: pgtype.Bool{Bool: l.IsFeatured, Valid: true},
|
||||
SportID: l.SportID,
|
||||
})
|
||||
}
|
||||
|
|
@ -33,6 +34,10 @@ func (s *Store) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) (
|
|||
Bool: filter.IsActive.Value,
|
||||
Valid: filter.IsActive.Valid,
|
||||
},
|
||||
IsFeatured: pgtype.Bool{
|
||||
Bool: filter.IsFeatured.Value,
|
||||
Valid: filter.IsFeatured.Valid,
|
||||
},
|
||||
Limit: pgtype.Int4{
|
||||
Int32: int32(filter.Limit.Value),
|
||||
Valid: filter.Limit.Valid,
|
||||
|
|
@ -54,12 +59,35 @@ func (s *Store) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) (
|
|||
CountryCode: league.CountryCode.String,
|
||||
Bet365ID: league.Bet365ID.Int32,
|
||||
IsActive: league.IsActive.Bool,
|
||||
IsFeatured: league.IsFeatured.Bool,
|
||||
SportID: league.SportID,
|
||||
}
|
||||
}
|
||||
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) {
|
||||
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,
|
||||
Valid: league.IsActive.Valid,
|
||||
},
|
||||
IsFeatured: pgtype.Bool{
|
||||
Bool: league.IsFeatured.Value,
|
||||
Valid: league.IsActive.Valid,
|
||||
},
|
||||
SportID: pgtype.Int4{
|
||||
Int32: league.SportID.Value,
|
||||
Valid: league.SportID.Valid,
|
||||
|
|
|
|||
|
|
@ -317,6 +317,40 @@ func (s *Store) CountUnreadNotifications(ctx context.Context, userID int64) (int
|
|||
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) {
|
||||
// dbNotifications, err := s.queries.GetAllNotifications(ctx, dbgen.GetAllNotificationsParams{
|
||||
// Limit: int32(limit),
|
||||
|
|
|
|||
|
|
@ -2,15 +2,28 @@ package repository
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type ReportRepository interface {
|
||||
GenerateReport(timeFrame domain.TimeFrame, start, end time.Time) (*domain.Report, error)
|
||||
SaveReport(report *domain.Report) error
|
||||
FindReportsByTimeFrame(timeFrame domain.TimeFrame, limit int) ([]*domain.Report, error)
|
||||
|
||||
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 {
|
||||
|
|
@ -105,3 +118,117 @@ func (r *ReportRepo) FindReportsByTimeFrame(timeFrame domain.TimeFrame, limit in
|
|||
|
||||
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 {
|
||||
return domain.Transfer{
|
||||
ID: transfer.ID,
|
||||
Amount: domain.Currency(transfer.Amount.Int64),
|
||||
Type: domain.TransferType(transfer.Type.String),
|
||||
Verified: transfer.Verified.Bool,
|
||||
ReceiverWalletID: transfer.ReceiverWalletID.Int64,
|
||||
SenderWalletID: transfer.SenderWalletID.Int64,
|
||||
ID: transfer.ID,
|
||||
Amount: domain.Currency(transfer.Amount.Int64),
|
||||
Type: domain.TransferType(transfer.Type.String),
|
||||
Verified: transfer.Verified.Bool,
|
||||
ReceiverWalletID: domain.ValidInt64{
|
||||
Value: transfer.ReceiverWalletID.Int64,
|
||||
Valid: transfer.ReceiverWalletID.Valid,
|
||||
},
|
||||
SenderWalletID: domain.ValidInt64{
|
||||
Value: transfer.SenderWalletID.Int64,
|
||||
Valid: transfer.SenderWalletID.Valid,
|
||||
},
|
||||
CashierID: domain.ValidInt64{
|
||||
Value: transfer.CashierID.Int64,
|
||||
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},
|
||||
Type: pgtype.Text{String: string(transfer.Type), Valid: true},
|
||||
ReceiverWalletID: pgtype.Int8{
|
||||
Int64: transfer.ReceiverWalletID,
|
||||
Valid: true,
|
||||
Int64: transfer.ReceiverWalletID.Value,
|
||||
Valid: transfer.ReceiverWalletID.Valid,
|
||||
},
|
||||
SenderWalletID: pgtype.Int8{
|
||||
Int64: transfer.SenderWalletID,
|
||||
Valid: true,
|
||||
Int64: transfer.SenderWalletID.Value,
|
||||
Valid: transfer.SenderWalletID.Valid,
|
||||
},
|
||||
CashierID: pgtype.Int8{
|
||||
Int64: transfer.CashierID.Value,
|
||||
Valid: transfer.CashierID.Valid,
|
||||
},
|
||||
ReferenceNumber: string(transfer.ReferenceNumber),
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (s *Store) GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) {
|
||||
transfers, err := s.queries.GetTransfersByWallet(ctx, pgtype.Int8{Int64: walletID, Valid: true})
|
||||
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) {
|
||||
transfer, err := s.queries.GetTransferByReference(ctx, pgtype.Text{String: reference, Valid: true})
|
||||
transfer, err := s.queries.GetTransferByReference(ctx, reference)
|
||||
if err != nil {
|
||||
return domain.Transfer{}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,13 @@ type VirtualGameRepository interface {
|
|||
GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error)
|
||||
UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) 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)
|
||||
GetUserGameHistory(ctx context.Context, userID int64) ([]domain.VirtualGameHistory, error)
|
||||
CreateVirtualGameHistory(ctx context.Context, his *domain.VirtualGameHistory) error
|
||||
}
|
||||
|
||||
type VirtualGameRepo struct {
|
||||
|
|
@ -36,6 +41,26 @@ func NewVirtualGameRepository(store *Store) VirtualGameRepository {
|
|||
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 {
|
||||
params := dbgen.CreateVirtualGameSessionParams{
|
||||
UserID: session.UserID,
|
||||
|
|
@ -92,6 +117,21 @@ func (r *VirtualGameRepo) CreateVirtualGameTransaction(ctx context.Context, tx *
|
|||
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) {
|
||||
dbTx, err := r.store.queries.GetVirtualGameTransactionByExternalID(ctx, externalID)
|
||||
if err != nil {
|
||||
|
|
@ -153,6 +193,24 @@ func (r *VirtualGameRepo) GetGameCounts(ctx context.Context, filter domain.Repor
|
|||
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 {
|
||||
// _, tx, err := r.store.BeginTx(ctx)
|
||||
// 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{
|
||||
ID: customerWallet.ID,
|
||||
RegularID: customerWallet.RegularID,
|
||||
|
|
@ -55,9 +55,14 @@ func convertDBGetCustomerWallet(customerWallet dbgen.GetCustomerWalletRow) domai
|
|||
StaticID: customerWallet.StaticID,
|
||||
StaticBalance: domain.Currency(customerWallet.StaticBalance),
|
||||
CustomerID: customerWallet.CustomerID,
|
||||
RegularIsActive: customerWallet.RegularIsActive,
|
||||
StaticIsActive: customerWallet.StaticIsActive,
|
||||
RegularUpdatedAt: customerWallet.RegularUpdatedAt.Time,
|
||||
StaticUpdatedAt: customerWallet.StaticUpdatedAt.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
|
||||
}
|
||||
|
||||
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) {
|
||||
customerWallet, err := s.queries.GetCustomerWallet(ctx, customerID)
|
||||
|
||||
|
|
@ -257,3 +275,4 @@ func (s *Store) GetTotalWallets(ctx context.Context, filter domain.ReportFilter)
|
|||
|
||||
return total, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ type BetStore interface {
|
|||
GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error)
|
||||
GetBetByBranchID(ctx context.Context, BranchID 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)
|
||||
GetBetCount(ctx context.Context, userID int64, outcomesHash string) (int64, error)
|
||||
UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error
|
||||
UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) 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) (
|
||||
totalStakes domain.Currency,
|
||||
|
|
|
|||
|
|
@ -3,13 +3,17 @@ package bet
|
|||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math/big"
|
||||
random "math/rand"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
|
|
@ -25,6 +29,12 @@ var (
|
|||
ErrGenerateRandomOutcome = errors.New("Failed to generate any random outcome for events")
|
||||
ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending")
|
||||
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 {
|
||||
|
|
@ -37,7 +47,15 @@ type Service struct {
|
|||
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{
|
||||
betStore: betStore,
|
||||
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) {
|
||||
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
const length int = 13
|
||||
|
|
@ -196,6 +207,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
|||
var totalOdds float32 = 1
|
||||
|
||||
for _, outcomeReq := range req.Outcomes {
|
||||
fmt.Println("reqq: ", outcomeReq)
|
||||
newOutcome, err := s.GenerateBetOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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()
|
||||
if err != nil {
|
||||
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{
|
||||
Amount: domain.ToCurrency(req.Amount),
|
||||
TotalOdds: totalOdds,
|
||||
Status: req.Status,
|
||||
FullName: req.FullName,
|
||||
PhoneNumber: req.PhoneNumber,
|
||||
CashoutID: cashoutID,
|
||||
Amount: domain.ToCurrency(req.Amount),
|
||||
TotalOdds: totalOdds,
|
||||
Status: req.Status,
|
||||
FullName: req.FullName,
|
||||
PhoneNumber: req.PhoneNumber,
|
||||
CashoutID: cashoutID,
|
||||
OutcomesHash: outcomesHash,
|
||||
}
|
||||
|
||||
switch role {
|
||||
|
|
@ -241,7 +271,11 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
|||
}
|
||||
|
||||
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 {
|
||||
s.mongoLogger.Error("failed to deduct from wallet",
|
||||
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
|
||||
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 {
|
||||
s.mongoLogger.Error("wallet deduction failed",
|
||||
zap.Int64("wallet_id", branch.WalletID),
|
||||
|
|
@ -290,7 +327,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
|||
newBet.IsShopBet = true
|
||||
|
||||
case domain.RoleCustomer:
|
||||
wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID)
|
||||
wallets, err := s.walletSvc.GetCustomerWallet(ctx, userID)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to get customer wallets",
|
||||
zap.Int64("user_id", userID),
|
||||
|
|
@ -298,16 +335,52 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
|||
)
|
||||
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}
|
||||
|
|
@ -321,6 +394,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
|||
return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type")
|
||||
}
|
||||
|
||||
fmt.Println("Bet is: ", newBet)
|
||||
bet, err := s.CreateBet(ctx, newBet)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
s.mongoLogger.Error("failed to add winnings to wallet",
|
||||
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 {
|
||||
return s.betStore.DeleteBet(ctx, id)
|
||||
func (s *Service) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error) {
|
||||
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)
|
||||
GetBranchByManagerID(ctx context.Context, branchManagerID 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)
|
||||
UpdateBranch(ctx context.Context, branch domain.UpdateBranch) (domain.Branch, error)
|
||||
DeleteBranch(ctx context.Context, id int64) error
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package branch
|
||||
package branch
|
||||
|
||||
import (
|
||||
"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) {
|
||||
return s.branchStore.GetBranchOperations(ctx, branchID)
|
||||
}
|
||||
func (s *Service) GetAllBranches(ctx context.Context) ([]domain.BranchDetail, error) {
|
||||
return s.branchStore.GetAllBranches(ctx)
|
||||
func (s *Service) GetAllBranches(ctx context.Context, filter domain.BranchFilter) ([]domain.BranchDetail, error) {
|
||||
return s.branchStore.GetAllBranches(ctx, filter)
|
||||
}
|
||||
|
||||
func (s *Service) GetBranchByCashier(ctx context.Context, userID int64) (domain.Branch, error) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
|
@ -30,9 +31,9 @@ func NewClient(baseURL, secretKey string) *Client {
|
|||
|
||||
func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaDepositRequest) (domain.ChapaDepositResponse, error) {
|
||||
payload := map[string]interface{}{
|
||||
"amount": req.Amount,
|
||||
"currency": req.Currency,
|
||||
"email": req.Email,
|
||||
"amount": fmt.Sprintf("%.2f", float64(req.Amount)/100),
|
||||
"currency": req.Currency,
|
||||
// "email": req.Email,
|
||||
"first_name": req.FirstName,
|
||||
"last_name": req.LastName,
|
||||
"tx_ref": req.TxRef,
|
||||
|
|
@ -40,6 +41,8 @@ func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaDepositR
|
|||
"return_url": req.ReturnURL,
|
||||
}
|
||||
|
||||
fmt.Printf("\n\nChapa Payload: %+v\n\n", payload)
|
||||
|
||||
payloadBytes, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
fmt.Printf("\n\nBase URL is: %+v\n\n", c.baseURL)
|
||||
|
||||
httpReq.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||
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()
|
||||
|
||||
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 {
|
||||
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{
|
||||
CheckoutURL: response.Data.CheckoutURL,
|
||||
// Reference: req.TxRef,
|
||||
Reference: req.TxRef,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -165,6 +175,51 @@ func (c *Client) ManualVerifyPayment(ctx context.Context, txRef string) (*domain
|
|||
}, 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) {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/banks", 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) {
|
||||
// base, err := url.Parse(c.baseURL)
|
||||
// if err != nil {
|
||||
// return false, fmt.Errorf("invalid base URL: %w", err)
|
||||
// }
|
||||
endpoint := c.baseURL + "/transfers"
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
|
|
@ -239,7 +292,8 @@ func (c *Client) InitiateTransfer(ctx context.Context, req domain.ChapaWithdrawa
|
|||
defer resp.Body.Close()
|
||||
|
||||
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
|
||||
|
|
@ -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 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) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
|
|
@ -31,6 +32,7 @@ func NewService(
|
|||
transferStore wallet.TransferStore,
|
||||
walletStore wallet.Service,
|
||||
userStore user.UserStore,
|
||||
cfg *config.Config,
|
||||
chapaClient *Client,
|
||||
|
||||
) *Service {
|
||||
|
|
@ -38,6 +40,7 @@ func NewService(
|
|||
transferStore: transferStore,
|
||||
walletStore: walletStore,
|
||||
userStore: userStore,
|
||||
cfg: cfg,
|
||||
chapaClient: chapaClient,
|
||||
}
|
||||
}
|
||||
|
|
@ -58,7 +61,9 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
|||
var senderWallet domain.Wallet
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
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,
|
||||
ReferenceNumber: reference,
|
||||
// ReceiverWalletID: 1,
|
||||
SenderWalletID: senderWallet.ID,
|
||||
Verified: false,
|
||||
SenderWalletID: domain.ValidInt64{
|
||||
Value: senderWallet.ID,
|
||||
Valid: true,
|
||||
},
|
||||
Verified: false,
|
||||
}
|
||||
|
||||
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
||||
return "", fmt.Errorf("failed to save payment: %w", err)
|
||||
}
|
||||
|
||||
// Initialize payment with Chapa
|
||||
response, err := s.chapaClient.InitializePayment(ctx, domain.ChapaDepositRequest{
|
||||
payload := domain.ChapaDepositRequest{
|
||||
Amount: amount,
|
||||
Currency: "ETB",
|
||||
Email: user.Email,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
TxRef: reference,
|
||||
CallbackURL: "https://fortunebet.com/api/v1/payments/callback",
|
||||
ReturnURL: "https://fortunebet.com/api/v1/payment-success",
|
||||
})
|
||||
CallbackURL: s.cfg.CHAPA_CALLBACK_URL,
|
||||
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 {
|
||||
// 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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req domain.ChapaWithdrawalRequest) (*domain.Transfer, error) {
|
||||
// Parse and validate amount
|
||||
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()
|
||||
|
||||
createTransfer := domain.CreateTransfer{
|
||||
Amount: domain.Currency(amount),
|
||||
Type: domain.WITHDRAW,
|
||||
ReceiverWalletID: 1,
|
||||
SenderWalletID: withdrawWallet.ID,
|
||||
Status: string(domain.PaymentStatusPending),
|
||||
Verified: false,
|
||||
ReferenceNumber: reference,
|
||||
PaymentMethod: domain.TRANSFER_CHAPA,
|
||||
Amount: domain.Currency(amount),
|
||||
Type: domain.WITHDRAW,
|
||||
SenderWalletID: domain.ValidInt64{
|
||||
Value: withdrawWallet.ID,
|
||||
Valid: true,
|
||||
},
|
||||
Status: string(domain.PaymentStatusPending),
|
||||
Verified: false,
|
||||
ReferenceNumber: reference,
|
||||
PaymentMethod: domain.TRANSFER_CHAPA,
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil || !success {
|
||||
// Update withdrawal status to failed
|
||||
if err != nil {
|
||||
_ = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusFailed))
|
||||
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
|
||||
if err := s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusProcessing)); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
func (s *Service) ManualVerifTransaction(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
||||
// First check if we already have a verified record
|
||||
func (s *Service) ManuallyVerify(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
||||
// Lookup transfer by reference
|
||||
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{
|
||||
Status: string(domain.PaymentStatusCompleted),
|
||||
Amount: float64(transfer.Amount) / 100, // Convert from cents/kobo
|
||||
Amount: float64(transfer.Amount) / 100,
|
||||
Currency: "ETB",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// If not verified or not found, verify with Chapa
|
||||
verification, err := s.chapaClient.VerifyPayment(ctx, txRef)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to verify payment: %w", err)
|
||||
// Validate sender wallet
|
||||
if !transfer.SenderWalletID.Valid {
|
||||
return nil, fmt.Errorf("invalid sender wallet ID: %v", transfer.SenderWalletID)
|
||||
}
|
||||
|
||||
// Update our records if payment is successful
|
||||
if verification.Status == domain.PaymentStatusCompleted {
|
||||
err = s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true)
|
||||
var verification *domain.ChapaVerificationResponse
|
||||
|
||||
// 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 {
|
||||
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
|
||||
err = s.walletStore.UpdateBalance(ctx, transfer.SenderWalletID, transfer.Amount)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update wallet balance: %w", err)
|
||||
if verification.Status == string(domain.PaymentStatusSuccessful) {
|
||||
// Mark verified
|
||||
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
||||
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{
|
||||
Status: string(verification.Status),
|
||||
Amount: float64(verification.Amount),
|
||||
Currency: verification.Currency,
|
||||
}, nil
|
||||
return verification, nil
|
||||
}
|
||||
|
||||
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
|
||||
// }
|
||||
|
||||
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 transfer.Status == string(domain.PaymentStatusCompleted) {
|
||||
if err := s.walletStore.AddToWallet(ctx, payment.SenderWalletID, payment.Amount); err != nil {
|
||||
if transfer.Status == string(domain.PaymentStatusSuccessful) {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -302,13 +356,12 @@ func (s *Service) HandleVerifyWithdrawWebhook(ctx context.Context, payment domai
|
|||
// verified = true
|
||||
// }
|
||||
|
||||
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 wallet
|
||||
if payment.Status == string(domain.PaymentStatusFailed) {
|
||||
if err := s.walletStore.AddToWallet(ctx, transfer.SenderWalletID, transfer.Amount); err != nil {
|
||||
if payment.Status == string(domain.PaymentStatusSuccessful) {
|
||||
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
|
||||
} else {
|
||||
if _, err := s.walletStore.AddToWallet(ctx, transfer.SenderWalletID.Value, transfer.Amount, domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}); err != nil {
|
||||
return fmt.Errorf("failed to credit user wallet: %w", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -202,8 +203,10 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
|||
}
|
||||
|
||||
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
|
||||
|
||||
// b, err := os.OpenFile("logs/skipped_leagues.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
// if err != nil {
|
||||
// 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 limit int = 200
|
||||
var count int = 0
|
||||
log.Printf("Sport ID %d", sportID)
|
||||
for page <= totalPages {
|
||||
page = page + 1
|
||||
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
|
||||
// no this its fine to keep it here
|
||||
// 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{
|
||||
ID: leagueID,
|
||||
Name: ev.League.Name,
|
||||
IsActive: true,
|
||||
SportID: convertInt32(ev.SportID),
|
||||
ID: leagueID,
|
||||
Name: ev.League.Name,
|
||||
IsActive: true,
|
||||
IsFeatured: slices.Contains(domain.FeaturedLeagues, leagueID),
|
||||
SportID: convertInt32(ev.SportID),
|
||||
})
|
||||
|
||||
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 {
|
||||
SaveLeague(ctx context.Context, l 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
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
return s.store.SetLeagueActive(ctx, leagueId, isActive)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import (
|
|||
)
|
||||
|
||||
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
|
||||
MarkAsRead(ctx context.Context, notificationID string, recipientID int64) error
|
||||
ListNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, error)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package notificationservice
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"sync"
|
||||
|
|
@ -11,23 +12,32 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
|
||||
// "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws"
|
||||
afro "github.com/amanuelabay/afrosms-go"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
repo repository.NotificationRepository
|
||||
Hub *ws.NotificationHub
|
||||
connections sync.Map
|
||||
notificationCh chan *domain.Notification
|
||||
stopCh chan struct{}
|
||||
config *config.Config
|
||||
logger *slog.Logger
|
||||
repo repository.NotificationRepository
|
||||
Hub *ws.NotificationHub
|
||||
notificationStore NotificationStore
|
||||
connections sync.Map
|
||||
notificationCh chan *domain.Notification
|
||||
stopCh chan struct{}
|
||||
config *config.Config
|
||||
logger *slog.Logger
|
||||
redisClient *redis.Client
|
||||
}
|
||||
|
||||
func New(repo repository.NotificationRepository, logger *slog.Logger, cfg *config.Config) *Service {
|
||||
hub := ws.NewNotificationHub()
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: cfg.RedisAddr, // e.g., "redis:6379"
|
||||
})
|
||||
|
||||
svc := &Service{
|
||||
repo: repo,
|
||||
Hub: hub,
|
||||
|
|
@ -36,11 +46,13 @@ func New(repo repository.NotificationRepository, logger *slog.Logger, cfg *confi
|
|||
notificationCh: make(chan *domain.Notification, 1000),
|
||||
stopCh: make(chan struct{}),
|
||||
config: cfg,
|
||||
redisClient: rdb,
|
||||
}
|
||||
|
||||
go hub.Run()
|
||||
go svc.startWorker()
|
||||
go svc.startRetryWorker()
|
||||
go svc.RunRedisSubscriber(context.Background())
|
||||
|
||||
return svc
|
||||
}
|
||||
|
|
@ -255,7 +267,8 @@ func (s *Service) retryFailedNotifications() {
|
|||
go func(notification *domain.Notification) {
|
||||
for attempt := 0; attempt < 3; attempt++ {
|
||||
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 {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||
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)
|
||||
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 {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||
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){
|
||||
// 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 (
|
||||
"context"
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo
|
|||
|
||||
walletID := wallets[0].ID
|
||||
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 {
|
||||
s.logger.Error("Failed to add referral reward to wallet", "walletID", walletID, "referrerID", referrerID, "error", err)
|
||||
return err
|
||||
|
|
@ -162,7 +162,7 @@ func (s *Service) ProcessDepositBonus(ctx context.Context, userPhone string, amo
|
|||
walletID := wallets[0].ID
|
||||
bonus := amount * (settings.CashbackPercentage / 100)
|
||||
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 {
|
||||
s.logger.Error("Failed to add deposit bonus to wallet", "walletID", walletID, "userID", userID, "bonus", bonus, "error", err)
|
||||
return err
|
||||
|
|
@ -216,7 +216,7 @@ func (s *Service) ProcessBetReferral(ctx context.Context, userPhone string, betA
|
|||
|
||||
walletID := wallets[0].ID
|
||||
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 {
|
||||
s.logger.Error("Failed to add bet referral bonus to wallet", "walletID", walletID, "referrerID", referrerID, "bonus", bonus, "error", err)
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -2,9 +2,14 @@ package report
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
|
|
@ -454,32 +459,299 @@ func (s *Service) GetSportPerformance(ctx context.Context, filter domain.ReportF
|
|||
return performances, nil
|
||||
}
|
||||
|
||||
func (s *Service) GenerateReport(timeFrame domain.TimeFrame) (*domain.Report, error) {
|
||||
now := time.Now()
|
||||
var start, end time.Time
|
||||
|
||||
switch timeFrame {
|
||||
case domain.Daily:
|
||||
start = now.AddDate(0, 0, -1)
|
||||
end = now
|
||||
case domain.Weekly:
|
||||
start = now.AddDate(0, 0, -7)
|
||||
end = now
|
||||
case domain.Monthly:
|
||||
start = now.AddDate(0, -1, 0)
|
||||
end = now
|
||||
}
|
||||
|
||||
report, err := s.repo.GenerateReport(timeFrame, start, end)
|
||||
func (s *Service) GenerateReport(ctx context.Context, period string) error {
|
||||
data, err := s.fetchReportData(ctx, period)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return fmt.Errorf("fetch data: %w", err)
|
||||
}
|
||||
|
||||
if err := s.repo.SaveReport(report); err != nil {
|
||||
return nil, err
|
||||
filePath := fmt.Sprintf("/host-desktop/report_%s_%s.csv", period, time.Now().Format("2006-01-02_15-04"))
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
|
@ -17,30 +16,33 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
"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"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
repo *repository.Store
|
||||
config *config.Config
|
||||
logger *slog.Logger
|
||||
client *http.Client
|
||||
betSvc bet.Service
|
||||
oddSvc odds.ServiceImpl
|
||||
eventSvc event.Service
|
||||
leagueSvc league.Service
|
||||
repo *repository.Store
|
||||
config *config.Config
|
||||
logger *slog.Logger
|
||||
client *http.Client
|
||||
betSvc bet.Service
|
||||
oddSvc odds.ServiceImpl
|
||||
eventSvc event.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{
|
||||
repo: repo,
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
betSvc: betSvc,
|
||||
oddSvc: oddSvc,
|
||||
eventSvc: eventSvc,
|
||||
leagueSvc: leagueSvc,
|
||||
repo: repo,
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
betSvc: betSvc,
|
||||
oddSvc: oddSvc,
|
||||
eventSvc: eventSvc,
|
||||
leagueSvc: leagueSvc,
|
||||
notificationSvc: notificationSvc,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,6 +50,127 @@ var (
|
|||
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 {
|
||||
// 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
|
||||
|
|
@ -58,29 +181,15 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
|||
}
|
||||
fmt.Printf("⚠️ Expired Events: %d \n", len(events))
|
||||
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)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse event id")
|
||||
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))
|
||||
s.logger.Error("Failed to parse", "eventID", event.ID, "err", err)
|
||||
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)
|
||||
if err != nil {
|
||||
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...
|
||||
if timeStatusParsed != int64(domain.TIME_STATUS_ENDED) {
|
||||
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))
|
||||
isDeleted = false
|
||||
// if timeStatusParsed != int64(domain.TIME_STATUS_ENDED) {
|
||||
// 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))
|
||||
// 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
|
||||
}
|
||||
|
||||
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))
|
||||
case int64(domain.TIME_STATUS_TO_BE_FIXED):
|
||||
s.logger.Warn("Event needs to be rescheduled or corrected", "event_id", eventID)
|
||||
// Admin users will be able to review the events
|
||||
|
||||
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)
|
||||
case int64(domain.TIME_STATUS_ENDED), int64(domain.TIME_STATUS_WALKOVER), int64(domain.TIME_STATUS_DECIDED_BY_FA):
|
||||
err = s.UpdateResultForOutcomes(ctx, eventID, result.Results[0], 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
|
||||
s.logger.Error("Error while updating result for event", "event_id", eventID)
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
}
|
||||
if isDeleted {
|
||||
removed += 1
|
||||
fmt.Printf("⚠️ Removing Event %v \n", event.ID)
|
||||
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)
|
||||
|
|
@ -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)
|
||||
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)
|
||||
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("Total Number of Removed Events", "count", removed)
|
||||
s.logger.Info("Successfully processed results", "removed_events", removed, "total_events", len(events))
|
||||
return nil
|
||||
}
|
||||
|
|
@ -211,10 +357,9 @@ func (s *Service) CheckAndUpdateExpiredEvents(ctx context.Context) (int64, error
|
|||
s.logger.Error("Failed to fetch events")
|
||||
return 0, err
|
||||
}
|
||||
fmt.Printf("⚠️ Expired Events: %d \n", len(events))
|
||||
updated := 0
|
||||
for i, event := range events {
|
||||
fmt.Printf("⚙️ Processing event %v (%d/%d) \n", event.ID, i+1, len(events))
|
||||
for _, event := range events {
|
||||
// fmt.Printf("⚙️ Processing event %v (%d/%d) \n", event.ID, i+1, len(events))
|
||||
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
||||
if err != nil {
|
||||
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 {
|
||||
s.logger.Error("Invalid API response", "event_id", eventID)
|
||||
fmt.Printf("⚠️ Invalid API response for event %v \n", result)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -282,12 +426,13 @@ func (s *Service) CheckAndUpdateExpiredEvents(ctx context.Context) (int64, error
|
|||
continue
|
||||
}
|
||||
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
|
||||
leagueID, err := strconv.ParseInt(commonResp.League.ID, 10, 64)
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -304,12 +449,11 @@ func (s *Service) CheckAndUpdateExpiredEvents(ctx context.Context) (int64, error
|
|||
})
|
||||
|
||||
if err != nil {
|
||||
log.Printf("❌ Error Updating League %v", commonResp.League.Name)
|
||||
log.Printf("err:%v", err)
|
||||
s.logger.Error("Error Updating League", "League Name", commonResp.League.Name, "err", err)
|
||||
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 {
|
||||
|
|
|
|||
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 (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
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{
|
||||
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) {
|
||||
return s.ticketStore.CreateTicket(ctx, ticket)
|
||||
func (s *Service) GenerateTicketOutcome(ctx context.Context, settings domain.SettingList, eventID int64, marketID int64, oddID int64) (domain.CreateTicketOutcome, error) {
|
||||
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) {
|
||||
return s.ticketStore.CreateTicketOutcome(ctx, outcomes)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,18 +10,29 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
|
||||
afro "github.com/amanuelabay/afrosms-go"
|
||||
"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"
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
message := fmt.Sprintf("Welcome to Fortune bets, your OTP is %s please don't share with anyone.", otpCode)
|
||||
|
||||
switch medium {
|
||||
case domain.OtpMediumSms:
|
||||
if err := s.SendSMSOTP(ctx, sentTo, message); err != nil {
|
||||
return err
|
||||
switch provider {
|
||||
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:
|
||||
if err := s.SendEmailOTP(ctx, sentTo, message); err != nil {
|
||||
|
|
@ -51,7 +62,7 @@ func hashPassword(plaintextPassword string) ([]byte, error) {
|
|||
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
|
||||
senderName := s.config.AFRO_SMS_SENDER_NAME
|
||||
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 {
|
||||
apiKey := s.config.ResendApiKey
|
||||
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
|
||||
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
|
||||
// check if user exists
|
||||
switch medium {
|
||||
|
|
@ -26,7 +26,7 @@ func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium,
|
|||
}
|
||||
|
||||
// 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
|
||||
// get otp
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"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
|
||||
// check if user exists
|
||||
|
|
@ -23,7 +23,7 @@ func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, se
|
|||
return err
|
||||
}
|
||||
|
||||
return s.SendOtp(ctx, sentTo, domain.OtpReset, medium)
|
||||
return s.SendOtp(ctx, sentTo, domain.OtpReset, medium, provider)
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswo
|
|||
return err
|
||||
}
|
||||
// reset pass and mark otp as used
|
||||
|
||||
|
||||
err = s.userStore.UpdatePassword(ctx, sentTo, hashedPassword, otp.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ func (s *AleaPlayService) processTransaction(ctx context.Context, tx *domain.Vir
|
|||
}
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ type VirtualGameService interface {
|
|||
GetPlayerInfo(ctx context.Context, req *domain.PopOKPlayerInfoRequest) (*domain.PopOKPlayerInfoResponse, error)
|
||||
ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (*domain.PopOKWinResponse, 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)
|
||||
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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
|
|
@ -8,7 +9,12 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
|
|
@ -43,14 +49,15 @@ func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameI
|
|||
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(
|
||||
userID,
|
||||
user.PhoneNumber,
|
||||
user.CompanyID,
|
||||
user.FirstName,
|
||||
currency,
|
||||
"en",
|
||||
mode,
|
||||
sessionToken,
|
||||
sessionId,
|
||||
s.config.PopOK.SecretKey,
|
||||
24*time.Hour,
|
||||
)
|
||||
|
|
@ -59,19 +66,33 @@ func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameI
|
|||
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(
|
||||
"partnerId=%s&gameId=%s&gameMode=%s&lang=en&platform=%s&externalToken=%s",
|
||||
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
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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 {
|
||||
s.logger.Error("Failed to update wallet", "walletID", walletID, "userID", session.UserID, "amount", amount, "error", 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) {
|
||||
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 {
|
||||
s.logger.Error("Failed to parse JWT", "error", err)
|
||||
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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
// Create transaction record
|
||||
tx := &domain.VirtualGameTransaction{
|
||||
UserID: claims.UserID,
|
||||
CompanyID: claims.CompanyID.Value,
|
||||
Provider: string(domain.PROVIDER_POPOK),
|
||||
GameID: req.GameID,
|
||||
TransactionType: "BET",
|
||||
Amount: -amountCents, // Negative for bets
|
||||
Amount: amountCents, // Negative for bets
|
||||
Currency: req.Currency,
|
||||
ExternalTransactionID: req.TransactionID,
|
||||
Status: "COMPLETED",
|
||||
|
|
@ -219,6 +245,8 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
|
|||
return nil, fmt.Errorf("invalid token")
|
||||
}
|
||||
|
||||
fmt.Printf("\n\nClaims: %+v\n\n", claims)
|
||||
|
||||
// 2. Check for duplicate transaction (idempotency)
|
||||
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
|
||||
if err != nil {
|
||||
|
|
@ -245,7 +273,7 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
|
|||
|
||||
// 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)
|
||||
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
|
||||
tx := &domain.VirtualGameTransaction{
|
||||
UserID: claims.UserID,
|
||||
CompanyID: claims.CompanyID.Value,
|
||||
Provider: string(domain.PROVIDER_POPOK),
|
||||
GameID: req.GameID,
|
||||
TransactionType: "WIN",
|
||||
Amount: amountCents,
|
||||
Currency: req.Currency,
|
||||
|
|
@ -277,14 +308,175 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
|
|||
}, 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
|
||||
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
|
||||
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")
|
||||
}
|
||||
|
||||
// 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
|
||||
originalBet, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
|
||||
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)
|
||||
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)
|
||||
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) {
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"time"
|
||||
// import (
|
||||
// "context"
|
||||
// "fmt"
|
||||
// "log/slog"
|
||||
// "time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
)
|
||||
// "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
// "github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
// "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
// )
|
||||
|
||||
type VeliPlayService struct {
|
||||
repo repository.VirtualGameRepository
|
||||
walletSvc wallet.Service
|
||||
config *config.VeliGamesConfig
|
||||
logger *slog.Logger
|
||||
}
|
||||
// type Service struct {
|
||||
// client *VeliClient
|
||||
// gameRepo repository.VeliGameRepository
|
||||
// playerRepo repository.VeliPlayerRepository
|
||||
// txRepo repository.VeliTransactionRepository
|
||||
// walletSvc wallet.Service
|
||||
// logger domain.Logger
|
||||
// }
|
||||
|
||||
func NewVeliPlayService(
|
||||
repo repository.VirtualGameRepository,
|
||||
walletSvc wallet.Service,
|
||||
cfg *config.Config,
|
||||
logger *slog.Logger,
|
||||
) *VeliPlayService {
|
||||
return &VeliPlayService{
|
||||
repo: repo,
|
||||
walletSvc: walletSvc,
|
||||
config: &cfg.VeliGames,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
// func NewService(
|
||||
// client *VeliClient,
|
||||
// gameRepo repository.VeliGameRepository,
|
||||
// playerRepo repository.VeliPlayerRepository,
|
||||
// txRepo repository.VeliTransactionRepository,
|
||||
// walletSvc wallet.Service,
|
||||
// logger *slog.Logger,
|
||||
// ) *Service {
|
||||
// return &Service{
|
||||
// client: client,
|
||||
// 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) {
|
||||
session := &domain.VirtualGameSession{
|
||||
UserID: userID,
|
||||
GameID: gameID,
|
||||
SessionToken: generateSessionToken(userID),
|
||||
Currency: currency,
|
||||
Status: "ACTIVE",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
ExpiresAt: time.Now().Add(24 * time.Hour),
|
||||
}
|
||||
// func (s *Service) SyncGames(ctx context.Context) error {
|
||||
// games, err := s.client.GetGameList(ctx)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("failed to get game list: %w", err)
|
||||
// }
|
||||
|
||||
if err := s.repo.CreateVirtualGameSession(ctx, session); err != nil {
|
||||
return "", fmt.Errorf("failed to create game session: %w", err)
|
||||
}
|
||||
// for _, game := range games {
|
||||
// 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
|
||||
params := url.Values{
|
||||
"operator_key": []string{s.config.OperatorKey}, // Different from Alea's operator_id
|
||||
"user_id": []string{fmt.Sprintf("%d", userID)},
|
||||
"game_id": []string{gameID},
|
||||
"currency": []string{currency},
|
||||
"mode": []string{mode},
|
||||
"timestamp": []string{fmt.Sprintf("%d", time.Now().Unix())},
|
||||
}
|
||||
// if existing == nil {
|
||||
// // New game - create
|
||||
// if err := s.gameRepo.CreateGame(ctx, game); err != nil {
|
||||
// s.logger.Error("failed to create game", "game_id", game.ID, "error", err)
|
||||
// continue
|
||||
// }
|
||||
// } else {
|
||||
// // 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())
|
||||
params.Add("signature", signature)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
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 {
|
||||
if !s.verifyCallbackSignature(callback) {
|
||||
return errors.New("invalid callback signature")
|
||||
}
|
||||
// // Verify game exists
|
||||
// game, err := s.gameRepo.GetGameByID(ctx, gameID)
|
||||
// if err != nil {
|
||||
// return "", fmt.Errorf("failed to get game: %w", err)
|
||||
// }
|
||||
|
||||
// Veli uses round_id instead of transaction_id for idempotency
|
||||
existing, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, callback.RoundID)
|
||||
if err != nil || existing != nil {
|
||||
s.logger.Warn("duplicate round detected", "round_id", callback.RoundID)
|
||||
return nil
|
||||
}
|
||||
// // Call Veli API
|
||||
// gameURL, err := s.client.LaunchGame(ctx, playerID, gameID)
|
||||
// if err != nil {
|
||||
// return "", fmt.Errorf("failed to launch game: %w", err)
|
||||
// }
|
||||
|
||||
session, err := s.repo.GetVirtualGameSessionByToken(ctx, callback.SessionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get game session: %w", err)
|
||||
}
|
||||
// // Create game session record
|
||||
// session := domain.GameSession{
|
||||
// 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.)
|
||||
amount := convertAmount(callback.Amount, callback.EventType)
|
||||
// if err := s.gameRepo.CreateGameSession(ctx, session); err != nil {
|
||||
// s.logger.Error("failed to create game session", "error", err)
|
||||
// }
|
||||
|
||||
tx := &domain.VirtualGameTransaction{
|
||||
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
|
||||
},
|
||||
}
|
||||
// return gameURL, nil
|
||||
// }
|
||||
|
||||
if err := s.processTransaction(ctx, tx, session.UserID); err != nil {
|
||||
return fmt.Errorf("failed to process transaction: %w", err)
|
||||
}
|
||||
// func (s *Service) PlaceBet(ctx context.Context, playerID, gameID string, amount float64) (*domain.VeliTransaction, error) {
|
||||
// // 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 {
|
||||
h := hmac.New(sha256.New, []byte(s.config.SecretKey))
|
||||
h.Write([]byte(data))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
// // 2. Create transaction record
|
||||
// tx := domain.VeliTransaction{
|
||||
// TransactionID: generateTransactionID(),
|
||||
// PlayerID: playerID,
|
||||
// GameID: gameID,
|
||||
// Amount: amount,
|
||||
// Type: "bet",
|
||||
// Status: "pending",
|
||||
// CreatedAt: time.Now(),
|
||||
// }
|
||||
|
||||
func (s *VeliPlayService) verifyCallbackSignature(cb *domain.VeliCallback) bool {
|
||||
signData := fmt.Sprintf("%s%s%s%.2f%s%d",
|
||||
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
|
||||
}
|
||||
// if err := s.txRepo.CreateTransaction(ctx, tx); err != nil {
|
||||
// return nil, fmt.Errorf("failed to create transaction: %w", err)
|
||||
// }
|
||||
|
||||
func convertAmount(amount float64, eventType string) int64 {
|
||||
cents := int64(amount * 100)
|
||||
if eventType == "bet_placed" {
|
||||
return -cents // Debit for bets
|
||||
}
|
||||
return cents // Credit for wins/results
|
||||
}
|
||||
// // 3. Call Veli API
|
||||
// if err := s.client.PlaceBet(ctx, tx.TransactionID, playerID, gameID, amount); err != nil {
|
||||
// // Update transaction status
|
||||
// tx.Status = "failed"
|
||||
// _ = s.txRepo.UpdateTransaction(ctx, tx)
|
||||
// return nil, fmt.Errorf("failed to place bet: %w", err)
|
||||
// }
|
||||
|
||||
func generateSessionToken(userID int64) string {
|
||||
return fmt.Sprintf("veli-%d-%d", userID, time.Now().UnixNano())
|
||||
}
|
||||
// // 4. Deduct from wallet
|
||||
// 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 {
|
||||
wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID)
|
||||
if err != nil || len(wallets) == 0 {
|
||||
return errors.New("no wallet available for user")
|
||||
}
|
||||
tx.WalletID = wallets[0].ID
|
||||
// // 5. Update transaction status
|
||||
// tx.Status = "completed"
|
||||
// if err := s.txRepo.UpdateTransaction(ctx, tx); err != nil {
|
||||
// s.logger.Error("failed to update transaction status", "error", err)
|
||||
// }
|
||||
|
||||
if err := s.walletSvc.AddToWallet(ctx, tx.WalletID, domain.Currency(tx.Amount)); err != nil {
|
||||
return fmt.Errorf("wallet update failed: %w", err)
|
||||
}
|
||||
// return &tx, nil
|
||||
// }
|
||||
|
||||
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
|
||||
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.mu.Unlock()
|
||||
continue
|
||||
initialDeposit = wallet.Balance // update local variable
|
||||
}
|
||||
initialDeposit := s.initialDeposits[company.ID]
|
||||
s.mu.Unlock()
|
||||
|
||||
if initialDeposit == 0 {
|
||||
|
|
|
|||
|
|
@ -7,11 +7,14 @@ import (
|
|||
)
|
||||
|
||||
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)
|
||||
CreateCustomerWallet(ctx context.Context, customerWallet domain.CreateCustomerWallet) (domain.CustomerWallet, error)
|
||||
GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error)
|
||||
GetAllWallets(ctx context.Context) ([]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)
|
||||
GetAllBranchWallets(ctx context.Context) ([]domain.BranchWallet, error)
|
||||
UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ type Service struct {
|
|||
walletStore WalletStore
|
||||
transferStore TransferStore
|
||||
notificationStore notificationservice.NotificationStore
|
||||
notificationSvc *notificationservice.Service
|
||||
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