From aaf14fedcfd6cf48bd5aa4edc96401c846f41f32 Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Mon, 25 Aug 2025 07:23:55 +0300 Subject: [PATCH] fix: global setting, company override settings, bet and ticket fixes for multi tenant --- db/migrations/000001_fortune.up.sql | 45 +- db/migrations/000007_setting_data.up.sql | 4 +- db/query/bet.sql | 11 +- db/query/settings.sql | 43 +- db/query/ticket.sql | 13 +- gen/db/bet.sql.go | 23 +- gen/db/custom_odds.sql.go | 167 ------- gen/db/models.go | 23 +- gen/db/odds.sql.go | 9 +- gen/db/settings.sql.go | 217 ++++++++- gen/db/ticket.sql.go | 23 +- internal/domain/bet.go | 116 ++++- internal/domain/common_log.go | 18 +- internal/domain/currency.go | 18 +- internal/domain/custom_odds.go | 118 ++--- internal/domain/disabled_odds.go | 40 +- internal/domain/result.go | 2 +- internal/domain/role.go | 9 + internal/domain/setting_list.go | 447 ++++++++++++++++++ internal/domain/settings.go | 191 ++------ internal/domain/ticket.go | 8 + internal/domain/user.go | 5 +- internal/domain/validtypes.go | 348 ++++++++------ internal/repository/bet.go | 160 +------ internal/repository/settings.go | 210 ++++---- internal/repository/shop_bet.go | 2 +- internal/repository/ticket.go | 6 +- internal/services/bet/service.go | 19 +- internal/services/messenger/sms.go | 20 +- internal/services/notification/service.go | 2 +- internal/services/odds/port.go | 2 +- internal/services/referal/port.go | 2 +- internal/services/referal/service.go | 7 +- internal/services/settings/port.go | 19 +- internal/services/settings/service.go | 47 +- internal/services/ticket/port.go | 2 +- internal/services/ticket/service.go | 11 +- internal/services/transaction/service.go | 2 +- internal/services/transaction/shop_bet.go | 2 +- internal/services/virtualGame/veli/service.go | 2 +- internal/web_server/handlers/auth_handler.go | 4 +- internal/web_server/handlers/bet_handler.go | 146 +++++- .../web_server/handlers/common_handler.go | 25 + internal/web_server/handlers/event_handler.go | 52 +- internal/web_server/handlers/leagues.go | 26 +- internal/web_server/handlers/odd_handler.go | 46 +- .../web_server/handlers/settings_handler.go | 155 +++++- internal/web_server/handlers/shop_handler.go | 2 +- .../web_server/handlers/ticket_handler.go | 39 +- internal/web_server/handlers/user.go | 10 +- internal/web_server/routes.go | 50 +- 51 files changed, 1897 insertions(+), 1071 deletions(-) delete mode 100644 gen/db/custom_odds.sql.go create mode 100644 internal/domain/setting_list.go create mode 100644 internal/web_server/handlers/common_handler.go diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 717d0d2..0a167ed 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -292,7 +292,7 @@ CREATE TABLE events ( default_is_featured BOOLEAN NOT NULL DEFAULT false, default_winning_upper_limit INT NOT NULL, is_monitored BOOLEAN NOT NULL DEFAULT FALSE, - UNIQUE(source_event_id, source) + UNIQUE(id, source) ); CREATE TABLE event_history ( id BIGSERIAL PRIMARY KEY, @@ -321,8 +321,6 @@ CREATE TABLE odds_market ( default_is_active BOOLEAN NOT NULL DEFAULT true, fetched_at TIMESTAMP DEFAULT now(), expires_at TIMESTAMP NOT NULL, - UNIQUE (market_id, name, handicap), - UNIQUE (event_id, market_id, name, handicap), UNIQUE (event_id, market_id) ); CREATE TABLE odd_history ( @@ -408,12 +406,21 @@ CREATE TABLE teams ( bet365_id BIGINT, img_url TEXT ); -CREATE TABLE IF NOT EXISTS settings ( +CREATE TABLE IF NOT EXISTS global_settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); +-- Tenant/Company-specific overrides +CREATE TABLE IF NOT EXISTS company_settings ( + 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, id BIGSERIAL PRIMARY KEY, @@ -430,11 +437,11 @@ CREATE TABLE flags ( CHECK ( ( bet_id IS NOT NULL - AND odd_id IS NULL + AND odds_market_id IS NULL ) OR ( bet_id IS NULL - AND odd_id IS NOT NULL + AND odds_market_id IS NOT NULL ) ) ); @@ -579,7 +586,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 leagues.id = cls.league_id; + JOIN company_league_settings cls ON l.id = cls.league_id; CREATE VIEW event_with_settings AS SELECT e.*, ces.company_id, @@ -592,15 +599,23 @@ SELECT e.*, ces.updated_at, l.country_code as league_cc FROM events e - JOIN company_event_settings ces ON events.id = ces.event_id - JOIN leagues l ON leagues.id = e.league_id; + 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.*, leagues.country_code as league_cc FROM events - LEFT JOIN leagues ON leagues.id = league_id; + LEFT JOIN leagues ON leagues.id = events.league_id; CREATE VIEW odds_market_with_settings AS -SELECT o.*, +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, @@ -654,4 +669,10 @@ 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 league_id(id) ON DELETE CASCADE; \ No newline at end of file + 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; +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 diff --git a/db/migrations/000007_setting_data.up.sql b/db/migrations/000007_setting_data.up.sql index e98967b..a381c02 100644 --- a/db/migrations/000007_setting_data.up.sql +++ b/db/migrations/000007_setting_data.up.sql @@ -1,6 +1,6 @@ -- Settings Initial Data -INSERT INTO settings (key, value) -VALUES ('sms_provider', '30'), +INSERT INTO global_settings (key, value) +VALUES ('sms_provider', 'afro_message'), ('max_number_of_outcomes', '30'), ('bet_amount_limit', '10000000'), ('daily_ticket_limit', '50'), diff --git a/db/query/bet.sql b/db/query/bet.sql index 47018e1..caef892 100644 --- a/db/query/bet.sql +++ b/db/query/bet.sql @@ -6,9 +6,10 @@ INSERT INTO bets ( user_id, is_shop_bet, outcomes_hash, - fast_code + fast_code, + company_id ) -VALUES ($1, $2, $3, $4, $5, $6, $7) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *; -- name: CreateBetOutcome :copyfrom INSERT INTO bet_outcomes ( @@ -52,6 +53,10 @@ wHERE ( is_shop_bet = sqlc.narg('is_shop_bet') OR sqlc.narg('is_shop_bet') IS NULL ) + AND ( + company_id = sqlc.narg('company_id') + OR sqlc.narg('company_id') IS NULL + ) AND ( cashed_out = sqlc.narg('cashed_out') OR sqlc.narg('cashed_out') IS NULL @@ -148,4 +153,4 @@ DELETE FROM bets WHERE id = $1; -- name: DeleteBetOutcome :exec DELETE FROM bet_outcomes -WHERE bet_id = $1; +WHERE bet_id = $1; \ No newline at end of file diff --git a/db/query/settings.sql b/db/query/settings.sql index 6a06aae..cda4e87 100644 --- a/db/query/settings.sql +++ b/db/query/settings.sql @@ -1,12 +1,41 @@ --- name: GetSettings :many +-- name: GetGlobalSettings :many SELECT * -FROM settings; --- name: GetSetting :one +FROM global_settings; +-- name: GetGlobalSetting :one SELECT * -FROM settings +FROM global_settings WHERE key = $1; --- name: UpdateSetting :exec -UPDATE settings +-- name: UpdateGlobalSetting :exec +UPDATE global_settings SET value = $2, updated_at = CURRENT_TIMESTAMP -WHERE key = $1; \ No newline at end of file +WHERE key = $1; +-- name: InsertCompanySetting :exec +INSERT INTO company_settings (company_id, key, value) +VALUES ($1, $2, $3) ON CONFLICT (company_id, key) DO +UPDATE +SET value = EXCLUDED.value; +-- name: GetAllCompanySettings :many +SELECT * +FROM company_settings; +-- name: GetCompanySetting :many +SELECT * +FROM company_settings +WHERE company_id = $1; +-- name: GetCompanySettingsByKey :many +SELECT * +FROM company_settings +WHERE key = $1; +-- name: GetOverrideSettings :many +SELECT gs.*, + COALESCE(cs.value, gs.value) AS value +FROM global_settings gs + LEFT JOIN company_settings cs ON cs.key = gs.key + AND cs.company_id = $1; +-- name: DeleteCompanySetting :exec +DELETE FROM company_settings +WHERE company_id = $1 + AND key = $2; +-- name: DeleteAllCompanySetting :exec +DELETE FROM company_settings +WHERE company_id = $1; \ No newline at end of file diff --git a/db/query/ticket.sql b/db/query/ticket.sql index 7648842..b93da70 100644 --- a/db/query/ticket.sql +++ b/db/query/ticket.sql @@ -1,6 +1,6 @@ -- name: CreateTicket :one -INSERT INTO tickets (amount, total_odds, ip) -VALUES ($1, $2, $3) +INSERT INTO tickets (amount, total_odds, ip, company_id) +VALUES ($1, $2, $3, $4) RETURNING *; -- name: CreateTicketOutcome :copyfrom INSERT INTO ticket_outcomes ( @@ -33,7 +33,11 @@ VALUES ( ); -- name: GetAllTickets :many SELECT * -FROM ticket_with_outcomes; +FROM ticket_with_outcomes +WHERE ( + company_id = sqlc.narg('company_id') + OR sqlc.narg('company_id') IS NULL + ); -- name: GetTicketByID :one SELECT * FROM ticket_with_outcomes @@ -60,6 +64,7 @@ where created_at < now() - interval '1 day'; Delete from ticket_outcomes where ticket_id = $1; -- name: GetAllTicketsInRange :one -SELECT COUNT(*) as total_tickets, COALESCE(SUM(amount), 0) as total_amount +SELECT COUNT(*) as total_tickets, + COALESCE(SUM(amount), 0) as total_amount FROM tickets WHERE created_at BETWEEN $1 AND $2; \ No newline at end of file diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index 29d1e10..ff64087 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -19,9 +19,10 @@ INSERT INTO bets ( user_id, is_shop_bet, outcomes_hash, - fast_code + fast_code, + company_id ) -VALUES ($1, $2, $3, $4, $5, $6, $7) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, company_id, amount, total_odds, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at ` @@ -33,6 +34,7 @@ type CreateBetParams struct { IsShopBet bool `json:"is_shop_bet"` OutcomesHash string `json:"outcomes_hash"` FastCode string `json:"fast_code"` + CompanyID int64 `json:"company_id"` } func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, error) { @@ -44,6 +46,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro arg.IsShopBet, arg.OutcomesHash, arg.FastCode, + arg.CompanyID, ) var i Bet err := row.Scan( @@ -112,27 +115,32 @@ wHERE ( OR $2 IS NULL ) AND ( - cashed_out = $3 + company_id = $3 OR $3 IS NULL ) AND ( - full_name ILIKE '%' || $4 || '%' - OR phone_number ILIKE '%' || $4 || '%' + cashed_out = $4 OR $4 IS NULL ) AND ( - created_at > $5 + full_name ILIKE '%' || $5 || '%' + OR phone_number ILIKE '%' || $5 || '%' OR $5 IS NULL ) AND ( - created_at < $6 + created_at > $6 OR $6 IS NULL ) + AND ( + created_at < $7 + OR $7 IS NULL + ) ` type GetAllBetsParams struct { UserID pgtype.Int8 `json:"user_id"` IsShopBet pgtype.Bool `json:"is_shop_bet"` + CompanyID pgtype.Int8 `json:"company_id"` CashedOut pgtype.Bool `json:"cashed_out"` Query pgtype.Text `json:"query"` CreatedBefore pgtype.Timestamp `json:"created_before"` @@ -143,6 +151,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi rows, err := q.db.Query(ctx, GetAllBets, arg.UserID, arg.IsShopBet, + arg.CompanyID, arg.CashedOut, arg.Query, arg.CreatedBefore, diff --git a/gen/db/custom_odds.sql.go b/gen/db/custom_odds.sql.go deleted file mode 100644 index efc1f02..0000000 --- a/gen/db/custom_odds.sql.go +++ /dev/null @@ -1,167 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.29.0 -// source: custom_odds.sql - -package dbgen - -import ( - "context" - - "github.com/jackc/pgx/v5/pgtype" -) - -const DeleteCustomOddByEventID = `-- name: DeleteCustomOddByEventID :exec -DELETE FROM custom_odds_market -WHERE event_id = $1 -` - -func (q *Queries) DeleteCustomOddByEventID(ctx context.Context, eventID string) error { - _, err := q.db.Exec(ctx, DeleteCustomOddByEventID, eventID) - return err -} - -const DeleteCustomOddsByID = `-- name: DeleteCustomOddsByID :exec -DELETE FROM custom_odds_market -WHERE id = $1 -` - -func (q *Queries) DeleteCustomOddsByID(ctx context.Context, id int64) error { - _, err := q.db.Exec(ctx, DeleteCustomOddsByID, id) - return err -} - -const DeleteCustomOddsByOddID = `-- name: DeleteCustomOddsByOddID :exec -DELETE FROM custom_odds_market -WHERE odds_market_id = $1 - AND company_id = $2 -` - -type DeleteCustomOddsByOddIDParams struct { - OddsMarketID int64 `json:"odds_market_id"` - CompanyID int64 `json:"company_id"` -} - -func (q *Queries) DeleteCustomOddsByOddID(ctx context.Context, arg DeleteCustomOddsByOddIDParams) error { - _, err := q.db.Exec(ctx, DeleteCustomOddsByOddID, arg.OddsMarketID, arg.CompanyID) - return err -} - -const GetAllCustomOdds = `-- name: GetAllCustomOdds :many -SELECT id, company_id, odds_market_id, event_id, raw_odds, created_at -FROM custom_odds_market -WHERE ( - company_id = $1 - OR $1 IS NULL - ) -` - -func (q *Queries) GetAllCustomOdds(ctx context.Context, companyID pgtype.Int8) ([]CustomOddsMarket, error) { - rows, err := q.db.Query(ctx, GetAllCustomOdds, companyID) - if err != nil { - return nil, err - } - defer rows.Close() - var items []CustomOddsMarket - for rows.Next() { - var i CustomOddsMarket - if err := rows.Scan( - &i.ID, - &i.CompanyID, - &i.OddsMarketID, - &i.EventID, - &i.RawOdds, - &i.CreatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const GetCustomOddByID = `-- name: GetCustomOddByID :one -SELECT id, company_id, odds_market_id, event_id, raw_odds, created_at -FROM custom_odds_market -WHERE id = $1 -` - -func (q *Queries) GetCustomOddByID(ctx context.Context, id int64) (CustomOddsMarket, error) { - row := q.db.QueryRow(ctx, GetCustomOddByID, id) - var i CustomOddsMarket - err := row.Scan( - &i.ID, - &i.CompanyID, - &i.OddsMarketID, - &i.EventID, - &i.RawOdds, - &i.CreatedAt, - ) - return i, err -} - -const GetCustomOddByOddID = `-- name: GetCustomOddByOddID :one -SELECT id, company_id, odds_market_id, event_id, raw_odds, created_at -FROM custom_odds_market -WHERE odds_market_id = $1 - AND company_id = $2 -` - -type GetCustomOddByOddIDParams struct { - OddsMarketID int64 `json:"odds_market_id"` - CompanyID int64 `json:"company_id"` -} - -func (q *Queries) GetCustomOddByOddID(ctx context.Context, arg GetCustomOddByOddIDParams) (CustomOddsMarket, error) { - row := q.db.QueryRow(ctx, GetCustomOddByOddID, arg.OddsMarketID, arg.CompanyID) - var i CustomOddsMarket - err := row.Scan( - &i.ID, - &i.CompanyID, - &i.OddsMarketID, - &i.EventID, - &i.RawOdds, - &i.CreatedAt, - ) - return i, err -} - -const InsertCustomOddsMarket = `-- name: InsertCustomOddsMarket :one -INSERT INTO custom_odds_market ( - odds_market_id, - company_id, - event_id, - raw_odds - ) -VALUES ($1, $2, $3, $4) -RETURNING id, company_id, odds_market_id, event_id, raw_odds, created_at -` - -type InsertCustomOddsMarketParams struct { - OddsMarketID int64 `json:"odds_market_id"` - CompanyID int64 `json:"company_id"` - EventID string `json:"event_id"` - RawOdds []byte `json:"raw_odds"` -} - -func (q *Queries) InsertCustomOddsMarket(ctx context.Context, arg InsertCustomOddsMarketParams) (CustomOddsMarket, error) { - row := q.db.QueryRow(ctx, InsertCustomOddsMarket, - arg.OddsMarketID, - arg.CompanyID, - arg.EventID, - arg.RawOdds, - ) - var i CustomOddsMarket - err := row.Scan( - &i.ID, - &i.CompanyID, - &i.OddsMarketID, - &i.EventID, - &i.RawOdds, - &i.CreatedAt, - ) - return i, err -} diff --git a/gen/db/models.go b/gen/db/models.go index 460b84d..fc95871 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -240,6 +240,14 @@ type CompanyOddSetting struct { UpdatedAt pgtype.Timestamp `json:"updated_at"` } +type CompanySetting struct { + CompanyID int64 `json:"company_id"` + Key string `json:"key"` + Value string `json:"value"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` +} + type CustomerWallet struct { ID int64 `json:"id"` CustomerID int64 `json:"customer_id"` @@ -412,6 +420,13 @@ type Flag struct { Resolved pgtype.Bool `json:"resolved"` } +type GlobalSetting struct { + Key string `json:"key"` + Value string `json:"value"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` +} + type League struct { ID int64 `json:"id"` Name string `json:"name"` @@ -502,7 +517,6 @@ type OddsMarketWithSetting struct { MarketName string `json:"market_name"` MarketCategory string `json:"market_category"` MarketID string `json:"market_id"` - RawOdds []byte `json:"raw_odds"` DefaultIsActive bool `json:"default_is_active"` FetchedAt pgtype.Timestamp `json:"fetched_at"` ExpiresAt pgtype.Timestamp `json:"expires_at"` @@ -604,13 +618,6 @@ type ResultLog struct { UpdatedAt pgtype.Timestamp `json:"updated_at"` } -type Setting struct { - Key string `json:"key"` - Value string `json:"value"` - CreatedAt pgtype.Timestamp `json:"created_at"` - UpdatedAt pgtype.Timestamp `json:"updated_at"` -} - type ShopBet struct { ID int64 `json:"id"` ShopTransactionID int64 `json:"shop_transaction_id"` diff --git a/gen/db/odds.sql.go b/gen/db/odds.sql.go index 966fcff..33fcde8 100644 --- a/gen/db/odds.sql.go +++ b/gen/db/odds.sql.go @@ -68,7 +68,7 @@ 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, odds_market_with_settings.raw_odds, default_is_active, fetched_at, expires_at, company_id, is_active, odds_market_with_settings.raw_odds, updated_at +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 LIMIT $3 OFFSET $2 @@ -96,7 +96,6 @@ func (q *Queries) GetAllOddsWithSettings(ctx context.Context, arg GetAllOddsWith &i.MarketName, &i.MarketCategory, &i.MarketID, - &i.RawOdds, &i.DefaultIsActive, &i.FetchedAt, &i.ExpiresAt, @@ -220,7 +219,7 @@ 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, odds_market_with_settings.raw_odds, default_is_active, fetched_at, expires_at, company_id, is_active, odds_market_with_settings.raw_odds, updated_at +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 AND company_id = $2 @@ -255,7 +254,6 @@ func (q *Queries) GetOddsWithSettingsByEventID(ctx context.Context, arg GetOddsW &i.MarketName, &i.MarketCategory, &i.MarketID, - &i.RawOdds, &i.DefaultIsActive, &i.FetchedAt, &i.ExpiresAt, @@ -275,7 +273,7 @@ func (q *Queries) GetOddsWithSettingsByEventID(ctx context.Context, arg GetOddsW } const GetOddsWithSettingsByMarketID = `-- name: GetOddsWithSettingsByMarketID :one -SELECT id, event_id, market_type, market_name, market_category, market_id, odds_market_with_settings.raw_odds, default_is_active, fetched_at, expires_at, company_id, is_active, odds_market_with_settings.raw_odds, updated_at +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 @@ -298,7 +296,6 @@ func (q *Queries) GetOddsWithSettingsByMarketID(ctx context.Context, arg GetOdds &i.MarketName, &i.MarketCategory, &i.MarketID, - &i.RawOdds, &i.DefaultIsActive, &i.FetchedAt, &i.ExpiresAt, diff --git a/gen/db/settings.sql.go b/gen/db/settings.sql.go index 8a9281b..f67fecc 100644 --- a/gen/db/settings.sql.go +++ b/gen/db/settings.sql.go @@ -7,17 +7,140 @@ package dbgen import ( "context" + + "github.com/jackc/pgx/v5/pgtype" ) -const GetSetting = `-- name: GetSetting :one -SELECT key, value, created_at, updated_at -FROM settings +const DeleteAllCompanySetting = `-- name: DeleteAllCompanySetting :exec +DELETE FROM company_settings +WHERE company_id = $1 +` + +func (q *Queries) DeleteAllCompanySetting(ctx context.Context, companyID int64) error { + _, err := q.db.Exec(ctx, DeleteAllCompanySetting, companyID) + return err +} + +const DeleteCompanySetting = `-- name: DeleteCompanySetting :exec +DELETE FROM company_settings +WHERE company_id = $1 + AND key = $2 +` + +type DeleteCompanySettingParams struct { + CompanyID int64 `json:"company_id"` + Key string `json:"key"` +} + +func (q *Queries) DeleteCompanySetting(ctx context.Context, arg DeleteCompanySettingParams) error { + _, err := q.db.Exec(ctx, DeleteCompanySetting, arg.CompanyID, arg.Key) + return err +} + +const GetAllCompanySettings = `-- name: GetAllCompanySettings :many +SELECT company_id, key, value, created_at, updated_at +FROM company_settings +` + +func (q *Queries) GetAllCompanySettings(ctx context.Context) ([]CompanySetting, error) { + rows, err := q.db.Query(ctx, GetAllCompanySettings) + if err != nil { + return nil, err + } + defer rows.Close() + var items []CompanySetting + for rows.Next() { + var i CompanySetting + if err := rows.Scan( + &i.CompanyID, + &i.Key, + &i.Value, + &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 GetCompanySetting = `-- name: GetCompanySetting :many +SELECT company_id, key, value, created_at, updated_at +FROM company_settings +WHERE company_id = $1 +` + +func (q *Queries) GetCompanySetting(ctx context.Context, companyID int64) ([]CompanySetting, error) { + rows, err := q.db.Query(ctx, GetCompanySetting, companyID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []CompanySetting + for rows.Next() { + var i CompanySetting + if err := rows.Scan( + &i.CompanyID, + &i.Key, + &i.Value, + &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 GetCompanySettingsByKey = `-- name: GetCompanySettingsByKey :many +SELECT company_id, key, value, created_at, updated_at +FROM company_settings WHERE key = $1 ` -func (q *Queries) GetSetting(ctx context.Context, key string) (Setting, error) { - row := q.db.QueryRow(ctx, GetSetting, key) - var i Setting +func (q *Queries) GetCompanySettingsByKey(ctx context.Context, key string) ([]CompanySetting, error) { + rows, err := q.db.Query(ctx, GetCompanySettingsByKey, key) + if err != nil { + return nil, err + } + defer rows.Close() + var items []CompanySetting + for rows.Next() { + var i CompanySetting + if err := rows.Scan( + &i.CompanyID, + &i.Key, + &i.Value, + &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 GetGlobalSetting = `-- name: GetGlobalSetting :one +SELECT key, value, created_at, updated_at +FROM global_settings +WHERE key = $1 +` + +func (q *Queries) GetGlobalSetting(ctx context.Context, key string) (GlobalSetting, error) { + row := q.db.QueryRow(ctx, GetGlobalSetting, key) + var i GlobalSetting err := row.Scan( &i.Key, &i.Value, @@ -27,20 +150,20 @@ func (q *Queries) GetSetting(ctx context.Context, key string) (Setting, error) { return i, err } -const GetSettings = `-- name: GetSettings :many +const GetGlobalSettings = `-- name: GetGlobalSettings :many SELECT key, value, created_at, updated_at -FROM settings +FROM global_settings ` -func (q *Queries) GetSettings(ctx context.Context) ([]Setting, error) { - rows, err := q.db.Query(ctx, GetSettings) +func (q *Queries) GetGlobalSettings(ctx context.Context) ([]GlobalSetting, error) { + rows, err := q.db.Query(ctx, GetGlobalSettings) if err != nil { return nil, err } defer rows.Close() - var items []Setting + var items []GlobalSetting for rows.Next() { - var i Setting + var i GlobalSetting if err := rows.Scan( &i.Key, &i.Value, @@ -57,19 +180,79 @@ func (q *Queries) GetSettings(ctx context.Context) ([]Setting, error) { return items, nil } -const UpdateSetting = `-- name: UpdateSetting :exec -UPDATE settings +const GetOverrideSettings = `-- name: GetOverrideSettings :many +SELECT gs.key, gs.value, 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 + AND cs.company_id = $1 +` + +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"` +} + +func (q *Queries) GetOverrideSettings(ctx context.Context, companyID int64) ([]GetOverrideSettingsRow, error) { + rows, err := q.db.Query(ctx, GetOverrideSettings, companyID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetOverrideSettingsRow + for rows.Next() { + var i GetOverrideSettingsRow + if err := rows.Scan( + &i.Key, + &i.Value, + &i.CreatedAt, + &i.UpdatedAt, + &i.Value_2, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const InsertCompanySetting = `-- name: InsertCompanySetting :exec +INSERT INTO company_settings (company_id, key, value) +VALUES ($1, $2, $3) ON CONFLICT (company_id, key) DO +UPDATE +SET value = EXCLUDED.value +` + +type InsertCompanySettingParams struct { + CompanyID int64 `json:"company_id"` + Key string `json:"key"` + Value string `json:"value"` +} + +func (q *Queries) InsertCompanySetting(ctx context.Context, arg InsertCompanySettingParams) error { + _, err := q.db.Exec(ctx, InsertCompanySetting, arg.CompanyID, arg.Key, arg.Value) + return err +} + +const UpdateGlobalSetting = `-- name: UpdateGlobalSetting :exec +UPDATE global_settings SET value = $2, updated_at = CURRENT_TIMESTAMP WHERE key = $1 ` -type UpdateSettingParams struct { +type UpdateGlobalSettingParams struct { Key string `json:"key"` Value string `json:"value"` } -func (q *Queries) UpdateSetting(ctx context.Context, arg UpdateSettingParams) error { - _, err := q.db.Exec(ctx, UpdateSetting, arg.Key, arg.Value) +func (q *Queries) UpdateGlobalSetting(ctx context.Context, arg UpdateGlobalSettingParams) error { + _, err := q.db.Exec(ctx, UpdateGlobalSetting, arg.Key, arg.Value) return err } diff --git a/gen/db/ticket.sql.go b/gen/db/ticket.sql.go index 90e11f6..bc9bb5f 100644 --- a/gen/db/ticket.sql.go +++ b/gen/db/ticket.sql.go @@ -25,8 +25,8 @@ func (q *Queries) CountTicketByIP(ctx context.Context, ip string) (int64, error) } const CreateTicket = `-- name: CreateTicket :one -INSERT INTO tickets (amount, total_odds, ip) -VALUES ($1, $2, $3) +INSERT INTO tickets (amount, total_odds, ip, company_id) +VALUES ($1, $2, $3, $4) RETURNING id, company_id, amount, total_odds, ip, created_at, updated_at ` @@ -34,10 +34,16 @@ type CreateTicketParams struct { Amount int64 `json:"amount"` TotalOdds float32 `json:"total_odds"` Ip string `json:"ip"` + CompanyID int64 `json:"company_id"` } func (q *Queries) CreateTicket(ctx context.Context, arg CreateTicketParams) (Ticket, error) { - row := q.db.QueryRow(ctx, CreateTicket, arg.Amount, arg.TotalOdds, arg.Ip) + row := q.db.QueryRow(ctx, CreateTicket, + arg.Amount, + arg.TotalOdds, + arg.Ip, + arg.CompanyID, + ) var i Ticket err := row.Scan( &i.ID, @@ -99,10 +105,14 @@ func (q *Queries) DeleteTicketOutcome(ctx context.Context, ticketID int64) error const GetAllTickets = `-- name: GetAllTickets :many SELECT id, company_id, amount, total_odds, ip, created_at, updated_at, outcomes FROM ticket_with_outcomes +WHERE ( + company_id = $1 + OR $1 IS NULL + ) ` -func (q *Queries) GetAllTickets(ctx context.Context) ([]TicketWithOutcome, error) { - rows, err := q.db.Query(ctx, GetAllTickets) +func (q *Queries) GetAllTickets(ctx context.Context, companyID pgtype.Int8) ([]TicketWithOutcome, error) { + rows, err := q.db.Query(ctx, GetAllTickets, companyID) if err != nil { return nil, err } @@ -131,7 +141,8 @@ func (q *Queries) GetAllTickets(ctx context.Context) ([]TicketWithOutcome, error } const GetAllTicketsInRange = `-- name: GetAllTicketsInRange :one -SELECT COUNT(*) as total_tickets, COALESCE(SUM(amount), 0) as total_amount +SELECT COUNT(*) as total_tickets, + COALESCE(SUM(amount), 0) as total_amount FROM tickets WHERE created_at BETWEEN $1 AND $2 ` diff --git a/internal/domain/bet.go b/internal/domain/bet.go index 16e55b0..6ee3734 100644 --- a/internal/domain/bet.go +++ b/internal/domain/bet.go @@ -2,6 +2,9 @@ package domain import ( "time" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/jackc/pgx/v5/pgtype" ) // The Odd ID here is not the odd id from our database @@ -49,6 +52,7 @@ type Bet struct { TotalOdds float32 Status OutcomeStatus UserID int64 + CompanyID int64 IsShopBet bool CashedOut bool FastCode string @@ -57,6 +61,7 @@ type Bet struct { type BetFilter struct { UserID ValidInt64 + CompanyID ValidInt64 CashedOut ValidBool IsShopBet ValidBool Query ValidString @@ -81,6 +86,7 @@ type GetBet struct { FullName string PhoneNumber string UserID int64 + CompanyID int64 IsShopBet bool CashedOut bool Outcomes []BetOutcome @@ -93,6 +99,7 @@ type CreateBet struct { TotalOdds float32 Status OutcomeStatus UserID int64 + CompanyID int64 IsShopBet bool OutcomesHash string FastCode string @@ -133,6 +140,7 @@ type CreateBetRes struct { TotalOdds float32 `json:"total_odds" example:"4.22"` Status OutcomeStatus `json:"status" example:"1"` UserID int64 `json:"user_id" example:"2"` + CompanyID int64 `json:"company_id" example:"1"` IsShopBet bool `json:"is_shop_bet" example:"false"` CreatedNumber int64 `json:"created_number" example:"2"` FastCode string `json:"fast_code"` @@ -145,19 +153,21 @@ type BetRes struct { Status OutcomeStatus `json:"status" example:"1"` Fullname string `json:"full_name" example:"John Smith"` UserID int64 `json:"user_id" example:"2"` + CompanyID int64 `json:"company_id" example:"1"` IsShopBet bool `json:"is_shop_bet" example:"false"` CashedOut bool `json:"cashed_out" example:"false"` CreatedAt time.Time `json:"created_at" example:"2025-04-08T12:00:00Z"` FastCode string `json:"fast_code"` } -func ConvertCreateBet(bet Bet, createdNumber int64) CreateBetRes { +func ConvertCreateBetRes(bet Bet, createdNumber int64) CreateBetRes { return CreateBetRes{ ID: bet.ID, Amount: bet.Amount.Float32(), TotalOdds: bet.TotalOdds, Status: bet.Status, UserID: bet.UserID, + CompanyID: bet.CompanyID, CreatedNumber: createdNumber, IsShopBet: bet.IsShopBet, FastCode: bet.FastCode, @@ -172,6 +182,7 @@ func ConvertBet(bet GetBet) BetRes { Status: bet.Status, Fullname: bet.FullName, UserID: bet.UserID, + CompanyID: bet.CompanyID, Outcomes: bet.Outcomes, IsShopBet: bet.IsShopBet, CashedOut: bet.CashedOut, @@ -179,3 +190,106 @@ func ConvertBet(bet GetBet) BetRes { FastCode: bet.FastCode, } } + +func ConvertDBBet(bet dbgen.Bet) Bet { + return Bet{ + ID: bet.ID, + Amount: Currency(bet.Amount), + TotalOdds: bet.TotalOdds, + Status: OutcomeStatus(bet.Status), + UserID: bet.UserID, + CompanyID: bet.CompanyID, + IsShopBet: bet.IsShopBet, + CashedOut: bet.CashedOut, + FastCode: bet.FastCode, + CreatedAt: bet.CreatedAt.Time, + } +} + +func ConvertDBBetOutcomes(outcome dbgen.BetOutcome) BetOutcome { + return BetOutcome{ + ID: outcome.ID, + BetID: outcome.BetID, + SportID: outcome.SportID, + EventID: outcome.EventID, + OddID: outcome.OddID, + HomeTeamName: outcome.HomeTeamName, + AwayTeamName: outcome.AwayTeamName, + MarketID: outcome.MarketID, + MarketName: outcome.MarketName, + Odd: outcome.Odd, + OddName: outcome.OddName, + OddHeader: outcome.OddHeader, + OddHandicap: outcome.OddHandicap, + Status: OutcomeStatus(outcome.Status), + Expires: outcome.Expires.Time, + } +} + +func ConvertDBBetWithOutcomes(bet dbgen.BetWithOutcome) GetBet { + var outcomes []BetOutcome = make([]BetOutcome, 0, len(bet.Outcomes)) + + for _, outcome := range bet.Outcomes { + outcomes = append(outcomes, ConvertDBBetOutcomes(outcome)) + } + + return GetBet{ + ID: bet.ID, + Amount: Currency(bet.Amount), + TotalOdds: bet.TotalOdds, + Status: OutcomeStatus(bet.Status), + FullName: bet.FullName.(string), + PhoneNumber: bet.PhoneNumber.String, + UserID: bet.UserID, + IsShopBet: bet.IsShopBet, + CashedOut: bet.CashedOut, + Outcomes: outcomes, + FastCode: bet.FastCode, + CreatedAt: bet.CreatedAt.Time, + } +} + +func ConvertDBFlag(flag dbgen.Flag) Flag { + return Flag{ + ID: flag.ID, + BetID: flag.BetID.Int64, + OddID: flag.OddsMarketID.Int64, + Reason: flag.Reason.String, + FlaggedAt: flag.FlaggedAt.Time, + Resolved: flag.Resolved.Bool, + } +} + +func ConvertDBCreateBetOutcome(betOutcome CreateBetOutcome) dbgen.CreateBetOutcomeParams { + return dbgen.CreateBetOutcomeParams{ + BetID: betOutcome.BetID, + EventID: betOutcome.EventID, + SportID: betOutcome.SportID, + OddID: betOutcome.OddID, + HomeTeamName: betOutcome.HomeTeamName, + AwayTeamName: betOutcome.AwayTeamName, + MarketID: betOutcome.MarketID, + MarketName: betOutcome.MarketName, + Odd: betOutcome.Odd, + OddName: betOutcome.OddName, + OddHeader: betOutcome.OddHeader, + OddHandicap: betOutcome.OddHandicap, + Expires: pgtype.Timestamp{ + Time: betOutcome.Expires, + Valid: true, + }, + } +} + +func ConvertCreateBet(bet CreateBet) dbgen.CreateBetParams { + return dbgen.CreateBetParams{ + Amount: int64(bet.Amount), + TotalOdds: bet.TotalOdds, + Status: int32(bet.Status), + UserID: bet.UserID, + CompanyID: bet.CompanyID, + IsShopBet: bet.IsShopBet, + OutcomesHash: bet.OutcomesHash, + FastCode: bet.FastCode, + } +} diff --git a/internal/domain/common_log.go b/internal/domain/common_log.go index a7cda24..43763a4 100644 --- a/internal/domain/common_log.go +++ b/internal/domain/common_log.go @@ -21,14 +21,14 @@ var SuccessResZapFields = []zap.Field{ zap.Time("timestamp", time.Now()), } -var BadRequestLogger = MongoDBLogger.With( - zap.Int("status_code", fiber.StatusBadRequest), - zap.Time("timestamp", time.Now())) +// var BadRequestLogger = MongoDBLogger.With( +// zap.Int("status_code", fiber.StatusBadRequest), +// zap.Time("timestamp", time.Now())) -var InternalServerErrorLogger = MongoDBLogger.With( - zap.Int("status_code", fiber.StatusInternalServerError), - zap.Time("timestamp", time.Now())) +// var InternalServerErrorLogger = MongoDBLogger.With( +// zap.Int("status_code", fiber.StatusInternalServerError), +// zap.Time("timestamp", time.Now())) -var SuccessResLogger = MongoDBLogger.With( - zap.Int("status_code", fiber.StatusOK), - zap.Time("timestamp", time.Now())) +// var SuccessResLogger = MongoDBLogger.With( +// zap.Int("status_code", fiber.StatusOK), +// zap.Time("timestamp", time.Now())) diff --git a/internal/domain/currency.go b/internal/domain/currency.go index e436a87..7ce3b3c 100644 --- a/internal/domain/currency.go +++ b/internal/domain/currency.go @@ -3,28 +3,26 @@ package domain import ( "errors" "fmt" + "math" "time" ) type Currency int64 -// ToCurrency converts a float32 to Currency +// ToCurrency converts a float32 (like 12.34) into Currency (stored in cents). func ToCurrency(f float32) Currency { - return Currency((f * 100) + 0.5) + cents := math.Round(float64(f) * 100) // avoid float32 precision issues + return Currency(int64(cents)) } -// Float32 converts a Currency to float32 +// Float32 converts a Currency back into float32 (like 12.34). func (m Currency) Float32() float32 { - x := float32(m) - x = x / 100 - return x + return float32(m) / 100 } -// String returns a formatted Currency value +// String returns a formatted Currency value for display. func (m Currency) String() string { - x := float32(m) - x = x / 100 - return fmt.Sprintf("$%.2f", x) + return fmt.Sprintf("$%.2f", m.Float32()) } type IntCurrency string diff --git a/internal/domain/custom_odds.go b/internal/domain/custom_odds.go index 1a0a171..6163c70 100644 --- a/internal/domain/custom_odds.go +++ b/internal/domain/custom_odds.go @@ -1,69 +1,69 @@ package domain -import ( - "encoding/json" - "time" +// import ( +// "encoding/json" +// "time" - dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" -) +// dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" +// ) -type CustomOdd struct { - ID int64 - OddID int64 - CompanyID int64 - EventID string - RawOdds []json.RawMessage - CreatedAt time.Time -} +// type CustomOdd struct { +// ID int64 +// OddID int64 +// CompanyID int64 +// EventID string +// RawOdds []json.RawMessage +// CreatedAt time.Time +// } -type CreateCustomOdd struct { - OddID int64 - CompanyID int64 - EventID string - RawOdds []json.RawMessage -} +// type CreateCustomOdd struct { +// OddID int64 +// CompanyID int64 +// EventID string +// RawOdds []json.RawMessage +// } -type CustomOddFilter struct { - CompanyID ValidInt64 -} +// type CustomOddFilter struct { +// CompanyID ValidInt64 +// } -func ConvertCreateCustomOdd(odd CreateCustomOdd) (dbgen.InsertCustomOddParams, error) { - rawOddsBytes, err := json.Marshal(odd.RawOdds) +// func ConvertCreateCustomOdd(odd CreateCustomOdd) (dbgen.InsertCustomOddParams, error) { +// rawOddsBytes, err := json.Marshal(odd.RawOdds) - if err != nil { - return dbgen.InsertCustomOddParams{}, err - } - return dbgen.InsertCustomOddParams{ - OddID: odd.OddID, - CompanyID: odd.CompanyID, - EventID: odd.EventID, - RawOdds: rawOddsBytes, - }, nil -} +// if err != nil { +// return dbgen.InsertCustomOddParams{}, err +// } +// return dbgen.InsertCustomOddParams{ +// OddID: odd.OddID, +// CompanyID: odd.CompanyID, +// EventID: odd.EventID, +// RawOdds: rawOddsBytes, +// }, nil +// } -func ConvertDBCustomOdd(dbCustomOdd dbgen.CustomOdd) (CustomOdd, error) { - var rawOdds []json.RawMessage - if err := json.Unmarshal(dbCustomOdd.RawOdds, &rawOdds); err != nil { - return CustomOdd{}, err - } - return CustomOdd{ - ID: dbCustomOdd.ID, - OddID: dbCustomOdd.OddID, - CompanyID: dbCustomOdd.CompanyID, - EventID: dbCustomOdd.EventID, - RawOdds: rawOdds, - CreatedAt: dbCustomOdd.CreatedAt.Time, - }, nil -} +// func ConvertDBCustomOdd(dbCustomOdd dbgen.CustomOdd) (CustomOdd, error) { +// var rawOdds []json.RawMessage +// if err := json.Unmarshal(dbCustomOdd.RawOdds, &rawOdds); err != nil { +// return CustomOdd{}, err +// } +// return CustomOdd{ +// ID: dbCustomOdd.ID, +// OddID: dbCustomOdd.OddID, +// CompanyID: dbCustomOdd.CompanyID, +// EventID: dbCustomOdd.EventID, +// RawOdds: rawOdds, +// CreatedAt: dbCustomOdd.CreatedAt.Time, +// }, nil +// } -func ConvertDbCustomOdds(list []dbgen.CustomOdd) ([]CustomOdd, error) { - result := make([]CustomOdd, 0, len(list)) - for _, item := range list { - convertedItem, err := ConvertDBCustomOdd(item) - if err != nil { - return nil, err - } - result = append(result, convertedItem) - } - return result, nil -} +// func ConvertDbCustomOdds(list []dbgen.CustomOdd) ([]CustomOdd, error) { +// result := make([]CustomOdd, 0, len(list)) +// for _, item := range list { +// convertedItem, err := ConvertDBCustomOdd(item) +// if err != nil { +// return nil, err +// } +// result = append(result, convertedItem) +// } +// return result, nil +// } diff --git a/internal/domain/disabled_odds.go b/internal/domain/disabled_odds.go index c4f731f..2a3c83c 100644 --- a/internal/domain/disabled_odds.go +++ b/internal/domain/disabled_odds.go @@ -7,38 +7,38 @@ import ( ) type DisabledOdd struct { - ID int64 - OddID int64 - RawOddID int64 - EventID string - CompanyID int64 - CreatedAt time.Time + ID int64 + OddMarketID int64 + RawOddID int64 + EventID string + CompanyID int64 + CreatedAt time.Time } type CreateDisabledOdd struct { - OddID int64 - RawOddID int64 - EventID string - CompanyID int64 + OddMarketID int64 + RawOddID int64 + EventID string + CompanyID int64 } func ConvertCreateDisabledOdd(odd CreateDisabledOdd) dbgen.InsertDisabledOddsParams { return dbgen.InsertDisabledOddsParams{ - OddID: odd.OddID, - EventID: odd.EventID, - CompanyID: odd.CompanyID, - RawOddID: odd.RawOddID, + OddsMarketID: odd.OddMarketID, + EventID: odd.EventID, + CompanyID: odd.CompanyID, + RawOddID: odd.RawOddID, } } func ConvertDBDisabledOdd(dbDisabledOdd dbgen.DisabledOdd) DisabledOdd { return DisabledOdd{ - ID: dbDisabledOdd.ID, - OddID: dbDisabledOdd.OddID, - RawOddID: dbDisabledOdd.RawOddID, - EventID: dbDisabledOdd.EventID, - CompanyID: dbDisabledOdd.CompanyID, - CreatedAt: dbDisabledOdd.CreatedAt.Time, + ID: dbDisabledOdd.ID, + OddMarketID: dbDisabledOdd.OddsMarketID, + RawOddID: dbDisabledOdd.RawOddID, + EventID: dbDisabledOdd.EventID, + CompanyID: dbDisabledOdd.CompanyID, + CreatedAt: dbDisabledOdd.CreatedAt.Time, } } diff --git a/internal/domain/result.go b/internal/domain/result.go index 69f74fc..5369a2b 100644 --- a/internal/domain/result.go +++ b/internal/domain/result.go @@ -23,7 +23,7 @@ type Result struct { FullTimeScore string HalfTimeScore string SS string - Scores map[string]Score + Scores map[string]ScoreResultResponse CreatedAt time.Time UpdatedAt time.Time } diff --git a/internal/domain/role.go b/internal/domain/role.go index 2643782..dcd2c57 100644 --- a/internal/domain/role.go +++ b/internal/domain/role.go @@ -9,3 +9,12 @@ const ( RoleCustomer Role = "customer" RoleCashier Role = "cashier" ) + +func (r Role) IsValid() bool { + switch r { + case RoleSuperAdmin, RoleAdmin, RoleBranchManager, RoleCustomer, RoleCashier: + return true + default: + return false + } +} diff --git a/internal/domain/setting_list.go b/internal/domain/setting_list.go new file mode 100644 index 0000000..87ac482 --- /dev/null +++ b/internal/domain/setting_list.go @@ -0,0 +1,447 @@ +package domain + +import ( + "errors" + "fmt" + "strconv" + "strings" + "time" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "go.uber.org/zap" +) + +var ( + ErrSettingNotFound = errors.New("cannot find setting in list") +) + +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"` +} + +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"` +} + +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"` +} + +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), + } +} + +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), + } +} + +// Custom Validations for non-generic types +func (vsl *ValidSettingList) CustomValidationSettings() error { + if !SMSProvider(vsl.SMSProvider.Value).IsValid() { + return fmt.Errorf("sms provider invalid") + } + return nil +} + +func (vsl *ValidSettingList) GetInt64SettingsMap() map[string]*ValidInt64 { + return map[string]*ValidInt64{ + "max_number_of_outcomes": &vsl.MaxNumberOfOutcomes, + "daily_ticket_limit": &vsl.DailyTicketPerIP, + } +} + +func (vsl *ValidSettingList) GetCurrencySettingsMap() map[string]*ValidCurrency { + return map[string]*ValidCurrency{ + "bet_amount_limit": &vsl.BetAmountLimit, + "total_winnings_limit": &vsl.TotalWinningLimit, + "amount_for_bet_referral": &vsl.AmountForBetReferral, + "cashback_amount_cap": &vsl.CashbackAmountCap, + } +} + +func (vsl *ValidSettingList) GetStringSettingsMap() map[string]*ValidString { + return map[string]*ValidString{ + "sms_provider": &vsl.SMSProvider, + } +} + +func (vsl *ValidSettingList) GetBoolSettingsMap() map[string]*ValidBool { + return map[string]*ValidBool{} +} + +func (vsl *ValidSettingList) GetFloat32SettingsMap() map[string]*ValidFloat32 { + return map[string]*ValidFloat32{} +} + +func (vsl *ValidSettingList) GetTimeSettingsMap() map[string]*ValidTime { + return map[string]*ValidTime{} +} + +func (vsl *ValidSettingList) GetTotalSettings() int { + return len(vsl.GetInt64SettingsMap()) + + len(vsl.GetCurrencySettingsMap()) + + len(vsl.GetStringSettingsMap()) + + len(vsl.GetBoolSettingsMap()) + + len(vsl.GetFloat32SettingsMap()) + + len(vsl.GetTimeSettingsMap()) +} + +func (vsl *ValidSettingList) GetAllValid() map[string]*bool { + + settingValid := make(map[string]*bool) + + for key, setting := range vsl.GetInt64SettingsMap() { + settingValid[key] = &(*setting).Valid + } + for key, setting := range vsl.GetCurrencySettingsMap() { + settingValid[key] = &(*setting).Valid + } + for key, setting := range vsl.GetStringSettingsMap() { + settingValid[key] = &(*setting).Valid + } + for key, setting := range vsl.GetBoolSettingsMap() { + settingValid[key] = &(*setting).Valid + } + for key, setting := range vsl.GetFloat32SettingsMap() { + settingValid[key] = &(*setting).Valid + } + for key, setting := range vsl.GetTimeSettingsMap() { + settingValid[key] = &(*setting).Valid + } + + return settingValid +} + +func setValidSetting[T any](settings map[string]*T, searchKey string, setVal T) error { + for key, setting := range settings { + if key == searchKey { + *setting = setVal + } + 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 + } + return setValidSetting(vsl.GetCurrencySettingsMap(), searchKey, ValidCurrency{Value: Currency(value), Valid: true}) +} + +func (vsl *ValidSettingList) SetStringSetting(searchKey string, searchVal string) error { + return setValidSetting(vsl.GetStringSettingsMap(), searchKey, ValidString{Value: searchVal, Valid: true}) +} + +func (vsl *ValidSettingList) SetBoolSetting(searchKey string, searchVal string) error { + value, err := strconv.ParseBool(searchVal) + if err != nil { + return err + } + return setValidSetting(vsl.GetBoolSettingsMap(), searchKey, ValidBool{Value: value, Valid: true}) +} + +func (vsl *ValidSettingList) SetFloat32Setting(searchKey string, searchVal string) error { + value, err := strconv.ParseFloat(searchVal, 32) + if err != nil { + return err + } + return setValidSetting(vsl.GetFloat32SettingsMap(), searchKey, ValidFloat32{Value: float32(value), Valid: true}) +} + +func (vsl *ValidSettingList) SetTimeSetting(searchKey string, searchVal string) error { + value, err := time.Parse(time.RFC3339, searchVal) + if err != nil { + return err + } + return setValidSetting(vsl.GetTimeSettingsMap(), searchKey, ValidTime{Value: value, Valid: true}) +} + +func (vsl *ValidSettingList) SetSetting(searchKey string, searchVal string) error { + setters := []func(string, string) error{ + vsl.SetInt64Setting, + vsl.SetCurrencySetting, + vsl.SetStringSetting, + vsl.SetBoolSetting, + vsl.SetFloat32Setting, + vsl.SetTimeSetting, + } + + for _, setter := range setters { + if err := setter(searchKey, searchVal); err != nil { + if err == ErrSettingNotFound { + continue // not this setter, try the next + } + return fmt.Errorf("error while processing setting %q: %w", searchKey, err) + } + return nil // successfully set + } + + // If we get here, none of the setters matched + return ErrSettingNotFound +} + +func convertValidSettings[T any]( + settings map[string]*T, + isValid func(*T) bool, + toString func(*T) string, +) []Setting { + result := make([]Setting, 0, len(settings)) + for key, s := range settings { + if isValid(s) { + result = append(result, Setting{ + Key: key, + Value: toString(s), + }) + } + } + return result +} + +func (vsl *ValidSettingList) ConvertInt64Settings() []Setting { + return convertValidSettings( + vsl.GetInt64SettingsMap(), + func(s *ValidInt64) bool { return s.Valid }, + func(s *ValidInt64) string { return strconv.FormatInt(s.Value, 10) }, + ) +} + +func (vsl *ValidSettingList) ConvertCurrencySettings() []Setting { + return convertValidSettings( + vsl.GetCurrencySettingsMap(), + func(s *ValidCurrency) bool { return s.Valid }, + func(s *ValidCurrency) string { return strconv.FormatInt(int64(s.Value), 10) }, + ) +} + +func (vsl *ValidSettingList) ConvertStringSettings() []Setting { + return convertValidSettings( + vsl.GetStringSettingsMap(), + func(s *ValidString) bool { return s.Valid }, + func(s *ValidString) string { return s.Value }, + ) +} + +func (vsl *ValidSettingList) ConvertBoolSettings() []Setting { + return convertValidSettings( + vsl.GetBoolSettingsMap(), + func(s *ValidBool) bool { return s.Valid }, + func(s *ValidBool) string { return strconv.FormatBool(s.Value) }, + ) +} + +func (vsl *ValidSettingList) ConvertFloat32Settings() []Setting { + return convertValidSettings( + vsl.GetFloat32SettingsMap(), + func(s *ValidFloat32) bool { return s.Valid }, + func(s *ValidFloat32) string { return strconv.FormatFloat(float64(s.Value), 'f', -1, 32) }, + ) +} + +func (vsl *ValidSettingList) ConvertTimeSettings() []Setting { + return convertValidSettings( + vsl.GetTimeSettingsMap(), + func(s *ValidTime) bool { return s.Valid }, + func(s *ValidTime) string { return s.Value.Format(time.RFC3339) }, + ) +} + +func validateSettings[T any]( + settings map[string]*T, + customValidator func(*T) bool, +) error { + var errs []string + for key, s := range settings { + if !customValidator(s) { + errs = append(errs, fmt.Sprintf("%v is invalid", key)) + } + } + if len(errs) > 0 { + return fmt.Errorf(strings.Join(errs, "; ")) + } + + return nil +} + +func (vsl *ValidSettingList) ValidateInt64Settings() error { + return validateSettings(vsl.GetInt64SettingsMap(), + func(s *ValidInt64) bool { + return s.Valid + }, + ) +} + +func (vsl *ValidSettingList) ValidateCurrencySettings() error { + return validateSettings(vsl.GetCurrencySettingsMap(), + func(s *ValidCurrency) bool { + return s.Valid + }, + ) +} + +func (vsl *ValidSettingList) ValidateStringSettings() error { + return validateSettings(vsl.GetStringSettingsMap(), + func(s *ValidString) bool { + return s.Valid + }, + ) +} + +func (vsl *ValidSettingList) ValidateBoolSettings() error { + return validateSettings(vsl.GetBoolSettingsMap(), + func(s *ValidBool) bool { + return s.Valid + }, + ) +} + +func (vsl *ValidSettingList) ValidateFloat32Settings() error { + return validateSettings(vsl.GetFloat32SettingsMap(), + func(s *ValidFloat32) bool { + return s.Valid + }, + ) +} + +func (vsl *ValidSettingList) ValidateTimeSettings() error { + return validateSettings(vsl.GetTimeSettingsMap(), + func(s *ValidTime) bool { + return s.Valid + }, + ) +} + +func (vsl *ValidSettingList) ValidateAllSettings() error { + var errs []string + validators := []func() error{ + vsl.ValidateInt64Settings, + vsl.ValidateCurrencySettings, + vsl.ValidateStringSettings, + vsl.ValidateBoolSettings, + vsl.ValidateFloat32Settings, + vsl.ValidateTimeSettings, + vsl.CustomValidationSettings, + } + + for _, validator := range validators { + if err := validator(); err != nil { + errs = append(errs, err.Error()) + } + } + if len(errs) > 0 { + return fmt.Errorf(strings.Join(errs, "; ")) + } + + return nil +} + +func (vsl *ValidSettingList) ConvertAllSettings() []Setting { + totalCap := vsl.GetTotalSettings() + all := make([]Setting, 0, totalCap) + + all = append(all, vsl.ConvertInt64Settings()...) + all = append(all, vsl.ConvertCurrencySettings()...) + all = append(all, vsl.ConvertStringSettings()...) + all = append(all, vsl.ConvertBoolSettings()...) + all = append(all, vsl.ConvertFloat32Settings()...) + all = append(all, vsl.ConvertTimeSettings()...) + + return all +} + +func ConvertDBGlobalSettingList(settings []dbgen.GlobalSetting) (SettingList, error) { + var dbSettingList ValidSettingList + + for _, setting := range settings { + if err := dbSettingList.SetSetting(setting.Key, setting.Value); err != nil { + if err == ErrSettingNotFound { + MongoDBLogger.Warn("unknown setting found on database", zap.String("setting", setting.Key)) + } + } + } + + if err := dbSettingList.ValidateAllSettings(); err != nil { + fmt.Printf("setting validation error: %v \n") + MongoDBLogger.Warn("setting validation error", zap.Error(err)) + return SettingList{}, err + } + + settingList := dbSettingList.ToSettingList() + + return settingList, nil +} + +func ConvertDBOverrideSettingList(settings []dbgen.GetOverrideSettingsRow) (SettingList, error) { + var dbSettingList ValidSettingList + + for _, setting := range settings { + if err := dbSettingList.SetSetting(setting.Key, setting.Value); err != nil { + if err == ErrSettingNotFound { + MongoDBLogger.Warn("unknown setting found on database", zap.String("setting", setting.Key)) + } + } + } + + if err := dbSettingList.ValidateAllSettings(); err != nil { + fmt.Printf("setting validation error: %v \n") + MongoDBLogger.Warn("setting validation error", zap.Error(err)) + return SettingList{}, err + } + + settingList := dbSettingList.ToSettingList() + + return settingList, nil +} diff --git a/internal/domain/settings.go b/internal/domain/settings.go index eaa6256..415816c 100644 --- a/internal/domain/settings.go +++ b/internal/domain/settings.go @@ -1,8 +1,9 @@ package domain import ( - "strconv" "time" + + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" ) type Setting struct { @@ -16,160 +17,20 @@ type SettingRes struct { Value string `json:"value"` UpdatedAt time.Time `json:"updated_at"` } - -type UpdateSettingListReq 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"` +type CompanySetting struct { + Key string + Value string + CompanyID int64 + UpdatedAt time.Time + CreatedAt time.Time } -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"` -} - -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"` -} - -type ValidSettingList struct { - SMSProvider ValidString - MaxNumberOfOutcomes ValidInt64 - BetAmountLimit ValidInt64 - DailyTicketPerIP ValidInt64 - TotalWinningLimit ValidInt64 - AmountForBetReferral ValidInt64 - CashbackAmountCap ValidInt64 -} - -func ConvertInt64SettingsMap(dbSettingList *ValidSettingList) map[string]*ValidInt64 { - return map[string]*ValidInt64{ - "max_number_of_outcomes": &dbSettingList.MaxNumberOfOutcomes, - "bet_amount_limit": &dbSettingList.BetAmountLimit, - "daily_ticket_limit": &dbSettingList.DailyTicketPerIP, - "total_winnings_limit": &dbSettingList.TotalWinningLimit, - "amount_for_bet_referral": &dbSettingList.AmountForBetReferral, - "cashback_amount_cap": &dbSettingList.CashbackAmountCap, - } -} - -func ConvertStringSettingsMap(dbSettingList *ValidSettingList) map[string]*ValidString { - return map[string]*ValidString{ - "sms_provider": &dbSettingList.SMSProvider, - } -} - -func ConvertBoolSettingsMap(dbSettingList *ValidSettingList) map[string]*ValidBool { - return map[string]*ValidBool{} -} - -func ConvertFloat32SettingsMap(dbSettingList *ValidSettingList) map[string]*ValidFloat32 { - return map[string]*ValidFloat32{} -} - -func ConvertTimeSettingsMap(dbSettingList *ValidSettingList) map[string]*ValidTime { - return map[string]*ValidTime{} -} - -func ValidateSettingList(dbSettingList ValidSettingList) SettingList { - // TODO: Add validation here - return SettingList{ - SMSProvider: SMSProvider(dbSettingList.SMSProvider.Value), - MaxNumberOfOutcomes: dbSettingList.MaxNumberOfOutcomes.Value, - BetAmountLimit: Currency(dbSettingList.BetAmountLimit.Value), - DailyTicketPerIP: dbSettingList.DailyTicketPerIP.Value, - TotalWinningLimit: Currency(dbSettingList.TotalWinningLimit.Value), - AmountForBetReferral: Currency(dbSettingList.AmountForBetReferral.Value), - CashbackAmountCap: Currency(dbSettingList.CashbackAmountCap.Value), - } -} - -func ConvertValidSettingList(settingList ValidSettingList) []Setting { - var convertedSettings []Setting - // if settingList.AmountForBetReferral.Valid { - // newValue := strconv.FormatInt(settingList.AmountForBetReferral.Value, 10) - // settings = append(settings, Setting{ - // Key: "amount_for_bet_referral", - // Value: newValue, - // }) - // } - - int64SettingsMap := ConvertInt64SettingsMap(&settingList) - stringSettingsMap := ConvertStringSettingsMap(&settingList) - boolSettingsMap := ConvertBoolSettingsMap(&settingList) - float32SettingsMap := ConvertFloat32SettingsMap(&settingList) - timeSettingsMap := ConvertTimeSettingsMap(&settingList) - - for key, settingPtr := range int64SettingsMap { - setting := *settingPtr - if setting.Valid { - stringVal := strconv.FormatInt(setting.Value, 10) - convertedSettings = append(convertedSettings, Setting{ - Key: key, - Value: stringVal, - }) - } - } - - for key, settingPtr := range stringSettingsMap { - setting := *settingPtr - if setting.Valid { - convertedSettings = append(convertedSettings, Setting{ - Key: key, - Value: setting.Value, - }) - } - } - - for key, settingPtr := range boolSettingsMap { - setting := *settingPtr - if setting.Valid { - stringVal := strconv.FormatBool(setting.Value) - convertedSettings = append(convertedSettings, Setting{ - Key: key, - Value: stringVal, - }) - } - } - - for key, settingPtr := range float32SettingsMap { - setting := *settingPtr - if setting.Valid { - stringVal := strconv.FormatFloat(float64(setting.Value), 'E', -1, 64) - convertedSettings = append(convertedSettings, Setting{ - Key: key, - Value: stringVal, - }) - } - } - - for key, settingPtr := range timeSettingsMap { - setting := *settingPtr - if setting.Valid { - var stringVal string = setting.Value.Format("2006-01-02 15:04:05") - convertedSettings = append(convertedSettings, Setting{ - Key: key, - Value: stringVal, - }) - } - } - - return convertedSettings +type CompanySettingRes struct { + Key string `json:"key"` + Value string `json:"value"` + CompanyID int64 `json:"company_id"` + UpdatedAt time.Time `json:"updated_at"` + CreatedAt time.Time `json:"created_at"` } func ConvertSetting(setting Setting) SettingRes { @@ -180,14 +41,20 @@ func ConvertSetting(setting Setting) SettingRes { } } -func ConvertUpdateSettingListReq(settings UpdateSettingListReq) ValidSettingList { - return ValidSettingList{ - SMSProvider: ConvertStringPtr(settings.SMSProvider), - MaxNumberOfOutcomes: ConvertInt64Ptr(settings.MaxNumberOfOutcomes), - BetAmountLimit: ConvertCurrencyFloatPtr(settings.BetAmountLimit), - DailyTicketPerIP: ConvertInt64Ptr(settings.DailyTicketPerIP), - TotalWinningLimit: ConvertCurrencyFloatPtr(settings.TotalWinningLimit), - AmountForBetReferral: ConvertCurrencyFloatPtr(settings.AmountForBetReferral), - CashbackAmountCap: ConvertCurrencyFloatPtr(settings.CashbackAmountCap), +func ConvertCompanySetting(companySetting dbgen.CompanySetting) CompanySetting { + return CompanySetting{ + Key: companySetting.Key, + Value: companySetting.Value, + CompanyID: companySetting.CompanyID, + UpdatedAt: companySetting.UpdatedAt.Time, + CreatedAt: companySetting.CreatedAt.Time, } } + +func ConvertCompanySettings(settings []dbgen.CompanySetting) []CompanySetting { + result := make([]CompanySetting, 0, len(settings)) + for _, setting := range settings { + result = append(result, ConvertCompanySetting(setting)) + } + return result +} diff --git a/internal/domain/ticket.go b/internal/domain/ticket.go index 63c4d29..aa724e3 100644 --- a/internal/domain/ticket.go +++ b/internal/domain/ticket.go @@ -39,6 +39,7 @@ type Ticket struct { ID int64 Amount Currency TotalOdds float32 + CompanyID int64 } type GetTicket struct { @@ -46,12 +47,14 @@ type GetTicket struct { Amount Currency TotalOdds float32 Outcomes []TicketOutcome + CompanyID int64 } type CreateTicket struct { Amount Currency TotalOdds float32 IP string + CompanyID int64 } type CreateTicketOutcomeReq struct { @@ -80,4 +83,9 @@ type TicketRes struct { Outcomes []TicketOutcome `json:"outcomes"` Amount float32 `json:"amount" example:"100.0"` TotalOdds float32 `json:"total_odds" example:"4.22"` + CompanyID int64 `json:"company_id" example:"1"` +} + +type TicketFilter struct { + CompanyID ValidInt64 } diff --git a/internal/domain/user.go b/internal/domain/user.go index ba0099d..194a89b 100644 --- a/internal/domain/user.go +++ b/internal/domain/user.go @@ -35,10 +35,7 @@ type UserFilter struct { CreatedBefore ValidTime CreatedAfter ValidTime } -type ValidRole struct { - Value Role - Valid bool -} + type RegisterUserReq struct { FirstName string diff --git a/internal/domain/validtypes.go b/internal/domain/validtypes.go index d402517..c20f6c3 100644 --- a/internal/domain/validtypes.go +++ b/internal/domain/validtypes.go @@ -9,38 +9,12 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +// Valid Int64 type ValidInt64 struct { Value int64 Valid bool } -type ValidInt struct { - Value int - Valid bool -} -type ValidInt32 struct { - Value int32 - Valid bool -} -type ValidFloat32 struct { - Value float32 - Valid bool -} - -type ValidString struct { - Value string - Valid bool -} -type ValidTime struct { - Value time.Time - Valid bool -} -type ValidBool struct { - Value bool - Valid bool -} - -// ValidInt64 → pgtype.Int8 func (v ValidInt64) ToPG() pgtype.Int8 { return pgtype.Int8{ Int64: v.Value, @@ -48,126 +22,6 @@ func (v ValidInt64) ToPG() pgtype.Int8 { } } -// ValidInt32 → pgtype.Int4 -func (v ValidInt32) ToPG() pgtype.Int4 { - return pgtype.Int4{ - Int32: v.Value, - Valid: v.Valid, - } -} - -// ValidInt → pgtype.Int4 (Go int mapped to int32 for pg compatibility) -func (v ValidInt) ToPG() pgtype.Int4 { - return pgtype.Int4{ - Int32: int32(v.Value), - Valid: v.Valid, - } -} - -// ValidFloat32 → pgtype.Float4 -func (v ValidFloat32) ToPG() pgtype.Float4 { - return pgtype.Float4{ - Float32: v.Value, - Valid: v.Valid, - } -} - -// ValidString → pgtype.Text -func (v ValidString) ToPG() pgtype.Text { - return pgtype.Text{ - String: v.Value, - Valid: v.Valid, - } -} - -// ValidTime → pgtype.Timestamp -func (v ValidTime) ToPG() pgtype.Timestamp { - return pgtype.Timestamp{ - Time: v.Value, - Valid: v.Valid, - } -} - -// ValidBool → pgtype.Bool -func (v ValidBool) ToPG() pgtype.Bool { - return pgtype.Bool{ - Bool: v.Value, - Valid: v.Valid, - } -} - -func ConvertInt64Ptr(value *int64) ValidInt64 { - if value == nil { - return ValidInt64{} - } - return ValidInt64{ - Value: *value, - Valid: true, - } -} - -func ConvertInt32Ptr(value *int32) ValidInt32 { - if value == nil { - return ValidInt32{} - } - return ValidInt32{ - Value: *value, - Valid: true, - } -} -func ConvertIntPtr(value *int) ValidInt { - if value == nil { - return ValidInt{} - } - return ValidInt{ - Value: *value, - Valid: true, - } -} - -func ConvertStringPtr(value *string) ValidString { - if value == nil { - return ValidString{} - } - return ValidString{ - Value: *value, - Valid: true, - } -} - -func ConvertFloat32Ptr(value *float32) ValidFloat32 { - if value == nil { - return ValidFloat32{} - } - return ValidFloat32{ - Value: *value, - Valid: true, - } -} - -func ConvertCurrencyFloatPtr(value *float32) ValidInt64 { - if value == nil { - return ValidInt64{} - } - - convertedCurrency := ToCurrency(*value) - - return ValidInt64{ - Value: int64(convertedCurrency), - Valid: true, - } -} - -func ConvertBoolPtr(value *bool) ValidBool { - if value == nil { - return ValidBool{} - } - return ValidBool{ - Value: *value, - Valid: true, - } -} - func (n *ValidInt64) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err == nil { @@ -190,4 +44,202 @@ func (n *ValidInt64) UnmarshalJSON(data []byte) error { } return fmt.Errorf("invalid int64 value: %s", string(data)) -} \ No newline at end of file +} + +func ConvertInt64Ptr(value *int64) ValidInt64 { + if value == nil { + return ValidInt64{} + } + return ValidInt64{ + Value: *value, + Valid: true, + } +} + +// Valid Int +type ValidInt struct { + Value int + Valid bool +} + +func (v ValidInt) ToPG() pgtype.Int4 { + return pgtype.Int4{ + Int32: int32(v.Value), + Valid: v.Valid, + } +} +func ConvertIntPtr(value *int) ValidInt { + if value == nil { + return ValidInt{} + } + return ValidInt{ + Value: *value, + Valid: true, + } +} + +// Valid Int32 +type ValidInt32 struct { + Value int32 + Valid bool +} + +func (v ValidInt32) ToPG() pgtype.Int4 { + return pgtype.Int4{ + Int32: v.Value, + Valid: v.Valid, + } +} + +func ConvertInt32Ptr(value *int32) ValidInt32 { + if value == nil { + return ValidInt32{} + } + return ValidInt32{ + Value: *value, + Valid: true, + } +} + +// Valid Float32 +type ValidFloat32 struct { + Value float32 + Valid bool +} + +func (v ValidFloat32) ToPG() pgtype.Float4 { + return pgtype.Float4{ + Float32: v.Value, + Valid: v.Valid, + } +} + +func ConvertFloat32Ptr(value *float32) ValidFloat32 { + if value == nil { + return ValidFloat32{} + } + return ValidFloat32{ + Value: *value, + Valid: true, + } +} + +// Valid String +type ValidString struct { + Value string + Valid bool +} + +func (v ValidString) ToPG() pgtype.Text { + return pgtype.Text{ + String: v.Value, + Valid: v.Valid, + } +} + +func ConvertStringPtr(value *string) ValidString { + if value == nil { + return ValidString{} + } + return ValidString{ + Value: *value, + Valid: true, + } +} + +// Valid Time +type ValidTime struct { + Value time.Time + Valid bool +} + +func (v ValidTime) ToPG() pgtype.Timestamp { + return pgtype.Timestamp{ + Time: v.Value, + Valid: v.Valid, + } +} + +// Valid Bool +type ValidBool struct { + Value bool + Valid bool +} + +func (v ValidBool) ToPG() pgtype.Bool { + return pgtype.Bool{ + Bool: v.Value, + Valid: v.Valid, + } +} + +func ConvertBoolPtr(value *bool) ValidBool { + if value == nil { + return ValidBool{} + } + return ValidBool{ + Value: *value, + Valid: true, + } +} + +// Valid Currency +type ValidCurrency struct { + Value Currency + Valid bool +} + +func (v ValidCurrency) ToPG() pgtype.Int8 { + return pgtype.Int8{ + Int64: int64(v.Value), + Valid: v.Valid, + } +} + +func ConvertCurrencyPtr(value *Currency) ValidCurrency { + if value == nil { + return ValidCurrency{} + } + + return ValidCurrency{ + Value: *value, + Valid: true, + } +} + +func ConvertFloat32PtrToCurrency(value *float32) ValidCurrency { + if value == nil { + return ValidCurrency{} + } + + converted := ToCurrency(*value) + + return ValidCurrency{ + Value: converted, + Valid: true, + } +} + +// Valid Role +type ValidRole struct { + Value Role + Valid bool +} + +func (v ValidRole) ToPG() pgtype.Text { + return pgtype.Text{ + String: string(v.Value), + Valid: v.Valid, + } +} + +func ConvertRolePtr(value *Role) ValidRole { + if value == nil { + return ValidRole{} + } + + return ValidRole{ + Value: *value, + Valid: true, + } +} diff --git a/internal/repository/bet.go b/internal/repository/bet.go index 2aa520a..b1d1e52 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -19,115 +19,14 @@ var ( mongoLogger *zap.Logger ) -func convertDBBet(bet dbgen.Bet) domain.Bet { - return domain.Bet{ - ID: bet.ID, - Amount: domain.Currency(bet.Amount), - TotalOdds: bet.TotalOdds, - Status: domain.OutcomeStatus(bet.Status), - UserID: bet.UserID, - IsShopBet: bet.IsShopBet, - CashedOut: bet.CashedOut, - FastCode: bet.FastCode, - CreatedAt: bet.CreatedAt.Time, - } -} - -func convertDBBetOutcomes(outcome dbgen.BetOutcome) domain.BetOutcome { - return domain.BetOutcome{ - ID: outcome.ID, - BetID: outcome.BetID, - SportID: outcome.SportID, - EventID: outcome.EventID, - OddID: outcome.OddID, - HomeTeamName: outcome.HomeTeamName, - AwayTeamName: outcome.AwayTeamName, - MarketID: outcome.MarketID, - MarketName: outcome.MarketName, - Odd: outcome.Odd, - OddName: outcome.OddName, - OddHeader: outcome.OddHeader, - OddHandicap: outcome.OddHandicap, - Status: domain.OutcomeStatus(outcome.Status), - Expires: outcome.Expires.Time, - } -} - -func convertDBBetWithOutcomes(bet dbgen.BetWithOutcome) domain.GetBet { - var outcomes []domain.BetOutcome = make([]domain.BetOutcome, 0, len(bet.Outcomes)) - - for _, outcome := range bet.Outcomes { - outcomes = append(outcomes, convertDBBetOutcomes(outcome)) - } - - return domain.GetBet{ - ID: bet.ID, - Amount: domain.Currency(bet.Amount), - TotalOdds: bet.TotalOdds, - Status: domain.OutcomeStatus(bet.Status), - FullName: bet.FullName.(string), - PhoneNumber: bet.PhoneNumber.String, - UserID: bet.UserID, - IsShopBet: bet.IsShopBet, - CashedOut: bet.CashedOut, - Outcomes: outcomes, - FastCode: bet.FastCode, - CreatedAt: bet.CreatedAt.Time, - } -} - -func convertDBFlag(flag dbgen.Flag) domain.Flag { - return domain.Flag{ - ID: flag.ID, - BetID: flag.BetID.Int64, - OddID: flag.OddsMarketID.Int64, - Reason: flag.Reason.String, - FlaggedAt: flag.FlaggedAt.Time, - Resolved: flag.Resolved.Bool, - } -} - -func convertDBCreateBetOutcome(betOutcome domain.CreateBetOutcome) dbgen.CreateBetOutcomeParams { - return dbgen.CreateBetOutcomeParams{ - BetID: betOutcome.BetID, - EventID: betOutcome.EventID, - SportID: betOutcome.SportID, - OddID: betOutcome.OddID, - HomeTeamName: betOutcome.HomeTeamName, - AwayTeamName: betOutcome.AwayTeamName, - MarketID: betOutcome.MarketID, - MarketName: betOutcome.MarketName, - Odd: betOutcome.Odd, - OddName: betOutcome.OddName, - OddHeader: betOutcome.OddHeader, - OddHandicap: betOutcome.OddHandicap, - Expires: pgtype.Timestamp{ - Time: betOutcome.Expires, - Valid: true, - }, - } -} - -func convertCreateBet(bet domain.CreateBet) dbgen.CreateBetParams { - return dbgen.CreateBetParams{ - Amount: int64(bet.Amount), - TotalOdds: bet.TotalOdds, - Status: int32(bet.Status), - UserID: bet.UserID, - IsShopBet: bet.IsShopBet, - OutcomesHash: bet.OutcomesHash, - FastCode: bet.FastCode, - } -} - func (s *Store) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) { - newBet, err := s.queries.CreateBet(ctx, convertCreateBet(bet)) + newBet, err := s.queries.CreateBet(ctx, domain.ConvertCreateBet(bet)) if err != nil { fmt.Println("We are here") logger.Error("Failed to create bet", slog.String("error", err.Error()), slog.Any("bet", bet)) return domain.Bet{}, err } - return convertDBBet(newBet), err + return domain.ConvertDBBet(newBet), err } @@ -135,7 +34,7 @@ func (s *Store) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBe var dbParams []dbgen.CreateBetOutcomeParams = make([]dbgen.CreateBetOutcomeParams, 0, len(outcomes)) for _, outcome := range outcomes { - dbParams = append(dbParams, convertDBCreateBetOutcome(outcome)) + dbParams = append(dbParams, domain.ConvertDBCreateBetOutcome(outcome)) } rows, err := s.queries.CreateBetOutcome(ctx, dbParams) @@ -177,7 +76,7 @@ func (s *Store) CreateFlag(ctx context.Context, flag domain.CreateFlagReq) (doma return domain.Flag{}, err } - return convertDBFlag(f), nil + return domain.ConvertDBFlag(f), nil } func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) { @@ -190,35 +89,18 @@ func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) return domain.GetBet{}, err } - return convertDBBetWithOutcomes(bet), nil + return domain.ConvertDBBetWithOutcomes(bet), nil } func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error) { bets, err := s.queries.GetAllBets(ctx, dbgen.GetAllBetsParams{ - UserID: pgtype.Int8{ - Int64: filter.UserID.Value, - Valid: filter.UserID.Valid, - }, - CashedOut: pgtype.Bool{ - Bool: filter.CashedOut.Value, - Valid: filter.CashedOut.Valid, - }, - IsShopBet: pgtype.Bool{ - Bool: filter.IsShopBet.Value, - Valid: filter.IsShopBet.Valid, - }, - Query: pgtype.Text{ - String: filter.Query.Value, - Valid: filter.Query.Valid, - }, - CreatedBefore: pgtype.Timestamp{ - Time: filter.CreatedBefore.Value, - Valid: filter.CreatedBefore.Valid, - }, - CreatedAfter: pgtype.Timestamp{ - Time: filter.CreatedAfter.Value, - Valid: filter.CreatedAfter.Valid, - }, + UserID: filter.UserID.ToPG(), + CompanyID: filter.CompanyID.ToPG(), + CashedOut: filter.CashedOut.ToPG(), + IsShopBet: filter.IsShopBet.ToPG(), + Query: filter.Query.ToPG(), + CreatedBefore: filter.CreatedBefore.ToPG(), + CreatedAfter: filter.CreatedAfter.ToPG(), }) if err != nil { domain.MongoDBLogger.Error("failed to get all bets", @@ -230,7 +112,7 @@ func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]doma var result []domain.GetBet = make([]domain.GetBet, 0, len(bets)) for _, bet := range bets { - result = append(result, convertDBBetWithOutcomes(bet)) + result = append(result, domain.ConvertDBBetWithOutcomes(bet)) } return result, nil @@ -245,7 +127,7 @@ func (s *Store) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetB var result []domain.GetBet = make([]domain.GetBet, 0, len(bets)) for _, bet := range bets { - result = append(result, convertDBBetWithOutcomes(bet)) + result = append(result, domain.ConvertDBBetWithOutcomes(bet)) } return result, nil @@ -258,7 +140,7 @@ func (s *Store) GetBetByFastCode(ctx context.Context, fastcode string) (domain.G return domain.GetBet{}, err } - return convertDBBetWithOutcomes(bet), nil + return domain.ConvertDBBetWithOutcomes(bet), nil } func (s *Store) GetBetsForCashback(ctx context.Context) ([]domain.GetBet, error) { @@ -270,7 +152,7 @@ func (s *Store) GetBetsForCashback(ctx context.Context) ([]domain.GetBet, error) } for _, bet := range bets { - cashbackBet := convertDBBetWithOutcomes(bet) + cashbackBet := domain.ConvertDBBetWithOutcomes(bet) res = append(res, cashbackBet) } @@ -361,7 +243,7 @@ func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_fi var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes)) for _, outcome := range outcomes { - result = append(result, convertDBBetOutcomes(outcome)) + result = append(result, domain.ConvertDBBetOutcomes(outcome)) } return result, nil } @@ -378,7 +260,7 @@ func (s *Store) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes)) for _, outcome := range outcomes { - result = append(result, convertDBBetOutcomes(outcome)) + result = append(result, domain.ConvertDBBetOutcomes(outcome)) } return result, nil } @@ -397,7 +279,7 @@ func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status dom return domain.BetOutcome{}, err } - res := convertDBBetOutcomes(update) + res := domain.ConvertDBBetOutcomes(update) return res, nil } @@ -415,7 +297,7 @@ func (s *Store) UpdateBetOutcomeStatusByBetID(ctx context.Context, id int64, sta return domain.BetOutcome{}, err } - res := convertDBBetOutcomes(update) + res := domain.ConvertDBBetOutcomes(update) return res, nil } @@ -436,7 +318,7 @@ func (s *Store) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int6 var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes)) for _, outcome := range outcomes { - result = append(result, convertDBBetOutcomes(outcome)) + result = append(result, domain.ConvertDBBetOutcomes(outcome)) } return result, nil } diff --git a/internal/repository/settings.go b/internal/repository/settings.go index cb2c2b2..333f280 100644 --- a/internal/repository/settings.go +++ b/internal/repository/settings.go @@ -2,116 +2,24 @@ package repository import ( "context" - "fmt" - "strconv" - "time" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "go.uber.org/zap" ) -func GetDBSettingList(settings []dbgen.Setting) (domain.SettingList, error) { - var dbSettingList domain.ValidSettingList - - var int64SettingsMap = domain.ConvertInt64SettingsMap(&dbSettingList) - var stringSettingsMap = domain.ConvertStringSettingsMap(&dbSettingList) - var boolSettingsMap = domain.ConvertBoolSettingsMap(&dbSettingList) - var float32SettingsMap = domain.ConvertFloat32SettingsMap(&dbSettingList) - var timeSettingsMap = domain.ConvertTimeSettingsMap(&dbSettingList) - - for _, setting := range settings { - isSettingUnknown := true - for key, dbSetting := range int64SettingsMap { - if setting.Key == key { - value, err := strconv.ParseInt(setting.Value, 10, 64) - if err != nil { - return domain.SettingList{}, err - } - *dbSetting = domain.ValidInt64{ - Value: value, - Valid: true, - } - isSettingUnknown = false - } - } - - for key, dbSetting := range stringSettingsMap { - if setting.Key == key { - *dbSetting = domain.ValidString{ - Value: setting.Value, - Valid: true, - } - isSettingUnknown = false - } - } - - for key, dbSetting := range boolSettingsMap { - if setting.Key == key { - value, err := strconv.ParseBool(setting.Value) - if err != nil { - return domain.SettingList{}, err - } - *dbSetting = domain.ValidBool{ - Value: value, - Valid: true, - } - isSettingUnknown = false - } - } - - for key, dbSetting := range float32SettingsMap { - if setting.Key == key { - value, err := strconv.ParseFloat(setting.Value, 32) - if err != nil { - return domain.SettingList{}, err - } - *dbSetting = domain.ValidFloat32{ - Value: float32(value), - Valid: true, - } - isSettingUnknown = false - } - } - for key, dbSetting := range timeSettingsMap { - if setting.Key == key { - value, err := time.Parse(time.RFC3339, setting.Value) - if err != nil { - return domain.SettingList{}, err - } - *dbSetting = domain.ValidTime{ - Value: value, - Valid: true, - } - isSettingUnknown = false - } - } - - if isSettingUnknown { - domain.MongoDBLogger.Warn("unknown setting found on database", zap.String("setting", setting.Key)) - } - } - - for key, dbSetting := range int64SettingsMap { - if !dbSetting.Valid { - fmt.Printf("setting value not found on database: %v \n", key) - domain.MongoDBLogger.Warn("setting value not found on database", zap.String("setting", key)) - } - } - - return domain.ValidateSettingList(dbSettingList), nil -} -func (s *Store) GetSettingList(ctx context.Context) (domain.SettingList, error) { - settings, err := s.queries.GetSettings(ctx) +func (s *Store) GetGlobalSettingList(ctx context.Context) (domain.SettingList, error) { + settings, err := s.queries.GetGlobalSettings(ctx) if err != nil { domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err)) + return domain.SettingList{}, err } - return GetDBSettingList(settings) + return domain.ConvertDBGlobalSettingList(settings) } -func (s *Store) GetSettings(ctx context.Context) ([]domain.Setting, error) { - settings, err := s.queries.GetSettings(ctx) +func (s *Store) GetGlobalSettings(ctx context.Context) ([]domain.Setting, error) { + settings, err := s.queries.GetGlobalSettings(ctx) if err != nil { domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err)) @@ -129,8 +37,8 @@ func (s *Store) GetSettings(ctx context.Context) ([]domain.Setting, error) { return result, nil } -func (s *Store) GetSetting(ctx context.Context, key string) (domain.Setting, error) { - dbSetting, err := s.queries.GetSetting(ctx, key) +func (s *Store) GetGlobalSetting(ctx context.Context, key string) (domain.Setting, error) { + dbSetting, err := s.queries.GetGlobalSetting(ctx, key) if err != nil { domain.MongoDBLogger.Error("failed to get all settings", zap.Error(err)) @@ -145,14 +53,18 @@ func (s *Store) GetSetting(ctx context.Context, key string) (domain.Setting, err return result, nil } -func (s *Store) UpdateSetting(ctx context.Context, key, value string) error { - err := s.queries.UpdateSetting(ctx, dbgen.UpdateSettingParams{ +func (s *Store) UpdateGlobalSetting(ctx context.Context, key, value string) error { + err := s.queries.UpdateGlobalSetting(ctx, dbgen.UpdateGlobalSettingParams{ Key: key, Value: value, }) if err != nil { - domain.MongoDBLogger.Error("failed to update setting", zap.String("key", key), zap.String("value", value), zap.Error(err)) + domain.MongoDBLogger.Error("failed to update setting", + zap.String("key", key), + zap.String("value", value), + zap.Error(err), + ) return err } @@ -161,11 +73,11 @@ func (s *Store) UpdateSetting(ctx context.Context, key, value string) error { } -func (s *Store) UpdateSettingList(ctx context.Context, settingList domain.ValidSettingList) error { - convertedSettings := domain.ConvertValidSettingList(settingList) +func (s *Store) UpdateGlobalSettingList(ctx context.Context, settingList domain.ValidSettingList) error { + convertedSettings := settingList.ConvertAllSettings() for _, setting := range convertedSettings { - err := s.UpdateSetting(ctx, setting.Key, setting.Value) + err := s.UpdateGlobalSetting(ctx, setting.Key, setting.Value) if err != nil { domain.MongoDBLogger.Warn("failed to update setting list", zap.String("key", setting.Key), zap.Error(err)) return err @@ -173,3 +85,89 @@ func (s *Store) UpdateSettingList(ctx context.Context, settingList domain.ValidS } return nil } + +func (s *Store) InsertCompanySetting(ctx context.Context, key, value string, companyID int64) error { + err := s.queries.InsertCompanySetting(ctx, dbgen.InsertCompanySettingParams{ + CompanyID: companyID, + Key: key, + Value: value, + }) + + if err != nil { + return err + } + + return nil +} + +func (s *Store) InsertCompanySettingList(ctx context.Context, settingList domain.ValidSettingList, companyID int64) error { + convertedSettings := settingList.ConvertAllSettings() + + for _, setting := range convertedSettings { + err := s.InsertCompanySetting(ctx, setting.Key, setting.Value, companyID) + if err != nil { + domain.MongoDBLogger.Warn("failed to update setting list", zap.String("key", setting.Key), zap.Error(err)) + return err + } + } + return nil +} + +func (s *Store) GetAllCompanySettings(ctx context.Context) ([]domain.CompanySetting, error) { + settings, err := s.queries.GetAllCompanySettings(ctx) + + if err != nil { + return nil, err + } + + return domain.ConvertCompanySettings(settings), nil +} + +func (s *Store) GetCompanySettingsByKey(ctx context.Context, key string) ([]domain.CompanySetting, error) { + settings, err := s.queries.GetCompanySettingsByKey(ctx, key) + + if err != nil { + return nil, err + } + + return domain.ConvertCompanySettings(settings), nil +} + +func (s *Store) GetOverrideSettings(ctx context.Context, companyID int64) ([]domain.Setting, error) { + settings, err := s.queries.GetOverrideSettings(ctx, companyID) + + if err != nil { + return nil, err + } + + result := make([]domain.Setting, 0, len(settings)) + for _, setting := range settings { + result = append(result, domain.Setting{ + Key: setting.Key, + Value: setting.Value, + UpdatedAt: setting.UpdatedAt.Time, + }) + } + + return result, nil +} + +func (s *Store) GetOverrideSettingsList(ctx context.Context, companyID int64) (domain.SettingList, error) { + settings, err := s.queries.GetOverrideSettings(ctx, companyID) + + if err != nil { + return domain.SettingList{}, err + } + + return domain.ConvertDBOverrideSettingList(settings) +} + +func (s *Store) DeleteCompanySetting(ctx context.Context, companyID int64, key string) error { + return s.queries.DeleteCompanySetting(ctx, dbgen.DeleteCompanySettingParams{ + CompanyID: companyID, + Key: key, + }) +} +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 d431dce..6896640 100644 --- a/internal/repository/shop_bet.go +++ b/internal/repository/shop_bet.go @@ -24,7 +24,7 @@ func convertDBShopBetDetail(bet dbgen.ShopBetDetail) domain.ShopBetDetail { var outcomes []domain.BetOutcome = make([]domain.BetOutcome, 0, len(bet.Outcomes)) for _, outcome := range bet.Outcomes { - outcomes = append(outcomes, convertDBBetOutcomes(outcome)) + outcomes = append(outcomes, domain.ConvertDBBetOutcomes(outcome)) } return domain.ShopBetDetail{ ID: bet.ID, diff --git a/internal/repository/ticket.go b/internal/repository/ticket.go index 337accb..ac140bf 100644 --- a/internal/repository/ticket.go +++ b/internal/repository/ticket.go @@ -13,6 +13,7 @@ func convertDBTicket(ticket dbgen.Ticket) domain.Ticket { ID: ticket.ID, Amount: domain.Currency(ticket.Amount), TotalOdds: ticket.TotalOdds, + CompanyID: ticket.CompanyID, } } @@ -71,6 +72,7 @@ func convertCreateTicket(ticket domain.CreateTicket) dbgen.CreateTicketParams { Amount: int64(ticket.Amount), TotalOdds: ticket.TotalOdds, Ip: ticket.IP, + CompanyID: ticket.CompanyID, } } @@ -110,8 +112,8 @@ func (s *Store) GetTicketByID(ctx context.Context, id int64) (domain.GetTicket, return convertDBTicketOutcomes(ticket), nil } -func (s *Store) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) { - tickets, err := s.queries.GetAllTickets(ctx) +func (s *Store) GetAllTickets(ctx context.Context, filter domain.TicketFilter) ([]domain.GetTicket, error) { + tickets, err := s.queries.GetAllTickets(ctx, filter.CompanyID.ToPG()) if err != nil { return nil, err } diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index ad44770..900317c 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -215,8 +215,8 @@ func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketI return newOutcome, nil } -func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID int64, role domain.Role, companyID domain.ValidInt64) (domain.CreateBetRes, error) { - settingsList, err := s.settingSvc.GetSettingList(ctx) +func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID int64, role domain.Role, companyID int64) (domain.CreateBetRes, error) { + settingsList, err := s.settingSvc.GetOverrideSettingsList(ctx, companyID) if err != nil { return domain.CreateBetRes{}, err @@ -297,6 +297,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID OutcomesHash: outcomesHash, FastCode: fastCode, UserID: userID, + CompanyID: companyID, } switch role { @@ -347,7 +348,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID return domain.CreateBetRes{}, err } - if companyID.Valid && branch.CompanyID == companyID.Value { + if branch.CompanyID == companyID { s.mongoLogger.Warn("unauthorized company", zap.Int64("branch_id", *req.BranchID), zap.Error(err), @@ -477,7 +478,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID } } - res := domain.ConvertCreateBet(bet, rows) + res := domain.ConvertCreateBetRes(bet, rows) return res, nil } @@ -714,7 +715,7 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string, return newOdds, totalOdds, nil } -func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, leagueID domain.ValidInt64, sportID domain.ValidInt32, firstStartTime, lastStartTime domain.ValidTime) (domain.CreateBetRes, error) { +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 @@ -816,6 +817,7 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le TotalOdds: totalOdds, Status: domain.OUTCOME_STATUS_PENDING, UserID: userID, + CompanyID: companyID, IsShopBet: true, FastCode: fastCode, } @@ -842,7 +844,7 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le return domain.CreateBetRes{}, err } - res := domain.ConvertCreateBet(bet, rows) + res := domain.ConvertCreateBetRes(bet, rows) s.mongoLogger.Info("Random bets placed successfully", zap.Int64("userID", userID), @@ -1310,7 +1312,7 @@ func (s *Service) SetBetToRemoved(ctx context.Context, id int64) error { } func (s *Service) ProcessBetCashback(ctx context.Context) error { - settingsList, err := s.settingSvc.GetSettingList(ctx) + bets, err := s.betStore.GetBetsForCashback(ctx) if err != nil { s.mongoLogger.Error("failed to fetch bets", @@ -1319,6 +1321,7 @@ func (s *Service) ProcessBetCashback(ctx context.Context) error { return err } + for _, bet := range bets { shouldProcess := true loseCount := 0 @@ -1359,6 +1362,8 @@ func (s *Service) ProcessBetCashback(ctx context.Context) error { ) continue } + + settingsList, err := s.settingSvc.GetOverrideSettingsList(ctx, bet.CompanyID) 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/messenger/sms.go b/internal/services/messenger/sms.go index d750d9a..77e1cd8 100644 --- a/internal/services/messenger/sms.go +++ b/internal/services/messenger/sms.go @@ -15,12 +15,24 @@ var ( ErrSMSProviderNotFound = errors.New("SMS Provider Not Found") ) -func (s *Service) SendSMS(ctx context.Context, receiverPhone, message string) error { +// If the company id is valid, it is a company based notification else its a global notification (by the super admin) +func (s *Service) SendSMS(ctx context.Context, receiverPhone, message string, companyID domain.ValidInt64) error { - settingsList, err := s.settingSvc.GetSettingList(ctx) + var settingsList domain.SettingList + var err error - if err != nil { - return err + if companyID.Valid { + settingsList, err = s.settingSvc.GetOverrideSettingsList(ctx, companyID.Value) + if err != nil { + // TODO: Send a log about the error + return err + } + } else { + settingsList, err = s.settingSvc.GetGlobalSettingList(ctx) + if err != nil { + // TODO: Send a log about the error + return err + } } switch settingsList.SMSProvider { diff --git a/internal/services/notification/service.go b/internal/services/notification/service.go index 38caf8d..ae0b990 100644 --- a/internal/services/notification/service.go +++ b/internal/services/notification/service.go @@ -340,7 +340,7 @@ func (s *Service) SendNotificationSMS(ctx context.Context, recipientID int64, me if user.PhoneNumber == "" { return fmt.Errorf("Phone Number is invalid") } - err = s.messengerSvc.SendSMS(ctx, user.PhoneNumber, message) + err = s.messengerSvc.SendSMS(ctx, user.PhoneNumber, message, user.CompanyID) if err != nil { return err } diff --git a/internal/services/odds/port.go b/internal/services/odds/port.go index 6014ed0..d1026aa 100644 --- a/internal/services/odds/port.go +++ b/internal/services/odds/port.go @@ -15,7 +15,7 @@ type Service interface { GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string) ([]domain.OddMarket, error) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit domain.ValidInt64, offset domain.ValidInt64) ([]domain.OddMarket, error) GetALLPrematchOdds(ctx context.Context) ([]domain.OddMarket, error) - GetRawOddsByMarketID(ctx context.Context, marketID string, upcomingID string) (domain.RawOddsByMarketID, error) + // GetRawOddsByMarketID(ctx context.Context, marketID string, upcomingID string) (domain.OddMarket, error) DeleteOddsForEvent(ctx context.Context, eventID string) error // Odd History diff --git a/internal/services/referal/port.go b/internal/services/referal/port.go index 1946e99..6add199 100644 --- a/internal/services/referal/port.go +++ b/internal/services/referal/port.go @@ -9,7 +9,7 @@ import ( type ReferralStore interface { GenerateReferralCode() (string, error) CreateReferral(ctx context.Context, userID int64) error - ProcessReferral(ctx context.Context, referredID, referralCode string) 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 diff --git a/internal/services/referal/service.go b/internal/services/referal/service.go index f75a809..d89b023 100644 --- a/internal/services/referal/service.go +++ b/internal/services/referal/service.go @@ -107,7 +107,7 @@ func (s *Service) CreateReferral(ctx context.Context, userID int64) error { return nil } -func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCode string) error { +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) @@ -121,7 +121,10 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo return ErrInvalidReferral } - user, err := s.store.GetUserByPhone(ctx, referredPhone) + user, err := s.store.GetUserByPhone(ctx, referredPhone, domain.ValidInt64{ + Value: companyID, + Valid: true, + }) if err != nil { if errors.Is(err, domain.ErrUserNotFound) { s.logger.Warn("User not found for referral", "referredPhone", referredPhone) diff --git a/internal/services/settings/port.go b/internal/services/settings/port.go index 9f83ab2..9f37866 100644 --- a/internal/services/settings/port.go +++ b/internal/services/settings/port.go @@ -7,9 +7,18 @@ import ( ) type SettingStore interface { - GetSettingList(ctx context.Context) (domain.SettingList, error) - GetSettings(ctx context.Context) ([]domain.Setting, error) - GetSetting(ctx context.Context, key string) (domain.Setting, error) - UpdateSetting(ctx context.Context, key, value string) error - UpdateSettingList(ctx context.Context, settingList domain.ValidSettingList) error + GetGlobalSettingList(ctx context.Context) (domain.SettingList, error) + GetGlobalSettings(ctx context.Context) ([]domain.Setting, error) + GetGlobalSetting(ctx context.Context, key string) (domain.Setting, error) + UpdateGlobalSetting(ctx context.Context, key, value string) error + UpdateGlobalSettingList(ctx context.Context, settingList domain.ValidSettingList) error + + InsertCompanySetting(ctx context.Context, key, value string, companyID int64) error + InsertCompanySettingList(ctx context.Context, settingList domain.ValidSettingList, companyID int64) error + GetAllCompanySettings(ctx context.Context) ([]domain.CompanySetting, error) + GetCompanySettingsByKey(ctx context.Context, key string) ([]domain.CompanySetting, error) + GetOverrideSettings(ctx context.Context, companyID int64) ([]domain.Setting, error) + GetOverrideSettingsList(ctx context.Context, companyID int64) (domain.SettingList, error) + DeleteCompanySetting(ctx context.Context, companyID int64, key string) error + DeleteAllCompanySetting(ctx context.Context, companyID int64) error } diff --git a/internal/services/settings/service.go b/internal/services/settings/service.go index 8180e94..5b5ea37 100644 --- a/internal/services/settings/service.go +++ b/internal/services/settings/service.go @@ -16,21 +16,48 @@ func NewService(settingStore SettingStore) *Service { } } -func (s *Service) GetSettingList(ctx context.Context) (domain.SettingList, error) { - return s.settingStore.GetSettingList(ctx) +func (s *Service) GetGlobalSettingList(ctx context.Context) (domain.SettingList, error) { + return s.settingStore.GetGlobalSettingList(ctx) } -func (s *Service) GetSettings(ctx context.Context) ([]domain.Setting, error) { - return s.settingStore.GetSettings(ctx) +func (s *Service) GetGlobalSettings(ctx context.Context) ([]domain.Setting, error) { + return s.settingStore.GetGlobalSettings(ctx) } -func (s *Service) GetSetting(ctx context.Context, key string) (domain.Setting, error) { - return s.settingStore.GetSetting(ctx, key) +func (s *Service) GetGlobalSetting(ctx context.Context, key string) (domain.Setting, error) { + return s.settingStore.GetGlobalSetting(ctx, key) } -func (s *Service) UpdateSetting(ctx context.Context, key, value string) error { - return s.settingStore.UpdateSetting(ctx, key, value) +func (s *Service) UpdateGlobalSetting(ctx context.Context, key, value string) error { + return s.settingStore.UpdateGlobalSetting(ctx, key, value) } -func (s *Service) UpdateSettingList(ctx context.Context, settingList domain.ValidSettingList) error { - return s.settingStore.UpdateSettingList(ctx, settingList) +func (s *Service) UpdateGlobalSettingList(ctx context.Context, settingList domain.ValidSettingList) error { + return s.settingStore.UpdateGlobalSettingList(ctx, settingList) +} + +func (s *Service) InsertCompanySetting(ctx context.Context, key, value string, companyID int64) error { + return s.settingStore.InsertCompanySetting(ctx, key, value, companyID) +} +func (s *Service) InsertCompanySettingList(ctx context.Context, settingList domain.ValidSettingList, companyID int64) error { + return s.settingStore.InsertCompanySettingList(ctx, settingList, companyID) +} +func (s *Service) GetAllCompanySettings(ctx context.Context) ([]domain.CompanySetting, error) { + return s.settingStore.GetAllCompanySettings(ctx) +} +func (s *Service) GetCompanySettingsByKey(ctx context.Context, key string) ([]domain.CompanySetting, error) { + return s.settingStore.GetCompanySettingsByKey(ctx, key) +} +func (s *Service) GetOverrideSettings(ctx context.Context, companyID int64) ([]domain.Setting, error) { + return s.settingStore.GetOverrideSettings(ctx, companyID) +} +func (s *Service) GetOverrideSettingsList(ctx context.Context, companyID int64) (domain.SettingList, error) { + return s.settingStore.GetOverrideSettingsList(ctx, companyID) +} + +func (s *Service) DeleteCompanySetting(ctx context.Context, companyID int64, key string) error { + return s.settingStore.DeleteCompanySetting(ctx, companyID, key) +} + +func (s *Service) DeleteAllCompanySetting(ctx context.Context, companyID int64) error { + return s.settingStore.DeleteAllCompanySetting(ctx, companyID) } diff --git a/internal/services/ticket/port.go b/internal/services/ticket/port.go index d4201e3..855d997 100644 --- a/internal/services/ticket/port.go +++ b/internal/services/ticket/port.go @@ -10,7 +10,7 @@ type TicketStore interface { CreateTicket(ctx context.Context, ticket domain.CreateTicket) (domain.Ticket, error) CreateTicketOutcome(ctx context.Context, outcomes []domain.CreateTicketOutcome) (int64, error) GetTicketByID(ctx context.Context, id int64) (domain.GetTicket, error) - GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) + GetAllTickets(ctx context.Context, filter domain.TicketFilter) ([]domain.GetTicket, error) CountTicketByIP(ctx context.Context, IP string) (int64, error) UpdateTicketOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error DeleteOldTickets(ctx context.Context) error diff --git a/internal/services/ticket/service.go b/internal/services/ticket/service.go index c67a657..3514960 100644 --- a/internal/services/ticket/service.go +++ b/internal/services/ticket/service.go @@ -80,7 +80,7 @@ func (s *Service) GenerateTicketOutcome(ctx context.Context, settings domain.Set return domain.CreateTicketOutcome{}, ErrTicketHasExpired } - odds, err := s.prematchSvc.GetRawOddsByMarketID(ctx, marketIDStr, eventIDStr) + odds, err := s.prematchSvc.GetOddsByMarketID(ctx, marketIDStr, eventIDStr) if err != nil { s.mongoLogger.Error("failed to get raw odds by market ID", @@ -158,8 +158,8 @@ func (s *Service) GenerateTicketOutcome(ctx context.Context, settings domain.Set } -func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq, clientIP string) (domain.Ticket, int64, error) { - settingsList, err := s.settingSvc.GetSettingList(ctx) +func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq, clientIP string, companyID int64) (domain.Ticket, int64, error) { + settingsList, err := s.settingSvc.GetOverrideSettingsList(ctx, companyID) // Check to see if the number of outcomes is above a set limit if len(req.Outcomes) > int(settingsList.MaxNumberOfOutcomes) { @@ -221,6 +221,7 @@ func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq, Amount: domain.ToCurrency(req.Amount), TotalOdds: totalOdds, IP: clientIP, + CompanyID: companyID, }) if err != nil { s.mongoLogger.Error("Error Creating Ticket", zap.Float32("Total Odds", totalOdds), zap.Float32("amount", req.Amount)) @@ -261,8 +262,8 @@ func (s *Service) CreateTicketOutcome(ctx context.Context, outcomes []domain.Cre func (s *Service) GetTicketByID(ctx context.Context, id int64) (domain.GetTicket, error) { return s.ticketStore.GetTicketByID(ctx, id) } -func (s *Service) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) { - return s.ticketStore.GetAllTickets(ctx) +func (s *Service) GetAllTickets(ctx context.Context, filter domain.TicketFilter) ([]domain.GetTicket, error) { + return s.ticketStore.GetAllTickets(ctx, filter) } func (s *Service) CountTicketByIP(ctx context.Context, IP string) (int64, error) { diff --git a/internal/services/transaction/service.go b/internal/services/transaction/service.go index bca21f6..1a10a78 100644 --- a/internal/services/transaction/service.go +++ b/internal/services/transaction/service.go @@ -155,7 +155,7 @@ func (s *Service) GetBranchByRole(ctx context.Context, branchID *int64, role dom // Check if the user has access to the company if role != domain.RoleSuperAdmin { - if !userCompanyID.Valid || userCompanyID.Value != branch.CompanyID { + if userCompanyID.Valid && userCompanyID.Value != branch.CompanyID { return nil, nil, ErrUnauthorizedCompanyID } } diff --git a/internal/services/transaction/shop_bet.go b/internal/services/transaction/shop_bet.go index 8296952..6c1ed20 100644 --- a/internal/services/transaction/shop_bet.go +++ b/internal/services/transaction/shop_bet.go @@ -52,7 +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, - }, userID, role, userCompanyID) + }, userID, role, *companyID) if err != nil { return domain.ShopBet{}, err diff --git a/internal/services/virtualGame/veli/service.go b/internal/services/virtualGame/veli/service.go index 432c706..e0fa68b 100644 --- a/internal/services/virtualGame/veli/service.go +++ b/internal/services/virtualGame/veli/service.go @@ -261,7 +261,7 @@ func (s *service) ProcessWin(ctx context.Context, req domain.WinRequest) (*domai // --- 1. Validate PlayerID --- playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64) if err != nil { -z`` return nil, fmt.Errorf("BAD_REQUEST: invalid PlayerID %s", req.PlayerID) + return nil, fmt.Errorf("BAD_REQUEST: invalid PlayerID %s", req.PlayerID) } // --- 2. Get player wallets --- diff --git a/internal/web_server/handlers/auth_handler.go b/internal/web_server/handlers/auth_handler.go index a5f215e..57081c1 100644 --- a/internal/web_server/handlers/auth_handler.go +++ b/internal/web_server/handlers/auth_handler.go @@ -42,7 +42,7 @@ type loginCustomerRes struct { func (h *Handler) LoginCustomer(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { - domain.BadRequestLogger.Error("invalid company id") + h.BadRequestLogger().Error("invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } var req loginCustomerReq @@ -163,7 +163,7 @@ type loginAdminRes struct { func (h *Handler) LoginAdmin(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { - domain.BadRequestLogger.Error("invalid company id") + h.BadRequestLogger().Error("invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } var req loginAdminReq diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index a323560..d9cea40 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -23,11 +23,16 @@ import ( // @Success 200 {object} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /api/v1/sport/bet [post] +// @Router /api/v1/{tenant_slug}/sport/bet [post] func (h *Handler) CreateBet(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } + userID := c.Locals("user_id").(int64) role := c.Locals("role").(domain.Role) - companyID := c.Locals("company_id").(domain.ValidInt64) var req domain.CreateBetReq if err := c.BodyParser(&req); err != nil { @@ -39,7 +44,7 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error()) } - res, err := h.CreateBetInternal(c, req, userID, role, companyID) + res, err := h.CreateBetInternal(c, req, userID, role, companyID.Value) if err != nil { h.mongoLoggerSvc.Error("Failed to create bet", zap.Int("status_code", fiber.StatusInternalServerError), @@ -70,11 +75,15 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error { // @Success 200 {object} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /api/v1/sport/bet/fastcode [post] +// @Router /api/v1/{tenant_slug}/sport/bet/fastcode [post] func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } userID := c.Locals("user_id").(int64) role := c.Locals("role").(domain.Role) - companyID := c.Locals("company_id").(domain.ValidInt64) var req domain.CreateBetWithFastCodeReq @@ -136,7 +145,7 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error { BranchID: req.BranchID, } - res, err := h.CreateBetInternal(c, newReq, userID, role, companyID) + res, err := h.CreateBetInternal(c, newReq, userID, role, companyID.Value) if err != nil { h.mongoLoggerSvc.Error("Failed to create bet", zap.Int("status_code", fiber.StatusInternalServerError), @@ -152,7 +161,7 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error { wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), bet.UserID) // amount added for fast code owner can be fetched from settings in db - settingList, err := h.settingSvc.GetSettingList(c.Context()) + settingList, err := h.settingSvc.GetOverrideSettingsList(c.Context(), companyID.Value) amount := settingList.AmountForBetReferral _, err = h.walletSvc.AddToWallet(c.Context(), wallet.StaticID, amount, domain.ValidInt64{}, @@ -177,7 +186,7 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusOK, "Bet Created", res, nil) } -func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userID int64, role domain.Role, companyID domain.ValidInt64) (domain.CreateBetRes, error) { +func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userID int64, role domain.Role, companyID int64) (domain.CreateBetRes, error) { valErrs, ok := h.validator.Validate(c, req) if !ok { h.mongoLoggerSvc.Error("CreateBet validation failed", @@ -195,7 +204,7 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI h.mongoLoggerSvc.Info("PlaceBet failed", zap.Int("status_code", fiber.StatusBadRequest), zap.Int64("userID", userID), - zap.Int64("companyID", companyID.Value), + zap.Int64("companyID", companyID), zap.String("role", string(role)), zap.Error(err), zap.Time("timestamp", time.Now()), @@ -206,7 +215,7 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI h.mongoLoggerSvc.Error("PlaceBet failed", zap.Int("status_code", fiber.StatusInternalServerError), zap.Int64("userID", userID), - zap.Int64("companyID", companyID.Value), + zap.Int64("companyID", companyID), zap.String("role", string(role)), zap.Error(err), zap.Time("timestamp", time.Now()), @@ -228,11 +237,17 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI // @Success 200 {object} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /api/v1/sport/random/bet [post] +// @Router /api/v1/{tenant_slug}/sport/random/bet [post] func (h *Handler) RandomBet(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } + userID := c.Locals("user_id").(int64) - leagueIDQuery, err := strconv.Atoi(c.Query("league_id")) + leagueIDQuery, err := strconv.ParseInt(c.Query("league_id"), 10, 64) if err != nil { h.mongoLoggerSvc.Info("invalid league id", zap.Int("status_code", fiber.StatusBadRequest), @@ -255,8 +270,8 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error { firstStartTimeQuery := c.Query("first_start_time") lastStartTimeQuery := c.Query("last_start_time") - leagueID := domain.ValidInt32{ - Value: int32(leagueIDQuery), + leagueID := domain.ValidInt64{ + Value: leagueIDQuery, Valid: leagueIDQuery != 0, } sportID := domain.ValidInt32{ @@ -326,7 +341,7 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error { var res domain.CreateBetRes for i := 0; i < int(req.NumberOfBets); i++ { - res, err = h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID, leagueID, sportID, firstStartTime, lastStartTime) + res, err = h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID, companyID.Value, leagueID, sportID, firstStartTime, lastStartTime) if err != nil { switch err { case bet.ErrNoEventsAvailable: @@ -362,8 +377,13 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error { // @Success 200 {array} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /api/v1/sport/bet [get] +// @Router /api/v1/{tenant_slug}/sport/bet [get] func (h *Handler) GetAllBet(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } role := c.Locals("role").(domain.Role) // companyID := c.Locals("company_id").(domain.ValidInt64) // branchID := c.Locals("branch_id").(domain.ValidInt64) @@ -432,6 +452,7 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error { } bets, err := h.betSvc.GetAllBets(c.Context(), domain.BetFilter{ + CompanyID: companyID, IsShopBet: isShopBet, Query: searchString, CreatedBefore: createdBefore, @@ -464,8 +485,13 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error { // @Success 200 {object} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /api/v1/sport/bet/{id} [get] +// @Router /api/v1/{tenant_slug}/sport/bet/{id} [get] func (h *Handler) GetBetByID(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } betID := c.Params("id") id, err := strconv.ParseInt(betID, 10, 64) if err != nil { @@ -489,6 +515,16 @@ func (h *Handler) GetBetByID(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve bet") } + if bet.CompanyID != companyID.Value { + h.mongoLoggerSvc.Warn("User Attempt to access another company bet", + zap.Int64("betID", id), + zap.Int("status_code", fiber.StatusNotFound), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve bet") + } + res := domain.ConvertBet(bet) // h.mongoLoggerSvc.Info("Bet retrieved successfully", @@ -510,8 +546,14 @@ func (h *Handler) GetBetByID(c *fiber.Ctx) error { // @Success 200 {object} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /api/v1/sport/bet/fastcode/{fast_code} [get] +// @Router /api/v1/{tenant_slug}/sport/bet/fastcode/{fast_code} [get] func (h *Handler) GetBetByFastCode(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } + fastCode := c.Params("fast_code") bet, err := h.betSvc.GetBetByFastCode(c.Context(), fastCode) @@ -525,6 +567,15 @@ func (h *Handler) GetBetByFastCode(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusNotFound, "Failed to find bet by fast code") } + if bet.CompanyID != companyID.Value { + h.mongoLoggerSvc.Warn("User Attempt to access another company bet", + zap.String("fast_code", fastCode), + zap.Int("status_code", fiber.StatusNotFound), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve bet") + } res := domain.ConvertBet(bet) // h.mongoLoggerSvc.Info("Bet retrieved successfully", @@ -551,8 +602,14 @@ type UpdateCashOutReq struct { // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /api/v1/sport/bet/{id} [patch] +// @Router /api/v1/{tenant_slug}/sport/bet/{id} [patch] func (h *Handler) UpdateCashOut(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } + type UpdateCashOutReq struct { CashedOut bool `json:"cashed_out" validate:"required" example:"true"` } @@ -588,6 +645,27 @@ func (h *Handler) UpdateCashOut(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, errMsg) } + bet, err := h.betSvc.GetBetByID(c.Context(), id) + if err != nil { + h.mongoLoggerSvc.Info("Failed to get bet", + zap.Int64("betID", id), + zap.Int("status_code", fiber.StatusNotFound), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve bet") + } + + if bet.CompanyID != companyID.Value { + h.mongoLoggerSvc.Warn("User Attempt to access another company bet", + zap.Int64("betID", id), + zap.Int("status_code", fiber.StatusNotFound), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve bet") + } + err = h.betSvc.UpdateCashOut(c.Context(), id, req.CashedOut) if err != nil { h.mongoLoggerSvc.Error("Failed to update cash out bet", @@ -618,8 +696,14 @@ func (h *Handler) UpdateCashOut(c *fiber.Ctx) error { // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /api/v1/sport/bet/{id} [delete] +// @Router /api/v1/{tenant_slug}/sport/bet/{id} [delete] func (h *Handler) DeleteBet(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } + betID := c.Params("id") id, err := strconv.ParseInt(betID, 10, 64) if err != nil { @@ -632,6 +716,28 @@ func (h *Handler) DeleteBet(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID") } + // This is to make sure that you can remove a bet only from the right route + bet, err := h.betSvc.GetBetByID(c.Context(), id) + if err != nil { + h.mongoLoggerSvc.Info("Failed to get bet", + zap.Int64("betID", id), + zap.Int("status_code", fiber.StatusNotFound), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve bet") + } + + if bet.CompanyID != companyID.Value { + h.mongoLoggerSvc.Warn("User Attempt to access another company bet", + zap.Int64("betID", id), + zap.Int("status_code", fiber.StatusNotFound), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve bet") + } + err = h.betSvc.SetBetToRemoved(c.Context(), id) if err != nil { h.mongoLoggerSvc.Error("Failed to delete bet by ID", diff --git a/internal/web_server/handlers/common_handler.go b/internal/web_server/handlers/common_handler.go new file mode 100644 index 0000000..1c2f09f --- /dev/null +++ b/internal/web_server/handlers/common_handler.go @@ -0,0 +1,25 @@ +package handlers + +import ( + "time" + + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" +) + +func (h *Handler) BadRequestLogger() *zap.Logger { + return h.mongoLoggerSvc.With( + zap.Int("status_code", fiber.StatusBadRequest), + zap.Time("timestamp", time.Now())) +} +func (h *Handler) InternalServerErrorLogger() *zap.Logger { + return h.mongoLoggerSvc.With( + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Time("timestamp", time.Now())) +} +func (h *Handler) SuccessResLogger() *zap.Logger { + return h.mongoLoggerSvc.With( + zap.Int("status_code", fiber.StatusOK), + zap.Time("timestamp", time.Now()), + ) +} diff --git a/internal/web_server/handlers/event_handler.go b/internal/web_server/handlers/event_handler.go index e503b29..51dd68a 100644 --- a/internal/web_server/handlers/event_handler.go +++ b/internal/web_server/handlers/event_handler.go @@ -43,7 +43,7 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error { if leagueIDQuery != "" { leagueIDInt, err := strconv.ParseInt(leagueIDQuery, 10, 64) if err != nil { - domain.BadRequestLogger.Error("invalid league id", + h.BadRequestLogger().Error("invalid league id", zap.String("league_id", leagueIDQuery), zap.Error(err), ) @@ -59,7 +59,7 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error { if sportIDQuery != "" { sportIDint, err := strconv.Atoi(sportIDQuery) if err != nil { - domain.BadRequestLogger.Info("invalid sport id", + h.BadRequestLogger().Info("invalid sport id", zap.String("sportID", sportIDQuery), zap.Error(err), ) @@ -82,7 +82,7 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error { if firstStartTimeQuery != "" { firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery) if err != nil { - domain.BadRequestLogger.Info("invalid start_time format", + h.BadRequestLogger().Info("invalid start_time format", zap.String("first_start_time", firstStartTimeQuery), zap.Error(err), ) @@ -99,7 +99,7 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error { if lastStartTimeQuery != "" { lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery) if err != nil { - domain.BadRequestLogger.Info("invalid last_start_time format", + h.BadRequestLogger().Info("invalid last_start_time format", zap.String("last_start_time", lastStartTimeQuery), zap.Error(err), ) @@ -122,7 +122,7 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error { if isFeaturedQuery != "" { isFeaturedParsed, err := strconv.ParseBool(isFeaturedQuery) if err != nil { - domain.BadRequestLogger.Error("Failed to parse isFeatured", + h.BadRequestLogger().Error("Failed to parse isFeatured", zap.String("is_featured", isFeaturedQuery), zap.Error(err), ) @@ -150,7 +150,7 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error { // fmt.Printf("League ID: %v", leagueID) if err != nil { - domain.InternalServerErrorLogger.Error("Failed to retrieve all upcoming events", + h.InternalServerErrorLogger().Error("Failed to retrieve all upcoming events", zap.Error(err), ) return fiber.NewError(fiber.StatusInternalServerError, err.Error()) @@ -180,7 +180,7 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error { func (h *Handler) GetTenantUpcomingEvents(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { - domain.BadRequestLogger.Error("invalid company id") + h.BadRequestLogger().Error("invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } @@ -200,7 +200,7 @@ func (h *Handler) GetTenantUpcomingEvents(c *fiber.Ctx) error { if leagueIDQuery != "" { leagueIDInt, err := strconv.ParseInt(leagueIDQuery, 10, 64) if err != nil { - domain.BadRequestLogger.Error("invalid league id", + h.BadRequestLogger().Error("invalid league id", zap.String("league_id", leagueIDQuery), zap.Error(err), ) @@ -216,7 +216,7 @@ func (h *Handler) GetTenantUpcomingEvents(c *fiber.Ctx) error { if sportIDQuery != "" { sportIDint, err := strconv.Atoi(sportIDQuery) if err != nil { - domain.BadRequestLogger.Info("invalid sport id", + h.BadRequestLogger().Info("invalid sport id", zap.String("sportID", sportIDQuery), zap.Error(err), ) @@ -239,7 +239,7 @@ func (h *Handler) GetTenantUpcomingEvents(c *fiber.Ctx) error { if firstStartTimeQuery != "" { firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery) if err != nil { - domain.BadRequestLogger.Info("invalid start_time format", + h.BadRequestLogger().Info("invalid start_time format", zap.String("first_start_time", firstStartTimeQuery), zap.Error(err), ) @@ -256,7 +256,7 @@ func (h *Handler) GetTenantUpcomingEvents(c *fiber.Ctx) error { if lastStartTimeQuery != "" { lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery) if err != nil { - domain.BadRequestLogger.Info("invalid last_start_time format", + h.BadRequestLogger().Info("invalid last_start_time format", zap.String("last_start_time", lastStartTimeQuery), zap.Error(err), ) @@ -279,7 +279,7 @@ func (h *Handler) GetTenantUpcomingEvents(c *fiber.Ctx) error { if isFeaturedQuery != "" { isFeaturedParsed, err := strconv.ParseBool(isFeaturedQuery) if err != nil { - domain.BadRequestLogger.Error("Failed to parse isFeatured", + h.BadRequestLogger().Error("Failed to parse isFeatured", zap.String("is_featured", isFeaturedQuery), zap.Error(err), ) @@ -307,7 +307,7 @@ func (h *Handler) GetTenantUpcomingEvents(c *fiber.Ctx) error { // fmt.Printf("League ID: %v", leagueID) if err != nil { - domain.InternalServerErrorLogger.Error("Failed to retrieve all upcoming events", + h.InternalServerErrorLogger().Error("Failed to retrieve all upcoming events", zap.Error(err), ) return fiber.NewError(fiber.StatusInternalServerError, err.Error()) @@ -343,7 +343,7 @@ type TopLeague struct { func (h *Handler) GetTopLeagues(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { - domain.BadRequestLogger.Error("invalid company id") + h.BadRequestLogger().Error("invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } @@ -355,7 +355,7 @@ func (h *Handler) GetTopLeagues(c *fiber.Ctx) error { }) if err != nil { - domain.InternalServerErrorLogger.Error("Error while fetching top leagues", + h.InternalServerErrorLogger().Error("Error while fetching top leagues", zap.Int64("company_id", companyID.Value), zap.Error(err), ) @@ -372,7 +372,7 @@ func (h *Handler) GetTopLeagues(c *fiber.Ctx) error { }, }) if err != nil { - domain.InternalServerErrorLogger.Warn("Error while fetching events for top league", + h.InternalServerErrorLogger().Warn("Error while fetching events for top league", zap.Int64("LeagueID", league.ID), zap.Int64("company_id", companyID.Value), zap.Error(err), @@ -408,13 +408,13 @@ func (h *Handler) GetUpcomingEventByID(c *fiber.Ctx) error { id := c.Params("id") if id == "" { - domain.BadRequestLogger.Info("Failed to parse event id", zap.String("id", id)) + h.BadRequestLogger().Info("Failed to parse event id", zap.String("id", id)) return fiber.NewError(fiber.StatusBadRequest, "Missing id") } event, err := h.eventSvc.GetUpcomingEventByID(c.Context(), id) if err != nil { - domain.InternalServerErrorLogger.Error("Failed to get upcoming event by id", + h.InternalServerErrorLogger().Error("Failed to get upcoming event by id", zap.String("eventID", id), zap.Error(err), ) @@ -440,19 +440,19 @@ func (h *Handler) GetUpcomingEventByID(c *fiber.Ctx) error { func (h *Handler) GetTenantEventByID(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { - domain.BadRequestLogger.Error("invalid company id") + h.BadRequestLogger().Error("invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } id := c.Params("id") if id == "" { - domain.BadRequestLogger.Info("Failed to parse event id", zap.String("id", id)) + h.BadRequestLogger().Info("Failed to parse event id", zap.String("id", id)) return fiber.NewError(fiber.StatusBadRequest, "Missing id") } event, err := h.eventSvc.GetEventWithSettingByID(c.Context(), id, companyID.Value) if err != nil { - domain.InternalServerErrorLogger.Error("Failed to get upcoming event by id", + h.InternalServerErrorLogger().Error("Failed to get upcoming event by id", zap.String("eventID", id), zap.Error(err), ) @@ -484,7 +484,7 @@ func (h *Handler) SetEventStatusToRemoved(c *fiber.Ctx) error { err := h.eventSvc.UpdateEventStatus(c.Context(), eventID, domain.STATUS_REMOVED) if err != nil { - domain.InternalServerErrorLogger.Error("Failed to update event status", + h.InternalServerErrorLogger().Error("Failed to update event status", zap.String("EventID", eventID), zap.Error(err), ) @@ -515,7 +515,7 @@ type UpdateEventSettingsReq struct { func (h *Handler) UpdateEventSettings(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { - domain.BadRequestLogger.Error("invalid company id") + h.BadRequestLogger().Error("invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } @@ -523,7 +523,7 @@ func (h *Handler) UpdateEventSettings(c *fiber.Ctx) error { var req UpdateEventSettingsReq if err := c.BodyParser(&req); err != nil { - domain.BadRequestLogger.Info("Failed to parse user id", + h.BadRequestLogger().Info("Failed to parse user id", zap.String("eventID", eventID), zap.Error(err), ) @@ -543,7 +543,7 @@ func (h *Handler) UpdateEventSettings(c *fiber.Ctx) error { for field, msg := range valErrs { errMsg += fmt.Sprintf("%s: %s; ", field, msg) } - domain.BadRequestLogger.Error("Failed to update event featured", + h.BadRequestLogger().Error("Failed to update event featured", append(logFields, zap.String("errMsg", errMsg))..., ) return fiber.NewError(fiber.StatusBadRequest, errMsg) @@ -557,7 +557,7 @@ func (h *Handler) UpdateEventSettings(c *fiber.Ctx) error { }) if err != nil { - domain.InternalServerErrorLogger.Error("Failed to update event featured", append(logFields, zap.Error(err))...) + h.InternalServerErrorLogger().Error("Failed to update event featured", append(logFields, zap.Error(err))...) return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } diff --git a/internal/web_server/handlers/leagues.go b/internal/web_server/handlers/leagues.go index 222d95d..6755b9a 100644 --- a/internal/web_server/handlers/leagues.go +++ b/internal/web_server/handlers/leagues.go @@ -50,7 +50,7 @@ func (h *Handler) GetAllLeagues(c *fiber.Ctx) error { if sportIDQuery != "" { sportIDint, err := strconv.Atoi(sportIDQuery) if err != nil { - domain.BadRequestLogger.Info("invalid sport id", + h.BadRequestLogger().Info("invalid sport id", zap.String("sport_id", sportIDQuery), zap.Error(err), ) @@ -89,7 +89,7 @@ func (h *Handler) GetAllLeagues(c *fiber.Ctx) error { if err != nil { fmt.Printf("Error fetching league %v \n", err) - domain.InternalServerErrorLogger.Error("Failed to get all leagues", append(queryLogFields, zap.Error(err))..., + h.InternalServerErrorLogger().Error("Failed to get all leagues", append(queryLogFields, zap.Error(err))..., ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to get leagues:"+err.Error()) } @@ -109,7 +109,7 @@ func (h *Handler) GetAllLeagues(c *fiber.Ctx) error { func (h *Handler) GetAllLeaguesForTenant(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { - domain.BadRequestLogger.Error("invalid company id") + h.BadRequestLogger().Error("invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } @@ -142,7 +142,7 @@ func (h *Handler) GetAllLeaguesForTenant(c *fiber.Ctx) error { if sportIDQuery != "" { sportIDint, err := strconv.Atoi(sportIDQuery) if err != nil { - domain.BadRequestLogger.Info("invalid sport id", + h.BadRequestLogger().Info("invalid sport id", zap.String("sport_id", sportIDQuery), zap.Error(err), ) @@ -183,7 +183,7 @@ func (h *Handler) GetAllLeaguesForTenant(c *fiber.Ctx) error { if err != nil { fmt.Printf("Error fetching league %v \n", err) - domain.InternalServerErrorLogger.Error("Failed to get all leagues", append(queryLogFields, zap.Error(err))...) + h.InternalServerErrorLogger().Error("Failed to get all leagues", append(queryLogFields, zap.Error(err))...) return fiber.NewError(fiber.StatusInternalServerError, "Failed to get leagues:"+err.Error()) } return response.WriteJSON(c, fiber.StatusOK, "All leagues retrieved", leagues, nil) @@ -228,7 +228,7 @@ func (h *Handler) SetLeagueActive(c *fiber.Ctx) error { var req SetLeagueActiveReq if err := c.BodyParser(&req); err != nil { - domain.InternalServerErrorLogger.Error("SetLeagueReq failed to parse request body", + h.InternalServerErrorLogger().Error("SetLeagueReq failed to parse request body", append(queryLogFields, zap.Error(err))..., ) return fiber.NewError(fiber.StatusBadRequest, "Failed to parse request:"+err.Error()) @@ -241,7 +241,7 @@ func (h *Handler) SetLeagueActive(c *fiber.Ctx) error { for field, msg := range valErrs { errMsg += fmt.Sprintf("%s: %s; ", field, msg) } - domain.BadRequestLogger.Info("Failed to validate SetLeagueActiveReq", append(queryLogFields, zap.Error(err))...) + h.BadRequestLogger().Info("Failed to validate SetLeagueActiveReq", append(queryLogFields, zap.Error(err))...) return fiber.NewError(fiber.StatusBadRequest, errMsg) } @@ -253,11 +253,11 @@ func (h *Handler) SetLeagueActive(c *fiber.Ctx) error { Valid: true, }, }); err != nil { - domain.InternalServerErrorLogger.Error("Failed to update league active", append(queryLogFields, zap.Error(err))...) + h.InternalServerErrorLogger().Error("Failed to update league active", append(queryLogFields, zap.Error(err))...) return fiber.NewError(fiber.StatusInternalServerError, "Failed to update league:"+err.Error()) } - domain.SuccessResLogger.Info("League Active has been successfully updated", queryLogFields...) + h.SuccessResLogger().Info("League Active has been successfully updated", queryLogFields...) return response.WriteJSON(c, fiber.StatusOK, "League updated successfully", nil, nil) } @@ -300,7 +300,7 @@ func (h *Handler) SetLeagueFeatured(c *fiber.Ctx) error { } var req SetLeagueAsFeatured if err := c.BodyParser(&req); err != nil { - domain.BadRequestLogger.Info("SetLeagueFeaturedReq failed to parse request body", append(queryLogFields, zap.Error(err))...) + h.BadRequestLogger().Info("SetLeagueFeaturedReq failed to parse request body", append(queryLogFields, zap.Error(err))...) return fiber.NewError(fiber.StatusBadRequest, "Failed to parse request body:"+err.Error()) } @@ -311,7 +311,7 @@ func (h *Handler) SetLeagueFeatured(c *fiber.Ctx) error { for field, msg := range valErrs { errMsg += fmt.Sprintf("%s: %s; ", field, msg) } - domain.BadRequestLogger.Info("Failed to validate SetLeagueFeaturedReq", append(queryLogFields, zap.Error(err))...) + h.BadRequestLogger().Info("Failed to validate SetLeagueFeaturedReq", append(queryLogFields, zap.Error(err))...) return fiber.NewError(fiber.StatusBadRequest, errMsg) } @@ -324,10 +324,10 @@ func (h *Handler) SetLeagueFeatured(c *fiber.Ctx) error { }, }) if err != nil { - domain.InternalServerErrorLogger.Error("Failed to update league", append(queryLogFields, zap.Error(err))...) + h.InternalServerErrorLogger().Error("Failed to update league", append(queryLogFields, zap.Error(err))...) return fiber.NewError(fiber.StatusInternalServerError, "Failed to update league:"+err.Error()) } - domain.SuccessResLogger.Info("League Featured has been successfully updated", queryLogFields...) + h.SuccessResLogger().Info("League Featured has been successfully updated", queryLogFields...) return response.WriteJSON(c, fiber.StatusOK, "League updated successfully", nil, nil) } diff --git a/internal/web_server/handlers/odd_handler.go b/internal/web_server/handlers/odd_handler.go index 3d62b35..bf89c84 100644 --- a/internal/web_server/handlers/odd_handler.go +++ b/internal/web_server/handlers/odd_handler.go @@ -21,13 +21,13 @@ import ( func (h *Handler) GetAllOdds(c *fiber.Ctx) error { limit, err := strconv.Atoi(c.Query("limit", "10")) // Default limit is 10 if err != nil || limit <= 0 { - domain.BadRequestLogger.Info("Invalid limit value", zap.Error(err)) + h.BadRequestLogger().Info("Invalid limit value", zap.Error(err)) return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value") } offset, err := strconv.Atoi(c.Query("offset", "0")) // Default offset is 0 if err != nil || offset < 0 { - domain.BadRequestLogger.Info("Invalid offset value", zap.Error(err)) + h.BadRequestLogger().Info("Invalid offset value", zap.Error(err)) return fiber.NewError(fiber.StatusBadRequest, err.Error()) } @@ -42,7 +42,7 @@ func (h *Handler) GetAllOdds(c *fiber.Ctx) error { }, }) if err != nil { - domain.InternalServerErrorLogger.Error("Failed to retrieve all odds", + h.InternalServerErrorLogger().Error("Failed to retrieve all odds", zap.Int("limit", limit), zap.Int("offset", offset), zap.Error(err), @@ -65,19 +65,19 @@ func (h *Handler) GetAllOdds(c *fiber.Ctx) error { func (h *Handler) GetAllTenantOdds(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { - domain.BadRequestLogger.Error("invalid company id") + h.BadRequestLogger().Error("invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } limit, err := strconv.Atoi(c.Query("limit", "10")) // Default limit is 10 if err != nil || limit <= 0 { - domain.BadRequestLogger.Info("Invalid limit value", zap.Error(err)) + h.BadRequestLogger().Info("Invalid limit value", zap.Error(err)) return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value") } offset, err := strconv.Atoi(c.Query("offset", "0")) // Default offset is 0 if err != nil || offset < 0 { - domain.BadRequestLogger.Info("Invalid offset value", zap.Error(err)) + h.BadRequestLogger().Info("Invalid offset value", zap.Error(err)) return fiber.NewError(fiber.StatusBadRequest, err.Error()) } @@ -92,7 +92,7 @@ func (h *Handler) GetAllTenantOdds(c *fiber.Ctx) error { }, }) if err != nil { - domain.InternalServerErrorLogger.Error("Failed to retrieve all odds", + h.InternalServerErrorLogger().Error("Failed to retrieve all odds", zap.Int("limit", limit), zap.Int("offset", offset), zap.Error(err), @@ -123,20 +123,20 @@ func (h *Handler) GetOddsByMarketID(c *fiber.Ctx) error { marketID := c.Params("market_id") if marketID == "" { - domain.BadRequestLogger.Info("Missing market_id", logFields...) + h.BadRequestLogger().Info("Missing market_id", logFields...) return fiber.NewError(fiber.StatusBadRequest, "Missing market_id") } upcomingID := c.Params("upcoming_id") if upcomingID == "" { - domain.BadRequestLogger.Info("Missing upcoming_id", logFields...) + h.BadRequestLogger().Info("Missing upcoming_id", logFields...) return fiber.NewError(fiber.StatusBadRequest, "Missing upcoming_id") } rawOdds, err := h.prematchSvc.GetOddsByMarketID(c.Context(), marketID, upcomingID) if err != nil { // Lets turn this into a warn because this is constantly going off - domain.InternalServerErrorLogger.Warn("Failed to get raw odds by market ID", append(logFields, zap.Error(err))...) + h.InternalServerErrorLogger().Warn("Failed to get raw odds by market ID", append(logFields, zap.Error(err))...) return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } @@ -159,7 +159,7 @@ func (h *Handler) GetOddsByMarketID(c *fiber.Ctx) error { func (h *Handler) GetTenantOddsByMarketID(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { - domain.BadRequestLogger.Error("invalid company id") + h.BadRequestLogger().Error("invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } logFields := []zap.Field{ @@ -170,13 +170,13 @@ func (h *Handler) GetTenantOddsByMarketID(c *fiber.Ctx) error { marketID := c.Params("market_id") if marketID == "" { - domain.BadRequestLogger.Info("Missing market_id", logFields...) + h.BadRequestLogger().Info("Missing market_id", logFields...) return fiber.NewError(fiber.StatusBadRequest, "Missing market_id") } upcomingID := c.Params("upcoming_id") if upcomingID == "" { - domain.BadRequestLogger.Info("Missing upcoming_id", logFields...) + h.BadRequestLogger().Info("Missing upcoming_id", logFields...) return fiber.NewError(fiber.StatusBadRequest, "Missing upcoming_id") } @@ -184,7 +184,7 @@ func (h *Handler) GetTenantOddsByMarketID(c *fiber.Ctx) error { if err != nil { // Lets turn this into a warn because this is constantly going off - domain.InternalServerErrorLogger.Warn("Failed to get raw odds by market ID", append(logFields, zap.Error(err))...) + h.InternalServerErrorLogger().Warn("Failed to get raw odds by market ID", append(logFields, zap.Error(err))...) return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } @@ -214,28 +214,28 @@ func (h *Handler) GetOddsByUpcomingID(c *fiber.Ctx) error { upcomingID := c.Params("upcoming_id") if upcomingID == "" { - domain.BadRequestLogger.Info("Missing upcoming_id", logFields...) + h.BadRequestLogger().Info("Missing upcoming_id", logFields...) return fiber.NewError(fiber.StatusBadRequest, "Missing upcoming_id") } limit, err := strconv.Atoi(c.Query("limit", "10")) // Default limit is 10 if err != nil || limit <= 0 { logFields = append(logFields, zap.Error(err)) - domain.BadRequestLogger.Info("Invalid limit value", logFields...) + h.BadRequestLogger().Info("Invalid limit value", logFields...) return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value") } offset, err := strconv.Atoi(c.Query("offset", "0")) // Default offset is 0 if err != nil || offset < 0 { logFields = append(logFields, zap.Error(err)) - domain.BadRequestLogger.Info("Invalid offset value", logFields...) + h.BadRequestLogger().Info("Invalid offset value", logFields...) return fiber.NewError(fiber.StatusBadRequest, err.Error()) } odds, err := h.prematchSvc.GetOddsByEventID(c.Context(), upcomingID, domain.OddMarketWithEventFilter{}) if err != nil { logFields = append(logFields, zap.Error(err)) - domain.InternalServerErrorLogger.Error("Failed to retrieve odds", append(logFields, zap.Error(err))...) + h.InternalServerErrorLogger().Error("Failed to retrieve odds", append(logFields, zap.Error(err))...) return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve odds"+err.Error()) } @@ -259,7 +259,7 @@ func (h *Handler) GetOddsByUpcomingID(c *fiber.Ctx) error { func (h *Handler) GetTenantOddsByUpcomingID(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { - domain.BadRequestLogger.Error("invalid company id") + h.BadRequestLogger().Error("invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } @@ -272,28 +272,28 @@ func (h *Handler) GetTenantOddsByUpcomingID(c *fiber.Ctx) error { upcomingID := c.Params("upcoming_id") if upcomingID == "" { - domain.BadRequestLogger.Info("Missing upcoming_id", logFields...) + h.BadRequestLogger().Info("Missing upcoming_id", logFields...) return fiber.NewError(fiber.StatusBadRequest, "Missing upcoming_id") } limit, err := strconv.Atoi(c.Query("limit", "10")) // Default limit is 10 if err != nil || limit <= 0 { logFields = append(logFields, zap.Error(err)) - domain.BadRequestLogger.Info("Invalid limit value", logFields...) + h.BadRequestLogger().Info("Invalid limit value", logFields...) return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value") } offset, err := strconv.Atoi(c.Query("offset", "0")) // Default offset is 0 if err != nil || offset < 0 { logFields = append(logFields, zap.Error(err)) - domain.BadRequestLogger.Info("Invalid offset value", logFields...) + h.BadRequestLogger().Info("Invalid offset value", logFields...) return fiber.NewError(fiber.StatusBadRequest, err.Error()) } odds, err := h.prematchSvc.GetOddsWithSettingsByEventID(c.Context(), upcomingID, companyID.Value, domain.OddMarketFilter{}) if err != nil { logFields = append(logFields, zap.Error(err)) - domain.InternalServerErrorLogger.Error("Failed to retrieve odds", append(logFields, zap.Error(err))...) + h.InternalServerErrorLogger().Error("Failed to retrieve odds", append(logFields, zap.Error(err))...) return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve odds"+err.Error()) } diff --git a/internal/web_server/handlers/settings_handler.go b/internal/web_server/handlers/settings_handler.go index 016b80b..bc30195 100644 --- a/internal/web_server/handlers/settings_handler.go +++ b/internal/web_server/handlers/settings_handler.go @@ -10,8 +10,8 @@ import ( "go.uber.org/zap" ) -func (h *Handler) GetSettingList(c *fiber.Ctx) error { - settingsList, err := h.settingSvc.GetSettingList(c.Context()) +func (h *Handler) GetGlobalSettingList(c *fiber.Ctx) error { + settingsList, err := h.settingSvc.GetGlobalSettingList(c.Context()) if err != nil { h.mongoLoggerSvc.Error("Failed to fetch settings", @@ -25,7 +25,7 @@ func (h *Handler) GetSettingList(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusOK, "All Settings retrieved successfully", settingsList, nil) } -func (h *Handler) GetSettingByKey(c *fiber.Ctx) error { +func (h *Handler) GetGlobalSettingByKey(c *fiber.Ctx) error { settingKey := c.Params("key") if settingKey == "" { h.mongoLoggerSvc.Info("empty setting key", @@ -35,7 +35,7 @@ func (h *Handler) GetSettingByKey(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "setting key must be passed") } - setting, err := h.settingSvc.GetSetting(c.Context(), settingKey) + setting, err := h.settingSvc.GetGlobalSetting(c.Context(), settingKey) if err != nil { h.mongoLoggerSvc.Info("invalid setting key", @@ -52,12 +52,12 @@ func (h *Handler) GetSettingByKey(c *fiber.Ctx) error { } -func (h *Handler) UpdateSettingList(c *fiber.Ctx) error { +func (h *Handler) UpdateGlobalSettingList(c *fiber.Ctx) error { - var req domain.UpdateSettingListReq + var req domain.SaveSettingListReq if err := c.BodyParser(&req); err != nil { - h.mongoLoggerSvc.Info("Failed to parse UpdateSettingListReq", + h.mongoLoggerSvc.Info("Failed to parse SaveSettingListReq", zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), @@ -70,26 +70,82 @@ func (h *Handler) UpdateSettingList(c *fiber.Ctx) error { for field, msg := range valErrs { errMsg += fmt.Sprintf("%s: %s; ", field, msg) } - h.mongoLoggerSvc.Info("Failed to validate UpdateSettingListReq", + h.mongoLoggerSvc.Info("Failed to validate SaveSettingListReq", zap.Int("status_code", fiber.StatusBadRequest), zap.String("errMsg", errMsg), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, errMsg) } - settingList := domain.ConvertUpdateSettingListReq(req) - err := h.settingSvc.UpdateSettingList(c.Context(), settingList) + settingList := domain.ConvertSaveSettingListReq(req) + err := h.settingSvc.UpdateGlobalSettingList(c.Context(), settingList) if err != nil { - h.mongoLoggerSvc.Info("failed to update setting", + h.mongoLoggerSvc.Info("failed to save setting", zap.Any("setting_list", settingList), zap.Int("status_code", fiber.StatusInternalServerError), zap.Time("timestamp", time.Now()), ) - return fiber.NewError(fiber.StatusInternalServerError, "failed to update setting") + return fiber.NewError(fiber.StatusInternalServerError, "failed to save setting") } - settingsList, err := h.settingSvc.GetSettingList(c.Context()) + settingsList, err := h.settingSvc.GetGlobalSettingList(c.Context()) + + if err != nil { + h.mongoLoggerSvc.Error("Failed to fetch settings", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get setting list:"+err.Error()) + } + + return response.WriteJSON(c, fiber.StatusOK, "setting updated", settingsList, nil) +} + +func (h *Handler) SaveCompanySettingList(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } + + var req domain.SaveSettingListReq + + if err := c.BodyParser(&req); err != nil { + h.mongoLoggerSvc.Info("Failed to parse SaveSettingListReq", + zap.Int("status_code", fiber.StatusBadRequest), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "Invalid request") + } + valErrs, ok := h.validator.Validate(c, req) + if !ok { + var errMsg string + for field, msg := range valErrs { + errMsg += fmt.Sprintf("%s: %s; ", field, msg) + } + h.mongoLoggerSvc.Info("Failed to validate SaveSettingListReq", + zap.Int("status_code", fiber.StatusBadRequest), + zap.String("errMsg", errMsg), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, errMsg) + } + settingList := domain.ConvertSaveSettingListReq(req) + err := h.settingSvc.InsertCompanySettingList(c.Context(), settingList, companyID.Value) + + if err != nil { + h.mongoLoggerSvc.Info("failed to save setting", + zap.Any("setting_list", settingList), + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusInternalServerError, "failed to save setting") + } + + settingsList, err := h.settingSvc.GetOverrideSettingsList(c.Context(), companyID.Value) if err != nil { h.mongoLoggerSvc.Error("Failed to fetch settings", @@ -103,3 +159,76 @@ func (h *Handler) UpdateSettingList(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusOK, "setting updated", settingsList, nil) } + +func (h *Handler) GetCompanySettingList(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } + + settingsList, err := h.settingSvc.GetOverrideSettingsList(c.Context(), companyID.Value) + + if err != nil { + h.mongoLoggerSvc.Error("Failed to fetch settings", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get setting list:"+err.Error()) + } + + return response.WriteJSON(c, fiber.StatusOK, "All Settings retrieved successfully", settingsList, nil) +} + +// /api/v1/{tenant_slug}/settings/{key} +func (h *Handler) DeleteCompanySetting(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } + + settingKey := c.Params("key") + if settingKey == "" { + h.mongoLoggerSvc.Info("empty setting key", + zap.Int("status_code", fiber.StatusBadRequest), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "setting key must be passed") + } + + err := h.settingSvc.DeleteCompanySetting(c.Context(), companyID.Value, settingKey) + + if err != nil { + h.mongoLoggerSvc.Error("Failed to delete company override settings", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete company override settings:"+err.Error()) + } + + return response.WriteJSON(c, fiber.StatusOK, "setting deleted", nil, nil) +} + +func (h *Handler) DeleteAllCompanySetting(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } + + err := h.settingSvc.DeleteAllCompanySetting(c.Context(), companyID.Value) + + if err != nil { + h.mongoLoggerSvc.Error("Failed to delete company override settings", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete company override settings:"+err.Error()) + } + + return response.WriteJSON(c, fiber.StatusOK, "setting deleted", nil, nil) +} diff --git a/internal/web_server/handlers/shop_handler.go b/internal/web_server/handlers/shop_handler.go index f63341f..7a25615 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", diff --git a/internal/web_server/handlers/ticket_handler.go b/internal/web_server/handlers/ticket_handler.go index b77f1e6..f3b108a 100644 --- a/internal/web_server/handlers/ticket_handler.go +++ b/internal/web_server/handlers/ticket_handler.go @@ -21,8 +21,13 @@ import ( // @Success 200 {object} domain.CreateTicketRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /api/v1/ticket [post] +// @Router /api/v1/{tenant_slug}/ticket [post] func (h *Handler) CreateTicket(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } var req domain.CreateTicketReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Info("Failed to parse CreateTicket request", @@ -47,7 +52,7 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, errMsg) } - newTicket, rows, err := h.ticketSvc.CreateTicket(c.Context(), req, c.IP()) + newTicket, rows, err := h.ticketSvc.CreateTicket(c.Context(), req, c.IP(), companyID.Value) if err != nil { @@ -85,8 +90,13 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error { // @Success 200 {object} domain.TicketRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /api/v1/ticket/{id} [get] +// @Router /api/v1/{tenant_slug}/ticket/{id} [get] func (h *Handler) GetTicketByID(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } ticketID := c.Params("id") id, err := strconv.ParseInt(ticketID, 10, 64) if err != nil { @@ -110,11 +120,22 @@ func (h *Handler) GetTicketByID(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve ticket") } + if ticket.CompanyID != companyID.Value { + h.mongoLoggerSvc.Warn("User attempt to access another company ticket", + zap.Int64("ticketID", id), + zap.Int("status_code", fiber.StatusNotFound), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve ticket") + } + res := domain.TicketRes{ ID: ticket.ID, Outcomes: ticket.Outcomes, Amount: ticket.Amount.Float32(), TotalOdds: ticket.TotalOdds, + CompanyID: ticket.CompanyID, } return response.WriteJSON(c, fiber.StatusOK, "Ticket retrieved successfully", res, nil) } @@ -128,10 +149,18 @@ func (h *Handler) GetTicketByID(c *fiber.Ctx) error { // @Success 200 {array} domain.TicketRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /api/v1/ticket [get] +// @Router /api/v1/{tenant_slug}/ticket [get] func (h *Handler) GetAllTickets(c *fiber.Ctx) error { + companyID := c.Locals("company_id").(domain.ValidInt64) + if !companyID.Valid { + h.BadRequestLogger().Error("invalid company id") + return fiber.NewError(fiber.StatusBadRequest, "invalid company id") + } + + tickets, err := h.ticketSvc.GetAllTickets(c.Context(), domain.TicketFilter{ + CompanyID: companyID, + }) - tickets, err := h.ticketSvc.GetAllTickets(c.Context()) if err != nil { h.mongoLoggerSvc.Error("Failed to get tickets", zap.Int("status_code", fiber.StatusInternalServerError), diff --git a/internal/web_server/handlers/user.go b/internal/web_server/handlers/user.go index f31452b..decf177 100644 --- a/internal/web_server/handlers/user.go +++ b/internal/web_server/handlers/user.go @@ -37,7 +37,7 @@ type CheckPhoneEmailExistRes struct { func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { - domain.BadRequestLogger.Error("invalid company id") + h.BadRequestLogger().Error("invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } var req CheckPhoneEmailExistReq @@ -95,7 +95,7 @@ type RegisterCodeReq struct { func (h *Handler) SendRegisterCode(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { - domain.BadRequestLogger.Error("invalid company id") + h.BadRequestLogger().Error("invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } var req RegisterCodeReq @@ -166,7 +166,7 @@ type RegisterUserReq struct { func (h *Handler) RegisterUser(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { - domain.BadRequestLogger.Error("invalid company id") + h.BadRequestLogger().Error("invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } var req RegisterUserReq @@ -248,7 +248,7 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error { } if req.ReferalCode != "" { - err = h.referralSvc.ProcessReferral(c.Context(), req.PhoneNumber, req.ReferalCode) + err = h.referralSvc.ProcessReferral(c.Context(), req.PhoneNumber, req.ReferalCode, companyID.Value) if err != nil { h.mongoLoggerSvc.Error("Failed to process referral during registration", zap.String("phone", req.PhoneNumber), @@ -297,7 +297,7 @@ type ResetCodeReq struct { func (h *Handler) SendResetCode(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { - domain.BadRequestLogger.Error("invalid company id") + h.BadRequestLogger().Error("invalid company id") return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } var req ResetCodeReq diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 0457919..2a4ead3 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -151,7 +151,7 @@ func (a *App) initAppRoutes() { tenantAuth.Get("/user/customer-profile", h.CustomerProfile) tenantAuth.Get("/user/admin-profile", h.AdminProfile) tenantAuth.Get("/user/bets", h.GetBetByUserID) - + groupV1.Get("/user/single/:id", a.authMiddleware, h.GetUserByID) groupV1.Post("/user/suspend", a.authMiddleware, h.UpdateUserSuspend) groupV1.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser) @@ -194,7 +194,7 @@ func (a *App) initAppRoutes() { groupV1.Get("/odds", a.authMiddleware, a.SuperAdminOnly, h.GetAllOdds) groupV1.Get("/odds/upcoming/:upcoming_id", a.authMiddleware, a.SuperAdminOnly, h.GetOddsByUpcomingID) groupV1.Get("/odds/upcoming/:upcoming_id/market/:market_id", a.authMiddleware, a.SuperAdminOnly, h.GetOddsByMarketID) - + tenant.Get("/odds", h.GetAllTenantOdds) tenant.Get("/odds/upcoming/:upcoming_id", h.GetTenantOddsByUpcomingID) tenant.Get("/odds/upcoming/:upcoming_id/market/:market_id", h.GetTenantOddsByMarketID) @@ -234,8 +234,8 @@ func (a *App) initAppRoutes() { groupV1.Get("/branchCashier", a.authMiddleware, h.GetBranchForCashier) // Branch Operation - groupV1.Get("/supportedOperation", a.authMiddleware, h.GetAllSupportedOperations) - groupV1.Post("/supportedOperation", a.authMiddleware, h.CreateSupportedOperation) + groupV1.Get("/supportedOperation", a.authMiddleware, a.SuperAdminOnly, h.GetAllSupportedOperations) + groupV1.Post("/supportedOperation", a.authMiddleware, a.SuperAdminOnly, h.CreateSupportedOperation) groupV1.Post("/operation", a.authMiddleware, h.CreateBranchOperation) groupV1.Get("/branch/:id/operation", a.authMiddleware, h.GetBranchOperations) @@ -252,20 +252,20 @@ func (a *App) initAppRoutes() { groupV1.Get("/admin-company", a.authMiddleware, h.GetCompanyForAdmin) // Ticket Routes - groupV1.Post("/ticket", h.CreateTicket) - groupV1.Get("/ticket", h.GetAllTickets) - groupV1.Get("/ticket/:id", h.GetTicketByID) + tenant.Post("/ticket", h.CreateTicket) + tenant.Get("/ticket", h.GetAllTickets) + tenant.Get("/ticket/:id", h.GetTicketByID) // Bet Routes - groupV1.Post("/sport/bet", a.authMiddleware, h.CreateBet) - groupV1.Post("/sport/bet/fastcode", a.authMiddleware, h.CreateBetWithFastCode) - groupV1.Get("/sport/bet/fastcode/:fast_code", h.GetBetByFastCode) - groupV1.Get("/sport/bet", a.authMiddleware, h.GetAllBet) - groupV1.Get("/sport/bet/:id", h.GetBetByID) - groupV1.Patch("/sport/bet/:id", a.authMiddleware, h.UpdateCashOut) - groupV1.Delete("/sport/bet/:id", a.authMiddleware, h.DeleteBet) + tenantAuth.Post("/sport/bet", h.CreateBet) + tenantAuth.Post("/sport/bet/fastcode", h.CreateBetWithFastCode) + tenant.Get("/sport/bet/fastcode/:fast_code", h.GetBetByFastCode) + tenantAuth.Get("/sport/bet", h.GetAllBet) + tenantAuth.Get("/sport/bet/:id", h.GetBetByID) + tenantAuth.Patch("/sport/bet/:id", h.UpdateCashOut) + tenantAuth.Delete("/sport/bet/:id", h.DeleteBet) - groupV1.Post("/sport/random/bet", a.authMiddleware, h.RandomBet) + tenantAuth.Post("/sport/random/bet", h.RandomBet) // Wallet groupV1.Get("/wallet", h.GetAllWallets) @@ -326,10 +326,11 @@ func (a *App) initAppRoutes() { groupV1.Post("/shop/cashout", a.authMiddleware, a.CompanyOnly, h.CashoutByCashoutID) groupV1.Post("/shop/deposit", a.authMiddleware, a.CompanyOnly, h.DepositForCustomer) // groupV1.Get("/shop/deposit", a.authMiddleware, a.CompanyOnly, h.DepositForCustomer) - groupV1.Get("/shop/transaction", a.authMiddleware, h.GetAllTransactions) - groupV1.Get("/shop/transaction/:id", a.authMiddleware, h.GetTransactionByID) - groupV1.Get("/shop/transaction/:id/bet", a.authMiddleware, h.GetShopBetByTransactionID) - groupV1.Put("/shop/transaction/:id", a.authMiddleware, h.UpdateTransactionVerified) + + groupV1.Get("/shop/transaction", a.authMiddleware, a.CompanyOnly, h.GetAllTransactions) + groupV1.Get("/shop/transaction/:id", a.authMiddleware, a.CompanyOnly, h.GetTransactionByID) + groupV1.Get("/shop/transaction/:id/bet", a.authMiddleware, a.CompanyOnly, h.GetShopBetByTransactionID) + groupV1.Put("/shop/transaction/:id", a.authMiddleware, a.CompanyOnly, h.UpdateTransactionVerified) // Notification Routes groupV1.Get("/ws/connect", a.WebsocketAuthMiddleware, h.ConnectSocket) @@ -362,8 +363,13 @@ func (a *App) initAppRoutes() { groupV1.Delete("/issues/:issue_id", a.authMiddleware, a.OnlyAdminAndAbove, h.DeleteIssue) // Settings - groupV1.Get("/settings", a.authMiddleware, h.GetSettingList) - groupV1.Get("/settings/:key", a.authMiddleware, h.GetSettingByKey) - groupV1.Put("/settings", a.authMiddleware, h.UpdateSettingList) + groupV1.Get("/settings", a.authMiddleware, a.SuperAdminOnly, h.GetGlobalSettingList) + groupV1.Get("/settings/:key", a.authMiddleware, a.SuperAdminOnly, h.GetGlobalSettingByKey) + groupV1.Put("/settings", a.authMiddleware, a.SuperAdminOnly, h.UpdateGlobalSettingList) + + tenantAuth.Post("/settings", h.SaveCompanySettingList) + tenantAuth.Get("/settings", h.GetCompanySettingList) + tenantAuth.Delete("/settings/:key", h.DeleteCompanySetting) + tenantAuth.Delete("/settings", h.DeleteAllCompanySetting) }