virtual_game_providers based SQL queries primary ID data type mismatch fix

This commit is contained in:
Yared Yemane 2025-11-27 17:56:52 +03:00
parent 168fcdf278
commit 13c470079c
17 changed files with 1704 additions and 752 deletions

View File

@ -49,7 +49,6 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/raffle" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/raffle"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/santimpay" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/santimpay"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
@ -286,23 +285,23 @@ func main() {
*userSvc, *userSvc,
) )
reportSvc := report.NewService( // reportSvc := report.NewService(
repository.NewReportStore(store), // repository.NewVirtualGameReportStore(store),
repository.NewBetStore(store), // repository.NewBetStore(store),
repository.NewWalletStore(store), // repository.NewWalletStore(store),
repository.NewTransactionStore(store), // repository.NewTransactionStore(store),
repository.NewBranchStore(store), // repository.NewBranchStore(store),
repository.NewUserStore(store), // repository.NewUserStore(store),
repository.NewOldRepositoryStore(store), // repository.NewOldRepositoryStore(store),
repository.NewCompanyStore(store), // repository.NewCompanyStore(store),
repository.NewVirtualGameRepository(store), // repository.NewVirtualGameRepository(store),
repository.NewNotificationStore(store), // repository.NewNotificationStore(store),
notificationSvc, // notificationSvc,
statSvc, // statSvc,
logger, // logger,
domain.MongoDBLogger, // domain.MongoDBLogger,
cfg, // cfg,
) // )
enePulseSvc := enetpulse.New( enePulseSvc := enetpulse.New(
*cfg, *cfg,
@ -310,7 +309,7 @@ func main() {
) )
go httpserver.StartEnetPulseCron(enePulseSvc, domain.MongoDBLogger) go httpserver.StartEnetPulseCron(enePulseSvc, domain.MongoDBLogger)
go httpserver.SetupReportandVirtualGameCronJobs(context.Background(), reportSvc, orchestrationSvc, "C:/Users/User/Desktop") // go httpserver.SetupReportandVirtualGameCronJobs(context.Background(), reportSvc, orchestrationSvc, "C:/Users/User/Desktop")
go httpserver.ProcessBetCashback(context.TODO(), betSvc) go httpserver.ProcessBetCashback(context.TODO(), betSvc)
bankRepository := repository.NewBankRepository(store) bankRepository := repository.NewBankRepository(store)
@ -363,7 +362,7 @@ func main() {
httpserver.StartBetAPIDataFetchingCrons(eventSvc, *oddsSvc, resultSvc, domain.MongoDBLogger) httpserver.StartBetAPIDataFetchingCrons(eventSvc, *oddsSvc, resultSvc, domain.MongoDBLogger)
httpserver.StartCleanupCrons(*ticketSvc, notificationSvc, domain.MongoDBLogger) httpserver.StartCleanupCrons(*ticketSvc, notificationSvc, domain.MongoDBLogger)
httpserver.StartStatCrons(statSvc, domain.MongoDBLogger) httpserver.StartStatCrons(statSvc, domain.MongoDBLogger)
httpserver.StartReportCrons(reportSvc, domain.MongoDBLogger) // httpserver.StartReportCrons(reportSvc, domain.MongoDBLogger)
issueReportingRepo := repository.NewReportedIssueRepository(store) issueReportingRepo := repository.NewReportedIssueRepository(store)
@ -405,7 +404,7 @@ func main() {
userSvc, userSvc,
ticketSvc, ticketSvc,
betSvc, betSvc,
reportSvc, // Make sure httpserver.NewApp accepts this parameter // reportSvc, // Make sure httpserver.NewApp accepts this parameter
chapaSvc, chapaSvc,
walletSvc, walletSvc,
transactionSvc, transactionSvc,

View File

@ -37,7 +37,7 @@ CREATE TABLE IF NOT EXISTS virtual_game_providers (
CREATE TABLE IF NOT EXISTS virtual_game_provider_reports ( CREATE TABLE IF NOT EXISTS virtual_game_provider_reports (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
provider_id VARCHAR(100) NOT NULL REFERENCES virtual_game_providers(provider_id) ON DELETE CASCADE, provider_id BIGINT NOT NULL REFERENCES virtual_game_providers(id) ON DELETE CASCADE,
report_date DATE NOT NULL, report_date DATE NOT NULL,
total_games_played BIGINT DEFAULT 0, total_games_played BIGINT DEFAULT 0,
total_bets NUMERIC(18,2) DEFAULT 0, total_bets NUMERIC(18,2) DEFAULT 0,
@ -55,7 +55,7 @@ ON virtual_game_provider_reports (provider_id, report_date, report_type);
CREATE TABLE IF NOT EXISTS virtual_games ( CREATE TABLE IF NOT EXISTS virtual_games (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
game_id VARCHAR(150) UNIQUE NOT NULL, game_id VARCHAR(150) UNIQUE NOT NULL,
provider_id VARCHAR(100) NOT NULL REFERENCES virtual_game_providers(provider_id) ON DELETE CASCADE, provider_id BIGINT NOT NULL REFERENCES virtual_game_providers(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
category VARCHAR(100), category VARCHAR(100),
device_type VARCHAR(100), device_type VARCHAR(100),
@ -75,7 +75,7 @@ CREATE TABLE IF NOT EXISTS virtual_game_favourites (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
game_id BIGINT NOT NULL REFERENCES virtual_games(id) ON DELETE CASCADE, game_id BIGINT NOT NULL REFERENCES virtual_games(id) ON DELETE CASCADE,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
provider_id VARCHAR(100) NOT NULL REFERENCES virtual_game_providers(id) ON DELETE CASCADE, provider_id BIGINT NOT NULL REFERENCES virtual_game_providers(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (game_id, user_id) UNIQUE (game_id, user_id)
); );
@ -83,7 +83,7 @@ CREATE TABLE IF NOT EXISTS virtual_game_favourites (
CREATE TABLE IF NOT EXISTS virtual_game_reports ( CREATE TABLE IF NOT EXISTS virtual_game_reports (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
game_id VARCHAR(150) NOT NULL REFERENCES virtual_games(game_id) ON DELETE CASCADE, game_id VARCHAR(150) NOT NULL REFERENCES virtual_games(game_id) ON DELETE CASCADE,
provider_id VARCHAR(100) NOT NULL REFERENCES virtual_game_providers(provider_id) ON DELETE CASCADE, provider_id BIGINT NOT NULL REFERENCES virtual_game_providers(id) ON DELETE CASCADE,
report_date DATE NOT NULL, report_date DATE NOT NULL,
total_rounds BIGINT DEFAULT 0, total_rounds BIGINT DEFAULT 0,
total_bets NUMERIC(18,2) DEFAULT 0, total_bets NUMERIC(18,2) DEFAULT 0,

View File

@ -0,0 +1,4 @@
DROP TABLE IF EXISTS virtual_game_financial_reports;
DROP TABLE IF EXISTS virtual_game_company_reports;
DROP TABLE IF EXISTS virtual_game_player_cashflow_reports;
DROP TABLE IF EXISTS virtual_game_player_session_reports;

View File

@ -0,0 +1,73 @@
CREATE TABLE IF NOT EXISTS virtual_game_financial_reports (
id BIGSERIAL PRIMARY KEY,
game_id VARCHAR(150) NOT NULL REFERENCES virtual_games(game_id) ON DELETE CASCADE,
provider_id VARCHAR(100) NOT NULL REFERENCES virtual_game_providers(provider_id) ON DELETE CASCADE,
report_date DATE NOT NULL,
report_type VARCHAR(50) NOT NULL DEFAULT 'daily',
total_bets NUMERIC(18,2) DEFAULT 0,
total_wins NUMERIC(18,2) DEFAULT 0,
ggr NUMERIC(18,2) GENERATED ALWAYS AS (total_bets - total_wins) STORED,
rtp NUMERIC(5,2) GENERATED ALWAYS AS (
CASE WHEN total_bets > 0 THEN (total_wins / total_bets) * 100 ELSE 0 END
) STORED,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
CREATE TABLE IF NOT EXISTS virtual_game_company_reports (
id BIGSERIAL PRIMARY KEY,
company_id BIGINT NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
provider_id VARCHAR(100) NOT NULL REFERENCES virtual_game_providers(provider_id) ON DELETE CASCADE,
report_date DATE NOT NULL,
report_type VARCHAR(50) NOT NULL DEFAULT 'daily',
total_bet_amount NUMERIC(18,2) DEFAULT 0,
total_win_amount NUMERIC(18,2) DEFAULT 0,
net_profit NUMERIC(18,2) GENERATED ALWAYS AS (total_bet_amount - total_win_amount) STORED,
profit_margin NUMERIC(6,3) GENERATED ALWAYS AS (
CASE WHEN total_bet_amount > 0 THEN (total_bet_amount - total_win_amount) / total_bet_amount ELSE 0 END
) STORED,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
CREATE TABLE IF NOT EXISTS virtual_game_player_activity_reports (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
-- Reporting scope
report_date DATE NOT NULL,
report_type VARCHAR(50) NOT NULL DEFAULT 'daily',
-- Cashflow information
total_deposits NUMERIC(18,2) DEFAULT 0,
total_withdrawals NUMERIC(18,2) DEFAULT 0,
net_contribution NUMERIC(18,2) GENERATED ALWAYS AS (
total_deposits - total_withdrawals
) STORED,
-- Betting and win/loss analytics
total_bet_amount NUMERIC(18,2) DEFAULT 0,
total_win_amount NUMERIC(18,2) DEFAULT 0,
net_result NUMERIC(18,2) GENERATED ALWAYS AS (
total_bet_amount - total_win_amount
) STORED,
rounds_played BIGINT DEFAULT 0,
avg_bet_size NUMERIC(18,4) GENERATED ALWAYS AS (
CASE WHEN rounds_played > 0 THEN total_bet_amount / rounds_played ELSE 0 END
) STORED,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);

View File

@ -1,74 +0,0 @@
-- name: CreateReportRequest :one
INSERT INTO report_requests (
company_id,
requested_by,
type,
metadata
)
VALUES ($1, $2, $3, $4)
RETURNING *;
-- name: GetAllReportRequests :many
SELECT *
FROM report_request_detail
WHERE (
company_id = sqlc.narg('company_id')
OR sqlc.narg('company_id') IS NULL
)
AND (
type = sqlc.narg('type')
OR sqlc.narg('type') IS NULL
)
AND (
status = sqlc.narg('status')
OR sqlc.narg('status') IS NULL
)
AND (
requested_by = sqlc.narg('requested_by')
OR sqlc.narg('requested_by') IS NULL
)
ORDER BY id DESC
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
-- name: GetTotalReportRequests :one
SELECT COUNT(id)
FROM report_request_detail
WHERE (
company_id = sqlc.narg('company_id')
OR sqlc.narg('company_id') IS NULL
)
AND (
type = sqlc.narg('type')
OR sqlc.narg('type') IS NULL
)
AND (
status = sqlc.narg('status')
OR sqlc.narg('status') IS NULL
)
AND (
requested_by = sqlc.narg('requested_by')
OR sqlc.narg('requested_by') IS NULL
);
-- name: GetReportRequestByID :one
SELECT *
FROM report_request_detail
WHERE id = $1;
-- name: GetReportRequestByRequestedByID :many
SELECT *
FROM report_request_detail
WHERE requested_by = $1
AND (
type = sqlc.narg('type')
OR sqlc.narg('type') IS NULL
)
AND (
status = sqlc.narg('status')
OR sqlc.narg('status') IS NULL
)
ORDER BY id DESC
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
-- name: UpdateReportRequest :exec
UPDATE report_requests
SET file_path = COALESCE(sqlc.narg(file_path), file_path),
reject_reason = COALESCE(sqlc.narg(reject_reason), reject_reason),
status = COALESCE(sqlc.narg(status), status),
completed_at = now()
WHERE id = $1;

139
db/query/virtual_report.sql Normal file
View File

@ -0,0 +1,139 @@
-- name: CreateFinancialReport :one
INSERT INTO virtual_game_financial_reports (
game_id, provider_id, report_date, report_type,
total_bets, total_wins, created_at
) VALUES ($1, $2, $3, $4, $5, $6, NOW())
RETURNING *;
-- name: UpsertFinancialReport :one
INSERT INTO virtual_game_financial_reports (
game_id, provider_id, report_date, report_type,
total_bets, total_wins, created_at, updated_at
)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())
ON CONFLICT (game_id, provider_id, report_date, report_type)
DO UPDATE SET
total_bets = EXCLUDED.total_bets,
total_wins = EXCLUDED.total_wins,
updated_at = NOW()
RETURNING *;
-- name: GetFinancialReportByID :one
SELECT * FROM virtual_game_financial_reports
WHERE id = $1;
-- name: GetFinancialReportsForGame :many
SELECT * FROM virtual_game_financial_reports
WHERE game_id = $1
AND provider_id = $2
AND report_date BETWEEN $3 AND $4
ORDER BY report_date;
-- name: GetDailyFinancialReports :many
SELECT * FROM virtual_game_financial_reports
WHERE report_date = $1
AND report_type = 'daily';
-- name: DeleteFinancialReport :exec
DELETE FROM virtual_game_financial_reports
WHERE id = $1;
-- name: CreateCompanyReport :one
INSERT INTO virtual_game_company_reports (
company_id, provider_id,
report_date, report_type,
total_bet_amount, total_win_amount, created_at
)
VALUES ($1, $2, $3, $4, $5, $6, NOW())
RETURNING *;
-- name: UpsertCompanyReport :one
INSERT INTO virtual_game_company_reports (
company_id, provider_id,
report_date, report_type,
total_bet_amount, total_win_amount,
created_at, updated_at
)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())
ON CONFLICT (company_id, provider_id, report_date, report_type)
DO UPDATE SET
total_bet_amount = EXCLUDED.total_bet_amount,
total_win_amount = EXCLUDED.total_win_amount,
updated_at = NOW()
RETURNING *;
-- name: GetCompanyReportByID :one
SELECT * FROM virtual_game_company_reports
WHERE id = $1;
-- name: GetCompanyReportsInRange :many
SELECT * FROM virtual_game_company_reports
WHERE company_id = $1
AND provider_id = $2
AND report_date BETWEEN $3 AND $4
ORDER BY report_date;
-- name: GetCompanyProfitTrend :many
SELECT report_date, SUM(net_profit) AS total_profit
FROM virtual_game_company_reports
WHERE company_id = $1
AND provider_id = $2
AND report_date BETWEEN $3 AND $4
GROUP BY report_date
ORDER BY report_date;
-- name: CreatePlayerActivityReport :one
INSERT INTO virtual_game_player_activity_reports (
user_id, report_date, report_type,
total_deposits, total_withdrawals,
total_bet_amount, total_win_amount,
rounds_played, created_at
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW())
RETURNING *;
-- name: UpsertPlayerActivityReport :one
INSERT INTO virtual_game_player_activity_reports (
user_id, report_date, report_type,
total_deposits, total_withdrawals,
total_bet_amount, total_win_amount,
rounds_played, created_at, updated_at
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW(), NOW())
ON CONFLICT (user_id, report_date, report_type)
DO UPDATE SET
total_deposits = EXCLUDED.total_deposits,
total_withdrawals = EXCLUDED.total_withdrawals,
total_bet_amount = EXCLUDED.total_bet_amount,
total_win_amount = EXCLUDED.total_win_amount,
rounds_played = EXCLUDED.rounds_played,
updated_at = NOW()
RETURNING *;
-- name: GetPlayerActivityByID :one
SELECT * FROM virtual_game_player_activity_reports
WHERE id = $1;
-- name: GetPlayerActivityByDate :one
SELECT * FROM virtual_game_player_activity_reports
WHERE user_id = $1
AND report_date = $2
AND report_type = $3;
-- name: GetPlayerActivityRange :many
SELECT * FROM virtual_game_player_activity_reports
WHERE user_id = $1
AND report_date BETWEEN $2 AND $3
ORDER BY report_date;
-- name: GetTopPlayersByNetResult :many
SELECT user_id, SUM(net_result) AS total_net
FROM virtual_game_player_activity_reports
WHERE report_date BETWEEN $1 AND $2
GROUP BY user_id
ORDER BY total_net DESC
LIMIT $3;
-- name: DeletePlayerActivityReport :exec
DELETE FROM virtual_game_player_activity_reports
WHERE id = $1;

View File

@ -1157,6 +1157,20 @@ type VirtualGame struct {
UpdatedAt pgtype.Timestamptz `json:"updated_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"`
} }
type VirtualGameCompanyReport struct {
ID int64 `json:"id"`
CompanyID int64 `json:"company_id"`
ProviderID string `json:"provider_id"`
ReportDate pgtype.Date `json:"report_date"`
ReportType string `json:"report_type"`
TotalBetAmount pgtype.Numeric `json:"total_bet_amount"`
TotalWinAmount pgtype.Numeric `json:"total_win_amount"`
NetProfit pgtype.Numeric `json:"net_profit"`
ProfitMargin pgtype.Numeric `json:"profit_margin"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type VirtualGameFavourite struct { type VirtualGameFavourite struct {
ID int64 `json:"id"` ID int64 `json:"id"`
GameID int64 `json:"game_id"` GameID int64 `json:"game_id"`
@ -1165,6 +1179,20 @@ type VirtualGameFavourite struct {
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
} }
type VirtualGameFinancialReport struct {
ID int64 `json:"id"`
GameID string `json:"game_id"`
ProviderID string `json:"provider_id"`
ReportDate pgtype.Date `json:"report_date"`
ReportType string `json:"report_type"`
TotalBets pgtype.Numeric `json:"total_bets"`
TotalWins pgtype.Numeric `json:"total_wins"`
Ggr pgtype.Numeric `json:"ggr"`
Rtp pgtype.Numeric `json:"rtp"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type VirtualGameHistory struct { type VirtualGameHistory struct {
ID int64 `json:"id"` ID int64 `json:"id"`
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
@ -1182,6 +1210,23 @@ type VirtualGameHistory struct {
UpdatedAt pgtype.Timestamp `json:"updated_at"` UpdatedAt pgtype.Timestamp `json:"updated_at"`
} }
type VirtualGamePlayerActivityReport struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
ReportDate pgtype.Date `json:"report_date"`
ReportType string `json:"report_type"`
TotalDeposits pgtype.Numeric `json:"total_deposits"`
TotalWithdrawals pgtype.Numeric `json:"total_withdrawals"`
NetContribution pgtype.Numeric `json:"net_contribution"`
TotalBetAmount pgtype.Numeric `json:"total_bet_amount"`
TotalWinAmount pgtype.Numeric `json:"total_win_amount"`
NetResult pgtype.Numeric `json:"net_result"`
RoundsPlayed pgtype.Int8 `json:"rounds_played"`
AvgBetSize pgtype.Numeric `json:"avg_bet_size"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type VirtualGameProvider struct { type VirtualGameProvider struct {
ID int64 `json:"id"` ID int64 `json:"id"`
ProviderID string `json:"provider_id"` ProviderID string `json:"provider_id"`

View File

@ -0,0 +1,721 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: virtual_report.sql
package dbgen
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const CreateCompanyReport = `-- name: CreateCompanyReport :one
INSERT INTO virtual_game_company_reports (
company_id, provider_id,
report_date, report_type,
total_bet_amount, total_win_amount, created_at
)
VALUES ($1, $2, $3, $4, $5, $6, NOW())
RETURNING id, company_id, provider_id, report_date, report_type, total_bet_amount, total_win_amount, net_profit, profit_margin, created_at, updated_at
`
type CreateCompanyReportParams struct {
CompanyID int64 `json:"company_id"`
ProviderID string `json:"provider_id"`
ReportDate pgtype.Date `json:"report_date"`
ReportType string `json:"report_type"`
TotalBetAmount pgtype.Numeric `json:"total_bet_amount"`
TotalWinAmount pgtype.Numeric `json:"total_win_amount"`
}
func (q *Queries) CreateCompanyReport(ctx context.Context, arg CreateCompanyReportParams) (VirtualGameCompanyReport, error) {
row := q.db.QueryRow(ctx, CreateCompanyReport,
arg.CompanyID,
arg.ProviderID,
arg.ReportDate,
arg.ReportType,
arg.TotalBetAmount,
arg.TotalWinAmount,
)
var i VirtualGameCompanyReport
err := row.Scan(
&i.ID,
&i.CompanyID,
&i.ProviderID,
&i.ReportDate,
&i.ReportType,
&i.TotalBetAmount,
&i.TotalWinAmount,
&i.NetProfit,
&i.ProfitMargin,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const CreateFinancialReport = `-- name: CreateFinancialReport :one
INSERT INTO virtual_game_financial_reports (
game_id, provider_id, report_date, report_type,
total_bets, total_wins, created_at
) VALUES ($1, $2, $3, $4, $5, $6, NOW())
RETURNING id, game_id, provider_id, report_date, report_type, total_bets, total_wins, ggr, rtp, created_at, updated_at
`
type CreateFinancialReportParams struct {
GameID string `json:"game_id"`
ProviderID string `json:"provider_id"`
ReportDate pgtype.Date `json:"report_date"`
ReportType string `json:"report_type"`
TotalBets pgtype.Numeric `json:"total_bets"`
TotalWins pgtype.Numeric `json:"total_wins"`
}
func (q *Queries) CreateFinancialReport(ctx context.Context, arg CreateFinancialReportParams) (VirtualGameFinancialReport, error) {
row := q.db.QueryRow(ctx, CreateFinancialReport,
arg.GameID,
arg.ProviderID,
arg.ReportDate,
arg.ReportType,
arg.TotalBets,
arg.TotalWins,
)
var i VirtualGameFinancialReport
err := row.Scan(
&i.ID,
&i.GameID,
&i.ProviderID,
&i.ReportDate,
&i.ReportType,
&i.TotalBets,
&i.TotalWins,
&i.Ggr,
&i.Rtp,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const CreatePlayerActivityReport = `-- name: CreatePlayerActivityReport :one
INSERT INTO virtual_game_player_activity_reports (
user_id, report_date, report_type,
total_deposits, total_withdrawals,
total_bet_amount, total_win_amount,
rounds_played, created_at
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW())
RETURNING id, user_id, report_date, report_type, total_deposits, total_withdrawals, net_contribution, total_bet_amount, total_win_amount, net_result, rounds_played, avg_bet_size, created_at, updated_at
`
type CreatePlayerActivityReportParams struct {
UserID int64 `json:"user_id"`
ReportDate pgtype.Date `json:"report_date"`
ReportType string `json:"report_type"`
TotalDeposits pgtype.Numeric `json:"total_deposits"`
TotalWithdrawals pgtype.Numeric `json:"total_withdrawals"`
TotalBetAmount pgtype.Numeric `json:"total_bet_amount"`
TotalWinAmount pgtype.Numeric `json:"total_win_amount"`
RoundsPlayed pgtype.Int8 `json:"rounds_played"`
}
func (q *Queries) CreatePlayerActivityReport(ctx context.Context, arg CreatePlayerActivityReportParams) (VirtualGamePlayerActivityReport, error) {
row := q.db.QueryRow(ctx, CreatePlayerActivityReport,
arg.UserID,
arg.ReportDate,
arg.ReportType,
arg.TotalDeposits,
arg.TotalWithdrawals,
arg.TotalBetAmount,
arg.TotalWinAmount,
arg.RoundsPlayed,
)
var i VirtualGamePlayerActivityReport
err := row.Scan(
&i.ID,
&i.UserID,
&i.ReportDate,
&i.ReportType,
&i.TotalDeposits,
&i.TotalWithdrawals,
&i.NetContribution,
&i.TotalBetAmount,
&i.TotalWinAmount,
&i.NetResult,
&i.RoundsPlayed,
&i.AvgBetSize,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const DeleteFinancialReport = `-- name: DeleteFinancialReport :exec
DELETE FROM virtual_game_financial_reports
WHERE id = $1
`
func (q *Queries) DeleteFinancialReport(ctx context.Context, id int64) error {
_, err := q.db.Exec(ctx, DeleteFinancialReport, id)
return err
}
const DeletePlayerActivityReport = `-- name: DeletePlayerActivityReport :exec
DELETE FROM virtual_game_player_activity_reports
WHERE id = $1
`
func (q *Queries) DeletePlayerActivityReport(ctx context.Context, id int64) error {
_, err := q.db.Exec(ctx, DeletePlayerActivityReport, id)
return err
}
const GetCompanyProfitTrend = `-- name: GetCompanyProfitTrend :many
SELECT report_date, SUM(net_profit) AS total_profit
FROM virtual_game_company_reports
WHERE company_id = $1
AND provider_id = $2
AND report_date BETWEEN $3 AND $4
GROUP BY report_date
ORDER BY report_date
`
type GetCompanyProfitTrendParams struct {
CompanyID int64 `json:"company_id"`
ProviderID string `json:"provider_id"`
ReportDate pgtype.Date `json:"report_date"`
ReportDate_2 pgtype.Date `json:"report_date_2"`
}
type GetCompanyProfitTrendRow struct {
ReportDate pgtype.Date `json:"report_date"`
TotalProfit int64 `json:"total_profit"`
}
func (q *Queries) GetCompanyProfitTrend(ctx context.Context, arg GetCompanyProfitTrendParams) ([]GetCompanyProfitTrendRow, error) {
rows, err := q.db.Query(ctx, GetCompanyProfitTrend,
arg.CompanyID,
arg.ProviderID,
arg.ReportDate,
arg.ReportDate_2,
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetCompanyProfitTrendRow
for rows.Next() {
var i GetCompanyProfitTrendRow
if err := rows.Scan(&i.ReportDate, &i.TotalProfit); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const GetCompanyReportByID = `-- name: GetCompanyReportByID :one
SELECT id, company_id, provider_id, report_date, report_type, total_bet_amount, total_win_amount, net_profit, profit_margin, created_at, updated_at FROM virtual_game_company_reports
WHERE id = $1
`
func (q *Queries) GetCompanyReportByID(ctx context.Context, id int64) (VirtualGameCompanyReport, error) {
row := q.db.QueryRow(ctx, GetCompanyReportByID, id)
var i VirtualGameCompanyReport
err := row.Scan(
&i.ID,
&i.CompanyID,
&i.ProviderID,
&i.ReportDate,
&i.ReportType,
&i.TotalBetAmount,
&i.TotalWinAmount,
&i.NetProfit,
&i.ProfitMargin,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const GetCompanyReportsInRange = `-- name: GetCompanyReportsInRange :many
SELECT id, company_id, provider_id, report_date, report_type, total_bet_amount, total_win_amount, net_profit, profit_margin, created_at, updated_at FROM virtual_game_company_reports
WHERE company_id = $1
AND provider_id = $2
AND report_date BETWEEN $3 AND $4
ORDER BY report_date
`
type GetCompanyReportsInRangeParams struct {
CompanyID int64 `json:"company_id"`
ProviderID string `json:"provider_id"`
ReportDate pgtype.Date `json:"report_date"`
ReportDate_2 pgtype.Date `json:"report_date_2"`
}
func (q *Queries) GetCompanyReportsInRange(ctx context.Context, arg GetCompanyReportsInRangeParams) ([]VirtualGameCompanyReport, error) {
rows, err := q.db.Query(ctx, GetCompanyReportsInRange,
arg.CompanyID,
arg.ProviderID,
arg.ReportDate,
arg.ReportDate_2,
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []VirtualGameCompanyReport
for rows.Next() {
var i VirtualGameCompanyReport
if err := rows.Scan(
&i.ID,
&i.CompanyID,
&i.ProviderID,
&i.ReportDate,
&i.ReportType,
&i.TotalBetAmount,
&i.TotalWinAmount,
&i.NetProfit,
&i.ProfitMargin,
&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 GetDailyFinancialReports = `-- name: GetDailyFinancialReports :many
SELECT id, game_id, provider_id, report_date, report_type, total_bets, total_wins, ggr, rtp, created_at, updated_at FROM virtual_game_financial_reports
WHERE report_date = $1
AND report_type = 'daily'
`
func (q *Queries) GetDailyFinancialReports(ctx context.Context, reportDate pgtype.Date) ([]VirtualGameFinancialReport, error) {
rows, err := q.db.Query(ctx, GetDailyFinancialReports, reportDate)
if err != nil {
return nil, err
}
defer rows.Close()
var items []VirtualGameFinancialReport
for rows.Next() {
var i VirtualGameFinancialReport
if err := rows.Scan(
&i.ID,
&i.GameID,
&i.ProviderID,
&i.ReportDate,
&i.ReportType,
&i.TotalBets,
&i.TotalWins,
&i.Ggr,
&i.Rtp,
&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 GetFinancialReportByID = `-- name: GetFinancialReportByID :one
SELECT id, game_id, provider_id, report_date, report_type, total_bets, total_wins, ggr, rtp, created_at, updated_at FROM virtual_game_financial_reports
WHERE id = $1
`
func (q *Queries) GetFinancialReportByID(ctx context.Context, id int64) (VirtualGameFinancialReport, error) {
row := q.db.QueryRow(ctx, GetFinancialReportByID, id)
var i VirtualGameFinancialReport
err := row.Scan(
&i.ID,
&i.GameID,
&i.ProviderID,
&i.ReportDate,
&i.ReportType,
&i.TotalBets,
&i.TotalWins,
&i.Ggr,
&i.Rtp,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const GetFinancialReportsForGame = `-- name: GetFinancialReportsForGame :many
SELECT id, game_id, provider_id, report_date, report_type, total_bets, total_wins, ggr, rtp, created_at, updated_at FROM virtual_game_financial_reports
WHERE game_id = $1
AND provider_id = $2
AND report_date BETWEEN $3 AND $4
ORDER BY report_date
`
type GetFinancialReportsForGameParams struct {
GameID string `json:"game_id"`
ProviderID string `json:"provider_id"`
ReportDate pgtype.Date `json:"report_date"`
ReportDate_2 pgtype.Date `json:"report_date_2"`
}
func (q *Queries) GetFinancialReportsForGame(ctx context.Context, arg GetFinancialReportsForGameParams) ([]VirtualGameFinancialReport, error) {
rows, err := q.db.Query(ctx, GetFinancialReportsForGame,
arg.GameID,
arg.ProviderID,
arg.ReportDate,
arg.ReportDate_2,
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []VirtualGameFinancialReport
for rows.Next() {
var i VirtualGameFinancialReport
if err := rows.Scan(
&i.ID,
&i.GameID,
&i.ProviderID,
&i.ReportDate,
&i.ReportType,
&i.TotalBets,
&i.TotalWins,
&i.Ggr,
&i.Rtp,
&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 GetPlayerActivityByDate = `-- name: GetPlayerActivityByDate :one
SELECT id, user_id, report_date, report_type, total_deposits, total_withdrawals, net_contribution, total_bet_amount, total_win_amount, net_result, rounds_played, avg_bet_size, created_at, updated_at FROM virtual_game_player_activity_reports
WHERE user_id = $1
AND report_date = $2
AND report_type = $3
`
type GetPlayerActivityByDateParams struct {
UserID int64 `json:"user_id"`
ReportDate pgtype.Date `json:"report_date"`
ReportType string `json:"report_type"`
}
func (q *Queries) GetPlayerActivityByDate(ctx context.Context, arg GetPlayerActivityByDateParams) (VirtualGamePlayerActivityReport, error) {
row := q.db.QueryRow(ctx, GetPlayerActivityByDate, arg.UserID, arg.ReportDate, arg.ReportType)
var i VirtualGamePlayerActivityReport
err := row.Scan(
&i.ID,
&i.UserID,
&i.ReportDate,
&i.ReportType,
&i.TotalDeposits,
&i.TotalWithdrawals,
&i.NetContribution,
&i.TotalBetAmount,
&i.TotalWinAmount,
&i.NetResult,
&i.RoundsPlayed,
&i.AvgBetSize,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const GetPlayerActivityByID = `-- name: GetPlayerActivityByID :one
SELECT id, user_id, report_date, report_type, total_deposits, total_withdrawals, net_contribution, total_bet_amount, total_win_amount, net_result, rounds_played, avg_bet_size, created_at, updated_at FROM virtual_game_player_activity_reports
WHERE id = $1
`
func (q *Queries) GetPlayerActivityByID(ctx context.Context, id int64) (VirtualGamePlayerActivityReport, error) {
row := q.db.QueryRow(ctx, GetPlayerActivityByID, id)
var i VirtualGamePlayerActivityReport
err := row.Scan(
&i.ID,
&i.UserID,
&i.ReportDate,
&i.ReportType,
&i.TotalDeposits,
&i.TotalWithdrawals,
&i.NetContribution,
&i.TotalBetAmount,
&i.TotalWinAmount,
&i.NetResult,
&i.RoundsPlayed,
&i.AvgBetSize,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const GetPlayerActivityRange = `-- name: GetPlayerActivityRange :many
SELECT id, user_id, report_date, report_type, total_deposits, total_withdrawals, net_contribution, total_bet_amount, total_win_amount, net_result, rounds_played, avg_bet_size, created_at, updated_at FROM virtual_game_player_activity_reports
WHERE user_id = $1
AND report_date BETWEEN $2 AND $3
ORDER BY report_date
`
type GetPlayerActivityRangeParams struct {
UserID int64 `json:"user_id"`
ReportDate pgtype.Date `json:"report_date"`
ReportDate_2 pgtype.Date `json:"report_date_2"`
}
func (q *Queries) GetPlayerActivityRange(ctx context.Context, arg GetPlayerActivityRangeParams) ([]VirtualGamePlayerActivityReport, error) {
rows, err := q.db.Query(ctx, GetPlayerActivityRange, arg.UserID, arg.ReportDate, arg.ReportDate_2)
if err != nil {
return nil, err
}
defer rows.Close()
var items []VirtualGamePlayerActivityReport
for rows.Next() {
var i VirtualGamePlayerActivityReport
if err := rows.Scan(
&i.ID,
&i.UserID,
&i.ReportDate,
&i.ReportType,
&i.TotalDeposits,
&i.TotalWithdrawals,
&i.NetContribution,
&i.TotalBetAmount,
&i.TotalWinAmount,
&i.NetResult,
&i.RoundsPlayed,
&i.AvgBetSize,
&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 GetTopPlayersByNetResult = `-- name: GetTopPlayersByNetResult :many
SELECT user_id, SUM(net_result) AS total_net
FROM virtual_game_player_activity_reports
WHERE report_date BETWEEN $1 AND $2
GROUP BY user_id
ORDER BY total_net DESC
LIMIT $3
`
type GetTopPlayersByNetResultParams struct {
ReportDate pgtype.Date `json:"report_date"`
ReportDate_2 pgtype.Date `json:"report_date_2"`
Limit int32 `json:"limit"`
}
type GetTopPlayersByNetResultRow struct {
UserID int64 `json:"user_id"`
TotalNet int64 `json:"total_net"`
}
func (q *Queries) GetTopPlayersByNetResult(ctx context.Context, arg GetTopPlayersByNetResultParams) ([]GetTopPlayersByNetResultRow, error) {
rows, err := q.db.Query(ctx, GetTopPlayersByNetResult, arg.ReportDate, arg.ReportDate_2, arg.Limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetTopPlayersByNetResultRow
for rows.Next() {
var i GetTopPlayersByNetResultRow
if err := rows.Scan(&i.UserID, &i.TotalNet); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const UpsertCompanyReport = `-- name: UpsertCompanyReport :one
INSERT INTO virtual_game_company_reports (
company_id, provider_id,
report_date, report_type,
total_bet_amount, total_win_amount,
created_at, updated_at
)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())
ON CONFLICT (company_id, provider_id, report_date, report_type)
DO UPDATE SET
total_bet_amount = EXCLUDED.total_bet_amount,
total_win_amount = EXCLUDED.total_win_amount,
updated_at = NOW()
RETURNING id, company_id, provider_id, report_date, report_type, total_bet_amount, total_win_amount, net_profit, profit_margin, created_at, updated_at
`
type UpsertCompanyReportParams struct {
CompanyID int64 `json:"company_id"`
ProviderID string `json:"provider_id"`
ReportDate pgtype.Date `json:"report_date"`
ReportType string `json:"report_type"`
TotalBetAmount pgtype.Numeric `json:"total_bet_amount"`
TotalWinAmount pgtype.Numeric `json:"total_win_amount"`
}
func (q *Queries) UpsertCompanyReport(ctx context.Context, arg UpsertCompanyReportParams) (VirtualGameCompanyReport, error) {
row := q.db.QueryRow(ctx, UpsertCompanyReport,
arg.CompanyID,
arg.ProviderID,
arg.ReportDate,
arg.ReportType,
arg.TotalBetAmount,
arg.TotalWinAmount,
)
var i VirtualGameCompanyReport
err := row.Scan(
&i.ID,
&i.CompanyID,
&i.ProviderID,
&i.ReportDate,
&i.ReportType,
&i.TotalBetAmount,
&i.TotalWinAmount,
&i.NetProfit,
&i.ProfitMargin,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const UpsertFinancialReport = `-- name: UpsertFinancialReport :one
INSERT INTO virtual_game_financial_reports (
game_id, provider_id, report_date, report_type,
total_bets, total_wins, created_at, updated_at
)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())
ON CONFLICT (game_id, provider_id, report_date, report_type)
DO UPDATE SET
total_bets = EXCLUDED.total_bets,
total_wins = EXCLUDED.total_wins,
updated_at = NOW()
RETURNING id, game_id, provider_id, report_date, report_type, total_bets, total_wins, ggr, rtp, created_at, updated_at
`
type UpsertFinancialReportParams struct {
GameID string `json:"game_id"`
ProviderID string `json:"provider_id"`
ReportDate pgtype.Date `json:"report_date"`
ReportType string `json:"report_type"`
TotalBets pgtype.Numeric `json:"total_bets"`
TotalWins pgtype.Numeric `json:"total_wins"`
}
func (q *Queries) UpsertFinancialReport(ctx context.Context, arg UpsertFinancialReportParams) (VirtualGameFinancialReport, error) {
row := q.db.QueryRow(ctx, UpsertFinancialReport,
arg.GameID,
arg.ProviderID,
arg.ReportDate,
arg.ReportType,
arg.TotalBets,
arg.TotalWins,
)
var i VirtualGameFinancialReport
err := row.Scan(
&i.ID,
&i.GameID,
&i.ProviderID,
&i.ReportDate,
&i.ReportType,
&i.TotalBets,
&i.TotalWins,
&i.Ggr,
&i.Rtp,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const UpsertPlayerActivityReport = `-- name: UpsertPlayerActivityReport :one
INSERT INTO virtual_game_player_activity_reports (
user_id, report_date, report_type,
total_deposits, total_withdrawals,
total_bet_amount, total_win_amount,
rounds_played, created_at, updated_at
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW(), NOW())
ON CONFLICT (user_id, report_date, report_type)
DO UPDATE SET
total_deposits = EXCLUDED.total_deposits,
total_withdrawals = EXCLUDED.total_withdrawals,
total_bet_amount = EXCLUDED.total_bet_amount,
total_win_amount = EXCLUDED.total_win_amount,
rounds_played = EXCLUDED.rounds_played,
updated_at = NOW()
RETURNING id, user_id, report_date, report_type, total_deposits, total_withdrawals, net_contribution, total_bet_amount, total_win_amount, net_result, rounds_played, avg_bet_size, created_at, updated_at
`
type UpsertPlayerActivityReportParams struct {
UserID int64 `json:"user_id"`
ReportDate pgtype.Date `json:"report_date"`
ReportType string `json:"report_type"`
TotalDeposits pgtype.Numeric `json:"total_deposits"`
TotalWithdrawals pgtype.Numeric `json:"total_withdrawals"`
TotalBetAmount pgtype.Numeric `json:"total_bet_amount"`
TotalWinAmount pgtype.Numeric `json:"total_win_amount"`
RoundsPlayed pgtype.Int8 `json:"rounds_played"`
}
func (q *Queries) UpsertPlayerActivityReport(ctx context.Context, arg UpsertPlayerActivityReportParams) (VirtualGamePlayerActivityReport, error) {
row := q.db.QueryRow(ctx, UpsertPlayerActivityReport,
arg.UserID,
arg.ReportDate,
arg.ReportType,
arg.TotalDeposits,
arg.TotalWithdrawals,
arg.TotalBetAmount,
arg.TotalWinAmount,
arg.RoundsPlayed,
)
var i VirtualGamePlayerActivityReport
err := row.Scan(
&i.ID,
&i.UserID,
&i.ReportDate,
&i.ReportType,
&i.TotalDeposits,
&i.TotalWithdrawals,
&i.NetContribution,
&i.TotalBetAmount,
&i.TotalWinAmount,
&i.NetResult,
&i.RoundsPlayed,
&i.AvgBetSize,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

View File

@ -0,0 +1,149 @@
package domain
import (
"math/big"
"time"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
)
type FinancialReport struct {
ID int64 `json:"id"`
GameID string `json:"gameId"`
ProviderID string `json:"providerId"`
ReportDate string `json:"reportDate"` // YYYY-MM-DD
ReportType string `json:"reportType"`
TotalBets float64 `json:"totalBets"`
TotalWins float64 `json:"totalWins"`
GGR float64 `json:"ggr"`
RTP float64 `json:"rtp"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
}
func ConvertDBFinancialReport(dbReport dbgen.VirtualGameFinancialReport) FinancialReport {
return FinancialReport{
ID: dbReport.ID,
GameID: dbReport.GameID,
ProviderID: dbReport.ProviderID,
ReportDate: dbReport.ReportDate.Time.Format("2006-01-02"),
ReportType: dbReport.ReportType,
TotalBets: float64(dbReport.TotalBets.Exp),
TotalWins: float64(dbReport.TotalWins.Exp),
GGR: float64(dbReport.Ggr.Exp),
RTP: float64(dbReport.Rtp.Exp),
CreatedAt: dbReport.CreatedAt.Time,
UpdatedAt: &dbReport.UpdatedAt.Time,
}
}
type CompanyReport struct {
ID int64 `json:"id"`
CompanyID int64 `json:"companyId"`
ProviderID string `json:"providerId"`
ReportDate string `json:"reportDate"` // YYYY-MM-DD
ReportType string `json:"reportType"`
TotalBetAmount float64 `json:"totalBetAmount"`
TotalWinAmount float64 `json:"totalWinAmount"`
NetProfit float64 `json:"netProfit"`
ProfitMargin float64 `json:"profitMargin"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
}
// ConvertDBCompanyReport converts the SQLC generated CompanyReport struct to domain.CompanyReport
func ConvertDBCompanyReport(dbReport dbgen.VirtualGameCompanyReport) CompanyReport {
var updatedAt *time.Time
if !dbReport.UpdatedAt.Time.IsZero() {
updatedAt = &dbReport.UpdatedAt.Time
}
// convert big.Int-backed numeric fields to float64 safely
var totalBetAmount float64
if dbReport.TotalBetAmount.Int != nil {
if f, _ := new(big.Float).SetInt(dbReport.TotalBetAmount.Int).Float64(); true {
totalBetAmount = f
}
}
var totalWinAmount float64
if dbReport.TotalWinAmount.Int != nil {
if f, _ := new(big.Float).SetInt(dbReport.TotalWinAmount.Int).Float64(); true {
totalWinAmount = f
}
}
var netProfit float64
if dbReport.NetProfit.Int != nil {
if f, _ := new(big.Float).SetInt(dbReport.NetProfit.Int).Float64(); true {
netProfit = f
}
}
var profitMargin float64
if dbReport.ProfitMargin.Int != nil {
if f, _ := new(big.Float).SetInt(dbReport.ProfitMargin.Int).Float64(); true {
profitMargin = f
}
}
return CompanyReport{
ID: dbReport.ID,
CompanyID: dbReport.CompanyID,
ProviderID: dbReport.ProviderID,
ReportDate: dbReport.ReportDate.Time.Format("2006-01-02"),
ReportType: dbReport.ReportType,
TotalBetAmount: totalBetAmount,
TotalWinAmount: totalWinAmount,
NetProfit: netProfit,
ProfitMargin: profitMargin,
CreatedAt: dbReport.CreatedAt.Time,
UpdatedAt: updatedAt,
}
}
type CompanyProfitTrend struct {
ReportDate string `json:"reportDate"`
TotalProfit float64 `json:"totalProfit"`
}
type PlayerActivityReport struct {
ID int64 `json:"id"`
UserID int64 `json:"userId"`
ReportDate string `json:"reportDate"` // YYYY-MM-DD
ReportType string `json:"reportType"`
TotalDeposits float64 `json:"totalDeposits"`
TotalWithdrawals float64 `json:"totalWithdrawals"`
NetContribution float64 `json:"netContribution"`
TotalBetAmount float64 `json:"totalBetAmount"`
TotalWinAmount float64 `json:"totalWinAmount"`
NetResult float64 `json:"netResult"`
RoundsPlayed int64 `json:"roundsPlayed"`
AvgBetSize float64 `json:"avgBetSize"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
}
func ConvertDBPlayerActivityReport(dbReport dbgen.VirtualGamePlayerActivityReport) PlayerActivityReport {
return PlayerActivityReport{
ID: dbReport.ID,
UserID: dbReport.UserID,
ReportDate: dbReport.ReportDate.Time.Format("2006-01-02"),
ReportType: dbReport.ReportType,
TotalDeposits: float64(dbReport.TotalDeposits.Exp),
TotalWithdrawals: float64(dbReport.TotalWithdrawals.Exp),
NetContribution: float64(dbReport.NetContribution.Exp),
TotalBetAmount: float64(dbReport.TotalBetAmount.Exp),
TotalWinAmount: float64(dbReport.TotalWinAmount.Exp),
NetResult: float64(dbReport.NetResult.Exp),
RoundsPlayed: dbReport.RoundsPlayed.Int64,
AvgBetSize: float64(dbReport.AvgBetSize.Exp),
CreatedAt: dbReport.CreatedAt.Time,
UpdatedAt: &dbReport.UpdatedAt.Time,
}
}
type TopPlayerNetResult struct {
UserID int64 `json:"userId"`
TotalNet float64 `json:"totalNet"`
}

View File

@ -0,0 +1,27 @@
package ports
import (
"context"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)
type VirtualGameReportStore interface {
CreateFinancialReport(ctx context.Context, report domain.FinancialReport) (domain.FinancialReport, error)
UpsertFinancialReport(ctx context.Context, report domain.FinancialReport) (domain.FinancialReport, error)
GetFinancialReportByID(ctx context.Context, id int64) (domain.FinancialReport, error)
GetFinancialReportsForGame(ctx context.Context, gameID, providerID string, from, to string) ([]domain.FinancialReport, error)
GetDailyFinancialReports(ctx context.Context, reportDate string) ([]domain.FinancialReport, error)
DeleteFinancialReport(ctx context.Context, id int64) error
CreateCompanyReport(ctx context.Context, report domain.CompanyReport) (domain.CompanyReport, error)
UpsertCompanyReport(ctx context.Context, report domain.CompanyReport) (domain.CompanyReport, error)
GetCompanyReportByID(ctx context.Context, id int64) (domain.CompanyReport, error)
GetCompanyReportsInRange(ctx context.Context, companyID int64, providerID string, startDate, endDate string) ([]domain.CompanyReport, error)
GetCompanyProfitTrend(ctx context.Context, companyID int64, providerID string, startDate, endDate string) ([]domain.CompanyProfitTrend, error)
CreatePlayerActivityReport(ctx context.Context, report domain.PlayerActivityReport) (domain.PlayerActivityReport, error)
GetPlayerActivityByID(ctx context.Context, id int64) (domain.PlayerActivityReport, error)
GetPlayerActivityByDate(ctx context.Context, userID int64, reportDate, reportType string) (domain.PlayerActivityReport, error)
GetPlayerActivityRange(ctx context.Context, userID int64, startDate, endDate string) ([]domain.PlayerActivityReport, error)
GetTopPlayersByNetResult(ctx context.Context, startDate, endDate string, limit int) ([]domain.TopPlayerNetResult, error)
DeletePlayerActivityReport(ctx context.Context, id int64) error
}

View File

@ -122,6 +122,14 @@ func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]doma
CreatedAfter: filter.CreatedAfter.ToPG(), CreatedAfter: filter.CreatedAfter.ToPG(),
}) })
if err != nil {
// domain.MongoDBLogger.Error("failed to get all bets",
// zap.Any("filter", filter),
// zap.Error(err),
// )
return nil, 0, err
}
var result []domain.GetBet = make([]domain.GetBet, 0, len(bets)) var result []domain.GetBet = make([]domain.GetBet, 0, len(bets))
for _, bet := range bets { for _, bet := range bets {
result = append(result, domain.ConvertDBBetWithOutcomes(bet)) result = append(result, domain.ConvertDBBetWithOutcomes(bet))

View File

@ -1,106 +0,0 @@
package repository
import (
"context"
"fmt"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/ports"
"github.com/jackc/pgx/v5/pgtype"
)
// Interface for ReportStore
func NewReportStore(s *Store) ports.ReportStore { return s }
func (s *Store) CreateReportRequest(ctx context.Context, report domain.CreateReportRequest) (domain.ReportRequest, error) {
reportMetadata, err := report.Metadata.ToPG()
if err != nil {
return domain.ReportRequest{}, err
}
dbReportRequest, err := s.queries.CreateReportRequest(ctx, dbgen.CreateReportRequestParams{
CompanyID: report.CompanyID.ToPG(),
RequestedBy: report.RequestedBy.ToPG(),
Type: string(report.Type),
Metadata: reportMetadata,
})
if err != nil {
return domain.ReportRequest{}, fmt.Errorf("failed to create report request: %w", err)
}
return domain.ConvertDBReportRequest(dbReportRequest)
}
func (s *Store) GetAllReportRequests(ctx context.Context, filter domain.ReportRequestFilter) ([]domain.ReportRequestDetail, int64, error) {
dbReportRequests, err := s.queries.GetAllReportRequests(ctx, dbgen.GetAllReportRequestsParams{
CompanyID: filter.CompanyID.ToPG(),
Type: filter.Type.ToPG(),
Status: filter.Status.ToPG(),
Limit: filter.Limit.ToPG(),
Offset: pgtype.Int4{
Int32: int32(filter.Offset.Value * filter.Limit.Value),
Valid: filter.Offset.Valid,
},
RequestedBy: filter.RequestedBy.ToPG(),
})
if err != nil {
return nil, 0, err
}
total, err := s.queries.GetTotalReportRequests(ctx, dbgen.GetTotalReportRequestsParams{
CompanyID: filter.CompanyID.ToPG(),
Type: filter.Type.ToPG(),
Status: filter.Status.ToPG(),
RequestedBy: filter.RequestedBy.ToPG(),
})
if err != nil {
return nil, 0, err
}
result, err := domain.ConvertDBReportRequestDetailList(dbReportRequests)
if err != nil {
return nil, 0, err
}
return result, total, nil
}
func (s *Store) GetReportRequestByRequestedByID(ctx context.Context, requestedBy int64, filter domain.ReportRequestFilter) ([]domain.ReportRequestDetail, error) {
dbReportRequests, err := s.queries.GetReportRequestByRequestedByID(ctx, dbgen.GetReportRequestByRequestedByIDParams{
RequestedBy: pgtype.Int8{
Int64: requestedBy,
Valid: true,
},
Type: filter.Type.ToPG(),
Status: filter.Status.ToPG(),
Limit: filter.Limit.ToPG(),
Offset: pgtype.Int4{
Int32: int32(filter.Offset.Value * filter.Limit.Value),
Valid: filter.Offset.Valid,
},
})
if err != nil {
return nil, err
}
return domain.ConvertDBReportRequestDetailList(dbReportRequests)
}
func (s *Store) GetReportRequestByID(ctx context.Context, ID int64) (domain.ReportRequestDetail, error) {
dbReportRequest, err := s.queries.GetReportRequestByID(ctx, ID)
if err != nil {
return domain.ReportRequestDetail{}, err
}
return domain.ConvertDBReportRequestDetail(dbReportRequest)
}
func (s *Store) UpdateReportRequest(ctx context.Context, report domain.UpdateRequestRequest) error {
err := s.queries.UpdateReportRequest(ctx, dbgen.UpdateReportRequestParams{
ID: report.ID,
FilePath: report.FilePath.ToPG(),
RejectReason: report.RejectReason.ToPG(),
Status: report.Status.ToPG(),
})
if err != nil {
return fmt.Errorf("failed to update report request: %w", err)
}
return nil
}

View File

@ -0,0 +1,450 @@
package repository
import (
"context"
"fmt"
"time"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/ports"
"github.com/jackc/pgx/v5/pgtype"
)
// NewVirtualGameReportStore returns a new VirtualGameReportStore interface
func NewVirtualGameReportStore(s *Store) ports.VirtualGameReportStore {
return s
}
// ------------------- Financial Reports -------------------
func (s *Store) CreateFinancialReport(ctx context.Context, report domain.FinancialReport) (domain.FinancialReport, error) {
// pgtype.Numeric no longer exposes a Set method in this pgx version;
// use zero-value pgtype.Numeric here (or replace with a proper conversion helper).
totalBets := pgtype.Numeric{}
totalWins := pgtype.Numeric{}
// parse report.ReportDate (try RFC3339 then YYYY-MM-DD)
t, err := time.Parse(time.RFC3339, report.ReportDate)
if err != nil {
t, err = time.Parse("2006-01-02", report.ReportDate)
if err != nil {
return domain.FinancialReport{}, fmt.Errorf("invalid report date: %w", err)
}
}
dbReport, err := s.queries.CreateFinancialReport(ctx, dbgen.CreateFinancialReportParams{
GameID: report.GameID,
ProviderID: report.ProviderID,
ReportDate: pgtype.Date{Time: t},
ReportType: report.ReportType,
TotalBets: totalBets,
TotalWins: totalWins,
})
if err != nil {
return domain.FinancialReport{}, fmt.Errorf("failed to create financial report: %w", err)
}
return domain.ConvertDBFinancialReport(dbReport), nil
}
func (s *Store) UpsertFinancialReport(ctx context.Context, report domain.FinancialReport) (domain.FinancialReport, error) {
totalBets := pgtype.Numeric{}
totalWins := pgtype.Numeric{}
// parse report.ReportDate
t, err := time.Parse(time.RFC3339, report.ReportDate)
if err != nil {
t, err = time.Parse("2006-01-02", report.ReportDate)
if err != nil {
return domain.FinancialReport{}, fmt.Errorf("invalid report date: %w", err)
}
}
dbReport, err := s.queries.UpsertFinancialReport(ctx, dbgen.UpsertFinancialReportParams{
GameID: report.GameID,
ProviderID: report.ProviderID,
ReportDate: pgtype.Date{Time: t},
ReportType: report.ReportType,
TotalBets: totalBets,
TotalWins: totalWins,
})
if err != nil {
return domain.FinancialReport{}, fmt.Errorf("failed to upsert financial report: %w", err)
}
return domain.ConvertDBFinancialReport(dbReport), nil
}
// GetFinancialReportByID fetches a single report by its ID
func (s *Store) GetFinancialReportByID(ctx context.Context, id int64) (domain.FinancialReport, error) {
dbReport, err := s.queries.GetFinancialReportByID(ctx, id)
if err != nil {
return domain.FinancialReport{}, fmt.Errorf("failed to get financial report by ID: %w", err)
}
return domain.ConvertDBFinancialReport(dbReport), nil
}
// GetFinancialReportsForGame fetches all reports for a specific game + provider in a date range
func (s *Store) GetFinancialReportsForGame(ctx context.Context, gameID, providerID string, from, to string) ([]domain.FinancialReport, error) {
fromDate, err := time.Parse("2006-01-02", from)
if err != nil {
return nil, fmt.Errorf("invalid from date: %w", err)
}
toDate, err := time.Parse("2006-01-02", to)
if err != nil {
return nil, fmt.Errorf("invalid to date: %w", err)
}
dbReports, err := s.queries.GetFinancialReportsForGame(ctx, dbgen.GetFinancialReportsForGameParams{
GameID: gameID,
ProviderID: providerID,
ReportDate: pgtype.Date{Time: fromDate},
ReportDate_2: pgtype.Date{Time: toDate},
})
if err != nil {
return nil, fmt.Errorf("failed to get financial reports for game: %w", err)
}
var result []domain.FinancialReport
for _, r := range dbReports {
result = append(result, domain.ConvertDBFinancialReport(r))
}
return result, nil
}
func (s *Store) GetDailyFinancialReports(ctx context.Context, reportDate string) ([]domain.FinancialReport, error) {
t, err := time.Parse("2006-01-02", reportDate)
if err != nil {
return nil, fmt.Errorf("invalid report date: %w", err)
}
dbReports, err := s.queries.GetDailyFinancialReports(ctx, pgtype.Date{Time: t})
if err != nil {
return nil, fmt.Errorf("failed to get daily financial reports: %w", err)
}
var result []domain.FinancialReport
for _, r := range dbReports {
result = append(result, domain.ConvertDBFinancialReport(r))
}
return result, nil
}
// DeleteFinancialReport deletes a financial report by ID
func (s *Store) DeleteFinancialReport(ctx context.Context, id int64) error {
err := s.queries.DeleteFinancialReport(ctx, id)
if err != nil {
return fmt.Errorf("failed to delete financial report: %w", err)
}
return nil
}
// CreateCompanyReport inserts a new company-level financial report
func (s *Store) CreateCompanyReport(ctx context.Context, report domain.CompanyReport) (domain.CompanyReport, error) {
// parse report date
t, err := time.Parse("2006-01-02", report.ReportDate)
if err != nil {
return domain.CompanyReport{}, fmt.Errorf("invalid report date: %w", err)
}
dbReport, err := s.queries.CreateCompanyReport(ctx, dbgen.CreateCompanyReportParams{
CompanyID: report.CompanyID,
ProviderID: report.ProviderID,
ReportDate: pgtype.Date{Time: t},
ReportType: report.ReportType,
TotalBetAmount: pgtype.Numeric{}, // zero value; set actual value if required
TotalWinAmount: pgtype.Numeric{},
})
if err != nil {
return domain.CompanyReport{}, fmt.Errorf("failed to create company report: %w", err)
}
return domain.ConvertDBCompanyReport(dbReport), nil
}
func (s *Store) UpsertCompanyReport(ctx context.Context, report domain.CompanyReport) (domain.CompanyReport, error) {
// Convert report date
t, err := time.Parse("2006-01-02", report.ReportDate)
if err != nil {
t, err = time.Parse(time.RFC3339, report.ReportDate)
if err != nil {
return domain.CompanyReport{}, fmt.Errorf("invalid report date: %w", err)
}
}
dbReport, err := s.queries.UpsertCompanyReport(ctx, dbgen.UpsertCompanyReportParams{
CompanyID: report.CompanyID,
ProviderID: report.ProviderID,
ReportDate: pgtype.Date{Time: t},
ReportType: report.ReportType,
TotalBetAmount: func() pgtype.Numeric {
var n pgtype.Numeric
_ = n.Scan(report.TotalBetAmount)
return n
}(),
TotalWinAmount: func() pgtype.Numeric {
var n pgtype.Numeric
_ = n.Scan(report.TotalWinAmount)
return n
}(),
})
if err != nil {
return domain.CompanyReport{}, fmt.Errorf("failed to upsert company report: %w", err)
}
return domain.ConvertDBCompanyReport(dbReport), nil
}
func (s *Store) GetCompanyReportByID(ctx context.Context, id int64) (domain.CompanyReport, error) {
dbReport, err := s.queries.GetCompanyReportByID(ctx, id)
if err != nil {
return domain.CompanyReport{}, fmt.Errorf("failed to get company report by ID: %w", err)
}
return domain.ConvertDBCompanyReport(dbReport), nil
}
func (s *Store) GetCompanyReportsInRange(ctx context.Context, companyID int64, providerID string, startDate, endDate string) ([]domain.CompanyReport, error) {
start, err := time.Parse("2006-01-02", startDate)
if err != nil {
start, err = time.Parse(time.RFC3339, startDate)
if err != nil {
return nil, fmt.Errorf("invalid start date: %w", err)
}
}
end, err := time.Parse("2006-01-02", endDate)
if err != nil {
end, err = time.Parse(time.RFC3339, endDate)
if err != nil {
return nil, fmt.Errorf("invalid end date: %w", err)
}
}
dbReports, err := s.queries.GetCompanyReportsInRange(ctx, dbgen.GetCompanyReportsInRangeParams{
CompanyID: companyID,
ProviderID: providerID,
ReportDate: pgtype.Date{Time: start},
ReportDate_2: pgtype.Date{Time: end},
})
if err != nil {
return nil, fmt.Errorf("failed to get company reports in range: %w", err)
}
reports := make([]domain.CompanyReport, 0, len(dbReports))
for _, r := range dbReports {
reports = append(reports, domain.ConvertDBCompanyReport(r))
}
return reports, nil
}
func (s *Store) GetCompanyProfitTrend(ctx context.Context, companyID int64, providerID string, startDate, endDate string) ([]domain.CompanyProfitTrend, error) {
start, err := time.Parse("2006-01-02", startDate)
if err != nil {
start, err = time.Parse(time.RFC3339, startDate)
if err != nil {
return nil, fmt.Errorf("invalid start date: %w", err)
}
}
end, err := time.Parse("2006-01-02", endDate)
if err != nil {
end, err = time.Parse(time.RFC3339, endDate)
if err != nil {
return nil, fmt.Errorf("invalid end date: %w", err)
}
}
rows, err := s.queries.GetCompanyProfitTrend(ctx, dbgen.GetCompanyProfitTrendParams{
CompanyID: companyID,
ProviderID: providerID,
ReportDate: pgtype.Date{Time: start},
ReportDate_2: pgtype.Date{Time: end},
})
if err != nil {
return nil, fmt.Errorf("failed to get company profit trend: %w", err)
}
trends := make([]domain.CompanyProfitTrend, 0, len(rows))
for _, r := range rows {
trends = append(trends, domain.CompanyProfitTrend{
ReportDate: r.ReportDate.Time.Format("2006-01-02"),
TotalProfit: float64(r.TotalProfit),
})
}
return trends, nil
}
// CreatePlayerActivityReport inserts a new player activity report
func (s *Store) CreatePlayerActivityReport(ctx context.Context, report domain.PlayerActivityReport) (domain.PlayerActivityReport, error) {
t, err := time.Parse("2006-01-02", report.ReportDate)
if err != nil {
t, err = time.Parse(time.RFC3339, report.ReportDate)
if err != nil {
return domain.PlayerActivityReport{}, fmt.Errorf("invalid report date: %w", err)
}
}
createParams := dbgen.CreatePlayerActivityReportParams{
UserID: report.UserID,
ReportDate: pgtype.Date{Time: t},
ReportType: report.ReportType,
TotalDeposits: func() pgtype.Numeric {
var n pgtype.Numeric
_ = n.Scan(report.TotalDeposits)
return n
}(),
TotalWithdrawals: func() pgtype.Numeric {
var n pgtype.Numeric
_ = n.Scan(report.TotalWithdrawals)
return n
}(),
TotalBetAmount: func() pgtype.Numeric {
var n pgtype.Numeric
_ = n.Scan(report.TotalBetAmount)
return n
}(),
TotalWinAmount: func() pgtype.Numeric {
var n pgtype.Numeric
_ = n.Scan(report.TotalWinAmount)
return n
}(),
RoundsPlayed: pgtype.Int8{Int64: int64(report.RoundsPlayed)},
}
var dbReport dbgen.VirtualGamePlayerActivityReport
dbReport, err = s.queries.CreatePlayerActivityReport(ctx, createParams)
if err != nil {
// try upsert as a fallback
upsertParams := dbgen.UpsertPlayerActivityReportParams{
UserID: report.UserID,
ReportDate: pgtype.Date{Time: t},
ReportType: report.ReportType,
TotalDeposits: pgtype.Numeric{Exp: int32(report.TotalDeposits)},
TotalWithdrawals: pgtype.Numeric{Exp: int32(report.TotalWithdrawals)},
TotalBetAmount: pgtype.Numeric{Exp: int32(report.TotalBetAmount)},
TotalWinAmount: pgtype.Numeric{Exp: int32(report.TotalWinAmount)},
RoundsPlayed: pgtype.Int8{Int64: int64(report.RoundsPlayed)},
}
dbReport, err = s.queries.UpsertPlayerActivityReport(ctx, upsertParams)
if err != nil {
return domain.PlayerActivityReport{}, fmt.Errorf("failed to create or upsert player activity report: %w", err)
}
}
return domain.ConvertDBPlayerActivityReport(dbReport), nil
}
func (s *Store) GetPlayerActivityByID(ctx context.Context, id int64) (domain.PlayerActivityReport, error) {
dbReport, err := s.queries.GetPlayerActivityByID(ctx, id)
if err != nil {
return domain.PlayerActivityReport{}, err
}
return domain.ConvertDBPlayerActivityReport(dbReport), nil
}
// GetPlayerActivityByDate fetches a player activity report for a specific user and date
func (s *Store) GetPlayerActivityByDate(ctx context.Context, userID int64, reportDate, reportType string) (domain.PlayerActivityReport, error) {
t, err := time.Parse("2006-01-02", reportDate)
if err != nil {
t, err = time.Parse(time.RFC3339, reportDate)
if err != nil {
return domain.PlayerActivityReport{}, fmt.Errorf("invalid report date: %w", err)
}
}
dbReport, err := s.queries.GetPlayerActivityByDate(ctx, dbgen.GetPlayerActivityByDateParams{
UserID: userID,
ReportDate: pgtype.Date{Time: t},
ReportType: reportType,
})
if err != nil {
return domain.PlayerActivityReport{}, err
}
return domain.ConvertDBPlayerActivityReport(dbReport), nil
}
// GetPlayerActivityRange fetches all activity reports for a user in a date range
func (s *Store) GetPlayerActivityRange(ctx context.Context, userID int64, startDate, endDate string) ([]domain.PlayerActivityReport, error) {
start, err := time.Parse("2006-01-02", startDate)
if err != nil {
start, err = time.Parse(time.RFC3339, startDate)
if err != nil {
return nil, fmt.Errorf("invalid start date: %w", err)
}
}
end, err := time.Parse("2006-01-02", endDate)
if err != nil {
end, err = time.Parse(time.RFC3339, endDate)
if err != nil {
return nil, fmt.Errorf("invalid end date: %w", err)
}
}
dbReports, err := s.queries.GetPlayerActivityRange(ctx, dbgen.GetPlayerActivityRangeParams{
UserID: userID,
ReportDate: pgtype.Date{Time: start},
ReportDate_2: pgtype.Date{Time: end},
})
if err != nil {
return nil, err
}
activities := make([]domain.PlayerActivityReport, 0, len(dbReports))
for _, r := range dbReports {
activities = append(activities, domain.ConvertDBPlayerActivityReport(r))
}
return activities, nil
}
// GetTopPlayersByNetResult returns the top N players by net result in a date range
func (s *Store) GetTopPlayersByNetResult(ctx context.Context, startDate, endDate string, limit int) ([]domain.TopPlayerNetResult, error) {
start, err := time.Parse("2006-01-02", startDate)
if err != nil {
start, err = time.Parse(time.RFC3339, startDate)
if err != nil {
return nil, fmt.Errorf("invalid start date: %w", err)
}
}
end, err := time.Parse("2006-01-02", endDate)
if err != nil {
end, err = time.Parse(time.RFC3339, endDate)
if err != nil {
return nil, fmt.Errorf("invalid end date: %w", err)
}
}
rows, err := s.conn.Query(ctx, `SELECT user_id, SUM(net_result) AS total_net
FROM virtual_game_player_activity_reports
WHERE report_date BETWEEN $1 AND $2
GROUP BY user_id
ORDER BY total_net DESC
LIMIT $3`, start, end, limit)
if err != nil {
return nil, fmt.Errorf("failed to fetch top players: %w", err)
}
defer rows.Close()
var topPlayers []domain.TopPlayerNetResult
for rows.Next() {
var tp domain.TopPlayerNetResult
var totalNet pgtype.Numeric
if err := rows.Scan(&tp.UserID, &totalNet); err != nil {
return nil, err
}
tp.TotalNet = float64(totalNet.Exp)
topPlayers = append(topPlayers, tp)
}
return topPlayers, nil
}
// DeletePlayerActivityReport deletes a player activity report by its ID
func (s *Store) DeletePlayerActivityReport(ctx context.Context, id int64) error {
err := s.queries.DeletePlayerActivityReport(ctx, id)
return err
}

View File

@ -23,7 +23,6 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/raffle" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/raffle"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/santimpay" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/santimpay"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
@ -75,7 +74,7 @@ type App struct {
userSvc *user.Service userSvc *user.Service
betSvc *bet.Service betSvc *bet.Service
virtualGameSvc virtualgameservice.VirtualGameService virtualGameSvc virtualgameservice.VirtualGameService
reportSvc report.ReportService // reportSvc report.ReportService
chapaSvc *chapa.Service chapaSvc *chapa.Service
walletSvc *wallet.Service walletSvc *wallet.Service
transactionSvc *transaction.Service transactionSvc *transaction.Service
@ -113,7 +112,7 @@ func NewApp(
userSvc *user.Service, userSvc *user.Service,
ticketSvc *ticket.Service, ticketSvc *ticket.Service,
betSvc *bet.Service, betSvc *bet.Service,
reportSvc report.ReportService, // reportSvc report.ReportService,
chapaSvc *chapa.Service, chapaSvc *chapa.Service,
walletSvc *wallet.Service, walletSvc *wallet.Service,
transactionSvc *transaction.Service, transactionSvc *transaction.Service,
@ -174,7 +173,7 @@ func NewApp(
userSvc: userSvc, userSvc: userSvc,
ticketSvc: ticketSvc, ticketSvc: ticketSvc,
betSvc: betSvc, betSvc: betSvc,
reportSvc: reportSvc, // reportSvc: reportSvc,
chapaSvc: chapaSvc, chapaSvc: chapaSvc,
walletSvc: walletSvc, walletSvc: walletSvc,
transactionSvc: transactionSvc, transactionSvc: transactionSvc,

View File

@ -23,7 +23,6 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/raffle" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/raffle"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/santimpay" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/santimpay"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
@ -60,7 +59,7 @@ type Handler struct {
referralSvc *referralservice.Service referralSvc *referralservice.Service
raffleSvc *raffle.Service raffleSvc *raffle.Service
bonusSvc *bonus.Service bonusSvc *bonus.Service
reportSvc report.ReportService // reportSvc report.ReportService
chapaSvc *chapa.Service chapaSvc *chapa.Service
walletSvc *wallet.Service walletSvc *wallet.Service
transactionSvc *transaction.Service transactionSvc *transaction.Service
@ -99,7 +98,7 @@ func New(
settingSvc *settings.Service, settingSvc *settings.Service,
notificationSvc *notificationservice.Service, notificationSvc *notificationservice.Service,
validator *customvalidator.CustomValidator, validator *customvalidator.CustomValidator,
reportSvc report.ReportService, // reportSvc report.ReportService,
chapaSvc *chapa.Service, chapaSvc *chapa.Service,
walletSvc *wallet.Service, walletSvc *wallet.Service,
referralSvc *referralservice.Service, referralSvc *referralservice.Service,
@ -139,7 +138,7 @@ func New(
logger: logger, logger: logger,
settingSvc: settingSvc, settingSvc: settingSvc,
notificationSvc: notificationSvc, notificationSvc: notificationSvc,
reportSvc: reportSvc, // reportSvc: reportSvc,
chapaSvc: chapaSvc, chapaSvc: chapaSvc,
walletSvc: walletSvc, walletSvc: walletSvc,
referralSvc: referralSvc, referralSvc: referralSvc,

View File

@ -1,481 +0,0 @@
package handlers
import (
"context"
"fmt"
"math"
"os"
"sort"
"strconv"
"strings"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
// GetDashboardReport returns a comprehensive dashboard report
// @Summary Get dashboard report
// @Description Returns a comprehensive dashboard report with key metrics
// @Tags Reports
// @Accept json
// @Produce json
// @Param company_id query int false "Company ID filter"
// @Param branch_id query int false "Branch ID filter"
// @Param user_id query int false "User ID filter"
// @Param start_time query string false "Start time filter (RFC3339 format)"
// @Param end_time query string false "End time filter (RFC3339 format)"
// @Param sport_id query string false "Sport ID filter"
// @Param status query int false "Status filter (0=Pending, 1=Win, 2=Loss, 3=Half, 4=Void, 5=Error)"
// @Security ApiKeyAuth
// @Success 200 {object} domain.DashboardSummary
// @Failure 400 {object} domain.ErrorResponse
// @Failure 401 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/reports/dashboard [get]
func (h *Handler) GetDashboardReport(c *fiber.Ctx) error {
role := c.Locals("role").(domain.Role)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Parse query parameters
filter, err := parseReportFilter(c, role)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid filter parameters",
Error: err.Error(),
})
}
// Get report data
summary, err := h.reportSvc.GetDashboardSummary(ctx, filter)
if err != nil {
h.logger.Error("failed to get dashboard report", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to generate report",
Error: err.Error(),
})
}
res := domain.ConvertDashboardSummaryToRes(summary)
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Dashboard reports generated successfully",
Success: true,
StatusCode: 200,
Data: res,
})
// return c.Status(fiber.StatusOK).JSON(summary)
}
// parseReportFilter parses query parameters into ReportFilter
func parseReportFilter(c *fiber.Ctx, role domain.Role) (domain.ReportFilter, error) {
var filter domain.ReportFilter
var err error
if c.Query("company_id") != "" && role == domain.RoleSuperAdmin {
companyID, err := strconv.ParseInt(c.Query("company_id"), 10, 64)
if err != nil {
return domain.ReportFilter{}, fmt.Errorf("invalid company_id: %w", err)
}
filter.CompanyID = domain.ValidInt64{Value: companyID, Valid: true}
} else {
filter.CompanyID = c.Locals("company_id").(domain.ValidInt64)
}
if c.Query("branch_id") != "" && role == domain.RoleSuperAdmin {
branchID, err := strconv.ParseInt(c.Query("branch_id"), 10, 64)
if err != nil {
return domain.ReportFilter{}, fmt.Errorf("invalid branch_id: %w", err)
}
filter.BranchID = domain.ValidInt64{Value: branchID, Valid: true}
} else {
filter.BranchID = c.Locals("branch_id").(domain.ValidInt64)
}
if c.Query("user_id") != "" {
userID, err := strconv.ParseInt(c.Query("user_id"), 10, 64)
if err != nil {
return domain.ReportFilter{}, fmt.Errorf("invalid user_id: %w", err)
}
filter.UserID = domain.ValidInt64{Value: userID, Valid: true}
}
if c.Query("start_time") != "" {
startTime, err := time.Parse(time.RFC3339, c.Query("start_time"))
if err != nil {
return domain.ReportFilter{}, fmt.Errorf("invalid start_time: %w", err)
}
filter.StartTime = domain.ValidTime{Value: startTime, Valid: true}
}
if c.Query("end_time") != "" {
endTime, err := time.Parse(time.RFC3339, c.Query("end_time"))
if err != nil {
return domain.ReportFilter{}, fmt.Errorf("invalid end_time: %w", err)
}
filter.EndTime = domain.ValidTime{Value: endTime, Valid: true}
}
if c.Query("sport_id") != "" {
filter.SportID = domain.ValidString{Value: c.Query("sport_id"), Valid: true}
}
if c.Query("status") != "" {
status, err := strconv.ParseInt(c.Query("status"), 10, 32)
if err != nil {
return domain.ReportFilter{}, fmt.Errorf("invalid status: %w", err)
}
filter.Status = domain.ValidOutcomeStatus{Value: domain.OutcomeStatus(status), Valid: true}
}
return filter, err
}
// DownloadReportFile godoc
// @Summary Download a CSV report file
// @Description Downloads a generated report CSV file from the server
// @Tags Reports
// @Param filename path string true "Name of the report file to download (e.g., report_daily_2025-06-21.csv)"
// @Produce text/csv
// @Success 200 {file} file "CSV file will be downloaded"
// @Failure 400 {object} domain.ErrorResponse "Missing or invalid filename"
// @Failure 404 {object} domain.ErrorResponse "Report file not found"
// @Failure 500 {object} domain.ErrorResponse "Internal server error while serving the file"
// @Router /api/v1/report-files/download/{filename} [get]
func (h *Handler) DownloadReportFile(c *fiber.Ctx) error {
filename := c.Params("filename")
if filename == "" || strings.Contains(filename, "..") {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid filename parameter",
Error: "filename is required and must not contain '..'",
})
}
reportDir := "C:/Users/User/Desktop/reports"
// Ensure reports directory exists
if _, err := os.Stat(reportDir); os.IsNotExist(err) {
if err := os.MkdirAll(reportDir, os.ModePerm); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to create report directory",
Error: err.Error(),
})
}
}
filePath := fmt.Sprintf("%s/%s", reportDir, filename)
// Check if the report file exists
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
Message: "Report file not found",
Error: "no such file",
})
}
// Set download headers
c.Set("Content-Type", "text/csv")
c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
// Serve the file
if err := c.SendFile(filePath); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to serve file",
Error: err.Error(),
})
}
return nil
}
// ListReportFiles godoc
// @Summary List available report CSV files
// @Description Returns a paginated list of generated report CSV files with search capability
// @Tags Reports
// @Produce json
// @Param search query string false "Search term to filter filenames"
// @Param page query int false "Page number (default: 1)" default(1)
// @Param limit query int false "Items per page (default: 20, max: 100)" default(20)
// @Success 200 {object} domain.PaginatedFileResponse "Paginated list of CSV report filenames"
// @Failure 400 {object} domain.ErrorResponse "Invalid pagination parameters"
// @Failure 500 {object} domain.ErrorResponse "Failed to read report directory"
// @Router /api/v1/report-files/list [get]
func (h *Handler) ListReportFiles(c *fiber.Ctx) error {
reportDir := "C:/Users/User/Desktop/reports"
searchTerm := c.Query("search")
page := c.QueryInt("page", 1)
limit := c.QueryInt("limit", 20)
// Validate pagination parameters
if page < 1 {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid page number",
Error: "Page must be greater than 0",
})
}
if limit < 1 || limit > 100 {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid limit value",
Error: "Limit must be between 1 and 100",
})
}
// Create the reports directory if it doesn't exist
if _, err := os.Stat(reportDir); os.IsNotExist(err) {
if err := os.MkdirAll(reportDir, os.ModePerm); err != nil {
h.mongoLoggerSvc.Error("failed to create report directory",
zap.Int64("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to create report directory",
Error: err.Error(),
})
}
}
files, err := os.ReadDir(reportDir)
if err != nil {
h.mongoLoggerSvc.Error("failed to read report directory",
zap.Int64("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to read report directory",
Error: err.Error(),
})
}
var allFiles []string
for _, file := range files {
if !file.IsDir() && strings.HasSuffix(file.Name(), ".csv") {
// Apply search filter if provided
if searchTerm == "" || strings.Contains(strings.ToLower(file.Name()), strings.ToLower(searchTerm)) {
allFiles = append(allFiles, file.Name())
}
}
}
// Sort files by name (descending to show newest first)
sort.Slice(allFiles, func(i, j int) bool {
return allFiles[i] > allFiles[j]
})
// Calculate pagination values
total := len(allFiles)
startIdx := (page - 1) * limit
endIdx := startIdx + limit
// Adjust end index if it exceeds the slice length
if endIdx > total {
endIdx = total
}
// Handle case where start index is beyond available items
if startIdx >= total {
return c.Status(fiber.StatusOK).JSON(domain.PaginatedFileResponse{
Response: domain.Response{
StatusCode: fiber.StatusOK,
Message: "No files found for the requested page",
Success: true,
},
Data: []string{},
Pagination: domain.Pagination{
Total: total,
TotalPages: int(math.Ceil(float64(total) / float64(limit))),
CurrentPage: page,
Limit: limit,
},
})
}
paginatedFiles := allFiles[startIdx:endIdx]
return c.Status(fiber.StatusOK).JSON(domain.PaginatedFileResponse{
Response: domain.Response{
StatusCode: fiber.StatusOK,
Message: "Report files retrieved successfully",
Success: true,
},
Data: paginatedFiles,
Pagination: domain.Pagination{
Total: total,
TotalPages: int(math.Ceil(float64(total) / float64(limit))),
CurrentPage: page,
Limit: limit,
},
})
}
func (h *Handler) CreateReportRequest(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64)
companyID := c.Locals("company_id").(domain.ValidInt64)
var req domain.CreateReportRequestReq
if err := c.BodyParser(&req); err != nil {
h.BadRequestLogger().Error(
"Failed to parse CreateReportRequestReq",
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request:", err.Error())
}
valErrs, ok := h.validator.Validate(c, req)
if !ok {
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
h.BadRequestLogger().Error(
"Failed to validate CreateReportRequestReq",
zap.String("errMsg", errMsg),
)
return fiber.NewError(fiber.StatusBadRequest, errMsg)
}
request, err := h.reportSvc.CreateReportRequest(c.Context(), domain.CreateReportRequest{
CompanyID: companyID,
RequestedBy: domain.ValidInt64{
Value: userID,
Valid: true,
},
Type: domain.ReportRequestType(req.Type),
Metadata: req.Metadata,
})
if err != nil {
h.InternalServerErrorLogger().Error("Failed to create report request", zap.Error(err))
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
res := domain.ConvertReportRequest(request)
return response.WriteJSON(c, fiber.StatusOK, "Report Request has been created", res, nil)
}
func (h *Handler) GetAllReportRequests(c *fiber.Ctx) error {
companyID := c.Locals("company_id").(domain.ValidInt64)
page := c.QueryInt("page", 1)
pageSize := c.QueryInt("page_size", 10)
limit := domain.ValidInt32{
Value: int32(pageSize),
Valid: true,
}
offset := domain.ValidInt32{
Value: int32(page - 1),
Valid: true,
}
statusQuery := c.Query("status")
var reportStatus domain.ValidReportRequestStatus
if statusQuery != "" {
reportStatusParsed, err := domain.ParseReportRequestStatus(statusQuery)
if err != nil {
h.BadRequestLogger().Error("Failed to parse statusQuery",
zap.String("status", statusQuery),
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "invalid report status")
}
reportStatus = domain.ValidReportRequestStatus{
Value: reportStatusParsed,
Valid: true,
}
}
typeQuery := c.Query("type")
var reportType domain.ValidReportRequestType
if typeQuery != "" {
reportTypeParsed, err := domain.ParseReportRequestType(typeQuery)
if err != nil {
h.BadRequestLogger().Error("Failed to parse typeQuery",
zap.String("type", typeQuery),
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "invalid report type")
}
reportType = domain.ValidReportRequestType{
Value: reportTypeParsed,
Valid: true,
}
}
requesterQuery := c.Query("requester")
var requestedBy domain.ValidInt64
if requesterQuery != "" {
parsedRequestedBy, err := strconv.ParseInt(requesterQuery, 10, 64)
if err != nil {
h.BadRequestLogger().Error("Failed to parse requester",
zap.String("requester", requesterQuery),
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "invalid report requester")
}
requestedBy = domain.ValidInt64{
Value: parsedRequestedBy,
Valid: true,
}
}
requests, total, err := h.reportSvc.GetAllReportRequests(c.Context(), domain.ReportRequestFilter{
CompanyID: companyID,
Limit: limit,
Offset: offset,
Status: reportStatus,
Type: reportType,
RequestedBy: requestedBy,
})
if err != nil {
h.InternalServerErrorLogger().Error("Failed to retrieve all report requests",
zap.Error(err),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
res := domain.ConvertReportRequestDetailList(requests)
return response.WritePaginatedJSON(c, fiber.StatusOK, "All Report Requests successfully retrieved", res, nil, page, int(total))
}
func (h *Handler) DownloadReportByID(c *fiber.Ctx) error {
requestID := c.Params("id")
id, err := strconv.ParseInt(requestID, 10, 64)
if err != nil {
h.BadRequestLogger().Info("Invalid report request ID",
zap.String("requestID", requestID),
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request ID")
}
file, err := h.reportSvc.CheckAndFetchReportFile(c.Context(), id)
if err != nil {
h.InternalServerErrorLogger().Error("Failed to check and fetch report file",
zap.Error(err),
zap.String("requestID", requestID),
)
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to check and fetch report file:%v", err.Error()))
}
c.Set("Content-Type", "text/csv")
c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", file))
if err := c.SendFile(file); err != nil {
h.InternalServerErrorLogger().Error("Unable to download report file",
zap.Error(err),
zap.String("requestID", requestID),
)
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Unable to download report file:%v", err.Error()))
}
return nil
}

View File

@ -33,7 +33,7 @@ func (a *App) initAppRoutes() {
a.settingSvc, a.settingSvc,
a.NotidicationStore, a.NotidicationStore,
a.validator, a.validator,
a.reportSvc, // a.reportSvc,
a.chapaSvc, a.chapaSvc,
a.walletSvc, a.walletSvc,
a.referralSvc, a.referralSvc,
@ -408,13 +408,13 @@ func (a *App) initAppRoutes() {
groupV1.Get("/currencies/convert", h.ConvertCurrency) groupV1.Get("/currencies/convert", h.ConvertCurrency)
//Report Routes //Report Routes
groupV1.Get("/reports/dashboard", a.authMiddleware, a.OnlyAdminAndAbove, h.GetDashboardReport) // groupV1.Get("/reports/dashboard", a.authMiddleware, a.OnlyAdminAndAbove, h.GetDashboardReport)
groupV1.Get("/report-files/download/:filename", h.DownloadReportFile) // groupV1.Get("/report-files/download/:filename", h.DownloadReportFile)
groupV1.Get("/report-files/list", a.authMiddleware, a.OnlyAdminAndAbove, h.ListReportFiles) // groupV1.Get("/report-files/list", a.authMiddleware, a.OnlyAdminAndAbove, h.ListReportFiles)
groupV1.Post("/reports/requests", a.authMiddleware, a.OnlyAdminAndAbove, h.CreateReportRequest) // groupV1.Post("/reports/requests", a.authMiddleware, a.OnlyAdminAndAbove, h.CreateReportRequest)
groupV1.Get("/reports/requests", a.authMiddleware, a.OnlyAdminAndAbove, h.GetAllReportRequests) // groupV1.Get("/reports/requests", a.authMiddleware, a.OnlyAdminAndAbove, h.GetAllReportRequests)
groupV1.Get("/reports/download/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.DownloadReportByID) // groupV1.Get("/reports/download/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.DownloadReportByID)
//Alea Play Virtual Game Routes //Alea Play Virtual Game Routes
groupV1.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame) groupV1.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame)