diff --git a/cmd/main.go b/cmd/main.go index 1297a2c..3f0972b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -41,7 +41,6 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/institutions" issuereporting "github.com/SamuelTariku/FortuneBet-Backend/internal/services/issue_reporting" - "github.com/SamuelTariku/FortuneBet-Backend/internal/services/kafka" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/league" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/messenger" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification" @@ -122,10 +121,10 @@ func main() { // var userStore user.UserStore // Initialize producer - topic := "wallet-balance-topic" - producer := kafka.NewProducer(cfg.KafkaBrokers, topic) + // topic := "wallet-balance-topic" + // producer := kafka.NewProducer(cfg.KafkaBrokers, topic) - notificationSvc := notificationservice.New(notificationRepo, domain.MongoDBLogger, logger, cfg, messengerSvc, userSvc, cfg.KafkaBrokers) + notificationSvc := notificationservice.New(notificationRepo, domain.MongoDBLogger, logger, cfg, messengerSvc, userSvc) walletSvc := wallet.NewService( wallet.WalletStore(store), @@ -135,7 +134,6 @@ func main() { userSvc, domain.MongoDBLogger, logger, - producer, ) branchSvc := branch.NewService(store) @@ -195,8 +193,8 @@ func main() { store, ) - go httpserver.SetupReportandVirtualGameCronJobs(context.Background(), reportSvc, veliVirtualGameService, "C:/Users/User/Desktop") go httpserver.StartEnetPulseCron(enePulseSvc, domain.MongoDBLogger) + go httpserver.SetupReportandVirtualGameCronJobs(context.Background(), reportSvc, veliVirtualGameService, "C:/Users/User/Desktop") go httpserver.ProcessBetCashback(context.TODO(), betSvc) bankRepository := repository.NewBankRepository(store) @@ -233,13 +231,13 @@ func main() { fixerFertcherSvc, ) - exchangeWorker := currency.NewExchangeRateWorker(fixerFertcherSvc, logger, cfg) - exchangeWorker.Start(context.Background()) - defer exchangeWorker.Stop() + // exchangeWorker := currency.NewExchangeRateWorker(fixerFertcherSvc, logger, cfg) + // exchangeWorker.Start(context.Background()) + // defer exchangeWorker.Stop() go walletMonitorSvc.Start() httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc, domain.MongoDBLogger) - httpserver.StartTicketCrons(*ticketSvc, domain.MongoDBLogger) + httpserver.StartCleanupCrons(*ticketSvc, notificationSvc, domain.MongoDBLogger) issueReportingRepo := repository.NewReportedIssueRepository(store) diff --git a/db/dev_data/betfidel_data.sql b/db/dev_data/betfidel_data.sql new file mode 100644 index 0000000..29bc5e3 --- /dev/null +++ b/db/dev_data/betfidel_data.sql @@ -0,0 +1,221 @@ +CREATE EXTENSION IF NOT EXISTS pgcrypto; +DO $$ +DECLARE _admin_id bigint; +_manager_id bigint; +_company_wallet_id bigint; +_company_id bigint; +_branch_id bigint; +_branch_wallet_id bigint; +_cashier_id bigint; +BEGIN +INSERT INTO users ( + first_name, + last_name, + email, + phone_number, + password, + role, + email_verified, + phone_verified, + created_at, + updated_at, + suspended + ) +VALUES ( + 'Admin', + 'BetFidel', + 'admin.betfidel@gmail.com', + NULL, + crypt('password@123', gen_salt('bf'))::bytea, + 'admin', + TRUE, + FALSE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + FALSE + ) ON CONFLICT (email) DO +UPDATE +SET updated_at = EXCLUDED.updated_at +RETURNING id INTO STRICT _admin_id; +INSERT INTO users ( + first_name, + last_name, + email, + phone_number, + password, + role, + email_verified, + phone_verified, + created_at, + updated_at, + suspended, + company_id + ) +VALUES ( + 'Manager', + 'BetFidel', + 'manager.betfidel@gmail.com', + NULL, + crypt('password@123', gen_salt('bf'))::bytea, + 'branch_manager', + TRUE, + FALSE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + FALSE, + _company_id + ) ON CONFLICT (email) DO +UPDATE +SET updated_at = EXCLUDED.updated_at +RETURNING id INTO STRICT _manager_id; +INSERT INTO wallets ( + balance, + is_withdraw, + is_bettable, + is_transferable, + user_id, + type, + currency, + is_active, + created_at, + updated_at + ) +VALUES ( + 10000, + TRUE, + TRUE, + TRUE, + _admin_id, + 'company_wallet', + 'ETB', + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ) ON CONFLICT (user_id, type) DO +UPDATE +SET updated_at = EXCLUDED.updated_at +RETURNING id INTO STRICT _company_wallet_id; +INSERT INTO companies ( + name, + slug, + admin_id, + wallet_id, + deducted_percentage, + is_active, + created_at, + updated_at + ) +VALUES ( + 'FidelBet', + 'betfidel', + _admin_id, + _company_wallet_id, + 0.15, + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ) ON CONFLICT (slug) DO +UPDATE +SET updated_at = EXCLUDED.updated_at +RETURNING id INTO STRICT _company_id; +UPDATE users +SET company_id = _company_id +WHERE id = _admin_id; +INSERT INTO wallets ( + balance, + is_withdraw, + is_bettable, + is_transferable, + user_id, + type, + currency, + is_active, + created_at, + updated_at + ) +VALUES ( + 10000, + TRUE, + TRUE, + TRUE, + _admin_id, + 'branch_wallet', + 'ETB', + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ) ON CONFLICT (user_id, type) DO +UPDATE +SET updated_at = EXCLUDED.updated_at +RETURNING id INTO STRICT _branch_wallet_id; +INSERT INTO branches ( + name, + location, + wallet_id, + branch_manager_id, + company_id, + is_self_owned, + profit_percent, + is_active, + created_at, + updated_at + ) +VALUES ( + 'Test Branch', + 'addis_ababa', + _branch_wallet_id, + _manager_id, + _company_id, + TRUE, + 0.10, + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ) ON CONFLICT (wallet_id) DO +UPDATE +SET updated_at = EXCLUDED.updated_at +RETURNING id INTO STRICT _branch_id; +INSERT INTO users ( + first_name, + last_name, + email, + phone_number, + password, + role, + email_verified, + phone_verified, + created_at, + updated_at, + suspended, + company_id + ) +VALUES ( + 'Cashier', + 'BetFidel', + 'cashier.betfidel@gmail.com', + NULL, + crypt('password@123', gen_salt('bf'))::bytea, + 'cashier', + TRUE, + FALSE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + FALSE, + _company_id + ) ON CONFLICT (email) DO +UPDATE +SET updated_at = EXCLUDED.updated_at +RETURNING id INTO STRICT _cashier_id; +INSERT INTO branch_cashiers (user_id, branch_id) +VALUES (_cashier_id, _branch_id); +RAISE NOTICE 'BETFIDEL_DEV_DATA (Admin ID: %, Company Wallet ID: %, Company ID: %)', +_admin_id, +_company_wallet_id, +_company_id; +RAISE NOTICE 'BETFIDEL_DEV_DATA (Branch ID: %, Branch Wallet ID: %, Manager ID: %)', +_branch_id, +_branch_wallet_id, +_manager_id; +RAISE NOTICE 'BETFIDEL_DEV_DATA (Cashier ID: %)', +_cashier_id; +END $$; \ No newline at end of file diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index a30f872..4b636ec 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -73,10 +73,9 @@ CREATE TABLE IF NOT EXISTS wallets ( is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(user_id, type) + UNIQUE(user_id, type), + CONSTRAINT balance_positve CHECK (balance >= 0) ); - - CREATE TABLE refresh_tokens ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL, @@ -186,19 +185,19 @@ CREATE TABLE IF NOT EXISTS banks ( currency VARCHAR(10) NOT NULL, bank_logo TEXT -- URL or base64 string ); -CREATE TABLE IF NOT EXISTS wallets ( - id BIGSERIAL PRIMARY KEY, - balance BIGINT NOT NULL DEFAULT 0, - is_withdraw BOOLEAN NOT NULL, - is_bettable BOOLEAN NOT NULL, - is_transferable BOOLEAN NOT NULL, - user_id BIGINT NOT NULL, - type VARCHAR(255) NOT NULL, - is_active BOOLEAN NOT NULL DEFAULT true, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT balance_positve CHECK (balance >= 0) -); +-- CREATE TABLE IF NOT EXISTS wallets ( +-- id BIGSERIAL PRIMARY KEY, +-- balance BIGINT NOT NULL DEFAULT 0, +-- is_withdraw BOOLEAN NOT NULL, +-- is_bettable BOOLEAN NOT NULL, +-- is_transferable BOOLEAN NOT NULL, +-- user_id BIGINT NOT NULL, +-- type VARCHAR(255) NOT NULL, +-- is_active BOOLEAN NOT NULL DEFAULT true, +-- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +-- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +-- CONSTRAINT balance_positve CHECK (balance >= 0) +-- ); CREATE TABLE IF NOT EXISTS customer_wallets ( id BIGSERIAL PRIMARY KEY, customer_id BIGINT NOT NULL, @@ -272,7 +271,7 @@ CREATE TABLE IF NOT EXISTS branches ( name VARCHAR(255) NOT NULL, location TEXT NOT NULL, profit_percent REAL NOT NULL, - is_active BOOLEAN NOT NULL DEFAULT false, + is_active BOOLEAN NOT NULL DEFAULT true, wallet_id BIGINT NOT NULL, branch_manager_id BIGINT NOT NULL, company_id BIGINT NOT NULL, @@ -321,6 +320,7 @@ CREATE TABLE events ( is_live BOOLEAN NOT NULL DEFAULT false, status TEXT NOT NULL, fetched_at TIMESTAMP DEFAULT now (), + updated_at TIMESTAMP DEFAULT now (), source TEXT NOT NULL DEFAULT 'b365api' CHECK ( source IN ('b365api', 'bfair', '1xbet', 'bwin', 'enetpulse') ), @@ -342,7 +342,7 @@ CREATE TABLE company_event_settings ( event_id BIGINT NOT NULL, is_active BOOLEAN, is_featured BOOLEAN, - winning_upper_limit INT, + winning_upper_limit BIGINT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE (company_id, event_id) ); @@ -354,6 +354,7 @@ CREATE TABLE odds_market ( market_category TEXT NOT NULL, market_id BIGINT NOT NULL, raw_odds JSONB NOT NULL, + number_of_outcomes BIGINT NOT NULL, default_is_active BOOLEAN NOT NULL DEFAULT true, fetched_at TIMESTAMP DEFAULT now (), expires_at TIMESTAMP NOT NULL, @@ -408,11 +409,11 @@ CREATE TABLE companies ( admin_id BIGINT NOT NULL, wallet_id BIGINT NOT NULL, deducted_percentage REAL NOT NULL, - is_active BOOLEAN NOT NULL DEFAULT false, + is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT deducted_percentage_check CHECK ( - deducted_percentage >= 0 + deducted_percentage > 0 AND deducted_percentage < 1 ) ); @@ -511,6 +512,8 @@ CREATE TABLE IF NOT EXISTS raffles ( name VARCHAR(255) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), expires_at TIMESTAMP NOT NULL, + -- -1 means there is no limit for the raffle + ticket_limit INT NOT NULL DEFAULT -1, type VARCHAR(50) NOT NULL CHECK (type IN ('virtual', 'sport')), status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'completed')) ); @@ -580,14 +583,17 @@ CREATE VIEW bet_with_outcomes AS SELECT bets.*, CONCAT (users.first_name, ' ', users.last_name) AS full_name, users.phone_number, - JSON_AGG (bet_outcomes.*) AS outcomes + JSON_AGG (bet_outcomes.*) AS outcomes, + companies.slug as company_slug FROM bets LEFT JOIN bet_outcomes ON bets.id = bet_outcomes.bet_id LEFT JOIN users ON bets.user_id = users.id + JOIN companies ON bets.company_id = companies.id GROUP BY bets.id, users.first_name, users.last_name, - users.phone_number; + users.phone_number, + companies.slug; CREATE VIEW ticket_with_outcomes AS SELECT tickets.*, JSON_AGG (ticket_outcomes.*) AS outcomes @@ -644,6 +650,7 @@ SELECT sb.*, st.verified AS transaction_verified, bets.status, bets.total_odds, + bets.fast_code, JSON_AGG (bet_outcomes.*) AS outcomes FROM shop_bets AS sb JOIN shop_transactions st ON st.id = sb.shop_transaction_id @@ -657,7 +664,8 @@ GROUP BY sb.id, st.amount, st.verified, bets.status, - bets.total_odds; + bets.total_odds, + bets.fast_code; CREATE VIEW shop_deposit_detail AS SELECT sd.*, st.full_name, @@ -685,16 +693,30 @@ SELECT e.*, ces.winning_upper_limit, e.default_winning_upper_limit ) AS winning_upper_limit, - ces.updated_at, - l.country_code as league_cc + ces.updated_at as company_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 - JOIN leagues l ON l.id = e.league_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; CREATE VIEW event_with_country AS SELECT events.*, - leagues.country_code as league_cc + 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 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, @@ -702,6 +724,7 @@ SELECT o.id, o.market_name, o.market_category, o.market_id, + o.number_of_outcomes, o.default_is_active, o.fetched_at, o.expires_at, diff --git a/db/migrations/000002_notification.up.sql b/db/migrations/000002_notification.up.sql index 3d74954..78b28c8 100644 --- a/db/migrations/000002_notification.up.sql +++ b/db/migrations/000002_notification.up.sql @@ -37,6 +37,8 @@ CREATE TABLE IF NOT EXISTS notifications ( priority INTEGER, version INTEGER NOT NULL DEFAULT 0, timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + img TEXT, + expires TIMESTAMPTZ NOT NULL, metadata JSONB ); CREATE TABLE IF NOT EXISTS wallet_threshold_notifications ( diff --git a/db/migrations/00008_enet_pulse.down.sql b/db/migrations/00008_enet_pulse.down.sql index 84c0daa..43196f1 100644 --- a/db/migrations/00008_enet_pulse.down.sql +++ b/db/migrations/00008_enet_pulse.down.sql @@ -2,4 +2,10 @@ DROP TABLE IF EXISTS enetpulse_sports; DROP TABLE IF EXISTS enetpulse_tournament_templates; DROP TABLE IF EXISTS enetpulse_tournaments; DROP TABLE IF EXISTS enetpulse_tournament_stages; -DROP TABLE IF EXISTS enetpulse_fixtures; \ No newline at end of file +DROP TABLE IF EXISTS enetpulse_fixtures; +DROP TABLE IF EXISTS enetpulse_results; +DROP TABLE IF EXISTS enetpulse_result_participants; +DROP TABLE IF EXISTS enetpulse_result_referees; +DROP TABLE IF EXISTS enetpulse_outcome_types; +DROP TABLE IF EXISTS enetpulse_preodds; +DROP TABLE IF EXISTS enetpulse_preodds_bettingoffers; \ No newline at end of file diff --git a/db/query/bet.sql b/db/query/bet.sql index 811dd1f..4a5307a 100644 --- a/db/query/bet.sql +++ b/db/query/bet.sql @@ -133,6 +133,40 @@ SELECT * FROM bet_with_outcomes WHERE status = 2 AND processed = false; +-- name: GetBetOutcomeViewByEventID :many +SELECT bet_outcomes.*, + users.first_name, + users.last_name, + bets.amount, + bets.total_odds, + companies.name as company_name +FROM bet_outcomes + JOIN bets ON bets.id = bet_outcomes.bet_id + JOIN users ON bets.user_id = users.id + JOIN companies ON bets.company_id = companies.id +WHERE bet_outcomes.event_id = $1 + AND ( + bets.company_id = sqlc.narg('company_id') + OR sqlc.narg('company_id') IS NULL + ) + AND ( + bet_outcomes.status = sqlc.narg('filter_status') + OR sqlc.narg('filter_status') IS NULL + ) +LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); +-- name: TotalBetOutcomeViewByEventID :one +SELECT count(*) +FROM bet_outcomes + JOIN bets ON bets.id = bet_outcomes.bet_id +WHERE bet_outcomes.event_id = $1 + AND ( + bets.company_id = sqlc.narg('company_id') + OR sqlc.narg('company_id') IS NULL + ) + AND ( + bet_outcomes.status = sqlc.narg('filter_status') + OR sqlc.narg('filter_status') IS NULL + ); -- name: GetBetOutcomeByEventID :many SELECT * FROM bet_outcomes @@ -180,6 +214,15 @@ UPDATE bet_outcomes SEt status = $1 WHERE event_id = $2 RETURNING *; +-- name: UpdateBetOutcomeStatusForOddID :many +UPDATE bet_outcomes +SEt status = $1 +WHERE odd_id = $2 +RETURNING *; +-- name: BulkUpdateBetOutcomeStatusByOddIDs :exec +UPDATE bet_outcomes +SET status = $1 +WHERE odd_id = ANY(sqlc.arg('odd_ids')::BIGINT []); -- name: UpdateStatus :exec UPDATE bets SET status = $1, diff --git a/db/query/bet_stat.sql b/db/query/bet_stat.sql index 76d8129..223c5e4 100644 --- a/db/query/bet_stat.sql +++ b/db/query/bet_stat.sql @@ -30,6 +30,10 @@ wHERE ( user_id = sqlc.narg('user_id') OR sqlc.narg('user_id') IS NULL ) + AND ( + company_id = sqlc.narg('company_id') + OR sqlc.narg('company_id') IS NULL + ) AND ( created_at > sqlc.narg('created_before') OR sqlc.narg('created_before') IS NULL @@ -60,6 +64,10 @@ wHERE ( user_id = sqlc.narg('user_id') OR sqlc.narg('user_id') IS NULL ) + AND ( + company_id = sqlc.narg('company_id') + OR sqlc.narg('company_id') IS NULL + ) AND ( is_shop_bet = sqlc.narg('is_shop_bet') OR sqlc.narg('is_shop_bet') IS NULL @@ -117,6 +125,10 @@ WITH market_counts AS ( user_id = sqlc.narg('user_id') OR sqlc.narg('user_id') IS NULL ) + AND ( + company_id = sqlc.narg('company_id') + OR sqlc.narg('company_id') IS NULL + ) AND ( created_at > sqlc.narg('created_before') OR sqlc.narg('created_before') IS NULL diff --git a/db/query/branch.sql b/db/query/branch.sql index 1e09f40..14fd768 100644 --- a/db/query/branch.sql +++ b/db/query/branch.sql @@ -65,7 +65,11 @@ WHERE branch_manager_id = $1; -- name: SearchBranchByName :many SELECT * FROM branch_details -WHERE name ILIKE '%' || $1 || '%'; +WHERE name ILIKE '%' || $1 || '%' + AND ( + company_id = sqlc.narg('company_id') + OR sqlc.narg('company_id') IS NULL + ); -- name: GetAllSupportedOperations :many SELECT * FROM supported_operations; diff --git a/db/query/company.sql b/db/query/company.sql index 00128f5..4f5952c 100644 --- a/db/query/company.sql +++ b/db/query/company.sql @@ -4,9 +4,10 @@ INSERT INTO companies ( slug, admin_id, wallet_id, - deducted_percentage + deducted_percentage, + is_active ) -VALUES ($1, $2, $3, $4, $5) +VALUES ($1, $2, $3, $4, $5, $6) RETURNING *; -- name: GetAllCompanies :many SELECT * @@ -30,15 +31,15 @@ WHERE ( SELECT * FROM companies_details WHERE id = $1; --- name: GetCompanyIDUsingSlug :one -SELECT id +-- name: GetCompanyUsingSlug :one +SELECT * FROM companies WHERE slug = $1; -- name: SearchCompanyByName :many SELECT * FROM companies_details WHERE name ILIKE '%' || $1 || '%'; --- name: UpdateCompany :one +-- name: UpdateCompany :exec UPDATE companies SET name = COALESCE(sqlc.narg(name), name), admin_id = COALESCE(sqlc.narg(admin_id), admin_id), @@ -47,9 +48,9 @@ SET name = COALESCE(sqlc.narg(name), name), sqlc.narg(deducted_percentage), deducted_percentage ), + slug = COALESCE(sqlc.narg(slug), slug), updated_at = CURRENT_TIMESTAMP -WHERE id = $1 -RETURNING *; +WHERE id = $1; -- name: DeleteCompany :exec DELETE FROM companies WHERE id = $1; \ No newline at end of file diff --git a/db/query/enet_pulse.sql b/db/query/enet_pulse.sql index f4c05bb..1f9c195 100644 --- a/db/query/enet_pulse.sql +++ b/db/query/enet_pulse.sql @@ -107,7 +107,8 @@ INSERT INTO enetpulse_tournament_stages ( updates_count, last_updated_at, status -) VALUES ( +) +VALUES ( $1, -- stage_id $2, -- name $3, -- tournament_fk @@ -120,6 +121,19 @@ INSERT INTO enetpulse_tournament_stages ( $10, -- last_updated_at $11 -- status ) +ON CONFLICT (stage_id) DO UPDATE +SET + name = EXCLUDED.name, + tournament_fk = EXCLUDED.tournament_fk, + gender = EXCLUDED.gender, + country_fk = EXCLUDED.country_fk, + country_name = EXCLUDED.country_name, + start_date = EXCLUDED.start_date, + end_date = EXCLUDED.end_date, + updates_count = EXCLUDED.updates_count, + last_updated_at = EXCLUDED.last_updated_at, + status = EXCLUDED.status, + updated_at = NOW() RETURNING *; -- name: GetAllEnetpulseTournamentStages :many @@ -367,6 +381,48 @@ SELECT * FROM enetpulse_preodds_bettingoffers ORDER BY created_at DESC; +-- name: GetFixturesWithPreodds :many +SELECT + f.fixture_id AS id, + f.fixture_id AS fixture_id, + f.name AS fixture_name, + f.sport_fk, + f.tournament_fk, + f.tournament_template_fk, + f.tournament_stage_fk, + f.start_date, + f.status_type, + f.status_desc_fk, + f.round_type_fk, + f.updates_count AS fixture_updates_count, + f.last_updated_at AS fixture_last_updated_at, + f.created_at AS fixture_created_at, + f.updated_at AS fixture_updated_at, + + -- Preodds fields + p.id AS preodds_db_id, + p.preodds_id, + p.event_fk, + p.outcome_type_fk, + p.outcome_scope_fk, + p.outcome_subtype_fk, + p.event_participant_number, + p.iparam, + p.iparam2, + p.dparam, + p.dparam2, + p.sparam, + p.updates_count AS preodds_updates_count, + p.last_updated_at AS preodds_last_updated_at, + p.created_at AS preodds_created_at, + p.updated_at AS preodds_updated_at + +FROM enetpulse_fixtures f +LEFT JOIN enetpulse_preodds p + ON p.event_fk = f.id +ORDER BY f.start_date DESC; + + diff --git a/db/query/events.sql b/db/query/events.sql index ff7b03f..d62b380 100644 --- a/db/query/events.sql +++ b/db/query/events.sql @@ -56,7 +56,7 @@ SET sport_id = EXCLUDED.sport_id, source = EXCLUDED.source, default_winning_upper_limit = EXCLUDED.default_winning_upper_limit, fetched_at = now(); --- name: SaveEventSettings :exec +-- name: SaveTenantEventSettings :exec INSERT INTO company_event_settings ( company_id, event_id, @@ -218,11 +218,18 @@ SELECT e.*, e.default_winning_upper_limit ) AS winning_upper_limit, ces.updated_at, - l.country_code as league_cc + 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 @@ -292,15 +299,24 @@ SELECT e.*, e.default_winning_upper_limit ) AS winning_upper_limit, ces.updated_at, - l.country_code as league_cc + 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 FROM events +SELECT sport_id, + league_id +FROM events WHERE id = $1; -- name: UpdateMatchResult :exec UPDATE events @@ -313,8 +329,22 @@ FROM events WHERE id = $1; -- name: UpdateEventMonitored :exec UPDATE events -SET is_monitored = $1 +SET is_monitored = $1, + updated_at = CURRENT_TIMESTAMP WHERE id = $2; +-- name: UpdateGlobalEventSettings :exec +UPDATE events +SET default_is_active = COALESCE(sqlc.narg(default_is_active), default_is_active), + default_is_featured = COALESCE( + sqlc.narg(default_is_featured), + default_is_featured + ), + default_winning_upper_limit = COALESCE( + sqlc.narg(default_winning_upper_limit), + default_winning_upper_limit + ), + updated_at = CURRENT_TIMESTAMP +WHERE id = $1; -- name: DeleteEvent :exec DELETE FROM events -WHERE id = $1; +WHERE id = $1; \ No newline at end of file diff --git a/db/query/leagues.sql b/db/query/leagues.sql index 476c3e8..047ce5d 100644 --- a/db/query/leagues.sql +++ b/db/query/leagues.sql @@ -14,7 +14,7 @@ SET name = EXCLUDED.name, country_code = EXCLUDED.country_code, bet365_id = EXCLUDED.bet365_id, sport_id = EXCLUDED.sport_id; --- name: InsertLeagueSettings :exec +-- name: SaveLeagueSettings :exec INSERT INTO company_league_settings ( company_id, league_id, @@ -40,8 +40,31 @@ WHERE ( name ILIKE '%' || sqlc.narg('query') || '%' OR sqlc.narg('query') IS NULL ) + AND ( + default_is_active = sqlc.narg('is_active') + OR sqlc.narg('is_active') IS NULL + ) ORDER BY name ASC LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); +-- name: GetTotalLeagues :one +SELECT COUNT(*) +FROM leagues +WHERE ( + country_code = sqlc.narg('country_code') + OR sqlc.narg('country_code') IS NULL + ) + AND ( + sport_id = sqlc.narg('sport_id') + OR sqlc.narg('sport_id') IS NULL + ) + AND ( + name ILIKE '%' || sqlc.narg('query') || '%' + OR sqlc.narg('query') IS NULL + ) + AND ( + default_is_active = sqlc.narg('is_active') + OR sqlc.narg('is_active') IS NULL + ); -- name: GetTotalLeaguesWithSettings :one SELECT COUNT(*) FROM leagues l @@ -118,7 +141,7 @@ SET name = COALESCE(sqlc.narg('name'), name), bet365_id = COALESCE(sqlc.narg('bet365_id'), bet365_id), sport_id = COALESCE(sqlc.narg('sport_id'), sport_id) WHERE id = $1; --- name: UpdateLeagueSettings :exec +-- name: UpdateCompanyLeagueSettings :exec UPDATE company_league_settings SET is_active = COALESCE(sqlc.narg('is_active'), is_active), is_featured = COALESCE( @@ -126,4 +149,9 @@ SET is_active = COALESCE(sqlc.narg('is_active'), is_active), is_featured ) WHERE league_id = $1 - AND company_id = $2; \ No newline at end of file + AND company_id = $2; +-- name: UpdateGlobalLeagueSettings :exec +UPDATE leagues +SET default_is_active = COALESCE(sqlc.narg('is_active'), default_is_active), + default_is_featured = COALESCE(sqlc.narg('is_featured'), default_is_featured) +WHERE id = $1; \ No newline at end of file diff --git a/db/query/notification.sql b/db/query/notification.sql index 76d0edc..c1dfa06 100644 --- a/db/query/notification.sql +++ b/db/query/notification.sql @@ -12,6 +12,8 @@ INSERT INTO notifications ( payload, priority, timestamp, + expires, + img, metadata ) VALUES ( @@ -27,7 +29,9 @@ VALUES ( $10, $11, $12, - $13 + $13, + $14, + $15 ) RETURNING *; -- name: GetNotification :one @@ -88,4 +92,8 @@ SELECT COUNT(*) as total, WHEN is_read = false THEN 1 END ) as unread -FROM notifications; \ No newline at end of file +FROM notifications; + +-- name: DeleteOldNotifications :exec +DELETE FROM notifications +WHERE expires < now(); \ No newline at end of file diff --git a/db/query/odds.sql b/db/query/odds.sql index dc467c6..6979426 100644 --- a/db/query/odds.sql +++ b/db/query/odds.sql @@ -5,6 +5,7 @@ INSERT INTO odds_market ( market_name, market_category, market_id, + number_of_outcomes, raw_odds, fetched_at, expires_at @@ -17,13 +18,15 @@ VALUES ( $5, $6, $7, - $8 + $8, + $9 ) ON CONFLICT (event_id, market_id) DO UPDATE SET market_type = EXCLUDED.market_type, market_name = EXCLUDED.market_name, market_category = EXCLUDED.market_category, raw_odds = EXCLUDED.raw_odds, + number_of_outcomes = EXCLUDED.number_of_outcomes, fetched_at = EXCLUDED.fetched_at, expires_at = EXCLUDED.expires_at; -- name: SaveOddSettings :exec @@ -48,6 +51,7 @@ SELECT o.id, o.market_name, o.market_category, o.market_id, + o.number_of_outcomes, o.default_is_active, o.fetched_at, o.expires_at, @@ -75,6 +79,7 @@ SELECT o.id, o.market_name, o.market_category, o.market_id, + o.number_of_outcomes, o.default_is_active, o.fetched_at, o.expires_at, @@ -94,6 +99,7 @@ SELECT o.id, o.market_name, o.market_category, o.market_id, + o.number_of_outcomes, o.default_is_active, o.fetched_at, o.expires_at, @@ -129,6 +135,7 @@ SELECT o.id, o.market_name, o.market_category, o.market_id, + o.number_of_outcomes, o.default_is_active, o.fetched_at, o.expires_at, @@ -143,4 +150,15 @@ WHERE event_id = $1 LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); -- name: DeleteOddsForEvent :exec DELETE FROM odds_market -Where event_id = $1; \ No newline at end of file +Where event_id = $1; +-- name: DeleteAllCompanyOddsSetting :exec +DELETE FROM company_odd_settings +WHERE company_id = $1; +-- name: DeleteCompanyOddsSettingByOddMarketID :exec +DELETE FROM company_odd_settings +WHERE company_id = $1 + AND odds_market_id = $2; +-- name: UpdateGlobalOddsSetting :exec +UPDATE odds_market +SET default_is_active = COALESCE(sqlc.narg(default_is_active), default_is_active) +WHERE id = $1; \ No newline at end of file diff --git a/db/query/raffle.sql b/db/query/raffle.sql index 55f302c..68318a6 100644 --- a/db/query/raffle.sql +++ b/db/query/raffle.sql @@ -1,6 +1,6 @@ -- name: CreateRaffle :one -INSERT INTO raffles (company_id, name, expires_at, type) -VALUES ($1, $2, $3, $4) +INSERT INTO raffles (company_id, name, expires_at, ticket_limit, type) +VALUES ($1, $2, $3, $4, $5) RETURNING *; -- name: GetRafflesOfCompany :many @@ -71,3 +71,19 @@ FROM raffle_sport_filters WHERE raffle_id = $1 AND sport_id = $2 AND league_id = $3; + +-- name: CheckSportRaffleHasFilter :one +SELECT EXISTS ( + SELECT 1 FROM raffle_sport_filters WHERE raffle_id = $1 +) AS has_filter; + +-- name: GetRaffleTicketLimit :one +SELECT ticket_limit +FROM raffles +WHERE id = $1; + +-- name: GetRaffleTicketCount :one +SELECT COUNT(*) +FROM raffle_tickets +WHERE raffle_id = $1 + AND user_id = $2; diff --git a/db/scripts/fix_autoincrement_desync.sql b/db/scripts/fix_autoincrement_desync.sql new file mode 100644 index 0000000..835e10e --- /dev/null +++ b/db/scripts/fix_autoincrement_desync.sql @@ -0,0 +1,31 @@ +-- For each table with an id sequence +SELECT setval( + pg_get_serial_sequence('users', 'id'), + COALESCE(MAX(id), 1) + ) +FROM users; +SELECT setval( + pg_get_serial_sequence('wallets', 'id'), + COALESCE(MAX(id), 1) + ) +FROM wallets; +SELECT setval( + pg_get_serial_sequence('customer_wallets', 'id'), + COALESCE(MAX(id), 1) + ) +FROM customer_wallets; +SELECT setval( + pg_get_serial_sequence('companies', 'id'), + COALESCE(MAX(id), 1) + ) +FROM companies; +SELECT setval( + pg_get_serial_sequence('branches', 'id'), + COALESCE(MAX(id), 1) + ) +FROM branches; +SELECT setval( + pg_get_serial_sequence('supported_operations', 'id'), + COALESCE(MAX(id), 1) + ) +FROM supported_operations; \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 1846a77..6e5e7de 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,7 +25,7 @@ services: image: mongo:7.0.11 restart: always ports: - - "27017:27017" + - "27022:27017" environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: secret @@ -56,47 +56,17 @@ services: ] networks: - app - - redis: - image: redis:7-alpine - ports: - - "6379:6379" - networks: - - app - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 - - zookeeper: - image: confluentinc/cp-zookeeper:7.5.0 - container_name: zookeeper - ports: - - "22181:2181" # remapped host port (Windows-safe) - environment: - ZOOKEEPER_CLIENT_PORT: 2181 - ZOOKEEPER_TICK_TIME: 2000 - networks: - - app - - kafka: - image: confluentinc/cp-kafka:7.5.0 - depends_on: - - zookeeper - environment: - KAFKA_BROKER_ID: 1 - KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 - KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,PLAINTEXT_HOST://0.0.0.0:29092 - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092 - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 - ports: - - "9092:9092" - - "29092:29092" - networks: - - app - + # redis: + # image: redis:7-alpine + # ports: + # - "6379:6379" + # networks: + # - app + # healthcheck: + # test: ["CMD", "redis-cli", "ping"] + # interval: 10s + # timeout: 5s + # retries: 5 app: build: context: . @@ -107,15 +77,9 @@ services: environment: - DB_URL=postgresql://root:secret@postgres:5432/gh?sslmode=disable - MONGO_URI=mongodb://root:secret@mongo:27017 - - REDIS_ADDR=redis:6379 - - KAFKA_BROKERS=kafka:9092 depends_on: migrate: condition: service_completed_successfully - mongo: - condition: service_healthy - redis: - condition: service_healthy networks: - app command: ["/app/bin/web"] diff --git a/docs/docs.go b/docs/docs.go index a7f73c8..47d40df 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -125,7 +125,7 @@ const docTemplate = `{ } }, "post": { - "description": "Create Admin", + "description": "Create transaction approver", "consumes": [ "application/json" ], @@ -135,15 +135,15 @@ const docTemplate = `{ "tags": [ "admin" ], - "summary": "Create Admin", + "summary": "Create transaction approver", "parameters": [ { - "description": "Create admin", + "description": "Create transaction approver", "name": "manger", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handlers.CreateAdminReq" + "$ref": "#/definitions/handlers.CreateTransactionApproverReq" } } ], @@ -800,6 +800,47 @@ const docTemplate = `{ } } }, + "/api/v1/atlas/games": { + "get": { + "description": "Retrieves available Atlas virtual games from the provider", + "produces": [ + "application/json" + ], + "tags": [ + "Virtual Games - Atlas" + ], + "summary": "List Atlas virtual games", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.AtlasGameEntity" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, "/api/v1/atlas/init-game": { "post": { "description": "Initializes a game session for the given player using Atlas virtual game provider", @@ -1613,6 +1654,50 @@ const docTemplate = `{ } } }, + "/api/v1/branch/{id}/return": { + "post": { + "description": "Unassign the branch wallet to company", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Unassign the branch wallet to company", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.BranchDetailRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/branchCashier": { "get": { "description": "Gets branch for cahier", @@ -2751,6 +2836,56 @@ const docTemplate = `{ } } }, + "/api/v1/customer/{id}/bets": { + "get": { + "description": "Get customer bets", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "customer" + ], + "summary": "Get customer bets", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CustomersRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/customerWallet": { "get": { "description": "Retrieve all customer wallets", @@ -2913,6 +3048,358 @@ const docTemplate = `{ } } }, + "/api/v1/enetpulse/fixtures": { + "get": { + "description": "Fetches all fixtures stored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Fixtures" + ], + "summary": "Get all stored fixtures", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulseFixture" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/enetpulse/fixtures/preodds": { + "get": { + "description": "Fetches all EnetPulse fixtures along with their associated pre-match odds", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Fixtures" + ], + "summary": "Get fixtures with preodds", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulseFixtureWithPreodds" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/enetpulse/preodds": { + "get": { + "description": "Fetches all EnetPulse pre-match odds stored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Preodds" + ], + "summary": "Get all preodds", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulsePreodds" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/enetpulse/results": { + "get": { + "description": "Fetches all EnetPulse match results stored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Results" + ], + "summary": "Get all results", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulseResult" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/enetpulse/sports": { + "get": { + "description": "Fetches all sports stored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Sports" + ], + "summary": "Get all sports", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulseSport" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/enetpulse/tournament-stages": { + "get": { + "description": "Fetches all tournament stages stored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Tournament Stages" + ], + "summary": "Get all tournament stages", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulseTournamentStage" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/enetpulse/tournament-templates": { + "get": { + "description": "Fetches all tournament templates stored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Tournament Templates" + ], + "summary": "Get all tournament templates", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulseTournamentTemplate" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/enetpulse/tournaments": { + "get": { + "description": "Fetches all tournaments stored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Tournaments" + ], + "summary": "Get all tournaments", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulseTournament" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, "/api/v1/events": { "get": { "description": "Retrieve all upcoming events from the database", @@ -3075,6 +3562,138 @@ const docTemplate = `{ } } }, + "/api/v1/events/{id}/bets": { + "get": { + "description": "Retrieve bet outcomes by event id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve bet outcomes by event id", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.BaseEvent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/events/{id}/is_monitored": { + "patch": { + "description": "Update the event is_monitored", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "event" + ], + "summary": "update the event is_monitored", + "parameters": [ + { + "type": "integer", + "description": "Event ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/events/{id}/settings": { + "put": { + "description": "Update the event settings", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "event" + ], + "summary": "update the event settings", + "parameters": [ + { + "type": "integer", + "description": "Event ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/issues": { "get": { "description": "Admin endpoint to list all reported issues with pagination", @@ -3681,6 +4300,108 @@ const docTemplate = `{ } } }, + "/api/v1/odds/pre-match": { + "get": { + "description": "Fetches pre-match odds from EnetPulse for a given event", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - PreMatch" + ], + "summary": "Get pre-match odds for an event", + "parameters": [ + { + "type": "integer", + "description": "Event ID", + "name": "objectFK", + "in": "query", + "required": true + }, + { + "type": "array", + "items": { + "type": "integer" + }, + "collectionFormat": "csv", + "description": "Odds provider IDs (comma separated)", + "name": "oddsProviderFK", + "in": "query" + }, + { + "type": "integer", + "description": "Outcome type ID", + "name": "outcomeTypeFK", + "in": "query" + }, + { + "type": "integer", + "description": "Outcome scope ID", + "name": "outcomeScopeFK", + "in": "query" + }, + { + "type": "integer", + "description": "Outcome subtype ID", + "name": "outcomeSubtypeFK", + "in": "query" + }, + { + "type": "integer", + "description": "Limit results", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset results", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "description": "Language type ID", + "name": "languageTypeFK", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/domain.PreMatchOddsResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, "/api/v1/odds/upcoming/{upcoming_id}": { "get": { "description": "Retrieve prematch odds by upcoming event ID (FI from Bet365) with optional pagination", @@ -3916,147 +4637,6 @@ const docTemplate = `{ } } }, - "/api/v1/referral/settings": { - "get": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Retrieves current referral settings (admin only)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "referral" - ], - "summary": "Get referral settings", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/domain.ReferralSettings" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "403": { - "description": "Forbidden", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - }, - "put": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Updates referral settings (admin only)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "referral" - ], - "summary": "Update referral settings", - "parameters": [ - { - "description": "Referral settings", - "name": "settings", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/domain.ReferralSettings" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "403": { - "description": "Forbidden", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/api/v1/referral/stats": { - "get": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Retrieves referral statistics for the authenticated user", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "referral" - ], - "summary": "Get referral statistics", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/domain.ReferralStats" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/api/v1/report-files/download/{filename}": { "get": { "description": "Downloads a generated report CSV file from the server", @@ -4248,7 +4828,7 @@ const docTemplate = `{ } } }, - "/api/v1/result/{id}": { + "/api/v1/result/b365/{id}": { "get": { "description": "Get results for an event", "consumes": [ @@ -5128,6 +5708,92 @@ const docTemplate = `{ } } }, + "/api/v1/sport/bet/{id}": { + "get": { + "description": "Gets a single bet by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "bet" + ], + "summary": "Gets bet by id", + "parameters": [ + { + "type": "integer", + "description": "Bet ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.BetRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "delete": { + "description": "Deletes bet by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "bet" + ], + "summary": "Deletes bet by id", + "parameters": [ + { + "type": "integer", + "description": "Bet ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/super-login": { "post": { "description": "Login super-admin", @@ -5262,6 +5928,161 @@ const docTemplate = `{ } } }, + "/api/v1/t-approver": { + "get": { + "description": "Get all Admins", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get all Admins", + "parameters": [ + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Page size", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.AdminRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/t-approver/{id}": { + "get": { + "description": "Get a single admin by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get admin by id", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.AdminRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "put": { + "description": "Update Admin", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Update Admin", + "parameters": [ + { + "description": "Update Admin", + "name": "admin", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.updateAdminReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/telebirr/callback": { "post": { "description": "Processes the Telebirr payment result and updates wallet balance.", @@ -5354,6 +6175,467 @@ const docTemplate = `{ } } }, + "/api/v1/tenant": { + "get": { + "description": "Check if phone number or email exist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Check if phone number or email exist", + "parameters": [ + { + "description": "Check phone number or email exist", + "name": "checkPhoneEmailExist", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CheckPhoneEmailExistReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CheckPhoneEmailExistRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tenant/{tenant_slug}/customer": { + "get": { + "description": "Get all Customers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "customer" + ], + "summary": "Get all Customers", + "parameters": [ + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Page size", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CustomersRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tenant/{tenant_slug}/customer/{id}": { + "get": { + "description": "Get a single customer by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "customer" + ], + "summary": "Get customer by id", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CustomersRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "put": { + "description": "Update Customers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "customer" + ], + "summary": "Update Customers", + "parameters": [ + { + "description": "Update Customers", + "name": "Customers", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.updateCustomerReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tenant/{tenant_slug}/customer/{id}/bets": { + "get": { + "description": "Get tenant customer bets", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "customer" + ], + "summary": "Get tenant customer bets", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CustomersRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tenant/{tenant_slug}/events/{id}/bets": { + "get": { + "description": "Retrieve bet outcomes by event id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve bet outcomes by event id", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.BaseEvent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tenant/{tenant_slug}/events/{id}/settings": { + "put": { + "description": "Update the event settings", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "event" + ], + "summary": "update the event settings", + "parameters": [ + { + "type": "integer", + "description": "Event ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tenant/{tenant_slug}/referral/stats": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Retrieves referral statistics for the authenticated user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "referral" + ], + "summary": "Get referral statistics", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.ReferralStats" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/ticket": { + "get": { + "description": "Retrieve all tickets", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ticket" + ], + "summary": "Get all tickets", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.TicketRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/ticket/{id}": { + "get": { + "description": "Retrieve ticket details by ticket ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ticket" + ], + "summary": "Get ticket by ID", + "parameters": [ + { + "type": "integer", + "description": "Ticket ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.TicketRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/transfer/refill/:id": { "post": { "description": "Super Admin route to refill a wallet", @@ -5536,6 +6818,144 @@ const docTemplate = `{ } } }, + "/api/v1/user/resetPassword": { + "post": { + "description": "Reset password", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Reset password", + "parameters": [ + { + "description": "Reset password", + "name": "resetPassword", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.ResetPasswordReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/user/search": { + "post": { + "description": "Search for user using name or phone", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Search for user using name or phone", + "parameters": [ + { + "description": "Search for using his name or phone", + "name": "searchUserByNameOrPhone", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.SearchUserByNameOrPhoneReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.UserProfileRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/user/sendResetCode": { + "post": { + "description": "Send reset code", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Send reset code", + "parameters": [ + { + "description": "Send reset code", + "name": "resetCode", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.ResetCodeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/user/single/{id}": { "get": { "description": "Get a single user by id", @@ -6702,50 +8122,6 @@ const docTemplate = `{ } } }, - "/api/v1/{tenant_slug}/events/{id}/settings": { - "put": { - "description": "Update the event settings", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "event" - ], - "summary": "update the event settings", - "parameters": [ - { - "type": "integer", - "description": "Event ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/api/v1/{tenant_slug}/leagues": { "get": { "description": "Gets all leagues", @@ -7760,7 +9136,7 @@ const docTemplate = `{ }, "/api/v1/{tenant_slug}/user/resetPassword": { "post": { - "description": "Reset password", + "description": "Reset tenant password", "consumes": [ "application/json" ], @@ -7770,7 +9146,7 @@ const docTemplate = `{ "tags": [ "user" ], - "summary": "Reset password", + "summary": "Reset tenant password", "parameters": [ { "description": "Reset password", @@ -8590,6 +9966,40 @@ const docTemplate = `{ } } }, + "domain.AtlasGameEntity": { + "type": "object", + "properties": { + "demo_url": { + "description": "✅ new field", + "type": "string" + }, + "deviceType": { + "type": "string" + }, + "game_id": { + "type": "string" + }, + "hasFreeBets": { + "type": "boolean" + }, + "has_demo": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "providerId": { + "type": "string" + }, + "thumbnail_img_url": { + "description": "✅ new field", + "type": "string" + }, + "type": { + "type": "string" + } + } + }, "domain.AtlasGameInitRequest": { "type": "object", "properties": { @@ -8733,7 +10143,7 @@ const docTemplate = `{ "type": "string" }, "id": { - "type": "string" + "type": "integer" }, "isLive": { "type": "boolean" @@ -8765,6 +10175,9 @@ const docTemplate = `{ "source": { "$ref": "#/definitions/domain.EventSource" }, + "sourceEventID": { + "type": "string" + }, "sportID": { "type": "integer" }, @@ -8776,6 +10189,9 @@ const docTemplate = `{ }, "timerStatus": { "$ref": "#/definitions/domain.ValidString" + }, + "totalOddOutcomes": { + "type": "integer" } } }, @@ -8889,6 +10305,10 @@ const docTemplate = `{ "type": "integer", "example": 1 }, + "company_slug": { + "type": "string", + "example": "fortune" + }, "created_at": { "type": "string", "example": "2025-04-08T12:00:00Z" @@ -9347,9 +10767,15 @@ const docTemplate = `{ "type": "number", "example": 0.1 }, + "is_active": { + "type": "boolean" + }, "name": { "type": "string", "example": "CompanyName" + }, + "slug": { + "type": "string" } } }, @@ -9591,6 +11017,444 @@ const docTemplate = `{ } } }, + "domain.EnetpulseFixture": { + "type": "object", + "properties": { + "gender": { + "type": "string" + }, + "id": { + "type": "string" + }, + "n": { + "description": "convert to int", + "type": "string" + }, + "name": { + "type": "string" + }, + "round_typeFK": { + "type": "string" + }, + "sportFK": { + "type": "string" + }, + "sport_name": { + "type": "string" + }, + "startdate": { + "description": "ISO 8601", + "type": "string" + }, + "status_descFK": { + "type": "string" + }, + "status_type": { + "type": "string" + }, + "tournamentFK": { + "type": "string" + }, + "tournament_name": { + "type": "string" + }, + "tournament_stageFK": { + "type": "string" + }, + "tournament_stage_name": { + "type": "string" + }, + "tournament_templateFK": { + "type": "string" + }, + "tournament_template_name": { + "type": "string" + }, + "ut": { + "description": "parse to time.Time", + "type": "string" + } + } + }, + "domain.EnetpulseFixtureWithPreodds": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "fixtureApiID": { + "type": "string" + }, + "fixtureID": { + "type": "string" + }, + "fixtureName": { + "type": "string" + }, + "lastUpdatedAt": { + "type": "string" + }, + "preodds": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulsePreodds" + } + }, + "roundTypeFk": { + "type": "string" + }, + "sportFk": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "statusDescFk": { + "type": "string" + }, + "statusType": { + "type": "string" + }, + "tournamentFk": { + "type": "string" + }, + "tournamentStageFk": { + "type": "string" + }, + "tournamentTemplateFk": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "updatesCount": { + "type": "integer" + } + } + }, + "domain.EnetpulsePreodds": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "dparam": { + "type": "string" + }, + "dparam2": { + "type": "string" + }, + "eventFK": { + "type": "integer" + }, + "eventParticipantNumber": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "iparam": { + "type": "string" + }, + "iparam2": { + "type": "string" + }, + "lastUpdatedAt": { + "type": "string" + }, + "outcomeScopeFK": { + "type": "integer" + }, + "outcomeSubtypeFK": { + "type": "integer" + }, + "outcomeTypeFK": { + "type": "integer" + }, + "preoddsID": { + "type": "string" + }, + "sparam": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "updatesCount": { + "type": "integer" + } + } + }, + "domain.EnetpulseResult": { + "type": "object", + "properties": { + "commentary": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "first_half_ended": { + "type": "string" + }, + "game_ended": { + "type": "string" + }, + "game_started": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "last_updated_at": { + "type": "string" + }, + "lineup_confirmed": { + "type": "boolean" + }, + "live": { + "type": "string" + }, + "livestats_plus": { + "type": "string" + }, + "livestats_type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "result_id": { + "type": "string" + }, + "round": { + "type": "string" + }, + "round_type_fk": { + "type": "string" + }, + "second_half_ended": { + "type": "string" + }, + "second_half_started": { + "type": "string" + }, + "spectators": { + "type": "integer" + }, + "sport_fk": { + "type": "string" + }, + "sport_name": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "status_desc_fk": { + "type": "string" + }, + "status_type": { + "type": "string" + }, + "tournament_fk": { + "type": "string" + }, + "tournament_name": { + "type": "string" + }, + "tournament_stage_fk": { + "type": "string" + }, + "tournament_stage_name": { + "type": "string" + }, + "tournament_template_fk": { + "type": "string" + }, + "tournament_template_name": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "updates_count": { + "type": "integer" + }, + "venue_name": { + "type": "string" + }, + "verified": { + "type": "boolean" + } + } + }, + "domain.EnetpulseSport": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "description": "DB primary key", + "type": "integer" + }, + "last_updated_at": { + "type": "string" + }, + "name": { + "description": "from API \"name\"", + "type": "string" + }, + "sport_id": { + "description": "from API \"id\"", + "type": "string" + }, + "status": { + "description": "active/inactive", + "type": "integer" + }, + "updated_at": { + "type": "string" + }, + "updates_count": { + "description": "from API \"n\"", + "type": "integer" + } + } + }, + "domain.EnetpulseTournament": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "id": { + "description": "internal DB PK", + "type": "integer" + }, + "lastUpdatedAt": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "integer" + }, + "tournamentID": { + "type": "string" + }, + "tournamentTemplateFK": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "updatesCount": { + "type": "integer" + } + } + }, + "domain.EnetpulseTournamentStage": { + "type": "object", + "properties": { + "country_fk": { + "description": "country FK from API", + "type": "string" + }, + "country_name": { + "description": "country name from API", + "type": "string" + }, + "created_at": { + "type": "string" + }, + "end_date": { + "description": "end date/time", + "type": "string" + }, + "gender": { + "description": "male/female/mixed/unknown", + "type": "string" + }, + "id": { + "type": "integer" + }, + "last_updated_at": { + "description": "ut from API", + "type": "string" + }, + "name": { + "description": "API name", + "type": "string" + }, + "stage_id": { + "description": "API id", + "type": "string" + }, + "start_date": { + "description": "start date/time", + "type": "string" + }, + "status": { + "description": "active/inactive", + "type": "integer" + }, + "tournament_fk": { + "description": "Foreign key to tournament", + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "updates_count": { + "description": "n from API", + "type": "integer" + } + } + }, + "domain.EnetpulseTournamentTemplate": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "gender": { + "description": "male, female, mixed, unknown", + "type": "string" + }, + "id": { + "type": "integer" + }, + "last_updated_at": { + "type": "string" + }, + "name": { + "description": "from API \"name\"", + "type": "string" + }, + "sport_fk": { + "description": "related sport id", + "type": "string" + }, + "status": { + "description": "optional", + "type": "integer" + }, + "template_id": { + "description": "from API \"id\"", + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "updates_count": { + "description": "from API \"n\"", + "type": "integer" + } + } + }, "domain.ErrorResponse": { "type": "object", "properties": { @@ -9667,6 +11531,15 @@ const docTemplate = `{ "away_team_image": { "type": "string" }, + "default_is_active": { + "type": "boolean" + }, + "default_is_featured": { + "type": "boolean" + }, + "default_winning_upper_limit": { + "type": "integer" + }, "fetched_at": { "type": "string" }, @@ -9680,7 +11553,7 @@ const docTemplate = `{ "type": "string" }, "id": { - "type": "string" + "type": "integer" }, "is_active": { "type": "boolean" @@ -9718,6 +11591,9 @@ const docTemplate = `{ "source": { "$ref": "#/definitions/domain.EventSource" }, + "source_event_id": { + "type": "string" + }, "sport_id": { "type": "integer" }, @@ -9730,6 +11606,9 @@ const docTemplate = `{ "timer_status": { "type": "string" }, + "total_odd_outcomes": { + "type": "integer" + }, "updated_at": { "type": "string" }, @@ -10344,11 +12223,17 @@ const docTemplate = `{ "domain.OddMarketWithEventFilter": { "type": "object", "properties": { + "isLive": { + "$ref": "#/definitions/domain.ValidBool" + }, "limit": { "$ref": "#/definitions/domain.ValidInt32" }, "offset": { "$ref": "#/definitions/domain.ValidInt32" + }, + "status": { + "$ref": "#/definitions/domain.ValidString" } } }, @@ -10517,6 +12402,41 @@ const docTemplate = `{ } } }, + "domain.PreMatchOddsResponse": { + "type": "object", + "properties": { + "eventFK": { + "description": "Define fields according to the Enetpulse preodds response structure\nExample:", + "type": "integer" + }, + "odds": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.PreMatchOutcome" + } + } + } + }, + "domain.PreMatchOutcome": { + "type": "object", + "properties": { + "oddsProviderFK": { + "type": "integer" + }, + "oddsValue": { + "type": "number" + }, + "outcomeFK": { + "type": "integer" + }, + "outcomeTypeFK": { + "type": "integer" + }, + "outcomeValue": { + "type": "string" + } + } + }, "domain.ProviderRequest": { "type": "object", "properties": { @@ -10603,55 +12523,14 @@ const docTemplate = `{ } } }, - "domain.ReferralSettings": { - "type": "object", - "properties": { - "betReferralBonusPercentage": { - "type": "number" - }, - "cashbackPercentage": { - "type": "number" - }, - "createdAt": { - "type": "string" - }, - "expiresAfterDays": { - "type": "integer" - }, - "id": { - "type": "integer" - }, - "maxReferrals": { - "type": "integer" - }, - "referralRewardAmount": { - "type": "number" - }, - "updatedAt": { - "type": "string" - }, - "updatedBy": { - "type": "string" - }, - "version": { - "type": "integer" - } - } - }, "domain.ReferralStats": { "type": "object", "properties": { - "completedReferrals": { - "type": "integer" - }, - "pendingRewards": { - "type": "number" - }, "totalReferrals": { "type": "integer" }, "totalRewardEarned": { - "type": "number" + "type": "integer" } } }, @@ -10713,14 +12592,16 @@ const docTemplate = `{ "admin", "branch_manager", "customer", - "cashier" + "cashier", + "transaction_approver" ], "x-enum-varnames": [ "RoleSuperAdmin", "RoleAdmin", "RoleBranchManager", "RoleCustomer", - "RoleCashier" + "RoleCashier", + "RoleTransactionApprover" ] }, "domain.RollbackRequest": { @@ -10929,7 +12810,7 @@ const docTemplate = `{ "type": "object", "properties": { "amount": { - "type": "integer" + "type": "number" }, "bet_id": { "type": "integer", @@ -10955,6 +12836,10 @@ const docTemplate = `{ "type": "string", "example": "2025-04-08T12:00:00Z" }, + "fast_code": { + "type": "string", + "example": "12SD1" + }, "full_name": { "type": "string", "example": "John" @@ -11362,6 +13247,9 @@ const docTemplate = `{ "category": { "type": "string" }, + "demoUrl": { + "type": "string" + }, "deviceType": { "type": "string" }, @@ -11415,6 +13303,9 @@ const docTemplate = `{ "name": { "type": "string", "example": "CompanyName" + }, + "slug": { + "type": "string" } } }, @@ -11427,6 +13318,17 @@ const docTemplate = `{ } } }, + "domain.ValidBool": { + "type": "object", + "properties": { + "valid": { + "type": "boolean" + }, + "value": { + "type": "boolean" + } + } + }, "domain.ValidInt": { "type": "object", "properties": { @@ -11748,6 +13650,35 @@ const docTemplate = `{ } } }, + "handlers.CreateTransactionApproverReq": { + "type": "object", + "properties": { + "company_id": { + "type": "integer", + "example": 1 + }, + "email": { + "type": "string", + "example": "john.doe@example.com" + }, + "first_name": { + "type": "string", + "example": "John" + }, + "last_name": { + "type": "string", + "example": "Doe" + }, + "password": { + "type": "string", + "example": "password123" + }, + "phone_number": { + "type": "string", + "example": "1234567890" + } + } + }, "handlers.CreateTransferReq": { "type": "object", "properties": { @@ -11869,6 +13800,12 @@ const docTemplate = `{ "handlers.CustomersRes": { "type": "object", "properties": { + "company_id": { + "type": "integer" + }, + "company_name": { + "type": "string" + }, "created_at": { "type": "string" }, @@ -12050,7 +13987,7 @@ const docTemplate = `{ "type": "string", "example": "1234567890" }, - "referal_code": { + "referral_code": { "type": "string", "example": "ABC123" } @@ -12513,10 +14450,6 @@ const docTemplate = `{ "handlers.updateCustomerReq": { "type": "object", "properties": { - "company_id": { - "type": "integer", - "example": 1 - }, "first_name": { "type": "string", "example": "John" diff --git a/docs/swagger.json b/docs/swagger.json index 672b70e..252532a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -117,7 +117,7 @@ } }, "post": { - "description": "Create Admin", + "description": "Create transaction approver", "consumes": [ "application/json" ], @@ -127,15 +127,15 @@ "tags": [ "admin" ], - "summary": "Create Admin", + "summary": "Create transaction approver", "parameters": [ { - "description": "Create admin", + "description": "Create transaction approver", "name": "manger", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handlers.CreateAdminReq" + "$ref": "#/definitions/handlers.CreateTransactionApproverReq" } } ], @@ -792,6 +792,47 @@ } } }, + "/api/v1/atlas/games": { + "get": { + "description": "Retrieves available Atlas virtual games from the provider", + "produces": [ + "application/json" + ], + "tags": [ + "Virtual Games - Atlas" + ], + "summary": "List Atlas virtual games", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.AtlasGameEntity" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, "/api/v1/atlas/init-game": { "post": { "description": "Initializes a game session for the given player using Atlas virtual game provider", @@ -1605,6 +1646,50 @@ } } }, + "/api/v1/branch/{id}/return": { + "post": { + "description": "Unassign the branch wallet to company", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "branch" + ], + "summary": "Unassign the branch wallet to company", + "parameters": [ + { + "type": "integer", + "description": "Branch ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.BranchDetailRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/branchCashier": { "get": { "description": "Gets branch for cahier", @@ -2743,6 +2828,56 @@ } } }, + "/api/v1/customer/{id}/bets": { + "get": { + "description": "Get customer bets", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "customer" + ], + "summary": "Get customer bets", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CustomersRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/customerWallet": { "get": { "description": "Retrieve all customer wallets", @@ -2905,6 +3040,358 @@ } } }, + "/api/v1/enetpulse/fixtures": { + "get": { + "description": "Fetches all fixtures stored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Fixtures" + ], + "summary": "Get all stored fixtures", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulseFixture" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/enetpulse/fixtures/preodds": { + "get": { + "description": "Fetches all EnetPulse fixtures along with their associated pre-match odds", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Fixtures" + ], + "summary": "Get fixtures with preodds", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulseFixtureWithPreodds" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/enetpulse/preodds": { + "get": { + "description": "Fetches all EnetPulse pre-match odds stored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Preodds" + ], + "summary": "Get all preodds", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulsePreodds" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/enetpulse/results": { + "get": { + "description": "Fetches all EnetPulse match results stored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Results" + ], + "summary": "Get all results", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulseResult" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/enetpulse/sports": { + "get": { + "description": "Fetches all sports stored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Sports" + ], + "summary": "Get all sports", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulseSport" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/enetpulse/tournament-stages": { + "get": { + "description": "Fetches all tournament stages stored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Tournament Stages" + ], + "summary": "Get all tournament stages", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulseTournamentStage" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/enetpulse/tournament-templates": { + "get": { + "description": "Fetches all tournament templates stored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Tournament Templates" + ], + "summary": "Get all tournament templates", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulseTournamentTemplate" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/enetpulse/tournaments": { + "get": { + "description": "Fetches all tournaments stored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - Tournaments" + ], + "summary": "Get all tournaments", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulseTournament" + } + } + } + } + ] + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, "/api/v1/events": { "get": { "description": "Retrieve all upcoming events from the database", @@ -3067,6 +3554,138 @@ } } }, + "/api/v1/events/{id}/bets": { + "get": { + "description": "Retrieve bet outcomes by event id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve bet outcomes by event id", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.BaseEvent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/events/{id}/is_monitored": { + "patch": { + "description": "Update the event is_monitored", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "event" + ], + "summary": "update the event is_monitored", + "parameters": [ + { + "type": "integer", + "description": "Event ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/events/{id}/settings": { + "put": { + "description": "Update the event settings", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "event" + ], + "summary": "update the event settings", + "parameters": [ + { + "type": "integer", + "description": "Event ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/issues": { "get": { "description": "Admin endpoint to list all reported issues with pagination", @@ -3673,6 +4292,108 @@ } } }, + "/api/v1/odds/pre-match": { + "get": { + "description": "Fetches pre-match odds from EnetPulse for a given event", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "EnetPulse - PreMatch" + ], + "summary": "Get pre-match odds for an event", + "parameters": [ + { + "type": "integer", + "description": "Event ID", + "name": "objectFK", + "in": "query", + "required": true + }, + { + "type": "array", + "items": { + "type": "integer" + }, + "collectionFormat": "csv", + "description": "Odds provider IDs (comma separated)", + "name": "oddsProviderFK", + "in": "query" + }, + { + "type": "integer", + "description": "Outcome type ID", + "name": "outcomeTypeFK", + "in": "query" + }, + { + "type": "integer", + "description": "Outcome scope ID", + "name": "outcomeScopeFK", + "in": "query" + }, + { + "type": "integer", + "description": "Outcome subtype ID", + "name": "outcomeSubtypeFK", + "in": "query" + }, + { + "type": "integer", + "description": "Limit results", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Offset results", + "name": "offset", + "in": "query" + }, + { + "type": "integer", + "description": "Language type ID", + "name": "languageTypeFK", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/domain.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/domain.PreMatchOddsResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, "/api/v1/odds/upcoming/{upcoming_id}": { "get": { "description": "Retrieve prematch odds by upcoming event ID (FI from Bet365) with optional pagination", @@ -3908,147 +4629,6 @@ } } }, - "/api/v1/referral/settings": { - "get": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Retrieves current referral settings (admin only)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "referral" - ], - "summary": "Get referral settings", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/domain.ReferralSettings" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "403": { - "description": "Forbidden", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - }, - "put": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Updates referral settings (admin only)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "referral" - ], - "summary": "Update referral settings", - "parameters": [ - { - "description": "Referral settings", - "name": "settings", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/domain.ReferralSettings" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "403": { - "description": "Forbidden", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/api/v1/referral/stats": { - "get": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Retrieves referral statistics for the authenticated user", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "referral" - ], - "summary": "Get referral statistics", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/domain.ReferralStats" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/api/v1/report-files/download/{filename}": { "get": { "description": "Downloads a generated report CSV file from the server", @@ -4240,7 +4820,7 @@ } } }, - "/api/v1/result/{id}": { + "/api/v1/result/b365/{id}": { "get": { "description": "Get results for an event", "consumes": [ @@ -5120,6 +5700,92 @@ } } }, + "/api/v1/sport/bet/{id}": { + "get": { + "description": "Gets a single bet by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "bet" + ], + "summary": "Gets bet by id", + "parameters": [ + { + "type": "integer", + "description": "Bet ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.BetRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "delete": { + "description": "Deletes bet by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "bet" + ], + "summary": "Deletes bet by id", + "parameters": [ + { + "type": "integer", + "description": "Bet ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/super-login": { "post": { "description": "Login super-admin", @@ -5254,6 +5920,161 @@ } } }, + "/api/v1/t-approver": { + "get": { + "description": "Get all Admins", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get all Admins", + "parameters": [ + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Page size", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.AdminRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/t-approver/{id}": { + "get": { + "description": "Get a single admin by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get admin by id", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.AdminRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "put": { + "description": "Update Admin", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Update Admin", + "parameters": [ + { + "description": "Update Admin", + "name": "admin", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.updateAdminReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/telebirr/callback": { "post": { "description": "Processes the Telebirr payment result and updates wallet balance.", @@ -5346,6 +6167,467 @@ } } }, + "/api/v1/tenant": { + "get": { + "description": "Check if phone number or email exist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Check if phone number or email exist", + "parameters": [ + { + "description": "Check phone number or email exist", + "name": "checkPhoneEmailExist", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.CheckPhoneEmailExistReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CheckPhoneEmailExistRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tenant/{tenant_slug}/customer": { + "get": { + "description": "Get all Customers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "customer" + ], + "summary": "Get all Customers", + "parameters": [ + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Page size", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CustomersRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tenant/{tenant_slug}/customer/{id}": { + "get": { + "description": "Get a single customer by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "customer" + ], + "summary": "Get customer by id", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CustomersRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + }, + "put": { + "description": "Update Customers", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "customer" + ], + "summary": "Update Customers", + "parameters": [ + { + "description": "Update Customers", + "name": "Customers", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.updateCustomerReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tenant/{tenant_slug}/customer/{id}/bets": { + "get": { + "description": "Get tenant customer bets", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "customer" + ], + "summary": "Get tenant customer bets", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.CustomersRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tenant/{tenant_slug}/events/{id}/bets": { + "get": { + "description": "Retrieve bet outcomes by event id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve bet outcomes by event id", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.BaseEvent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tenant/{tenant_slug}/events/{id}/settings": { + "put": { + "description": "Update the event settings", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "event" + ], + "summary": "update the event settings", + "parameters": [ + { + "type": "integer", + "description": "Event ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/tenant/{tenant_slug}/referral/stats": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Retrieves referral statistics for the authenticated user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "referral" + ], + "summary": "Get referral statistics", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.ReferralStats" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/ticket": { + "get": { + "description": "Retrieve all tickets", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ticket" + ], + "summary": "Get all tickets", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.TicketRes" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/ticket/{id}": { + "get": { + "description": "Retrieve ticket details by ticket ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ticket" + ], + "summary": "Get ticket by ID", + "parameters": [ + { + "type": "integer", + "description": "Ticket ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.TicketRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/transfer/refill/:id": { "post": { "description": "Super Admin route to refill a wallet", @@ -5528,6 +6810,144 @@ } } }, + "/api/v1/user/resetPassword": { + "post": { + "description": "Reset password", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Reset password", + "parameters": [ + { + "description": "Reset password", + "name": "resetPassword", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.ResetPasswordReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/user/search": { + "post": { + "description": "Search for user using name or phone", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Search for user using name or phone", + "parameters": [ + { + "description": "Search for using his name or phone", + "name": "searchUserByNameOrPhone", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.SearchUserByNameOrPhoneReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handlers.UserProfileRes" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/user/sendResetCode": { + "post": { + "description": "Send reset code", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Send reset code", + "parameters": [ + { + "description": "Send reset code", + "name": "resetCode", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.ResetCodeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/user/single/{id}": { "get": { "description": "Get a single user by id", @@ -6694,50 +8114,6 @@ } } }, - "/api/v1/{tenant_slug}/events/{id}/settings": { - "put": { - "description": "Update the event settings", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "event" - ], - "summary": "update the event settings", - "parameters": [ - { - "type": "integer", - "description": "Event ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, "/api/v1/{tenant_slug}/leagues": { "get": { "description": "Gets all leagues", @@ -7752,7 +9128,7 @@ }, "/api/v1/{tenant_slug}/user/resetPassword": { "post": { - "description": "Reset password", + "description": "Reset tenant password", "consumes": [ "application/json" ], @@ -7762,7 +9138,7 @@ "tags": [ "user" ], - "summary": "Reset password", + "summary": "Reset tenant password", "parameters": [ { "description": "Reset password", @@ -8582,6 +9958,40 @@ } } }, + "domain.AtlasGameEntity": { + "type": "object", + "properties": { + "demo_url": { + "description": "✅ new field", + "type": "string" + }, + "deviceType": { + "type": "string" + }, + "game_id": { + "type": "string" + }, + "hasFreeBets": { + "type": "boolean" + }, + "has_demo": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "providerId": { + "type": "string" + }, + "thumbnail_img_url": { + "description": "✅ new field", + "type": "string" + }, + "type": { + "type": "string" + } + } + }, "domain.AtlasGameInitRequest": { "type": "object", "properties": { @@ -8725,7 +10135,7 @@ "type": "string" }, "id": { - "type": "string" + "type": "integer" }, "isLive": { "type": "boolean" @@ -8757,6 +10167,9 @@ "source": { "$ref": "#/definitions/domain.EventSource" }, + "sourceEventID": { + "type": "string" + }, "sportID": { "type": "integer" }, @@ -8768,6 +10181,9 @@ }, "timerStatus": { "$ref": "#/definitions/domain.ValidString" + }, + "totalOddOutcomes": { + "type": "integer" } } }, @@ -8881,6 +10297,10 @@ "type": "integer", "example": 1 }, + "company_slug": { + "type": "string", + "example": "fortune" + }, "created_at": { "type": "string", "example": "2025-04-08T12:00:00Z" @@ -9339,9 +10759,15 @@ "type": "number", "example": 0.1 }, + "is_active": { + "type": "boolean" + }, "name": { "type": "string", "example": "CompanyName" + }, + "slug": { + "type": "string" } } }, @@ -9583,6 +11009,444 @@ } } }, + "domain.EnetpulseFixture": { + "type": "object", + "properties": { + "gender": { + "type": "string" + }, + "id": { + "type": "string" + }, + "n": { + "description": "convert to int", + "type": "string" + }, + "name": { + "type": "string" + }, + "round_typeFK": { + "type": "string" + }, + "sportFK": { + "type": "string" + }, + "sport_name": { + "type": "string" + }, + "startdate": { + "description": "ISO 8601", + "type": "string" + }, + "status_descFK": { + "type": "string" + }, + "status_type": { + "type": "string" + }, + "tournamentFK": { + "type": "string" + }, + "tournament_name": { + "type": "string" + }, + "tournament_stageFK": { + "type": "string" + }, + "tournament_stage_name": { + "type": "string" + }, + "tournament_templateFK": { + "type": "string" + }, + "tournament_template_name": { + "type": "string" + }, + "ut": { + "description": "parse to time.Time", + "type": "string" + } + } + }, + "domain.EnetpulseFixtureWithPreodds": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "fixtureApiID": { + "type": "string" + }, + "fixtureID": { + "type": "string" + }, + "fixtureName": { + "type": "string" + }, + "lastUpdatedAt": { + "type": "string" + }, + "preodds": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.EnetpulsePreodds" + } + }, + "roundTypeFk": { + "type": "string" + }, + "sportFk": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "statusDescFk": { + "type": "string" + }, + "statusType": { + "type": "string" + }, + "tournamentFk": { + "type": "string" + }, + "tournamentStageFk": { + "type": "string" + }, + "tournamentTemplateFk": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "updatesCount": { + "type": "integer" + } + } + }, + "domain.EnetpulsePreodds": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "dparam": { + "type": "string" + }, + "dparam2": { + "type": "string" + }, + "eventFK": { + "type": "integer" + }, + "eventParticipantNumber": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "iparam": { + "type": "string" + }, + "iparam2": { + "type": "string" + }, + "lastUpdatedAt": { + "type": "string" + }, + "outcomeScopeFK": { + "type": "integer" + }, + "outcomeSubtypeFK": { + "type": "integer" + }, + "outcomeTypeFK": { + "type": "integer" + }, + "preoddsID": { + "type": "string" + }, + "sparam": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "updatesCount": { + "type": "integer" + } + } + }, + "domain.EnetpulseResult": { + "type": "object", + "properties": { + "commentary": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "first_half_ended": { + "type": "string" + }, + "game_ended": { + "type": "string" + }, + "game_started": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "last_updated_at": { + "type": "string" + }, + "lineup_confirmed": { + "type": "boolean" + }, + "live": { + "type": "string" + }, + "livestats_plus": { + "type": "string" + }, + "livestats_type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "result_id": { + "type": "string" + }, + "round": { + "type": "string" + }, + "round_type_fk": { + "type": "string" + }, + "second_half_ended": { + "type": "string" + }, + "second_half_started": { + "type": "string" + }, + "spectators": { + "type": "integer" + }, + "sport_fk": { + "type": "string" + }, + "sport_name": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "status_desc_fk": { + "type": "string" + }, + "status_type": { + "type": "string" + }, + "tournament_fk": { + "type": "string" + }, + "tournament_name": { + "type": "string" + }, + "tournament_stage_fk": { + "type": "string" + }, + "tournament_stage_name": { + "type": "string" + }, + "tournament_template_fk": { + "type": "string" + }, + "tournament_template_name": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "updates_count": { + "type": "integer" + }, + "venue_name": { + "type": "string" + }, + "verified": { + "type": "boolean" + } + } + }, + "domain.EnetpulseSport": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "description": "DB primary key", + "type": "integer" + }, + "last_updated_at": { + "type": "string" + }, + "name": { + "description": "from API \"name\"", + "type": "string" + }, + "sport_id": { + "description": "from API \"id\"", + "type": "string" + }, + "status": { + "description": "active/inactive", + "type": "integer" + }, + "updated_at": { + "type": "string" + }, + "updates_count": { + "description": "from API \"n\"", + "type": "integer" + } + } + }, + "domain.EnetpulseTournament": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "id": { + "description": "internal DB PK", + "type": "integer" + }, + "lastUpdatedAt": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "integer" + }, + "tournamentID": { + "type": "string" + }, + "tournamentTemplateFK": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "updatesCount": { + "type": "integer" + } + } + }, + "domain.EnetpulseTournamentStage": { + "type": "object", + "properties": { + "country_fk": { + "description": "country FK from API", + "type": "string" + }, + "country_name": { + "description": "country name from API", + "type": "string" + }, + "created_at": { + "type": "string" + }, + "end_date": { + "description": "end date/time", + "type": "string" + }, + "gender": { + "description": "male/female/mixed/unknown", + "type": "string" + }, + "id": { + "type": "integer" + }, + "last_updated_at": { + "description": "ut from API", + "type": "string" + }, + "name": { + "description": "API name", + "type": "string" + }, + "stage_id": { + "description": "API id", + "type": "string" + }, + "start_date": { + "description": "start date/time", + "type": "string" + }, + "status": { + "description": "active/inactive", + "type": "integer" + }, + "tournament_fk": { + "description": "Foreign key to tournament", + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "updates_count": { + "description": "n from API", + "type": "integer" + } + } + }, + "domain.EnetpulseTournamentTemplate": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "gender": { + "description": "male, female, mixed, unknown", + "type": "string" + }, + "id": { + "type": "integer" + }, + "last_updated_at": { + "type": "string" + }, + "name": { + "description": "from API \"name\"", + "type": "string" + }, + "sport_fk": { + "description": "related sport id", + "type": "string" + }, + "status": { + "description": "optional", + "type": "integer" + }, + "template_id": { + "description": "from API \"id\"", + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "updates_count": { + "description": "from API \"n\"", + "type": "integer" + } + } + }, "domain.ErrorResponse": { "type": "object", "properties": { @@ -9659,6 +11523,15 @@ "away_team_image": { "type": "string" }, + "default_is_active": { + "type": "boolean" + }, + "default_is_featured": { + "type": "boolean" + }, + "default_winning_upper_limit": { + "type": "integer" + }, "fetched_at": { "type": "string" }, @@ -9672,7 +11545,7 @@ "type": "string" }, "id": { - "type": "string" + "type": "integer" }, "is_active": { "type": "boolean" @@ -9710,6 +11583,9 @@ "source": { "$ref": "#/definitions/domain.EventSource" }, + "source_event_id": { + "type": "string" + }, "sport_id": { "type": "integer" }, @@ -9722,6 +11598,9 @@ "timer_status": { "type": "string" }, + "total_odd_outcomes": { + "type": "integer" + }, "updated_at": { "type": "string" }, @@ -10336,11 +12215,17 @@ "domain.OddMarketWithEventFilter": { "type": "object", "properties": { + "isLive": { + "$ref": "#/definitions/domain.ValidBool" + }, "limit": { "$ref": "#/definitions/domain.ValidInt32" }, "offset": { "$ref": "#/definitions/domain.ValidInt32" + }, + "status": { + "$ref": "#/definitions/domain.ValidString" } } }, @@ -10509,6 +12394,41 @@ } } }, + "domain.PreMatchOddsResponse": { + "type": "object", + "properties": { + "eventFK": { + "description": "Define fields according to the Enetpulse preodds response structure\nExample:", + "type": "integer" + }, + "odds": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.PreMatchOutcome" + } + } + } + }, + "domain.PreMatchOutcome": { + "type": "object", + "properties": { + "oddsProviderFK": { + "type": "integer" + }, + "oddsValue": { + "type": "number" + }, + "outcomeFK": { + "type": "integer" + }, + "outcomeTypeFK": { + "type": "integer" + }, + "outcomeValue": { + "type": "string" + } + } + }, "domain.ProviderRequest": { "type": "object", "properties": { @@ -10595,55 +12515,14 @@ } } }, - "domain.ReferralSettings": { - "type": "object", - "properties": { - "betReferralBonusPercentage": { - "type": "number" - }, - "cashbackPercentage": { - "type": "number" - }, - "createdAt": { - "type": "string" - }, - "expiresAfterDays": { - "type": "integer" - }, - "id": { - "type": "integer" - }, - "maxReferrals": { - "type": "integer" - }, - "referralRewardAmount": { - "type": "number" - }, - "updatedAt": { - "type": "string" - }, - "updatedBy": { - "type": "string" - }, - "version": { - "type": "integer" - } - } - }, "domain.ReferralStats": { "type": "object", "properties": { - "completedReferrals": { - "type": "integer" - }, - "pendingRewards": { - "type": "number" - }, "totalReferrals": { "type": "integer" }, "totalRewardEarned": { - "type": "number" + "type": "integer" } } }, @@ -10705,14 +12584,16 @@ "admin", "branch_manager", "customer", - "cashier" + "cashier", + "transaction_approver" ], "x-enum-varnames": [ "RoleSuperAdmin", "RoleAdmin", "RoleBranchManager", "RoleCustomer", - "RoleCashier" + "RoleCashier", + "RoleTransactionApprover" ] }, "domain.RollbackRequest": { @@ -10921,7 +12802,7 @@ "type": "object", "properties": { "amount": { - "type": "integer" + "type": "number" }, "bet_id": { "type": "integer", @@ -10947,6 +12828,10 @@ "type": "string", "example": "2025-04-08T12:00:00Z" }, + "fast_code": { + "type": "string", + "example": "12SD1" + }, "full_name": { "type": "string", "example": "John" @@ -11354,6 +13239,9 @@ "category": { "type": "string" }, + "demoUrl": { + "type": "string" + }, "deviceType": { "type": "string" }, @@ -11407,6 +13295,9 @@ "name": { "type": "string", "example": "CompanyName" + }, + "slug": { + "type": "string" } } }, @@ -11419,6 +13310,17 @@ } } }, + "domain.ValidBool": { + "type": "object", + "properties": { + "valid": { + "type": "boolean" + }, + "value": { + "type": "boolean" + } + } + }, "domain.ValidInt": { "type": "object", "properties": { @@ -11740,6 +13642,35 @@ } } }, + "handlers.CreateTransactionApproverReq": { + "type": "object", + "properties": { + "company_id": { + "type": "integer", + "example": 1 + }, + "email": { + "type": "string", + "example": "john.doe@example.com" + }, + "first_name": { + "type": "string", + "example": "John" + }, + "last_name": { + "type": "string", + "example": "Doe" + }, + "password": { + "type": "string", + "example": "password123" + }, + "phone_number": { + "type": "string", + "example": "1234567890" + } + } + }, "handlers.CreateTransferReq": { "type": "object", "properties": { @@ -11861,6 +13792,12 @@ "handlers.CustomersRes": { "type": "object", "properties": { + "company_id": { + "type": "integer" + }, + "company_name": { + "type": "string" + }, "created_at": { "type": "string" }, @@ -12042,7 +13979,7 @@ "type": "string", "example": "1234567890" }, - "referal_code": { + "referral_code": { "type": "string", "example": "ABC123" } @@ -12505,10 +14442,6 @@ "handlers.updateCustomerReq": { "type": "object", "properties": { - "company_id": { - "type": "integer", - "example": 1 - }, "first_name": { "type": "string", "example": "John" diff --git a/docs/swagger.yaml b/docs/swagger.yaml index b54bc7b..2fee177 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -92,6 +92,29 @@ definitions: player_id: type: string type: object + domain.AtlasGameEntity: + properties: + demo_url: + description: ✅ new field + type: string + deviceType: + type: string + game_id: + type: string + has_demo: + type: boolean + hasFreeBets: + type: boolean + name: + type: string + providerId: + type: string + thumbnail_img_url: + description: ✅ new field + type: string + type: + type: string + type: object domain.AtlasGameInitRequest: properties: currency: @@ -187,7 +210,7 @@ definitions: homeTeamImage: type: string id: - type: string + type: integer isLive: type: boolean isMonitored: @@ -208,6 +231,8 @@ definitions: $ref: '#/definitions/domain.ValidString' source: $ref: '#/definitions/domain.EventSource' + sourceEventID: + type: string sportID: type: integer startTime: @@ -216,6 +241,8 @@ definitions: $ref: '#/definitions/domain.EventStatus' timerStatus: $ref: '#/definitions/domain.ValidString' + totalOddOutcomes: + type: integer type: object domain.BaseLeague: properties: @@ -294,6 +321,9 @@ definitions: company_id: example: 1 type: integer + company_slug: + example: fortune + type: string created_at: example: "2025-04-08T12:00:00Z" type: string @@ -613,9 +643,13 @@ definitions: deducted_percentage: example: 0.1 type: number + is_active: + type: boolean name: example: CompanyName type: string + slug: + type: string type: object domain.CreateSupportedOperationReq: properties: @@ -777,6 +811,304 @@ definitions: - customer_id - sender_account type: object + domain.EnetpulseFixture: + properties: + gender: + type: string + id: + type: string + "n": + description: convert to int + type: string + name: + type: string + round_typeFK: + type: string + sport_name: + type: string + sportFK: + type: string + startdate: + description: ISO 8601 + type: string + status_descFK: + type: string + status_type: + type: string + tournament_name: + type: string + tournament_stage_name: + type: string + tournament_stageFK: + type: string + tournament_template_name: + type: string + tournament_templateFK: + type: string + tournamentFK: + type: string + ut: + description: parse to time.Time + type: string + type: object + domain.EnetpulseFixtureWithPreodds: + properties: + createdAt: + type: string + fixtureApiID: + type: string + fixtureID: + type: string + fixtureName: + type: string + lastUpdatedAt: + type: string + preodds: + items: + $ref: '#/definitions/domain.EnetpulsePreodds' + type: array + roundTypeFk: + type: string + sportFk: + type: string + startDate: + type: string + statusDescFk: + type: string + statusType: + type: string + tournamentFk: + type: string + tournamentStageFk: + type: string + tournamentTemplateFk: + type: string + updatedAt: + type: string + updatesCount: + type: integer + type: object + domain.EnetpulsePreodds: + properties: + createdAt: + type: string + dparam: + type: string + dparam2: + type: string + eventFK: + type: integer + eventParticipantNumber: + type: integer + id: + type: integer + iparam: + type: string + iparam2: + type: string + lastUpdatedAt: + type: string + outcomeScopeFK: + type: integer + outcomeSubtypeFK: + type: integer + outcomeTypeFK: + type: integer + preoddsID: + type: string + sparam: + type: string + updatedAt: + type: string + updatesCount: + type: integer + type: object + domain.EnetpulseResult: + properties: + commentary: + type: string + created_at: + type: string + first_half_ended: + type: string + game_ended: + type: string + game_started: + type: string + id: + type: integer + last_updated_at: + type: string + lineup_confirmed: + type: boolean + live: + type: string + livestats_plus: + type: string + livestats_type: + type: string + name: + type: string + result_id: + type: string + round: + type: string + round_type_fk: + type: string + second_half_ended: + type: string + second_half_started: + type: string + spectators: + type: integer + sport_fk: + type: string + sport_name: + type: string + start_date: + type: string + status_desc_fk: + type: string + status_type: + type: string + tournament_fk: + type: string + tournament_name: + type: string + tournament_stage_fk: + type: string + tournament_stage_name: + type: string + tournament_template_fk: + type: string + tournament_template_name: + type: string + updated_at: + type: string + updates_count: + type: integer + venue_name: + type: string + verified: + type: boolean + type: object + domain.EnetpulseSport: + properties: + created_at: + type: string + id: + description: DB primary key + type: integer + last_updated_at: + type: string + name: + description: from API "name" + type: string + sport_id: + description: from API "id" + type: string + status: + description: active/inactive + type: integer + updated_at: + type: string + updates_count: + description: from API "n" + type: integer + type: object + domain.EnetpulseTournament: + properties: + createdAt: + type: string + id: + description: internal DB PK + type: integer + lastUpdatedAt: + type: string + name: + type: string + status: + type: integer + tournamentID: + type: string + tournamentTemplateFK: + type: string + updatedAt: + type: string + updatesCount: + type: integer + type: object + domain.EnetpulseTournamentStage: + properties: + country_fk: + description: country FK from API + type: string + country_name: + description: country name from API + type: string + created_at: + type: string + end_date: + description: end date/time + type: string + gender: + description: male/female/mixed/unknown + type: string + id: + type: integer + last_updated_at: + description: ut from API + type: string + name: + description: API name + type: string + stage_id: + description: API id + type: string + start_date: + description: start date/time + type: string + status: + description: active/inactive + type: integer + tournament_fk: + description: Foreign key to tournament + type: string + updated_at: + type: string + updates_count: + description: n from API + type: integer + type: object + domain.EnetpulseTournamentTemplate: + properties: + created_at: + type: string + gender: + description: male, female, mixed, unknown + type: string + id: + type: integer + last_updated_at: + type: string + name: + description: from API "name" + type: string + sport_fk: + description: related sport id + type: string + status: + description: optional + type: integer + template_id: + description: from API "id" + type: string + updated_at: + type: string + updates_count: + description: from API "n" + type: integer + type: object domain.ErrorResponse: properties: error: @@ -838,6 +1170,12 @@ definitions: type: integer away_team_image: type: string + default_is_active: + type: boolean + default_is_featured: + type: boolean + default_winning_upper_limit: + type: integer fetched_at: type: string home_team: @@ -847,7 +1185,7 @@ definitions: home_team_image: type: string id: - type: string + type: integer is_active: type: boolean is_featured: @@ -872,6 +1210,8 @@ definitions: type: string source: $ref: '#/definitions/domain.EventSource' + source_event_id: + type: string sport_id: type: integer start_time: @@ -880,6 +1220,8 @@ definitions: $ref: '#/definitions/domain.EventStatus' timer_status: type: string + total_odd_outcomes: + type: integer updated_at: type: string winning_upper_limit: @@ -1289,10 +1631,14 @@ definitions: type: object domain.OddMarketWithEventFilter: properties: + isLive: + $ref: '#/definitions/domain.ValidBool' limit: $ref: '#/definitions/domain.ValidInt32' offset: $ref: '#/definitions/domain.ValidInt32' + status: + $ref: '#/definitions/domain.ValidString' type: object domain.OutcomeStatus: enum: @@ -1411,6 +1757,31 @@ definitions: thumbnail: type: string type: object + domain.PreMatchOddsResponse: + properties: + eventFK: + description: |- + Define fields according to the Enetpulse preodds response structure + Example: + type: integer + odds: + items: + $ref: '#/definitions/domain.PreMatchOutcome' + type: array + type: object + domain.PreMatchOutcome: + properties: + oddsProviderFK: + type: integer + oddsValue: + type: number + outcomeFK: + type: integer + outcomeTypeFK: + type: integer + outcomeValue: + type: string + type: object domain.ProviderRequest: properties: brandId: @@ -1468,39 +1839,12 @@ definitions: type: object type: array type: object - domain.ReferralSettings: - properties: - betReferralBonusPercentage: - type: number - cashbackPercentage: - type: number - createdAt: - type: string - expiresAfterDays: - type: integer - id: - type: integer - maxReferrals: - type: integer - referralRewardAmount: - type: number - updatedAt: - type: string - updatedBy: - type: string - version: - type: integer - type: object domain.ReferralStats: properties: - completedReferrals: - type: integer - pendingRewards: - type: number totalReferrals: type: integer totalRewardEarned: - type: number + type: integer type: object domain.ReportedIssue: properties: @@ -1543,6 +1887,7 @@ definitions: - branch_manager - customer - cashier + - transaction_approver type: string x-enum-varnames: - RoleSuperAdmin @@ -1550,6 +1895,7 @@ definitions: - RoleBranchManager - RoleCustomer - RoleCashier + - RoleTransactionApprover domain.RollbackRequest: properties: bet_transaction_id: @@ -1687,7 +2033,7 @@ definitions: domain.ShopBetRes: properties: amount: - type: integer + type: number bet_id: example: 1 type: integer @@ -1706,6 +2052,9 @@ definitions: created_at: example: "2025-04-08T12:00:00Z" type: string + fast_code: + example: 12SD1 + type: string full_name: example: John type: string @@ -1992,6 +2341,8 @@ definitions: type: array category: type: string + demoUrl: + type: string deviceType: type: string gameId: @@ -2029,6 +2380,8 @@ definitions: name: example: CompanyName type: string + slug: + type: string type: object domain.UpdateTransactionVerifiedReq: properties: @@ -2036,6 +2389,13 @@ definitions: example: true type: boolean type: object + domain.ValidBool: + properties: + valid: + type: boolean + value: + type: boolean + type: object domain.ValidInt: properties: valid: @@ -2253,6 +2613,27 @@ definitions: example: "1234567890" type: string type: object + handlers.CreateTransactionApproverReq: + properties: + company_id: + example: 1 + type: integer + email: + example: john.doe@example.com + type: string + first_name: + example: John + type: string + last_name: + example: Doe + type: string + password: + example: password123 + type: string + phone_number: + example: "1234567890" + type: string + type: object handlers.CreateTransferReq: properties: amount: @@ -2337,6 +2718,10 @@ definitions: type: object handlers.CustomersRes: properties: + company_id: + type: integer + company_name: + type: string created_at: type: string email: @@ -2459,7 +2844,7 @@ definitions: phone_number: example: "1234567890" type: string - referal_code: + referral_code: example: ABC123 type: string type: object @@ -2774,9 +3159,6 @@ definitions: type: object handlers.updateCustomerReq: properties: - company_id: - example: 1 - type: integer first_name: example: John type: string @@ -2986,35 +3368,6 @@ paths: summary: Retrieve all upcoming events with settings tags: - prematch - /api/v1/{tenant_slug}/events/{id}/settings: - put: - consumes: - - application/json - description: Update the event settings - parameters: - - description: Event ID - in: path - name: id - required: true - type: integer - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: update the event settings - tags: - - event /api/v1/{tenant_slug}/leagues: get: consumes: @@ -3684,7 +4037,7 @@ paths: post: consumes: - application/json - description: Reset password + description: Reset tenant password parameters: - description: Reset password in: body @@ -3707,7 +4060,7 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/response.APIResponse' - summary: Reset password + summary: Reset tenant password tags: - user /api/v1/{tenant_slug}/user/search: @@ -3899,14 +4252,14 @@ paths: post: consumes: - application/json - description: Create Admin + description: Create transaction approver parameters: - - description: Create admin + - description: Create transaction approver in: body name: manger required: true schema: - $ref: '#/definitions/handlers.CreateAdminReq' + $ref: '#/definitions/handlers.CreateTransactionApproverReq' produces: - application/json responses: @@ -3926,7 +4279,7 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/response.APIResponse' - summary: Create Admin + summary: Create transaction approver tags: - admin /api/v1/admin-company: @@ -4339,6 +4692,30 @@ paths: summary: Create free spins for a player tags: - Virtual Games - Atlas + /api/v1/atlas/games: + get: + description: Retrieves available Atlas virtual games from the provider + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/domain.Response' + - properties: + data: + items: + $ref: '#/definitions/domain.AtlasGameEntity' + type: array + type: object + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: List Atlas virtual games + tags: + - Virtual Games - Atlas /api/v1/atlas/init-game: post: consumes: @@ -4873,6 +5250,35 @@ paths: summary: Delete the branch operation tags: - branch + /api/v1/branch/{id}/return: + post: + consumes: + - application/json + description: Unassign the branch wallet to company + parameters: + - description: Branch ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.BranchDetailRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Unassign the branch wallet to company + tags: + - branch /api/v1/branchCashier: get: consumes: @@ -5616,6 +6022,39 @@ paths: summary: Update Customers tags: - customer + /api/v1/customer/{id}/bets: + get: + consumes: + - application/json + description: Get customer bets + parameters: + - description: User ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.CustomersRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get customer bets + tags: + - customer /api/v1/customerWallet: get: consumes: @@ -5722,6 +6161,215 @@ paths: summary: Verify a direct deposit tags: - Direct Deposits + /api/v1/enetpulse/fixtures: + get: + consumes: + - application/json + description: Fetches all fixtures stored in the database + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/domain.Response' + - properties: + data: + items: + $ref: '#/definitions/domain.EnetpulseFixture' + type: array + type: object + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: Get all stored fixtures + tags: + - EnetPulse - Fixtures + /api/v1/enetpulse/fixtures/preodds: + get: + consumes: + - application/json + description: Fetches all EnetPulse fixtures along with their associated pre-match + odds + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/domain.Response' + - properties: + data: + items: + $ref: '#/definitions/domain.EnetpulseFixtureWithPreodds' + type: array + type: object + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: Get fixtures with preodds + tags: + - EnetPulse - Fixtures + /api/v1/enetpulse/preodds: + get: + consumes: + - application/json + description: Fetches all EnetPulse pre-match odds stored in the database + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/domain.Response' + - properties: + data: + items: + $ref: '#/definitions/domain.EnetpulsePreodds' + type: array + type: object + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: Get all preodds + tags: + - EnetPulse - Preodds + /api/v1/enetpulse/results: + get: + consumes: + - application/json + description: Fetches all EnetPulse match results stored in the database + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/domain.Response' + - properties: + data: + items: + $ref: '#/definitions/domain.EnetpulseResult' + type: array + type: object + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: Get all results + tags: + - EnetPulse - Results + /api/v1/enetpulse/sports: + get: + consumes: + - application/json + description: Fetches all sports stored in the database + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/domain.Response' + - properties: + data: + items: + $ref: '#/definitions/domain.EnetpulseSport' + type: array + type: object + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: Get all sports + tags: + - EnetPulse - Sports + /api/v1/enetpulse/tournament-stages: + get: + consumes: + - application/json + description: Fetches all tournament stages stored in the database + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/domain.Response' + - properties: + data: + items: + $ref: '#/definitions/domain.EnetpulseTournamentStage' + type: array + type: object + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: Get all tournament stages + tags: + - EnetPulse - Tournament Stages + /api/v1/enetpulse/tournament-templates: + get: + consumes: + - application/json + description: Fetches all tournament templates stored in the database + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/domain.Response' + - properties: + data: + items: + $ref: '#/definitions/domain.EnetpulseTournamentTemplate' + type: array + type: object + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: Get all tournament templates + tags: + - EnetPulse - Tournament Templates + /api/v1/enetpulse/tournaments: + get: + consumes: + - application/json + description: Fetches all tournaments stored in the database + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/domain.Response' + - properties: + data: + items: + $ref: '#/definitions/domain.EnetpulseTournament' + type: array + type: object + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: Get all tournaments + tags: + - EnetPulse - Tournaments /api/v1/events: get: consumes: @@ -5829,6 +6477,93 @@ paths: summary: Retrieve an upcoming by ID tags: - prematch + /api/v1/events/{id}/bets: + get: + consumes: + - application/json + description: Retrieve bet outcomes by event id + parameters: + - description: ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.BaseEvent' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Retrieve bet outcomes by event id + tags: + - prematch + /api/v1/events/{id}/is_monitored: + patch: + consumes: + - application/json + description: Update the event is_monitored + parameters: + - description: Event ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: update the event is_monitored + tags: + - event + /api/v1/events/{id}/settings: + put: + consumes: + - application/json + description: Update the event settings + parameters: + - description: Event ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: update the event settings + tags: + - event /api/v1/issues: get: description: Admin endpoint to list all reported issues with pagination @@ -6231,6 +6966,71 @@ paths: summary: Retrieve all odds tags: - prematch + /api/v1/odds/pre-match: + get: + consumes: + - application/json + description: Fetches pre-match odds from EnetPulse for a given event + parameters: + - description: Event ID + in: query + name: objectFK + required: true + type: integer + - collectionFormat: csv + description: Odds provider IDs (comma separated) + in: query + items: + type: integer + name: oddsProviderFK + type: array + - description: Outcome type ID + in: query + name: outcomeTypeFK + type: integer + - description: Outcome scope ID + in: query + name: outcomeScopeFK + type: integer + - description: Outcome subtype ID + in: query + name: outcomeSubtypeFK + type: integer + - description: Limit results + in: query + name: limit + type: integer + - description: Offset results + in: query + name: offset + type: integer + - description: Language type ID + in: query + name: languageTypeFK + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/domain.Response' + - properties: + data: + $ref: '#/definitions/domain.PreMatchOddsResponse' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/domain.ErrorResponse' + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: Get pre-match odds for an event + tags: + - EnetPulse - PreMatch /api/v1/odds/upcoming/{upcoming_id}: get: consumes: @@ -6385,95 +7185,6 @@ paths: summary: List all virtual games tags: - VirtualGames - Orchestration - /api/v1/referral/settings: - get: - consumes: - - application/json - description: Retrieves current referral settings (admin only) - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/domain.ReferralSettings' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "403": - description: Forbidden - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - Bearer: [] - summary: Get referral settings - tags: - - referral - put: - consumes: - - application/json - description: Updates referral settings (admin only) - parameters: - - description: Referral settings - in: body - name: settings - required: true - schema: - $ref: '#/definitions/domain.ReferralSettings' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/response.APIResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "403": - description: Forbidden - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - Bearer: [] - summary: Update referral settings - tags: - - referral - /api/v1/referral/stats: - get: - consumes: - - application/json - description: Retrieves referral statistics for the authenticated user - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/domain.ReferralStats' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - security: - - Bearer: [] - summary: Get referral statistics - tags: - - referral /api/v1/report-files/download/{filename}: get: description: Downloads a generated report CSV file from the server @@ -6600,7 +7311,7 @@ paths: summary: Get dashboard report tags: - Reports - /api/v1/result/{id}: + /api/v1/result/b365/{id}: get: consumes: - application/json @@ -7181,6 +7892,63 @@ paths: summary: Gets shop bet by transaction id tags: - transaction + /api/v1/sport/bet/{id}: + delete: + consumes: + - application/json + description: Deletes bet by id + parameters: + - description: Bet ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Deletes bet by id + tags: + - bet + get: + consumes: + - application/json + description: Gets a single bet by id + parameters: + - description: Bet ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.BetRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Gets bet by id + tags: + - bet /api/v1/super-login: post: consumes: @@ -7269,6 +8037,108 @@ paths: summary: Create a supported operation tags: - branch + /api/v1/t-approver: + get: + consumes: + - application/json + description: Get all Admins + parameters: + - description: Page number + in: query + name: page + type: integer + - description: Page size + in: query + name: page_size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.AdminRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get all Admins + tags: + - admin + /api/v1/t-approver/{id}: + get: + consumes: + - application/json + description: Get a single admin by id + parameters: + - description: User ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.AdminRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get admin by id + tags: + - admin + put: + consumes: + - application/json + description: Update Admin + parameters: + - description: Update Admin + in: body + name: admin + required: true + schema: + $ref: '#/definitions/handlers.updateAdminReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Update Admin + tags: + - admin /api/v1/telebirr/callback: post: consumes: @@ -7329,6 +8199,308 @@ paths: summary: Create Telebirr Payment Session tags: - Telebirr + /api/v1/tenant: + get: + consumes: + - application/json + description: Check if phone number or email exist + parameters: + - description: Check phone number or email exist + in: body + name: checkPhoneEmailExist + required: true + schema: + $ref: '#/definitions/handlers.CheckPhoneEmailExistReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.CheckPhoneEmailExistRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Check if phone number or email exist + tags: + - user + /api/v1/tenant/{tenant_slug}/customer: + get: + consumes: + - application/json + description: Get all Customers + parameters: + - description: Page number + in: query + name: page + type: integer + - description: Page size + in: query + name: page_size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.CustomersRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get all Customers + tags: + - customer + /api/v1/tenant/{tenant_slug}/customer/{id}: + get: + consumes: + - application/json + description: Get a single customer by id + parameters: + - description: User ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.CustomersRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get customer by id + tags: + - customer + put: + consumes: + - application/json + description: Update Customers + parameters: + - description: Update Customers + in: body + name: Customers + required: true + schema: + $ref: '#/definitions/handlers.updateCustomerReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Update Customers + tags: + - customer + /api/v1/tenant/{tenant_slug}/customer/{id}/bets: + get: + consumes: + - application/json + description: Get tenant customer bets + parameters: + - description: User ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.CustomersRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get tenant customer bets + tags: + - customer + /api/v1/tenant/{tenant_slug}/events/{id}/bets: + get: + consumes: + - application/json + description: Retrieve bet outcomes by event id + parameters: + - description: ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.BaseEvent' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Retrieve bet outcomes by event id + tags: + - prematch + /api/v1/tenant/{tenant_slug}/events/{id}/settings: + put: + consumes: + - application/json + description: Update the event settings + parameters: + - description: Event ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: update the event settings + tags: + - event + /api/v1/tenant/{tenant_slug}/referral/stats: + get: + consumes: + - application/json + description: Retrieves referral statistics for the authenticated user + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.ReferralStats' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - Bearer: [] + summary: Get referral statistics + tags: + - referral + /api/v1/ticket: + get: + consumes: + - application/json + description: Retrieve all tickets + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/domain.TicketRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get all tickets + tags: + - ticket + /api/v1/ticket/{id}: + get: + consumes: + - application/json + description: Retrieve ticket details by ticket ID + parameters: + - description: Ticket ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.TicketRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get ticket by ID + tags: + - ticket /api/v1/transfer/refill/:id: post: consumes: @@ -7448,6 +8620,96 @@ paths: summary: Delete user by ID tags: - user + /api/v1/user/resetPassword: + post: + consumes: + - application/json + description: Reset password + parameters: + - description: Reset password + in: body + name: resetPassword + required: true + schema: + $ref: '#/definitions/handlers.ResetPasswordReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Reset password + tags: + - user + /api/v1/user/search: + post: + consumes: + - application/json + description: Search for user using name or phone + parameters: + - description: Search for using his name or phone + in: body + name: searchUserByNameOrPhone + required: true + schema: + $ref: '#/definitions/handlers.SearchUserByNameOrPhoneReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handlers.UserProfileRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Search for user using name or phone + tags: + - user + /api/v1/user/sendResetCode: + post: + consumes: + - application/json + description: Send reset code + parameters: + - description: Send reset code + in: body + name: resetCode + required: true + schema: + $ref: '#/definitions/handlers.ResetCodeReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Send reset code + tags: + - user /api/v1/user/single/{id}: get: consumes: diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index 9813c89..a2e631d 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -11,6 +11,22 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +const BulkUpdateBetOutcomeStatusByOddIDs = `-- name: BulkUpdateBetOutcomeStatusByOddIDs :exec +UPDATE bet_outcomes +SET status = $1 +WHERE odd_id = ANY($2::BIGINT []) +` + +type BulkUpdateBetOutcomeStatusByOddIDsParams struct { + Status int32 `json:"status"` + OddIds []int64 `json:"odd_ids"` +} + +func (q *Queries) BulkUpdateBetOutcomeStatusByOddIDs(ctx context.Context, arg BulkUpdateBetOutcomeStatusByOddIDsParams) error { + _, err := q.db.Exec(ctx, BulkUpdateBetOutcomeStatusByOddIDs, arg.Status, arg.OddIds) + return err +} + const CreateBet = `-- name: CreateBet :one INSERT INTO bets ( amount, @@ -104,7 +120,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 +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 FROM bet_with_outcomes wHERE ( user_id = $1 @@ -192,6 +208,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi &i.FullName, &i.PhoneNumber, &i.Outcomes, + &i.CompanySlug, ); err != nil { return nil, err } @@ -204,7 +221,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 +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 FROM bet_with_outcomes WHERE fast_code = $1 LIMIT 1 @@ -230,12 +247,13 @@ func (q *Queries) GetBetByFastCode(ctx context.Context, fastCode string) (BetWit &i.FullName, &i.PhoneNumber, &i.Outcomes, + &i.CompanySlug, ) return i, err } 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 +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 FROM bet_with_outcomes WHERE id = $1 ` @@ -260,12 +278,13 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err &i.FullName, &i.PhoneNumber, &i.Outcomes, + &i.CompanySlug, ) return i, 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 +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 FROM bet_with_outcomes WHERE user_id = $1 ` @@ -296,6 +315,7 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID int64) ([]BetWithOu &i.FullName, &i.PhoneNumber, &i.Outcomes, + &i.CompanySlug, ); err != nil { return nil, err } @@ -448,8 +468,109 @@ func (q *Queries) GetBetOutcomeCountByOddID(ctx context.Context, oddID int64) (i return count, err } +const GetBetOutcomeViewByEventID = `-- name: GetBetOutcomeViewByEventID :many +SELECT bet_outcomes.id, bet_outcomes.bet_id, bet_outcomes.sport_id, bet_outcomes.event_id, bet_outcomes.odd_id, bet_outcomes.home_team_name, bet_outcomes.away_team_name, bet_outcomes.market_id, bet_outcomes.market_name, bet_outcomes.odd, bet_outcomes.odd_name, bet_outcomes.odd_header, bet_outcomes.odd_handicap, bet_outcomes.status, bet_outcomes.expires, + users.first_name, + users.last_name, + bets.amount, + bets.total_odds, + companies.name as company_name +FROM bet_outcomes + JOIN bets ON bets.id = bet_outcomes.bet_id + JOIN users ON bets.user_id = users.id + JOIN companies ON bets.company_id = companies.id +WHERE bet_outcomes.event_id = $1 + AND ( + bets.company_id = $2 + OR $2 IS NULL + ) + AND ( + bet_outcomes.status = $3 + OR $3 IS NULL + ) +LIMIT $5 OFFSET $4 +` + +type GetBetOutcomeViewByEventIDParams struct { + EventID int64 `json:"event_id"` + CompanyID pgtype.Int8 `json:"company_id"` + FilterStatus pgtype.Int4 `json:"filter_status"` + Offset pgtype.Int4 `json:"offset"` + Limit pgtype.Int4 `json:"limit"` +} + +type GetBetOutcomeViewByEventIDRow struct { + ID int64 `json:"id"` + BetID int64 `json:"bet_id"` + SportID int64 `json:"sport_id"` + EventID int64 `json:"event_id"` + OddID int64 `json:"odd_id"` + HomeTeamName string `json:"home_team_name"` + AwayTeamName string `json:"away_team_name"` + MarketID int64 `json:"market_id"` + MarketName string `json:"market_name"` + Odd float32 `json:"odd"` + OddName string `json:"odd_name"` + OddHeader string `json:"odd_header"` + OddHandicap string `json:"odd_handicap"` + Status int32 `json:"status"` + Expires pgtype.Timestamp `json:"expires"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Amount int64 `json:"amount"` + TotalOdds float32 `json:"total_odds"` + CompanyName string `json:"company_name"` +} + +func (q *Queries) GetBetOutcomeViewByEventID(ctx context.Context, arg GetBetOutcomeViewByEventIDParams) ([]GetBetOutcomeViewByEventIDRow, error) { + rows, err := q.db.Query(ctx, GetBetOutcomeViewByEventID, + arg.EventID, + arg.CompanyID, + arg.FilterStatus, + arg.Offset, + arg.Limit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetBetOutcomeViewByEventIDRow + for rows.Next() { + var i GetBetOutcomeViewByEventIDRow + if err := rows.Scan( + &i.ID, + &i.BetID, + &i.SportID, + &i.EventID, + &i.OddID, + &i.HomeTeamName, + &i.AwayTeamName, + &i.MarketID, + &i.MarketName, + &i.Odd, + &i.OddName, + &i.OddHeader, + &i.OddHandicap, + &i.Status, + &i.Expires, + &i.FirstName, + &i.LastName, + &i.Amount, + &i.TotalOdds, + &i.CompanyName, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + 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 +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 FROM bet_with_outcomes WHERE status = 2 AND processed = false @@ -481,6 +602,7 @@ func (q *Queries) GetBetsForCashback(ctx context.Context) ([]BetWithOutcome, err &i.FullName, &i.PhoneNumber, &i.Outcomes, + &i.CompanySlug, ); err != nil { return nil, err } @@ -557,6 +679,34 @@ func (q *Queries) GetTotalBets(ctx context.Context, arg GetTotalBetsParams) (int return count, err } +const TotalBetOutcomeViewByEventID = `-- name: TotalBetOutcomeViewByEventID :one +SELECT count(*) +FROM bet_outcomes + JOIN bets ON bets.id = bet_outcomes.bet_id +WHERE bet_outcomes.event_id = $1 + AND ( + bets.company_id = $2 + OR $2 IS NULL + ) + AND ( + bet_outcomes.status = $3 + OR $3 IS NULL + ) +` + +type TotalBetOutcomeViewByEventIDParams struct { + EventID int64 `json:"event_id"` + CompanyID pgtype.Int8 `json:"company_id"` + FilterStatus pgtype.Int4 `json:"filter_status"` +} + +func (q *Queries) TotalBetOutcomeViewByEventID(ctx context.Context, arg TotalBetOutcomeViewByEventIDParams) (int64, error) { + row := q.db.QueryRow(ctx, TotalBetOutcomeViewByEventID, arg.EventID, arg.CompanyID, arg.FilterStatus) + var count int64 + err := row.Scan(&count) + return count, err +} + const UpdateBetOutcomeStatus = `-- name: UpdateBetOutcomeStatus :one UPDATE bet_outcomes SET status = $1 @@ -675,6 +825,54 @@ func (q *Queries) UpdateBetOutcomeStatusForEvent(ctx context.Context, arg Update return items, nil } +const UpdateBetOutcomeStatusForOddID = `-- name: UpdateBetOutcomeStatusForOddID :many +UPDATE bet_outcomes +SEt status = $1 +WHERE odd_id = $2 +RETURNING id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires +` + +type UpdateBetOutcomeStatusForOddIDParams struct { + Status int32 `json:"status"` + OddID int64 `json:"odd_id"` +} + +func (q *Queries) UpdateBetOutcomeStatusForOddID(ctx context.Context, arg UpdateBetOutcomeStatusForOddIDParams) ([]BetOutcome, error) { + rows, err := q.db.Query(ctx, UpdateBetOutcomeStatusForOddID, arg.Status, arg.OddID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []BetOutcome + for rows.Next() { + var i BetOutcome + if err := rows.Scan( + &i.ID, + &i.BetID, + &i.SportID, + &i.EventID, + &i.OddID, + &i.HomeTeamName, + &i.AwayTeamName, + &i.MarketID, + &i.MarketName, + &i.Odd, + &i.OddName, + &i.OddHeader, + &i.OddHandicap, + &i.Status, + &i.Expires, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const UpdateBetWithCashback = `-- name: UpdateBetWithCashback :exec UPDATE bets SET processed = $1 diff --git a/gen/db/bet_stat.sql.go b/gen/db/bet_stat.sql.go index 275ef07..03ffd04 100644 --- a/gen/db/bet_stat.sql.go +++ b/gen/db/bet_stat.sql.go @@ -34,32 +34,37 @@ wHERE ( OR $1 IS NULL ) AND ( - is_shop_bet = $2 + company_id = $2 OR $2 IS NULL ) AND ( - cashed_out = $3 + is_shop_bet = $3 OR $3 IS NULL ) AND ( - full_name ILIKE '%' || $4 || '%' - OR phone_number ILIKE '%' || $4 || '%' + cashed_out = $4 OR $4 IS NULL ) AND ( - created_at > $5 + full_name ILIKE '%' || $5 || '%' + OR phone_number ILIKE '%' || $5 || '%' OR $5 IS NULL ) AND ( - created_at < $6 + created_at > $6 OR $6 IS NULL ) + AND ( + created_at < $7 + OR $7 IS NULL + ) GROUP BY DATE(created_at) ORDER BY DATE(created_at) ` type GetBetStatsParams struct { UserID pgtype.Int8 `json:"user_id"` + CompanyID pgtype.Int8 `json:"company_id"` IsShopBet pgtype.Bool `json:"is_shop_bet"` CashedOut pgtype.Bool `json:"cashed_out"` Query pgtype.Text `json:"query"` @@ -79,6 +84,7 @@ type GetBetStatsRow struct { func (q *Queries) GetBetStats(ctx context.Context, arg GetBetStatsParams) ([]GetBetStatsRow, error) { rows, err := q.db.Query(ctx, GetBetStats, arg.UserID, + arg.CompanyID, arg.IsShopBet, arg.CashedOut, arg.Query, @@ -143,17 +149,22 @@ wHERE ( OR $1 IS NULL ) AND ( - created_at > $2 + company_id = $2 OR $2 IS NULL ) AND ( - created_at < $3 + created_at > $3 OR $3 IS NULL ) + AND ( + created_at < $4 + OR $4 IS NULL + ) ` type GetBetSummaryParams struct { UserID pgtype.Int8 `json:"user_id"` + CompanyID pgtype.Int8 `json:"company_id"` CreatedBefore pgtype.Timestamp `json:"created_before"` CreatedAfter pgtype.Timestamp `json:"created_after"` } @@ -168,7 +179,12 @@ type GetBetSummaryRow struct { } func (q *Queries) GetBetSummary(ctx context.Context, arg GetBetSummaryParams) (GetBetSummaryRow, error) { - row := q.db.QueryRow(ctx, GetBetSummary, arg.UserID, arg.CreatedBefore, arg.CreatedAfter) + row := q.db.QueryRow(ctx, GetBetSummary, + arg.UserID, + arg.CompanyID, + arg.CreatedBefore, + arg.CreatedAfter, + ) var i GetBetSummaryRow err := row.Scan( &i.TotalStakes, @@ -198,13 +214,17 @@ WITH market_counts AS ( OR $1 IS NULL ) AND ( - created_at > $2 + company_id = $2 OR $2 IS NULL ) AND ( - created_at < $3 + created_at > $3 OR $3 IS NULL ) + AND ( + created_at < $4 + OR $4 IS NULL + ) GROUP BY DATE(b.created_at), bo.market_name ) @@ -216,6 +236,7 @@ WHERE rank = 1 type GetMarketPopularityParams struct { UserID pgtype.Int8 `json:"user_id"` + CompanyID pgtype.Int8 `json:"company_id"` CreatedBefore pgtype.Timestamp `json:"created_before"` CreatedAfter pgtype.Timestamp `json:"created_after"` } @@ -226,7 +247,12 @@ type GetMarketPopularityRow struct { } func (q *Queries) GetMarketPopularity(ctx context.Context, arg GetMarketPopularityParams) (GetMarketPopularityRow, error) { - row := q.db.QueryRow(ctx, GetMarketPopularity, arg.UserID, arg.CreatedBefore, arg.CreatedAfter) + row := q.db.QueryRow(ctx, GetMarketPopularity, + arg.UserID, + arg.CompanyID, + arg.CreatedBefore, + arg.CreatedAfter, + ) var i GetMarketPopularityRow err := row.Scan(&i.Date, &i.MarketName) return i, err diff --git a/gen/db/branch.sql.go b/gen/db/branch.sql.go index a9a57b8..64cd62e 100644 --- a/gen/db/branch.sql.go +++ b/gen/db/branch.sql.go @@ -455,10 +455,19 @@ const SearchBranchByName = `-- name: SearchBranchByName :many SELECT id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance, wallet_is_active FROM branch_details WHERE name ILIKE '%' || $1 || '%' + AND ( + company_id = $2 + OR $2 IS NULL + ) ` -func (q *Queries) SearchBranchByName(ctx context.Context, dollar_1 pgtype.Text) ([]BranchDetail, error) { - rows, err := q.db.Query(ctx, SearchBranchByName, dollar_1) +type SearchBranchByNameParams struct { + Column1 pgtype.Text `json:"column_1"` + CompanyID pgtype.Int8 `json:"company_id"` +} + +func (q *Queries) SearchBranchByName(ctx context.Context, arg SearchBranchByNameParams) ([]BranchDetail, error) { + rows, err := q.db.Query(ctx, SearchBranchByName, arg.Column1, arg.CompanyID) if err != nil { return nil, err } diff --git a/gen/db/company.sql.go b/gen/db/company.sql.go index 506eaca..32d9ee2 100644 --- a/gen/db/company.sql.go +++ b/gen/db/company.sql.go @@ -17,9 +17,10 @@ INSERT INTO companies ( slug, admin_id, wallet_id, - deducted_percentage + deducted_percentage, + is_active ) -VALUES ($1, $2, $3, $4, $5) +VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at ` @@ -29,6 +30,7 @@ type CreateCompanyParams struct { AdminID int64 `json:"admin_id"` WalletID int64 `json:"wallet_id"` DeductedPercentage float32 `json:"deducted_percentage"` + IsActive bool `json:"is_active"` } func (q *Queries) CreateCompany(ctx context.Context, arg CreateCompanyParams) (Company, error) { @@ -38,6 +40,7 @@ func (q *Queries) CreateCompany(ctx context.Context, arg CreateCompanyParams) (C arg.AdminID, arg.WalletID, arg.DeductedPercentage, + arg.IsActive, ) var i Company err := row.Scan( @@ -153,17 +156,27 @@ func (q *Queries) GetCompanyByID(ctx context.Context, id int64) (CompaniesDetail return i, err } -const GetCompanyIDUsingSlug = `-- name: GetCompanyIDUsingSlug :one -SELECT id +const GetCompanyUsingSlug = `-- name: GetCompanyUsingSlug :one +SELECT id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at FROM companies WHERE slug = $1 ` -func (q *Queries) GetCompanyIDUsingSlug(ctx context.Context, slug string) (int64, error) { - row := q.db.QueryRow(ctx, GetCompanyIDUsingSlug, slug) - var id int64 - err := row.Scan(&id) - return id, err +func (q *Queries) GetCompanyUsingSlug(ctx context.Context, slug string) (Company, error) { + row := q.db.QueryRow(ctx, GetCompanyUsingSlug, slug) + var i Company + err := row.Scan( + &i.ID, + &i.Name, + &i.Slug, + &i.AdminID, + &i.WalletID, + &i.DeductedPercentage, + &i.IsActive, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err } const SearchCompanyByName = `-- name: SearchCompanyByName :many @@ -207,7 +220,7 @@ func (q *Queries) SearchCompanyByName(ctx context.Context, dollar_1 pgtype.Text) return items, nil } -const UpdateCompany = `-- name: UpdateCompany :one +const UpdateCompany = `-- name: UpdateCompany :exec UPDATE companies SET name = COALESCE($2, name), admin_id = COALESCE($3, admin_id), @@ -216,9 +229,9 @@ SET name = COALESCE($2, name), $5, deducted_percentage ), + slug = COALESCE($6, slug), updated_at = CURRENT_TIMESTAMP WHERE id = $1 -RETURNING id, name, slug, admin_id, wallet_id, deducted_percentage, is_active, created_at, updated_at ` type UpdateCompanyParams struct { @@ -227,27 +240,17 @@ type UpdateCompanyParams struct { AdminID pgtype.Int8 `json:"admin_id"` IsActive pgtype.Bool `json:"is_active"` DeductedPercentage pgtype.Float4 `json:"deducted_percentage"` + Slug pgtype.Text `json:"slug"` } -func (q *Queries) UpdateCompany(ctx context.Context, arg UpdateCompanyParams) (Company, error) { - row := q.db.QueryRow(ctx, UpdateCompany, +func (q *Queries) UpdateCompany(ctx context.Context, arg UpdateCompanyParams) error { + _, err := q.db.Exec(ctx, UpdateCompany, arg.ID, arg.Name, arg.AdminID, arg.IsActive, arg.DeductedPercentage, + arg.Slug, ) - var i Company - err := row.Scan( - &i.ID, - &i.Name, - &i.Slug, - &i.AdminID, - &i.WalletID, - &i.DeductedPercentage, - &i.IsActive, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err + return err } diff --git a/gen/db/enet_pulse.sql.go b/gen/db/enet_pulse.sql.go index 03ed49a..6a8c063 100644 --- a/gen/db/enet_pulse.sql.go +++ b/gen/db/enet_pulse.sql.go @@ -643,7 +643,8 @@ INSERT INTO enetpulse_tournament_stages ( updates_count, last_updated_at, status -) VALUES ( +) +VALUES ( $1, -- stage_id $2, -- name $3, -- tournament_fk @@ -656,6 +657,19 @@ INSERT INTO enetpulse_tournament_stages ( $10, -- last_updated_at $11 -- status ) +ON CONFLICT (stage_id) DO UPDATE +SET + name = EXCLUDED.name, + tournament_fk = EXCLUDED.tournament_fk, + gender = EXCLUDED.gender, + country_fk = EXCLUDED.country_fk, + country_name = EXCLUDED.country_name, + start_date = EXCLUDED.start_date, + end_date = EXCLUDED.end_date, + updates_count = EXCLUDED.updates_count, + last_updated_at = EXCLUDED.last_updated_at, + status = EXCLUDED.status, + updated_at = NOW() RETURNING id, stage_id, name, tournament_fk, gender, country_fk, country_name, start_date, end_date, updates_count, last_updated_at, status, created_at, updated_at ` @@ -1158,6 +1172,134 @@ func (q *Queries) GetAllEnetpulseTournaments(ctx context.Context) ([]EnetpulseTo return items, nil } +const GetFixturesWithPreodds = `-- name: GetFixturesWithPreodds :many +SELECT + f.fixture_id AS id, + f.fixture_id AS fixture_id, + f.name AS fixture_name, + f.sport_fk, + f.tournament_fk, + f.tournament_template_fk, + f.tournament_stage_fk, + f.start_date, + f.status_type, + f.status_desc_fk, + f.round_type_fk, + f.updates_count AS fixture_updates_count, + f.last_updated_at AS fixture_last_updated_at, + f.created_at AS fixture_created_at, + f.updated_at AS fixture_updated_at, + + -- Preodds fields + p.id AS preodds_db_id, + p.preodds_id, + p.event_fk, + p.outcome_type_fk, + p.outcome_scope_fk, + p.outcome_subtype_fk, + p.event_participant_number, + p.iparam, + p.iparam2, + p.dparam, + p.dparam2, + p.sparam, + p.updates_count AS preodds_updates_count, + p.last_updated_at AS preodds_last_updated_at, + p.created_at AS preodds_created_at, + p.updated_at AS preodds_updated_at + +FROM enetpulse_fixtures f +LEFT JOIN enetpulse_preodds p + ON p.event_fk = f.id +ORDER BY f.start_date DESC +` + +type GetFixturesWithPreoddsRow struct { + ID string `json:"id"` + FixtureID string `json:"fixture_id"` + FixtureName string `json:"fixture_name"` + SportFk string `json:"sport_fk"` + TournamentFk pgtype.Text `json:"tournament_fk"` + TournamentTemplateFk pgtype.Text `json:"tournament_template_fk"` + TournamentStageFk pgtype.Text `json:"tournament_stage_fk"` + StartDate pgtype.Timestamptz `json:"start_date"` + StatusType pgtype.Text `json:"status_type"` + StatusDescFk pgtype.Text `json:"status_desc_fk"` + RoundTypeFk pgtype.Text `json:"round_type_fk"` + FixtureUpdatesCount pgtype.Int4 `json:"fixture_updates_count"` + FixtureLastUpdatedAt pgtype.Timestamptz `json:"fixture_last_updated_at"` + FixtureCreatedAt pgtype.Timestamptz `json:"fixture_created_at"` + FixtureUpdatedAt pgtype.Timestamptz `json:"fixture_updated_at"` + PreoddsDbID pgtype.Int8 `json:"preodds_db_id"` + PreoddsID pgtype.Text `json:"preodds_id"` + EventFk pgtype.Int8 `json:"event_fk"` + OutcomeTypeFk pgtype.Int4 `json:"outcome_type_fk"` + OutcomeScopeFk pgtype.Int4 `json:"outcome_scope_fk"` + OutcomeSubtypeFk pgtype.Int4 `json:"outcome_subtype_fk"` + EventParticipantNumber pgtype.Int4 `json:"event_participant_number"` + Iparam pgtype.Text `json:"iparam"` + Iparam2 pgtype.Text `json:"iparam2"` + Dparam pgtype.Text `json:"dparam"` + Dparam2 pgtype.Text `json:"dparam2"` + Sparam pgtype.Text `json:"sparam"` + PreoddsUpdatesCount pgtype.Int4 `json:"preodds_updates_count"` + PreoddsLastUpdatedAt pgtype.Timestamptz `json:"preodds_last_updated_at"` + PreoddsCreatedAt pgtype.Timestamptz `json:"preodds_created_at"` + PreoddsUpdatedAt pgtype.Timestamptz `json:"preodds_updated_at"` +} + +func (q *Queries) GetFixturesWithPreodds(ctx context.Context) ([]GetFixturesWithPreoddsRow, error) { + rows, err := q.db.Query(ctx, GetFixturesWithPreodds) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetFixturesWithPreoddsRow + for rows.Next() { + var i GetFixturesWithPreoddsRow + if err := rows.Scan( + &i.ID, + &i.FixtureID, + &i.FixtureName, + &i.SportFk, + &i.TournamentFk, + &i.TournamentTemplateFk, + &i.TournamentStageFk, + &i.StartDate, + &i.StatusType, + &i.StatusDescFk, + &i.RoundTypeFk, + &i.FixtureUpdatesCount, + &i.FixtureLastUpdatedAt, + &i.FixtureCreatedAt, + &i.FixtureUpdatedAt, + &i.PreoddsDbID, + &i.PreoddsID, + &i.EventFk, + &i.OutcomeTypeFk, + &i.OutcomeScopeFk, + &i.OutcomeSubtypeFk, + &i.EventParticipantNumber, + &i.Iparam, + &i.Iparam2, + &i.Dparam, + &i.Dparam2, + &i.Sparam, + &i.PreoddsUpdatesCount, + &i.PreoddsLastUpdatedAt, + &i.PreoddsCreatedAt, + &i.PreoddsUpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const GetTournamentStagesByTournamentFK = `-- name: GetTournamentStagesByTournamentFK :many SELECT id, stage_id, name, tournament_fk, gender, country_fk, country_name, start_date, end_date, updates_count, last_updated_at, status, created_at, updated_at FROM enetpulse_tournament_stages diff --git a/gen/db/events.sql.go b/gen/db/events.sql.go index fc793b1..767a4e2 100644 --- a/gen/db/events.sql.go +++ b/gen/db/events.sql.go @@ -22,7 +22,7 @@ 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, source, default_is_active, default_is_featured, default_winning_upper_limit, is_monitored, league_cc +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 WHERE ( is_live = $1 @@ -122,12 +122,14 @@ func (q *Queries) GetAllEvents(ctx context.Context, arg GetAllEventsParams) ([]E &i.IsLive, &i.Status, &i.FetchedAt, + &i.UpdatedAt, &i.Source, &i.DefaultIsActive, &i.DefaultIsFeatured, &i.DefaultWinningUpperLimit, &i.IsMonitored, &i.LeagueCc, + &i.TotalOutcomes, ); err != nil { return nil, err } @@ -140,7 +142,7 @@ 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, source, default_is_active, default_is_featured, default_winning_upper_limit, is_monitored, league_cc +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 WHERE id = $1 LIMIT 1 @@ -171,18 +173,20 @@ func (q *Queries) GetEventByID(ctx context.Context, id int64) (EventWithCountry, &i.IsLive, &i.Status, &i.FetchedAt, + &i.UpdatedAt, &i.Source, &i.DefaultIsActive, &i.DefaultIsFeatured, &i.DefaultWinningUpperLimit, &i.IsMonitored, &i.LeagueCc, + &i.TotalOutcomes, ) 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, source, default_is_active, default_is_featured, default_winning_upper_limit, is_monitored, league_cc +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 WHERE source_event_id = $1 AND source = $2 @@ -218,18 +222,20 @@ func (q *Queries) GetEventBySourceID(ctx context.Context, arg GetEventBySourceID &i.IsLive, &i.Status, &i.FetchedAt, + &i.UpdatedAt, &i.Source, &i.DefaultIsActive, &i.DefaultIsFeatured, &i.DefaultWinningUpperLimit, &i.IsMonitored, &i.LeagueCc, + &i.TotalOutcomes, ) 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.source, e.default_is_active, e.default_is_featured, e.default_winning_upper_limit, e.is_monitored, +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, @@ -238,11 +244,18 @@ SELECT e.id, e.source_event_id, e.sport_id, e.match_name, e.home_team, e.away_te e.default_winning_upper_limit ) AS winning_upper_limit, ces.updated_at, - l.country_code as league_cc + 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 ` @@ -274,6 +287,7 @@ type GetEventWithSettingByIDRow struct { 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"` @@ -282,9 +296,10 @@ type GetEventWithSettingByIDRow struct { CompanyID pgtype.Int8 `json:"company_id"` IsActive bool `json:"is_active"` IsFeatured bool `json:"is_featured"` - WinningUpperLimit int32 `json:"winning_upper_limit"` - UpdatedAt pgtype.Timestamp `json:"updated_at"` + 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) { @@ -312,6 +327,7 @@ func (q *Queries) GetEventWithSettingByID(ctx context.Context, arg GetEventWithS &i.IsLive, &i.Status, &i.FetchedAt, + &i.UpdatedAt, &i.Source, &i.DefaultIsActive, &i.DefaultIsFeatured, @@ -321,14 +337,15 @@ func (q *Queries) GetEventWithSettingByID(ctx context.Context, arg GetEventWithS &i.IsActive, &i.IsFeatured, &i.WinningUpperLimit, - &i.UpdatedAt, + &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.source, e.default_is_active, e.default_is_featured, e.default_winning_upper_limit, e.is_monitored, +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, @@ -337,11 +354,18 @@ SELECT e.id, e.source_event_id, e.sport_id, e.match_name, e.home_team, e.away_te e.default_winning_upper_limit ) AS winning_upper_limit, ces.updated_at, - l.country_code as league_cc + 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 @@ -432,6 +456,7 @@ type GetEventsWithSettingsRow struct { 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"` @@ -440,9 +465,10 @@ type GetEventsWithSettingsRow struct { CompanyID pgtype.Int8 `json:"company_id"` IsActive bool `json:"is_active"` IsFeatured bool `json:"is_featured"` - WinningUpperLimit int32 `json:"winning_upper_limit"` - UpdatedAt pgtype.Timestamp `json:"updated_at"` + 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) { @@ -491,6 +517,7 @@ func (q *Queries) GetEventsWithSettings(ctx context.Context, arg GetEventsWithSe &i.IsLive, &i.Status, &i.FetchedAt, + &i.UpdatedAt, &i.Source, &i.DefaultIsActive, &i.DefaultIsFeatured, @@ -500,8 +527,9 @@ func (q *Queries) GetEventsWithSettings(ctx context.Context, arg GetEventsWithSe &i.IsActive, &i.IsFeatured, &i.WinningUpperLimit, - &i.UpdatedAt, + &i.UpdatedAt_2, &i.LeagueCc, + &i.TotalOutcomes, ); err != nil { return nil, err } @@ -514,7 +542,9 @@ func (q *Queries) GetEventsWithSettings(ctx context.Context, arg GetEventsWithSe } const GetSportAndLeagueIDs = `-- name: GetSportAndLeagueIDs :one -SELECT sport_id, league_id FROM events +SELECT sport_id, + league_id +FROM events WHERE id = $1 ` @@ -831,7 +861,7 @@ func (q *Queries) ListLiveEvents(ctx context.Context) ([]int64, error) { return items, nil } -const SaveEventSettings = `-- name: SaveEventSettings :exec +const SaveTenantEventSettings = `-- name: SaveTenantEventSettings :exec INSERT INTO company_event_settings ( company_id, event_id, @@ -846,16 +876,16 @@ SET is_active = EXCLUDED.is_active, winning_upper_limit = EXCLUDED.winning_upper_limit ` -type SaveEventSettingsParams struct { +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.Int4 `json:"winning_upper_limit"` + WinningUpperLimit pgtype.Int8 `json:"winning_upper_limit"` } -func (q *Queries) SaveEventSettings(ctx context.Context, arg SaveEventSettingsParams) error { - _, err := q.db.Exec(ctx, SaveEventSettings, +func (q *Queries) SaveTenantEventSettings(ctx context.Context, arg SaveTenantEventSettingsParams) error { + _, err := q.db.Exec(ctx, SaveTenantEventSettings, arg.CompanyID, arg.EventID, arg.IsActive, @@ -867,7 +897,8 @@ func (q *Queries) SaveEventSettings(ctx context.Context, arg SaveEventSettingsPa const UpdateEventMonitored = `-- name: UpdateEventMonitored :exec UPDATE events -SET is_monitored = $1 +SET is_monitored = $1, + updated_at = CURRENT_TIMESTAMP WHERE id = $2 ` @@ -881,6 +912,38 @@ func (q *Queries) UpdateEventMonitored(ctx context.Context, arg UpdateEventMonit return err } +const UpdateGlobalEventSettings = `-- name: UpdateGlobalEventSettings :exec +UPDATE events +SET default_is_active = COALESCE($2, default_is_active), + default_is_featured = COALESCE( + $3, + default_is_featured + ), + default_winning_upper_limit = COALESCE( + $4, + default_winning_upper_limit + ), + updated_at = CURRENT_TIMESTAMP +WHERE id = $1 +` + +type UpdateGlobalEventSettingsParams struct { + ID int64 `json:"id"` + DefaultIsActive pgtype.Bool `json:"default_is_active"` + DefaultIsFeatured pgtype.Bool `json:"default_is_featured"` + DefaultWinningUpperLimit pgtype.Int8 `json:"default_winning_upper_limit"` +} + +func (q *Queries) UpdateGlobalEventSettings(ctx context.Context, arg UpdateGlobalEventSettingsParams) error { + _, err := q.db.Exec(ctx, UpdateGlobalEventSettings, + arg.ID, + arg.DefaultIsActive, + arg.DefaultIsFeatured, + arg.DefaultWinningUpperLimit, + ) + return err +} + const UpdateMatchResult = `-- name: UpdateMatchResult :exec UPDATE events SET score = $1, diff --git a/gen/db/leagues.sql.go b/gen/db/leagues.sql.go index 1d2800b..1413699 100644 --- a/gen/db/leagues.sql.go +++ b/gen/db/leagues.sql.go @@ -48,14 +48,19 @@ WHERE ( name ILIKE '%' || $3 || '%' OR $3 IS NULL ) + AND ( + default_is_active = $4 + OR $4 IS NULL + ) ORDER BY name ASC -LIMIT $5 OFFSET $4 +LIMIT $6 OFFSET $5 ` type GetAllLeaguesParams struct { CountryCode pgtype.Text `json:"country_code"` SportID pgtype.Int4 `json:"sport_id"` Query pgtype.Text `json:"query"` + IsActive pgtype.Bool `json:"is_active"` Offset pgtype.Int4 `json:"offset"` Limit pgtype.Int4 `json:"limit"` } @@ -65,6 +70,7 @@ func (q *Queries) GetAllLeagues(ctx context.Context, arg GetAllLeaguesParams) ([ arg.CountryCode, arg.SportID, arg.Query, + arg.IsActive, arg.Offset, arg.Limit, ) @@ -199,6 +205,46 @@ func (q *Queries) GetAllLeaguesWithSettings(ctx context.Context, arg GetAllLeagu return items, nil } +const GetTotalLeagues = `-- name: GetTotalLeagues :one +SELECT COUNT(*) +FROM leagues +WHERE ( + country_code = $1 + OR $1 IS NULL + ) + AND ( + sport_id = $2 + OR $2 IS NULL + ) + AND ( + name ILIKE '%' || $3 || '%' + OR $3 IS NULL + ) + AND ( + default_is_active = $4 + OR $4 IS NULL + ) +` + +type GetTotalLeaguesParams struct { + CountryCode pgtype.Text `json:"country_code"` + SportID pgtype.Int4 `json:"sport_id"` + Query pgtype.Text `json:"query"` + IsActive pgtype.Bool `json:"is_active"` +} + +func (q *Queries) GetTotalLeagues(ctx context.Context, arg GetTotalLeaguesParams) (int64, error) { + row := q.db.QueryRow(ctx, GetTotalLeagues, + arg.CountryCode, + arg.SportID, + arg.Query, + arg.IsActive, + ) + var count int64 + err := row.Scan(&count) + return count, err +} + const GetTotalLeaguesWithSettings = `-- name: GetTotalLeaguesWithSettings :one SELECT COUNT(*) FROM leagues l @@ -292,7 +338,7 @@ func (q *Queries) InsertLeague(ctx context.Context, arg InsertLeagueParams) erro return err } -const InsertLeagueSettings = `-- name: InsertLeagueSettings :exec +const SaveLeagueSettings = `-- name: SaveLeagueSettings :exec INSERT INTO company_league_settings ( company_id, league_id, @@ -305,15 +351,15 @@ SET is_active = EXCLUDED.is_active, is_featured = EXCLUDED.is_featured ` -type InsertLeagueSettingsParams struct { +type SaveLeagueSettingsParams struct { CompanyID int64 `json:"company_id"` LeagueID int64 `json:"league_id"` IsActive pgtype.Bool `json:"is_active"` IsFeatured pgtype.Bool `json:"is_featured"` } -func (q *Queries) InsertLeagueSettings(ctx context.Context, arg InsertLeagueSettingsParams) error { - _, err := q.db.Exec(ctx, InsertLeagueSettings, +func (q *Queries) SaveLeagueSettings(ctx context.Context, arg SaveLeagueSettingsParams) error { + _, err := q.db.Exec(ctx, SaveLeagueSettings, arg.CompanyID, arg.LeagueID, arg.IsActive, @@ -322,6 +368,52 @@ func (q *Queries) InsertLeagueSettings(ctx context.Context, arg InsertLeagueSett return err } +const UpdateCompanyLeagueSettings = `-- name: UpdateCompanyLeagueSettings :exec +UPDATE company_league_settings +SET is_active = COALESCE($3, is_active), + is_featured = COALESCE( + $4, + is_featured + ) +WHERE league_id = $1 + AND company_id = $2 +` + +type UpdateCompanyLeagueSettingsParams struct { + LeagueID int64 `json:"league_id"` + CompanyID int64 `json:"company_id"` + IsActive pgtype.Bool `json:"is_active"` + IsFeatured pgtype.Bool `json:"is_featured"` +} + +func (q *Queries) UpdateCompanyLeagueSettings(ctx context.Context, arg UpdateCompanyLeagueSettingsParams) error { + _, err := q.db.Exec(ctx, UpdateCompanyLeagueSettings, + arg.LeagueID, + arg.CompanyID, + arg.IsActive, + arg.IsFeatured, + ) + return err +} + +const UpdateGlobalLeagueSettings = `-- name: UpdateGlobalLeagueSettings :exec +UPDATE leagues +SET default_is_active = COALESCE($2, default_is_active), + default_is_featured = COALESCE($3, default_is_featured) +WHERE id = $1 +` + +type UpdateGlobalLeagueSettingsParams struct { + ID int64 `json:"id"` + IsActive pgtype.Bool `json:"is_active"` + IsFeatured pgtype.Bool `json:"is_featured"` +} + +func (q *Queries) UpdateGlobalLeagueSettings(ctx context.Context, arg UpdateGlobalLeagueSettingsParams) error { + _, err := q.db.Exec(ctx, UpdateGlobalLeagueSettings, arg.ID, arg.IsActive, arg.IsFeatured) + return err +} + const UpdateLeague = `-- name: UpdateLeague :exec UPDATE leagues SET name = COALESCE($2, name), @@ -349,31 +441,3 @@ func (q *Queries) UpdateLeague(ctx context.Context, arg UpdateLeagueParams) erro ) return err } - -const UpdateLeagueSettings = `-- name: UpdateLeagueSettings :exec -UPDATE company_league_settings -SET is_active = COALESCE($3, is_active), - is_featured = COALESCE( - $4, - is_featured - ) -WHERE league_id = $1 - AND company_id = $2 -` - -type UpdateLeagueSettingsParams struct { - LeagueID int64 `json:"league_id"` - CompanyID int64 `json:"company_id"` - IsActive pgtype.Bool `json:"is_active"` - IsFeatured pgtype.Bool `json:"is_featured"` -} - -func (q *Queries) UpdateLeagueSettings(ctx context.Context, arg UpdateLeagueSettingsParams) error { - _, err := q.db.Exec(ctx, UpdateLeagueSettings, - arg.LeagueID, - arg.CompanyID, - arg.IsActive, - arg.IsFeatured, - ) - return err -} diff --git a/gen/db/models.go b/gen/db/models.go index 50f63d1..65348ad 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -82,6 +82,7 @@ type BetWithOutcome struct { FullName interface{} `json:"full_name"` PhoneNumber pgtype.Text `json:"phone_number"` Outcomes []BetOutcome `json:"outcomes"` + CompanySlug string `json:"company_slug"` } type Branch struct { @@ -177,7 +178,7 @@ type CompanyEventSetting struct { EventID int64 `json:"event_id"` IsActive pgtype.Bool `json:"is_active"` IsFeatured pgtype.Bool `json:"is_featured"` - WinningUpperLimit pgtype.Int4 `json:"winning_upper_limit"` + WinningUpperLimit pgtype.Int8 `json:"winning_upper_limit"` UpdatedAt pgtype.Timestamp `json:"updated_at"` } @@ -468,6 +469,7 @@ type Event struct { 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"` @@ -504,12 +506,14 @@ type EventWithCountry struct { 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"` } type EventWithSetting struct { @@ -534,6 +538,7 @@ type EventWithSetting struct { 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"` @@ -542,9 +547,10 @@ type EventWithSetting struct { CompanyID pgtype.Int8 `json:"company_id"` IsActive bool `json:"is_active"` IsFeatured bool `json:"is_featured"` - WinningUpperLimit int32 `json:"winning_upper_limit"` - UpdatedAt pgtype.Timestamp `json:"updated_at"` + WinningUpperLimit int64 `json:"winning_upper_limit"` + CompanyUpdatedAt pgtype.Timestamp `json:"company_updated_at"` LeagueCc pgtype.Text `json:"league_cc"` + TotalOutcomes int64 `json:"total_outcomes"` } type ExchangeRate struct { @@ -619,6 +625,8 @@ type Notification struct { Priority pgtype.Int4 `json:"priority"` Version int32 `json:"version"` Timestamp pgtype.Timestamptz `json:"timestamp"` + Img pgtype.Text `json:"img"` + Expires pgtype.Timestamptz `json:"expires"` Metadata []byte `json:"metadata"` } @@ -633,49 +641,52 @@ type OddHistory struct { } type OddsMarket struct { - ID int64 `json:"id"` - EventID int64 `json:"event_id"` - MarketType string `json:"market_type"` - MarketName string `json:"market_name"` - MarketCategory string `json:"market_category"` - MarketID int64 `json:"market_id"` - RawOdds []byte `json:"raw_odds"` - DefaultIsActive bool `json:"default_is_active"` - FetchedAt pgtype.Timestamp `json:"fetched_at"` - ExpiresAt pgtype.Timestamp `json:"expires_at"` + ID int64 `json:"id"` + EventID int64 `json:"event_id"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID int64 `json:"market_id"` + RawOdds []byte `json:"raw_odds"` + NumberOfOutcomes int64 `json:"number_of_outcomes"` + DefaultIsActive bool `json:"default_is_active"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` } type OddsMarketWithEvent struct { - ID int64 `json:"id"` - EventID int64 `json:"event_id"` - MarketType string `json:"market_type"` - MarketName string `json:"market_name"` - MarketCategory string `json:"market_category"` - MarketID int64 `json:"market_id"` - RawOdds []byte `json:"raw_odds"` - DefaultIsActive bool `json:"default_is_active"` - FetchedAt pgtype.Timestamp `json:"fetched_at"` - ExpiresAt pgtype.Timestamp `json:"expires_at"` - IsMonitored bool `json:"is_monitored"` - IsLive bool `json:"is_live"` - Status string `json:"status"` - Source string `json:"source"` + ID int64 `json:"id"` + EventID int64 `json:"event_id"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID int64 `json:"market_id"` + RawOdds []byte `json:"raw_odds"` + NumberOfOutcomes int64 `json:"number_of_outcomes"` + DefaultIsActive bool `json:"default_is_active"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` + IsMonitored bool `json:"is_monitored"` + IsLive bool `json:"is_live"` + Status string `json:"status"` + Source string `json:"source"` } type OddsMarketWithSetting struct { - ID int64 `json:"id"` - EventID int64 `json:"event_id"` - MarketType string `json:"market_type"` - MarketName string `json:"market_name"` - MarketCategory string `json:"market_category"` - MarketID int64 `json:"market_id"` - DefaultIsActive bool `json:"default_is_active"` - FetchedAt pgtype.Timestamp `json:"fetched_at"` - ExpiresAt pgtype.Timestamp `json:"expires_at"` - CompanyID pgtype.Int8 `json:"company_id"` - IsActive bool `json:"is_active"` - RawOdds []byte `json:"raw_odds"` - UpdatedAt pgtype.Timestamp `json:"updated_at"` + ID int64 `json:"id"` + EventID int64 `json:"event_id"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID int64 `json:"market_id"` + NumberOfOutcomes int64 `json:"number_of_outcomes"` + DefaultIsActive bool `json:"default_is_active"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` + CompanyID pgtype.Int8 `json:"company_id"` + IsActive bool `json:"is_active"` + RawOdds []byte `json:"raw_odds"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` } type Otp struct { @@ -691,13 +702,14 @@ type Otp struct { } type Raffle struct { - ID int32 `json:"id"` - CompanyID int32 `json:"company_id"` - Name string `json:"name"` - CreatedAt pgtype.Timestamp `json:"created_at"` - ExpiresAt pgtype.Timestamp `json:"expires_at"` - Type string `json:"type"` - Status string `json:"status"` + ID int32 `json:"id"` + CompanyID int32 `json:"company_id"` + Name string `json:"name"` + CreatedAt pgtype.Timestamp `json:"created_at"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` + TicketLimit int32 `json:"ticket_limit"` + Type string `json:"type"` + Status string `json:"status"` } type RaffleGameFilter struct { @@ -824,6 +836,7 @@ type ShopBetDetail struct { TransactionVerified bool `json:"transaction_verified"` Status int32 `json:"status"` TotalOdds float32 `json:"total_odds"` + FastCode string `json:"fast_code"` Outcomes []BetOutcome `json:"outcomes"` } diff --git a/gen/db/notification.sql.go b/gen/db/notification.sql.go index f6747fb..3c029fa 100644 --- a/gen/db/notification.sql.go +++ b/gen/db/notification.sql.go @@ -39,6 +39,8 @@ INSERT INTO notifications ( payload, priority, timestamp, + expires, + img, metadata ) VALUES ( @@ -54,9 +56,11 @@ VALUES ( $10, $11, $12, - $13 + $13, + $14, + $15 ) -RETURNING id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata +RETURNING id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, img, expires, metadata ` type CreateNotificationParams struct { @@ -72,6 +76,8 @@ type CreateNotificationParams struct { Payload []byte `json:"payload"` Priority pgtype.Int4 `json:"priority"` Timestamp pgtype.Timestamptz `json:"timestamp"` + Expires pgtype.Timestamptz `json:"expires"` + Img pgtype.Text `json:"img"` Metadata []byte `json:"metadata"` } @@ -89,6 +95,8 @@ func (q *Queries) CreateNotification(ctx context.Context, arg CreateNotification arg.Payload, arg.Priority, arg.Timestamp, + arg.Expires, + arg.Img, arg.Metadata, ) var i Notification @@ -106,13 +114,25 @@ func (q *Queries) CreateNotification(ctx context.Context, arg CreateNotification &i.Priority, &i.Version, &i.Timestamp, + &i.Img, + &i.Expires, &i.Metadata, ) return i, err } +const DeleteOldNotifications = `-- name: DeleteOldNotifications :exec +DELETE FROM notifications +WHERE expires < now() +` + +func (q *Queries) DeleteOldNotifications(ctx context.Context) error { + _, err := q.db.Exec(ctx, DeleteOldNotifications) + return err +} + const GetAllNotifications = `-- name: GetAllNotifications :many -SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata +SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, img, expires, metadata FROM notifications ORDER BY timestamp DESC LIMIT $1 OFFSET $2 @@ -146,6 +166,8 @@ func (q *Queries) GetAllNotifications(ctx context.Context, arg GetAllNotificatio &i.Priority, &i.Version, &i.Timestamp, + &i.Img, + &i.Expires, &i.Metadata, ); err != nil { return nil, err @@ -159,7 +181,7 @@ func (q *Queries) GetAllNotifications(ctx context.Context, arg GetAllNotificatio } const GetNotification = `-- name: GetNotification :one -SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata +SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, img, expires, metadata FROM notifications WHERE id = $1 LIMIT 1 @@ -182,6 +204,8 @@ func (q *Queries) GetNotification(ctx context.Context, id string) (Notification, &i.Priority, &i.Version, &i.Timestamp, + &i.Img, + &i.Expires, &i.Metadata, ) return i, err @@ -254,7 +278,7 @@ func (q *Queries) GetUserNotificationCount(ctx context.Context, recipientID int6 } const GetUserNotifications = `-- name: GetUserNotifications :many -SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata +SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, img, expires, metadata FROM notifications WHERE recipient_id = $1 ORDER BY timestamp DESC @@ -290,6 +314,8 @@ func (q *Queries) GetUserNotifications(ctx context.Context, arg GetUserNotificat &i.Priority, &i.Version, &i.Timestamp, + &i.Img, + &i.Expires, &i.Metadata, ); err != nil { return nil, err @@ -303,7 +329,7 @@ func (q *Queries) GetUserNotifications(ctx context.Context, arg GetUserNotificat } const ListFailedNotifications = `-- name: ListFailedNotifications :many -SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata +SELECT id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, img, expires, metadata FROM notifications WHERE delivery_status = 'failed' AND timestamp < NOW() - INTERVAL '1 hour' @@ -334,6 +360,8 @@ func (q *Queries) ListFailedNotifications(ctx context.Context, limit int32) ([]N &i.Priority, &i.Version, &i.Timestamp, + &i.Img, + &i.Expires, &i.Metadata, ); err != nil { return nil, err @@ -378,7 +406,7 @@ SET delivery_status = $2, is_read = $3, metadata = $4 WHERE id = $1 -RETURNING id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, metadata +RETURNING id, recipient_id, type, level, error_severity, reciever, is_read, delivery_status, delivery_channel, payload, priority, version, timestamp, img, expires, metadata ` type UpdateNotificationStatusParams struct { @@ -410,6 +438,8 @@ func (q *Queries) UpdateNotificationStatus(ctx context.Context, arg UpdateNotifi &i.Priority, &i.Version, &i.Timestamp, + &i.Img, + &i.Expires, &i.Metadata, ) return i, err diff --git a/gen/db/odds.sql.go b/gen/db/odds.sql.go index ac9974c..632f03c 100644 --- a/gen/db/odds.sql.go +++ b/gen/db/odds.sql.go @@ -11,6 +11,32 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +const DeleteAllCompanyOddsSetting = `-- name: DeleteAllCompanyOddsSetting :exec +DELETE FROM company_odd_settings +WHERE company_id = $1 +` + +func (q *Queries) DeleteAllCompanyOddsSetting(ctx context.Context, companyID int64) error { + _, err := q.db.Exec(ctx, DeleteAllCompanyOddsSetting, companyID) + return err +} + +const DeleteCompanyOddsSettingByOddMarketID = `-- name: DeleteCompanyOddsSettingByOddMarketID :exec +DELETE FROM company_odd_settings +WHERE company_id = $1 + AND odds_market_id = $2 +` + +type DeleteCompanyOddsSettingByOddMarketIDParams struct { + CompanyID int64 `json:"company_id"` + OddsMarketID int64 `json:"odds_market_id"` +} + +func (q *Queries) DeleteCompanyOddsSettingByOddMarketID(ctx context.Context, arg DeleteCompanyOddsSettingByOddMarketIDParams) error { + _, err := q.db.Exec(ctx, DeleteCompanyOddsSettingByOddMarketID, arg.CompanyID, arg.OddsMarketID) + return err +} + const DeleteOddsForEvent = `-- name: DeleteOddsForEvent :exec DELETE FROM odds_market Where event_id = $1 @@ -22,7 +48,7 @@ func (q *Queries) DeleteOddsForEvent(ctx context.Context, eventID int64) error { } const GetAllOdds = `-- name: GetAllOdds :many -SELECT id, event_id, market_type, market_name, market_category, market_id, raw_odds, default_is_active, fetched_at, expires_at, is_monitored, is_live, status, source +SELECT id, event_id, market_type, market_name, market_category, market_id, raw_odds, number_of_outcomes, default_is_active, fetched_at, expires_at, is_monitored, is_live, status, source FROM odds_market_with_event LIMIT $2 OFFSET $1 ` @@ -49,6 +75,7 @@ func (q *Queries) GetAllOdds(ctx context.Context, arg GetAllOddsParams) ([]OddsM &i.MarketCategory, &i.MarketID, &i.RawOdds, + &i.NumberOfOutcomes, &i.DefaultIsActive, &i.FetchedAt, &i.ExpiresAt, @@ -74,6 +101,7 @@ SELECT o.id, o.market_name, o.market_category, o.market_id, + o.number_of_outcomes, o.default_is_active, o.fetched_at, o.expires_at, @@ -94,19 +122,20 @@ type GetAllOddsWithSettingsParams struct { } type GetAllOddsWithSettingsRow struct { - ID int64 `json:"id"` - EventID int64 `json:"event_id"` - MarketType string `json:"market_type"` - MarketName string `json:"market_name"` - MarketCategory string `json:"market_category"` - MarketID int64 `json:"market_id"` - DefaultIsActive bool `json:"default_is_active"` - FetchedAt pgtype.Timestamp `json:"fetched_at"` - ExpiresAt pgtype.Timestamp `json:"expires_at"` - CompanyID pgtype.Int8 `json:"company_id"` - IsActive bool `json:"is_active"` - RawOdds []byte `json:"raw_odds"` - UpdatedAt pgtype.Timestamp `json:"updated_at"` + ID int64 `json:"id"` + EventID int64 `json:"event_id"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID int64 `json:"market_id"` + NumberOfOutcomes int64 `json:"number_of_outcomes"` + DefaultIsActive bool `json:"default_is_active"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` + CompanyID pgtype.Int8 `json:"company_id"` + IsActive bool `json:"is_active"` + RawOdds []byte `json:"raw_odds"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` } func (q *Queries) GetAllOddsWithSettings(ctx context.Context, arg GetAllOddsWithSettingsParams) ([]GetAllOddsWithSettingsRow, error) { @@ -125,6 +154,7 @@ func (q *Queries) GetAllOddsWithSettings(ctx context.Context, arg GetAllOddsWith &i.MarketName, &i.MarketCategory, &i.MarketID, + &i.NumberOfOutcomes, &i.DefaultIsActive, &i.FetchedAt, &i.ExpiresAt, @@ -144,7 +174,7 @@ func (q *Queries) GetAllOddsWithSettings(ctx context.Context, arg GetAllOddsWith } const GetOddByID = `-- name: GetOddByID :one -SELECT id, event_id, market_type, market_name, market_category, market_id, raw_odds, default_is_active, fetched_at, expires_at, is_monitored, is_live, status, source +SELECT id, event_id, market_type, market_name, market_category, market_id, raw_odds, number_of_outcomes, default_is_active, fetched_at, expires_at, is_monitored, is_live, status, source FROM odds_market_with_event WHERE id = $1 ` @@ -160,6 +190,7 @@ func (q *Queries) GetOddByID(ctx context.Context, id int64) (OddsMarketWithEvent &i.MarketCategory, &i.MarketID, &i.RawOdds, + &i.NumberOfOutcomes, &i.DefaultIsActive, &i.FetchedAt, &i.ExpiresAt, @@ -172,7 +203,7 @@ func (q *Queries) GetOddByID(ctx context.Context, id int64) (OddsMarketWithEvent } const GetOddsByEventID = `-- name: GetOddsByEventID :many -SELECT id, event_id, market_type, market_name, market_category, market_id, raw_odds, default_is_active, fetched_at, expires_at, is_monitored, is_live, status, source +SELECT id, event_id, market_type, market_name, market_category, market_id, raw_odds, number_of_outcomes, default_is_active, fetched_at, expires_at, is_monitored, is_live, status, source FROM odds_market_with_event WHERE event_id = $1 AND ( @@ -223,6 +254,7 @@ func (q *Queries) GetOddsByEventID(ctx context.Context, arg GetOddsByEventIDPara &i.MarketCategory, &i.MarketID, &i.RawOdds, + &i.NumberOfOutcomes, &i.DefaultIsActive, &i.FetchedAt, &i.ExpiresAt, @@ -242,7 +274,7 @@ func (q *Queries) GetOddsByEventID(ctx context.Context, arg GetOddsByEventIDPara } const GetOddsByMarketID = `-- name: GetOddsByMarketID :one -SELECT id, event_id, market_type, market_name, market_category, market_id, raw_odds, default_is_active, fetched_at, expires_at, is_monitored, is_live, status, source +SELECT id, event_id, market_type, market_name, market_category, market_id, raw_odds, number_of_outcomes, default_is_active, fetched_at, expires_at, is_monitored, is_live, status, source FROM odds_market_with_event WHERE market_id = $1 AND event_id = $2 @@ -264,6 +296,7 @@ func (q *Queries) GetOddsByMarketID(ctx context.Context, arg GetOddsByMarketIDPa &i.MarketCategory, &i.MarketID, &i.RawOdds, + &i.NumberOfOutcomes, &i.DefaultIsActive, &i.FetchedAt, &i.ExpiresAt, @@ -282,6 +315,7 @@ SELECT o.id, o.market_name, o.market_category, o.market_id, + o.number_of_outcomes, o.default_is_active, o.fetched_at, o.expires_at, @@ -304,19 +338,20 @@ type GetOddsWithSettingsByEventIDParams struct { } type GetOddsWithSettingsByEventIDRow struct { - ID int64 `json:"id"` - EventID int64 `json:"event_id"` - MarketType string `json:"market_type"` - MarketName string `json:"market_name"` - MarketCategory string `json:"market_category"` - MarketID int64 `json:"market_id"` - DefaultIsActive bool `json:"default_is_active"` - FetchedAt pgtype.Timestamp `json:"fetched_at"` - ExpiresAt pgtype.Timestamp `json:"expires_at"` - CompanyID pgtype.Int8 `json:"company_id"` - IsActive bool `json:"is_active"` - RawOdds []byte `json:"raw_odds"` - UpdatedAt pgtype.Timestamp `json:"updated_at"` + ID int64 `json:"id"` + EventID int64 `json:"event_id"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID int64 `json:"market_id"` + NumberOfOutcomes int64 `json:"number_of_outcomes"` + DefaultIsActive bool `json:"default_is_active"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` + CompanyID pgtype.Int8 `json:"company_id"` + IsActive bool `json:"is_active"` + RawOdds []byte `json:"raw_odds"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` } func (q *Queries) GetOddsWithSettingsByEventID(ctx context.Context, arg GetOddsWithSettingsByEventIDParams) ([]GetOddsWithSettingsByEventIDRow, error) { @@ -340,6 +375,7 @@ func (q *Queries) GetOddsWithSettingsByEventID(ctx context.Context, arg GetOddsW &i.MarketName, &i.MarketCategory, &i.MarketID, + &i.NumberOfOutcomes, &i.DefaultIsActive, &i.FetchedAt, &i.ExpiresAt, @@ -365,6 +401,7 @@ SELECT o.id, o.market_name, o.market_category, o.market_id, + o.number_of_outcomes, o.default_is_active, o.fetched_at, o.expires_at, @@ -384,19 +421,20 @@ type GetOddsWithSettingsByIDParams struct { } type GetOddsWithSettingsByIDRow struct { - ID int64 `json:"id"` - EventID int64 `json:"event_id"` - MarketType string `json:"market_type"` - MarketName string `json:"market_name"` - MarketCategory string `json:"market_category"` - MarketID int64 `json:"market_id"` - DefaultIsActive bool `json:"default_is_active"` - FetchedAt pgtype.Timestamp `json:"fetched_at"` - ExpiresAt pgtype.Timestamp `json:"expires_at"` - CompanyID pgtype.Int8 `json:"company_id"` - IsActive bool `json:"is_active"` - RawOdds []byte `json:"raw_odds"` - UpdatedAt pgtype.Timestamp `json:"updated_at"` + ID int64 `json:"id"` + EventID int64 `json:"event_id"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID int64 `json:"market_id"` + NumberOfOutcomes int64 `json:"number_of_outcomes"` + DefaultIsActive bool `json:"default_is_active"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` + CompanyID pgtype.Int8 `json:"company_id"` + IsActive bool `json:"is_active"` + RawOdds []byte `json:"raw_odds"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` } func (q *Queries) GetOddsWithSettingsByID(ctx context.Context, arg GetOddsWithSettingsByIDParams) (GetOddsWithSettingsByIDRow, error) { @@ -409,6 +447,7 @@ func (q *Queries) GetOddsWithSettingsByID(ctx context.Context, arg GetOddsWithSe &i.MarketName, &i.MarketCategory, &i.MarketID, + &i.NumberOfOutcomes, &i.DefaultIsActive, &i.FetchedAt, &i.ExpiresAt, @@ -427,6 +466,7 @@ SELECT o.id, o.market_name, o.market_category, o.market_id, + o.number_of_outcomes, o.default_is_active, o.fetched_at, o.expires_at, @@ -448,19 +488,20 @@ type GetOddsWithSettingsByMarketIDParams struct { } type GetOddsWithSettingsByMarketIDRow struct { - ID int64 `json:"id"` - EventID int64 `json:"event_id"` - MarketType string `json:"market_type"` - MarketName string `json:"market_name"` - MarketCategory string `json:"market_category"` - MarketID int64 `json:"market_id"` - DefaultIsActive bool `json:"default_is_active"` - FetchedAt pgtype.Timestamp `json:"fetched_at"` - ExpiresAt pgtype.Timestamp `json:"expires_at"` - CompanyID pgtype.Int8 `json:"company_id"` - IsActive bool `json:"is_active"` - RawOdds []byte `json:"raw_odds"` - UpdatedAt pgtype.Timestamp `json:"updated_at"` + ID int64 `json:"id"` + EventID int64 `json:"event_id"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID int64 `json:"market_id"` + NumberOfOutcomes int64 `json:"number_of_outcomes"` + DefaultIsActive bool `json:"default_is_active"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` + CompanyID pgtype.Int8 `json:"company_id"` + IsActive bool `json:"is_active"` + RawOdds []byte `json:"raw_odds"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` } func (q *Queries) GetOddsWithSettingsByMarketID(ctx context.Context, arg GetOddsWithSettingsByMarketIDParams) (GetOddsWithSettingsByMarketIDRow, error) { @@ -473,6 +514,7 @@ func (q *Queries) GetOddsWithSettingsByMarketID(ctx context.Context, arg GetOdds &i.MarketName, &i.MarketCategory, &i.MarketID, + &i.NumberOfOutcomes, &i.DefaultIsActive, &i.FetchedAt, &i.ExpiresAt, @@ -491,6 +533,7 @@ INSERT INTO odds_market ( market_name, market_category, market_id, + number_of_outcomes, raw_odds, fetched_at, expires_at @@ -503,26 +546,29 @@ VALUES ( $5, $6, $7, - $8 + $8, + $9 ) ON CONFLICT (event_id, market_id) DO UPDATE SET market_type = EXCLUDED.market_type, market_name = EXCLUDED.market_name, market_category = EXCLUDED.market_category, raw_odds = EXCLUDED.raw_odds, + number_of_outcomes = EXCLUDED.number_of_outcomes, fetched_at = EXCLUDED.fetched_at, expires_at = EXCLUDED.expires_at ` type InsertOddsMarketParams struct { - EventID int64 `json:"event_id"` - MarketType string `json:"market_type"` - MarketName string `json:"market_name"` - MarketCategory string `json:"market_category"` - MarketID int64 `json:"market_id"` - RawOdds []byte `json:"raw_odds"` - FetchedAt pgtype.Timestamp `json:"fetched_at"` - ExpiresAt pgtype.Timestamp `json:"expires_at"` + EventID int64 `json:"event_id"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID int64 `json:"market_id"` + NumberOfOutcomes int64 `json:"number_of_outcomes"` + RawOdds []byte `json:"raw_odds"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` } func (q *Queries) InsertOddsMarket(ctx context.Context, arg InsertOddsMarketParams) error { @@ -532,6 +578,7 @@ func (q *Queries) InsertOddsMarket(ctx context.Context, arg InsertOddsMarketPara arg.MarketName, arg.MarketCategory, arg.MarketID, + arg.NumberOfOutcomes, arg.RawOdds, arg.FetchedAt, arg.ExpiresAt, @@ -568,3 +615,19 @@ func (q *Queries) SaveOddSettings(ctx context.Context, arg SaveOddSettingsParams ) return err } + +const UpdateGlobalOddsSetting = `-- name: UpdateGlobalOddsSetting :exec +UPDATE odds_market +SET default_is_active = COALESCE($2, default_is_active) +WHERE id = $1 +` + +type UpdateGlobalOddsSettingParams struct { + ID int64 `json:"id"` + DefaultIsActive pgtype.Bool `json:"default_is_active"` +} + +func (q *Queries) UpdateGlobalOddsSetting(ctx context.Context, arg UpdateGlobalOddsSettingParams) error { + _, err := q.db.Exec(ctx, UpdateGlobalOddsSetting, arg.ID, arg.DefaultIsActive) + return err +} diff --git a/gen/db/raffle.sql.go b/gen/db/raffle.sql.go index a7a364e..d4b85d3 100644 --- a/gen/db/raffle.sql.go +++ b/gen/db/raffle.sql.go @@ -35,6 +35,19 @@ func (q *Queries) AddSportRaffleFilter(ctx context.Context, arg AddSportRaffleFi return i, err } +const CheckSportRaffleHasFilter = `-- name: CheckSportRaffleHasFilter :one +SELECT EXISTS ( + SELECT 1 FROM raffle_sport_filters WHERE raffle_id = $1 +) AS has_filter +` + +func (q *Queries) CheckSportRaffleHasFilter(ctx context.Context, raffleID int32) (bool, error) { + row := q.db.QueryRow(ctx, CheckSportRaffleHasFilter, raffleID) + var has_filter bool + err := row.Scan(&has_filter) + return has_filter, err +} + const CheckValidSportRaffleFilter = `-- name: CheckValidSportRaffleFilter :one SELECT COUNT(*) > 0 AS exists FROM raffle_sport_filters @@ -57,16 +70,17 @@ func (q *Queries) CheckValidSportRaffleFilter(ctx context.Context, arg CheckVali } const CreateRaffle = `-- name: CreateRaffle :one -INSERT INTO raffles (company_id, name, expires_at, type) -VALUES ($1, $2, $3, $4) -RETURNING id, company_id, name, created_at, expires_at, type, status +INSERT INTO raffles (company_id, name, expires_at, ticket_limit, type) +VALUES ($1, $2, $3, $4, $5) +RETURNING id, company_id, name, created_at, expires_at, ticket_limit, type, status ` type CreateRaffleParams struct { - CompanyID int32 `json:"company_id"` - Name string `json:"name"` - ExpiresAt pgtype.Timestamp `json:"expires_at"` - Type string `json:"type"` + CompanyID int32 `json:"company_id"` + Name string `json:"name"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` + TicketLimit int32 `json:"ticket_limit"` + Type string `json:"type"` } func (q *Queries) CreateRaffle(ctx context.Context, arg CreateRaffleParams) (Raffle, error) { @@ -74,6 +88,7 @@ func (q *Queries) CreateRaffle(ctx context.Context, arg CreateRaffleParams) (Raf arg.CompanyID, arg.Name, arg.ExpiresAt, + arg.TicketLimit, arg.Type, ) var i Raffle @@ -83,6 +98,7 @@ func (q *Queries) CreateRaffle(ctx context.Context, arg CreateRaffleParams) (Raf &i.Name, &i.CreatedAt, &i.ExpiresAt, + &i.TicketLimit, &i.Type, &i.Status, ) @@ -140,7 +156,7 @@ func (q *Queries) CreateRaffleWinner(ctx context.Context, arg CreateRaffleWinner const DeleteRaffle = `-- name: DeleteRaffle :one DELETE FROM raffles WHERE id = $1 -RETURNING id, company_id, name, created_at, expires_at, type, status +RETURNING id, company_id, name, created_at, expires_at, ticket_limit, type, status ` func (q *Queries) DeleteRaffle(ctx context.Context, id int32) (Raffle, error) { @@ -152,6 +168,7 @@ func (q *Queries) DeleteRaffle(ctx context.Context, id int32) (Raffle, error) { &i.Name, &i.CreatedAt, &i.ExpiresAt, + &i.TicketLimit, &i.Type, &i.Status, ) @@ -219,8 +236,40 @@ func (q *Queries) GetRaffleStanding(ctx context.Context, arg GetRaffleStandingPa return items, nil } +const GetRaffleTicketCount = `-- name: GetRaffleTicketCount :one +SELECT COUNT(*) +FROM raffle_tickets +WHERE raffle_id = $1 + AND user_id = $2 +` + +type GetRaffleTicketCountParams struct { + RaffleID int32 `json:"raffle_id"` + UserID int32 `json:"user_id"` +} + +func (q *Queries) GetRaffleTicketCount(ctx context.Context, arg GetRaffleTicketCountParams) (int64, error) { + row := q.db.QueryRow(ctx, GetRaffleTicketCount, arg.RaffleID, arg.UserID) + var count int64 + err := row.Scan(&count) + return count, err +} + +const GetRaffleTicketLimit = `-- name: GetRaffleTicketLimit :one +SELECT ticket_limit +FROM raffles +WHERE id = $1 +` + +func (q *Queries) GetRaffleTicketLimit(ctx context.Context, id int32) (int32, error) { + row := q.db.QueryRow(ctx, GetRaffleTicketLimit, id) + var ticket_limit int32 + err := row.Scan(&ticket_limit) + return ticket_limit, err +} + const GetRafflesOfCompany = `-- name: GetRafflesOfCompany :many -SELECT id, company_id, name, created_at, expires_at, type, status FROM raffles WHERE company_id = $1 +SELECT id, company_id, name, created_at, expires_at, ticket_limit, type, status FROM raffles WHERE company_id = $1 ` func (q *Queries) GetRafflesOfCompany(ctx context.Context, companyID int32) ([]Raffle, error) { @@ -238,6 +287,7 @@ func (q *Queries) GetRafflesOfCompany(ctx context.Context, companyID int32) ([]R &i.Name, &i.CreatedAt, &i.ExpiresAt, + &i.TicketLimit, &i.Type, &i.Status, ); err != nil { diff --git a/gen/db/shop_transactions.sql.go b/gen/db/shop_transactions.sql.go index bcd884e..5c4b52e 100644 --- a/gen/db/shop_transactions.sql.go +++ b/gen/db/shop_transactions.sql.go @@ -173,7 +173,7 @@ func (q *Queries) CreateShopTransaction(ctx context.Context, arg CreateShopTrans } const GetAllShopBets = `-- name: GetAllShopBets :many -SELECT id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at, customer_full_name, customer_phone_number, branch_id, company_id, amount, transaction_verified, status, total_odds, outcomes +SELECT id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at, customer_full_name, customer_phone_number, branch_id, company_id, amount, transaction_verified, status, total_odds, fast_code, outcomes FROM shop_bet_detail WHERE ( full_name ILIKE '%' || $1 || '%' @@ -239,6 +239,7 @@ func (q *Queries) GetAllShopBets(ctx context.Context, arg GetAllShopBetsParams) &i.TransactionVerified, &i.Status, &i.TotalOdds, + &i.FastCode, &i.Outcomes, ); err != nil { return nil, err @@ -419,7 +420,7 @@ func (q *Queries) GetAllShopTransactions(ctx context.Context, arg GetAllShopTran } const GetShopBetByBetID = `-- name: GetShopBetByBetID :one -SELECT id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at, customer_full_name, customer_phone_number, branch_id, company_id, amount, transaction_verified, status, total_odds, outcomes +SELECT id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at, customer_full_name, customer_phone_number, branch_id, company_id, amount, transaction_verified, status, total_odds, fast_code, outcomes FROM shop_bet_detail WHERE bet_id = $1 ` @@ -445,13 +446,14 @@ func (q *Queries) GetShopBetByBetID(ctx context.Context, betID int64) (ShopBetDe &i.TransactionVerified, &i.Status, &i.TotalOdds, + &i.FastCode, &i.Outcomes, ) return i, err } const GetShopBetByCashoutID = `-- name: GetShopBetByCashoutID :one -SELECT id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at, customer_full_name, customer_phone_number, branch_id, company_id, amount, transaction_verified, status, total_odds, outcomes +SELECT id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at, customer_full_name, customer_phone_number, branch_id, company_id, amount, transaction_verified, status, total_odds, fast_code, outcomes FROM shop_bet_detail WHERE cashout_id = $1 ` @@ -477,13 +479,14 @@ func (q *Queries) GetShopBetByCashoutID(ctx context.Context, cashoutID string) ( &i.TransactionVerified, &i.Status, &i.TotalOdds, + &i.FastCode, &i.Outcomes, ) return i, err } const GetShopBetByID = `-- name: GetShopBetByID :one -SELECT id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at, customer_full_name, customer_phone_number, branch_id, company_id, amount, transaction_verified, status, total_odds, outcomes +SELECT id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at, customer_full_name, customer_phone_number, branch_id, company_id, amount, transaction_verified, status, total_odds, fast_code, outcomes FROM shop_bet_detail WHERE id = $1 ` @@ -509,13 +512,14 @@ func (q *Queries) GetShopBetByID(ctx context.Context, id int64) (ShopBetDetail, &i.TransactionVerified, &i.Status, &i.TotalOdds, + &i.FastCode, &i.Outcomes, ) return i, err } const GetShopBetByShopTransactionID = `-- name: GetShopBetByShopTransactionID :one -SELECT id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at, customer_full_name, customer_phone_number, branch_id, company_id, amount, transaction_verified, status, total_odds, outcomes +SELECT id, shop_transaction_id, cashout_id, cashed_out_by, bet_id, number_of_outcomes, cashed_out, created_at, updated_at, customer_full_name, customer_phone_number, branch_id, company_id, amount, transaction_verified, status, total_odds, fast_code, outcomes FROM shop_bet_detail WHERE shop_transaction_id = $1 ` @@ -541,6 +545,7 @@ func (q *Queries) GetShopBetByShopTransactionID(ctx context.Context, shopTransac &i.TransactionVerified, &i.Status, &i.TotalOdds, + &i.FastCode, &i.Outcomes, ) return i, err diff --git a/go.mod b/go.mod index 9001f12..207087d 100644 --- a/go.mod +++ b/go.mod @@ -81,19 +81,11 @@ require ( require github.com/twilio/twilio-go v1.26.3 require ( - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/golang/mock v1.6.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/redis/go-redis/v9 v9.10.0 // direct go.uber.org/atomic v1.9.0 // indirect ) -require ( - github.com/pierrec/lz4/v4 v4.1.15 // indirect - github.com/segmentio/kafka-go v0.4.48 // direct -) - // require github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f // require github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f // direct diff --git a/go.sum b/go.sum index 868b34b..d639aba 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f h1:UOp9At84RG8OT2Nw2TQidYWaoW1rAfTqChOJLdhYcm8= -github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f/go.mod h1:N2NQ6ad3i+oLQU+MlPci2f7mx6Mtg+wUcvzCv77KCl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= @@ -12,17 +10,11 @@ github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= -github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= -github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= -github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= -github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= @@ -31,8 +23,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -91,7 +81,6 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= @@ -129,15 +118,11 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= -github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs= -github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/resend/resend-go/v2 v2.20.0 h1:MrIrgV0aHhwRgmcRPw33Nexn6aGJvCvG2XwfFpAMBGM= github.com/resend/resend-go/v2 v2.20.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -150,8 +135,6 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/segmentio/kafka-go v0.4.48 h1:9jyu9CWK4W5W+SroCe8EffbrRZVqAOkuaLd/ApID4Vs= -github.com/segmentio/kafka-go v0.4.48/go.mod h1:HjF6XbOKh0Pjlkr5GVZxt6CsjjwnmhVOfURM5KMd8qg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -215,12 +198,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -233,15 +214,11 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -257,25 +234,16 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -284,7 +252,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/domain/bet.go b/internal/domain/bet.go index e4939ba..fcad545 100644 --- a/internal/domain/bet.go +++ b/internal/domain/bet.go @@ -90,6 +90,7 @@ type GetBet struct { PhoneNumber string UserID int64 CompanyID int64 + CompanySlug string IsShopBet bool CashedOut bool Outcomes []BetOutcome @@ -149,18 +150,49 @@ type CreateBetRes struct { FastCode string `json:"fast_code"` } type BetRes struct { - ID int64 `json:"id" example:"1"` - Outcomes []BetOutcome `json:"outcomes"` - Amount float32 `json:"amount" example:"100.0"` - TotalOdds float32 `json:"total_odds" example:"4.22"` - Status OutcomeStatus `json:"status" example:"1"` - Fullname string `json:"full_name" example:"John Smith"` - UserID int64 `json:"user_id" example:"2"` - CompanyID int64 `json:"company_id" example:"1"` - IsShopBet bool `json:"is_shop_bet" example:"false"` - CashedOut bool `json:"cashed_out" example:"false"` - CreatedAt time.Time `json:"created_at" example:"2025-04-08T12:00:00Z"` - FastCode string `json:"fast_code"` + ID int64 `json:"id" example:"1"` + Outcomes []BetOutcome `json:"outcomes"` + Amount float32 `json:"amount" example:"100.0"` + TotalOdds float32 `json:"total_odds" example:"4.22"` + Status OutcomeStatus `json:"status" example:"1"` + Fullname string `json:"full_name" example:"John Smith"` + UserID int64 `json:"user_id" example:"2"` + CompanyID int64 `json:"company_id" example:"1"` + CompanySlug string `json:"company_slug" example:"fortune"` + IsShopBet bool `json:"is_shop_bet" example:"false"` + CashedOut bool `json:"cashed_out" example:"false"` + CreatedAt time.Time `json:"created_at" example:"2025-04-08T12:00:00Z"` + FastCode string `json:"fast_code"` +} + +type BetOutcomeViewRes struct { + ID int64 `json:"id"` + BetID int64 `json:"bet_id"` + CompanyName string `json:"company_name"` + SportID int64 `json:"sport_id"` + EventID int64 `json:"event_id"` + OddID int64 `json:"odd_id"` + HomeTeamName string `json:"home_team_name"` + AwayTeamName string `json:"away_team_name"` + MarketID int64 `json:"market_id"` + MarketName string `json:"market_name"` + Odd float32 `json:"odd"` + OddName string `json:"odd_name"` + OddHeader string `json:"odd_header"` + OddHandicap string `json:"odd_handicap"` + Status OutcomeStatus `json:"status"` + Expires time.Time `json:"expires"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Amount int64 `json:"amount"` + TotalOdds float32 `json:"total_odds"` +} + +type BetOutcomeViewFilter struct { + OutcomeStatus ValidOutcomeStatus + CompanyID ValidInt64 + Limit ValidInt32 + Offset ValidInt32 } func ConvertCreateBetRes(bet Bet, createdNumber int64) CreateBetRes { @@ -179,18 +211,19 @@ func ConvertCreateBetRes(bet Bet, createdNumber int64) CreateBetRes { func ConvertBet(bet GetBet) BetRes { return BetRes{ - ID: bet.ID, - Amount: bet.Amount.Float32(), - TotalOdds: bet.TotalOdds, - Status: bet.Status, - Fullname: bet.FullName, - UserID: bet.UserID, - CompanyID: bet.CompanyID, - Outcomes: bet.Outcomes, - IsShopBet: bet.IsShopBet, - CashedOut: bet.CashedOut, - CreatedAt: bet.CreatedAt, - FastCode: bet.FastCode, + ID: bet.ID, + Amount: bet.Amount.Float32(), + TotalOdds: bet.TotalOdds, + Status: bet.Status, + Fullname: bet.FullName, + UserID: bet.UserID, + CompanyID: bet.CompanyID, + CompanySlug: bet.CompanySlug, + Outcomes: bet.Outcomes, + IsShopBet: bet.IsShopBet, + CashedOut: bet.CashedOut, + CreatedAt: bet.CreatedAt, + FastCode: bet.FastCode, } } @@ -228,6 +261,30 @@ func ConvertDBBetOutcomes(outcome dbgen.BetOutcome) BetOutcome { Expires: outcome.Expires.Time, } } +func ConvertDBBetOutcomesView(outcome dbgen.GetBetOutcomeViewByEventIDRow) BetOutcomeViewRes { + return BetOutcomeViewRes{ + ID: outcome.ID, + BetID: outcome.BetID, + CompanyName: outcome.CompanyName, + SportID: outcome.SportID, + EventID: outcome.EventID, + OddID: outcome.OddID, + HomeTeamName: outcome.HomeTeamName, + AwayTeamName: outcome.AwayTeamName, + MarketID: outcome.MarketID, + MarketName: outcome.MarketName, + Odd: outcome.Odd, + OddName: outcome.OddName, + OddHeader: outcome.OddHeader, + OddHandicap: outcome.OddHandicap, + Status: OutcomeStatus(outcome.Status), + Expires: outcome.Expires.Time, + FirstName: outcome.FirstName, + LastName: outcome.LastName, + Amount: outcome.Amount, + TotalOdds: outcome.TotalOdds, + } +} func ConvertDBBetWithOutcomes(bet dbgen.BetWithOutcome) GetBet { var outcomes []BetOutcome = make([]BetOutcome, 0, len(bet.Outcomes)) @@ -239,6 +296,7 @@ func ConvertDBBetWithOutcomes(bet dbgen.BetWithOutcome) GetBet { return GetBet{ ID: bet.ID, CompanyID: bet.CompanyID, + CompanySlug: bet.CompanySlug, Amount: Currency(bet.Amount), TotalOdds: bet.TotalOdds, Status: OutcomeStatus(bet.Status), diff --git a/internal/domain/company.go b/internal/domain/company.go index 0d7eccd..f4ee64a 100644 --- a/internal/domain/company.go +++ b/internal/domain/company.go @@ -45,6 +45,8 @@ type CreateCompany struct { AdminID int64 WalletID int64 DeductedPercentage float32 + Slug string + IsActive bool } type UpdateCompany struct { @@ -53,18 +55,22 @@ type UpdateCompany struct { AdminID ValidInt64 IsActive ValidBool DeductedPercentage ValidFloat32 + Slug ValidString } type CreateCompanyReq struct { Name string `json:"name" example:"CompanyName"` AdminID int64 `json:"admin_id" example:"1"` DeductedPercentage float32 `json:"deducted_percentage" example:"0.1" validate:"lt=1"` + Slug string `json:"slug"` + IsActive bool `json:"is_active"` } type UpdateCompanyReq struct { Name *string `json:"name,omitempty" example:"CompanyName"` AdminID *int64 `json:"admin_id,omitempty" example:"1"` IsActive *bool `json:"is_active,omitempty" example:"true"` DeductedPercentage *float32 `json:"deducted_percentage,omitempty" example:"0.1" validate:"lt=1"` + Slug *string `json:"slug"` } type CompanyRes struct { @@ -113,13 +119,14 @@ func ConvertGetCompany(company GetCompany) GetCompanyRes { } } -func ConvertCreateCompany(company CreateCompany, uniqueSlug string) dbgen.CreateCompanyParams { +func ConvertCreateCompany(company CreateCompany) dbgen.CreateCompanyParams { return dbgen.CreateCompanyParams{ Name: company.Name, - Slug: uniqueSlug, + Slug: company.Slug, AdminID: company.AdminID, WalletID: company.WalletID, DeductedPercentage: company.DeductedPercentage, + IsActive: company.IsActive, } } @@ -154,31 +161,22 @@ func ConvertDBCompanyDetails(dbCompany dbgen.CompaniesDetail) GetCompany { func ConvertUpdateCompany(updateCompany UpdateCompany) dbgen.UpdateCompanyParams { newUpdateCompany := dbgen.UpdateCompanyParams{ - ID: updateCompany.ID, - Name: pgtype.Text{ - String: updateCompany.Name.Value, - Valid: updateCompany.Name.Valid, - }, - AdminID: pgtype.Int8{ - Int64: updateCompany.AdminID.Value, - Valid: updateCompany.AdminID.Valid, - }, - IsActive: pgtype.Bool{ - Bool: updateCompany.IsActive.Value, - Valid: updateCompany.IsActive.Valid, - }, - DeductedPercentage: pgtype.Float4{ - Float32: updateCompany.DeductedPercentage.Value, - Valid: updateCompany.DeductedPercentage.Valid, - }, + ID: updateCompany.ID, + Name: updateCompany.Name.ToPG(), + AdminID: updateCompany.AdminID.ToPG(), + IsActive: updateCompany.IsActive.ToPG(), + DeductedPercentage: updateCompany.DeductedPercentage.ToPG(), + Slug: updateCompany.Slug.ToPG(), } return newUpdateCompany } -func ConvertUpdateCompanyReq(req UpdateCompanyReq) UpdateCompany { +func ConvertUpdateCompanyReq(req UpdateCompanyReq, companyID int64) UpdateCompany { var updateCompany UpdateCompany + updateCompany.ID = companyID + if req.Name != nil { updateCompany.Name = ValidString{ Value: *req.Name, @@ -206,6 +204,12 @@ func ConvertUpdateCompanyReq(req UpdateCompanyReq) UpdateCompany { Valid: true, } } + if req.Slug != nil { + updateCompany.Slug = ValidString{ + Value: *req.Slug, + Valid: true, + } + } return updateCompany } diff --git a/internal/domain/currency.go b/internal/domain/currency.go index 7ce3b3c..a15ba52 100644 --- a/internal/domain/currency.go +++ b/internal/domain/currency.go @@ -25,49 +25,83 @@ func (m Currency) String() string { return fmt.Sprintf("$%.2f", m.Float32()) } + +// TODO: Change the currency to this format when implementing multi-currency +// type Currency struct { +// Value int64 +// Type IntCurrency +// } + +// // ToCurrency converts a float32 (like 12.34) into Currency (stored in cents). +// func NewCurrency(f float32, currencyType IntCurrency) Currency { +// cents := math.Round(float64(f) * 100) // avoid float32 precision issues +// return Currency{ +// Value: int64(cents), +// Type: currencyType, +// } +// } + +// func NewBase(v int64) Currency { +// return Currency{ +// Value: v, +// Type: BASE, +// } +// } + +// // Float32 converts a Currency back into float32 (like 12.34). +// func (m Currency) Float32() float32 { +// return float32(m.Value) / 100 +// } + +// // String returns a formatted Currency value for display. +// func (m Currency) String() string { +// return fmt.Sprintf("$%.2f", m.Float32()) +// } + type IntCurrency string const ( - ETB IntCurrency = "ETB" // Ethiopian Birr - NGN IntCurrency = "NGN" // Nigerian Naira - ZAR IntCurrency = "ZAR" // South African Rand - EGP IntCurrency = "EGP" // Egyptian Pound - KES IntCurrency = "KES" // Kenyan Shilling - UGX IntCurrency = "UGX" // Ugandan Shilling - TZS IntCurrency = "TZS" // Tanzanian Shilling - RWF IntCurrency = "RWF" // Rwandan Franc - BIF IntCurrency = "BIF" // Burundian Franc - XOF IntCurrency = "XOF" // West African CFA Franc (BCEAO) - XAF IntCurrency = "XAF" // Central African CFA Franc (BEAC) - GHS IntCurrency = "GHS" // Ghanaian Cedi - SDG IntCurrency = "SDG" // Sudanese Pound - SSP IntCurrency = "SSP" // South Sudanese Pound - DZD IntCurrency = "DZD" // Algerian Dinar - MAD IntCurrency = "MAD" // Moroccan Dirham - TND IntCurrency = "TND" // Tunisian Dinar - LYD IntCurrency = "LYD" // Libyan Dinar - MZN IntCurrency = "MZN" // Mozambican Metical - AOA IntCurrency = "AOA" // Angolan Kwanza - BWP IntCurrency = "BWP" // Botswana Pula - ZMW IntCurrency = "ZMW" // Zambian Kwacha - MWK IntCurrency = "MWK" // Malawian Kwacha - LSL IntCurrency = "LSL" // Lesotho Loti - NAD IntCurrency = "NAD" // Namibian Dollar - SZL IntCurrency = "SZL" // Swazi Lilangeni - CVE IntCurrency = "CVE" // Cape Verdean Escudo - GMD IntCurrency = "GMD" // Gambian Dalasi - SLL IntCurrency = "SLL" // Sierra Leonean Leone - LRD IntCurrency = "LRD" // Liberian Dollar - GNF IntCurrency = "GNF" // Guinean Franc - XCD IntCurrency = "XCD" // Eastern Caribbean Dollar (used in Saint Lucia) - MRU IntCurrency = "MRU" // Mauritanian Ouguiya - KMF IntCurrency = "KMF" // Comorian Franc - DJF IntCurrency = "DJF" // Djiboutian Franc - SOS IntCurrency = "SOS" // Somali Shilling - ERN IntCurrency = "ERN" // Eritrean Nakfa - MGA IntCurrency = "MGA" // Malagasy Ariary - SCR IntCurrency = "SCR" // Seychellois Rupee - MUR IntCurrency = "MUR" // Mauritian Rupee + BASE IntCurrency = "BASE" + ETB IntCurrency = "ETB" // Ethiopian Birr + NGN IntCurrency = "NGN" // Nigerian Naira + ZAR IntCurrency = "ZAR" // South African Rand + EGP IntCurrency = "EGP" // Egyptian Pound + KES IntCurrency = "KES" // Kenyan Shilling + UGX IntCurrency = "UGX" // Ugandan Shilling + TZS IntCurrency = "TZS" // Tanzanian Shilling + RWF IntCurrency = "RWF" // Rwandan Franc + BIF IntCurrency = "BIF" // Burundian Franc + XOF IntCurrency = "XOF" // West African CFA Franc (BCEAO) + XAF IntCurrency = "XAF" // Central African CFA Franc (BEAC) + GHS IntCurrency = "GHS" // Ghanaian Cedi + SDG IntCurrency = "SDG" // Sudanese Pound + SSP IntCurrency = "SSP" // South Sudanese Pound + DZD IntCurrency = "DZD" // Algerian Dinar + MAD IntCurrency = "MAD" // Moroccan Dirham + TND IntCurrency = "TND" // Tunisian Dinar + LYD IntCurrency = "LYD" // Libyan Dinar + MZN IntCurrency = "MZN" // Mozambican Metical + AOA IntCurrency = "AOA" // Angolan Kwanza + BWP IntCurrency = "BWP" // Botswana Pula + ZMW IntCurrency = "ZMW" // Zambian Kwacha + MWK IntCurrency = "MWK" // Malawian Kwacha + LSL IntCurrency = "LSL" // Lesotho Loti + NAD IntCurrency = "NAD" // Namibian Dollar + SZL IntCurrency = "SZL" // Swazi Lilangeni + CVE IntCurrency = "CVE" // Cape Verdean Escudo + GMD IntCurrency = "GMD" // Gambian Dalasi + SLL IntCurrency = "SLL" // Sierra Leonean Leone + LRD IntCurrency = "LRD" // Liberian Dollar + GNF IntCurrency = "GNF" // Guinean Franc + XCD IntCurrency = "XCD" // Eastern Caribbean Dollar (used in Saint Lucia) + MRU IntCurrency = "MRU" // Mauritanian Ouguiya + KMF IntCurrency = "KMF" // Comorian Franc + DJF IntCurrency = "DJF" // Djiboutian Franc + SOS IntCurrency = "SOS" // Somali Shilling + ERN IntCurrency = "ERN" // Eritrean Nakfa + MGA IntCurrency = "MGA" // Malagasy Ariary + SCR IntCurrency = "SCR" // Seychellois Rupee + MUR IntCurrency = "MUR" // Mauritian Rupee // International currencies (already listed) USD IntCurrency = "USD" // US Dollar diff --git a/internal/domain/enet_pulse.go b/internal/domain/enet_pulse.go index a4c75eb..6a5881a 100644 --- a/internal/domain/enet_pulse.go +++ b/internal/domain/enet_pulse.go @@ -486,25 +486,23 @@ type CreateEnetpulseFixture struct { // Full domain model type EnetpulseFixture struct { - FixtureID string - Name string - SportFK string - TournamentFK string - TournamentTemplateFK string - TournamentStageFK string - TournamentStageName string - TournamentName string - TournamentTemplateName string - SportName string - Gender string - StartDate time.Time - StatusType string - StatusDescFK string - RoundTypeFK string - UpdatesCount int - LastUpdatedAt time.Time - CreatedAt time.Time - UpdatedAt time.Time + FixtureID string `json:"id"` + Name string `json:"name"` + SportFK string `json:"sportFK"` + TournamentFK string `json:"tournamentFK"` + TournamentTemplateFK string `json:"tournament_templateFK"` + TournamentStageFK string `json:"tournament_stageFK"` + TournamentStageName string `json:"tournament_stage_name"` + TournamentName string `json:"tournament_name"` + TournamentTemplateName string `json:"tournament_template_name"` + SportName string `json:"sport_name"` + Gender string `json:"gender"` + StartDate string `json:"startdate"` // ISO 8601 + StatusType string `json:"status_type"` + StatusDescFK string `json:"status_descFK"` + RoundTypeFK string `json:"round_typeFK"` + UpdatesCount string `json:"n"` // convert to int + LastUpdatedAt string `json:"ut"` // parse to time.Time } type CreateEnetpulseResult struct { @@ -604,7 +602,7 @@ type CreateEnetpulseOutcomeType struct { LastUpdatedAt time.Time `json:"last_updated_at"` } -type EnetpulsePreodds struct { +type EnetpulsePreoddsTemp struct { PreoddsID string `json:"preodds_id"` EventFK string `json:"event_fk"` OutcomeTypeFK string `json:"outcome_type_fk"` @@ -668,3 +666,41 @@ type EnetpulsePreoddsBettingOffer struct { CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } + +type EnetpulseFixtureWithPreodds struct { + FixtureID string + FixtureApiID string + FixtureName string + SportFk string + TournamentFk string + TournamentTemplateFk string + TournamentStageFk string + StartDate time.Time + StatusType string + StatusDescFk string + RoundTypeFk string + UpdatesCount int32 + LastUpdatedAt time.Time + CreatedAt time.Time + UpdatedAt time.Time + Preodds []EnetpulsePreodds +} + +type EnetpulsePreodds struct { + ID int64 + PreoddsID string + EventFK int64 + OutcomeTypeFK int32 + OutcomeScopeFK int32 + OutcomeSubtypeFK int32 + EventParticipantNumber int32 + IParam string + IParam2 string + DParam string + DParam2 string + SParam string + UpdatesCount int32 + LastUpdatedAt time.Time + CreatedAt time.Time + UpdatedAt time.Time +} diff --git a/internal/domain/event.go b/internal/domain/event.go index d898150..f46032d 100644 --- a/internal/domain/event.go +++ b/internal/domain/event.go @@ -1,6 +1,7 @@ package domain import ( + "fmt" "time" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" @@ -50,6 +51,58 @@ const ( 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 @@ -67,6 +120,7 @@ type BaseEvent struct { StartTime time.Time Source EventSource Status EventStatus + TotalOddOutcomes int64 IsMonitored bool DefaultIsFeatured bool DefaultIsActive bool @@ -96,6 +150,7 @@ 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"` @@ -125,10 +180,11 @@ type EventWithSettings struct { StartTime time.Time Source EventSource Status EventStatus + TotalOddOutcomes int64 IsMonitored bool IsFeatured bool IsActive bool - WinningUpperLimit int32 + WinningUpperLimit int64 DefaultIsFeatured bool DefaultIsActive bool DefaultWinningUpperLimit int64 @@ -178,10 +234,11 @@ type EventWithSettingsRes 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"` IsFeatured bool `json:"is_featured"` IsActive bool `json:"is_active"` - WinningUpperLimit int32 `json:"winning_upper_limit"` + 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"` @@ -204,12 +261,18 @@ type EventSettings struct { UpdatedAt time.Time } -type CreateEventSettings struct { +type UpdateTenantEventSettings struct { CompanyID int64 EventID int64 IsActive ValidBool IsFeatured ValidBool - WinningUpperLimit ValidInt + WinningUpperLimit ValidInt64 +} +type UpdateGlobalEventSettings struct { + EventID int64 + IsActive ValidBool + IsFeatured ValidBool + WinningUpperLimit ValidInt64 } type ValidEventStatus struct { @@ -273,6 +336,7 @@ 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, @@ -331,8 +395,8 @@ func ConvertCreateEvent(e CreateEvent) dbgen.InsertEventParams { } } -func ConvertCreateEventSettings(eventSettings CreateEventSettings) dbgen.SaveEventSettingsParams { - return dbgen.SaveEventSettingsParams{ +func ConvertCreateEventSettings(eventSettings UpdateTenantEventSettings) dbgen.SaveTenantEventSettingsParams { + return dbgen.SaveTenantEventSettingsParams{ CompanyID: eventSettings.CompanyID, EventID: eventSettings.EventID, IsActive: eventSettings.IsActive.ToPG(), @@ -343,17 +407,19 @@ func ConvertCreateEventSettings(eventSettings CreateEventSettings) dbgen.SaveEve func ConvertDBEventWithSetting(event dbgen.EventWithSetting) EventWithSettings { return 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, + 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, @@ -361,6 +427,7 @@ func ConvertDBEventWithSetting(event dbgen.EventWithSetting) EventWithSettings { 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, @@ -401,8 +468,8 @@ func ConvertDBEventWithSettings(events []dbgen.EventWithSetting) []EventWithSett return result } -func ConvertUpdateEventSettings(event CreateEventSettings) dbgen.SaveEventSettingsParams { - return dbgen.SaveEventSettingsParams{ +func ConvertUpdateTenantEventSettings(event UpdateTenantEventSettings) dbgen.SaveTenantEventSettingsParams { + return dbgen.SaveTenantEventSettingsParams{ EventID: event.EventID, CompanyID: event.CompanyID, IsActive: event.IsActive.ToPG(), @@ -410,10 +477,19 @@ func ConvertUpdateEventSettings(event CreateEventSettings) dbgen.SaveEventSettin 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{ ID: event.ID, + SourceEventID: event.SourceEventID, SportID: event.SportID, MatchName: event.MatchName, HomeTeam: event.HomeTeam, @@ -428,6 +504,7 @@ 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, @@ -452,6 +529,7 @@ func ConvertEventResList(events []BaseEvent) []BaseEventRes { func ConvertEventWitSettingRes(event EventWithSettings) EventWithSettingsRes { return EventWithSettingsRes{ ID: event.ID, + SourceEventID: event.SourceEventID, SportID: event.SportID, MatchName: event.MatchName, HomeTeam: event.HomeTeam, @@ -466,6 +544,7 @@ func ConvertEventWitSettingRes(event EventWithSettings) EventWithSettingsRes { StartTime: event.StartTime.UTC(), Source: EventSource(event.Source), Status: EventStatus(event.Status), + TotalOddOutcomes: event.TotalOddOutcomes, IsFeatured: event.IsFeatured, IsMonitored: event.IsMonitored, IsActive: event.IsActive, @@ -480,6 +559,7 @@ func ConvertEventWitSettingRes(event EventWithSettings) EventWithSettingsRes { MatchPeriod: event.MatchPeriod.Value, IsLive: event.IsLive, FetchedAt: event.FetchedAt.UTC(), + UpdatedAt: event.UpdatedAt, } } diff --git a/internal/domain/league.go b/internal/domain/league.go index 6743b6e..c2de5d1 100644 --- a/internal/domain/league.go +++ b/internal/domain/league.go @@ -87,6 +87,17 @@ type UpdateLeague struct { SportID ValidInt32 `json:"sport_id" example:"1"` } +type UpdateLeagueSettingsReq struct { + IsFeatured *bool `json:"is_featured" example:"true"` + IsActive *bool `json:"is_active" example:"true"` +} + +type UpdateGlobalLeagueSettings struct { + ID int64 + DefaultIsActive ValidBool + DefaultIsFeatured ValidBool +} + type LeagueFilter struct { Query ValidString CountryCode ValidString @@ -109,8 +120,8 @@ func ConvertCreateLeague(league CreateLeague) dbgen.InsertLeagueParams { } } -func ConvertCreateLeagueSettings(leagueSetting CreateLeagueSettings) dbgen.InsertLeagueSettingsParams { - return dbgen.InsertLeagueSettingsParams{ +func ConvertCreateLeagueSettings(leagueSetting CreateLeagueSettings) dbgen.SaveLeagueSettingsParams { + return dbgen.SaveLeagueSettingsParams{ CompanyID: leagueSetting.CompanyID, LeagueID: leagueSetting.LeagueID, IsActive: leagueSetting.IsActive.ToPG(), @@ -149,7 +160,7 @@ func ConvertDBLeagueWithSetting(lws dbgen.GetAllLeaguesWithSettingsRow) LeagueWi ID: lws.ID, Name: lws.Name, CompanyID: lws.CompanyID.Int64, - CountryCode: ValidString{ + CountryCode: ValidString{ Value: lws.CountryCode.String, Valid: lws.CountryCode.Valid, }, @@ -187,15 +198,15 @@ func ConvertUpdateLeague(updateLeague UpdateLeague) dbgen.UpdateLeagueParams { func ConvertLeagueWithSettingRes(lws LeagueWithSettings) LeagueWithSettingsRes { return LeagueWithSettingsRes{ - ID: lws.ID, - Name: lws.Name, - CompanyID: lws.CompanyID, - CountryCode: lws.CountryCode.Value, - Bet365ID: lws.Bet365ID.Value, - IsActive: lws.IsActive, - SportID: lws.SportID, - IsFeatured: lws.IsFeatured, - UpdatedAt: lws.UpdatedAt, + ID: lws.ID, + Name: lws.Name, + CompanyID: lws.CompanyID, + CountryCode: lws.CountryCode.Value, + Bet365ID: lws.Bet365ID.Value, + IsActive: lws.IsActive, + SportID: lws.SportID, + IsFeatured: lws.IsFeatured, + UpdatedAt: lws.UpdatedAt, DefaultIsActive: lws.DefaultIsActive, DefaultIsFeatured: lws.DefaultIsFeatured, } @@ -213,12 +224,12 @@ func ConvertLeagueWithSettingResList(leagues []LeagueWithSettings) []LeagueWithS func ConvertBaseLeagueRes(league BaseLeague) BaseLeagueRes { return BaseLeagueRes{ - ID: league.ID, - Name: league.Name, - CountryCode: league.CountryCode.Value, - Bet365ID: league.Bet365ID.Value, - SportID: league.SportID, - DefaultIsActive: league.DefaultIsActive, + ID: league.ID, + Name: league.Name, + CountryCode: league.CountryCode.Value, + Bet365ID: league.Bet365ID.Value, + SportID: league.SportID, + DefaultIsActive: league.DefaultIsActive, DefaultIsFeatured: league.DefaultIsFeatured, } } @@ -231,3 +242,11 @@ func ConvertBaseLeagueResList(leagues []BaseLeague) []BaseLeagueRes { return result } + +func ConvertUpdateGlobalLeagueSetting(league UpdateGlobalLeagueSettings) dbgen.UpdateGlobalLeagueSettingsParams { + return dbgen.UpdateGlobalLeagueSettingsParams{ + ID: league.ID, + IsActive: league.DefaultIsActive.ToPG(), + IsFeatured: league.DefaultIsFeatured.ToPG(), + } +} diff --git a/internal/domain/notification.go b/internal/domain/notification.go index b7193d7..046b3a9 100644 --- a/internal/domain/notification.go +++ b/internal/domain/notification.go @@ -84,6 +84,8 @@ type Notification struct { Priority int `json:"priority,omitempty"` Version int `json:"-"` Timestamp time.Time `json:"timestamp"` + Expires time.Time `json:"expires"` + Image string `json:"image"` Metadata json.RawMessage `json:"metadata,omitempty"` } type CreateNotification struct { @@ -97,6 +99,8 @@ type CreateNotification struct { DeliveryChannel DeliveryChannel `json:"delivery_channel,omitempty"` Payload NotificationPayload `json:"payload"` Priority int `json:"priority,omitempty"` + Expires time.Time `json:"expires"` + Image string `json:"image,omitempty"` Metadata json.RawMessage `json:"metadata,omitempty"` } diff --git a/internal/domain/odds.go b/internal/domain/odds.go index 2c94c78..9d6ff2f 100644 --- a/internal/domain/odds.go +++ b/internal/domain/odds.go @@ -9,42 +9,45 @@ import ( ) type CreateOddMarket struct { - EventID int64 - MarketCategory string - MarketType string - MarketName string - MarketID int64 - UpdatedAt time.Time - Odds []map[string]interface{} + EventID int64 + MarketCategory string + MarketType string + MarketName string + MarketID int64 + NumberOfOutcomes int64 + UpdatedAt time.Time + Odds []map[string]interface{} } type OddMarket struct { - ID int64 `json:"id"` - EventID int64 `json:"event_id"` - MarketType string `json:"market_type"` - MarketName string `json:"market_name"` - MarketCategory string `json:"market_category"` - MarketID int64 `json:"market_id"` - RawOdds []json.RawMessage `json:"raw_odds"` - FetchedAt time.Time `json:"fetched_at"` - ExpiresAt time.Time `json:"expires_at"` - DefaultIsActive bool `json:"is_active"` - IsMonitored bool `json:"is_monitored"` - IsLive bool `json:"is_live"` - Status EventStatus `json:"status"` - Source EventSource `json:"source"` + ID int64 `json:"id"` + EventID int64 `json:"event_id"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID int64 `json:"market_id"` + NumberOfOutcomes int64 `json:"number_of_outcomes"` + RawOdds []json.RawMessage `json:"raw_odds"` + FetchedAt time.Time `json:"fetched_at"` + ExpiresAt time.Time `json:"expires_at"` + DefaultIsActive bool `json:"is_active"` + IsMonitored bool `json:"is_monitored"` + IsLive bool `json:"is_live"` + Status EventStatus `json:"status"` + Source EventSource `json:"source"` } type OddMarketWithSettings struct { - ID int64 `json:"id"` - EventID int64 `json:"event_id"` - MarketType string `json:"market_type"` - MarketName string `json:"market_name"` - MarketCategory string `json:"market_category"` - MarketID int64 `json:"market_id"` - RawOdds []json.RawMessage `json:"raw_odds"` - FetchedAt time.Time `json:"fetched_at"` - ExpiresAt time.Time `json:"expires_at"` - IsActive bool `json:"is_active"` + ID int64 `json:"id"` + EventID int64 `json:"event_id"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID int64 `json:"market_id"` + NumberOfOutcomes int64 `json:"number_of_outcomes"` + RawOdds []json.RawMessage `json:"raw_odds"` + FetchedAt time.Time `json:"fetched_at"` + ExpiresAt time.Time `json:"expires_at"` + IsActive bool `json:"is_active"` } type OddMarketSettings struct { @@ -61,6 +64,11 @@ type CreateOddMarketSettings struct { CustomRawOdds []map[string]interface{} } +type UpdateGlobalOddMarketSettings struct { + OddMarketID int64 + IsActive ValidBool +} + type CustomOdd struct { OddID int64 `json:"odd_id"` OddValue float32 `json:"odd_value"` @@ -72,6 +80,11 @@ type CreateOddMarketSettingsReq struct { CustomOdd []CustomOdd `json:"custom_odd,omitempty"` } +type UpdateGlobalOddMarketSettingsReq struct { + OddMarketID int64 `json:"odd_market_id"` + IsActive *bool `json:"is_active,omitempty"` +} + type RawOddsByMarketID struct { ID int64 `json:"id"` MarketName string `json:"market_name"` @@ -86,6 +99,8 @@ type OddMarketFilter struct { Offset ValidInt32 } type OddMarketWithEventFilter struct { + Status ValidString + IsLive ValidBool Limit ValidInt32 Offset ValidInt32 } @@ -100,20 +115,21 @@ func ConvertDBOddMarket(oddMarket dbgen.OddsMarketWithEvent) (OddMarket, error) rawOdds = []json.RawMessage{} // explicit empty slice } return OddMarket{ - ID: oddMarket.ID, - EventID: oddMarket.EventID, - MarketType: oddMarket.MarketType, - MarketName: oddMarket.MarketName, - MarketCategory: oddMarket.MarketCategory, - MarketID: oddMarket.MarketID, - RawOdds: rawOdds, - FetchedAt: oddMarket.FetchedAt.Time, - ExpiresAt: oddMarket.ExpiresAt.Time, - DefaultIsActive: oddMarket.DefaultIsActive, - IsMonitored: oddMarket.IsMonitored, - IsLive: oddMarket.IsLive, - Status: EventStatus(oddMarket.Status), - Source: EventSource(oddMarket.Source), + ID: oddMarket.ID, + EventID: oddMarket.EventID, + MarketType: oddMarket.MarketType, + MarketName: oddMarket.MarketName, + MarketCategory: oddMarket.MarketCategory, + MarketID: oddMarket.MarketID, + NumberOfOutcomes: oddMarket.NumberOfOutcomes, + RawOdds: rawOdds, + FetchedAt: oddMarket.FetchedAt.Time, + ExpiresAt: oddMarket.ExpiresAt.Time, + DefaultIsActive: oddMarket.DefaultIsActive, + IsMonitored: oddMarket.IsMonitored, + IsLive: oddMarket.IsLive, + Status: EventStatus(oddMarket.Status), + Source: EventSource(oddMarket.Source), }, nil } @@ -136,14 +152,15 @@ func ConvertCreateOddMarket(oddMarket CreateOddMarket) (dbgen.InsertOddsMarketPa } return dbgen.InsertOddsMarketParams{ - EventID: oddMarket.EventID, - MarketType: oddMarket.MarketType, - MarketName: oddMarket.MarketName, - MarketCategory: oddMarket.MarketCategory, - MarketID: oddMarket.MarketID, - RawOdds: rawOddsBytes, - FetchedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, - ExpiresAt: pgtype.Timestamp{Time: (time.Now()).Add(time.Hour), Valid: true}, + EventID: oddMarket.EventID, + MarketType: oddMarket.MarketType, + MarketName: oddMarket.MarketName, + MarketCategory: oddMarket.MarketCategory, + MarketID: oddMarket.MarketID, + NumberOfOutcomes: oddMarket.NumberOfOutcomes, + RawOdds: rawOddsBytes, + FetchedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + ExpiresAt: pgtype.Timestamp{Time: (time.Now()).Add(time.Hour), Valid: true}, }, nil } @@ -170,16 +187,17 @@ func ConvertDBOddMarketWithSetting(oms dbgen.OddsMarketWithSetting) (OddMarketWi rawOdds = []json.RawMessage{} // explicit empty slice } return OddMarketWithSettings{ - ID: oms.ID, - EventID: oms.EventID, - MarketType: oms.MarketType, - MarketName: oms.MarketName, - MarketCategory: oms.MarketCategory, - MarketID: oms.MarketID, - RawOdds: rawOdds, - FetchedAt: oms.FetchedAt.Time, - ExpiresAt: oms.ExpiresAt.Time, - IsActive: oms.IsActive, + ID: oms.ID, + EventID: oms.EventID, + MarketType: oms.MarketType, + MarketName: oms.MarketName, + MarketCategory: oms.MarketCategory, + MarketID: oms.MarketID, + NumberOfOutcomes: oms.NumberOfOutcomes, + RawOdds: rawOdds, + FetchedAt: oms.FetchedAt.Time, + ExpiresAt: oms.ExpiresAt.Time, + IsActive: oms.IsActive, }, nil } diff --git a/internal/domain/raffle.go b/internal/domain/raffle.go index a8307b8..ebbd9bf 100644 --- a/internal/domain/raffle.go +++ b/internal/domain/raffle.go @@ -3,13 +3,14 @@ package domain import "time" type Raffle struct { - ID int32 - CompanyID int32 - Name string - CreatedAt time.Time - ExpiresAt time.Time - Type string - Status string + ID int32 + CompanyID int32 + Name string + CreatedAt time.Time + ExpiresAt time.Time + TicketLimit int32 + Type string + Status string } type RaffleFilter struct { @@ -64,10 +65,11 @@ type RaffleTicketRes struct { } type CreateRaffle struct { - CompanyID int32 `json:"company_id" validate:"required"` - Name string `json:"name" validate:"required"` - ExpiresAt *time.Time `json:"expires_at" validate:"required"` - Type string `json:"type" validate:"required"` + CompanyID int32 `json:"company_id" validate:"required"` + Name string `json:"name" validate:"required"` + ExpiresAt *time.Time `json:"expires_at" validate:"required"` + TicketLimit int32 `json:"ticket_limit" validate:"required"` + Type string `json:"type" validate:"required"` } type CreateRaffleTicket struct { diff --git a/internal/domain/result.go b/internal/domain/result.go index 698e01e..70993fc 100644 --- a/internal/domain/result.go +++ b/internal/domain/result.go @@ -1,6 +1,7 @@ package domain import ( + "fmt" "time" "github.com/jackc/pgx/v5/pgtype" @@ -48,6 +49,28 @@ const ( OUTCOME_STATUS_ERROR OutcomeStatus = 5 //Half Win and Half Given Back ) +func (o OutcomeStatus) IsValid() bool { + switch o { + case OUTCOME_STATUS_PENDING, + OUTCOME_STATUS_WIN, + OUTCOME_STATUS_LOSS, + OUTCOME_STATUS_VOID, + OUTCOME_STATUS_HALF, + OUTCOME_STATUS_ERROR: + return true + default: + return false + } +} + +func ParseOutcomeStatus(val int) (OutcomeStatus, error) { + o := OutcomeStatus(val) + if !o.IsValid() { + return 0, fmt.Errorf("invalid OutcomeStatus: %d", val) + } + return o, nil +} + func (o *OutcomeStatus) String() string { switch *o { case OUTCOME_STATUS_PENDING: @@ -72,7 +95,6 @@ type ValidOutcomeStatus struct { Valid bool } - func (v ValidOutcomeStatus) ToPG() pgtype.Int4 { return pgtype.Int4{ Int32: int32(v.Value), @@ -80,7 +102,6 @@ func (v ValidOutcomeStatus) ToPG() pgtype.Int4 { } } - type TimeStatus int32 const ( diff --git a/internal/domain/shop_bet.go b/internal/domain/shop_bet.go index 72f280b..c321a02 100644 --- a/internal/domain/shop_bet.go +++ b/internal/domain/shop_bet.go @@ -34,6 +34,7 @@ type ShopBetDetail struct { CompanyID int64 FullName string PhoneNumber string + FastCode string CashoutID string CashedOut bool BetID int64 @@ -80,12 +81,13 @@ type ShopBetRes struct { CompanyID int64 `json:"company_id" example:"2"` FullName string `json:"full_name" example:"John"` PhoneNumber string `json:"phone_number" example:"1234567890"` + FastCode string `json:"fast_code" example:"12SD1"` CashoutID string `json:"cashout_id" example:"21234"` CashedOut bool `json:"cashed_out" example:"false"` BetID int64 `json:"bet_id" example:"1"` NumberOfOutcomes int64 `json:"number_of_outcomes" example:"1"` Status OutcomeStatus `json:"status" example:"1"` - Amount Currency `json:"amount"` + Amount float32 `json:"amount"` Outcomes []BetOutcome `json:"outcomes"` TransactionVerified bool `json:"transaction_verified" example:"true"` UpdatedAt time.Time `json:"updated_at" example:"2025-04-08T12:00:00Z"` @@ -111,12 +113,13 @@ func ConvertShopBetDetail(shopBet ShopBetDetail) ShopBetRes { CompanyID: shopBet.CompanyID, FullName: shopBet.FullName, PhoneNumber: shopBet.PhoneNumber, + FastCode: shopBet.FastCode, CashoutID: shopBet.CashoutID, CashedOut: shopBet.CashedOut, BetID: shopBet.BetID, NumberOfOutcomes: shopBet.NumberOfOutcomes, Status: shopBet.Status, - Amount: shopBet.Amount, + Amount: shopBet.Amount.Float32(), Outcomes: shopBet.Outcomes, TransactionVerified: shopBet.TransactionVerified, UpdatedAt: shopBet.UpdatedAt, diff --git a/internal/domain/wallet.go b/internal/domain/wallet.go index 947f3c8..17d69d9 100644 --- a/internal/domain/wallet.go +++ b/internal/domain/wallet.go @@ -1,6 +1,13 @@ package domain -import "time" +import ( + "errors" + "time" +) + +var ( + ErrWalletIDDuplicate = errors.New("there already exists user id with wallet_type") +) type Wallet struct { ID int64 diff --git a/internal/repository/bet.go b/internal/repository/bet.go index bb2a7d7..d8d3394 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -103,8 +103,11 @@ func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]doma Query: filter.Query.ToPG(), CreatedBefore: filter.CreatedBefore.ToPG(), CreatedAfter: filter.CreatedAfter.ToPG(), - Offset: filter.Offset.ToPG(), - Limit: filter.Limit.ToPG(), + Offset: pgtype.Int4{ + Int32: int32(filter.Offset.Value * filter.Limit.Value), + Valid: filter.Offset.Valid, + }, + Limit: filter.Limit.ToPG(), }) if err != nil { domain.MongoDBLogger.Error("failed to get all bets", @@ -123,7 +126,7 @@ func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]doma Query: filter.Query.ToPG(), CreatedBefore: filter.CreatedBefore.ToPG(), CreatedAfter: filter.CreatedAfter.ToPG(), - }); + }) var result []domain.GetBet = make([]domain.GetBet, 0, len(bets)) for _, bet := range bets { @@ -275,6 +278,36 @@ func (s *Store) SettleWinningBet(ctx context.Context, betID int64, userID int64, return nil } +func (s *Store) GetBetOutcomeViewByEventID(ctx context.Context, eventID int64, filter domain.BetOutcomeViewFilter) ([]domain.BetOutcomeViewRes, int64, error) { + + outcomes, err := s.queries.GetBetOutcomeViewByEventID(ctx, dbgen.GetBetOutcomeViewByEventIDParams{ + EventID: eventID, + FilterStatus: filter.OutcomeStatus.ToPG(), + CompanyID: filter.CompanyID.ToPG(), + Offset: filter.Offset.ToPG(), + Limit: filter.Limit.ToPG(), + }) + + if err != nil { + domain.MongoDBLogger.Error("failed to get bet outcomes by event ID", + zap.Int64("event_id", eventID), + zap.Error(err), + ) + return nil, 0, err + } + + total, err := s.queries.TotalBetOutcomeViewByEventID(ctx, dbgen.TotalBetOutcomeViewByEventIDParams{ + EventID: eventID, + FilterStatus: filter.OutcomeStatus.ToPG(), + CompanyID: filter.CompanyID.ToPG(), + }) + + var result []domain.BetOutcomeViewRes = make([]domain.BetOutcomeViewRes, 0, len(outcomes)) + for _, outcome := range outcomes { + result = append(result, domain.ConvertDBBetOutcomesView(outcome)) + } + return result, total, nil +} func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) { outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, dbgen.GetBetOutcomeByEventIDParams{ @@ -377,6 +410,45 @@ func (s *Store) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int6 } return result, nil } +func (s *Store) UpdateBetOutcomeStatusForOddId(ctx context.Context, oddID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error) { + outcomes, err := s.queries.UpdateBetOutcomeStatusForOddID(ctx, dbgen.UpdateBetOutcomeStatusForOddIDParams{ + OddID: oddID, + Status: int32(status), + }) + + if err != nil { + domain.MongoDBLogger.Error("failed to update bet outcome status for oddID", + zap.Int64("oddId", oddID), + zap.Int32("status", int32(status)), + zap.Error(err), + ) + return nil, err + } + + var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes)) + for _, outcome := range outcomes { + result = append(result, domain.ConvertDBBetOutcomes(outcome)) + } + return result, nil +} + +func (s *Store) BulkUpdateBetOutcomeStatusForOddIds(ctx context.Context, oddID []int64, status domain.OutcomeStatus) (error) { + err := s.queries.BulkUpdateBetOutcomeStatusByOddIDs(ctx, dbgen.BulkUpdateBetOutcomeStatusByOddIDsParams{ + Status: int32(status), + OddIds: oddID, + }) + + if err != nil { + domain.MongoDBLogger.Error("failed to update bet outcome status for oddIDs", + zap.Int64s("oddIds", oddID), + zap.Int32("status", int32(status)), + zap.Error(err), + ) + return err + } + + return nil +} func (s *Store) UpdateBetWithCashback(ctx context.Context, betID int64, cashbackStatus bool) error { err := s.queries.UpdateBetWithCashback(ctx, dbgen.UpdateBetWithCashbackParams{ diff --git a/internal/repository/branch.go b/internal/repository/branch.go index 89b329e..4c5041d 100644 --- a/internal/repository/branch.go +++ b/internal/repository/branch.go @@ -68,8 +68,11 @@ func (s *Store) GetAllBranches(ctx context.Context, filter domain.BranchFilter) return branches, nil } -func (s *Store) SearchBranchByName(ctx context.Context, name string) ([]domain.BranchDetail, error) { - dbBranches, err := s.queries.SearchBranchByName(ctx, pgtype.Text{String: name, Valid: true}) +func (s *Store) SearchBranchByName(ctx context.Context, name string, companyID domain.ValidInt64) ([]domain.BranchDetail, error) { + dbBranches, err := s.queries.SearchBranchByName(ctx, dbgen.SearchBranchByNameParams{ + Column1: pgtype.Text{String: name, Valid: true}, + CompanyID: companyID.ToPG(), + }) if err != nil { return nil, err } diff --git a/internal/repository/common.go b/internal/repository/common.go new file mode 100644 index 0000000..5cf051e --- /dev/null +++ b/internal/repository/common.go @@ -0,0 +1,12 @@ +package repository + +import ( + "errors" + + "github.com/jackc/pgx/v5/pgconn" +) + +func IsUniqueViolation(err error) bool { + var pgErr *pgconn.PgError + return errors.As(err, &pgErr) && pgErr.Code == "23505" +} \ No newline at end of file diff --git a/internal/repository/company.go b/internal/repository/company.go index 08f5251..a72310b 100644 --- a/internal/repository/company.go +++ b/internal/repository/company.go @@ -2,36 +2,34 @@ package repository import ( "context" - "errors" "fmt" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" - "github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers" - "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" ) func (s *Store) CreateCompany(ctx context.Context, company domain.CreateCompany) (domain.Company, error) { - baseSlug := helpers.GenerateSlug(company.Name) - uniqueSlug := baseSlug - i := 1 + // baseSlug := helpers.GenerateSlug(company.Name) + // uniqueSlug := baseSlug + // i := 1 - for { - _, err := s.queries.GetCompanyIDUsingSlug(ctx, uniqueSlug) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - // slug is unique - break - } else { - // real DB error - return domain.Company{}, err - } - } - uniqueSlug = fmt.Sprintf("%s-%d", baseSlug, i) - i++ - } + // for { + // _, err := s.queries.GetCompanyUsingSlug(ctx, uniqueSlug) + // if err != nil { + // if errors.Is(err, pgx.ErrNoRows) { + // // slug is unique + // break + // } else { + // // real DB error + // return domain.Company{}, err + // } + // } + // uniqueSlug = fmt.Sprintf("%s-%d", baseSlug, i) + // i++ + // } + fmt.Printf("\ncompany %v\n\n", company) + dbCompany, err := s.queries.CreateCompany(ctx, domain.ConvertCreateCompany(company)) - dbCompany, err := s.queries.CreateCompany(ctx, domain.ConvertCreateCompany(company, uniqueSlug)) if err != nil { return domain.Company{}, err } @@ -78,25 +76,26 @@ func (s *Store) GetCompanyByID(ctx context.Context, id int64) (domain.GetCompany return domain.ConvertDBCompanyDetails(dbCompany), nil } -func (s *Store) GetCompanyIDBySlug(ctx context.Context, slug string) (int64, error) { - dbCompanyID, err := s.queries.GetCompanyIDUsingSlug(ctx, slug) - - if err != nil { - return 0, err - } - return dbCompanyID, nil -} - -func (s *Store) UpdateCompany(ctx context.Context, company domain.UpdateCompany) (domain.Company, error) { - dbCompany, err := s.queries.UpdateCompany(ctx, domain.ConvertUpdateCompany(company)) +func (s *Store) GetCompanyBySlug(ctx context.Context, slug string) (domain.Company, error) { + dbCompany, err := s.queries.GetCompanyUsingSlug(ctx, slug) if err != nil { return domain.Company{}, err } - return domain.ConvertDBCompany(dbCompany), nil } +func (s *Store) UpdateCompany(ctx context.Context, company domain.UpdateCompany) error { + fmt.Printf("company %v\n", company) + err := s.queries.UpdateCompany(ctx, domain.ConvertUpdateCompany(company)) + + if err != nil { + return err + } + + return nil +} + func (s *Store) DeleteCompany(ctx context.Context, id int64) error { return s.queries.DeleteCompany(ctx, id) } diff --git a/internal/repository/enet_pulse.go b/internal/repository/enet_pulse.go index 208bf5d..b66e225 100644 --- a/internal/repository/enet_pulse.go +++ b/internal/repository/enet_pulse.go @@ -302,6 +302,71 @@ func (s *Store) GetAllEnetpulsePreoddsBettingOffers(ctx context.Context) ([]doma return offers, nil } +func (s *Store) GetFixturesWithPreodds(ctx context.Context) ([]domain.EnetpulseFixtureWithPreodds, error) { + dbRows, err := s.queries.GetFixturesWithPreodds(ctx) + if err != nil { + return nil, err + } + + // Use a map to group preodds by fixture + fixtureMap := make(map[string]*domain.EnetpulseFixtureWithPreodds) + + for _, row := range dbRows { + // If fixture not yet in map, add it + if _, exists := fixtureMap[row.FixtureID]; !exists { + fixtureMap[row.FixtureID] = &domain.EnetpulseFixtureWithPreodds{ + FixtureID: row.FixtureID, + FixtureApiID: row.FixtureID, // same alias used in query + FixtureName: row.FixtureName, + SportFk: row.SportFk, + TournamentFk: row.TournamentFk.String, + TournamentTemplateFk: row.TournamentTemplateFk.String, + TournamentStageFk: row.TournamentStageFk.String, + StartDate: row.StartDate.Time, + StatusType: row.StatusType.String, + StatusDescFk: row.StatusDescFk.String, + RoundTypeFk: row.RoundTypeFk.String, + UpdatesCount: row.FixtureUpdatesCount.Int32, + LastUpdatedAt: row.FixtureLastUpdatedAt.Time, + CreatedAt: row.FixtureCreatedAt.Time, + UpdatedAt: row.FixtureUpdatedAt.Time, + Preodds: []domain.EnetpulsePreodds{}, // initialize slice + } + } + + // Add preodds only if it exists (avoid NULL rows) + if row.PreoddsDbID.Valid { + preodds := domain.EnetpulsePreodds{ + ID: row.PreoddsDbID.Int64, + PreoddsID: row.PreoddsID.String, + EventFK: row.EventFk.Int64, + OutcomeTypeFK: row.OutcomeTypeFk.Int32, + OutcomeScopeFK: row.OutcomeScopeFk.Int32, + OutcomeSubtypeFK: row.OutcomeSubtypeFk.Int32, + EventParticipantNumber: row.EventParticipantNumber.Int32, + IParam: row.Iparam.String, + IParam2: row.Iparam2.String, + DParam: row.Dparam.String, + DParam2: row.Dparam2.String, + SParam: row.Sparam.String, + UpdatesCount: row.PreoddsUpdatesCount.Int32, + LastUpdatedAt: row.PreoddsLastUpdatedAt.Time, + CreatedAt: row.PreoddsCreatedAt.Time, + UpdatedAt: row.PreoddsUpdatedAt.Time, + } + fixtureMap[row.FixtureID].Preodds = append(fixtureMap[row.FixtureID].Preodds, preodds) + } + } + + // Flatten the map into a slice + result := make([]domain.EnetpulseFixtureWithPreodds, 0, len(fixtureMap)) + for _, f := range fixtureMap { + result = append(result, *f) + } + + return result, nil +} + // func ConvertCreateEnetpulseTournamentStage(stage domain.CreateEnetpulseTournamentStage) dbgen.EnetpulseTournamentStage { // return dbgen.EnetpulseTournamentStage{ // StageID: stage.StageID, @@ -356,14 +421,14 @@ func ConvertDBEnetpulseFixture(dbF dbgen.EnetpulseFixture) domain.EnetpulseFixtu TournamentTemplateName: dbF.TournamentTemplateName.String, SportName: dbF.SportName.String, Gender: dbF.Gender.String, - StartDate: dbF.StartDate.Time, + StartDate: dbF.StartDate.Time.String(), StatusType: dbF.StatusType.String, StatusDescFK: dbF.StatusDescFk.String, RoundTypeFK: dbF.RoundTypeFk.String, - UpdatesCount: int(dbF.UpdatesCount.Int32), - LastUpdatedAt: dbF.LastUpdatedAt.Time, - CreatedAt: dbF.CreatedAt.Time, - UpdatedAt: dbF.UpdatedAt.Time, + UpdatesCount: fmt.Sprintf("%v", dbF.UpdatesCount), + LastUpdatedAt: dbF.LastUpdatedAt.Time.String(), + // CreatedAt: dbF.CreatedAt.Time, + // UpdatedAt: dbF.UpdatedAt.Time, } } @@ -672,17 +737,17 @@ func ConvertCreateEnetpulsePreodds(p domain.CreateEnetpulsePreodds) (dbgen.Creat func ConvertDBEnetpulsePreodds(dbP dbgen.EnetpulsePreodd) domain.EnetpulsePreodds { return domain.EnetpulsePreodds{ PreoddsID: dbP.PreoddsID, - EventFK: fmt.Sprintf("%v", dbP.EventFk), - OutcomeTypeFK: fmt.Sprintf("%v", dbP.OutcomeTypeFk), - OutcomeScopeFK: fmt.Sprintf("%v", dbP.OutcomeScopeFk), - OutcomeSubtypeFK: fmt.Sprintf("%v", dbP.OutcomeSubtypeFk), - EventParticipantNumber: int(dbP.EventParticipantNumber.Int32), + EventFK: dbP.EventFk, + OutcomeTypeFK: dbP.OutcomeTypeFk.Int32, + OutcomeScopeFK: dbP.OutcomeScopeFk.Int32, + OutcomeSubtypeFK: dbP.OutcomeSubtypeFk.Int32, + EventParticipantNumber: dbP.EventParticipantNumber.Int32, IParam: dbP.Iparam.String, IParam2: dbP.Iparam2.String, DParam: dbP.Dparam.String, DParam2: dbP.Dparam2.String, SParam: dbP.Sparam.String, - UpdatesCount: int(dbP.UpdatesCount.Int32), + UpdatesCount: dbP.UpdatesCount.Int32, LastUpdatedAt: dbP.LastUpdatedAt.Time, CreatedAt: dbP.CreatedAt.Time, UpdatedAt: dbP.UpdatedAt.Time, diff --git a/internal/repository/event.go b/internal/repository/event.go index b1e5c56..fcc20c5 100644 --- a/internal/repository/event.go +++ b/internal/repository/event.go @@ -3,7 +3,6 @@ package repository import ( "context" "fmt" - "math" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" @@ -23,11 +22,15 @@ func (s *Store) GetLiveEventIDs(ctx context.Context) ([]int64, error) { func (s *Store) GetAllEvents(ctx context.Context, filter domain.EventFilter) ([]domain.BaseEvent, int64, error) { events, err := s.queries.GetAllEvents(ctx, dbgen.GetAllEventsParams{ - LeagueID: filter.LeagueID.ToPG(), - SportID: filter.SportID.ToPG(), - Query: filter.Query.ToPG(), - Limit: filter.Limit.ToPG(), - Offset: filter.Offset.ToPG(), + 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(), @@ -55,18 +58,20 @@ func (s *Store) GetAllEvents(ctx context.Context, filter domain.EventFilter) ([] return nil, 0, err } - numberOfPages := math.Ceil(float64(totalCount) / float64(filter.Limit.Value)) - return domain.ConvertDBEvents(events), int64(numberOfPages), nil + return domain.ConvertDBEvents(events), totalCount, nil } func (s *Store) GetEventsWithSettings(ctx context.Context, companyID int64, filter domain.EventFilter) ([]domain.EventWithSettings, int64, error) { events, err := s.queries.GetEventsWithSettings(ctx, dbgen.GetEventsWithSettingsParams{ - CompanyID: companyID, - LeagueID: filter.LeagueID.ToPG(), - SportID: filter.SportID.ToPG(), - Query: filter.Query.ToPG(), - Limit: filter.Limit.ToPG(), - Offset: filter.Offset.ToPG(), + 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(), @@ -99,8 +104,6 @@ func (s *Store) GetEventsWithSettings(ctx context.Context, companyID int64, filt return nil, 0, err } - numberOfPages := math.Ceil(float64(totalCount) / float64(filter.Limit.Value)) - result := make([]domain.EventWithSettings, len(events)) for i, event := range events { @@ -123,6 +126,9 @@ func (s *Store) GetEventsWithSettings(ctx context.Context, companyID int64, filt 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, @@ -155,7 +161,7 @@ func (s *Store) GetEventsWithSettings(ctx context.Context, companyID int64, filt } } - return result, int64(numberOfPages), nil + return result, totalCount, nil } func (s *Store) GetEventByID(ctx context.Context, ID int64) (domain.BaseEvent, error) { event, err := s.queries.GetEventByID(ctx, ID) @@ -204,6 +210,9 @@ func (s *Store) GetEventWithSettingByID(ctx context.Context, ID int64, companyID 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, @@ -281,10 +290,13 @@ func (s *Store) UpdateEventMonitored(ctx context.Context, eventID int64, IsMonit }) } -func (s *Store) UpdateEventSettings(ctx context.Context, event domain.CreateEventSettings) error { - return s.queries.SaveEventSettings(ctx, domain.ConvertUpdateEventSettings(event)) +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/league.go b/internal/repository/league.go index ae0a4d5..e2e614e 100644 --- a/internal/repository/league.go +++ b/internal/repository/league.go @@ -13,10 +13,10 @@ func (s *Store) SaveLeague(ctx context.Context, league domain.CreateLeague) erro } func (s *Store) SaveLeagueSettings(ctx context.Context, leagueSettings domain.CreateLeagueSettings) error { - return s.queries.InsertLeagueSettings(ctx, domain.ConvertCreateLeagueSettings(leagueSettings)) + return s.queries.SaveLeagueSettings(ctx, domain.ConvertCreateLeagueSettings(leagueSettings)) } -func (s *Store) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.BaseLeague, error) { +func (s *Store) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.BaseLeague,int64, error) { l, err := s.queries.GetAllLeagues(ctx, dbgen.GetAllLeaguesParams{ Query: filter.Query.ToPG(), CountryCode: filter.CountryCode.ToPG(), @@ -31,10 +31,17 @@ func (s *Store) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ( }, }) if err != nil { - return nil, err + return nil, 0, err } - return domain.ConvertDBBaseLeagues(l), nil + total, err := s.queries.GetTotalLeagues(ctx, dbgen.GetTotalLeaguesParams{ + Query: filter.Query.ToPG(), + CountryCode: filter.CountryCode.ToPG(), + SportID: filter.SportID.ToPG(), + IsActive: filter.IsActive.ToPG(), + }) + + return domain.ConvertDBBaseLeagues(l), total, nil } func (s *Store) GetAllLeaguesByCompany(ctx context.Context, companyID int64, filter domain.LeagueFilter) ([]domain.LeagueWithSettings, int64, error) { @@ -85,3 +92,7 @@ func (s *Store) CheckLeagueSupport(ctx context.Context, leagueID int64, companyI func (s *Store) UpdateLeague(ctx context.Context, league domain.UpdateLeague) error { return s.queries.UpdateLeague(ctx, domain.ConvertUpdateLeague(league)) } + +func (s *Store) UpdateGlobalLeagueSettings(ctx context.Context, league domain.UpdateGlobalLeagueSettings) error { + return s.queries.UpdateGlobalLeagueSettings(ctx, domain.ConvertUpdateGlobalLeagueSetting(league)) +} diff --git a/internal/repository/notification.go b/internal/repository/notification.go index eea21e5..0c91e82 100644 --- a/internal/repository/notification.go +++ b/internal/repository/notification.go @@ -14,12 +14,13 @@ import ( type NotificationRepository interface { CreateNotification(ctx context.Context, notification *domain.Notification) (*domain.Notification, error) UpdateNotificationStatus(ctx context.Context, id, status string, isRead bool, metadata []byte) (*domain.Notification, error) - GetUserNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, int64, error) + GetUserNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, int64, error) ListFailedNotifications(ctx context.Context, limit int) ([]domain.Notification, error) ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) GetNotificationCounts(ctx context.Context, filter domain.ReportFilter) (total, read, unread int64, err error) + DeleteOldNotifications(ctx context.Context) error } type Repository struct { @@ -69,6 +70,8 @@ func (r *Repository) CreateNotification(ctx context.Context, notification *domai Payload: marshalPayload(notification.Payload), Priority: priority, Timestamp: pgtype.Timestamptz{Time: notification.Timestamp, Valid: true}, + Expires: pgtype.Timestamptz{Time: notification.Expires, Valid: true}, + Img: pgtype.Text{String: notification.Image, Valid: notification.Image != ""}, Metadata: notification.Metadata, } @@ -113,7 +116,7 @@ func (r *Repository) GetUserNotifications(ctx context.Context, recipientID int64 if err != nil { return nil, 0, err } - + var result []domain.Notification = make([]domain.Notification, 0, len(dbNotifications)) for _, dbNotif := range dbNotifications { domainNotif := r.mapDBToDomain(&dbNotif) @@ -160,6 +163,10 @@ func (r *Repository) ListRecipientIDs(ctx context.Context, receiver domain.Notif return r.store.queries.ListRecipientIDsByReceiver(ctx, string(receiver)) } +func (s *Repository) DeleteOldNotifications(ctx context.Context) error { + return s.store.queries.DeleteOldNotifications(ctx) +} + func (r *Repository) mapDBToDomain(dbNotif *dbgen.Notification) *domain.Notification { var errorSeverity domain.NotificationErrorSeverity if dbNotif.ErrorSeverity.Valid { @@ -199,6 +206,8 @@ func (r *Repository) mapDBToDomain(dbNotif *dbgen.Notification) *domain.Notifica Payload: payload, Priority: priority, Timestamp: dbNotif.Timestamp.Time, + Expires: dbNotif.Expires.Time, + Image: dbNotif.Img.String, Metadata: dbNotif.Metadata, } } diff --git a/internal/repository/odds.go b/internal/repository/odds.go index 51e1c6a..8dd7c7f 100644 --- a/internal/repository/odds.go +++ b/internal/repository/odds.go @@ -180,16 +180,17 @@ func (s *Store) GetOddsWithSettingsByMarketID(ctx context.Context, marketID int6 } converted := domain.OddMarketWithSettings{ - ID: odds.ID, - EventID: odds.EventID, - MarketType: odds.MarketType, - MarketName: odds.MarketName, - MarketCategory: odds.MarketCategory, - MarketID: odds.MarketID, - RawOdds: rawOdds, - FetchedAt: odds.FetchedAt.Time, - ExpiresAt: odds.ExpiresAt.Time, - IsActive: odds.IsActive, + ID: odds.ID, + EventID: odds.EventID, + MarketType: odds.MarketType, + MarketName: odds.MarketName, + MarketCategory: odds.MarketCategory, + MarketID: odds.MarketID, + NumberOfOutcomes: odds.NumberOfOutcomes, + RawOdds: rawOdds, + FetchedAt: odds.FetchedAt.Time, + ExpiresAt: odds.ExpiresAt.Time, + IsActive: odds.IsActive, } return converted, nil } @@ -221,16 +222,17 @@ func (s *Store) GetOddsWithSettingsByID(ctx context.Context, ID int64, companyID } converted := domain.OddMarketWithSettings{ - ID: odds.ID, - EventID: odds.EventID, - MarketType: odds.MarketType, - MarketName: odds.MarketName, - MarketCategory: odds.MarketCategory, - MarketID: odds.MarketID, - RawOdds: rawOdds, - FetchedAt: odds.FetchedAt.Time, - ExpiresAt: odds.ExpiresAt.Time, - IsActive: odds.IsActive, + ID: odds.ID, + EventID: odds.EventID, + MarketType: odds.MarketType, + MarketName: odds.MarketName, + MarketCategory: odds.MarketCategory, + MarketID: odds.MarketID, + NumberOfOutcomes: odds.NumberOfOutcomes, + RawOdds: rawOdds, + FetchedAt: odds.FetchedAt.Time, + ExpiresAt: odds.ExpiresAt.Time, + IsActive: odds.IsActive, } return converted, nil @@ -239,17 +241,11 @@ func (s *Store) GetOddsWithSettingsByID(ctx context.Context, ID int64, companyID func (s *Store) GetOddsByEventID(ctx context.Context, eventID int64, filter domain.OddMarketWithEventFilter) ([]domain.OddMarket, error) { odds, err := s.queries.GetOddsByEventID(ctx, dbgen.GetOddsByEventIDParams{ EventID: eventID, + Status: filter.Status.ToPG(), + IsLive: filter.IsLive.ToPG(), Limit: filter.Limit.ToPG(), Offset: filter.Offset.ToPG(), - IsLive: pgtype.Bool{ - Bool: false, - Valid: true, - }, - Status: pgtype.Text{ - String: string(domain.STATUS_PENDING), - Valid: true, - }, - Source: pgtype.Text{}, + Source: pgtype.Text{}, }) if err != nil { return nil, err @@ -293,16 +289,17 @@ func (s *Store) GetOddsWithSettingsByEventID(ctx context.Context, eventID int64, } result[i] = domain.OddMarketWithSettings{ - ID: o.ID, - EventID: o.EventID, - MarketType: o.MarketType, - MarketName: o.MarketName, - MarketCategory: o.MarketCategory, - MarketID: o.MarketID, - RawOdds: rawOdds, - FetchedAt: o.FetchedAt.Time, - ExpiresAt: o.ExpiresAt.Time, - IsActive: o.IsActive, + ID: o.ID, + EventID: o.EventID, + MarketType: o.MarketType, + MarketName: o.MarketName, + MarketCategory: o.MarketCategory, + MarketID: o.MarketID, + NumberOfOutcomes: o.NumberOfOutcomes, + RawOdds: rawOdds, + FetchedAt: o.FetchedAt.Time, + ExpiresAt: o.ExpiresAt.Time, + IsActive: o.IsActive, } } @@ -322,3 +319,21 @@ func (s *Store) SaveOddsSetting(ctx context.Context, odd domain.CreateOddMarketS } return s.queries.SaveOddSettings(ctx, res) } + +func (s *Store) UpdateGlobalOddsSetting(ctx context.Context, odd domain.UpdateGlobalOddMarketSettings) error { + return s.queries.UpdateGlobalOddsSetting(ctx, dbgen.UpdateGlobalOddsSettingParams{ + ID: odd.OddMarketID, + DefaultIsActive: odd.IsActive.ToPG(), + }) +} + +func (s *Store) DeleteAllCompanyOddsSetting(ctx context.Context, companyID int64) error { + return s.queries.DeleteAllCompanyOddsSetting(ctx, companyID) +} + +func (s *Store) DeleteCompanyOddsSettingByOddMarketID(ctx context.Context, companyID int64, oddMarketID int64) error { + return s.queries.DeleteCompanyOddsSettingByOddMarketID(ctx, dbgen.DeleteCompanyOddsSettingByOddMarketIDParams{ + CompanyID: companyID, + OddsMarketID: oddMarketID, + }) +} diff --git a/internal/repository/raffel.go b/internal/repository/raffel.go index 6e37013..298ed9e 100644 --- a/internal/repository/raffel.go +++ b/internal/repository/raffel.go @@ -10,13 +10,14 @@ import ( func convertRaffleOutcome(raffle dbgen.Raffle) domain.Raffle { return domain.Raffle{ - ID: raffle.ID, - CompanyID: raffle.CompanyID, - Name: raffle.Name, - CreatedAt: raffle.CreatedAt.Time, - ExpiresAt: raffle.ExpiresAt.Time, - Type: raffle.Type, - Status: raffle.Status, + ID: raffle.ID, + CompanyID: raffle.CompanyID, + Name: raffle.Name, + CreatedAt: raffle.CreatedAt.Time, + ExpiresAt: raffle.ExpiresAt.Time, + TicketLimit: raffle.TicketLimit, + Type: raffle.Type, + Status: raffle.Status, } } @@ -48,7 +49,8 @@ func convertCreateRaffle(raffle domain.CreateRaffle) dbgen.CreateRaffleParams { Time: *raffle.ExpiresAt, Valid: true, }, - Type: raffle.Type, + TicketLimit: raffle.TicketLimit, + Type: raffle.Type, } } @@ -191,3 +193,18 @@ func (s *Store) CheckValidSportRaffleFilter(ctx context.Context, raffleID int32, return res, nil } + +func (s *Store) GetRaffleTicketLimit(ctx context.Context, raffleID int32) (int32, error) { + return s.queries.GetRaffleTicketLimit(ctx, raffleID) +} + +func (s *Store) GetRaffleTicketCount(ctx context.Context, raffleID, userID int32) (int64, error) { + return s.queries.GetRaffleTicketCount(ctx, dbgen.GetRaffleTicketCountParams{ + RaffleID: raffleID, + UserID: userID, + }) +} + +func (s *Store) CheckSportRaffleHasFilter(ctx context.Context, raffleID int32) (bool, error) { + return s.queries.CheckSportRaffleHasFilter(ctx, raffleID) +} diff --git a/internal/repository/shop_bet.go b/internal/repository/shop_bet.go index 66e3e63..3a5bd5a 100644 --- a/internal/repository/shop_bet.go +++ b/internal/repository/shop_bet.go @@ -35,6 +35,7 @@ func convertDBShopBetDetail(bet dbgen.ShopBetDetail) domain.ShopBetDetail { CompanyID: bet.CompanyID, FullName: bet.CustomerFullName, PhoneNumber: bet.CustomerPhoneNumber, + FastCode: bet.FastCode, CashoutID: bet.CashoutID, CashedOut: bet.CashedOut, BetID: bet.BetID, @@ -63,7 +64,7 @@ func (s *Store) CreateShopBet(ctx context.Context, bet domain.CreateShopBet) (do if err != nil { return domain.ShopBet{}, err } - + return convertDBShopBet(newShopBet), err } @@ -108,7 +109,7 @@ func (s *Store) GetShopBetByID(ctx context.Context, id int64) (domain.ShopBetDet fmt.Printf("GetShopBetByID Repo BetID %d err %v \n", id, err.Error()) return domain.ShopBetDetail{}, err } - + return convertDBShopBetDetail(bet), nil } diff --git a/internal/repository/user.go b/internal/repository/user.go index 8102bb6..40f620f 100644 --- a/internal/repository/user.go +++ b/internal/repository/user.go @@ -111,7 +111,7 @@ func (s *Store) GetAllUsers(ctx context.Context, filter domain.UserFilter) ([]do Valid: filter.PageSize.Valid, }, Offset: pgtype.Int4{ - Int32: int32(filter.Page.Value), + Int32: int32(filter.Page.Value * filter.PageSize.Value), Valid: filter.Page.Valid, }, Query: pgtype.Text{ diff --git a/internal/repository/virtual_game.go b/internal/repository/virtual_game.go index ab93c29..c792801 100644 --- a/internal/repository/virtual_game.go +++ b/internal/repository/virtual_game.go @@ -4,7 +4,6 @@ import ( "context" "database/sql" "errors" - "fmt" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" @@ -30,7 +29,7 @@ type VirtualGameRepository interface { RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error ListFavoriteGames(ctx context.Context, userID int64) ([]int64, error) - GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) + // GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) GetUserGameHistory(ctx context.Context, userID int64) ([]domain.VirtualGameHistory, error) CreateVirtualGameHistory(ctx context.Context, his *domain.VirtualGameHistory) error @@ -255,36 +254,36 @@ func (r *VirtualGameRepo) UpdateVirtualGameTransactionStatus(ctx context.Context }) } -func (r *VirtualGameRepo) GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) { - query := `SELECT - COUNT(*) as total, - COUNT(CASE WHEN is_active = true THEN 1 END) as active, - COUNT(CASE WHEN is_active = false THEN 1 END) as inactive - FROM virtual_games` +// func (r *VirtualGameRepo) GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) { +// query := `SELECT +// COUNT(*) as total, +// COUNT(CASE WHEN is_active = true THEN 1 END) as active, +// COUNT(CASE WHEN is_active = false THEN 1 END) as inactive +// FROM virtual_games` - args := []interface{}{} - argPos := 1 +// args := []interface{}{} +// argPos := 1 - // Add filters if provided - if filter.StartTime.Valid { - query += fmt.Sprintf(" WHERE created_at >= $%d", argPos) - args = append(args, filter.StartTime.Value) - argPos++ - } - if filter.EndTime.Valid { - query += fmt.Sprintf(" AND created_at <= $%d", argPos) - args = append(args, filter.EndTime.Value) - argPos++ - } +// // Add filters if provided +// if filter.StartTime.Valid { +// query += fmt.Sprintf(" WHERE created_at >= $%d", argPos) +// args = append(args, filter.StartTime.Value) +// argPos++ +// } +// if filter.EndTime.Valid { +// query += fmt.Sprintf(" AND created_at <= $%d", argPos) +// args = append(args, filter.EndTime.Value) +// argPos++ +// } - row := r.store.conn.QueryRow(ctx, query, args...) - err = row.Scan(&total, &active, &inactive) - if err != nil { - return 0, 0, 0, fmt.Errorf("failed to get game counts: %w", err) - } +// row := r.store.conn.QueryRow(ctx, query, args...) +// err = row.Scan(&total, &active, &inactive) +// if err != nil { +// return 0, 0, 0, fmt.Errorf("failed to get game counts: %w", err) +// } - return total, active, inactive, nil -} +// return total, active, inactive, nil +// } func (r *VirtualGameRepo) GetUserGameHistory(ctx context.Context, userID int64) ([]domain.VirtualGameHistory, error) { query := `SELECT game_id FROM virtual_game_histories WHERE user_id = $1 AND transaction_type = 'BET' ORDER BY created_at DESC LIMIT 100` @@ -315,4 +314,3 @@ func (r *VirtualGameRepo) ListAllVirtualGames(ctx context.Context, arg dbgen.Get func (r *VirtualGameRepo) RemoveAllVirtualGames(ctx context.Context) error { return r.store.queries.DeleteAllVirtualGames(ctx) } - diff --git a/internal/repository/wallet.go b/internal/repository/wallet.go index 6aa8d4d..02767ef 100644 --- a/internal/repository/wallet.go +++ b/internal/repository/wallet.go @@ -71,6 +71,9 @@ func convertDBGetCustomerWallet(customerWallet dbgen.CustomerWalletDetail) domai func (s *Store) CreateWallet(ctx context.Context, wallet domain.CreateWallet) (domain.Wallet, error) { newWallet, err := s.queries.CreateWallet(ctx, convertCreateWallet(wallet)) if err != nil { + if IsUniqueViolation(err) { + return domain.Wallet{}, domain.ErrWalletIDDuplicate + } return domain.Wallet{}, err } return convertDBWallet(newWallet), nil diff --git a/internal/services/bet/notification.go b/internal/services/bet/notification.go index e9dd185..edeb70c 100644 --- a/internal/services/bet/notification.go +++ b/internal/services/bet/notification.go @@ -231,7 +231,7 @@ func (s *Service) SendAdminErrorNotification(ctx context.Context, betID int64, s for _, user := range users { for _, channel := range []domain.DeliveryChannel{ domain.DeliveryChannelInApp, - domain.DeliveryChannelEmail, + // domain.DeliveryChannelEmail, } { n := newBetResultNotification(user.ID, domain.NotificationLevelError, channel, headline, message, map[string]any{ "status": status, @@ -247,7 +247,7 @@ func (s *Service) SendAdminErrorNotification(ctx context.Context, betID int64, s } func (s *Service) SendAdminLargeBetNotification(ctx context.Context, betID int64, totalWinnings float32, extra string, companyID int64) error { - headline := fmt.Sprintf("SYSTEM WARNING: High Risk Bet", betID, totalWinnings) + headline := "SYSTEM WARNING: High Risk Bet" message := fmt.Sprintf("Bet #%d has been created with %v payout", betID, totalWinnings) super_admin_users, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{ @@ -283,7 +283,7 @@ func (s *Service) SendAdminLargeBetNotification(ctx context.Context, betID int64 for _, user := range users { for _, channel := range []domain.DeliveryChannel{ domain.DeliveryChannelInApp, - domain.DeliveryChannelEmail, + // domain.DeliveryChannelEmail, } { raw, _ := json.Marshal(map[string]any{ "winnings": totalWinnings, diff --git a/internal/services/bet/port.go b/internal/services/bet/port.go index dc582b4..84b0fc2 100644 --- a/internal/services/bet/port.go +++ b/internal/services/bet/port.go @@ -15,6 +15,7 @@ type BetStore interface { GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, int64, error) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) GetBetByFastCode(ctx context.Context, fastcode string) (domain.GetBet, error) + GetBetOutcomeViewByEventID(ctx context.Context, eventID int64, filter domain.BetOutcomeViewFilter) ([]domain.BetOutcomeViewRes, int64, error) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error) GetBetOutcomeCountByOddID(ctx context.Context, oddID int64) (int64, error) @@ -25,7 +26,8 @@ type BetStore interface { UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) UpdateBetOutcomeStatusByBetID(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error) - + UpdateBetOutcomeStatusForOddId(ctx context.Context, oddID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error) + BulkUpdateBetOutcomeStatusForOddIds(ctx context.Context, oddID []int64, status domain.OutcomeStatus) error GetBetSummary(ctx context.Context, filter domain.ReportFilter) ( totalStakes domain.Currency, totalBets int64, diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index 009f6fd..d6ff26a 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -35,8 +35,10 @@ var ( ErrGenerateRandomOutcome = errors.New("failed to generate any random outcome for events") ErrOutcomesNotCompleted = errors.New("some bet outcomes are still pending") ErrEventHasBeenRemoved = errors.New("event has been removed") + ErrEventHasBeenDisabled = errors.New("event has been disabled") ErrEventHasNotEnded = errors.New("event has not ended yet") + ErrOddHasBeenDisabled = errors.New("odd has been disabled") ErrRawOddInvalid = errors.New("prematch Raw Odd is Invalid") ErrBranchIDRequired = errors.New("branch ID required for this role") ErrOutcomeLimit = errors.New("too many outcomes on a single bet") @@ -46,6 +48,8 @@ var ( ErrInvalidAmount = errors.New("invalid amount") ErrBetAmountTooHigh = errors.New("cannot create a bet with an amount above limit") ErrBetWinningTooHigh = errors.New("total Winnings over set limit") + + ErrCompanyDeductedPercentInvalid = errors.New("invalid company deducted percentage") ) type Service struct { @@ -106,10 +110,10 @@ func (s *Service) GenerateCashoutID() (string, error) { return string(result), nil } -func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketID int64, oddID int64) (domain.CreateBetOutcome, error) { +func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketID int64, oddID int64, companyID int64) (domain.CreateBetOutcome, error) { oddIDStr := strconv.FormatInt(oddID, 10) - event, err := s.eventSvc.GetEventByID(ctx, eventID) + event, err := s.eventSvc.GetEventWithSettingByID(ctx, eventID, companyID) if err != nil { s.mongoLogger.Error("failed to fetch upcoming event by ID", zap.Int64("event_id", eventID), @@ -118,6 +122,14 @@ func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketI return domain.CreateBetOutcome{}, ErrEventHasBeenRemoved } + if !event.IsActive { + s.mongoLogger.Warn("attempting to create bet with disabled event", + zap.Int64("event_id", eventID), + zap.Error(err), + ) + return domain.CreateBetOutcome{}, ErrEventHasBeenDisabled + } + currentTime := time.Now() if event.StartTime.Before(currentTime) { s.mongoLogger.Error("event has already started", @@ -128,7 +140,7 @@ func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketI return domain.CreateBetOutcome{}, ErrEventHasNotEnded } - odds, err := s.prematchSvc.GetOddsByMarketID(ctx, marketID, eventID) + odds, err := s.prematchSvc.GetOddsWithSettingsByMarketID(ctx, marketID, eventID, companyID) if err != nil { s.mongoLogger.Error("failed to get raw odds by market ID", zap.Int64("event_id", eventID), @@ -138,6 +150,15 @@ func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketI return domain.CreateBetOutcome{}, err } + if !odds.IsActive { + s.mongoLogger.Error("failed to get raw odds by market ID", + zap.Int64("event_id", eventID), + zap.Int64("market_id", marketID), + zap.Error(err), + ) + return domain.CreateBetOutcome{}, ErrOddHasBeenDisabled + } + type rawOddType struct { ID string Name string @@ -255,7 +276,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID var totalOdds float32 = 1 for _, outcomeReq := range req.Outcomes { - newOutcome, err := s.GenerateBetOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID) + newOutcome, err := s.GenerateBetOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID, companyID) if err != nil { s.mongoLogger.Error("failed to generate outcome", zap.Int64("event_id", outcomeReq.EventID), @@ -303,8 +324,9 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID } fastCode := helpers.GenerateFastCode() - accumulator := calculateAccumulator(len(outcomes)) - amount := req.Amount + (req.Amount * accumulator) + // accumulator := calculateAccumulator(len(outcomes)) + // amount := req.Amount + (req.Amount * accumulator) + amount := req.Amount newBet := domain.CreateBet{ Amount: domain.ToCurrency(amount), @@ -524,7 +546,27 @@ func (s *Service) DeductBetFromBranchWallet(ctx context.Context, amount float32, return err } + if company.DeductedPercentage > 1 { + s.mongoLogger.Error("Invalid company deducted percentage", + zap.Int64("wallet_id", walletID), + zap.Float32("amount", company.DeductedPercentage), + zap.Error(err), + ) + return ErrCompanyDeductedPercentInvalid + } + + // 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 + + 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, @@ -848,6 +890,9 @@ func (s *Service) GetBetOutcomeByBetID(ctx context.Context, UserID int64) ([]dom return s.betStore.GetBetOutcomeByBetID(ctx, UserID) } +func (s *Service) GetBetOutcomeViewByEventID(ctx context.Context, eventID int64, filter domain.BetOutcomeViewFilter) ([]domain.BetOutcomeViewRes, int64, error) { + return s.betStore.GetBetOutcomeViewByEventID(ctx, eventID, filter) +} func (s *Service) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) { return s.betStore.GetBetOutcomeByEventID(ctx, eventID, is_filtered) } @@ -1076,6 +1121,32 @@ func (s *Service) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID in return outcomes, nil } +func (s *Service) UpdateBetOutcomeStatusForOddId(ctx context.Context, oddID int64, status domain.OutcomeStatus) ([]domain.BetOutcome, error) { + outcomes, err := s.betStore.UpdateBetOutcomeStatusForOddId(ctx, oddID, status) + if err != nil { + s.mongoLogger.Error("failed to update bet outcome status", + zap.Int64("oddID", oddID), + zap.Error(err), + ) + return nil, err + } + + return outcomes, nil +} + +func (s *Service) BulkUpdateBetOutcomeStatusForOddIds(ctx context.Context, oddID []int64, status domain.OutcomeStatus) error { + err := s.betStore.BulkUpdateBetOutcomeStatusForOddIds(ctx, oddID, status) + if err != nil { + s.mongoLogger.Error("failed to update bet outcome status by oddIds", + zap.Int64s("oddID", oddID), + zap.Error(err), + ) + return err + } + + return nil +} + func (s *Service) SetBetToRemoved(ctx context.Context, id int64) error { _, err := s.betStore.UpdateBetOutcomeStatusByBetID(ctx, id, domain.OUTCOME_STATUS_VOID) if err != nil { diff --git a/internal/services/branch/port.go b/internal/services/branch/port.go index 8b17ae1..d23f91a 100644 --- a/internal/services/branch/port.go +++ b/internal/services/branch/port.go @@ -12,7 +12,7 @@ type BranchStore interface { GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([]domain.BranchDetail, error) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]domain.BranchDetail, error) GetAllBranches(ctx context.Context, filter domain.BranchFilter) ([]domain.BranchDetail, error) - SearchBranchByName(ctx context.Context, name string) ([]domain.BranchDetail, error) + SearchBranchByName(ctx context.Context, name string, companyID domain.ValidInt64) ([]domain.BranchDetail, error) UpdateBranch(ctx context.Context, branch domain.UpdateBranch) (domain.Branch, error) DeleteBranch(ctx context.Context, id int64) error CreateBranchOperation(ctx context.Context, branchOperation domain.CreateBranchOperation) error diff --git a/internal/services/branch/service.go b/internal/services/branch/service.go index 9e4f641..1b44651 100644 --- a/internal/services/branch/service.go +++ b/internal/services/branch/service.go @@ -54,8 +54,8 @@ func (s *Service) GetAllSupportedOperations(ctx context.Context) ([]domain.Suppo return s.branchStore.GetAllSupportedOperations(ctx) } -func (s *Service) SearchBranchByName(ctx context.Context, name string) ([]domain.BranchDetail, error) { - return s.branchStore.SearchBranchByName(ctx, name) +func (s *Service) SearchBranchByName(ctx context.Context, name string, companyID domain.ValidInt64) ([]domain.BranchDetail, error) { + return s.branchStore.SearchBranchByName(ctx, name, companyID) } func (s *Service) UpdateBranch(ctx context.Context, branch domain.UpdateBranch) (domain.Branch, error) { return s.branchStore.UpdateBranch(ctx, branch) diff --git a/internal/services/company/port.go b/internal/services/company/port.go index 10bfa70..3f43db8 100644 --- a/internal/services/company/port.go +++ b/internal/services/company/port.go @@ -11,8 +11,8 @@ type CompanyStore interface { GetAllCompanies(ctx context.Context, filter domain.CompanyFilter) ([]domain.GetCompany, error) SearchCompanyByName(ctx context.Context, name string) ([]domain.GetCompany, error) GetCompanyByID(ctx context.Context, id int64) (domain.GetCompany, error) - GetCompanyIDBySlug(ctx context.Context, slug string) (int64, error) - UpdateCompany(ctx context.Context, company domain.UpdateCompany) (domain.Company, error) + GetCompanyBySlug(ctx context.Context, slug string) (domain.Company, error) + UpdateCompany(ctx context.Context, company domain.UpdateCompany) (error) DeleteCompany(ctx context.Context, id int64) error GetCompanyCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) diff --git a/internal/services/company/service.go b/internal/services/company/service.go index a396a10..2dd6624 100644 --- a/internal/services/company/service.go +++ b/internal/services/company/service.go @@ -26,15 +26,15 @@ func (s *Service) GetAllCompanies(ctx context.Context, filter domain.CompanyFilt func (s *Service) GetCompanyByID(ctx context.Context, id int64) (domain.GetCompany, error) { return s.companyStore.GetCompanyByID(ctx, id) } -func (s *Service) GetCompanyIDBySlug(ctx context.Context, slug string) (int64, error){ - return s.companyStore.GetCompanyIDBySlug(ctx, slug) +func (s *Service) GetCompanyBySlug(ctx context.Context, slug string) (domain.Company, error) { + return s.companyStore.GetCompanyBySlug(ctx, slug) } func (s *Service) SearchCompanyByName(ctx context.Context, name string) ([]domain.GetCompany, error) { return s.companyStore.SearchCompanyByName(ctx, name) } -func (s *Service) UpdateCompany(ctx context.Context, company domain.UpdateCompany) (domain.Company, error) { +func (s *Service) UpdateCompany(ctx context.Context, company domain.UpdateCompany) (error) { return s.companyStore.UpdateCompany(ctx, company) } func (s *Service) DeleteCompany(ctx context.Context, id int64) error { diff --git a/internal/services/enet_pulse/service.go b/internal/services/enet_pulse/service.go index f813ad9..ed31572 100644 --- a/internal/services/enet_pulse/service.go +++ b/internal/services/enet_pulse/service.go @@ -241,7 +241,7 @@ func (s *Service) FetchAndStoreTournamentTemplates(ctx context.Context) error { } } - fmt.Println("✅ Successfully fetched and stored all tournament templates") + // fmt.Println("✅ Successfully fetched and stored all tournament templates") return nil } @@ -367,7 +367,7 @@ func (s *Service) FetchAndStoreTournamentStages(ctx context.Context) error { for _, t := range tournaments { // Compose URL for each tournament url := fmt.Sprintf( - "http://eapi.enetpulse.com/tournament_stage/list/?language_typeFK=3&tz=Europe/Sofia&tournamentFK=%s&username=%s&token=%s", + "https://eapi.enetpulse.com/tournament_stage/list/?language_typeFK=3&tz=Europe/Sofia&tournamentFK=%s&username=%s&token=%s", t.TournamentID, s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, @@ -460,125 +460,13 @@ func (s *Service) GetAllTournamentStages(ctx context.Context) ([]domain.Enetpuls } func (s *Service) FetchAndStoreFixtures(ctx context.Context, date string) error { - // 1️⃣ Fetch all sports from the database + // 1️⃣ Fetch all sports from DB sports, err := s.store.GetAllEnetpulseSports(ctx) if err != nil { return fmt.Errorf("failed to fetch sports from DB: %w", err) } - // Struct for decoding each fixture from API - type Fixture struct { - FixtureID string `json:"id"` - Name string `json:"name"` - SportFK string `json:"sportFK"` - TournamentFK string `json:"tournamentFK"` - TournamentName string `json:"tournament_name"` - StartDate string `json:"startdate"` - StatusType string `json:"status_type"` - HomeTeam string `json:"home_team"` - AwayTeam string `json:"away_team"` - HomeTeamID string `json:"home_team_id"` - AwayTeamID string `json:"away_team_id"` - HomeKitImage string `json:"home_kit_image"` - AwayKitImage string `json:"away_kit_image"` - } - - // 2️⃣ Loop through each sport - for _, sport := range sports { - if sport.SportID != "1" { - continue - } - - url := fmt.Sprintf( - "http://eapi.enetpulse.com/event/fixtures/?username=%s&token=%s&sportFK=%s&language_typeFK=3&date=%s", - s.cfg.EnetPulseConfig.UserName, - s.cfg.EnetPulseConfig.Token, - sport.SportID, - date, - ) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - fmt.Printf("creating fixtures request for sport %s: %v\n", sport.SportID, err) - continue - } - - resp, err := s.httpClient.Do(req) - if err != nil { - fmt.Printf("requesting fixtures for sport %s: %v\n", sport.SportID, err) - continue - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - fmt.Printf("failed to fetch fixtures for sport %s (status %d): %s\n", - sport.SportID, resp.StatusCode, string(body)) - continue - } - - // 3️⃣ Decode API response - var fixturesResp struct { - Events map[string]Fixture `json:"events"` - } - if err := json.NewDecoder(resp.Body).Decode(&fixturesResp); err != nil { - fmt.Printf("decoding fixtures response for sport %s: %v\n", sport.SportID, err) - continue - } - - // 4️⃣ Iterate over fixtures and store as events - for _, fx := range fixturesResp.Events { - // Conversions - sportID, _ := strconv.Atoi(fx.SportFK) - homeTeamID, _ := strconv.ParseInt(fx.HomeTeamID, 10, 64) - awayTeamID, _ := strconv.ParseInt(fx.AwayTeamID, 10, 64) - leagueID, _ := strconv.ParseInt(fx.TournamentFK, 10, 64) - startDate, _ := time.Parse("2006-01-02 15:04:05", fx.StartDate) - - event := domain.CreateEvent{ - SourceEventID: fx.FixtureID, - SportID: int32(sportID), - MatchName: fx.Name, - HomeTeam: fx.HomeTeam, - AwayTeam: fx.AwayTeam, - HomeTeamID: homeTeamID, - AwayTeamID: awayTeamID, - HomeTeamImage: fx.HomeKitImage, - AwayTeamImage: fx.AwayKitImage, - LeagueID: leagueID, - LeagueName: fx.TournamentName, - StartTime: startDate, - IsLive: false, // default, can update later from live feed - Status: domain.STATUS_PENDING, // map to enum if needed - Source: "EnetPulse", // custom enum constant - DefaultWinningUpperLimit: 0, // default, can adjust - } - - // 5️⃣ Save event in DB (UPSERT) - if err := s.store.SaveEvent(ctx, event); err != nil { - fmt.Printf("failed storing event %s: %v\n", fx.FixtureID, err) - continue - } - } - - fmt.Printf("✅ Successfully fetched and stored events for sport %s\n", sport.SportID) - break - } - - fmt.Println("✅ Completed fetching and storing events for all sports") - return nil -} - -func (s *Service) FetchFixtures(ctx context.Context, date string) ([]domain.EnetpulseFixture, error) { - var allFixtures []domain.EnetpulseFixture - - // 1️⃣ Fetch all sports from the database - sports, err := s.store.GetAllEnetpulseSports(ctx) - if err != nil { - return nil, fmt.Errorf("failed to fetch sports from DB: %w", err) - } - - // Struct for decoding each fixture from API + // Define API fixture struct type Fixture struct { FixtureID string `json:"id"` Name string `json:"name"` @@ -591,23 +479,22 @@ func (s *Service) FetchFixtures(ctx context.Context, date string) ([]domain.Enet TournamentTemplateName string `json:"tournament_template_name"` SportName string `json:"sport_name"` Gender string `json:"gender"` - StartDate string `json:"startdate"` + StartDate string `json:"startdate"` // ISO 8601 StatusType string `json:"status_type"` StatusDescFK string `json:"status_descFK"` RoundTypeFK string `json:"round_typeFK"` - UpdatesCount string `json:"n"` - LastUpdatedAt string `json:"ut"` + UpdatesCount string `json:"n"` // convert to int + LastUpdatedAt string `json:"ut"` // parse to time.Time } // 2️⃣ Loop through each sport for _, sport := range sports { - // Only fetch for sport "1" (Football) if sport.SportID != "1" { continue } url := fmt.Sprintf( - "http://eapi.enetpulse.com/event/fixtures/?username=%s&token=%s&sportFK=%s&language_typeFK=3&date=%s", + "https://eapi.enetpulse.com/event/fixtures/?username=%s&token=%s&sportFK=%s&language_typeFK=3&date=%s", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, sport.SportID, @@ -616,7 +503,7 @@ func (s *Service) FetchFixtures(ctx context.Context, date string) ([]domain.Enet req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { - fmt.Printf("creating fixtures request for sport %s: %v\n", sport.SportID, err) + fmt.Printf("creating request for sport %s: %v\n", sport.SportID, err) continue } @@ -639,29 +526,38 @@ func (s *Service) FetchFixtures(ctx context.Context, date string) ([]domain.Enet Events map[string]Fixture `json:"events"` } if err := json.NewDecoder(resp.Body).Decode(&fixturesResp); err != nil { - fmt.Printf("decoding fixtures response for sport %s: %v\n", sport.SportID, err) + fmt.Printf("decoding fixtures for sport %s: %v\n", sport.SportID, err) continue } - // 4️⃣ Iterate over fixtures and store them + // 4️⃣ Iterate and upsert fixtures for _, fx := range fixturesResp.Events { - tournamentFK, _ := strconv.Atoi(fx.TournamentFK) - tournamentTemplateFK, _ := strconv.Atoi(fx.TournamentTemplateFK) - tournamentStageFK, _ := strconv.Atoi(fx.TournamentStageFK) - statusDescFK, _ := strconv.Atoi(fx.StatusDescFK) - roundTypeFK, _ := strconv.Atoi(fx.RoundTypeFK) - updatesCount, _ := strconv.Atoi(fx.UpdatesCount) + // Parse StartDate and LastUpdatedAt + startDate, err := time.Parse(time.RFC3339, fx.StartDate) + if err != nil { + fmt.Printf("invalid startDate for fixture %s: %v\n", fx.FixtureID, err) + continue + } + lastUpdated, err := time.Parse(time.RFC3339, fx.LastUpdatedAt) + if err != nil { + fmt.Printf("invalid lastUpdatedAt for fixture %s: %v\n", fx.FixtureID, err) + continue + } - startDate, _ := time.Parse(time.RFC3339, fx.StartDate) - lastUpdatedAt, _ := time.Parse(time.RFC3339, fx.LastUpdatedAt) + // Convert UpdatesCount + updatesCount, err := strconv.Atoi(fx.UpdatesCount) + if err != nil { + fmt.Printf("invalid updatesCount for fixture %s: %v\n", fx.FixtureID, err) + updatesCount = 0 + } - createFixture := domain.CreateEnetpulseFixture{ + fixture := domain.CreateEnetpulseFixture{ FixtureID: fx.FixtureID, Name: fx.Name, SportFK: fx.SportFK, - TournamentFK: strconv.Itoa(tournamentFK), - TournamentTemplateFK: strconv.Itoa(tournamentTemplateFK), - TournamentStageFK: strconv.Itoa(tournamentStageFK), + TournamentFK: fx.TournamentFK, + TournamentTemplateFK: fx.TournamentTemplateFK, + TournamentStageFK: fx.TournamentStageFK, TournamentStageName: fx.TournamentStageName, TournamentName: fx.TournamentName, TournamentTemplateName: fx.TournamentTemplateName, @@ -669,29 +565,150 @@ func (s *Service) FetchFixtures(ctx context.Context, date string) ([]domain.Enet Gender: fx.Gender, StartDate: startDate, StatusType: fx.StatusType, - StatusDescFK: strconv.Itoa(statusDescFK), - RoundTypeFK: strconv.Itoa(roundTypeFK), + StatusDescFK: fx.StatusDescFK, + RoundTypeFK: fx.RoundTypeFK, UpdatesCount: updatesCount, - LastUpdatedAt: lastUpdatedAt, + LastUpdatedAt: lastUpdated, } - dbFixture, err := s.store.CreateEnetpulseFixture(ctx, createFixture) - if err != nil { - fmt.Printf("failed storing fixture %s: %v\n", fx.FixtureID, err) + // 5️⃣ Save fixture using UPSERT repository method + if _, err := s.store.CreateEnetpulseFixture(ctx, fixture); err != nil { + fmt.Printf("failed upserting fixture %s: %v\n", fx.FixtureID, err) continue } - - allFixtures = append(allFixtures, dbFixture) } fmt.Printf("✅ Successfully fetched and stored fixtures for sport %s\n", sport.SportID) - break // stop after first relevant sport + break } - fmt.Println("✅ Completed fetching and storing fixtures for all sports") - return allFixtures, nil + return nil } + +// func (s *Service) FetchFixtures(ctx context.Context, date string) ([]domain.EnetpulseFixture, error) { +// var allFixtures []domain.EnetpulseFixture + +// // 1️⃣ Fetch all sports from the database +// sports, err := s.store.GetAllEnetpulseSports(ctx) +// if err != nil { +// return nil, fmt.Errorf("failed to fetch sports from DB: %w", err) +// } + +// // Struct for decoding each fixture from API +// type Fixture struct { +// FixtureID string `json:"id"` +// Name string `json:"name"` +// SportFK string `json:"sportFK"` +// TournamentFK string `json:"tournamentFK"` +// TournamentTemplateFK string `json:"tournament_templateFK"` +// TournamentStageFK string `json:"tournament_stageFK"` +// TournamentStageName string `json:"tournament_stage_name"` +// TournamentName string `json:"tournament_name"` +// TournamentTemplateName string `json:"tournament_template_name"` +// SportName string `json:"sport_name"` +// Gender string `json:"gender"` +// StartDate string `json:"startdate"` +// StatusType string `json:"status_type"` +// StatusDescFK string `json:"status_descFK"` +// RoundTypeFK string `json:"round_typeFK"` +// UpdatesCount string `json:"n"` +// LastUpdatedAt string `json:"ut"` +// } + +// // 2️⃣ Loop through each sport +// for _, sport := range sports { +// // Only fetch for sport "1" (Football) +// if sport.SportID != "1" { +// continue +// } + +// url := fmt.Sprintf( +// "http://eapi.enetpulse.com/event/fixtures/?username=%s&token=%s&sportFK=%s&language_typeFK=3&date=%s", +// s.cfg.EnetPulseConfig.UserName, +// s.cfg.EnetPulseConfig.Token, +// sport.SportID, +// date, +// ) + +// req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) +// if err != nil { +// fmt.Printf("creating fixtures request for sport %s: %v\n", sport.SportID, err) +// continue +// } + +// resp, err := s.httpClient.Do(req) +// if err != nil { +// fmt.Printf("requesting fixtures for sport %s: %v\n", sport.SportID, err) +// continue +// } +// defer resp.Body.Close() + +// if resp.StatusCode != http.StatusOK { +// body, _ := io.ReadAll(resp.Body) +// fmt.Printf("failed to fetch fixtures for sport %s (status %d): %s\n", +// sport.SportID, resp.StatusCode, string(body)) +// continue +// } + +// // 3️⃣ Decode API response +// var fixturesResp struct { +// Events map[string]Fixture `json:"events"` +// } +// if err := json.NewDecoder(resp.Body).Decode(&fixturesResp); err != nil { +// fmt.Printf("decoding fixtures response for sport %s: %v\n", sport.SportID, err) +// continue +// } + +// // 4️⃣ Iterate over fixtures and store them +// for _, fx := range fixturesResp.Events { +// tournamentFK, _ := strconv.Atoi(fx.TournamentFK) +// tournamentTemplateFK, _ := strconv.Atoi(fx.TournamentTemplateFK) +// tournamentStageFK, _ := strconv.Atoi(fx.TournamentStageFK) +// statusDescFK, _ := strconv.Atoi(fx.StatusDescFK) +// roundTypeFK, _ := strconv.Atoi(fx.RoundTypeFK) +// updatesCount, _ := strconv.Atoi(fx.UpdatesCount) + +// startDate, _ := time.Parse(time.RFC3339, fx.StartDate) +// lastUpdatedAt, _ := time.Parse(time.RFC3339, fx.LastUpdatedAt) + +// createFixture := domain.CreateEnetpulseFixture{ +// FixtureID: fx.FixtureID, +// Name: fx.Name, +// SportFK: fx.SportFK, +// TournamentFK: strconv.Itoa(tournamentFK), +// TournamentTemplateFK: strconv.Itoa(tournamentTemplateFK), +// TournamentStageFK: strconv.Itoa(tournamentStageFK), +// TournamentStageName: fx.TournamentStageName, +// TournamentName: fx.TournamentName, +// TournamentTemplateName: fx.TournamentTemplateName, +// SportName: fx.SportName, +// Gender: fx.Gender, +// StartDate: startDate, +// StatusType: fx.StatusType, +// StatusDescFK: strconv.Itoa(statusDescFK), +// RoundTypeFK: strconv.Itoa(roundTypeFK), +// UpdatesCount: updatesCount, +// LastUpdatedAt: lastUpdatedAt, +// } + +// dbFixture, err := s.store.CreateEnetpulseFixture(ctx, createFixture) +// if err != nil { +// fmt.Printf("failed storing fixture %s: %v\n", fx.FixtureID, err) +// continue +// } + +// allFixtures = append(allFixtures, dbFixture) +// } + +// // fmt.Printf("✅ Successfully fetched and stored fixtures for sport %s\n", sport.SportID) +// break // stop after first relevant sport +// } + +// // fmt.Println("✅ Completed fetching and storing fixtures for all sports") +// return allFixtures, nil +// } + func (s *Service) GetAllFixtures(ctx context.Context) ([]domain.EnetpulseFixture, error) { // 1️⃣ Fetch all from store fixtures, err := s.store.GetAllEnetpulseFixtures(ctx) @@ -955,136 +972,141 @@ func (s *Service) GetAllOutcomeTypes(ctx context.Context) ([]domain.EnetpulseOut } func (s *Service) FetchAndStorePreodds(ctx context.Context) error { - // 1️⃣ Fetch all events from DB + // 1️⃣ Fetch all fixtures fixtures, err := s.store.GetAllEnetpulseFixtures(ctx) if err != nil { return fmt.Errorf("failed to fetch fixtures: %w", err) } - // providerIDStr := strconv.Itoa(int(s.cfg.EnetPulseConfig.ProviderID)) + // 2️⃣ Fetch all outcome types + outcomeTypes, err := s.store.GetAllEnetpulseOutcomeTypes(ctx) + if err != nil { + return fmt.Errorf("failed to fetch outcome types: %w", err) + } - // 2️⃣ Loop through each fixture/event + // 3️⃣ Loop through each fixture for _, fixture := range fixtures { - url := fmt.Sprintf( - "http://eapi.enetpulse.com/preodds/event/?objectFK=%s&odds_providerFK=%s&username=%s&token=%s", - fixture.FixtureID, - s.cfg.EnetPulseConfig.ProviderID, - s.cfg.EnetPulseConfig.UserName, - s.cfg.EnetPulseConfig.Token, - ) + // 4️⃣ Loop through each outcome type + for _, outcome := range outcomeTypes { + url := fmt.Sprintf( + "http://eapi.enetpulse.com/preodds/event/?objectFK=%s&odds_providerFK=%s&outcome_typeFK=%s&username=%s&token=%s", + fixture.FixtureID, + s.cfg.EnetPulseConfig.ProviderID, + outcome.OutcomeTypeID, + s.cfg.EnetPulseConfig.UserName, + s.cfg.EnetPulseConfig.Token, + ) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - // optionally log error and continue to next fixture - continue - } - - resp, err := s.httpClient.Do(req) - if err != nil { - continue - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - continue - } - - // Decode API response - var preoddsResp struct { - Preodds map[string]struct { - ID string `json:"id"` - OutcomeTypeFK string `json:"outcome_typeFK"` - OutcomeScopeFK string `json:"outcome_scopeFK"` - OutcomeSubtypeFK string `json:"outcome_subtypeFK"` - EventParticipantNumber string `json:"event_participant_number"` - Iparam string `json:"iparam"` - Iparam2 string `json:"iparam2"` - Dparam string `json:"dparam"` - Dparam2 string `json:"dparam2"` - Sparam string `json:"sparam"` - N string `json:"n"` - UT string `json:"ut"` - BettingOffers []struct { - ID string `json:"id"` - BettingOfferStatusFK int32 `json:"bettingoffer_status_fk"` - OddsProviderFK int32 `json:"odds_provider_fk"` - Odds float64 `json:"odds"` - OddsOld float64 `json:"odds_old"` - Active string `json:"active"` - CouponKey string `json:"coupon_key"` - N string `json:"n"` - UT string `json:"ut"` - } `json:"bettingoffers"` - } `json:"preodds"` - } - - if err := json.NewDecoder(resp.Body).Decode(&preoddsResp); err != nil { - continue - } - - // Iterate and store preodds and nested betting offers - for _, p := range preoddsResp.Preodds { - updatesCount := 0 - if p.N != "" { - if n, err := strconv.Atoi(p.N); err == nil { - updatesCount = n - } - } - - lastUpdatedAt, _ := time.Parse(time.RFC3339, p.UT) - - eventParticipantNumber := int32(0) - if p.EventParticipantNumber != "" { - if epn, err := strconv.Atoi(p.EventParticipantNumber); err == nil { - eventParticipantNumber = int32(epn) - } - } - - createPreodds := domain.CreateEnetpulsePreodds{ - PreoddsID: p.ID, - EventFK: fixture.FixtureID, - OutcomeTypeFK: string(p.OutcomeTypeFK), - OutcomeScopeFK: string(p.OutcomeScopeFK), - OutcomeSubtypeFK: string(p.OutcomeSubtypeFK), - EventParticipantNumber: int(eventParticipantNumber), - IParam: p.Iparam, - IParam2: p.Iparam2, - DParam: p.Dparam, - DParam2: p.Dparam2, - SParam: p.Sparam, - UpdatesCount: int(updatesCount), - LastUpdatedAt: lastUpdatedAt, - } - - storedPreodds, err := s.store.CreateEnetpulsePreodds(ctx, createPreodds) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { continue } - for _, o := range p.BettingOffers { - bettingUpdates := 0 - if o.N != "" { - if n, err := strconv.Atoi(o.N); err == nil { - bettingUpdates = n + resp, err := s.httpClient.Do(req) + if err != nil { + continue + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + continue + } + + var preoddsResp struct { + Preodds map[string]struct { + ID string `json:"id"` + OutcomeTypeFK string `json:"outcome_typeFK"` + OutcomeScopeFK string `json:"outcome_scopeFK"` + OutcomeSubtypeFK string `json:"outcome_subtypeFK"` + EventParticipantNumber string `json:"event_participant_number"` + Iparam string `json:"iparam"` + Iparam2 string `json:"iparam2"` + Dparam string `json:"dparam"` + Dparam2 string `json:"dparam2"` + Sparam string `json:"sparam"` + N string `json:"n"` + UT string `json:"ut"` + BettingOffers []struct { + ID string `json:"id"` + BettingOfferStatusFK int32 `json:"bettingoffer_status_fk"` + OddsProviderFK int32 `json:"odds_provider_fk"` + Odds float64 `json:"odds"` + OddsOld float64 `json:"odds_old"` + Active string `json:"active"` + CouponKey string `json:"coupon_key"` + N string `json:"n"` + UT string `json:"ut"` + } `json:"bettingoffers"` + } `json:"preodds"` + } + + if err := json.NewDecoder(resp.Body).Decode(&preoddsResp); err != nil { + continue + } + + for _, p := range preoddsResp.Preodds { + updatesCount := 0 + if p.N != "" { + if n, err := strconv.Atoi(p.N); err == nil { + updatesCount = n } } - bettingLastUpdatedAt, _ := time.Parse(time.RFC3339, o.UT) + lastUpdatedAt, _ := time.Parse(time.RFC3339, p.UT) - createOffer := domain.CreateEnetpulsePreoddsBettingOffer{ - BettingOfferID: o.ID, - PreoddsFK: storedPreodds.PreoddsID, - BettingOfferStatusFK: o.BettingOfferStatusFK, - OddsProviderFK: o.OddsProviderFK, - Odds: o.Odds, - OddsOld: o.OddsOld, - Active: o.Active, - CouponKey: o.CouponKey, - UpdatesCount: int(bettingUpdates), - LastUpdatedAt: bettingLastUpdatedAt, + eventParticipantNumber := int32(0) + if p.EventParticipantNumber != "" { + if epn, err := strconv.Atoi(p.EventParticipantNumber); err == nil { + eventParticipantNumber = int32(epn) + } } - _, _ = s.store.CreateEnetpulsePreoddsBettingOffer(ctx, createOffer) + createPreodds := domain.CreateEnetpulsePreodds{ + PreoddsID: p.ID, + EventFK: fixture.FixtureID, + OutcomeTypeFK: outcome.OutcomeTypeID, + OutcomeScopeFK: string(p.OutcomeScopeFK), + OutcomeSubtypeFK: string(p.OutcomeSubtypeFK), + EventParticipantNumber: int(eventParticipantNumber), + IParam: p.Iparam, + IParam2: p.Iparam2, + DParam: p.Dparam, + DParam2: p.Dparam2, + SParam: p.Sparam, + UpdatesCount: int(updatesCount), + LastUpdatedAt: lastUpdatedAt, + } + + storedPreodds, err := s.store.CreateEnetpulsePreodds(ctx, createPreodds) + if err != nil { + continue + } + + for _, o := range p.BettingOffers { + bettingUpdates := 0 + if o.N != "" { + if n, err := strconv.Atoi(o.N); err == nil { + bettingUpdates = n + } + } + + bettingLastUpdatedAt, _ := time.Parse(time.RFC3339, o.UT) + + createOffer := domain.CreateEnetpulsePreoddsBettingOffer{ + BettingOfferID: o.ID, + PreoddsFK: storedPreodds.PreoddsID, + BettingOfferStatusFK: o.BettingOfferStatusFK, + OddsProviderFK: o.OddsProviderFK, + Odds: o.Odds, + OddsOld: o.OddsOld, + Active: o.Active, + CouponKey: o.CouponKey, + UpdatesCount: int(bettingUpdates), + LastUpdatedAt: bettingLastUpdatedAt, + } + + _, _ = s.store.CreateEnetpulsePreoddsBettingOffer(ctx, createOffer) + } } } } @@ -1208,6 +1230,16 @@ func (s *Service) GetAllBettingOffers(ctx context.Context) ([]domain.EnetpulsePr return offers, nil } +func (s *Service) GetFixturesWithPreodds(ctx context.Context) ([]domain.EnetpulseFixtureWithPreodds, error) { + // 1️⃣ Fetch fixtures and their associated preodds from the repository + fixtures, err := s.store.GetFixturesWithPreodds(ctx) + if err != nil { + return nil, fmt.Errorf("failed to fetch fixtures with preodds from DB: %w", err) + } + + return fixtures, nil +} + // helper to safely parse string to int32 // func parseStringToInt32(s string) int32 { // if s == "" { diff --git a/internal/services/event/port.go b/internal/services/event/port.go index 546699e..8653ce6 100644 --- a/internal/services/event/port.go +++ b/internal/services/event/port.go @@ -18,6 +18,7 @@ type Service interface { UpdateEventMonitored(ctx context.Context, eventID int64, IsMonitored bool) error GetEventsWithSettings(ctx context.Context, companyID int64, filter domain.EventFilter) ([]domain.EventWithSettings, int64, error) GetEventWithSettingByID(ctx context.Context, ID int64, companyID int64) (domain.EventWithSettings, error) - UpdateEventSettings(ctx context.Context, event domain.CreateEventSettings) 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) } diff --git a/internal/services/event/service.go b/internal/services/event/service.go index 5bc27e6..b083641 100644 --- a/internal/services/event/service.go +++ b/internal/services/event/service.go @@ -491,8 +491,11 @@ func (s *service) GetEventWithSettingByID(ctx context.Context, ID int64, company return s.store.GetEventWithSettingByID(ctx, ID, companyID) } -func (s *service) UpdateEventSettings(ctx context.Context, event domain.CreateEventSettings) error { - return s.store.UpdateEventSettings(ctx, event) +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) { diff --git a/internal/services/kafka/consumer.go b/internal/services/kafka/consumer.go index c2343fe..fb55eea 100644 --- a/internal/services/kafka/consumer.go +++ b/internal/services/kafka/consumer.go @@ -1,67 +1,67 @@ package kafka -import ( - "context" - "encoding/json" - "log" +// import ( +// "context" +// "encoding/json" +// "log" - "github.com/SamuelTariku/FortuneBet-Backend/internal/event" - "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws" - "github.com/segmentio/kafka-go" -) +// "github.com/SamuelTariku/FortuneBet-Backend/internal/event" +// "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws" +// "github.com/segmentio/kafka-go" +// ) -type WalletConsumer struct { - reader *kafka.Reader - hub *ws.NotificationHub - topic string - groupID string - brokers []string -} +// type WalletConsumer struct { +// reader *kafka.Reader +// hub *ws.NotificationHub +// topic string +// groupID string +// brokers []string +// } -func NewWalletConsumer(brokers []string, topic, groupID string, hub *ws.NotificationHub) *WalletConsumer { - return &WalletConsumer{ - brokers: brokers, - topic: topic, - groupID: groupID, - hub: hub, - reader: kafka.NewReader(kafka.ReaderConfig{ - Brokers: brokers, - GroupID: groupID, - Topic: topic, - }), - } -} +// func NewWalletConsumer(brokers []string, topic, groupID string, hub *ws.NotificationHub) *WalletConsumer { +// return &WalletConsumer{ +// brokers: brokers, +// topic: topic, +// groupID: groupID, +// hub: hub, +// reader: kafka.NewReader(kafka.ReaderConfig{ +// Brokers: brokers, +// GroupID: groupID, +// Topic: topic, +// }), +// } +// } -func (c *WalletConsumer) Start(ctx context.Context) { - go func() { - for { - m, err := c.reader.ReadMessage(ctx) - if err != nil { - log.Printf("Error reading wallet Kafka message: %v", err) - continue - } +// func (c *WalletConsumer) Start(ctx context.Context) { +// go func() { +// for { +// m, err := c.reader.ReadMessage(ctx) +// if err != nil { +// log.Printf("Error reading wallet Kafka message: %v", err) +// continue +// } - var evt event.WalletEvent - if err := json.Unmarshal(m.Value, &evt); err != nil { - log.Printf("Failed to unmarshal wallet event: %v", err) - continue - } +// var evt event.WalletEvent +// if err := json.Unmarshal(m.Value, &evt); err != nil { +// log.Printf("Failed to unmarshal wallet event: %v", err) +// continue +// } - payload := map[string]interface{}{ - "type": evt.EventType, - "wallet_id": evt.WalletID, - "user_id": evt.UserID, - "balance": evt.Balance, - "wallet_type": evt.WalletType, - "trigger": evt.Trigger, - "recipient_id": evt.UserID, - } +// payload := map[string]interface{}{ +// "type": evt.EventType, +// "wallet_id": evt.WalletID, +// "user_id": evt.UserID, +// "balance": evt.Balance, +// "wallet_type": evt.WalletType, +// "trigger": evt.Trigger, +// "recipient_id": evt.UserID, +// } - // Broadcast to appropriate WebSocket clients - c.hub.Broadcast <- payload - } - }() -} +// // Broadcast to appropriate WebSocket clients +// c.hub.Broadcast <- payload +// } +// }() +// } // func (c *WalletConsumer) Shutdown() error { // return c.reader.Close() diff --git a/internal/services/kafka/producer.go b/internal/services/kafka/producer.go index 8c03b5c..e2720ee 100644 --- a/internal/services/kafka/producer.go +++ b/internal/services/kafka/producer.go @@ -1,36 +1,36 @@ package kafka -import ( - "context" - "encoding/json" - "time" +// import ( +// "context" +// "encoding/json" +// "time" - "github.com/segmentio/kafka-go" -) +// "github.com/segmentio/kafka-go" +// ) -type Producer struct { - writer *kafka.Writer -} +// type Producer struct { +// writer *kafka.Writer +// } -func NewProducer(brokers []string, topic string) *Producer { - return &Producer{ - writer: &kafka.Writer{ - Addr: kafka.TCP(brokers...), - Topic: topic, - Balancer: &kafka.LeastBytes{}, - }, - } -} +// func NewProducer(brokers []string, topic string) *Producer { +// return &Producer{ +// writer: &kafka.Writer{ +// Addr: kafka.TCP(brokers...), +// Topic: topic, +// Balancer: &kafka.LeastBytes{}, +// }, +// } +// } -func (p *Producer) Publish(ctx context.Context, key string, event any) error { - msgBytes, err := json.Marshal(event) - if err != nil { - return err - } +// func (p *Producer) Publish(ctx context.Context, key string, event any) error { +// msgBytes, err := json.Marshal(event) +// if err != nil { +// return err +// } - return p.writer.WriteMessages(ctx, kafka.Message{ - Key: []byte(key), - Value: msgBytes, - Time: time.Now(), - }) -} +// return p.writer.WriteMessages(ctx, kafka.Message{ +// Key: []byte(key), +// Value: msgBytes, +// Time: time.Now(), +// }) +// } diff --git a/internal/services/league/port.go b/internal/services/league/port.go index 54dc626..31d38d2 100644 --- a/internal/services/league/port.go +++ b/internal/services/league/port.go @@ -9,8 +9,9 @@ import ( type Service interface { SaveLeague(ctx context.Context, league domain.CreateLeague) error SaveLeagueSettings(ctx context.Context, leagueSettings domain.CreateLeagueSettings) error - GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.BaseLeague, error) - GetAllLeaguesByCompany(ctx context.Context, companyID int64, filter domain.LeagueFilter) ([]domain.LeagueWithSettings, int64, error) + GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.BaseLeague, int64, error) + GetAllLeaguesByCompany(ctx context.Context, companyID int64, filter domain.LeagueFilter) ([]domain.LeagueWithSettings, int64, error) CheckLeagueSupport(ctx context.Context, leagueID int64, companyID int64) (bool, error) UpdateLeague(ctx context.Context, league domain.UpdateLeague) error + UpdateGlobalLeagueSettings(ctx context.Context, league domain.UpdateGlobalLeagueSettings) error } diff --git a/internal/services/league/service.go b/internal/services/league/service.go index 9a3e1a3..0e40604 100644 --- a/internal/services/league/service.go +++ b/internal/services/league/service.go @@ -25,7 +25,7 @@ func (s *service) SaveLeagueSettings(ctx context.Context, leagueSettings domain. return s.store.SaveLeagueSettings(ctx, leagueSettings) } -func (s *service) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.BaseLeague, error) { +func (s *service) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.BaseLeague, int64, error) { return s.store.GetAllLeagues(ctx, filter) } @@ -40,3 +40,7 @@ func (s *service) CheckLeagueSupport(ctx context.Context, leagueID int64, compan func (s *service) UpdateLeague(ctx context.Context, league domain.UpdateLeague) error { return s.store.UpdateLeague(ctx, league) } + +func (s *service) UpdateGlobalLeagueSettings(ctx context.Context, league domain.UpdateGlobalLeagueSettings) error { + return s.store.UpdateGlobalLeagueSettings(ctx, league) +} diff --git a/internal/services/notification/service.go b/internal/services/notification/service.go index bc31c02..283ed9a 100644 --- a/internal/services/notification/service.go +++ b/internal/services/notification/service.go @@ -2,7 +2,6 @@ package notificationservice import ( "context" - "encoding/json" "fmt" // "errors" @@ -12,19 +11,19 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/config" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" - "github.com/SamuelTariku/FortuneBet-Backend/internal/event" + "github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/messenger" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" - "github.com/segmentio/kafka-go" + // "github.com/segmentio/kafka-go" "go.uber.org/zap" // "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws" // afro "github.com/amanuelabay/afrosms-go" "github.com/gorilla/websocket" - "github.com/redis/go-redis/v9" + // "github.com/redis/go-redis/v9" ) type Service struct { @@ -39,8 +38,6 @@ type Service struct { messengerSvc *messenger.Service mongoLogger *zap.Logger logger *slog.Logger - redisClient *redis.Client - reader *kafka.Reader } func New(repo repository.NotificationRepository, @@ -49,17 +46,8 @@ func New(repo repository.NotificationRepository, cfg *config.Config, messengerSvc *messenger.Service, userSvc *user.Service, - kafkaBrokers []string, ) *Service { hub := ws.NewNotificationHub() - rdb := redis.NewClient(&redis.Options{ - Addr: cfg.RedisAddr, // e.g., "redis:6379" - }) - walletReader := kafka.NewReader(kafka.ReaderConfig{ - Brokers: kafkaBrokers, - Topic: "wallet-balance-topic", - GroupID: "notification-service-group", // Each service should have its own group - }) svc := &Service{ repo: repo, @@ -72,15 +60,13 @@ func New(repo repository.NotificationRepository, messengerSvc: messengerSvc, userSvc: userSvc, config: cfg, - redisClient: rdb, - reader: walletReader, } go hub.Run() go svc.startWorker() go svc.startRetryWorker() - go svc.RunRedisSubscriber(context.Background()) - go svc.StartKafkaConsumer(context.Background()) + // go svc.RunRedisSubscriber(context.Background()) + // go svc.StartKafkaConsumer(context.Background()) return svc } @@ -484,189 +470,192 @@ func (s *Service) CountUnreadNotifications(ctx context.Context, recipient_id int return s.repo.CountUnreadNotifications(ctx, recipient_id) } +func (s *Service) DeleteOldNotifications(ctx context.Context) error { + return s.repo.DeleteOldNotifications(ctx) +} + // func (s *Service) GetNotificationCounts(ctx context.Context, filter domain.ReportFilter) (total, read, unread int64, err error){ // return s.repo.Get(ctx, filter) // } -func (s *Service) RunRedisSubscriber(ctx context.Context) { - pubsub := s.redisClient.Subscribe(ctx, "live_metrics") - defer pubsub.Close() +// func (s *Service) RunRedisSubscriber(ctx context.Context) { +// pubsub := s.redisClient.Subscribe(ctx, "live_metrics") +// defer pubsub.Close() - ch := pubsub.Channel() - for msg := range ch { - var parsed map[string]interface{} - if err := json.Unmarshal([]byte(msg.Payload), &parsed); err != nil { - // s.logger.Error("invalid Redis message format", "payload", msg.Payload, "error", err) - s.mongoLogger.Error("invalid Redis message format", - zap.String("payload", msg.Payload), - zap.Error(err), - zap.Time("timestamp", time.Now()), - ) - continue - } +// ch := pubsub.Channel() +// for msg := range ch { +// var parsed map[string]interface{} +// if err := json.Unmarshal([]byte(msg.Payload), &parsed); err != nil { +// // s.logger.Error("invalid Redis message format", "payload", msg.Payload, "error", err) +// s.mongoLogger.Error("invalid Redis message format", +// zap.String("payload", msg.Payload), +// zap.Error(err), +// zap.Time("timestamp", time.Now()), +// ) +// continue +// } - eventType, _ := parsed["type"].(string) - payload := parsed["payload"] - recipientID, hasRecipient := parsed["recipient_id"] - recipientType, _ := parsed["recipient_type"].(string) +// eventType, _ := parsed["type"].(string) +// payload := parsed["payload"] +// recipientID, hasRecipient := parsed["recipient_id"] +// recipientType, _ := parsed["recipient_type"].(string) - message := map[string]interface{}{ - "type": eventType, - "payload": payload, - } +// message := map[string]interface{}{ +// "type": eventType, +// "payload": payload, +// } - if hasRecipient { - message["recipient_id"] = recipientID - message["recipient_type"] = recipientType - } +// if hasRecipient { +// message["recipient_id"] = recipientID +// message["recipient_type"] = recipientType +// } - s.Hub.Broadcast <- message - } -} +// s.Hub.Broadcast <- message +// } +// } -func (s *Service) UpdateLiveWalletMetrics(ctx context.Context, companies []domain.GetCompany, branches []domain.BranchWallet) error { - const key = "live_metrics" +// func (s *Service) UpdateLiveWalletMetrics(ctx context.Context, companies []domain.GetCompany, branches []domain.BranchWallet) error { +// const key = "live_metrics" - companyBalances := make([]domain.CompanyWalletBalance, 0, len(companies)) - for _, c := range companies { - companyBalances = append(companyBalances, domain.CompanyWalletBalance{ - CompanyID: c.ID, - CompanyName: c.Name, - Balance: float64(c.WalletBalance.Float32()), - }) - } +// companyBalances := make([]domain.CompanyWalletBalance, 0, len(companies)) +// for _, c := range companies { +// companyBalances = append(companyBalances, domain.CompanyWalletBalance{ +// CompanyID: c.ID, +// CompanyName: c.Name, +// Balance: float64(c.WalletBalance.Float32()), +// }) +// } - branchBalances := make([]domain.BranchWalletBalance, 0, len(branches)) - for _, b := range branches { - branchBalances = append(branchBalances, domain.BranchWalletBalance{ - BranchID: b.ID, - BranchName: b.Name, - CompanyID: b.CompanyID, - Balance: float64(b.Balance.Float32()), - }) - } +// branchBalances := make([]domain.BranchWalletBalance, 0, len(branches)) +// for _, b := range branches { +// branchBalances = append(branchBalances, domain.BranchWalletBalance{ +// BranchID: b.ID, +// BranchName: b.Name, +// CompanyID: b.CompanyID, +// Balance: float64(b.Balance.Float32()), +// }) +// } - payload := domain.LiveWalletMetrics{ - Timestamp: time.Now(), - CompanyBalances: companyBalances, - BranchBalances: branchBalances, - } +// payload := domain.LiveWalletMetrics{ +// Timestamp: time.Now(), +// CompanyBalances: companyBalances, +// BranchBalances: branchBalances, +// } - updatedData, err := json.Marshal(payload) - if err != nil { - return err - } +// updatedData, err := json.Marshal(payload) +// if err != nil { +// return err +// } - if err := s.redisClient.Set(ctx, key, updatedData, 0).Err(); err != nil { - return err - } +// if err := s.redisClient.Set(ctx, key, updatedData, 0).Err(); err != nil { +// return err +// } - if err := s.redisClient.Publish(ctx, key, updatedData).Err(); err != nil { - return err - } - return nil -} +// if err := s.redisClient.Publish(ctx, key, updatedData).Err(); err != nil { +// return err +// } +// return nil +// } -func (s *Service) GetLiveMetrics(ctx context.Context) (domain.LiveMetric, error) { - const key = "live_metrics" - var metric domain.LiveMetric +// func (s *Service) GetLiveMetrics(ctx context.Context) (domain.LiveMetric, error) { +// const key = "live_metrics" +// var metric domain.LiveMetric - val, err := s.redisClient.Get(ctx, key).Result() - if err == redis.Nil { - // Key does not exist yet, return zero-valued struct - return domain.LiveMetric{}, nil - } else if err != nil { - return domain.LiveMetric{}, err - } +// val, err := s.redisClient.Get(ctx, key).Result() +// if err == redis.Nil { +// // Key does not exist yet, return zero-valued struct +// return domain.LiveMetric{}, nil +// } else if err != nil { +// return domain.LiveMetric{}, err +// } - if err := json.Unmarshal([]byte(val), &metric); err != nil { - return domain.LiveMetric{}, err - } +// if err := json.Unmarshal([]byte(val), &metric); err != nil { +// return domain.LiveMetric{}, err +// } - return metric, nil -} +// return metric, nil +// } +// func (s *Service) StartKafkaConsumer(ctx context.Context) { +// go func() { +// for { +// m, err := s.reader.ReadMessage(ctx) +// if err != nil { +// if err == context.Canceled { +// s.mongoLogger.Info("[NotificationSvc.KafkaConsumer] Stopped by context") +// return +// } +// s.mongoLogger.Error("[NotificationSvc.KafkaConsumer] Error reading message", +// zap.Error(err), +// zap.Time("timestamp", time.Now()), +// ) +// time.Sleep(1 * time.Second) // backoff +// continue +// } -func (s *Service) StartKafkaConsumer(ctx context.Context) { - go func() { - for { - m, err := s.reader.ReadMessage(ctx) - if err != nil { - if err == context.Canceled { - s.mongoLogger.Info("[NotificationSvc.KafkaConsumer] Stopped by context") - return - } - s.mongoLogger.Error("[NotificationSvc.KafkaConsumer] Error reading message", - zap.Error(err), - zap.Time("timestamp", time.Now()), - ) - time.Sleep(1 * time.Second) // backoff - continue - } +// var walletEvent event.WalletEvent +// if err := json.Unmarshal(m.Value, &walletEvent); err != nil { +// s.mongoLogger.Error("[NotificationSvc.KafkaConsumer] Failed to unmarshal wallet event", +// zap.String("message", string(m.Value)), +// zap.Error(err), +// zap.Time("timestamp", time.Now()), +// ) +// continue +// } - var walletEvent event.WalletEvent - if err := json.Unmarshal(m.Value, &walletEvent); err != nil { - s.mongoLogger.Error("[NotificationSvc.KafkaConsumer] Failed to unmarshal wallet event", - zap.String("message", string(m.Value)), - zap.Error(err), - zap.Time("timestamp", time.Now()), - ) - continue - } +// raw, _ := json.Marshal(map[string]any{ +// "balance": walletEvent.Balance.Float32(), +// "type": walletEvent.WalletType, +// "timestamp": time.Now(), +// }) - raw, _ := json.Marshal(map[string]any{ - "balance": walletEvent.Balance.Float32(), - "type": walletEvent.WalletType, - "timestamp": time.Now(), - }) +// headline := "" +// message := "" +// var receiver domain.NotificationRecieverSide +// switch walletEvent.WalletType { - headline := "" - message := "" - var receiver domain.NotificationRecieverSide - switch walletEvent.WalletType { +// case domain.StaticWalletType: +// headline = "Referral and Bonus Wallet Updated" +// message = fmt.Sprintf("Your referral and bonus wallet balance is now %.2f", walletEvent.Balance.Float32()) +// receiver = domain.NotificationRecieverSideCustomer +// case domain.RegularWalletType: +// headline = "Wallet Updated" +// message = fmt.Sprintf("Your wallet balance is now %.2f", walletEvent.Balance.Float32()) +// receiver = domain.NotificationRecieverSideCustomer +// case domain.BranchWalletType: +// headline = "Branch Wallet Updated" +// message = fmt.Sprintf("branch wallet balance is now %.2f", walletEvent.Balance.Float32()) +// receiver = domain.NotificationRecieverSideBranchManager +// case domain.CompanyWalletType: +// headline = "Company Wallet Updated" +// message = fmt.Sprintf("company wallet balance is now %.2f", walletEvent.Balance.Float32()) +// receiver = domain.NotificationRecieverSideAdmin +// } +// // Handle the wallet event: send notification +// notification := &domain.Notification{ +// RecipientID: walletEvent.UserID, +// DeliveryChannel: domain.DeliveryChannelInApp, +// Reciever: receiver, +// Type: domain.NotificationTypeWalletUpdated, +// DeliveryStatus: domain.DeliveryStatusPending, +// IsRead: false, +// Level: domain.NotificationLevelInfo, +// Priority: 2, +// Metadata: raw, +// Payload: domain.NotificationPayload{ +// Headline: headline, +// Message: message, +// }, +// } - case domain.StaticWalletType: - headline = "Referral and Bonus Wallet Updated" - message = fmt.Sprintf("Your referral and bonus wallet balance is now %.2f", walletEvent.Balance.Float32()) - receiver = domain.NotificationRecieverSideCustomer - case domain.RegularWalletType: - headline = "Wallet Updated" - message = fmt.Sprintf("Your wallet balance is now %.2f", walletEvent.Balance.Float32()) - receiver = domain.NotificationRecieverSideCustomer - case domain.BranchWalletType: - headline = "Branch Wallet Updated" - message = fmt.Sprintf("branch wallet balance is now %.2f", walletEvent.Balance.Float32()) - receiver = domain.NotificationRecieverSideBranchManager - case domain.CompanyWalletType: - headline = "Company Wallet Updated" - message = fmt.Sprintf("company wallet balance is now %.2f", walletEvent.Balance.Float32()) - receiver = domain.NotificationRecieverSideAdmin - } - // Handle the wallet event: send notification - notification := &domain.Notification{ - RecipientID: walletEvent.UserID, - DeliveryChannel: domain.DeliveryChannelInApp, - Reciever: receiver, - Type: domain.NotificationTypeWalletUpdated, - DeliveryStatus: domain.DeliveryStatusPending, - IsRead: false, - Level: domain.NotificationLevelInfo, - Priority: 2, - Metadata: raw, - Payload: domain.NotificationPayload{ - Headline: headline, - Message: message, - }, - } - - if err := s.SendNotification(ctx, notification); err != nil { - s.mongoLogger.Error("[NotificationSvc.KafkaConsumer] Failed to send notification", - zap.Error(err), - zap.Time("timestamp", time.Now()), - ) - } - } - }() -} +// if err := s.SendNotification(ctx, notification); err != nil { +// s.mongoLogger.Error("[NotificationSvc.KafkaConsumer] Failed to send notification", +// zap.Error(err), +// zap.Time("timestamp", time.Now()), +// ) +// } +// } +// }() +// } // func (s *Service) UpdateLiveWalletMetricForWallet(ctx context.Context, wallet domain.Wallet) { // var ( diff --git a/internal/services/odds/port.go b/internal/services/odds/port.go index 3ec57b9..b67b91e 100644 --- a/internal/services/odds/port.go +++ b/internal/services/odds/port.go @@ -17,31 +17,17 @@ type Service interface { GetALLPrematchOdds(ctx context.Context) ([]domain.OddMarket, error) // GetRawOddsByMarketID(ctx context.Context, marketID string, upcomingID string) (domain.OddMarket, error) DeleteOddsForEvent(ctx context.Context, eventID string) error - GetOddByID(ctx context.Context, id int64) (domain.OddMarket, error) - GetOddsWithSettingsByID(ctx context.Context, ID int64, companyID int64) (domain.OddMarketWithSettings, error) + GetOddsWithSettingsByID(ctx context.Context, ID int64, companyID int64) (domain.OddMarketWithSettings, error) + // Settings SaveOddsSetting(ctx context.Context, odd domain.CreateOddMarketSettings) error + UpdateGlobalOddsSetting(ctx context.Context, odd domain.UpdateGlobalOddMarketSettings) error + DeleteAllCompanyOddsSetting(ctx context.Context, companyID int64) error + DeleteCompanyOddsSettingByOddMarketID(ctx context.Context, companyID int64, oddMarketID int64) error // Odd History InsertOddHistory(ctx context.Context, odd domain.CreateOddHistory) (domain.OddHistory, error) GetAllOddHistory(ctx context.Context, filter domain.OddHistoryFilter) ([]domain.OddHistory, error) GetInitialOddPerDay(ctx context.Context, filter domain.OddHistoryFilter) ([]domain.OddHistory, error) - - // Disabling Odds - InsertDisabledOdd(ctx context.Context, odd domain.CreateDisabledOdd) (domain.DisabledOdd, error) - GetAllDisabledOdds(ctx context.Context) ([]domain.DisabledOdd, error) - GetDisabledOddByRawOddID(ctx context.Context, rawOddID int64) (domain.DisabledOdd, error) - GetDisabledOddByID(ctx context.Context, id int64) (domain.DisabledOdd, error) - DeleteDisabledOddsByID(ctx context.Context, id int64) error - DeleteDisabledOddsByRawOddID(ctx context.Context, id int64) error - - // Custom Odds - // InsertCustomOdds(ctx context.Context, odd domain.CreateCustomOdd) (domain.CustomOdd, error) - // GetAllCustomOdds(ctx context.Context, filter domain.CustomOddFilter) ([]domain.CustomOdd, error) - // GetCustomOddByID(ctx context.Context, id int64) (domain.CustomOdd, error) - // GetCustomOddByOddID(ctx context.Context, oddId int64, companyID int64) (domain.CustomOdd, error) - // DeleteCustomOddByID(ctx context.Context, id int64) error - // DeleteCustomOddsByOddID(ctx context.Context, oddId int64, companyID int64) error - // DeleteCustomOddByEventID(ctx context.Context, eventID string) error } diff --git a/internal/services/odds/service.go b/internal/services/odds/service.go index dc2700d..f42f6a1 100644 --- a/internal/services/odds/service.go +++ b/internal/services/odds/service.go @@ -557,13 +557,14 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID int64, fi, secti } marketRecord := domain.CreateOddMarket{ - EventID: eventID, - MarketCategory: sectionName, - MarketType: marketType, - MarketName: market.Name, - MarketID: marketIDint, - UpdatedAt: updatedAt, - Odds: marketOdds, + EventID: eventID, + MarketCategory: sectionName, + MarketType: marketType, + MarketName: market.Name, + MarketID: marketIDint, + NumberOfOutcomes: int64(len(market.Odds)), + UpdatedAt: updatedAt, + Odds: marketOdds, // bwin won't reach this code so bet365 is hardcoded for now } @@ -676,6 +677,10 @@ func (s *ServiceImpl) SaveOddsSetting(ctx context.Context, odd domain.CreateOddM return s.store.SaveOddsSetting(ctx, odd) } +func (s *ServiceImpl) UpdateGlobalOddsSetting(ctx context.Context, odd domain.UpdateGlobalOddMarketSettings) error { + return s.store.UpdateGlobalOddsSetting(ctx, odd) +} + func (s *ServiceImpl) SaveOddsSettingReq(ctx context.Context, companyID int64, req domain.CreateOddMarketSettingsReq) error { odd, err := s.GetOddsWithSettingsByID(ctx, req.OddMarketID, companyID) @@ -741,6 +746,14 @@ func (s *ServiceImpl) DeleteOddsForEvent(ctx context.Context, eventID int64) err return s.store.DeleteOddsForEvent(ctx, eventID) } +func (s *ServiceImpl) DeleteAllCompanyOddsSetting(ctx context.Context, companyID int64) error { + return s.store.DeleteAllCompanyOddsSetting(ctx, companyID) +} + +func (s *ServiceImpl) DeleteCompanyOddsSettingByOddMarketID(ctx context.Context, companyID int64, oddMarketID int64) error { + return s.store.DeleteCompanyOddsSettingByOddMarketID(ctx, companyID, oddMarketID) +} + // func getString(v interface{}) string { // if str, ok := v.(string); ok { // return str diff --git a/internal/services/raffle/port.go b/internal/services/raffle/port.go index 39f5bfa..edbe4d3 100644 --- a/internal/services/raffle/port.go +++ b/internal/services/raffle/port.go @@ -16,9 +16,12 @@ type RaffleStore interface { CreateRaffleWinner(ctx context.Context, raffleWinnerParams domain.RaffleWinnerParams) error SetRaffleComplete(ctx context.Context, raffleID int32) error CheckValidSportRaffleFilter(ctx context.Context, raffleID int32, sportID, leagueID int64) (bool, error) + CheckSportRaffleHasFilter(ctx context.Context, raffleID int32) (bool, error) CreateRaffleTicket(ctx context.Context, raffleTicketParams domain.CreateRaffleTicket) (domain.RaffleTicket, error) GetUserRaffleTickets(ctx context.Context, userID int32) ([]domain.RaffleTicketRes, error) SuspendRaffleTicket(ctx context.Context, raffleTicketID int32) error UnSuspendRaffleTicket(ctx context.Context, raffleID int32) error + GetRaffleTicketCount(ctx context.Context, raffleID, userID int32) (int64, error) + GetRaffleTicketLimit(ctx context.Context, raffleID int32) (int32, error) } diff --git a/internal/services/raffle/service.go b/internal/services/raffle/service.go index 017d164..3483839 100644 --- a/internal/services/raffle/service.go +++ b/internal/services/raffle/service.go @@ -64,3 +64,15 @@ func (s *Service) UnSuspendRaffleTicket(ctx context.Context, raffleID int32) err func (s *Service) CheckValidSportRaffleFilter(ctx context.Context, raffleID int32, sportID, leagueID int64) (bool, error) { return s.raffleStore.CheckValidSportRaffleFilter(ctx, raffleID, sportID, leagueID) } + +func (s *Service) CheckSportRaffleHasFilter(ctx context.Context, raffleID int32) (bool, error) { + return s.raffleStore.CheckSportRaffleHasFilter(ctx, raffleID) +} + +func (s *Service) GetRaffleTicketCount(ctx context.Context, raffleID, userID int32) (int64, error) { + return s.raffleStore.GetRaffleTicketCount(ctx, raffleID, userID) +} + +func (s *Service) GetRaffleTicketLimit(ctx context.Context, raffleID int32) (int32, error) { + return s.raffleStore.GetRaffleTicketLimit(ctx, raffleID) +} diff --git a/internal/services/report/service.go b/internal/services/report/service.go index 1454bae..d19cef3 100644 --- a/internal/services/report/service.go +++ b/internal/services/report/service.go @@ -135,11 +135,11 @@ func (s *Service) GetDashboardSummary(ctx context.Context, filter domain.ReportF } // Get sport/game metrics - summary.TotalGames, summary.ActiveGames, summary.InactiveGames, err = s.virtulaGamesStore.GetGameCounts(ctx, filter) - if err != nil { - s.logger.Error("failed to get game counts", "error", err) - return domain.DashboardSummary{}, err - } + // summary.TotalGames, summary.ActiveGames, summary.InactiveGames, err = s.virtulaGamesStore.GetGameCounts(ctx, filter) + // if err != nil { + // s.logger.Error("failed to get game counts", "error", err) + // return domain.DashboardSummary{}, err + // } // Get company metrics summary.TotalCompanies, summary.ActiveCompanies, summary.InactiveCompanies, err = s.companyStore.GetCompanyCounts(ctx, filter) diff --git a/internal/services/result/notification.go b/internal/services/result/notification.go new file mode 100644 index 0000000..3c88649 --- /dev/null +++ b/internal/services/result/notification.go @@ -0,0 +1,292 @@ +package result + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "go.uber.org/zap" +) + +func (s *Service) CheckAndSendResultNotifications(ctx context.Context, createdAfter time.Time) error { + + resultLog, err := s.repo.GetAllResultLog(ctx, domain.ResultLogFilter{ + CreatedAfter: domain.ValidTime{ + Value: createdAfter, + Valid: true, + }, + }) + + if err != nil { + s.mongoLogger.Error( + "Failed to get result log", + zap.Time("CreatedAfter", createdAfter), + zap.Error(err), + ) + return err + } + + if len(resultLog) == 0 { + s.mongoLogger.Info( + "No results found for check and send result notification", + zap.Time("CreatedAfter", createdAfter), + ) + return nil + } + + totalResultLog := domain.ResultLog{ + StatusNotFinishedCount: resultLog[0].StatusNotFinishedCount, + StatusPostponedCount: resultLog[0].StatusPostponedCount, + } + for _, log := range resultLog { + // Add all the bets + totalResultLog.StatusNotFinishedBets += log.StatusNotFinishedBets + totalResultLog.StatusPostponedBets += log.StatusPostponedBets + totalResultLog.StatusToBeFixedBets += log.StatusToBeFixedBets + totalResultLog.StatusRemovedBets += log.StatusRemovedBets + totalResultLog.StatusEndedBets += log.StatusEndedBets + + totalResultLog.StatusToBeFixedCount += log.StatusToBeFixedCount + totalResultLog.StatusRemovedCount += log.StatusRemovedCount + totalResultLog.StatusEndedCount += log.StatusEndedCount + totalResultLog.RemovedCount += log.RemovedCount + } + + err = s.SendAdminResultStatusErrorNotification(ctx, totalResultLog, createdAfter, time.Now()) + if err != nil { + s.mongoLogger.Error( + "Failed to send admin result status notification", + zap.Time("CreatedAfter", createdAfter), + zap.Error(err), + ) + return err + } + + return nil +} + +func buildHeadlineAndMessage(counts domain.ResultLog, createdAfter time.Time, endTime time.Time) (string, string) { + period := fmt.Sprintf("%s - %s", createdAfter.Format("02 Jan 2006"), endTime.Format("02 Jan 2006")) + + totalIssues := counts.StatusNotFinishedCount + counts.StatusToBeFixedCount + counts.StatusPostponedCount + counts.StatusRemovedCount + totalBets := counts.StatusEndedBets + counts.StatusNotFinishedBets + counts.StatusPostponedBets + counts.StatusRemovedBets + counts.StatusToBeFixedBets + if totalIssues == 0 { + return "✅ Successfully Processed Event Results", fmt.Sprintf( + "%d total ended events with %d total bets. No issues detected", counts.StatusEndedCount, totalBets, + ) + } + + parts := []string{} + if counts.StatusNotFinishedCount > 0 { + parts = append(parts, fmt.Sprintf("%d unfinished with %d bets", counts.StatusNotFinishedCount, counts.StatusNotFinishedBets)) + } + if counts.StatusToBeFixedCount > 0 { + parts = append(parts, fmt.Sprintf("%d to-fix with %d bets", counts.StatusToBeFixedCount, counts.StatusToBeFixedBets)) + } + if counts.StatusPostponedCount > 0 { + parts = append(parts, fmt.Sprintf("%d postponed with %d bets", counts.StatusPostponedCount, counts.StatusPostponedBets)) + } + if counts.StatusRemovedCount > 0 { + parts = append(parts, fmt.Sprintf("%d removed with %d bets", counts.StatusRemovedCount, counts.StatusRemovedBets)) + } + if counts.StatusEndedCount > 0 { + parts = append(parts, fmt.Sprintf("%d ended with %d bets", counts.StatusEndedCount, counts.StatusEndedBets)) + } + + headline := "⚠️ Issues Found Processing Event Results" + message := fmt.Sprintf("Processed expired event results (%s): %s. Please review pending entries.", + period, strings.Join(parts, ", ")) + return headline, message +} + +func buildHeadlineAndMessageEmail(counts domain.ResultLog, user domain.User, createdAfter time.Time, endTime time.Time) (string, string, string) { + period := fmt.Sprintf("%s - %s", createdAfter.Format("02 Jan 2006"), endTime.Format("02 Jan 2006")) + + totalIssues := counts.StatusNotFinishedCount + counts.StatusToBeFixedCount + + counts.StatusPostponedCount + counts.StatusRemovedCount + totalEvents := counts.StatusEndedCount + counts.StatusNotFinishedCount + + counts.StatusToBeFixedCount + counts.StatusPostponedCount + counts.StatusRemovedCount + totalBets := counts.StatusEndedBets + counts.StatusNotFinishedBets + + counts.StatusPostponedBets + counts.StatusRemovedBets + counts.StatusToBeFixedBets + + greeting := fmt.Sprintf("Hi %s %s,", user.FirstName, user.LastName) + + if totalIssues == 0 { + headline := "✅ Weekly Results Report — All Events Processed Successfully" + plain := fmt.Sprintf(`%s + +Weekly Results Summary (%s): +- %d Ended Events +- %d Total Bets + +All events were processed successfully, and no issues were detected. + +Best regards, +The System`, greeting, period, counts.StatusEndedCount, totalBets) + + html := fmt.Sprintf(`
%s
+Period: %s
+All events were processed successfully, and no issues were detected.
+Best regards,
The System
%s
+Period: %s
+Next Steps:
Some events require your attention. Please log into the admin dashboard to review pending issues.
Best regards,
The System
%s
-All events were processed successfully, and no issues were detected.
-Best regards,
The System
%s
-Next Steps:
Some events require your attention. Please log into the admin dashboard to review pending issues.
Best regards,
The System