diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 5214d69..b33edf3 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -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); diff --git a/db/query/branch_stats.sql b/db/query/branch_stats.sql new file mode 100644 index 0000000..471b66e --- /dev/null +++ b/db/query/branch_stats.sql @@ -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; \ No newline at end of file diff --git a/db/query/company_stats.sql b/db/query/company_stats.sql new file mode 100644 index 0000000..b843f45 --- /dev/null +++ b/db/query/company_stats.sql @@ -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; \ No newline at end of file diff --git a/db/query/events.sql b/db/query/events.sql index d62b380..63017c9 100644 --- a/db/query/events.sql +++ b/db/query/events.sql @@ -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 diff --git a/db/query/events_bet_stats.sql b/db/query/events_bet_stats.sql new file mode 100644 index 0000000..d839a05 --- /dev/null +++ b/db/query/events_bet_stats.sql @@ -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; \ No newline at end of file diff --git a/db/query/events_stat.sql b/db/query/events_stat.sql index cf96ad5..384e0f5 100644 --- a/db/query/events_stat.sql +++ b/db/query/events_stat.sql @@ -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; \ No newline at end of file +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; \ No newline at end of file diff --git a/db/query/events_with_settings.sql b/db/query/events_with_settings.sql new file mode 100644 index 0000000..c9bbb34 --- /dev/null +++ b/db/query/events_with_settings.sql @@ -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; \ No newline at end of file diff --git a/db/query/league_stats.sql b/db/query/league_stats.sql new file mode 100644 index 0000000..669529d --- /dev/null +++ b/db/query/league_stats.sql @@ -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; \ No newline at end of file diff --git a/db/query/report.sql b/db/query/report.sql index b352554..e69de29 100644 --- a/db/query/report.sql +++ b/db/query/report.sql @@ -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; diff --git a/docker-compose.yml b/docker-compose.yml index d80ff06..3005b90 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,7 @@ version: "3.8" services: postgres: + container_name: fortunebet-backend-postgres-1 image: postgres:16-alpine ports: - 5422:5432 diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index a2e631d..9379338 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -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, diff --git a/gen/db/branch_stats.sql.go b/gen/db/branch_stats.sql.go new file mode 100644 index 0000000..b1204c4 --- /dev/null +++ b/gen/db/branch_stats.sql.go @@ -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 +} diff --git a/gen/db/company_stats.sql.go b/gen/db/company_stats.sql.go new file mode 100644 index 0000000..26eb0e0 --- /dev/null +++ b/gen/db/company_stats.sql.go @@ -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 +} diff --git a/gen/db/events.sql.go b/gen/db/events.sql.go index 767a4e2..0621dd5 100644 --- a/gen/db/events.sql.go +++ b/gen/db/events.sql.go @@ -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, diff --git a/gen/db/events_bet_stats.sql.go b/gen/db/events_bet_stats.sql.go new file mode 100644 index 0000000..942dbcc --- /dev/null +++ b/gen/db/events_bet_stats.sql.go @@ -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 +} diff --git a/gen/db/events_stat.sql.go b/gen/db/events_stat.sql.go index 677fa2a..816f493 100644 --- a/gen/db/events_stat.sql.go +++ b/gen/db/events_stat.sql.go @@ -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,44 +62,178 @@ 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"` - 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 GetTotalEventStatsParams struct { + LeagueID pgtype.Int8 `json:"league_id"` + SportID pgtype.Int4 `json:"sport_id"` } -func (q *Queries) GetLeagueEventStat(ctx context.Context) ([]GetLeagueEventStatRow, error) { - rows, err := q.db.Query(ctx, GetLeagueEventStat) +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"` + Ended int64 `json:"ended"` + Postponed int64 `json:"postponed"` + Cancelled int64 `json:"cancelled"` + Walkover int64 `json:"walkover"` + Interrupted int64 `json:"interrupted"` + Abandoned int64 `json:"abandoned"` + Retired int64 `json:"retired"` + Suspended int64 `json:"suspended"` + DecidedByFa int64 `json:"decided_by_fa"` + Removed int64 `json:"removed"` +} + +func (q *Queries) GetTotalEventStats(ctx context.Context, arg GetTotalEventStatsParams) (GetTotalEventStatsRow, error) { + row := q.db.QueryRow(ctx, GetTotalEventStats, arg.LeagueID, arg.SportID) + var i GetTotalEventStatsRow + err := row.Scan( + &i.EventCount, + &i.TotalActiveEvents, + &i.TotalInactiveEvents, + &i.TotalFeaturedEvents, + &i.TotalLeagues, + &i.Pending, + &i.InPlay, + &i.ToBeFixed, + &i.Ended, + &i.Postponed, + &i.Cancelled, + &i.Walkover, + &i.Interrupted, + &i.Abandoned, + &i.Retired, + &i.Suspended, + &i.DecidedByFa, + &i.Removed, + ) + return i, err +} + +const GetTotalEventStatsByInterval = `-- name: GetTotalEventStatsByInterval :many +SELECT DATE_TRUNC($1, start_time)::timestamp AS date, + COUNT(*) AS event_count, + COUNT(*) FILTER ( + WHERE events.default_is_active = TRUE + ) AS total_active_events, + COUNT(*) FILTER ( + WHERE events.default_is_active = FALSE + ) AS total_inactive_events, + COUNT(*) FILTER ( + WHERE events.default_is_featured = TRUE + ) AS total_featured_events, + COUNT(DISTINCT league_id) as total_leagues, + COUNT(*) FILTER ( + WHERE events.status = 'upcoming' + ) AS pending, + COUNT(*) FILTER ( + WHERE events.status = 'in_play' + ) AS in_play, + COUNT(*) FILTER ( + WHERE events.status = 'to_be_fixed' + ) AS to_be_fixed, + COUNT(*) FILTER ( + WHERE events.status = 'ended' + ) AS ended, + COUNT(*) FILTER ( + WHERE events.status = 'postponed' + ) AS postponed, + COUNT(*) FILTER ( + WHERE events.status = 'cancelled' + ) AS cancelled, + COUNT(*) FILTER ( + WHERE events.status = 'walkover' + ) AS walkover, + COUNT(*) FILTER ( + WHERE events.status = 'interrupted' + ) AS interrupted, + COUNT(*) FILTER ( + WHERE events.status = 'abandoned' + ) AS abandoned, + COUNT(*) FILTER ( + WHERE events.status = 'retired' + ) AS retired, + COUNT(*) FILTER ( + WHERE events.status = 'suspended' + ) AS suspended, + COUNT(*) FILTER ( + WHERE events.status = 'decided_by_fa' + ) AS decided_by_fa, + COUNT(*) FILTER ( + WHERE events.status = 'removed' + ) AS removed +FROM events +WHERE ( + events.league_id = $2 + OR $2 IS NULL + ) + AND ( + events.sport_id = $3 + OR $3 IS NULL + ) +GROUP BY date +ORDER BY date +` + +type GetTotalEventStatsByIntervalParams struct { + Interval pgtype.Text `json:"interval"` + LeagueID pgtype.Int8 `json:"league_id"` + SportID pgtype.Int4 `json:"sport_id"` +} + +type GetTotalEventStatsByIntervalRow struct { + Date pgtype.Timestamp `json:"date"` + EventCount int64 `json:"event_count"` + TotalActiveEvents int64 `json:"total_active_events"` + TotalInactiveEvents int64 `json:"total_inactive_events"` + TotalFeaturedEvents int64 `json:"total_featured_events"` + TotalLeagues int64 `json:"total_leagues"` + Pending int64 `json:"pending"` + InPlay int64 `json:"in_play"` + ToBeFixed int64 `json:"to_be_fixed"` + Ended int64 `json:"ended"` + Postponed int64 `json:"postponed"` + Cancelled int64 `json:"cancelled"` + Walkover int64 `json:"walkover"` + Interrupted int64 `json:"interrupted"` + Abandoned int64 `json:"abandoned"` + Retired int64 `json:"retired"` + Suspended int64 `json:"suspended"` + DecidedByFa int64 `json:"decided_by_fa"` + Removed int64 `json:"removed"` +} + +func (q *Queries) GetTotalEventStatsByInterval(ctx context.Context, arg GetTotalEventStatsByIntervalParams) ([]GetTotalEventStatsByIntervalRow, error) { + rows, err := q.db.Query(ctx, GetTotalEventStatsByInterval, arg.Interval, arg.LeagueID, arg.SportID) if err != nil { 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 -} diff --git a/gen/db/events_with_settings.sql.go b/gen/db/events_with_settings.sql.go new file mode 100644 index 0000000..c320e67 --- /dev/null +++ b/gen/db/events_with_settings.sql.go @@ -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 +} diff --git a/gen/db/league_stats.sql.go b/gen/db/league_stats.sql.go new file mode 100644 index 0000000..b1ad816 --- /dev/null +++ b/gen/db/league_stats.sql.go @@ -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 +} diff --git a/gen/db/models.go b/gen/db/models.go index f2cbd47..eac3dbd 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -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"` - EventID int64 `json:"event_id"` - Status string `json:"status"` - CreatedAt pgtype.Timestamp `json:"created_at"` +type EventBetStat struct { + EventID int64 `json:"event_id"` + 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"` diff --git a/internal/domain/branch_report.go b/internal/domain/branch_report.go new file mode 100644 index 0000000..038d5e7 --- /dev/null +++ b/internal/domain/branch_report.go @@ -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 +} diff --git a/internal/domain/company_report.go b/internal/domain/company_report.go new file mode 100644 index 0000000..fba3a81 --- /dev/null +++ b/internal/domain/company_report.go @@ -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 +} diff --git a/internal/domain/event.go b/internal/domain/event.go index f46032d..40aa66d 100644 --- a/internal/domain/event.go +++ b/internal/domain/event.go @@ -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, @@ -361,12 +155,17 @@ func ConvertDBEvent(event dbgen.EventWithCountry) BaseEvent { Value: int(event.MatchPeriod.Int32), Valid: event.MatchPeriod.Valid, }, - IsLive: event.IsLive, - FetchedAt: event.FetchedAt.Time, + 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 -} diff --git a/internal/domain/event_source.go b/internal/domain/event_source.go new file mode 100644 index 0000000..c13fbda --- /dev/null +++ b/internal/domain/event_source.go @@ -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, + } +} diff --git a/internal/domain/event_stats.go b/internal/domain/event_stats.go new file mode 100644 index 0000000..2657e43 --- /dev/null +++ b/internal/domain/event_stats.go @@ -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"` +} diff --git a/internal/domain/event_status.go b/internal/domain/event_status.go new file mode 100644 index 0000000..38d74da --- /dev/null +++ b/internal/domain/event_status.go @@ -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, + } +} + diff --git a/internal/domain/event_with_settings.go b/internal/domain/event_with_settings.go new file mode 100644 index 0000000..a441856 --- /dev/null +++ b/internal/domain/event_with_settings.go @@ -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 +} diff --git a/internal/domain/interval.go b/internal/domain/interval.go new file mode 100644 index 0000000..b2cd493 --- /dev/null +++ b/internal/domain/interval.go @@ -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 +} diff --git a/internal/domain/report.go b/internal/domain/report_data.go similarity index 96% rename from internal/domain/report.go rename to internal/domain/report_data.go index bfa6f9d..87f1fb9 100644 --- a/internal/domain/report.go +++ b/internal/domain/report_data.go @@ -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 diff --git a/internal/repository/company.go b/internal/repository/company.go index a72310b..c89307c 100644 --- a/internal/repository/company.go +++ b/internal/repository/company.go @@ -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, diff --git a/internal/repository/company_stats.go b/internal/repository/company_stats.go new file mode 100644 index 0000000..995c6a5 --- /dev/null +++ b/internal/repository/company_stats.go @@ -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) +} \ No newline at end of file diff --git a/internal/repository/event.go b/internal/repository/event.go index fcc20c5..d318de0 100644 --- a/internal/repository/event.go +++ b/internal/repository/event.go @@ -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 { diff --git a/internal/repository/event_stats.go b/internal/repository/event_stats.go new file mode 100644 index 0000000..8fde225 --- /dev/null +++ b/internal/repository/event_stats.go @@ -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 +} diff --git a/internal/repository/event_with_settings.go b/internal/repository/event_with_settings.go new file mode 100644 index 0000000..98611ab --- /dev/null +++ b/internal/repository/event_with_settings.go @@ -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)) +} \ No newline at end of file diff --git a/internal/repository/report.go b/internal/repository/report.go index bff2ad0..365a4b7 100644 --- a/internal/repository/report.go +++ b/internal/repository/report.go @@ -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) +// } diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index d6ff26a..d9927e4 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -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 } diff --git a/internal/services/event/port.go b/internal/services/event/port.go index 8653ce6..1229b2c 100644 --- a/internal/services/event/port.go +++ b/internal/services/event/port.go @@ -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) + UpdateTenantEventSettings(ctx context.Context, event domain.UpdateTenantEventSettings) error + UpdateGlobalEventSettings(ctx context.Context, event domain.UpdateGlobalEventSettings) error + + // Stats + GetEventStats(ctx context.Context, filter domain.EventStatsFilter) (domain.EventStats, error) + GetEventStatsByInterval(ctx context.Context, filter domain.EventStatsByIntervalFilter) ([]domain.EventStatsByInterval, error) } diff --git a/internal/services/event/service.go b/internal/services/event/service.go index b083641..0f5c897 100644 --- a/internal/services/event/service.go +++ b/internal/services/event/service.go @@ -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) } + + diff --git a/internal/services/event/settings.go b/internal/services/event/settings.go new file mode 100644 index 0000000..b711722 --- /dev/null +++ b/internal/services/event/settings.go @@ -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) +} diff --git a/internal/services/event/stats.go b/internal/services/event/stats.go new file mode 100644 index 0000000..bc33e62 --- /dev/null +++ b/internal/services/event/stats.go @@ -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) +} diff --git a/internal/services/report/service.go b/internal/services/report/service.go index d19cef3..8028af2 100644 --- a/internal/services/report/service.go +++ b/internal/services/report/service.go @@ -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, ) { diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index d296879..a304883 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -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), diff --git a/internal/web_server/handlers/event_handler.go b/internal/web_server/handlers/event_handler.go index 533c76f..3a2534b 100644 --- a/internal/web_server/handlers/event_handler.go +++ b/internal/web_server/handlers/event_handler.go @@ -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 diff --git a/internal/web_server/handlers/event_stats_handler.go b/internal/web_server/handlers/event_stats_handler.go new file mode 100644 index 0000000..e641bbc --- /dev/null +++ b/internal/web_server/handlers/event_stats_handler.go @@ -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) +} diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 7fc7049..63e9193 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -275,6 +275,12 @@ func (a *App) initAppRoutes() { groupV1.Patch("/events/:id/is_monitored", a.authMiddleware, a.SuperAdminOnly, h.SetEventIsMonitored) 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)