feat: Refactor report generation and management
- Removed detailed event routes from the API. - Added new report request routes for creating and fetching report requests. - Introduced new domain models for report requests, including metadata and status handling. - Implemented report request processing logic, including CSV generation for event interval reports. - Enhanced company statistics handling with new domain models and service methods. - Updated repository interfaces and implementations to support new report functionalities. - Added error handling and logging for report file operations and notifications.
This commit is contained in:
parent
5af3c5d978
commit
0ffba57ec5
|
|
@ -33,6 +33,15 @@ Clone the repository:
|
|||
git clone https://github.com/your-org/fortunebet-backend.git
|
||||
cd fortunebet-backend
|
||||
```
|
||||
Then you will need to setup the database, which you can do using:
|
||||
```bash
|
||||
make db-up
|
||||
```
|
||||
|
||||
You will also need to setup the necessary seed_data using:
|
||||
```bash
|
||||
make seed_data
|
||||
```
|
||||
|
||||
## Environment Configuration
|
||||
Create a .env file in the root directory. This file is critical for running the application as it contains database credentials, secret keys, third-party integrations, and configuration flags.
|
||||
|
|
|
|||
12
cmd/main.go
12
cmd/main.go
|
|
@ -114,7 +114,7 @@ func main() {
|
|||
|
||||
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
|
||||
userSvc := user.NewService(store, store, messengerSvc, cfg)
|
||||
eventSvc := event.New(cfg.Bet365Token, store, *settingSvc, domain.MongoDBLogger, cfg)
|
||||
eventSvc := event.New(cfg.Bet365Token, store, settingSvc, domain.MongoDBLogger, cfg)
|
||||
oddsSvc := odds.New(store, cfg, eventSvc, logger, domain.MongoDBLogger)
|
||||
notificationRepo := repository.NewNotificationRepository(store)
|
||||
virtuaGamesRepo := repository.NewVirtualGameRepository(store)
|
||||
|
|
@ -139,7 +139,7 @@ func main() {
|
|||
branchSvc := branch.NewService(store)
|
||||
companySvc := company.NewService(store)
|
||||
leagueSvc := league.New(store)
|
||||
ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger, *settingSvc, notificationSvc)
|
||||
ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger, settingSvc, notificationSvc)
|
||||
betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, *companySvc, *settingSvc, *userSvc, notificationSvc, logger, domain.MongoDBLogger)
|
||||
resultSvc := result.NewService(store, cfg, logger, domain.MongoDBLogger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc, messengerSvc, *userSvc)
|
||||
bonusSvc := bonus.NewService(store, walletSvc, settingSvc, notificationSvc, domain.MongoDBLogger)
|
||||
|
|
@ -176,6 +176,7 @@ func main() {
|
|||
transactionSvc := transaction.NewService(store, *branchSvc, *betSvc, *walletSvc, *userSvc)
|
||||
|
||||
reportSvc := report.NewService(
|
||||
store,
|
||||
bet.BetStore(store),
|
||||
wallet.WalletStore(store),
|
||||
transaction.TransactionStore(store),
|
||||
|
|
@ -185,7 +186,12 @@ func main() {
|
|||
company.CompanyStore(store),
|
||||
virtuaGamesRepo,
|
||||
notificationRepo,
|
||||
notificationSvc,
|
||||
eventSvc,
|
||||
companySvc,
|
||||
logger,
|
||||
domain.MongoDBLogger,
|
||||
cfg,
|
||||
)
|
||||
|
||||
enePulseSvc := enetpulse.New(
|
||||
|
|
@ -238,6 +244,8 @@ func main() {
|
|||
|
||||
httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc, domain.MongoDBLogger)
|
||||
httpserver.StartCleanupCrons(*ticketSvc, notificationSvc, domain.MongoDBLogger)
|
||||
httpserver.StartStatCrons(*companySvc, eventSvc, domain.MongoDBLogger)
|
||||
httpserver.StartReportCrons(reportSvc, domain.MongoDBLogger)
|
||||
|
||||
issueReportingRepo := repository.NewReportedIssueRepository(store)
|
||||
|
||||
|
|
|
|||
|
|
@ -442,12 +442,23 @@ CREATE TABLE companies (
|
|||
)
|
||||
);
|
||||
CREATE TABLE company_stats (
|
||||
company_id BIGINT PRIMARY KEY,
|
||||
total_bets BIGINT,
|
||||
total_cash_made BIGINT,
|
||||
total_cash_out BIGINT,
|
||||
total_cash_backs BIGINT,
|
||||
updated_at TIMESTAMP DEFAULT now()
|
||||
company_id BIGINT NOT NULL,
|
||||
interval_start TIMESTAMP NOT NULL,
|
||||
total_bets BIGINT NOT NULL,
|
||||
total_stake BIGINT NOT NULL,
|
||||
deducted_stake BIGINT NOT NULL,
|
||||
total_cash_out BIGINT NOT NULL,
|
||||
total_cash_backs BIGINT NOT NULL,
|
||||
number_of_unsettled BIGINT NOT NULL,
|
||||
total_unsettled_amount BIGINT NOT NULL,
|
||||
total_admins BIGINT NOT NULL,
|
||||
total_managers BIGINT NOT NULL,
|
||||
total_cashiers BIGINT NOT NULL,
|
||||
total_customers BIGINT NOT NULL,
|
||||
total_approvers BIGINT NOT NULL,
|
||||
total_branches BIGINT NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT now(),
|
||||
UNIQUE(company_id, interval_start)
|
||||
);
|
||||
CREATE TABLE leagues (
|
||||
id BIGINT PRIMARY KEY,
|
||||
|
|
@ -575,23 +586,21 @@ CREATE TABLE IF NOT EXISTS raffle_game_filters (
|
|||
game_id VARCHAR(150) NOT NULL,
|
||||
CONSTRAINT unique_raffle_game UNIQUE (raffle_id, game_id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS accumulator (
|
||||
outcome_count BIGINT PRIMARY KEY,
|
||||
default_multiplier REAL NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS company_accumulator (
|
||||
id SERIAL PRIMARY KEY,
|
||||
company_id BIGINT NOT NULL,
|
||||
outcome_count BIGINT NOT NULL,
|
||||
multiplier REAL NOT NULL
|
||||
);
|
||||
CREATE TABLE reports (
|
||||
CREATE TABLE report_requests (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
company_id BIGINT,
|
||||
requested_by BIGINT,
|
||||
--For System Generated Reports
|
||||
file_path TEXT,
|
||||
type TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
metadata JSONB NOT NULL,
|
||||
reject_reason TEXT,
|
||||
created_at TIMESTAMP DEFAULT now(),
|
||||
completed_at TIMESTAMP
|
||||
);
|
||||
|
|
@ -602,20 +611,42 @@ SELECT companies.*,
|
|||
wallets.is_active as wallet_is_active,
|
||||
users.first_name AS admin_first_name,
|
||||
users.last_name AS admin_last_name,
|
||||
users.phone_number AS admin_phone_number
|
||||
users.phone_number AS admin_phone_number,
|
||||
COALESCE(cs.total_bets, 0) AS total_bets,
|
||||
COALESCE(cs.total_stake, 0) AS total_stake,
|
||||
COALESCE(cs.deducted_stake, 0) AS deducted_stake,
|
||||
COALESCE(cs.total_cash_out, 0) AS total_cash_out,
|
||||
COALESCE(cs.total_cash_backs, 0) AS total_cash_backs,
|
||||
COALESCE(cs.number_of_unsettled, 0) AS number_of_unsettled,
|
||||
COALESCE(cs.total_unsettled_amount, 0) AS total_unsettled_amount,
|
||||
COALESCE(cs.total_admins, 0) AS total_admins,
|
||||
COALESCE(cs.total_managers, 0) AS total_managers,
|
||||
COALESCE(cs.total_cashiers, 0) AS total_cashiers,
|
||||
COALESCE(cs.total_customers, 0) AS total_customers,
|
||||
COALESCE(cs.total_approvers, 0) AS total_approvers,
|
||||
COALESCE(cs.total_branches, 0) AS total_branches,
|
||||
cs.updated_at AS stats_updated_at
|
||||
FROM companies
|
||||
JOIN wallets ON wallets.id = companies.wallet_id
|
||||
JOIN users ON users.id = companies.admin_id;
|
||||
;
|
||||
JOIN users ON users.id = companies.admin_id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT *
|
||||
FROM company_stats s
|
||||
WHERE s.company_id = companies.id
|
||||
ORDER BY s.interval_start DESC
|
||||
LIMIT 1
|
||||
) cs ON true;
|
||||
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.is_active AS wallet_is_active
|
||||
wallets.is_active AS wallet_is_active,
|
||||
companies.name AS company_name
|
||||
FROM branches
|
||||
LEFT JOIN users ON branches.branch_manager_id = users.id
|
||||
LEFT JOIN wallets ON wallets.id = branches.wallet_id;
|
||||
LEFT JOIN wallets ON wallets.id = branches.wallet_id
|
||||
JOIN companies ON companies.id = branches.company_id;
|
||||
CREATE TABLE IF NOT EXISTS supported_operations (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
|
|
@ -719,15 +750,15 @@ SELECT sd.*,
|
|||
FROM shop_deposits AS sd
|
||||
JOIN shop_transactions st ON st.id = sd.shop_transaction_id;
|
||||
CREATE OR REPLACE VIEW event_detailed AS
|
||||
SELECT ewc.*,
|
||||
SELECT events.*,
|
||||
leagues.country_code as league_cc,
|
||||
COALESCE(om.total_outcomes, 0) AS total_outcomes,
|
||||
COALESCE(ebs.number_of_bets, 0) AS number_of_bets,
|
||||
COALESCE(ebs.total_amount, 0) AS total_amount,
|
||||
COALESCE(ebs.avg_bet_amount, 0) AS avg_bet_amount,
|
||||
COALESCE(ebs.total_potential_winnings, 0) AS total_potential_winnings
|
||||
FROM events ewc
|
||||
LEFT JOIN event_bet_stats ebs ON ebs.event_id = ewc.id
|
||||
FROM events
|
||||
LEFT JOIN event_bet_stats ebs ON ebs.event_id = events.id
|
||||
LEFT JOIN leagues ON leagues.id = events.league_id
|
||||
LEFT JOIN (
|
||||
SELECT event_id,
|
||||
|
|
@ -735,9 +766,6 @@ FROM events ewc
|
|||
FROM odds_market
|
||||
GROUP BY event_id
|
||||
) om ON om.event_id = events.id;
|
||||
CREATE MATERIALIZED VIEW event_detailed_mat AS
|
||||
SELECT *
|
||||
FROM event_detailed;
|
||||
CREATE VIEW odds_market_with_event AS
|
||||
SELECT o.*,
|
||||
e.is_monitored,
|
||||
|
|
@ -774,7 +802,7 @@ SELECT e.*,
|
|||
FROM events e
|
||||
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
|
||||
JOIN leagues l ON l.id = e.league_id
|
||||
LEFT JOIN event_bet_stats ebs ON ebs.event_id = ewc.id
|
||||
LEFT JOIN event_bet_stats ebs ON ebs.event_id = e.id
|
||||
LEFT JOIN (
|
||||
SELECT event_id,
|
||||
SUM(number_of_outcomes) AS total_outcomes
|
||||
|
|
@ -798,6 +826,15 @@ SELECT o.id,
|
|||
cos.updated_at
|
||||
FROM odds_market o
|
||||
LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id;
|
||||
CREATE VIEW report_request_detail AS
|
||||
SELECT r.*,
|
||||
c.name AS company_name,
|
||||
c.slug AS company_slug,
|
||||
u.first_name AS requester_first_name,
|
||||
u.last_name AS requester_last_name
|
||||
FROM report_requests r
|
||||
LEFT JOIN companies c ON c.id = r.company_id
|
||||
LEFT JOIN users u ON u.id = r.requested_by;
|
||||
-- Foreign Keys
|
||||
ALTER TABLE refresh_tokens
|
||||
ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users (id);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ CREATE TABLE IF NOT EXISTS notifications (
|
|||
'withdraw_success',
|
||||
'bet_placed',
|
||||
'daily_report',
|
||||
'report_request',
|
||||
'high_loss_on_bet',
|
||||
'bet_overload',
|
||||
'signup_welcome',
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
-- Aggregate company stats
|
||||
-- name: UpdateCompanyStats :exec
|
||||
INSERT INTO company_stats (
|
||||
company_id,
|
||||
total_bets,
|
||||
total_cash_made,
|
||||
total_cash_backs,
|
||||
updated_at
|
||||
)
|
||||
SELECT b.company_id,
|
||||
WITH -- Aggregate bet data per company
|
||||
bet_stats AS (
|
||||
SELECT company_id,
|
||||
COUNT(*) AS total_bets,
|
||||
COALESCE(SUM(b.amount), 0) AS total_cash_made,
|
||||
COALESCE(SUM(amount), 0) AS total_stake,
|
||||
COALESCE(
|
||||
SUM(amount) * MAX(companies.deducted_percentage),
|
||||
0
|
||||
) AS deducted_stake,
|
||||
COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN sb.cashed_out THEN b.amount -- use actual cashed_out flag from shop_bets
|
||||
WHEN cashed_out THEN amount
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
|
|
@ -22,21 +20,121 @@ SELECT b.company_id,
|
|||
COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN b.status = 5 THEN b.amount
|
||||
WHEN status = 3 THEN amount
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS total_cash_backs,
|
||||
COUNT(*) FILTER (
|
||||
WHERE status = 5
|
||||
) AS number_of_unsettled,
|
||||
COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN status = 5 THEN amount
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS total_unsettled_amount
|
||||
FROM shop_bet_detail
|
||||
LEFT JOIN companies ON companies.id = shop_bet_detail.company_id
|
||||
GROUP BY company_id
|
||||
),
|
||||
-- Aggregate user counts per company
|
||||
user_stats AS (
|
||||
SELECT company_id,
|
||||
COUNT(*) FILTER (
|
||||
WHERE role = 'admin'
|
||||
) AS total_admins,
|
||||
COUNT(*) FILTER (
|
||||
WHERE role = 'branch_manager'
|
||||
) AS total_managers,
|
||||
COUNT(*) FILTER (
|
||||
WHERE role = 'cashier'
|
||||
) AS total_cashiers,
|
||||
COUNT(*) FILTER (
|
||||
WHERE role = 'customer'
|
||||
) AS total_customers,
|
||||
COUNT(*) FILTER (
|
||||
WHERE role = 'transaction_approver'
|
||||
) AS total_approvers
|
||||
FROM users
|
||||
GROUP BY company_id
|
||||
),
|
||||
-- Aggregate branch counts per company
|
||||
branch_stats AS (
|
||||
SELECT company_id,
|
||||
COUNT(*) AS total_branches
|
||||
FROM branches
|
||||
GROUP BY company_id
|
||||
) -- Final combined aggregation
|
||||
INSERT INTO company_stats (
|
||||
company_id,
|
||||
interval_start,
|
||||
total_bets,
|
||||
total_stake,
|
||||
deducted_stake,
|
||||
total_cash_out,
|
||||
total_cash_backs,
|
||||
number_of_unsettled,
|
||||
total_unsettled_amount,
|
||||
total_admins,
|
||||
total_managers,
|
||||
total_cashiers,
|
||||
total_customers,
|
||||
total_approvers,
|
||||
total_branches,
|
||||
updated_at
|
||||
)
|
||||
SELECT c.id AS company_id,
|
||||
DATE_TRUNC('day', NOW() AT TIME ZONE 'UTC') AS interval_start,
|
||||
COALESCE(b.total_bets, 0) AS total_bets,
|
||||
COALESCE(b.total_stake, 0) AS total_stake,
|
||||
COALESCE(b.deducted_stake, 0) AS deducted_stake,
|
||||
COALESCE(b.total_cash_out, 0) AS total_cash_out,
|
||||
COALESCE(b.total_cash_backs, 0) AS total_cash_backs,
|
||||
COALESCE(b.number_of_unsettled, 0) AS number_of_unsettled,
|
||||
COALESCE(b.total_unsettled_amount, 0) AS total_unsettled_amount,
|
||||
COALESCE(u.total_admins, 0) AS total_admins,
|
||||
COALESCE(u.total_managers, 0) AS total_managers,
|
||||
COALESCE(u.total_cashiers, 0) AS total_cashiers,
|
||||
COALESCE(u.total_customers, 0) AS total_customers,
|
||||
COALESCE(u.total_approvers, 0) AS total_approvers,
|
||||
COALESCE(br.total_branches, 0) AS total_branches,
|
||||
NOW() AS updated_at
|
||||
FROM shop_bet_detail b
|
||||
JOIN companies c ON b.company_id = c.id
|
||||
JOIN shop_bets sb ON sb.id = b.id -- join to get cashed_out
|
||||
GROUP BY b.company_id,
|
||||
c.name ON CONFLICT (company_id) DO
|
||||
FROM companies c
|
||||
LEFT JOIN bet_stats b ON b.company_id = c.id
|
||||
LEFT JOIN user_stats u ON u.company_id = c.id
|
||||
LEFT JOIN branch_stats br ON br.company_id = c.id ON CONFLICT (company_id, interval_start) DO
|
||||
UPDATE
|
||||
SET total_bets = EXCLUDED.total_bets,
|
||||
total_cash_made = EXCLUDED.total_cash_made,
|
||||
total_stake = EXCLUDED.total_stake,
|
||||
deducted_stake = EXCLUDED.deducted_stake,
|
||||
total_cash_out = EXCLUDED.total_cash_out,
|
||||
total_cash_back = EXCLUDED.total_cash_back,
|
||||
total_cash_backs = EXCLUDED.total_cash_backs,
|
||||
number_of_unsettled = EXCLUDED.number_of_unsettled,
|
||||
total_unsettled_amount = EXCLUDED.total_unsettled_amount,
|
||||
total_admins = EXCLUDED.total_admins,
|
||||
total_managers = EXCLUDED.total_managers,
|
||||
total_cashiers = EXCLUDED.total_cashiers,
|
||||
total_customers = EXCLUDED.total_customers,
|
||||
total_approvers = EXCLUDED.total_approvers,
|
||||
total_branches = EXCLUDED.total_branches,
|
||||
updated_at = EXCLUDED.updated_at;
|
||||
-- name: GetCompanyStatsByID :many
|
||||
SELECT *
|
||||
FROM company_stats
|
||||
WHERE company_id = $1
|
||||
ORDER BY interval_start DESC;
|
||||
-- name: GetCompanyStats :many
|
||||
SELECT DATE_TRUNC(sqlc.narg('interval'), interval_start)::timestamp AS interval_start,
|
||||
company_stats.*
|
||||
FROM company_stats
|
||||
WHERE (
|
||||
company_stats.company_id = sqlc.narg('company_id')
|
||||
OR sqlc.narg('company_id') IS NULL
|
||||
)
|
||||
GROUP BY interval_start
|
||||
ORDER BY interval_start DESC;
|
||||
|
|
@ -23,6 +23,3 @@ SET number_of_bets = EXCLUDED.number_of_bets,
|
|||
avg_bet_amount = EXCLUDED.avg_bet_amount,
|
||||
total_potential_winnings = EXCLUDED.total_potential_winnings,
|
||||
updated_at = EXCLUDED.updated_at;
|
||||
|
||||
-- name: UpdateEventDetailedViewMat :exec
|
||||
REFRESH MATERIALIZED VIEW event_detailed_mat;
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
-- 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;
|
||||
|
|
@ -159,7 +159,7 @@ func (q *Queries) DeleteBranchOperation(ctx context.Context, arg DeleteBranchOpe
|
|||
}
|
||||
|
||||
const GetAllBranches = `-- name: GetAllBranches :many
|
||||
SELECT id, name, location, profit_percent, 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
|
||||
SELECT id, name, location, profit_percent, 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, company_name
|
||||
FROM branch_details
|
||||
WHERE (
|
||||
company_id = $1
|
||||
|
|
@ -229,6 +229,7 @@ func (q *Queries) GetAllBranches(ctx context.Context, arg GetAllBranchesParams)
|
|||
&i.ManagerPhoneNumber,
|
||||
&i.Balance,
|
||||
&i.WalletIsActive,
|
||||
&i.CompanyName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -292,7 +293,7 @@ func (q *Queries) GetBranchByCashier(ctx context.Context, userID int64) (Branch,
|
|||
}
|
||||
|
||||
const GetBranchByCompanyID = `-- name: GetBranchByCompanyID :many
|
||||
SELECT id, name, location, profit_percent, 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
|
||||
SELECT id, name, location, profit_percent, 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, company_name
|
||||
FROM branch_details
|
||||
WHERE company_id = $1
|
||||
`
|
||||
|
|
@ -322,6 +323,7 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]
|
|||
&i.ManagerPhoneNumber,
|
||||
&i.Balance,
|
||||
&i.WalletIsActive,
|
||||
&i.CompanyName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -334,7 +336,7 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]
|
|||
}
|
||||
|
||||
const GetBranchByID = `-- name: GetBranchByID :one
|
||||
SELECT id, name, location, profit_percent, 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
|
||||
SELECT id, name, location, profit_percent, 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, company_name
|
||||
FROM branch_details
|
||||
WHERE id = $1
|
||||
`
|
||||
|
|
@ -358,12 +360,13 @@ func (q *Queries) GetBranchByID(ctx context.Context, id int64) (BranchDetail, er
|
|||
&i.ManagerPhoneNumber,
|
||||
&i.Balance,
|
||||
&i.WalletIsActive,
|
||||
&i.CompanyName,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetBranchByManagerID = `-- name: GetBranchByManagerID :many
|
||||
SELECT id, name, location, profit_percent, 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
|
||||
SELECT id, name, location, profit_percent, 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, company_name
|
||||
FROM branch_details
|
||||
WHERE branch_manager_id = $1
|
||||
`
|
||||
|
|
@ -393,6 +396,7 @@ func (q *Queries) GetBranchByManagerID(ctx context.Context, branchManagerID int6
|
|||
&i.ManagerPhoneNumber,
|
||||
&i.Balance,
|
||||
&i.WalletIsActive,
|
||||
&i.CompanyName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -452,7 +456,7 @@ func (q *Queries) GetBranchOperations(ctx context.Context, branchID int64) ([]Ge
|
|||
}
|
||||
|
||||
const SearchBranchByName = `-- name: SearchBranchByName :many
|
||||
SELECT id, name, location, profit_percent, 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
|
||||
SELECT id, name, location, profit_percent, 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, company_name
|
||||
FROM branch_details
|
||||
WHERE name ILIKE '%' || $1 || '%'
|
||||
AND (
|
||||
|
|
@ -491,6 +495,7 @@ func (q *Queries) SearchBranchByName(ctx context.Context, arg SearchBranchByName
|
|||
&i.ManagerPhoneNumber,
|
||||
&i.Balance,
|
||||
&i.WalletIsActive,
|
||||
&i.CompanyName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ func (q *Queries) DeleteCompany(ctx context.Context, id int64) error {
|
|||
}
|
||||
|
||||
const GetAllCompanies = `-- name: GetAllCompanies :many
|
||||
SELECT id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number
|
||||
SELECT id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number, total_bets, total_stake, deducted_stake, total_cash_out, total_cash_backs, number_of_unsettled, total_unsettled_amount, total_admins, total_managers, total_cashiers, total_customers, total_approvers, total_branches, stats_updated_at
|
||||
FROM companies_details
|
||||
WHERE (
|
||||
name ILIKE '%' || $1 || '%'
|
||||
|
|
@ -117,6 +117,20 @@ func (q *Queries) GetAllCompanies(ctx context.Context, arg GetAllCompaniesParams
|
|||
&i.AdminFirstName,
|
||||
&i.AdminLastName,
|
||||
&i.AdminPhoneNumber,
|
||||
&i.TotalBets,
|
||||
&i.TotalStake,
|
||||
&i.DeductedStake,
|
||||
&i.TotalCashOut,
|
||||
&i.TotalCashBacks,
|
||||
&i.NumberOfUnsettled,
|
||||
&i.TotalUnsettledAmount,
|
||||
&i.TotalAdmins,
|
||||
&i.TotalManagers,
|
||||
&i.TotalCashiers,
|
||||
&i.TotalCustomers,
|
||||
&i.TotalApprovers,
|
||||
&i.TotalBranches,
|
||||
&i.StatsUpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -129,7 +143,7 @@ func (q *Queries) GetAllCompanies(ctx context.Context, arg GetAllCompaniesParams
|
|||
}
|
||||
|
||||
const GetCompanyByID = `-- name: GetCompanyByID :one
|
||||
SELECT id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number
|
||||
SELECT id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number, total_bets, total_stake, deducted_stake, total_cash_out, total_cash_backs, number_of_unsettled, total_unsettled_amount, total_admins, total_managers, total_cashiers, total_customers, total_approvers, total_branches, stats_updated_at
|
||||
FROM companies_details
|
||||
WHERE id = $1
|
||||
`
|
||||
|
|
@ -152,6 +166,20 @@ func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (CompaniesDetail
|
|||
&i.AdminFirstName,
|
||||
&i.AdminLastName,
|
||||
&i.AdminPhoneNumber,
|
||||
&i.TotalBets,
|
||||
&i.TotalStake,
|
||||
&i.DeductedStake,
|
||||
&i.TotalCashOut,
|
||||
&i.TotalCashBacks,
|
||||
&i.NumberOfUnsettled,
|
||||
&i.TotalUnsettledAmount,
|
||||
&i.TotalAdmins,
|
||||
&i.TotalManagers,
|
||||
&i.TotalCashiers,
|
||||
&i.TotalCustomers,
|
||||
&i.TotalApprovers,
|
||||
&i.TotalBranches,
|
||||
&i.StatsUpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -180,7 +208,7 @@ func (q *Queries) GetCompanyUsingSlug(ctx context.Context, slug string) (Company
|
|||
}
|
||||
|
||||
const SearchCompanyByName = `-- name: SearchCompanyByName :many
|
||||
SELECT id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number
|
||||
SELECT id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at, balance, wallet_is_active, admin_first_name, admin_last_name, admin_phone_number, total_bets, total_stake, deducted_stake, total_cash_out, total_cash_backs, number_of_unsettled, total_unsettled_amount, total_admins, total_managers, total_cashiers, total_customers, total_approvers, total_branches, stats_updated_at
|
||||
FROM companies_details
|
||||
WHERE name ILIKE '%' || $1 || '%'
|
||||
`
|
||||
|
|
@ -209,6 +237,20 @@ func (q *Queries) SearchCompanyByName(ctx context.Context, dollar_1 pgtype.Text)
|
|||
&i.AdminFirstName,
|
||||
&i.AdminLastName,
|
||||
&i.AdminPhoneNumber,
|
||||
&i.TotalBets,
|
||||
&i.TotalStake,
|
||||
&i.DeductedStake,
|
||||
&i.TotalCashOut,
|
||||
&i.TotalCashBacks,
|
||||
&i.NumberOfUnsettled,
|
||||
&i.TotalUnsettledAmount,
|
||||
&i.TotalAdmins,
|
||||
&i.TotalManagers,
|
||||
&i.TotalCashiers,
|
||||
&i.TotalCustomers,
|
||||
&i.TotalApprovers,
|
||||
&i.TotalBranches,
|
||||
&i.StatsUpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,23 +7,143 @@ package dbgen
|
|||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const UpdateCompanyStats = `-- name: UpdateCompanyStats :exec
|
||||
INSERT INTO company_stats (
|
||||
company_id,
|
||||
total_bets,
|
||||
total_cash_made,
|
||||
total_cash_backs,
|
||||
updated_at
|
||||
const GetCompanyStats = `-- name: GetCompanyStats :many
|
||||
SELECT DATE_TRUNC($1, interval_start)::timestamp AS interval_start,
|
||||
company_stats.company_id, company_stats.interval_start, company_stats.total_bets, company_stats.total_stake, company_stats.deducted_stake, company_stats.total_cash_out, company_stats.total_cash_backs, company_stats.number_of_unsettled, company_stats.total_unsettled_amount, company_stats.total_admins, company_stats.total_managers, company_stats.total_cashiers, company_stats.total_customers, company_stats.total_approvers, company_stats.total_branches, company_stats.updated_at
|
||||
FROM company_stats
|
||||
WHERE (
|
||||
company_stats.company_id = $2
|
||||
OR $2 IS NULL
|
||||
)
|
||||
SELECT b.company_id,
|
||||
GROUP BY interval_start
|
||||
ORDER BY interval_start DESC
|
||||
`
|
||||
|
||||
type GetCompanyStatsParams struct {
|
||||
Interval pgtype.Text `json:"interval"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
}
|
||||
|
||||
type GetCompanyStatsRow struct {
|
||||
IntervalStart pgtype.Timestamp `json:"interval_start"`
|
||||
CompanyID int64 `json:"company_id"`
|
||||
IntervalStart_2 pgtype.Timestamp `json:"interval_start_2"`
|
||||
TotalBets int64 `json:"total_bets"`
|
||||
TotalStake int64 `json:"total_stake"`
|
||||
DeductedStake int64 `json:"deducted_stake"`
|
||||
TotalCashOut int64 `json:"total_cash_out"`
|
||||
TotalCashBacks int64 `json:"total_cash_backs"`
|
||||
NumberOfUnsettled int64 `json:"number_of_unsettled"`
|
||||
TotalUnsettledAmount int64 `json:"total_unsettled_amount"`
|
||||
TotalAdmins int64 `json:"total_admins"`
|
||||
TotalManagers int64 `json:"total_managers"`
|
||||
TotalCashiers int64 `json:"total_cashiers"`
|
||||
TotalCustomers int64 `json:"total_customers"`
|
||||
TotalApprovers int64 `json:"total_approvers"`
|
||||
TotalBranches int64 `json:"total_branches"`
|
||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetCompanyStats(ctx context.Context, arg GetCompanyStatsParams) ([]GetCompanyStatsRow, error) {
|
||||
rows, err := q.db.Query(ctx, GetCompanyStats, arg.Interval, arg.CompanyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetCompanyStatsRow
|
||||
for rows.Next() {
|
||||
var i GetCompanyStatsRow
|
||||
if err := rows.Scan(
|
||||
&i.IntervalStart,
|
||||
&i.CompanyID,
|
||||
&i.IntervalStart_2,
|
||||
&i.TotalBets,
|
||||
&i.TotalStake,
|
||||
&i.DeductedStake,
|
||||
&i.TotalCashOut,
|
||||
&i.TotalCashBacks,
|
||||
&i.NumberOfUnsettled,
|
||||
&i.TotalUnsettledAmount,
|
||||
&i.TotalAdmins,
|
||||
&i.TotalManagers,
|
||||
&i.TotalCashiers,
|
||||
&i.TotalCustomers,
|
||||
&i.TotalApprovers,
|
||||
&i.TotalBranches,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const GetCompanyStatsByID = `-- name: GetCompanyStatsByID :many
|
||||
SELECT company_id, interval_start, total_bets, total_stake, deducted_stake, total_cash_out, total_cash_backs, number_of_unsettled, total_unsettled_amount, total_admins, total_managers, total_cashiers, total_customers, total_approvers, total_branches, updated_at
|
||||
FROM company_stats
|
||||
WHERE company_id = $1
|
||||
ORDER BY interval_start DESC
|
||||
`
|
||||
|
||||
func (q *Queries) GetCompanyStatsByID(ctx context.Context, companyID int64) ([]CompanyStat, error) {
|
||||
rows, err := q.db.Query(ctx, GetCompanyStatsByID, companyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []CompanyStat
|
||||
for rows.Next() {
|
||||
var i CompanyStat
|
||||
if err := rows.Scan(
|
||||
&i.CompanyID,
|
||||
&i.IntervalStart,
|
||||
&i.TotalBets,
|
||||
&i.TotalStake,
|
||||
&i.DeductedStake,
|
||||
&i.TotalCashOut,
|
||||
&i.TotalCashBacks,
|
||||
&i.NumberOfUnsettled,
|
||||
&i.TotalUnsettledAmount,
|
||||
&i.TotalAdmins,
|
||||
&i.TotalManagers,
|
||||
&i.TotalCashiers,
|
||||
&i.TotalCustomers,
|
||||
&i.TotalApprovers,
|
||||
&i.TotalBranches,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const UpdateCompanyStats = `-- name: UpdateCompanyStats :exec
|
||||
WITH -- Aggregate bet data per company
|
||||
bet_stats AS (
|
||||
SELECT company_id,
|
||||
COUNT(*) AS total_bets,
|
||||
COALESCE(SUM(b.amount), 0) AS total_cash_made,
|
||||
COALESCE(SUM(amount), 0) AS total_stake,
|
||||
COALESCE(
|
||||
SUM(amount) * MAX(companies.deducted_percentage),
|
||||
0
|
||||
) AS deducted_stake,
|
||||
COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN sb.cashed_out THEN b.amount -- use actual cashed_out flag from shop_bets
|
||||
WHEN cashed_out THEN amount
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
|
|
@ -32,27 +152,111 @@ SELECT b.company_id,
|
|||
COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN b.status = 5 THEN b.amount
|
||||
WHEN status = 3 THEN amount
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS total_cash_backs,
|
||||
COUNT(*) FILTER (
|
||||
WHERE status = 5
|
||||
) AS number_of_unsettled,
|
||||
COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN status = 5 THEN amount
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS total_unsettled_amount
|
||||
FROM shop_bet_detail
|
||||
LEFT JOIN companies ON companies.id = shop_bet_detail.company_id
|
||||
GROUP BY company_id
|
||||
),
|
||||
user_stats AS (
|
||||
SELECT company_id,
|
||||
COUNT(*) FILTER (
|
||||
WHERE role = 'admin'
|
||||
) AS total_admins,
|
||||
COUNT(*) FILTER (
|
||||
WHERE role = 'branch_manager'
|
||||
) AS total_managers,
|
||||
COUNT(*) FILTER (
|
||||
WHERE role = 'cashier'
|
||||
) AS total_cashiers,
|
||||
COUNT(*) FILTER (
|
||||
WHERE role = 'customer'
|
||||
) AS total_customers,
|
||||
COUNT(*) FILTER (
|
||||
WHERE role = 'transaction_approver'
|
||||
) AS total_approvers
|
||||
FROM users
|
||||
GROUP BY company_id
|
||||
),
|
||||
branch_stats AS (
|
||||
SELECT company_id,
|
||||
COUNT(*) AS total_branches
|
||||
FROM branches
|
||||
GROUP BY company_id
|
||||
) -- Final combined aggregation
|
||||
INSERT INTO company_stats (
|
||||
company_id,
|
||||
interval_start,
|
||||
total_bets,
|
||||
total_stake,
|
||||
deducted_stake,
|
||||
total_cash_out,
|
||||
total_cash_backs,
|
||||
number_of_unsettled,
|
||||
total_unsettled_amount,
|
||||
total_admins,
|
||||
total_managers,
|
||||
total_cashiers,
|
||||
total_customers,
|
||||
total_approvers,
|
||||
total_branches,
|
||||
updated_at
|
||||
)
|
||||
SELECT c.id AS company_id,
|
||||
DATE_TRUNC('day', NOW() AT TIME ZONE 'UTC') AS interval_start,
|
||||
COALESCE(b.total_bets, 0) AS total_bets,
|
||||
COALESCE(b.total_stake, 0) AS total_stake,
|
||||
COALESCE(b.deducted_stake, 0) AS deducted_stake,
|
||||
COALESCE(b.total_cash_out, 0) AS total_cash_out,
|
||||
COALESCE(b.total_cash_backs, 0) AS total_cash_backs,
|
||||
COALESCE(b.number_of_unsettled, 0) AS number_of_unsettled,
|
||||
COALESCE(b.total_unsettled_amount, 0) AS total_unsettled_amount,
|
||||
COALESCE(u.total_admins, 0) AS total_admins,
|
||||
COALESCE(u.total_managers, 0) AS total_managers,
|
||||
COALESCE(u.total_cashiers, 0) AS total_cashiers,
|
||||
COALESCE(u.total_customers, 0) AS total_customers,
|
||||
COALESCE(u.total_approvers, 0) AS total_approvers,
|
||||
COALESCE(br.total_branches, 0) AS total_branches,
|
||||
NOW() AS updated_at
|
||||
FROM shop_bet_detail b
|
||||
JOIN companies c ON b.company_id = c.id
|
||||
JOIN shop_bets sb ON sb.id = b.id -- join to get cashed_out
|
||||
GROUP BY b.company_id,
|
||||
c.name ON CONFLICT (company_id) DO
|
||||
FROM companies c
|
||||
LEFT JOIN bet_stats b ON b.company_id = c.id
|
||||
LEFT JOIN user_stats u ON u.company_id = c.id
|
||||
LEFT JOIN branch_stats br ON br.company_id = c.id ON CONFLICT (company_id, interval_start) DO
|
||||
UPDATE
|
||||
SET total_bets = EXCLUDED.total_bets,
|
||||
total_cash_made = EXCLUDED.total_cash_made,
|
||||
total_stake = EXCLUDED.total_stake,
|
||||
deducted_stake = EXCLUDED.deducted_stake,
|
||||
total_cash_out = EXCLUDED.total_cash_out,
|
||||
total_cash_back = EXCLUDED.total_cash_back,
|
||||
total_cash_backs = EXCLUDED.total_cash_backs,
|
||||
number_of_unsettled = EXCLUDED.number_of_unsettled,
|
||||
total_unsettled_amount = EXCLUDED.total_unsettled_amount,
|
||||
total_admins = EXCLUDED.total_admins,
|
||||
total_managers = EXCLUDED.total_managers,
|
||||
total_cashiers = EXCLUDED.total_cashiers,
|
||||
total_customers = EXCLUDED.total_customers,
|
||||
total_approvers = EXCLUDED.total_approvers,
|
||||
total_branches = EXCLUDED.total_branches,
|
||||
updated_at = EXCLUDED.updated_at
|
||||
`
|
||||
|
||||
// Aggregate company stats
|
||||
// Aggregate user counts per company
|
||||
// Aggregate branch counts per company
|
||||
func (q *Queries) UpdateCompanyStats(ctx context.Context) error {
|
||||
_, err := q.db.Exec(ctx, UpdateCompanyStats)
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -40,12 +40,3 @@ func (q *Queries) UpdateEventBetStats(ctx context.Context) error {
|
|||
_, err := q.db.Exec(ctx, UpdateEventBetStats)
|
||||
return err
|
||||
}
|
||||
|
||||
const UpdateEventDetailedViewMat = `-- name: UpdateEventDetailedViewMat :exec
|
||||
REFRESH MATERIALIZED VIEW event_detailed_mat
|
||||
`
|
||||
|
||||
func (q *Queries) UpdateEventDetailedViewMat(ctx context.Context) error {
|
||||
_, err := q.db.Exec(ctx, UpdateEventDetailedViewMat)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,6 @@ import (
|
|||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type Accumulator struct {
|
||||
OutcomeCount int64 `json:"outcome_count"`
|
||||
DefaultMultiplier float32 `json:"default_multiplier"`
|
||||
}
|
||||
|
||||
type Bank struct {
|
||||
ID int64 `json:"id"`
|
||||
Slug string `json:"slug"`
|
||||
|
|
@ -123,6 +118,7 @@ type BranchDetail struct {
|
|||
ManagerPhoneNumber pgtype.Text `json:"manager_phone_number"`
|
||||
Balance pgtype.Int8 `json:"balance"`
|
||||
WalletIsActive pgtype.Bool `json:"wallet_is_active"`
|
||||
CompanyName string `json:"company_name"`
|
||||
}
|
||||
|
||||
type BranchLocation struct {
|
||||
|
|
@ -153,6 +149,20 @@ type CompaniesDetail struct {
|
|||
AdminFirstName string `json:"admin_first_name"`
|
||||
AdminLastName string `json:"admin_last_name"`
|
||||
AdminPhoneNumber pgtype.Text `json:"admin_phone_number"`
|
||||
TotalBets int64 `json:"total_bets"`
|
||||
TotalStake int64 `json:"total_stake"`
|
||||
DeductedStake int64 `json:"deducted_stake"`
|
||||
TotalCashOut int64 `json:"total_cash_out"`
|
||||
TotalCashBacks int64 `json:"total_cash_backs"`
|
||||
NumberOfUnsettled int64 `json:"number_of_unsettled"`
|
||||
TotalUnsettledAmount int64 `json:"total_unsettled_amount"`
|
||||
TotalAdmins int64 `json:"total_admins"`
|
||||
TotalManagers int64 `json:"total_managers"`
|
||||
TotalCashiers int64 `json:"total_cashiers"`
|
||||
TotalCustomers int64 `json:"total_customers"`
|
||||
TotalApprovers int64 `json:"total_approvers"`
|
||||
TotalBranches int64 `json:"total_branches"`
|
||||
StatsUpdatedAt pgtype.Timestamp `json:"stats_updated_at"`
|
||||
}
|
||||
|
||||
type Company struct {
|
||||
|
|
@ -212,10 +222,20 @@ type CompanySetting struct {
|
|||
|
||||
type CompanyStat struct {
|
||||
CompanyID int64 `json:"company_id"`
|
||||
TotalBets pgtype.Int8 `json:"total_bets"`
|
||||
TotalCashMade pgtype.Int8 `json:"total_cash_made"`
|
||||
TotalCashOut pgtype.Int8 `json:"total_cash_out"`
|
||||
TotalCashBacks pgtype.Int8 `json:"total_cash_backs"`
|
||||
IntervalStart pgtype.Timestamp `json:"interval_start"`
|
||||
TotalBets int64 `json:"total_bets"`
|
||||
TotalStake int64 `json:"total_stake"`
|
||||
DeductedStake int64 `json:"deducted_stake"`
|
||||
TotalCashOut int64 `json:"total_cash_out"`
|
||||
TotalCashBacks int64 `json:"total_cash_backs"`
|
||||
NumberOfUnsettled int64 `json:"number_of_unsettled"`
|
||||
TotalUnsettledAmount int64 `json:"total_unsettled_amount"`
|
||||
TotalAdmins int64 `json:"total_admins"`
|
||||
TotalManagers int64 `json:"total_managers"`
|
||||
TotalCashiers int64 `json:"total_cashiers"`
|
||||
TotalCustomers int64 `json:"total_customers"`
|
||||
TotalApprovers int64 `json:"total_approvers"`
|
||||
TotalBranches int64 `json:"total_branches"`
|
||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
}
|
||||
|
||||
|
|
@ -396,42 +416,6 @@ type EventDetailed struct {
|
|||
TotalPotentialWinnings int64 `json:"total_potential_winnings"`
|
||||
}
|
||||
|
||||
type EventDetailedMat struct {
|
||||
ID int64 `json:"id"`
|
||||
SourceEventID string `json:"source_event_id"`
|
||||
SportID int32 `json:"sport_id"`
|
||||
MatchName string `json:"match_name"`
|
||||
HomeTeam string `json:"home_team"`
|
||||
AwayTeam string `json:"away_team"`
|
||||
HomeTeamID int64 `json:"home_team_id"`
|
||||
AwayTeamID int64 `json:"away_team_id"`
|
||||
HomeKitImage string `json:"home_kit_image"`
|
||||
AwayKitImage string `json:"away_kit_image"`
|
||||
LeagueID int64 `json:"league_id"`
|
||||
LeagueName string `json:"league_name"`
|
||||
StartTime pgtype.Timestamp `json:"start_time"`
|
||||
Score pgtype.Text `json:"score"`
|
||||
MatchMinute pgtype.Int4 `json:"match_minute"`
|
||||
TimerStatus pgtype.Text `json:"timer_status"`
|
||||
AddedTime pgtype.Int4 `json:"added_time"`
|
||||
MatchPeriod pgtype.Int4 `json:"match_period"`
|
||||
IsLive bool `json:"is_live"`
|
||||
Status string `json:"status"`
|
||||
FetchedAt pgtype.Timestamp `json:"fetched_at"`
|
||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
Source string `json:"source"`
|
||||
DefaultIsActive bool `json:"default_is_active"`
|
||||
DefaultIsFeatured bool `json:"default_is_featured"`
|
||||
DefaultWinningUpperLimit int64 `json:"default_winning_upper_limit"`
|
||||
IsMonitored bool `json:"is_monitored"`
|
||||
LeagueCc pgtype.Text `json:"league_cc"`
|
||||
TotalOutcomes int64 `json:"total_outcomes"`
|
||||
NumberOfBets int64 `json:"number_of_bets"`
|
||||
TotalAmount int64 `json:"total_amount"`
|
||||
AvgBetAmount float64 `json:"avg_bet_amount"`
|
||||
TotalPotentialWinnings int64 `json:"total_potential_winnings"`
|
||||
}
|
||||
|
||||
type EventHistory struct {
|
||||
ID int64 `json:"id"`
|
||||
EventID int64 `json:"event_id"`
|
||||
|
|
@ -688,16 +672,36 @@ type RefreshToken struct {
|
|||
Revoked bool `json:"revoked"`
|
||||
}
|
||||
|
||||
type Report struct {
|
||||
type ReportRequest struct {
|
||||
ID int64 `json:"id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
RequestedBy pgtype.Int8 `json:"requested_by"`
|
||||
FilePath pgtype.Text `json:"file_path"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Metadata []byte `json:"metadata"`
|
||||
RejectReason pgtype.Text `json:"reject_reason"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
CompletedAt pgtype.Timestamp `json:"completed_at"`
|
||||
}
|
||||
|
||||
type ReportRequestDetail struct {
|
||||
ID int64 `json:"id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
RequestedBy pgtype.Int8 `json:"requested_by"`
|
||||
FilePath pgtype.Text `json:"file_path"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Metadata []byte `json:"metadata"`
|
||||
RejectReason pgtype.Text `json:"reject_reason"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
CompletedAt pgtype.Timestamp `json:"completed_at"`
|
||||
CompanyName pgtype.Text `json:"company_name"`
|
||||
CompanySlug pgtype.Text `json:"company_slug"`
|
||||
RequesterFirstName pgtype.Text `json:"requester_first_name"`
|
||||
RequesterLastName pgtype.Text `json:"requester_last_name"`
|
||||
}
|
||||
|
||||
type ReportedIssue struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
|
|
|
|||
|
|
@ -11,138 +11,110 @@ import (
|
|||
"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 sb.cashed_out THEN b.amount -- use cashed_out from shop_bets
|
||||
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 shop_bet_detail b
|
||||
JOIN branches br ON b.branch_id = br.id
|
||||
JOIN shop_bets sb ON sb.id = b.id -- join to get cashed_out
|
||||
WHERE b.created_at BETWEEN $1 AND $2
|
||||
GROUP BY b.branch_id, br.name, br.company_id
|
||||
const CreateReportRequest = `-- name: CreateReportRequest :one
|
||||
INSERT INTO report_requests (
|
||||
company_id,
|
||||
requested_by,
|
||||
type,
|
||||
metadata
|
||||
)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, company_id, requested_by, file_path, type, status, metadata, reject_reason, created_at, completed_at
|
||||
`
|
||||
|
||||
type GetBranchWiseReportParams struct {
|
||||
From pgtype.Timestamp `json:"from"`
|
||||
To pgtype.Timestamp `json:"to"`
|
||||
type CreateReportRequestParams struct {
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
RequestedBy pgtype.Int8 `json:"requested_by"`
|
||||
Type string `json:"type"`
|
||||
Metadata []byte `json:"metadata"`
|
||||
}
|
||||
|
||||
type GetBranchWiseReportRow struct {
|
||||
BranchID int64 `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) CreateReportRequest(ctx context.Context, arg CreateReportRequestParams) (ReportRequest, error) {
|
||||
row := q.db.QueryRow(ctx, CreateReportRequest,
|
||||
arg.CompanyID,
|
||||
arg.RequestedBy,
|
||||
arg.Type,
|
||||
arg.Metadata,
|
||||
)
|
||||
var i ReportRequest
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CompanyID,
|
||||
&i.RequestedBy,
|
||||
&i.FilePath,
|
||||
&i.Type,
|
||||
&i.Status,
|
||||
&i.Metadata,
|
||||
&i.RejectReason,
|
||||
&i.CreatedAt,
|
||||
&i.CompletedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
func (q *Queries) GetBranchWiseReport(ctx context.Context, arg GetBranchWiseReportParams) ([]GetBranchWiseReportRow, error) {
|
||||
rows, err := q.db.Query(ctx, GetBranchWiseReport, arg.From, arg.To)
|
||||
const GetAllReportRequests = `-- name: GetAllReportRequests :many
|
||||
SELECT id, company_id, requested_by, file_path, type, status, metadata, reject_reason, created_at, completed_at, company_name, company_slug, requester_first_name, requester_last_name
|
||||
FROM report_request_detail
|
||||
WHERE (
|
||||
company_id = $1
|
||||
OR $1 IS NULL
|
||||
)
|
||||
AND (
|
||||
type = $2
|
||||
OR $2 IS NULL
|
||||
)
|
||||
AND (
|
||||
status = $3
|
||||
OR $3 IS NULL
|
||||
)
|
||||
AND (
|
||||
requested_by = $4
|
||||
OR $4 IS NULL
|
||||
)
|
||||
ORDER BY id DESC
|
||||
LIMIT $6 OFFSET $5
|
||||
`
|
||||
|
||||
type GetAllReportRequestsParams struct {
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
Type pgtype.Text `json:"type"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
RequestedBy pgtype.Int8 `json:"requested_by"`
|
||||
Offset pgtype.Int4 `json:"offset"`
|
||||
Limit pgtype.Int4 `json:"limit"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetAllReportRequests(ctx context.Context, arg GetAllReportRequestsParams) ([]ReportRequestDetail, error) {
|
||||
rows, err := q.db.Query(ctx, GetAllReportRequests,
|
||||
arg.CompanyID,
|
||||
arg.Type,
|
||||
arg.Status,
|
||||
arg.RequestedBy,
|
||||
arg.Offset,
|
||||
arg.Limit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetBranchWiseReportRow
|
||||
var items []ReportRequestDetail
|
||||
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 sb.cashed_out THEN b.amount -- use actual cashed_out flag from shop_bets
|
||||
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 shop_bet_detail b
|
||||
JOIN companies c ON b.company_id = c.id
|
||||
JOIN shop_bets sb ON sb.id = b.id -- join to get cashed_out
|
||||
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 int64 `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
|
||||
var i ReportRequestDetail
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CompanyID,
|
||||
&i.RequestedBy,
|
||||
&i.FilePath,
|
||||
&i.Type,
|
||||
&i.Status,
|
||||
&i.Metadata,
|
||||
&i.RejectReason,
|
||||
&i.CreatedAt,
|
||||
&i.CompletedAt,
|
||||
&i.CompanyName,
|
||||
&i.TotalBets,
|
||||
&i.TotalCashMade,
|
||||
&i.TotalCashOut,
|
||||
&i.TotalCashBacks,
|
||||
&i.CompanySlug,
|
||||
&i.RequesterFirstName,
|
||||
&i.RequesterLastName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -153,3 +125,162 @@ func (q *Queries) GetCompanyWiseReport(ctx context.Context, arg GetCompanyWiseRe
|
|||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const GetReportRequestByID = `-- name: GetReportRequestByID :one
|
||||
SELECT id, company_id, requested_by, file_path, type, status, metadata, reject_reason, created_at, completed_at, company_name, company_slug, requester_first_name, requester_last_name
|
||||
FROM report_request_detail
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetReportRequestByID(ctx context.Context, id int64) (ReportRequestDetail, error) {
|
||||
row := q.db.QueryRow(ctx, GetReportRequestByID, id)
|
||||
var i ReportRequestDetail
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CompanyID,
|
||||
&i.RequestedBy,
|
||||
&i.FilePath,
|
||||
&i.Type,
|
||||
&i.Status,
|
||||
&i.Metadata,
|
||||
&i.RejectReason,
|
||||
&i.CreatedAt,
|
||||
&i.CompletedAt,
|
||||
&i.CompanyName,
|
||||
&i.CompanySlug,
|
||||
&i.RequesterFirstName,
|
||||
&i.RequesterLastName,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetReportRequestByRequestedByID = `-- name: GetReportRequestByRequestedByID :many
|
||||
SELECT id, company_id, requested_by, file_path, type, status, metadata, reject_reason, created_at, completed_at, company_name, company_slug, requester_first_name, requester_last_name
|
||||
FROM report_request_detail
|
||||
WHERE requested_by = $1
|
||||
AND (
|
||||
type = $2
|
||||
OR $2 IS NULL
|
||||
)
|
||||
AND (
|
||||
status = $3
|
||||
OR $3 IS NULL
|
||||
)
|
||||
ORDER BY id DESC
|
||||
LIMIT $5 OFFSET $4
|
||||
`
|
||||
|
||||
type GetReportRequestByRequestedByIDParams struct {
|
||||
RequestedBy pgtype.Int8 `json:"requested_by"`
|
||||
Type pgtype.Text `json:"type"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
Offset pgtype.Int4 `json:"offset"`
|
||||
Limit pgtype.Int4 `json:"limit"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetReportRequestByRequestedByID(ctx context.Context, arg GetReportRequestByRequestedByIDParams) ([]ReportRequestDetail, error) {
|
||||
rows, err := q.db.Query(ctx, GetReportRequestByRequestedByID,
|
||||
arg.RequestedBy,
|
||||
arg.Type,
|
||||
arg.Status,
|
||||
arg.Offset,
|
||||
arg.Limit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ReportRequestDetail
|
||||
for rows.Next() {
|
||||
var i ReportRequestDetail
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CompanyID,
|
||||
&i.RequestedBy,
|
||||
&i.FilePath,
|
||||
&i.Type,
|
||||
&i.Status,
|
||||
&i.Metadata,
|
||||
&i.RejectReason,
|
||||
&i.CreatedAt,
|
||||
&i.CompletedAt,
|
||||
&i.CompanyName,
|
||||
&i.CompanySlug,
|
||||
&i.RequesterFirstName,
|
||||
&i.RequesterLastName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const GetTotalReportRequests = `-- name: GetTotalReportRequests :one
|
||||
SELECT COUNT(id)
|
||||
FROM report_request_detail
|
||||
WHERE (
|
||||
company_id = $1
|
||||
OR $1 IS NULL
|
||||
)
|
||||
AND (
|
||||
type = $2
|
||||
OR $2 IS NULL
|
||||
)
|
||||
AND (
|
||||
status = $3
|
||||
OR $3 IS NULL
|
||||
)
|
||||
AND (
|
||||
requested_by = $4
|
||||
OR $4 IS NULL
|
||||
)
|
||||
`
|
||||
|
||||
type GetTotalReportRequestsParams struct {
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
Type pgtype.Text `json:"type"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
RequestedBy pgtype.Int8 `json:"requested_by"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetTotalReportRequests(ctx context.Context, arg GetTotalReportRequestsParams) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, GetTotalReportRequests,
|
||||
arg.CompanyID,
|
||||
arg.Type,
|
||||
arg.Status,
|
||||
arg.RequestedBy,
|
||||
)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const UpdateReportRequest = `-- name: UpdateReportRequest :exec
|
||||
UPDATE report_requests
|
||||
SET file_path = COALESCE($2, file_path),
|
||||
reject_reason = COALESCE($3, reject_reason),
|
||||
status = COALESCE($4, status),
|
||||
completed_at = now()
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type UpdateReportRequestParams struct {
|
||||
ID int64 `json:"id"`
|
||||
FilePath pgtype.Text `json:"file_path"`
|
||||
RejectReason pgtype.Text `json:"reject_reason"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateReportRequest(ctx context.Context, arg UpdateReportRequestParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdateReportRequest,
|
||||
arg.ID,
|
||||
arg.FilePath,
|
||||
arg.RejectReason,
|
||||
arg.Status,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ var (
|
|||
ErrLogLevel = errors.New("log level not set")
|
||||
ErrInvalidLevel = errors.New("invalid log level")
|
||||
ErrInvalidEnv = errors.New("env not set or invalid")
|
||||
ErrInvalidReportExportPath = errors.New("report export path is 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")
|
||||
|
|
@ -183,6 +184,9 @@ func (c *Config) loadEnv() error {
|
|||
|
||||
c.ReportExportPath = os.Getenv("REPORT_EXPORT_PATH")
|
||||
|
||||
if c.ReportExportPath == "" {
|
||||
return ErrInvalidReportExportPath
|
||||
}
|
||||
c.RedisAddr = os.Getenv("REDIS_ADDR")
|
||||
c.KafkaBrokers = strings.Split(os.Getenv("KAFKA_BROKERS"), ",")
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@ package domain
|
|||
// Branch-level aggregated report
|
||||
type BranchStats struct {
|
||||
BranchID int64
|
||||
BranchName string
|
||||
CompanyID int64
|
||||
TotalBets int64
|
||||
TotalCashIn float64
|
||||
TotalCashOut float64
|
||||
TotalCashBacks float64
|
||||
TotalCashIn Currency
|
||||
TotalCashOut Currency
|
||||
TotalCashBacks Currency
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
|
@ -38,6 +40,20 @@ type GetCompany struct {
|
|||
IsWalletActive bool
|
||||
DeductedPercentage float32
|
||||
IsActive bool
|
||||
TotalBets int64
|
||||
TotalStake Currency
|
||||
DeductedStake Currency
|
||||
TotalCashOut Currency
|
||||
TotalCashBacks Currency
|
||||
NumberOfUnsettled int64
|
||||
TotalUnsettledAmount Currency
|
||||
TotalAdmins int64
|
||||
TotalManagers int64
|
||||
TotalCashiers int64
|
||||
TotalCustomers int64
|
||||
TotalApprovers int64
|
||||
TotalBranches int64
|
||||
StatsUpdatedAt time.Time
|
||||
}
|
||||
|
||||
type CreateCompany struct {
|
||||
|
|
@ -96,6 +112,20 @@ type GetCompanyRes struct {
|
|||
AdminFirstName string `json:"admin_first_name" example:"John"`
|
||||
AdminLastName string `json:"admin_last_name" example:"Doe"`
|
||||
AdminPhoneNumber string `json:"admin_phone_number" example:"1234567890"`
|
||||
TotalBets int64 `json:"total_bets"`
|
||||
TotalStake float32 `json:"total_stake"`
|
||||
DeductedStake float32 `json:"deducted_stake"`
|
||||
TotalCashOut float32 `json:"total_cash_out"`
|
||||
TotalCashBacks float32 `json:"total_cash_backs"`
|
||||
NumberOfUnsettled int64 `json:"number_of_unsettled"`
|
||||
TotalUnsettledAmount float32 `json:"total_unsettled_amount"`
|
||||
TotalAdmins int64 `json:"total_admins"`
|
||||
TotalManagers int64 `json:"total_managers"`
|
||||
TotalCashiers int64 `json:"total_cashiers"`
|
||||
TotalCustomers int64 `json:"total_customers"`
|
||||
TotalApprovers int64 `json:"total_approvers"`
|
||||
TotalBranches int64 `json:"total_branches"`
|
||||
StatsUpdatedAt time.Time `json:"stats_updated_at"`
|
||||
}
|
||||
|
||||
func ConvertCompany(company Company) CompanyRes {
|
||||
|
|
@ -116,6 +146,20 @@ func ConvertGetCompany(company GetCompany) GetCompanyRes {
|
|||
AdminFirstName: company.AdminFirstName,
|
||||
AdminLastName: company.AdminLastName,
|
||||
AdminPhoneNumber: company.AdminPhoneNumber,
|
||||
TotalBets: company.TotalBets,
|
||||
TotalStake: company.TotalStake.Float32(),
|
||||
DeductedStake: company.DeductedPercentage,
|
||||
TotalCashOut: company.TotalCashOut.Float32(),
|
||||
TotalCashBacks: company.TotalCashBacks.Float32(),
|
||||
NumberOfUnsettled: company.NumberOfUnsettled,
|
||||
TotalUnsettledAmount: company.TotalUnsettledAmount.Float32(),
|
||||
TotalAdmins: company.TotalAdmins,
|
||||
TotalManagers: company.TotalManagers,
|
||||
TotalCashiers: company.TotalCashiers,
|
||||
TotalCustomers: company.TotalCustomers,
|
||||
TotalApprovers: company.TotalApprovers,
|
||||
TotalBranches: company.TotalBranches,
|
||||
StatsUpdatedAt: company.StatsUpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -156,6 +200,20 @@ func ConvertDBCompanyDetails(dbCompany dbgen.CompaniesDetail) GetCompany {
|
|||
AdminPhoneNumber: dbCompany.AdminPhoneNumber.String,
|
||||
DeductedPercentage: dbCompany.DeductedPercentage,
|
||||
IsActive: dbCompany.IsActive,
|
||||
TotalBets: dbCompany.TotalBets,
|
||||
TotalStake: Currency(dbCompany.TotalStake),
|
||||
DeductedStake: Currency(dbCompany.DeductedPercentage),
|
||||
TotalCashOut: Currency(dbCompany.TotalCashOut),
|
||||
TotalCashBacks: Currency(dbCompany.TotalCashBacks),
|
||||
NumberOfUnsettled: dbCompany.NumberOfUnsettled,
|
||||
TotalUnsettledAmount: Currency(dbCompany.TotalUnsettledAmount),
|
||||
TotalAdmins: dbCompany.TotalAdmins,
|
||||
TotalManagers: dbCompany.TotalManagers,
|
||||
TotalCashiers: dbCompany.TotalCashiers,
|
||||
TotalCustomers: dbCompany.TotalCustomers,
|
||||
TotalApprovers: dbCompany.TotalApprovers,
|
||||
TotalBranches: dbCompany.TotalBranches,
|
||||
StatsUpdatedAt: dbCompany.StatsUpdatedAt.Time,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
package domain
|
||||
|
||||
// Company-level aggregated report
|
||||
type CompanyStats struct {
|
||||
CompanyID int64
|
||||
CompanyName string
|
||||
TotalBets int64
|
||||
TotalCashIn float64
|
||||
TotalCashOut float64
|
||||
TotalCashBacks float64
|
||||
}
|
||||
105
internal/domain/company_stats.go
Normal file
105
internal/domain/company_stats.go
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CompanyStat struct {
|
||||
CompanyID int64
|
||||
IntervalStart time.Time
|
||||
TotalBets int64
|
||||
TotalStake Currency
|
||||
DeductedStake Currency
|
||||
TotalCashOut Currency
|
||||
TotalCashBacks Currency
|
||||
NumberOfUnsettled int64
|
||||
TotalUnsettledAmount Currency
|
||||
TotalAdmins int64
|
||||
TotalManagers int64
|
||||
TotalCashiers int64
|
||||
TotalCustomers int64
|
||||
TotalApprovers int64
|
||||
TotalBranches int64
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
type CompanyStatRes struct {
|
||||
CompanyID int64 `json:"company_id"`
|
||||
IntervalStart time.Time `json:"interval_start"`
|
||||
TotalBets int64 `json:"total_bets"`
|
||||
TotalStake float32 `json:"total_stake"`
|
||||
DeductedStake float32 `json:"deducted_stake"`
|
||||
TotalCashOut float32 `json:"total_cash_out"`
|
||||
TotalCashBacks float32 `json:"total_cash_backs"`
|
||||
NumberOfUnsettled int64 `json:"number_of_unsettled"`
|
||||
TotalUnsettledAmount float32 `json:"total_unsettled_amount"`
|
||||
TotalAdmins int64 `json:"total_admins"`
|
||||
TotalManagers int64 `json:"total_managers"`
|
||||
TotalCashiers int64 `json:"total_cashiers"`
|
||||
TotalCustomers int64 `json:"total_customers"`
|
||||
TotalApprovers int64 `json:"total_approvers"`
|
||||
TotalBranches int64 `json:"total_branches"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CompanyStatFilter struct {
|
||||
Interval ValidDateInterval
|
||||
CompanyID ValidInt64
|
||||
}
|
||||
|
||||
func ConvertDBCompanyStats(company dbgen.CompanyStat) CompanyStat {
|
||||
return CompanyStat{
|
||||
CompanyID: company.CompanyID,
|
||||
TotalBets: company.TotalBets,
|
||||
TotalStake: Currency(company.TotalStake),
|
||||
DeductedStake: Currency(company.DeductedStake),
|
||||
TotalCashOut: Currency(company.TotalCashOut),
|
||||
TotalCashBacks: Currency(company.TotalCashBacks),
|
||||
NumberOfUnsettled: company.NumberOfUnsettled,
|
||||
TotalUnsettledAmount: Currency(company.TotalUnsettledAmount),
|
||||
TotalAdmins: company.TotalAdmins,
|
||||
TotalManagers: company.TotalManagers,
|
||||
TotalCashiers: company.TotalCashiers,
|
||||
TotalCustomers: company.TotalCustomers,
|
||||
TotalApprovers: company.TotalApprovers,
|
||||
TotalBranches: company.TotalBranches,
|
||||
UpdatedAt: company.UpdatedAt.Time,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertDBCompanyStatsList(stats []dbgen.CompanyStat) []CompanyStat {
|
||||
result := make([]CompanyStat, len(stats))
|
||||
for i, stat := range stats {
|
||||
result[i] = ConvertDBCompanyStats(stat)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ConvertDBCompanyStatsByInterval(company dbgen.GetCompanyStatsRow) CompanyStat {
|
||||
return CompanyStat{
|
||||
CompanyID: company.CompanyID,
|
||||
IntervalStart: company.IntervalStart.Time,
|
||||
TotalBets: company.TotalBets,
|
||||
TotalStake: Currency(company.TotalStake),
|
||||
DeductedStake: Currency(company.DeductedStake),
|
||||
TotalCashOut: Currency(company.TotalCashOut),
|
||||
TotalCashBacks: Currency(company.TotalCashBacks),
|
||||
NumberOfUnsettled: company.NumberOfUnsettled,
|
||||
TotalUnsettledAmount: Currency(company.TotalUnsettledAmount),
|
||||
TotalAdmins: company.TotalAdmins,
|
||||
TotalManagers: company.TotalManagers,
|
||||
TotalCashiers: company.TotalCashiers,
|
||||
TotalCustomers: company.TotalCustomers,
|
||||
TotalApprovers: company.TotalApprovers,
|
||||
TotalBranches: company.TotalBranches,
|
||||
UpdatedAt: company.UpdatedAt.Time,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertDBCompanyStatsByIntervalList(stats []dbgen.GetCompanyStatsRow) []CompanyStat {
|
||||
result := make([]CompanyStat, len(stats))
|
||||
for i, stat := range stats {
|
||||
result[i] = ConvertDBCompanyStatsByInterval(stat)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
@ -28,12 +28,12 @@ type EventStats struct {
|
|||
}
|
||||
|
||||
type EventStatsFilter struct {
|
||||
Interval DateInterval
|
||||
Interval ValidDateInterval
|
||||
LeagueID ValidInt64
|
||||
SportID ValidInt32
|
||||
}
|
||||
type EventStatsByIntervalFilter struct {
|
||||
Interval DateInterval
|
||||
Interval ValidDateInterval
|
||||
LeagueID ValidInt64
|
||||
SportID ValidInt32
|
||||
}
|
||||
|
|
@ -60,7 +60,7 @@ type EventStatsByInterval struct {
|
|||
Removed int64 `json:"removed"`
|
||||
}
|
||||
|
||||
func ConvertDBEventStats(stats dbgen.GetEventStatsRow) EventStats {
|
||||
func ConvertDBEventStats(stats dbgen.GetTotalEventStatsRow) EventStats {
|
||||
return EventStats{
|
||||
EventCount: stats.EventCount,
|
||||
TotalActiveEvents: stats.TotalActiveEvents,
|
||||
|
|
@ -83,7 +83,7 @@ func ConvertDBEventStats(stats dbgen.GetEventStatsRow) EventStats {
|
|||
}
|
||||
}
|
||||
|
||||
func ConvertDBEventStatsByInterval(stats dbgen.GetEventStatsByIntervalRow) EventStatsByInterval {
|
||||
func ConvertDBEventStatsByInterval(stats dbgen.GetTotalEventStatsByIntervalRow) EventStatsByInterval {
|
||||
return EventStatsByInterval{
|
||||
Date: stats.Date.Time,
|
||||
EventCount: stats.EventCount,
|
||||
|
|
@ -107,19 +107,10 @@ func ConvertDBEventStatsByInterval(stats dbgen.GetEventStatsByIntervalRow) Event
|
|||
}
|
||||
}
|
||||
|
||||
func ConvertDBEventStatsByIntervalList(stats []dbgen.GetEventStatsByIntervalRow) []EventStatsByInterval {
|
||||
func ConvertDBEventStatsByIntervalList(stats []dbgen.GetTotalEventStatsByIntervalRow) []EventStatsByInterval {
|
||||
result := make([]EventStatsByInterval, len(stats))
|
||||
for i, e := range stats {
|
||||
result[i] = ConvertDBEventStatsByInterval(e)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type AggregateEventBetStats struct {
|
||||
ID int64 `json:"id"`
|
||||
MatchName string `json:"match_name"`
|
||||
NumberOfBets int64 `json:"number_of_bets"`
|
||||
TotalAmount Currency `json:"total_amount"`
|
||||
AvgBetAmount Currency `json:"avg_bet_amount"`
|
||||
TotalPotentialWinnings Currency `json:"total_potential_winnings"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
package domain
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type DateInterval string
|
||||
|
||||
|
|
@ -29,3 +34,46 @@ func ParseDateInterval(val string) (DateInterval, error) {
|
|||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
type ValidDateInterval struct {
|
||||
Value DateInterval
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (v ValidDateInterval) ToPG() pgtype.Text {
|
||||
return pgtype.Text{
|
||||
String: string(v.Value),
|
||||
Valid: v.Valid,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertStringPtrInterval(value *string) (ValidDateInterval, error) {
|
||||
if value == nil {
|
||||
return ValidDateInterval{}, nil
|
||||
}
|
||||
|
||||
parsedDateInterval, err := ParseDateInterval(*value)
|
||||
if err != nil {
|
||||
return ValidDateInterval{}, err
|
||||
}
|
||||
|
||||
return ValidDateInterval{
|
||||
Value: parsedDateInterval,
|
||||
Valid: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetEndDateFromInterval(interval DateInterval, startDate time.Time) (time.Time, error) {
|
||||
var endDate time.Time
|
||||
switch interval {
|
||||
case "day":
|
||||
endDate = startDate
|
||||
case "week":
|
||||
endDate = startDate.AddDate(0, 0, 6) // 7-day window
|
||||
case "month":
|
||||
endDate = startDate.AddDate(0, 1, -1) // till end of month
|
||||
default:
|
||||
return time.Time{}, fmt.Errorf("unknown date interval")
|
||||
}
|
||||
return endDate, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const (
|
|||
NotificationTypeWithdrawSuccess NotificationType = "withdraw_success"
|
||||
NotificationTypeBetPlaced NotificationType = "bet_placed"
|
||||
NotificationTypeDailyReport NotificationType = "daily_report"
|
||||
NotificationTypeReportRequest NotificationType = "report_request"
|
||||
NotificationTypeHighLossOnBet NotificationType = "high_loss_on_bet"
|
||||
NotificationTypeBetOverload NotificationType = "bet_overload"
|
||||
NotificationTypeSignUpWelcome NotificationType = "signup_welcome"
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ type PaginatedFileResponse struct {
|
|||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type ReportRequest struct {
|
||||
Frequency ReportFrequency
|
||||
StartDate time.Time
|
||||
EndDate time.Time
|
||||
}
|
||||
// type ReportRequest struct {
|
||||
// Frequency ReportFrequency
|
||||
// StartDate time.Time
|
||||
// EndDate time.Time
|
||||
// }
|
||||
|
||||
type ReportData struct {
|
||||
TotalBets int64
|
||||
|
|
@ -39,7 +39,7 @@ type ReportData struct {
|
|||
Deposits float64
|
||||
TotalTickets int64
|
||||
VirtualGameStats []VirtualGameStat
|
||||
CompanyReports []CompanyStats
|
||||
CompanyReports []CompanyStat
|
||||
BranchReports []BranchStats
|
||||
}
|
||||
|
||||
|
|
|
|||
296
internal/domain/report_request.go
Normal file
296
internal/domain/report_request.go
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
)
|
||||
|
||||
type ReportRequest struct {
|
||||
ID int64
|
||||
CompanyID ValidInt64
|
||||
RequestedBy ValidInt64
|
||||
FilePath ValidString
|
||||
Type ReportRequestType
|
||||
Status ReportRequestStatus
|
||||
Metadata ReportMetadataJSON
|
||||
RejectReason ValidString
|
||||
CreatedAt time.Time
|
||||
CompletedAt ValidTime
|
||||
}
|
||||
|
||||
type ReportRequestDetail struct {
|
||||
ID int64
|
||||
CompanyID ValidInt64
|
||||
CompanyName ValidString
|
||||
CompanySlug ValidString
|
||||
RequestedBy ValidInt64
|
||||
RequesterFirstName ValidString
|
||||
RequesterLastName ValidString
|
||||
FilePath ValidString
|
||||
Type ReportRequestType
|
||||
Status ReportRequestStatus
|
||||
Metadata ReportMetadataJSON
|
||||
RejectReason ValidString
|
||||
CreatedAt time.Time
|
||||
CompletedAt ValidTime
|
||||
}
|
||||
|
||||
type CreateReportRequest struct {
|
||||
CompanyID ValidInt64
|
||||
RequestedBy ValidInt64
|
||||
Type ReportRequestType
|
||||
Metadata ReportMetadataJSON
|
||||
}
|
||||
|
||||
type CreateReportRequestReq struct {
|
||||
Type string `json:"type"`
|
||||
Metadata ReportMetadataJSON `json:"metadata"`
|
||||
}
|
||||
|
||||
type ReportRequestRes struct {
|
||||
ID int64 `json:"id"`
|
||||
RequestedBy *int64 `json:"requested_by,omitempty"`
|
||||
CompanyID *int64 `json:"company_id,omitempty"`
|
||||
FilePath *string `json:"file_path,omitempty"`
|
||||
Type ReportRequestType `json:"type"`
|
||||
Status string `json:"status"`
|
||||
RejectReason *string `json:"reject_reason,omitempty"`
|
||||
Metadata ReportMetadataJSON `json:"metadata"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
CompletedAt *string `json:"completed_at,omitempty"`
|
||||
}
|
||||
type ReportRequestDetailRes struct {
|
||||
ID int64 `json:"id"`
|
||||
CompanyID *int64 `json:"company_id,omitempty"`
|
||||
CompanyName *string `json:"company_name,omitempty"`
|
||||
CompanySlug *string `json:"company_slug,omitempty"`
|
||||
RequestedBy *int64 `json:"requested_by,omitempty"`
|
||||
RequesterFirstName *string `json:"requester_first_name,omitempty"`
|
||||
RequesterLastName *string `json:"requester_last_name,omitempty"`
|
||||
FilePath *string `json:"file_path,omitempty"`
|
||||
Type ReportRequestType `json:"type"`
|
||||
Status string `json:"status"`
|
||||
RejectReason *string `json:"reject_reason,omitempty"`
|
||||
Metadata ReportMetadataJSON `json:"metadata"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
CompletedAt *string `json:"completed_at,omitempty"`
|
||||
}
|
||||
|
||||
type ReportRequestFilter struct {
|
||||
CompanyID ValidInt64
|
||||
Type ValidReportRequestType
|
||||
Status ValidReportRequestStatus
|
||||
Limit ValidInt32
|
||||
Offset ValidInt32
|
||||
RequestedBy ValidInt64
|
||||
}
|
||||
|
||||
type UpdateRequestRequest struct {
|
||||
ID int64
|
||||
FilePath ValidString
|
||||
RejectReason ValidString
|
||||
Status ValidReportRequestStatus
|
||||
}
|
||||
|
||||
func ConvertDBReportRequest(report dbgen.ReportRequest) (ReportRequest, error) {
|
||||
parsedMetadataJSON, err := ParseReportMetadataJSON(report.Metadata)
|
||||
if err != nil {
|
||||
return ReportRequest{}, fmt.Errorf("failed to parse report metadata: %w", err)
|
||||
}
|
||||
|
||||
return ReportRequest{
|
||||
ID: report.ID,
|
||||
CompanyID: ValidInt64{
|
||||
Value: report.CompanyID.Int64,
|
||||
Valid: report.CompanyID.Valid,
|
||||
},
|
||||
RequestedBy: ValidInt64{
|
||||
Value: report.RequestedBy.Int64,
|
||||
Valid: report.RequestedBy.Valid,
|
||||
},
|
||||
FilePath: ValidString{
|
||||
Value: report.FilePath.String,
|
||||
Valid: report.FilePath.Valid,
|
||||
},
|
||||
Type: ReportRequestType(report.Type),
|
||||
Status: ReportRequestStatus(report.Status),
|
||||
Metadata: parsedMetadataJSON,
|
||||
RejectReason: ValidString{
|
||||
Value: report.RejectReason.String,
|
||||
Valid: report.RejectReason.Valid,
|
||||
},
|
||||
CreatedAt: report.CreatedAt.Time,
|
||||
CompletedAt: ValidTime{
|
||||
Value: report.CompletedAt.Time,
|
||||
Valid: report.CompletedAt.Valid,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ConvertDBReportRequestList(reports []dbgen.ReportRequest) ([]ReportRequest, error) {
|
||||
result := make([]ReportRequest, len(reports))
|
||||
var err error
|
||||
for i, request := range reports {
|
||||
result[i], err = ConvertDBReportRequest(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func ConvertDBReportRequestDetail(report dbgen.ReportRequestDetail) (ReportRequestDetail, error) {
|
||||
parsedMetadataJSON, err := ParseReportMetadataJSON(report.Metadata)
|
||||
if err != nil {
|
||||
return ReportRequestDetail{}, fmt.Errorf("failed to parse report metadata: %w", err)
|
||||
}
|
||||
|
||||
return ReportRequestDetail{
|
||||
ID: report.ID,
|
||||
CompanyID: ValidInt64{
|
||||
Value: report.CompanyID.Int64,
|
||||
Valid: report.CompanyID.Valid,
|
||||
},
|
||||
CompanyName: ValidString{
|
||||
Value: report.CompanyName.String,
|
||||
Valid: report.CompanyName.Valid,
|
||||
},
|
||||
CompanySlug: ValidString{
|
||||
Value: report.CompanySlug.String,
|
||||
Valid: report.CompanySlug.Valid,
|
||||
},
|
||||
RequestedBy: ValidInt64{
|
||||
Value: report.RequestedBy.Int64,
|
||||
Valid: report.RequestedBy.Valid,
|
||||
},
|
||||
RequesterFirstName: ValidString{
|
||||
Value: report.RequesterFirstName.String,
|
||||
Valid: report.RequesterFirstName.Valid,
|
||||
},
|
||||
RequesterLastName: ValidString{
|
||||
Value: report.RequesterLastName.String,
|
||||
Valid: report.RequesterLastName.Valid,
|
||||
},
|
||||
FilePath: ValidString{
|
||||
Value: report.FilePath.String,
|
||||
Valid: report.FilePath.Valid,
|
||||
},
|
||||
Type: ReportRequestType(report.Type),
|
||||
Status: ReportRequestStatus(report.Status),
|
||||
Metadata: parsedMetadataJSON,
|
||||
RejectReason: ValidString{
|
||||
Value: report.RejectReason.String,
|
||||
Valid: report.RejectReason.Valid,
|
||||
},
|
||||
CreatedAt: report.CreatedAt.Time,
|
||||
CompletedAt: ValidTime{
|
||||
Value: report.CompletedAt.Time,
|
||||
Valid: report.CompletedAt.Valid,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ConvertDBReportRequestDetailList(reports []dbgen.ReportRequestDetail) ([]ReportRequestDetail, error) {
|
||||
result := make([]ReportRequestDetail, len(reports))
|
||||
var err error
|
||||
for i, request := range reports {
|
||||
result[i], err = ConvertDBReportRequestDetail(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func ConvertReportRequest(request ReportRequest) ReportRequestRes {
|
||||
var res ReportRequestRes
|
||||
|
||||
if request.RequestedBy.Valid {
|
||||
res.RequestedBy = &request.RequestedBy.Value
|
||||
}
|
||||
|
||||
if request.CompanyID.Valid {
|
||||
res.CompanyID = &request.CompanyID.Value
|
||||
}
|
||||
|
||||
if request.FilePath.Valid {
|
||||
res.FilePath = &request.FilePath.Value
|
||||
}
|
||||
|
||||
if request.RejectReason.Valid {
|
||||
res.RejectReason = &request.RejectReason.Value
|
||||
}
|
||||
|
||||
if request.CompletedAt.Valid {
|
||||
str := request.CompletedAt.Value.Format(time.RFC3339)
|
||||
res.CompletedAt = &str
|
||||
}
|
||||
|
||||
res.ID = request.ID
|
||||
res.Type = request.Type
|
||||
res.Status = string(request.Status)
|
||||
res.Metadata = request.Metadata
|
||||
res.CreatedAt = request.CreatedAt
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func ConvertReportRequestList(requests []ReportRequest) []ReportRequestRes {
|
||||
result := make([]ReportRequestRes, len(requests))
|
||||
for i, request := range requests {
|
||||
result[i] = ConvertReportRequest(request)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ConvertReportRequestDetail(request ReportRequestDetail) ReportRequestDetailRes {
|
||||
var res ReportRequestDetailRes
|
||||
|
||||
if request.RequestedBy.Valid {
|
||||
res.RequestedBy = &request.RequestedBy.Value
|
||||
}
|
||||
if request.RequesterFirstName.Valid {
|
||||
res.RequesterFirstName = &request.RequesterFirstName.Value
|
||||
}
|
||||
if request.RequesterLastName.Valid {
|
||||
res.RequesterLastName = &request.RequesterLastName.Value
|
||||
}
|
||||
if request.CompanyID.Valid {
|
||||
res.CompanyID = &request.CompanyID.Value
|
||||
}
|
||||
if request.CompanyName.Valid {
|
||||
res.CompanyName = &request.CompanyName.Value
|
||||
}
|
||||
if request.CompanySlug.Valid {
|
||||
res.CompanySlug = &request.CompanySlug.Value
|
||||
}
|
||||
if request.FilePath.Valid {
|
||||
res.FilePath = &request.FilePath.Value
|
||||
}
|
||||
if request.RejectReason.Valid {
|
||||
res.RejectReason = &request.RejectReason.Value
|
||||
}
|
||||
|
||||
if request.CompletedAt.Valid {
|
||||
str := request.CompletedAt.Value.Format(time.RFC3339)
|
||||
res.CompletedAt = &str
|
||||
}
|
||||
|
||||
res.ID = request.ID
|
||||
res.Type = request.Type
|
||||
res.Status = string(request.Status)
|
||||
res.Metadata = request.Metadata
|
||||
res.CreatedAt = request.CreatedAt
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func ConvertReportRequestDetailList(requests []ReportRequestDetail) []ReportRequestDetailRes {
|
||||
result := make([]ReportRequestDetailRes, len(requests))
|
||||
for i, request := range requests {
|
||||
result[i] = ConvertReportRequestDetail(request)
|
||||
}
|
||||
return result
|
||||
}
|
||||
29
internal/domain/report_request_metadata.go
Normal file
29
internal/domain/report_request_metadata.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ReportMetadataJSON struct {
|
||||
BranchID *int64 `json:"branch_id,omitempty"`
|
||||
Interval *string `json:"interval,omitempty"`
|
||||
IntervalStart *string `json:"interval_start,omitempty"`
|
||||
IntervalEnd *string `json:"interval_end,omitempty"`
|
||||
}
|
||||
|
||||
func (r ReportMetadataJSON) ToPG() ([]byte, error) {
|
||||
metadata, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal report request metadata: %w", err)
|
||||
}
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func ParseReportMetadataJSON(jsonData []byte) (ReportMetadataJSON, error) {
|
||||
var metadata ReportMetadataJSON
|
||||
if err := json.Unmarshal(jsonData, &metadata); err != nil {
|
||||
return ReportMetadataJSON{}, err
|
||||
}
|
||||
return metadata, nil
|
||||
}
|
||||
45
internal/domain/report_request_status.go
Normal file
45
internal/domain/report_request_status.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
|
||||
type ReportRequestStatus string
|
||||
|
||||
var (
|
||||
PendingReportRequest ReportRequestStatus = "pending"
|
||||
SuccessReportRequest ReportRequestStatus = "success"
|
||||
RejectReportRequest ReportRequestStatus = "reject"
|
||||
)
|
||||
|
||||
func (r ReportRequestStatus) IsValid() bool {
|
||||
switch r {
|
||||
case PendingReportRequest, SuccessReportRequest, RejectReportRequest:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func ParseReportRequestStatus(val string) (ReportRequestStatus, error) {
|
||||
r := ReportRequestStatus(val)
|
||||
if !r.IsValid() {
|
||||
return "", fmt.Errorf("invalid ReportRequestStatus: %q", val)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type ValidReportRequestStatus struct {
|
||||
Value ReportRequestStatus
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (v ValidReportRequestStatus) ToPG() pgtype.Text {
|
||||
return pgtype.Text{
|
||||
String: string(v.Value),
|
||||
Valid: v.Valid,
|
||||
}
|
||||
}
|
||||
44
internal/domain/report_request_type.go
Normal file
44
internal/domain/report_request_type.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type ReportRequestType string
|
||||
|
||||
var (
|
||||
EventIntervalReportRequest ReportRequestType = "event_interval"
|
||||
EventBetReportRequest ReportRequestType = "event_bet"
|
||||
CompanyReportRequest ReportRequestType = "company"
|
||||
)
|
||||
|
||||
func (r ReportRequestType) IsValid() bool {
|
||||
switch r {
|
||||
case EventIntervalReportRequest, CompanyReportRequest:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func ParseReportRequestType(val string) (ReportRequestType, error) {
|
||||
r := ReportRequestType(val)
|
||||
if !r.IsValid() {
|
||||
return "", fmt.Errorf("invalid ReportRequestType: %q", val)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type ValidReportRequestType struct {
|
||||
Value ReportRequestType
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (v ValidReportRequestType) ToPG() pgtype.Text {
|
||||
return pgtype.Text{
|
||||
String: string(v.Value),
|
||||
Valid: v.Valid,
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ const (
|
|||
OUTCOME_STATUS_LOSS OutcomeStatus = 2
|
||||
OUTCOME_STATUS_VOID OutcomeStatus = 3 //Give Back
|
||||
OUTCOME_STATUS_HALF OutcomeStatus = 4 //Half Win and Half Given Back
|
||||
OUTCOME_STATUS_ERROR OutcomeStatus = 5 //Half Win and Half Given Back
|
||||
OUTCOME_STATUS_ERROR OutcomeStatus = 5 //Error (Unsettled Bet)
|
||||
)
|
||||
|
||||
func (o OutcomeStatus) IsValid() bool {
|
||||
|
|
|
|||
|
|
@ -244,3 +244,18 @@ func ConvertRolePtr(value *Role) ValidRole {
|
|||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertStringPtrToTime(value *string) (ValidTime, error) {
|
||||
if value == nil {
|
||||
return ValidTime{}, nil
|
||||
}
|
||||
|
||||
parsedIntervalStart, err := time.Parse(time.RFC3339, *value)
|
||||
if err != nil {
|
||||
return ValidTime{}, nil
|
||||
}
|
||||
return ValidTime{
|
||||
Value: parsedIntervalStart,
|
||||
Valid: true,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,25 +9,6 @@ import (
|
|||
)
|
||||
|
||||
func (s *Store) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) {
|
||||
// baseSlug := helpers.GenerateSlug(company.Name)
|
||||
// uniqueSlug := baseSlug
|
||||
// i := 1
|
||||
|
||||
// for {
|
||||
// _, err := s.queries.GetCompanyUsingSlug(ctx, uniqueSlug)
|
||||
// if err != nil {
|
||||
// if errors.Is(err, pgx.ErrNoRows) {
|
||||
// // slug is unique
|
||||
// break
|
||||
// } else {
|
||||
// // real DB error
|
||||
// return domain.Company{}, err
|
||||
// }
|
||||
// }
|
||||
// uniqueSlug = fmt.Sprintf("%s-%d", baseSlug, i)
|
||||
// i++
|
||||
// }
|
||||
fmt.Printf("\ncompany %v\n\n", company)
|
||||
dbCompany, err := s.queries.CreateCompany(ctx, domain.ConvertCreateCompany(company))
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,34 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
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)
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
func (s *Store) UpdateCompanyStats(ctx context.Context) error {
|
||||
return s.queries.UpdateCompanyStats(ctx)
|
||||
}
|
||||
|
||||
func (s *Store) GetCompanyStatByID(ctx context.Context, companyID int64) ([]domain.CompanyStat, error) {
|
||||
stats, err := s.queries.GetCompanyStatsByID(ctx, companyID)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return domain.ConvertDBCompanyStatsList(stats), nil
|
||||
}
|
||||
|
||||
func (s *Store) GetCompanyStatsByInterval(ctx context.Context, filter domain.CompanyStatFilter) ([]domain.CompanyStat, error) {
|
||||
stats, err := s.queries.GetCompanyStats(ctx, dbgen.GetCompanyStatsParams{
|
||||
Interval: filter.Interval.ToPG(),
|
||||
CompanyID: filter.CompanyID.ToPG(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return domain.ConvertDBCompanyStatsByIntervalList(stats), nil
|
||||
}
|
||||
|
|
@ -5,11 +5,10 @@ import (
|
|||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
func (s *Store) GetEventStats(ctx context.Context, filter domain.EventStatsFilter) (domain.EventStats, error) {
|
||||
stats, err := s.queries.GetEventStats(ctx, dbgen.GetEventStatsParams{
|
||||
func (s *Store) GetTotalEventStats(ctx context.Context, filter domain.EventStatsFilter) (domain.EventStats, error) {
|
||||
stats, err := s.queries.GetTotalEventStats(ctx, dbgen.GetTotalEventStatsParams{
|
||||
LeagueID: filter.LeagueID.ToPG(),
|
||||
SportID: filter.SportID.ToPG(),
|
||||
})
|
||||
|
|
@ -20,12 +19,9 @@ func (s *Store) GetEventStats(ctx context.Context, filter domain.EventStatsFilte
|
|||
return domain.ConvertDBEventStats(stats), nil
|
||||
}
|
||||
|
||||
func (s *Store) GetEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error) {
|
||||
stats, err := s.queries.GetEventStatsByInterval(ctx, dbgen.GetEventStatsByIntervalParams{
|
||||
Interval: pgtype.Text{
|
||||
String: string(filter.Interval),
|
||||
Valid: true,
|
||||
},
|
||||
func (s *Store) GetTotalEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error) {
|
||||
stats, err := s.queries.GetTotalEventStatsByInterval(ctx, dbgen.GetTotalEventStatsByIntervalParams{
|
||||
Interval: filter.Interval.ToPG(),
|
||||
LeagueID: filter.LeagueID.ToPG(),
|
||||
SportID: filter.SportID.ToPG(),
|
||||
})
|
||||
|
|
@ -36,3 +32,7 @@ func (s *Store) GetEventStatsByInterval(ctx context.Context, filter domain.Event
|
|||
|
||||
return domain.ConvertDBEventStatsByIntervalList(stats), nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateEventBetStats(ctx context.Context) error {
|
||||
return s.queries.UpdateEventBetStats(ctx)
|
||||
}
|
||||
|
|
|
|||
234
internal/repository/old_report.go
Normal file
234
internal/repository/old_report.go
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
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.ReportTimeFrame, start, end time.Time) (*domain.Report, error)
|
||||
// SaveReport(report *domain.Report) error
|
||||
// FindReportsByTimeFrame(timeFrame domain.ReportTimeFrame, 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 {
|
||||
store *Store
|
||||
}
|
||||
|
||||
func NewReportRepo(store *Store) ReportRepository {
|
||||
return &ReportRepo{store: store}
|
||||
}
|
||||
|
||||
// func (r *ReportRepo) GenerateReport(timeFrame domain.ReportTimeFrame, start, end time.Time) (*domain.Report, error) {
|
||||
// // Implement SQL queries to calculate metrics
|
||||
// var report domain.Report
|
||||
|
||||
// // Total Bets
|
||||
// err := r.store.conn.QueryRow(
|
||||
// context.Background(),
|
||||
// `SELECT COUNT(*) FROM bets
|
||||
// WHERE created_at BETWEEN $1 AND $2`, start, end).Scan(&report.TotalBets)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// // Total Cash In
|
||||
// err = r.store.conn.QueryRow(
|
||||
// context.Background(),
|
||||
// `SELECT COALESCE(SUM(amount), 0) FROM transactions
|
||||
// WHERE type = 'stake' AND created_at BETWEEN $1 AND $2`, start, end).Scan(&report.TotalCashIn)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// // Similar queries for Cash Out and Cash Back...
|
||||
|
||||
// report.TimeFrame = timeFrame
|
||||
// report.PeriodStart = start
|
||||
// report.PeriodEnd = end
|
||||
// report.GeneratedAt = time.Now()
|
||||
|
||||
// return &report, nil
|
||||
// }
|
||||
|
||||
// func (r *ReportRepo) SaveReport(report *domain.Report) error {
|
||||
// _, err := r.store.conn.Exec(
|
||||
// context.Background(),
|
||||
// `INSERT INTO reports
|
||||
// (id, time_frame, period_start, period_end, total_bets, total_cash_in, total_cash_out, total_cash_back, generated_at)
|
||||
// VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||
// report.ID, report.TimeFrame, report.PeriodStart, report.PeriodEnd,
|
||||
// report.TotalBets, report.TotalCashIn, report.TotalCashOut, report.TotalCashBack, report.GeneratedAt)
|
||||
// return err
|
||||
// }
|
||||
|
||||
// func (r *ReportRepo) FindReportsByTimeFrame(timeFrame domain.ReportTimeFrame, limit int) ([]*domain.Report, error) {
|
||||
// rows, err := r.store.conn.Query(
|
||||
// context.Background(),
|
||||
// `SELECT id, time_frame, period_start, period_end, total_bets,
|
||||
// total_cash_in, total_cash_out, total_cash_back, generated_at
|
||||
// FROM reports
|
||||
// WHERE time_frame = $1
|
||||
// ORDER BY generated_at DESC
|
||||
// LIMIT $2`,
|
||||
// timeFrame, limit)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// defer rows.Close()
|
||||
|
||||
// var reports []*domain.Report
|
||||
// for rows.Next() {
|
||||
// var report domain.Report
|
||||
// err := rows.Scan(
|
||||
// &report.ID,
|
||||
// &report.TimeFrame,
|
||||
// &report.PeriodStart,
|
||||
// &report.PeriodEnd,
|
||||
// &report.TotalBets,
|
||||
// &report.TotalCashIn,
|
||||
// &report.TotalCashOut,
|
||||
// &report.TotalCashBack,
|
||||
// &report.GeneratedAt,
|
||||
// )
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// reports = append(reports, &report)
|
||||
// }
|
||||
|
||||
// if err := rows.Err(); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// return reports, nil
|
||||
// }
|
||||
|
||||
// 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)
|
||||
// }
|
||||
|
|
@ -1,234 +1,103 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
// "context"
|
||||
// "fmt"
|
||||
// "time"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
// dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
// "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
// "github.com/jackc/pgx/v5/pgtype"
|
||||
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.ReportTimeFrame, start, end time.Time) (*domain.Report, error)
|
||||
// SaveReport(report *domain.Report) error
|
||||
// FindReportsByTimeFrame(timeFrame domain.ReportTimeFrame, limit int) ([]*domain.Report, error)
|
||||
func (s *Store) CreateReportRequest(ctx context.Context, report domain.CreateReportRequest) (domain.ReportRequest, 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)
|
||||
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)
|
||||
}
|
||||
|
||||
type ReportRepo struct {
|
||||
store *Store
|
||||
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 NewReportRepo(store *Store) ReportRepository {
|
||||
return &ReportRepo{store: store}
|
||||
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 (r *ReportRepo) GenerateReport(timeFrame domain.ReportTimeFrame, start, end time.Time) (*domain.Report, error) {
|
||||
// // Implement SQL queries to calculate metrics
|
||||
// var report domain.Report
|
||||
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(),
|
||||
})
|
||||
|
||||
// // Total Bets
|
||||
// err := r.store.conn.QueryRow(
|
||||
// context.Background(),
|
||||
// `SELECT COUNT(*) FROM bets
|
||||
// WHERE created_at BETWEEN $1 AND $2`, start, end).Scan(&report.TotalBets)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// // Total Cash In
|
||||
// err = r.store.conn.QueryRow(
|
||||
// context.Background(),
|
||||
// `SELECT COALESCE(SUM(amount), 0) FROM transactions
|
||||
// WHERE type = 'stake' AND created_at BETWEEN $1 AND $2`, start, end).Scan(&report.TotalCashIn)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// // Similar queries for Cash Out and Cash Back...
|
||||
|
||||
// report.TimeFrame = timeFrame
|
||||
// report.PeriodStart = start
|
||||
// report.PeriodEnd = end
|
||||
// report.GeneratedAt = time.Now()
|
||||
|
||||
// return &report, nil
|
||||
// }
|
||||
|
||||
// func (r *ReportRepo) SaveReport(report *domain.Report) error {
|
||||
// _, err := r.store.conn.Exec(
|
||||
// context.Background(),
|
||||
// `INSERT INTO reports
|
||||
// (id, time_frame, period_start, period_end, total_bets, total_cash_in, total_cash_out, total_cash_back, generated_at)
|
||||
// VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||
// report.ID, report.TimeFrame, report.PeriodStart, report.PeriodEnd,
|
||||
// report.TotalBets, report.TotalCashIn, report.TotalCashOut, report.TotalCashBack, report.GeneratedAt)
|
||||
// return err
|
||||
// }
|
||||
|
||||
// func (r *ReportRepo) FindReportsByTimeFrame(timeFrame domain.ReportTimeFrame, limit int) ([]*domain.Report, error) {
|
||||
// rows, err := r.store.conn.Query(
|
||||
// context.Background(),
|
||||
// `SELECT id, time_frame, period_start, period_end, total_bets,
|
||||
// total_cash_in, total_cash_out, total_cash_back, generated_at
|
||||
// FROM reports
|
||||
// WHERE time_frame = $1
|
||||
// ORDER BY generated_at DESC
|
||||
// LIMIT $2`,
|
||||
// timeFrame, limit)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// defer rows.Close()
|
||||
|
||||
// var reports []*domain.Report
|
||||
// for rows.Next() {
|
||||
// var report domain.Report
|
||||
// err := rows.Scan(
|
||||
// &report.ID,
|
||||
// &report.TimeFrame,
|
||||
// &report.PeriodStart,
|
||||
// &report.PeriodEnd,
|
||||
// &report.TotalBets,
|
||||
// &report.TotalCashIn,
|
||||
// &report.TotalCashOut,
|
||||
// &report.TotalCashBack,
|
||||
// &report.GeneratedAt,
|
||||
// )
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// reports = append(reports, &report)
|
||||
// }
|
||||
|
||||
// if err := rows.Err(); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// return reports, nil
|
||||
// }
|
||||
|
||||
// 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)
|
||||
// }
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update report request: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ var (
|
|||
|
||||
type Service struct {
|
||||
betStore BetStore
|
||||
eventSvc event.Service
|
||||
eventSvc *event.Service
|
||||
prematchSvc odds.ServiceImpl
|
||||
walletSvc wallet.Service
|
||||
branchSvc branch.Service
|
||||
|
|
@ -68,7 +68,7 @@ type Service struct {
|
|||
|
||||
func NewService(
|
||||
betStore BetStore,
|
||||
eventSvc event.Service,
|
||||
eventSvc *event.Service,
|
||||
prematchSvc odds.ServiceImpl,
|
||||
walletSvc wallet.Service,
|
||||
branchSvc branch.Service,
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ func shouldSend(channel domain.DeliveryChannel, sendEmail, sendSMS bool) bool {
|
|||
|
||||
func (s *Service) SendBonusNotification(ctx context.Context, param SendBonusNotificationParam) error {
|
||||
|
||||
var headline string
|
||||
var message string
|
||||
var (
|
||||
headline string
|
||||
message string
|
||||
)
|
||||
|
||||
switch param.Type {
|
||||
case domain.WelcomeBonus:
|
||||
|
|
|
|||
|
|
@ -12,8 +12,11 @@ type CompanyStore interface {
|
|||
SearchCompanyByName(ctx context.Context, name string) ([]domain.GetCompany, error)
|
||||
GetCompanyByID(ctx context.Context, id int64) (domain.GetCompany, error)
|
||||
GetCompanyBySlug(ctx context.Context, slug string) (domain.Company, error)
|
||||
UpdateCompany(ctx context.Context, company domain.UpdateCompany) (error)
|
||||
UpdateCompany(ctx context.Context, company domain.UpdateCompany) error
|
||||
DeleteCompany(ctx context.Context, id int64) error
|
||||
|
||||
GetCompanyCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
||||
UpdateCompanyStats(ctx context.Context) error
|
||||
GetCompanyStatByID(ctx context.Context, companyID int64) ([]domain.CompanyStat, error)
|
||||
GetCompanyStatsByInterval(ctx context.Context, filter domain.CompanyStatFilter) ([]domain.CompanyStat, error)
|
||||
}
|
||||
|
|
|
|||
19
internal/services/company/stats.go
Normal file
19
internal/services/company/stats.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package company
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
func (s *Service) UpdateCompanyStats(ctx context.Context) error {
|
||||
return s.companyStore.UpdateCompanyStats(ctx);
|
||||
}
|
||||
|
||||
func (s *Service) GetCompanyStatByID(ctx context.Context, companyID int64) ([]domain.CompanyStat, error) {
|
||||
return s.companyStore.GetCompanyStatByID(ctx, companyID);
|
||||
}
|
||||
|
||||
func (s *Service) GetCompanyStatsByInterval(ctx context.Context, filter domain.CompanyStatFilter) ([]domain.CompanyStat, error) {
|
||||
return s.companyStore.GetCompanyStatsByInterval(ctx, filter)
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
type EventStore interface {
|
||||
// FetchLiveEvents(ctx context.Context) error
|
||||
FetchUpcomingEvents(ctx context.Context) error
|
||||
GetAllEvents(ctx context.Context, filter domain.EventFilter) ([]domain.BaseEvent, int64, error)
|
||||
|
|
@ -25,6 +25,7 @@ type Service interface {
|
|||
UpdateGlobalEventSettings(ctx context.Context, event domain.UpdateGlobalEventSettings) error
|
||||
|
||||
// Stats
|
||||
GetEventStats(ctx context.Context, filter domain.EventStatsFilter) (domain.EventStats, error)
|
||||
GetEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error)
|
||||
GetTotalEventStats(ctx context.Context, filter domain.EventStatsFilter) (domain.EventStats, error)
|
||||
GetTotalEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error)
|
||||
UpdateEventBetStats(ctx context.Context) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,16 +20,16 @@ import (
|
|||
// "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
type Service struct {
|
||||
token string
|
||||
store *repository.Store
|
||||
settingSvc settings.Service
|
||||
settingSvc *settings.Service
|
||||
mongoLogger *zap.Logger
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func New(token string, store *repository.Store, settingSvc settings.Service, mongoLogger *zap.Logger, cfg *config.Config) Service {
|
||||
return &service{
|
||||
func New(token string, store *repository.Store, settingSvc *settings.Service, mongoLogger *zap.Logger, cfg *config.Config) *Service {
|
||||
return &Service{
|
||||
token: token,
|
||||
store: store,
|
||||
settingSvc: settingSvc,
|
||||
|
|
@ -187,7 +187,7 @@ func New(token string, store *repository.Store, settingSvc settings.Service, mon
|
|||
// return events
|
||||
// }
|
||||
|
||||
func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
||||
func (s *Service) FetchUpcomingEvents(ctx context.Context) error {
|
||||
var wg sync.WaitGroup
|
||||
urls := []struct {
|
||||
name string
|
||||
|
|
@ -211,7 +211,7 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, source_url string, source domain.EventSource) {
|
||||
func (s *Service) fetchUpcomingEventsFromProvider(ctx context.Context, source_url string, source domain.EventSource) {
|
||||
|
||||
settingsList, err := s.settingSvc.GetGlobalSettingList(ctx)
|
||||
|
||||
|
|
@ -391,7 +391,7 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, source_ur
|
|||
)
|
||||
}
|
||||
|
||||
func (s *service) CheckAndInsertEventHistory(ctx context.Context, newEvent domain.CreateEvent) (bool, error) {
|
||||
func (s *Service) CheckAndInsertEventHistory(ctx context.Context, newEvent domain.CreateEvent) (bool, error) {
|
||||
|
||||
eventLogger := s.mongoLogger.With(
|
||||
zap.String("sourceEventID", newEvent.SourceEventID),
|
||||
|
|
@ -461,30 +461,28 @@ func convertInt64(num string) int64 {
|
|||
}
|
||||
return 0
|
||||
}
|
||||
func (s *service) GetAllEvents(ctx context.Context, filter domain.EventFilter) ([]domain.BaseEvent, int64, error) {
|
||||
func (s *Service) GetAllEvents(ctx context.Context, filter domain.EventFilter) ([]domain.BaseEvent, int64, error) {
|
||||
return s.store.GetAllEvents(ctx, filter)
|
||||
}
|
||||
|
||||
func (s *service) GetEventByID(ctx context.Context, ID int64) (domain.BaseEvent, error) {
|
||||
func (s *Service) GetEventByID(ctx context.Context, ID int64) (domain.BaseEvent, error) {
|
||||
return s.store.GetEventByID(ctx, ID)
|
||||
}
|
||||
|
||||
func (s *service) UpdateFinalScore(ctx context.Context, eventID int64, fullScore string, status domain.EventStatus) error {
|
||||
func (s *Service) UpdateFinalScore(ctx context.Context, eventID int64, fullScore string, status domain.EventStatus) error {
|
||||
return s.store.UpdateFinalScore(ctx, eventID, fullScore, status)
|
||||
}
|
||||
func (s *service) UpdateEventStatus(ctx context.Context, eventID int64, status domain.EventStatus) error {
|
||||
func (s *Service) UpdateEventStatus(ctx context.Context, eventID int64, status domain.EventStatus) error {
|
||||
return s.store.UpdateEventStatus(ctx, eventID, status)
|
||||
}
|
||||
|
||||
func (s *service) IsEventMonitored(ctx context.Context, eventID int64) (bool, error) {
|
||||
func (s *Service) IsEventMonitored(ctx context.Context, eventID int64) (bool, error) {
|
||||
return s.store.IsEventMonitored(ctx, eventID)
|
||||
}
|
||||
func (s *service) UpdateEventMonitored(ctx context.Context, eventID int64, IsMonitored bool) error {
|
||||
func (s *Service) UpdateEventMonitored(ctx context.Context, eventID int64, IsMonitored bool) error {
|
||||
return s.store.UpdateEventMonitored(ctx, eventID, IsMonitored)
|
||||
}
|
||||
|
||||
func (s *service) GetSportAndLeagueIDs(ctx context.Context, eventID int64) ([]int64, error) {
|
||||
func (s *Service) GetSportAndLeagueIDs(ctx context.Context, eventID int64) ([]int64, error) {
|
||||
return s.store.GetSportAndLeagueIDs(ctx, eventID)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,17 +6,17 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
func (s *service) GetEventsWithSettings(ctx context.Context, companyID int64, filter domain.EventFilter) ([]domain.EventWithSettings, int64, error) {
|
||||
func (s *Service) GetEventsWithSettings(ctx context.Context, companyID int64, filter domain.EventFilter) ([]domain.EventWithSettings, int64, error) {
|
||||
return s.store.GetEventsWithSettings(ctx, companyID, filter)
|
||||
}
|
||||
|
||||
func (s *service) GetEventWithSettingByID(ctx context.Context, ID int64, companyID int64) (domain.EventWithSettings, error) {
|
||||
func (s *Service) GetEventWithSettingByID(ctx context.Context, ID int64, companyID int64) (domain.EventWithSettings, error) {
|
||||
return s.store.GetEventWithSettingByID(ctx, ID, companyID)
|
||||
}
|
||||
|
||||
func (s *service) UpdateTenantEventSettings(ctx context.Context, event domain.UpdateTenantEventSettings) error {
|
||||
func (s *Service) UpdateTenantEventSettings(ctx context.Context, event domain.UpdateTenantEventSettings) error {
|
||||
return s.store.UpdateTenantEventSettings(ctx, event)
|
||||
}
|
||||
func (s *service) UpdateGlobalEventSettings(ctx context.Context, event domain.UpdateGlobalEventSettings) error {
|
||||
func (s *Service) UpdateGlobalEventSettings(ctx context.Context, event domain.UpdateGlobalEventSettings) error {
|
||||
return s.store.UpdateGlobalEventSettings(ctx, event)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,14 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
func (s *service) GetEventStats(ctx context.Context, filter domain.EventStatsFilter) (domain.EventStats, error) {
|
||||
return s.store.GetEventStats(ctx, filter)
|
||||
func (s *Service) GetTotalEventStats(ctx context.Context, filter domain.EventStatsFilter) (domain.EventStats, error) {
|
||||
return s.store.GetTotalEventStats(ctx, filter)
|
||||
}
|
||||
func (s *service) GetEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error) {
|
||||
return s.store.GetEventStatsByInterval(ctx, filter)
|
||||
func (s *Service) GetTotalEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error) {
|
||||
return s.store.GetTotalEventStatsByInterval(ctx, filter)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateEventBetStats(ctx context.Context) error {
|
||||
return s.store.UpdateEventBetStats(ctx)
|
||||
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
type LeagueStore interface {
|
||||
SaveLeague(ctx context.Context, league domain.CreateLeague) error
|
||||
SaveLeagueSettings(ctx context.Context, leagueSettings domain.CreateLeagueSettings) error
|
||||
GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.BaseLeague, int64, error)
|
||||
|
|
|
|||
|
|
@ -7,40 +7,40 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
type Service struct {
|
||||
store *repository.Store
|
||||
}
|
||||
|
||||
func New(store *repository.Store) Service {
|
||||
return &service{
|
||||
func New(store *repository.Store) *Service {
|
||||
return &Service{
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) SaveLeague(ctx context.Context, league domain.CreateLeague) error {
|
||||
func (s *Service) SaveLeague(ctx context.Context, league domain.CreateLeague) error {
|
||||
return s.store.SaveLeague(ctx, league)
|
||||
}
|
||||
|
||||
func (s *service) SaveLeagueSettings(ctx context.Context, leagueSettings domain.CreateLeagueSettings) error {
|
||||
func (s *Service) SaveLeagueSettings(ctx context.Context, leagueSettings domain.CreateLeagueSettings) error {
|
||||
return s.store.SaveLeagueSettings(ctx, leagueSettings)
|
||||
}
|
||||
|
||||
func (s *service) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.BaseLeague, int64, error) {
|
||||
func (s *Service) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.BaseLeague, int64, error) {
|
||||
return s.store.GetAllLeagues(ctx, filter)
|
||||
}
|
||||
|
||||
func (s *service) GetAllLeaguesByCompany(ctx context.Context, companyID int64, filter domain.LeagueFilter) ([]domain.LeagueWithSettings, int64, error) {
|
||||
func (s *Service) GetAllLeaguesByCompany(ctx context.Context, companyID int64, filter domain.LeagueFilter) ([]domain.LeagueWithSettings, int64, error) {
|
||||
return s.store.GetAllLeaguesByCompany(ctx, companyID, filter)
|
||||
}
|
||||
|
||||
func (s *service) CheckLeagueSupport(ctx context.Context, leagueID int64, companyID int64) (bool, error) {
|
||||
func (s *Service) CheckLeagueSupport(ctx context.Context, leagueID int64, companyID int64) (bool, error) {
|
||||
return s.store.CheckLeagueSupport(ctx, leagueID, companyID)
|
||||
}
|
||||
|
||||
func (s *service) UpdateLeague(ctx context.Context, league domain.UpdateLeague) error {
|
||||
func (s *Service) UpdateLeague(ctx context.Context, league domain.UpdateLeague) error {
|
||||
return s.store.UpdateLeague(ctx, league)
|
||||
}
|
||||
|
||||
func (s *service) UpdateGlobalLeagueSettings(ctx context.Context, league domain.UpdateGlobalLeagueSettings) error {
|
||||
func (s *Service) UpdateGlobalLeagueSettings(ctx context.Context, league domain.UpdateGlobalLeagueSettings) error {
|
||||
return s.store.UpdateGlobalLeagueSettings(ctx, league)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ import (
|
|||
type ServiceImpl struct {
|
||||
store *repository.Store
|
||||
config *config.Config
|
||||
eventSvc event.Service
|
||||
eventSvc *event.Service
|
||||
logger *slog.Logger
|
||||
mongoLogger *zap.Logger
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func New(store *repository.Store, cfg *config.Config, eventSvc event.Service, logger *slog.Logger, mongoLogger *zap.Logger) *ServiceImpl {
|
||||
func New(store *repository.Store, cfg *config.Config, eventSvc *event.Service, logger *slog.Logger, mongoLogger *zap.Logger) *ServiceImpl {
|
||||
return &ServiceImpl{
|
||||
store: store,
|
||||
config: cfg,
|
||||
|
|
|
|||
104
internal/services/report/csv.go
Normal file
104
internal/services/report/csv.go
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
package report
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrReportFileNotFound = errors.New("failed to find report file")
|
||||
ErrReportFileError = errors.New("unknown error with report file")
|
||||
ErrReportNotComplete = errors.New("report is not completed")
|
||||
ErrReportFilePathInvalid = errors.New("report file path is invalid")
|
||||
)
|
||||
|
||||
func (s *Service) WriteCSV(rows [][]string, filePrefix string) (string, error) {
|
||||
if len(rows) == 0 {
|
||||
s.mongoLogger.Error("[WriteCSV] CSV with no data",
|
||||
zap.String("file_prefix", filePrefix),
|
||||
)
|
||||
return "", errors.New("no data to write")
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%s_%s_%s.csv",
|
||||
filePrefix,
|
||||
time.Now().Format("2006-01-02_15-04-05"),
|
||||
uuid.NewString()[:8],
|
||||
)
|
||||
|
||||
filePath := filepath.Join(s.cfg.ReportExportPath, filename)
|
||||
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("[WriteCSV] Failed to create file",
|
||||
zap.String("file", filename),
|
||||
zap.String("path", s.cfg.ReportExportPath),
|
||||
zap.Error(err),
|
||||
)
|
||||
return "", fmt.Errorf("create csv: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
writer := csv.NewWriter(file)
|
||||
if err := writer.WriteAll(rows); err != nil {
|
||||
s.mongoLogger.Error("[WriteCSV] Error while writing csv",
|
||||
zap.String("file", filename),
|
||||
zap.String("path", s.cfg.ReportExportPath),
|
||||
zap.Error(err),
|
||||
)
|
||||
return "", fmt.Errorf("write csv: %w", err)
|
||||
}
|
||||
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
func (s *Service) CheckAndFetchReportFile(ctx context.Context, ID int64) (string, error) {
|
||||
report, err := s.GetReportRequestByID(ctx, ID)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("[CheckAndFetchReportFile] Failed to get report request by id",
|
||||
zap.Error(err),
|
||||
)
|
||||
return "", fmt.Errorf("failed to get report request:%w", err)
|
||||
}
|
||||
|
||||
if report.Status != domain.SuccessReportRequest {
|
||||
s.mongoLogger.Error("[CheckAndFetchReportFile] Attempted download of report that isn't completed",
|
||||
zap.String("status", string(report.Status)),
|
||||
)
|
||||
return "", ErrReportNotComplete
|
||||
}
|
||||
|
||||
if !report.FilePath.Valid {
|
||||
s.mongoLogger.Error("[CheckAndFetchReportFile] File Path is invalid even though the report is a success",
|
||||
zap.String("file path", report.FilePath.Value),
|
||||
)
|
||||
return "", ErrReportFilePathInvalid
|
||||
}
|
||||
|
||||
// Check if the report file exists
|
||||
if _, err := os.Stat(report.FilePath.Value); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
s.mongoLogger.Error("[CheckAndFetchReportFile] Unable to find report file",
|
||||
zap.String("file path", report.FilePath.Value),
|
||||
)
|
||||
return "", ErrReportFileNotFound
|
||||
}
|
||||
|
||||
s.mongoLogger.Error("[CheckAndFetchReportFile] Unable to check report file",
|
||||
zap.String("file path", report.FilePath.Value),
|
||||
zap.Error(err),
|
||||
)
|
||||
return "", ErrReportFileError
|
||||
}
|
||||
|
||||
return report.FilePath.Value, nil
|
||||
}
|
||||
91
internal/services/report/event.go
Normal file
91
internal/services/report/event.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
package report
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidInterval = errors.New("invalid interval provided")
|
||||
)
|
||||
|
||||
func (s *Service) GenerateEventIntervalReport(ctx context.Context, request domain.ReportRequestDetail) (string, error) {
|
||||
if request.Metadata.Interval == nil {
|
||||
s.mongoLogger.Error("[GenerateEventIntervalReport] Metadata interval is empty")
|
||||
return "", ErrInvalidInterval
|
||||
}
|
||||
|
||||
interval, err := domain.ParseDateInterval(*request.Metadata.Interval)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("[GenerateEventIntervalReport] Failed to parse date interval",
|
||||
zap.String("interval", *request.Metadata.Interval),
|
||||
zap.Error(err),
|
||||
)
|
||||
return "", ErrInvalidInterval
|
||||
}
|
||||
|
||||
stats, err := s.eventSvc.GetTotalEventStatsByInterval(ctx, domain.EventStatsByIntervalFilter{
|
||||
Interval: domain.ValidDateInterval{
|
||||
Value: interval,
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("[GenerateEventIntervalReport] Failed to fetch event stats",
|
||||
zap.String("interval", string(interval)),
|
||||
zap.Error(err),
|
||||
)
|
||||
return "", fmt.Errorf("fetching event stats: %w", err)
|
||||
}
|
||||
|
||||
rows := [][]string{{
|
||||
"Period", "Total Events", "Active Events", "In-Active Events", "Featured Events", "Leagues",
|
||||
"Pending", "In-Play", "To-Be-Fixed", "Ended", "Postponed", "Cancelled", "Walkover",
|
||||
"Interrupted", "Abandoned", "Retired", "Suspended", "Decided-By-FA", "Removed",
|
||||
}}
|
||||
|
||||
for _, stat := range stats {
|
||||
endDate, err := domain.GetEndDateFromInterval(interval, stat.Date)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("[GenerateEventIntervalReport] Failed to get end date from interval",
|
||||
zap.String("interval", string(interval)),
|
||||
zap.Error(err),
|
||||
)
|
||||
return "", fmt.Errorf("invalid interval end date: %w", err)
|
||||
}
|
||||
|
||||
period := fmt.Sprintf("%s to %s",
|
||||
stat.Date.Format("2006-01-02"),
|
||||
endDate.Format("2006-01-02"),
|
||||
)
|
||||
|
||||
rows = append(rows, []string{
|
||||
period,
|
||||
fmt.Sprint(stat.EventCount),
|
||||
fmt.Sprint(stat.TotalActiveEvents),
|
||||
fmt.Sprint(stat.TotalInActiveEvents),
|
||||
fmt.Sprint(stat.TotalFeaturedEvents),
|
||||
fmt.Sprint(stat.TotalLeagues),
|
||||
fmt.Sprint(stat.Pending),
|
||||
fmt.Sprint(stat.InPlay),
|
||||
fmt.Sprint(stat.ToBeFixed),
|
||||
fmt.Sprint(stat.Ended),
|
||||
fmt.Sprint(stat.Postponed),
|
||||
fmt.Sprint(stat.Cancelled),
|
||||
fmt.Sprint(stat.Walkover),
|
||||
fmt.Sprint(stat.Interrupted),
|
||||
fmt.Sprint(stat.Abandoned),
|
||||
fmt.Sprint(stat.Retired),
|
||||
fmt.Sprint(stat.Suspended),
|
||||
fmt.Sprint(stat.DecidedByFa),
|
||||
fmt.Sprint(stat.Removed),
|
||||
})
|
||||
}
|
||||
|
||||
return s.WriteCSV(rows, "event_interval")
|
||||
}
|
||||
72
internal/services/report/notification.go
Normal file
72
internal/services/report/notification.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package report
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidRequestedByID = errors.New("requested_by needs to be filled in to send report notification")
|
||||
)
|
||||
|
||||
func (s *Service) SendReportRequestNotification(ctx context.Context, param domain.ReportRequestDetail) error {
|
||||
if !param.RequestedBy.Valid {
|
||||
return ErrInvalidRequestedByID
|
||||
}
|
||||
|
||||
var (
|
||||
headline string
|
||||
message string
|
||||
level domain.NotificationLevel
|
||||
)
|
||||
|
||||
switch param.Status {
|
||||
case domain.SuccessReportRequest:
|
||||
headline = "Report Ready for Download"
|
||||
message = fmt.Sprintf(
|
||||
"Your %s report has been successfully generated and is now available for download.",
|
||||
strings.ToLower(string(param.Type)),
|
||||
)
|
||||
level = domain.NotificationLevelSuccess
|
||||
|
||||
case domain.RejectReportRequest:
|
||||
headline = "Report Generation Failed"
|
||||
message = fmt.Sprintf(
|
||||
"We were unable to generate your %s report. Please review your request and try again.",
|
||||
strings.ToLower(string(param.Type)),
|
||||
)
|
||||
level = domain.NotificationLevelError
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported request status: %v", param.Status)
|
||||
}
|
||||
|
||||
raw, _ := json.Marshal(map[string]any{
|
||||
"report_id": param.ID,
|
||||
"type": param.Type,
|
||||
"status": param.Status,
|
||||
})
|
||||
|
||||
n := &domain.Notification{
|
||||
RecipientID: param.RequestedBy.Value,
|
||||
DeliveryStatus: domain.DeliveryStatusPending,
|
||||
IsRead: false,
|
||||
Type: domain.NotificationTypeReportRequest,
|
||||
Level: level,
|
||||
Reciever: domain.NotificationRecieverSideCustomer,
|
||||
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: headline,
|
||||
Message: message,
|
||||
},
|
||||
Priority: 2,
|
||||
Metadata: raw,
|
||||
}
|
||||
|
||||
return s.notificationSvc.SendNotification(ctx, n)
|
||||
}
|
||||
|
|
@ -15,4 +15,10 @@ type ReportStore interface {
|
|||
// GetNotificationReport(ctx context.Context, filter domain.ReportFilter) (domain.NotificationReport, error)
|
||||
// GetCashierPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CashierPerformance, error)
|
||||
// GetCompanyPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CompanyPerformance, error)
|
||||
|
||||
CreateReportRequest(ctx context.Context, report domain.CreateReportRequest) (domain.ReportRequest, error)
|
||||
GetAllReportRequests(ctx context.Context, filter domain.ReportRequestFilter) ([]domain.ReportRequestDetail, int64, error)
|
||||
GetReportRequestByRequestedByID(ctx context.Context, requestedBy int64, filter domain.ReportRequestFilter) ([]domain.ReportRequestDetail, error)
|
||||
GetReportRequestByID(ctx context.Context, ID int64) (domain.ReportRequestDetail, error)
|
||||
UpdateReportRequest(ctx context.Context, report domain.UpdateRequestRequest) error
|
||||
}
|
||||
|
|
|
|||
107
internal/services/report/process.go
Normal file
107
internal/services/report/process.go
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
package report
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (s *Service) ProcessReportRequests(ctx context.Context) error {
|
||||
requests, total, err := s.GetAllReportRequests(ctx, domain.ReportRequestFilter{
|
||||
Status: domain.ValidReportRequestStatus{
|
||||
Value: domain.PendingReportRequest,
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to get pending report requests", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
for i, req := range requests {
|
||||
if err := s.processSingleReportRequest(ctx, req); err != nil {
|
||||
s.mongoLogger.Error("failed to process report request",
|
||||
zap.Int64("id", req.ID),
|
||||
zap.Int("index", i),
|
||||
zap.Int64("total", total),
|
||||
zap.String("type", string(req.Type)),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) processSingleReportRequest(ctx context.Context, req domain.ReportRequestDetail) error {
|
||||
var (
|
||||
filePath string
|
||||
rejectReason string
|
||||
status = domain.SuccessReportRequest
|
||||
)
|
||||
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
s.mongoLogger.Info("report request processed",
|
||||
zap.Int64("id", req.ID),
|
||||
zap.String("type", string(req.Type)),
|
||||
zap.String("status", string(status)),
|
||||
zap.Duration("duration", time.Since(start)),
|
||||
)
|
||||
}()
|
||||
|
||||
switch req.Type {
|
||||
case domain.EventIntervalReportRequest:
|
||||
if req.Metadata.Interval == nil {
|
||||
status = domain.RejectReportRequest
|
||||
rejectReason = "invalid interval provided"
|
||||
break
|
||||
}
|
||||
|
||||
fp, genErr := s.GenerateEventIntervalReport(ctx, req)
|
||||
if genErr != nil {
|
||||
status = domain.RejectReportRequest
|
||||
rejectReason = fmt.Sprintf("failed to generate report: %v", genErr)
|
||||
} else {
|
||||
filePath = fp
|
||||
}
|
||||
|
||||
default:
|
||||
status = domain.RejectReportRequest
|
||||
rejectReason = fmt.Sprintf("unsupported report type: %s", req.Type)
|
||||
}
|
||||
|
||||
update := domain.UpdateRequestRequest{
|
||||
ID: req.ID,
|
||||
Status: domain.ValidReportRequestStatus{
|
||||
Value: status,
|
||||
Valid: true,
|
||||
},
|
||||
FilePath: domain.ValidString{
|
||||
Value: filePath,
|
||||
Valid: filePath != "",
|
||||
},
|
||||
RejectReason: domain.ValidString{
|
||||
Value: rejectReason,
|
||||
Valid: rejectReason != "",
|
||||
},
|
||||
}
|
||||
|
||||
if err := s.UpdateReportRequest(ctx, update); err != nil {
|
||||
return fmt.Errorf("failed to update report request: %w", err)
|
||||
}
|
||||
|
||||
// Prepare updated object for notification
|
||||
updatedReq := req
|
||||
updatedReq.FilePath = update.FilePath
|
||||
updatedReq.Status = update.Status.Value
|
||||
updatedReq.RejectReason = update.RejectReason
|
||||
|
||||
if err := s.SendReportRequestNotification(ctx, updatedReq); err != nil {
|
||||
s.mongoLogger.Warn("failed to send notification", zap.Int64("id", req.ID), zap.Error(err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
24
internal/services/report/request.go
Normal file
24
internal/services/report/request.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package report
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
|
||||
func (s *Service) CreateReportRequest(ctx context.Context, report domain.CreateReportRequest) (domain.ReportRequest, error) {
|
||||
return s.store.CreateReportRequest(ctx, report)
|
||||
}
|
||||
func (s *Service) GetAllReportRequests(ctx context.Context, filter domain.ReportRequestFilter) ([]domain.ReportRequestDetail, int64, error) {
|
||||
return s.store.GetAllReportRequests(ctx, filter)
|
||||
}
|
||||
func (s *Service) GetReportRequestByRequestedByID(ctx context.Context, requestedBy int64, filter domain.ReportRequestFilter) ([]domain.ReportRequestDetail, error) {
|
||||
return s.store.GetReportRequestByRequestedByID(ctx, requestedBy, filter)
|
||||
}
|
||||
func (s *Service) GetReportRequestByID(ctx context.Context, ID int64) (domain.ReportRequestDetail, error) {
|
||||
return s.store.GetReportRequestByID(ctx, ID)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateReportRequest(ctx context.Context, report domain.UpdateRequestRequest) error {
|
||||
return s.store.UpdateReportRequest(ctx, report)
|
||||
}
|
||||
|
|
@ -2,23 +2,26 @@ package report
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/csv"
|
||||
// "encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
// "fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
// "os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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/bet"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
"go.uber.org/zap"
|
||||
|
||||
// notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||
|
||||
|
|
@ -32,6 +35,7 @@ var (
|
|||
)
|
||||
|
||||
type Service struct {
|
||||
store *repository.Store
|
||||
betStore bet.BetStore
|
||||
walletStore wallet.WalletStore
|
||||
transactionStore transaction.TransactionStore
|
||||
|
|
@ -41,10 +45,16 @@ type Service struct {
|
|||
companyStore company.CompanyStore
|
||||
virtulaGamesStore repository.VirtualGameRepository
|
||||
notificationStore repository.NotificationRepository
|
||||
notificationSvc *notificationservice.Service
|
||||
eventSvc *event.Service
|
||||
companySvc *company.Service
|
||||
logger *slog.Logger
|
||||
mongoLogger *zap.Logger
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func NewService(
|
||||
store *repository.Store,
|
||||
betStore bet.BetStore,
|
||||
walletStore wallet.WalletStore,
|
||||
transactionStore transaction.TransactionStore,
|
||||
|
|
@ -54,9 +64,15 @@ func NewService(
|
|||
companyStore company.CompanyStore,
|
||||
virtulaGamesStore repository.VirtualGameRepository,
|
||||
notificationStore repository.NotificationRepository,
|
||||
notificationSvc *notificationservice.Service,
|
||||
eventSvc *event.Service,
|
||||
companySvc *company.Service,
|
||||
logger *slog.Logger,
|
||||
mongoLogger *zap.Logger,
|
||||
cfg *config.Config,
|
||||
) *Service {
|
||||
return &Service{
|
||||
store: store,
|
||||
betStore: betStore,
|
||||
walletStore: walletStore,
|
||||
transactionStore: transactionStore,
|
||||
|
|
@ -66,7 +82,12 @@ func NewService(
|
|||
companyStore: companyStore,
|
||||
virtulaGamesStore: virtulaGamesStore,
|
||||
notificationStore: notificationStore,
|
||||
notificationSvc: notificationSvc,
|
||||
eventSvc: eventSvc,
|
||||
companySvc: companySvc,
|
||||
logger: logger,
|
||||
mongoLogger: mongoLogger,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -459,199 +480,199 @@ func (s *Service) GetSportPerformance(ctx context.Context, filter domain.ReportF
|
|||
return performances, nil
|
||||
}
|
||||
|
||||
func (s *Service) GenerateReport(ctx context.Context, from, to time.Time) error {
|
||||
// Hardcoded output directory
|
||||
outputDir := "reports"
|
||||
// func (s *Service) GenerateReport(ctx context.Context, from, to time.Time) error {
|
||||
// // Hardcoded output directory
|
||||
// outputDir := "reports"
|
||||
|
||||
// Ensure directory exists
|
||||
if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to create report directory: %w", err)
|
||||
}
|
||||
// // Ensure directory exists
|
||||
// if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
|
||||
// return fmt.Errorf("failed to create report directory: %w", err)
|
||||
// }
|
||||
|
||||
companies, branchMap, err := s.fetchReportData(ctx, from, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// companies, branchMap, err := s.fetchReportData(ctx, from, to)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// per-company reports
|
||||
for _, company := range companies {
|
||||
branches := branchMap[company.CompanyID]
|
||||
if err := writeCompanyCSV(company, branches, from, to, outputDir); err != nil {
|
||||
return fmt.Errorf("company %d CSV: %w", company.CompanyID, err)
|
||||
}
|
||||
}
|
||||
// // per-company reports
|
||||
// for _, company := range companies {
|
||||
// branches := branchMap[company.CompanyID]
|
||||
// if err := writeCompanyCSV(company, branches, from, to, outputDir); err != nil {
|
||||
// return fmt.Errorf("company %d CSV: %w", company.CompanyID, err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// summary report
|
||||
if err := writeSummaryCSV(companies, from, to, outputDir); err != nil {
|
||||
return fmt.Errorf("summary CSV: %w", err)
|
||||
}
|
||||
// // summary report
|
||||
// if err := writeSummaryCSV(companies, from, to, outputDir); err != nil {
|
||||
// return fmt.Errorf("summary CSV: %w", err)
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// writeCompanyCSV writes the company report to CSV in the hardcoded folder
|
||||
func writeCompanyCSV(company domain.CompanyReport, branches []domain.BranchReport, from, to time.Time, outputDir string) error {
|
||||
period := fmt.Sprintf("%s to %s", from.Format("2006-01-02"), to.Format("2006-01-02"))
|
||||
// // writeCompanyCSV writes the company report to CSV in the hardcoded folder
|
||||
// func writeCompanyCSV(company domain.CompanyReport, branches []domain.BranchReport, from, to time.Time, outputDir string) error {
|
||||
// period := fmt.Sprintf("%s to %s", from.Format("2006-01-02"), to.Format("2006-01-02"))
|
||||
|
||||
filePath := fmt.Sprintf("%s/company_%d_%s_%s_%s.csv",
|
||||
outputDir,
|
||||
company.CompanyID,
|
||||
from.Format("2006-01-02"),
|
||||
to.Format("2006-01-02"),
|
||||
time.Now().Format("2006-01-02_15-04"),
|
||||
)
|
||||
// filePath := fmt.Sprintf("%s/company_%d_%s_%s_%s.csv",
|
||||
// outputDir,
|
||||
// company.CompanyID,
|
||||
// from.Format("2006-01-02"),
|
||||
// to.Format("2006-01-02"),
|
||||
// time.Now().Format("2006-01-02_15-04"),
|
||||
// )
|
||||
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create company csv: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
// file, err := os.Create(filePath)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("create company csv: %w", err)
|
||||
// }
|
||||
// defer file.Close()
|
||||
|
||||
writer := csv.NewWriter(file)
|
||||
defer writer.Flush()
|
||||
// writer := csv.NewWriter(file)
|
||||
// defer writer.Flush()
|
||||
|
||||
// Company summary section
|
||||
writer.Write([]string{"Company Betting Report"})
|
||||
writer.Write([]string{"Period", "Company ID", "Company Name", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||
writer.Write([]string{
|
||||
period,
|
||||
fmt.Sprintf("%d", company.CompanyID),
|
||||
company.CompanyName,
|
||||
fmt.Sprintf("%d", company.TotalBets),
|
||||
fmt.Sprintf("%.2f", company.TotalCashIn),
|
||||
fmt.Sprintf("%.2f", company.TotalCashOut),
|
||||
fmt.Sprintf("%.2f", company.TotalCashBacks),
|
||||
})
|
||||
writer.Write([]string{}) // Empty line
|
||||
// // Company summary section
|
||||
// writer.Write([]string{"Company Betting Report"})
|
||||
// writer.Write([]string{"Period", "Company ID", "Company Name", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||
// writer.Write([]string{
|
||||
// period,
|
||||
// fmt.Sprintf("%d", company.CompanyID),
|
||||
// company.CompanyName,
|
||||
// fmt.Sprintf("%d", company.TotalBets),
|
||||
// fmt.Sprintf("%.2f", company.TotalCashIn),
|
||||
// fmt.Sprintf("%.2f", company.TotalCashOut),
|
||||
// fmt.Sprintf("%.2f", company.TotalCashBacks),
|
||||
// })
|
||||
// writer.Write([]string{}) // Empty line
|
||||
|
||||
// Branch reports
|
||||
writer.Write([]string{"Branch Reports"})
|
||||
writer.Write([]string{"Branch ID", "Branch Name", "Company ID", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||
for _, br := range branches {
|
||||
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),
|
||||
})
|
||||
}
|
||||
// // Branch reports
|
||||
// writer.Write([]string{"Branch Reports"})
|
||||
// writer.Write([]string{"Branch ID", "Branch Name", "Company ID", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||
// for _, br := range branches {
|
||||
// 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),
|
||||
// })
|
||||
// }
|
||||
|
||||
if err := writer.Error(); err != nil {
|
||||
return fmt.Errorf("flush error: %w", err)
|
||||
}
|
||||
// if err := writer.Error(); err != nil {
|
||||
// return fmt.Errorf("flush error: %w", err)
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// writeSummaryCSV writes the summary report to CSV in the hardcoded folder
|
||||
func writeSummaryCSV(companies []domain.CompanyReport, from, to time.Time, outputDir string) error {
|
||||
period := fmt.Sprintf("%s to %s", from.Format("2006-01-02"), to.Format("2006-01-02"))
|
||||
// func writeSummaryCSV(companies []domain.CompanyReport, from, to time.Time, outputDir string) error {
|
||||
// period := fmt.Sprintf("%s to %s", from.Format("2006-01-02"), to.Format("2006-01-02"))
|
||||
|
||||
filePath := fmt.Sprintf("%s/summary_%s_%s_%s.csv",
|
||||
outputDir,
|
||||
from.Format("2006-01-02"),
|
||||
to.Format("2006-01-02"),
|
||||
time.Now().Format("2006-01-02_15-04"),
|
||||
)
|
||||
// filePath := fmt.Sprintf("%s/summary_%s_%s_%s.csv",
|
||||
// outputDir,
|
||||
// from.Format("2006-01-02"),
|
||||
// to.Format("2006-01-02"),
|
||||
// time.Now().Format("2006-01-02_15-04"),
|
||||
// )
|
||||
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create summary csv: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
// file, err := os.Create(filePath)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("create summary csv: %w", err)
|
||||
// }
|
||||
// defer file.Close()
|
||||
|
||||
writer := csv.NewWriter(file)
|
||||
defer writer.Flush()
|
||||
// writer := csv.NewWriter(file)
|
||||
// defer writer.Flush()
|
||||
|
||||
// Global summary
|
||||
writer.Write([]string{"Global Betting Summary"})
|
||||
writer.Write([]string{"Period", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||
// // Global summary
|
||||
// writer.Write([]string{"Global Betting Summary"})
|
||||
// writer.Write([]string{"Period", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||
|
||||
var totalBets int64
|
||||
var totalIn, totalOut, totalBack float64
|
||||
for _, c := range companies {
|
||||
totalBets += c.TotalBets
|
||||
totalIn += c.TotalCashIn
|
||||
totalOut += c.TotalCashOut
|
||||
totalBack += c.TotalCashBacks
|
||||
}
|
||||
// var totalBets int64
|
||||
// var totalIn, totalOut, totalBack float64
|
||||
// for _, c := range companies {
|
||||
// totalBets += c.TotalBets
|
||||
// totalIn += c.TotalCashIn
|
||||
// totalOut += c.TotalCashOut
|
||||
// totalBack += c.TotalCashBacks
|
||||
// }
|
||||
|
||||
writer.Write([]string{
|
||||
period,
|
||||
fmt.Sprintf("%d", totalBets),
|
||||
fmt.Sprintf("%.2f", totalIn),
|
||||
fmt.Sprintf("%.2f", totalOut),
|
||||
fmt.Sprintf("%.2f", totalBack),
|
||||
})
|
||||
writer.Write([]string{}) // Empty line
|
||||
// writer.Write([]string{
|
||||
// period,
|
||||
// fmt.Sprintf("%d", totalBets),
|
||||
// fmt.Sprintf("%.2f", totalIn),
|
||||
// fmt.Sprintf("%.2f", totalOut),
|
||||
// fmt.Sprintf("%.2f", totalBack),
|
||||
// })
|
||||
// writer.Write([]string{}) // Empty line
|
||||
|
||||
// Company breakdown
|
||||
writer.Write([]string{"Company Reports"})
|
||||
writer.Write([]string{"Company ID", "Company Name", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||
for _, cr := range companies {
|
||||
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),
|
||||
})
|
||||
}
|
||||
// // Company breakdown
|
||||
// writer.Write([]string{"Company Reports"})
|
||||
// writer.Write([]string{"Company ID", "Company Name", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||
// for _, cr := range companies {
|
||||
// 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),
|
||||
// })
|
||||
// }
|
||||
|
||||
if err := writer.Error(); err != nil {
|
||||
return fmt.Errorf("flush error: %w", err)
|
||||
}
|
||||
// if err := writer.Error(); err != nil {
|
||||
// return fmt.Errorf("flush error: %w", err)
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (s *Service) fetchReportData(ctx context.Context, from, to time.Time) (
|
||||
[]domain.CompanyReport, map[int64][]domain.BranchReport, error,
|
||||
) {
|
||||
// --- company level ---
|
||||
companyRows, err := s.repo.GetCompanyWiseReport(ctx, from, to)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("company-wise report: %w", err)
|
||||
}
|
||||
// func (s *Service) fetchReportData(ctx context.Context, from, to time.Time) (
|
||||
// []domain.CompanyReport, map[int64][]domain.BranchReport, error,
|
||||
// ) {
|
||||
// // --- company level ---
|
||||
// companyRows, err := s.repo.GetCompanyWiseReport(ctx, from, to)
|
||||
// if err != nil {
|
||||
// return nil, nil, fmt.Errorf("company-wise report: %w", err)
|
||||
// }
|
||||
|
||||
companies := make([]domain.CompanyReport, 0, len(companyRows))
|
||||
for _, row := range companyRows {
|
||||
companies = append(companies, domain.CompanyReport{
|
||||
CompanyID: row.CompanyID,
|
||||
CompanyName: row.CompanyName,
|
||||
TotalBets: row.TotalBets,
|
||||
TotalCashIn: toFloat(row.TotalCashMade),
|
||||
TotalCashOut: toFloat(row.TotalCashOut),
|
||||
TotalCashBacks: toFloat(row.TotalCashBacks),
|
||||
})
|
||||
}
|
||||
// companies := make([]domain.CompanyReport, 0, len(companyRows))
|
||||
// for _, row := range companyRows {
|
||||
// companies = append(companies, domain.CompanyReport{
|
||||
// CompanyID: row.CompanyID,
|
||||
// CompanyName: row.CompanyName,
|
||||
// TotalBets: row.TotalBets,
|
||||
// TotalCashIn: toFloat(row.TotalCashMade),
|
||||
// TotalCashOut: toFloat(row.TotalCashOut),
|
||||
// TotalCashBacks: toFloat(row.TotalCashBacks),
|
||||
// })
|
||||
// }
|
||||
|
||||
// --- branch level ---
|
||||
branchRows, err := s.repo.GetBranchWiseReport(ctx, from, to)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("branch-wise report: %w", err)
|
||||
}
|
||||
// // --- branch level ---
|
||||
// branchRows, err := s.repo.GetBranchWiseReport(ctx, from, to)
|
||||
// if err != nil {
|
||||
// return nil, nil, fmt.Errorf("branch-wise report: %w", err)
|
||||
// }
|
||||
|
||||
branchMap := make(map[int64][]domain.BranchReport)
|
||||
for _, row := range branchRows {
|
||||
branch := domain.BranchReport{
|
||||
BranchID: row.BranchID,
|
||||
BranchName: row.BranchName,
|
||||
CompanyID: row.CompanyID,
|
||||
TotalBets: row.TotalBets,
|
||||
TotalCashIn: toFloat(row.TotalCashMade),
|
||||
TotalCashOut: toFloat(row.TotalCashOut),
|
||||
TotalCashBacks: toFloat(row.TotalCashBacks),
|
||||
}
|
||||
branchMap[row.CompanyID] = append(branchMap[row.CompanyID], branch)
|
||||
}
|
||||
// branchMap := make(map[int64][]domain.BranchReport)
|
||||
// for _, row := range branchRows {
|
||||
// branch := domain.BranchReport{
|
||||
// BranchID: row.BranchID,
|
||||
// BranchName: row.BranchName,
|
||||
// CompanyID: row.CompanyID,
|
||||
// TotalBets: row.TotalBets,
|
||||
// TotalCashIn: toFloat(row.TotalCashMade),
|
||||
// TotalCashOut: toFloat(row.TotalCashOut),
|
||||
// TotalCashBacks: toFloat(row.TotalCashBacks),
|
||||
// }
|
||||
// branchMap[row.CompanyID] = append(branchMap[row.CompanyID], branch)
|
||||
// }
|
||||
|
||||
return companies, branchMap, nil
|
||||
}
|
||||
// return companies, branchMap, nil
|
||||
// }
|
||||
|
||||
// helper to unify float conversions
|
||||
func toFloat(val interface{}) float64 {
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ type Service struct {
|
|||
client *http.Client
|
||||
betSvc bet.Service
|
||||
oddSvc odds.ServiceImpl
|
||||
eventSvc event.Service
|
||||
leagueSvc league.Service
|
||||
eventSvc *event.Service
|
||||
leagueSvc *league.Service
|
||||
notificationSvc *notificationservice.Service
|
||||
messengerSvc *messenger.Service
|
||||
userSvc user.Service
|
||||
|
|
@ -46,8 +46,8 @@ func NewService(
|
|||
mongoLogger *zap.Logger,
|
||||
betSvc bet.Service,
|
||||
oddSvc odds.ServiceImpl,
|
||||
eventSvc event.Service,
|
||||
leagueSvc league.Service,
|
||||
eventSvc *event.Service,
|
||||
leagueSvc *league.Service,
|
||||
notificationSvc *notificationservice.Service,
|
||||
messengerSvc *messenger.Service,
|
||||
userSvc user.Service,
|
||||
|
|
|
|||
|
|
@ -31,19 +31,19 @@ var (
|
|||
|
||||
type Service struct {
|
||||
ticketStore TicketStore
|
||||
eventSvc event.Service
|
||||
eventSvc *event.Service
|
||||
prematchSvc odds.ServiceImpl
|
||||
mongoLogger *zap.Logger
|
||||
settingSvc settings.Service
|
||||
settingSvc *settings.Service
|
||||
notificationSvc *notificationservice.Service
|
||||
}
|
||||
|
||||
func NewService(
|
||||
ticketStore TicketStore,
|
||||
eventSvc event.Service,
|
||||
eventSvc *event.Service,
|
||||
prematchSvc odds.ServiceImpl,
|
||||
mongoLogger *zap.Logger,
|
||||
settingSvc settings.Service,
|
||||
settingSvc *settings.Service,
|
||||
notificationSvc *notificationservice.Service,
|
||||
) *Service {
|
||||
return &Service{
|
||||
|
|
|
|||
|
|
@ -81,8 +81,8 @@ type App struct {
|
|||
JwtConfig jwtutil.JwtConfig
|
||||
Logger *slog.Logger
|
||||
prematchSvc *odds.ServiceImpl
|
||||
eventSvc event.Service
|
||||
leagueSvc league.Service
|
||||
eventSvc *event.Service
|
||||
leagueSvc *league.Service
|
||||
resultSvc *result.Service
|
||||
mongoLoggerSvc *zap.Logger
|
||||
}
|
||||
|
|
@ -113,8 +113,8 @@ func NewApp(
|
|||
companySvc *company.Service,
|
||||
notidicationStore *notificationservice.Service,
|
||||
prematchSvc *odds.ServiceImpl,
|
||||
eventSvc event.Service,
|
||||
leagueSvc league.Service,
|
||||
eventSvc *event.Service,
|
||||
leagueSvc *league.Service,
|
||||
referralSvc *referralservice.Service,
|
||||
raffleSvc raffle.RaffleStore,
|
||||
bonusSvc *bonus.Service,
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ package httpserver
|
|||
import (
|
||||
"context"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
// "time"
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
betSvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||
enetpulse "github.com/SamuelTariku/FortuneBet-Backend/internal/services/enet_pulse"
|
||||
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification"
|
||||
|
|
@ -21,26 +23,26 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.ServiceImpl, resultService *resultsvc.Service, mongoLogger *zap.Logger) {
|
||||
func StartDataFetchingCrons(eventService *eventsvc.Service, oddsService oddssvc.ServiceImpl, resultService *resultsvc.Service, mongoLogger *zap.Logger) {
|
||||
c := cron.New(cron.WithSeconds())
|
||||
|
||||
schedule := []struct {
|
||||
spec string
|
||||
task func()
|
||||
}{
|
||||
// {
|
||||
// spec: "0 0 * * * *", // Every 1 hour
|
||||
// task: func() {
|
||||
// mongoLogger.Info("Began fetching upcoming events cron task")
|
||||
// if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
|
||||
// mongoLogger.Error("Failed to fetch upcoming events",
|
||||
// zap.Error(err),
|
||||
// )
|
||||
// } else {
|
||||
// mongoLogger.Info("Completed fetching upcoming events without errors")
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
{
|
||||
spec: "0 0 * * * *", // Every 1 hour
|
||||
task: func() {
|
||||
mongoLogger.Info("Began fetching upcoming events cron task")
|
||||
if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
|
||||
mongoLogger.Error("Failed to fetch upcoming events",
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
mongoLogger.Info("Completed fetching upcoming events without errors")
|
||||
}
|
||||
},
|
||||
},
|
||||
// {
|
||||
// spec: "0 0 * * * *", // Every 1 hour (since its takes that long to fetch all the events)
|
||||
// task: func() {
|
||||
|
|
@ -96,7 +98,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
|||
}
|
||||
|
||||
for _, job := range schedule {
|
||||
// job.task()
|
||||
job.task()
|
||||
if _, err := c.AddFunc(job.spec, job.task); err != nil {
|
||||
mongoLogger.Error("Failed to schedule data fetching cron job",
|
||||
zap.Error(err),
|
||||
|
|
@ -105,8 +107,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
|||
}
|
||||
|
||||
c.Start()
|
||||
log.Println("Cron jobs started for event and odds services")
|
||||
mongoLogger.Info("Cron jobs started for event and odds services")
|
||||
mongoLogger.Info("Data Fetching Cron jobs started")
|
||||
}
|
||||
|
||||
func StartCleanupCrons(ticketService ticket.Service, notificationSvc *notificationservice.Service, mongoLogger *zap.Logger) {
|
||||
|
|
@ -156,6 +157,89 @@ func StartCleanupCrons(ticketService ticket.Service, notificationSvc *notificati
|
|||
mongoLogger.Info("Cron jobs started for ticket service")
|
||||
}
|
||||
|
||||
func StartStatCrons(companyService company.Service, eventService *eventsvc.Service, mongoLogger *zap.Logger) {
|
||||
c := cron.New(cron.WithSeconds())
|
||||
|
||||
schedule := []struct {
|
||||
spec string
|
||||
task func()
|
||||
}{
|
||||
{
|
||||
spec: "0 0 * * * *", // Every hour
|
||||
task: func() {
|
||||
mongoLogger.Info("[Company Stats Crons] Updating company stats")
|
||||
if err := companyService.UpdateCompanyStats(context.Background()); err != nil {
|
||||
mongoLogger.Error("[Company Stats Crons] Failed to update company stats",
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
mongoLogger.Info("[Company Stats Crons] Successfully updated company stats")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
spec: "0 0 * * * *", // Hourly
|
||||
task: func() {
|
||||
mongoLogger.Info("[Event Stats Crons] Updating event stats")
|
||||
if err := eventService.UpdateEventBetStats(context.Background()); err != nil {
|
||||
mongoLogger.Error("[Event Stats Crons] Failed to update event bet stats",
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
mongoLogger.Info("[Event Stats Crons] Successfully updated event stats")
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, job := range schedule {
|
||||
job.task()
|
||||
if _, err := c.AddFunc(job.spec, job.task); err != nil {
|
||||
mongoLogger.Error("[Stats Crons] Failed to schedule stats cron job",
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
c.Start()
|
||||
mongoLogger.Info("Cron jobs started for stats")
|
||||
}
|
||||
|
||||
func StartReportCrons(reportService *report.Service, mongoLogger *zap.Logger) {
|
||||
c := cron.New(cron.WithSeconds())
|
||||
|
||||
schedule := []struct {
|
||||
spec string
|
||||
task func()
|
||||
}{
|
||||
{
|
||||
spec: "0 * * * * *", // Every 5 Minutes
|
||||
task: func() {
|
||||
mongoLogger.Info("[Process Report Crons] Started Checking and Processing Reports")
|
||||
if err := reportService.ProcessReportRequests(context.Background()); err != nil {
|
||||
mongoLogger.Error("[Process Report Crons] Failed to process reports",
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
mongoLogger.Info("[Process Report Crons] Successfully processed all reports")
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, job := range schedule {
|
||||
job.task()
|
||||
if _, err := c.AddFunc(job.spec, job.task); err != nil {
|
||||
mongoLogger.Error("[Report Crons] Failed to schedule report cron job",
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
c.Start()
|
||||
mongoLogger.Info("[Report Crons] Cron jobs started for reports")
|
||||
}
|
||||
|
||||
// SetupReportCronJobs schedules periodic report generation
|
||||
func SetupReportandVirtualGameCronJobs(
|
||||
ctx context.Context,
|
||||
|
|
@ -207,18 +291,18 @@ func SetupReportandVirtualGameCronJobs(
|
|||
log.Printf("[%s] Successfully fetched & stored %d virtual games", period, len(allGames))
|
||||
|
||||
// --- Generate reports only for daily runs ---
|
||||
if period == "daily" {
|
||||
now := time.Now()
|
||||
from := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, now.Location())
|
||||
to := time.Date(now.Year(), now.Month(), now.Day()-1, 23, 59, 59, 0, now.Location())
|
||||
// if period == "daily" {
|
||||
// now := time.Now()
|
||||
// from := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, now.Location())
|
||||
// to := time.Date(now.Year(), now.Month(), now.Day()-1, 23, 59, 59, 0, now.Location())
|
||||
|
||||
log.Printf("Running daily report for period %s -> %s", from.Format(time.RFC3339), to.Format(time.RFC3339))
|
||||
if err := reportService.GenerateReport(ctx, from, to); err != nil {
|
||||
log.Printf("Error generating daily report: %v", err)
|
||||
} else {
|
||||
log.Printf("Successfully generated daily report")
|
||||
}
|
||||
}
|
||||
// log.Printf("Running daily report for period %s -> %s", from.Format(time.RFC3339), to.Format(time.RFC3339))
|
||||
// if err := reportService.GenerateReport(ctx, from, to); err != nil {
|
||||
// log.Printf("Error generating daily report: %v", err)
|
||||
// } else {
|
||||
// log.Printf("Successfully generated daily report")
|
||||
// }
|
||||
// }
|
||||
}); err != nil {
|
||||
log.Fatalf("Failed to schedule %s cron job: %v", period, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ func (h *Handler) GetAllEvents(c *fiber.Ctx) error {
|
|||
eventStatusParsed, err := domain.ParseEventStatus(statusQuery)
|
||||
if err != nil {
|
||||
h.BadRequestLogger().Error("Failed to parse statusQuery",
|
||||
zap.String("is_featured", isFeaturedQuery),
|
||||
zap.String("status", statusQuery),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid event status string")
|
||||
|
|
@ -222,204 +222,6 @@ func (h *Handler) GetAllEvents(c *fiber.Ctx) error {
|
|||
return response.WritePaginatedJSON(c, fiber.StatusOK, "All upcoming events retrieved successfully", res, nil, page, int(total))
|
||||
}
|
||||
|
||||
// @Summary Retrieve all upcoming events
|
||||
// @Description Retrieve all upcoming events from the database
|
||||
// @Tags prematch
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number"
|
||||
// @Param page_size query int false "Page size"
|
||||
// @Param league_id query string false "League ID Filter"
|
||||
// @Param sport_id query string false "Sport ID Filter"
|
||||
// @Param cc query string false "Country Code Filter"
|
||||
// @Param first_start_time query string false "Start Time"
|
||||
// @Param last_start_time query string false "End Time"
|
||||
// @Success 200 {array} domain.BaseEvent
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/detailed/events [get]
|
||||
func (h *Handler) GetAllDetailedEvents(c *fiber.Ctx) error {
|
||||
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,
|
||||
}
|
||||
|
||||
leagueIDQuery := c.Query("league_id")
|
||||
var leagueID domain.ValidInt64
|
||||
if leagueIDQuery != "" {
|
||||
leagueIDInt, err := strconv.ParseInt(leagueIDQuery, 10, 64)
|
||||
if err != nil {
|
||||
h.BadRequestLogger().Error("invalid league id",
|
||||
zap.String("league_id", leagueIDQuery),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid league id")
|
||||
}
|
||||
leagueID = domain.ValidInt64{
|
||||
Value: leagueIDInt,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Go through the all the handler functions and change them into something like this
|
||||
// leagueID, err := ParseLeagueIDFromQuery(c)
|
||||
// if err != nil {
|
||||
// h.BadRequestLogger().Info("invalid league id", zap.Error(err))
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "invalid league id")
|
||||
// }
|
||||
|
||||
sportIDQuery := c.Query("sport_id")
|
||||
var sportID domain.ValidInt32
|
||||
if sportIDQuery != "" {
|
||||
sportIDint, err := strconv.Atoi(sportIDQuery)
|
||||
if err != nil {
|
||||
h.BadRequestLogger().Info("invalid sport id",
|
||||
zap.String("sportID", sportIDQuery),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid sport id")
|
||||
}
|
||||
sportID = domain.ValidInt32{
|
||||
Value: int32(sportIDint),
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
searchQuery := c.Query("query")
|
||||
searchString := domain.ValidString{
|
||||
Value: searchQuery,
|
||||
Valid: searchQuery != "",
|
||||
}
|
||||
|
||||
firstStartTimeQuery := c.Query("first_start_time")
|
||||
var firstStartTime domain.ValidTime
|
||||
if firstStartTimeQuery != "" {
|
||||
firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery)
|
||||
if err != nil {
|
||||
h.BadRequestLogger().Info("invalid start_time format",
|
||||
zap.String("first_start_time", firstStartTimeQuery),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
|
||||
}
|
||||
firstStartTime = domain.ValidTime{
|
||||
Value: firstStartTimeParsed,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
lastStartTimeQuery := c.Query("last_start_time")
|
||||
var lastStartTime domain.ValidTime
|
||||
if lastStartTimeQuery != "" {
|
||||
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
|
||||
if err != nil {
|
||||
h.BadRequestLogger().Info("invalid last_start_time format",
|
||||
zap.String("last_start_time", lastStartTimeQuery),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
|
||||
}
|
||||
lastStartTime = domain.ValidTime{
|
||||
Value: lastStartTimeParsed,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
countryCodeQuery := c.Query("cc")
|
||||
countryCode := domain.ValidString{
|
||||
Value: countryCodeQuery,
|
||||
Valid: countryCodeQuery != "",
|
||||
}
|
||||
|
||||
isFeaturedQuery := c.Query("is_featured")
|
||||
var isFeatured domain.ValidBool
|
||||
if isFeaturedQuery != "" {
|
||||
isFeaturedParsed, err := strconv.ParseBool(isFeaturedQuery)
|
||||
if err != nil {
|
||||
h.BadRequestLogger().Error("Failed to parse isFeatured",
|
||||
zap.String("is_featured", isFeaturedQuery),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Failed to parse is_shop_bet")
|
||||
}
|
||||
|
||||
isFeatured = domain.ValidBool{
|
||||
Value: isFeaturedParsed,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
isActiveQuery := c.Query("is_active")
|
||||
var isActive domain.ValidBool
|
||||
if isActiveQuery != "" {
|
||||
isActiveParsed, err := strconv.ParseBool(isActiveQuery)
|
||||
if err != nil {
|
||||
h.BadRequestLogger().Error("Failed to parse isActive",
|
||||
zap.String("is_active", isActiveQuery),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Failed to parse is_active")
|
||||
}
|
||||
|
||||
isActive = domain.ValidBool{
|
||||
Value: isActiveParsed,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
statusQuery := c.Query("status")
|
||||
var eventStatus domain.ValidEventStatus
|
||||
if statusQuery != "" {
|
||||
eventStatusParsed, err := domain.ParseEventStatus(statusQuery)
|
||||
if err != nil {
|
||||
h.BadRequestLogger().Error("Failed to parse statusQuery",
|
||||
zap.String("is_featured", isFeaturedQuery),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid event status string")
|
||||
}
|
||||
eventStatus = domain.ValidEventStatus{
|
||||
Value: eventStatusParsed,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
events, total, err := h.eventSvc.GetAllDetailedEvents(
|
||||
c.Context(), domain.EventFilter{
|
||||
SportID: sportID,
|
||||
LeagueID: leagueID,
|
||||
Query: searchString,
|
||||
FirstStartTime: firstStartTime,
|
||||
LastStartTime: lastStartTime,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
CountryCode: countryCode,
|
||||
Featured: isFeatured,
|
||||
Active: isActive,
|
||||
Status: eventStatus,
|
||||
})
|
||||
|
||||
// fmt.Printf("League ID: %v", leagueID)
|
||||
if err != nil {
|
||||
h.InternalServerErrorLogger().Error("Failed to retrieve all upcoming events",
|
||||
zap.Error(err),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
res := domain.ConvertDetailedEventResList(events)
|
||||
|
||||
return response.WritePaginatedJSON(c, fiber.StatusOK, "All upcoming events retrieved successfully", res, nil, page, int(total))
|
||||
}
|
||||
|
||||
func (h *Handler) ExportEvents(c *fiber.Ctx) error {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// @Summary Retrieve all upcoming events with settings
|
||||
// @Description Retrieve all upcoming events settings from the database
|
||||
|
|
@ -899,40 +701,6 @@ func (h *Handler) GetEventByID(c *fiber.Ctx) error {
|
|||
|
||||
}
|
||||
|
||||
// @Summary Retrieve an upcoming by ID
|
||||
// @Description Retrieve an upcoming event by ID
|
||||
// @Tags prematch
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "ID"
|
||||
// @Success 200 {object} domain.BaseEvent
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/detailed/events/{id} [get]
|
||||
func (h *Handler) GetDetailedEventByID(c *fiber.Ctx) error {
|
||||
|
||||
idStr := c.Params("id")
|
||||
eventID, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
h.BadRequestLogger().Info("Failed to parse event id", zap.String("id", idStr))
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Missing id")
|
||||
}
|
||||
|
||||
event, err := h.eventSvc.GetDetailedEventByID(c.Context(), eventID)
|
||||
if err != nil {
|
||||
h.InternalServerErrorLogger().Error("Failed to get event by id",
|
||||
zap.Int64("eventID", eventID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
res := domain.ConvertDetailedEventRes(event)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Upcoming event retrieved successfully", res, nil)
|
||||
|
||||
}
|
||||
|
||||
// @Summary Retrieve an upcoming by ID
|
||||
// @Description Retrieve an upcoming event by ID
|
||||
// @Tags prematch
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (h *Handler) GetEventStats(c *fiber.Ctx) error {
|
||||
func (h *Handler) GetTotalEventStats(c *fiber.Ctx) error {
|
||||
leagueIDQuery := c.Query("league_id")
|
||||
var leagueID domain.ValidInt64
|
||||
if leagueIDQuery != "" {
|
||||
|
|
@ -44,7 +44,7 @@ func (h *Handler) GetEventStats(c *fiber.Ctx) error {
|
|||
}
|
||||
}
|
||||
|
||||
stats, err := h.eventSvc.GetEventStats(c.Context(), domain.EventStatsFilter{
|
||||
stats, err := h.eventSvc.GetTotalEventStats(c.Context(), domain.EventStatsFilter{
|
||||
LeagueID: leagueID,
|
||||
SportID: sportID,
|
||||
})
|
||||
|
|
@ -59,7 +59,7 @@ func (h *Handler) GetEventStats(c *fiber.Ctx) error {
|
|||
return response.WriteJSON(c, fiber.StatusOK, "Event Statistics retrieved successfully", stats, nil)
|
||||
}
|
||||
|
||||
func (h *Handler) GetEventStatsByInterval(c *fiber.Ctx) error {
|
||||
func (h *Handler) GetTotalEventStatsByInterval(c *fiber.Ctx) error {
|
||||
intervalParam := c.Query("interval", "day")
|
||||
interval, err := domain.ParseDateInterval(intervalParam)
|
||||
if err != nil {
|
||||
|
|
@ -103,8 +103,11 @@ func (h *Handler) GetEventStatsByInterval(c *fiber.Ctx) error {
|
|||
}
|
||||
}
|
||||
|
||||
stats, err := h.eventSvc.GetEventStatsByInterval(c.Context(), domain.EventStatsByIntervalFilter{
|
||||
Interval: interval,
|
||||
stats, err := h.eventSvc.GetTotalEventStatsByInterval(c.Context(), domain.EventStatsByIntervalFilter{
|
||||
Interval: domain.ValidDateInterval{
|
||||
Value: interval,
|
||||
Valid: true,
|
||||
},
|
||||
LeagueID: leagueID,
|
||||
SportID: sportID,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ type Handler struct {
|
|||
referralSvc *referralservice.Service
|
||||
raffleSvc raffle.RaffleStore
|
||||
bonusSvc *bonus.Service
|
||||
reportSvc report.ReportStore
|
||||
reportSvc *report.Service
|
||||
chapaSvc *chapa.Service
|
||||
walletSvc *wallet.Service
|
||||
transactionSvc *transaction.Service
|
||||
|
|
@ -64,8 +64,8 @@ type Handler struct {
|
|||
branchSvc *branch.Service
|
||||
companySvc *company.Service
|
||||
prematchSvc *odds.ServiceImpl
|
||||
eventSvc event.Service
|
||||
leagueSvc league.Service
|
||||
eventSvc *event.Service
|
||||
leagueSvc *league.Service
|
||||
virtualGameSvc virtualgameservice.VirtualGameService
|
||||
aleaVirtualGameSvc alea.AleaVirtualGameService
|
||||
veliVirtualGameSvc veli.VeliVirtualGameService
|
||||
|
|
@ -91,7 +91,7 @@ func New(
|
|||
settingSvc *settings.Service,
|
||||
notificationSvc *notificationservice.Service,
|
||||
validator *customvalidator.CustomValidator,
|
||||
reportSvc report.ReportStore,
|
||||
reportSvc *report.Service,
|
||||
chapaSvc *chapa.Service,
|
||||
walletSvc *wallet.Service,
|
||||
referralSvc *referralservice.Service,
|
||||
|
|
@ -111,8 +111,8 @@ func New(
|
|||
branchSvc *branch.Service,
|
||||
companySvc *company.Service,
|
||||
prematchSvc *odds.ServiceImpl,
|
||||
eventSvc event.Service,
|
||||
leagueSvc league.Service,
|
||||
eventSvc *event.Service,
|
||||
leagueSvc *league.Service,
|
||||
resultSvc result.Service,
|
||||
cfg *config.Config,
|
||||
mongoLoggerSvc *zap.Logger,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"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"
|
||||
)
|
||||
|
|
@ -315,3 +316,166 @@ func (h *Handler) ListReportFiles(c *fiber.Ctx) error {
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -276,12 +276,6 @@ func (a *App) initAppRoutes() {
|
|||
groupV1.Put("/events/:id/settings", a.authMiddleware, a.SuperAdminOnly, h.UpdateGlobalSettingList)
|
||||
groupV1.Get("/events/:id/bets", a.authMiddleware, a.SuperAdminOnly, h.GetBetsByEventID)
|
||||
|
||||
groupV1.Get("/detailed/events", a.authMiddleware, h.GetAllDetailedEvents)
|
||||
groupV1.Get("/detailed/events/:id", a.authMiddleware, h.GetDetailedEventByID)
|
||||
|
||||
groupV1.Get("/stats/total/events", h.GetEventStats)
|
||||
groupV1.Get("/stats/interval/events", h.GetEventStatsByInterval)
|
||||
|
||||
tenant.Get("/upcoming-events", h.GetTenantUpcomingEvents)
|
||||
tenant.Get("/top-leagues", h.GetTopLeagues)
|
||||
tenant.Get("/events", h.GetTenantEvents)
|
||||
|
|
@ -395,6 +389,10 @@ func (a *App) initAppRoutes() {
|
|||
groupV1.Get("/report-files/download/:filename", h.DownloadReportFile)
|
||||
groupV1.Get("/report-files/list", a.authMiddleware, a.OnlyAdminAndAbove, h.ListReportFiles)
|
||||
|
||||
groupV1.Post("/reports/requests", a.authMiddleware, a.OnlyAdminAndAbove, h.CreateReportRequest)
|
||||
groupV1.Get("/reports/requests", a.authMiddleware, a.OnlyAdminAndAbove, h.GetAllReportRequests)
|
||||
groupV1.Get("/reports/download/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.DownloadReportByID)
|
||||
|
||||
//Alea Play Virtual Game Routes
|
||||
groupV1.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame)
|
||||
groupV1.Post("/webhooks/alea-play", a.authMiddleware, h.HandleAleaCallback)
|
||||
|
|
@ -488,4 +486,7 @@ func (a *App) initAppRoutes() {
|
|||
tenant.Delete("/settings/:key", a.authMiddleware, a.OnlyAdminAndAbove, h.DeleteCompanySetting)
|
||||
tenant.Delete("/settings", a.authMiddleware, a.OnlyAdminAndAbove, h.DeleteAllCompanySetting)
|
||||
|
||||
groupV1.Get("/stats/total/events", h.GetTotalEventStats)
|
||||
groupV1.Get("/stats/interval/events", h.GetTotalEventStatsByInterval)
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user