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,
amount BIGINT NOT NULL,
total_odds REAL NOT NULL,
potential_win BIGINT GENERATED ALWAYS AS (amount * total_odds) STORED,
status INT NOT NULL,
user_id BIGINT NOT NULL,
is_shop_bet BOOLEAN NOT NULL,
@ -329,6 +330,29 @@ CREATE TABLE events (
is_monitored BOOLEAN NOT NULL DEFAULT FALSE,
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 (
id BIGSERIAL PRIMARY KEY,
event_id BIGINT NOT NULL,
@ -359,6 +383,7 @@ CREATE TABLE odds_market (
expires_at TIMESTAMP NOT NULL,
UNIQUE (event_id, market_id)
);
CREATE INDEX IF NOT EXISTS idx_odds_market_event_id ON odds_market (event_id);
CREATE TABLE odd_history (
id BIGSERIAL PRIMARY KEY,
odds_market_id BIGINT NOT NULL REFERENCES odds_market (id),
@ -416,6 +441,14 @@ CREATE TABLE companies (
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 (
id BIGINT PRIMARY KEY,
name TEXT NOT NULL,
@ -552,6 +585,16 @@ CREATE TABLE IF NOT EXISTS company_accumulator (
outcome_count BIGINT 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
CREATE VIEW companies_details AS
SELECT companies.*,
@ -675,6 +718,35 @@ SELECT sd.*,
st.verified AS transaction_verified
FROM shop_deposits AS sd
JOIN shop_transactions st ON st.id = sd.shop_transaction_id;
CREATE OR REPLACE VIEW event_detailed AS
SELECT ewc.*,
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
SELECT l.*,
cls.company_id,
@ -694,28 +766,21 @@ SELECT e.*,
) AS winning_upper_limit,
ces.updated_at as company_updated_at,
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
LEFT JOIN company_event_settings ces ON e.id = ces.event_id
JOIN leagues l ON l.id = e.league_id
LEFT JOIN event_bet_stats ebs ON ebs.event_id = ewc.id
LEFT JOIN (
SELECT event_id,
SUM(number_of_outcomes) AS total_outcomes
FROM odds_market
GROUP BY event_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
SELECT o.id,
o.event_id,
@ -733,14 +798,6 @@ SELECT o.id,
cos.updated_at
FROM odds_market o
LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id;
CREATE VIEW 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
ALTER TABLE refresh_tokens
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,
default_winning_upper_limit = EXCLUDED.default_winning_upper_limit,
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
SELECT id
FROM event_with_country
FROM event_detailed
WHERE is_live = true;
-- TODO: Figure out how to make this code reusable. SQLC prohibits CTEs within multiple queries
-- name: GetAllEvents :many
SELECT *
FROM event_with_country
FROM event_detailed
WHERE (
is_live = sqlc.narg('is_live')
OR sqlc.narg('is_live') IS NULL
@ -117,7 +105,7 @@ ORDER BY start_time ASC
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
-- name: GetTotalEvents :one
SELECT COUNT(*)
FROM event_with_country
FROM event_detailed
WHERE (
is_live = sqlc.narg('is_live')
OR sqlc.narg('is_live') IS NULL
@ -155,164 +143,17 @@ WHERE (
source = sqlc.narg('source')
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
SELECT *
FROM event_with_country
FROM event_detailed
WHERE id = $1
LIMIT 1;
-- name: GetEventBySourceID :one
SELECT *
FROM event_with_country
FROM event_detailed
WHERE source_event_id = $1
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
SELECT sport_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
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 = 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,
-- name: GetTotalEventStats :one
SELECT COUNT(*) AS event_count,
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,
COUNT(*) FILTER (
WHERE events.status = 'in_play'
@ -52,7 +49,75 @@ SELECT leagues.id,
COUNT(*) FILTER (
WHERE events.status = 'removed'
) AS removed
FROM leagues
JOIN events ON leagues.id = events.league_id
GROUP BY leagues.id,
leagues.name;
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
);
-- 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:
postgres:
container_name: fortunebet-backend-postgres-1
image: postgres:16-alpine
ports:
- 5422:5432

View File

@ -39,7 +39,7 @@ INSERT INTO bets (
company_id
)
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 {
@ -70,6 +70,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
&i.CompanyID,
&i.Amount,
&i.TotalOdds,
&i.PotentialWin,
&i.Status,
&i.UserID,
&i.IsShopBet,
@ -120,7 +121,7 @@ func (q *Queries) DeleteBetOutcome(ctx context.Context, betID int64) error {
}
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
wHERE (
user_id = $1
@ -196,6 +197,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi
&i.CompanyID,
&i.Amount,
&i.TotalOdds,
&i.PotentialWin,
&i.Status,
&i.UserID,
&i.IsShopBet,
@ -221,7 +223,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi
}
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
WHERE fast_code = $1
LIMIT 1
@ -235,6 +237,7 @@ func (q *Queries) GetBetByFastCode(ctx context.Context, fastCode string) (BetWit
&i.CompanyID,
&i.Amount,
&i.TotalOdds,
&i.PotentialWin,
&i.Status,
&i.UserID,
&i.IsShopBet,
@ -253,7 +256,7 @@ func (q *Queries) GetBetByFastCode(ctx context.Context, fastCode string) (BetWit
}
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
WHERE id = $1
`
@ -266,6 +269,7 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err
&i.CompanyID,
&i.Amount,
&i.TotalOdds,
&i.PotentialWin,
&i.Status,
&i.UserID,
&i.IsShopBet,
@ -284,7 +288,7 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err
}
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
WHERE user_id = $1
`
@ -303,6 +307,7 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID int64) ([]BetWithOu
&i.CompanyID,
&i.Amount,
&i.TotalOdds,
&i.PotentialWin,
&i.Status,
&i.UserID,
&i.IsShopBet,
@ -570,7 +575,7 @@ func (q *Queries) GetBetOutcomeViewByEventID(ctx context.Context, arg GetBetOutc
}
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
WHERE status = 2
AND processed = false
@ -590,6 +595,7 @@ func (q *Queries) GetBetsForCashback(ctx context.Context) ([]BetWithOutcome, err
&i.CompanyID,
&i.Amount,
&i.TotalOdds,
&i.PotentialWin,
&i.Status,
&i.UserID,
&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
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
FROM event_with_country
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_detailed
WHERE (
is_live = $1
OR $1 IS NULL
@ -79,7 +79,8 @@ type GetAllEventsParams struct {
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,
arg.IsLive,
arg.Status,
@ -97,9 +98,9 @@ func (q *Queries) GetAllEvents(ctx context.Context, arg GetAllEventsParams) ([]E
return nil, err
}
defer rows.Close()
var items []EventWithCountry
var items []EventDetailed
for rows.Next() {
var i EventWithCountry
var i EventDetailed
if err := rows.Scan(
&i.ID,
&i.SourceEventID,
@ -130,6 +131,10 @@ func (q *Queries) GetAllEvents(ctx context.Context, arg GetAllEventsParams) ([]E
&i.IsMonitored,
&i.LeagueCc,
&i.TotalOutcomes,
&i.NumberOfBets,
&i.TotalAmount,
&i.AvgBetAmount,
&i.TotalPotentialWinnings,
); err != nil {
return nil, err
}
@ -142,15 +147,15 @@ func (q *Queries) GetAllEvents(ctx context.Context, arg GetAllEventsParams) ([]E
}
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
FROM event_with_country
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_detailed
WHERE id = $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)
var i EventWithCountry
var i EventDetailed
err := row.Scan(
&i.ID,
&i.SourceEventID,
@ -181,13 +186,17 @@ func (q *Queries) GetEventByID(ctx context.Context, id int64) (EventWithCountry,
&i.IsMonitored,
&i.LeagueCc,
&i.TotalOutcomes,
&i.NumberOfBets,
&i.TotalAmount,
&i.AvgBetAmount,
&i.TotalPotentialWinnings,
)
return i, err
}
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
FROM event_with_country
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_detailed
WHERE source_event_id = $1
AND source = $2
`
@ -197,9 +206,9 @@ type GetEventBySourceIDParams struct {
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)
var i EventWithCountry
var i EventDetailed
err := row.Scan(
&i.ID,
&i.SourceEventID,
@ -230,317 +239,14 @@ func (q *Queries) GetEventBySourceID(ctx context.Context, arg GetEventBySourceID
&i.IsMonitored,
&i.LeagueCc,
&i.TotalOutcomes,
&i.NumberOfBets,
&i.TotalAmount,
&i.AvgBetAmount,
&i.TotalPotentialWinnings,
)
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
SELECT sport_id,
league_id
@ -560,99 +266,9 @@ func (q *Queries) GetSportAndLeagueIDs(ctx context.Context, id int64) (GetSportA
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
SELECT COUNT(*)
FROM event_with_country
FROM event_detailed
WHERE (
is_live = $1
OR $1 IS NULL
@ -837,7 +453,7 @@ func (q *Queries) IsEventMonitored(ctx context.Context, id int64) (bool, error)
const ListLiveEvents = `-- name: ListLiveEvents :many
SELECT id
FROM event_with_country
FROM event_detailed
WHERE is_live = true
`
@ -861,40 +477,6 @@ func (q *Queries) ListLiveEvents(ctx context.Context) ([]int64, error) {
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
UPDATE events
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"
)
const GetLeagueEventStat = `-- name: GetLeagueEventStat :many
SELECT leagues.id,
leagues.name,
COUNT(*) AS total_events,
const GetTotalEventStats = `-- name: GetTotalEventStats :one
SELECT COUNT(*) AS event_count,
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,
COUNT(*) FILTER (
WHERE events.status = 'in_play'
@ -54,16 +62,28 @@ SELECT leagues.id,
COUNT(*) FILTER (
WHERE events.status = 'removed'
) AS removed
FROM leagues
JOIN events ON leagues.id = events.league_id
GROUP BY leagues.id,
leagues.name
FROM events
WHERE (
events.league_id = $1
OR $1 IS NULL
)
AND (
events.sport_id = $2
OR $2 IS NULL
)
`
type GetLeagueEventStatRow struct {
ID int64 `json:"id"`
Name string `json:"name"`
TotalEvents int64 `json:"total_events"`
type GetTotalEventStatsParams struct {
LeagueID pgtype.Int8 `json:"league_id"`
SportID pgtype.Int4 `json:"sport_id"`
}
type GetTotalEventStatsRow 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"`
@ -79,19 +99,141 @@ type GetLeagueEventStatRow struct {
Removed int64 `json:"removed"`
}
func (q *Queries) GetLeagueEventStat(ctx context.Context) ([]GetLeagueEventStatRow, error) {
rows, err := q.db.Query(ctx, GetLeagueEventStat)
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 {
return nil, err
}
defer rows.Close()
var items []GetLeagueEventStatRow
var items []GetTotalEventStatsByIntervalRow
for rows.Next() {
var i GetLeagueEventStatRow
var i GetTotalEventStatsByIntervalRow
if err := rows.Scan(
&i.ID,
&i.Name,
&i.TotalEvents,
&i.Date,
&i.EventCount,
&i.TotalActiveEvents,
&i.TotalInactiveEvents,
&i.TotalFeaturedEvents,
&i.TotalLeagues,
&i.Pending,
&i.InPlay,
&i.ToBeFixed,
@ -115,41 +257,3 @@ func (q *Queries) GetLeagueEventStat(ctx context.Context) ([]GetLeagueEventStatR
}
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"`
Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"`
PotentialWin pgtype.Int8 `json:"potential_win"`
Status int32 `json:"status"`
UserID int64 `json:"user_id"`
IsShopBet bool `json:"is_shop_bet"`
@ -70,6 +71,7 @@ type BetWithOutcome struct {
CompanyID int64 `json:"company_id"`
Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"`
PotentialWin pgtype.Int8 `json:"potential_win"`
Status int32 `json:"status"`
UserID int64 `json:"user_id"`
IsShopBet bool `json:"is_shop_bet"`
@ -208,6 +210,15 @@ type CompanySetting struct {
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 {
ID int64 `json:"id"`
CustomerID int64 `json:"customer_id"`
@ -340,14 +351,16 @@ type Event struct {
IsMonitored bool `json:"is_monitored"`
}
type EventHistory struct {
ID int64 `json:"id"`
type EventBetStat struct {
EventID int64 `json:"event_id"`
Status string `json:"status"`
CreatedAt pgtype.Timestamp `json:"created_at"`
NumberOfBets pgtype.Int8 `json:"number_of_bets"`
TotalAmount pgtype.Int8 `json:"total_amount"`
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"`
SourceEventID string `json:"source_event_id"`
SportID int32 `json:"sport_id"`
@ -377,6 +390,53 @@ type EventWithCountry struct {
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 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 {
@ -414,6 +474,10 @@ type EventWithSetting struct {
CompanyUpdatedAt pgtype.Timestamp `json:"company_updated_at"`
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 ExchangeRate struct {
@ -624,6 +688,16 @@ type RefreshToken struct {
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 {
ID int64 `json:"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
import (
"fmt"
"time"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"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 {
ID int64
SourceEventID string
@ -120,7 +23,6 @@ type BaseEvent struct {
StartTime time.Time
Source EventSource
Status EventStatus
TotalOddOutcomes int64
IsMonitored bool
DefaultIsFeatured bool
DefaultIsActive bool
@ -132,7 +34,13 @@ type BaseEvent struct {
MatchPeriod ValidInt
IsLive bool
FetchedAt time.Time
TotalOddOutcomes int64
NumberOfBets int64
TotalAmount Currency
AvgBetAmount Currency
TotalPotentialWinnings Currency
}
type BaseEventRes struct {
ID int64 `json:"id"`
SourceEventID string `json:"source_event_id"`
@ -150,7 +58,6 @@ type BaseEventRes struct {
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"`
DefaultIsFeatured bool `json:"default_is_featured"`
DefaultIsActive bool `json:"default_is_active"`
@ -162,40 +69,11 @@ type BaseEventRes struct {
MatchPeriod int `json:"match_period"`
IsLive bool `json:"is_live"`
FetchedAt time.Time `json:"fetched_at"`
}
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
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
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 CreateEvent struct {
@ -216,89 +94,6 @@ type CreateEvent struct {
Source EventSource
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 {
Query ValidString
SportID ValidInt32
@ -315,7 +110,7 @@ type EventFilter struct {
Source ValidEventSource
}
func ConvertDBEvent(event dbgen.EventWithCountry) BaseEvent {
func ConvertDBEvent(event dbgen.EventDetailed) BaseEvent {
return BaseEvent{
ID: event.ID,
SourceEventID: event.SourceEventID,
@ -336,7 +131,6 @@ func ConvertDBEvent(event dbgen.EventWithCountry) BaseEvent {
StartTime: event.StartTime.Time.UTC(),
Source: EventSource(event.Source),
Status: EventStatus(event.Status),
TotalOddOutcomes: event.TotalOutcomes,
DefaultIsFeatured: event.DefaultIsFeatured,
IsMonitored: event.IsMonitored,
DefaultIsActive: event.DefaultIsActive,
@ -363,10 +157,15 @@ func ConvertDBEvent(event dbgen.EventWithCountry) BaseEvent {
},
IsLive: event.IsLive,
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))
for i, e := range events {
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 {
return BaseEventRes{
@ -504,7 +213,6 @@ func ConvertEventRes(event BaseEvent) BaseEventRes {
StartTime: event.StartTime.UTC(),
Source: EventSource(event.Source),
Status: EventStatus(event.Status),
TotalOddOutcomes: event.TotalOddOutcomes,
DefaultIsFeatured: event.DefaultIsFeatured,
IsMonitored: event.IsMonitored,
DefaultIsActive: event.DefaultIsActive,
@ -516,6 +224,11 @@ func ConvertEventRes(event BaseEvent) BaseEventRes {
MatchPeriod: event.MatchPeriod.Value,
IsLive: event.IsLive,
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 {
@ -526,47 +239,3 @@ func ConvertEventResList(events []BaseEvent) []BaseEventRes {
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"
type TimeFrame string
type ReportTimeFrame string
const (
Daily TimeFrame = "daily"
Weekly TimeFrame = "weekly"
Monthly TimeFrame = "monthly"
Daily ReportTimeFrame = "daily"
Weekly ReportTimeFrame = "weekly"
Monthly ReportTimeFrame = "monthly"
)
type ReportFrequency string
@ -39,8 +39,8 @@ type ReportData struct {
Deposits float64
TotalTickets int64
VirtualGameStats []VirtualGameStat
CompanyReports []CompanyReport
BranchReports []BranchReport
CompanyReports []CompanyStats
BranchReports []BranchStats
}
type VirtualGameStat struct {
@ -51,7 +51,7 @@ type VirtualGameStat struct {
type Report struct {
ID string
TimeFrame TimeFrame
TimeFrame ReportTimeFrame
PeriodStart time.Time
PeriodEnd time.Time
TotalBets int
@ -282,7 +282,6 @@ type BetAnalysis struct {
AverageOdds float64 `json:"average_odds"`
}
// ReportFilter contains filters for report generation
type ReportFilter struct {
StartTime ValidTime `json:"start_time"`
@ -484,27 +483,6 @@ type LiveWalletMetrics struct {
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 {
// CompanyID int64

View File

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

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
// 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 {
s.mongoLogger.Fatal("Amount",
zap.Int64("wallet_id", walletID),
zap.Float32("amount", deductedAmount),
zap.Error(err),
)
return err
}
_, err = s.walletSvc.DeductFromWallet(ctx,
walletID, domain.ToCurrency(deductedAmount), domain.ValidInt64{
Value: userID,
Valid: true,
}, domain.TRANSFER_DIRECT,
fmt.Sprintf("Deducted %v amount from wallet by system while placing bet", deductedAmount))
// if deductedAmount == 0 {
// s.mongoLogger.Fatal("Amount",
// zap.Int64("wallet_id", walletID),
// zap.Float32("amount", deductedAmount),
// zap.Error(err),
// )
// return err
// }
// _, err = s.walletSvc.DeductFromWallet(ctx,
// walletID, domain.ToCurrency(deductedAmount), domain.ValidInt64{
// Value: userID,
// Valid: true,
// }, domain.TRANSFER_DIRECT,
// fmt.Sprintf("Deducted %v amount from wallet by system while placing bet", deductedAmount))
if err != nil {
s.mongoLogger.Error("failed to deduct from wallet",
zap.Int64("wallet_id", walletID),
zap.Float32("amount", deductedAmount),
zap.Error(err),
)
return err
}
// if err != nil {
// s.mongoLogger.Error("failed to deduct from wallet",
// zap.Int64("wallet_id", walletID),
// zap.Float32("amount", deductedAmount),
// zap.Error(err),
// )
// return err
// }
return nil
}

View File

@ -16,9 +16,15 @@ type Service interface {
UpdateEventStatus(ctx context.Context, eventID int64, status domain.EventStatus) error
IsEventMonitored(ctx context.Context, eventID int64) (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)
GetEventWithSettingByID(ctx context.Context, ID int64, companyID int64) (domain.EventWithSettings, error)
UpdateTenantEventSettings(ctx context.Context, event domain.UpdateTenantEventSettings) 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)
}
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) {
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 {
// Hardcoded output directory
outputDir := "C:/Users/User/Desktop/reports"
outputDir := "reports"
// Ensure directory exists
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
}
func (s *Service) fetchReportData(ctx context.Context, from, to time.Time) (
[]domain.CompanyReport, map[int64][]domain.BranchReport, error,
) {

View File

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

View File

@ -11,6 +11,23 @@ import (
"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
// @Description Retrieve all upcoming events from the database
// @Tags prematch
@ -54,6 +71,14 @@ func (h *Handler) GetAllEvents(c *fiber.Ctx) error {
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 != "" {
@ -195,9 +220,207 @@ func (h *Handler) GetAllEvents(c *fiber.Ctx) error {
res := domain.ConvertEventResList(events)
return response.WritePaginatedJSON(c, fiber.StatusOK, "All upcoming events retrieved successfully", res, nil, page, int(total))
}
// @Summary Retrieve all upcoming events
// @Description Retrieve all upcoming events from the database
// @Tags prematch
// @Accept json
// @Produce json
// @Param page query int false "Page number"
// @Param page_size query int false "Page size"
// @Param league_id query string false "League ID Filter"
// @Param sport_id query string false "Sport ID Filter"
// @Param cc query string false "Country Code Filter"
// @Param first_start_time query string false "Start Time"
// @Param last_start_time query string false "End Time"
// @Success 200 {array} domain.BaseEvent
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/detailed/events [get]
func (h *Handler) GetAllDetailedEvents(c *fiber.Ctx) error {
page := c.QueryInt("page", 1)
pageSize := c.QueryInt("page_size", 10)
limit := domain.ValidInt32{
Value: int32(pageSize),
Valid: true,
}
offset := domain.ValidInt32{
Value: int32(page - 1),
Valid: true,
}
leagueIDQuery := c.Query("league_id")
var leagueID domain.ValidInt64
if leagueIDQuery != "" {
leagueIDInt, err := strconv.ParseInt(leagueIDQuery, 10, 64)
if err != nil {
h.BadRequestLogger().Error("invalid league id",
zap.String("league_id", leagueIDQuery),
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "invalid league id")
}
leagueID = domain.ValidInt64{
Value: leagueIDInt,
Valid: true,
}
}
// TODO: Go through the all the handler functions and change them into something like this
// leagueID, err := ParseLeagueIDFromQuery(c)
// if err != nil {
// h.BadRequestLogger().Info("invalid league id", zap.Error(err))
// return fiber.NewError(fiber.StatusBadRequest, "invalid league id")
// }
sportIDQuery := c.Query("sport_id")
var sportID domain.ValidInt32
if sportIDQuery != "" {
sportIDint, err := strconv.Atoi(sportIDQuery)
if err != nil {
h.BadRequestLogger().Info("invalid sport id",
zap.String("sportID", sportIDQuery),
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "invalid sport id")
}
sportID = domain.ValidInt32{
Value: int32(sportIDint),
Valid: true,
}
}
searchQuery := c.Query("query")
searchString := domain.ValidString{
Value: searchQuery,
Valid: searchQuery != "",
}
firstStartTimeQuery := c.Query("first_start_time")
var firstStartTime domain.ValidTime
if firstStartTimeQuery != "" {
firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery)
if err != nil {
h.BadRequestLogger().Info("invalid start_time format",
zap.String("first_start_time", firstStartTimeQuery),
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
}
firstStartTime = domain.ValidTime{
Value: firstStartTimeParsed,
Valid: true,
}
}
lastStartTimeQuery := c.Query("last_start_time")
var lastStartTime domain.ValidTime
if lastStartTimeQuery != "" {
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
if err != nil {
h.BadRequestLogger().Info("invalid last_start_time format",
zap.String("last_start_time", lastStartTimeQuery),
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid start_time format")
}
lastStartTime = domain.ValidTime{
Value: lastStartTimeParsed,
Valid: true,
}
}
countryCodeQuery := c.Query("cc")
countryCode := domain.ValidString{
Value: countryCodeQuery,
Valid: countryCodeQuery != "",
}
isFeaturedQuery := c.Query("is_featured")
var isFeatured domain.ValidBool
if isFeaturedQuery != "" {
isFeaturedParsed, err := strconv.ParseBool(isFeaturedQuery)
if err != nil {
h.BadRequestLogger().Error("Failed to parse isFeatured",
zap.String("is_featured", isFeaturedQuery),
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "Failed to parse is_shop_bet")
}
isFeatured = domain.ValidBool{
Value: isFeaturedParsed,
Valid: true,
}
}
isActiveQuery := c.Query("is_active")
var isActive domain.ValidBool
if isActiveQuery != "" {
isActiveParsed, err := strconv.ParseBool(isActiveQuery)
if err != nil {
h.BadRequestLogger().Error("Failed to parse isActive",
zap.String("is_active", isActiveQuery),
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "Failed to parse is_active")
}
isActive = domain.ValidBool{
Value: isActiveParsed,
Valid: true,
}
}
statusQuery := c.Query("status")
var eventStatus domain.ValidEventStatus
if statusQuery != "" {
eventStatusParsed, err := domain.ParseEventStatus(statusQuery)
if err != nil {
h.BadRequestLogger().Error("Failed to parse statusQuery",
zap.String("is_featured", isFeaturedQuery),
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "invalid event status string")
}
eventStatus = domain.ValidEventStatus{
Value: eventStatusParsed,
Valid: true,
}
}
events, total, err := h.eventSvc.GetAllDetailedEvents(
c.Context(), domain.EventFilter{
SportID: sportID,
LeagueID: leagueID,
Query: searchString,
FirstStartTime: firstStartTime,
LastStartTime: lastStartTime,
Limit: limit,
Offset: offset,
CountryCode: countryCode,
Featured: isFeatured,
Active: isActive,
Status: eventStatus,
})
// fmt.Printf("League ID: %v", leagueID)
if err != nil {
h.InternalServerErrorLogger().Error("Failed to retrieve all upcoming events",
zap.Error(err),
)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
res := domain.ConvertDetailedEventResList(events)
return response.WritePaginatedJSON(c, fiber.StatusOK, "All upcoming events retrieved successfully", res, nil, page, int(total))
}
func (h *Handler) ExportEvents(c *fiber.Ctx) error {
}
// @Summary Retrieve all upcoming events with settings
// @Description Retrieve all upcoming events settings from the database
// @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
// @Description Retrieve an upcoming event by ID
// @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.Get("/events/:id/bets", a.authMiddleware, a.SuperAdminOnly, h.GetBetsByEventID)
groupV1.Get("/detailed/events", a.authMiddleware, h.GetAllDetailedEvents)
groupV1.Get("/detailed/events/:id", a.authMiddleware, h.GetDetailedEventByID)
groupV1.Get("/stats/total/events", h.GetEventStats)
groupV1.Get("/stats/interval/events", h.GetEventStatsByInterval)
tenant.Get("/upcoming-events", h.GetTenantUpcomingEvents)
tenant.Get("/top-leagues", h.GetTopLeagues)
tenant.Get("/events", h.GetTenantEvents)