feat: Add EventWithSettings domain model and related conversion functions

- Introduced EventWithSettings and EventWithSettingsRes structs for enhanced event data handling.
- Implemented conversion functions for creating and updating event settings.
- Added support for fetching events with settings from the database.
- Created new report data structures for comprehensive reporting capabilities.
- Implemented event statistics retrieval and filtering by league and sport.
- Added handlers for event statistics endpoints in the web server.
- Introduced DateInterval type for managing time intervals in reports.
This commit is contained in:
Samuel Tariku 2025-10-18 11:50:17 +03:00
parent 528a948f20
commit 18689ea124
44 changed files with 3086 additions and 1641 deletions

View File

@ -101,6 +101,7 @@ CREATE TABLE IF NOT EXISTS bets (
company_id BIGINT NOT NULL, company_id BIGINT NOT NULL,
amount BIGINT NOT NULL, amount BIGINT NOT NULL,
total_odds REAL NOT NULL, total_odds REAL NOT NULL,
potential_win BIGINT GENERATED ALWAYS AS (amount * total_odds) STORED,
status INT NOT NULL, status INT NOT NULL,
user_id BIGINT NOT NULL, user_id BIGINT NOT NULL,
is_shop_bet BOOLEAN NOT NULL, is_shop_bet BOOLEAN NOT NULL,
@ -329,6 +330,29 @@ CREATE TABLE events (
is_monitored BOOLEAN NOT NULL DEFAULT FALSE, is_monitored BOOLEAN NOT NULL DEFAULT FALSE,
UNIQUE(source_event_id, source) UNIQUE(source_event_id, source)
); );
CREATE INDEX idx_events_league_start_time ON events(league_id, start_time);
CREATE INDEX IF NOT EXISTS idx_events_league_id ON events (league_id);
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_events_search_trgm ON events USING GIN (
match_name gin_trgm_ops,
league_name gin_trgm_ops
);
CREATE INDEX idx_events_core_filter ON events (
source,
sport_id,
league_id,
status,
is_live,
start_time DESC
);
CREATE TABLE event_bet_stats (
event_id BIGINT PRIMARY KEY,
number_of_bets BIGINT,
total_amount BIGINT,
avg_bet_amount DOUBLE PRECISION,
total_potential_winnings BIGINT,
updated_at TIMESTAMP DEFAULT now()
);
CREATE TABLE event_history ( CREATE TABLE event_history (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
event_id BIGINT NOT NULL, event_id BIGINT NOT NULL,
@ -359,6 +383,7 @@ CREATE TABLE odds_market (
expires_at TIMESTAMP NOT NULL, expires_at TIMESTAMP NOT NULL,
UNIQUE (event_id, market_id) UNIQUE (event_id, market_id)
); );
CREATE INDEX IF NOT EXISTS idx_odds_market_event_id ON odds_market (event_id);
CREATE TABLE odd_history ( CREATE TABLE odd_history (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
odds_market_id BIGINT NOT NULL REFERENCES odds_market (id), odds_market_id BIGINT NOT NULL REFERENCES odds_market (id),
@ -416,6 +441,14 @@ CREATE TABLE companies (
AND deducted_percentage < 1 AND deducted_percentage < 1
) )
); );
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()
);
CREATE TABLE leagues ( CREATE TABLE leagues (
id BIGINT PRIMARY KEY, id BIGINT PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
@ -552,6 +585,16 @@ CREATE TABLE IF NOT EXISTS company_accumulator (
outcome_count BIGINT NOT NULL, outcome_count BIGINT NOT NULL,
multiplier REAL NOT NULL multiplier REAL NOT NULL
); );
CREATE TABLE reports (
id BIGSERIAL PRIMARY KEY,
company_id BIGINT,
requested_by BIGINT,
--For System Generated Reports
file_path TEXT,
status TEXT NOT NULL DEFAULT 'pending',
created_at TIMESTAMP DEFAULT now(),
completed_at TIMESTAMP
);
------ Views ------ Views
CREATE VIEW companies_details AS CREATE VIEW companies_details AS
SELECT companies.*, SELECT companies.*,
@ -675,6 +718,35 @@ SELECT sd.*,
st.verified AS transaction_verified st.verified AS transaction_verified
FROM shop_deposits AS sd FROM shop_deposits AS sd
JOIN shop_transactions st ON st.id = sd.shop_transaction_id; JOIN shop_transactions st ON st.id = sd.shop_transaction_id;
CREATE OR REPLACE VIEW event_detailed AS
SELECT ewc.*,
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
LEFT JOIN leagues ON leagues.id = events.league_id
LEFT JOIN (
SELECT event_id,
SUM(number_of_outcomes) AS total_outcomes
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,
e.is_live,
e.status,
e.source
FROM odds_market o
JOIN events e ON o.event_id = e.id;
-- Views only for SQLC to generate structs for them (so that we can reuse those structs)
CREATE VIEW league_with_settings AS CREATE VIEW league_with_settings AS
SELECT l.*, SELECT l.*,
cls.company_id, cls.company_id,
@ -694,28 +766,21 @@ SELECT e.*,
) AS winning_upper_limit, ) AS winning_upper_limit,
ces.updated_at as company_updated_at, ces.updated_at as company_updated_at,
l.country_code as league_cc, l.country_code as league_cc,
COALESCE(om.total_outcomes, 0) AS total_outcomes 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 e FROM events e
LEFT JOIN company_event_settings ces ON e.id = ces.event_id LEFT JOIN company_event_settings ces ON e.id = ces.event_id
JOIN leagues l ON l.id = e.league_id JOIN leagues l ON l.id = e.league_id
LEFT JOIN event_bet_stats ebs ON ebs.event_id = ewc.id
LEFT JOIN ( LEFT JOIN (
SELECT event_id, SELECT event_id,
SUM(number_of_outcomes) AS total_outcomes SUM(number_of_outcomes) AS total_outcomes
FROM odds_market FROM odds_market
GROUP BY event_id GROUP BY event_id
) om ON om.event_id = e.id; ) om ON om.event_id = e.id;
CREATE VIEW event_with_country AS
SELECT events.*,
leagues.country_code as league_cc,
COALESCE(om.total_outcomes, 0) AS total_outcomes
FROM events
LEFT JOIN leagues ON leagues.id = events.league_id
LEFT JOIN (
SELECT event_id,
SUM(number_of_outcomes) AS total_outcomes
FROM odds_market
GROUP BY event_id
) om ON om.event_id = events.id;
CREATE VIEW odds_market_with_settings AS CREATE VIEW odds_market_with_settings AS
SELECT o.id, SELECT o.id,
o.event_id, o.event_id,
@ -733,14 +798,6 @@ SELECT o.id,
cos.updated_at cos.updated_at
FROM odds_market o FROM odds_market o
LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id; LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id;
CREATE VIEW odds_market_with_event AS
SELECT o.*,
e.is_monitored,
e.is_live,
e.status,
e.source
FROM odds_market o
JOIN events e ON o.event_id = e.id;
-- Foreign Keys -- Foreign Keys
ALTER TABLE refresh_tokens ALTER TABLE refresh_tokens
ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users (id); ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users (id);

31
db/query/branch_stats.sql Normal file
View File

@ -0,0 +1,31 @@
-- name: GetBranchStats :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 sqlc.arg('from') AND sqlc.arg('to')
GROUP BY b.branch_id,
br.name,
br.company_id;

View File

@ -0,0 +1,42 @@
-- 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,
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,
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
UPDATE
SET total_bets = EXCLUDED.total_bets,
total_cash_made = EXCLUDED.total_cash_made,
total_cash_out = EXCLUDED.total_cash_out,
total_cash_back = EXCLUDED.total_cash_back,
updated_at = EXCLUDED.updated_at;

View File

