diff --git a/cmd/main.go b/cmd/main.go index 3660fa5..0969e35 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -46,6 +46,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/messenger" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/raffle" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation" referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/report" @@ -114,13 +115,11 @@ func main() { authSvc := authentication.NewService(store, store, cfg.RefreshExpiry) userSvc := user.NewService(store, store, messengerSvc, cfg) - eventSvc := event.New(cfg.Bet365Token, store, domain.MongoDBLogger) + eventSvc := event.New(cfg.Bet365Token, store, *settingSvc, domain.MongoDBLogger, cfg) oddsSvc := odds.New(store, cfg, eventSvc, logger, domain.MongoDBLogger) notificationRepo := repository.NewNotificationRepository(store) virtuaGamesRepo := repository.NewVirtualGameRepository(store) notificationSvc := notificationservice.New(notificationRepo, domain.MongoDBLogger, logger, cfg, messengerSvc, userSvc) - - var notificatioStore notificationservice.NotificationStore // var userStore user.UserStore // Initialize producer @@ -132,7 +131,6 @@ func main() { wallet.WalletStore(store), wallet.TransferStore(store), wallet.DirectDepositStore(store), - notificatioStore, notificationSvc, userSvc, domain.MongoDBLogger, @@ -145,17 +143,18 @@ func main() { leagueSvc := league.New(store) ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger, *settingSvc, notificationSvc) betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, *companySvc, *settingSvc, *userSvc, notificationSvc, logger, domain.MongoDBLogger) - resultSvc := result.NewService(store, cfg, logger, domain.MongoDBLogger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc, *userSvc) - bonusSvc := bonus.NewService(store) + resultSvc := result.NewService(store, cfg, logger, domain.MongoDBLogger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc, messengerSvc, *userSvc) + bonusSvc := bonus.NewService(store, walletSvc, settingSvc, notificationSvc, domain.MongoDBLogger) referalRepo := repository.NewReferralRepository(store) vitualGameRepo := repository.NewVirtualGameRepository(store) recommendationRepo := repository.NewRecommendationRepository(store) - referalSvc := referralservice.New(referalRepo, *walletSvc, store, cfg, logger) + referalSvc := referralservice.New(referalRepo, *walletSvc, *settingSvc, cfg, logger, domain.MongoDBLogger) + raffleSvc := raffle.NewService(store) virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, cfg, logger) aleaService := alea.NewAleaPlayService(vitualGameRepo, *walletSvc, cfg, logger) veliCLient := veli.NewClient(cfg, walletSvc) - veliVirtualGameService := veli.New(virtualGameSvc, vitualGameRepo, veliCLient, walletSvc, wallet.TransferStore(store), cfg) + veliVirtualGameService := veli.New(virtualGameSvc, vitualGameRepo, veliCLient, walletSvc, wallet.TransferStore(store), domain.MongoDBLogger, cfg) atlasClient := atlas.NewClient(cfg, walletSvc) atlasVirtualGameService := atlas.New(virtualGameSvc, vitualGameRepo, atlasClient, walletSvc, wallet.TransferStore(store), cfg) recommendationSvc := recommendation.NewService(recommendationRepo) @@ -285,6 +284,7 @@ func main() { eventSvc, leagueSvc, referalSvc, + raffleSvc, bonusSvc, virtualGameSvc, aleaService, diff --git a/db/data/001_initial_seed_data.sql b/db/data/001_initial_seed_data.sql new file mode 100644 index 0000000..95a21d8 --- /dev/null +++ b/db/data/001_initial_seed_data.sql @@ -0,0 +1,379 @@ +CREATE EXTENSION IF NOT EXISTS pgcrypto; +-- Locations Initial Data +INSERT INTO branch_locations (key, value) +VALUES ('addis_ababa', 'Addis Ababa'), + ('dire_dawa', 'Dire Dawa'), + ('mekelle', 'Mekelle'), + ('adama', 'Adama'), + ('awassa', 'Awassa'), + ('bahir_dar', 'Bahir Dar'), + ('gonder', 'Gonder'), + ('dessie', 'Dessie'), + ('jimma', 'Jimma'), + ('jijiga', 'Jijiga'), + ('shashamane', 'Shashamane'), + ('bishoftu', 'Bishoftu'), + ('sodo', 'Sodo'), + ('arba_minch', 'Arba Minch'), + ('hosaena', 'Hosaena'), + ('harar', 'Harar'), + ('dilla', 'Dilla'), + ('nekemte', 'Nekemte'), + ('debre_birhan', 'Debre Birhan'), + ('asella', 'Asella'), + ('debre_markos', 'Debre Markos'), + ('kombolcha', 'Kombolcha'), + ('debre_tabor', 'Debre Tabor'), + ('adigrat', 'Adigrat'), + ('areka', 'Areka'), + ('weldiya', 'Weldiya'), + ('sebeta', 'Sebeta'), + ('burayu', 'Burayu'), + ('shire', 'Shire'), + ('ambo', 'Ambo'), + ('arsi_negele', 'Arsi Negele'), + ('aksum', 'Aksum'), + ('gambela', 'Gambela'), + ('bale_robe', 'Bale Robe'), + ('butajira', 'Butajira'), + ('batu', 'Batu'), + ('boditi', 'Boditi'), + ('adwa', 'Adwa'), + ('yirgalem', 'Yirgalem'), + ('waliso', 'Waliso'), + ('welkite', 'Welkite'), + ('gode', 'Gode'), + ('meki', 'Meki'), + ('negele_borana', 'Negele Borana'), + ('alaba_kulito', 'Alaba Kulito'), + ('alamata,', 'Alamata,'), + ('chiro', 'Chiro'), + ('tepi', 'Tepi'), + ('durame', 'Durame'), + ('goba', 'Goba'), + ('assosa', 'Assosa'), + ('gimbi', 'Gimbi'), + ('wukro', 'Wukro'), + ('haramaya', 'Haramaya'), + ('mizan_teferi', 'Mizan Teferi'), + ('sawla', 'Sawla'), + ('mojo', 'Mojo'), + ('dembi_dolo', 'Dembi Dolo'), + ('aleta_wendo', 'Aleta Wendo'), + ('metu', 'Metu'), + ('mota', 'Mota'), + ('fiche', 'Fiche'), + ('finote_selam', 'Finote Selam'), + ('bule_hora_town', 'Bule Hora Town'), + ('bonga', 'Bonga'), + ('kobo', 'Kobo'), + ('jinka', 'Jinka'), + ('dangila', 'Dangila'), + ('degehabur', 'Degehabur'), + ('bedessa', 'Bedessa'), + ('agaro', 'Agaro') ON CONFLICT (key) DO +UPDATE +SET value = EXCLUDED.value; +-- Settings Initial Data +INSERT INTO global_settings (key, value) +VALUES ('sms_provider', 'afro_message'), + ('max_number_of_outcomes', '30'), + ('bet_amount_limit', '10000000'), + ('daily_ticket_limit', '50'), + ('total_winnings_limit', '1000000'), + ('amount_for_bet_referral', '1000000'), + ('cashback_amount_cap', '1000'), + ('default_winning_limit', '5000000'), + ('referral_reward_amount', '10000'), + ('cashback_percentage', '0.2'), + ('default_max_referrals', '15'), + ('minimum_bet_amount', '100'), + ('bet_duplicate_limit', '5'), + ('send_email_on_bet_finish', 'true'), + ('send_sms_on_bet_finish', 'false'), + ('welcome_bonus_active', 'false'), + ('welcome_bonus_multiplier', '1.5'), + ('welcome_bonus_cap', '100000'), + ('welcome_bonus_count', '3'), + ('welcome_bonus_expiry', '10') ON CONFLICT (key) DO NOTHING; +-- Users +INSERT INTO users ( + id, + first_name, + last_name, + email, + phone_number, + password, + role, + email_verified, + phone_verified, + created_at, + updated_at, + suspended, + company_id + ) +VALUES ( + 1, + 'John', + 'Doe', + 'john.doe@example.com', + NULL, + crypt('password@123', gen_salt('bf'))::bytea, + 'customer', + TRUE, + FALSE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + FALSE, + 1 + ), + ( + 2, + 'Test', + 'Admin', + 'test.admin@gmail.com', + '0988554466', + crypt('password@123', gen_salt('bf'))::bytea, + 'admin', + TRUE, + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + FALSE, + 1 + ), + ( + 3, + 'Samuel', + 'Tariku', + 'cybersamt@gmail.com', + '0911111111', + crypt('password@123', gen_salt('bf'))::bytea, + 'super_admin', + TRUE, + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + FALSE, + NULL + ), + ( + 4, + 'Kirubel', + 'Kibru', + 'kirubel.jkl679@gmail.com', + '0911554486', + crypt('password@123', gen_salt('bf'))::bytea, + 'super_admin', + TRUE, + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + FALSE, + NULL + ) ON CONFLICT (id) DO +UPDATE +SET first_name = EXCLUDED.first_name, + last_name = EXCLUDED.last_name, + email = EXCLUDED.email, + phone_number = EXCLUDED.phone_number, + password = EXCLUDED.password, + role = EXCLUDED.role, + email_verified = EXCLUDED.email_verified, + phone_verified = EXCLUDED.phone_verified, + created_at = EXCLUDED.created_at, + updated_at = EXCLUDED.updated_at, + suspended = EXCLUDED.suspended, + company_id = EXCLUDED.company_id; +-- Supported Operations +INSERT INTO supported_operations (id, name, description) +VALUES (1, 'SportBook', 'Sportbook operations'), + (2, 'Virtual', 'Virtual operations') ON CONFLICT (id) DO +UPDATE +SET name = EXCLUDED.name, + description = EXCLUDED.description; +-- Wallets +INSERT INTO wallets ( + id, + balance, + is_withdraw, + is_bettable, + is_transferable, + user_id, + type, + currency, + is_active, + created_at, + updated_at + ) +VALUES ( + 1, + 10000, + TRUE, + TRUE, + TRUE, + 1, + 'regular_wallet', + 'ETB', + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ), + ( + 2, + 5000, + FALSE, + TRUE, + TRUE, + 1, + 'static_wallet', + 'ETB', + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ), + ( + 3, + 100000000, + TRUE, + TRUE, + TRUE, + 2, + 'company_wallet', + 'ETB', + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ), + ( + 4, + 50000000, + TRUE, + TRUE, + TRUE, + 2, + 'branch_wallet', + 'ETB', + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ) ON CONFLICT (id) DO +UPDATE +SET balance = EXCLUDED.balance, + is_withdraw = EXCLUDED.is_withdraw, + is_bettable = EXCLUDED.is_bettable, + is_transferable = EXCLUDED.is_transferable, + user_id = EXCLUDED.user_id, + type = EXCLUDED.type, + currency = EXCLUDED.currency, + is_active = EXCLUDED.is_active, + created_at = EXCLUDED.created_at, + updated_at = EXCLUDED.updated_at; +-- Customer Wallets +INSERT INTO customer_wallets ( + id, + customer_id, + regular_wallet_id, + static_wallet_id + ) +VALUES (1, 1, 1, 2) ON CONFLICT (id) DO +UPDATE +SET customer_id = EXCLUDED.customer_id, + regular_wallet_id = EXCLUDED.regular_wallet_id, + static_wallet_id = EXCLUDED.static_wallet_id; +-- Company +INSERT INTO companies ( + id, + name, + slug, + admin_id, + wallet_id, + deducted_percentage, + is_active, + created_at, + updated_at + ) +VALUES ( + 1, + 'FortuneBets', + 'fortunebets', + 2, + 3, + 0.10, + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ) ON CONFLICT (id) DO +UPDATE +SET name = EXCLUDED.name, + slug = EXCLUDED.slug, + admin_id = EXCLUDED.admin_id, + wallet_id = EXCLUDED.wallet_id, + deducted_percentage = EXCLUDED.deducted_percentage, + is_active = EXCLUDED.is_active, + created_at = EXCLUDED.created_at, + updated_at = EXCLUDED.updated_at; +-- Branch +INSERT INTO branches ( + id, + name, + location, + wallet_id, + branch_manager_id, + company_id, + is_self_owned, + profit_percent, + is_active, + created_at, + updated_at + ) +VALUES ( + 1, + 'Test Branch', + 'addis_ababa', + 4, + 2, + 1, + TRUE, + 0.10, + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ) ON CONFLICT (id) DO +UPDATE +SET name = EXCLUDED.name, + location = EXCLUDED.location, + wallet_id = EXCLUDED.wallet_id, + branch_manager_id = EXCLUDED.branch_manager_id, + company_id = EXCLUDED.company_id, + is_self_owned = EXCLUDED.is_self_owned, + profit_percent = EXCLUDED.profit_percent, + is_active = EXCLUDED.is_active, + created_at = EXCLUDED.created_at, + updated_at = EXCLUDED.updated_at; +-- Bonus +INSERT INTO user_bonuses ( + id, + name, + description, + type, + user_id, + reward_amount, + expires_at + ) +VALUES ( + 1, + 'Welcome Bonus', + 'Awarded for deposit number (1 / 3)', + 'welcome_bonus', + 1, + 1000, + now() + INTERVAL '1 day' + ) ON CONFLICT (id) DO +UPDATE +SET name = EXCLUDED.name, + description = EXCLUDED.description, + type = EXCLUDED.type, + user_id = EXCLUDED.user_id, + reward_amount = EXCLUDED.reward_amount, + expires_at = EXCLUDED.expires_at; \ No newline at end of file diff --git a/db/data/002_veli_user.sql b/db/data/002_veli_user.sql new file mode 100644 index 0000000..1dfe96a --- /dev/null +++ b/db/data/002_veli_user.sql @@ -0,0 +1,148 @@ +-- Users +INSERT INTO users ( + id, + first_name, + last_name, + email, + phone_number, + password, + role, + email_verified, + phone_verified, + created_at, + updated_at, + suspended, + company_id + ) +VALUES ( + 5, + 'Test', + 'Veli', + 'test.veli@example.com', + NULL, + crypt('password@123', gen_salt('bf'))::bytea, + 'customer', + TRUE, + FALSE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + FALSE, + 1 + ), + ( + 6, + 'Kirubel', + 'Kibru', + 'modernkibru@gmail.com', + NULL, + crypt('password@123', gen_salt('bf'))::bytea, + 'customer', + TRUE, + FALSE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + FALSE, + 1 + ) ON CONFLICT (id) DO +UPDATE +SET first_name = EXCLUDED.first_name, + last_name = EXCLUDED.last_name, + email = EXCLUDED.email, + phone_number = EXCLUDED.phone_number, + password = EXCLUDED.password, + role = EXCLUDED.role, + email_verified = EXCLUDED.email_verified, + phone_verified = EXCLUDED.phone_verified, + created_at = EXCLUDED.created_at, + updated_at = EXCLUDED.updated_at, + suspended = EXCLUDED.suspended, + company_id = EXCLUDED.company_id; +INSERT INTO wallets ( + id, + balance, + is_withdraw, + is_bettable, + is_transferable, + user_id, + type, + currency, + is_active, + created_at, + updated_at + ) +VALUES ( + 5, + 10000, + TRUE, + TRUE, + TRUE, + 1, + 'regular_wallet', + 'ETB', + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ), + ( + 6, + 5000, + FALSE, + TRUE, + TRUE, + 1, + 'static_wallet', + 'ETB', + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ), + ( + 7, + 1000000, + TRUE, + TRUE, + TRUE, + 1, + 'regular_wallet', + 'ETB', + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ), + ( + 8, + 5000, + FALSE, + TRUE, + TRUE, + 1, + 'static_wallet', + 'ETB', + TRUE, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ) ON CONFLICT (id) DO +UPDATE +SET balance = EXCLUDED.balance, + is_withdraw = EXCLUDED.is_withdraw, + is_bettable = EXCLUDED.is_bettable, + is_transferable = EXCLUDED.is_transferable, + user_id = EXCLUDED.user_id, + type = EXCLUDED.type, + currency = EXCLUDED.currency, + is_active = EXCLUDED.is_active, + created_at = EXCLUDED.created_at, + updated_at = EXCLUDED.updated_at; +-- Customer Wallets +INSERT INTO customer_wallets ( + id, + customer_id, + regular_wallet_id, + static_wallet_id + ) +VALUES (2, 5, 5, 6), + (3, 6, 7, 8) ON CONFLICT (id) DO +UPDATE +SET customer_id = EXCLUDED.customer_id, + regular_wallet_id = EXCLUDED.regular_wallet_id, + static_wallet_id = EXCLUDED.static_wallet_id; \ No newline at end of file diff --git a/db/data/003_fix_autoincrement_desync.sql b/db/data/003_fix_autoincrement_desync.sql new file mode 100644 index 0000000..835e10e --- /dev/null +++ b/db/data/003_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/db/data/seed_data.sql b/db/data/seed_data.sql deleted file mode 100644 index 860d7e5..0000000 --- a/db/data/seed_data.sql +++ /dev/null @@ -1,222 +0,0 @@ -BEGIN; -CREATE EXTENSION IF NOT EXISTS pgcrypto; --- Users -INSERT INTO users ( - id, - first_name, - last_name, - email, - phone_number, - password, - role, - email_verified, - phone_verified, - created_at, - updated_at, - suspended, - company_id - ) -VALUES ( - 1, - 'John', - 'Doe', - 'john.doe@example.com', - NULL, - crypt('password@123', gen_salt('bf'))::bytea, - 'customer', - TRUE, - FALSE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP, - FALSE, - NULL - ), - ( - 2, - 'Test', - 'Admin', - 'test.admin@gmail.com', - '0988554466', - crypt('password123', gen_salt('bf'))::bytea, - 'admin', - TRUE, - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP, - FALSE, - 1 - ), - ( - 3, - 'Samuel', - 'Tariku', - 'cybersamt@gmail.com', - '0911111111', - crypt('password@123', gen_salt('bf'))::bytea, - 'super_admin', - TRUE, - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP, - FALSE, - NULL - ), - ( - 4, - 'Kirubel', - 'Kibru', - 'kirubel.jkl679@gmail.com', - '0911554486', - crypt('password@123', gen_salt('bf'))::bytea, - 'super_admin', - TRUE, - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP, - FALSE, - NULL - ), - ( - 5, - 'Test', - 'Veli', - 'test.veli@example.com', - NULL, - crypt('password@123', gen_salt('bf'))::bytea, - 'customer', - TRUE, - FALSE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP, - FALSE, - NULL - ); --- Supported Operations -INSERT INTO supported_operations (id, name, description) -VALUES (1, 'SportBook', 'Sportbook operations'), - (2, 'Virtual', 'Virtual operations'); --- Wallets -INSERT INTO wallets ( - id, - balance, - is_withdraw, - is_bettable, - is_transferable, - user_id, - type, - currency, - is_active, - created_at, - updated_at - ) -VALUES ( - 1, - 10000, - TRUE, - TRUE, - TRUE, - 1, - 'regular', - 'ETB', - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP - ), - ( - 2, - 5000, - FALSE, - TRUE, - TRUE, - 1, - 'static', - 'ETB', - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP - ), - ( - 3, - 20000, - TRUE, - TRUE, - TRUE, - 2, - 'company_main', - 'ETB', - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP - ), - ( - 4, - 15000, - TRUE, - TRUE, - TRUE, - 2, - 'branch_main', - 'ETB', - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP - ); --- Customer Wallets -INSERT INTO customer_wallets ( - id, - customer_id, - regular_wallet_id, - static_wallet_id - ) -VALUES (1, 1, 1, 2); --- Company -INSERT INTO companies ( - id, - name, - slug, - admin_id, - wallet_id, - deducted_percentage, - is_active, - created_at, - updated_at - ) -VALUES ( - 1, - 'FortuneBets', - 'fortunebets', - 2, - 3, - 0.10, - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP - ); --- Branch -INSERT INTO branches ( - id, - name, - location, - wallet_id, - branch_manager_id, - company_id, - is_self_owned, - profit_percent, - is_active, - created_at, - updated_at - ) -VALUES ( - 1, - 'Test Branch', - 'addis_ababa', - 4, - 2, - 1, - TRUE, - 0.10, - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP - ); -COMMIT; \ No newline at end of file diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 710fbd7..c6c550d 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -18,42 +18,42 @@ CREATE TABLE IF NOT EXISTS users ( email IS NOT NULL OR phone_number IS NOT NULL ), - UNIQUE(email, company_id), + UNIQUE (email, company_id), UNIQUE (phone_number, company_id) ); - CREATE TABLE IF NOT EXISTS virtual_game_providers ( id BIGSERIAL PRIMARY KEY, - provider_id VARCHAR(100) UNIQUE NOT NULL, -- providerId from Veli Games - provider_name VARCHAR(255) NOT NULL, -- providerName - logo_dark TEXT, -- logoForDark (URL) - logo_light TEXT, -- logoForLight (URL) - enabled BOOLEAN NOT NULL DEFAULT TRUE, -- allow enabling/disabling providers + provider_id VARCHAR(100) UNIQUE NOT NULL, + -- providerId from Veli Games + provider_name VARCHAR(255) NOT NULL, + -- providerName + logo_dark TEXT, + -- logoForDark (URL) + logo_light TEXT, + -- logoForLight (URL) + enabled BOOLEAN NOT NULL DEFAULT TRUE, + -- allow enabling/disabling providers created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ ); - CREATE TABLE IF NOT EXISTS virtual_games ( id BIGSERIAL PRIMARY KEY, - game_id VARCHAR(150) NOT NULL, + game_id VARCHAR(150) NOT NULL, provider_id VARCHAR(100) NOT NULL REFERENCES virtual_game_providers(provider_id) ON DELETE CASCADE, - name VARCHAR(255) NOT NULL, - category VARCHAR(100), - device_type VARCHAR(100), - volatility VARCHAR(50), - rtp NUMERIC(5,2), + name VARCHAR(255) NOT NULL, + category VARCHAR(100), + device_type VARCHAR(100), + volatility VARCHAR(50), + rtp NUMERIC(5, 2), has_demo BOOLEAN DEFAULT FALSE, has_free_bets BOOLEAN DEFAULT FALSE, - bets NUMERIC[] DEFAULT '{}', - thumbnail TEXT, - status INT, + bets NUMERIC [] DEFAULT '{}', + thumbnail TEXT, + status INT, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ ); - -CREATE UNIQUE INDEX IF NOT EXISTS ux_virtual_games_provider_game - ON virtual_games (provider_id, game_id); - +CREATE UNIQUE INDEX IF NOT EXISTS ux_virtual_games_provider_game ON virtual_games (provider_id, game_id); CREATE TABLE IF NOT EXISTS wallets ( id BIGSERIAL PRIMARY KEY, balance BIGINT NOT NULL DEFAULT 0, @@ -62,7 +62,14 @@ CREATE TABLE IF NOT EXISTS wallets ( is_bettable BOOLEAN NOT NULL, is_transferable BOOLEAN NOT NULL, user_id BIGINT NOT NULL, - type VARCHAR(255) NOT NULL, + type TEXT NOT NULL CHECK ( + type IN ( + 'regular_wallet', + 'static_wallet', + 'branch_wallet', + 'company_wallet' + ) + ), is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP @@ -118,7 +125,7 @@ CREATE TABLE exchange_rates ( to_currency VARCHAR(3) NOT NULL, rate DECIMAL(19, 6) NOT NULL, valid_until TIMESTAMP NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), + created_at TIMESTAMP NOT NULL DEFAULT NOW (), UNIQUE (from_currency, to_currency) ); CREATE TABLE IF NOT EXISTS bet_outcomes ( @@ -186,7 +193,8 @@ CREATE TABLE IF NOT EXISTS wallets ( type VARCHAR(255) NOT NULL, is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_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, @@ -242,9 +250,9 @@ CREATE TABLE IF NOT EXISTS shop_bets ( cashed_out BOOLEAN DEFAULT FALSE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(shop_transaction_id), - UNIQUE(bet_id), - UNIQUE(cashout_id) + UNIQUE (shop_transaction_id), + UNIQUE (bet_id), + UNIQUE (cashout_id) ); CREATE TABLE IF NOT EXISTS shop_deposits ( id BIGSERIAL PRIMARY KEY, @@ -254,7 +262,7 @@ CREATE TABLE IF NOT EXISTS shop_deposits ( branch_wallet_id BIGINT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(shop_transaction_id) + UNIQUE (shop_transaction_id) ); CREATE TABLE IF NOT EXISTS branches ( id BIGSERIAL PRIMARY KEY, @@ -268,7 +276,7 @@ CREATE TABLE IF NOT EXISTS branches ( is_self_owned BOOLEAN NOT NULL DEFAULT false, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(wallet_id), + UNIQUE (wallet_id), CONSTRAINT profit_percentage_check CHECK ( profit_percent >= 0 AND profit_percent < 1 @@ -285,12 +293,9 @@ CREATE TABLE IF NOT EXISTS branch_cashiers ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL, branch_id BIGINT NOT NULL, - UNIQUE(user_id, branch_id) -); -CREATE TABLE IF NOT EXISTS branch_locations ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL + UNIQUE (user_id, branch_id) ); +CREATE TABLE IF NOT EXISTS branch_locations (key TEXT PRIMARY KEY, value TEXT NOT NULL); CREATE TABLE events ( id TEXT PRIMARY KEY, sport_id INT NOT NULL, @@ -311,21 +316,15 @@ CREATE TABLE events ( match_period INT, is_live BOOLEAN NOT NULL DEFAULT false, status TEXT NOT NULL, - fetched_at TIMESTAMP DEFAULT now(), + fetched_at TIMESTAMP DEFAULT now (), source TEXT NOT NULL DEFAULT 'b365api' CHECK ( - source IN ( - 'b365api', - 'bfair', - '1xbet', - 'bwin', - 'enetpulse' - ) + source IN ('b365api', 'bfair', '1xbet', 'bwin', 'enetpulse') ), default_is_active BOOLEAN NOT NULL DEFAULT true, default_is_featured BOOLEAN NOT NULL DEFAULT false, - default_winning_upper_limit INT NOT NULL, + default_winning_upper_limit BIGINT NOT NULL, is_monitored BOOLEAN NOT NULL DEFAULT FALSE, - UNIQUE(id, source) + UNIQUE (id, source) ); CREATE TABLE event_history ( id BIGSERIAL PRIMARY KEY, @@ -341,7 +340,7 @@ CREATE TABLE company_event_settings ( is_featured BOOLEAN, winning_upper_limit INT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(company_id, event_id) + UNIQUE (company_id, event_id) ); CREATE TABLE odds_market ( id BIGSERIAL PRIMARY KEY, @@ -352,13 +351,13 @@ CREATE TABLE odds_market ( market_id TEXT NOT NULL, raw_odds JSONB NOT NULL, default_is_active BOOLEAN NOT NULL DEFAULT true, - fetched_at TIMESTAMP DEFAULT now(), + fetched_at TIMESTAMP DEFAULT now (), expires_at TIMESTAMP NOT NULL, UNIQUE (event_id, market_id) ); CREATE TABLE odd_history ( id BIGSERIAL PRIMARY KEY, - odds_market_id BIGINT NOT NULL REFERENCES odds_market(id), + odds_market_id BIGINT NOT NULL REFERENCES odds_market (id), raw_odd_id BIGINT NOT NULL, market_id TEXT NOT NULL, event_id TEXT NOT NULL, @@ -380,7 +379,7 @@ CREATE TABLE company_odd_settings ( is_active BOOLEAN, custom_raw_odds JSONB, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(company_id, odds_market_id) + UNIQUE (company_id, odds_market_id) ); CREATE TABLE result_log ( id BIGSERIAL PRIMARY KEY, @@ -430,7 +429,7 @@ CREATE TABLE company_league_settings ( is_active BOOLEAN, is_featured BOOLEAN, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(league_id, company_id) + UNIQUE (league_id, company_id) ); CREATE TABLE teams ( id BIGSERIAL PRIMARY KEY, @@ -447,24 +446,32 @@ CREATE TABLE IF NOT EXISTS global_settings ( ); -- Tenant/Company-specific overrides CREATE TABLE IF NOT EXISTS company_settings ( - company_id BIGINT NOT NULL REFERENCES companies(id) ON DELETE CASCADE, + company_id BIGINT NOT NULL REFERENCES companies (id) ON DELETE CASCADE, key TEXT NOT NULL, value TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (company_id, key) ); -CREATE TABLE bonus ( - multiplier REAL NOT NULL, +CREATE TABLE user_bonuses ( id BIGSERIAL PRIMARY KEY, - balance_cap BIGINT NOT NULL DEFAULT 0 + name TEXT NOT NULL, + description TEXT NOT NULL, + type TEXT NOT NULL, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + reward_amount BIGINT NOT NULL, + is_claimed BOOLEAN NOT NULL DEFAULT false, + expires_at TIMESTAMP NOT NULL, + claimed_at TIMESTAMP, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE flags ( id BIGSERIAL PRIMARY KEY, - bet_id BIGINT REFERENCES bets(id) ON DELETE CASCADE, - odds_market_id BIGINT REFERENCES odds_market(id), + bet_id BIGINT REFERENCES bets (id) ON DELETE CASCADE, + odds_market_id BIGINT REFERENCES odds_market (id), reason TEXT, - flagged_at TIMESTAMP DEFAULT NOW(), + flagged_at TIMESTAMP DEFAULT NOW (), resolved BOOLEAN DEFAULT FALSE, -- either bet or odd is flagged (not at the same time) CHECK ( @@ -480,21 +487,56 @@ CREATE TABLE flags ( ); CREATE TABLE direct_deposits ( id BIGSERIAL PRIMARY KEY, - customer_id BIGINT NOT NULL REFERENCES users(id), - wallet_id BIGINT NOT NULL REFERENCES wallets(id), + customer_id BIGINT NOT NULL REFERENCES users (id), + wallet_id BIGINT NOT NULL REFERENCES wallets (id), amount NUMERIC(15, 2) NOT NULL, bank_reference TEXT NOT NULL, sender_account TEXT NOT NULL, status TEXT NOT NULL CHECK (status IN ('pending', 'completed', 'rejected')), - created_at TIMESTAMP NOT NULL DEFAULT NOW(), - verified_by BIGINT REFERENCES users(id), + created_at TIMESTAMP NOT NULL DEFAULT NOW (), + verified_by BIGINT REFERENCES users (id), verification_notes TEXT, verified_at TIMESTAMP ); -CREATE INDEX idx_direct_deposits_status ON direct_deposits(status); -CREATE INDEX idx_direct_deposits_customer ON direct_deposits(customer_id); -CREATE INDEX idx_direct_deposits_reference ON direct_deposits(bank_reference); --- Views +CREATE INDEX idx_direct_deposits_status ON direct_deposits (status); +CREATE INDEX idx_direct_deposits_customer ON direct_deposits (customer_id); +CREATE INDEX idx_direct_deposits_reference ON direct_deposits (bank_reference); +CREATE TABLE IF NOT EXISTS raffles ( + id SERIAL PRIMARY KEY, + company_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + expires_at TIMESTAMP NOT NULL, + type VARCHAR(50) NOT NULL CHECK (type IN ('virtual', 'sport')), + status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'completed')) +); +CREATE TABLE IF NOT EXISTS raffle_tickets ( + id SERIAL PRIMARY KEY, + raffle_id INT NOT NULL REFERENCES raffles(id) ON DELETE CASCADE, + user_id INT NOT NULL, + is_active BOOL DEFAULT true +); +CREATE TABLE IF NOT EXISTS raffle_winners ( + id SERIAL PRIMARY KEY, + raffle_id INT NOT NULL REFERENCES raffles(id) ON DELETE CASCADE, + user_id INT NOT NULL, + rank INT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT NOW() +); +CREATE TABLE IF NOT EXISTS raffle_sport_filters ( + id SERIAL PRIMARY KEY, + raffle_id INT NOT NULL REFERENCES raffles(id) ON DELETE CASCADE, + sport_id BIGINT NOT NULL, + league_id BIGINT NOT NULL, + CONSTRAINT unique_raffle_sport_league UNIQUE (raffle_id, sport_id, league_id) +); +CREATE TABLE IF NOT EXISTS raffle_game_filters ( + id SERIAL PRIMARY KEY, + raffle_id INT NOT NULL REFERENCES raffles(id) ON DELETE CASCADE, + game_id VARCHAR(150) NOT NULL, + CONSTRAINT unique_raffle_game UNIQUE (raffle_id, game_id) +); +------ Views CREATE VIEW companies_details AS SELECT companies.*, wallets.balance, @@ -508,7 +550,7 @@ FROM companies ; CREATE VIEW branch_details AS SELECT branches.*, - CONCAT(users.first_name, ' ', users.last_name) AS manager_name, + CONCAT (users.first_name, ' ', users.last_name) AS manager_name, users.phone_number AS manager_phone_number, wallets.balance, wallets.is_active AS wallet_is_active @@ -522,9 +564,9 @@ CREATE TABLE IF NOT EXISTS supported_operations ( ); CREATE VIEW bet_with_outcomes AS SELECT bets.*, - CONCAT(users.first_name, ' ', users.last_name) AS full_name, + 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 FROM bets LEFT JOIN bet_outcomes ON bets.id = bet_outcomes.bet_id LEFT JOIN users ON bets.user_id = users.id @@ -534,7 +576,7 @@ GROUP BY bets.id, users.phone_number; CREATE VIEW ticket_with_outcomes AS SELECT tickets.*, - JSON_AGG(ticket_outcomes.*) AS outcomes + JSON_AGG (ticket_outcomes.*) AS outcomes FROM tickets LEFT JOIN ticket_outcomes ON tickets.id = ticket_outcomes.ticket_id GROUP BY tickets.id; @@ -588,7 +630,7 @@ SELECT sb.*, st.verified AS transaction_verified, bets.status, bets.total_odds, - JSON_AGG(bet_outcomes.*) AS outcomes + JSON_AGG (bet_outcomes.*) AS outcomes FROM shop_bets AS sb JOIN shop_transactions st ON st.id = sb.shop_transaction_id JOIN bets ON bets.id = sb.bet_id @@ -619,7 +661,7 @@ SELECT l.*, COALESCE(cls.is_featured, l.default_is_featured) AS is_featured, cls.updated_at FROM leagues l - JOIN company_league_settings cls ON l.id = cls.league_id; + LEFT JOIN company_league_settings cls ON l.id = cls.league_id; CREATE VIEW event_with_settings AS SELECT e.*, ces.company_id, @@ -632,7 +674,7 @@ SELECT e.*, ces.updated_at, l.country_code as league_cc FROM events e - JOIN company_event_settings ces ON e.id = ces.event_id + LEFT JOIN company_event_settings ces ON e.id = ces.event_id JOIN leagues l ON l.id = e.league_id; CREATE VIEW event_with_country AS SELECT events.*, @@ -654,7 +696,7 @@ SELECT o.id, COALESCE(cos.custom_raw_odds, o.raw_odds) AS raw_odds, cos.updated_at FROM odds_market o - JOIN company_odd_settings cos ON o.id = cos.odds_market_id; + LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id; CREATE VIEW odds_market_with_event AS SELECT o.*, e.is_monitored, @@ -665,47 +707,47 @@ FROM odds_market o JOIN events e ON o.event_id = e.id; -- Foreign Keys ALTER TABLE refresh_tokens -ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users(id); +ADD CONSTRAINT fk_refresh_tokens_users FOREIGN KEY (user_id) REFERENCES users (id); ALTER TABLE bets -ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id); +ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users (id); ALTER TABLE wallets -ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id); +ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users (id); ALTER TABLE customer_wallets -ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users(id), - ADD CONSTRAINT fk_customer_wallets_regular_wallet FOREIGN KEY (regular_wallet_id) REFERENCES wallets(id), - ADD CONSTRAINT fk_customer_wallets_static_wallet FOREIGN KEY (static_wallet_id) REFERENCES wallets(id); +ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users (id), + ADD CONSTRAINT fk_customer_wallets_regular_wallet FOREIGN KEY (regular_wallet_id) REFERENCES wallets (id), + ADD CONSTRAINT fk_customer_wallets_static_wallet FOREIGN KEY (static_wallet_id) REFERENCES wallets (id); ALTER TABLE wallet_transfer -ADD CONSTRAINT fk_wallet_transfer_receiver_wallet FOREIGN KEY (receiver_wallet_id) REFERENCES wallets(id), - ADD CONSTRAINT fk_wallet_transfer_sender_wallet FOREIGN KEY (sender_wallet_id) REFERENCES wallets(id), - ADD CONSTRAINT fk_wallet_transfer_cashier FOREIGN KEY (cashier_id) REFERENCES users(id); +ADD CONSTRAINT fk_wallet_transfer_receiver_wallet FOREIGN KEY (receiver_wallet_id) REFERENCES wallets (id), + ADD CONSTRAINT fk_wallet_transfer_sender_wallet FOREIGN KEY (sender_wallet_id) REFERENCES wallets (id), + ADD CONSTRAINT fk_wallet_transfer_cashier FOREIGN KEY (cashier_id) REFERENCES users (id); ALTER TABLE shop_transactions -ADD CONSTRAINT fk_shop_transactions_branches FOREIGN KEY (branch_id) REFERENCES branches(id), - ADD CONSTRAINT fk_shop_transactions_users FOREIGN KEY (user_id) REFERENCES users(id); +ADD CONSTRAINT fk_shop_transactions_branches FOREIGN KEY (branch_id) REFERENCES branches (id), + ADD CONSTRAINT fk_shop_transactions_users FOREIGN KEY (user_id) REFERENCES users (id); ALTER TABLE shop_bets -ADD CONSTRAINT fk_shop_bet_transactions FOREIGN KEY (shop_transaction_id) REFERENCES shop_transactions(id), - ADD CONSTRAINT fk_shop_bet_bets FOREIGN KEY (bet_id) REFERENCES bets(id); +ADD CONSTRAINT fk_shop_bet_transactions FOREIGN KEY (shop_transaction_id) REFERENCES shop_transactions (id), + ADD CONSTRAINT fk_shop_bet_bets FOREIGN KEY (bet_id) REFERENCES bets (id); ALTER TABLE shop_deposits -ADD CONSTRAINT fk_shop_deposit_transactions FOREIGN KEY (shop_transaction_id) REFERENCES shop_transactions(id), - ADD CONSTRAINT fk_shop_deposit_customers FOREIGN KEY (customer_id) REFERENCES users(id); +ADD CONSTRAINT fk_shop_deposit_transactions FOREIGN KEY (shop_transaction_id) REFERENCES shop_transactions (id), + ADD CONSTRAINT fk_shop_deposit_customers FOREIGN KEY (customer_id) REFERENCES users (id); ALTER TABLE branches -ADD CONSTRAINT fk_branches_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id), - ADD CONSTRAINT fk_branches_manager FOREIGN KEY (branch_manager_id) REFERENCES users(id), - ADD CONSTRAINT fk_branches_location FOREIGN KEY (location) REFERENCES branch_locations(key); +ADD CONSTRAINT fk_branches_wallet FOREIGN KEY (wallet_id) REFERENCES wallets (id), + ADD CONSTRAINT fk_branches_manager FOREIGN KEY (branch_manager_id) REFERENCES users (id), + ADD CONSTRAINT fk_branches_location FOREIGN KEY (location) REFERENCES branch_locations (key); ALTER TABLE branch_operations -ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERENCES supported_operations(id) ON DELETE CASCADE, - ADD CONSTRAINT fk_branch_operations_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE; +ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERENCES supported_operations (id) ON DELETE CASCADE, + ADD CONSTRAINT fk_branch_operations_branches FOREIGN KEY (branch_id) REFERENCES branches (id) ON DELETE CASCADE; ALTER TABLE branch_cashiers -ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE; +ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, + ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches (id) ON DELETE CASCADE; ALTER TABLE companies -ADD CONSTRAINT fk_companies_admin FOREIGN KEY (admin_id) REFERENCES users(id), - ADD CONSTRAINT fk_companies_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id) ON DELETE CASCADE; +ADD CONSTRAINT fk_companies_admin FOREIGN KEY (admin_id) REFERENCES users (id), + ADD CONSTRAINT fk_companies_wallet FOREIGN KEY (wallet_id) REFERENCES wallets (id) ON DELETE CASCADE; ALTER TABLE company_league_settings -ADD CONSTRAINT fk_league_settings_company FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE, - ADD CONSTRAINT fk_league_settings_league FOREIGN KEY (league_id) REFERENCES leagues(id) ON DELETE CASCADE; +ADD CONSTRAINT fk_league_settings_company FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE, + ADD CONSTRAINT fk_league_settings_league FOREIGN KEY (league_id) REFERENCES leagues (id) ON DELETE CASCADE; ALTER TABLE company_event_settings -ADD CONSTRAINT fk_event_settings_company FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE, - ADD CONSTRAINT fk_event_settings_event FOREIGN KEY (event_id) REFERENCES events(id) ON DELETE CASCADE; +ADD CONSTRAINT fk_event_settings_company FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE, + ADD CONSTRAINT fk_event_settings_event FOREIGN KEY (event_id) REFERENCES events (id) ON DELETE CASCADE; ALTER TABLE company_odd_settings -ADD CONSTRAINT fk_odds_settings_company FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE, - ADD CONSTRAINT fk_odds_settings_odds_market FOREIGN KEY (odds_market_id) REFERENCES odds_market(id) ON DELETE CASCADE; \ No newline at end of file +ADD CONSTRAINT fk_odds_settings_company FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE, + ADD CONSTRAINT fk_odds_settings_odds_market FOREIGN KEY (odds_market_id) REFERENCES odds_market (id) ON DELETE CASCADE; diff --git a/db/migrations/000002_notification.up.sql b/db/migrations/000002_notification.up.sql index 1845f48..8fd9ad8 100644 --- a/db/migrations/000002_notification.up.sql +++ b/db/migrations/000002_notification.up.sql @@ -18,7 +18,8 @@ CREATE TABLE IF NOT EXISTS notifications ( 'admin_alert', 'bet_result', 'transfer_rejected', - 'approval_required' + 'approval_required', + 'bonus_awarded' ) ), level TEXT NOT NULL CHECK (level IN ('info', 'error', 'warning', 'success')), diff --git a/db/migrations/000003_referal.up.sql b/db/migrations/000003_referal.up.sql index 521e3e3..badf443 100644 --- a/db/migrations/000003_referal.up.sql +++ b/db/migrations/000003_referal.up.sql @@ -1,46 +1,37 @@ -CREATE TYPE ReferralStatus AS ENUM ('PENDING', 'COMPLETED', 'EXPIRED', 'CANCELLED'); -CREATE TABLE IF NOT EXISTS referral_settings ( - id BIGSERIAL PRIMARY KEY, - referral_reward_amount DECIMAL(15, 2) NOT NULL DEFAULT 0.00, - cashback_percentage DECIMAL(5, 2) NOT NULL DEFAULT 0.00, - bet_referral_bonus_percentage NUMERIC DEFAULT 5.0, - max_referrals INTEGER NOT NULL DEFAULT 0, - expires_after_days INTEGER NOT NULL DEFAULT 30, - updated_by VARCHAR(255) NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - version INTEGER NOT NULL DEFAULT 0, - CONSTRAINT referral_reward_amount_positive CHECK (referral_reward_amount >= 0), - CONSTRAINT cashback_percentage_range CHECK ( - cashback_percentage >= 0 - AND cashback_percentage <= 100 - ) -); -CREATE TABLE IF NOT EXISTS referrals ( +-- CREATE TYPE ReferralStatus AS ENUM ('PENDING', 'COMPLETED', 'EXPIRED', 'CANCELLED'); +-- CREATE TABLE IF NOT EXISTS referral_settings ( +-- id BIGSERIAL PRIMARY KEY, +-- referral_reward_amount DECIMAL(15, 2) NOT NULL DEFAULT 0.00, +-- cashback_percentage DECIMAL(5, 2) NOT NULL DEFAULT 0.00, +-- bet_referral_bonus_percentage NUMERIC DEFAULT 5.0, +-- max_referrals INTEGER NOT NULL DEFAULT 0, +-- expires_after_days INTEGER NOT NULL DEFAULT 30, +-- updated_by VARCHAR(255) NOT NULL, +-- created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, +-- updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, +-- version INTEGER NOT NULL DEFAULT 0, +-- CONSTRAINT referral_reward_amount_positive CHECK (referral_reward_amount >= 0), +-- CONSTRAINT cashback_percentage_range CHECK ( +-- cashback_percentage >= 0 +-- AND cashback_percentage <= 100 +-- ) +-- ); +CREATE TABLE IF NOT EXISTS referral_codes ( id BIGSERIAL PRIMARY KEY, referral_code VARCHAR(10) NOT NULL UNIQUE, - referrer_id VARCHAR(255) NOT NULL, - referred_id VARCHAR(255) UNIQUE, - status ReferralStatus NOT NULL DEFAULT 'PENDING', - reward_amount DECIMAL(15, 2) NOT NULL DEFAULT 0.00, - cashback_amount DECIMAL(15, 2) NOT NULL DEFAULT 0.00, + referrer_id BIGINT NOT NULL UNIQUE REFERENCES users (id), + company_id BIGINT NOT NULL REFERENCES companies (id), + is_active BOOLEAN NOT NULL DEFAULT true, + number_of_referrals BIGINT NOT NULL, + reward_amount BIGINT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMPTZ NOT NULL, - -- FOREIGN KEY (referrer_id) REFERENCES users (id), - -- FOREIGN KEY (referred_id) REFERENCES users (id), - CONSTRAINT reward_amount_positive CHECK (reward_amount >= 0), - CONSTRAINT cashback_amount_positive CHECK (cashback_amount >= 0) + CONSTRAINT reward_amount_positive CHECK (reward_amount >= 0) ); -CREATE INDEX idx_referrals_referral_code ON referrals (referral_code); -CREATE INDEX idx_referrals_referrer_id ON referrals (referrer_id); -CREATE INDEX idx_referrals_status ON referrals (status); -ALTER TABLE users -ADD COLUMN IF NOT EXISTS referral_code VARCHAR(10) UNIQUE, - ADD COLUMN IF NOT EXISTS referred_by VARCHAR(10); --- Modify wallet table to track bonus money separately -ALTER TABLE wallets -ADD COLUMN IF NOT EXISTS bonus_balance DECIMAL(15, 2) NOT NULL DEFAULT 0.00, - ADD COLUMN IF NOT EXISTS cash_balance DECIMAL(15, 2) NOT NULL DEFAULT 0.00, - ADD CONSTRAINT bonus_balance_positive CHECK (bonus_balance >= 0), - ADD CONSTRAINT cash_balance_positive CHECK (cash_balance >= 0); \ No newline at end of file +CREATE INDEX idx_referrals_referrer_id ON referral_codes (referrer_id); +CREATE TABLE IF NOT EXISTS user_referrals ( + id BIGSERIAL PRIMARY KEY, + referred_id BIGINT UNIQUE NOT NULL REFERENCES users (id), + referral_code_id BIGINT NOT NULL REFERENCES referral_codes (id), + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/db/migrations/000007_setting_data.up.sql b/db/migrations/000007_setting_data.up.sql index a381c02..9ae381a 100644 --- a/db/migrations/000007_setting_data.up.sql +++ b/db/migrations/000007_setting_data.up.sql @@ -1,11 +1,11 @@ --- Settings Initial Data -INSERT INTO global_settings (key, value) -VALUES ('sms_provider', 'afro_message'), - ('max_number_of_outcomes', '30'), - ('bet_amount_limit', '10000000'), - ('daily_ticket_limit', '50'), - ('total_winnings_limit', '1000000'), - ('amount_for_bet_referral', '1000000'), - ('cashback_amount_cap', '1000') ON CONFLICT (key) DO -UPDATE -SET value = EXCLUDED.value; \ No newline at end of file +-- -- Settings Initial Data +-- INSERT INTO global_settings (key, value) +-- VALUES ('sms_provider', 'afro_message'), +-- ('max_number_of_outcomes', '30'), +-- ('bet_amount_limit', '10000000'), +-- ('daily_ticket_limit', '50'), +-- ('total_winnings_limit', '1000000'), +-- ('amount_for_bet_referral', '1000000'), +-- ('cashback_amount_cap', '1000') ON CONFLICT (key) DO +-- UPDATE +-- SET value = EXCLUDED.value; \ No newline at end of file diff --git a/db/migrations/000009_location_data.up.sql b/db/migrations/000009_location_data.up.sql index 156831d..1f88a7f 100644 --- a/db/migrations/000009_location_data.up.sql +++ b/db/migrations/000009_location_data.up.sql @@ -1,75 +1,75 @@ --- Locations Initial Data -INSERT INTO branch_locations (key, value) -VALUES ('addis_ababa', 'Addis Ababa'), - ('dire_dawa', 'Dire Dawa'), - ('mekelle', 'Mekelle'), - ('adama', 'Adama'), - ('awassa', 'Awassa'), - ('bahir_dar', 'Bahir Dar'), - ('gonder', 'Gonder'), - ('dessie', 'Dessie'), - ('jimma', 'Jimma'), - ('jijiga', 'Jijiga'), - ('shashamane', 'Shashamane'), - ('bishoftu', 'Bishoftu'), - ('sodo', 'Sodo'), - ('arba_minch', 'Arba Minch'), - ('hosaena', 'Hosaena'), - ('harar', 'Harar'), - ('dilla', 'Dilla'), - ('nekemte', 'Nekemte'), - ('debre_birhan', 'Debre Birhan'), - ('asella', 'Asella'), - ('debre_markos', 'Debre Markos'), - ('kombolcha', 'Kombolcha'), - ('debre_tabor', 'Debre Tabor'), - ('adigrat', 'Adigrat'), - ('areka', 'Areka'), - ('weldiya', 'Weldiya'), - ('sebeta', 'Sebeta'), - ('burayu', 'Burayu'), - ('shire', 'Shire'), - ('ambo', 'Ambo'), - ('arsi_negele', 'Arsi Negele'), - ('aksum', 'Aksum'), - ('gambela', 'Gambela'), - ('bale_robe', 'Bale Robe'), - ('butajira', 'Butajira'), - ('batu', 'Batu'), - ('boditi', 'Boditi'), - ('adwa', 'Adwa'), - ('yirgalem', 'Yirgalem'), - ('waliso', 'Waliso'), - ('welkite', 'Welkite'), - ('gode', 'Gode'), - ('meki', 'Meki'), - ('negele_borana', 'Negele Borana'), - ('alaba_kulito', 'Alaba Kulito'), - ('alamata,', 'Alamata,'), - ('chiro', 'Chiro'), - ('tepi', 'Tepi'), - ('durame', 'Durame'), - ('goba', 'Goba'), - ('assosa', 'Assosa'), - ('gimbi', 'Gimbi'), - ('wukro', 'Wukro'), - ('haramaya', 'Haramaya'), - ('mizan_teferi', 'Mizan Teferi'), - ('sawla', 'Sawla'), - ('mojo', 'Mojo'), - ('dembi_dolo', 'Dembi Dolo'), - ('aleta_wendo', 'Aleta Wendo'), - ('metu', 'Metu'), - ('mota', 'Mota'), - ('fiche', 'Fiche'), - ('finote_selam', 'Finote Selam'), - ('bule_hora_town', 'Bule Hora Town'), - ('bonga', 'Bonga'), - ('kobo', 'Kobo'), - ('jinka', 'Jinka'), - ('dangila', 'Dangila'), - ('degehabur', 'Degehabur'), - ('bedessa', 'Bedessa'), - ('agaro', 'Agaro') ON CONFLICT (key) DO -UPDATE -SET value = EXCLUDED.value; \ No newline at end of file +-- -- Locations Initial Data +-- INSERT INTO branch_locations (key, value) +-- VALUES ('addis_ababa', 'Addis Ababa'), +-- ('dire_dawa', 'Dire Dawa'), +-- ('mekelle', 'Mekelle'), +-- ('adama', 'Adama'), +-- ('awassa', 'Awassa'), +-- ('bahir_dar', 'Bahir Dar'), +-- ('gonder', 'Gonder'), +-- ('dessie', 'Dessie'), +-- ('jimma', 'Jimma'), +-- ('jijiga', 'Jijiga'), +-- ('shashamane', 'Shashamane'), +-- ('bishoftu', 'Bishoftu'), +-- ('sodo', 'Sodo'), +-- ('arba_minch', 'Arba Minch'), +-- ('hosaena', 'Hosaena'), +-- ('harar', 'Harar'), +-- ('dilla', 'Dilla'), +-- ('nekemte', 'Nekemte'), +-- ('debre_birhan', 'Debre Birhan'), +-- ('asella', 'Asella'), +-- ('debre_markos', 'Debre Markos'), +-- ('kombolcha', 'Kombolcha'), +-- ('debre_tabor', 'Debre Tabor'), +-- ('adigrat', 'Adigrat'), +-- ('areka', 'Areka'), +-- ('weldiya', 'Weldiya'), +-- ('sebeta', 'Sebeta'), +-- ('burayu', 'Burayu'), +-- ('shire', 'Shire'), +-- ('ambo', 'Ambo'), +-- ('arsi_negele', 'Arsi Negele'), +-- ('aksum', 'Aksum'), +-- ('gambela', 'Gambela'), +-- ('bale_robe', 'Bale Robe'), +-- ('butajira', 'Butajira'), +-- ('batu', 'Batu'), +-- ('boditi', 'Boditi'), +-- ('adwa', 'Adwa'), +-- ('yirgalem', 'Yirgalem'), +-- ('waliso', 'Waliso'), +-- ('welkite', 'Welkite'), +-- ('gode', 'Gode'), +-- ('meki', 'Meki'), +-- ('negele_borana', 'Negele Borana'), +-- ('alaba_kulito', 'Alaba Kulito'), +-- ('alamata,', 'Alamata,'), +-- ('chiro', 'Chiro'), +-- ('tepi', 'Tepi'), +-- ('durame', 'Durame'), +-- ('goba', 'Goba'), +-- ('assosa', 'Assosa'), +-- ('gimbi', 'Gimbi'), +-- ('wukro', 'Wukro'), +-- ('haramaya', 'Haramaya'), +-- ('mizan_teferi', 'Mizan Teferi'), +-- ('sawla', 'Sawla'), +-- ('mojo', 'Mojo'), +-- ('dembi_dolo', 'Dembi Dolo'), +-- ('aleta_wendo', 'Aleta Wendo'), +-- ('metu', 'Metu'), +-- ('mota', 'Mota'), +-- ('fiche', 'Fiche'), +-- ('finote_selam', 'Finote Selam'), +-- ('bule_hora_town', 'Bule Hora Town'), +-- ('bonga', 'Bonga'), +-- ('kobo', 'Kobo'), +-- ('jinka', 'Jinka'), +-- ('dangila', 'Dangila'), +-- ('degehabur', 'Degehabur'), +-- ('bedessa', 'Bedessa'), +-- ('agaro', 'Agaro') ON CONFLICT (key) DO +-- UPDATE +-- SET value = EXCLUDED.value; \ No newline at end of file diff --git a/db/migrations/000010_seed_data.up.sql b/db/migrations/000010_seed_data.up.sql index 6534e76..b42fb9a 100644 --- a/db/migrations/000010_seed_data.up.sql +++ b/db/migrations/000010_seed_data.up.sql @@ -1,218 +1,220 @@ -CREATE EXTENSION IF NOT EXISTS pgcrypto; --- Users -INSERT INTO users ( - id, - first_name, - last_name, - email, - phone_number, - password, - role, - email_verified, - phone_verified, - created_at, - updated_at, - suspended, - company_id - ) -VALUES ( - 1, - 'John', - 'Doe', - 'john.doe@example.com', - NULL, - crypt('password123', gen_salt('bf'))::bytea, - 'customer', - TRUE, - FALSE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP, - FALSE, - NULL - ), - ( - 2, - 'Test', - 'Admin', - 'test.admin@gmail.com', - '0988554466', - crypt('password123', gen_salt('bf'))::bytea, - 'admin', - TRUE, - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP, - FALSE, - 1 - ), - ( - 3, - 'Samuel', - 'Tariku', - 'cybersamt@gmail.com', - '0911111111', - crypt('password@123', gen_salt('bf'))::bytea, - 'super_admin', - TRUE, - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP, - FALSE, - NULL - ), - ( - 4, - 'Kirubel', - 'Kibru', - 'kirubel.jkl679@gmail.com', - '0911554486', - crypt('password@123', gen_salt('bf'))::bytea, - 'super_admin', - TRUE, - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP, - FALSE, - NULL - ), - ( - 5, - 'Test', - 'Veli', - 'test.veli@example.com', - NULL, - crypt('password@123', gen_salt('bf'))::bytea, - 'customer', - TRUE, - FALSE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP, - FALSE, - NULL - ); --- Supported Operations -INSERT INTO supported_operations (id, name, description) -VALUES (1, 'SportBook', 'Sportbook operations'), - (2, 'Virtual', 'Virtual operations'); --- Wallets -INSERT INTO wallets ( - id, - balance, - is_withdraw, - is_bettable, - is_transferable, - user_id, - type, - currency, - is_active, - created_at, - updated_at - ) -VALUES ( - 1, - 10000, - TRUE, - TRUE, - TRUE, - 1, - 'regular', - 'ETB', - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP - ), - ( - 2, - 5000, - FALSE, - TRUE, - TRUE, - 1, - 'static', - 'ETB', - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP - ), - ( - 3, - 20000, - TRUE, - TRUE, - TRUE, - 2, - 'company_main', - 'ETB', - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP - ), - ( - 4, - 15000, - TRUE, - TRUE, - TRUE, - 2, - 'branch_main', - 'ETB', - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP - ); --- Customer Wallets -INSERT INTO customer_wallets ( - id, - customer_id, - regular_wallet_id, - static_wallet_id - ) -VALUES (1, 1, 1, 2); --- Company -INSERT INTO companies ( - id, - name, - admin_id, - wallet_id, - deducted_percentage, - is_active, - created_at, - updated_at - ) -VALUES ( - 1, - 'Test Company', - 2, - 3, - 0.10, - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP - ); --- Branch -INSERT INTO branches ( - id, - name, - location, - wallet_id, - branch_manager_id, - company_id, - is_self_owned, - profit_percent, - is_active, - created_at, - updated_at - ) -VALUES ( - 1, - 'Test Branch', - 'addis_ababa', - 4, - 2, - 1, - TRUE, - 0.10, - TRUE, - CURRENT_TIMESTAMP, - CURRENT_TIMESTAMP - ); \ No newline at end of file +-- CREATE EXTENSION IF NOT EXISTS pgcrypto; +-- -- Users +-- INSERT INTO users ( +-- id, +-- first_name, +-- last_name, +-- email, +-- phone_number, +-- password, +-- role, +-- email_verified, +-- phone_verified, +-- created_at, +-- updated_at, +-- suspended, +-- company_id +-- ) +-- VALUES ( +-- 1, +-- 'John', +-- 'Doe', +-- 'john.doe@example.com', +-- NULL, +-- crypt('password@123', gen_salt('bf'))::bytea, +-- 'customer', +-- TRUE, +-- FALSE, +-- CURRENT_TIMESTAMP, +-- CURRENT_TIMESTAMP, +-- FALSE, +-- 1 +-- ), +-- ( +-- 2, +-- 'Test', +-- 'Admin', +-- 'test.admin@gmail.com', +-- '0988554466', +-- crypt('password@123', gen_salt('bf'))::bytea, +-- 'admin', +-- TRUE, +-- TRUE, +-- CURRENT_TIMESTAMP, +-- CURRENT_TIMESTAMP, +-- FALSE, +-- 1 +-- ), +-- ( +-- 3, +-- 'Samuel', +-- 'Tariku', +-- 'cybersamt@gmail.com', +-- '0911111111', +-- crypt('password@123', gen_salt('bf'))::bytea, +-- 'super_admin', +-- TRUE, +-- TRUE, +-- CURRENT_TIMESTAMP, +-- CURRENT_TIMESTAMP, +-- FALSE, +-- NULL +-- ), +-- ( +-- 4, +-- 'Kirubel', +-- 'Kibru', +-- 'kirubel.jkl679@gmail.com', +-- '0911554486', +-- crypt('password@123', gen_salt('bf'))::bytea, +-- 'super_admin', +-- TRUE, +-- TRUE, +-- CURRENT_TIMESTAMP, +-- CURRENT_TIMESTAMP, +-- FALSE, +-- NULL +-- ), +-- ( +-- 5, +-- 'Test', +-- 'Veli', +-- 'test.veli@example.com', +-- NULL, +-- crypt('password@123', gen_salt('bf'))::bytea, +-- 'customer', +-- TRUE, +-- FALSE, +-- CURRENT_TIMESTAMP, +-- CURRENT_TIMESTAMP, +-- FALSE, +-- 1 +-- ); +-- -- Supported Operations +-- INSERT INTO supported_operations (id, name, description) +-- VALUES (1, 'SportBook', 'Sportbook operations'), +-- (2, 'Virtual', 'Virtual operations'); +-- -- Wallets +-- INSERT INTO wallets ( +-- id, +-- balance, +-- is_withdraw, +-- is_bettable, +-- is_transferable, +-- user_id, +-- type, +-- currency, +-- is_active, +-- created_at, +-- updated_at +-- ) +-- VALUES ( +-- 1, +-- 10000, +-- TRUE, +-- TRUE, +-- TRUE, +-- 1, +-- 'regular_wallet', +-- 'ETB', +-- TRUE, +-- CURRENT_TIMESTAMP, +-- CURRENT_TIMESTAMP +-- ), +-- ( +-- 2, +-- 5000, +-- FALSE, +-- TRUE, +-- TRUE, +-- 1, +-- 'static_wallet', +-- 'ETB', +-- TRUE, +-- CURRENT_TIMESTAMP, +-- CURRENT_TIMESTAMP +-- ), +-- ( +-- 3, +-- 20000, +-- TRUE, +-- TRUE, +-- TRUE, +-- 2, +-- 'company_wallet', +-- 'ETB', +-- TRUE, +-- CURRENT_TIMESTAMP, +-- CURRENT_TIMESTAMP +-- ), +-- ( +-- 4, +-- 15000, +-- TRUE, +-- TRUE, +-- TRUE, +-- 2, +-- 'branch_wallet', +-- 'ETB', +-- TRUE, +-- CURRENT_TIMESTAMP, +-- CURRENT_TIMESTAMP +-- ); +-- -- Customer Wallets +-- INSERT INTO customer_wallets ( +-- id, +-- customer_id, +-- regular_wallet_id, +-- static_wallet_id +-- ) +-- VALUES (1, 1, 1, 2); +-- -- Company +-- INSERT INTO companies ( +-- id, +-- name, +-- slug, +-- admin_id, +-- wallet_id, +-- deducted_percentage, +-- is_active, +-- created_at, +-- updated_at +-- ) +-- VALUES ( +-- 1, +-- 'FortuneBets', +-- 'fortunebets', +-- 2, +-- 3, +-- 0.10, +-- TRUE, +-- CURRENT_TIMESTAMP, +-- CURRENT_TIMESTAMP +-- ); +-- -- Branch +-- INSERT INTO branches ( +-- id, +-- name, +-- location, +-- wallet_id, +-- branch_manager_id, +-- company_id, +-- is_self_owned, +-- profit_percent, +-- is_active, +-- created_at, +-- updated_at +-- ) +-- VALUES ( +-- 1, +-- 'Test Branch', +-- 'addis_ababa', +-- 4, +-- 2, +-- 1, +-- TRUE, +-- 0.10, +-- TRUE, +-- CURRENT_TIMESTAMP, +-- CURRENT_TIMESTAMP +-- ); \ No newline at end of file diff --git a/db/query/bonus.sql b/db/query/bonus.sql index 82b3113..216528a 100644 --- a/db/query/bonus.sql +++ b/db/query/bonus.sql @@ -1,17 +1,61 @@ --- name: CreateBonusMultiplier :exec -INSERT INTO bonus (multiplier, balance_cap) -VALUES ($1, $2); +-- name: CreateUserBonus :one +INSERT INTO user_bonuses ( + name, + description, + type, + user_id, + reward_amount, + expires_at + ) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING *; +-- name: GetAllUserBonuses :many +SELECT * +FROM user_bonuses +WHERE ( + user_id = sqlc.narg('user_id') + OR sqlc.narg('user_id') IS NULL + ) +LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); +-- name: GetUserBonusByID :one +SELECT * +FROM user_bonuses +WHERE id = $1; --- name: GetBonusMultiplier :many -SELECT id, multiplier -FROM bonus; - --- name: GetBonusBalanceCap :many -SELECT id, balance_cap -FROM bonus; - --- name: UpdateBonusMultiplier :exec -UPDATE bonus -SET multiplier = $1, - balance_cap = $2 -WHERE id = $3; \ No newline at end of file +-- name: GetBonusCount :one +SELECT COUNT(*) +FROM user_bonuses +WHERE ( + user_id = sqlc.narg('user_id') + OR sqlc.narg('user_id') IS NULL + ); +-- name: GetBonusStats :one +SELECT COUNT(*) AS total_bonuses, + COALESCE(SUM(reward_amount), 0)::bigint AS total_reward_earned, + COUNT( + CASE + WHEN is_claimed = true THEN 1 + END + ) AS claimed_bonuses, + COUNT( + CASE + WHEN expires_at < now() THEN 1 + END + ) AS expired_bonuses +FROM user_bonuses + JOIN users ON users.id = user_bonuses.user_id +WHERE ( + company_id = sqlc.narg('company_id') + OR sqlc.narg('company_id') IS NULL + ) + AND ( + user_id = sqlc.narg('user_id') + OR sqlc.narg('user_id') IS NULL + ); +-- name: UpdateUserBonus :exec +UPDATE user_bonuses +SET is_claimed = $2 +WHERE id = $1; +-- name: DeleteUserBonus :exec +DELETE FROM user_bonuses +WHERE id = $1; \ No newline at end of file diff --git a/db/query/events.sql b/db/query/events.sql index 1a61445..c5ec974 100644 --- a/db/query/events.sql +++ b/db/query/events.sql @@ -14,7 +14,8 @@ INSERT INTO events ( start_time, is_live, status, - source + source, + default_winning_upper_limit ) VALUES ( $1, @@ -31,7 +32,8 @@ VALUES ( $12, $13, $14, - $15 + $15, + $16 ) ON CONFLICT (id) DO UPDATE SET sport_id = EXCLUDED.sport_id, @@ -44,7 +46,6 @@ SET sport_id = EXCLUDED.sport_id, away_kit_image = EXCLUDED.away_kit_image, league_id = EXCLUDED.league_id, league_name = EXCLUDED.league_name, - league_cc = EXCLUDED.league_cc, start_time = EXCLUDED.start_time, score = EXCLUDED.score, match_minute = EXCLUDED.match_minute, @@ -53,8 +54,9 @@ SET sport_id = EXCLUDED.sport_id, match_period = EXCLUDED.match_period, is_live = EXCLUDED.is_live, source = EXCLUDED.source, + default_winning_upper_limit = EXCLUDED.default_winning_upper_limit, fetched_at = now(); --- name: InsertEventSettings :exec +-- name: SaveEventSettings :exec INSERT INTO company_event_settings ( company_id, event_id, @@ -152,16 +154,18 @@ ORDER BY start_time ASC LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); -- name: GetTotalCompanyEvents :one SELECT COUNT(*) -FROM event_with_settings -WHERE company_id = $1 - AND is_live = false +FROM events e + LEFT JOIN company_event_settings ces ON e.id = ces.event_id + AND ces.company_id = $1 + JOIN leagues l ON l.id = e.league_id +WHERE is_live = false AND status = 'upcoming' AND ( league_id = sqlc.narg('league_id') OR sqlc.narg('league_id') IS NULL ) AND ( - sport_id = sqlc.narg('sport_id') + e.sport_id = sqlc.narg('sport_id') OR sqlc.narg('sport_id') IS NULL ) AND ( @@ -178,14 +182,35 @@ WHERE company_id = $1 OR sqlc.narg('first_start_time') IS NULL ) AND ( - league_cc = sqlc.narg('country_code') + l.country_code = sqlc.narg('country_code') OR sqlc.narg('country_code') IS NULL + ) + AND ( + ces.is_featured = sqlc.narg('is_featured') + OR e.default_is_featured = sqlc.narg('is_featured') + OR sqlc.narg('is_featured') IS NULL + ) + AND ( + ces.is_active = sqlc.narg('is_active') + OR e.default_is_active = sqlc.narg('is_active') + OR sqlc.narg('is_active') IS NULL ); -- name: GetEventsWithSettings :many -SELECT * -FROM event_with_settings -WHERE company_id = $1 - AND start_time > now() +SELECT e.*, + ces.company_id, + COALESCE(ces.is_active, e.default_is_active) AS is_active, + COALESCE(ces.is_featured, e.default_is_featured) AS is_featured, + COALESCE( + ces.winning_upper_limit, + e.default_winning_upper_limit + ) AS winning_upper_limit, + ces.updated_at, + l.country_code as league_cc +FROM events e + LEFT JOIN company_event_settings ces ON e.id = ces.event_id + AND ces.company_id = $1 + JOIN leagues l ON l.id = e.league_id +WHERE start_time > now() AND is_live = false AND status = 'upcoming' AND ( @@ -193,7 +218,7 @@ WHERE company_id = $1 OR sqlc.narg('league_id') IS NULL ) AND ( - sport_id = sqlc.narg('sport_id') + e.sport_id = sqlc.narg('sport_id') OR sqlc.narg('sport_id') IS NULL ) AND ( @@ -210,9 +235,19 @@ WHERE company_id = $1 OR sqlc.narg('first_start_time') IS NULL ) AND ( - league_cc = sqlc.narg('country_code') + l.country_code = sqlc.narg('country_code') OR sqlc.narg('country_code') IS NULL ) + AND ( + ces.is_featured = sqlc.narg('is_featured') + OR e.default_is_featured = sqlc.narg('is_featured') + OR sqlc.narg('is_featured') IS NULL + ) + AND ( + ces.is_active = sqlc.narg('is_active') + OR e.default_is_active = sqlc.narg('is_active') + OR sqlc.narg('is_active') IS NULL + ) ORDER BY start_time ASC LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); -- name: GetUpcomingByID :one @@ -223,13 +258,27 @@ WHERE id = $1 AND status = 'upcoming' LIMIT 1; -- name: GetEventWithSettingByID :one -SELECT * -FROM event_with_settings -WHERE id = $1 - AND company_id = $2 +SELECT e.*, + ces.company_id, + COALESCE(ces.is_active, e.default_is_active) AS is_active, + COALESCE(ces.is_featured, e.default_is_featured) AS is_featured, + COALESCE( + ces.winning_upper_limit, + e.default_winning_upper_limit + ) AS winning_upper_limit, + ces.updated_at, + l.country_code as league_cc +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 +WHERE e.id = $1 AND is_live = false AND status = 'upcoming' LIMIT 1; +-- name: GetSportAndLeagueIDs :one +SELECT sport_id, league_id FROM events +WHERE id = $1; -- name: UpdateMatchResult :exec UPDATE events SET score = $1, @@ -243,19 +292,6 @@ WHERE id = $1; UPDATE events SET is_monitored = $1 WHERE id = $2; --- name: UpdateEventSettings :exec -UPDATE company_event_settings -SET is_active = COALESCE(sqlc.narg('is_active'), is_active), - is_featured = COALESCE( - sqlc.narg('is_featured'), - is_featured - ), - winning_upper_limit = COALESCE( - sqlc.narg('winning_upper_limit'), - winning_upper_limit - ) -WHERE event_id = $1 - AND company_id = $2; -- name: DeleteEvent :exec DELETE FROM events -WHERE id = $1; \ No newline at end of file +WHERE id = $1; diff --git a/db/query/leagues.sql b/db/query/leagues.sql index fbbc562..476c3e8 100644 --- a/db/query/leagues.sql +++ b/db/query/leagues.sql @@ -36,13 +36,18 @@ WHERE ( 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 + ) ORDER BY name ASC LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); --- name: GetAllLeaguesWithSettings :many -SELECT * -FROM league_with_settings -WHERE (company_id = $1) - AND ( +-- name: GetTotalLeaguesWithSettings :one +SELECT COUNT(*) +FROM leagues l + LEFT JOIN company_league_settings cls ON l.id = cls.league_id + AND company_id = $1 +WHERE ( country_code = sqlc.narg('country_code') OR sqlc.narg('country_code') IS NULL ) @@ -52,12 +57,49 @@ WHERE (company_id = $1) ) AND ( is_active = sqlc.narg('is_active') + OR default_is_active = sqlc.narg('is_active') OR sqlc.narg('is_active') IS NULL ) AND ( is_featured = sqlc.narg('is_featured') + OR default_is_featured = sqlc.narg('is_featured') OR sqlc.narg('is_featured') IS NULL ) + AND ( + name ILIKE '%' || sqlc.narg('query') || '%' + OR sqlc.narg('query') IS NULL + ); +-- name: GetAllLeaguesWithSettings :many +SELECT l.*, + cls.company_id, + COALESCE(cls.is_active, l.default_is_active) AS is_active, + COALESCE(cls.is_featured, l.default_is_featured) AS is_featured, + cls.updated_at +FROM leagues l + LEFT JOIN company_league_settings cls ON l.id = cls.league_id + AND company_id = $1 +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 ( + is_active = sqlc.narg('is_active') + OR default_is_active = sqlc.narg('is_active') + OR sqlc.narg('is_active') IS NULL + ) + AND ( + is_featured = sqlc.narg('is_featured') + OR default_is_featured = sqlc.narg('is_featured') + OR sqlc.narg('is_featured') IS NULL + ) + AND ( + name ILIKE '%' || sqlc.narg('query') || '%' + OR sqlc.narg('query') IS NULL + ) ORDER BY is_featured DESC, name ASC LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); diff --git a/db/query/odds.sql b/db/query/odds.sql index 8c231b1..dc467c6 100644 --- a/db/query/odds.sql +++ b/db/query/odds.sql @@ -26,7 +26,7 @@ SET market_type = EXCLUDED.market_type, raw_odds = EXCLUDED.raw_odds, fetched_at = EXCLUDED.fetched_at, expires_at = EXCLUDED.expires_at; --- name: InsertOddSettings :exec +-- name: SaveOddSettings :exec INSERT INTO company_odd_settings ( company_id, odds_market_id, @@ -42,21 +42,69 @@ SELECT * FROM odds_market_with_event LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); -- name: GetAllOddsWithSettings :many -SELECT * -FROM odds_market_with_settings -WHERE company_id = $1 +SELECT o.id, + o.event_id, + o.market_type, + o.market_name, + o.market_category, + o.market_id, + o.default_is_active, + o.fetched_at, + o.expires_at, + cos.company_id, + COALESCE(cos.is_active, o.default_is_active) AS is_active, + COALESCE(cos.custom_raw_odds, o.raw_odds) AS raw_odds, + cos.updated_at +FROM odds_market o + LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id + AND company_id = $1 LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); +-- name: GetOddByID :one +SELECT * +FROM odds_market_with_event +WHERE id = $1; -- name: GetOddsByMarketID :one SELECT * FROM odds_market_with_event WHERE market_id = $1 AND event_id = $2; -- name: GetOddsWithSettingsByMarketID :one -SELECT * -FROM odds_market_with_settings +SELECT o.id, + o.event_id, + o.market_type, + o.market_name, + o.market_category, + o.market_id, + o.default_is_active, + o.fetched_at, + o.expires_at, + cos.company_id, + COALESCE(cos.is_active, o.default_is_active) AS is_active, + COALESCE(cos.custom_raw_odds, o.raw_odds) AS raw_odds, + cos.updated_at +FROM odds_market o + LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id + AND company_id = $3 WHERE market_id = $1 - AND event_id = $2 - AND company_id = $3; + AND event_id = $2; +-- name: GetOddsWithSettingsByID :one +SELECT o.id, + o.event_id, + o.market_type, + o.market_name, + o.market_category, + o.market_id, + o.default_is_active, + o.fetched_at, + o.expires_at, + cos.company_id, + COALESCE(cos.is_active, o.default_is_active) AS is_active, + COALESCE(cos.custom_raw_odds, o.raw_odds) AS raw_odds, + cos.updated_at +FROM odds_market o + LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id + AND company_id = $2 +WHERE o.id = $1; -- name: GetOddsByEventID :many SELECT * FROM odds_market_with_event @@ -75,10 +123,23 @@ WHERE event_id = $1 ) LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); -- name: GetOddsWithSettingsByEventID :many -SELECT * -FROM odds_market_with_settings -WHERE event_id = $1 +SELECT o.id, + o.event_id, + o.market_type, + o.market_name, + o.market_category, + o.market_id, + o.default_is_active, + o.fetched_at, + o.expires_at, + cos.company_id, + COALESCE(cos.is_active, o.default_is_active) AS is_active, + COALESCE(cos.custom_raw_odds, o.raw_odds) AS raw_odds, + cos.updated_at +FROM odds_market o + LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id AND company_id = $2 +WHERE event_id = $1 LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); -- name: DeleteOddsForEvent :exec DELETE FROM odds_market diff --git a/db/query/raffle.sql b/db/query/raffle.sql new file mode 100644 index 0000000..55f302c --- /dev/null +++ b/db/query/raffle.sql @@ -0,0 +1,73 @@ +-- name: CreateRaffle :one +INSERT INTO raffles (company_id, name, expires_at, type) +VALUES ($1, $2, $3, $4) +RETURNING *; + +-- name: GetRafflesOfCompany :many +SELECT * FROM raffles WHERE company_id = $1; + +-- name: DeleteRaffle :one +DELETE FROM raffles +WHERE id = $1 +RETURNING *; + +-- name: UpdateRaffleTicketStatus :exec +UPDATE raffle_tickets +SET is_active = $1 +WHERE id = $2; + +-- name: CreateRaffleTicket :one +INSERT INTO raffle_tickets (raffle_id, user_id) +VALUES ($1, $2) +RETURNING *; + +-- name: GetUserRaffleTickets :many +SELECT + rt.id AS ticket_id, + rt.user_id, + r.name, + r.type, + r.expires_at, + r.status +FROM raffle_tickets rt +JOIN raffles r ON rt.raffle_id = r.id +WHERE rt.user_id = $1; + +-- name: GetRaffleStanding :many +SELECT + u.id AS user_id, + rt.raffle_id, + u.first_name, + u.last_name, + u.phone_number, + u.email, + COUNT(*) AS ticket_count +FROM raffle_tickets rt +JOIN users u ON rt.user_id = u.id +WHERE rt.is_active = true + AND rt.raffle_id = $1 +GROUP BY u.id, rt.raffle_id, u.first_name, u.last_name, u.phone_number, u.email +ORDER BY ticket_count DESC +LIMIT $2; + +-- name: CreateRaffleWinner :one +INSERT INTO raffle_winners (raffle_id, user_id, rank) +VALUES ($1, $2, $3) +RETURNING *; + +-- name: SetRaffleComplete :exec +UPDATE raffles +SET status = 'completed' +WHERE id = $1; + +-- name: AddSportRaffleFilter :one +INSERT INTO raffle_sport_filters (raffle_id, sport_id, league_id) +VALUES ($1, $2, $3) +RETURNING *; + +-- name: CheckValidSportRaffleFilter :one +SELECT COUNT(*) > 0 AS exists +FROM raffle_sport_filters +WHERE raffle_id = $1 + AND sport_id = $2 + AND league_id = $3; diff --git a/db/query/referal.sql b/db/query/referal.sql index 206606e..3dbe00e 100644 --- a/db/query/referal.sql +++ b/db/query/referal.sql @@ -1,77 +1,51 @@ --- name: CreateReferral :one -INSERT INTO referrals ( - referral_code, - referrer_id, - status, - reward_amount, - expires_at -) VALUES ( - $1, $2, $3, $4, $5 -) RETURNING *; - --- name: GetReferralByCode :one -SELECT * FROM referrals -WHERE referral_code = $1; - --- name: UpdateReferral :one -UPDATE referrals -SET - referred_id = $2, - status = $3, - updated_at = CURRENT_TIMESTAMP -WHERE id = $1 +-- name: CreateReferralCode :one +INSERT INTO referral_codes ( + referral_code, + referrer_id, + company_id, + number_of_referrals, + reward_amount + ) +VALUES ($1, $2, $3, $4, $5) RETURNING *; - --- name: UpdateReferralCode :exec -UPDATE users -SET - referral_code = $2, - updated_at = CURRENT_TIMESTAMP -WHERE id = $1; - --- name: GetReferralStats :one -SELECT - COUNT(*) as total_referrals, - COUNT(CASE WHEN status = 'COMPLETED' THEN 1 END) as completed_referrals, - COALESCE(SUM(reward_amount), 0) as total_reward_earned, - COALESCE(SUM(CASE WHEN status = 'PENDING' THEN reward_amount END), 0) as pending_rewards -FROM referrals +-- name: CreateUserReferral :one +INSERT INTO user_referrals (referred_id, referral_code_id) +VALUES ($1, $2) +RETURNING *; +-- name: GetReferralCodeByUser :many +SELECt * +FROM referral_codes WHERE referrer_id = $1; - --- name: GetReferralSettings :one -SELECT * FROM referral_settings -LIMIT 1; - --- name: UpdateReferralSettings :one -UPDATE referral_settings -SET - referral_reward_amount = $2, - cashback_percentage = $3, - bet_referral_bonus_percentage= $4, - max_referrals = $5, - expires_after_days = $6, - updated_by = $7, - updated_at = CURRENT_TIMESTAMP -WHERE id = $1 -RETURNING *; - --- name: CreateReferralSettings :one -INSERT INTO referral_settings ( - referral_reward_amount, - cashback_percentage, - max_referrals, - bet_referral_bonus_percentage, - expires_after_days, - updated_by -) VALUES ( - $1, $2, $3, $4, $5, $6 -) RETURNING *; - --- name: GetReferralByReferredID :one -SELECT * FROM referrals WHERE referred_id = $1 LIMIT 1; - --- name: GetActiveReferralByReferrerID :one -SELECT * FROM referrals WHERE referrer_id = $1 AND status = 'PENDING' LIMIT 1; - --- name: GetReferralCountByID :one -SELECT count(*) FROM referrals WHERE referrer_id = $1; \ No newline at end of file +-- name: GetReferralCode :one +SELECT * +FROM referral_codes +WHERE referral_code = $1; +-- name: UpdateReferralCode :exec +UPDATE referral_codes +SET is_active = $2, + referral_code = $3, + number_of_referrals = $4, + reward_amount = $5, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1; +-- name: GetReferralStats :one +SELECT COUNT(*) AS total_referrals, + COALESCE(SUM(reward_amount), 0)::bigint AS total_reward_earned +FROM user_referrals + JOIN referral_codes ON referral_codes.id = referral_code_id +WHERE referrer_id = $1 + AND company_id = $2; +-- name: GetUserReferral :one +SELECT * +FROM user_referrals +WHERE referred_id = $1; +-- name: GetUserReferralsByCode :many +SELECT user_referrals.* +FROM user_referrals + JOIN referral_codes ON referral_codes.id = referral_code_id +WHERE referral_code = $1; +-- name: GetUserReferralsCount :one +SELECT COUNT(*) +FROM user_referrals + JOIN referral_codes ON referral_codes.id = referral_code_id +WHERE referrer_id = $1; \ No newline at end of file diff --git a/db/query/settings.sql b/db/query/settings.sql index cda4e87..e51c1ff 100644 --- a/db/query/settings.sql +++ b/db/query/settings.sql @@ -27,7 +27,9 @@ SELECT * FROM company_settings WHERE key = $1; -- name: GetOverrideSettings :many -SELECT gs.*, +SELECT gs.key, + gs.created_at, + gs.updated_at, COALESCE(cs.value, gs.value) AS value FROM global_settings gs LEFT JOIN company_settings cs ON cs.key = gs.key diff --git a/db/query/transfer.sql b/db/query/transfer.sql index dc4c156..0229d0f 100644 --- a/db/query/transfer.sql +++ b/db/query/transfer.sql @@ -30,6 +30,19 @@ WHERE id = $1; SELECT * FROM wallet_transfer_details WHERE reference_number = $1; +-- name: GetTransferStats :one +SELECT COUNT(*) AS total_transfers, COUNT(*) FILTER ( + WHERE type = 'deposit' + ) AS total_deposits, + COUNT(*) FILTER ( + WHERE type = 'withdraw' + ) AS total_withdraw, + COUNT(*) FILTER ( + WHERE type = 'wallet' + ) AS total_wallet_to_wallet +FROM wallet_transfer +WHERE sender_wallet_id = $1 + OR receiver_wallet_id = $1; -- name: UpdateTransferVerification :exec UPDATE wallet_transfer SET verified = $1, diff --git a/db/query/user.sql b/db/query/user.sql index 1b408cf..e749355 100644 --- a/db/query/user.sql +++ b/db/query/user.sql @@ -193,7 +193,7 @@ SET password = $1, WHERE ( email = $2 OR phone_number = $3 - AND company_id = $4 + AND company_id = $5 ); -- name: GetAdminByCompanyID :one SELECT users.* diff --git a/db/query/virtual_games.sql b/db/query/virtual_games.sql index 1e81b98..46e5061 100644 --- a/db/query/virtual_games.sql +++ b/db/query/virtual_games.sql @@ -1,77 +1,157 @@ -- name: CreateVirtualGameProvider :one INSERT INTO virtual_game_providers ( - provider_id, provider_name, logo_dark, logo_light, enabled -) VALUES ( - $1, $2, $3, $4, $5 -) RETURNING id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at; - + provider_id, + provider_name, + logo_dark, + logo_light, + enabled + ) +VALUES ($1, $2, $3, $4, $5) +RETURNING id, + provider_id, + provider_name, + logo_dark, + logo_light, + enabled, + created_at, + updated_at; -- name: DeleteVirtualGameProvider :exec DELETE FROM virtual_game_providers WHERE provider_id = $1; - -- name: DeleteAllVirtualGameProviders :exec DELETE FROM virtual_game_providers; - -- name: GetVirtualGameProviderByID :one -SELECT id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at +SELECT id, + provider_id, + provider_name, + logo_dark, + logo_light, + enabled, + created_at, + updated_at FROM virtual_game_providers WHERE provider_id = $1; - -- name: ListVirtualGameProviders :many -SELECT id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at +SELECT id, + provider_id, + provider_name, + logo_dark, + logo_light, + enabled, + created_at, + updated_at FROM virtual_game_providers ORDER BY created_at DESC LIMIT $1 OFFSET $2; - -- name: CountVirtualGameProviders :one SELECT COUNT(*) AS total FROM virtual_game_providers; - -- name: UpdateVirtualGameProviderEnabled :one UPDATE virtual_game_providers SET enabled = $2, updated_at = CURRENT_TIMESTAMP WHERE provider_id = $1 -RETURNING id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at; - +RETURNING id, + provider_id, + provider_name, + logo_dark, + logo_light, + enabled, + created_at, + updated_at; -- name: CreateVirtualGameSession :one INSERT INTO virtual_game_sessions ( - user_id, game_id, session_token, currency, status, expires_at -) VALUES ( - $1, $2, $3, $4, $5, $6 -) RETURNING id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at; + user_id, + game_id, + session_token, + currency, + status, + expires_at + ) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING id, + user_id, + game_id, + session_token, + currency, + status, + created_at, + updated_at, + expires_at; -- name: GetVirtualGameSessionByToken :one -SELECT id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at +SELECT id, + user_id, + game_id, + session_token, + currency, + status, + created_at, + updated_at, + expires_at FROM virtual_game_sessions WHERE session_token = $1; -- name: UpdateVirtualGameSessionStatus :exec UPDATE virtual_game_sessions -SET status = $2, updated_at = CURRENT_TIMESTAMP +SET status = $2, + updated_at = CURRENT_TIMESTAMP WHERE id = $1; -- name: CreateVirtualGameTransaction :one INSERT INTO virtual_game_transactions ( - session_id, user_id, company_id, provider, wallet_id, transaction_type, amount, currency, external_transaction_id, status -) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 -) RETURNING id, session_id, user_id, company_id, provider, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at; --- name: CreateVirtualGameHistory :one -INSERT INTO virtual_game_histories ( + session_id, + user_id, + company_id, + provider, + wallet_id, + transaction_type, + amount, + currency, + external_transaction_id, + status + ) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) +RETURNING id, session_id, user_id, company_id, provider, wallet_id, - game_id, transaction_type, amount, currency, external_transaction_id, - reference_transaction_id, - status -) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 -) RETURNING - id, + status, + created_at, + updated_at; +-- name: CreateVirtualGameHistory :one +INSERT INTO virtual_game_histories ( + session_id, + user_id, + company_id, + provider, + wallet_id, + game_id, + transaction_type, + amount, + currency, + external_transaction_id, + reference_transaction_id, + status + ) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12 + ) +RETURNING id, session_id, user_id, company_id, @@ -87,60 +167,78 @@ INSERT INTO virtual_game_histories ( created_at, updated_at; -- name: GetVirtualGameTransactionByExternalID :one -SELECT id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at +SELECT id, + session_id, + user_id, + wallet_id, + transaction_type, + amount, + currency, + external_transaction_id, + status, + created_at, + updated_at FROM virtual_game_transactions WHERE external_transaction_id = $1; -- name: UpdateVirtualGameTransactionStatus :exec UPDATE virtual_game_transactions -SET status = $2, updated_at = CURRENT_TIMESTAMP +SET status = $2, + updated_at = CURRENT_TIMESTAMP WHERE id = $1; -- name: GetVirtualGameSummaryInRange :many -SELECT - c.name AS company_name, +SELECT c.name AS company_name, vg.name AS game_name, COUNT(vgt.id) AS number_of_bets, COALESCE(SUM(vgt.amount), 0) AS total_transaction_sum FROM virtual_game_transactions vgt -JOIN virtual_game_sessions vgs ON vgt.session_id = vgs.id -JOIN virtual_games vg ON vgs.game_id = vg.id -JOIN companies c ON vgt.company_id = c.id + JOIN virtual_game_sessions vgs ON vgt.session_id = vgs.id + JOIN virtual_games vg ON vgs.game_id = vg.id + JOIN companies c ON vgt.company_id = c.id WHERE vgt.transaction_type = 'BET' - AND vgt.created_at BETWEEN $1 AND $2 -GROUP BY c.name, vg.name; + AND vgt.created_at BETWEEN $1 AND $2 +GROUP BY c.name, + vg.name; -- name: AddFavoriteGame :exec -INSERT INTO favorite_games ( - user_id, - game_id, - created_at -) VALUES ($1, $2, NOW()) -ON CONFLICT (user_id, game_id) DO NOTHING; +INSERT INTO favorite_games (user_id, game_id, created_at) +VALUES ($1, $2, NOW()) ON CONFLICT (user_id, game_id) DO NOTHING; -- name: RemoveFavoriteGame :exec DELETE FROM favorite_games -WHERE user_id = $1 AND game_id = $2; +WHERE user_id = $1 + AND game_id = $2; -- name: ListFavoriteGames :many SELECT game_id FROM favorite_games WHERE user_id = $1; - -- name: CreateVirtualGame :one INSERT INTO virtual_games ( - game_id, - provider_id, - name, - category, - device_type, - volatility, - rtp, - has_demo, - has_free_bets, - bets, - thumbnail, - status -) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 -) -RETURNING - id, + game_id, + provider_id, + name, + category, + device_type, + volatility, + rtp, + has_demo, + has_free_bets, + bets, + thumbnail, + status + ) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12 + ) +RETURNING id, game_id, provider_id, name, @@ -155,10 +253,8 @@ RETURNING status, created_at, updated_at; - -- name: GetAllVirtualGames :many -SELECT - vg.id, +SELECT vg.id, vg.game_id, vg.provider_id, vp.provider_name, @@ -175,14 +271,20 @@ SELECT vg.created_at, vg.updated_at FROM virtual_games vg -JOIN virtual_game_providers vp ON vg.provider_id = vp.provider_id -WHERE - ($1::text IS NULL OR vg.category = $1) -- category filter (optional) - AND ($2::text IS NULL OR vg.name ILIKE '%' || $2 || '%') -- search by name (optional) + JOIN virtual_game_providers vp ON vg.provider_id = vp.provider_id +WHERE ( + vg.category = sqlc.narg('category') + OR sqlc.narg('category') IS NULL + ) + AND ( + name ILIKE '%' || sqlc.narg('name') || '%' + OR sqlc.narg('name') IS NULL + ) + AND ( + vg.provider_id = sqlc.narg('provider_id') + OR sqlc.narg('provider_id') IS NULL + ) ORDER BY vg.created_at DESC -LIMIT $3 OFFSET $4; - +LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); -- name: DeleteAllVirtualGames :exec -DELETE FROM virtual_games; - - +DELETE FROM virtual_games; \ No newline at end of file diff --git a/gen/db/auth.sql.go b/gen/db/auth.sql.go index 1817514..8dd2280 100644 --- a/gen/db/auth.sql.go +++ b/gen/db/auth.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: auth.sql package dbgen @@ -76,7 +76,7 @@ func (q *Queries) GetRefreshTokenByUserID(ctx context.Context, userID int64) (Re } const GetUserByEmailPhone = `-- name: GetUserByEmailPhone :one -SELECT id, first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at, company_id, suspended_at, suspended, referral_code, referred_by +SELECT id, first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at, company_id, suspended_at, suspended FROM users WHERE ( email = $1 @@ -112,8 +112,6 @@ func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPho &i.CompanyID, &i.SuspendedAt, &i.Suspended, - &i.ReferralCode, - &i.ReferredBy, ) return i, err } diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index ff64087..573c4c2 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: bet.sql package dbgen diff --git a/gen/db/bet_stat.sql.go b/gen/db/bet_stat.sql.go index 275ef07..9a7b494 100644 --- a/gen/db/bet_stat.sql.go +++ b/gen/db/bet_stat.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: bet_stat.sql package dbgen diff --git a/gen/db/bonus.sql.go b/gen/db/bonus.sql.go index 12677b8..1a5d8e9 100644 --- a/gen/db/bonus.sql.go +++ b/gen/db/bonus.sql.go @@ -1,49 +1,112 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: bonus.sql package dbgen import ( "context" + + "github.com/jackc/pgx/v5/pgtype" ) -const CreateBonusMultiplier = `-- name: CreateBonusMultiplier :exec -INSERT INTO bonus (multiplier, balance_cap) -VALUES ($1, $2) +const CreateUserBonus = `-- name: CreateUserBonus :one +INSERT INTO user_bonuses ( + name, + description, + type, + user_id, + reward_amount, + expires_at + ) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING id, name, description, type, user_id, reward_amount, is_claimed, expires_at, claimed_at, created_at, updated_at ` -type CreateBonusMultiplierParams struct { - Multiplier float32 `json:"multiplier"` - BalanceCap int64 `json:"balance_cap"` +type CreateUserBonusParams struct { + Name string `json:"name"` + Description string `json:"description"` + Type string `json:"type"` + UserID int64 `json:"user_id"` + RewardAmount int64 `json:"reward_amount"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` } -func (q *Queries) CreateBonusMultiplier(ctx context.Context, arg CreateBonusMultiplierParams) error { - _, err := q.db.Exec(ctx, CreateBonusMultiplier, arg.Multiplier, arg.BalanceCap) +func (q *Queries) CreateUserBonus(ctx context.Context, arg CreateUserBonusParams) (UserBonuse, error) { + row := q.db.QueryRow(ctx, CreateUserBonus, + arg.Name, + arg.Description, + arg.Type, + arg.UserID, + arg.RewardAmount, + arg.ExpiresAt, + ) + var i UserBonuse + err := row.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.Type, + &i.UserID, + &i.RewardAmount, + &i.IsClaimed, + &i.ExpiresAt, + &i.ClaimedAt, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const DeleteUserBonus = `-- name: DeleteUserBonus :exec +DELETE FROM user_bonuses +WHERE id = $1 +` + +func (q *Queries) DeleteUserBonus(ctx context.Context, id int64) error { + _, err := q.db.Exec(ctx, DeleteUserBonus, id) return err } -const GetBonusBalanceCap = `-- name: GetBonusBalanceCap :many -SELECT id, balance_cap -FROM bonus +const GetAllUserBonuses = `-- name: GetAllUserBonuses :many +SELECT id, name, description, type, user_id, reward_amount, is_claimed, expires_at, claimed_at, created_at, updated_at +FROM user_bonuses +WHERE ( + user_id = $1 + OR $1 IS NULL + ) +LIMIT $3 OFFSET $2 ` -type GetBonusBalanceCapRow struct { - ID int64 `json:"id"` - BalanceCap int64 `json:"balance_cap"` +type GetAllUserBonusesParams struct { + UserID pgtype.Int8 `json:"user_id"` + Offset pgtype.Int4 `json:"offset"` + Limit pgtype.Int4 `json:"limit"` } -func (q *Queries) GetBonusBalanceCap(ctx context.Context) ([]GetBonusBalanceCapRow, error) { - rows, err := q.db.Query(ctx, GetBonusBalanceCap) +func (q *Queries) GetAllUserBonuses(ctx context.Context, arg GetAllUserBonusesParams) ([]UserBonuse, error) { + rows, err := q.db.Query(ctx, GetAllUserBonuses, arg.UserID, arg.Offset, arg.Limit) if err != nil { return nil, err } defer rows.Close() - var items []GetBonusBalanceCapRow + var items []UserBonuse for rows.Next() { - var i GetBonusBalanceCapRow - if err := rows.Scan(&i.ID, &i.BalanceCap); err != nil { + var i UserBonuse + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.Type, + &i.UserID, + &i.RewardAmount, + &i.IsClaimed, + &i.ExpiresAt, + &i.ClaimedAt, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { return nil, err } items = append(items, i) @@ -54,50 +117,108 @@ func (q *Queries) GetBonusBalanceCap(ctx context.Context) ([]GetBonusBalanceCapR return items, nil } -const GetBonusMultiplier = `-- name: GetBonusMultiplier :many -SELECT id, multiplier -FROM bonus +const GetBonusCount = `-- name: GetBonusCount :one +SELECT COUNT(*) +FROM user_bonuses +WHERE ( + user_id = $1 + OR $1 IS NULL + ) ` -type GetBonusMultiplierRow struct { - ID int64 `json:"id"` - Multiplier float32 `json:"multiplier"` +func (q *Queries) GetBonusCount(ctx context.Context, userID pgtype.Int8) (int64, error) { + row := q.db.QueryRow(ctx, GetBonusCount, userID) + var count int64 + err := row.Scan(&count) + return count, err } -func (q *Queries) GetBonusMultiplier(ctx context.Context) ([]GetBonusMultiplierRow, error) { - rows, err := q.db.Query(ctx, GetBonusMultiplier) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GetBonusMultiplierRow - for rows.Next() { - var i GetBonusMultiplierRow - if err := rows.Scan(&i.ID, &i.Multiplier); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const UpdateBonusMultiplier = `-- name: UpdateBonusMultiplier :exec -UPDATE bonus -SET multiplier = $1, - balance_cap = $2 -WHERE id = $3 +const GetBonusStats = `-- name: GetBonusStats :one +SELECT COUNT(*) AS total_bonuses, + COALESCE(SUM(reward_amount), 0)::bigint AS total_reward_earned, + COUNT( + CASE + WHEN is_claimed = true THEN 1 + END + ) AS claimed_bonuses, + COUNT( + CASE + WHEN expires_at < now() THEN 1 + END + ) AS expired_bonuses +FROM user_bonuses + JOIN users ON users.id = user_bonuses.user_id +WHERE ( + company_id = $1 + OR $1 IS NULL + ) + AND ( + user_id = $2 + OR $2 IS NULL + ) ` -type UpdateBonusMultiplierParams struct { - Multiplier float32 `json:"multiplier"` - BalanceCap int64 `json:"balance_cap"` - ID int64 `json:"id"` +type GetBonusStatsParams struct { + CompanyID pgtype.Int8 `json:"company_id"` + UserID pgtype.Int8 `json:"user_id"` } -func (q *Queries) UpdateBonusMultiplier(ctx context.Context, arg UpdateBonusMultiplierParams) error { - _, err := q.db.Exec(ctx, UpdateBonusMultiplier, arg.Multiplier, arg.BalanceCap, arg.ID) +type GetBonusStatsRow struct { + TotalBonuses int64 `json:"total_bonuses"` + TotalRewardEarned int64 `json:"total_reward_earned"` + ClaimedBonuses int64 `json:"claimed_bonuses"` + ExpiredBonuses int64 `json:"expired_bonuses"` +} + +func (q *Queries) GetBonusStats(ctx context.Context, arg GetBonusStatsParams) (GetBonusStatsRow, error) { + row := q.db.QueryRow(ctx, GetBonusStats, arg.CompanyID, arg.UserID) + var i GetBonusStatsRow + err := row.Scan( + &i.TotalBonuses, + &i.TotalRewardEarned, + &i.ClaimedBonuses, + &i.ExpiredBonuses, + ) + return i, err +} + +const GetUserBonusByID = `-- name: GetUserBonusByID :one +SELECT id, name, description, type, user_id, reward_amount, is_claimed, expires_at, claimed_at, created_at, updated_at +FROM user_bonuses +WHERE id = $1 +` + +func (q *Queries) GetUserBonusByID(ctx context.Context, id int64) (UserBonuse, error) { + row := q.db.QueryRow(ctx, GetUserBonusByID, id) + var i UserBonuse + err := row.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.Type, + &i.UserID, + &i.RewardAmount, + &i.IsClaimed, + &i.ExpiresAt, + &i.ClaimedAt, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const UpdateUserBonus = `-- name: UpdateUserBonus :exec +UPDATE user_bonuses +SET is_claimed = $2 +WHERE id = $1 +` + +type UpdateUserBonusParams struct { + ID int64 `json:"id"` + IsClaimed bool `json:"is_claimed"` +} + +func (q *Queries) UpdateUserBonus(ctx context.Context, arg UpdateUserBonusParams) error { + _, err := q.db.Exec(ctx, UpdateUserBonus, arg.ID, arg.IsClaimed) return err } diff --git a/gen/db/branch.sql.go b/gen/db/branch.sql.go index a9a57b8..89d2959 100644 --- a/gen/db/branch.sql.go +++ b/gen/db/branch.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: branch.sql package dbgen diff --git a/gen/db/cashier.sql.go b/gen/db/cashier.sql.go index c15f497..55e69d2 100644 --- a/gen/db/cashier.sql.go +++ b/gen/db/cashier.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: cashier.sql package dbgen @@ -12,7 +12,7 @@ import ( ) const GetAllCashiers = `-- name: GetAllCashiers :many -SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by, +SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, branch_id, branches.name AS branch_name, branches.wallet_id AS branch_wallet, @@ -57,8 +57,6 @@ type GetAllCashiersRow struct { CompanyID pgtype.Int8 `json:"company_id"` SuspendedAt pgtype.Timestamptz `json:"suspended_at"` Suspended bool `json:"suspended"` - ReferralCode pgtype.Text `json:"referral_code"` - ReferredBy pgtype.Text `json:"referred_by"` BranchID int64 `json:"branch_id"` BranchName string `json:"branch_name"` BranchWallet int64 `json:"branch_wallet"` @@ -89,8 +87,6 @@ func (q *Queries) GetAllCashiers(ctx context.Context, arg GetAllCashiersParams) &i.CompanyID, &i.SuspendedAt, &i.Suspended, - &i.ReferralCode, - &i.ReferredBy, &i.BranchID, &i.BranchName, &i.BranchWallet, @@ -107,7 +103,7 @@ func (q *Queries) GetAllCashiers(ctx context.Context, arg GetAllCashiersParams) } const GetCashierByID = `-- name: GetCashierByID :one -SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by, +SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, branch_id, branches.name AS branch_name, branches.wallet_id AS branch_wallet, @@ -133,8 +129,6 @@ type GetCashierByIDRow struct { CompanyID pgtype.Int8 `json:"company_id"` SuspendedAt pgtype.Timestamptz `json:"suspended_at"` Suspended bool `json:"suspended"` - ReferralCode pgtype.Text `json:"referral_code"` - ReferredBy pgtype.Text `json:"referred_by"` BranchID int64 `json:"branch_id"` BranchName string `json:"branch_name"` BranchWallet int64 `json:"branch_wallet"` @@ -159,8 +153,6 @@ func (q *Queries) GetCashierByID(ctx context.Context, id int64) (GetCashierByIDR &i.CompanyID, &i.SuspendedAt, &i.Suspended, - &i.ReferralCode, - &i.ReferredBy, &i.BranchID, &i.BranchName, &i.BranchWallet, @@ -170,7 +162,7 @@ func (q *Queries) GetCashierByID(ctx context.Context, id int64) (GetCashierByIDR } const GetCashiersByBranch = `-- name: GetCashiersByBranch :many -SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by +SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended FROM branch_cashiers JOIN users ON branch_cashiers.user_id = users.id JOIN branches ON branches.id = branch_id @@ -201,8 +193,6 @@ func (q *Queries) GetCashiersByBranch(ctx context.Context, branchID int64) ([]Us &i.CompanyID, &i.SuspendedAt, &i.Suspended, - &i.ReferralCode, - &i.ReferredBy, ); err != nil { return nil, err } diff --git a/gen/db/company.sql.go b/gen/db/company.sql.go index 506eaca..18bc509 100644 --- a/gen/db/company.sql.go +++ b/gen/db/company.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: company.sql package dbgen diff --git a/gen/db/copyfrom.go b/gen/db/copyfrom.go index 1212253..f7a4793 100644 --- a/gen/db/copyfrom.go +++ b/gen/db/copyfrom.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: copyfrom.go package dbgen diff --git a/gen/db/db.go b/gen/db/db.go index 84de07c..8134784 100644 --- a/gen/db/db.go +++ b/gen/db/db.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 package dbgen diff --git a/gen/db/direct_deposit.sql.go b/gen/db/direct_deposit.sql.go index be02750..ff5a3b2 100644 --- a/gen/db/direct_deposit.sql.go +++ b/gen/db/direct_deposit.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: direct_deposit.sql package dbgen diff --git a/gen/db/disabled_odds.sql.go b/gen/db/disabled_odds.sql.go index 85dcd2e..917acce 100644 --- a/gen/db/disabled_odds.sql.go +++ b/gen/db/disabled_odds.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: disabled_odds.sql package dbgen diff --git a/gen/db/event_history.sql.go b/gen/db/event_history.sql.go index ab29359..64762c3 100644 --- a/gen/db/event_history.sql.go +++ b/gen/db/event_history.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: event_history.sql package dbgen diff --git a/gen/db/events.sql.go b/gen/db/events.sql.go index 313b240..9c9afe7 100644 --- a/gen/db/events.sql.go +++ b/gen/db/events.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: events.sql package dbgen @@ -78,10 +78,21 @@ func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]EventWithCountry, } const GetEventWithSettingByID = `-- name: GetEventWithSettingByID :one -SELECT 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, company_id, is_active, is_featured, winning_upper_limit, updated_at, league_cc -FROM event_with_settings -WHERE id = $1 - AND company_id = $2 +SELECT e.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, + ces.company_id, + COALESCE(ces.is_active, e.default_is_active) AS is_active, + COALESCE(ces.is_featured, e.default_is_featured) AS is_featured, + COALESCE( + ces.winning_upper_limit, + e.default_winning_upper_limit + ) AS winning_upper_limit, + ces.updated_at, + l.country_code as league_cc +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 +WHERE e.id = $1 AND is_live = false AND status = 'upcoming' LIMIT 1 @@ -92,9 +103,43 @@ type GetEventWithSettingByIDParams struct { CompanyID int64 `json:"company_id"` } -func (q *Queries) GetEventWithSettingByID(ctx context.Context, arg GetEventWithSettingByIDParams) (EventWithSetting, error) { +type GetEventWithSettingByIDRow struct { + ID string `json:"id"` + SportID int32 `json:"sport_id"` + MatchName string `json:"match_name"` + HomeTeam string `json:"home_team"` + AwayTeam string `json:"away_team"` + HomeTeamID int64 `json:"home_team_id"` + AwayTeamID int64 `json:"away_team_id"` + HomeKitImage string `json:"home_kit_image"` + AwayKitImage string `json:"away_kit_image"` + LeagueID int64 `json:"league_id"` + LeagueName string `json:"league_name"` + StartTime pgtype.Timestamp `json:"start_time"` + Score pgtype.Text `json:"score"` + MatchMinute pgtype.Int4 `json:"match_minute"` + TimerStatus pgtype.Text `json:"timer_status"` + AddedTime pgtype.Int4 `json:"added_time"` + MatchPeriod pgtype.Int4 `json:"match_period"` + IsLive bool `json:"is_live"` + Status string `json:"status"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` + Source string `json:"source"` + DefaultIsActive bool `json:"default_is_active"` + DefaultIsFeatured bool `json:"default_is_featured"` + DefaultWinningUpperLimit int64 `json:"default_winning_upper_limit"` + IsMonitored bool `json:"is_monitored"` + CompanyID pgtype.Int8 `json:"company_id"` + IsActive bool `json:"is_active"` + IsFeatured bool `json:"is_featured"` + WinningUpperLimit int32 `json:"winning_upper_limit"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + LeagueCc pgtype.Text `json:"league_cc"` +} + +func (q *Queries) GetEventWithSettingByID(ctx context.Context, arg GetEventWithSettingByIDParams) (GetEventWithSettingByIDRow, error) { row := q.db.QueryRow(ctx, GetEventWithSettingByID, arg.ID, arg.CompanyID) - var i EventWithSetting + var i GetEventWithSettingByIDRow err := row.Scan( &i.ID, &i.SportID, @@ -132,10 +177,21 @@ func (q *Queries) GetEventWithSettingByID(ctx context.Context, arg GetEventWithS } const GetEventsWithSettings = `-- name: GetEventsWithSettings :many -SELECT 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, company_id, is_active, is_featured, winning_upper_limit, updated_at, league_cc -FROM event_with_settings -WHERE company_id = $1 - AND start_time > now() +SELECT e.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, + ces.company_id, + COALESCE(ces.is_active, e.default_is_active) AS is_active, + COALESCE(ces.is_featured, e.default_is_featured) AS is_featured, + COALESCE( + ces.winning_upper_limit, + e.default_winning_upper_limit + ) AS winning_upper_limit, + ces.updated_at, + l.country_code as league_cc +FROM events e + LEFT JOIN company_event_settings ces ON e.id = ces.event_id + AND ces.company_id = $1 + JOIN leagues l ON l.id = e.league_id +WHERE start_time > now() AND is_live = false AND status = 'upcoming' AND ( @@ -143,7 +199,7 @@ WHERE company_id = $1 OR $2 IS NULL ) AND ( - sport_id = $3 + e.sport_id = $3 OR $3 IS NULL ) AND ( @@ -160,11 +216,21 @@ WHERE company_id = $1 OR $6 IS NULL ) AND ( - league_cc = $7 + l.country_code = $7 OR $7 IS NULL ) + AND ( + ces.is_featured = $8 + OR e.default_is_featured = $8 + OR $8 IS NULL + ) + AND ( + ces.is_active = $9 + OR e.default_is_active = $9 + OR $9 IS NULL + ) ORDER BY start_time ASC -LIMIT $9 OFFSET $8 +LIMIT $11 OFFSET $10 ` type GetEventsWithSettingsParams struct { @@ -175,11 +241,47 @@ type GetEventsWithSettingsParams struct { LastStartTime pgtype.Timestamp `json:"last_start_time"` FirstStartTime pgtype.Timestamp `json:"first_start_time"` CountryCode pgtype.Text `json:"country_code"` + IsFeatured pgtype.Bool `json:"is_featured"` + IsActive pgtype.Bool `json:"is_active"` Offset pgtype.Int4 `json:"offset"` Limit pgtype.Int4 `json:"limit"` } -func (q *Queries) GetEventsWithSettings(ctx context.Context, arg GetEventsWithSettingsParams) ([]EventWithSetting, error) { +type GetEventsWithSettingsRow struct { + ID string `json:"id"` + SportID int32 `json:"sport_id"` + MatchName string `json:"match_name"` + HomeTeam string `json:"home_team"` + AwayTeam string `json:"away_team"` + HomeTeamID int64 `json:"home_team_id"` + AwayTeamID int64 `json:"away_team_id"` + HomeKitImage string `json:"home_kit_image"` + AwayKitImage string `json:"away_kit_image"` + LeagueID int64 `json:"league_id"` + LeagueName string `json:"league_name"` + StartTime pgtype.Timestamp `json:"start_time"` + Score pgtype.Text `json:"score"` + MatchMinute pgtype.Int4 `json:"match_minute"` + TimerStatus pgtype.Text `json:"timer_status"` + AddedTime pgtype.Int4 `json:"added_time"` + MatchPeriod pgtype.Int4 `json:"match_period"` + IsLive bool `json:"is_live"` + Status string `json:"status"` + FetchedAt pgtype.Timestamp `json:"fetched_at"` + Source string `json:"source"` + DefaultIsActive bool `json:"default_is_active"` + DefaultIsFeatured bool `json:"default_is_featured"` + DefaultWinningUpperLimit int64 `json:"default_winning_upper_limit"` + IsMonitored bool `json:"is_monitored"` + CompanyID pgtype.Int8 `json:"company_id"` + IsActive bool `json:"is_active"` + IsFeatured bool `json:"is_featured"` + WinningUpperLimit int32 `json:"winning_upper_limit"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + LeagueCc pgtype.Text `json:"league_cc"` +} + +func (q *Queries) GetEventsWithSettings(ctx context.Context, arg GetEventsWithSettingsParams) ([]GetEventsWithSettingsRow, error) { rows, err := q.db.Query(ctx, GetEventsWithSettings, arg.CompanyID, arg.LeagueID, @@ -188,6 +290,8 @@ func (q *Queries) GetEventsWithSettings(ctx context.Context, arg GetEventsWithSe arg.LastStartTime, arg.FirstStartTime, arg.CountryCode, + arg.IsFeatured, + arg.IsActive, arg.Offset, arg.Limit, ) @@ -195,9 +299,9 @@ func (q *Queries) GetEventsWithSettings(ctx context.Context, arg GetEventsWithSe return nil, err } defer rows.Close() - var items []EventWithSetting + var items []GetEventsWithSettingsRow for rows.Next() { - var i EventWithSetting + var i GetEventsWithSettingsRow if err := rows.Scan( &i.ID, &i.SportID, @@ -401,18 +505,37 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat return items, nil } +const GetSportAndLeagueIDs = `-- name: GetSportAndLeagueIDs :one +SELECT sport_id, league_id FROM events +WHERE id = $1 +` + +type GetSportAndLeagueIDsRow struct { + SportID int32 `json:"sport_id"` + LeagueID int64 `json:"league_id"` +} + +func (q *Queries) GetSportAndLeagueIDs(ctx context.Context, id string) (GetSportAndLeagueIDsRow, error) { + row := q.db.QueryRow(ctx, GetSportAndLeagueIDs, id) + var i GetSportAndLeagueIDsRow + err := row.Scan(&i.SportID, &i.LeagueID) + return i, err +} + const GetTotalCompanyEvents = `-- name: GetTotalCompanyEvents :one SELECT COUNT(*) -FROM event_with_settings -WHERE company_id = $1 - AND is_live = false +FROM events e + LEFT JOIN company_event_settings ces ON e.id = ces.event_id + AND ces.company_id = $1 + JOIN leagues l ON l.id = e.league_id +WHERE is_live = false AND status = 'upcoming' AND ( league_id = $2 OR $2 IS NULL ) AND ( - sport_id = $3 + e.sport_id = $3 OR $3 IS NULL ) AND ( @@ -429,9 +552,19 @@ WHERE company_id = $1 OR $6 IS NULL ) AND ( - league_cc = $7 + l.country_code = $7 OR $7 IS NULL ) + AND ( + ces.is_featured = $8 + OR e.default_is_featured = $8 + OR $8 IS NULL + ) + AND ( + ces.is_active = $9 + OR e.default_is_active = $9 + OR $9 IS NULL + ) ` type GetTotalCompanyEventsParams struct { @@ -442,6 +575,8 @@ type GetTotalCompanyEventsParams struct { LastStartTime pgtype.Timestamp `json:"last_start_time"` FirstStartTime pgtype.Timestamp `json:"first_start_time"` CountryCode pgtype.Text `json:"country_code"` + IsFeatured pgtype.Bool `json:"is_featured"` + IsActive pgtype.Bool `json:"is_active"` } func (q *Queries) GetTotalCompanyEvents(ctx context.Context, arg GetTotalCompanyEventsParams) (int64, error) { @@ -453,6 +588,8 @@ func (q *Queries) GetTotalCompanyEvents(ctx context.Context, arg GetTotalCompany arg.LastStartTime, arg.FirstStartTime, arg.CountryCode, + arg.IsFeatured, + arg.IsActive, ) var count int64 err := row.Scan(&count) @@ -573,7 +710,8 @@ INSERT INTO events ( start_time, is_live, status, - source + source, + default_winning_upper_limit ) VALUES ( $1, @@ -590,7 +728,8 @@ VALUES ( $12, $13, $14, - $15 + $15, + $16 ) ON CONFLICT (id) DO UPDATE SET sport_id = EXCLUDED.sport_id, @@ -603,7 +742,6 @@ SET sport_id = EXCLUDED.sport_id, away_kit_image = EXCLUDED.away_kit_image, league_id = EXCLUDED.league_id, league_name = EXCLUDED.league_name, - league_cc = EXCLUDED.league_cc, start_time = EXCLUDED.start_time, score = EXCLUDED.score, match_minute = EXCLUDED.match_minute, @@ -612,25 +750,27 @@ SET sport_id = EXCLUDED.sport_id, match_period = EXCLUDED.match_period, is_live = EXCLUDED.is_live, source = EXCLUDED.source, + default_winning_upper_limit = EXCLUDED.default_winning_upper_limit, fetched_at = now() ` type InsertEventParams struct { - ID string `json:"id"` - SportID int32 `json:"sport_id"` - MatchName string `json:"match_name"` - HomeTeam string `json:"home_team"` - AwayTeam string `json:"away_team"` - HomeTeamID int64 `json:"home_team_id"` - AwayTeamID int64 `json:"away_team_id"` - HomeKitImage string `json:"home_kit_image"` - AwayKitImage string `json:"away_kit_image"` - LeagueID int64 `json:"league_id"` - LeagueName string `json:"league_name"` - StartTime pgtype.Timestamp `json:"start_time"` - IsLive bool `json:"is_live"` - Status string `json:"status"` - Source string `json:"source"` + ID string `json:"id"` + SportID int32 `json:"sport_id"` + MatchName string `json:"match_name"` + HomeTeam string `json:"home_team"` + AwayTeam string `json:"away_team"` + HomeTeamID int64 `json:"home_team_id"` + AwayTeamID int64 `json:"away_team_id"` + HomeKitImage string `json:"home_kit_image"` + AwayKitImage string `json:"away_kit_image"` + LeagueID int64 `json:"league_id"` + LeagueName string `json:"league_name"` + StartTime pgtype.Timestamp `json:"start_time"` + IsLive bool `json:"is_live"` + Status string `json:"status"` + Source string `json:"source"` + DefaultWinningUpperLimit int64 `json:"default_winning_upper_limit"` } func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) error { @@ -650,40 +790,7 @@ func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) error arg.IsLive, arg.Status, arg.Source, - ) - return err -} - -const InsertEventSettings = `-- name: InsertEventSettings :exec -INSERT INTO company_event_settings ( - company_id, - event_id, - is_active, - is_featured, - winning_upper_limit - ) -VALUES ($1, $2, $3, $4, $5) ON CONFLICT(company_id, event_id) DO -UPDATE -SET is_active = EXCLUDED.is_active, - is_featured = EXCLUDED.is_featured, - winning_upper_limit = EXCLUDED.winning_upper_limit -` - -type InsertEventSettingsParams struct { - CompanyID int64 `json:"company_id"` - EventID string `json:"event_id"` - IsActive pgtype.Bool `json:"is_active"` - IsFeatured pgtype.Bool `json:"is_featured"` - WinningUpperLimit pgtype.Int4 `json:"winning_upper_limit"` -} - -func (q *Queries) InsertEventSettings(ctx context.Context, arg InsertEventSettingsParams) error { - _, err := q.db.Exec(ctx, InsertEventSettings, - arg.CompanyID, - arg.EventID, - arg.IsActive, - arg.IsFeatured, - arg.WinningUpperLimit, + arg.DefaultWinningUpperLimit, ) return err } @@ -727,6 +834,40 @@ func (q *Queries) ListLiveEvents(ctx context.Context) ([]string, error) { return items, nil } +const SaveEventSettings = `-- name: SaveEventSettings :exec +INSERT INTO company_event_settings ( + company_id, + event_id, + is_active, + is_featured, + winning_upper_limit + ) +VALUES ($1, $2, $3, $4, $5) ON CONFLICT(company_id, event_id) DO +UPDATE +SET is_active = EXCLUDED.is_active, + is_featured = EXCLUDED.is_featured, + winning_upper_limit = EXCLUDED.winning_upper_limit +` + +type SaveEventSettingsParams struct { + CompanyID int64 `json:"company_id"` + EventID string `json:"event_id"` + IsActive pgtype.Bool `json:"is_active"` + IsFeatured pgtype.Bool `json:"is_featured"` + WinningUpperLimit pgtype.Int4 `json:"winning_upper_limit"` +} + +func (q *Queries) SaveEventSettings(ctx context.Context, arg SaveEventSettingsParams) error { + _, err := q.db.Exec(ctx, SaveEventSettings, + arg.CompanyID, + arg.EventID, + arg.IsActive, + arg.IsFeatured, + arg.WinningUpperLimit, + ) + return err +} + const UpdateEventMonitored = `-- name: UpdateEventMonitored :exec UPDATE events SET is_monitored = $1 @@ -743,40 +884,6 @@ func (q *Queries) UpdateEventMonitored(ctx context.Context, arg UpdateEventMonit return err } -const UpdateEventSettings = `-- name: UpdateEventSettings :exec -UPDATE company_event_settings -SET is_active = COALESCE($3, is_active), - is_featured = COALESCE( - $4, - is_featured - ), - winning_upper_limit = COALESCE( - $5, - winning_upper_limit - ) -WHERE event_id = $1 - AND company_id = $2 -` - -type UpdateEventSettingsParams struct { - EventID string `json:"event_id"` - CompanyID int64 `json:"company_id"` - IsActive pgtype.Bool `json:"is_active"` - IsFeatured pgtype.Bool `json:"is_featured"` - WinningUpperLimit pgtype.Int4 `json:"winning_upper_limit"` -} - -func (q *Queries) UpdateEventSettings(ctx context.Context, arg UpdateEventSettingsParams) error { - _, err := q.db.Exec(ctx, UpdateEventSettings, - arg.EventID, - arg.CompanyID, - arg.IsActive, - arg.IsFeatured, - arg.WinningUpperLimit, - ) - return err -} - const UpdateMatchResult = `-- name: UpdateMatchResult :exec UPDATE events SET score = $1, diff --git a/gen/db/events_stat.sql.go b/gen/db/events_stat.sql.go index 677fa2a..615e2fa 100644 --- a/gen/db/events_stat.sql.go +++ b/gen/db/events_stat.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: events_stat.sql package dbgen diff --git a/gen/db/flags.sql.go b/gen/db/flags.sql.go index 653543f..4b82cac 100644 --- a/gen/db/flags.sql.go +++ b/gen/db/flags.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: flags.sql package dbgen diff --git a/gen/db/institutions.sql.go b/gen/db/institutions.sql.go index 324ac3e..61ca108 100644 --- a/gen/db/institutions.sql.go +++ b/gen/db/institutions.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: institutions.sql package dbgen diff --git a/gen/db/issue_reporting.sql.go b/gen/db/issue_reporting.sql.go index 7fcb4af..e35fba1 100644 --- a/gen/db/issue_reporting.sql.go +++ b/gen/db/issue_reporting.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: issue_reporting.sql package dbgen diff --git a/gen/db/leagues.sql.go b/gen/db/leagues.sql.go index 5d49d4d..0aaad2c 100644 --- a/gen/db/leagues.sql.go +++ b/gen/db/leagues.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: leagues.sql package dbgen @@ -44,13 +44,18 @@ WHERE ( sport_id = $2 OR $2 IS NULL ) + AND ( + name ILIKE '%' || $3 || '%' + OR $3 IS NULL + ) ORDER BY name ASC -LIMIT $4 OFFSET $3 +LIMIT $5 OFFSET $4 ` type GetAllLeaguesParams struct { CountryCode pgtype.Text `json:"country_code"` SportID pgtype.Int4 `json:"sport_id"` + Query pgtype.Text `json:"query"` Offset pgtype.Int4 `json:"offset"` Limit pgtype.Int4 `json:"limit"` } @@ -59,6 +64,7 @@ func (q *Queries) GetAllLeagues(ctx context.Context, arg GetAllLeaguesParams) ([ rows, err := q.db.Query(ctx, GetAllLeagues, arg.CountryCode, arg.SportID, + arg.Query, arg.Offset, arg.Limit, ) @@ -90,10 +96,15 @@ func (q *Queries) GetAllLeagues(ctx context.Context, arg GetAllLeaguesParams) ([ } const GetAllLeaguesWithSettings = `-- name: GetAllLeaguesWithSettings :many -SELECT id, name, img_url, country_code, bet365_id, sport_id, default_is_active, default_is_featured, company_id, is_active, is_featured, updated_at -FROM league_with_settings -WHERE (company_id = $1) - AND ( +SELECT l.id, l.name, l.img_url, l.country_code, l.bet365_id, l.sport_id, l.default_is_active, l.default_is_featured, + cls.company_id, + COALESCE(cls.is_active, l.default_is_active) AS is_active, + COALESCE(cls.is_featured, l.default_is_featured) AS is_featured, + cls.updated_at +FROM leagues l + LEFT JOIN company_league_settings cls ON l.id = cls.league_id + AND company_id = $1 +WHERE ( country_code = $2 OR $2 IS NULL ) @@ -103,15 +114,21 @@ WHERE (company_id = $1) ) AND ( is_active = $4 + OR default_is_active = $4 OR $4 IS NULL ) AND ( is_featured = $5 + OR default_is_featured = $5 OR $5 IS NULL ) + AND ( + name ILIKE '%' || $6 || '%' + OR $6 IS NULL + ) ORDER BY is_featured DESC, name ASC -LIMIT $7 OFFSET $6 +LIMIT $8 OFFSET $7 ` type GetAllLeaguesWithSettingsParams struct { @@ -120,17 +137,34 @@ type GetAllLeaguesWithSettingsParams struct { SportID pgtype.Int4 `json:"sport_id"` IsActive pgtype.Bool `json:"is_active"` IsFeatured pgtype.Bool `json:"is_featured"` + Query pgtype.Text `json:"query"` Offset pgtype.Int4 `json:"offset"` Limit pgtype.Int4 `json:"limit"` } -func (q *Queries) GetAllLeaguesWithSettings(ctx context.Context, arg GetAllLeaguesWithSettingsParams) ([]LeagueWithSetting, error) { +type GetAllLeaguesWithSettingsRow struct { + ID int64 `json:"id"` + Name string `json:"name"` + ImgUrl pgtype.Text `json:"img_url"` + CountryCode pgtype.Text `json:"country_code"` + Bet365ID pgtype.Int4 `json:"bet365_id"` + SportID int32 `json:"sport_id"` + DefaultIsActive bool `json:"default_is_active"` + DefaultIsFeatured bool `json:"default_is_featured"` + CompanyID pgtype.Int8 `json:"company_id"` + IsActive bool `json:"is_active"` + IsFeatured bool `json:"is_featured"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` +} + +func (q *Queries) GetAllLeaguesWithSettings(ctx context.Context, arg GetAllLeaguesWithSettingsParams) ([]GetAllLeaguesWithSettingsRow, error) { rows, err := q.db.Query(ctx, GetAllLeaguesWithSettings, arg.CompanyID, arg.CountryCode, arg.SportID, arg.IsActive, arg.IsFeatured, + arg.Query, arg.Offset, arg.Limit, ) @@ -138,9 +172,9 @@ func (q *Queries) GetAllLeaguesWithSettings(ctx context.Context, arg GetAllLeagu return nil, err } defer rows.Close() - var items []LeagueWithSetting + var items []GetAllLeaguesWithSettingsRow for rows.Next() { - var i LeagueWithSetting + var i GetAllLeaguesWithSettingsRow if err := rows.Scan( &i.ID, &i.Name, @@ -165,6 +199,58 @@ func (q *Queries) GetAllLeaguesWithSettings(ctx context.Context, arg GetAllLeagu return items, nil } +const GetTotalLeaguesWithSettings = `-- name: GetTotalLeaguesWithSettings :one +SELECT COUNT(*) +FROM leagues l + LEFT JOIN company_league_settings cls ON l.id = cls.league_id + AND company_id = $1 +WHERE ( + country_code = $2 + OR $2 IS NULL + ) + AND ( + sport_id = $3 + OR $3 IS NULL + ) + AND ( + is_active = $4 + OR default_is_active = $4 + OR $4 IS NULL + ) + AND ( + is_featured = $5 + OR default_is_featured = $5 + OR $5 IS NULL + ) + AND ( + name ILIKE '%' || $6 || '%' + OR $6 IS NULL + ) +` + +type GetTotalLeaguesWithSettingsParams struct { + CompanyID int64 `json:"company_id"` + CountryCode pgtype.Text `json:"country_code"` + SportID pgtype.Int4 `json:"sport_id"` + IsActive pgtype.Bool `json:"is_active"` + IsFeatured pgtype.Bool `json:"is_featured"` + Query pgtype.Text `json:"query"` +} + +func (q *Queries) GetTotalLeaguesWithSettings(ctx context.Context, arg GetTotalLeaguesWithSettingsParams) (int64, error) { + row := q.db.QueryRow(ctx, GetTotalLeaguesWithSettings, + arg.CompanyID, + arg.CountryCode, + arg.SportID, + arg.IsActive, + arg.IsFeatured, + arg.Query, + ) + var count int64 + err := row.Scan(&count) + return count, err +} + const InsertLeague = `-- name: InsertLeague :exec INSERT INTO leagues ( id, diff --git a/gen/db/location.sql.go b/gen/db/location.sql.go index 008aa61..254c73a 100644 --- a/gen/db/location.sql.go +++ b/gen/db/location.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: location.sql package dbgen diff --git a/gen/db/models.go b/gen/db/models.go index 68ebcd4..1e27632 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -1,60 +1,13 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 package dbgen import ( - "database/sql/driver" - "fmt" - "github.com/jackc/pgx/v5/pgtype" ) -type Referralstatus string - -const ( - ReferralstatusPENDING Referralstatus = "PENDING" - ReferralstatusCOMPLETED Referralstatus = "COMPLETED" - ReferralstatusEXPIRED Referralstatus = "EXPIRED" - ReferralstatusCANCELLED Referralstatus = "CANCELLED" -) - -func (e *Referralstatus) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = Referralstatus(s) - case string: - *e = Referralstatus(s) - default: - return fmt.Errorf("unsupported scan type for Referralstatus: %T", src) - } - return nil -} - -type NullReferralstatus struct { - Referralstatus Referralstatus `json:"referralstatus"` - Valid bool `json:"valid"` // Valid is true if Referralstatus is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullReferralstatus) Scan(value interface{}) error { - if value == nil { - ns.Referralstatus, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.Referralstatus.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullReferralstatus) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.Referralstatus), nil -} - type Bank struct { ID int64 `json:"id"` Slug string `json:"slug"` @@ -126,12 +79,6 @@ type BetWithOutcome struct { Outcomes []BetOutcome `json:"outcomes"` } -type Bonu struct { - Multiplier float32 `json:"multiplier"` - ID int64 `json:"id"` - BalanceCap int64 `json:"balance_cap"` -} - type Branch struct { ID int64 `json:"id"` Name string `json:"name"` @@ -321,7 +268,7 @@ type Event struct { Source string `json:"source"` DefaultIsActive bool `json:"default_is_active"` DefaultIsFeatured bool `json:"default_is_featured"` - DefaultWinningUpperLimit int32 `json:"default_winning_upper_limit"` + DefaultWinningUpperLimit int64 `json:"default_winning_upper_limit"` IsMonitored bool `json:"is_monitored"` } @@ -356,7 +303,7 @@ type EventWithCountry struct { Source string `json:"source"` DefaultIsActive bool `json:"default_is_active"` DefaultIsFeatured bool `json:"default_is_featured"` - DefaultWinningUpperLimit int32 `json:"default_winning_upper_limit"` + DefaultWinningUpperLimit int64 `json:"default_winning_upper_limit"` IsMonitored bool `json:"is_monitored"` LeagueCc pgtype.Text `json:"league_cc"` } @@ -385,9 +332,9 @@ type EventWithSetting struct { Source string `json:"source"` DefaultIsActive bool `json:"default_is_active"` DefaultIsFeatured bool `json:"default_is_featured"` - DefaultWinningUpperLimit int32 `json:"default_winning_upper_limit"` + DefaultWinningUpperLimit int64 `json:"default_winning_upper_limit"` IsMonitored bool `json:"is_monitored"` - CompanyID int64 `json:"company_id"` + CompanyID pgtype.Int8 `json:"company_id"` IsActive bool `json:"is_active"` IsFeatured bool `json:"is_featured"` WinningUpperLimit int32 `json:"winning_upper_limit"` @@ -447,7 +394,7 @@ type LeagueWithSetting struct { SportID int32 `json:"sport_id"` DefaultIsActive bool `json:"default_is_active"` DefaultIsFeatured bool `json:"default_is_featured"` - CompanyID int64 `json:"company_id"` + CompanyID pgtype.Int8 `json:"company_id"` IsActive bool `json:"is_active"` IsFeatured bool `json:"is_featured"` UpdatedAt pgtype.Timestamp `json:"updated_at"` @@ -520,7 +467,7 @@ type OddsMarketWithSetting struct { DefaultIsActive bool `json:"default_is_active"` FetchedAt pgtype.Timestamp `json:"fetched_at"` ExpiresAt pgtype.Timestamp `json:"expires_at"` - CompanyID int64 `json:"company_id"` + CompanyID pgtype.Int8 `json:"company_id"` IsActive bool `json:"is_active"` RawOdds []byte `json:"raw_odds"` UpdatedAt pgtype.Timestamp `json:"updated_at"` @@ -538,30 +485,54 @@ type Otp struct { ExpiresAt pgtype.Timestamptz `json:"expires_at"` } -type Referral struct { - ID int64 `json:"id"` - ReferralCode string `json:"referral_code"` - ReferrerID string `json:"referrer_id"` - ReferredID pgtype.Text `json:"referred_id"` - Status Referralstatus `json:"status"` - RewardAmount pgtype.Numeric `json:"reward_amount"` - CashbackAmount pgtype.Numeric `json:"cashback_amount"` - CreatedAt pgtype.Timestamptz `json:"created_at"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` - ExpiresAt pgtype.Timestamptz `json:"expires_at"` +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"` } -type ReferralSetting struct { - ID int64 `json:"id"` - ReferralRewardAmount pgtype.Numeric `json:"referral_reward_amount"` - CashbackPercentage pgtype.Numeric `json:"cashback_percentage"` - BetReferralBonusPercentage pgtype.Numeric `json:"bet_referral_bonus_percentage"` - MaxReferrals int32 `json:"max_referrals"` - ExpiresAfterDays int32 `json:"expires_after_days"` - UpdatedBy string `json:"updated_by"` - CreatedAt pgtype.Timestamptz `json:"created_at"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` - Version int32 `json:"version"` +type RaffleGameFilter struct { + ID int32 `json:"id"` + RaffleID int32 `json:"raffle_id"` + GameID string `json:"game_id"` +} + +type RaffleSportFilter struct { + ID int32 `json:"id"` + RaffleID int32 `json:"raffle_id"` + SportID int64 `json:"sport_id"` + LeagueID int64 `json:"league_id"` +} + +type RaffleTicket struct { + ID int32 `json:"id"` + RaffleID int32 `json:"raffle_id"` + UserID int32 `json:"user_id"` + IsActive pgtype.Bool `json:"is_active"` +} + +type RaffleWinner struct { + ID int32 `json:"id"` + RaffleID int32 `json:"raffle_id"` + UserID int32 `json:"user_id"` + Rank int32 `json:"rank"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +type ReferralCode struct { + ID int64 `json:"id"` + ReferralCode string `json:"referral_code"` + ReferrerID int64 `json:"referrer_id"` + CompanyID int64 `json:"company_id"` + IsActive bool `json:"is_active"` + NumberOfReferrals int64 `json:"number_of_referrals"` + RewardAmount int64 `json:"reward_amount"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + UpdatedAt pgtype.Timestamptz `json:"updated_at"` } type RefreshToken struct { @@ -794,8 +765,20 @@ type User struct { CompanyID pgtype.Int8 `json:"company_id"` SuspendedAt pgtype.Timestamptz `json:"suspended_at"` Suspended bool `json:"suspended"` - ReferralCode pgtype.Text `json:"referral_code"` - ReferredBy pgtype.Text `json:"referred_by"` +} + +type UserBonuse struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Type string `json:"type"` + UserID int64 `json:"user_id"` + RewardAmount int64 `json:"reward_amount"` + IsClaimed bool `json:"is_claimed"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` + ClaimedAt pgtype.Timestamp `json:"claimed_at"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` } type UserGameInteraction struct { @@ -808,6 +791,13 @@ type UserGameInteraction struct { CreatedAt pgtype.Timestamptz `json:"created_at"` } +type UserReferral struct { + ID int64 `json:"id"` + ReferredID int64 `json:"referred_id"` + ReferralCodeID int64 `json:"referral_code_id"` + CreatedAt pgtype.Timestamptz `json:"created_at"` +} + type VirtualGame struct { ID int64 `json:"id"` GameID string `json:"game_id"` @@ -896,8 +886,6 @@ type Wallet struct { IsActive bool `json:"is_active"` CreatedAt pgtype.Timestamp `json:"created_at"` UpdatedAt pgtype.Timestamp `json:"updated_at"` - BonusBalance pgtype.Numeric `json:"bonus_balance"` - CashBalance pgtype.Numeric `json:"cash_balance"` } type WalletThresholdNotification struct { diff --git a/gen/db/monitor.sql.go b/gen/db/monitor.sql.go index a9a7ecb..b5f248f 100644 --- a/gen/db/monitor.sql.go +++ b/gen/db/monitor.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: monitor.sql package dbgen diff --git a/gen/db/notification.sql.go b/gen/db/notification.sql.go index ba9882b..9ce7e42 100644 --- a/gen/db/notification.sql.go +++ b/gen/db/notification.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: notification.sql package dbgen diff --git a/gen/db/odd_history.sql.go b/gen/db/odd_history.sql.go index 0a0333d..dd69a51 100644 --- a/gen/db/odd_history.sql.go +++ b/gen/db/odd_history.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: odd_history.sql package dbgen diff --git a/gen/db/odds.sql.go b/gen/db/odds.sql.go index 33fcde8..d194d14 100644 --- a/gen/db/odds.sql.go +++ b/gen/db/odds.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: odds.sql package dbgen @@ -68,9 +68,22 @@ func (q *Queries) GetAllOdds(ctx context.Context, arg GetAllOddsParams) ([]OddsM } const GetAllOddsWithSettings = `-- name: GetAllOddsWithSettings :many -SELECT id, event_id, market_type, market_name, market_category, market_id, default_is_active, fetched_at, expires_at, company_id, is_active, raw_odds, updated_at -FROM odds_market_with_settings -WHERE company_id = $1 +SELECT o.id, + o.event_id, + o.market_type, + o.market_name, + o.market_category, + o.market_id, + o.default_is_active, + o.fetched_at, + o.expires_at, + cos.company_id, + COALESCE(cos.is_active, o.default_is_active) AS is_active, + COALESCE(cos.custom_raw_odds, o.raw_odds) AS raw_odds, + cos.updated_at +FROM odds_market o + LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id + AND company_id = $1 LIMIT $3 OFFSET $2 ` @@ -80,15 +93,31 @@ type GetAllOddsWithSettingsParams struct { Limit pgtype.Int4 `json:"limit"` } -func (q *Queries) GetAllOddsWithSettings(ctx context.Context, arg GetAllOddsWithSettingsParams) ([]OddsMarketWithSetting, error) { +type GetAllOddsWithSettingsRow struct { + ID int64 `json:"id"` + EventID string `json:"event_id"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID string `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"` +} + +func (q *Queries) GetAllOddsWithSettings(ctx context.Context, arg GetAllOddsWithSettingsParams) ([]GetAllOddsWithSettingsRow, error) { rows, err := q.db.Query(ctx, GetAllOddsWithSettings, arg.CompanyID, arg.Offset, arg.Limit) if err != nil { return nil, err } defer rows.Close() - var items []OddsMarketWithSetting + var items []GetAllOddsWithSettingsRow for rows.Next() { - var i OddsMarketWithSetting + var i GetAllOddsWithSettingsRow if err := rows.Scan( &i.ID, &i.EventID, @@ -114,6 +143,34 @@ func (q *Queries) GetAllOddsWithSettings(ctx context.Context, arg GetAllOddsWith return items, nil } +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 +FROM odds_market_with_event +WHERE id = $1 +` + +func (q *Queries) GetOddByID(ctx context.Context, id int64) (OddsMarketWithEvent, error) { + row := q.db.QueryRow(ctx, GetOddByID, id) + var i OddsMarketWithEvent + err := row.Scan( + &i.ID, + &i.EventID, + &i.MarketType, + &i.MarketName, + &i.MarketCategory, + &i.MarketID, + &i.RawOdds, + &i.DefaultIsActive, + &i.FetchedAt, + &i.ExpiresAt, + &i.IsMonitored, + &i.IsLive, + &i.Status, + &i.Source, + ) + return i, err +} + 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 FROM odds_market_with_event @@ -219,10 +276,23 @@ func (q *Queries) GetOddsByMarketID(ctx context.Context, arg GetOddsByMarketIDPa } const GetOddsWithSettingsByEventID = `-- name: GetOddsWithSettingsByEventID :many -SELECT id, event_id, market_type, market_name, market_category, market_id, default_is_active, fetched_at, expires_at, company_id, is_active, raw_odds, updated_at -FROM odds_market_with_settings -WHERE event_id = $1 +SELECT o.id, + o.event_id, + o.market_type, + o.market_name, + o.market_category, + o.market_id, + o.default_is_active, + o.fetched_at, + o.expires_at, + cos.company_id, + COALESCE(cos.is_active, o.default_is_active) AS is_active, + COALESCE(cos.custom_raw_odds, o.raw_odds) AS raw_odds, + cos.updated_at +FROM odds_market o + LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id AND company_id = $2 +WHERE event_id = $1 LIMIT $4 OFFSET $3 ` @@ -233,7 +303,23 @@ type GetOddsWithSettingsByEventIDParams struct { Limit pgtype.Int4 `json:"limit"` } -func (q *Queries) GetOddsWithSettingsByEventID(ctx context.Context, arg GetOddsWithSettingsByEventIDParams) ([]OddsMarketWithSetting, error) { +type GetOddsWithSettingsByEventIDRow struct { + ID int64 `json:"id"` + EventID string `json:"event_id"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID string `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"` +} + +func (q *Queries) GetOddsWithSettingsByEventID(ctx context.Context, arg GetOddsWithSettingsByEventIDParams) ([]GetOddsWithSettingsByEventIDRow, error) { rows, err := q.db.Query(ctx, GetOddsWithSettingsByEventID, arg.EventID, arg.CompanyID, @@ -244,9 +330,9 @@ func (q *Queries) GetOddsWithSettingsByEventID(ctx context.Context, arg GetOddsW return nil, err } defer rows.Close() - var items []OddsMarketWithSetting + var items []GetOddsWithSettingsByEventIDRow for rows.Next() { - var i OddsMarketWithSetting + var i GetOddsWithSettingsByEventIDRow if err := rows.Scan( &i.ID, &i.EventID, @@ -272,23 +358,50 @@ func (q *Queries) GetOddsWithSettingsByEventID(ctx context.Context, arg GetOddsW return items, nil } -const GetOddsWithSettingsByMarketID = `-- name: GetOddsWithSettingsByMarketID :one -SELECT id, event_id, market_type, market_name, market_category, market_id, default_is_active, fetched_at, expires_at, company_id, is_active, raw_odds, updated_at -FROM odds_market_with_settings -WHERE market_id = $1 - AND event_id = $2 - AND company_id = $3 +const GetOddsWithSettingsByID = `-- name: GetOddsWithSettingsByID :one +SELECT o.id, + o.event_id, + o.market_type, + o.market_name, + o.market_category, + o.market_id, + o.default_is_active, + o.fetched_at, + o.expires_at, + cos.company_id, + COALESCE(cos.is_active, o.default_is_active) AS is_active, + COALESCE(cos.custom_raw_odds, o.raw_odds) AS raw_odds, + cos.updated_at +FROM odds_market o + LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id + AND company_id = $2 +WHERE o.id = $1 ` -type GetOddsWithSettingsByMarketIDParams struct { - MarketID string `json:"market_id"` - EventID string `json:"event_id"` - CompanyID int64 `json:"company_id"` +type GetOddsWithSettingsByIDParams struct { + ID int64 `json:"id"` + CompanyID int64 `json:"company_id"` } -func (q *Queries) GetOddsWithSettingsByMarketID(ctx context.Context, arg GetOddsWithSettingsByMarketIDParams) (OddsMarketWithSetting, error) { - row := q.db.QueryRow(ctx, GetOddsWithSettingsByMarketID, arg.MarketID, arg.EventID, arg.CompanyID) - var i OddsMarketWithSetting +type GetOddsWithSettingsByIDRow struct { + ID int64 `json:"id"` + EventID string `json:"event_id"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID string `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"` +} + +func (q *Queries) GetOddsWithSettingsByID(ctx context.Context, arg GetOddsWithSettingsByIDParams) (GetOddsWithSettingsByIDRow, error) { + row := q.db.QueryRow(ctx, GetOddsWithSettingsByID, arg.ID, arg.CompanyID) + var i GetOddsWithSettingsByIDRow err := row.Scan( &i.ID, &i.EventID, @@ -307,34 +420,68 @@ func (q *Queries) GetOddsWithSettingsByMarketID(ctx context.Context, arg GetOdds return i, err } -const InsertOddSettings = `-- name: InsertOddSettings :exec -INSERT INTO company_odd_settings ( - company_id, - odds_market_id, - is_active, - custom_raw_odds - ) -VALUES ($1, $2, $3, $4) ON CONFLICT (company_id, odds_market_id) DO -UPDATE -SET is_active = EXCLUDED.is_active, - custom_raw_odds = EXCLUDED.custom_raw_odds +const GetOddsWithSettingsByMarketID = `-- name: GetOddsWithSettingsByMarketID :one +SELECT o.id, + o.event_id, + o.market_type, + o.market_name, + o.market_category, + o.market_id, + o.default_is_active, + o.fetched_at, + o.expires_at, + cos.company_id, + COALESCE(cos.is_active, o.default_is_active) AS is_active, + COALESCE(cos.custom_raw_odds, o.raw_odds) AS raw_odds, + cos.updated_at +FROM odds_market o + LEFT JOIN company_odd_settings cos ON o.id = cos.odds_market_id + AND company_id = $3 +WHERE market_id = $1 + AND event_id = $2 ` -type InsertOddSettingsParams struct { - CompanyID int64 `json:"company_id"` - OddsMarketID int64 `json:"odds_market_id"` - IsActive pgtype.Bool `json:"is_active"` - CustomRawOdds []byte `json:"custom_raw_odds"` +type GetOddsWithSettingsByMarketIDParams struct { + MarketID string `json:"market_id"` + EventID string `json:"event_id"` + CompanyID int64 `json:"company_id"` } -func (q *Queries) InsertOddSettings(ctx context.Context, arg InsertOddSettingsParams) error { - _, err := q.db.Exec(ctx, InsertOddSettings, - arg.CompanyID, - arg.OddsMarketID, - arg.IsActive, - arg.CustomRawOdds, +type GetOddsWithSettingsByMarketIDRow struct { + ID int64 `json:"id"` + EventID string `json:"event_id"` + MarketType string `json:"market_type"` + MarketName string `json:"market_name"` + MarketCategory string `json:"market_category"` + MarketID string `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"` +} + +func (q *Queries) GetOddsWithSettingsByMarketID(ctx context.Context, arg GetOddsWithSettingsByMarketIDParams) (GetOddsWithSettingsByMarketIDRow, error) { + row := q.db.QueryRow(ctx, GetOddsWithSettingsByMarketID, arg.MarketID, arg.EventID, arg.CompanyID) + var i GetOddsWithSettingsByMarketIDRow + err := row.Scan( + &i.ID, + &i.EventID, + &i.MarketType, + &i.MarketName, + &i.MarketCategory, + &i.MarketID, + &i.DefaultIsActive, + &i.FetchedAt, + &i.ExpiresAt, + &i.CompanyID, + &i.IsActive, + &i.RawOdds, + &i.UpdatedAt, ) - return err + return i, err } const InsertOddsMarket = `-- name: InsertOddsMarket :exec @@ -391,3 +538,33 @@ func (q *Queries) InsertOddsMarket(ctx context.Context, arg InsertOddsMarketPara ) return err } + +const SaveOddSettings = `-- name: SaveOddSettings :exec +INSERT INTO company_odd_settings ( + company_id, + odds_market_id, + is_active, + custom_raw_odds + ) +VALUES ($1, $2, $3, $4) ON CONFLICT (company_id, odds_market_id) DO +UPDATE +SET is_active = EXCLUDED.is_active, + custom_raw_odds = EXCLUDED.custom_raw_odds +` + +type SaveOddSettingsParams struct { + CompanyID int64 `json:"company_id"` + OddsMarketID int64 `json:"odds_market_id"` + IsActive pgtype.Bool `json:"is_active"` + CustomRawOdds []byte `json:"custom_raw_odds"` +} + +func (q *Queries) SaveOddSettings(ctx context.Context, arg SaveOddSettingsParams) error { + _, err := q.db.Exec(ctx, SaveOddSettings, + arg.CompanyID, + arg.OddsMarketID, + arg.IsActive, + arg.CustomRawOdds, + ) + return err +} diff --git a/gen/db/otp.sql.go b/gen/db/otp.sql.go index 7dba175..c96aaaa 100644 --- a/gen/db/otp.sql.go +++ b/gen/db/otp.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: otp.sql package dbgen diff --git a/gen/db/raffle.sql.go b/gen/db/raffle.sql.go new file mode 100644 index 0000000..a4888f9 --- /dev/null +++ b/gen/db/raffle.sql.go @@ -0,0 +1,328 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: raffle.sql + +package dbgen + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const AddSportRaffleFilter = `-- name: AddSportRaffleFilter :one +INSERT INTO raffle_sport_filters (raffle_id, sport_id, league_id) +VALUES ($1, $2, $3) +RETURNING id, raffle_id, sport_id, league_id +` + +type AddSportRaffleFilterParams struct { + RaffleID int32 `json:"raffle_id"` + SportID int64 `json:"sport_id"` + LeagueID int64 `json:"league_id"` +} + +func (q *Queries) AddSportRaffleFilter(ctx context.Context, arg AddSportRaffleFilterParams) (RaffleSportFilter, error) { + row := q.db.QueryRow(ctx, AddSportRaffleFilter, arg.RaffleID, arg.SportID, arg.LeagueID) + var i RaffleSportFilter + err := row.Scan( + &i.ID, + &i.RaffleID, + &i.SportID, + &i.LeagueID, + ) + return i, err +} + +const CheckValidSportRaffleFilter = `-- name: CheckValidSportRaffleFilter :one +SELECT COUNT(*) > 0 AS exists +FROM raffle_sport_filters +WHERE raffle_id = $1 + AND sport_id = $2 + AND league_id = $3 +` + +type CheckValidSportRaffleFilterParams struct { + RaffleID int32 `json:"raffle_id"` + SportID int64 `json:"sport_id"` + LeagueID int64 `json:"league_id"` +} + +func (q *Queries) CheckValidSportRaffleFilter(ctx context.Context, arg CheckValidSportRaffleFilterParams) (bool, error) { + row := q.db.QueryRow(ctx, CheckValidSportRaffleFilter, arg.RaffleID, arg.SportID, arg.LeagueID) + var exists bool + err := row.Scan(&exists) + return exists, err +} + +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 +` + +type CreateRaffleParams struct { + CompanyID int32 `json:"company_id"` + Name string `json:"name"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` + Type string `json:"type"` +} + +func (q *Queries) CreateRaffle(ctx context.Context, arg CreateRaffleParams) (Raffle, error) { + row := q.db.QueryRow(ctx, CreateRaffle, + arg.CompanyID, + arg.Name, + arg.ExpiresAt, + arg.Type, + ) + var i Raffle + err := row.Scan( + &i.ID, + &i.CompanyID, + &i.Name, + &i.CreatedAt, + &i.ExpiresAt, + &i.Type, + &i.Status, + ) + return i, err +} + +const CreateRaffleTicket = `-- name: CreateRaffleTicket :one +INSERT INTO raffle_tickets (raffle_id, user_id) +VALUES ($1, $2) +RETURNING id, raffle_id, user_id, is_active +` + +type CreateRaffleTicketParams struct { + RaffleID int32 `json:"raffle_id"` + UserID int32 `json:"user_id"` +} + +func (q *Queries) CreateRaffleTicket(ctx context.Context, arg CreateRaffleTicketParams) (RaffleTicket, error) { + row := q.db.QueryRow(ctx, CreateRaffleTicket, arg.RaffleID, arg.UserID) + var i RaffleTicket + err := row.Scan( + &i.ID, + &i.RaffleID, + &i.UserID, + &i.IsActive, + ) + return i, err +} + +const CreateRaffleWinner = `-- name: CreateRaffleWinner :one +INSERT INTO raffle_winners (raffle_id, user_id, rank) +VALUES ($1, $2, $3) +RETURNING id, raffle_id, user_id, rank, created_at +` + +type CreateRaffleWinnerParams struct { + RaffleID int32 `json:"raffle_id"` + UserID int32 `json:"user_id"` + Rank int32 `json:"rank"` +} + +func (q *Queries) CreateRaffleWinner(ctx context.Context, arg CreateRaffleWinnerParams) (RaffleWinner, error) { + row := q.db.QueryRow(ctx, CreateRaffleWinner, arg.RaffleID, arg.UserID, arg.Rank) + var i RaffleWinner + err := row.Scan( + &i.ID, + &i.RaffleID, + &i.UserID, + &i.Rank, + &i.CreatedAt, + ) + return i, err +} + +const DeleteRaffle = `-- name: DeleteRaffle :one +DELETE FROM raffles +WHERE id = $1 +RETURNING id, company_id, name, created_at, expires_at, type, status +` + +func (q *Queries) DeleteRaffle(ctx context.Context, id int32) (Raffle, error) { + row := q.db.QueryRow(ctx, DeleteRaffle, id) + var i Raffle + err := row.Scan( + &i.ID, + &i.CompanyID, + &i.Name, + &i.CreatedAt, + &i.ExpiresAt, + &i.Type, + &i.Status, + ) + return i, err +} + +const GetRaffleStanding = `-- name: GetRaffleStanding :many +SELECT + u.id AS user_id, + rt.raffle_id, + u.first_name, + u.last_name, + u.phone_number, + u.email, + COUNT(*) AS ticket_count +FROM raffle_tickets rt +JOIN users u ON rt.user_id = u.id +WHERE rt.is_active = true + AND rt.raffle_id = $1 +GROUP BY u.id, rt.raffle_id, u.first_name, u.last_name, u.phone_number, u.email +ORDER BY ticket_count DESC +LIMIT $2 +` + +type GetRaffleStandingParams struct { + RaffleID int32 `json:"raffle_id"` + Limit int32 `json:"limit"` +} + +type GetRaffleStandingRow struct { + UserID int64 `json:"user_id"` + RaffleID int32 `json:"raffle_id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + PhoneNumber pgtype.Text `json:"phone_number"` + Email pgtype.Text `json:"email"` + TicketCount int64 `json:"ticket_count"` +} + +func (q *Queries) GetRaffleStanding(ctx context.Context, arg GetRaffleStandingParams) ([]GetRaffleStandingRow, error) { + rows, err := q.db.Query(ctx, GetRaffleStanding, arg.RaffleID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetRaffleStandingRow + for rows.Next() { + var i GetRaffleStandingRow + if err := rows.Scan( + &i.UserID, + &i.RaffleID, + &i.FirstName, + &i.LastName, + &i.PhoneNumber, + &i.Email, + &i.TicketCount, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetRafflesOfCompany = `-- name: GetRafflesOfCompany :many +SELECT id, company_id, name, created_at, expires_at, type, status FROM raffles WHERE company_id = $1 +` + +func (q *Queries) GetRafflesOfCompany(ctx context.Context, companyID int32) ([]Raffle, error) { + rows, err := q.db.Query(ctx, GetRafflesOfCompany, companyID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Raffle + for rows.Next() { + var i Raffle + if err := rows.Scan( + &i.ID, + &i.CompanyID, + &i.Name, + &i.CreatedAt, + &i.ExpiresAt, + &i.Type, + &i.Status, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetUserRaffleTickets = `-- name: GetUserRaffleTickets :many +SELECT + rt.id AS ticket_id, + rt.user_id, + r.name, + r.type, + r.expires_at, + r.status +FROM raffle_tickets rt +JOIN raffles r ON rt.raffle_id = r.id +WHERE rt.user_id = $1 +` + +type GetUserRaffleTicketsRow struct { + TicketID int32 `json:"ticket_id"` + UserID int32 `json:"user_id"` + Name string `json:"name"` + Type string `json:"type"` + ExpiresAt pgtype.Timestamp `json:"expires_at"` + Status string `json:"status"` +} + +func (q *Queries) GetUserRaffleTickets(ctx context.Context, userID int32) ([]GetUserRaffleTicketsRow, error) { + rows, err := q.db.Query(ctx, GetUserRaffleTickets, userID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetUserRaffleTicketsRow + for rows.Next() { + var i GetUserRaffleTicketsRow + if err := rows.Scan( + &i.TicketID, + &i.UserID, + &i.Name, + &i.Type, + &i.ExpiresAt, + &i.Status, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const SetRaffleComplete = `-- name: SetRaffleComplete :exec +UPDATE raffles +SET status = 'completed' +WHERE id = $1 +` + +func (q *Queries) SetRaffleComplete(ctx context.Context, id int32) error { + _, err := q.db.Exec(ctx, SetRaffleComplete, id) + return err +} + +const UpdateRaffleTicketStatus = `-- name: UpdateRaffleTicketStatus :exec +UPDATE raffle_tickets +SET is_active = $1 +WHERE id = $2 +` + +type UpdateRaffleTicketStatusParams struct { + IsActive pgtype.Bool `json:"is_active"` + ID int32 `json:"id"` +} + +func (q *Queries) UpdateRaffleTicketStatus(ctx context.Context, arg UpdateRaffleTicketStatusParams) error { + _, err := q.db.Exec(ctx, UpdateRaffleTicketStatus, arg.IsActive, arg.ID) + return err +} diff --git a/gen/db/referal.sql.go b/gen/db/referal.sql.go index b5ceeed..99d8bb2 100644 --- a/gen/db/referal.sql.go +++ b/gen/db/referal.sql.go @@ -1,335 +1,254 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: referal.sql package dbgen import ( "context" - - "github.com/jackc/pgx/v5/pgtype" ) -const CreateReferral = `-- name: CreateReferral :one -INSERT INTO referrals ( - referral_code, - referrer_id, - status, - reward_amount, - expires_at -) VALUES ( - $1, $2, $3, $4, $5 -) RETURNING id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at +const CreateReferralCode = `-- name: CreateReferralCode :one +INSERT INTO referral_codes ( + referral_code, + referrer_id, + company_id, + number_of_referrals, + reward_amount + ) +VALUES ($1, $2, $3, $4, $5) +RETURNING id, referral_code, referrer_id, company_id, is_active, number_of_referrals, reward_amount, created_at, updated_at ` -type CreateReferralParams struct { - ReferralCode string `json:"referral_code"` - ReferrerID string `json:"referrer_id"` - Status Referralstatus `json:"status"` - RewardAmount pgtype.Numeric `json:"reward_amount"` - ExpiresAt pgtype.Timestamptz `json:"expires_at"` +type CreateReferralCodeParams struct { + ReferralCode string `json:"referral_code"` + ReferrerID int64 `json:"referrer_id"` + CompanyID int64 `json:"company_id"` + NumberOfReferrals int64 `json:"number_of_referrals"` + RewardAmount int64 `json:"reward_amount"` } -func (q *Queries) CreateReferral(ctx context.Context, arg CreateReferralParams) (Referral, error) { - row := q.db.QueryRow(ctx, CreateReferral, +func (q *Queries) CreateReferralCode(ctx context.Context, arg CreateReferralCodeParams) (ReferralCode, error) { + row := q.db.QueryRow(ctx, CreateReferralCode, arg.ReferralCode, arg.ReferrerID, - arg.Status, + arg.CompanyID, + arg.NumberOfReferrals, arg.RewardAmount, - arg.ExpiresAt, ) - var i Referral + var i ReferralCode err := row.Scan( &i.ID, &i.ReferralCode, &i.ReferrerID, - &i.ReferredID, - &i.Status, + &i.CompanyID, + &i.IsActive, + &i.NumberOfReferrals, &i.RewardAmount, - &i.CashbackAmount, &i.CreatedAt, &i.UpdatedAt, - &i.ExpiresAt, ) return i, err } -const CreateReferralSettings = `-- name: CreateReferralSettings :one -INSERT INTO referral_settings ( - referral_reward_amount, - cashback_percentage, - max_referrals, - bet_referral_bonus_percentage, - expires_after_days, - updated_by -) VALUES ( - $1, $2, $3, $4, $5, $6 -) RETURNING id, referral_reward_amount, cashback_percentage, bet_referral_bonus_percentage, max_referrals, expires_after_days, updated_by, created_at, updated_at, version +const CreateUserReferral = `-- name: CreateUserReferral :one +INSERT INTO user_referrals (referred_id, referral_code_id) +VALUES ($1, $2) +RETURNING id, referred_id, referral_code_id, created_at ` -type CreateReferralSettingsParams struct { - ReferralRewardAmount pgtype.Numeric `json:"referral_reward_amount"` - CashbackPercentage pgtype.Numeric `json:"cashback_percentage"` - MaxReferrals int32 `json:"max_referrals"` - BetReferralBonusPercentage pgtype.Numeric `json:"bet_referral_bonus_percentage"` - ExpiresAfterDays int32 `json:"expires_after_days"` - UpdatedBy string `json:"updated_by"` +type CreateUserReferralParams struct { + ReferredID int64 `json:"referred_id"` + ReferralCodeID int64 `json:"referral_code_id"` } -func (q *Queries) CreateReferralSettings(ctx context.Context, arg CreateReferralSettingsParams) (ReferralSetting, error) { - row := q.db.QueryRow(ctx, CreateReferralSettings, - arg.ReferralRewardAmount, - arg.CashbackPercentage, - arg.MaxReferrals, - arg.BetReferralBonusPercentage, - arg.ExpiresAfterDays, - arg.UpdatedBy, - ) - var i ReferralSetting +func (q *Queries) CreateUserReferral(ctx context.Context, arg CreateUserReferralParams) (UserReferral, error) { + row := q.db.QueryRow(ctx, CreateUserReferral, arg.ReferredID, arg.ReferralCodeID) + var i UserReferral err := row.Scan( &i.ID, - &i.ReferralRewardAmount, - &i.CashbackPercentage, - &i.BetReferralBonusPercentage, - &i.MaxReferrals, - &i.ExpiresAfterDays, - &i.UpdatedBy, - &i.CreatedAt, - &i.UpdatedAt, - &i.Version, - ) - return i, err -} - -const GetActiveReferralByReferrerID = `-- name: GetActiveReferralByReferrerID :one -SELECT id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at FROM referrals WHERE referrer_id = $1 AND status = 'PENDING' LIMIT 1 -` - -func (q *Queries) GetActiveReferralByReferrerID(ctx context.Context, referrerID string) (Referral, error) { - row := q.db.QueryRow(ctx, GetActiveReferralByReferrerID, referrerID) - var i Referral - err := row.Scan( - &i.ID, - &i.ReferralCode, - &i.ReferrerID, &i.ReferredID, - &i.Status, - &i.RewardAmount, - &i.CashbackAmount, + &i.ReferralCodeID, &i.CreatedAt, - &i.UpdatedAt, - &i.ExpiresAt, ) return i, err } -const GetReferralByCode = `-- name: GetReferralByCode :one -SELECT id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at FROM referrals +const GetReferralCode = `-- name: GetReferralCode :one +SELECT id, referral_code, referrer_id, company_id, is_active, number_of_referrals, reward_amount, created_at, updated_at +FROM referral_codes WHERE referral_code = $1 ` -func (q *Queries) GetReferralByCode(ctx context.Context, referralCode string) (Referral, error) { - row := q.db.QueryRow(ctx, GetReferralByCode, referralCode) - var i Referral +func (q *Queries) GetReferralCode(ctx context.Context, referralCode string) (ReferralCode, error) { + row := q.db.QueryRow(ctx, GetReferralCode, referralCode) + var i ReferralCode err := row.Scan( &i.ID, &i.ReferralCode, &i.ReferrerID, - &i.ReferredID, - &i.Status, + &i.CompanyID, + &i.IsActive, + &i.NumberOfReferrals, &i.RewardAmount, - &i.CashbackAmount, &i.CreatedAt, &i.UpdatedAt, - &i.ExpiresAt, ) return i, err } -const GetReferralByReferredID = `-- name: GetReferralByReferredID :one -SELECT id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at FROM referrals WHERE referred_id = $1 LIMIT 1 +const GetReferralCodeByUser = `-- name: GetReferralCodeByUser :many +SELECt id, referral_code, referrer_id, company_id, is_active, number_of_referrals, reward_amount, created_at, updated_at +FROM referral_codes +WHERE referrer_id = $1 ` -func (q *Queries) GetReferralByReferredID(ctx context.Context, referredID pgtype.Text) (Referral, error) { - row := q.db.QueryRow(ctx, GetReferralByReferredID, referredID) - var i Referral +func (q *Queries) GetReferralCodeByUser(ctx context.Context, referrerID int64) ([]ReferralCode, error) { + rows, err := q.db.Query(ctx, GetReferralCodeByUser, referrerID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ReferralCode + for rows.Next() { + var i ReferralCode + if err := rows.Scan( + &i.ID, + &i.ReferralCode, + &i.ReferrerID, + &i.CompanyID, + &i.IsActive, + &i.NumberOfReferrals, + &i.RewardAmount, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetReferralStats = `-- name: GetReferralStats :one +SELECT COUNT(*) AS total_referrals, + COALESCE(SUM(reward_amount), 0)::bigint AS total_reward_earned +FROM user_referrals + JOIN referral_codes ON referral_codes.id = referral_code_id +WHERE referrer_id = $1 + AND company_id = $2 +` + +type GetReferralStatsParams struct { + ReferrerID int64 `json:"referrer_id"` + CompanyID int64 `json:"company_id"` +} + +type GetReferralStatsRow struct { + TotalReferrals int64 `json:"total_referrals"` + TotalRewardEarned int64 `json:"total_reward_earned"` +} + +func (q *Queries) GetReferralStats(ctx context.Context, arg GetReferralStatsParams) (GetReferralStatsRow, error) { + row := q.db.QueryRow(ctx, GetReferralStats, arg.ReferrerID, arg.CompanyID) + var i GetReferralStatsRow + err := row.Scan(&i.TotalReferrals, &i.TotalRewardEarned) + return i, err +} + +const GetUserReferral = `-- name: GetUserReferral :one +SELECT id, referred_id, referral_code_id, created_at +FROM user_referrals +WHERE referred_id = $1 +` + +func (q *Queries) GetUserReferral(ctx context.Context, referredID int64) (UserReferral, error) { + row := q.db.QueryRow(ctx, GetUserReferral, referredID) + var i UserReferral err := row.Scan( &i.ID, - &i.ReferralCode, - &i.ReferrerID, &i.ReferredID, - &i.Status, - &i.RewardAmount, - &i.CashbackAmount, + &i.ReferralCodeID, &i.CreatedAt, - &i.UpdatedAt, - &i.ExpiresAt, ) return i, err } -const GetReferralCountByID = `-- name: GetReferralCountByID :one -SELECT count(*) FROM referrals WHERE referrer_id = $1 +const GetUserReferralsByCode = `-- name: GetUserReferralsByCode :many +SELECT user_referrals.id, user_referrals.referred_id, user_referrals.referral_code_id, user_referrals.created_at +FROM user_referrals + JOIN referral_codes ON referral_codes.id = referral_code_id +WHERE referral_code = $1 ` -func (q *Queries) GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) { - row := q.db.QueryRow(ctx, GetReferralCountByID, referrerID) +func (q *Queries) GetUserReferralsByCode(ctx context.Context, referralCode string) ([]UserReferral, error) { + rows, err := q.db.Query(ctx, GetUserReferralsByCode, referralCode) + if err != nil { + return nil, err + } + defer rows.Close() + var items []UserReferral + for rows.Next() { + var i UserReferral + if err := rows.Scan( + &i.ID, + &i.ReferredID, + &i.ReferralCodeID, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetUserReferralsCount = `-- name: GetUserReferralsCount :one +SELECT COUNT(*) +FROM user_referrals + JOIN referral_codes ON referral_codes.id = referral_code_id +WHERE referrer_id = $1 +` + +func (q *Queries) GetUserReferralsCount(ctx context.Context, referrerID int64) (int64, error) { + row := q.db.QueryRow(ctx, GetUserReferralsCount, referrerID) var count int64 err := row.Scan(&count) return count, err } -const GetReferralSettings = `-- name: GetReferralSettings :one -SELECT id, referral_reward_amount, cashback_percentage, bet_referral_bonus_percentage, max_referrals, expires_after_days, updated_by, created_at, updated_at, version FROM referral_settings -LIMIT 1 -` - -func (q *Queries) GetReferralSettings(ctx context.Context) (ReferralSetting, error) { - row := q.db.QueryRow(ctx, GetReferralSettings) - var i ReferralSetting - err := row.Scan( - &i.ID, - &i.ReferralRewardAmount, - &i.CashbackPercentage, - &i.BetReferralBonusPercentage, - &i.MaxReferrals, - &i.ExpiresAfterDays, - &i.UpdatedBy, - &i.CreatedAt, - &i.UpdatedAt, - &i.Version, - ) - return i, err -} - -const GetReferralStats = `-- name: GetReferralStats :one -SELECT - COUNT(*) as total_referrals, - COUNT(CASE WHEN status = 'COMPLETED' THEN 1 END) as completed_referrals, - COALESCE(SUM(reward_amount), 0) as total_reward_earned, - COALESCE(SUM(CASE WHEN status = 'PENDING' THEN reward_amount END), 0) as pending_rewards -FROM referrals -WHERE referrer_id = $1 -` - -type GetReferralStatsRow struct { - TotalReferrals int64 `json:"total_referrals"` - CompletedReferrals int64 `json:"completed_referrals"` - TotalRewardEarned interface{} `json:"total_reward_earned"` - PendingRewards interface{} `json:"pending_rewards"` -} - -func (q *Queries) GetReferralStats(ctx context.Context, referrerID string) (GetReferralStatsRow, error) { - row := q.db.QueryRow(ctx, GetReferralStats, referrerID) - var i GetReferralStatsRow - err := row.Scan( - &i.TotalReferrals, - &i.CompletedReferrals, - &i.TotalRewardEarned, - &i.PendingRewards, - ) - return i, err -} - -const UpdateReferral = `-- name: UpdateReferral :one -UPDATE referrals -SET - referred_id = $2, - status = $3, - updated_at = CURRENT_TIMESTAMP -WHERE id = $1 -RETURNING id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at -` - -type UpdateReferralParams struct { - ID int64 `json:"id"` - ReferredID pgtype.Text `json:"referred_id"` - Status Referralstatus `json:"status"` -} - -func (q *Queries) UpdateReferral(ctx context.Context, arg UpdateReferralParams) (Referral, error) { - row := q.db.QueryRow(ctx, UpdateReferral, arg.ID, arg.ReferredID, arg.Status) - var i Referral - err := row.Scan( - &i.ID, - &i.ReferralCode, - &i.ReferrerID, - &i.ReferredID, - &i.Status, - &i.RewardAmount, - &i.CashbackAmount, - &i.CreatedAt, - &i.UpdatedAt, - &i.ExpiresAt, - ) - return i, err -} - const UpdateReferralCode = `-- name: UpdateReferralCode :exec -UPDATE users -SET - referral_code = $2, - updated_at = CURRENT_TIMESTAMP +UPDATE referral_codes +SET is_active = $2, + referral_code = $3, + number_of_referrals = $4, + reward_amount = $5, + updated_at = CURRENT_TIMESTAMP WHERE id = $1 ` type UpdateReferralCodeParams struct { - ID int64 `json:"id"` - ReferralCode pgtype.Text `json:"referral_code"` + ID int64 `json:"id"` + IsActive bool `json:"is_active"` + ReferralCode string `json:"referral_code"` + NumberOfReferrals int64 `json:"number_of_referrals"` + RewardAmount int64 `json:"reward_amount"` } func (q *Queries) UpdateReferralCode(ctx context.Context, arg UpdateReferralCodeParams) error { - _, err := q.db.Exec(ctx, UpdateReferralCode, arg.ID, arg.ReferralCode) + _, err := q.db.Exec(ctx, UpdateReferralCode, + arg.ID, + arg.IsActive, + arg.ReferralCode, + arg.NumberOfReferrals, + arg.RewardAmount, + ) return err } - -const UpdateReferralSettings = `-- name: UpdateReferralSettings :one -UPDATE referral_settings -SET - referral_reward_amount = $2, - cashback_percentage = $3, - bet_referral_bonus_percentage= $4, - max_referrals = $5, - expires_after_days = $6, - updated_by = $7, - updated_at = CURRENT_TIMESTAMP -WHERE id = $1 -RETURNING id, referral_reward_amount, cashback_percentage, bet_referral_bonus_percentage, max_referrals, expires_after_days, updated_by, created_at, updated_at, version -` - -type UpdateReferralSettingsParams struct { - ID int64 `json:"id"` - ReferralRewardAmount pgtype.Numeric `json:"referral_reward_amount"` - CashbackPercentage pgtype.Numeric `json:"cashback_percentage"` - BetReferralBonusPercentage pgtype.Numeric `json:"bet_referral_bonus_percentage"` - MaxReferrals int32 `json:"max_referrals"` - ExpiresAfterDays int32 `json:"expires_after_days"` - UpdatedBy string `json:"updated_by"` -} - -func (q *Queries) UpdateReferralSettings(ctx context.Context, arg UpdateReferralSettingsParams) (ReferralSetting, error) { - row := q.db.QueryRow(ctx, UpdateReferralSettings, - arg.ID, - arg.ReferralRewardAmount, - arg.CashbackPercentage, - arg.BetReferralBonusPercentage, - arg.MaxReferrals, - arg.ExpiresAfterDays, - arg.UpdatedBy, - ) - var i ReferralSetting - err := row.Scan( - &i.ID, - &i.ReferralRewardAmount, - &i.CashbackPercentage, - &i.BetReferralBonusPercentage, - &i.MaxReferrals, - &i.ExpiresAfterDays, - &i.UpdatedBy, - &i.CreatedAt, - &i.UpdatedAt, - &i.Version, - ) - return i, err -} diff --git a/gen/db/report.sql.go b/gen/db/report.sql.go index 1a1ccde..d6193c1 100644 --- a/gen/db/report.sql.go +++ b/gen/db/report.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: report.sql package dbgen diff --git a/gen/db/result.sql.go b/gen/db/result.sql.go index bff7b1e..899561b 100644 --- a/gen/db/result.sql.go +++ b/gen/db/result.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: result.sql package dbgen diff --git a/gen/db/result_log.sql.go b/gen/db/result_log.sql.go index 468795e..3f11e16 100644 --- a/gen/db/result_log.sql.go +++ b/gen/db/result_log.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: result_log.sql package dbgen diff --git a/gen/db/settings.sql.go b/gen/db/settings.sql.go index f67fecc..76eb504 100644 --- a/gen/db/settings.sql.go +++ b/gen/db/settings.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: settings.sql package dbgen @@ -181,7 +181,9 @@ func (q *Queries) GetGlobalSettings(ctx context.Context) ([]GlobalSetting, error } const GetOverrideSettings = `-- name: GetOverrideSettings :many -SELECT gs.key, gs.value, gs.created_at, gs.updated_at, +SELECT gs.key, + gs.created_at, + gs.updated_at, COALESCE(cs.value, gs.value) AS value FROM global_settings gs LEFT JOIN company_settings cs ON cs.key = gs.key @@ -190,10 +192,9 @@ FROM global_settings gs type GetOverrideSettingsRow struct { Key string `json:"key"` - Value string `json:"value"` CreatedAt pgtype.Timestamp `json:"created_at"` UpdatedAt pgtype.Timestamp `json:"updated_at"` - Value_2 string `json:"value_2"` + Value string `json:"value"` } func (q *Queries) GetOverrideSettings(ctx context.Context, companyID int64) ([]GetOverrideSettingsRow, error) { @@ -207,10 +208,9 @@ func (q *Queries) GetOverrideSettings(ctx context.Context, companyID int64) ([]G var i GetOverrideSettingsRow if err := rows.Scan( &i.Key, - &i.Value, &i.CreatedAt, &i.UpdatedAt, - &i.Value_2, + &i.Value, ); err != nil { return nil, err } diff --git a/gen/db/shop_transactions.sql.go b/gen/db/shop_transactions.sql.go index bcd884e..7664dbb 100644 --- a/gen/db/shop_transactions.sql.go +++ b/gen/db/shop_transactions.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: shop_transactions.sql package dbgen diff --git a/gen/db/ticket.sql.go b/gen/db/ticket.sql.go index bc9bb5f..45603ba 100644 --- a/gen/db/ticket.sql.go +++ b/gen/db/ticket.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: ticket.sql package dbgen diff --git a/gen/db/transfer.sql.go b/gen/db/transfer.sql.go index 35e38d4..fe25cbe 100644 --- a/gen/db/transfer.sql.go +++ b/gen/db/transfer.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: transfer.sql package dbgen @@ -182,6 +182,40 @@ func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber st return i, err } +const GetTransferStats = `-- name: GetTransferStats :one +SELECT COUNT(*) AS total_transfers, COUNT(*) FILTER ( + WHERE type = 'deposit' + ) AS total_deposits, + COUNT(*) FILTER ( + WHERE type = 'withdraw' + ) AS total_withdraw, + COUNT(*) FILTER ( + WHERE type = 'wallet' + ) AS total_wallet_to_wallet +FROM wallet_transfer +WHERE sender_wallet_id = $1 + OR receiver_wallet_id = $1 +` + +type GetTransferStatsRow struct { + TotalTransfers int64 `json:"total_transfers"` + TotalDeposits int64 `json:"total_deposits"` + TotalWithdraw int64 `json:"total_withdraw"` + TotalWalletToWallet int64 `json:"total_wallet_to_wallet"` +} + +func (q *Queries) GetTransferStats(ctx context.Context, senderWalletID pgtype.Int8) (GetTransferStatsRow, error) { + row := q.db.QueryRow(ctx, GetTransferStats, senderWalletID) + var i GetTransferStatsRow + err := row.Scan( + &i.TotalTransfers, + &i.TotalDeposits, + &i.TotalWithdraw, + &i.TotalWalletToWallet, + ) + return i, err +} + const GetTransfersByWallet = `-- name: GetTransfersByWallet :many SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number FROM wallet_transfer_details diff --git a/gen/db/user.sql.go b/gen/db/user.sql.go index 0d4c33b..9f9cd95 100644 --- a/gen/db/user.sql.go +++ b/gen/db/user.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: user.sql package dbgen @@ -163,7 +163,7 @@ func (q *Queries) DeleteUser(ctx context.Context, id int64) error { } const GetAdminByCompanyID = `-- name: GetAdminByCompanyID :one -SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by +SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended FROM companies JOIN users ON companies.admin_id = users.id where companies.id = $1 @@ -187,8 +187,6 @@ func (q *Queries) GetAdminByCompanyID(ctx context.Context, id int64) (User, erro &i.CompanyID, &i.SuspendedAt, &i.Suspended, - &i.ReferralCode, - &i.ReferredBy, ) return i, err } @@ -388,7 +386,7 @@ func (q *Queries) GetUserByEmail(ctx context.Context, arg GetUserByEmailParams) } const GetUserByID = `-- name: GetUserByID :one -SELECT id, first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at, company_id, suspended_at, suspended, referral_code, referred_by +SELECT id, first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at, company_id, suspended_at, suspended FROM users WHERE id = $1 ` @@ -411,8 +409,6 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) { &i.CompanyID, &i.SuspendedAt, &i.Suspended, - &i.ReferralCode, - &i.ReferredBy, ) return i, err } @@ -587,7 +583,7 @@ SET password = $1, WHERE ( email = $2 OR phone_number = $3 - AND company_id = $4 + AND company_id = $5 ) ` @@ -596,6 +592,7 @@ type UpdatePasswordParams struct { Email pgtype.Text `json:"email"` PhoneNumber pgtype.Text `json:"phone_number"` UpdatedAt pgtype.Timestamptz `json:"updated_at"` + CompanyID pgtype.Int8 `json:"company_id"` } func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error { @@ -604,6 +601,7 @@ func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) arg.Email, arg.PhoneNumber, arg.UpdatedAt, + arg.CompanyID, ) return err } diff --git a/gen/db/virtual_games.sql.go b/gen/db/virtual_games.sql.go index 33697d7..b98f602 100644 --- a/gen/db/virtual_games.sql.go +++ b/gen/db/virtual_games.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: virtual_games.sql package dbgen @@ -12,12 +12,8 @@ import ( ) const AddFavoriteGame = `-- name: AddFavoriteGame :exec -INSERT INTO favorite_games ( - user_id, - game_id, - created_at -) VALUES ($1, $2, NOW()) -ON CONFLICT (user_id, game_id) DO NOTHING +INSERT INTO favorite_games (user_id, game_id, created_at) +VALUES ($1, $2, NOW()) ON CONFLICT (user_id, game_id) DO NOTHING ` type AddFavoriteGameParams struct { @@ -44,23 +40,34 @@ func (q *Queries) CountVirtualGameProviders(ctx context.Context) (int64, error) const CreateVirtualGame = `-- name: CreateVirtualGame :one INSERT INTO virtual_games ( - game_id, - provider_id, - name, - category, - device_type, - volatility, - rtp, - has_demo, - has_free_bets, - bets, - thumbnail, - status -) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 -) -RETURNING - id, + game_id, + provider_id, + name, + category, + device_type, + volatility, + rtp, + has_demo, + has_free_bets, + bets, + thumbnail, + status + ) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12 + ) +RETURNING id, game_id, provider_id, name, @@ -130,22 +137,34 @@ func (q *Queries) CreateVirtualGame(ctx context.Context, arg CreateVirtualGamePa const CreateVirtualGameHistory = `-- name: CreateVirtualGameHistory :one INSERT INTO virtual_game_histories ( - session_id, - user_id, - company_id, - provider, - wallet_id, - game_id, - transaction_type, - amount, - currency, - external_transaction_id, - reference_transaction_id, - status -) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 -) RETURNING - id, + session_id, + user_id, + company_id, + provider, + wallet_id, + game_id, + transaction_type, + amount, + currency, + external_transaction_id, + reference_transaction_id, + status + ) +VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + $11, + $12 + ) +RETURNING id, session_id, user_id, company_id, @@ -215,10 +234,21 @@ func (q *Queries) CreateVirtualGameHistory(ctx context.Context, arg CreateVirtua const CreateVirtualGameProvider = `-- name: CreateVirtualGameProvider :one INSERT INTO virtual_game_providers ( - provider_id, provider_name, logo_dark, logo_light, enabled -) VALUES ( - $1, $2, $3, $4, $5 -) RETURNING id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at + provider_id, + provider_name, + logo_dark, + logo_light, + enabled + ) +VALUES ($1, $2, $3, $4, $5) +RETURNING id, + provider_id, + provider_name, + logo_dark, + logo_light, + enabled, + created_at, + updated_at ` type CreateVirtualGameProviderParams struct { @@ -253,10 +283,23 @@ func (q *Queries) CreateVirtualGameProvider(ctx context.Context, arg CreateVirtu const CreateVirtualGameSession = `-- name: CreateVirtualGameSession :one INSERT INTO virtual_game_sessions ( - user_id, game_id, session_token, currency, status, expires_at -) VALUES ( - $1, $2, $3, $4, $5, $6 -) RETURNING id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at + user_id, + game_id, + session_token, + currency, + status, + expires_at + ) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING id, + user_id, + game_id, + session_token, + currency, + status, + created_at, + updated_at, + expires_at ` type CreateVirtualGameSessionParams struct { @@ -294,10 +337,31 @@ func (q *Queries) CreateVirtualGameSession(ctx context.Context, arg CreateVirtua const CreateVirtualGameTransaction = `-- name: CreateVirtualGameTransaction :one INSERT INTO virtual_game_transactions ( - session_id, user_id, company_id, provider, wallet_id, transaction_type, amount, currency, external_transaction_id, status -) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 -) RETURNING id, session_id, user_id, company_id, provider, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at + session_id, + user_id, + company_id, + provider, + wallet_id, + transaction_type, + amount, + currency, + external_transaction_id, + status + ) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) +RETURNING id, + session_id, + user_id, + company_id, + provider, + wallet_id, + transaction_type, + amount, + currency, + external_transaction_id, + status, + created_at, + updated_at ` type CreateVirtualGameTransactionParams struct { @@ -390,8 +454,7 @@ func (q *Queries) DeleteVirtualGameProvider(ctx context.Context, providerID stri } const GetAllVirtualGames = `-- name: GetAllVirtualGames :many -SELECT - vg.id, +SELECT vg.id, vg.game_id, vg.provider_id, vp.provider_name, @@ -408,19 +471,29 @@ SELECT vg.created_at, vg.updated_at FROM virtual_games vg -JOIN virtual_game_providers vp ON vg.provider_id = vp.provider_id -WHERE - ($1::text IS NULL OR vg.category = $1) -- category filter (optional) - AND ($2::text IS NULL OR vg.name ILIKE '%' || $2 || '%') -- search by name (optional) + JOIN virtual_game_providers vp ON vg.provider_id = vp.provider_id +WHERE ( + vg.category = $1 + OR $1 IS NULL + ) + AND ( + name ILIKE '%' || $2 || '%' + OR $2 IS NULL + ) + AND ( + vg.provider_id = $3 + OR $3 IS NULL + ) ORDER BY vg.created_at DESC -LIMIT $3 OFFSET $4 +LIMIT $5 OFFSET $4 ` type GetAllVirtualGamesParams struct { - Column1 string `json:"column_1"` - Column2 string `json:"column_2"` - Limit int32 `json:"limit"` - Offset int32 `json:"offset"` + Category pgtype.Text `json:"category"` + Name pgtype.Text `json:"name"` + ProviderID pgtype.Text `json:"provider_id"` + Offset pgtype.Int4 `json:"offset"` + Limit pgtype.Int4 `json:"limit"` } type GetAllVirtualGamesRow struct { @@ -444,10 +517,11 @@ type GetAllVirtualGamesRow struct { func (q *Queries) GetAllVirtualGames(ctx context.Context, arg GetAllVirtualGamesParams) ([]GetAllVirtualGamesRow, error) { rows, err := q.db.Query(ctx, GetAllVirtualGames, - arg.Column1, - arg.Column2, - arg.Limit, + arg.Category, + arg.Name, + arg.ProviderID, arg.Offset, + arg.Limit, ) if err != nil { return nil, err @@ -485,7 +559,14 @@ func (q *Queries) GetAllVirtualGames(ctx context.Context, arg GetAllVirtualGames } const GetVirtualGameProviderByID = `-- name: GetVirtualGameProviderByID :one -SELECT id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at +SELECT id, + provider_id, + provider_name, + logo_dark, + logo_light, + enabled, + created_at, + updated_at FROM virtual_game_providers WHERE provider_id = $1 ` @@ -507,7 +588,15 @@ func (q *Queries) GetVirtualGameProviderByID(ctx context.Context, providerID str } const GetVirtualGameSessionByToken = `-- name: GetVirtualGameSessionByToken :one -SELECT id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at +SELECT id, + user_id, + game_id, + session_token, + currency, + status, + created_at, + updated_at, + expires_at FROM virtual_game_sessions WHERE session_token = $1 ` @@ -530,18 +619,18 @@ func (q *Queries) GetVirtualGameSessionByToken(ctx context.Context, sessionToken } const GetVirtualGameSummaryInRange = `-- name: GetVirtualGameSummaryInRange :many -SELECT - c.name AS company_name, +SELECT c.name AS company_name, vg.name AS game_name, COUNT(vgt.id) AS number_of_bets, COALESCE(SUM(vgt.amount), 0) AS total_transaction_sum FROM virtual_game_transactions vgt -JOIN virtual_game_sessions vgs ON vgt.session_id = vgs.id -JOIN virtual_games vg ON vgs.game_id = vg.id -JOIN companies c ON vgt.company_id = c.id + JOIN virtual_game_sessions vgs ON vgt.session_id = vgs.id + JOIN virtual_games vg ON vgs.game_id = vg.id + JOIN companies c ON vgt.company_id = c.id WHERE vgt.transaction_type = 'BET' - AND vgt.created_at BETWEEN $1 AND $2 -GROUP BY c.name, vg.name + AND vgt.created_at BETWEEN $1 AND $2 +GROUP BY c.name, + vg.name ` type GetVirtualGameSummaryInRangeParams struct { @@ -582,7 +671,17 @@ func (q *Queries) GetVirtualGameSummaryInRange(ctx context.Context, arg GetVirtu } const GetVirtualGameTransactionByExternalID = `-- name: GetVirtualGameTransactionByExternalID :one -SELECT id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at +SELECT id, + session_id, + user_id, + wallet_id, + transaction_type, + amount, + currency, + external_transaction_id, + status, + created_at, + updated_at FROM virtual_game_transactions WHERE external_transaction_id = $1 ` @@ -647,7 +746,14 @@ func (q *Queries) ListFavoriteGames(ctx context.Context, userID int64) ([]int64, } const ListVirtualGameProviders = `-- name: ListVirtualGameProviders :many -SELECT id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at +SELECT id, + provider_id, + provider_name, + logo_dark, + logo_light, + enabled, + created_at, + updated_at FROM virtual_game_providers ORDER BY created_at DESC LIMIT $1 OFFSET $2 @@ -689,7 +795,8 @@ func (q *Queries) ListVirtualGameProviders(ctx context.Context, arg ListVirtualG const RemoveFavoriteGame = `-- name: RemoveFavoriteGame :exec DELETE FROM favorite_games -WHERE user_id = $1 AND game_id = $2 +WHERE user_id = $1 + AND game_id = $2 ` type RemoveFavoriteGameParams struct { @@ -707,7 +814,14 @@ UPDATE virtual_game_providers SET enabled = $2, updated_at = CURRENT_TIMESTAMP WHERE provider_id = $1 -RETURNING id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at +RETURNING id, + provider_id, + provider_name, + logo_dark, + logo_light, + enabled, + created_at, + updated_at ` type UpdateVirtualGameProviderEnabledParams struct { @@ -733,7 +847,8 @@ func (q *Queries) UpdateVirtualGameProviderEnabled(ctx context.Context, arg Upda const UpdateVirtualGameSessionStatus = `-- name: UpdateVirtualGameSessionStatus :exec UPDATE virtual_game_sessions -SET status = $2, updated_at = CURRENT_TIMESTAMP +SET status = $2, + updated_at = CURRENT_TIMESTAMP WHERE id = $1 ` @@ -749,7 +864,8 @@ func (q *Queries) UpdateVirtualGameSessionStatus(ctx context.Context, arg Update const UpdateVirtualGameTransactionStatus = `-- name: UpdateVirtualGameTransactionStatus :exec UPDATE virtual_game_transactions -SET status = $2, updated_at = CURRENT_TIMESTAMP +SET status = $2, + updated_at = CURRENT_TIMESTAMP WHERE id = $1 ` diff --git a/gen/db/wallet.sql.go b/gen/db/wallet.sql.go index 7e3eb7f..ccb2d37 100644 --- a/gen/db/wallet.sql.go +++ b/gen/db/wallet.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.30.0 // source: wallet.sql package dbgen @@ -50,7 +50,7 @@ INSERT INTO wallets ( type ) VALUES ($1, $2, $3, $4, $5) -RETURNING id, balance, currency, is_withdraw, is_bettable, is_transferable, user_id, type, is_active, created_at, updated_at, bonus_balance, cash_balance +RETURNING id, balance, currency, is_withdraw, is_bettable, is_transferable, user_id, type, is_active, created_at, updated_at ` type CreateWalletParams struct { @@ -82,8 +82,6 @@ func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wal &i.IsActive, &i.CreatedAt, &i.UpdatedAt, - &i.BonusBalance, - &i.CashBalance, ) return i, err } @@ -188,7 +186,7 @@ func (q *Queries) GetAllCustomerWallet(ctx context.Context) ([]CustomerWalletDet } const GetAllWallets = `-- name: GetAllWallets :many -SELECT id, balance, currency, is_withdraw, is_bettable, is_transferable, user_id, type, is_active, created_at, updated_at, bonus_balance, cash_balance +SELECT id, balance, currency, is_withdraw, is_bettable, is_transferable, user_id, type, is_active, created_at, updated_at FROM wallets ` @@ -213,8 +211,6 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) { &i.IsActive, &i.CreatedAt, &i.UpdatedAt, - &i.BonusBalance, - &i.CashBalance, ); err != nil { return nil, err } @@ -319,7 +315,7 @@ func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (Cust } const GetWalletByID = `-- name: GetWalletByID :one -SELECT id, balance, currency, is_withdraw, is_bettable, is_transferable, user_id, type, is_active, created_at, updated_at, bonus_balance, cash_balance +SELECT id, balance, currency, is_withdraw, is_bettable, is_transferable, user_id, type, is_active, created_at, updated_at FROM wallets WHERE id = $1 ` @@ -339,14 +335,12 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) { &i.IsActive, &i.CreatedAt, &i.UpdatedAt, - &i.BonusBalance, - &i.CashBalance, ) return i, err } const GetWalletByUserID = `-- name: GetWalletByUserID :many -SELECT id, balance, currency, is_withdraw, is_bettable, is_transferable, user_id, type, is_active, created_at, updated_at, bonus_balance, cash_balance +SELECT id, balance, currency, is_withdraw, is_bettable, is_transferable, user_id, type, is_active, created_at, updated_at FROM wallets WHERE user_id = $1 ` @@ -372,8 +366,6 @@ func (q *Queries) GetWalletByUserID(ctx context.Context, userID int64) ([]Wallet &i.IsActive, &i.CreatedAt, &i.UpdatedAt, - &i.BonusBalance, - &i.CashBalance, ); err != nil { return nil, err } diff --git a/internal/domain/auth.go b/internal/domain/auth.go index 513ff8e..374fe91 100644 --- a/internal/domain/auth.go +++ b/internal/domain/auth.go @@ -10,3 +10,10 @@ type RefreshToken struct { CreatedAt time.Time Revoked bool } + +// I used this because i was getting an error with the ValidInt64 +// when it was being unmarshaled by the jwt +type NullJwtInt64 struct { + Value int64 + Valid bool +} diff --git a/internal/domain/bet.go b/internal/domain/bet.go index 6ee3734..d59b68f 100644 --- a/internal/domain/bet.go +++ b/internal/domain/bet.go @@ -235,6 +235,7 @@ func ConvertDBBetWithOutcomes(bet dbgen.BetWithOutcome) GetBet { return GetBet{ ID: bet.ID, + CompanyID: bet.CompanyID, Amount: Currency(bet.Amount), TotalOdds: bet.TotalOdds, Status: OutcomeStatus(bet.Status), diff --git a/internal/domain/bonus.go b/internal/domain/bonus.go new file mode 100644 index 0000000..f436381 --- /dev/null +++ b/internal/domain/bonus.go @@ -0,0 +1,172 @@ +package domain + +import ( + "time" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/jackc/pgx/v5/pgtype" +) + +type BonusType string + +var ( + WelcomeBonus BonusType = "welcome_bonus" + DepositBonus BonusType = "deposit_bonus" +) + +type UserBonus struct { + ID int64 + Name string + Description string + UserID int64 + Type BonusType + RewardAmount Currency + IsClaimed bool + ExpiresAt time.Time + CreatedAt time.Time + UpdatedAt time.Time +} + +type UserBonusRes struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + UserID int64 `json:"user_id"` + Type BonusType `json:"type"` + RewardAmount float32 `json:"reward_amount"` + IsClaimed bool `json:"is_claimed"` + ExpiresAt time.Time `json:"expires_at"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func ConvertToBonusRes(bonus UserBonus) UserBonusRes { + return UserBonusRes{ + ID: bonus.ID, + Name: bonus.Name, + Description: bonus.Description, + Type: bonus.Type, + UserID: bonus.UserID, + RewardAmount: bonus.RewardAmount.Float32(), + IsClaimed: bonus.IsClaimed, + ExpiresAt: bonus.ExpiresAt, + CreatedAt: bonus.CreatedAt, + UpdatedAt: bonus.UpdatedAt, + } +} + +func ConvertToBonusResList(bonuses []UserBonus) []UserBonusRes { + result := make([]UserBonusRes, len(bonuses)) + + for i, bonus := range bonuses { + result[i] = ConvertToBonusRes(bonus) + } + + return result +} + +type CreateBonus struct { + Name string + Description string + Type BonusType + UserID int64 + RewardAmount Currency + ExpiresAt time.Time +} + +// type CreateBonusReq struct { +// Name string `json:"name"` +// Description string `json:"description"` +// Type BonusType `json:"type"` +// UserID int64 `json:"user_id"` +// RewardAmount float32 `json:"reward_amount"` +// ExpiresAt time.Time `json:"expires_at"` +// } + +// func ConvertCreateBonusReq(bonus CreateBonusReq, companyID int64) CreateBonus { +// return CreateBonus{ +// Name: bonus.Name, +// Description: bonus.Description, +// Type: bonus.Type, +// UserID: bonus.UserID, +// RewardAmount: ToCurrency(bonus.RewardAmount), +// ExpiresAt: bonus.ExpiresAt, +// } +// } + +func ConvertCreateBonus(bonus CreateBonus) dbgen.CreateUserBonusParams { + return dbgen.CreateUserBonusParams{ + Name: bonus.Name, + Description: bonus.Description, + Type: string(bonus.Type), + UserID: bonus.UserID, + RewardAmount: int64(bonus.RewardAmount), + ExpiresAt: pgtype.Timestamp{ + Time: bonus.ExpiresAt, + Valid: true, + }, + } +} + +func ConvertDBBonus(bonus dbgen.UserBonuse) UserBonus { + return UserBonus{ + ID: bonus.ID, + Name: bonus.Name, + Description: bonus.Description, + Type: BonusType(bonus.Type), + UserID: bonus.UserID, + + RewardAmount: Currency(bonus.RewardAmount), + IsClaimed: bonus.IsClaimed, + ExpiresAt: bonus.ExpiresAt.Time, + CreatedAt: bonus.CreatedAt.Time, + UpdatedAt: bonus.UpdatedAt.Time, + } +} + +func ConvertDBBonuses(bonuses []dbgen.UserBonuse) []UserBonus { + result := make([]UserBonus, len(bonuses)) + for i, bonus := range bonuses { + result[i] = ConvertDBBonus(bonus) + } + return result +} + +type BonusFilter struct { + UserID ValidInt64 + CompanyID ValidInt64 + Limit ValidInt + Offset ValidInt +} + +type BonusStats struct { + TotalBonus int64 + TotalRewardAmount Currency + ClaimedBonuses int64 + ExpiredBonuses int64 +} + +type BonusStatsRes struct { + TotalBonus int64 `json:"total_bonus"` + TotalRewardAmount float32 `json:"total_reward_amount"` + ClaimedBonuses int64 `json:"claimed_bonuses"` + ExpiredBonuses int64 `json:"expired_bonuses"` +} + +func ConvertToBonusStatsRes(bonus BonusStats) BonusStatsRes { + return BonusStatsRes{ + TotalBonus: bonus.TotalBonus, + TotalRewardAmount: bonus.TotalRewardAmount.Float32(), + ClaimedBonuses: bonus.ClaimedBonuses, + ExpiredBonuses: bonus.ExpiredBonuses, + } +} + +func ConvertDBBonusStats(stats dbgen.GetBonusStatsRow) BonusStats { + return BonusStats{ + TotalBonus: stats.TotalBonuses, + TotalRewardAmount: Currency(stats.TotalRewardEarned), + ClaimedBonuses: stats.ClaimedBonuses, + ExpiredBonuses: stats.ExpiredBonuses, + } +} diff --git a/internal/domain/event.go b/internal/domain/event.go index 0906918..152f1de 100644 --- a/internal/domain/event.go +++ b/internal/domain/event.go @@ -69,7 +69,7 @@ type BaseEvent struct { IsMonitored bool DefaultIsFeatured bool DefaultIsActive bool - DefaultWinningUpperLimit int32 + DefaultWinningUpperLimit int64 Score ValidString MatchMinute ValidInt TimerStatus ValidString @@ -97,92 +97,99 @@ type BaseEventRes struct { IsMonitored bool `json:"is_monitored"` DefaultIsFeatured bool `json:"default_is_featured"` DefaultIsActive bool `json:"default_is_active"` - DefaultWinningUpperLimit int32 `json:"default_winning_upper_limit"` + DefaultWinningUpperLimit int64 `json:"default_winning_upper_limit"` + Score string `json:"score"` + MatchMinute int `json:"match_minute"` + TimerStatus string `json:"timer_status"` + AddedTime int `json:"added_time"` + MatchPeriod int `json:"match_period"` + IsLive bool `json:"is_live"` + FetchedAt time.Time `json:"fetched_at"` +} +type EventWithSettings struct { + ID string + SportID int32 + MatchName string + HomeTeam string + AwayTeam string + HomeTeamID int64 + AwayTeamID int64 + HomeTeamImage string + AwayTeamImage string + LeagueID int64 + LeagueName string + LeagueCC ValidString + StartTime time.Time + Source EventSource + Status EventStatus + IsMonitored bool + IsFeatured bool + IsActive bool + WinningUpperLimit int32 + DefaultIsFeatured bool + DefaultIsActive bool + DefaultWinningUpperLimit int64 + Score ValidString + MatchMinute ValidInt + TimerStatus ValidString + AddedTime ValidInt + MatchPeriod ValidInt + IsLive bool + UpdatedAt time.Time + FetchedAt time.Time +} + +type CreateEvent struct { + ID string + SportID int32 + MatchName string + HomeTeam string + AwayTeam string + HomeTeamID int64 + AwayTeamID int64 + HomeTeamImage string + AwayTeamImage string + LeagueID int64 + LeagueName string + StartTime time.Time + IsLive bool + Status EventStatus + Source EventSource + DefaultWinningUpperLimit int64 +} + +type EventWithSettingsRes struct { + ID string `json:"id"` + SportID int32 `json:"sport_id"` + MatchName string `json:"match_name"` + HomeTeam string `json:"home_team"` + AwayTeam string `json:"away_team"` + HomeTeamID int64 `json:"home_team_id"` + AwayTeamID int64 `json:"away_team_id"` + HomeTeamImage string `json:"home_team_image"` + AwayTeamImage string `json:"away_team_image"` + LeagueID int64 `json:"league_id"` + LeagueName string `json:"league_name"` + LeagueCC string `json:"league_cc"` + StartTime time.Time `json:"start_time"` + Source EventSource `json:"source"` + Status EventStatus `json:"status"` + IsMonitored bool `json:"is_monitored"` + IsFeatured bool `json:"is_featured"` + IsActive bool `json:"is_active"` + WinningUpperLimit int32 `json:"winning_upper_limit"` + DefaultIsFeatured bool `json:"default_is_featured"` + DefaultIsActive bool `json:"default_is_active"` + DefaultWinningUpperLimit int64 `json:"default_winning_upper_limit"` Score string `json:"score,omitempty"` MatchMinute int `json:"match_minute,omitempty"` TimerStatus string `json:"timer_status,omitempty"` AddedTime int `json:"added_time,omitempty"` MatchPeriod int `json:"match_period,omitempty"` IsLive bool `json:"is_live"` + UpdatedAt time.Time `json:"updated_at"` FetchedAt time.Time `json:"fetched_at"` } -type EventWithSettings struct { - ID string - SportID int32 - MatchName string - HomeTeam string - AwayTeam string - HomeTeamID int64 - AwayTeamID int64 - HomeTeamImage string - AwayTeamImage string - LeagueID int64 - LeagueName string - LeagueCC ValidString - StartTime time.Time - Source EventSource - Status EventStatus - IsFeatured bool - IsMonitored bool - IsActive bool - WinningUpperLimit int32 - Score ValidString - MatchMinute ValidInt - TimerStatus ValidString - AddedTime ValidInt - MatchPeriod ValidInt - IsLive bool - UpdatedAt time.Time - FetchedAt time.Time -} - -type CreateEvent struct { - ID string - SportID int32 - MatchName string - HomeTeam string - AwayTeam string - HomeTeamID int64 - AwayTeamID int64 - HomeTeamImage string - AwayTeamImage string - LeagueID int64 - LeagueName string - StartTime time.Time - IsLive bool - Status EventStatus - Source EventSource -} - -type EventWithSettingsRes struct { - ID string `json:"id"` - SportID int32 `json:"sport_id"` - MatchName string `json:"match_name"` - HomeTeam string `json:"home_team"` - AwayTeam string `json:"away_team"` - HomeTeamID int64 `json:"home_team_id"` - AwayTeamID int64 `json:"away_team_id"` - HomeTeamImage string `json:"home_team_image"` - AwayTeamImage string `json:"away_team_image"` - LeagueID int64 `json:"league_id"` - LeagueName string `json:"league_name"` - LeagueCC string `json:"league_cc"` - StartTime time.Time `json:"start_time"` - Source EventSource `json:"source"` - Status EventStatus `json:"status"` - IsFeatured bool `json:"is_featured"` - IsMonitored bool `json:"is_monitored"` - IsActive bool `json:"is_active"` - WinningUpperLimit int32 `json:"winning_upper_limit"` - Score string `json:"score,omitempty"` - MatchMinute int `json:"match_minute,omitempty"` - TimerStatus string `json:"timer_status,omitempty"` - AddedTime int `json:"added_time,omitempty"` - MatchPeriod int `json:"match_period,omitempty"` - IsLive bool `json:"is_live"` - UpdatedAt time.Time `json:"updated_at"` - FetchedAt time.Time `json:"fetched_at"` -} type EventSettings struct { CompanyID int64 @@ -212,6 +219,7 @@ type EventFilter struct { Offset ValidInt32 MatchStatus ValidString // e.g., "upcoming", "in_play", "ended" Featured ValidBool + Active ValidBool } func ConvertDBEvent(event dbgen.EventWithCountry) BaseEvent { @@ -273,26 +281,27 @@ func ConvertDBEvents(events []dbgen.EventWithCountry) []BaseEvent { func ConvertCreateEvent(e CreateEvent) dbgen.InsertEventParams { return dbgen.InsertEventParams{ - ID: e.ID, - SportID: e.SportID, - MatchName: e.MatchName, - HomeTeam: e.HomeTeam, - AwayTeam: e.AwayTeam, - HomeTeamID: e.HomeTeamID, - AwayTeamID: e.AwayTeamID, - HomeKitImage: e.HomeTeamImage, - AwayKitImage: e.AwayTeamImage, - LeagueID: e.LeagueID, - LeagueName: e.LeagueName, - StartTime: pgtype.Timestamp{Time: e.StartTime, Valid: true}, - IsLive: e.IsLive, - Status: string(e.Status), - Source: string(e.Source), + ID: e.ID, + SportID: e.SportID, + MatchName: e.MatchName, + HomeTeam: e.HomeTeam, + AwayTeam: e.AwayTeam, + HomeTeamID: e.HomeTeamID, + AwayTeamID: e.AwayTeamID, + HomeKitImage: e.HomeTeamImage, + AwayKitImage: e.AwayTeamImage, + LeagueID: e.LeagueID, + LeagueName: e.LeagueName, + StartTime: pgtype.Timestamp{Time: e.StartTime, Valid: true}, + IsLive: e.IsLive, + Status: string(e.Status), + Source: string(e.Source), + DefaultWinningUpperLimit: e.DefaultWinningUpperLimit, } } -func ConvertCreateEventSettings(eventSettings CreateEventSettings) dbgen.InsertEventSettingsParams { - return dbgen.InsertEventSettingsParams{ +func ConvertCreateEventSettings(eventSettings CreateEventSettings) dbgen.SaveEventSettingsParams { + return dbgen.SaveEventSettingsParams{ CompanyID: eventSettings.CompanyID, EventID: eventSettings.EventID, IsActive: eventSettings.IsActive.ToPG(), @@ -318,13 +327,15 @@ func ConvertDBEventWithSetting(event dbgen.EventWithSetting) EventWithSettings { Value: event.LeagueCc.String, Valid: event.LeagueCc.Valid, }, - StartTime: event.StartTime.Time.UTC(), - Source: EventSource(event.Source), - Status: EventStatus(event.Status), - IsFeatured: event.IsFeatured, - IsMonitored: event.IsMonitored, - IsActive: event.IsActive, - WinningUpperLimit: event.WinningUpperLimit, + StartTime: event.StartTime.Time.UTC(), + Source: EventSource(event.Source), + Status: EventStatus(event.Status), + IsFeatured: event.IsFeatured, + IsMonitored: event.IsMonitored, + IsActive: event.IsActive, + DefaultIsFeatured: event.DefaultIsFeatured, + DefaultIsActive: event.DefaultIsActive, + DefaultWinningUpperLimit: event.DefaultWinningUpperLimit, Score: ValidString{ Value: event.Score.String, Valid: event.Score.Valid, @@ -359,8 +370,8 @@ func ConvertDBEventWithSettings(events []dbgen.EventWithSetting) []EventWithSett return result } -func ConvertUpdateEventSettings(event CreateEventSettings) dbgen.UpdateEventSettingsParams { - return dbgen.UpdateEventSettingsParams{ +func ConvertUpdateEventSettings(event CreateEventSettings) dbgen.SaveEventSettingsParams { + return dbgen.SaveEventSettingsParams{ EventID: event.EventID, CompanyID: event.CompanyID, IsActive: event.IsActive.ToPG(), @@ -409,32 +420,35 @@ func ConvertEventResList(events []BaseEvent) []BaseEventRes { func ConvertEventWitSettingRes(event EventWithSettings) EventWithSettingsRes { return EventWithSettingsRes{ - ID: event.ID, - SportID: event.SportID, - MatchName: event.MatchName, - HomeTeam: event.HomeTeam, - AwayTeam: event.AwayTeam, - HomeTeamID: event.HomeTeamID, - AwayTeamID: event.AwayTeamID, - HomeTeamImage: event.HomeTeamImage, - AwayTeamImage: event.AwayTeamImage, - LeagueID: event.LeagueID, - LeagueName: event.LeagueName, - LeagueCC: event.LeagueCC.Value, - StartTime: event.StartTime.UTC(), - Source: EventSource(event.Source), - Status: EventStatus(event.Status), - IsFeatured: event.IsFeatured, - IsMonitored: event.IsMonitored, - IsActive: event.IsActive, - WinningUpperLimit: event.WinningUpperLimit, - Score: event.Score.Value, - MatchMinute: event.MatchMinute.Value, - TimerStatus: event.TimerStatus.Value, - AddedTime: event.AddedTime.Value, - MatchPeriod: event.MatchPeriod.Value, - IsLive: event.IsLive, - FetchedAt: event.FetchedAt.UTC(), + ID: event.ID, + SportID: event.SportID, + MatchName: event.MatchName, + HomeTeam: event.HomeTeam, + AwayTeam: event.AwayTeam, + HomeTeamID: event.HomeTeamID, + AwayTeamID: event.AwayTeamID, + HomeTeamImage: event.HomeTeamImage, + AwayTeamImage: event.AwayTeamImage, + LeagueID: event.LeagueID, + LeagueName: event.LeagueName, + LeagueCC: event.LeagueCC.Value, + StartTime: event.StartTime.UTC(), + Source: EventSource(event.Source), + Status: EventStatus(event.Status), + IsFeatured: event.IsFeatured, + IsMonitored: event.IsMonitored, + IsActive: event.IsActive, + DefaultIsFeatured: event.DefaultIsFeatured, + DefaultIsActive: event.DefaultIsActive, + DefaultWinningUpperLimit: event.DefaultWinningUpperLimit, + WinningUpperLimit: event.WinningUpperLimit, + Score: event.Score.Value, + MatchMinute: event.MatchMinute.Value, + TimerStatus: event.TimerStatus.Value, + AddedTime: event.AddedTime.Value, + MatchPeriod: event.MatchPeriod.Value, + IsLive: event.IsLive, + FetchedAt: event.FetchedAt.UTC(), } } diff --git a/internal/domain/jsontypes.go b/internal/domain/jsontypes.go new file mode 100644 index 0000000..3e52375 --- /dev/null +++ b/internal/domain/jsontypes.go @@ -0,0 +1,27 @@ +package domain + +import ( + "encoding/json" + "fmt" +) + +// Custom type for fields that can be string or int +type StringOrNumber string + +func (s *StringOrNumber) UnmarshalJSON(data []byte) error { + // Try as string + var str string + if err := json.Unmarshal(data, &str); err == nil { + *s = StringOrNumber(str) + return nil + } + + // Try as number + var num json.Number + if err := json.Unmarshal(data, &num); err == nil { + *s = StringOrNumber(num.String()) + return nil + } + + return fmt.Errorf("StringOrNumber: cannot unmarshal %s", string(data)) +} diff --git a/internal/domain/league.go b/internal/domain/league.go index a44ae70..6743b6e 100644 --- a/internal/domain/league.go +++ b/internal/domain/league.go @@ -8,24 +8,30 @@ import ( // The ID and the Bet365 IDs we have here are both gotten from the betapi type LeagueWithSettings struct { - ID int64 - Name string - CompanyID int64 - CountryCode ValidString - Bet365ID ValidInt32 - SportID int32 - IsActive bool - IsFeatured bool - UpdatedAt time.Time + ID int64 + Name string + CompanyID int64 + CountryCode ValidString + Bet365ID ValidInt32 + SportID int32 + IsActive bool + IsFeatured bool + DefaultIsActive bool + DefaultIsFeatured bool + UpdatedAt time.Time } type LeagueWithSettingsRes struct { - ID int64 `json:"id" example:"1"` - Name string `json:"name" example:"BPL"` - CountryCode string `json:"cc" example:"uk"` - Bet365ID int32 `json:"bet365_id" example:"1121"` - IsActive bool `json:"is_active" example:"false"` - SportID int32 `json:"sport_id" example:"1"` - IsFeatured bool `json:"is_featured" example:"false"` + ID int64 `json:"id" example:"1"` + Name string `json:"name" example:"BPL"` + CompanyID int64 `json:"company_id" example:"1"` + CountryCode string `json:"cc" example:"uk"` + Bet365ID int32 `json:"bet365_id" example:"1121"` + IsActive bool `json:"is_active" example:"false"` + SportID int32 `json:"sport_id" example:"1"` + IsFeatured bool `json:"is_featured" example:"false"` + DefaultIsActive bool `json:"default_is_active" example:"false"` + DefaultIsFeatured bool `json:"default_is_featured" example:"false"` + UpdatedAt time.Time `json:"updated_at"` } type BaseLeague struct { ID int64 @@ -82,6 +88,7 @@ type UpdateLeague struct { } type LeagueFilter struct { + Query ValidString CountryCode ValidString SportID ValidInt32 IsActive ValidBool @@ -137,12 +144,12 @@ func ConvertDBBaseLeagues(leagues []dbgen.League) []BaseLeague { return result } -func ConvertDBLeagueWithSetting(lws dbgen.LeagueWithSetting) LeagueWithSettings { +func ConvertDBLeagueWithSetting(lws dbgen.GetAllLeaguesWithSettingsRow) LeagueWithSettings { return LeagueWithSettings{ ID: lws.ID, Name: lws.Name, - CompanyID: lws.CompanyID, - CountryCode: ValidString{ + CompanyID: lws.CompanyID.Int64, + CountryCode: ValidString{ Value: lws.CountryCode.String, Valid: lws.CountryCode.Valid, }, @@ -154,10 +161,13 @@ func ConvertDBLeagueWithSetting(lws dbgen.LeagueWithSetting) LeagueWithSettings SportID: lws.SportID, IsFeatured: lws.IsFeatured, UpdatedAt: lws.UpdatedAt.Time, + + DefaultIsActive: lws.DefaultIsActive, + DefaultIsFeatured: lws.DefaultIsFeatured, } } -func ConvertDBLeagueWithSettings(lws []dbgen.LeagueWithSetting) []LeagueWithSettings { +func ConvertDBLeagueWithSettings(lws []dbgen.GetAllLeaguesWithSettingsRow) []LeagueWithSettings { result := make([]LeagueWithSettings, len(lws)) for i, league := range lws { result[i] = ConvertDBLeagueWithSetting(league) @@ -174,3 +184,50 @@ func ConvertUpdateLeague(updateLeague UpdateLeague) dbgen.UpdateLeagueParams { SportID: updateLeague.SportID.ToPG(), } } + +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, + DefaultIsActive: lws.DefaultIsActive, + DefaultIsFeatured: lws.DefaultIsFeatured, + } +} + +func ConvertLeagueWithSettingResList(leagues []LeagueWithSettings) []LeagueWithSettingsRes { + result := make([]LeagueWithSettingsRes, len(leagues)) + + for i, lws := range leagues { + result[i] = ConvertLeagueWithSettingRes(lws) + } + + return result +} + +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, + DefaultIsFeatured: league.DefaultIsFeatured, + } +} + +func ConvertBaseLeagueResList(leagues []BaseLeague) []BaseLeagueRes { + result := make([]BaseLeagueRes, len(leagues)) + for i, league := range leagues { + result[i] = ConvertBaseLeagueRes(league) + } + + return result +} diff --git a/internal/domain/notification.go b/internal/domain/notification.go index d10f3d7..97fc9d1 100644 --- a/internal/domain/notification.go +++ b/internal/domain/notification.go @@ -32,6 +32,7 @@ const ( NOTIFICATION_TYPE_BET_RESULT NotificationType = "bet_result" NOTIFICATION_TYPE_TRANSFER_REJECTED NotificationType = "transfer_rejected" NOTIFICATION_TYPE_APPROVAL_REQUIRED NotificationType = "approval_required" + NOTIFICATION_TYPE_BONUS_AWARDED NotificationType = "bonus_awarded" NotificationRecieverSideAdmin NotificationRecieverSide = "admin" NotificationRecieverSideCustomer NotificationRecieverSide = "customer" @@ -73,7 +74,7 @@ type Notification struct { RecipientID int64 `json:"recipient_id"` Type NotificationType `json:"type"` Level NotificationLevel `json:"level"` - ErrorSeverity *NotificationErrorSeverity `json:"error_severity"` + ErrorSeverity NotificationErrorSeverity `json:"error_severity"` Reciever NotificationRecieverSide `json:"reciever"` IsRead bool `json:"is_read"` DeliveryStatus NotificationDeliveryStatus `json:"delivery_status,omitempty"` diff --git a/internal/domain/oddres.go b/internal/domain/oddres.go index 2334b16..19c2cb2 100644 --- a/internal/domain/oddres.go +++ b/internal/domain/oddres.go @@ -27,7 +27,7 @@ type RawOdd struct { // The Market ID for the json data can be either string / int which is causing problems when UnMarshalling type OddsMarket struct { - ID ValidInt64 `json:"id"` + ID StringOrNumber `json:"id"` Name string `json:"name"` Odds []json.RawMessage `json:"odds"` Header string `json:"header,omitempty"` diff --git a/internal/domain/odds.go b/internal/domain/odds.go index c56d320..88092e1 100644 --- a/internal/domain/odds.go +++ b/internal/domain/odds.go @@ -61,6 +61,17 @@ type CreateOddMarketSettings struct { CustomRawOdds []map[string]interface{} } +type CustomOdd struct { + OddID int64 `json:"odd_id"` + OddValue float32 `json:"odd_value"` +} + +type CreateOddMarketSettingsReq struct { + OddMarketID int64 `json:"odd_market_id"` + IsActive *bool `json:"is_active,omitempty"` + CustomOdd []CustomOdd `json:"custom_odd,omitempty"` +} + type RawOddsByMarketID struct { ID int64 `json:"id"` MarketName string `json:"market_name"` @@ -136,12 +147,12 @@ func ConvertCreateOddMarket(oddMarket CreateOddMarket) (dbgen.InsertOddsMarketPa }, nil } -func ConvertCreateOddMarketSetting(oms CreateOddMarketSettings) (dbgen.InsertOddSettingsParams, error) { +func ConvertCreateOddMarketSetting(oms CreateOddMarketSettings) (dbgen.SaveOddSettingsParams, error) { rawOddsBytes, err := json.Marshal(oms.CustomRawOdds) if err != nil { - return dbgen.InsertOddSettingsParams{}, err + return dbgen.SaveOddSettingsParams{}, err } - return dbgen.InsertOddSettingsParams{ + return dbgen.SaveOddSettingsParams{ CompanyID: oms.CompanyID, OddsMarketID: oms.OddMarketID, IsActive: oms.IsActive.ToPG(), diff --git a/internal/domain/raffle.go b/internal/domain/raffle.go new file mode 100644 index 0000000..a8307b8 --- /dev/null +++ b/internal/domain/raffle.go @@ -0,0 +1,83 @@ +package domain + +import "time" + +type Raffle struct { + ID int32 + CompanyID int32 + Name string + CreatedAt time.Time + ExpiresAt time.Time + Type string + Status string +} + +type RaffleFilter struct { + // requireds will depend on type of raffle (sport or game) + Type string `json:"type" validate:"required,oneof=sport game"` + RaffleID int32 `json:"raffle_id" validate:"required"` + SportID int32 `json:"sport_id" validate:"required_if=Type sport"` + LeagueID int32 `json:"league_id" validate:"required_if=Type sport"` + GameID string `json:"game_id" validate:"required_if=Type game"` +} + +type RaffleStanding struct { + UserID int64 + RaffleID int32 + FirstName string + LastName string + PhoneNumber string + Email string + TicketCount int64 +} + +type RaffleStandingRes struct { + UserID int64 `json:"user_id"` + RaffleID int32 `json:"raffle_id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + PhoneNumber string `json:"phone_number"` + Email string `json:"email"` + TicketCount int64 `json:"ticket_count"` +} + +type RaffleWinnerParams struct { + RaffleID int32 + UserID int32 + Rank int32 +} + +type RaffleTicket struct { + ID int32 + RaffleID int32 + UserID int32 + IsActive bool +} + +type RaffleTicketRes struct { + TicketID int32 + UserID int32 + Name string + Type string + ExpiresAt time.Time + Status string +} + +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"` +} + +type CreateRaffleTicket struct { + RaffleID int32 `json:"raffle_id" validate:"required"` + UserID int32 `json:"user_id" validate:"required"` +} + +// aside from ID, atleast one of the fields should be required +type UpdateRaffleParams struct { + ID int32 `json:"id" validate:"required"` + Name string `json:"name" validate:"required_without_all=ExpiresAt"` + ExpiresAt *time.Time `json:"expires_at" validate:"required_without_all=Name"` +} diff --git a/internal/domain/referal.go b/internal/domain/referal.go index 1e528a4..bb9e1bb 100644 --- a/internal/domain/referal.go +++ b/internal/domain/referal.go @@ -1,73 +1,186 @@ package domain import ( - "database/sql/driver" - "fmt" "time" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" ) -type ReferralStatus string - -const ( - ReferralPending ReferralStatus = "PENDING" - ReferralCompleted ReferralStatus = "COMPLETED" - ReferralExpired ReferralStatus = "EXPIRED" - ReferralCancelled ReferralStatus = "CANCELLED" -) - -func (rs *ReferralStatus) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *rs = ReferralStatus(s) - case string: - *rs = ReferralStatus(s) - default: - return fmt.Errorf("unsupported scan type for ReferralStatus: %T", src) - } - return nil +type ReferralCode struct { + ID int64 + ReferrerID int64 + ReferralCode string + CompanyID int64 + NumberOfReferrals int64 + RewardAmount Currency + CreatedAt time.Time + UpdatedAt time.Time } -func (rs ReferralStatus) Value() (driver.Value, error) { - return string(rs), nil +type ReferralCodeRes struct { + ID int64 `json:"id"` + ReferrerID int64 `json:"referrer_id"` + ReferralCode string `json:"referral_code"` + CompanyID int64 `json:"company_id"` + NumberOfReferrals int64 `json:"number_of_referrals"` + RewardAmount float32 `json:"reward_amount"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type CreateReferralCode struct { + ReferrerID int64 + ReferralCode string + CompanyID int64 + NumberOfReferrals int64 + RewardAmount Currency +} + +type UserReferral struct { + ReferredID int64 + ReferralCodeID int64 +} + +type CreateUserReferrals struct { + ReferredID int64 + ReferralCodeID int64 +} + +type UpdateReferralCode struct { + ID int64 + IsActive bool + ReferralCode string + RewardAmount Currency + NumberOfReferrals int64 } type ReferralStats struct { - TotalReferrals int - CompletedReferrals int - TotalRewardEarned float64 - PendingRewards float64 + TotalReferrals int64 + TotalRewardEarned Currency } -type ReferralSettings struct { - ID int64 - ReferralRewardAmount float64 - CashbackPercentage float64 - BetReferralBonusPercentage float64 - MaxReferrals int32 - ExpiresAfterDays int32 - UpdatedBy string - CreatedAt time.Time - UpdatedAt time.Time - Version int32 +type ReferralStatsRes struct { + TotalReferrals int64 `json:"total_referrals"` + TotalRewardEarned float32 `json:"total_reward_earned"` } -type ReferralSettingsReq struct { - ReferralRewardAmount float64 `json:"referral_reward_amount" validate:"required"` - CashbackPercentage float64 `json:"cashback_percentage" validate:"required"` - MaxReferrals int32 `json:"max_referrals" validate:"required"` - ExpiresAfterDays int32 `json:"expires_afterdays" validate:"required"` - UpdatedBy string `json:"updated_by" validate:"required"` +// type ReferralSettings struct { +// ID int64 +// ReferralRewardAmount float64 +// CashbackPercentage float64 +// BetReferralBonusPercentage float64 +// MaxReferrals int32 +// ExpiresAfterDays int32 +// UpdatedBy string +// CreatedAt time.Time +// UpdatedAt time.Time +// Version int32 +// } + +// type ReferralSettingsReq struct { +// ReferralRewardAmount float64 `json:"referral_reward_amount" validate:"required"` +// CashbackPercentage float64 `json:"cashback_percentage" validate:"required"` +// MaxReferrals int32 `json:"max_referrals" validate:"required"` +// UpdatedBy string `json:"updated_by" validate:"required"` +// } + +func ConvertCreateReferralCode(code CreateReferralCode) dbgen.CreateReferralCodeParams { + return dbgen.CreateReferralCodeParams{ + ReferralCode: code.ReferralCode, + ReferrerID: code.ReferrerID, + CompanyID: code.CompanyID, + NumberOfReferrals: code.NumberOfReferrals, + RewardAmount: int64(code.RewardAmount), + } } -type Referral struct { - ID int64 - ReferralCode string - ReferrerID string - ReferredID *string - Status ReferralStatus - RewardAmount float64 - CashbackAmount float64 - CreatedAt time.Time - UpdatedAt time.Time - ExpiresAt time.Time + func ConvertDBReferralCode(code dbgen.ReferralCode) ReferralCode { + return ReferralCode{ + ID: code.ID, + ReferrerID: code.ReferrerID, + ReferralCode: code.ReferralCode, + NumberOfReferrals: code.NumberOfReferrals, + RewardAmount: Currency(code.RewardAmount), + CompanyID: code.CompanyID, + CreatedAt: code.CreatedAt.Time, + UpdatedAt: code.UpdatedAt.Time, + } +} + +func ConvertDBReferralCodes(codes []dbgen.ReferralCode) []ReferralCode { + result := make([]ReferralCode, len(codes)) + for i, code := range codes { + result[i] = ConvertDBReferralCode(code) + } + return result +} + +func ConvertCreateUserReferral(referral CreateUserReferrals) dbgen.CreateUserReferralParams { + return dbgen.CreateUserReferralParams{ + ReferredID: referral.ReferredID, + ReferralCodeID: referral.ReferralCodeID, + } +} + +func ConvertDBUserReferral(referral dbgen.UserReferral) UserReferral { + return UserReferral{ + ReferredID: referral.ReferredID, + ReferralCodeID: referral.ReferralCodeID, + } +} + +func ConvertDBUserReferrals(referrals []dbgen.UserReferral) []UserReferral { + result := make([]UserReferral, len(referrals)) + for i, referral := range referrals { + result[i] = ConvertDBUserReferral(referral) + } + + return result +} + +func ConvertUpdateReferralCode(referralCode UpdateReferralCode) dbgen.UpdateReferralCodeParams { + return dbgen.UpdateReferralCodeParams{ + ID: referralCode.ID, + IsActive: referralCode.IsActive, + ReferralCode: referralCode.ReferralCode, + NumberOfReferrals: referralCode.NumberOfReferrals, + RewardAmount: int64(referralCode.RewardAmount), + } +} + +func ConvertDBReferralStats(stats dbgen.GetReferralStatsRow) ReferralStats { + return ReferralStats{ + TotalReferrals: stats.TotalReferrals, + TotalRewardEarned: Currency(stats.TotalRewardEarned), + } +} + +func ConvertReferralCodeRes(referral ReferralCode) ReferralCodeRes { + return ReferralCodeRes{ + ID: referral.ID, + ReferrerID: referral.ReferrerID, + ReferralCode: referral.ReferralCode, + CompanyID: referral.CompanyID, + NumberOfReferrals: referral.NumberOfReferrals, + RewardAmount: referral.RewardAmount.Float32(), + CreatedAt: referral.CreatedAt, + UpdatedAt: referral.UpdatedAt, + } +} + +func ConvertReferralCodeResList(referrals []ReferralCode) []ReferralCodeRes { + result := make([]ReferralCodeRes, len(referrals)) + + for i, referral := range referrals { + result[i] = ConvertReferralCodeRes(referral) + } + + return result +} + +func ConvertReferralStatsRes(stats ReferralStats) ReferralStatsRes { + return ReferralStatsRes{ + TotalReferrals: stats.TotalReferrals, + TotalRewardEarned: stats.TotalRewardEarned.Float32(), + } } diff --git a/internal/domain/role.go b/internal/domain/role.go index dcd2c57..f1ddfc4 100644 --- a/internal/domain/role.go +++ b/internal/domain/role.go @@ -3,16 +3,17 @@ package domain type Role string const ( - RoleSuperAdmin Role = "super_admin" - RoleAdmin Role = "admin" - RoleBranchManager Role = "branch_manager" - RoleCustomer Role = "customer" - RoleCashier Role = "cashier" + RoleSuperAdmin Role = "super_admin" + RoleAdmin Role = "admin" + RoleBranchManager Role = "branch_manager" + RoleCustomer Role = "customer" + RoleCashier Role = "cashier" + RoleTransactionApprover Role = "transaction_approver" ) func (r Role) IsValid() bool { switch r { - case RoleSuperAdmin, RoleAdmin, RoleBranchManager, RoleCustomer, RoleCashier: + case RoleSuperAdmin, RoleAdmin, RoleBranchManager, RoleCustomer, RoleCashier, RoleTransactionApprover: return true default: return false diff --git a/internal/domain/setting_list.go b/internal/domain/setting_list.go index 5f4a20e..5a5c86e 100644 --- a/internal/domain/setting_list.go +++ b/internal/domain/setting_list.go @@ -16,67 +16,170 @@ var ( ) type SettingList struct { - SMSProvider SMSProvider `json:"sms_provider"` - MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"` - BetAmountLimit Currency `json:"bet_amount_limit"` - DailyTicketPerIP int64 `json:"daily_ticket_limit"` - TotalWinningLimit Currency `json:"total_winning_limit"` - AmountForBetReferral Currency `json:"amount_for_bet_referral"` - CashbackAmountCap Currency `json:"cashback_amount_cap"` + SMSProvider SMSProvider `json:"sms_provider"` + MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"` + BetAmountLimit Currency `json:"bet_amount_limit"` + DailyTicketPerIP int64 `json:"daily_ticket_limit"` + TotalWinningLimit Currency `json:"total_winning_limit"` + AmountForBetReferral Currency `json:"amount_for_bet_referral"` + CashbackAmountCap Currency `json:"cashback_amount_cap"` + DefaultWinningLimit int64 `json:"default_winning_limit"` + ReferralRewardAmount Currency `json:"referral_reward_amount"` + CashbackPercentage float32 `json:"cashback_percentage"` + DefaultMaxReferrals int64 `json:"default_max_referrals"` + MinimumBetAmount Currency `json:"minimum_bet_amount"` + BetDuplicateLimit int64 `json:"bet_duplicate_limit"` + SendEmailOnBetFinish bool `json:"send_email_on_bet_finish"` + SendSMSOnBetFinish bool `json:"send_sms_on_bet_finish"` + WelcomeBonusActive bool `json:"welcome_bonus_active"` + WelcomeBonusMultiplier float32 `json:"welcome_bonus_multiplier"` + WelcomeBonusCap Currency `json:"welcome_bonus_cap"` + WelcomeBonusCount int64 `json:"welcome_bonus_count"` + WelcomeBonusExpire int64 `json:"welcome_bonus_expiry"` } type SettingListRes struct { - SMSProvider SMSProvider `json:"sms_provider"` - MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"` - BetAmountLimit float32 `json:"bet_amount_limit"` - DailyTicketPerIP int64 `json:"daily_ticket_limit"` - TotalWinningLimit float32 `json:"total_winning_limit"` - AmountForBetReferral float32 `json:"amount_for_bet_referral"` - CashbackAmountCap float32 `json:"cashback_amount_cap"` + SMSProvider SMSProvider `json:"sms_provider"` + MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"` + BetAmountLimit float32 `json:"bet_amount_limit"` + DailyTicketPerIP int64 `json:"daily_ticket_limit"` + TotalWinningLimit float32 `json:"total_winning_limit"` + AmountForBetReferral float32 `json:"amount_for_bet_referral"` + CashbackAmountCap float32 `json:"cashback_amount_cap"` + DefaultWinningLimit int64 `json:"default_winning_limit"` + ReferralRewardAmount float32 `json:"referral_reward_amount"` + CashbackPercentage float32 `json:"cashback_percentage"` + DefaultMaxReferrals int64 `json:"default_max_referrals"` + MinimumBetAmount float32 `json:"minimum_bet_amount"` + BetDuplicateLimit int64 `json:"bet_duplicate_limit"` + SendEmailOnBetFinish bool `json:"send_email_on_bet_finish"` + SendSMSOnBetFinish bool `json:"send_sms_on_bet_finish"` + WelcomeBonusActive bool `json:"welcome_bonus_active"` + WelcomeBonusMultiplier float32 `json:"welcome_bonus_multiplier"` + WelcomeBonusCap float32 `json:"welcome_bonus_cap"` + WelcomeBonusCount int64 `json:"welcome_bonus_count"` + WelcomeBonusExpire int64 `json:"welcome_bonus_expiry"` +} + +func ConvertSettingListRes(settings SettingList) SettingListRes { + return SettingListRes{ + SMSProvider: settings.SMSProvider, + MaxNumberOfOutcomes: settings.MaxNumberOfOutcomes, + BetAmountLimit: settings.BetAmountLimit.Float32(), + DailyTicketPerIP: settings.DailyTicketPerIP, + TotalWinningLimit: settings.TotalWinningLimit.Float32(), + AmountForBetReferral: settings.AmountForBetReferral.Float32(), + CashbackAmountCap: settings.CashbackAmountCap.Float32(), + DefaultWinningLimit: settings.DefaultWinningLimit, + ReferralRewardAmount: settings.ReferralRewardAmount.Float32(), + CashbackPercentage: settings.CashbackPercentage, + DefaultMaxReferrals: settings.DefaultMaxReferrals, + MinimumBetAmount: settings.MinimumBetAmount.Float32(), + BetDuplicateLimit: settings.BetDuplicateLimit, + SendEmailOnBetFinish: settings.SendEmailOnBetFinish, + SendSMSOnBetFinish: settings.SendSMSOnBetFinish, + WelcomeBonusActive: settings.WelcomeBonusActive, + WelcomeBonusMultiplier: settings.WelcomeBonusMultiplier, + WelcomeBonusCap: settings.WelcomeBonusCap.Float32(), + WelcomeBonusCount: settings.WelcomeBonusCount, + WelcomeBonusExpire: settings.WelcomeBonusExpire, + } } type SaveSettingListReq struct { - SMSProvider *string `json:"sms_provider,omitempty"` - MaxNumberOfOutcomes *int64 `json:"max_number_of_outcomes,omitempty"` - BetAmountLimit *float32 `json:"bet_amount_limit,omitempty"` - DailyTicketPerIP *int64 `json:"daily_ticket_limit,omitempty"` - TotalWinningLimit *float32 `json:"total_winning_limit,omitempty"` - AmountForBetReferral *float32 `json:"amount_for_bet_referral,omitempty"` - CashbackAmountCap *float32 `json:"cashback_amount_cap,omitempty"` + SMSProvider *string `json:"sms_provider,omitempty"` + MaxNumberOfOutcomes *int64 `json:"max_number_of_outcomes,omitempty"` + BetAmountLimit *float32 `json:"bet_amount_limit,omitempty"` + DailyTicketPerIP *int64 `json:"daily_ticket_limit,omitempty"` + TotalWinningLimit *float32 `json:"total_winning_limit,omitempty"` + AmountForBetReferral *float32 `json:"amount_for_bet_referral,omitempty"` + CashbackAmountCap *float32 `json:"cashback_amount_cap,omitempty"` + DefaultWinningLimit *int64 `json:"default_winning_limit,omitempty"` + ReferralRewardAmount *float32 `json:"referral_reward_amount"` + CashbackPercentage *float32 `json:"cashback_percentage"` + DefaultMaxReferrals *int64 `json:"default_max_referrals"` + MinimumBetAmount *float32 `json:"minimum_bet_amount"` + BetDuplicateLimit *int64 `json:"bet_duplicate_limit"` + SendEmailOnBetFinish *bool `json:"send_email_on_bet_finish"` + SendSMSOnBetFinish *bool `json:"send_sms_on_bet_finish"` + WelcomeBonusActive *bool `json:"welcome_bonus_active"` + WelcomeBonusMultiplier *float32 `json:"welcome_bonus_multiplier"` + WelcomeBonusCap *float32 `json:"welcome_bonus_cap"` + WelcomeBonusCount *int64 `json:"welcome_bonus_count"` + WelcomeBonusExpire *int64 `json:"welcome_bonus_expiry"` +} + +type ValidSettingList struct { + SMSProvider ValidString + MaxNumberOfOutcomes ValidInt64 + BetAmountLimit ValidCurrency + DailyTicketPerIP ValidInt64 + TotalWinningLimit ValidCurrency + AmountForBetReferral ValidCurrency + CashbackAmountCap ValidCurrency + DefaultWinningLimit ValidInt64 + ReferralRewardAmount ValidCurrency + CashbackPercentage ValidFloat32 + DefaultMaxReferrals ValidInt64 + MinimumBetAmount ValidCurrency + BetDuplicateLimit ValidInt64 + SendEmailOnBetFinish ValidBool + SendSMSOnBetFinish ValidBool + WelcomeBonusActive ValidBool + WelcomeBonusMultiplier ValidFloat32 + WelcomeBonusCap ValidCurrency + WelcomeBonusCount ValidInt64 + WelcomeBonusExpire ValidInt64 } func ConvertSaveSettingListReq(settings SaveSettingListReq) ValidSettingList { return ValidSettingList{ - SMSProvider: ConvertStringPtr(settings.SMSProvider), - MaxNumberOfOutcomes: ConvertInt64Ptr(settings.MaxNumberOfOutcomes), - BetAmountLimit: ConvertFloat32PtrToCurrency(settings.BetAmountLimit), - DailyTicketPerIP: ConvertInt64Ptr(settings.DailyTicketPerIP), - TotalWinningLimit: ConvertFloat32PtrToCurrency(settings.TotalWinningLimit), - AmountForBetReferral: ConvertFloat32PtrToCurrency(settings.AmountForBetReferral), - CashbackAmountCap: ConvertFloat32PtrToCurrency(settings.CashbackAmountCap), + SMSProvider: ConvertStringPtr(settings.SMSProvider), + MaxNumberOfOutcomes: ConvertInt64Ptr(settings.MaxNumberOfOutcomes), + BetAmountLimit: ConvertFloat32PtrToCurrency(settings.BetAmountLimit), + DailyTicketPerIP: ConvertInt64Ptr(settings.DailyTicketPerIP), + TotalWinningLimit: ConvertFloat32PtrToCurrency(settings.TotalWinningLimit), + AmountForBetReferral: ConvertFloat32PtrToCurrency(settings.AmountForBetReferral), + CashbackAmountCap: ConvertFloat32PtrToCurrency(settings.CashbackAmountCap), + DefaultWinningLimit: ConvertInt64Ptr(settings.DefaultWinningLimit), + ReferralRewardAmount: ConvertFloat32PtrToCurrency(settings.ReferralRewardAmount), + CashbackPercentage: ConvertFloat32Ptr(settings.CashbackPercentage), + DefaultMaxReferrals: ConvertInt64Ptr(settings.DefaultMaxReferrals), + MinimumBetAmount: ConvertFloat32PtrToCurrency(settings.MinimumBetAmount), + BetDuplicateLimit: ConvertInt64Ptr(settings.BetDuplicateLimit), + SendEmailOnBetFinish: ConvertBoolPtr(settings.SendEmailOnBetFinish), + SendSMSOnBetFinish: ConvertBoolPtr(settings.SendSMSOnBetFinish), + WelcomeBonusActive: ConvertBoolPtr(settings.WelcomeBonusActive), + WelcomeBonusMultiplier: ConvertFloat32Ptr(settings.WelcomeBonusMultiplier), + WelcomeBonusCap: ConvertFloat32PtrToCurrency(settings.WelcomeBonusCap), + WelcomeBonusCount: ConvertInt64Ptr(settings.WelcomeBonusCount), + WelcomeBonusExpire: ConvertInt64Ptr(settings.WelcomeBonusExpire), } } -type ValidSettingList struct { - SMSProvider ValidString - MaxNumberOfOutcomes ValidInt64 - BetAmountLimit ValidCurrency - DailyTicketPerIP ValidInt64 - TotalWinningLimit ValidCurrency - AmountForBetReferral ValidCurrency - CashbackAmountCap ValidCurrency -} - // Always make sure to run the validation before converting this func (vsl *ValidSettingList) ToSettingList() SettingList { return SettingList{ - SMSProvider: SMSProvider(vsl.SMSProvider.Value), - MaxNumberOfOutcomes: vsl.MaxNumberOfOutcomes.Value, - BetAmountLimit: Currency(vsl.BetAmountLimit.Value), - DailyTicketPerIP: vsl.DailyTicketPerIP.Value, - TotalWinningLimit: Currency(vsl.TotalWinningLimit.Value), - AmountForBetReferral: Currency(vsl.AmountForBetReferral.Value), - CashbackAmountCap: Currency(vsl.CashbackAmountCap.Value), + SMSProvider: SMSProvider(vsl.SMSProvider.Value), + MaxNumberOfOutcomes: vsl.MaxNumberOfOutcomes.Value, + BetAmountLimit: vsl.BetAmountLimit.Value, + DailyTicketPerIP: vsl.DailyTicketPerIP.Value, + TotalWinningLimit: vsl.TotalWinningLimit.Value, + AmountForBetReferral: vsl.AmountForBetReferral.Value, + CashbackAmountCap: vsl.CashbackAmountCap.Value, + DefaultWinningLimit: vsl.DefaultWinningLimit.Value, + ReferralRewardAmount: vsl.ReferralRewardAmount.Value, + CashbackPercentage: vsl.CashbackPercentage.Value, + DefaultMaxReferrals: vsl.DefaultMaxReferrals.Value, + MinimumBetAmount: vsl.MinimumBetAmount.Value, + BetDuplicateLimit: vsl.BetDuplicateLimit.Value, + SendEmailOnBetFinish: vsl.SendEmailOnBetFinish.Value, + SendSMSOnBetFinish: vsl.SendSMSOnBetFinish.Value, + WelcomeBonusActive: vsl.WelcomeBonusActive.Value, + WelcomeBonusMultiplier: vsl.WelcomeBonusMultiplier.Value, + WelcomeBonusCap: vsl.WelcomeBonusCap.Value, + WelcomeBonusCount: vsl.WelcomeBonusCount.Value, + WelcomeBonusExpire: vsl.WelcomeBonusExpire.Value, } } @@ -92,6 +195,11 @@ func (vsl *ValidSettingList) GetInt64SettingsMap() map[string]*ValidInt64 { return map[string]*ValidInt64{ "max_number_of_outcomes": &vsl.MaxNumberOfOutcomes, "daily_ticket_limit": &vsl.DailyTicketPerIP, + "default_winning_limit": &vsl.DefaultWinningLimit, + "default_max_referrals": &vsl.DefaultMaxReferrals, + "bet_duplicate_limit": &vsl.BetDuplicateLimit, + "welcome_bonus_count": &vsl.WelcomeBonusCount, + "welcome_bonus_expiry": &vsl.WelcomeBonusExpire, } } @@ -101,6 +209,9 @@ func (vsl *ValidSettingList) GetCurrencySettingsMap() map[string]*ValidCurrency "total_winnings_limit": &vsl.TotalWinningLimit, "amount_for_bet_referral": &vsl.AmountForBetReferral, "cashback_amount_cap": &vsl.CashbackAmountCap, + "referral_reward_amount": &vsl.ReferralRewardAmount, + "minimum_bet_amount": &vsl.MinimumBetAmount, + "welcome_bonus_cap": &vsl.WelcomeBonusCap, } } @@ -111,17 +222,26 @@ func (vsl *ValidSettingList) GetStringSettingsMap() map[string]*ValidString { } func (vsl *ValidSettingList) GetBoolSettingsMap() map[string]*ValidBool { - return map[string]*ValidBool{} + return map[string]*ValidBool{ + "send_email_on_bet_finish": &vsl.SendEmailOnBetFinish, + "send_sms_on_bet_finish": &vsl.SendSMSOnBetFinish, + "welcome_bonus_active": &vsl.WelcomeBonusActive, + } } func (vsl *ValidSettingList) GetFloat32SettingsMap() map[string]*ValidFloat32 { - return map[string]*ValidFloat32{} + return map[string]*ValidFloat32{ + "cashback_percentage": &vsl.CashbackPercentage, + "welcome_bonus_multiplier": &vsl.WelcomeBonusMultiplier, + } } func (vsl *ValidSettingList) GetTimeSettingsMap() map[string]*ValidTime { return map[string]*ValidTime{} } +// Setting Functions + func (vsl *ValidSettingList) GetTotalSettings() int { return len(vsl.GetInt64SettingsMap()) + len(vsl.GetCurrencySettingsMap()) + @@ -157,57 +277,105 @@ func (vsl *ValidSettingList) GetAllValid() map[string]*bool { return settingValid } -func setValidSetting[T any](settings map[string]*T, searchKey string, setVal T) error { - for key, setting := range settings { +// func setValidSetting[T any](settings map[string]*T, searchKey string, searchVal string, setVal func(string) (T, error)) error { +// for key, setting := range settings { + +// if key == searchKey { +// s, err := setVal(searchVal) +// if err != nil { +// return err +// } +// *setting = s +// } +// return nil +// } +// return ErrSettingNotFound +// } +func (vsl *ValidSettingList) SetInt64Setting(searchKey string, searchVal string) error { + for key, setting := range vsl.GetInt64SettingsMap() { if key == searchKey { - *setting = setVal + value, err := strconv.ParseInt(searchVal, 10, 64) + if err != nil { + return err + } + *setting = ValidInt64{Value: value, Valid: true} + return nil } - return nil } return ErrSettingNotFound } -func (vsl *ValidSettingList) SetInt64Setting(searchKey string, searchVal string) error { - value, err := strconv.ParseInt(searchVal, 10, 64) - if err != nil { - return err - } - return setValidSetting(vsl.GetInt64SettingsMap(), searchKey, ValidInt64{Value: value, Valid: true}) -} func (vsl *ValidSettingList) SetCurrencySetting(searchKey string, searchVal string) error { - value, err := strconv.ParseInt(searchVal, 10, 64) - if err != nil { - return err + for key, setting := range vsl.GetCurrencySettingsMap() { + if key == searchKey { + value, err := strconv.ParseInt(searchVal, 10, 64) + if err != nil { + return err + } + *setting = ValidCurrency{Value: Currency(value), Valid: true} + return nil + } } - return setValidSetting(vsl.GetCurrencySettingsMap(), searchKey, ValidCurrency{Value: Currency(value), Valid: true}) + + return ErrSettingNotFound } func (vsl *ValidSettingList) SetStringSetting(searchKey string, searchVal string) error { - return setValidSetting(vsl.GetStringSettingsMap(), searchKey, ValidString{Value: searchVal, Valid: true}) + for key, setting := range vsl.GetStringSettingsMap() { + if key == searchKey { + *setting = ValidString{Value: searchVal, Valid: true} + return nil + } + } + + return ErrSettingNotFound } func (vsl *ValidSettingList) SetBoolSetting(searchKey string, searchVal string) error { - value, err := strconv.ParseBool(searchVal) - if err != nil { - return err + for key, setting := range vsl.GetBoolSettingsMap() { + + if key == searchKey { + value, err := strconv.ParseBool(searchVal) + if err != nil { + return err + } + + *setting = ValidBool{Value: value, Valid: true} + return nil + } + } - return setValidSetting(vsl.GetBoolSettingsMap(), searchKey, ValidBool{Value: value, Valid: true}) + + return ErrSettingNotFound } func (vsl *ValidSettingList) SetFloat32Setting(searchKey string, searchVal string) error { - value, err := strconv.ParseFloat(searchVal, 32) - if err != nil { - return err + for key, setting := range vsl.GetFloat32SettingsMap() { + if key == searchKey { + value, err := strconv.ParseFloat(searchVal, 32) + if err != nil { + return err + } + *setting = ValidFloat32{Value: float32(value), Valid: true} + return nil + } } - return setValidSetting(vsl.GetFloat32SettingsMap(), searchKey, ValidFloat32{Value: float32(value), Valid: true}) + + return ErrSettingNotFound } func (vsl *ValidSettingList) SetTimeSetting(searchKey string, searchVal string) error { - value, err := time.Parse(time.RFC3339, searchVal) - if err != nil { - return err + for key, setting := range vsl.GetTimeSettingsMap() { + if key == searchKey { + value, err := time.Parse(time.RFC3339, searchVal) + if err != nil { + return err + } + *setting = ValidTime{Value: value, Valid: true} + return nil + } } - return setValidSetting(vsl.GetTimeSettingsMap(), searchKey, ValidTime{Value: value, Valid: true}) + return ErrSettingNotFound } func (vsl *ValidSettingList) SetSetting(searchKey string, searchVal string) error { @@ -223,9 +391,10 @@ func (vsl *ValidSettingList) SetSetting(searchKey string, searchVal string) erro for _, setter := range setters { if err := setter(searchKey, searchVal); err != nil { if err == ErrSettingNotFound { + // fmt.Printf("setting is not found %v \n", searchKey) continue // not this setter, try the next } - return fmt.Errorf("error while processing setting %q: %w", searchKey, err) + return fmt.Errorf("error while processing setting %q: %w \n", searchKey, err) } return nil // successfully set } @@ -306,6 +475,7 @@ func validateSettings[T any]( var errs []string for key, s := range settings { if !customValidator(s) { + errs = append(errs, fmt.Sprintf("%v is invalid", key)) } } @@ -378,6 +548,7 @@ func (vsl *ValidSettingList) ValidateAllSettings() error { for _, validator := range validators { if err := validator(); err != nil { + errs = append(errs, err.Error()) } } @@ -410,12 +581,12 @@ func ConvertDBGlobalSettingList(settings []dbgen.GlobalSetting) (SettingList, er if err == ErrSettingNotFound { MongoDBLogger.Warn("unknown setting found on database", zap.String("setting", setting.Key)) } + MongoDBLogger.Error("unknown error while fetching settings", zap.Error(err)) } } if err := dbSettingList.ValidateAllSettings(); err != nil { - fmt.Printf("setting validation error: %v \n", err) - MongoDBLogger.Warn("setting validation error", zap.Error(err)) + MongoDBLogger.Warn("setting validation error", zap.Error(err), zap.Any("db_setting_list", dbSettingList)) return SettingList{}, err } @@ -436,7 +607,6 @@ func ConvertDBOverrideSettingList(settings []dbgen.GetOverrideSettingsRow) (Sett } if err := dbSettingList.ValidateAllSettings(); err != nil { - fmt.Printf("setting validation error: %v \n", err) MongoDBLogger.Warn("setting validation error", zap.Error(err)) return SettingList{}, err } diff --git a/internal/domain/transfer.go b/internal/domain/transfer.go index cf629c3..dd32c18 100644 --- a/internal/domain/transfer.go +++ b/internal/domain/transfer.go @@ -105,3 +105,10 @@ type CreateTransfer struct { Status string `json:"status"` CashierID ValidInt64 `json:"cashier_id"` } + +type TransferStats struct { + TotalTransfer int64 + TotalDeposits int64 + TotalWithdraws int64 + TotalWalletToWallet int64 +} diff --git a/internal/domain/user.go b/internal/domain/user.go index 194a89b..73920a5 100644 --- a/internal/domain/user.go +++ b/internal/domain/user.go @@ -23,7 +23,7 @@ type User struct { UpdatedAt time.Time SuspendedAt time.Time Suspended bool - CompanyID ValidInt64 //This should be null + CompanyID ValidInt64 } type UserFilter struct { @@ -36,7 +36,6 @@ type UserFilter struct { CreatedAfter ValidTime } - type RegisterUserReq struct { FirstName string LastName string @@ -65,6 +64,7 @@ type ResetPasswordReq struct { Password string Otp string OtpMedium OtpMedium + CompanyID int64 } type UpdateUserReq struct { UserId int64 diff --git a/internal/domain/validtypes.go b/internal/domain/validtypes.go index c20f6c3..99ad794 100644 --- a/internal/domain/validtypes.go +++ b/internal/domain/validtypes.go @@ -31,6 +31,7 @@ func (n *ValidInt64) UnmarshalJSON(data []byte) error { } v, err := strconv.ParseInt(s, 10, 64) if err != nil { + fmt.Printf("Failed to parse the value of %v \n\n", s) return err } n.Value, n.Valid = v, true @@ -42,7 +43,7 @@ func (n *ValidInt64) UnmarshalJSON(data []byte) error { n.Value, n.Valid = v, true return nil } - + fmt.Printf("Failed to parse the value of %v", s) return fmt.Errorf("invalid int64 value: %s", string(data)) } diff --git a/internal/domain/wallet.go b/internal/domain/wallet.go index aec3895..947f3c8 100644 --- a/internal/domain/wallet.go +++ b/internal/domain/wallet.go @@ -76,9 +76,10 @@ type CreateCustomerWallet struct { type WalletType string const ( - CustomerWalletType WalletType = "customer_wallet" - BranchWalletType WalletType = "branch_wallet" - CompanyWalletType WalletType = "company_wallet" + RegularWalletType WalletType = "regular_wallet" + StaticWalletType WalletType = "static_wallet" + BranchWalletType WalletType = "branch_wallet" + CompanyWalletType WalletType = "company_wallet" ) // domain/wallet.go @@ -92,18 +93,18 @@ const ( ) type DirectDeposit struct { - ID int64 - CustomerID int64 - WalletID int64 - Wallet Wallet // Joined data - Amount Currency - BankReference string - SenderAccount string - Status DirectDepositStatus - CreatedAt time.Time - VerifiedBy *int64 // Nullable - VerificationNotes string - VerifiedAt *time.Time // Nullable + ID int64 `json:"id"` + CustomerID int64 `json:"customer_id"` + WalletID int64 `json:"wallet_id"` + Wallet Wallet `json:"wallet"` + Amount Currency `json:"amount"` + BankReference string `json:"bank_reference"` + SenderAccount string `json:"sender_account"` + Status DirectDepositStatus `json:"status"` + CreatedAt time.Time `json:"created_at"` + VerifiedBy *int64 `json:"verified_by"` + VerificationNotes string `json:"verification_notes"` + VerifiedAt *time.Time `json:"verified_at"` } type CreateDirectDeposit struct { @@ -124,14 +125,14 @@ type UpdateDirectDeposit struct { } type DirectDepositRequest struct { - CustomerID int64 `json:"customer_id" binding:"required"` - Amount Currency `json:"amount" binding:"required,gt=0"` - BankReference string `json:"bank_reference" binding:"required"` - SenderAccount string `json:"sender_account" binding:"required"` + CustomerID int64 `json:"customer_id" binding:"required"` + Amount Currency `json:"amount" binding:"required,gt=0"` + BankReference string `json:"bank_reference" binding:"required"` + SenderAccount string `json:"sender_account" binding:"required"` } type VerifyDirectDepositRequest struct { - DepositID int64 `json:"deposit_id" binding:"required"` - IsVerified bool `json:"is_verified" binding:"required"` - Notes string `json:"notes"` + DepositID int64 `json:"deposit_id" binding:"required"` + IsVerified bool `json:"is_verified" binding:"required"` + Notes string `json:"notes"` } diff --git a/internal/pkgs/helpers/helpers.go b/internal/pkgs/helpers/helpers.go index d9be84a..cb4a4ec 100644 --- a/internal/pkgs/helpers/helpers.go +++ b/internal/pkgs/helpers/helpers.go @@ -1,10 +1,13 @@ package helpers import ( + random "crypto/rand" "fmt" - "math/rand/v2" + "strings" "github.com/google/uuid" + "math/big" + "math/rand/v2" ) func GenerateID() string { @@ -24,3 +27,34 @@ func GenerateFastCode() string { } return code } + +func GenerateCashoutID() (string, error) { + const chars = "abcdefghijklmnopqrstuvwxyz0123456789" + const length int = 13 + charLen := big.NewInt(int64(len(chars))) + result := make([]byte, length) + + for i := 0; i < length; i++ { + index, err := random.Int(random.Reader, charLen) + if err != nil { + return "", err + } + result[i] = chars[index.Int64()] + } + + return string(result), nil +} + +func MaskPhone(phone string) string { + if phone == "" { + return "" + } + return phone[:4] + "**" + phone[len(phone)-2:] +} + +func MaskEmail(email string) string { + if email == "" { + return "" + } + return email[:3] + "**" + email[strings.Index(email, "@"):] +} diff --git a/internal/repository/bet.go b/internal/repository/bet.go index b1d1e52..09a667b 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -10,6 +10,7 @@ import ( dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" "go.uber.org/zap" ) @@ -220,6 +221,46 @@ func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.Outcom return err } +func (s *Store) SettleWinningBet(ctx context.Context, betID int64, userID int64, amount domain.Currency, status domain.OutcomeStatus) error { + tx, err := s.conn.BeginTx(ctx, pgx.TxOptions{}) + if err != nil { + return err + } + qtx := s.queries.WithTx(tx) + + wallet, err := qtx.GetCustomerWallet(ctx, userID) + if err != nil { + tx.Rollback(ctx) + return err + } + + // 1. Update wallet + newAmount := wallet.RegularBalance + int64(amount) + if err := qtx.UpdateBalance(ctx, dbgen.UpdateBalanceParams{ + Balance: newAmount, + ID: wallet.RegularID, + }); err != nil { + tx.Rollback(ctx) + return err + } + + // 2. Update bet + if err := qtx.UpdateStatus(ctx, dbgen.UpdateStatusParams{ + Status: int32(status), + ID: betID, + }); err != nil { + tx.Rollback(ctx) + return err + } + + // 3. Commit both together + if err := tx.Commit(ctx); err != nil { + return err + } + + return nil +} + func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) { outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, dbgen.GetBetOutcomeByEventIDParams{ diff --git a/internal/repository/bonus.go b/internal/repository/bonus.go index c4f57ac..6b95b3e 100644 --- a/internal/repository/bonus.go +++ b/internal/repository/bonus.go @@ -4,27 +4,80 @@ import ( "context" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" ) -func (s *Store) CreateBonusMultiplier(ctx context.Context, multiplier float32, balance_cap int64) error { - return s.queries.CreateBonusMultiplier(ctx, dbgen.CreateBonusMultiplierParams{ - Multiplier: multiplier, - BalanceCap: balance_cap, +func (s *Store) CreateUserBonus(ctx context.Context, bonus domain.CreateBonus) (domain.UserBonus, error) { + newBonus, err := s.queries.CreateUserBonus(ctx, domain.ConvertCreateBonus(bonus)) + + if err != nil { + return domain.UserBonus{}, err + } + + return domain.ConvertDBBonus(newBonus), nil +} + +func (s *Store) GetAllUserBonuses(ctx context.Context, filter domain.BonusFilter) ([]domain.UserBonus, error) { + bonuses, err := s.queries.GetAllUserBonuses(ctx, dbgen.GetAllUserBonusesParams{ + UserID: filter.UserID.ToPG(), + Offset: filter.Offset.ToPG(), + Limit: filter.Limit.ToPG(), }) + + if err != nil { + return nil, err + } + + return domain.ConvertDBBonuses(bonuses), nil } -func (s *Store) GetBonusMultiplier(ctx context.Context) ([]dbgen.GetBonusMultiplierRow, error) { - return s.queries.GetBonusMultiplier(ctx) +func (s *Store) GetBonusCount(ctx context.Context, filter domain.BonusFilter) (int64, error) { + count, err := s.queries.GetBonusCount(ctx, filter.UserID.ToPG()) + if err != nil { + return 0, err + } + return count, nil } -func (s *Store) GetBonusBalanceCap(ctx context.Context) ([]dbgen.GetBonusBalanceCapRow, error) { - return s.queries.GetBonusBalanceCap(ctx) +func (s *Store) GetBonusByID(ctx context.Context, bonusID int64) (domain.UserBonus, error) { + bonus, err := s.queries.GetUserBonusByID(ctx, bonusID) + if err != nil { + return domain.UserBonus{}, err + } + return domain.ConvertDBBonus(bonus), nil } -func (s *Store) UpdateBonusMultiplier(ctx context.Context, id int64, mulitplier float32, balance_cap int64) error { - return s.queries.UpdateBonusMultiplier(ctx, dbgen.UpdateBonusMultiplierParams{ - ID: id, - Multiplier: mulitplier, - BalanceCap: balance_cap, + + + +func (s *Store) GetBonusStats(ctx context.Context, filter domain.BonusFilter) (domain.BonusStats, error) { + bonus, err := s.queries.GetBonusStats(ctx, dbgen.GetBonusStatsParams{ + CompanyID: filter.CompanyID.ToPG(), + UserID: filter.UserID.ToPG(), }) + if err != nil { + return domain.BonusStats{}, err + } + return domain.ConvertDBBonusStats(bonus), nil +} + +func (s *Store) UpdateUserBonus(ctx context.Context, bonusID int64, IsClaimed bool) (error) { + err := s.queries.UpdateUserBonus(ctx, dbgen.UpdateUserBonusParams{ + ID: bonusID, + IsClaimed: IsClaimed, + }) + + if err != nil { + return err + } + return nil +} + + +func (s *Store) DeleteUserBonus(ctx context.Context, bonusID int64) (error) { + err := s.queries.DeleteUserBonus(ctx, bonusID) + if err != nil { + return err + } + return nil } diff --git a/internal/repository/company.go b/internal/repository/company.go index dc440e9..08f5251 100644 --- a/internal/repository/company.go +++ b/internal/repository/company.go @@ -2,12 +2,12 @@ package repository import ( "context" - "database/sql" "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" ) @@ -15,11 +15,11 @@ func (s *Store) CreateCompany(ctx context.Context, company domain.CreateCompany) baseSlug := helpers.GenerateSlug(company.Name) uniqueSlug := baseSlug i := 1 - + for { _, err := s.queries.GetCompanyIDUsingSlug(ctx, uniqueSlug) if err != nil { - if errors.Is(err, sql.ErrNoRows) { + if errors.Is(err, pgx.ErrNoRows) { // slug is unique break } else { diff --git a/internal/repository/event.go b/internal/repository/event.go index d0798d7..8b4e87e 100644 --- a/internal/repository/event.go +++ b/internal/repository/event.go @@ -16,10 +16,6 @@ func (s *Store) SaveEvent(ctx context.Context, e domain.CreateEvent) error { return s.queries.InsertEvent(ctx, domain.ConvertCreateEvent(e)) } -func (s *Store) InsertEventSettings(ctx context.Context, eventSetting domain.CreateEventSettings) error { - return s.queries.InsertEventSettings(ctx, domain.ConvertCreateEventSettings(eventSetting)) -} - func (s *Store) GetLiveEventIDs(ctx context.Context) ([]string, error) { return s.queries.ListLiveEvents(ctx) } @@ -89,6 +85,8 @@ func (s *Store) GetEventsWithSettings(ctx context.Context, companyID int64, filt FirstStartTime: filter.FirstStartTime.ToPG(), LastStartTime: filter.LastStartTime.ToPG(), CountryCode: filter.CountryCode.ToPG(), + IsFeatured: filter.Featured.ToPG(), + IsActive: filter.Active.ToPG(), }) if err != nil { @@ -103,13 +101,70 @@ func (s *Store) GetEventsWithSettings(ctx context.Context, companyID int64, filt FirstStartTime: filter.FirstStartTime.ToPG(), LastStartTime: filter.LastStartTime.ToPG(), CountryCode: filter.CountryCode.ToPG(), + IsFeatured: filter.Featured.ToPG(), + IsActive: filter.Active.ToPG(), }) if err != nil { return nil, 0, err } numberOfPages := math.Ceil(float64(totalCount) / float64(filter.Limit.Value)) - return domain.ConvertDBEventWithSettings(events), int64(numberOfPages), nil + + result := make([]domain.EventWithSettings, len(events)) + + for i, event := range events { + result[i] = domain.EventWithSettings{ + ID: event.ID, + SportID: event.SportID, + MatchName: event.MatchName, + HomeTeam: event.HomeTeam, + AwayTeam: event.AwayTeam, + HomeTeamID: event.HomeTeamID, + AwayTeamID: event.AwayTeamID, + HomeTeamImage: event.HomeKitImage, + AwayTeamImage: event.AwayKitImage, + LeagueID: event.LeagueID, + LeagueName: event.LeagueName, + LeagueCC: domain.ValidString{ + Value: event.LeagueCc.String, + Valid: event.LeagueCc.Valid, + }, + StartTime: event.StartTime.Time.UTC(), + Source: domain.EventSource(event.Source), + Status: domain.EventStatus(event.Status), + IsFeatured: event.IsFeatured, + IsMonitored: event.IsMonitored, + IsActive: event.IsActive, + DefaultIsFeatured: event.DefaultIsFeatured, + DefaultIsActive: event.DefaultIsActive, + DefaultWinningUpperLimit: event.DefaultWinningUpperLimit, + Score: domain.ValidString{ + Value: event.Score.String, + Valid: event.Score.Valid, + }, + MatchMinute: domain.ValidInt{ + Value: int(event.MatchMinute.Int32), + Valid: event.MatchMinute.Valid, + }, + TimerStatus: domain.ValidString{ + Value: event.TimerStatus.String, + Valid: event.TimerStatus.Valid, + }, + AddedTime: domain.ValidInt{ + Value: int(event.AddedTime.Int32), + Valid: event.AddedTime.Valid, + }, + MatchPeriod: domain.ValidInt{ + Value: int(event.MatchPeriod.Int32), + Valid: event.MatchPeriod.Valid, + }, + IsLive: event.IsLive, + UpdatedAt: event.UpdatedAt.Time, + FetchedAt: event.FetchedAt.Time, + } + } + + return result, int64(numberOfPages), nil } func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.BaseEvent, error) { event, err := s.queries.GetUpcomingByID(ctx, ID) @@ -121,14 +176,63 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Bas } func (s *Store) GetEventWithSettingByID(ctx context.Context, ID string, companyID int64) (domain.EventWithSettings, error) { event, err := s.queries.GetEventWithSettingByID(ctx, dbgen.GetEventWithSettingByIDParams{ - ID: ID, + ID: ID, CompanyID: companyID, }) if err != nil { return domain.EventWithSettings{}, err } - return domain.ConvertDBEventWithSetting(event), nil + res := domain.EventWithSettings{ + ID: event.ID, + SportID: event.SportID, + MatchName: event.MatchName, + HomeTeam: event.HomeTeam, + AwayTeam: event.AwayTeam, + HomeTeamID: event.HomeTeamID, + AwayTeamID: event.AwayTeamID, + HomeTeamImage: event.HomeKitImage, + AwayTeamImage: event.AwayKitImage, + LeagueID: event.LeagueID, + LeagueName: event.LeagueName, + LeagueCC: domain.ValidString{ + Value: event.LeagueCc.String, + Valid: event.LeagueCc.Valid, + }, + StartTime: event.StartTime.Time.UTC(), + Source: domain.EventSource(event.Source), + Status: domain.EventStatus(event.Status), + IsFeatured: event.IsFeatured, + IsMonitored: event.IsMonitored, + IsActive: event.IsActive, + DefaultIsFeatured: event.DefaultIsFeatured, + DefaultIsActive: event.DefaultIsActive, + DefaultWinningUpperLimit: event.DefaultWinningUpperLimit, + Score: domain.ValidString{ + Value: event.Score.String, + Valid: event.Score.Valid, + }, + MatchMinute: domain.ValidInt{ + Value: int(event.MatchMinute.Int32), + Valid: event.MatchMinute.Valid, + }, + TimerStatus: domain.ValidString{ + Value: event.TimerStatus.String, + Valid: event.TimerStatus.Valid, + }, + AddedTime: domain.ValidInt{ + Value: int(event.AddedTime.Int32), + Valid: event.AddedTime.Valid, + }, + MatchPeriod: domain.ValidInt{ + Value: int(event.MatchPeriod.Int32), + Valid: event.MatchPeriod.Valid, + }, + IsLive: event.IsLive, + UpdatedAt: event.UpdatedAt.Time, + FetchedAt: event.FetchedAt.Time, + } + return res, nil } func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error { params := dbgen.UpdateMatchResultParams{ @@ -176,7 +280,7 @@ func (s *Store) UpdateEventMonitored(ctx context.Context, eventID string, IsMoni } func (s *Store) UpdateEventSettings(ctx context.Context, event domain.CreateEventSettings) error { - return s.queries.UpdateEventSettings(ctx, domain.ConvertUpdateEventSettings(event)) + return s.queries.SaveEventSettings(ctx, domain.ConvertUpdateEventSettings(event)) } func (s *Store) DeleteEvent(ctx context.Context, eventID string) error { @@ -186,3 +290,13 @@ func (s *Store) DeleteEvent(ctx context.Context, eventID string) error { } return nil } + +func (s *Store) GetSportAndLeagueIDs(ctx context.Context, eventID string) ([]int64, error) { + sportAndLeagueIDs, err := s.queries.GetSportAndLeagueIDs(ctx, eventID) + if err != nil { + return nil, err + } + + res := []int64{int64(sportAndLeagueIDs.SportID), sportAndLeagueIDs.LeagueID} + return res, err +} diff --git a/internal/repository/league.go b/internal/repository/league.go index f003fd9..ae0a4d5 100644 --- a/internal/repository/league.go +++ b/internal/repository/league.go @@ -18,6 +18,7 @@ func (s *Store) SaveLeagueSettings(ctx context.Context, leagueSettings domain.Cr func (s *Store) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.BaseLeague, error) { l, err := s.queries.GetAllLeagues(ctx, dbgen.GetAllLeaguesParams{ + Query: filter.Query.ToPG(), CountryCode: filter.CountryCode.ToPG(), SportID: filter.SportID.ToPG(), Limit: pgtype.Int4{ @@ -36,8 +37,10 @@ func (s *Store) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ( return domain.ConvertDBBaseLeagues(l), nil } -func (s *Store) GetAllLeaguesByCompany(ctx context.Context, companyID int64, filter domain.LeagueFilter) ([]domain.LeagueWithSettings, error) { +func (s *Store) GetAllLeaguesByCompany(ctx context.Context, companyID int64, filter domain.LeagueFilter) ([]domain.LeagueWithSettings, int64, error) { + l, err := s.queries.GetAllLeaguesWithSettings(ctx, dbgen.GetAllLeaguesWithSettingsParams{ + Query: filter.Query.ToPG(), CompanyID: companyID, CountryCode: filter.CountryCode.ToPG(), SportID: filter.SportID.ToPG(), @@ -49,13 +52,27 @@ func (s *Store) GetAllLeaguesByCompany(ctx context.Context, companyID int64, fil Int32: int32(filter.Offset.Value * filter.Limit.Value), Valid: filter.Offset.Valid, }, + IsFeatured: filter.IsFeatured.ToPG(), + IsActive: filter.IsActive.ToPG(), }) if err != nil { - return nil, err + return nil, 0, err } - return domain.ConvertDBLeagueWithSettings(l), nil + total, err := s.queries.GetTotalLeaguesWithSettings(ctx, dbgen.GetTotalLeaguesWithSettingsParams{ + Query: filter.Query.ToPG(), + CompanyID: companyID, + CountryCode: filter.CountryCode.ToPG(), + SportID: filter.SportID.ToPG(), + IsFeatured: filter.IsFeatured.ToPG(), + IsActive: filter.IsActive.ToPG(), + }) + + if err != nil { + return nil, 0, err + } + return domain.ConvertDBLeagueWithSettings(l), total, nil } func (s *Store) CheckLeagueSupport(ctx context.Context, leagueID int64, companyID int64) (bool, error) { diff --git a/internal/repository/notification.go b/internal/repository/notification.go index 1034bfc..29e7b8c 100644 --- a/internal/repository/notification.go +++ b/internal/repository/notification.go @@ -39,8 +39,8 @@ func (s *Store) DisconnectWebSocket(recipientID int64) { func (r *Repository) CreateNotification(ctx context.Context, notification *domain.Notification) (*domain.Notification, error) { var errorSeverity pgtype.Text - if notification.ErrorSeverity != nil { - errorSeverity.String = string(*notification.ErrorSeverity) + if notification.ErrorSeverity != "" { + errorSeverity.String = string(notification.ErrorSeverity) errorSeverity.Valid = true } @@ -155,10 +155,12 @@ func (r *Repository) ListRecipientIDs(ctx context.Context, receiver domain.Notif } func (r *Repository) mapDBToDomain(dbNotif *dbgen.Notification) *domain.Notification { - var errorSeverity *domain.NotificationErrorSeverity + var errorSeverity domain.NotificationErrorSeverity if dbNotif.ErrorSeverity.Valid { - s := domain.NotificationErrorSeverity(dbNotif.ErrorSeverity.String) - errorSeverity = &s + errorSeverity = domain.NotificationErrorSeverity(dbNotif.ErrorSeverity.String) + + } else { + errorSeverity = "" } var deliveryChannel domain.DeliveryChannel @@ -317,8 +319,6 @@ func (s *Store) CountUnreadNotifications(ctx context.Context, userID int64) (int return count, nil } - - // func (s *Store) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) { // dbNotifications, err := s.queries.GetAllNotifications(ctx, dbgen.GetAllNotificationsParams{ // Limit: int32(limit), diff --git a/internal/repository/odds.go b/internal/repository/odds.go index cb684ce..3e09a91 100644 --- a/internal/repository/odds.go +++ b/internal/repository/odds.go @@ -88,13 +88,51 @@ func (s *Store) GetAllOddsWithSettings(ctx context.Context, companyID int64, fil return nil, err } - domainOdds, err := domain.ConvertDBOddMarketWithSettings(odds) + // domainOdds, err := domain.ConvertDBOddMarketWithSettings(odds) + // if err != nil { + // return nil, err + // } - if err != nil { - return nil, err + result := make([]domain.OddMarketWithSettings, len(odds)) + for i, o := range odds { + var rawOdds []json.RawMessage + if len(o.RawOdds) > 0 { + if err := json.Unmarshal(o.RawOdds, &rawOdds); err != nil { + return nil, err + } + } else { + rawOdds = []json.RawMessage{} // explicit empty slice + } + + 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, + } } - return domainOdds, nil + return result, nil +} + +func (s *Store) GetOddByID(ctx context.Context, id int64) (domain.OddMarket, error) { + odd, err := s.queries.GetOddByID(ctx, id) + if err != nil { + return domain.OddMarket{}, err + } + + convertedOdd, err := domain.ConvertDBOddMarket(odd) + + if err != nil { + return domain.OddMarket{}, err + } + return convertedOdd, nil } func (s *Store) GetOddsByMarketID(ctx context.Context, marketID string, eventID string) (domain.OddMarket, error) { @@ -126,12 +164,76 @@ func (s *Store) GetOddsWithSettingsByMarketID(ctx context.Context, marketID stri return domain.OddMarketWithSettings{}, err } - convertedOdd, err := domain.ConvertDBOddMarketWithSetting(odds) + // convertedOdd, err := domain.ConvertDBOddMarketWithSetting(odds) + + // if err != nil { + // return domain.OddMarketWithSettings{}, err + // } + + var rawOdds []json.RawMessage + if len(odds.RawOdds) > 0 { + if err := json.Unmarshal(odds.RawOdds, &rawOdds); err != nil { + return domain.OddMarketWithSettings{}, err + } + } else { + rawOdds = []json.RawMessage{} // explicit empty slice + } + + 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, + } + return converted, nil +} + +func (s *Store) GetOddsWithSettingsByID(ctx context.Context, ID int64, companyID int64) (domain.OddMarketWithSettings, error) { + + odds, err := s.queries.GetOddsWithSettingsByID(ctx, dbgen.GetOddsWithSettingsByIDParams{ + ID: ID, + CompanyID: companyID, + }) if err != nil { return domain.OddMarketWithSettings{}, err } - return convertedOdd, nil + + // convertedOdd, err := domain.ConvertDBOddMarketWithSetting(odds) + + // if err != nil { + // return domain.OddMarketWithSettings{}, err + // } + + var rawOdds []json.RawMessage + if len(odds.RawOdds) > 0 { + if err := json.Unmarshal(odds.RawOdds, &rawOdds); err != nil { + return domain.OddMarketWithSettings{}, err + } + } else { + rawOdds = []json.RawMessage{} // explicit empty slice + } + + 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, + } + + return converted, nil } func (s *Store) GetOddsByEventID(ctx context.Context, upcomingID string, filter domain.OddMarketWithEventFilter) ([]domain.OddMarket, error) { @@ -174,14 +276,49 @@ func (s *Store) GetOddsWithSettingsByEventID(ctx context.Context, upcomingID str } // Map the results to domain.Odd - domainOdds, err := domain.ConvertDBOddMarketWithSettings(odds) - if err != nil { - return nil, err + // domainOdds, err := domain.ConvertDBOddMarketWithSettings(odds) + // if err != nil { + // return nil, err + // } + + result := make([]domain.OddMarketWithSettings, len(odds)) + for i, o := range odds { + var rawOdds []json.RawMessage + if len(o.RawOdds) > 0 { + if err := json.Unmarshal(o.RawOdds, &rawOdds); err != nil { + return nil, err + } + } else { + rawOdds = []json.RawMessage{} // explicit empty slice + } + + 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, + } } - return domainOdds, nil + return result, nil } func (s *Store) DeleteOddsForEvent(ctx context.Context, eventID string) error { return s.queries.DeleteOddsForEvent(ctx, eventID) } + +func (s *Store) SaveOddsSetting(ctx context.Context, odd domain.CreateOddMarketSettings) error { + + res, err := domain.ConvertCreateOddMarketSetting(odd) + + if err != nil { + return nil + } + return s.queries.SaveOddSettings(ctx, res) +} diff --git a/internal/repository/raffel.go b/internal/repository/raffel.go new file mode 100644 index 0000000..6e37013 --- /dev/null +++ b/internal/repository/raffel.go @@ -0,0 +1,193 @@ +package repository + +import ( + "context" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/jackc/pgx/v5/pgtype" +) + +func 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, + } +} + +func convertRaffleTicketOutcome(raffle dbgen.RaffleTicket) domain.RaffleTicket { + return domain.RaffleTicket{ + ID: raffle.ID, + RaffleID: raffle.RaffleID, + UserID: raffle.UserID, + IsActive: raffle.IsActive.Bool, + } +} + +func convertJoinedRaffleTicketOutcome(raffle dbgen.GetUserRaffleTicketsRow) domain.RaffleTicketRes { + return domain.RaffleTicketRes{ + TicketID: raffle.TicketID, + UserID: raffle.UserID, + Name: raffle.Name, + Type: raffle.Type, + ExpiresAt: raffle.ExpiresAt.Time, + Status: raffle.Status, + } +} + +func convertCreateRaffle(raffle domain.CreateRaffle) dbgen.CreateRaffleParams { + return dbgen.CreateRaffleParams{ + CompanyID: raffle.CompanyID, + Name: raffle.Name, + ExpiresAt: pgtype.Timestamp{ + Time: *raffle.ExpiresAt, + Valid: true, + }, + Type: raffle.Type, + } +} + +func convertRaffleStanding(raffleStanding dbgen.GetRaffleStandingRow) domain.RaffleStanding { + return domain.RaffleStanding{ + UserID: raffleStanding.UserID, + RaffleID: raffleStanding.RaffleID, + FirstName: raffleStanding.FirstName, + LastName: raffleStanding.LastName, + PhoneNumber: raffleStanding.PhoneNumber.String, + Email: raffleStanding.Email.String, + TicketCount: raffleStanding.TicketCount, + } +} + +func (s *Store) CreateRaffle(ctx context.Context, raffle domain.CreateRaffle) (domain.Raffle, error) { + raffleRes, err := s.queries.CreateRaffle(ctx, convertCreateRaffle(raffle)) + if err != nil { + return domain.Raffle{}, err + } + + return convertRaffleOutcome(raffleRes), nil +} + +func (s *Store) DeleteRaffle(ctx context.Context, raffleID int32) (domain.Raffle, error) { + raffleRes, err := s.queries.DeleteRaffle(ctx, raffleID) + if err != nil { + return domain.Raffle{}, err + } + + return convertRaffleOutcome(raffleRes), nil +} + +func (s *Store) GetRafflesOfCompany(ctx context.Context, companyID int32) ([]dbgen.Raffle, error) { + raffles, err := s.queries.GetRafflesOfCompany(ctx, companyID) + if err != nil { + return nil, err + } + + return raffles, nil +} + +func (s *Store) CreateRaffleTicket(ctx context.Context, raffleTicketParams domain.CreateRaffleTicket) (domain.RaffleTicket, error) { + raffleTicket, err := s.queries.CreateRaffleTicket(ctx, dbgen.CreateRaffleTicketParams{ + RaffleID: raffleTicketParams.RaffleID, + UserID: raffleTicketParams.UserID, + }) + if err != nil { + return domain.RaffleTicket{}, err + } + + return convertRaffleTicketOutcome(raffleTicket), nil +} + +func (s *Store) GetUserRaffleTickets(ctx context.Context, userID int32) ([]domain.RaffleTicketRes, error) { + raffleTickets, err := s.queries.GetUserRaffleTickets(ctx, userID) + if err != nil { + return nil, err + } + + res := []domain.RaffleTicketRes{} + for _, raffle := range raffleTickets { + res = append(res, convertJoinedRaffleTicketOutcome(raffle)) + } + + return res, nil +} + +func (s *Store) SuspendRaffleTicket(ctx context.Context, raffleTicketID int32) error { + return s.queries.UpdateRaffleTicketStatus(ctx, dbgen.UpdateRaffleTicketStatusParams{ + ID: raffleTicketID, + IsActive: pgtype.Bool{ + Bool: false, + Valid: true, + }, + }) +} + +func (s *Store) UnSuspendRaffleTicket(ctx context.Context, raffleID int32) error { + return s.queries.UpdateRaffleTicketStatus(ctx, dbgen.UpdateRaffleTicketStatusParams{ + ID: raffleID, + IsActive: pgtype.Bool{ + Bool: true, + Valid: true, + }, + }) +} + +// TODO: could also add -> suspend a specific user's raffle tickets + +func (s *Store) GetRaffleStanding(ctx context.Context, raffleID, limit int32) ([]domain.RaffleStanding, error) { + raffleStanding, err := s.queries.GetRaffleStanding(ctx, dbgen.GetRaffleStandingParams{ + RaffleID: raffleID, + Limit: limit, + }) + if err != nil { + return nil, err + } + + res := []domain.RaffleStanding{} + for _, standing := range raffleStanding { + res = append(res, convertRaffleStanding(standing)) + } + + return res, nil +} + +func (s *Store) CreateRaffleWinner(ctx context.Context, raffleWinnerParams domain.RaffleWinnerParams) error { + _, err := s.queries.CreateRaffleWinner(ctx, dbgen.CreateRaffleWinnerParams{ + RaffleID: raffleWinnerParams.RaffleID, + UserID: raffleWinnerParams.UserID, + Rank: raffleWinnerParams.Rank, + }) + + return err +} + +func (s *Store) SetRaffleComplete(ctx context.Context, raffleID int32) error { + return s.queries.SetRaffleComplete(ctx, raffleID) +} + +func (s *Store) AddSportRaffleFilter(ctx context.Context, raffleID int32, sportID, leagueID int64) error { + _, err := s.queries.AddSportRaffleFilter(ctx, dbgen.AddSportRaffleFilterParams{ + RaffleID: raffleID, + SportID: sportID, + LeagueID: leagueID, + }) + return err +} + +func (s *Store) CheckValidSportRaffleFilter(ctx context.Context, raffleID int32, sportID, leagueID int64) (bool, error) { + res, err := s.queries.CheckValidSportRaffleFilter(ctx, dbgen.CheckValidSportRaffleFilterParams{ + RaffleID: raffleID, + SportID: sportID, + LeagueID: leagueID, + }) + if err != nil { + return false, err + } + + return res, nil +} diff --git a/internal/repository/referal.go b/internal/repository/referal.go index d214c54..cda22e2 100644 --- a/internal/repository/referal.go +++ b/internal/repository/referal.go @@ -2,28 +2,21 @@ package repository import ( "context" - "database/sql" - "errors" - "fmt" - "strconv" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" - "github.com/jackc/pgx/v5/pgtype" ) type ReferralRepository interface { - CreateReferral(ctx context.Context, referral *domain.Referral) error - GetReferralByCode(ctx context.Context, code string) (*domain.Referral, error) - UpdateReferral(ctx context.Context, referral *domain.Referral) error - GetReferralStats(ctx context.Context, userID string) (*domain.ReferralStats, error) - GetSettings(ctx context.Context) (*domain.ReferralSettings, error) - UpdateSettings(ctx context.Context, settings *domain.ReferralSettings) error - CreateSettings(ctx context.Context, settings *domain.ReferralSettings) error - GetReferralByReferredID(ctx context.Context, referredID string) (*domain.Referral, error) // New method - GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) - GetActiveReferralByReferrerID(ctx context.Context, referrerID string) (*domain.Referral, error) - UpdateUserReferalCode(ctx context.Context, codedata domain.UpdateUserReferalCode) error + CreateReferralCode(ctx context.Context, referralCode domain.CreateReferralCode) (domain.ReferralCode, error) + CreateUserReferral(ctx context.Context, referral domain.CreateUserReferrals) (domain.UserReferral, error) + GetReferralCodesByUser(ctx context.Context, userID int64) ([]domain.ReferralCode, error) + GetReferralCode(ctx context.Context, code string) (domain.ReferralCode, error) + UpdateReferralCode(ctx context.Context, referral domain.UpdateReferralCode) error + GetReferralStats(ctx context.Context, userID int64, companyID int64) (domain.ReferralStats, error) + GetUserReferral(ctx context.Context, referredID int64) (domain.UserReferral, error) + GetUserReferralsByCode(ctx context.Context, code string) ([]domain.UserReferral, error) + GetUserReferralCount(ctx context.Context, referrerID int64) (int64, error) } type ReferralRepo struct { @@ -34,248 +27,159 @@ func NewReferralRepository(store *Store) ReferralRepository { return &ReferralRepo{store: store} } -func (r *ReferralRepo) UpdateUserReferalCode(ctx context.Context, codedata domain.UpdateUserReferalCode) error { - params := dbgen.UpdateReferralCodeParams{ - ID: codedata.UserID, - ReferralCode: pgtype.Text{ - String: codedata.Code, - Valid: true, - }, - } +func (r *ReferralRepo) CreateReferralCode(ctx context.Context, referralCode domain.CreateReferralCode) (domain.ReferralCode, error) { + newReferralCode, err := r.store.queries.CreateReferralCode(ctx, domain.ConvertCreateReferralCode(referralCode)) - return r.store.queries.UpdateReferralCode(ctx, params) -} - -func (r *ReferralRepo) CreateReferral(ctx context.Context, referral *domain.Referral) error { - rewardAmount := pgtype.Numeric{} - if err := rewardAmount.Scan(strconv.Itoa(int(referral.RewardAmount))); err != nil { - return err - } - - params := dbgen.CreateReferralParams{ - ReferralCode: referral.ReferralCode, - ReferrerID: referral.ReferrerID, - Status: dbgen.Referralstatus(referral.Status), - RewardAmount: rewardAmount, - ExpiresAt: pgtype.Timestamptz{Time: referral.ExpiresAt, Valid: true}, - } - - _, err := r.store.queries.CreateReferral(ctx, params) - return err -} - -func (r *ReferralRepo) GetReferralByCode(ctx context.Context, code string) (*domain.Referral, error) { - dbReferral, err := r.store.queries.GetReferralByCode(ctx, code) if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return nil, nil - } - return nil, err + return domain.ReferralCode{}, err } - return r.mapToDomainReferral(&dbReferral), nil + return domain.ConvertDBReferralCode(newReferralCode), nil } -func (r *ReferralRepo) UpdateReferral(ctx context.Context, referral *domain.Referral) error { - var referredID pgtype.Text - if referral.ReferredID != nil { - referredID = pgtype.Text{String: *referral.ReferredID, Valid: true} +func (r *ReferralRepo) CreateUserReferral(ctx context.Context, referral domain.CreateUserReferrals) (domain.UserReferral, error) { + newReferral, err := r.store.queries.CreateUserReferral(ctx, domain.ConvertCreateUserReferral(referral)) + + if err != nil { + return domain.UserReferral{}, err } - params := dbgen.UpdateReferralParams{ - ID: referral.ID, - ReferredID: referredID, - Status: dbgen.Referralstatus(referral.Status), - } - - _, err := r.store.queries.UpdateReferral(ctx, params) - return err + return domain.ConvertDBUserReferral(newReferral), nil } -func (r *ReferralRepo) GetReferralStats(ctx context.Context, userID string) (*domain.ReferralStats, error) { - stats, err := r.store.queries.GetReferralStats(ctx, userID) +func (r *ReferralRepo) GetReferralCodesByUser(ctx context.Context, userID int64) ([]domain.ReferralCode, error) { + codes, err := r.store.queries.GetReferralCodeByUser(ctx, userID) + if err != nil { return nil, err } - return &domain.ReferralStats{ - TotalReferrals: int(stats.TotalReferrals), - CompletedReferrals: int(stats.CompletedReferrals), - TotalRewardEarned: stats.TotalRewardEarned.(float64), - PendingRewards: stats.PendingRewards.(float64), - }, nil + return domain.ConvertDBReferralCodes(codes), nil } -func (r *ReferralRepo) GetSettings(ctx context.Context) (*domain.ReferralSettings, error) { - settings, err := r.store.queries.GetReferralSettings(ctx) +func (r *ReferralRepo) GetReferralCode(ctx context.Context, code string) (domain.ReferralCode, error) { + referralCode, err := r.store.queries.GetReferralCode(ctx, code) + + if err != nil { + return domain.ReferralCode{}, err + } + + return domain.ConvertDBReferralCode(referralCode), nil + +} +func (r *ReferralRepo) UpdateReferralCode(ctx context.Context, referral domain.UpdateReferralCode) error { + err := r.store.queries.UpdateReferralCode(ctx, domain.ConvertUpdateReferralCode(referral)) + + if err != nil { + return err + } + + return nil +} + +func (r *ReferralRepo) GetReferralStats(ctx context.Context, userID int64, companyID int64) (domain.ReferralStats, error) { + stats, err := r.store.queries.GetReferralStats(ctx, dbgen.GetReferralStatsParams{ + ReferrerID: userID, + CompanyID: companyID, + }) + if err != nil { + return domain.ReferralStats{}, err + } + + return domain.ConvertDBReferralStats(stats), nil +} + +func (r *ReferralRepo) GetUserReferral(ctx context.Context, referredID int64) (domain.UserReferral, error) { + dbReferral, err := r.store.queries.GetUserReferral(ctx, referredID) + if err != nil { + return domain.UserReferral{}, err + } + return domain.ConvertDBUserReferral(dbReferral), nil +} + +func (r *ReferralRepo) GetUserReferralsByCode(ctx context.Context, code string) ([]domain.UserReferral, error) { + dbReferrals, err := r.store.queries.GetUserReferralsByCode(ctx, code) + if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return nil, nil - } return nil, err } - return r.mapToDomainSettings(&settings), nil + + return domain.ConvertDBUserReferrals(dbReferrals), nil } -func (r *ReferralRepo) UpdateSettings(ctx context.Context, settings *domain.ReferralSettings) error { - rewardAmount := pgtype.Numeric{} - if err := rewardAmount.Scan(settings.ReferralRewardAmount); err != nil { - return err - } - - cashbackPercentage := pgtype.Numeric{} - if err := cashbackPercentage.Scan(settings.CashbackPercentage); err != nil { - return err - } - - betReferralBonusPercentage := pgtype.Numeric{} - if err := betReferralBonusPercentage.Scan(settings.BetReferralBonusPercentage); err != nil { - return err - } - - params := dbgen.UpdateReferralSettingsParams{ - ID: settings.ID, - ReferralRewardAmount: rewardAmount, - CashbackPercentage: cashbackPercentage, - BetReferralBonusPercentage: betReferralBonusPercentage, // New field - MaxReferrals: settings.MaxReferrals, - ExpiresAfterDays: settings.ExpiresAfterDays, - UpdatedBy: settings.UpdatedBy, - } - - _, err := r.store.queries.UpdateReferralSettings(ctx, params) - return err -} - -func (r *ReferralRepo) CreateSettings(ctx context.Context, settings *domain.ReferralSettings) error { - rewardAmount := pgtype.Numeric{} - if err := rewardAmount.Scan(fmt.Sprintf("%f", settings.ReferralRewardAmount)); err != nil { - return err - } - - cashbackPercentage := pgtype.Numeric{} - if err := cashbackPercentage.Scan(fmt.Sprintf("%f", settings.CashbackPercentage)); err != nil { - return err - } - - betReferralBonusPercentage := pgtype.Numeric{} - if err := betReferralBonusPercentage.Scan(fmt.Sprintf("%f", settings.BetReferralBonusPercentage)); err != nil { - return err - } - - params := dbgen.CreateReferralSettingsParams{ - ReferralRewardAmount: rewardAmount, - CashbackPercentage: cashbackPercentage, - BetReferralBonusPercentage: betReferralBonusPercentage, // New field - MaxReferrals: settings.MaxReferrals, - ExpiresAfterDays: settings.ExpiresAfterDays, - UpdatedBy: settings.UpdatedBy, - } - - _, err := r.store.queries.CreateReferralSettings(ctx, params) - return err -} - -func (r *ReferralRepo) GetReferralByReferredID(ctx context.Context, referredID string) (*domain.Referral, error) { - dbReferral, err := r.store.queries.GetReferralByReferredID(ctx, pgtype.Text{String: referredID, Valid: true}) +func (r *ReferralRepo) GetUserReferralCount(ctx context.Context, referrerID int64) (int64, error) { + count, err := r.store.queries.GetUserReferralsCount(ctx, referrerID) if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return nil, nil - } - return nil, err - } - return r.mapToDomainReferral(&dbReferral), nil -} - -func (r *ReferralRepo) GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) { - count, err := r.store.queries.GetReferralCountByID(ctx, referrerID) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return 0, nil - } return 0, err } - return count, nil } -func (r *ReferralRepo) GetActiveReferralByReferrerID(ctx context.Context, referrerID string) (*domain.Referral, error) { - referral, err := r.store.queries.GetActiveReferralByReferrerID(ctx, referrerID) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return &domain.Referral{}, nil - } - return &domain.Referral{}, err - } +// func (r *ReferralRepo) mapToDomainReferral(dbRef *dbgen.Referral) *domain.Referral { +// var referredID *int64 +// if dbRef.ReferredID.Valid { +// referredID = &dbRef.ReferredID.Int64 +// } - return r.mapToDomainReferral(&referral), nil -} +// rewardAmount := 0.0 +// if dbRef.RewardAmount.Valid { +// if f8, err := dbRef.RewardAmount.Float64Value(); err == nil { +// rewardAmount = f8.Float64 +// } +// } -func (r *ReferralRepo) mapToDomainReferral(dbRef *dbgen.Referral) *domain.Referral { - var referredID *string - if dbRef.ReferredID.Valid { - referredID = &dbRef.ReferredID.String - } +// cashbackAmount := 0.0 +// if dbRef.CashbackAmount.Valid { +// if f8, err := dbRef.CashbackAmount.Float64Value(); err == nil { +// cashbackAmount = f8.Float64 +// } +// } - rewardAmount := 0.0 - if dbRef.RewardAmount.Valid { - if f8, err := dbRef.RewardAmount.Float64Value(); err == nil { - rewardAmount = f8.Float64 - } - } +// return &domain.Referral{ +// ID: dbRef.ID, +// ReferralCode: dbRef.ReferralCode, +// ReferrerID: dbRef.ReferrerID, +// ReferredID: referredID, +// Status: domain.ReferralStatus(dbRef.Status), +// RewardAmount: rewardAmount, +// CashbackAmount: cashbackAmount, +// CreatedAt: dbRef.CreatedAt.Time, +// UpdatedAt: dbRef.UpdatedAt.Time, +// ExpiresAt: dbRef.ExpiresAt.Time, +// } +// } - cashbackAmount := 0.0 - if dbRef.CashbackAmount.Valid { - if f8, err := dbRef.CashbackAmount.Float64Value(); err == nil { - cashbackAmount = f8.Float64 - } - } +// func (r *ReferralRepo) mapToDomainSettings(dbSettings *dbgen.ReferralSetting) *domain.ReferralSettings { +// rewardAmount := 0.0 +// if dbSettings.ReferralRewardAmount.Valid { +// if f8, err := dbSettings.ReferralRewardAmount.Float64Value(); err == nil { +// rewardAmount = f8.Float64 +// } +// } - return &domain.Referral{ - ID: dbRef.ID, - ReferralCode: dbRef.ReferralCode, - ReferrerID: dbRef.ReferrerID, - ReferredID: referredID, - Status: domain.ReferralStatus(dbRef.Status), - RewardAmount: rewardAmount, - CashbackAmount: cashbackAmount, - CreatedAt: dbRef.CreatedAt.Time, - UpdatedAt: dbRef.UpdatedAt.Time, - ExpiresAt: dbRef.ExpiresAt.Time, - } -} +// cashbackPercentage := 0.0 +// if dbSettings.CashbackPercentage.Valid { +// if f8, err := dbSettings.CashbackPercentage.Float64Value(); err == nil { +// cashbackPercentage = f8.Float64 +// } +// } -func (r *ReferralRepo) mapToDomainSettings(dbSettings *dbgen.ReferralSetting) *domain.ReferralSettings { - rewardAmount := 0.0 - if dbSettings.ReferralRewardAmount.Valid { - if f8, err := dbSettings.ReferralRewardAmount.Float64Value(); err == nil { - rewardAmount = f8.Float64 - } - } +// betReferralBonusPercentage := 0.0 +// if dbSettings.BetReferralBonusPercentage.Valid { +// if f8, err := dbSettings.BetReferralBonusPercentage.Float64Value(); err == nil { +// betReferralBonusPercentage = f8.Float64 +// } +// } - cashbackPercentage := 0.0 - if dbSettings.CashbackPercentage.Valid { - if f8, err := dbSettings.CashbackPercentage.Float64Value(); err == nil { - cashbackPercentage = f8.Float64 - } - } - - betReferralBonusPercentage := 0.0 - if dbSettings.BetReferralBonusPercentage.Valid { - if f8, err := dbSettings.BetReferralBonusPercentage.Float64Value(); err == nil { - betReferralBonusPercentage = f8.Float64 - } - } - - return &domain.ReferralSettings{ - ID: dbSettings.ID, - ReferralRewardAmount: rewardAmount, - CashbackPercentage: cashbackPercentage, - BetReferralBonusPercentage: betReferralBonusPercentage, // New field - MaxReferrals: dbSettings.MaxReferrals, - ExpiresAfterDays: dbSettings.ExpiresAfterDays, - UpdatedBy: dbSettings.UpdatedBy, - CreatedAt: dbSettings.CreatedAt.Time, - UpdatedAt: dbSettings.UpdatedAt.Time, - Version: dbSettings.Version, - } -} +// return &domain.ReferralSettings{ +// ID: dbSettings.ID, +// ReferralRewardAmount: rewardAmount, +// CashbackPercentage: cashbackPercentage, +// BetReferralBonusPercentage: betReferralBonusPercentage, // New field +// MaxReferrals: dbSettings.MaxReferrals, +// ExpiresAfterDays: dbSettings.ExpiresAfterDays, +// UpdatedBy: dbSettings.UpdatedBy, +// CreatedAt: dbSettings.CreatedAt.Time, +// UpdatedAt: dbSettings.UpdatedAt.Time, +// Version: dbSettings.Version, +// } +// } diff --git a/internal/repository/settings.go b/internal/repository/settings.go index 333f280..f456bff 100644 --- a/internal/repository/settings.go +++ b/internal/repository/settings.go @@ -165,9 +165,9 @@ func (s *Store) GetOverrideSettingsList(ctx context.Context, companyID int64) (d func (s *Store) DeleteCompanySetting(ctx context.Context, companyID int64, key string) error { return s.queries.DeleteCompanySetting(ctx, dbgen.DeleteCompanySettingParams{ CompanyID: companyID, - Key: key, + Key: key, }) } -func (s *Store) DeleteAllCompanySetting(ctx context.Context, companyID int64,) error { +func (s *Store) DeleteAllCompanySetting(ctx context.Context, companyID int64) error { return s.queries.DeleteAllCompanySetting(ctx, companyID) } diff --git a/internal/repository/shop_bet.go b/internal/repository/shop_bet.go index 6896640..66e3e63 100644 --- a/internal/repository/shop_bet.go +++ b/internal/repository/shop_bet.go @@ -2,6 +2,7 @@ package repository import ( "context" + "fmt" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" @@ -62,7 +63,7 @@ func (s *Store) CreateShopBet(ctx context.Context, bet domain.CreateShopBet) (do if err != nil { return domain.ShopBet{}, err } - + return convertDBShopBet(newShopBet), err } @@ -104,8 +105,10 @@ func (s *Store) GetAllShopBet(ctx context.Context, filter domain.ShopBetFilter) func (s *Store) GetShopBetByID(ctx context.Context, id int64) (domain.ShopBetDetail, error) { bet, err := s.queries.GetShopBetByID(ctx, id) if err != nil { + 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/ticket.go b/internal/repository/ticket.go index ac140bf..1e2ef36 100644 --- a/internal/repository/ticket.go +++ b/internal/repository/ticket.go @@ -41,6 +41,7 @@ func convertDBTicketOutcomes(ticket dbgen.TicketWithOutcome) domain.GetTicket { } return domain.GetTicket{ ID: ticket.ID, + CompanyID: ticket.CompanyID, Amount: domain.Currency(ticket.Amount), TotalOdds: ticket.TotalOdds, Outcomes: outcomes, diff --git a/internal/repository/transfer.go b/internal/repository/transfer.go index cad330e..3b5e5a9 100644 --- a/internal/repository/transfer.go +++ b/internal/repository/transfer.go @@ -148,6 +148,24 @@ func (s *Store) GetTransferByID(ctx context.Context, id int64) (domain.TransferD return convertDBTransferDetail(transfer), nil } +func (s *Store) GetTransferStats(ctx context.Context, walletID int64) (domain.TransferStats, error) { + stats, err := s.queries.GetTransferStats(ctx, pgtype.Int8{ + Int64: walletID, + Valid: true, + }) + + if err != nil { + return domain.TransferStats{}, err + } + + return domain.TransferStats{ + TotalTransfer: stats.TotalTransfers, + TotalDeposits: stats.TotalDeposits, + TotalWithdraws: stats.TotalWithdraw, + TotalWalletToWallet: stats.TotalWalletToWallet, + }, nil +} + func (s *Store) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { err := s.queries.UpdateTransferVerification(ctx, dbgen.UpdateTransferVerificationParams{ ID: id, diff --git a/internal/repository/user.go b/internal/repository/user.go index 4198b7d..50c6593 100644 --- a/internal/repository/user.go +++ b/internal/repository/user.go @@ -9,6 +9,7 @@ import ( dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" ) @@ -73,7 +74,7 @@ func (s *Store) CreateUser(ctx context.Context, user domain.User, usedOtpId int6 func (s *Store) GetUserByID(ctx context.Context, id int64) (domain.User, error) { user, err := s.queries.GetUserByID(ctx, id) if err != nil { - if errors.Is(err, sql.ErrNoRows) { + if errors.Is(err, pgx.ErrNoRows) { return domain.User{}, domain.ErrUserNotFound } return domain.User{}, err @@ -428,7 +429,7 @@ func (s *Store) GetUserByPhone(ctx context.Context, phoneNum string, companyID d }, nil } -func (s *Store) UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64) error { +func (s *Store) UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64, companyId int64) error { err := s.queries.MarkOtpAsUsed(ctx, dbgen.MarkOtpAsUsedParams{ ID: usedOtpId, UsedAt: pgtype.Timestamptz{ @@ -449,6 +450,10 @@ func (s *Store) UpdatePassword(ctx context.Context, identifier string, password String: identifier, Valid: true, }, + CompanyID: pgtype.Int8{ + Int64: companyId, + Valid: true, + }, }) if err != nil { return err diff --git a/internal/services/bet/notification.go b/internal/services/bet/notification.go new file mode 100644 index 0000000..2d4de4e --- /dev/null +++ b/internal/services/bet/notification.go @@ -0,0 +1,247 @@ +package bet + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "go.uber.org/zap" +) + +func newBetResultNotification(userID int64, level domain.NotificationLevel, channel domain.DeliveryChannel, headline, message string, metadata any) *domain.Notification { + raw, _ := json.Marshal(metadata) + return &domain.Notification{ + RecipientID: userID, + DeliveryStatus: domain.DeliveryStatusPending, + IsRead: false, + Type: domain.NOTIFICATION_TYPE_BET_RESULT, + Level: level, + Reciever: domain.NotificationRecieverSideCustomer, + DeliveryChannel: channel, + Payload: domain.NotificationPayload{ + Headline: headline, + Message: message, + }, + Priority: 2, + Metadata: raw, + } +} + +type SendResultNotificationParam struct { + BetID int64 + Status domain.OutcomeStatus + UserID int64 + WinningAmount domain.Currency + Extra string + SendEmail bool + SendSMS bool +} + +func (p SendResultNotificationParam) Validate() error { + if p.BetID == 0 { + return errors.New("BetID is required") + } + if p.UserID == 0 { + return errors.New("UserID is required") + } + return nil +} + +func shouldSend(channel domain.DeliveryChannel, sendEmail, sendSMS bool) bool { + switch { + case channel == domain.DeliveryChannelEmail && sendEmail: + return true + case channel == domain.DeliveryChannelSMS && sendSMS: + return true + case channel == domain.DeliveryChannelInApp: + return true + default: + return false + } +} + +func (s *Service) SendWinningStatusNotification(ctx context.Context, param SendResultNotificationParam) error { + if err := param.Validate(); err != nil { + return err + } + + var headline string + var message string + + switch param.Status { + case domain.OUTCOME_STATUS_WIN: + headline = fmt.Sprintf("Bet #%v Won!", param.BetID) + message = fmt.Sprintf( + "Congratulations! Your bet #%v has won. %.2f has been credited to your wallet.", + param.BetID, + param.WinningAmount.Float32(), + ) + case domain.OUTCOME_STATUS_HALF: + headline = fmt.Sprintf("Bet #%v Half-Win", param.BetID) + message = fmt.Sprintf( + "Your bet #%v resulted in a half-win. %.2f has been credited to your wallet.", + param.BetID, + param.WinningAmount.Float32(), + ) + case domain.OUTCOME_STATUS_VOID: + headline = fmt.Sprintf("Bet #%v Refunded", param.BetID) + message = fmt.Sprintf( + "Your bet #%v has been voided. %.2f has been refunded to your wallet.", + param.BetID, + param.WinningAmount.Float32(), + ) + + default: + return fmt.Errorf("unsupported status: %v", param.Status) + } + + for _, channel := range []domain.DeliveryChannel{ + domain.DeliveryChannelInApp, + domain.DeliveryChannelEmail, + domain.DeliveryChannelSMS, + } { + if !shouldSend(channel, param.SendEmail, param.SendSMS) { + continue + } + n := newBetResultNotification(param.UserID, domain.NotificationLevelSuccess, channel, headline, message, map[string]any{ + "winning_amount": param.WinningAmount.Float32(), + "status": param.Status, + "more": param.Extra, + }) + if err := s.notificationSvc.SendNotification(ctx, n); err != nil { + return err + } + } + + return nil +} + +func (s *Service) SendLosingStatusNotification(ctx context.Context, param SendResultNotificationParam) error { + if err := param.Validate(); err != nil { + return err + } + + var headline string + var message string + + switch param.Status { + case domain.OUTCOME_STATUS_LOSS: + headline = fmt.Sprintf("Bet #%v Lost", param.BetID) + message = "Unfortunately, your bet did not win this time. Better luck next time!" + default: + return fmt.Errorf("unsupported status: %v", param.Status) + } + + for _, channel := range []domain.DeliveryChannel{ + domain.DeliveryChannelInApp, + domain.DeliveryChannelEmail, + domain.DeliveryChannelSMS, + } { + if !shouldSend(channel, param.SendEmail, param.SendSMS) { + continue + } + n := newBetResultNotification(param.UserID, domain.NotificationLevelWarning, channel, headline, message, map[string]any{ + "status": param.Status, + "more": param.Extra, + }) + if err := s.notificationSvc.SendNotification(ctx, n); err != nil { + return err + } + } + + return nil +} + +func (s *Service) SendErrorStatusNotification(ctx context.Context, betID int64, status domain.OutcomeStatus, userID int64, extra string) error { + + var headline string + var message string + + switch status { + case domain.OUTCOME_STATUS_ERROR, domain.OUTCOME_STATUS_PENDING: + headline = fmt.Sprintf("Bet #%v Processing Issue", betID) + message = "We encountered a problem while processing your bet. Our team is working to resolve it as soon as possible." + + default: + return fmt.Errorf("unsupported status: %v", status) + } + + for _, channel := range []domain.DeliveryChannel{ + domain.DeliveryChannelInApp, + domain.DeliveryChannelEmail, + } { + n := newBetResultNotification(userID, domain.NotificationLevelError, channel, headline, message, map[string]any{ + "status": status, + "more": extra, + }) + if err := s.notificationSvc.SendNotification(ctx, n); err != nil { + return err + } + } + return nil +} + +func (s *Service) SendAdminAlertNotification(ctx context.Context, betID int64, status domain.OutcomeStatus, extra string, companyID int64) error { + + var headline string + var message string + + switch status { + case domain.OUTCOME_STATUS_ERROR, domain.OUTCOME_STATUS_PENDING: + headline = fmt.Sprintf("Processing Error for Bet #%v", betID) + message = "A processing error occurred with this bet. Please review and take corrective action." + + default: + return fmt.Errorf("unsupported status: %v", status) + } + + super_admin_users, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{ + Role: string(domain.RoleSuperAdmin), + }) + + if err != nil { + s.mongoLogger.Error("failed to get super_admin recipients", + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return err + } + + admin_users, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{ + Role: string(domain.RoleAdmin), + CompanyID: domain.ValidInt64{ + Value: companyID, + Valid: true, + }, + }) + + if err != nil { + s.mongoLogger.Error("failed to get admin recipients", + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return err + } + + users := append(super_admin_users, admin_users...) + + for _, user := range users { + for _, channel := range []domain.DeliveryChannel{ + domain.DeliveryChannelInApp, + domain.DeliveryChannelEmail, + } { + n := newBetResultNotification(user.ID, domain.NotificationLevelError, channel, headline, message, map[string]any{ + "status": status, + "more": extra, + }) + if err := s.notificationSvc.SendNotification(ctx, n); err != nil { + return err + } + } + } + + return nil +} diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index e026110..4ba3a66 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -31,20 +31,20 @@ import ( ) var ( - ErrNoEventsAvailable = errors.New("Not enough events available with the given filters") - 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") + ErrNoEventsAvailable = errors.New("not enough events available with the given filters") + 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") - ErrEventHasNotEnded = errors.New("Event has not ended yet") - 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") - ErrTotalBalanceNotEnough = errors.New("Total Wallet balance is insufficient to create bet") + ErrEventHasNotEnded = errors.New("event has not ended yet") + 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") + ErrTotalBalanceNotEnough = errors.New("total Wallet balance is insufficient to create bet") - 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") + 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") ) type Service struct { @@ -97,10 +97,6 @@ func (s *Service) GenerateCashoutID() (string, error) { for i := 0; i < length; i++ { index, err := rand.Int(rand.Reader, charLen) if err != nil { - s.mongoLogger.Error("failed to generate random index for cashout ID", - zap.Int("position", i), - zap.Error(err), - ) return "", err } result[i] = chars[index.Int64()] @@ -221,7 +217,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID if err != nil { return domain.CreateBetRes{}, err } - if req.Amount < 1 { + if req.Amount < settingsList.MinimumBetAmount.Float32() { return domain.CreateBetRes{}, ErrInvalidAmount } @@ -283,8 +279,9 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID ) return domain.CreateBetRes{}, err } - if count >= 2 { - return domain.CreateBetRes{}, fmt.Errorf("bet already placed twice") + + if role == domain.RoleCustomer && count >= settingsList.BetDuplicateLimit { + return domain.CreateBetRes{}, fmt.Errorf("max user limit for duplicate bet") } fastCode := helpers.GenerateFastCode() @@ -340,17 +337,20 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID return domain.CreateBetRes{}, err } - if branch.BranchManagerID != userID { - s.mongoLogger.Warn("unauthorized branch for branch manager", - zap.Int64("branch_id", *req.BranchID), - zap.Error(err), - ) - return domain.CreateBetRes{}, err + if role == domain.RoleBranchManager { + if branch.BranchManagerID != userID { + s.mongoLogger.Warn("unauthorized branch for branch manager", + zap.Int64("branch_id", *req.BranchID), + zap.Error(err), + ) + return domain.CreateBetRes{}, err + } } - - if branch.CompanyID == companyID { + if branch.CompanyID != companyID { s.mongoLogger.Warn("unauthorized company", zap.Int64("branch_id", *req.BranchID), + zap.Int64("branch_company_id", branch.CompanyID), + zap.Int64("company_id", companyID), zap.Error(err), ) } @@ -376,13 +376,13 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID ) return domain.CreateBetRes{}, err } - // + // default: s.mongoLogger.Error("unknown role type", zap.String("role", string(role)), zap.Int64("user_id", userID), ) - return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type") + return domain.CreateBetRes{}, fmt.Errorf("unknown role type") } bet, err := s.CreateBet(ctx, newBet) @@ -583,25 +583,21 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string, var newOdds []domain.CreateBetOutcome var totalOdds float32 = 1 + eventLogger := s.mongoLogger.With( + zap.String("eventID", eventID), + zap.Int32("sportID", sportID), + zap.String("homeTeam", HomeTeam), + zap.String("awayTeam", AwayTeam), + ) markets, err := s.prematchSvc.GetOddsByEventID(ctx, eventID, domain.OddMarketWithEventFilter{}) + if err != nil { - s.logger.Error("failed to get odds for event", "event id", eventID, "error", err) - s.mongoLogger.Error("failed to get odds for event", - zap.String("eventID", eventID), - zap.Int32("sportID", sportID), - zap.String("homeTeam", HomeTeam), - zap.String("awayTeam", AwayTeam), - zap.Error(err)) + eventLogger.Error("failed to get odds for event", zap.Error(err)) return nil, 0, err } if len(markets) == 0 { - s.logger.Error("empty odds for event", "event id", eventID) - s.mongoLogger.Warn("empty odds for event", - zap.String("eventID", eventID), - zap.Int32("sportID", sportID), - zap.String("homeTeam", HomeTeam), - zap.String("awayTeam", AwayTeam)) + eventLogger.Warn("empty odds for event") return nil, 0, fmt.Errorf("empty odds or event %v", eventID) } @@ -630,19 +626,13 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string, err = json.Unmarshal(rawBytes, &selectedOdd) if err != nil { - s.logger.Error("Failed to unmarshal raw odd", "error", err) - s.mongoLogger.Warn("Failed to unmarshal raw odd", - zap.String("eventID", eventID), - zap.Int32("sportID", sportID), - zap.Error(err)) + eventLogger.Warn("Failed to unmarshal raw odd", zap.Error(err)) continue } parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32) if err != nil { - s.logger.Error("Failed to parse odd", "error", err) - s.mongoLogger.Warn("Failed to parse odd", - zap.String("eventID", eventID), + eventLogger.Warn("Failed to parse odd", zap.String("oddValue", selectedOdd.Odds), zap.Error(err)) continue @@ -650,17 +640,13 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string, eventIDInt, err := strconv.ParseInt(eventID, 10, 64) if err != nil { - s.logger.Error("Failed to parse eventID", "error", err) - s.mongoLogger.Warn("Failed to parse eventID", - zap.String("eventID", eventID), - zap.Error(err)) + eventLogger.Warn("Failed to parse eventID", zap.Error(err)) continue } oddID, err := strconv.ParseInt(selectedOdd.ID, 10, 64) if err != nil { - s.logger.Error("Failed to parse oddID", "error", err) - s.mongoLogger.Warn("Failed to parse oddID", + eventLogger.Warn("Failed to parse oddID", zap.String("oddID", selectedOdd.ID), zap.Error(err)) continue @@ -668,8 +654,7 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string, marketID, err := strconv.ParseInt(market.MarketID, 10, 64) if err != nil { - s.logger.Error("Failed to parse marketID", "error", err) - s.mongoLogger.Warn("Failed to parse marketID", + eventLogger.Warn("Failed to parse marketID", zap.String("marketID", market.MarketID), zap.Error(err)) continue @@ -696,22 +681,12 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string, } if len(newOdds) == 0 { - s.logger.Error("Bet Outcomes is empty for market", "selectedMarkets", len(selectedMarkets)) - s.mongoLogger.Error("Bet Outcomes is empty for market", - zap.String("eventID", eventID), - zap.Int32("sportID", sportID), - zap.String("homeTeam", HomeTeam), - zap.String("awayTeam", AwayTeam), - zap.Int("selectedMarkets", len(selectedMarkets))) + eventLogger.Error("Bet Outcomes is empty for market", zap.Int("selectedMarkets", len(selectedMarkets))) return nil, 0, ErrGenerateRandomOutcome } // ✅ Final success log (optional) - s.mongoLogger.Info("Random bet outcomes generated successfully", - zap.String("eventID", eventID), - zap.Int32("sportID", sportID), - zap.Int("numOutcomes", len(newOdds)), - zap.Float32("totalOdds", totalOdds)) + eventLogger.Info("Random bet outcomes generated successfully", zap.Int("numOutcomes", len(newOdds)), zap.Float32("totalOdds", totalOdds)) return newOdds, totalOdds, nil } @@ -719,7 +694,15 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string, func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID, companyID int64, leagueID domain.ValidInt64, sportID domain.ValidInt32, firstStartTime, lastStartTime domain.ValidTime) (domain.CreateBetRes, error) { // Get a unexpired event id - + randomBetLogger := s.mongoLogger.With( + zap.Int64("userID", userID), + zap.Int64("branchID", branchID), + zap.Int64("companyID", companyID), + zap.Any("leagueID", leagueID), + zap.Any("sportID", sportID), + zap.Any("firstStartTime", firstStartTime), + zap.Any("lastStartTime", lastStartTime), + ) events, _, err := s.eventSvc.GetPaginatedUpcomingEvents(ctx, domain.EventFilter{ SportID: sportID, @@ -729,17 +712,12 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID, companyI }) if err != nil { - s.mongoLogger.Error("failed to get paginated upcoming events", - zap.Int64("userID", userID), - zap.Int64("branchID", branchID), - zap.Error(err)) + randomBetLogger.Error("failed to get paginated upcoming events", zap.Error(err)) return domain.CreateBetRes{}, err } if len(events) == 0 { - s.mongoLogger.Warn("no events available for random bet", - zap.Int64("userID", userID), - zap.Int64("branchID", branchID)) + randomBetLogger.Warn("no events available for random bet") return domain.CreateBetRes{}, ErrNoEventsAvailable } @@ -765,12 +743,7 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID, companyI newOdds, total, err := s.GenerateRandomBetOutcomes(ctx, event.ID, event.SportID, event.HomeTeam, event.AwayTeam, event.StartTime, numMarketsPerBet) if err != nil { - s.logger.Error("failed to generate random bet outcome", "event id", event.ID, "error", err) - s.mongoLogger.Error("failed to generate random bet outcome", - zap.Int64("userID", userID), - zap.Int64("branchID", branchID), - zap.String("eventID", event.ID), - zap.String("error", fmt.Sprintf("%v", err))) + s.mongoLogger.Error("failed to generate random bet outcome", zap.String("eventID", event.ID), zap.Error(err)) continue } @@ -779,10 +752,7 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID, companyI } if len(randomOdds) == 0 { - s.logger.Error("Failed to generate random any outcomes for all events") - s.mongoLogger.Error("Failed to generate random any outcomes for all events", - zap.Int64("userID", userID), - zap.Int64("branchID", branchID)) + randomBetLogger.Error("Failed to generate random any outcomes for all events") return domain.CreateBetRes{}, ErrGenerateRandomOutcome } @@ -790,20 +760,13 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID, companyI outcomesHash, err := generateOutcomeHash(randomOdds) if err != nil { - s.mongoLogger.Error("failed to generate outcome hash", - zap.Int64("user_id", userID), - zap.Error(err), - ) + randomBetLogger.Error("failed to generate outcome hash", zap.Error(err)) return domain.CreateBetRes{}, err } count, err := s.GetBetCountByUserID(ctx, userID, outcomesHash) if err != nil { - s.mongoLogger.Error("failed to get bet count", - zap.Int64("user_id", userID), - zap.String("outcome_hash", outcomesHash), - zap.Error(err), - ) + randomBetLogger.Error("failed to get bet count", zap.String("outcome_hash", outcomesHash), zap.Error(err)) return domain.CreateBetRes{}, err } @@ -825,10 +788,7 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID, companyI bet, err := s.CreateBet(ctx, newBet) if err != nil { - s.mongoLogger.Error("Failed to create a new random bet", - zap.Int64("userID", userID), - zap.Int64("branchID", branchID), - zap.String("bet", fmt.Sprintf("%+v", newBet))) + randomBetLogger.Error("Failed to create a new random bet", zap.Error(err)) return domain.CreateBetRes{}, err } @@ -838,19 +798,13 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID, companyI rows, err := s.betStore.CreateBetOutcome(ctx, randomOdds) if err != nil { - s.mongoLogger.Error("Failed to create a new random bet outcome", - zap.Int64("userID", userID), - zap.Int64("branchID", branchID), - zap.String("randomOdds", fmt.Sprintf("%+v", randomOdds))) + randomBetLogger.Error("Failed to create a new random bet outcome", zap.Any("randomOdds", randomOdds)) return domain.CreateBetRes{}, err } res := domain.ConvertCreateBetRes(bet, rows) - s.mongoLogger.Info("Random bets placed successfully", - zap.Int64("userID", userID), - zap.Int64("branchID", branchID), - zap.String("response", fmt.Sprintf("%+v", res))) + randomBetLogger.Info("Random bets placed successfully") return res, nil } @@ -897,53 +851,73 @@ func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) e return s.betStore.UpdateCashOut(ctx, id, cashedOut) } -func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { - bet, err := s.GetBetByID(ctx, id) +func (s *Service) UpdateStatus(ctx context.Context, betId int64, status domain.OutcomeStatus) error { + + updateLogger := s.mongoLogger.With( + zap.Int64("bet_id", betId), + zap.String("status", status.String()), + ) + bet, err := s.GetBetByID(ctx, betId) if err != nil { - s.mongoLogger.Error("failed to update bet status: invalid bet ID", - zap.Int64("bet_id", id), - zap.Error(err), - ) + updateLogger.Error("failed to update bet status: invalid bet ID", zap.Error(err)) + return err + } + + settingsList, err := s.settingSvc.GetOverrideSettingsList(ctx, bet.CompanyID) + if err != nil { + updateLogger.Error("failed to get settings", zap.Error(err)) return err } if status == domain.OUTCOME_STATUS_ERROR || status == domain.OUTCOME_STATUS_PENDING { - s.SendAdminErrorAlertNotification(ctx, status, "") - s.SendErrorStatusNotification(ctx, status, bet.UserID, "") - s.mongoLogger.Error("Bet Status is error", - zap.Int64("bet_id", id), - zap.Error(err), - ) - return s.betStore.UpdateStatus(ctx, id, status) + if err := s.SendAdminAlertNotification(ctx, betId, status, "", bet.CompanyID); err != nil { + updateLogger.Error("failed to send admin notification", zap.Error(err)) + return err + } + + if err := s.SendErrorStatusNotification(ctx, betId, status, bet.UserID, ""); err != nil { + updateLogger.Error("failed to send error notification to user", zap.Error(err)) + return err + } + updateLogger.Error("bet entered error/pending state") + return s.betStore.UpdateStatus(ctx, betId, status) } if bet.IsShopBet { - return s.betStore.UpdateStatus(ctx, id, status) + return s.betStore.UpdateStatus(ctx, betId, status) } - customerWallet, err := s.walletSvc.GetCustomerWallet(ctx, id) + // After this point the bet is known to be a online customer bet + + customerWallet, err := s.walletSvc.GetCustomerWallet(ctx, bet.UserID) if err != nil { - s.mongoLogger.Error("failed to get customer wallet", - zap.Int64("bet_id", id), - zap.Error(err), - ) + updateLogger.Error("failed to get customer wallet", zap.Error(err)) return err } + resultNotification := SendResultNotificationParam{ + BetID: betId, + Status: status, + UserID: bet.UserID, + SendEmail: settingsList.SendEmailOnBetFinish, + SendSMS: settingsList.SendSMSOnBetFinish, + } var amount domain.Currency switch status { case domain.OUTCOME_STATUS_LOSS: - s.SendLosingStatusNotification(ctx, status, bet.UserID, "") - return s.betStore.UpdateStatus(ctx, id, status) + err := s.SendLosingStatusNotification(ctx, resultNotification) + if err != nil { + updateLogger.Error("failed to send notification", zap.Error(err)) + return err + } + return s.betStore.UpdateStatus(ctx, betId, status) case domain.OUTCOME_STATUS_WIN: amount = domain.CalculateWinnings(bet.Amount, bet.TotalOdds) - s.SendWinningStatusNotification(ctx, status, bet.UserID, amount, "") case domain.OUTCOME_STATUS_HALF: amount = domain.CalculateWinnings(bet.Amount, bet.TotalOdds) / 2 - s.SendWinningStatusNotification(ctx, status, bet.UserID, amount, "") case domain.OUTCOME_STATUS_VOID: amount = bet.Amount - s.SendWinningStatusNotification(ctx, status, bet.UserID, amount, "") default: + updateLogger.Error("invalid outcome status") return fmt.Errorf("invalid outcome status") } @@ -951,7 +925,7 @@ func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.Outc domain.TRANSFER_DIRECT, domain.PaymentDetails{}, fmt.Sprintf("Added %v to wallet by system for winning a bet", amount.Float32())) if err != nil { - s.mongoLogger.Error("failed to add winnings to wallet", + updateLogger.Error("failed to add winnings to wallet", zap.Int64("wallet_id", customerWallet.RegularID), zap.Float32("amount", float32(amount)), zap.Error(err), @@ -959,231 +933,23 @@ func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.Outc return err } - return s.betStore.UpdateStatus(ctx, id, status) -} - -func (s *Service) SendWinningStatusNotification(ctx context.Context, status domain.OutcomeStatus, userID int64, winningAmount domain.Currency, extra string) error { - - var headline string - var message string - - switch status { - case domain.OUTCOME_STATUS_WIN: - headline = "You Bet Has Won!" - message = fmt.Sprintf( - "You have been awarded %.2f", - winningAmount.Float32(), - ) - case domain.OUTCOME_STATUS_HALF: - headline = "You have a half win" - message = fmt.Sprintf( - "You have been awarded %.2f", - winningAmount.Float32(), - ) - case domain.OUTCOME_STATUS_VOID: - headline = "Your bet has been refunded" - message = fmt.Sprintf( - "You have been awarded %.2f", - winningAmount.Float32(), - ) - } - - betNotification := &domain.Notification{ - RecipientID: userID, - DeliveryStatus: domain.DeliveryStatusPending, - IsRead: false, - Type: domain.NOTIFICATION_TYPE_BET_RESULT, - Level: domain.NotificationLevelSuccess, - Reciever: domain.NotificationRecieverSideCustomer, - DeliveryChannel: domain.DeliveryChannelInApp, - Payload: domain.NotificationPayload{ - Headline: headline, - Message: message, - }, - Priority: 2, - Metadata: fmt.Appendf(nil, `{ - "winning_amount":%.2f, - "status":%v - "more": %v - }`, winningAmount.Float32(), status, extra), - } - - if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil { - return err - } - - betNotification.DeliveryChannel = domain.DeliveryChannelEmail - if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil { - return err - } - - return nil -} - -func (s *Service) SendLosingStatusNotification(ctx context.Context, status domain.OutcomeStatus, userID int64, extra string) error { - - var headline string - var message string - - switch status { - case domain.OUTCOME_STATUS_LOSS: - headline = "Your bet has lost" - message = "Better luck next time" - } - - betNotification := &domain.Notification{ - RecipientID: userID, - DeliveryStatus: domain.DeliveryStatusPending, - IsRead: false, - Type: domain.NOTIFICATION_TYPE_BET_RESULT, - Level: domain.NotificationLevelSuccess, - Reciever: domain.NotificationRecieverSideCustomer, - DeliveryChannel: domain.DeliveryChannelInApp, - Payload: domain.NotificationPayload{ - Headline: headline, - Message: message, - }, - Priority: 2, - Metadata: fmt.Appendf(nil, `{ - "status":%v - "more": %v - }`, status, extra), - } - - if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil { - return err - } - - betNotification.DeliveryChannel = domain.DeliveryChannelEmail - if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil { - return err - } - - return nil -} - -func (s *Service) SendErrorStatusNotification(ctx context.Context, status domain.OutcomeStatus, userID int64, extra string) error { - - var headline string - var message string - - switch status { - case domain.OUTCOME_STATUS_ERROR, domain.OUTCOME_STATUS_PENDING: - headline = "There was an error with your bet" - message = "We have encounter an error with your bet. We will fix it as soon as we can" - } - - errorSeverityLevel := domain.NotificationErrorSeverityFatal - - betNotification := &domain.Notification{ - RecipientID: userID, - DeliveryStatus: domain.DeliveryStatusPending, - IsRead: false, - Type: domain.NOTIFICATION_TYPE_BET_RESULT, - Level: domain.NotificationLevelSuccess, - Reciever: domain.NotificationRecieverSideCustomer, - DeliveryChannel: domain.DeliveryChannelInApp, - Payload: domain.NotificationPayload{ - Headline: headline, - Message: message, - }, - Priority: 1, - ErrorSeverity: &errorSeverityLevel, - Metadata: fmt.Appendf(nil, `{ - "status":%v - "more": %v - }`, status, extra), - } - - if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil { - return err - } - - betNotification.DeliveryChannel = domain.DeliveryChannelEmail - if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil { - return err - } - return nil -} - -func (s *Service) SendAdminErrorAlertNotification(ctx context.Context, status domain.OutcomeStatus, extra string) error { - - var headline string - var message string - - switch status { - case domain.OUTCOME_STATUS_ERROR, domain.OUTCOME_STATUS_PENDING: - headline = "There was an error processing bet" - message = "We have encounter an error with bet. We will fix it as soon as we can" - } - - errorSeverity := domain.NotificationErrorSeverityHigh - betNotification := &domain.Notification{ - ErrorSeverity: &errorSeverity, - DeliveryStatus: domain.DeliveryStatusPending, - IsRead: false, - Type: domain.NOTIFICATION_TYPE_BET_RESULT, - Level: domain.NotificationLevelSuccess, - Reciever: domain.NotificationRecieverSideCustomer, - DeliveryChannel: domain.DeliveryChannelEmail, - Payload: domain.NotificationPayload{ - Headline: headline, - Message: message, - }, - Priority: 2, - Metadata: fmt.Appendf(nil, `{ - "status":%v - "more": %v - }`, status, extra), - } - - super_admin_users, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{ - Role: string(domain.RoleSuperAdmin), - }) - - if err != nil { - s.mongoLogger.Error("failed to get super_admin recipients", + if err := s.betStore.UpdateStatus(ctx, betId, status); err != nil { + updateLogger.Error("failed to update bet status", + zap.String("status", status.String()), zap.Error(err), - zap.Time("timestamp", time.Now()), ) return err } - admin_users, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{ - Role: string(domain.RoleAdmin), - }) + resultNotification.WinningAmount = amount + if err := s.SendWinningStatusNotification(ctx, resultNotification); err != nil { - if err != nil { - s.mongoLogger.Error("failed to get admin recipients", + updateLogger.Error("failed to send winning notification", zap.Error(err), - zap.Time("timestamp", time.Now()), ) return err } - users := append(super_admin_users, admin_users...) - - for _, user := range users { - betNotification.RecipientID = user.ID - if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil { - s.mongoLogger.Error("failed to send admin notification", - zap.Int64("admin_id", user.ID), - zap.Error(err), - zap.Time("timestamp", time.Now()), - ) - return err - } - betNotification.DeliveryChannel = domain.DeliveryChannelEmail - if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil { - s.mongoLogger.Error("failed to send email admin notification", - zap.Int64("admin_id", user.ID), - zap.Error(err), - zap.Time("timestamp", time.Now()), - ) - return err - } - } - return nil } @@ -1313,7 +1079,7 @@ func (s *Service) SetBetToRemoved(ctx context.Context, id int64) error { } func (s *Service) ProcessBetCashback(ctx context.Context) error { - + bets, err := s.betStore.GetBetsForCashback(ctx) if err != nil { s.mongoLogger.Error("failed to fetch bets", @@ -1322,7 +1088,6 @@ func (s *Service) ProcessBetCashback(ctx context.Context) error { return err } - for _, bet := range bets { shouldProcess := true loseCount := 0 @@ -1365,6 +1130,14 @@ func (s *Service) ProcessBetCashback(ctx context.Context) error { } settingsList, err := s.settingSvc.GetOverrideSettingsList(ctx, bet.CompanyID) + if err != nil { + s.mongoLogger.Error("Failed to get settings", + zap.Int64("userID", bet.UserID), + zap.Error(err)) + + return err + } + cashbackAmount := math.Min(float64(settingsList.CashbackAmountCap.Float32()), float64(calculateCashbackAmount(bet.Amount.Float32(), bet.TotalOdds))) _, err = s.walletSvc.AddToWallet(ctx, wallets.StaticID, domain.ToCurrency(float32(cashbackAmount)), domain.ValidInt64{}, domain.TRANSFER_DIRECT, diff --git a/internal/services/bonus/notification.go b/internal/services/bonus/notification.go new file mode 100644 index 0000000..8497a53 --- /dev/null +++ b/internal/services/bonus/notification.go @@ -0,0 +1,83 @@ +package bonus + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type SendBonusNotificationParam struct { + BonusID int64 + UserID int64 + Type domain.BonusType + Amount domain.Currency + SendEmail bool + SendSMS bool +} + +func shouldSend(channel domain.DeliveryChannel, sendEmail, sendSMS bool) bool { + switch { + case channel == domain.DeliveryChannelEmail && sendEmail: + return true + case channel == domain.DeliveryChannelSMS && sendSMS: + return true + case channel == domain.DeliveryChannelInApp: + return true + default: + return false + } +} + +func (s *Service) SendBonusNotification(ctx context.Context, param SendBonusNotificationParam) error { + + var headline string + var message string + + switch param.Type { + case domain.WelcomeBonus: + headline = "You've been awarded a welcome bonus!" + message = fmt.Sprintf( + "Congratulations! A you've been given %.2f as a welcome bonus for you to bet on.", + param.Amount, + ) + default: + return fmt.Errorf("unsupported bonus type: %v", param.Type) + } + for _, channel := range []domain.DeliveryChannel{ + domain.DeliveryChannelInApp, + domain.DeliveryChannelEmail, + domain.DeliveryChannelSMS, + } { + if !shouldSend(channel, param.SendEmail, param.SendSMS) { + continue + } + + raw, _ := json.Marshal(map[string]any{ + "bonus_id": param.BonusID, + "type": param.Type, + }) + + n := &domain.Notification{ + RecipientID: param.UserID, + DeliveryStatus: domain.DeliveryStatusPending, + IsRead: false, + Type: domain.NOTIFICATION_TYPE_BONUS_AWARDED, + Level: domain.NotificationLevelSuccess, + Reciever: domain.NotificationRecieverSideCustomer, + DeliveryChannel: channel, + Payload: domain.NotificationPayload{ + Headline: headline, + Message: message, + }, + Priority: 2, + Metadata: raw, + } + if err := s.notificationSvc.SendNotification(ctx, n); err != nil { + return err + } + } + + return nil +} diff --git a/internal/services/bonus/port.go b/internal/services/bonus/port.go index 2147b51..4bbd877 100644 --- a/internal/services/bonus/port.go +++ b/internal/services/bonus/port.go @@ -3,12 +3,15 @@ package bonus import ( "context" - dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" ) type BonusStore interface { - CreateBonusMultiplier(ctx context.Context, multiplier float32, balance_cap int64) error - GetBonusMultiplier(ctx context.Context) ([]dbgen.GetBonusMultiplierRow, error) - GetBonusBalanceCap(ctx context.Context) ([]dbgen.GetBonusBalanceCapRow, error) - UpdateBonusMultiplier(ctx context.Context, id int64, mulitplier float32, balance_cap int64) error + CreateUserBonus(ctx context.Context, bonus domain.CreateBonus) (domain.UserBonus, error) + GetAllUserBonuses(ctx context.Context, filter domain.BonusFilter) ([]domain.UserBonus, error) + GetBonusCount(ctx context.Context, filter domain.BonusFilter) (int64, error) + GetBonusByID(ctx context.Context, bonusID int64) (domain.UserBonus, error) + GetBonusStats(ctx context.Context, filter domain.BonusFilter) (domain.BonusStats, error) + UpdateUserBonus(ctx context.Context, bonusID int64, IsClaimed bool) error + DeleteUserBonus(ctx context.Context, bonusID int64) error } diff --git a/internal/services/bonus/service.go b/internal/services/bonus/service.go index 51e008a..3daaf71 100644 --- a/internal/services/bonus/service.go +++ b/internal/services/bonus/service.go @@ -2,32 +2,162 @@ package bonus import ( "context" + "errors" + "fmt" + "math" + "time" - dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" + "go.uber.org/zap" ) type Service struct { - bonusStore BonusStore + bonusStore BonusStore + walletSvc *wallet.Service + settingSvc *settings.Service + notificationSvc *notificationservice.Service + mongoLogger *zap.Logger } -func NewService(bonusStore BonusStore) *Service { +func NewService(bonusStore BonusStore, walletSvc *wallet.Service, settingSvc *settings.Service, notificationSvc *notificationservice.Service, mongoLogger *zap.Logger) *Service { return &Service{ - bonusStore: bonusStore, + bonusStore: bonusStore, + walletSvc: walletSvc, + settingSvc: settingSvc, + notificationSvc: notificationSvc, + mongoLogger: mongoLogger, } } -func (s *Service) CreateBonusMultiplier(ctx context.Context, multiplier float32, balance_cap int64) error { - return s.bonusStore.CreateBonusMultiplier(ctx, multiplier, balance_cap) +var ( + ErrWelcomeBonusNotActive = errors.New("welcome bonus is not active") + ErrWelcomeBonusCountReached = errors.New("welcome bonus max deposit count reached") +) + +func (s *Service) CreateWelcomeBonus(ctx context.Context, amount domain.Currency, companyID int64, userID int64) error { + settingsList, err := s.settingSvc.GetOverrideSettingsList(ctx, companyID) + if err != nil { + s.mongoLogger.Error("Failed to get settings", + zap.Int64("companyID", companyID), + zap.Error(err)) + + return err + } + + if !settingsList.WelcomeBonusActive { + return ErrWelcomeBonusNotActive + } + + wallet, err := s.walletSvc.GetCustomerWallet(ctx, userID) + if err != nil { + return err + } + + stats, err := s.walletSvc.GetTransferStats(ctx, wallet.ID) + + if err != nil { + return err + } + + if stats.TotalDeposits > settingsList.WelcomeBonusCount { + return ErrWelcomeBonusCountReached + } + + newBalance := math.Min(float64(amount)*float64(settingsList.WelcomeBonusMultiplier), float64(settingsList.WelcomeBonusCap)) + + bonus, err := s.CreateUserBonus(ctx, domain.CreateBonus{ + Name: "Welcome Bonus", + Description: fmt.Sprintf("Awarded for deposit number (%v / %v)", stats.TotalDeposits, settingsList.WelcomeBonusCount), + UserID: userID, + Type: domain.WelcomeBonus, + RewardAmount: domain.Currency(newBalance), + ExpiresAt: time.Now().Add(time.Duration(settingsList.WelcomeBonusExpire) * 24 * time.Hour), + }) + + if err != nil { + return err + } + + err = s.SendBonusNotification(ctx, SendBonusNotificationParam{ + BonusID: bonus.ID, + UserID: userID, + Type: domain.DepositBonus, + Amount: domain.Currency(newBalance), + SendEmail: true, + SendSMS: false, + }) + + if err != nil { + return err + } + + return nil } -func (s *Service) GetBonusMultiplier(ctx context.Context) ([]dbgen.GetBonusMultiplierRow, error) { - return s.bonusStore.GetBonusMultiplier(ctx) +var ( + ErrBonusIsAlreadyClaimed = errors.New("bonus is already claimed") + ErrBonusUserIDNotMatch = errors.New("bonus user id is not a match") +) + +func (s *Service) ProcessBonusClaim(ctx context.Context, bonusID, userID int64) error { + + bonus, err := s.GetBonusByID(ctx, bonusID) + + if err != nil { + return err + } + + if bonus.UserID != userID { + + } + if bonus.IsClaimed { + return ErrBonusIsAlreadyClaimed + } + + wallet, err := s.walletSvc.GetCustomerWallet(ctx, bonus.UserID) + + if err != nil { + return err + } + + _, err = s.walletSvc.AddToWallet( + ctx, wallet.StaticID, bonus.RewardAmount, + domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, + fmt.Sprintf("Added %v to bonus wallet due to %v", bonus.RewardAmount, bonus.Type), + ) + if err != nil { + return err + } + + if err := s.UpdateUserBonus(ctx, bonusID, true); err != nil { + return err + } + + return nil } -func (s *Service) GetBonusBalanceCap(ctx context.Context) ([]dbgen.GetBonusBalanceCapRow, error) { - return s.bonusStore.GetBonusBalanceCap(ctx) +func (s *Service) CreateUserBonus(ctx context.Context, bonus domain.CreateBonus) (domain.UserBonus, error) { + return s.bonusStore.CreateUserBonus(ctx, bonus) +} +func (s *Service) GetAllUserBonuses(ctx context.Context, filter domain.BonusFilter) ([]domain.UserBonus, error) { + return s.bonusStore.GetAllUserBonuses(ctx, filter) } -func (s *Service) UpdateBonusMultiplier(ctx context.Context, id int64, mulitplier float32, balance_cap int64) error { - return s.bonusStore.UpdateBonusMultiplier(ctx, id, mulitplier, balance_cap) +func (s *Service) GetBonusCount(ctx context.Context, filter domain.BonusFilter) (int64, error) { + return s.bonusStore.GetBonusCount(ctx, filter) +} +func (s *Service) GetBonusByID(ctx context.Context, bonusID int64) (domain.UserBonus, error) { + return s.bonusStore.GetBonusByID(ctx, bonusID) +} +func (s *Service) GetBonusStats(ctx context.Context, filter domain.BonusFilter) (domain.BonusStats, error) { + return s.bonusStore.GetBonusStats(ctx, filter) +} +func (s *Service) UpdateUserBonus(ctx context.Context, bonusID int64, IsClaimed bool) error { + return s.bonusStore.UpdateUserBonus(ctx, bonusID, IsClaimed) +} +func (s *Service) DeleteUserBonus(ctx context.Context, bonusID int64) error { + return s.bonusStore.DeleteUserBonus(ctx, bonusID) } diff --git a/internal/services/event/port.go b/internal/services/event/port.go index e02be83..74305f9 100644 --- a/internal/services/event/port.go +++ b/internal/services/event/port.go @@ -19,6 +19,7 @@ type Service interface { IsEventMonitored(ctx context.Context, eventID string) (bool, error) UpdateEventMonitored(ctx context.Context, eventID string, IsMonitored bool) error GetEventsWithSettings(ctx context.Context, companyID int64, filter domain.EventFilter) ([]domain.EventWithSettings, int64, error) - GetEventWithSettingByID(ctx context.Context, ID string, companyID int64) (domain.EventWithSettings, error) + GetEventWithSettingByID(ctx context.Context, ID string, companyID int64) (domain.EventWithSettings, error) UpdateEventSettings(ctx context.Context, event domain.CreateEventSettings) error + GetSportAndLeagueIDs(ctx context.Context, eventID string) ([]int64, error) } diff --git a/internal/services/event/service.go b/internal/services/event/service.go index 6dcf6ee..4f56c2a 100644 --- a/internal/services/event/service.go +++ b/internal/services/event/service.go @@ -12,8 +12,11 @@ import ( "sync" "time" + "github.com/SamuelTariku/FortuneBet-Backend/internal/config" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings" + "github.com/jackc/pgx/v5" "go.uber.org/zap" // "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" ) @@ -21,14 +24,18 @@ import ( type service struct { token string store *repository.Store + settingSvc settings.Service mongoLogger *zap.Logger + cfg *config.Config } -func New(token string, store *repository.Store, mongoLogger *zap.Logger) Service { +func New(token string, store *repository.Store, settingSvc settings.Service, mongoLogger *zap.Logger, cfg *config.Config) Service { return &service{ token: token, store: store, + settingSvc: settingSvc, mongoLogger: mongoLogger, + cfg: cfg, } } @@ -206,22 +213,39 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error { } func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, source_url string, source domain.EventSource) { - const pageLimit int = 200 - sportIDs := []int{1, 18, 17, 3, 83, 15, 12, 19, 8, 16, 91} - // sportIDs := []int{1} + settingsList, err := s.settingSvc.GetGlobalSettingList(ctx) + + if err != nil { + s.mongoLogger.Error("Failed to fetch event data for page", zap.Error(err)) + return + } + + var pageLimit int + var sportIDs []int + + // Restricting the page to 1 on development, which drastically reduces the amount of events that is fetched + if s.cfg.Env == "development" { + pageLimit = 1 + sportIDs = []int{1} + } else { + pageLimit = 200 + sportIDs = []int{1, 18, 17, 3, 83, 15, 12, 19, 8, 16, 91} + } + + var skippedLeague []string + var totalEvents = 0 + nilAway := 0 for sportIndex, sportID := range sportIDs { var totalPages int = 1 var page int = 0 - var count int = 0 - var skippedLeague []string - var totalEvents = 0 + var pageCount int = 0 + var sportEvents = 0 logger := s.mongoLogger.With( zap.String("source", string(source)), zap.Int("sport_id", sportID), zap.String("sport_name", domain.Sport(sportID).String()), - zap.Int("count", count), - zap.Int("totalEvents", totalEvents), + zap.Int("count", pageCount), zap.Int("Skipped leagues", len(skippedLeague)), ) for page <= totalPages { @@ -301,34 +325,33 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, source_ur // } event := domain.CreateEvent{ - ID: ev.ID, - SportID: convertInt32(ev.SportID), - MatchName: "", - HomeTeam: ev.Home.Name, - AwayTeam: "", // handle nil safely - HomeTeamID: convertInt64(ev.Home.ID), - AwayTeamID: 0, - HomeTeamImage: "", - AwayTeamImage: "", - LeagueID: convertInt64(ev.League.ID), - LeagueName: ev.League.Name, - StartTime: time.Unix(startUnix, 0).UTC(), - Source: source, - IsLive: false, - Status: domain.STATUS_PENDING, + ID: ev.ID, + SportID: convertInt32(ev.SportID), + HomeTeam: ev.Home.Name, + AwayTeam: "", // handle nil safely + HomeTeamID: convertInt64(ev.Home.ID), + AwayTeamID: 0, + LeagueID: convertInt64(ev.League.ID), + LeagueName: ev.League.Name, + StartTime: time.Unix(startUnix, 0).UTC(), + Source: source, + IsLive: false, + Status: domain.STATUS_PENDING, + DefaultWinningUpperLimit: settingsList.DefaultWinningLimit, } if ev.Away != nil { - dataLogger.Info("event away is empty") event.AwayTeam = ev.Away.Name event.AwayTeamID = convertInt64(ev.Away.ID) event.MatchName = ev.Home.Name + " vs " + ev.Away.Name + } else { + nilAway += 1 } - ok, err := s.CheckAndInsertEventHistory(ctx, event) + ok, _ := s.CheckAndInsertEventHistory(ctx, event) - if err != nil { - dataLogger.Error("failed to check and insert event history", zap.Error(err)) - } + // if err != nil { + // dataLogger.Error("failed to check and insert event history", zap.Error(err)) + // } if ok { dataLogger.Info("event history has been recorded") @@ -338,7 +361,8 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, source_ur if err != nil { dataLogger.Error("failed to save upcoming event", zap.Error(err)) } - totalEvents += 1 + sportEvents += 1 + } // log.Printf("⚠️ Skipped leagues %v", len(skippedLeague)) @@ -346,26 +370,26 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, source_ur totalPages = data.Pager.Total / data.Pager.PerPage - if count >= pageLimit { + if pageCount >= pageLimit { break } if page > totalPages { break } - count++ + pageCount++ } - s.mongoLogger.Info( - "Successfully fetched upcoming events", - zap.String("source", string(source)), - zap.Int("totalEvents", totalEvents), - zap.Int("sport_id", sportID), - zap.String("sport_name", domain.Sport(sportID).String()), - zap.Int("page", page), - zap.Int("total_pages", totalPages), - zap.Int("Skipped leagues", len(skippedLeague)), - ) + logger.Info("Completed adding sport", zap.Int("number_of_events_in_sport", sportEvents)) + totalEvents += sportEvents } + + s.mongoLogger.Info( + "Successfully fetched upcoming events", + zap.String("source", string(source)), + zap.Int("totalEvents", totalEvents), + zap.Int("Skipped leagues", len(skippedLeague)), + zap.Int("Events with empty away data", nilAway), + ) } func (s *service) CheckAndInsertEventHistory(ctx context.Context, event domain.CreateEvent) (bool, error) { @@ -379,7 +403,9 @@ func (s *service) CheckAndInsertEventHistory(ctx context.Context, event domain.C ) if err != nil { - eventLogger.Error("failed to get event is_monitored", zap.Error(err)) + if err != pgx.ErrNoRows { + eventLogger.Info("failed to get event is_monitored", zap.Error(err)) + } return false, err } @@ -479,9 +505,14 @@ func (s *service) GetEventsWithSettings(ctx context.Context, companyID int64, fi return s.store.GetEventsWithSettings(ctx, companyID, filter) } -func (s *service) GetEventWithSettingByID(ctx context.Context, ID string, companyID int64) (domain.EventWithSettings, error) { +func (s *service) GetEventWithSettingByID(ctx context.Context, ID string, companyID int64) (domain.EventWithSettings, error) { 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) GetSportAndLeagueIDs(ctx context.Context, eventID string) ([]int64, error) { + return s.store.GetSportAndLeagueIDs(ctx, eventID) +} diff --git a/internal/services/league/port.go b/internal/services/league/port.go index b203c4f..54dc626 100644 --- a/internal/services/league/port.go +++ b/internal/services/league/port.go @@ -10,7 +10,7 @@ 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, 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 } diff --git a/internal/services/league/service.go b/internal/services/league/service.go index d82b38f..9a3e1a3 100644 --- a/internal/services/league/service.go +++ b/internal/services/league/service.go @@ -29,7 +29,7 @@ func (s *service) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) return s.store.GetAllLeagues(ctx, filter) } -func (s *service) GetAllLeaguesByCompany(ctx context.Context, companyID int64, filter domain.LeagueFilter) ([]domain.LeagueWithSettings, error) { +func (s *service) GetAllLeaguesByCompany(ctx context.Context, companyID int64, filter domain.LeagueFilter) ([]domain.LeagueWithSettings, int64, error) { return s.store.GetAllLeaguesByCompany(ctx, companyID, filter) } diff --git a/internal/services/messenger/email.go b/internal/services/messenger/email.go index ddb3542..a99d2fe 100644 --- a/internal/services/messenger/email.go +++ b/internal/services/messenger/email.go @@ -3,10 +3,9 @@ package messenger import ( "context" "github.com/resend/resend-go/v2" - ) -func (s *Service) SendEmail(ctx context.Context, receiverEmail, message string, subject string) error { +func (s *Service) SendEmail(ctx context.Context, receiverEmail, message string, messageHTML string, subject string) error { apiKey := s.config.ResendApiKey client := resend.NewClient(apiKey) formattedSenderEmail := "FortuneBets <" + s.config.ResendSenderEmail + ">" @@ -15,6 +14,7 @@ func (s *Service) SendEmail(ctx context.Context, receiverEmail, message string, To: []string{receiverEmail}, Subject: subject, Text: message, + Html: messageHTML, } _, err := client.Emails.Send(params) diff --git a/internal/services/notfication/service.go b/internal/services/notfication/service.go deleted file mode 100644 index c6a5457..0000000 --- a/internal/services/notfication/service.go +++ /dev/null @@ -1,475 +0,0 @@ -package notificationservice - -import ( - "context" - "encoding/json" - "errors" - "log/slog" - "sync" - "time" - - "github.com/SamuelTariku/FortuneBet-Backend/internal/config" - "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" - "github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers" - "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" - - // "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" -) - -type Service struct { - repo repository.NotificationRepository - Hub *ws.NotificationHub - // notificationStore - connections sync.Map - notificationCh chan *domain.Notification - stopCh chan struct{} - config *config.Config - logger *slog.Logger - redisClient *redis.Client -} - -func New(repo repository.NotificationRepository, logger *slog.Logger, cfg *config.Config) *Service { - hub := ws.NewNotificationHub() - rdb := redis.NewClient(&redis.Options{ - Addr: cfg.RedisAddr, // e.g., "redis:6379" - }) - - svc := &Service{ - repo: repo, - Hub: hub, - logger: logger, - connections: sync.Map{}, - notificationCh: make(chan *domain.Notification, 1000), - stopCh: make(chan struct{}), - config: cfg, - redisClient: rdb, - } - - go hub.Run() - go svc.startWorker() - go svc.startRetryWorker() - go svc.RunRedisSubscriber(context.Background()) - - return svc -} - -func (s *Service) addConnection(recipientID int64, c *websocket.Conn) { - if c == nil { - s.logger.Warn("[NotificationSvc.AddConnection] Attempted to add nil WebSocket connection", "recipientID", recipientID) - return - } - - s.connections.Store(recipientID, c) - s.logger.Info("[NotificationSvc.AddConnection] Added WebSocket connection", "recipientID", recipientID) -} - -func (s *Service) SendNotification(ctx context.Context, notification *domain.Notification) error { - notification.ID = helpers.GenerateID() - notification.Timestamp = time.Now() - notification.DeliveryStatus = domain.DeliveryStatusPending - - created, err := s.repo.CreateNotification(ctx, notification) - if err != nil { - s.logger.Error("[NotificationSvc.SendNotification] Failed to create notification", "id", notification.ID, "error", err) - return err - } - - notification = created - - if notification.DeliveryChannel == domain.DeliveryChannelInApp { - s.Hub.Broadcast <- map[string]interface{}{ - "type": "CREATED_NOTIFICATION", - "recipient_id": notification.RecipientID, - "payload": notification, - } - } - - select { - case s.notificationCh <- notification: - default: - s.logger.Warn("[NotificationSvc.SendNotification] Notification channel full, dropping notification", "id", notification.ID) - } - - return nil -} - -func (s *Service) MarkAsRead(ctx context.Context, notificationIDs []string, recipientID int64) error { - for _, notificationID := range notificationIDs { - _, err := s.repo.UpdateNotificationStatus(ctx, notificationID, string(domain.DeliveryStatusSent), true, nil) - if err != nil { - s.logger.Error("[NotificationSvc.MarkAsRead] Failed to mark notification as read", "notificationID", notificationID, "recipientID", recipientID, "error", err) - return err - } - - // count, err := s.repo.CountUnreadNotifications(ctx, recipientID) - // if err != nil { - // s.logger.Error("[NotificationSvc.MarkAsRead] Failed to count unread notifications", "recipientID", recipientID, "error", err) - // return err - // } - - // s.Hub.Broadcast <- map[string]interface{}{ - // "type": "COUNT_NOT_OPENED_NOTIFICATION", - // "recipient_id": recipientID, - // "payload": map[string]int{ - // "not_opened_notifications_count": int(count), - // }, - // } - - s.logger.Info("[NotificationSvc.MarkAsRead] Notification marked as read", "notificationID", notificationID, "recipientID", recipientID) - } - - return nil -} - -func (s *Service) ListNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, error) { - notifications, err := s.repo.ListNotifications(ctx, recipientID, limit, offset) - if err != nil { - s.logger.Error("[NotificationSvc.ListNotifications] Failed to list notifications", "recipientID", recipientID, "limit", limit, "offset", offset, "error", err) - return nil, err - } - s.logger.Info("[NotificationSvc.ListNotifications] Successfully listed notifications", "recipientID", recipientID, "count", len(notifications)) - return notifications, nil -} - -func (s *Service) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) { - notifications, err := s.repo.GetAllNotifications(ctx, limit, offset) - if err != nil { - s.logger.Error("[NotificationSvc.ListNotifications] Failed to get all notifications") - return nil, err - } - s.logger.Info("[NotificationSvc.ListNotifications] Successfully retrieved all notifications", "count", len(notifications)) - return notifications, nil -} - -func (s *Service) ConnectWebSocket(ctx context.Context, recipientID int64, c *websocket.Conn) error { - s.addConnection(recipientID, c) - s.logger.Info("[NotificationSvc.ConnectWebSocket] WebSocket connection established", "recipientID", recipientID) - return nil -} - -func (s *Service) DisconnectWebSocket(recipientID int64) { - if conn, loaded := s.connections.LoadAndDelete(recipientID); loaded { - conn.(*websocket.Conn).Close() - s.logger.Info("[NotificationSvc.DisconnectWebSocket] Disconnected WebSocket", "recipientID", recipientID) - } -} - -func (s *Service) SendSMS(ctx context.Context, recipientID int64, message string) error { - s.logger.Info("[NotificationSvc.SendSMS] SMS notification requested", "recipientID", recipientID, "message", message) - - apiKey := s.config.AFRO_SMS_API_KEY - senderName := s.config.AFRO_SMS_SENDER_NAME - receiverPhone := s.config.AFRO_SMS_RECEIVER_PHONE_NUMBER - hostURL := s.config.ADRO_SMS_HOST_URL - endpoint := "/api/send" - - request := afro.GetRequest(apiKey, endpoint, hostURL) - request.Method = "GET" - request.Sender(senderName) - request.To(receiverPhone, message) - - response, err := afro.MakeRequestWithContext(ctx, request) - if err != nil { - s.logger.Error("[NotificationSvc.SendSMS] Failed to send SMS", "recipientID", recipientID, "error", err) - return err - } - - if response["acknowledge"] == "success" { - s.logger.Info("[NotificationSvc.SendSMS] SMS sent successfully", "recipientID", recipientID) - } else { - s.logger.Error("[NotificationSvc.SendSMS] Failed to send SMS", "recipientID", recipientID, "response", response["response"]) - return errors.New("SMS delivery failed: " + response["response"].(string)) - } - - return nil -} - -func (s *Service) SendEmail(ctx context.Context, recipientID int64, subject, message string) error { - s.logger.Info("[NotificationSvc.SendEmail] Email notification requested", "recipientID", recipientID, "subject", subject) - return nil -} - -func (s *Service) startWorker() { - for { - select { - case notification := <-s.notificationCh: - s.handleNotification(notification) - case <-s.stopCh: - s.logger.Info("[NotificationSvc.StartWorker] Worker stopped") - return - } - } -} - -func (s *Service) ListRecipientIDs(ctx context.Context, receiver domain.NotificationRecieverSide) ([]int64, error) { - return s.repo.ListRecipientIDs(ctx, receiver) -} - -func (s *Service) handleNotification(notification *domain.Notification) { - ctx := context.Background() - - switch notification.DeliveryChannel { - case domain.DeliveryChannelSMS: - err := s.SendSMS(ctx, notification.RecipientID, notification.Payload.Message) - if err != nil { - notification.DeliveryStatus = domain.DeliveryStatusFailed - } else { - notification.DeliveryStatus = domain.DeliveryStatusSent - } - case domain.DeliveryChannelEmail: - err := s.SendEmail(ctx, notification.RecipientID, notification.Payload.Headline, notification.Payload.Message) - if err != nil { - notification.DeliveryStatus = domain.DeliveryStatusFailed - } else { - notification.DeliveryStatus = domain.DeliveryStatusSent - } - default: - if notification.DeliveryChannel != domain.DeliveryChannelInApp { - s.logger.Warn("[NotificationSvc.HandleNotification] Unsupported delivery channel", "channel", notification.DeliveryChannel) - notification.DeliveryStatus = domain.DeliveryStatusFailed - } - } - - if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil { - s.logger.Error("[NotificationSvc.HandleNotification] Failed to update notification status", "id", notification.ID, "error", err) - } -} - -func (s *Service) startRetryWorker() { - ticker := time.NewTicker(1 * time.Minute) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - s.retryFailedNotifications() - case <-s.stopCh: - s.logger.Info("[NotificationSvc.StartRetryWorker] Retry worker stopped") - return - } - } -} - -func (s *Service) retryFailedNotifications() { - ctx := context.Background() - failedNotifications, err := s.repo.ListFailedNotifications(ctx, 100) - if err != nil { - s.logger.Error("[NotificationSvc.RetryFailedNotifications] Failed to list failed notifications", "error", err) - return - } - - for _, n := range failedNotifications { - notification := &n - go func(notification *domain.Notification) { - for attempt := 0; attempt < 3; attempt++ { - time.Sleep(time.Duration(attempt) * time.Second) - switch notification.DeliveryChannel { - case domain.DeliveryChannelSMS: - if err := s.SendSMS(ctx, notification.RecipientID, notification.Payload.Message); err == nil { - notification.DeliveryStatus = domain.DeliveryStatusSent - if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil { - s.logger.Error("[NotificationSvc.RetryFailedNotifications] Failed to update after retry", "id", notification.ID, "error", err) - } - s.logger.Info("[NotificationSvc.RetryFailedNotifications] Successfully retried notification", "id", notification.ID) - return - } - case domain.DeliveryChannelEmail: - if err := s.SendEmail(ctx, notification.RecipientID, notification.Payload.Headline, notification.Payload.Message); err == nil { - notification.DeliveryStatus = domain.DeliveryStatusSent - if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil { - s.logger.Error("[NotificationSvc.RetryFailedNotifications] Failed to update after retry", "id", notification.ID, "error", err) - } - s.logger.Info("[NotificationSvc.RetryFailedNotifications] Successfully retried notification", "id", notification.ID) - return - } - } - } - s.logger.Error("[NotificationSvc.RetryFailedNotifications] Max retries reached for notification", "id", notification.ID) - }(notification) - } -} - -func (s *Service) CountUnreadNotifications(ctx context.Context, recipient_id int64) (int64, error) { - return s.repo.CountUnreadNotifications(ctx, recipient_id) -} - -// 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() - - 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) - continue - } - - 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, - } - - if hasRecipient { - message["recipient_id"] = recipientID - message["recipient_type"] = recipientType - } - - s.Hub.Broadcast <- message - } -} - -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()), - }) - } - - 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, - } - - 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.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 - - 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 - } - - return metric, nil -} - -func (s *Service) UpdateLiveMetricForWallet(ctx context.Context, wallet domain.Wallet) { - var ( - payload domain.LiveWalletMetrics - event map[string]interface{} - key = "live_metrics" - ) - - // Try company first - company, companyErr := s.GetCompanyByWalletID(ctx, wallet.ID) - if companyErr == nil { - payload = domain.LiveWalletMetrics{ - Timestamp: time.Now(), - CompanyBalances: []domain.CompanyWalletBalance{{ - CompanyID: company.ID, - CompanyName: company.Name, - Balance: float64(wallet.Balance), - }}, - BranchBalances: []domain.BranchWalletBalance{}, - } - - event = map[string]interface{}{ - "type": "LIVE_WALLET_METRICS_UPDATE", - "recipient_id": company.ID, - "recipient_type": "company", - "payload": payload, - } - } else { - // Try branch next - branch, branchErr := s.GetBranchByWalletID(ctx, wallet.ID) - if branchErr == nil { - payload = domain.LiveWalletMetrics{ - Timestamp: time.Now(), - CompanyBalances: []domain.CompanyWalletBalance{}, - BranchBalances: []domain.BranchWalletBalance{{ - BranchID: branch.ID, - BranchName: branch.Name, - CompanyID: branch.CompanyID, - Balance: float64(wallet.Balance), - }}, - } - - event = map[string]interface{}{ - "type": "LIVE_WALLET_METRICS_UPDATE", - "recipient_id": branch.ID, - "recipient_type": "branch", - "payload": payload, - } - } else { - // Neither company nor branch matched this wallet - s.logger.Warn("wallet not linked to any company or branch", "walletID", wallet.ID) - return - } - } - - // Save latest metric to Redis - if jsonBytes, err := json.Marshal(payload); err == nil { - s.redisClient.Set(ctx, key, jsonBytes, 0) - } else { - s.logger.Error("failed to marshal wallet metrics payload", "walletID", wallet.ID, "err", err) - } - - // Publish via Redis - if jsonEvent, err := json.Marshal(event); err == nil { - s.redisClient.Publish(ctx, key, jsonEvent) - } else { - s.logger.Error("failed to marshal event payload", "walletID", wallet.ID, "err", err) - } - - // Broadcast over WebSocket - s.Hub.Broadcast <- event -} - -func (s *Service) GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error) { - return s.GetCompanyByWalletID(ctx, walletID) -} - -func (s *Service) GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error) { - return s.GetBranchByWalletID(ctx, walletID) -} diff --git a/internal/services/notification/service.go b/internal/services/notification/service.go index ae0b990..6ba4044 100644 --- a/internal/services/notification/service.go +++ b/internal/services/notification/service.go @@ -93,7 +93,7 @@ func (s *Service) addConnection(recipientID int64, c *websocket.Conn) error { } func (s *Service) SendNotification(ctx context.Context, notification *domain.Notification) error { - + notification.ID = helpers.GenerateID() notification.Timestamp = time.Now() notification.DeliveryStatus = domain.DeliveryStatusPending @@ -300,7 +300,7 @@ func (s *Service) handleNotification(notification *domain.Notification) { } case domain.DeliveryChannelEmail: - err := s.SendNotificationEmail(ctx, notification.RecipientID, notification.Payload.Headline, notification.Payload.Message) + err := s.SendNotificationEmail(ctx, notification.RecipientID, notification.Payload.Message, notification.Payload.Headline) if err != nil { notification.DeliveryStatus = domain.DeliveryStatusFailed } else { @@ -334,14 +334,22 @@ func (s *Service) SendNotificationSMS(ctx context.Context, recipientID int64, me } if !user.PhoneVerified { - return fmt.Errorf("Cannot send notification to unverified phone number") + return fmt.Errorf("cannot send notification to unverified phone number") } if user.PhoneNumber == "" { - return fmt.Errorf("Phone Number is invalid") + return fmt.Errorf("phone Number is invalid") } err = s.messengerSvc.SendSMS(ctx, user.PhoneNumber, message, user.CompanyID) if err != nil { + s.mongoLogger.Error("[NotificationSvc.HandleNotification] Failed to send notification SMS", + zap.Int64("recipient_id", recipientID), + zap.String("user_phone_number", user.PhoneNumber), + zap.String("message", message), + zap.Int64("company_id", user.CompanyID.Value), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) return err } @@ -357,14 +365,22 @@ func (s *Service) SendNotificationEmail(ctx context.Context, recipientID int64, } if !user.EmailVerified { - return fmt.Errorf("Cannot send notification to unverified email") + return fmt.Errorf("cannot send notification to unverified email") } - if user.PhoneNumber == "" { - return fmt.Errorf("Email is invalid") + if user.Email == "" { + return fmt.Errorf("email is invalid") } - err = s.messengerSvc.SendEmail(ctx, user.PhoneNumber, message, subject) + err = s.messengerSvc.SendEmail(ctx, user.Email, message, message, subject) if err != nil { + s.mongoLogger.Error("[NotificationSvc.HandleNotification] Failed to send notification SMS", + zap.Int64("recipient_id", recipientID), + zap.String("user_email", user.Email), + zap.String("message", message), + zap.Int64("company_id", user.CompanyID.Value), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) return err } @@ -424,7 +440,7 @@ func (s *Service) retryFailedNotifications() { return } case domain.DeliveryChannelEmail: - if err := s.SendNotificationEmail(ctx, notification.RecipientID, notification.Payload.Headline, notification.Payload.Message); err == nil { + if err := s.SendNotificationEmail(ctx, notification.RecipientID, notification.Payload.Message, notification.Payload.Headline); err == nil { notification.DeliveryStatus = domain.DeliveryStatusSent if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil { s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Failed to update after retry", diff --git a/internal/services/odds/port.go b/internal/services/odds/port.go index d1026aa..3ec57b9 100644 --- a/internal/services/odds/port.go +++ b/internal/services/odds/port.go @@ -17,6 +17,11 @@ 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) + // Settings + SaveOddsSetting(ctx context.Context, odd domain.CreateOddMarketSettings) error // Odd History InsertOddHistory(ctx context.Context, odd domain.CreateOddHistory) (domain.OddHistory, error) diff --git a/internal/services/odds/service.go b/internal/services/odds/service.go index 33e8db8..b52bf0e 100644 --- a/internal/services/odds/service.go +++ b/internal/services/odds/service.go @@ -88,67 +88,49 @@ func (s *ServiceImpl) fetchBet365Odds(ctx context.Context) error { return err } - var errs []error - for index, event := range eventIDs { - log.Printf("📡 Fetching prematch odds for event ID: %v (%d/%d) ", event.ID, index, len(eventIDs)) + if s.config.Env == "development" { + log.Printf("📡 Fetching prematch odds for event ID: %v (%d/%d) ", event.ID, index, len(eventIDs)) + } + eventLogger := s.mongoLogger.With( + zap.String("eventID", event.ID), + zap.Int32("sportID", event.SportID), + ) oddsData, err := s.FetchNonLiveOddsByEventID(ctx, event.ID) if err != nil || oddsData.Success != 1 { - s.mongoLogger.Error( - "Failed to fetch prematch odds", - zap.String("eventID", event.ID), - zap.Error(err), - ) - errs = append(errs, fmt.Errorf("failed to fetch prematch odds for event %v: %w", event.ID, err)) + eventLogger.Error("Failed to fetch prematch odds", zap.Error(err)) continue } parsedOddSections, err := s.ParseOddSections(ctx, oddsData.Results[0], event.SportID) if err != nil { - s.mongoLogger.Error( - "Failed to parse odd section", - zap.String("eventID", event.ID), - zap.Int32("sportID", event.SportID), - zap.Error(err), - ) - errs = append(errs, fmt.Errorf("failed to parse odd section for event %v: %w", event.ID, err)) + eventLogger.Error("Failed to parse odd section", zap.Error(err)) continue } + parsedOddLogger := eventLogger.With( + zap.String("parsedOddSectionFI", parsedOddSections.EventFI), + zap.Int("main_sections_count", len(parsedOddSections.Sections)), + zap.Int("other_sections_count", len(parsedOddSections.OtherRes)), + ) + if parsedOddSections.EventFI == "" { - s.mongoLogger.Error( - "Skipping result with no valid Event FI field", - zap.String("FI", parsedOddSections.EventFI), - zap.String("eventID", event.ID), - zap.Int32("sportID", event.SportID), - zap.Error(err), - ) - errs = append(errs, errors.New("event FI is empty")) + parsedOddLogger.Error("Skipping result with no valid Event FI field", zap.Error(err)) continue } + if len(parsedOddSections.Sections) == 0 { + parsedOddLogger.Warn("Event has no odds in main sections", zap.Error(err)) + } for oddCategory, section := range parsedOddSections.Sections { if err := s.storeSection(ctx, event.ID, parsedOddSections.EventFI, oddCategory, section); err != nil { - s.mongoLogger.Error( - "Error storing odd section", - zap.String("eventID", event.ID), - zap.String("odd", oddCategory), - zap.Int32("sportID", event.SportID), - zap.Error(err), - ) - errs = append(errs, err) + parsedOddLogger.Error("Error storing odd section", zap.String("odd", oddCategory), zap.Error(err)) } } for _, section := range parsedOddSections.OtherRes { if err := s.storeSection(ctx, event.ID, parsedOddSections.EventFI, "others", section); err != nil { - s.mongoLogger.Error( - "Error storing odd other section", - zap.String("eventID", event.ID), - zap.Int32("sportID", event.SportID), - zap.Error(err), - ) - errs = append(errs, err) + parsedOddLogger.Error("Error storing odd other section", zap.Error(err)) continue } } @@ -157,10 +139,6 @@ func (s *ServiceImpl) fetchBet365Odds(ctx context.Context) error { } - for err := range errs { - log.Printf("❌ Error: %v", err) - } - return nil } @@ -345,6 +323,7 @@ func (s *ServiceImpl) ParseOddSections(ctx context.Context, res json.RawMessage, if err := json.Unmarshal(res, &footballRes); err != nil { s.mongoLogger.Error( "Failed to unmarshal football result", + zap.Error(err), ) return domain.ParseOddSectionsRes{}, err @@ -534,6 +513,10 @@ func (s *ServiceImpl) ParseOddSections(ctx context.Context, res json.RawMessage, func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName string, section domain.OddsSection) error { if len(section.Sp) == 0 { + s.mongoLogger.Warn("Event Section is empty", + zap.String("eventID", eventID), + zap.String("sectionName", sectionName), + ) return nil } @@ -542,41 +525,36 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName var errs []error for marketType, market := range section.Sp { + marketLogger := s.mongoLogger.With( + zap.String("eventID", eventID), + zap.String("sectionName", sectionName), + zap.String("market_id", string(market.ID)), + zap.String("marketType", marketType), + zap.String("marketName", market.Name), + ) if len(market.Odds) == 0 { + // marketLogger.Warn("Skipping market with no odds") continue } - // Check if the market id is a string - marketIDint := market.ID.Value - // if err != nil { - // s.mongoLogger.Error( - // "Invalid market id", - // zap.Int64("market_id", marketIDint), - // zap.String("market_name", market.Name), - // zap.String("eventID", eventID), - // zap.Error(err), - // ) - // continue - // } + marketIDint, err := strconv.ParseInt(string(market.ID), 10, 64) + if err != nil { + marketLogger.Warn("skipping market section where market_id is not int") + continue + } marketIDstr := strconv.FormatInt(marketIDint, 10) isSupported, ok := domain.SupportedMarkets[marketIDint] if !ok || !isSupported { - // s.logger.Info("Unsupported market_id", "marketID", marketIDint, "marketName", market.Name) + // marketLogger.Warn("skipping market that isn't supported", zap.Bool("is_market_found", ok)) continue } marketOdds, err := convertRawMessage(market.Odds) if err != nil { - s.mongoLogger.Error( - "failed to convert market.Odds to json.RawMessage to []map[string]interface{}", - zap.String("market_id", marketIDstr), - zap.String("market_name", market.Name), - zap.String("eventID", eventID), - zap.Error(err), - ) + marketLogger.Error("failed to convert market.Odds to json.RawMessage to []map[string]interface{}", zap.Error(err)) errs = append(errs, err) continue } @@ -593,25 +571,13 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName } if err := s.CheckAndInsertOddHistory(ctx, marketRecord); err != nil { - s.mongoLogger.Error( - "failed to check and insert odd history", - zap.String("market_id", marketIDstr), - zap.String("market_name", market.Name), - zap.String("eventID", eventID), - zap.Error(err), - ) + marketLogger.Error("failed to check and insert odd history", zap.Error(err)) continue } err = s.store.SaveOddMarket(ctx, marketRecord) if err != nil { - s.mongoLogger.Error( - "failed to save market", - zap.String("market_id", marketIDstr), - zap.String("market_name", market.Name), - zap.String("eventID", eventID), - zap.Error(err), - ) + marketLogger.Error("failed to save market", zap.Error(err)) errs = append(errs, fmt.Errorf("market %v: %w", market.ID, err)) continue } @@ -705,6 +671,46 @@ func (s *ServiceImpl) GetAllOddsWithSettings(ctx context.Context, companyID int6 return s.store.GetAllOddsWithSettings(ctx, companyID, filter) } +func (s *ServiceImpl) GetOddByID(ctx context.Context, id int64) (domain.OddMarket, error) { + return s.store.GetOddByID(ctx, id) +} + +func (s *ServiceImpl) SaveOddsSetting(ctx context.Context, odd domain.CreateOddMarketSettings) error { + return s.store.SaveOddsSetting(ctx, odd) +} + +func (s *ServiceImpl) SaveOddsSettingReq(ctx context.Context, companyID int64, req domain.CreateOddMarketSettingsReq) error { + + odd, err := s.GetOddsWithSettingsByID(ctx, req.OddMarketID, companyID) + if err != nil { + return err + } + + newOdds, err := convertRawMessage(odd.RawOdds) + if err != nil { + return err + } + + if len(req.CustomOdd) != 0 { + for _, customOdd := range req.CustomOdd { + for _, newOdd := range newOdds { + oldRawOddID := getInt(newOdd["id"]) + + if oldRawOddID == int(customOdd.OddID) { + newOdd["odds"] = customOdd.OddValue + } + } + } + } + + return s.SaveOddsSetting(ctx, domain.CreateOddMarketSettings{ + CompanyID: companyID, + OddMarketID: req.OddMarketID, + IsActive: domain.ConvertBoolPtr(req.IsActive), + CustomRawOdds: newOdds, + }) +} + func (s *ServiceImpl) GetOddsByMarketID(ctx context.Context, marketID string, eventID string) (domain.OddMarket, error) { rows, err := s.store.GetOddsByMarketID(ctx, marketID, eventID) if err != nil { @@ -722,6 +728,10 @@ func (s *ServiceImpl) GetOddsWithSettingsByMarketID(ctx context.Context, marketI return rows, nil } +func (s *ServiceImpl) GetOddsWithSettingsByID(ctx context.Context, ID int64, companyID int64) (domain.OddMarketWithSettings, error) { + return s.store.GetOddsWithSettingsByID(ctx, ID, companyID) +} + func (s *ServiceImpl) GetOddsByEventID(ctx context.Context, upcomingID string, filter domain.OddMarketWithEventFilter) ([]domain.OddMarket, error) { return s.store.GetOddsByEventID(ctx, upcomingID, filter) } diff --git a/internal/services/raffle/port.go b/internal/services/raffle/port.go new file mode 100644 index 0000000..39f5bfa --- /dev/null +++ b/internal/services/raffle/port.go @@ -0,0 +1,24 @@ +package raffle + +import ( + "context" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type RaffleStore interface { + CreateRaffle(ctx context.Context, raffle domain.CreateRaffle) (domain.Raffle, error) + AddSportRaffleFilter(ctx context.Context, raffleID int32, sportID, leagueID int64) error + DeleteRaffle(ctx context.Context, raffleID int32) (domain.Raffle, error) + GetRafflesOfCompany(ctx context.Context, companyID int32) ([]dbgen.Raffle, error) + GetRaffleStanding(ctx context.Context, raffleID, limit int32) ([]domain.RaffleStanding, error) + 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) + + 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 +} diff --git a/internal/services/raffle/service.go b/internal/services/raffle/service.go new file mode 100644 index 0000000..017d164 --- /dev/null +++ b/internal/services/raffle/service.go @@ -0,0 +1,66 @@ +package raffle + +import ( + "context" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" +) + +type Service struct { + raffleStore RaffleStore +} + +func NewService(raffleStore RaffleStore) *Service { + return &Service{ + raffleStore: raffleStore, + } +} + +func (s *Service) CreateRaffle(ctx context.Context, raffle domain.CreateRaffle) (domain.Raffle, error) { + return s.raffleStore.CreateRaffle(ctx, raffle) +} + +func (s *Service) AddSportRaffleFilter(ctx context.Context, raffleID int32, sportID, leagueID int64) error { + return s.raffleStore.AddSportRaffleFilter(ctx, raffleID, sportID, leagueID) +} + +func (s *Service) DeleteRaffle(ctx context.Context, raffleID int32) (domain.Raffle, error) { + return s.raffleStore.DeleteRaffle(ctx, raffleID) +} + +func (s *Service) GetRafflesOfCompany(ctx context.Context, companyID int32) ([]dbgen.Raffle, error) { + return s.raffleStore.GetRafflesOfCompany(ctx, companyID) +} + +func (s *Service) GetRaffleStanding(ctx context.Context, raffleID, limit int32) ([]domain.RaffleStanding, error) { + return s.raffleStore.GetRaffleStanding(ctx, raffleID, limit) +} + +func (s *Service) CreateRaffleWinner(ctx context.Context, raffleWinnerParams domain.RaffleWinnerParams) error { + return s.raffleStore.CreateRaffleWinner(ctx, raffleWinnerParams) +} + +func (s *Service) SetRaffleComplete(ctx context.Context, raffleID int32) error { + return s.raffleStore.SetRaffleComplete(ctx, raffleID) +} + +func (s *Service) CreateRaffleTicket(ctx context.Context, raffleTicketParams domain.CreateRaffleTicket) (domain.RaffleTicket, error) { + return s.raffleStore.CreateRaffleTicket(ctx, raffleTicketParams) +} + +func (s *Service) GetUserRaffleTickets(ctx context.Context, userID int32) ([]domain.RaffleTicketRes, error) { + return s.raffleStore.GetUserRaffleTickets(ctx, userID) +} + +func (s *Service) SuspendRaffleTicket(ctx context.Context, raffleTicketID int32) error { + return s.raffleStore.SuspendRaffleTicket(ctx, raffleTicketID) +} + +func (s *Service) UnSuspendRaffleTicket(ctx context.Context, raffleID int32) error { + return s.raffleStore.UnSuspendRaffleTicket(ctx, raffleID) +} + +func (s *Service) CheckValidSportRaffleFilter(ctx context.Context, raffleID int32, sportID, leagueID int64) (bool, error) { + return s.raffleStore.CheckValidSportRaffleFilter(ctx, raffleID, sportID, leagueID) +} diff --git a/internal/services/referal/port.go b/internal/services/referal/port.go index 6add199..6930c0e 100644 --- a/internal/services/referal/port.go +++ b/internal/services/referal/port.go @@ -8,13 +8,10 @@ import ( type ReferralStore interface { GenerateReferralCode() (string, error) - CreateReferral(ctx context.Context, userID int64) error - ProcessReferral(ctx context.Context, referredPhone, referralCode string, companyID int64) error - ProcessDepositBonus(ctx context.Context, userID string, amount float64) error - GetReferralStats(ctx context.Context, userID string) (*domain.ReferralStats, error) - CreateReferralSettings(ctx context.Context, req domain.ReferralSettingsReq) error - UpdateReferralSettings(ctx context.Context, settings *domain.ReferralSettings) error - GetReferralSettings(ctx context.Context) (*domain.ReferralSettings, error) - GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) - ProcessBetReferral(ctx context.Context, userPhone string, betAmount float64) error + CreateReferral(ctx context.Context, userID int64, companyID int64) error + ProcessReferral(ctx context.Context, referredPhone, referralCode string, companyID int64) error + ProcessDepositBonus(ctx context.Context, userPhone string, amount float64) error + ProcessBetReferral(ctx context.Context, userId int64, betAmount float64) error + GetReferralStats(ctx context.Context, userID int64, companyID int64) (*domain.ReferralStats, error) + GetReferralCountByID(ctx context.Context, referrerID int64) (int64, error) } diff --git a/internal/services/referal/service.go b/internal/services/referal/service.go index d89b023..eb5b021 100644 --- a/internal/services/referal/service.go +++ b/internal/services/referal/service.go @@ -7,333 +7,252 @@ import ( "errors" "fmt" "log/slog" - "strconv" - "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/config" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" + "go.uber.org/zap" ) type Service struct { - repo repository.ReferralRepository - walletSvc wallet.Service - store *repository.Store - config *config.Config - logger *slog.Logger + repo repository.ReferralRepository + walletSvc wallet.Service + settingSvc settings.Service + config *config.Config + logger *slog.Logger + mongoLogger *zap.Logger } -func New(repo repository.ReferralRepository, walletSvc wallet.Service, store *repository.Store, cfg *config.Config, logger *slog.Logger) *Service { +func New(repo repository.ReferralRepository, walletSvc wallet.Service, settingSvc settings.Service, cfg *config.Config, logger *slog.Logger, mongoLogger *zap.Logger) *Service { return &Service{ - repo: repo, - walletSvc: walletSvc, - store: store, - config: cfg, - logger: logger, + repo: repo, + walletSvc: walletSvc, + settingSvc: settingSvc, + config: cfg, + logger: logger, + mongoLogger: mongoLogger, } } var ( - ErrInvalidReferral = errors.New("invalid or expired referral") - ErrInvalidReferralSignup = errors.New("referral requires phone signup") - ErrUserNotFound = errors.New("user not found") - ErrNoReferralFound = errors.New("no referral found for this user") + ErrInvalidReferral = errors.New("invalid or expired referral") + ErrUserNotFound = errors.New("user not found") + ErrNoReferralFound = errors.New("no referral found for this user") + ErrUserAlreadyHasReferralCode = errors.New("user already has an active referral code") + ErrMaxReferralCountLimitReached = errors.New("referral count limit has been reached") ) func (s *Service) GenerateReferralCode() (string, error) { b := make([]byte, 8) if _, err := rand.Read(b); err != nil { - s.logger.Error("Failed to generate random bytes for referral code", "error", err) + s.mongoLogger.Error("Failed to generate random bytes for referral code", zap.Error(err)) return "", err } code := base32.StdEncoding.EncodeToString(b)[:10] - s.logger.Debug("Generated referral code", "code", code) + s.mongoLogger.Debug("Generated referral code", zap.String("code", code)) return code, nil } -func (s *Service) CreateReferral(ctx context.Context, userID int64) error { - s.logger.Info("Creating referral code for user", "userID", userID) +func (s *Service) CreateReferralCode(ctx context.Context, userID int64, companyID int64) (domain.ReferralCode, error) { + + settingsList, err := s.settingSvc.GetOverrideSettingsList(ctx, companyID) + + if err != nil { + s.mongoLogger.Error("Failed to fetch settings", zap.Error(err)) + return domain.ReferralCode{}, err + } // check if user already has an active referral code - referral, err := s.repo.GetActiveReferralByReferrerID(ctx, fmt.Sprintf("%d", userID)) + referralCodes, err := s.repo.GetReferralCodesByUser(ctx, userID) if err != nil { - s.logger.Error("Failed to check if user alredy has active referral code", "error", err) - return err + s.mongoLogger.Error("Failed to check if user already has active referral code", zap.Int64("userID", userID), zap.Error(err)) + return domain.ReferralCode{}, err } - if referral != nil && referral.Status == domain.ReferralPending && referral.ExpiresAt.After(time.Now()) { - s.logger.Error("user already has an active referral code", "error", err) - return err - } - - settings, err := s.GetReferralSettings(ctx) - if err != nil || settings == nil { - s.logger.Error("Failed to fetch referral settings", "error", err) - return err - } - - // check referral count limit - referralCount, err := s.GetReferralCountByID(ctx, fmt.Sprintf("%d", userID)) - if err != nil { - s.logger.Error("Failed to get referral count", "userID", userID, "error", err) - return err - } - - fmt.Println("referralCount: ", referralCount) - if referralCount == int64(settings.MaxReferrals) { - s.logger.Error("referral count limit has been reached", "referralCount", referralCount, "error", err) - return err + if len(referralCodes) != 0 { + s.mongoLogger.Error("user already has an active referral code", zap.Int64("userID", userID), zap.Any("codes", referralCodes), zap.Error(err)) + return domain.ReferralCode{}, ErrUserAlreadyHasReferralCode } code, err := s.GenerateReferralCode() if err != nil { - s.logger.Error("Failed to generate referral code", "error", err) - return err + return domain.ReferralCode{}, err } - var rewardAmount float64 = settings.ReferralRewardAmount - var expireDuration time.Time = time.Now().Add(time.Duration((24 * settings.ExpiresAfterDays)) * time.Hour) + newReferralCode, err := s.repo.CreateReferralCode(ctx, domain.CreateReferralCode{ + ReferrerID: userID, + ReferralCode: code, + CompanyID: companyID, + NumberOfReferrals: settingsList.DefaultMaxReferrals, + RewardAmount: settingsList.ReferralRewardAmount, + }) - if err := s.repo.CreateReferral(ctx, &domain.Referral{ - ReferralCode: code, - ReferrerID: fmt.Sprintf("%d", userID), - Status: domain.ReferralPending, - RewardAmount: rewardAmount, - ExpiresAt: expireDuration, - }); err != nil { - return err + if err != nil { + return domain.ReferralCode{}, err } - return nil + return newReferralCode, nil } -func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCode string, companyID int64) error { - s.logger.Info("Processing referral", "referredPhone", referredPhone, "referralCode", referralCode) - - referral, err := s.repo.GetReferralByCode(ctx, referralCode) - if err != nil || referral == nil { - s.logger.Error("Failed to get referral by code", "referralCode", referralCode, "error", err) - return err - } - - if referral.Status != domain.ReferralPending || referral.ExpiresAt.Before(time.Now()) { - s.logger.Warn("Invalid or expired referral", "referralCode", referralCode, "status", referral.Status) - return ErrInvalidReferral - } - - user, err := s.store.GetUserByPhone(ctx, referredPhone, domain.ValidInt64{ - Value: companyID, - Valid: true, - }) +func (s *Service) ProcessReferral(ctx context.Context, referredID int64, referralCode string, companyID int64) error { + paramLogger := s.mongoLogger.With( + zap.Int64("referredID", referredID), + zap.String("referralCode", referralCode), + zap.Int64("companyID", companyID), + ) + referral, err := s.repo.GetReferralCode(ctx, referralCode) if err != nil { - if errors.Is(err, domain.ErrUserNotFound) { - s.logger.Warn("User not found for referral", "referredPhone", referredPhone) - return ErrUserNotFound - } - s.logger.Error("Failed to get user by phone", "referredPhone", referredPhone, "error", err) - return err - } - if !user.PhoneVerified { - s.logger.Warn("Phone not verified for referral", "referredPhone", referredPhone) - return ErrInvalidReferralSignup - } - - referral.ReferredID = &referredPhone - referral.Status = domain.ReferralCompleted - referral.UpdatedAt = time.Now() - - if err := s.repo.UpdateReferral(ctx, referral); err != nil { - s.logger.Error("Failed to update referral", "referralCode", referralCode, "error", err) + paramLogger.Error("Failed to get referral by code", zap.Error(err)) return err } - referrerId, err := strconv.Atoi(referral.ReferrerID) + wallets, err := s.walletSvc.GetCustomerWallet(ctx, referral.ReferrerID) if err != nil { - s.logger.Error("Failed to convert referrer id", "referrerId", referral.ReferrerID, "error", err) - return err - } - - wallets, err := s.store.GetCustomerWallet(ctx, int64(referrerId)) - if err != nil { - s.logger.Error("Failed to get referrer wallets", "referrerId", referral.ReferrerID, "error", err) + paramLogger.Error("Failed to get referrer wallets", zap.Error(err)) return err } _, err = s.walletSvc.AddToWallet(ctx, wallets.StaticID, - domain.ToCurrency(float32(referral.RewardAmount)), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, - fmt.Sprintf("Added %v to static wallet because of referral ID %v", referral.RewardAmount, referrerId), + referral.RewardAmount, domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, + fmt.Sprintf("Added %v to static wallet due to %v referral code being used", referral.RewardAmount, referral.ReferralCode), ) if err != nil { - s.logger.Error("Failed to add referral reward to static wallet", "walletID", wallets.StaticID, "referrer phone number", referredPhone, "error", err) + paramLogger.Error("Failed to add referral reward to static wallet", zap.Int64("static_wallet_id", wallets.StaticID), zap.Error(err)) return err } - s.logger.Info("Referral processed successfully", "referredPhone", referredPhone, "referralCode", referralCode, "rewardAmount", referral.RewardAmount) + _, err = s.repo.CreateUserReferral(ctx, domain.CreateUserReferrals{ + ReferredID: referredID, + ReferralCodeID: referral.ID, + }) + + if err != nil { + paramLogger.Error("Failed to create user referral", zap.Error(err)) + return err + } + + paramLogger.Info("Referral processed successfully", zap.String("rewardAmount", referral.ReferralCode)) return nil } -func (s *Service) ProcessDepositBonus(ctx context.Context, userPhone string, amount float64) error { - s.logger.Info("Processing deposit bonus", "userPhone", userPhone, "amount", amount) +func (s *Service) GetReferralStats(ctx context.Context, userID int64, companyID int64) (domain.ReferralStats, error) { + paramLogger := s.mongoLogger.With(zap.Int64("userID", userID), zap.Int64("companyID", companyID)) - settings, err := s.repo.GetSettings(ctx) + stats, err := s.repo.GetReferralStats(ctx, userID, companyID) if err != nil { - s.logger.Error("Failed to get referral settings", "error", err) - return err + paramLogger.Error("Failed to get referral stats", zap.Error(err)) + return domain.ReferralStats{}, err } - userID, err := strconv.ParseInt(userPhone, 10, 64) - if err != nil { - s.logger.Error("Invalid phone number format", "userPhone", userPhone, "error", err) - return errors.New("invalid phone number format") - } - - wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID) - if err != nil { - s.logger.Error("Failed to get wallets for user", "userID", userID, "error", err) - return err - } - if len(wallets) == 0 { - s.logger.Error("User has no wallet", "userID", userID) - return errors.New("user has no wallet") - } - - walletID := wallets[0].ID - bonus := amount * (settings.CashbackPercentage / 100) - currentBonus := float64(wallets[0].Balance) - _, err = s.walletSvc.AddToWallet(ctx, walletID, domain.ToCurrency(float32(currentBonus+bonus)), domain.ValidInt64{}, - domain.TRANSFER_DIRECT, domain.PaymentDetails{}, - fmt.Sprintf("Added %v to static wallet because of Deposit Cashback Bonus %d", currentBonus+bonus, bonus)) - if err != nil { - s.logger.Error("Failed to add deposit bonus to wallet", "walletID", walletID, "userID", userID, "bonus", bonus, "error", err) - return err - } - - s.logger.Info("Deposit bonus processed successfully", "userPhone", userPhone, "bonus", bonus) - return nil -} - -func (s *Service) ProcessBetReferral(ctx context.Context, userPhone string, betAmount float64) error { - s.logger.Info("Processing bet referral", "userPhone", userPhone, "betAmount", betAmount) - - settings, err := s.repo.GetSettings(ctx) - if err != nil { - s.logger.Error("Failed to get referral settings", "error", err) - return err - } - - referral, err := s.repo.GetReferralByReferredID(ctx, userPhone) - if err != nil { - s.logger.Error("Failed to get referral by referred ID", "userPhone", userPhone, "error", err) - return err - } - if referral == nil || referral.Status != domain.ReferralCompleted { - s.logger.Warn("No valid referral found", "userPhone", userPhone, "status", referral.Status) - return ErrNoReferralFound - } - - referrerID, err := strconv.ParseInt(referral.ReferrerID, 10, 64) - if err != nil { - s.logger.Error("Invalid referrer phone number format", "referrerID", referral.ReferrerID, "error", err) - return errors.New("invalid referrer phone number format") - } - - wallets, err := s.walletSvc.GetWalletsByUser(ctx, referrerID) - if err != nil { - s.logger.Error("Failed to get wallets for referrer", "referrerID", referrerID, "error", err) - return err - } - if len(wallets) == 0 { - s.logger.Error("Referrer has no wallet", "referrerID", referrerID) - return errors.New("referrer has no wallet") - } - - bonusPercentage := settings.BetReferralBonusPercentage - if bonusPercentage == 0 { - bonusPercentage = 5.0 - s.logger.Debug("Using default bet referral bonus percentage", "percentage", bonusPercentage) - } - bonus := betAmount * (bonusPercentage / 100) - - walletID := wallets[0].ID - currentBalance := float64(wallets[0].Balance) - _, err = s.walletSvc.AddToWallet(ctx, walletID, domain.ToCurrency(float32(currentBalance+bonus)), domain.ValidInt64{}, - domain.TRANSFER_DIRECT, domain.PaymentDetails{}, - fmt.Sprintf("Added %v to static wallet because of bet referral", referral.RewardAmount)) - if err != nil { - s.logger.Error("Failed to add bet referral bonus to wallet", "walletID", walletID, "referrerID", referrerID, "bonus", bonus, "error", err) - return err - } - - s.logger.Info("Bet referral processed successfully", "userPhone", userPhone, "referrerID", referrerID, "bonus", bonus) - return nil -} - -func (s *Service) GetReferralStats(ctx context.Context, userPhone string) (*domain.ReferralStats, error) { - s.logger.Info("Fetching referral stats", "userPhone", userPhone) - - stats, err := s.repo.GetReferralStats(ctx, userPhone) - if err != nil { - s.logger.Error("Failed to get referral stats", "userPhone", userPhone, "error", err) - return nil, err - } - - s.logger.Info("Referral stats retrieved successfully", "userPhone", userPhone, "totalReferrals", stats.TotalReferrals) return stats, nil } -func (s *Service) CreateReferralSettings(ctx context.Context, req domain.ReferralSettingsReq) error { - s.logger.Info("Creating referral setting") - - if err := s.repo.CreateSettings(ctx, &domain.ReferralSettings{ - ReferralRewardAmount: req.ReferralRewardAmount, - CashbackPercentage: req.CashbackPercentage, - MaxReferrals: req.MaxReferrals, - ExpiresAfterDays: req.ExpiresAfterDays, - UpdatedBy: req.UpdatedBy, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - }); err != nil { - s.logger.Error("Failed to create referral setting", "error", err) - return err - } - - s.logger.Info("Referral setting created succesfully") - return nil -} - -func (s *Service) UpdateReferralSettings(ctx context.Context, settings *domain.ReferralSettings) error { - s.logger.Info("Updating referral settings", "settingsID", settings.ID) - - settings.UpdatedAt = time.Now() - err := s.repo.UpdateSettings(ctx, settings) +func (s *Service) GetUserReferralCount(ctx context.Context, referrerID int64) (int64, error) { + count, err := s.repo.GetUserReferralCount(ctx, referrerID) if err != nil { - s.logger.Error("Failed to update referral settings", "settingsID", settings.ID, "error", err) - return err - } - - s.logger.Info("Referral settings updated successfully", "settingsID", settings.ID) - return nil -} - -func (s *Service) GetReferralSettings(ctx context.Context) (*domain.ReferralSettings, error) { - s.logger.Info("Fetching referral settings") - - settings, err := s.repo.GetSettings(ctx) - if err != nil { - s.logger.Error("Failed to get referral settings", "error", err) - return nil, err - } - - s.logger.Info("Referral settings retrieved successfully", "settings", settings) - return settings, nil -} - -func (s *Service) GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) { - count, err := s.repo.GetReferralCountByID(ctx, referrerID) - if err != nil { - s.logger.Error("Failed to get referral count", "userID", referrerID, "error", err) + s.mongoLogger.Error("Failed to get referral count", zap.Int64("referrerID", referrerID), zap.Error(err)) return 0, err } return count, nil } + +func (s *Service) GetReferralCodesByUser(ctx context.Context, userID int64) ([]domain.ReferralCode, error) { + return s.repo.GetReferralCodesByUser(ctx, userID) +} + +func (s *Service) GetReferralCode(ctx context.Context, code string) (domain.ReferralCode, error) { + return s.repo.GetReferralCode(ctx, code) +} + +func (s *Service) UpdateReferralCode(ctx context.Context, referral domain.UpdateReferralCode) error { + return s.repo.UpdateReferralCode(ctx, referral) +} + +func (s *Service) GetUserReferral(ctx context.Context, referrerID int64) (domain.UserReferral, error) { + return s.repo.GetUserReferral(ctx, referrerID) +} + +func (s *Service) GetUserReferralsByCode(ctx context.Context, code string) ([]domain.UserReferral, error) { + return s.repo.GetUserReferralsByCode(ctx, code) +} + +// func (s *Service) ProcessDepositBonus(ctx context.Context, userID int64, amount float32, companyID int64) error { +// settingsList, err := s.settingSvc.GetOverrideSettingsList(ctx, companyID) +// if err != nil { +// s.logger.Error("Failed to fetch settings", "error", err) +// return err +// } + +// s.logger.Info("Processing deposit bonus", "amount", amount) + +// customerWallet, err := s.walletSvc.GetCustomerWallet(ctx, userID) +// if err != nil { +// s.logger.Error("Failed to get wallets for user", "userID", userID, "error", err) +// return err +// } + +// bonus := amount * settingsList.CashbackPercentage + +// _, err = s.walletSvc.AddToWallet(ctx, customerWallet.StaticID, domain.ToCurrency(bonus), domain.ValidInt64{}, +// domain.TRANSFER_DIRECT, domain.PaymentDetails{}, +// fmt.Sprintf("Added to bonus wallet because of Deposit Cashback Bonus %d", bonus)) +// if err != nil { +// s.logger.Error("Failed to add deposit bonus to wallet", "staticWalletID", customerWallet.StaticID, "userID", userID, "bonus", bonus, "error", err) +// return err +// } + +// s.logger.Info("Deposit bonus processed successfully", "bonus", bonus) +// return nil +// } + +// func (s *Service) ProcessBetReferral(ctx context.Context, userId int64, betAmount float64) error { +// s.logger.Info("Processing bet referral", "userID", userId, "betAmount", betAmount) + +// settings, err := s.repo.GetSettings(ctx) +// if err != nil { +// s.logger.Error("Failed to get referral settings", "error", err) +// return err +// } + +// referral, err := s.repo.GetReferralByReferredID(ctx, userId) +// if err != nil { +// s.logger.Error("Failed to get referral by referred ID", "userId", userId, "error", err) +// return err +// } +// if referral == nil || referral.Status != domain.ReferralCompleted { +// s.logger.Warn("No valid referral found", "userId", userId, "status", referral.Status) +// return ErrNoReferralFound +// } + +// wallets, err := s.walletSvc.GetWalletsByUser(ctx, referral.ReferrerID) +// if err != nil { +// s.logger.Error("Failed to get wallets for referrer", "referrerID", referral.ReferrerID, "error", err) +// return err +// } +// if len(wallets) == 0 { +// s.logger.Error("Referrer has no wallet", "referrerID", referral.ReferrerID) +// return errors.New("referrer has no wallet") +// } + +// bonusPercentage := settings.BetReferralBonusPercentage +// if bonusPercentage == 0 { +// bonusPercentage = 5.0 +// s.logger.Debug("Using default bet referral bonus percentage", "percentage", bonusPercentage) +// } +// bonus := betAmount * (bonusPercentage / 100) + +// walletID := wallets[0].ID +// currentBalance := float64(wallets[0].Balance) +// _, err = s.walletSvc.AddToWallet(ctx, walletID, domain.ToCurrency(float32(currentBalance+bonus)), domain.ValidInt64{}, +// domain.TRANSFER_DIRECT, domain.PaymentDetails{}, +// fmt.Sprintf("Added %v to static wallet because of bet referral", referral.RewardAmount)) +// if err != nil { +// s.logger.Error("Failed to add bet referral bonus to wallet", "walletID", walletID, "referrerID", referral.ReferrerID, "bonus", bonus, "error", err) +// return err +// } + +// s.logger.Info("Bet referral processed successfully", "referrer ID", referral.ReferrerID, "referrerID", referral.ReferrerID, "bonus", bonus) +// return nil +// } diff --git a/internal/services/result/service.go b/internal/services/result/service.go index ad1f1c5..cb19963 100644 --- a/internal/services/result/service.go +++ b/internal/services/result/service.go @@ -16,6 +16,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "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" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" @@ -33,6 +34,7 @@ type Service struct { eventSvc event.Service leagueSvc league.Service notificationSvc *notificationservice.Service + messengerSvc *messenger.Service userSvc user.Service } @@ -46,6 +48,7 @@ func NewService( eventSvc event.Service, leagueSvc league.Service, notificationSvc *notificationservice.Service, + messengerSvc *messenger.Service, userSvc user.Service, ) *Service { return &Service{ @@ -59,6 +62,7 @@ func NewService( eventSvc: eventSvc, leagueSvc: leagueSvc, notificationSvc: notificationSvc, + messengerSvc: messengerSvc, userSvc: userSvc, } } @@ -491,6 +495,7 @@ func (s *Service) CheckAndSendResultNotifications(ctx context.Context, createdAf } func buildHeadlineAndMessage(counts domain.ResultLog) (string, string) { + totalIssues := counts.StatusNotFinishedCount + counts.StatusToBeFixedCount + counts.StatusPostponedCount + counts.StatusRemovedCount totalBets := counts.StatusEndedBets + counts.StatusNotFinishedBets + counts.StatusPostponedBets + counts.StatusRemovedBets + counts.StatusToBeFixedBets if totalIssues == 0 { @@ -517,10 +522,123 @@ func buildHeadlineAndMessage(counts domain.ResultLog) (string, string) { } headline := "⚠️ Issues Found Processing Event Results" - message := fmt.Sprintf("Processed expired event results: %s. Please review pending entries.", strings.Join(parts, ", ")) + message := fmt.Sprintf("Processed expired event results: %s. Please review pending entries.", strings.Join(parts, ", ")) return headline, message } +func buildHeadlineAndMessageEmail(counts domain.ResultLog, user domain.User) (string, string, string) { + 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 := "✅ Daily Results Report — All Events Processed Successfully" + plain := fmt.Sprintf(`%s + +Daily Results Summary: +- %d Ended Events +- %d Total Bets + +All events were processed successfully, and no issues were detected. + +Best regards, +The System`, greeting, counts.StatusEndedCount, totalBets) + + html := fmt.Sprintf(`
%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