From 6b09c3c8d2698a6543c845fe5fe678d86796d8c5 Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Wed, 3 Sep 2025 03:16:37 +0300 Subject: [PATCH] fix: resolved issue on event, odds and creating a bet --- cmd/main.go | 7 +- db/data/001_initial_seed_data.sql | 10 +- db/data/002_veli_user.sql | 4 +- db/migrations/000001_fortune.up.sql | 32 ++- db/query/events.sql | 72 +++++-- db/query/leagues.sql | 13 +- db/query/odds.sql | 80 ++++++-- gen/db/events.sql.go | 187 ++++++++++++++---- gen/db/leagues.sql.go | 34 +++- gen/db/models.go | 12 +- gen/db/odds.sql.go | 160 ++++++++++++--- internal/domain/bet.go | 1 + internal/domain/event.go | 70 +++---- internal/domain/league.go | 4 +- internal/domain/notification.go | 2 +- internal/domain/oddres.go | 2 +- internal/domain/setting_list.go | 28 ++- internal/repository/company.go | 6 +- internal/repository/event.go | 113 ++++++++++- internal/repository/league.go | 28 ++- internal/repository/notification.go | 14 +- internal/repository/odds.go | 137 ++++++++++--- internal/repository/shop_bet.go | 5 +- internal/repository/ticket.go | 1 + internal/services/bet/service.go | 37 ++-- internal/services/event/service.go | 103 +++++----- internal/services/notification/service.go | 30 ++- internal/services/result/service.go | 3 +- internal/services/transaction/shop_bet.go | 5 + internal/services/wallet/direct_deposit.go | 4 +- internal/services/wallet/service.go | 3 - internal/services/wallet/transfer.go | 4 +- internal/services/wallet/wallet.go | 21 +- internal/web_server/handlers/bet_handler.go | 16 +- internal/web_server/handlers/event_handler.go | 1 + .../handlers/notification_handler.go | 14 +- internal/web_server/handlers/shop_handler.go | 13 +- .../web_server/handlers/ticket_handler.go | 4 + 38 files changed, 952 insertions(+), 328 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 38797ae..fe8f4f1 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -112,13 +112,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) 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 @@ -130,7 +128,6 @@ func main() { wallet.WalletStore(store), wallet.TransferStore(store), wallet.DirectDepositStore(store), - notificatioStore, notificationSvc, userSvc, domain.MongoDBLogger, @@ -153,7 +150,7 @@ func main() { 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), cfg) recommendationSvc := recommendation.NewService(recommendationRepo) chapaClient := chapa.NewClient(cfg.CHAPA_BASE_URL, cfg.CHAPA_SECRET_KEY) diff --git a/db/data/001_initial_seed_data.sql b/db/data/001_initial_seed_data.sql index 9081dee..eb64f5c 100644 --- a/db/data/001_initial_seed_data.sql +++ b/db/data/001_initial_seed_data.sql @@ -1,5 +1,4 @@ CREATE EXTENSION IF NOT EXISTS pgcrypto; - -- Locations Initial Data INSERT INTO branch_locations (key, value) VALUES ('addis_ababa', 'Addis Ababa'), @@ -75,7 +74,6 @@ VALUES ('addis_ababa', 'Addis Ababa'), ('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'), @@ -84,8 +82,8 @@ VALUES ('sms_provider', 'afro_message'), ('daily_ticket_limit', '50'), ('total_winnings_limit', '1000000'), ('amount_for_bet_referral', '1000000'), - ('cashback_amount_cap', '1000') ON CONFLICT (key) DO NOTHING; - + ('cashback_amount_cap', '1000'), + ('default_winning_limit', '5000000') ON CONFLICT (key) DO NOTHING; -- Users INSERT INTO users ( id, @@ -224,7 +222,7 @@ VALUES ( ), ( 3, - 20000, + 100000000 , TRUE, TRUE, TRUE, @@ -237,7 +235,7 @@ VALUES ( ), ( 4, - 15000, + 50000000, TRUE, TRUE, TRUE, diff --git a/db/data/002_veli_user.sql b/db/data/002_veli_user.sql index 5323bac..1dfe96a 100644 --- a/db/data/002_veli_user.sql +++ b/db/data/002_veli_user.sql @@ -33,7 +33,7 @@ VALUES ( 6, 'Kirubel', 'Kibru', - 'modernkibru @gmail.com', + 'modernkibru@gmail.com', NULL, crypt('password@123', gen_salt('bf'))::bytea, 'customer', @@ -98,7 +98,7 @@ VALUES ( ), ( 7, - 10000, + 1000000, TRUE, TRUE, TRUE, diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 6af0cb2..898d587 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -36,28 +36,24 @@ CREATE TABLE IF NOT EXISTS virtual_game_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, @@ -325,7 +321,7 @@ CREATE TABLE events ( ), 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) ); @@ -621,7 +617,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, @@ -634,7 +630,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.*, @@ -656,7 +652,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, diff --git a/db/query/events.sql b/db/query/events.sql index 82c7ed8..61f5a86 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,6 +54,7 @@ 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: SaveEventSettings :exec INSERT INTO company_event_settings ( @@ -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,29 @@ 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 sqlc.narg('is_featured') 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 +212,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 +229,12 @@ 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 sqlc.narg('is_featured') IS NULL +) ORDER BY start_time ASC LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); -- name: GetUpcomingByID :one @@ -223,10 +245,21 @@ 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; @@ -243,7 +276,6 @@ WHERE id = $1; UPDATE events SET is_monitored = $1 WHERE id = $2; - -- name: DeleteEvent :exec DELETE FROM events WHERE id = $1; \ No newline at end of file diff --git a/db/query/leagues.sql b/db/query/leagues.sql index 1063480..21765b9 100644 --- a/db/query/leagues.sql +++ b/db/query/leagues.sql @@ -43,10 +43,14 @@ WHERE ( 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 ( +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 ) @@ -64,7 +68,6 @@ WHERE (company_id = $1) ) AND ( name ILIKE '%' || sqlc.narg('query') || '%' - OR league_name ILIKE '%' || sqlc.narg('query') || '%' OR sqlc.narg('query') IS NULL ) ORDER BY is_featured DESC, diff --git a/db/query/odds.sql b/db/query/odds.sql index 0a4916e..dc467c6 100644 --- a/db/query/odds.sql +++ b/db/query/odds.sql @@ -42,9 +42,22 @@ 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 * @@ -56,16 +69,42 @@ 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 * -FROM odds_market_with_settings -WHERE id = $1 - AND company_id = $2; +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 @@ -84,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/gen/db/events.sql.go b/gen/db/events.sql.go index 0932419..3ee77ad 100644 --- a/gen/db/events.sql.go +++ b/gen/db/events.sql.go @@ -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,14 @@ 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 $8 IS NULL +) ORDER BY start_time ASC -LIMIT $9 OFFSET $8 +LIMIT $10 OFFSET $9 ` type GetEventsWithSettingsParams struct { @@ -175,11 +234,46 @@ 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"` 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 +282,7 @@ func (q *Queries) GetEventsWithSettings(ctx context.Context, arg GetEventsWithSe arg.LastStartTime, arg.FirstStartTime, arg.CountryCode, + arg.IsFeatured, arg.Offset, arg.Limit, ) @@ -195,9 +290,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, @@ -403,16 +498,18 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat 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 +526,13 @@ 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 $8 IS NULL + ) ` type GetTotalCompanyEventsParams struct { @@ -442,6 +543,7 @@ 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"` } func (q *Queries) GetTotalCompanyEvents(ctx context.Context, arg GetTotalCompanyEventsParams) (int64, error) { @@ -453,6 +555,7 @@ func (q *Queries) GetTotalCompanyEvents(ctx context.Context, arg GetTotalCompany arg.LastStartTime, arg.FirstStartTime, arg.CountryCode, + arg.IsFeatured, ) var count int64 err := row.Scan(&count) @@ -573,7 +676,8 @@ INSERT INTO events ( start_time, is_live, status, - source + source, + default_winning_upper_limit ) VALUES ( $1, @@ -590,7 +694,8 @@ VALUES ( $12, $13, $14, - $15 + $15, + $16 ) ON CONFLICT (id) DO UPDATE SET sport_id = EXCLUDED.sport_id, @@ -603,7 +708,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 +716,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,6 +756,7 @@ func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) error arg.IsLive, arg.Status, arg.Source, + arg.DefaultWinningUpperLimit, ) return err } diff --git a/gen/db/leagues.sql.go b/gen/db/leagues.sql.go index ff9ea19..57d3c28 100644 --- a/gen/db/leagues.sql.go +++ b/gen/db/leagues.sql.go @@ -96,10 +96,14 @@ 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 ) @@ -117,7 +121,6 @@ WHERE (company_id = $1) ) AND ( name ILIKE '%' || $6 || '%' - OR league_name ILIKE '%' || $6 || '%' OR $6 IS NULL ) ORDER BY is_featured DESC, @@ -136,7 +139,22 @@ type GetAllLeaguesWithSettingsParams struct { 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, @@ -151,9 +169,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, diff --git a/gen/db/models.go b/gen/db/models.go index 428889c..e41c659 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -321,7 +321,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 +356,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 +385,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 +447,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 +520,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"` diff --git a/gen/db/odds.sql.go b/gen/db/odds.sql.go index a0bb5a9..79da894 100644 --- a/gen/db/odds.sql.go +++ b/gen/db/odds.sql.go @@ -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, @@ -247,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 ` @@ -261,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, @@ -272,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, @@ -301,10 +359,23 @@ func (q *Queries) GetOddsWithSettingsByEventID(ctx context.Context, arg GetOddsW } const GetOddsWithSettingsByID = `-- name: GetOddsWithSettingsByID :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 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 o.id = $1 ` type GetOddsWithSettingsByIDParams struct { @@ -312,9 +383,25 @@ type GetOddsWithSettingsByIDParams struct { CompanyID int64 `json:"company_id"` } -func (q *Queries) GetOddsWithSettingsByID(ctx context.Context, arg GetOddsWithSettingsByIDParams) (OddsMarketWithSetting, error) { +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 OddsMarketWithSetting + var i GetOddsWithSettingsByIDRow err := row.Scan( &i.ID, &i.EventID, @@ -334,11 +421,24 @@ func (q *Queries) GetOddsWithSettingsByID(ctx context.Context, arg GetOddsWithSe } 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 +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 ` type GetOddsWithSettingsByMarketIDParams struct { @@ -347,9 +447,25 @@ type GetOddsWithSettingsByMarketIDParams struct { CompanyID int64 `json:"company_id"` } -func (q *Queries) GetOddsWithSettingsByMarketID(ctx context.Context, arg GetOddsWithSettingsByMarketIDParams) (OddsMarketWithSetting, error) { +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 OddsMarketWithSetting + var i GetOddsWithSettingsByMarketIDRow err := row.Scan( &i.ID, &i.EventID, 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/event.go b/internal/domain/event.go index 6bae5a1..57a1d32 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,7 +97,7 @@ 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"` @@ -128,7 +128,7 @@ type EventWithSettings struct { WinningUpperLimit int32 DefaultIsFeatured bool DefaultIsActive bool - DefaultWinningUpperLimit int32 + DefaultWinningUpperLimit int64 Score ValidString MatchMinute ValidInt TimerStatus ValidString @@ -140,21 +140,22 @@ type EventWithSettings struct { } 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 + 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 { @@ -179,7 +180,7 @@ type EventWithSettingsRes struct { WinningUpperLimit int32 `json:"winning_upper_limit"` 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,omitempty"` MatchMinute int `json:"match_minute,omitempty"` TimerStatus string `json:"timer_status,omitempty"` @@ -279,21 +280,22 @@ 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, } } diff --git a/internal/domain/league.go b/internal/domain/league.go index 88862b3..0e0d65c 100644 --- a/internal/domain/league.go +++ b/internal/domain/league.go @@ -148,8 +148,8 @@ func ConvertDBLeagueWithSetting(lws dbgen.LeagueWithSetting) 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, }, diff --git a/internal/domain/notification.go b/internal/domain/notification.go index d10f3d7..28dbed2 100644 --- a/internal/domain/notification.go +++ b/internal/domain/notification.go @@ -73,7 +73,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..266aa11 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 ValidInt64 `json:"id"` Name string `json:"name"` Odds []json.RawMessage `json:"odds"` Header string `json:"header,omitempty"` diff --git a/internal/domain/setting_list.go b/internal/domain/setting_list.go index 5f90621..52ae2a4 100644 --- a/internal/domain/setting_list.go +++ b/internal/domain/setting_list.go @@ -23,6 +23,7 @@ type SettingList struct { 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"` } type SettingListRes struct { @@ -33,6 +34,7 @@ type SettingListRes struct { 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"` } func ConvertSettingListRes(settings SettingList) SettingListRes { @@ -44,6 +46,7 @@ func ConvertSettingListRes(settings SettingList) SettingListRes { TotalWinningLimit: settings.TotalWinningLimit.Float32(), AmountForBetReferral: settings.AmountForBetReferral.Float32(), CashbackAmountCap: settings.CashbackAmountCap.Float32(), + DefaultWinningLimit: settings.DefaultWinningLimit, } } @@ -55,6 +58,18 @@ type SaveSettingListReq struct { 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"` +} + +type ValidSettingList struct { + SMSProvider ValidString + MaxNumberOfOutcomes ValidInt64 + BetAmountLimit ValidCurrency + DailyTicketPerIP ValidInt64 + TotalWinningLimit ValidCurrency + AmountForBetReferral ValidCurrency + CashbackAmountCap ValidCurrency + DefaultWinningLimit ValidInt64 } func ConvertSaveSettingListReq(settings SaveSettingListReq) ValidSettingList { @@ -66,19 +81,10 @@ func ConvertSaveSettingListReq(settings SaveSettingListReq) ValidSettingList { TotalWinningLimit: ConvertFloat32PtrToCurrency(settings.TotalWinningLimit), AmountForBetReferral: ConvertFloat32PtrToCurrency(settings.AmountForBetReferral), CashbackAmountCap: ConvertFloat32PtrToCurrency(settings.CashbackAmountCap), + DefaultWinningLimit: ConvertInt64Ptr(settings.DefaultWinningLimit), } } -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{ @@ -89,6 +95,7 @@ func (vsl *ValidSettingList) ToSettingList() SettingList { TotalWinningLimit: Currency(vsl.TotalWinningLimit.Value), AmountForBetReferral: Currency(vsl.AmountForBetReferral.Value), CashbackAmountCap: Currency(vsl.CashbackAmountCap.Value), + DefaultWinningLimit: vsl.DefaultWinningLimit.Value, } } @@ -104,6 +111,7 @@ 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, } } 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 dc593bc..faf073e 100644 --- a/internal/repository/event.go +++ b/internal/repository/event.go @@ -16,7 +16,6 @@ func (s *Store) SaveEvent(ctx context.Context, e domain.CreateEvent) error { return s.queries.InsertEvent(ctx, domain.ConvertCreateEvent(e)) } - func (s *Store) GetLiveEventIDs(ctx context.Context) ([]string, error) { return s.queries.ListLiveEvents(ctx) } @@ -86,6 +85,7 @@ 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(), }) if err != nil { @@ -100,13 +100,69 @@ 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(), }) 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) @@ -125,7 +181,56 @@ func (s *Store) GetEventWithSettingByID(ctx context.Context, ID string, companyI 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{ @@ -173,7 +278,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.SaveEventSettings(ctx, domain.ConvertUpdateEventSettings(event)); + return s.queries.SaveEventSettings(ctx, domain.ConvertUpdateEventSettings(event)) } func (s *Store) DeleteEvent(ctx context.Context, eventID string) error { diff --git a/internal/repository/league.go b/internal/repository/league.go index 52b8aef..4000b00 100644 --- a/internal/repository/league.go +++ b/internal/repository/league.go @@ -51,13 +51,39 @@ 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 domain.ConvertDBLeagueWithSettings(l), nil + result := make([]domain.LeagueWithSettings, len(l)) + for i, league := range l { + result[i] = domain.LeagueWithSettings{ + ID: league.ID, + Name: league.Name, + CompanyID: league.CompanyID.Int64, + CountryCode: domain.ValidString{ + Value: league.CountryCode.String, + Valid: league.CountryCode.Valid, + }, + Bet365ID: domain.ValidInt32{ + Value: league.Bet365ID.Int32, + Valid: league.Bet365ID.Valid, + }, + IsActive: league.IsActive, + SportID: league.SportID, + IsFeatured: league.IsFeatured, + UpdatedAt: league.UpdatedAt.Time, + + DefaultIsActive: league.DefaultIsActive, + DefaultIsFeatured: league.DefaultIsFeatured, + } + } + + return result, 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 976c007..3e09a91 100644 --- a/internal/repository/odds.go +++ b/internal/repository/odds.go @@ -88,23 +88,46 @@ 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) + convertedOdd, err := domain.ConvertDBOddMarket(odd) if err != nil { return domain.OddMarket{}, err @@ -141,31 +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 + // 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 } - return convertedOdd, nil + + 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, + ID: ID, CompanyID: companyID, }) - - if err != nil { - return domain.OddMarketWithSettings{}, err - } - - convertedOdd, err := domain.ConvertDBOddMarketWithSetting(odds) 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) { @@ -208,12 +276,37 @@ 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 { 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/services/bet/service.go b/internal/services/bet/service.go index e026110..b2ef38f 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -283,8 +283,10 @@ 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") + + // TODO: Make this a setting + if role == domain.RoleCustomer && count >= 10 { + return domain.CreateBetRes{}, fmt.Errorf("max user limit for single outcome") } fastCode := helpers.GenerateFastCode() @@ -340,17 +342,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,7 +381,7 @@ 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)), @@ -1073,8 +1078,6 @@ func (s *Service) SendErrorStatusNotification(ctx context.Context, status domain 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, @@ -1088,7 +1091,7 @@ func (s *Service) SendErrorStatusNotification(ctx context.Context, status domain Message: message, }, Priority: 1, - ErrorSeverity: &errorSeverityLevel, + ErrorSeverity: domain.NotificationErrorSeverityHigh, Metadata: fmt.Appendf(nil, `{ "status":%v "more": %v @@ -1117,9 +1120,8 @@ func (s *Service) SendAdminErrorAlertNotification(ctx context.Context, status do 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, + ErrorSeverity: domain.NotificationErrorSeverityHigh, DeliveryStatus: domain.DeliveryStatusPending, IsRead: false, Type: domain.NOTIFICATION_TYPE_BET_RESULT, @@ -1313,7 +1315,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 +1324,6 @@ func (s *Service) ProcessBetCashback(ctx context.Context) error { return err } - for _, bet := range bets { shouldProcess := true loseCount := 0 diff --git a/internal/services/event/service.go b/internal/services/event/service.go index ab227bc..fa53e92 100644 --- a/internal/services/event/service.go +++ b/internal/services/event/service.go @@ -14,6 +14,8 @@ import ( "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,13 +23,15 @@ import ( type service struct { token string store *repository.Store + settingSvc settings.Service mongoLogger *zap.Logger } -func New(token string, store *repository.Store, mongoLogger *zap.Logger) Service { +func New(token string, store *repository.Store, settingSvc settings.Service, mongoLogger *zap.Logger) Service { return &service{ token: token, store: store, + settingSvc: settingSvc, mongoLogger: mongoLogger, } } @@ -206,22 +210,31 @@ 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 + } + + const pageLimit int = 1 + // sportIDs := []int{1, 18, 17, 3, 83, 15, 12, 19, 8, 16, 91} + sportIDs := []int{1} + + 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 +314,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 +350,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 +359,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 +392,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 } @@ -478,7 +493,7 @@ 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 { diff --git a/internal/services/notification/service.go b/internal/services/notification/service.go index ae0b990..76531db 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 @@ -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, 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 } diff --git a/internal/services/result/service.go b/internal/services/result/service.go index ad1f1c5..a872924 100644 --- a/internal/services/result/service.go +++ b/internal/services/result/service.go @@ -541,9 +541,8 @@ func (s *Service) SendAdminResultStatusErrorNotification( } headline, message := buildHeadlineAndMessage(counts) - errorSeverity := domain.NotificationErrorSeverityLow notification := &domain.Notification{ - ErrorSeverity: &errorSeverity, + ErrorSeverity: domain.NotificationErrorSeverityHigh, DeliveryStatus: domain.DeliveryStatusPending, IsRead: false, Type: domain.NOTIFICATION_TYPE_BET_RESULT, diff --git a/internal/services/transaction/shop_bet.go b/internal/services/transaction/shop_bet.go index 6c1ed20..372726f 100644 --- a/internal/services/transaction/shop_bet.go +++ b/internal/services/transaction/shop_bet.go @@ -52,6 +52,7 @@ func (s *Service) CreateShopBet(ctx context.Context, userID int64, role domain.R newBet, err := s.betSvc.PlaceBet(ctx, domain.CreateBetReq{ Outcomes: req.Outcomes, Amount: req.Amount, + BranchID: branchID, }, userID, role, *companyID) if err != nil { @@ -94,6 +95,10 @@ func (s *Service) CreateShopBet(ctx context.Context, userID int64, role domain.R }, }) + if err != nil { + return domain.ShopBet{}, err + } + return s.transactionStore.CreateShopBet(ctx, domain.CreateShopBet{ ShopTransactionID: newTransaction.ID, CashoutID: cashoutID, diff --git a/internal/services/wallet/direct_deposit.go b/internal/services/wallet/direct_deposit.go index fc25861..a049d66 100644 --- a/internal/services/wallet/direct_deposit.go +++ b/internal/services/wallet/direct_deposit.go @@ -148,7 +148,7 @@ func (s *Service) notifyCashiersForVerification(ctx context.Context, depositID, Metadata: metadataJSON, } - if err := s.notificationStore.SendNotification(ctx, notification); err != nil { + if err := s.notificationSvc.SendNotification(ctx, notification); err != nil { s.logger.Error("failed to send verification notification", "cashier_id", cashier.ID, "error", err) @@ -199,7 +199,7 @@ func (s *Service) notifyCustomerVerificationResult(ctx context.Context, deposit Metadata: metadataJSON, } - if err := s.notificationStore.SendNotification(ctx, notification); err != nil { + if err := s.notificationSvc.SendNotification(ctx, notification); err != nil { s.logger.Error("failed to send deposit result notification", "customer_id", deposit.CustomerID, "error", err) diff --git a/internal/services/wallet/service.go b/internal/services/wallet/service.go index 8b0d216..2e248a5 100644 --- a/internal/services/wallet/service.go +++ b/internal/services/wallet/service.go @@ -14,7 +14,6 @@ type Service struct { walletStore WalletStore transferStore TransferStore directDepositStore DirectDepositStore - notificationStore notificationservice.NotificationStore notificationSvc *notificationservice.Service userSvc *user.Service mongoLogger *zap.Logger @@ -26,7 +25,6 @@ func NewService( walletStore WalletStore, transferStore TransferStore, directDepositStore DirectDepositStore, - notificationStore notificationservice.NotificationStore, notificationSvc *notificationservice.Service, userSvc *user.Service, mongoLogger *zap.Logger, @@ -38,7 +36,6 @@ func NewService( transferStore: transferStore, directDepositStore: directDepositStore, // approvalStore: approvalStore, - notificationStore: notificationStore, notificationSvc: notificationSvc, userSvc: userSvc, mongoLogger: mongoLogger, diff --git a/internal/services/wallet/transfer.go b/internal/services/wallet/transfer.go index ad3ef1d..fd6bc04 100644 --- a/internal/services/wallet/transfer.go +++ b/internal/services/wallet/transfer.go @@ -135,7 +135,7 @@ func (s *Service) SendTransferNotification(ctx context.Context, senderWallet dom } // Sender notifications - if err := s.notificationStore.SendNotification(ctx, senderNotify); err != nil { + if err := s.notificationSvc.SendNotification(ctx, senderNotify); err != nil { s.logger.Error("failed to send sender notification", "user_id", "", "error", err) @@ -163,7 +163,7 @@ func (s *Service) SendTransferNotification(ctx context.Context, senderWallet dom }`, amount, receiverWallet.Balance, receiverWallet.ID)), } // Sender notifications - if err := s.notificationStore.SendNotification(ctx, receiverNotify); err != nil { + if err := s.notificationSvc.SendNotification(ctx, receiverNotify); err != nil { s.logger.Error("failed to send sender notification", "user_id", "", "error", err) diff --git a/internal/services/wallet/wallet.go b/internal/services/wallet/wallet.go index 7d7bd5b..df67111 100644 --- a/internal/services/wallet/wallet.go +++ b/internal/services/wallet/wallet.go @@ -304,10 +304,10 @@ func (s *Service) GetAdminNotificationRecipients(ctx context.Context, walletID i } func (s *Service) SendAdminWalletLowNotification(ctx context.Context, adminWallet domain.Wallet) error { - errorSeverity := domain.NotificationErrorSeverityLow + // Send notification to admin team adminNotification := &domain.Notification{ - ErrorSeverity: &errorSeverity, + ErrorSeverity: "low", IsRead: false, DeliveryStatus: domain.DeliveryStatusPending, RecipientID: adminWallet.UserID, @@ -343,7 +343,7 @@ func (s *Service) SendAdminWalletLowNotification(ctx context.Context, adminWalle for _, adminID := range adminRecipients { adminNotification.RecipientID = adminID - if err := s.notificationStore.SendNotification(ctx, adminNotification); err != nil { + if err := s.notificationSvc.SendNotification(ctx, adminNotification); err != nil { s.mongoLogger.Error("failed to send admin notification", zap.Int64("admin_id", adminID), zap.Error(err), @@ -353,7 +353,7 @@ func (s *Service) SendAdminWalletLowNotification(ctx context.Context, adminWalle adminNotification.DeliveryChannel = domain.DeliveryChannelEmail - if err := s.notificationStore.SendNotification(ctx, adminNotification); err != nil { + if err := s.notificationSvc.SendNotification(ctx, adminNotification); err != nil { s.mongoLogger.Error("failed to send email admin notification", zap.Int64("admin_id", adminID), zap.Error(err), @@ -366,11 +366,9 @@ func (s *Service) SendAdminWalletLowNotification(ctx context.Context, adminWalle } func (s *Service) SendAdminWalletInsufficientNotification(ctx context.Context, adminWallet domain.Wallet, amount domain.Currency) error { - - errorSeverity := domain.NotificationErrorSeverityLow // Send notification to admin team adminNotification := &domain.Notification{ - ErrorSeverity: &errorSeverity, + ErrorSeverity: domain.NotificationErrorSeverityLow, IsRead: false, DeliveryStatus: domain.DeliveryStatusPending, RecipientID: adminWallet.UserID, @@ -408,7 +406,7 @@ func (s *Service) SendAdminWalletInsufficientNotification(ctx context.Context, a } for _, adminID := range recipients { adminNotification.RecipientID = adminID - if err := s.notificationStore.SendNotification(ctx, adminNotification); err != nil { + if err := s.notificationSvc.SendNotification(ctx, adminNotification); err != nil { s.mongoLogger.Error("failed to send admin notification", zap.Int64("admin_id", adminID), zap.Error(err), @@ -417,7 +415,7 @@ func (s *Service) SendAdminWalletInsufficientNotification(ctx context.Context, a } adminNotification.DeliveryChannel = domain.DeliveryChannelEmail - if err := s.notificationStore.SendNotification(ctx, adminNotification); err != nil { + if err := s.notificationSvc.SendNotification(ctx, adminNotification); err != nil { s.mongoLogger.Error("failed to send email admin notification", zap.Int64("admin_id", adminID), zap.Error(err), @@ -431,10 +429,9 @@ func (s *Service) SendAdminWalletInsufficientNotification(ctx context.Context, a } func (s *Service) SendCustomerWalletInsufficientNotification(ctx context.Context, customerWallet domain.Wallet, amount domain.Currency) error { - errorSeverity := domain.NotificationErrorSeverityLow // Send notification to admin team customerNotification := &domain.Notification{ - ErrorSeverity: &errorSeverity, + ErrorSeverity: domain.NotificationErrorSeverityLow, IsRead: false, DeliveryStatus: domain.DeliveryStatusPending, RecipientID: customerWallet.UserID, @@ -460,7 +457,7 @@ func (s *Service) SendCustomerWalletInsufficientNotification(ctx context.Context }`, customerWallet.ID, customerWallet.Balance, amount.Float32()), } - if err := s.notificationStore.SendNotification(ctx, customerNotification); err != nil { + if err := s.notificationSvc.SendNotification(ctx, customerNotification); err != nil { s.mongoLogger.Error("failed to create customer notification", zap.Int64("customer_id", customerWallet.UserID), zap.Error(err), diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index 5ea6c56..664a1f0 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -7,7 +7,6 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" - "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/gofiber/fiber/v2" "go.uber.org/zap" @@ -46,6 +45,19 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error { res, err := h.CreateBetInternal(c, req, userID, role, companyID.Value) if err != nil { + switch err { + case bet.ErrEventHasBeenRemoved, bet.ErrEventHasNotEnded, bet.ErrRawOddInvalid, bet.ErrTotalBalanceNotEnough: + h.mongoLoggerSvc.Info("PlaceBet failed", + zap.Int("status_code", fiber.StatusBadRequest), + zap.Int64("userID", userID), + zap.Int64("companyID", companyID.Value), + zap.String("role", string(role)), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + h.mongoLoggerSvc.Error("Failed to create bet", zap.Int("status_code", fiber.StatusInternalServerError), zap.Int64("user_id", userID), @@ -200,7 +212,7 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI res, err := h.betSvc.PlaceBet(c.Context(), req, userID, role, companyID) if err != nil { switch err { - case bet.ErrEventHasBeenRemoved, bet.ErrEventHasNotEnded, bet.ErrRawOddInvalid, wallet.ErrBalanceInsufficient: + case bet.ErrEventHasBeenRemoved, bet.ErrEventHasNotEnded, bet.ErrRawOddInvalid, bet.ErrTotalBalanceNotEnough: h.mongoLoggerSvc.Info("PlaceBet failed", zap.Int("status_code", fiber.StatusBadRequest), zap.Int64("userID", userID), diff --git a/internal/web_server/handlers/event_handler.go b/internal/web_server/handlers/event_handler.go index 6c3c286..2de000d 100644 --- a/internal/web_server/handlers/event_handler.go +++ b/internal/web_server/handlers/event_handler.go @@ -352,6 +352,7 @@ func (h *Handler) GetTopLeagues(c *fiber.Ctx) error { Value: true, Valid: true, }, + }) if err != nil { diff --git a/internal/web_server/handlers/notification_handler.go b/internal/web_server/handlers/notification_handler.go index e278581..b822e6d 100644 --- a/internal/web_server/handlers/notification_handler.go +++ b/internal/web_server/handlers/notification_handler.go @@ -208,12 +208,17 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error { // return fiber.NewError(fiber.StatusForbidden, "Unauthorized to send notification to this recipient") // } + errorSeverity := domain.NotificationErrorSeverityMedium + if req.ErrorSeverity != nil { + errorSeverity = *req.ErrorSeverity + } + notification := &domain.Notification{ ID: "", RecipientID: req.RecipientID, Type: req.Type, Level: req.Level, - ErrorSeverity: req.ErrorSeverity, + ErrorSeverity: errorSeverity, Reciever: req.Reciever, IsRead: false, DeliveryStatus: domain.DeliveryStatusPending, @@ -257,12 +262,17 @@ func (h *Handler) CreateAndSendNotification(c *fiber.Ctx) error { notificationIDs := make([]string, 0, len(recipients)) for _, user := range recipients { + errorSeverity := domain.NotificationErrorSeverityMedium + if req.ErrorSeverity != nil { + errorSeverity = *req.ErrorSeverity + } + notification := &domain.Notification{ ID: "", RecipientID: user.ID, Type: req.Type, Level: req.Level, - ErrorSeverity: req.ErrorSeverity, + ErrorSeverity: errorSeverity, Reciever: req.Reciever, IsRead: false, DeliveryStatus: domain.DeliveryStatusPending, diff --git a/internal/web_server/handlers/shop_handler.go b/internal/web_server/handlers/shop_handler.go index 7a25615..ff363b8 100644 --- a/internal/web_server/handlers/shop_handler.go +++ b/internal/web_server/handlers/shop_handler.go @@ -27,7 +27,7 @@ func (h *Handler) CreateShopBet(c *fiber.Ctx) error { userID := c.Locals("user_id").(int64) role := c.Locals("role").(domain.Role) company_id := c.Locals("company_id").(domain.ValidInt64) - + var req domain.ShopBetReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Info("CreateBetReq failed to parse request", @@ -65,7 +65,7 @@ func (h *Handler) CreateShopBet(c *fiber.Ctx) error { zap.Error(err), zap.Time("timestamp", time.Now()), ) - return fiber.NewError(statusCode, "failed to create shop bet"+err.Error()) + return fiber.NewError(statusCode, err.Error()) } res := domain.ConvertShopBet(shopBet) @@ -464,6 +464,7 @@ func (h *Handler) GetAllTransactions(c *fiber.Ctx) error { // role := c.Locals("role").(domain.Role) companyID := c.Locals("company_id").(domain.ValidInt64) branchID := c.Locals("branch_id").(domain.ValidInt64) + role := c.Locals("role").(domain.Role) searchQuery := c.Query("query") searchString := domain.ValidString{ @@ -509,6 +510,14 @@ func (h *Handler) GetAllTransactions(c *fiber.Ctx) error { } } + companyFilter := int64(c.QueryInt("company_id")) + if role == domain.RoleSuperAdmin { + companyID = domain.ValidInt64{ + Value: companyFilter, + Valid: companyFilter != 0, + } + } + // Check user role and fetch transactions accordingly transactions, err := h.transactionSvc.GetAllShopTransactions(c.Context(), domain.ShopTransactionFilter{ CompanyID: companyID, diff --git a/internal/web_server/handlers/ticket_handler.go b/internal/web_server/handlers/ticket_handler.go index f3b108a..8f335c3 100644 --- a/internal/web_server/handlers/ticket_handler.go +++ b/internal/web_server/handlers/ticket_handler.go @@ -123,6 +123,10 @@ func (h *Handler) GetTicketByID(c *fiber.Ctx) error { if ticket.CompanyID != companyID.Value { h.mongoLoggerSvc.Warn("User attempt to access another company ticket", zap.Int64("ticketID", id), + zap.Int64("ticket CompanyID", ticket.CompanyID), + zap.Int64("companyID", companyID.Value), + zap.Bool("companyID Valid", companyID.Valid), + zap.Int("status_code", fiber.StatusNotFound), zap.Error(err), zap.Time("timestamp", time.Now()),