@ -56,26 +56,14 @@ SET sport_id = EXCLUDED.sport_id,
source = EXCLUDED.source, source = EXCLUDED.source,
default_winning_upper_limit = EXCLUDED.default_winning_upper_limit, default_winning_upper_limit = EXCLUDED.default_winning_upper_limit,
fetched_at = now(); fetched_at = now();
-- name: SaveTenantEventSettings :exec
INSERT INTO company_event_settings (
company_id,
event_id,
is_active,
is_featured,
winning_upper_limit
)
VALUES ($1, $2, $3, $4, $5) ON CONFLICT(company_id, event_id) DO
UPDATE
SET is_active = EXCLUDED.is_active,
is_featured = EXCLUDED.is_featured,
winning_upper_limit = EXCLUDED.winning_upper_limit;
-- name: ListLiveEvents :many -- name: ListLiveEvents :many
SELECT id SELECT id
FROM event_with_country FROM event_detailed
WHERE is_live = true; WHERE is_live = true;
-- TODO: Figure out how to make this code reusable. SQLC prohibits CTEs within multiple queries
-- name: GetAllEvents :many -- name: GetAllEvents :many
SELECT * SELECT *
FROM event_with_country FROM event_detailed
WHERE ( WHERE (
is_live = sqlc.narg('is_live') is_live = sqlc.narg('is_live')
OR sqlc.narg('is_live') IS NULL OR sqlc.narg('is_live') IS NULL
@ -117,7 +105,7 @@ ORDER BY start_time ASC
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
-- name: GetTotalEvents :one -- name: GetTotalEvents :one
SELECT COUNT(*) SELECT COUNT(*)
FROM event_with_country FROM event_detailed
WHERE ( WHERE (
is_live = sqlc.narg('is_live') is_live = sqlc.narg('is_live')
OR sqlc.narg('is_live') IS NULL OR sqlc.narg('is_live') IS NULL
@ -155,164 +143,17 @@ WHERE (
source = sqlc.narg('source') source = sqlc.narg('source')
OR sqlc.narg('source') IS NULL OR sqlc.narg('source') IS NULL
); );
-- name: GetTotalCompanyEvents :one
SELECT COUNT(*)
FROM events e
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
AND ces.company_id = $1
JOIN leagues l ON l.id = e.league_id
WHERE (
is_live = sqlc.narg('is_live')
OR sqlc.narg('is_live') IS NULL
)
AND (
status = sqlc.narg('status')
OR sqlc.narg('status') IS NULL
)
AND(
league_id = sqlc.narg('league_id')
OR sqlc.narg('league_id') IS NULL
)
AND (
e.sport_id = sqlc.narg('sport_id')
OR sqlc.narg('sport_id') IS NULL
)
AND (
match_name ILIKE '%' || sqlc.narg('query') || '%'
OR league_name ILIKE '%' || sqlc.narg('query') || '%'
OR sqlc.narg('query') IS NULL
)
AND (
start_time < sqlc.narg('last_start_time')
OR sqlc.narg('last_start_time') IS NULL
)
AND (
start_time > sqlc.narg('first_start_time')
OR sqlc.narg('first_start_time') IS NULL
)
AND (
l.country_code = sqlc.narg('country_code')
OR sqlc.narg('country_code') IS NULL
)
AND (
ces.is_featured = sqlc.narg('is_featured')
OR e.default_is_featured = sqlc.narg('is_featured')
OR sqlc.narg('is_featured') IS NULL
)
AND (
ces.is_active = sqlc.narg('is_active')
OR e.default_is_active = sqlc.narg('is_active')
OR sqlc.narg('is_active') IS NULL
)
AND (
source = sqlc.narg('source')
OR sqlc.narg('source') IS NULL
);
-- name: GetEventsWithSettings :many
SELECT e.*,
ces.company_id,
COALESCE(ces.is_active, e.default_is_active) AS is_active,
COALESCE(ces.is_featured, e.default_is_featured) AS is_featured,
COALESCE(
ces.winning_upper_limit,
e.default_winning_upper_limit
) AS winning_upper_limit,
ces.updated_at,
l.country_code as league_cc,
COALESCE(om.total_outcomes, 0) AS total_outcomes
FROM events e
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
AND ces.company_id = $1
JOIN leagues l ON l.id = e.league_id
LEFT JOIN (
SELECT event_id,
SUM(number_of_outcomes) AS total_outcomes
FROM odds_market
GROUP BY event_id
) om ON om.event_id = e.id
WHERE (
is_live = sqlc.narg('is_live')
OR sqlc.narg('is_live') IS NULL
)
AND (
status = sqlc.narg('status')
OR sqlc.narg('status') IS NULL
)
AND(
league_id = sqlc.narg('league_id')
OR sqlc.narg('league_id') IS NULL
)
AND (
e.sport_id = sqlc.narg('sport_id')
OR sqlc.narg('sport_id') IS NULL
)
AND (
match_name ILIKE '%' || sqlc.narg('query') || '%'
OR league_name ILIKE '%' || sqlc.narg('query') || '%'
OR sqlc.narg('query') IS NULL
)
AND (
start_time < sqlc.narg('last_start_time')
OR sqlc.narg('last_start_time') IS NULL
)
AND (
start_time > sqlc.narg('first_start_time')
OR sqlc.narg('first_start_time') IS NULL
)
AND (
l.country_code = sqlc.narg('country_code')
OR sqlc.narg('country_code') IS NULL
)
AND (
ces.is_featured = sqlc.narg('is_featured')
OR e.default_is_featured = sqlc.narg('is_featured')
OR sqlc.narg('is_featured') IS NULL
)
AND (
ces.is_active = sqlc.narg('is_active')
OR e.default_is_active = sqlc.narg('is_active')
OR sqlc.narg('is_active') IS NULL
)
AND (
source = sqlc.narg('source')
OR sqlc.narg('source') IS NULL
)
ORDER BY start_time ASC
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
-- name: GetEventByID :one -- name: GetEventByID :one
SELECT * SELECT *
FROM event_with_country FROM event_detailed
WHERE id = $1 WHERE id = $1
LIMIT 1; LIMIT 1;
-- name: GetEventBySourceID :one -- name: GetEventBySourceID :one
SELECT * SELECT *
FROM event_with_country FROM event_detailed
WHERE source_event_id = $1 WHERE source_event_id = $1
AND source = $2; AND source = $2;
-- name: GetEventWithSettingByID :one
SELECT e.*,
ces.company_id,
COALESCE(ces.is_active, e.default_is_active) AS is_active,
COALESCE(ces.is_featured, e.default_is_featured) AS is_featured,
COALESCE(
ces.winning_upper_limit,
e.default_winning_upper_limit
) AS winning_upper_limit,
ces.updated_at,
l.country_code as league_cc,
COALESCE(om.total_outcomes, 0) AS total_outcomes
FROM events e
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
AND ces.company_id = $2
JOIN leagues l ON l.id = e.league_id
LEFT JOIN (
SELECT event_id,
SUM(number_of_outcomes) AS total_outcomes
FROM odds_market
GROUP BY event_id
) om ON om.event_id = e.id
WHERE e.id = $1
LIMIT 1;
-- name: GetSportAndLeagueIDs :one -- name: GetSportAndLeagueIDs :one
SELECT sport_id, SELECT sport_id,
league_id league_id

View File

@ -0,0 +1,28 @@
-- Aggregate bet stats per event
-- name: UpdateEventBetStats :exec
INSERT INTO event_bet_stats (
event_id,
number_of_bets,
total_amount,
avg_bet_amount,
total_potential_winnings,
updated_at
)
SELECT bo.event_id,
COUNT(DISTINCT b.id) AS number_of_bets,
COALESCE(SUM(b.amount), 0) AS total_amount,
COALESCE(AVG(b.amount), 0) AS avg_bet_amount,
COALESCE(SUM(b.potential_win), 0) AS total_potential_winnings,
NOW() AS updated_at
FROM bet_outcomes bo
JOIN bets b ON bo.bet_id = b.id
GROUP BY bo.event_id ON CONFLICT (event_id) DO
UPDATE
SET number_of_bets = EXCLUDED.number_of_bets,
total_amount = EXCLUDED.total_amount,
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;

View File

@ -1,20 +1,17 @@
-- name: GetTotalMontlyEventStat :many -- name: GetTotalEventStats :one
SELECT DATE_TRUNC('month', start_time) AS month, SELECT COUNT(*) AS event_count,
COUNT(*) AS event_count
FROM events
JOIN leagues ON leagues.id = events.league_id
WHERE (
events.league_id = sqlc.narg('league_id')
OR sqlc.narg('league_id') IS NULL
)
GROUP BY month
ORDER BY month;
-- name: GetLeagueEventStat :many
SELECT leagues.id,
leagues.name,
COUNT(*) AS total_events,
COUNT(*) FILTER ( COUNT(*) FILTER (
WHERE events.status = 'pending' WHERE events.default_is_active = TRUE
) AS total_active_events,
COUNT(*) FILTER (
WHERE events.default_is_active = FALSE
) AS total_inactive_events,
COUNT(*) FILTER (
WHERE events.default_is_featured = TRUE
) AS total_featured_events,
COUNT(DISTINCT league_id) as total_leagues,
COUNT(*) FILTER (
WHERE events.status = 'upcoming'
) AS pending, ) AS pending,
COUNT(*) FILTER ( COUNT(*) FILTER (
WHERE events.status = 'in_play' WHERE events.status = 'in_play'
@ -52,7 +49,75 @@ SELECT leagues.id,
COUNT(*) FILTER ( COUNT(*) FILTER (
WHERE events.status = 'removed' WHERE events.status = 'removed'
) AS removed ) AS removed
FROM leagues FROM events
JOIN events ON leagues.id = events.league_id WHERE (
GROUP BY leagues.id, events.league_id = sqlc.narg('league_id')
leagues.name; OR sqlc.narg('league_id') IS NULL
)
AND (
events.sport_id = sqlc.narg('sport_id')
OR sqlc.narg('sport_id') IS NULL
);
-- name: GetTotalEventStatsByInterval :many
SELECT DATE_TRUNC(sqlc.narg('interval'), start_time)::timestamp AS date,
COUNT(*) AS event_count,
COUNT(*) FILTER (
WHERE events.default_is_active = TRUE
) AS total_active_events,
COUNT(*) FILTER (
WHERE events.default_is_active = FALSE
) AS total_inactive_events,
COUNT(*) FILTER (
WHERE events.default_is_featured = TRUE
) AS total_featured_events,
COUNT(DISTINCT league_id) as total_leagues,
COUNT(*) FILTER (
WHERE events.status = 'upcoming'
) AS pending,
COUNT(*) FILTER (
WHERE events.status = 'in_play'
) AS in_play,
COUNT(*) FILTER (
WHERE events.status = 'to_be_fixed'
) AS to_be_fixed,
COUNT(*) FILTER (
WHERE events.status = 'ended'
) AS ended,
COUNT(*) FILTER (
WHERE events.status = 'postponed'
) AS postponed,
COUNT(*) FILTER (
WHERE events.status = 'cancelled'
) AS cancelled,
COUNT(*) FILTER (
WHERE events.status = 'walkover'
) AS walkover,
COUNT(*) FILTER (
WHERE events.status = 'interrupted'
) AS interrupted,
COUNT(*) FILTER (
WHERE events.status = 'abandoned'
) AS abandoned,
COUNT(*) FILTER (
WHERE events.status = 'retired'
) AS retired,
COUNT(*) FILTER (
WHERE events.status = 'suspended'
) AS suspended,
COUNT(*) FILTER (
WHERE events.status = 'decided_by_fa'
) AS decided_by_fa,
COUNT(*) FILTER (
WHERE events.status = 'removed'
) AS removed
FROM events
WHERE (
events.league_id = sqlc.narg('league_id')
OR sqlc.narg('league_id') IS NULL
)
AND (
events.sport_id = sqlc.narg('sport_id')
OR sqlc.narg('sport_id') IS NULL
)
GROUP BY date
ORDER BY date;

View File

@ -0,0 +1,171 @@
-- name: SaveTenantEventSettings :exec
INSERT INTO company_event_settings (
company_id,
event_id,
is_active,
is_featured,
winning_upper_limit
)
VALUES ($1, $2, $3, $4, $5) ON CONFLICT(company_id, event_id) DO
UPDATE
SET is_active = EXCLUDED.is_active,
is_featured = EXCLUDED.is_featured,
winning_upper_limit = EXCLUDED.winning_upper_limit;
-- name: GetTotalCompanyEvents :one
SELECT COUNT(*)
FROM events e
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
AND ces.company_id = $1
JOIN leagues l ON l.id = e.league_id
WHERE (
is_live = sqlc.narg('is_live')
OR sqlc.narg('is_live') IS NULL
)
AND (
status = sqlc.narg('status')
OR sqlc.narg('status') IS NULL
)
AND(
league_id = sqlc.narg('league_id')
OR sqlc.narg('league_id') IS NULL
)
AND (
e.sport_id = sqlc.narg('sport_id')
OR sqlc.narg('sport_id') IS NULL
)
AND (
match_name ILIKE '%' || sqlc.narg('query') || '%'
OR league_name ILIKE '%' || sqlc.narg('query') || '%'
OR sqlc.narg('query') IS NULL
)
AND (
start_time < sqlc.narg('last_start_time')
OR sqlc.narg('last_start_time') IS NULL
)
AND (
start_time > sqlc.narg('first_start_time')
OR sqlc.narg('first_start_time') IS NULL
)
AND (
l.country_code = sqlc.narg('country_code')
OR sqlc.narg('country_code') IS NULL
)
AND (
ces.is_featured = sqlc.narg('is_featured')
OR e.default_is_featured = sqlc.narg('is_featured')
OR sqlc.narg('is_featured') IS NULL
)
AND (
ces.is_active = sqlc.narg('is_active')
OR e.default_is_active = sqlc.narg('is_active')
OR sqlc.narg('is_active') IS NULL
)
AND (
source = sqlc.narg('source')
OR sqlc.narg('source') IS NULL
);
-- name: GetEventsWithSettings :many
SELECT e.*,
ces.company_id,
COALESCE(ces.is_active, e.default_is_active) AS is_active,
COALESCE(ces.is_featured, e.default_is_featured) AS is_featured,
COALESCE(
ces.winning_upper_limit,
e.default_winning_upper_limit
) AS winning_upper_limit,
ces.updated_at,
l.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 e
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
AND ces.company_id = $1
LEFT JOIN event_bet_stats ebs ON ebs.event_id = ewc.id
JOIN leagues l ON l.id = e.league_id
LEFT JOIN (
SELECT event_id,
SUM(number_of_outcomes) AS total_outcomes
FROM odds_market
GROUP BY event_id
) om ON om.event_id = e.id
WHERE (
is_live = sqlc.narg('is_live')
OR sqlc.narg('is_live') IS NULL
)
AND (
status = sqlc.narg('status')
OR sqlc.narg('status') IS NULL
)
AND(
league_id = sqlc.narg('league_id')
OR sqlc.narg('league_id') IS NULL
)
AND (
e.sport_id = sqlc.narg('sport_id')
OR sqlc.narg('sport_id') IS NULL
)
AND (
match_name ILIKE '%' || sqlc.narg('query') || '%'
OR league_name ILIKE '%' || sqlc.narg('query') || '%'
OR sqlc.narg('query') IS NULL
)
AND (
start_time < sqlc.narg('last_start_time')
OR sqlc.narg('last_start_time') IS NULL
)
AND (
start_time > sqlc.narg('first_start_time')
OR sqlc.narg('first_start_time') IS NULL
)
AND (
l.country_code = sqlc.narg('country_code')
OR sqlc.narg('country_code') IS NULL
)
AND (
ces.is_featured = sqlc.narg('is_featured')
OR e.default_is_featured = sqlc.narg('is_featured')
OR sqlc.narg('is_featured') IS NULL
)
AND (
ces.is_active = sqlc.narg('is_active')
OR e.default_is_active = sqlc.narg('is_active')
OR sqlc.narg('is_active') IS NULL
)
AND (
source = sqlc.narg('source')
OR sqlc.narg('source') IS NULL
)
ORDER BY start_time ASC
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
-- name: GetEventWithSettingByID :one
SELECT e.*,
ces.company_id,
COALESCE(ces.is_active, e.default_is_active) AS is_active,
COALESCE(ces.is_featured, e.default_is_featured) AS is_featured,
COALESCE(
ces.winning_upper_limit,
e.default_winning_upper_limit
) AS winning_upper_limit,
ces.updated_at,
l.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 e
LEFT JOIN event_bet_stats ebs ON ebs.event_id = ewc.id
AND ces.company_id = $2
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
JOIN leagues l ON l.id = e.league_id
LEFT JOIN (
SELECT event_id,
SUM(number_of_outcomes) AS total_outcomes
FROM odds_market
GROUP BY event_id
) om ON om.event_id = e.id
WHERE e.id = $1
LIMIT 1;

47
db/query/league_stats.sql Normal file
View File

@ -0,0 +1,47 @@
-- name: GetLeagueEventStat :many
SELECT leagues.id,
leagues.name,
COUNT(*) AS total_events,
COUNT(*) FILTER (
WHERE events.status = 'upcoming'
) AS pending,
COUNT(*) FILTER (
WHERE events.status = 'in_play'
) AS in_play,
COUNT(*) FILTER (
WHERE events.status = 'to_be_fixed'
) AS to_be_fixed,
COUNT(*) FILTER (
WHERE events.status = 'ended'
) AS ended,
COUNT(*) FILTER (
WHERE events.status = 'postponed'
) AS postponed,
COUNT(*) FILTER (
WHERE events.status = 'cancelled'
) AS cancelled,
COUNT(*) FILTER (
WHERE events.status = 'walkover'
) AS walkover,
COUNT(*) FILTER (
WHERE events.status = 'interrupted'
) AS interrupted,
COUNT(*) FILTER (
WHERE events.status = 'abandoned'
) AS abandoned,
COUNT(*) FILTER (
WHERE events.status = 'retired'
) AS retired,
COUNT(*) FILTER (
WHERE events.status = 'suspended'
) AS suspended,
COUNT(*) FILTER (
WHERE events.status = 'decided_by_fa'
) AS decided_by_fa,
COUNT(*) FILTER (
WHERE events.status = 'removed'
) AS removed
FROM leagues
JOIN events ON leagues.id = events.league_id
GROUP BY leagues.id,
leagues.name;

View File

@ -1,57 +0,0 @@
-- 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 sqlc.arg('from') AND sqlc.arg('to')
GROUP BY b.company_id, c.name;
-- name: GetBranchWiseReport :many
SELECT
b.branch_id,
br.name AS branch_name,
br.company_id,
COUNT(*) AS total_bets,
COALESCE(SUM(b.amount), 0) AS total_cash_made,
COALESCE(
SUM(
CASE
WHEN 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 sqlc.arg('from') AND sqlc.arg('to')
GROUP BY b.branch_id, br.name, br.company_id;

View File

@ -2,6 +2,7 @@ version: "3.8"
services: services:
postgres: postgres:
container_name: fortunebet-backend-postgres-1
image: postgres:16-alpine image: postgres:16-alpine
ports: ports:
- 5422:5432 - 5422:5432

View File

@ -39,7 +39,7 @@ INSERT INTO bets (
company_id company_id
) )
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id, company_id, amount, total_odds, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at RETURNING id, company_id, amount, total_odds, potential_win, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at
` `
type CreateBetParams struct { type CreateBetParams struct {
@ -70,6 +70,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
&i.CompanyID, &i.CompanyID,
&i.Amount, &i.Amount,
&i.TotalOdds, &i.TotalOdds,
&i.PotentialWin,
&i.Status, &i.Status,
&i.UserID, &i.UserID,
&i.IsShopBet, &i.IsShopBet,
@ -120,7 +121,7 @@ func (q *Queries) DeleteBetOutcome(ctx context.Context, betID int64) error {
} }
const GetAllBets = `-- name: GetAllBets :many const GetAllBets = `-- name: GetAllBets :many
SELECT id, company_id, amount, total_odds, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at, full_name, phone_number, outcomes, company_slug SELECT id, company_id, amount, total_odds, potential_win, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at, full_name, phone_number, outcomes, company_slug
FROM bet_with_outcomes FROM bet_with_outcomes
wHERE ( wHERE (
user_id = $1 user_id = $1
@ -196,6 +197,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi
&i.CompanyID, &i.CompanyID,
&i.Amount, &i.Amount,
&i.TotalOdds, &i.TotalOdds,
&i.PotentialWin,
&i.Status, &i.Status,
&i.UserID, &i.UserID,
&i.IsShopBet, &i.IsShopBet,
@ -221,7 +223,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi
} }
const GetBetByFastCode = `-- name: GetBetByFastCode :one const GetBetByFastCode = `-- name: GetBetByFastCode :one
SELECT id, company_id, amount, total_odds, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at, full_name, phone_number, outcomes, company_slug SELECT id, company_id, amount, total_odds, potential_win, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at, full_name, phone_number, outcomes, company_slug
FROM bet_with_outcomes FROM bet_with_outcomes
WHERE fast_code = $1 WHERE fast_code = $1
LIMIT 1 LIMIT 1
@ -235,6 +237,7 @@ func (q *Queries) GetBetByFastCode(ctx context.Context, fastCode string) (BetWit
&i.CompanyID, &i.CompanyID,
&i.Amount, &i.Amount,
&i.TotalOdds, &i.TotalOdds,
&i.PotentialWin,
&i.Status, &i.Status,
&i.UserID, &i.UserID,
&i.IsShopBet, &i.IsShopBet,
@ -253,7 +256,7 @@ func (q *Queries) GetBetByFastCode(ctx context.Context, fastCode string) (BetWit
} }
const GetBetByID = `-- name: GetBetByID :one const GetBetByID = `-- name: GetBetByID :one
SELECT id, company_id, amount, total_odds, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at, full_name, phone_number, outcomes, company_slug SELECT id, company_id, amount, total_odds, potential_win, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at, full_name, phone_number, outcomes, company_slug
FROM bet_with_outcomes FROM bet_with_outcomes
WHERE id = $1 WHERE id = $1
` `
@ -266,6 +269,7 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err
&i.CompanyID, &i.CompanyID,
&i.Amount, &i.Amount,
&i.TotalOdds, &i.TotalOdds,
&i.PotentialWin,
&i.Status, &i.Status,
&i.UserID, &i.UserID,
&i.IsShopBet, &i.IsShopBet,
@ -284,7 +288,7 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err
} }
const GetBetByUserID = `-- name: GetBetByUserID :many const GetBetByUserID = `-- name: GetBetByUserID :many
SELECT id, company_id, amount, total_odds, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at, full_name, phone_number, outcomes, company_slug SELECT id, company_id, amount, total_odds, potential_win, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at, full_name, phone_number, outcomes, company_slug
FROM bet_with_outcomes FROM bet_with_outcomes
WHERE user_id = $1 WHERE user_id = $1
` `
@ -303,6 +307,7 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID int64) ([]BetWithOu
&i.CompanyID, &i.CompanyID,
&i.Amount, &i.Amount,
&i.TotalOdds, &i.TotalOdds,
&i.PotentialWin,
&i.Status, &i.Status,
&i.UserID, &i.UserID,
&i.IsShopBet, &i.IsShopBet,
@ -570,7 +575,7 @@ func (q *Queries) GetBetOutcomeViewByEventID(ctx context.Context, arg GetBetOutc
} }
const GetBetsForCashback = `-- name: GetBetsForCashback :many const GetBetsForCashback = `-- name: GetBetsForCashback :many
SELECT id, company_id, amount, total_odds, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at, full_name, phone_number, outcomes, company_slug SELECT id, company_id, amount, total_odds, potential_win, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at, full_name, phone_number, outcomes, company_slug
FROM bet_with_outcomes FROM bet_with_outcomes
WHERE status = 2 WHERE status = 2
AND processed = false AND processed = false
@ -590,6 +595,7 @@ func (q *Queries) GetBetsForCashback(ctx context.Context) ([]BetWithOutcome, err
&i.CompanyID, &i.CompanyID,
&i.Amount, &i.Amount,
&i.TotalOdds, &i.TotalOdds,
&i.PotentialWin,
&i.Status, &i.Status,
&i.UserID, &i.UserID,
&i.IsShopBet, &i.IsShopBet,

View File

@ -0,0 +1,88 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: branch_stats.sql
package dbgen
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const GetBranchStats = `-- name: GetBranchStats :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
`
type GetBranchStatsParams struct {
From pgtype.Timestamp `json:"from"`
To pgtype.Timestamp `json:"to"`
}
type GetBranchStatsRow 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) GetBranchStats(ctx context.Context, arg GetBranchStatsParams) ([]GetBranchStatsRow, error) {
rows, err := q.db.Query(ctx, GetBranchStats, arg.From, arg.To)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetBranchStatsRow
for rows.Next() {
var i GetBranchStatsRow
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
}

View File

@ -0,0 +1,59 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: company_stats.sql
package dbgen
import (
"context"
)
const UpdateCompanyStats = `-- name: UpdateCompanyStats :exec
INSERT INTO company_stats (
company_id,
total_bets,
total_cash_made,
total_cash_backs,
updated_at
)
SELECT b.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 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,
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
UPDATE
SET total_bets = EXCLUDED.total_bets,
total_cash_made = EXCLUDED.total_cash_made,
total_cash_out = EXCLUDED.total_cash_out,
total_cash_back = EXCLUDED.total_cash_back,
updated_at = EXCLUDED.updated_at
`
// Aggregate company stats
func (q *Queries) UpdateCompanyStats(ctx context.Context) error {
_, err := q.db.Exec(ctx, UpdateCompanyStats)
return err
}

View File

@ -22,8 +22,8 @@ func (q *Queries) DeleteEvent(ctx context.Context, id int64) error {
} }
const GetAllEvents = `-- name: GetAllEvents :many const GetAllEvents = `-- name: GetAllEvents :many
SELECT id, source_event_id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, updated_at, source, default_is_active, default_is_featured, default_winning_upper_limit, is_monitored, league_cc, total_outcomes SELECT id, source_event_id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, updated_at, source, default_is_active, default_is_featured, default_winning_upper_limit, is_monitored, league_cc, total_outcomes, number_of_bets, total_amount, avg_bet_amount, total_potential_winnings
FROM event_with_country FROM event_detailed
WHERE ( WHERE (
is_live = $1 is_live = $1
OR $1 IS NULL OR $1 IS NULL
@ -79,7 +79,8 @@ type GetAllEventsParams struct {
Limit pgtype.Int4 `json:"limit"` Limit pgtype.Int4 `json:"limit"`
} }
func (q *Queries) GetAllEvents(ctx context.Context, arg GetAllEventsParams) ([]EventWithCountry, error) { // TODO: Figure out how to make this code reusable. SQLC prohibits CTEs within multiple queries
func (q *Queries) GetAllEvents(ctx context.Context, arg GetAllEventsParams) ([]EventDetailed, error) {
rows, err := q.db.Query(ctx, GetAllEvents, rows, err := q.db.Query(ctx, GetAllEvents,
arg.IsLive, arg.IsLive,
arg.Status, arg.Status,
@ -97,9 +98,9 @@ func (q *Queries) GetAllEvents(ctx context.Context, arg GetAllEventsParams) ([]E
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
var items []EventWithCountry var items []EventDetailed
for rows.Next() { for rows.Next() {
var i EventWithCountry var i EventDetailed
if err := rows.Scan( if err := rows.Scan(
&i.ID, &i.ID,
&i.SourceEventID, &i.SourceEventID,
@ -130,6 +131,10 @@ func (q *Queries) GetAllEvents(ctx context.Context, arg GetAllEventsParams) ([]E
&i.IsMonitored, &i.IsMonitored,
&i.LeagueCc, &i.LeagueCc,
&i.TotalOutcomes, &i.TotalOutcomes,
&i.NumberOfBets,
&i.TotalAmount,
&i.AvgBetAmount,
&i.TotalPotentialWinnings,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -142,15 +147,15 @@ func (q *Queries) GetAllEvents(ctx context.Context, arg GetAllEventsParams) ([]E
} }
const GetEventByID = `-- name: GetEventByID :one const GetEventByID = `-- name: GetEventByID :one
SELECT id, source_event_id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, updated_at, source, default_is_active, default_is_featured, default_winning_upper_limit, is_monitored, league_cc, total_outcomes SELECT id, source_event_id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, updated_at, source, default_is_active, default_is_featured, default_winning_upper_limit, is_monitored, league_cc, total_outcomes, number_of_bets, total_amount, avg_bet_amount, total_potential_winnings
FROM event_with_country FROM event_detailed
WHERE id = $1 WHERE id = $1
LIMIT 1 LIMIT 1
` `
func (q *Queries) GetEventByID(ctx context.Context, id int64) (EventWithCountry, error) { func (q *Queries) GetEventByID(ctx context.Context, id int64) (EventDetailed, error) {
row := q.db.QueryRow(ctx, GetEventByID, id) row := q.db.QueryRow(ctx, GetEventByID, id)
var i EventWithCountry var i EventDetailed
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.SourceEventID, &i.SourceEventID,
@ -181,13 +186,17 @@ func (q *Queries) GetEventByID(ctx context.Context, id int64) (EventWithCountry,
&i.IsMonitored, &i.IsMonitored,
&i.LeagueCc, &i.LeagueCc,
&i.TotalOutcomes, &i.TotalOutcomes,
&i.NumberOfBets,
&i.TotalAmount,
&i.AvgBetAmount,
&i.TotalPotentialWinnings,
) )
return i, err return i, err
} }
const GetEventBySourceID = `-- name: GetEventBySourceID :one const GetEventBySourceID = `-- name: GetEventBySourceID :one
SELECT id, source_event_id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, updated_at, source, default_is_active, default_is_featured, default_winning_upper_limit, is_monitored, league_cc, total_outcomes SELECT id, source_event_id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, updated_at, source, default_is_active, default_is_featured, default_winning_upper_limit, is_monitored, league_cc, total_outcomes, number_of_bets, total_amount, avg_bet_amount, total_potential_winnings
FROM event_with_country FROM event_detailed
WHERE source_event_id = $1 WHERE source_event_id = $1
AND source = $2 AND source = $2
` `
@ -197,9 +206,9 @@ type GetEventBySourceIDParams struct {
Source string `json:"source"` Source string `json:"source"`
} }
func (q *Queries) GetEventBySourceID(ctx context.Context, arg GetEventBySourceIDParams) (EventWithCountry, error) { func (q *Queries) GetEventBySourceID(ctx context.Context, arg GetEventBySourceIDParams) (EventDetailed, error) {
row := q.db.QueryRow(ctx, GetEventBySourceID, arg.SourceEventID, arg.Source) row := q.db.QueryRow(ctx, GetEventBySourceID, arg.SourceEventID, arg.Source)
var i EventWithCountry var i EventDetailed
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.SourceEventID, &i.SourceEventID,
@ -230,317 +239,14 @@ func (q *Queries) GetEventBySourceID(ctx context.Context, arg GetEventBySourceID
&i.IsMonitored, &i.IsMonitored,
&i.LeagueCc, &i.LeagueCc,
&i.TotalOutcomes, &i.TotalOutcomes,
&i.NumberOfBets,
&i.TotalAmount,
&i.AvgBetAmount,
&i.TotalPotentialWinnings,
) )
return i, err return i, err
} }
const GetEventWithSettingByID = `-- name: GetEventWithSettingByID :one
SELECT e.id, e.source_event_id, e.sport_id, e.match_name, e.home_team, e.away_team, e.home_team_id, e.away_team_id, e.home_kit_image, e.away_kit_image, e.league_id, e.league_name, e.start_time, e.score, e.match_minute, e.timer_status, e.added_time, e.match_period, e.is_live, e.status, e.fetched_at, e.updated_at, e.source, e.default_is_active, e.default_is_featured, e.default_winning_upper_limit, e.is_monitored,
ces.company_id,
COALESCE(ces.is_active, e.default_is_active) AS is_active,
COALESCE(ces.is_featured, e.default_is_featured) AS is_featured,
COALESCE(
ces.winning_upper_limit,
e.default_winning_upper_limit
) AS winning_upper_limit,
ces.updated_at,
l.country_code as league_cc,
COALESCE(om.total_outcomes, 0) AS total_outcomes
FROM events e
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
AND ces.company_id = $2
JOIN leagues l ON l.id = e.league_id
LEFT JOIN (
SELECT event_id,
SUM(number_of_outcomes) AS total_outcomes
FROM odds_market
GROUP BY event_id
) om ON om.event_id = e.id
WHERE e.id = $1
LIMIT 1
`
type GetEventWithSettingByIDParams struct {
ID int64 `json:"id"`
CompanyID int64 `json:"company_id"`
}
type GetEventWithSettingByIDRow 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"`
CompanyID pgtype.Int8 `json:"company_id"`
IsActive bool `json:"is_active"`
IsFeatured bool `json:"is_featured"`
WinningUpperLimit int64 `json:"winning_upper_limit"`
UpdatedAt_2 pgtype.Timestamp `json:"updated_at_2"`
LeagueCc pgtype.Text `json:"league_cc"`
TotalOutcomes int64 `json:"total_outcomes"`
}
func (q *Queries) GetEventWithSettingByID(ctx context.Context, arg GetEventWithSettingByIDParams) (GetEventWithSettingByIDRow, error) {
row := q.db.QueryRow(ctx, GetEventWithSettingByID, arg.ID, arg.CompanyID)
var i GetEventWithSettingByIDRow
err := row.Scan(
&i.ID,
&i.SourceEventID,
&i.SportID,
&i.MatchName,
&i.HomeTeam,
&i.AwayTeam,
&i.HomeTeamID,
&i.AwayTeamID,
&i.HomeKitImage,
&i.AwayKitImage,
&i.LeagueID,
&i.LeagueName,
&i.StartTime,
&i.Score,
&i.MatchMinute,
&i.TimerStatus,
&i.AddedTime,
&i.MatchPeriod,
&i.IsLive,
&i.Status,
&i.FetchedAt,
&i.UpdatedAt,
&i.Source,
&i.DefaultIsActive,
&i.DefaultIsFeatured,
&i.DefaultWinningUpperLimit,
&i.IsMonitored,
&i.CompanyID,
&i.IsActive,
&i.IsFeatured,
&i.WinningUpperLimit,
&i.UpdatedAt_2,
&i.LeagueCc,
&i.TotalOutcomes,
)
return i, err
}
const GetEventsWithSettings = `-- name: GetEventsWithSettings :many
SELECT e.id, e.source_event_id, e.sport_id, e.match_name, e.home_team, e.away_team, e.home_team_id, e.away_team_id, e.home_kit_image, e.away_kit_image, e.league_id, e.league_name, e.start_time, e.score, e.match_minute, e.timer_status, e.added_time, e.match_period, e.is_live, e.status, e.fetched_at, e.updated_at, e.source, e.default_is_active, e.default_is_featured, e.default_winning_upper_limit, e.is_monitored,
ces.company_id,
COALESCE(ces.is_active, e.default_is_active) AS is_active,
COALESCE(ces.is_featured, e.default_is_featured) AS is_featured,
COALESCE(
ces.winning_upper_limit,
e.default_winning_upper_limit
) AS winning_upper_limit,
ces.updated_at,
l.country_code as league_cc,
COALESCE(om.total_outcomes, 0) AS total_outcomes
FROM events e
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
AND ces.company_id = $1
JOIN leagues l ON l.id = e.league_id
LEFT JOIN (
SELECT event_id,
SUM(number_of_outcomes) AS total_outcomes
FROM odds_market
GROUP BY event_id
) om ON om.event_id = e.id
WHERE (
is_live = $2
OR $2 IS NULL
)
AND (
status = $3
OR $3 IS NULL
)
AND(
league_id = $4
OR $4 IS NULL
)
AND (
e.sport_id = $5
OR $5 IS NULL
)
AND (
match_name ILIKE '%' || $6 || '%'
OR league_name ILIKE '%' || $6 || '%'
OR $6 IS NULL
)
AND (
start_time < $7
OR $7 IS NULL
)
AND (
start_time > $8
OR $8 IS NULL
)
AND (
l.country_code = $9
OR $9 IS NULL
)
AND (
ces.is_featured = $10
OR e.default_is_featured = $10
OR $10 IS NULL
)
AND (
ces.is_active = $11
OR e.default_is_active = $11
OR $11 IS NULL
)
AND (
source = $12
OR $12 IS NULL
)
ORDER BY start_time ASC
LIMIT $14 OFFSET $13
`
type GetEventsWithSettingsParams struct {
CompanyID int64 `json:"company_id"`
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
LeagueID pgtype.Int8 `json:"league_id"`
SportID pgtype.Int4 `json:"sport_id"`
Query pgtype.Text `json:"query"`
LastStartTime pgtype.Timestamp `json:"last_start_time"`
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
CountryCode pgtype.Text `json:"country_code"`
IsFeatured pgtype.Bool `json:"is_featured"`
IsActive pgtype.Bool `json:"is_active"`
Source pgtype.Text `json:"source"`
Offset pgtype.Int4 `json:"offset"`
Limit pgtype.Int4 `json:"limit"`
}
type GetEventsWithSettingsRow 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"`
CompanyID pgtype.Int8 `json:"company_id"`
IsActive bool `json:"is_active"`
IsFeatured bool `json:"is_featured"`
WinningUpperLimit int64 `json:"winning_upper_limit"`
UpdatedAt_2 pgtype.Timestamp `json:"updated_at_2"`
LeagueCc pgtype.Text `json:"league_cc"`
TotalOutcomes int64 `json:"total_outcomes"`
}
func (q *Queries) GetEventsWithSettings(ctx context.Context, arg GetEventsWithSettingsParams) ([]GetEventsWithSettingsRow, error) {
rows, err := q.db.Query(ctx, GetEventsWithSettings,
arg.CompanyID,
arg.IsLive,
arg.Status,
arg.LeagueID,
arg.SportID,
arg.Query,
arg.LastStartTime,
arg.FirstStartTime,
arg.CountryCode,
arg.IsFeatured,
arg.IsActive,
arg.Source,
arg.Offset,
arg.Limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetEventsWithSettingsRow
for rows.Next() {
var i GetEventsWithSettingsRow
if err := rows.Scan(
&i.ID,
&i.SourceEventID,
&i.SportID,
&i.MatchName,
&i.HomeTeam,
&i.AwayTeam,
&i.HomeTeamID,
&i.AwayTeamID,
&i.HomeKitImage,
&i.AwayKitImage,
&i.LeagueID,
&i.LeagueName,
&i.StartTime,
&i.Score,
&i.MatchMinute,
&i.TimerStatus,
&i.AddedTime,
&i.MatchPeriod,
&i.IsLive,
&i.Status,
&i.FetchedAt,
&i.UpdatedAt,
&i.Source,
&i.DefaultIsActive,
&i.DefaultIsFeatured,
&i.DefaultWinningUpperLimit,
&i.IsMonitored,
&i.CompanyID,
&i.IsActive,
&i.IsFeatured,
&i.WinningUpperLimit,
&i.UpdatedAt_2,
&i.LeagueCc,
&i.TotalOutcomes,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const GetSportAndLeagueIDs = `-- name: GetSportAndLeagueIDs :one const GetSportAndLeagueIDs = `-- name: GetSportAndLeagueIDs :one
SELECT sport_id, SELECT sport_id,
league_id league_id
@ -560,99 +266,9 @@ func (q *Queries) GetSportAndLeagueIDs(ctx context.Context, id int64) (GetSportA
return i, err return i, err
} }
const GetTotalCompanyEvents = `-- name: GetTotalCompanyEvents :one
SELECT COUNT(*)
FROM events e
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
AND ces.company_id = $1
JOIN leagues l ON l.id = e.league_id
WHERE (
is_live = $2
OR $2 IS NULL
)
AND (
status = $3
OR $3 IS NULL
)
AND(
league_id = $4
OR $4 IS NULL
)
AND (
e.sport_id = $5
OR $5 IS NULL
)
AND (
match_name ILIKE '%' || $6 || '%'
OR league_name ILIKE '%' || $6 || '%'
OR $6 IS NULL
)
AND (
start_time < $7
OR $7 IS NULL
)
AND (
start_time > $8
OR $8 IS NULL
)
AND (
l.country_code = $9
OR $9 IS NULL
)
AND (
ces.is_featured = $10
OR e.default_is_featured = $10
OR $10 IS NULL
)
AND (
ces.is_active = $11
OR e.default_is_active = $11
OR $11 IS NULL
)
AND (
source = $12
OR $12 IS NULL
)
`
type GetTotalCompanyEventsParams struct {
CompanyID int64 `json:"company_id"`
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
LeagueID pgtype.Int8 `json:"league_id"`
SportID pgtype.Int4 `json:"sport_id"`
Query pgtype.Text `json:"query"`
LastStartTime pgtype.Timestamp `json:"last_start_time"`
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
CountryCode pgtype.Text `json:"country_code"`
IsFeatured pgtype.Bool `json:"is_featured"`
IsActive pgtype.Bool `json:"is_active"`
Source pgtype.Text `json:"source"`
}
func (q *Queries) GetTotalCompanyEvents(ctx context.Context, arg GetTotalCompanyEventsParams) (int64, error) {
row := q.db.QueryRow(ctx, GetTotalCompanyEvents,
arg.CompanyID,
arg.IsLive,
arg.Status,
arg.LeagueID,
arg.SportID,
arg.Query,
arg.LastStartTime,
arg.FirstStartTime,
arg.CountryCode,
arg.IsFeatured,
arg.IsActive,
arg.Source,
)
var count int64
err := row.Scan(&count)
return count, err
}
const GetTotalEvents = `-- name: GetTotalEvents :one const GetTotalEvents = `-- name: GetTotalEvents :one
SELECT COUNT(*) SELECT COUNT(*)
FROM event_with_country FROM event_detailed
WHERE ( WHERE (
is_live = $1 is_live = $1
OR $1 IS NULL OR $1 IS NULL
@ -837,7 +453,7 @@ func (q *Queries) IsEventMonitored(ctx context.Context, id int64) (bool, error)
const ListLiveEvents = `-- name: ListLiveEvents :many const ListLiveEvents = `-- name: ListLiveEvents :many
SELECT id SELECT id
FROM event_with_country FROM event_detailed
WHERE is_live = true WHERE is_live = true
` `
@ -861,40 +477,6 @@ func (q *Queries) ListLiveEvents(ctx context.Context) ([]int64, error) {
return items, nil return items, nil
} }
const SaveTenantEventSettings = `-- name: SaveTenantEventSettings :exec
INSERT INTO company_event_settings (
company_id,
event_id,
is_active,
is_featured,
winning_upper_limit
)
VALUES ($1, $2, $3, $4, $5) ON CONFLICT(company_id, event_id) DO
UPDATE
SET is_active = EXCLUDED.is_active,
is_featured = EXCLUDED.is_featured,
winning_upper_limit = EXCLUDED.winning_upper_limit
`
type SaveTenantEventSettingsParams struct {
CompanyID int64 `json:"company_id"`
EventID int64 `json:"event_id"`
IsActive pgtype.Bool `json:"is_active"`
IsFeatured pgtype.Bool `json:"is_featured"`
WinningUpperLimit pgtype.Int8 `json:"winning_upper_limit"`
}
func (q *Queries) SaveTenantEventSettings(ctx context.Context, arg SaveTenantEventSettingsParams) error {
_, err := q.db.Exec(ctx, SaveTenantEventSettings,
arg.CompanyID,
arg.EventID,
arg.IsActive,
arg.IsFeatured,
arg.WinningUpperLimit,
)
return err
}
const UpdateEventMonitored = `-- name: UpdateEventMonitored :exec const UpdateEventMonitored = `-- name: UpdateEventMonitored :exec
UPDATE events UPDATE events
SET is_monitored = $1, SET is_monitored = $1,

View File

@ -0,0 +1,51 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: events_bet_stats.sql
package dbgen
import (
"context"
)
const UpdateEventBetStats = `-- name: UpdateEventBetStats :exec
INSERT INTO event_bet_stats (
event_id,
number_of_bets,
total_amount,
avg_bet_amount,
total_potential_winnings,
updated_at
)
SELECT bo.event_id,
COUNT(DISTINCT b.id) AS number_of_bets,
COALESCE(SUM(b.amount), 0) AS total_amount,
COALESCE(AVG(b.amount), 0) AS avg_bet_amount,
COALESCE(SUM(b.potential_win), 0) AS total_potential_winnings,
NOW() AS updated_at
FROM bet_outcomes bo
JOIN bets b ON bo.bet_id = b.id
GROUP BY bo.event_id ON CONFLICT (event_id) DO
UPDATE
SET number_of_bets = EXCLUDED.number_of_bets,
total_amount = EXCLUDED.total_amount,
avg_bet_amount = EXCLUDED.avg_bet_amount,
total_potential_winnings = EXCLUDED.total_potential_winnings,
updated_at = EXCLUDED.updated_at
`
// Aggregate bet stats per event
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
}

View File

@ -11,12 +11,20 @@ import (
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
) )
const GetLeagueEventStat = `-- name: GetLeagueEventStat :many const GetTotalEventStats = `-- name: GetTotalEventStats :one
SELECT leagues.id, SELECT COUNT(*) AS event_count,
leagues.name,
COUNT(*) AS total_events,
COUNT(*) FILTER ( COUNT(*) FILTER (
WHERE events.status = 'pending' WHERE events.default_is_active = TRUE
) AS total_active_events,
COUNT(*) FILTER (
WHERE events.default_is_active = FALSE
) AS total_inactive_events,
COUNT(*) FILTER (
WHERE events.default_is_featured = TRUE
) AS total_featured_events,
COUNT(DISTINCT league_id) as total_leagues,
COUNT(*) FILTER (
WHERE events.status = 'upcoming'
) AS pending, ) AS pending,
COUNT(*) FILTER ( COUNT(*) FILTER (
WHERE events.status = 'in_play' WHERE events.status = 'in_play'
@ -54,44 +62,178 @@ SELECT leagues.id,
COUNT(*) FILTER ( COUNT(*) FILTER (
WHERE events.status = 'removed' WHERE events.status = 'removed'
) AS removed ) AS removed
FROM leagues FROM events
JOIN events ON leagues.id = events.league_id WHERE (
GROUP BY leagues.id, events.league_id = $1
leagues.name OR $1 IS NULL
)
AND (
events.sport_id = $2
OR $2 IS NULL
)
` `
type GetLeagueEventStatRow struct { type GetTotalEventStatsParams struct {
ID int64 `json:"id"` LeagueID pgtype.Int8 `json:"league_id"`
Name string `json:"name"` SportID pgtype.Int4 `json:"sport_id"`
TotalEvents int64 `json:"total_events"`
Pending int64 `json:"pending"`
InPlay int64 `json:"in_play"`
ToBeFixed int64 `json:"to_be_fixed"`
Ended int64 `json:"ended"`
Postponed int64 `json:"postponed"`
Cancelled int64 `json:"cancelled"`
Walkover int64 `json:"walkover"`
Interrupted int64 `json:"interrupted"`
Abandoned int64 `json:"abandoned"`
Retired int64 `json:"retired"`
Suspended int64 `json:"suspended"`
DecidedByFa int64 `json:"decided_by_fa"`
Removed int64 `json:"removed"`
} }
func (q *Queries) GetLeagueEventStat(ctx context.Context) ([]GetLeagueEventStatRow, error) { type GetTotalEventStatsRow struct {
rows, err := q.db.Query(ctx, GetLeagueEventStat) EventCount int64 `json:"event_count"`
TotalActiveEvents int64 `json:"total_active_events"`
TotalInactiveEvents int64 `json:"total_inactive_events"`
TotalFeaturedEvents int64 `json:"total_featured_events"`
TotalLeagues int64 `json:"total_leagues"`
Pending int64 `json:"pending"`
InPlay int64 `json:"in_play"`
ToBeFixed int64 `json:"to_be_fixed"`
Ended int64 `json:"ended"`
Postponed int64 `json:"postponed"`
Cancelled int64 `json:"cancelled"`
Walkover int64 `json:"walkover"`
Interrupted int64 `json:"interrupted"`
Abandoned int64 `json:"abandoned"`
Retired int64 `json:"retired"`
Suspended int64 `json:"suspended"`
DecidedByFa int64 `json:"decided_by_fa"`
Removed int64 `json:"removed"`
}
func (q *Queries) GetTotalEventStats(ctx context.Context, arg GetTotalEventStatsParams) (GetTotalEventStatsRow, error) {
row := q.db.QueryRow(ctx, GetTotalEventStats, arg.LeagueID, arg.SportID)
var i GetTotalEventStatsRow
err := row.Scan(
&i.EventCount,
&i.TotalActiveEvents,
&i.TotalInactiveEvents,
&i.TotalFeaturedEvents,
&i.TotalLeagues,
&i.Pending,
&i.InPlay,
&i.ToBeFixed,
&i.Ended,
&i.Postponed,
&i.Cancelled,
&i.Walkover,
&i.Interrupted,
&i.Abandoned,
&i.Retired,
&i.Suspended,
&i.DecidedByFa,
&i.Removed,
)
return i, err
}
const GetTotalEventStatsByInterval = `-- name: GetTotalEventStatsByInterval :many
SELECT DATE_TRUNC($1, start_time)::timestamp AS date,
COUNT(*) AS event_count,
COUNT(*) FILTER (
WHERE events.default_is_active = TRUE
) AS total_active_events,
COUNT(*) FILTER (
WHERE events.default_is_active = FALSE
) AS total_inactive_events,
COUNT(*) FILTER (
WHERE events.default_is_featured = TRUE
) AS total_featured_events,
COUNT(DISTINCT league_id) as total_leagues,
COUNT(*) FILTER (
WHERE events.status = 'upcoming'
) AS pending,
COUNT(*) FILTER (
WHERE events.status = 'in_play'
) AS in_play,
COUNT(*) FILTER (
WHERE events.status = 'to_be_fixed'
) AS to_be_fixed,
COUNT(*) FILTER (
WHERE events.status = 'ended'
) AS ended,
COUNT(*) FILTER (
WHERE events.status = 'postponed'
) AS postponed,
COUNT(*) FILTER (
WHERE events.status = 'cancelled'
) AS cancelled,
COUNT(*) FILTER (
WHERE events.status = 'walkover'
) AS walkover,
COUNT(*) FILTER (
WHERE events.status = 'interrupted'
) AS interrupted,
COUNT(*) FILTER (
WHERE events.status = 'abandoned'
) AS abandoned,
COUNT(*) FILTER (
WHERE events.status = 'retired'
) AS retired,
COUNT(*) FILTER (
WHERE events.status = 'suspended'
) AS suspended,
COUNT(*) FILTER (
WHERE events.status = 'decided_by_fa'
) AS decided_by_fa,
COUNT(*) FILTER (
WHERE events.status = 'removed'
) AS removed
FROM events
WHERE (
events.league_id = $2
OR $2 IS NULL
)
AND (
events.sport_id = $3
OR $3 IS NULL
)
GROUP BY date
ORDER BY date
`
type GetTotalEventStatsByIntervalParams struct {
Interval pgtype.Text `json:"interval"`
LeagueID pgtype.Int8 `json:"league_id"`
SportID pgtype.Int4 `json:"sport_id"`
}
type GetTotalEventStatsByIntervalRow struct {
Date pgtype.Timestamp `json:"date"`
EventCount int64 `json:"event_count"`
TotalActiveEvents int64 `json:"total_active_events"`
TotalInactiveEvents int64 `json:"total_inactive_events"`
TotalFeaturedEvents int64 `json:"total_featured_events"`
TotalLeagues int64 `json:"total_leagues"`
Pending int64 `json:"pending"`
InPlay int64 `json:"in_play"`
ToBeFixed int64 `json:"to_be_fixed"`
Ended int64 `json:"ended"`
Postponed int64 `json:"postponed"`
Cancelled int64 `json:"cancelled"`
Walkover int64 `json:"walkover"`
Interrupted int64 `json:"interrupted"`
Abandoned int64 `json:"abandoned"`
Retired int64 `json:"retired"`
Suspended int64 `json:"suspended"`
DecidedByFa int64 `json:"decided_by_fa"`
Removed int64 `json:"removed"`
}
func (q *Queries) GetTotalEventStatsByInterval(ctx context.Context, arg GetTotalEventStatsByIntervalParams) ([]GetTotalEventStatsByIntervalRow, error) {
rows, err := q.db.Query(ctx, GetTotalEventStatsByInterval, arg.Interval, arg.LeagueID, arg.SportID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
var items []GetLeagueEventStatRow var items []GetTotalEventStatsByIntervalRow
for rows.Next() { for rows.Next() {
var i GetLeagueEventStatRow var i GetTotalEventStatsByIntervalRow
if err := rows.Scan( if err := rows.Scan(
&i.ID, &i.Date,
&i.Name, &i.EventCount,
&i.TotalEvents, &i.TotalActiveEvents,
&i.TotalInactiveEvents,
&i.TotalFeaturedEvents,
&i.TotalLeagues,
&i.Pending, &i.Pending,
&i.InPlay, &i.InPlay,
&i.ToBeFixed, &i.ToBeFixed,
@ -115,41 +257,3 @@ func (q *Queries) GetLeagueEventStat(ctx context.Context) ([]GetLeagueEventStatR
} }
return items, nil return items, nil
} }
const GetTotalMontlyEventStat = `-- name: GetTotalMontlyEventStat :many
SELECT DATE_TRUNC('month', start_time) AS month,
COUNT(*) AS event_count
FROM events
JOIN leagues ON leagues.id = events.league_id
WHERE (
events.league_id = $1
OR $1 IS NULL
)
GROUP BY month
ORDER BY month
`
type GetTotalMontlyEventStatRow struct {
Month pgtype.Interval `json:"month"`
EventCount int64 `json:"event_count"`
}
func (q *Queries) GetTotalMontlyEventStat(ctx context.Context, leagueID pgtype.Int8) ([]GetTotalMontlyEventStatRow, error) {
rows, err := q.db.Query(ctx, GetTotalMontlyEventStat, leagueID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetTotalMontlyEventStatRow
for rows.Next() {
var i GetTotalMontlyEventStatRow
if err := rows.Scan(&i.Month, &i.EventCount); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@ -0,0 +1,469 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: events_with_settings.sql
package dbgen
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const GetEventWithSettingByID = `-- name: GetEventWithSettingByID :one
SELECT e.id, e.source_event_id, e.sport_id, e.match_name, e.home_team, e.away_team, e.home_team_id, e.away_team_id, e.home_kit_image, e.away_kit_image, e.league_id, e.league_name, e.start_time, e.score, e.match_minute, e.timer_status, e.added_time, e.match_period, e.is_live, e.status, e.fetched_at, e.updated_at, e.source, e.default_is_active, e.default_is_featured, e.default_winning_upper_limit, e.is_monitored,
ces.company_id,
COALESCE(ces.is_active, e.default_is_active) AS is_active,
COALESCE(ces.is_featured, e.default_is_featured) AS is_featured,
COALESCE(
ces.winning_upper_limit,
e.default_winning_upper_limit
) AS winning_upper_limit,
ces.updated_at,
l.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 e
LEFT JOIN event_bet_stats ebs ON ebs.event_id = ewc.id
AND ces.company_id = $2
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
JOIN leagues l ON l.id = e.league_id
LEFT JOIN (
SELECT event_id,
SUM(number_of_outcomes) AS total_outcomes
FROM odds_market
GROUP BY event_id
) om ON om.event_id = e.id
WHERE e.id = $1
LIMIT 1
`
type GetEventWithSettingByIDParams struct {
ID int64 `json:"id"`
CompanyID int64 `json:"company_id"`
}
type GetEventWithSettingByIDRow 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"`
CompanyID pgtype.Int8 `json:"company_id"`
IsActive bool `json:"is_active"`
IsFeatured bool `json:"is_featured"`
WinningUpperLimit int64 `json:"winning_upper_limit"`
UpdatedAt_2 pgtype.Timestamp `json:"updated_at_2"`
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"`
}
func (q *Queries) GetEventWithSettingByID(ctx context.Context, arg GetEventWithSettingByIDParams) (GetEventWithSettingByIDRow, error) {
row := q.db.QueryRow(ctx, GetEventWithSettingByID, arg.ID, arg.CompanyID)
var i GetEventWithSettingByIDRow
err := row.Scan(
&i.ID,
&i.SourceEventID,
&i.SportID,
&i.MatchName,
&i.HomeTeam,
&i.AwayTeam,
&i.HomeTeamID,
&i.AwayTeamID,
&i.HomeKitImage,
&i.AwayKitImage,
&i.LeagueID,
&i.LeagueName,
&i.StartTime,
&i.Score,
&i.MatchMinute,
&i.TimerStatus,
&i.AddedTime,
&i.MatchPeriod,
&i.IsLive,
&i.Status,
&i.FetchedAt,
&i.UpdatedAt,
&i.Source,
&i.DefaultIsActive,
&i.DefaultIsFeatured,
&i.DefaultWinningUpperLimit,
&i.IsMonitored,
&i.CompanyID,
&i.IsActive,
&i.IsFeatured,
&i.WinningUpperLimit,
&i.UpdatedAt_2,
&i.LeagueCc,
&i.TotalOutcomes,
&i.NumberOfBets,
&i.TotalAmount,
&i.AvgBetAmount,
&i.TotalPotentialWinnings,
)
return i, err
}
const GetEventsWithSettings = `-- name: GetEventsWithSettings :many
SELECT e.id, e.source_event_id, e.sport_id, e.match_name, e.home_team, e.away_team, e.home_team_id, e.away_team_id, e.home_kit_image, e.away_kit_image, e.league_id, e.league_name, e.start_time, e.score, e.match_minute, e.timer_status, e.added_time, e.match_period, e.is_live, e.status, e.fetched_at, e.updated_at, e.source, e.default_is_active, e.default_is_featured, e.default_winning_upper_limit, e.is_monitored,
ces.company_id,
COALESCE(ces.is_active, e.default_is_active) AS is_active,
COALESCE(ces.is_featured, e.default_is_featured) AS is_featured,
COALESCE(
ces.winning_upper_limit,
e.default_winning_upper_limit
) AS winning_upper_limit,
ces.updated_at,
l.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 e
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
AND ces.company_id = $1
LEFT JOIN event_bet_stats ebs ON ebs.event_id = ewc.id
JOIN leagues l ON l.id = e.league_id
LEFT JOIN (
SELECT event_id,
SUM(number_of_outcomes) AS total_outcomes
FROM odds_market
GROUP BY event_id
) om ON om.event_id = e.id
WHERE (
is_live = $2
OR $2 IS NULL
)
AND (
status = $3
OR $3 IS NULL
)
AND(
league_id = $4
OR $4 IS NULL
)
AND (
e.sport_id = $5
OR $5 IS NULL
)
AND (
match_name ILIKE '%' || $6 || '%'
OR league_name ILIKE '%' || $6 || '%'
OR $6 IS NULL
)
AND (
start_time < $7
OR $7 IS NULL
)
AND (
start_time > $8
OR $8 IS NULL
)
AND (
l.country_code = $9
OR $9 IS NULL
)
AND (
ces.is_featured = $10
OR e.default_is_featured = $10
OR $10 IS NULL
)
AND (
ces.is_active = $11
OR e.default_is_active = $11
OR $11 IS NULL
)
AND (
source = $12
OR $12 IS NULL
)
ORDER BY start_time ASC
LIMIT $14 OFFSET $13
`
type GetEventsWithSettingsParams struct {
CompanyID int64 `json:"company_id"`
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
LeagueID pgtype.Int8 `json:"league_id"`
SportID pgtype.Int4 `json:"sport_id"`
Query pgtype.Text `json:"query"`
LastStartTime pgtype.Timestamp `json:"last_start_time"`
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
CountryCode pgtype.Text `json:"country_code"`
IsFeatured pgtype.Bool `json:"is_featured"`
IsActive pgtype.Bool `json:"is_active"`
Source pgtype.Text `json:"source"`
Offset pgtype.Int4 `json:"offset"`
Limit pgtype.Int4 `json:"limit"`
}
type GetEventsWithSettingsRow 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"`
CompanyID pgtype.Int8 `json:"company_id"`
IsActive bool `json:"is_active"`
IsFeatured bool `json:"is_featured"`
WinningUpperLimit int64 `json:"winning_upper_limit"`
UpdatedAt_2 pgtype.Timestamp `json:"updated_at_2"`
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"`
}
func (q *Queries) GetEventsWithSettings(ctx context.Context, arg GetEventsWithSettingsParams) ([]GetEventsWithSettingsRow, error) {
rows, err := q.db.Query(ctx, GetEventsWithSettings,
arg.CompanyID,
arg.IsLive,
arg.Status,
arg.LeagueID,
arg.SportID,
arg.Query,
arg.LastStartTime,
arg.FirstStartTime,
arg.CountryCode,
arg.IsFeatured,
arg.IsActive,
arg.Source,
arg.Offset,
arg.Limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetEventsWithSettingsRow
for rows.Next() {
var i GetEventsWithSettingsRow
if err := rows.Scan(
&i.ID,
&i.SourceEventID,
&i.SportID,
&i.MatchName,
&i.HomeTeam,
&i.AwayTeam,
&i.HomeTeamID,
&i.AwayTeamID,
&i.HomeKitImage,
&i.AwayKitImage,
&i.LeagueID,
&i.LeagueName,
&i.StartTime,
&i.Score,
&i.MatchMinute,
&i.TimerStatus,
&i.AddedTime,
&i.MatchPeriod,
&i.IsLive,
&i.Status,
&i.FetchedAt,
&i.UpdatedAt,
&i.Source,
&i.DefaultIsActive,
&i.DefaultIsFeatured,
&i.DefaultWinningUpperLimit,
&i.IsMonitored,
&i.CompanyID,
&i.IsActive,
&i.IsFeatured,
&i.WinningUpperLimit,
&i.UpdatedAt_2,
&i.LeagueCc,
&i.TotalOutcomes,
&i.NumberOfBets,
&i.TotalAmount,
&i.AvgBetAmount,
&i.TotalPotentialWinnings,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const GetTotalCompanyEvents = `-- name: GetTotalCompanyEvents :one
SELECT COUNT(*)
FROM events e
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
AND ces.company_id = $1
JOIN leagues l ON l.id = e.league_id
WHERE (
is_live = $2
OR $2 IS NULL
)
AND (
status = $3
OR $3 IS NULL
)
AND(
league_id = $4
OR $4 IS NULL
)
AND (
e.sport_id = $5
OR $5 IS NULL
)
AND (
match_name ILIKE '%' || $6 || '%'
OR league_name ILIKE '%' || $6 || '%'
OR $6 IS NULL
)
AND (
start_time < $7
OR $7 IS NULL
)
AND (
start_time > $8
OR $8 IS NULL
)
AND (
l.country_code = $9
OR $9 IS NULL
)
AND (
ces.is_featured = $10
OR e.default_is_featured = $10
OR $10 IS NULL
)
AND (
ces.is_active = $11
OR e.default_is_active = $11
OR $11 IS NULL
)
AND (
source = $12
OR $12 IS NULL
)
`
type GetTotalCompanyEventsParams struct {
CompanyID int64 `json:"company_id"`
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
LeagueID pgtype.Int8 `json:"league_id"`
SportID pgtype.Int4 `json:"sport_id"`
Query pgtype.Text `json:"query"`
LastStartTime pgtype.Timestamp `json:"last_start_time"`
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
CountryCode pgtype.Text `json:"country_code"`
IsFeatured pgtype.Bool `json:"is_featured"`
IsActive pgtype.Bool `json:"is_active"`
Source pgtype.Text `json:"source"`
}
func (q *Queries) GetTotalCompanyEvents(ctx context.Context, arg GetTotalCompanyEventsParams) (int64, error) {
row := q.db.QueryRow(ctx, GetTotalCompanyEvents,
arg.CompanyID,
arg.IsLive,
arg.Status,
arg.LeagueID,
arg.SportID,
arg.Query,
arg.LastStartTime,
arg.FirstStartTime,
arg.CountryCode,
arg.IsFeatured,
arg.IsActive,
arg.Source,
)
var count int64
err := row.Scan(&count)
return count, err
}
const SaveTenantEventSettings = `-- name: SaveTenantEventSettings :exec
INSERT INTO company_event_settings (
company_id,
event_id,
is_active,
is_featured,
winning_upper_limit
)
VALUES ($1, $2, $3, $4, $5) ON CONFLICT(company_id, event_id) DO
UPDATE
SET is_active = EXCLUDED.is_active,
is_featured = EXCLUDED.is_featured,
winning_upper_limit = EXCLUDED.winning_upper_limit
`
type SaveTenantEventSettingsParams struct {
CompanyID int64 `json:"company_id"`
EventID int64 `json:"event_id"`
IsActive pgtype.Bool `json:"is_active"`
IsFeatured pgtype.Bool `json:"is_featured"`
WinningUpperLimit pgtype.Int8 `json:"winning_upper_limit"`
}
func (q *Queries) SaveTenantEventSettings(ctx context.Context, arg SaveTenantEventSettingsParams) error {
_, err := q.db.Exec(ctx, SaveTenantEventSettings,
arg.CompanyID,
arg.EventID,
arg.IsActive,
arg.IsFeatured,
arg.WinningUpperLimit,
)
return err
}

115
gen/db/league_stats.sql.go Normal file
View File

@ -0,0 +1,115 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: league_stats.sql
package dbgen
import (
"context"
)
const GetLeagueEventStat = `-- name: GetLeagueEventStat :many
SELECT leagues.id,
leagues.name,
COUNT(*) AS total_events,
COUNT(*) FILTER (
WHERE events.status = 'upcoming'
) AS pending,
COUNT(*) FILTER (
WHERE events.status = 'in_play'
) AS in_play,
COUNT(*) FILTER (
WHERE events.status = 'to_be_fixed'
) AS to_be_fixed,
COUNT(*) FILTER (
WHERE events.status = 'ended'
) AS ended,
COUNT(*) FILTER (
WHERE events.status = 'postponed'
) AS postponed,
COUNT(*) FILTER (
WHERE events.status = 'cancelled'
) AS cancelled,
COUNT(*) FILTER (
WHERE events.status = 'walkover'
) AS walkover,
COUNT(*) FILTER (
WHERE events.status = 'interrupted'
) AS interrupted,
COUNT(*) FILTER (
WHERE events.status = 'abandoned'
) AS abandoned,
COUNT(*) FILTER (
WHERE events.status = 'retired'
) AS retired,
COUNT(*) FILTER (
WHERE events.status = 'suspended'
) AS suspended,
COUNT(*) FILTER (
WHERE events.status = 'decided_by_fa'
) AS decided_by_fa,
COUNT(*) FILTER (
WHERE events.status = 'removed'
) AS removed
FROM leagues
JOIN events ON leagues.id = events.league_id
GROUP BY leagues.id,
leagues.name
`
type GetLeagueEventStatRow struct {
ID int64 `json:"id"`
Name string `json:"name"`
TotalEvents int64 `json:"total_events"`
Pending int64 `json:"pending"`
InPlay int64 `json:"in_play"`
ToBeFixed int64 `json:"to_be_fixed"`
Ended int64 `json:"ended"`
Postponed int64 `json:"postponed"`
Cancelled int64 `json:"cancelled"`
Walkover int64 `json:"walkover"`
Interrupted int64 `json:"interrupted"`
Abandoned int64 `json:"abandoned"`
Retired int64 `json:"retired"`
Suspended int64 `json:"suspended"`
DecidedByFa int64 `json:"decided_by_fa"`
Removed int64 `json:"removed"`
}
func (q *Queries) GetLeagueEventStat(ctx context.Context) ([]GetLeagueEventStatRow, error) {
rows, err := q.db.Query(ctx, GetLeagueEventStat)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetLeagueEventStatRow
for rows.Next() {
var i GetLeagueEventStatRow
if err := rows.Scan(
&i.ID,
&i.Name,
&i.TotalEvents,
&i.Pending,
&i.InPlay,
&i.ToBeFixed,
&i.Ended,
&i.Postponed,
&i.Cancelled,
&i.Walkover,
&i.Interrupted,
&i.Abandoned,
&i.Retired,
&i.Suspended,
&i.DecidedByFa,
&i.Removed,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@ -36,6 +36,7 @@ type Bet struct {
CompanyID int64 `json:"company_id"` CompanyID int64 `json:"company_id"`
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"` TotalOdds float32 `json:"total_odds"`
PotentialWin pgtype.Int8 `json:"potential_win"`
Status int32 `json:"status"` Status int32 `json:"status"`
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
IsShopBet bool `json:"is_shop_bet"` IsShopBet bool `json:"is_shop_bet"`
@ -70,6 +71,7 @@ type BetWithOutcome struct {
CompanyID int64 `json:"company_id"` CompanyID int64 `json:"company_id"`
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"` TotalOdds float32 `json:"total_odds"`
PotentialWin pgtype.Int8 `json:"potential_win"`
Status int32 `json:"status"` Status int32 `json:"status"`
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
IsShopBet bool `json:"is_shop_bet"` IsShopBet bool `json:"is_shop_bet"`
@ -208,6 +210,15 @@ type CompanySetting struct {
UpdatedAt pgtype.Timestamp `json:"updated_at"` UpdatedAt pgtype.Timestamp `json:"updated_at"`
} }
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"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
}
type CustomerWallet struct { type CustomerWallet struct {
ID int64 `json:"id"` ID int64 `json:"id"`
CustomerID int64 `json:"customer_id"` CustomerID int64 `json:"customer_id"`
@ -340,14 +351,16 @@ type Event struct {
IsMonitored bool `json:"is_monitored"` IsMonitored bool `json:"is_monitored"`
} }
type EventHistory struct { type EventBetStat struct {
ID int64 `json:"id"` EventID int64 `json:"event_id"`
EventID int64 `json:"event_id"` NumberOfBets pgtype.Int8 `json:"number_of_bets"`
Status string `json:"status"` TotalAmount pgtype.Int8 `json:"total_amount"`
CreatedAt pgtype.Timestamp `json:"created_at"` AvgBetAmount pgtype.Float8 `json:"avg_bet_amount"`
TotalPotentialWinnings pgtype.Int8 `json:"total_potential_winnings"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
} }
type EventWithCountry struct { type EventDetailed struct {
ID int64 `json:"id"` ID int64 `json:"id"`
SourceEventID string `json:"source_event_id"` SourceEventID string `json:"source_event_id"`
SportID int32 `json:"sport_id"` SportID int32 `json:"sport_id"`
@ -377,6 +390,53 @@ type EventWithCountry struct {
IsMonitored bool `json:"is_monitored"` IsMonitored bool `json:"is_monitored"`
LeagueCc pgtype.Text `json:"league_cc"` LeagueCc pgtype.Text `json:"league_cc"`
TotalOutcomes int64 `json:"total_outcomes"` 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 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"`
Status string `json:"status"`
CreatedAt pgtype.Timestamp `json:"created_at"`
} }
type EventWithSetting struct { type EventWithSetting struct {
@ -414,6 +474,10 @@ type EventWithSetting struct {
CompanyUpdatedAt pgtype.Timestamp `json:"company_updated_at"` CompanyUpdatedAt pgtype.Timestamp `json:"company_updated_at"`
LeagueCc pgtype.Text `json:"league_cc"` LeagueCc pgtype.Text `json:"league_cc"`
TotalOutcomes int64 `json:"total_outcomes"` 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 ExchangeRate struct { type ExchangeRate struct {
@ -624,6 +688,16 @@ type RefreshToken struct {
Revoked bool `json:"revoked"` Revoked bool `json:"revoked"`
} }
type Report struct {
ID int64 `json:"id"`
CompanyID pgtype.Int8 `json:"company_id"`
RequestedBy pgtype.Int8 `json:"requested_by"`
FilePath pgtype.Text `json:"file_path"`
Status string `json:"status"`
CreatedAt pgtype.Timestamp `json:"created_at"`
CompletedAt pgtype.Timestamp `json:"completed_at"`
}
type ReportedIssue struct { type ReportedIssue struct {
ID int64 `json:"id"` ID int64 `json:"id"`
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`

View File

@ -0,0 +1,12 @@
package domain
// Branch-level aggregated report
type BranchStats struct {
BranchID int64
BranchName string
CompanyID int64
TotalBets int64
TotalCashIn float64
TotalCashOut float64
TotalCashBacks float64
}

View File

@ -0,0 +1,11 @@
package domain
// Company-level aggregated report
type CompanyStats struct {
CompanyID int64
CompanyName string
TotalBets int64
TotalCashIn float64
TotalCashOut float64
TotalCashBacks float64
}

View File

@ -1,108 +1,11 @@
package domain package domain
import ( import (
"fmt"
"time"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"time"
) )
// TODO: turn status into an enum
// Status represents the status of an event.
// 0 Not Started
// 1 InPlay
// 2 TO BE FIXED
// 3 Ended
// 4 Postponed
// 5 Cancelled
// 6 Walkover
// 7 Interrupted
// 8 Abandoned
// 9 Retired
// 10 Suspended
// 11 Decided by FA
// 99 Removed
type EventStatus string
const (
STATUS_PENDING EventStatus = "upcoming"
STATUS_IN_PLAY EventStatus = "in_play"
STATUS_TO_BE_FIXED EventStatus = "to_be_fixed"
STATUS_ENDED EventStatus = "ended"
STATUS_POSTPONED EventStatus = "postponed"
STATUS_CANCELLED EventStatus = "cancelled"
STATUS_WALKOVER EventStatus = "walkover"
STATUS_INTERRUPTED EventStatus = "interrupted"
STATUS_ABANDONED EventStatus = "abandoned"
STATUS_RETIRED EventStatus = "retired"
STATUS_SUSPENDED EventStatus = "suspended"
STATUS_DECIDED_BY_FA EventStatus = "decided_by_fa"
STATUS_REMOVED EventStatus = "removed"
)
type EventSource string
const (
EVENT_SOURCE_BET365 EventSource = "b365api"
EVENT_SOURCE_BWIN EventSource = "bwin"
EVENT_SOURCE_BETFAIR EventSource = "bfair"
EVENT_SOURCE_1XBET EventSource = "1xbet"
EVENT_SOURCE_ENET EventSource = "enetpulse"
)
// --- EventStatus Validation ---
func (s EventStatus) IsValid() bool {
switch s {
case STATUS_PENDING,
STATUS_IN_PLAY,
STATUS_TO_BE_FIXED,
STATUS_ENDED,
STATUS_POSTPONED,
STATUS_CANCELLED,
STATUS_WALKOVER,
STATUS_INTERRUPTED,
STATUS_ABANDONED,
STATUS_RETIRED,
STATUS_SUSPENDED,
STATUS_DECIDED_BY_FA,
STATUS_REMOVED:
return true
default:
return false
}
}
func ParseEventStatus(val string) (EventStatus, error) {
s := EventStatus(val)
if !s.IsValid() {
return "", fmt.Errorf("invalid EventStatus: %q", val)
}
return s, nil
}
// --- EventSource Validation ---
func (s EventSource) IsValid() bool {
switch s {
case EVENT_SOURCE_BET365,
EVENT_SOURCE_BWIN,
EVENT_SOURCE_BETFAIR,
EVENT_SOURCE_1XBET,
EVENT_SOURCE_ENET:
return true
default:
return false
}
}
func ParseEventSource(val string) (EventSource, error) {
s := EventSource(val)
if !s.IsValid() {
return "", fmt.Errorf("invalid EventSource: %q", val)
}
return s, nil
}
type BaseEvent struct { type BaseEvent struct {
ID int64 ID int64
SourceEventID string SourceEventID string
@ -120,7 +23,6 @@ type BaseEvent struct {
StartTime time.Time StartTime time.Time
Source EventSource Source EventSource
Status EventStatus Status EventStatus
TotalOddOutcomes int64
IsMonitored bool IsMonitored bool
DefaultIsFeatured bool DefaultIsFeatured bool
DefaultIsActive bool DefaultIsActive bool
@ -132,7 +34,13 @@ type BaseEvent struct {
MatchPeriod ValidInt MatchPeriod ValidInt
IsLive bool IsLive bool
FetchedAt time.Time FetchedAt time.Time
TotalOddOutcomes int64
NumberOfBets int64
TotalAmount Currency
AvgBetAmount Currency
TotalPotentialWinnings Currency
} }
type BaseEventRes struct { type BaseEventRes struct {
ID int64 `json:"id"` ID int64 `json:"id"`
SourceEventID string `json:"source_event_id"` SourceEventID string `json:"source_event_id"`
@ -150,7 +58,6 @@ type BaseEventRes struct {
StartTime time.Time `json:"start_time"` StartTime time.Time `json:"start_time"`
Source EventSource `json:"source"` Source EventSource `json:"source"`
Status EventStatus `json:"status"` Status EventStatus `json:"status"`
TotalOddOutcomes int64 `json:"total_odd_outcomes"`
IsMonitored bool `json:"is_monitored"` IsMonitored bool `json:"is_monitored"`
DefaultIsFeatured bool `json:"default_is_featured"` DefaultIsFeatured bool `json:"default_is_featured"`
DefaultIsActive bool `json:"default_is_active"` DefaultIsActive bool `json:"default_is_active"`
@ -162,40 +69,11 @@ type BaseEventRes struct {
MatchPeriod int `json:"match_period"` MatchPeriod int `json:"match_period"`
IsLive bool `json:"is_live"` IsLive bool `json:"is_live"`
FetchedAt time.Time `json:"fetched_at"` FetchedAt time.Time `json:"fetched_at"`
} TotalOddOutcomes int64 `json:"total_odd_outcomes"`
type EventWithSettings struct { NumberOfBets int64 `json:"number_of_bets"`
ID int64 TotalAmount float32 `json:"total_amount"`
SourceEventID string AvgBetAmount float32 `json:"average_bet_amount"`
SportID int32 TotalPotentialWinnings float32 `json:"total_potential_winnings"`
MatchName string
HomeTeam string
AwayTeam string
HomeTeamID int64
AwayTeamID int64
HomeTeamImage string
AwayTeamImage string
LeagueID int64
LeagueName string
LeagueCC ValidString
StartTime time.Time
Source EventSource
Status EventStatus
TotalOddOutcomes int64
IsMonitored bool
IsFeatured bool
IsActive bool
WinningUpperLimit int64
DefaultIsFeatured bool
DefaultIsActive bool
DefaultWinningUpperLimit int64
Score ValidString
MatchMinute ValidInt
TimerStatus ValidString
AddedTime ValidInt
MatchPeriod ValidInt
IsLive bool
UpdatedAt time.Time
FetchedAt time.Time
} }
type CreateEvent struct { type CreateEvent struct {
@ -216,89 +94,6 @@ type CreateEvent struct {
Source EventSource Source EventSource
DefaultWinningUpperLimit int64 DefaultWinningUpperLimit int64
} }
type EventWithSettingsRes 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"`
HomeTeamImage string `json:"home_team_image"`
AwayTeamImage string `json:"away_team_image"`
LeagueID int64 `json:"league_id"`
LeagueName string `json:"league_name"`
LeagueCC string `json:"league_cc"`
StartTime time.Time `json:"start_time"`
Source EventSource `json:"source"`
Status EventStatus `json:"status"`
TotalOddOutcomes int64 `json:"total_odd_outcomes"`
IsMonitored bool `json:"is_monitored"`
IsFeatured bool `json:"is_featured"`
IsActive bool `json:"is_active"`
WinningUpperLimit int64 `json:"winning_upper_limit"`
DefaultIsFeatured bool `json:"default_is_featured"`
DefaultIsActive bool `json:"default_is_active"`
DefaultWinningUpperLimit int64 `json:"default_winning_upper_limit"`
Score string `json:"score,omitempty"`
MatchMinute int `json:"match_minute,omitempty"`
TimerStatus string `json:"timer_status,omitempty"`
AddedTime int `json:"added_time,omitempty"`
MatchPeriod int `json:"match_period,omitempty"`
IsLive bool `json:"is_live"`
UpdatedAt time.Time `json:"updated_at"`
FetchedAt time.Time `json:"fetched_at"`
}
type EventSettings struct {
CompanyID int64
EventID int64
IsActive ValidBool
IsFeatured ValidBool
WinningUpperLimit ValidInt
UpdatedAt time.Time
}
type UpdateTenantEventSettings struct {
CompanyID int64
EventID int64
IsActive ValidBool
IsFeatured ValidBool
WinningUpperLimit ValidInt64
}
type UpdateGlobalEventSettings struct {
EventID int64
IsActive ValidBool
IsFeatured ValidBool
WinningUpperLimit ValidInt64
}
type ValidEventStatus struct {
Value EventStatus
Valid bool
}
func (v ValidEventStatus) ToPG() pgtype.Text {
return pgtype.Text{
String: string(v.Value),
Valid: v.Valid,
}
}
type ValidEventSource struct {
Value EventSource
Valid bool
}
func (v ValidEventSource) ToPG() pgtype.Text {
return pgtype.Text{
String: string(v.Value),
Valid: v.Valid,
}
}
type EventFilter struct { type EventFilter struct {
Query ValidString Query ValidString
SportID ValidInt32 SportID ValidInt32
@ -315,7 +110,7 @@ type EventFilter struct {
Source ValidEventSource Source ValidEventSource
} }
func ConvertDBEvent(event dbgen.EventWithCountry) BaseEvent { func ConvertDBEvent(event dbgen.EventDetailed) BaseEvent {
return BaseEvent{ return BaseEvent{
ID: event.ID, ID: event.ID,
SourceEventID: event.SourceEventID, SourceEventID: event.SourceEventID,
@ -336,7 +131,6 @@ func ConvertDBEvent(event dbgen.EventWithCountry) BaseEvent {
StartTime: event.StartTime.Time.UTC(), StartTime: event.StartTime.Time.UTC(),
Source: EventSource(event.Source), Source: EventSource(event.Source),
Status: EventStatus(event.Status), Status: EventStatus(event.Status),
TotalOddOutcomes: event.TotalOutcomes,
DefaultIsFeatured: event.DefaultIsFeatured, DefaultIsFeatured: event.DefaultIsFeatured,
IsMonitored: event.IsMonitored, IsMonitored: event.IsMonitored,
DefaultIsActive: event.DefaultIsActive, DefaultIsActive: event.DefaultIsActive,
@ -361,12 +155,17 @@ func ConvertDBEvent(event dbgen.EventWithCountry) BaseEvent {
Value: int(event.MatchPeriod.Int32), Value: int(event.MatchPeriod.Int32),
Valid: event.MatchPeriod.Valid, Valid: event.MatchPeriod.Valid,
}, },
IsLive: event.IsLive, IsLive: event.IsLive,
FetchedAt: event.FetchedAt.Time, FetchedAt: event.FetchedAt.Time,
TotalOddOutcomes: event.TotalOutcomes,
NumberOfBets: event.NumberOfBets,
TotalAmount: Currency(event.TotalAmount),
AvgBetAmount: Currency(event.AvgBetAmount),
TotalPotentialWinnings: Currency(event.TotalPotentialWinnings),
} }
} }
func ConvertDBEvents(events []dbgen.EventWithCountry) []BaseEvent { func ConvertDBEvents(events []dbgen.EventDetailed) []BaseEvent {
result := make([]BaseEvent, len(events)) result := make([]BaseEvent, len(events))
for i, e := range events { for i, e := range events {
result[i] = ConvertDBEvent(e) result[i] = ConvertDBEvent(e)
@ -395,96 +194,6 @@ func ConvertCreateEvent(e CreateEvent) dbgen.InsertEventParams {
} }
} }
func ConvertCreateEventSettings(eventSettings UpdateTenantEventSettings) dbgen.SaveTenantEventSettingsParams {
return dbgen.SaveTenantEventSettingsParams{
CompanyID: eventSettings.CompanyID,
EventID: eventSettings.EventID,
IsActive: eventSettings.IsActive.ToPG(),
IsFeatured: eventSettings.IsFeatured.ToPG(),
WinningUpperLimit: eventSettings.WinningUpperLimit.ToPG(),
}
}
func ConvertDBEventWithSetting(event dbgen.EventWithSetting) EventWithSettings {
return EventWithSettings{
ID: event.ID,
SourceEventID: event.SourceEventID,
WinningUpperLimit: event.WinningUpperLimit,
SportID: event.SportID,
MatchName: event.MatchName,
HomeTeam: event.HomeTeam,
AwayTeam: event.AwayTeam,
HomeTeamID: event.HomeTeamID,
AwayTeamID: event.AwayTeamID,
HomeTeamImage: event.HomeKitImage,
AwayTeamImage: event.AwayKitImage,
LeagueID: event.LeagueID,
LeagueName: event.LeagueName,
LeagueCC: ValidString{
Value: event.LeagueCc.String,
Valid: event.LeagueCc.Valid,
},
StartTime: event.StartTime.Time.UTC(),
Source: EventSource(event.Source),
Status: EventStatus(event.Status),
TotalOddOutcomes: event.TotalOutcomes,
IsFeatured: event.IsFeatured,
IsMonitored: event.IsMonitored,
IsActive: event.IsActive,
DefaultIsFeatured: event.DefaultIsFeatured,
DefaultIsActive: event.DefaultIsActive,
DefaultWinningUpperLimit: event.DefaultWinningUpperLimit,
Score: ValidString{
Value: event.Score.String,
Valid: event.Score.Valid,
},
MatchMinute: ValidInt{
Value: int(event.MatchMinute.Int32),
Valid: event.MatchMinute.Valid,
},
TimerStatus: ValidString{
Value: event.TimerStatus.String,
Valid: event.TimerStatus.Valid,
},
AddedTime: ValidInt{
Value: int(event.AddedTime.Int32),
Valid: event.AddedTime.Valid,
},
MatchPeriod: ValidInt{
Value: int(event.MatchPeriod.Int32),
Valid: event.MatchPeriod.Valid,
},
IsLive: event.IsLive,
UpdatedAt: event.UpdatedAt.Time,
FetchedAt: event.FetchedAt.Time,
}
}
func ConvertDBEventWithSettings(events []dbgen.EventWithSetting) []EventWithSettings {
result := make([]EventWithSettings, len(events))
for i, e := range events {
result[i] = ConvertDBEventWithSetting(e)
}
return result
}
func ConvertUpdateTenantEventSettings(event UpdateTenantEventSettings) dbgen.SaveTenantEventSettingsParams {
return dbgen.SaveTenantEventSettingsParams{
EventID: event.EventID,
CompanyID: event.CompanyID,
IsActive: event.IsActive.ToPG(),
IsFeatured: event.IsFeatured.ToPG(),
WinningUpperLimit: event.WinningUpperLimit.ToPG(),
}
}
func ConvertUpdateGlobalEventSettings(event UpdateGlobalEventSettings) dbgen.UpdateGlobalEventSettingsParams {
return dbgen.UpdateGlobalEventSettingsParams{
ID: event.EventID,
DefaultIsActive: event.IsActive.ToPG(),
DefaultIsFeatured: event.IsFeatured.ToPG(),
DefaultWinningUpperLimit: event.WinningUpperLimit.ToPG(),
}
}
func ConvertEventRes(event BaseEvent) BaseEventRes { func ConvertEventRes(event BaseEvent) BaseEventRes {
return BaseEventRes{ return BaseEventRes{
@ -504,7 +213,6 @@ func ConvertEventRes(event BaseEvent) BaseEventRes {
StartTime: event.StartTime.UTC(), StartTime: event.StartTime.UTC(),
Source: EventSource(event.Source), Source: EventSource(event.Source),
Status: EventStatus(event.Status), Status: EventStatus(event.Status),
TotalOddOutcomes: event.TotalOddOutcomes,
DefaultIsFeatured: event.DefaultIsFeatured, DefaultIsFeatured: event.DefaultIsFeatured,
IsMonitored: event.IsMonitored, IsMonitored: event.IsMonitored,
DefaultIsActive: event.DefaultIsActive, DefaultIsActive: event.DefaultIsActive,
@ -516,6 +224,11 @@ func ConvertEventRes(event BaseEvent) BaseEventRes {
MatchPeriod: event.MatchPeriod.Value, MatchPeriod: event.MatchPeriod.Value,
IsLive: event.IsLive, IsLive: event.IsLive,
FetchedAt: event.FetchedAt.UTC(), FetchedAt: event.FetchedAt.UTC(),
TotalOddOutcomes: event.TotalOddOutcomes,
NumberOfBets: event.NumberOfBets,
TotalAmount: event.TotalAmount.Float32(),
AvgBetAmount: event.AvgBetAmount.Float32(),
TotalPotentialWinnings: event.TotalPotentialWinnings.Float32(),
} }
} }
func ConvertEventResList(events []BaseEvent) []BaseEventRes { func ConvertEventResList(events []BaseEvent) []BaseEventRes {
@ -526,47 +239,3 @@ func ConvertEventResList(events []BaseEvent) []BaseEventRes {
return result return result
} }
func ConvertEventWitSettingRes(event EventWithSettings) EventWithSettingsRes {
return EventWithSettingsRes{
ID: event.ID,
SourceEventID: event.SourceEventID,
SportID: event.SportID,
MatchName: event.MatchName,
HomeTeam: event.HomeTeam,
AwayTeam: event.AwayTeam,
HomeTeamID: event.HomeTeamID,
AwayTeamID: event.AwayTeamID,
HomeTeamImage: event.HomeTeamImage,
AwayTeamImage: event.AwayTeamImage,
LeagueID: event.LeagueID,
LeagueName: event.LeagueName,
LeagueCC: event.LeagueCC.Value,
StartTime: event.StartTime.UTC(),
Source: EventSource(event.Source),
Status: EventStatus(event.Status),
TotalOddOutcomes: event.TotalOddOutcomes,
IsFeatured: event.IsFeatured,
IsMonitored: event.IsMonitored,
IsActive: event.IsActive,
DefaultIsFeatured: event.DefaultIsFeatured,
DefaultIsActive: event.DefaultIsActive,
DefaultWinningUpperLimit: event.DefaultWinningUpperLimit,
WinningUpperLimit: event.WinningUpperLimit,
Score: event.Score.Value,
MatchMinute: event.MatchMinute.Value,
TimerStatus: event.TimerStatus.Value,
AddedTime: event.AddedTime.Value,
MatchPeriod: event.MatchPeriod.Value,
IsLive: event.IsLive,
FetchedAt: event.FetchedAt.UTC(),
UpdatedAt: event.UpdatedAt,
}
}
func ConvertEventWithSettingResList(events []EventWithSettings) []EventWithSettingsRes {
result := make([]EventWithSettingsRes, 0, len(events))
for _, event := range events {
result = append(result, ConvertEventWitSettingRes(event))
}
return result
}

View File

@ -0,0 +1,51 @@
package domain
import (
"fmt"
"github.com/jackc/pgx/v5/pgtype"
)
type EventSource string
const (
EVENT_SOURCE_BET365 EventSource = "b365api"
EVENT_SOURCE_BWIN EventSource = "bwin"
EVENT_SOURCE_BETFAIR EventSource = "bfair"
EVENT_SOURCE_1XBET EventSource = "1xbet"
EVENT_SOURCE_ENET EventSource = "enetpulse"
)
// --- EventSource Validation ---
func (s EventSource) IsValid() bool {
switch s {
case EVENT_SOURCE_BET365,
EVENT_SOURCE_BWIN,
EVENT_SOURCE_BETFAIR,
EVENT_SOURCE_1XBET,
EVENT_SOURCE_ENET:
return true
default:
return false
}
}
func ParseEventSource(val string) (EventSource, error) {
s := EventSource(val)
if !s.IsValid() {
return "", fmt.Errorf("invalid EventSource: %q", val)
}
return s, nil
}
type ValidEventSource struct {
Value EventSource
Valid bool
}
func (v ValidEventSource) ToPG() pgtype.Text {
return pgtype.Text{
String: string(v.Value),
Valid: v.Valid,
}
}

View File

@ -0,0 +1,125 @@
package domain
import (
"time"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
)
type EventStats struct {
EventCount int64 `json:"event_count"`
TotalActiveEvents int64 `json:"total_active_events"`
TotalInActiveEvents int64 `json:"total_inactive_events"`
TotalFeaturedEvents int64 `json:"total_featured_events"`
TotalLeagues int64 `json:"total_leagues"`
Pending int64 `json:"pending"`
InPlay int64 `json:"in_play"`
ToBeFixed int64 `json:"to_be_fixed"`
Ended int64 `json:"ended"`
Postponed int64 `json:"postponed"`
Cancelled int64 `json:"cancelled"`
Walkover int64 `json:"walkover"`
Interrupted int64 `json:"interrupted"`
Abandoned int64 `json:"abandoned"`
Retired int64 `json:"retired"`
Suspended int64 `json:"suspended"`
DecidedByFa int64 `json:"decided_by_fa"`
Removed int64 `json:"removed"`
}
type EventStatsFilter struct {
Interval DateInterval
LeagueID ValidInt64
SportID ValidInt32
}
type EventStatsByIntervalFilter struct {
Interval DateInterval
LeagueID ValidInt64
SportID ValidInt32
}
type EventStatsByInterval struct {
Date time.Time `json:"date"`
EventCount int64 `json:"event_count"`
TotalActiveEvents int64 `json:"total_active_events"`
TotalInActiveEvents int64 `json:"total_inactive_events"`
TotalFeaturedEvents int64 `json:"total_featured_events"`
TotalLeagues int64 `json:"total_leagues"`
Pending int64 `json:"pending"`
InPlay int64 `json:"in_play"`
ToBeFixed int64 `json:"to_be_fixed"`
Ended int64 `json:"ended"`
Postponed int64 `json:"postponed"`
Cancelled int64 `json:"cancelled"`
Walkover int64 `json:"walkover"`
Interrupted int64 `json:"interrupted"`
Abandoned int64 `json:"abandoned"`
Retired int64 `json:"retired"`
Suspended int64 `json:"suspended"`
DecidedByFa int64 `json:"decided_by_fa"`
Removed int64 `json:"removed"`
}
func ConvertDBEventStats(stats dbgen.GetEventStatsRow) EventStats {
return EventStats{
EventCount: stats.EventCount,
TotalActiveEvents: stats.TotalActiveEvents,
TotalInActiveEvents: stats.TotalInactiveEvents,
TotalFeaturedEvents: stats.TotalFeaturedEvents,
TotalLeagues: stats.TotalLeagues,
Pending: stats.Pending,
InPlay: stats.InPlay,
ToBeFixed: stats.ToBeFixed,
Ended: stats.Ended,
Postponed: stats.Postponed,
Cancelled: stats.Cancelled,
Walkover: stats.Walkover,
Interrupted: stats.Interrupted,
Abandoned: stats.Abandoned,
Retired: stats.Retired,
Suspended: stats.Suspended,
DecidedByFa: stats.DecidedByFa,
Removed: stats.Removed,
}
}
func ConvertDBEventStatsByInterval(stats dbgen.GetEventStatsByIntervalRow) EventStatsByInterval {
return EventStatsByInterval{
Date: stats.Date.Time,
EventCount: stats.EventCount,
TotalActiveEvents: stats.TotalActiveEvents,
TotalInActiveEvents: stats.TotalInactiveEvents,
TotalFeaturedEvents: stats.TotalFeaturedEvents,
TotalLeagues: stats.TotalLeagues,
Pending: stats.Pending,
InPlay: stats.InPlay,
ToBeFixed: stats.ToBeFixed,
Ended: stats.Ended,
Postponed: stats.Postponed,
Cancelled: stats.Cancelled,
Walkover: stats.Walkover,
Interrupted: stats.Interrupted,
Abandoned: stats.Abandoned,
Retired: stats.Retired,
Suspended: stats.Suspended,
DecidedByFa: stats.DecidedByFa,
Removed: stats.Removed,
}
}
func ConvertDBEventStatsByIntervalList(stats []dbgen.GetEventStatsByIntervalRow) []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"`
}

View File

@ -0,0 +1,69 @@
package domain
import (
"fmt"
"github.com/jackc/pgx/v5/pgtype"
)
type EventStatus string
const (
STATUS_PENDING EventStatus = "upcoming"
STATUS_IN_PLAY EventStatus = "in_play"
STATUS_TO_BE_FIXED EventStatus = "to_be_fixed"
STATUS_ENDED EventStatus = "ended"
STATUS_POSTPONED EventStatus = "postponed"
STATUS_CANCELLED EventStatus = "cancelled"
STATUS_WALKOVER EventStatus = "walkover"
STATUS_INTERRUPTED EventStatus = "interrupted"
STATUS_ABANDONED EventStatus = "abandoned"
STATUS_RETIRED EventStatus = "retired"
STATUS_SUSPENDED EventStatus = "suspended"
STATUS_DECIDED_BY_FA EventStatus = "decided_by_fa"
STATUS_REMOVED EventStatus = "removed"
)
func (s EventStatus) IsValid() bool {
switch s {
case STATUS_PENDING,
STATUS_IN_PLAY,
STATUS_TO_BE_FIXED,
STATUS_ENDED,
STATUS_POSTPONED,
STATUS_CANCELLED,
STATUS_WALKOVER,
STATUS_INTERRUPTED,
STATUS_ABANDONED,
STATUS_RETIRED,
STATUS_SUSPENDED,
STATUS_DECIDED_BY_FA,
STATUS_REMOVED:
return true
default:
return false
}
}
func ParseEventStatus(val string) (EventStatus, error) {
s := EventStatus(val)
if !s.IsValid() {
return "", fmt.Errorf("invalid EventStatus: %q", val)
}
return s, nil
}
type ValidEventStatus struct {
Value EventStatus
Valid bool
}
func (v ValidEventStatus) ToPG() pgtype.Text {
return pgtype.Text{
String: string(v.Value),
Valid: v.Valid,
}
}

View File

@ -0,0 +1,252 @@
package domain
import (
"time"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
)
type EventWithSettings struct {
ID int64
SourceEventID string
SportID int32
MatchName string
HomeTeam string
AwayTeam string
HomeTeamID int64
AwayTeamID int64
HomeTeamImage string
AwayTeamImage string
LeagueID int64
LeagueName string
LeagueCC ValidString
StartTime time.Time
Source EventSource
Status EventStatus
IsMonitored bool
IsFeatured bool
IsActive bool
WinningUpperLimit int64
DefaultIsFeatured bool
DefaultIsActive bool
DefaultWinningUpperLimit int64
Score ValidString
MatchMinute ValidInt
TimerStatus ValidString
AddedTime ValidInt
MatchPeriod ValidInt
IsLive bool
UpdatedAt time.Time
FetchedAt time.Time
TotalOddOutcomes int64
NumberOfBets int64
TotalAmount Currency
AvgBetAmount Currency
TotalPotentialWinnings Currency
}
type EventWithSettingsRes 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"`
HomeTeamImage string `json:"home_team_image"`
AwayTeamImage string `json:"away_team_image"`
LeagueID int64 `json:"league_id"`
LeagueName string `json:"league_name"`
LeagueCC string `json:"league_cc"`
StartTime time.Time `json:"start_time"`
Source EventSource `json:"source"`
Status EventStatus `json:"status"`
IsMonitored bool `json:"is_monitored"`
IsFeatured bool `json:"is_featured"`
IsActive bool `json:"is_active"`
WinningUpperLimit int64 `json:"winning_upper_limit"`
DefaultIsFeatured bool `json:"default_is_featured"`
DefaultIsActive bool `json:"default_is_active"`
DefaultWinningUpperLimit int64 `json:"default_winning_upper_limit"`
Score string `json:"score,omitempty"`
MatchMinute int `json:"match_minute,omitempty"`
TimerStatus string `json:"timer_status,omitempty"`
AddedTime int `json:"added_time,omitempty"`
MatchPeriod int `json:"match_period,omitempty"`
IsLive bool `json:"is_live"`
UpdatedAt time.Time `json:"updated_at"`
FetchedAt time.Time `json:"fetched_at"`
TotalOddOutcomes int64 `json:"total_odd_outcomes"`
NumberOfBets int64 `json:"number_of_bets"`
TotalAmount float32 `json:"total_amount"`
AvgBetAmount float32 `json:"average_bet_amount"`
TotalPotentialWinnings float32 `json:"total_potential_winnings"`
}
type EventSettings struct {
CompanyID int64
EventID int64
IsActive ValidBool
IsFeatured ValidBool
WinningUpperLimit ValidInt
UpdatedAt time.Time
}
type UpdateTenantEventSettings struct {
CompanyID int64
EventID int64
IsActive ValidBool
IsFeatured ValidBool
WinningUpperLimit ValidInt64
}
type UpdateGlobalEventSettings struct {
EventID int64
IsActive ValidBool
IsFeatured ValidBool
WinningUpperLimit ValidInt64
}
func ConvertCreateEventSettings(eventSettings UpdateTenantEventSettings) dbgen.SaveTenantEventSettingsParams {
return dbgen.SaveTenantEventSettingsParams{
CompanyID: eventSettings.CompanyID,
EventID: eventSettings.EventID,
IsActive: eventSettings.IsActive.ToPG(),
IsFeatured: eventSettings.IsFeatured.ToPG(),
WinningUpperLimit: eventSettings.WinningUpperLimit.ToPG(),
}
}
func ConvertDBEventWithSetting(event dbgen.EventWithSetting) EventWithSettings {
return EventWithSettings{
ID: event.ID,
SourceEventID: event.SourceEventID,
WinningUpperLimit: event.WinningUpperLimit,
SportID: event.SportID,
MatchName: event.MatchName,
HomeTeam: event.HomeTeam,
AwayTeam: event.AwayTeam,
HomeTeamID: event.HomeTeamID,
AwayTeamID: event.AwayTeamID,
HomeTeamImage: event.HomeKitImage,
AwayTeamImage: event.AwayKitImage,
LeagueID: event.LeagueID,
LeagueName: event.LeagueName,
LeagueCC: ValidString{
Value: event.LeagueCc.String,
Valid: event.LeagueCc.Valid,
},
StartTime: event.StartTime.Time.UTC(),
Source: EventSource(event.Source),
Status: EventStatus(event.Status),
IsFeatured: event.IsFeatured,
IsMonitored: event.IsMonitored,
IsActive: event.IsActive,
DefaultIsFeatured: event.DefaultIsFeatured,
DefaultIsActive: event.DefaultIsActive,
DefaultWinningUpperLimit: event.DefaultWinningUpperLimit,
Score: ValidString{
Value: event.Score.String,
Valid: event.Score.Valid,
},
MatchMinute: ValidInt{
Value: int(event.MatchMinute.Int32),
Valid: event.MatchMinute.Valid,
},
TimerStatus: ValidString{
Value: event.TimerStatus.String,
Valid: event.TimerStatus.Valid,
},
AddedTime: ValidInt{
Value: int(event.AddedTime.Int32),
Valid: event.AddedTime.Valid,
},
MatchPeriod: ValidInt{
Value: int(event.MatchPeriod.Int32),
Valid: event.MatchPeriod.Valid,
},
IsLive: event.IsLive,
UpdatedAt: event.UpdatedAt.Time,
FetchedAt: event.FetchedAt.Time,
TotalOddOutcomes: event.TotalOutcomes,
NumberOfBets: event.NumberOfBets,
TotalAmount: Currency(event.TotalAmount),
AvgBetAmount: Currency(event.AvgBetAmount),
TotalPotentialWinnings: Currency(event.TotalPotentialWinnings),
}
}
func ConvertDBEventWithSettings(events []dbgen.EventWithSetting) []EventWithSettings {
result := make([]EventWithSettings, len(events))
for i, e := range events {
result[i] = ConvertDBEventWithSetting(e)
}
return result
}
func ConvertUpdateTenantEventSettings(event UpdateTenantEventSettings) dbgen.SaveTenantEventSettingsParams {
return dbgen.SaveTenantEventSettingsParams{
EventID: event.EventID,
CompanyID: event.CompanyID,
IsActive: event.IsActive.ToPG(),
IsFeatured: event.IsFeatured.ToPG(),
WinningUpperLimit: event.WinningUpperLimit.ToPG(),
}
}
func ConvertUpdateGlobalEventSettings(event UpdateGlobalEventSettings) dbgen.UpdateGlobalEventSettingsParams {
return dbgen.UpdateGlobalEventSettingsParams{
ID: event.EventID,
DefaultIsActive: event.IsActive.ToPG(),
DefaultIsFeatured: event.IsFeatured.ToPG(),
DefaultWinningUpperLimit: event.WinningUpperLimit.ToPG(),
}
}
func ConvertEventWitSettingRes(event EventWithSettings) EventWithSettingsRes {
return EventWithSettingsRes{
ID: event.ID,
SourceEventID: event.SourceEventID,
SportID: event.SportID,
MatchName: event.MatchName,
HomeTeam: event.HomeTeam,
AwayTeam: event.AwayTeam,
HomeTeamID: event.HomeTeamID,
AwayTeamID: event.AwayTeamID,
HomeTeamImage: event.HomeTeamImage,
AwayTeamImage: event.AwayTeamImage,
LeagueID: event.LeagueID,
LeagueName: event.LeagueName,
LeagueCC: event.LeagueCC.Value,
StartTime: event.StartTime.UTC(),
Source: EventSource(event.Source),
Status: EventStatus(event.Status),
IsFeatured: event.IsFeatured,
IsMonitored: event.IsMonitored,
IsActive: event.IsActive,
DefaultIsFeatured: event.DefaultIsFeatured,
DefaultIsActive: event.DefaultIsActive,
DefaultWinningUpperLimit: event.DefaultWinningUpperLimit,
WinningUpperLimit: event.WinningUpperLimit,
Score: event.Score.Value,
MatchMinute: event.MatchMinute.Value,
TimerStatus: event.TimerStatus.Value,
AddedTime: event.AddedTime.Value,
MatchPeriod: event.MatchPeriod.Value,
IsLive: event.IsLive,
FetchedAt: event.FetchedAt.UTC(),
UpdatedAt: event.UpdatedAt,
TotalOddOutcomes: event.TotalOddOutcomes,
NumberOfBets: event.NumberOfBets,
TotalAmount: event.TotalAmount.Float32(),
AvgBetAmount: event.AvgBetAmount.Float32(),
TotalPotentialWinnings: event.TotalPotentialWinnings.Float32(),
}
}
func ConvertEventWithSettingResList(events []EventWithSettings) []EventWithSettingsRes {
result := make([]EventWithSettingsRes, 0, len(events))
for _, event := range events {
result = append(result, ConvertEventWitSettingRes(event))
}
return result
}

View File

@ -0,0 +1,31 @@
package domain
import "fmt"
type DateInterval string
var (
MonthInterval DateInterval = "month"
WeekInterval DateInterval = "week"
DayInterval DateInterval = "day"
)
func (d DateInterval) IsValid() bool {
switch d {
case MonthInterval,
WeekInterval,
DayInterval:
return true
default:
return false
}
}
func ParseDateInterval(val string) (DateInterval, error) {
d := DateInterval(val)
if !d.IsValid() {
return "", fmt.Errorf("invalid date interval: %q", val)
}
return d, nil
}

View File

@ -2,12 +2,12 @@ package domain
import "time" import "time"
type TimeFrame string type ReportTimeFrame string
const ( const (
Daily TimeFrame = "daily" Daily ReportTimeFrame = "daily"
Weekly TimeFrame = "weekly" Weekly ReportTimeFrame = "weekly"
Monthly TimeFrame = "monthly" Monthly ReportTimeFrame = "monthly"
) )
type ReportFrequency string type ReportFrequency string
@ -39,8 +39,8 @@ type ReportData struct {
Deposits float64 Deposits float64
TotalTickets int64 TotalTickets int64
VirtualGameStats []VirtualGameStat VirtualGameStats []VirtualGameStat
CompanyReports []CompanyReport CompanyReports []CompanyStats
BranchReports []BranchReport BranchReports []BranchStats
} }
type VirtualGameStat struct { type VirtualGameStat struct {
@ -51,7 +51,7 @@ type VirtualGameStat struct {
type Report struct { type Report struct {
ID string ID string
TimeFrame TimeFrame TimeFrame ReportTimeFrame
PeriodStart time.Time PeriodStart time.Time
PeriodEnd time.Time PeriodEnd time.Time
TotalBets int TotalBets int
@ -282,7 +282,6 @@ type BetAnalysis struct {
AverageOdds float64 `json:"average_odds"` AverageOdds float64 `json:"average_odds"`
} }
// ReportFilter contains filters for report generation // ReportFilter contains filters for report generation
type ReportFilter struct { type ReportFilter struct {
StartTime ValidTime `json:"start_time"` StartTime ValidTime `json:"start_time"`
@ -484,27 +483,6 @@ type LiveWalletMetrics struct {
BranchBalances []BranchWalletBalance `json:"branch_balances"` BranchBalances []BranchWalletBalance `json:"branch_balances"`
} }
// Company-level aggregated report
type CompanyReport struct {
CompanyID int64
CompanyName string
TotalBets int64
TotalCashIn float64
TotalCashOut float64
TotalCashBacks float64
}
// Branch-level aggregated report
type BranchReport struct {
BranchID int64
BranchName string
CompanyID int64
TotalBets int64
TotalCashIn float64
TotalCashOut float64
TotalCashBacks float64
}
// type CompanyReport struct { // type CompanyReport struct {
// CompanyID int64 // CompanyID int64

View File

@ -100,6 +100,8 @@ func (s *Store) DeleteCompany(ctx context.Context, id int64) error {
return s.queries.DeleteCompany(ctx, id) return s.queries.DeleteCompany(ctx, id)
} }
func (s *Store) GetCompanyCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) { func (s *Store) GetCompanyCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) {
query := `SELECT query := `SELECT
COUNT(*) as total, COUNT(*) as total,

View File

@ -0,0 +1,10 @@
package repository
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)
}

View File

@ -61,108 +61,6 @@ func (s *Store) GetAllEvents(ctx context.Context, filter domain.EventFilter) ([]
return domain.ConvertDBEvents(events), totalCount, nil return domain.ConvertDBEvents(events), totalCount, nil
} }
func (s *Store) GetEventsWithSettings(ctx context.Context, companyID int64, filter domain.EventFilter) ([]domain.EventWithSettings, int64, error) {
events, err := s.queries.GetEventsWithSettings(ctx, dbgen.GetEventsWithSettingsParams{
CompanyID: companyID,
LeagueID: filter.LeagueID.ToPG(),
SportID: filter.SportID.ToPG(),
Query: filter.Query.ToPG(),
Limit: filter.Limit.ToPG(),
Offset: pgtype.Int4{
Int32: int32(filter.Offset.Value * filter.Limit.Value),
Valid: filter.Offset.Valid,
},
FirstStartTime: filter.FirstStartTime.ToPG(),
LastStartTime: filter.LastStartTime.ToPG(),
CountryCode: filter.CountryCode.ToPG(),
IsFeatured: filter.Featured.ToPG(),
IsActive: filter.Active.ToPG(),
IsLive: filter.IsLive.ToPG(),
Status: filter.Status.ToPG(),
Source: filter.Source.ToPG(),
})
if err != nil {
return nil, 0, err
}
totalCount, err := s.queries.GetTotalCompanyEvents(ctx, dbgen.GetTotalCompanyEventsParams{
CompanyID: companyID,
LeagueID: filter.LeagueID.ToPG(),
SportID: filter.SportID.ToPG(),
Query: filter.Query.ToPG(),
FirstStartTime: filter.FirstStartTime.ToPG(),
LastStartTime: filter.LastStartTime.ToPG(),
CountryCode: filter.CountryCode.ToPG(),
IsFeatured: filter.Featured.ToPG(),
IsActive: filter.Active.ToPG(),
IsLive: filter.IsLive.ToPG(),
Status: filter.Status.ToPG(),
Source: filter.Source.ToPG(),
})
if err != nil {
return nil, 0, err
}
result := make([]domain.EventWithSettings, len(events))
for i, event := range events {
result[i] = domain.EventWithSettings{
ID: event.ID,
SportID: event.SportID,
MatchName: event.MatchName,
HomeTeam: event.HomeTeam,
AwayTeam: event.AwayTeam,
HomeTeamID: event.HomeTeamID,
AwayTeamID: event.AwayTeamID,
HomeTeamImage: event.HomeKitImage,
AwayTeamImage: event.AwayKitImage,
LeagueID: event.LeagueID,
LeagueName: event.LeagueName,
LeagueCC: domain.ValidString{
Value: event.LeagueCc.String,
Valid: event.LeagueCc.Valid,
},
StartTime: event.StartTime.Time.UTC(),
Source: domain.EventSource(event.Source),
Status: domain.EventStatus(event.Status),
TotalOddOutcomes: event.TotalOutcomes,
SourceEventID: event.SourceEventID,
WinningUpperLimit: event.WinningUpperLimit,
IsFeatured: event.IsFeatured,
IsMonitored: event.IsMonitored,
IsActive: event.IsActive,
DefaultIsFeatured: event.DefaultIsFeatured,
DefaultIsActive: event.DefaultIsActive,
DefaultWinningUpperLimit: event.DefaultWinningUpperLimit,
Score: domain.ValidString{
Value: event.Score.String,
Valid: event.Score.Valid,
},
MatchMinute: domain.ValidInt{
Value: int(event.MatchMinute.Int32),
Valid: event.MatchMinute.Valid,
},
TimerStatus: domain.ValidString{
Value: event.TimerStatus.String,
Valid: event.TimerStatus.Valid,
},
AddedTime: domain.ValidInt{
Value: int(event.AddedTime.Int32),
Valid: event.AddedTime.Valid,
},
MatchPeriod: domain.ValidInt{
Value: int(event.MatchPeriod.Int32),
Valid: event.MatchPeriod.Valid,
},
IsLive: event.IsLive,
UpdatedAt: event.UpdatedAt.Time,
FetchedAt: event.FetchedAt.Time,
}
}
return result, totalCount, nil
}
func (s *Store) GetEventByID(ctx context.Context, ID int64) (domain.BaseEvent, error) { func (s *Store) GetEventByID(ctx context.Context, ID int64) (domain.BaseEvent, error) {
event, err := s.queries.GetEventByID(ctx, ID) event, err := s.queries.GetEventByID(ctx, ID)
if err != nil { if err != nil {
@ -182,69 +80,6 @@ func (s *Store) GetEventBySourceID(ctx context.Context, id string, source domain
return domain.ConvertDBEvent(event), nil return domain.ConvertDBEvent(event), nil
} }
func (s *Store) GetEventWithSettingByID(ctx context.Context, ID int64, companyID int64) (domain.EventWithSettings, error) {
event, err := s.queries.GetEventWithSettingByID(ctx, dbgen.GetEventWithSettingByIDParams{
ID: ID,
CompanyID: companyID,
})
if err != nil {
return domain.EventWithSettings{}, err
}
res := domain.EventWithSettings{
ID: event.ID,
SportID: event.SportID,
MatchName: event.MatchName,
HomeTeam: event.HomeTeam,
AwayTeam: event.AwayTeam,
HomeTeamID: event.HomeTeamID,
AwayTeamID: event.AwayTeamID,
HomeTeamImage: event.HomeKitImage,
AwayTeamImage: event.AwayKitImage,
LeagueID: event.LeagueID,
LeagueName: event.LeagueName,
LeagueCC: domain.ValidString{
Value: event.LeagueCc.String,
Valid: event.LeagueCc.Valid,
},
StartTime: event.StartTime.Time.UTC(),
Source: domain.EventSource(event.Source),
Status: domain.EventStatus(event.Status),
TotalOddOutcomes: event.TotalOutcomes,
SourceEventID: event.SourceEventID,
WinningUpperLimit: event.WinningUpperLimit,
IsFeatured: event.IsFeatured,
IsMonitored: event.IsMonitored,
IsActive: event.IsActive,
DefaultIsFeatured: event.DefaultIsFeatured,
DefaultIsActive: event.DefaultIsActive,
DefaultWinningUpperLimit: event.DefaultWinningUpperLimit,
Score: domain.ValidString{
Value: event.Score.String,
Valid: event.Score.Valid,
},
MatchMinute: domain.ValidInt{
Value: int(event.MatchMinute.Int32),
Valid: event.MatchMinute.Valid,
},
TimerStatus: domain.ValidString{
Value: event.TimerStatus.String,
Valid: event.TimerStatus.Valid,
},
AddedTime: domain.ValidInt{
Value: int(event.AddedTime.Int32),
Valid: event.AddedTime.Valid,
},
MatchPeriod: domain.ValidInt{
Value: int(event.MatchPeriod.Int32),
Valid: event.MatchPeriod.Valid,
},
IsLive: event.IsLive,
UpdatedAt: event.UpdatedAt.Time,
FetchedAt: event.FetchedAt.Time,
}
return res, nil
}
func (s *Store) UpdateFinalScore(ctx context.Context, eventID int64, fullScore string, status domain.EventStatus) error { func (s *Store) UpdateFinalScore(ctx context.Context, eventID int64, fullScore string, status domain.EventStatus) error {
params := dbgen.UpdateMatchResultParams{ params := dbgen.UpdateMatchResultParams{
Score: pgtype.Text{String: fullScore, Valid: true}, Score: pgtype.Text{String: fullScore, Valid: true},
@ -290,13 +125,7 @@ func (s *Store) UpdateEventMonitored(ctx context.Context, eventID int64, IsMonit
}) })
} }
func (s *Store) UpdateTenantEventSettings(ctx context.Context, event domain.UpdateTenantEventSettings) error {
return s.queries.SaveTenantEventSettings(ctx, domain.ConvertUpdateTenantEventSettings(event))
}
func (s *Store) UpdateGlobalEventSettings(ctx context.Context, event domain.UpdateGlobalEventSettings) error {
return s.queries.UpdateGlobalEventSettings(ctx, domain.ConvertUpdateGlobalEventSettings(event))
}
func (s *Store) DeleteEvent(ctx context.Context, eventID int64) error { func (s *Store) DeleteEvent(ctx context.Context, eventID int64) error {
err := s.queries.DeleteEvent(ctx, eventID) err := s.queries.DeleteEvent(ctx, eventID)
if err != nil { if err != nil {

View File

@ -0,0 +1,38 @@
package repository
import (
"context"
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{
LeagueID: filter.LeagueID.ToPG(),
SportID: filter.SportID.ToPG(),
})
if err != nil {
return domain.EventStats{}, err
}
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,
},
LeagueID: filter.LeagueID.ToPG(),
SportID: filter.SportID.ToPG(),
})
if err != nil {
return nil, err
}
return domain.ConvertDBEventStatsByIntervalList(stats), nil
}

View File

@ -0,0 +1,183 @@
package repository
import (
"context"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/jackc/pgx/v5/pgtype"
)
func (s *Store) GetEventsWithSettings(ctx context.Context, companyID int64, filter domain.EventFilter) ([]domain.EventWithSettings, int64, error) {
events, err := s.queries.GetEventsWithSettings(ctx, dbgen.GetEventsWithSettingsParams{
CompanyID: companyID,
LeagueID: filter.LeagueID.ToPG(),
SportID: filter.SportID.ToPG(),
Query: filter.Query.ToPG(),
Limit: filter.Limit.ToPG(),
Offset: pgtype.Int4{
Int32: int32(filter.Offset.Value * filter.Limit.Value),
Valid: filter.Offset.Valid,
},
FirstStartTime: filter.FirstStartTime.ToPG(),
LastStartTime: filter.LastStartTime.ToPG(),
CountryCode: filter.CountryCode.ToPG(),
IsFeatured: filter.Featured.ToPG(),
IsActive: filter.Active.ToPG(),
IsLive: filter.IsLive.ToPG(),
Status: filter.Status.ToPG(),
Source: filter.Source.ToPG(),
})
if err != nil {
return nil, 0, err
}
totalCount, err := s.queries.GetTotalCompanyEvents(ctx, dbgen.GetTotalCompanyEventsParams{
CompanyID: companyID,
LeagueID: filter.LeagueID.ToPG(),
SportID: filter.SportID.ToPG(),
Query: filter.Query.ToPG(),
FirstStartTime: filter.FirstStartTime.ToPG(),
LastStartTime: filter.LastStartTime.ToPG(),
CountryCode: filter.CountryCode.ToPG(),
IsFeatured: filter.Featured.ToPG(),
IsActive: filter.Active.ToPG(),
IsLive: filter.IsLive.ToPG(),
Status: filter.Status.ToPG(),
Source: filter.Source.ToPG(),
})
if err != nil {
return nil, 0, err
}
result := make([]domain.EventWithSettings, len(events))
for i, event := range events {
result[i] = domain.EventWithSettings{
ID: event.ID,
SportID: event.SportID,
MatchName: event.MatchName,
HomeTeam: event.HomeTeam,
AwayTeam: event.AwayTeam,
HomeTeamID: event.HomeTeamID,
AwayTeamID: event.AwayTeamID,
HomeTeamImage: event.HomeKitImage,
AwayTeamImage: event.AwayKitImage,
LeagueID: event.LeagueID,
LeagueName: event.LeagueName,
LeagueCC: domain.ValidString{
Value: event.LeagueCc.String,
Valid: event.LeagueCc.Valid,
},
StartTime: event.StartTime.Time.UTC(),
Source: domain.EventSource(event.Source),
Status: domain.EventStatus(event.Status),
TotalOddOutcomes: event.TotalOutcomes,
SourceEventID: event.SourceEventID,
WinningUpperLimit: event.WinningUpperLimit,
IsFeatured: event.IsFeatured,
IsMonitored: event.IsMonitored,
IsActive: event.IsActive,
DefaultIsFeatured: event.DefaultIsFeatured,
DefaultIsActive: event.DefaultIsActive,
DefaultWinningUpperLimit: event.DefaultWinningUpperLimit,
Score: domain.ValidString{
Value: event.Score.String,
Valid: event.Score.Valid,
},
MatchMinute: domain.ValidInt{
Value: int(event.MatchMinute.Int32),
Valid: event.MatchMinute.Valid,
},
TimerStatus: domain.ValidString{
Value: event.TimerStatus.String,
Valid: event.TimerStatus.Valid,
},
AddedTime: domain.ValidInt{
Value: int(event.AddedTime.Int32),
Valid: event.AddedTime.Valid,
},
MatchPeriod: domain.ValidInt{
Value: int(event.MatchPeriod.Int32),
Valid: event.MatchPeriod.Valid,
},
IsLive: event.IsLive,
UpdatedAt: event.UpdatedAt.Time,
FetchedAt: event.FetchedAt.Time,
}
}
return result, totalCount, nil
}
func (s *Store) GetEventWithSettingByID(ctx context.Context, ID int64, companyID int64) (domain.EventWithSettings, error) {
event, err := s.queries.GetEventWithSettingByID(ctx, dbgen.GetEventWithSettingByIDParams{
ID: ID,
CompanyID: companyID,
})
if err != nil {
return domain.EventWithSettings{}, err
}
res := domain.EventWithSettings{
ID: event.ID,
SportID: event.SportID,
MatchName: event.MatchName,
HomeTeam: event.HomeTeam,
AwayTeam: event.AwayTeam,
HomeTeamID: event.HomeTeamID,
AwayTeamID: event.AwayTeamID,
HomeTeamImage: event.HomeKitImage,
AwayTeamImage: event.AwayKitImage,
LeagueID: event.LeagueID,
LeagueName: event.LeagueName,
LeagueCC: domain.ValidString{
Value: event.LeagueCc.String,
Valid: event.LeagueCc.Valid,
},
StartTime: event.StartTime.Time.UTC(),
Source: domain.EventSource(event.Source),
Status: domain.EventStatus(event.Status),
TotalOddOutcomes: event.TotalOutcomes,
SourceEventID: event.SourceEventID,
WinningUpperLimit: event.WinningUpperLimit,
IsFeatured: event.IsFeatured,
IsMonitored: event.IsMonitored,
IsActive: event.IsActive,
DefaultIsFeatured: event.DefaultIsFeatured,
DefaultIsActive: event.DefaultIsActive,
DefaultWinningUpperLimit: event.DefaultWinningUpperLimit,
Score: domain.ValidString{
Value: event.Score.String,
Valid: event.Score.Valid,
},
MatchMinute: domain.ValidInt{
Value: int(event.MatchMinute.Int32),
Valid: event.MatchMinute.Valid,
},
TimerStatus: domain.ValidString{
Value: event.TimerStatus.String,
Valid: event.TimerStatus.Valid,
},
AddedTime: domain.ValidInt{
Value: int(event.AddedTime.Int32),
Valid: event.AddedTime.Valid,
},
MatchPeriod: domain.ValidInt{
Value: int(event.MatchPeriod.Int32),
Valid: event.MatchPeriod.Valid,
},
IsLive: event.IsLive,
UpdatedAt: event.UpdatedAt.Time,
FetchedAt: event.FetchedAt.Time,
}
return res, nil
}
func (s *Store) UpdateTenantEventSettings(ctx context.Context, event domain.UpdateTenantEventSettings) error {
return s.queries.SaveTenantEventSettings(ctx, domain.ConvertUpdateTenantEventSettings(event))
}
func (s *Store) UpdateGlobalEventSettings(ctx context.Context, event domain.UpdateGlobalEventSettings) error {
return s.queries.UpdateGlobalEventSettings(ctx, domain.ConvertUpdateGlobalEventSettings(event))
}

View File

@ -1,29 +1,29 @@
package repository package repository
import ( import (
"context" // "context"
"fmt" // "fmt"
"time" // "time"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" // dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" // "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/jackc/pgx/v5/pgtype" // "github.com/jackc/pgx/v5/pgtype"
) )
type ReportRepository interface { type ReportRepository interface {
GenerateReport(timeFrame domain.TimeFrame, start, end time.Time) (*domain.Report, error) // GenerateReport(timeFrame domain.ReportTimeFrame, start, end time.Time) (*domain.Report, error)
SaveReport(report *domain.Report) error // SaveReport(report *domain.Report) error
FindReportsByTimeFrame(timeFrame domain.TimeFrame, limit int) ([]*domain.Report, error) // FindReportsByTimeFrame(timeFrame domain.ReportTimeFrame, limit int) ([]*domain.Report, error)
GetTotalCashOutInRange(ctx context.Context, from, to time.Time) (float64, error) // GetTotalCashOutInRange(ctx context.Context, from, to time.Time) (float64, error)
GetTotalCashMadeInRange(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) // GetTotalCashBacksInRange(ctx context.Context, from, to time.Time) (float64, error)
GetTotalBetsMadeInRange(ctx context.Context, from, to time.Time) (int64, error) // GetTotalBetsMadeInRange(ctx context.Context, from, to time.Time) (int64, error)
GetVirtualGameSummaryInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetVirtualGameSummaryInRangeRow, error) // GetVirtualGameSummaryInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetVirtualGameSummaryInRangeRow, error)
GetAllTicketsInRange(ctx context.Context, from, to time.Time) (dbgen.GetAllTicketsInRangeRow, error) // GetAllTicketsInRange(ctx context.Context, from, to time.Time) (dbgen.GetAllTicketsInRangeRow, error)
GetWalletTransactionsInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetWalletTransactionsInRangeRow, error) // GetWalletTransactionsInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetWalletTransactionsInRangeRow, error)
GetCompanyWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetCompanyWiseReportRow, error) // GetCompanyWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetCompanyWiseReportRow, error)
GetBranchWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetBranchWiseReportRow, error) // GetBranchWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetBranchWiseReportRow, error)
} }
type ReportRepo struct { type ReportRepo struct {
@ -34,201 +34,201 @@ func NewReportRepo(store *Store) ReportRepository {
return &ReportRepo{store: store} return &ReportRepo{store: store}
} }
func (r *ReportRepo) GenerateReport(timeFrame domain.TimeFrame, start, end time.Time) (*domain.Report, error) { // func (r *ReportRepo) GenerateReport(timeFrame domain.ReportTimeFrame, start, end time.Time) (*domain.Report, error) {
// Implement SQL queries to calculate metrics // // Implement SQL queries to calculate metrics
var report domain.Report // var report domain.Report
// Total Bets // // Total Bets
err := r.store.conn.QueryRow( // err := r.store.conn.QueryRow(
context.Background(), // context.Background(),
`SELECT COUNT(*) FROM bets // `SELECT COUNT(*) FROM bets
WHERE created_at BETWEEN $1 AND $2`, start, end).Scan(&report.TotalBets) // WHERE created_at BETWEEN $1 AND $2`, start, end).Scan(&report.TotalBets)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
// Total Cash In // // Total Cash In
err = r.store.conn.QueryRow( // err = r.store.conn.QueryRow(
context.Background(), // context.Background(),
`SELECT COALESCE(SUM(amount), 0) FROM transactions // `SELECT COALESCE(SUM(amount), 0) FROM transactions
WHERE type = 'stake' AND created_at BETWEEN $1 AND $2`, start, end).Scan(&report.TotalCashIn) // WHERE type = 'stake' AND created_at BETWEEN $1 AND $2`, start, end).Scan(&report.TotalCashIn)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
// Similar queries for Cash Out and Cash Back... // // Similar queries for Cash Out and Cash Back...
report.TimeFrame = timeFrame // report.TimeFrame = timeFrame
report.PeriodStart = start // report.PeriodStart = start
report.PeriodEnd = end // report.PeriodEnd = end
report.GeneratedAt = time.Now() // report.GeneratedAt = time.Now()
return &report, nil // return &report, nil
} // }
func (r *ReportRepo) SaveReport(report *domain.Report) error { // func (r *ReportRepo) SaveReport(report *domain.Report) error {
_, err := r.store.conn.Exec( // _, err := r.store.conn.Exec(
context.Background(), // context.Background(),
`INSERT INTO reports // `INSERT INTO reports
(id, time_frame, period_start, period_end, total_bets, total_cash_in, total_cash_out, total_cash_back, generated_at) // (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)`, // VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
report.ID, report.TimeFrame, report.PeriodStart, report.PeriodEnd, // report.ID, report.TimeFrame, report.PeriodStart, report.PeriodEnd,
report.TotalBets, report.TotalCashIn, report.TotalCashOut, report.TotalCashBack, report.GeneratedAt) // report.TotalBets, report.TotalCashIn, report.TotalCashOut, report.TotalCashBack, report.GeneratedAt)
return err // return err
} // }
func (r *ReportRepo) FindReportsByTimeFrame(timeFrame domain.TimeFrame, limit int) ([]*domain.Report, error) { // func (r *ReportRepo) FindReportsByTimeFrame(timeFrame domain.ReportTimeFrame, limit int) ([]*domain.Report, error) {
rows, err := r.store.conn.Query( // rows, err := r.store.conn.Query(
context.Background(), // context.Background(),
`SELECT id, time_frame, period_start, period_end, total_bets, // `SELECT id, time_frame, period_start, period_end, total_bets,
total_cash_in, total_cash_out, total_cash_back, generated_at // total_cash_in, total_cash_out, total_cash_back, generated_at
FROM reports // FROM reports
WHERE time_frame = $1 // WHERE time_frame = $1
ORDER BY generated_at DESC // ORDER BY generated_at DESC
LIMIT $2`, // LIMIT $2`,
timeFrame, limit) // timeFrame, limit)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
defer rows.Close() // defer rows.Close()
var reports []*domain.Report // var reports []*domain.Report
for rows.Next() { // for rows.Next() {
var report domain.Report // var report domain.Report
err := rows.Scan( // err := rows.Scan(
&report.ID, // &report.ID,
&report.TimeFrame, // &report.TimeFrame,
&report.PeriodStart, // &report.PeriodStart,
&report.PeriodEnd, // &report.PeriodEnd,
&report.TotalBets, // &report.TotalBets,
&report.TotalCashIn, // &report.TotalCashIn,
&report.TotalCashOut, // &report.TotalCashOut,
&report.TotalCashBack, // &report.TotalCashBack,
&report.GeneratedAt, // &report.GeneratedAt,
) // )
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
reports = append(reports, &report) // reports = append(reports, &report)
} // }
if err := rows.Err(); err != nil { // if err := rows.Err(); err != nil {
return nil, err // return nil, err
} // }
return reports, nil // return reports, nil
} // }
func (r *ReportRepo) GetTotalBetsMadeInRange(ctx context.Context, from, to time.Time) (int64, error) { // func (r *ReportRepo) GetTotalBetsMadeInRange(ctx context.Context, from, to time.Time) (int64, error) {
params := dbgen.GetTotalBetsMadeInRangeParams{ // params := dbgen.GetTotalBetsMadeInRangeParams{
From: ToPgTimestamp(from), // From: ToPgTimestamp(from),
To: ToPgTimestamp(to), // To: ToPgTimestamp(to),
} // }
return r.store.queries.GetTotalBetsMadeInRange(ctx, params) // return r.store.queries.GetTotalBetsMadeInRange(ctx, params)
} // }
func (r *ReportRepo) GetTotalCashBacksInRange(ctx context.Context, from, to time.Time) (float64, error) { // func (r *ReportRepo) GetTotalCashBacksInRange(ctx context.Context, from, to time.Time) (float64, error) {
params := dbgen.GetTotalCashBacksInRangeParams{ // params := dbgen.GetTotalCashBacksInRangeParams{
From: ToPgTimestamp(from), // From: ToPgTimestamp(from),
To: ToPgTimestamp(to), // To: ToPgTimestamp(to),
} // }
value, err := r.store.queries.GetTotalCashBacksInRange(ctx, params) // value, err := r.store.queries.GetTotalCashBacksInRange(ctx, params)
if err != nil { // if err != nil {
return 0, err // return 0, err
} // }
return parseFloat(value) // return parseFloat(value)
} // }
func (r *ReportRepo) GetTotalCashMadeInRange(ctx context.Context, from, to time.Time) (float64, error) { // func (r *ReportRepo) GetTotalCashMadeInRange(ctx context.Context, from, to time.Time) (float64, error) {
params := dbgen.GetTotalCashMadeInRangeParams{ // params := dbgen.GetTotalCashMadeInRangeParams{
From: ToPgTimestamp(from), // From: ToPgTimestamp(from),
To: ToPgTimestamp(to), // To: ToPgTimestamp(to),
} // }
value, err := r.store.queries.GetTotalCashMadeInRange(ctx, params) // value, err := r.store.queries.GetTotalCashMadeInRange(ctx, params)
if err != nil { // if err != nil {
return 0, err // return 0, err
} // }
return parseFloat(value) // return parseFloat(value)
} // }
func (r *ReportRepo) GetTotalCashOutInRange(ctx context.Context, from, to time.Time) (float64, error) { // func (r *ReportRepo) GetTotalCashOutInRange(ctx context.Context, from, to time.Time) (float64, error) {
params := dbgen.GetTotalCashOutInRangeParams{ // params := dbgen.GetTotalCashOutInRangeParams{
From: ToPgTimestamp(from), // From: ToPgTimestamp(from),
To: ToPgTimestamp(to), // To: ToPgTimestamp(to),
} // }
value, err := r.store.queries.GetTotalCashOutInRange(ctx, params) // value, err := r.store.queries.GetTotalCashOutInRange(ctx, params)
if err != nil { // if err != nil {
return 0, err // return 0, err
} // }
return parseFloat(value) // return parseFloat(value)
} // }
func (r *ReportRepo) GetWalletTransactionsInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetWalletTransactionsInRangeRow, error) { // func (r *ReportRepo) GetWalletTransactionsInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetWalletTransactionsInRangeRow, error) {
params := dbgen.GetWalletTransactionsInRangeParams{ // params := dbgen.GetWalletTransactionsInRangeParams{
CreatedAt: ToPgTimestamp(from), // CreatedAt: ToPgTimestamp(from),
CreatedAt_2: ToPgTimestamp(to), // CreatedAt_2: ToPgTimestamp(to),
} // }
return r.store.queries.GetWalletTransactionsInRange(ctx, params) // return r.store.queries.GetWalletTransactionsInRange(ctx, params)
} // }
func (r *ReportRepo) GetAllTicketsInRange(ctx context.Context, from, to time.Time) (dbgen.GetAllTicketsInRangeRow, error) { // func (r *ReportRepo) GetAllTicketsInRange(ctx context.Context, from, to time.Time) (dbgen.GetAllTicketsInRangeRow, error) {
params := dbgen.GetAllTicketsInRangeParams{ // params := dbgen.GetAllTicketsInRangeParams{
CreatedAt: ToPgTimestamp(from), // CreatedAt: ToPgTimestamp(from),
CreatedAt_2: ToPgTimestamp(to), // CreatedAt_2: ToPgTimestamp(to),
} // }
return r.store.queries.GetAllTicketsInRange(ctx, params) // return r.store.queries.GetAllTicketsInRange(ctx, params)
} // }
func (r *ReportRepo) GetVirtualGameSummaryInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetVirtualGameSummaryInRangeRow, error) { // func (r *ReportRepo) GetVirtualGameSummaryInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetVirtualGameSummaryInRangeRow, error) {
params := dbgen.GetVirtualGameSummaryInRangeParams{ // params := dbgen.GetVirtualGameSummaryInRangeParams{
CreatedAt: ToPgTimestamptz(from), // CreatedAt: ToPgTimestamptz(from),
CreatedAt_2: ToPgTimestamptz(to), // CreatedAt_2: ToPgTimestamptz(to),
} // }
return r.store.queries.GetVirtualGameSummaryInRange(ctx, params) // return r.store.queries.GetVirtualGameSummaryInRange(ctx, params)
} // }
func ToPgTimestamp(t time.Time) pgtype.Timestamp { // func ToPgTimestamp(t time.Time) pgtype.Timestamp {
return pgtype.Timestamp{Time: t, Valid: true} // return pgtype.Timestamp{Time: t, Valid: true}
} // }
func ToPgTimestamptz(t time.Time) pgtype.Timestamptz { // func ToPgTimestamptz(t time.Time) pgtype.Timestamptz {
return pgtype.Timestamptz{Time: t, Valid: true} // return pgtype.Timestamptz{Time: t, Valid: true}
} // }
func parseFloat(value interface{}) (float64, error) { // func parseFloat(value interface{}) (float64, error) {
switch v := value.(type) { // switch v := value.(type) {
case float64: // case float64:
return v, nil // return v, nil
case int64: // case int64:
return float64(v), nil // return float64(v), nil
case pgtype.Numeric: // case pgtype.Numeric:
if !v.Valid { // if !v.Valid {
return 0, nil // return 0, nil
} // }
f, err := v.Float64Value() // f, err := v.Float64Value()
if err != nil { // if err != nil {
return 0, fmt.Errorf("failed to convert pgtype.Numeric to float64: %w", err) // return 0, fmt.Errorf("failed to convert pgtype.Numeric to float64: %w", err)
} // }
return f.Float64, nil // return f.Float64, nil
case nil: // case nil:
return 0, nil // return 0, nil
default: // default:
return 0, fmt.Errorf("unexpected type %T for value: %+v", v, v) // 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) { // func (r *ReportRepo) GetCompanyWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetCompanyWiseReportRow, error) {
params := dbgen.GetCompanyWiseReportParams{ // params := dbgen.GetCompanyWiseReportParams{
From: ToPgTimestamp(from), // From: ToPgTimestamp(from),
To: ToPgTimestamp(to), // To: ToPgTimestamp(to),
} // }
return r.store.queries.GetCompanyWiseReport(ctx, params) // return r.store.queries.GetCompanyWiseReport(ctx, params)
} // }
func (r *ReportRepo) GetBranchWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetBranchWiseReportRow, error) { // func (r *ReportRepo) GetBranchWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetBranchWiseReportRow, error) {
params := dbgen.GetBranchWiseReportParams{ // params := dbgen.GetBranchWiseReportParams{
From: ToPgTimestamp(from), // From: ToPgTimestamp(from),
To: ToPgTimestamp(to), // To: ToPgTimestamp(to),
} // }
return r.store.queries.GetBranchWiseReport(ctx, params) // return r.store.queries.GetBranchWiseReport(ctx, params)
} // }

View File

@ -557,31 +557,31 @@ func (s *Service) DeductBetFromBranchWallet(ctx context.Context, amount float32,
// This is the amount that we take from a company/tenant when they // This is the amount that we take from a company/tenant when they
// create a bet. I.e. if its 5% (0.05), then thats the percentage we take every // create a bet. I.e. if its 5% (0.05), then thats the percentage we take every
deductedAmount := amount * company.DeductedPercentage // deductedAmount := amount * company.DeductedPercentage
if deductedAmount == 0 { // if deductedAmount == 0 {
s.mongoLogger.Fatal("Amount", // s.mongoLogger.Fatal("Amount",
zap.Int64("wallet_id", walletID), // zap.Int64("wallet_id", walletID),
zap.Float32("amount", deductedAmount), // zap.Float32("amount", deductedAmount),
zap.Error(err), // zap.Error(err),
) // )
return err // return err
} // }
_, err = s.walletSvc.DeductFromWallet(ctx, // _, err = s.walletSvc.DeductFromWallet(ctx,
walletID, domain.ToCurrency(deductedAmount), domain.ValidInt64{ // walletID, domain.ToCurrency(deductedAmount), domain.ValidInt64{
Value: userID, // Value: userID,
Valid: true, // Valid: true,
}, domain.TRANSFER_DIRECT, // }, domain.TRANSFER_DIRECT,
fmt.Sprintf("Deducted %v amount from wallet by system while placing bet", deductedAmount)) // fmt.Sprintf("Deducted %v amount from wallet by system while placing bet", deductedAmount))
if err != nil { // if err != nil {
s.mongoLogger.Error("failed to deduct from wallet", // s.mongoLogger.Error("failed to deduct from wallet",
zap.Int64("wallet_id", walletID), // zap.Int64("wallet_id", walletID),
zap.Float32("amount", deductedAmount), // zap.Float32("amount", deductedAmount),
zap.Error(err), // zap.Error(err),
) // )
return err // return err
} // }
return nil return nil
} }

View File

@ -16,9 +16,15 @@ type Service interface {
UpdateEventStatus(ctx context.Context, eventID int64, status domain.EventStatus) error UpdateEventStatus(ctx context.Context, eventID int64, status domain.EventStatus) error
IsEventMonitored(ctx context.Context, eventID int64) (bool, error) IsEventMonitored(ctx context.Context, eventID int64) (bool, error)
UpdateEventMonitored(ctx context.Context, eventID int64, IsMonitored bool) error UpdateEventMonitored(ctx context.Context, eventID int64, IsMonitored bool) error
GetSportAndLeagueIDs(ctx context.Context, eventID int64) ([]int64, error)
// Event Settings Views
GetEventsWithSettings(ctx context.Context, companyID int64, filter domain.EventFilter) ([]domain.EventWithSettings, int64, error) GetEventsWithSettings(ctx context.Context, companyID int64, filter domain.EventFilter) ([]domain.EventWithSettings, int64, error)
GetEventWithSettingByID(ctx context.Context, ID int64, companyID int64) (domain.EventWithSettings, error) GetEventWithSettingByID(ctx context.Context, ID int64, companyID int64) (domain.EventWithSettings, error)
UpdateTenantEventSettings(ctx context.Context, event domain.UpdateTenantEventSettings) error UpdateTenantEventSettings(ctx context.Context, event domain.UpdateTenantEventSettings) error
UpdateGlobalEventSettings(ctx context.Context, event domain.UpdateGlobalEventSettings) error UpdateGlobalEventSettings(ctx context.Context, event domain.UpdateGlobalEventSettings) error
GetSportAndLeagueIDs(ctx context.Context, eventID int64) ([]int64, error)
// Stats
GetEventStats(ctx context.Context, filter domain.EventStatsFilter) (domain.EventStats, error)
GetEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error)
} }

View File

@ -483,21 +483,8 @@ func (s *service) UpdateEventMonitored(ctx context.Context, eventID int64, IsMon
return s.store.UpdateEventMonitored(ctx, eventID, IsMonitored) return s.store.UpdateEventMonitored(ctx, eventID, IsMonitored)
} }
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) {
return s.store.GetEventWithSettingByID(ctx, ID, companyID)
}
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 {
return s.store.UpdateGlobalEventSettings(ctx, event)
}
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) return s.store.GetSportAndLeagueIDs(ctx, eventID)
} }

View File

@ -0,0 +1,22 @@
package event
import (
"context"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)
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) {
return s.store.GetEventWithSettingByID(ctx, ID, companyID)
}
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 {
return s.store.UpdateGlobalEventSettings(ctx, event)
}

View File

@ -0,0 +1,14 @@
package event
import (
"context"
"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) GetEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error) {
return s.store.GetEventStatsByInterval(ctx, filter)
}

View File

@ -461,7 +461,7 @@ func (s *Service) GetSportPerformance(ctx context.Context, filter domain.ReportF
func (s *Service) GenerateReport(ctx context.Context, from, to time.Time) error { func (s *Service) GenerateReport(ctx context.Context, from, to time.Time) error {
// Hardcoded output directory // Hardcoded output directory
outputDir := "C:/Users/User/Desktop/reports" outputDir := "reports"
// Ensure directory exists // Ensure directory exists
if err := os.MkdirAll(outputDir, os.ModePerm); err != nil { if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
@ -609,7 +609,6 @@ func writeSummaryCSV(companies []domain.CompanyReport, from, to time.Time, outpu
return nil return nil
} }
func (s *Service) fetchReportData(ctx context.Context, from, to time.Time) ( func (s *Service) fetchReportData(ctx context.Context, from, to time.Time) (
[]domain.CompanyReport, map[int64][]domain.BranchReport, error, []domain.CompanyReport, map[int64][]domain.BranchReport, error,
) { ) {

View File

@ -28,58 +28,58 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
spec string spec string
task func() task func()
}{ }{
{ // {
spec: "0 0 * * * *", // Every 1 hour // spec: "0 0 * * * *", // Every 1 hour
task: func() { // task: func() {
mongoLogger.Info("Began fetching upcoming events cron task") // mongoLogger.Info("Began fetching upcoming events cron task")
if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { // if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
mongoLogger.Error("Failed to fetch upcoming events", // mongoLogger.Error("Failed to fetch upcoming events",
zap.Error(err), // zap.Error(err),
) // )
} else { // } else {
mongoLogger.Info("Completed fetching upcoming events without errors") // mongoLogger.Info("Completed fetching upcoming events without errors")
} // }
}, // },
}, // },
{ // {
spec: "0 0 * * * *", // Every 1 hour (since its takes that long to fetch all the events) // spec: "0 0 * * * *", // Every 1 hour (since its takes that long to fetch all the events)
task: func() { // task: func() {
mongoLogger.Info("Began fetching non live odds cron task") // mongoLogger.Info("Began fetching non live odds cron task")
if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { // if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
mongoLogger.Error("Failed to fetch non live odds", // mongoLogger.Error("Failed to fetch non live odds",
zap.Error(err), // zap.Error(err),
) // )
} else { // } else {
mongoLogger.Info("Completed fetching non live odds without errors") // mongoLogger.Info("Completed fetching non live odds without errors")
} // }
}, // },
}, // },
{ // {
spec: "0 */5 * * * *", // Every 5 Minutes // spec: "0 */5 * * * *", // Every 5 Minutes
task: func() { // task: func() {
mongoLogger.Info("Began update all expired events status cron task") // mongoLogger.Info("Began update all expired events status cron task")
if _, err := resultService.CheckAndUpdateExpiredB365Events(context.Background()); err != nil { // if _, err := resultService.CheckAndUpdateExpiredB365Events(context.Background()); err != nil {
mongoLogger.Error("Failed to update expired events status", // mongoLogger.Error("Failed to update expired events status",
zap.Error(err), // zap.Error(err),
) // )
} else { // } else {
mongoLogger.Info("Completed expired events without errors") // mongoLogger.Info("Completed expired events without errors")
} // }
}, // },
}, // },
{ // {
spec: "0 */15 * * * *", // Every 15 Minutes // spec: "0 */15 * * * *", // Every 15 Minutes
task: func() { // task: func() {
mongoLogger.Info("Began updating bets based on event results cron task") // mongoLogger.Info("Began updating bets based on event results cron task")
if err := resultService.FetchB365ResultAndUpdateBets(context.Background()); err != nil { // if err := resultService.FetchB365ResultAndUpdateBets(context.Background()); err != nil {
mongoLogger.Error("Failed to process result", // mongoLogger.Error("Failed to process result",
zap.Error(err), // zap.Error(err),
) // )
} else { // } else {
mongoLogger.Info("Completed processing all event result outcomes without errors") // mongoLogger.Info("Completed processing all event result outcomes without errors")
} // }
}, // },
}, // },
// { // {
// spec: "0 0 0 * * 1", // Every Monday // spec: "0 0 0 * * 1", // Every Monday
// task: func() { // task: func() {
@ -96,7 +96,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
} }
for _, job := range schedule { for _, job := range schedule {
job.task() // job.task()
if _, err := c.AddFunc(job.spec, job.task); err != nil { if _, err := c.AddFunc(job.spec, job.task); err != nil {
mongoLogger.Error("Failed to schedule data fetching cron job", mongoLogger.Error("Failed to schedule data fetching cron job",
zap.Error(err), zap.Error(err),

View File

@ -11,6 +11,23 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
func ParseLeagueIDFromQuery(c *fiber.Ctx) (domain.ValidInt64, error) {
leagueIDQuery := c.Query("league_id")
if leagueIDQuery != "" {
leagueIDInt, err := strconv.ParseInt(leagueIDQuery, 10, 64)
if err != nil {
return domain.ValidInt64{}, fmt.Errorf("Failed to parse league_id %v: %w", leagueIDQuery, err)
}
return domain.ValidInt64{
Value: leagueIDInt,
Valid: true,
}, nil
}
return domain.ValidInt64{}, nil
}
// @Summary Retrieve all upcoming events // @Summary Retrieve all upcoming events
// @Description Retrieve all upcoming events from the database // @Description Retrieve all upcoming events from the database
// @Tags prematch // @Tags prematch
@ -54,6 +71,14 @@ func (h *Handler) GetAllEvents(c *fiber.Ctx) error {
Valid: true, 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") sportIDQuery := c.Query("sport_id")
var sportID domain.ValidInt32 var sportID domain.ValidInt32
if sportIDQuery != "" { if sportIDQuery != "" {
@ -195,9 +220,207 @@ func (h *Handler) GetAllEvents(c *fiber.Ctx) error {
res := domain.ConvertEventResList(events) res := domain.ConvertEventResList(events)
return response.WritePaginatedJSON(c, fiber.StatusOK, "All upcoming events retrieved successfully", res, nil, page, int(total)) 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 // @Summary Retrieve all upcoming events with settings
// @Description Retrieve all upcoming events settings from the database // @Description Retrieve all upcoming events settings from the database
// @Tags prematch // @Tags prematch
@ -676,6 +899,40 @@ 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 // @Summary Retrieve an upcoming by ID
// @Description Retrieve an upcoming event by ID // @Description Retrieve an upcoming event by ID
// @Tags prematch // @Tags prematch

View File

@ -0,0 +1,120 @@
package handlers
import (
"strconv"
"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"
)
func (h *Handler) GetEventStats(c *fiber.Ctx) error {
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,
}
}
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,
}
}
stats, err := h.eventSvc.GetEventStats(c.Context(), domain.EventStatsFilter{
LeagueID: leagueID,
SportID: sportID,
})
if err != nil {
h.InternalServerErrorLogger().Error("Failed to retrieve event status",
zap.Error(err),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
return response.WriteJSON(c, fiber.StatusOK, "Event Statistics retrieved successfully", stats, nil)
}
func (h *Handler) GetEventStatsByInterval(c *fiber.Ctx) error {
intervalParam := c.Query("interval", "day")
interval, err := domain.ParseDateInterval(intervalParam)
if err != nil {
h.BadRequestLogger().Error("invalid date interval",
zap.String("interval", c.Query("interval", "day")),
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "invalid date interval")
}
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,
}
}
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,
}
}
stats, err := h.eventSvc.GetEventStatsByInterval(c.Context(), domain.EventStatsByIntervalFilter{
Interval: interval,
LeagueID: leagueID,
SportID: sportID,
})
if err != nil {
h.InternalServerErrorLogger().Error("Failed to retrieve event status interval",
zap.Error(err),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
return response.WriteJSON(c, fiber.StatusOK, "Event Statistics retrieved successfully", stats, nil)
}

View File

@ -276,6 +276,12 @@ func (a *App) initAppRoutes() {
groupV1.Put("/events/:id/settings", a.authMiddleware, a.SuperAdminOnly, h.UpdateGlobalSettingList) groupV1.Put("/events/:id/settings", a.authMiddleware, a.SuperAdminOnly, h.UpdateGlobalSettingList)
groupV1.Get("/events/:id/bets", a.authMiddleware, a.SuperAdminOnly, h.GetBetsByEventID) 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("/upcoming-events", h.GetTenantUpcomingEvents)
tenant.Get("/top-leagues", h.GetTopLeagues) tenant.Get("/top-leagues", h.GetTopLeagues)
tenant.Get("/events", h.GetTenantEvents) tenant.Get("/events", h.GetTenantEvents)