From e3545f3f8ca7081bd6f144a799c488cb67aae6a6 Mon Sep 17 00:00:00 2001 From: Asher Samuel Date: Mon, 14 Jul 2025 19:30:37 +0300 Subject: [PATCH 1/4] flag too many outcomes --- db/migrations/000001_fortune.down.sql | 2 + db/migrations/000001_fortune.up.sql | 17 +++++++- db/migrations/000007_setting_data.up.sql | 2 +- db/query/bet.sql | 2 +- db/query/flags.sql | 8 ++++ gen/db/bet.sql.go | 8 ++-- gen/db/flags.sql.go | 42 ++++++++++++++++++++ gen/db/models.go | 9 +++++ internal/domain/bet.go | 25 +++++++++--- internal/repository/bet.go | 44 ++++++++++++++++++++- internal/services/bet/port.go | 3 +- internal/services/bet/service.go | 43 ++++++++++++++------ internal/web_server/handlers/bet_handler.go | 2 +- internal/web_server/routes.go | 2 +- reports/report_5min_2025-07-14_18-11.csv | 16 ++++++++ reports/report_5min_2025-07-14_18-15.csv | 16 ++++++++ reports/report_5min_2025-07-14_18-16.csv | 16 ++++++++ 17 files changed, 227 insertions(+), 30 deletions(-) create mode 100644 db/query/flags.sql create mode 100644 gen/db/flags.sql.go create mode 100644 reports/report_5min_2025-07-14_18-11.csv create mode 100644 reports/report_5min_2025-07-14_18-15.csv create mode 100644 reports/report_5min_2025-07-14_18-16.csv diff --git a/db/migrations/000001_fortune.down.sql b/db/migrations/000001_fortune.down.sql index 2332e39..d854608 100644 --- a/db/migrations/000001_fortune.down.sql +++ b/db/migrations/000001_fortune.down.sql @@ -79,4 +79,6 @@ DROP TABLE IF EXISTS events; DROP TABLE IF EXISTS leagues; DROP TABLE IF EXISTS teams; DROP TABLE IF EXISTS settings; +DROP TABLE IF EXISTS bonus; +DROP TABLE IF EXISTS flags; -- DELETE FROM wallet_transfer; \ No newline at end of file diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 5a74346..e1b8391 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -208,7 +208,7 @@ CREATE TABLE IF NOT EXISTS branches ( id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, location TEXT NOT NULL, - profit_percent REAL NOt NULL, + profit_percent REAL NOT NULL, is_active BOOLEAN NOT NULL DEFAULT false, wallet_id BIGINT NOT NULL, branch_manager_id BIGINT NOT NULL, @@ -319,6 +319,21 @@ CREATE TABLE bonus ( multiplier REAL NOT NULL, balance_cap BIGINT NOT NULL DEFAULT 0 ); +CREATE TABLE flags ( + id BIGSERIAL PRIMARY KEY, + bet_id BIGINT REFERENCES bets(id) ON DELETE CASCADE, + odd_id BIGINT REFERENCES odds(id), + reason TEXT, + flagged_at TIMESTAMP DEFAULT NOW(), + resolved BOOLEAN DEFAULT FALSE, + + -- either bet or odd is flagged (not at the same time) + CHECK ( + (bet_id IS NOT NULL AND odd_id IS NULL) + OR + (bet_id IS NULL AND odd_id IS NOT NULL) + ) +); -- Views CREATE VIEW companies_details AS SELECT companies.*, diff --git a/db/migrations/000007_setting_data.up.sql b/db/migrations/000007_setting_data.up.sql index adbc6c2..22f5974 100644 --- a/db/migrations/000007_setting_data.up.sql +++ b/db/migrations/000007_setting_data.up.sql @@ -4,7 +4,7 @@ VALUES ('max_number_of_outcomes', '30'), ('bet_amount_limit', '100000'), ('daily_ticket_limit', '50'), ('total_winnings_limit', '1000000'), - ('amount_for_bet_referral', '1000000') + ('amount_for_bet_referral', '1000000'), ('cashback_amount_cap', '1000') ON CONFLICT (key) DO UPDATE diff --git a/db/query/bet.sql b/db/query/bet.sql index 8cca28e..588e3c0 100644 --- a/db/query/bet.sql +++ b/db/query/bet.sql @@ -101,7 +101,7 @@ WHERE (event_id = $1) SELECT * FROM bet_outcomes WHERE bet_id = $1; --- name: GetBetCount :one +-- name: GetBetCountByUserID :one SELECT COUNT(*) FROM bets WHERE user_id = $1 diff --git a/db/query/flags.sql b/db/query/flags.sql new file mode 100644 index 0000000..60093c5 --- /dev/null +++ b/db/query/flags.sql @@ -0,0 +1,8 @@ +-- name: CreateFlag :one +INSERT INTO flags ( + bet_id, + odd_id, + reason +) VALUES ( + $1, $2, $3 +) RETURNING *; \ No newline at end of file diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index cae3d8b..965ccdf 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -282,20 +282,20 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID int64) ([]BetWithOu return items, nil } -const GetBetCount = `-- name: GetBetCount :one +const GetBetCountByUserID = `-- name: GetBetCountByUserID :one SELECT COUNT(*) FROM bets WHERE user_id = $1 AND outcomes_hash = $2 ` -type GetBetCountParams struct { +type GetBetCountByUserIDParams struct { UserID int64 `json:"user_id"` OutcomesHash string `json:"outcomes_hash"` } -func (q *Queries) GetBetCount(ctx context.Context, arg GetBetCountParams) (int64, error) { - row := q.db.QueryRow(ctx, GetBetCount, arg.UserID, arg.OutcomesHash) +func (q *Queries) GetBetCountByUserID(ctx context.Context, arg GetBetCountByUserIDParams) (int64, error) { + row := q.db.QueryRow(ctx, GetBetCountByUserID, arg.UserID, arg.OutcomesHash) var count int64 err := row.Scan(&count) return count, err diff --git a/gen/db/flags.sql.go b/gen/db/flags.sql.go new file mode 100644 index 0000000..17b406e --- /dev/null +++ b/gen/db/flags.sql.go @@ -0,0 +1,42 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: flags.sql + +package dbgen + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const CreateFlag = `-- name: CreateFlag :one +INSERT INTO flags ( + bet_id, + odd_id, + reason +) VALUES ( + $1, $2, $3 +) RETURNING id, bet_id, odd_id, reason, flagged_at, resolved +` + +type CreateFlagParams struct { + BetID pgtype.Int8 `json:"bet_id"` + OddID pgtype.Int8 `json:"odd_id"` + Reason pgtype.Text `json:"reason"` +} + +func (q *Queries) CreateFlag(ctx context.Context, arg CreateFlagParams) (Flag, error) { + row := q.db.QueryRow(ctx, CreateFlag, arg.BetID, arg.OddID, arg.Reason) + var i Flag + err := row.Scan( + &i.ID, + &i.BetID, + &i.OddID, + &i.Reason, + &i.FlaggedAt, + &i.Resolved, + ) + return i, err +} diff --git a/gen/db/models.go b/gen/db/models.go index cccf340..66a9bed 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -276,6 +276,15 @@ type FavoriteGame struct { CreatedAt pgtype.Timestamp `json:"created_at"` } +type Flag struct { + ID int64 `json:"id"` + BetID pgtype.Int8 `json:"bet_id"` + OddID pgtype.Int8 `json:"odd_id"` + Reason pgtype.Text `json:"reason"` + FlaggedAt pgtype.Timestamp `json:"flagged_at"` + Resolved pgtype.Bool `json:"resolved"` +} + type League struct { ID int64 `json:"id"` Name string `json:"name"` diff --git a/internal/domain/bet.go b/internal/domain/bet.go index a3f0c2b..8b9ae32 100644 --- a/internal/domain/bet.go +++ b/internal/domain/bet.go @@ -61,6 +61,15 @@ type BetFilter struct { CreatedAfter ValidTime } +type Flag struct { + ID int64 + BetID int64 + OddID int64 + Reason string + FlaggedAt time.Time + Resolved bool +} + type GetBet struct { ID int64 Amount Currency @@ -93,16 +102,22 @@ type CreateBetOutcomeReq struct { } type CreateBetReq struct { - Outcomes []CreateBetOutcomeReq `json:"outcomes" validate:"required"` - Amount float32 `json:"amount" validate:"required,gt=0" example:"100.0"` - BranchID *int64 `json:"branch_id,omitempty" validate:"required" example:"1"` + Outcomes []CreateBetOutcomeReq `json:"outcomes" validate:"required"` + Amount float32 `json:"amount" validate:"required,gt=0" example:"100.0"` + BranchID *int64 `json:"branch_id,omitempty" validate:"required" example:"1"` } -type CreateBetWithFastCodeReq struct { +type CreateBetWithFastCodeReq struct { FastCode string `json:"fast_code"` Amount float32 `json:"amount"` BranchID *int64 `json:"branch_id"` -} +} + +type CreateFlagReq struct { + BetID int64 + OddID int64 + Reason string +} type RandomBetReq struct { BranchID int64 `json:"branch_id" validate:"required" example:"1"` diff --git a/internal/repository/bet.go b/internal/repository/bet.go index bdf83d3..75714f8 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -76,6 +76,17 @@ func convertDBBetWithOutcomes(bet dbgen.BetWithOutcome) domain.GetBet { } } +func convertDBFlag(flag dbgen.Flag) domain.Flag { + return domain.Flag{ + ID: flag.ID, + BetID: flag.BetID.Int64, + OddID: flag.OddID.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, @@ -140,6 +151,35 @@ func (s *Store) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBe return rows, nil } +func (s *Store) CreateFlag(ctx context.Context, flag domain.CreateFlagReq) (domain.Flag, error) { + createFlag := dbgen.CreateFlagParams{ + BetID: pgtype.Int8{ + Int64: flag.BetID, + Valid: flag.BetID != 0, + }, + OddID: pgtype.Int8{ + Int64: flag.OddID, + Valid: flag.OddID != 0, + }, + Reason: pgtype.Text{ + String: flag.Reason, + Valid: true, + }, + } + + f, err := s.queries.CreateFlag(ctx, createFlag) + if err != nil { + domain.MongoDBLogger.Error("failed to create flag", + zap.String("flag", f.Reason.String), + zap.Any("flag_id", f.ID), + zap.Error(err), + ) + return domain.Flag{}, err + } + + return convertDBFlag(f), nil +} + func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) { bet, err := s.queries.GetBetByID(ctx, id) if err != nil { @@ -237,8 +277,8 @@ func (s *Store) GetBetsForCashback(ctx context.Context) ([]domain.GetBet, error) return res, nil } -func (s *Store) GetBetCount(ctx context.Context, UserID int64, outcomesHash string) (int64, error) { - count, err := s.queries.GetBetCount(ctx, dbgen.GetBetCountParams{ +func (s *Store) GetBetCountByUserID(ctx context.Context, UserID int64, outcomesHash string) (int64, error) { + count, err := s.queries.GetBetCountByUserID(ctx, dbgen.GetBetCountByUserIDParams{ UserID: UserID, OutcomesHash: outcomesHash, }) diff --git a/internal/services/bet/port.go b/internal/services/bet/port.go index 9a116be..e56cd40 100644 --- a/internal/services/bet/port.go +++ b/internal/services/bet/port.go @@ -10,13 +10,14 @@ import ( type BetStore interface { CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error) + CreateFlag(ctx context.Context, flag domain.CreateFlagReq) (domain.Flag, error) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) GetBetByFastCode(ctx context.Context, fastcode string) (domain.GetBet, error) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error) - GetBetCount(ctx context.Context, userID int64, outcomesHash string) (int64, error) + GetBetCountByUserID(ctx context.Context, userID int64, outcomesHash string) (int64, error) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index a3952db..81cd6bf 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -269,7 +269,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID return domain.CreateBetRes{}, err } - count, err := s.GetBetCount(ctx, userID, outcomesHash) + count, err := s.GetBetCountByUserID(ctx, userID, outcomesHash) if err != nil { s.mongoLogger.Error("failed to generate cashout ID", zap.Int64("user_id", userID), @@ -360,15 +360,15 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID case domain.RoleCustomer: // Only the customer is able to create a online bet newBet.IsShopBet = false - err = s.DeductBetFromCustomerWallet(ctx, req.Amount, userID) - if err != nil { - s.mongoLogger.Error("customer wallet deduction failed", - zap.Float32("amount", req.Amount), - zap.Int64("user_id", userID), - zap.Error(err), - ) - return domain.CreateBetRes{}, err - } + // err = s.DeductBetFromCustomerWallet(ctx, req.Amount, userID) + // if err != nil { + // s.mongoLogger.Error("customer wallet deduction failed", + // zap.Float32("amount", req.Amount), + // zap.Int64("user_id", userID), + // zap.Error(err), + // ) + // return domain.CreateBetRes{}, err + // } default: s.mongoLogger.Error("unknown role type", zap.String("role", string(role)), @@ -398,6 +398,23 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID return domain.CreateBetRes{}, err } + // flag bets that have more than three outcomes + if len(outcomes) > 3 { + flag := domain.CreateFlagReq{ + BetID: bet.ID, + OddID: 0, + Reason: fmt.Sprintf("too many outcomes - (%d)", len(outcomes)), + } + + _, err := s.betStore.CreateFlag(ctx, flag) + if err != nil { + s.mongoLogger.Error("failed to create flag for bet", + zap.Int64("bet_id", bet.ID), + zap.Error(err), + ) + } + } + res := domain.ConvertCreateBet(bet, rows) return res, nil @@ -716,7 +733,7 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le return domain.CreateBetRes{}, err } - count, err := s.GetBetCount(ctx, userID, outcomesHash) + count, err := s.GetBetCountByUserID(ctx, userID, outcomesHash) if err != nil { s.mongoLogger.Error("failed to get bet count", zap.Int64("user_id", userID), @@ -799,8 +816,8 @@ func (s *Service) GetBetByFastCode(ctx context.Context, fastcode string) (domain return s.betStore.GetBetByFastCode(ctx, fastcode) } -func (s *Service) GetBetCount(ctx context.Context, UserID int64, outcomesHash string) (int64, error) { - return s.betStore.GetBetCount(ctx, UserID, outcomesHash) +func (s *Service) GetBetCountByUserID(ctx context.Context, UserID int64, outcomesHash string) (int64, error) { + return s.betStore.GetBetCountByUserID(ctx, UserID, outcomesHash) } func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error { diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index 0c335a6..024c2a7 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -118,7 +118,7 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error { } // This can be for both online and offline bets - // If bet is an online bet (if the customer role creates the bet on their own) + // If bet is an online bet (if the customer role creates the bet on their own) // then the branchID is null newReq := domain.CreateBetReq{ Amount: req.Amount, diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index d5bf75a..5d08063 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -161,7 +161,7 @@ func (a *App) initAppRoutes() { groupV1.Put("/leagues/:id/featured", h.SetLeagueFeatured) groupV1.Get("/result/:id", h.GetResultsByEventID) - + // Branch groupV1.Post("/branch", a.authMiddleware, h.CreateBranch) groupV1.Get("/branch", a.authMiddleware, h.GetAllBranches) diff --git a/reports/report_5min_2025-07-14_18-11.csv b/reports/report_5min_2025-07-14_18-11.csv new file mode 100644 index 0000000..c9ad19d --- /dev/null +++ b/reports/report_5min_2025-07-14_18-11.csv @@ -0,0 +1,16 @@ +Sports Betting Reports (Periodic) +Period,Total Bets,Total Cash Made,Total Cash Out,Total Cash Backs,Total Deposits,Total Withdrawals,Total Tickets +5min,0,0.00,0.00,0.00,0.00,0.00,0 + +Virtual Game Reports (Periodic) +Game Name,Number of Bets,Total Transaction Sum + +Company Reports (Periodic) +Company ID,Company Name,Total Bets,Total Cash In,Total Cash Out,Total Cash Backs + +Branch Reports (Periodic) +Branch ID,Branch Name,Company ID,Total Bets,Total Cash In,Total Cash Out,Total Cash Backs + +Total Summary +Total Bets,Total Cash In,Total Cash Out,Total Cash Backs +0,0.00,0.00,0.00 diff --git a/reports/report_5min_2025-07-14_18-15.csv b/reports/report_5min_2025-07-14_18-15.csv new file mode 100644 index 0000000..c9ad19d --- /dev/null +++ b/reports/report_5min_2025-07-14_18-15.csv @@ -0,0 +1,16 @@ +Sports Betting Reports (Periodic) +Period,Total Bets,Total Cash Made,Total Cash Out,Total Cash Backs,Total Deposits,Total Withdrawals,Total Tickets +5min,0,0.00,0.00,0.00,0.00,0.00,0 + +Virtual Game Reports (Periodic) +Game Name,Number of Bets,Total Transaction Sum + +Company Reports (Periodic) +Company ID,Company Name,Total Bets,Total Cash In,Total Cash Out,Total Cash Backs + +Branch Reports (Periodic) +Branch ID,Branch Name,Company ID,Total Bets,Total Cash In,Total Cash Out,Total Cash Backs + +Total Summary +Total Bets,Total Cash In,Total Cash Out,Total Cash Backs +0,0.00,0.00,0.00 diff --git a/reports/report_5min_2025-07-14_18-16.csv b/reports/report_5min_2025-07-14_18-16.csv new file mode 100644 index 0000000..c9ad19d --- /dev/null +++ b/reports/report_5min_2025-07-14_18-16.csv @@ -0,0 +1,16 @@ +Sports Betting Reports (Periodic) +Period,Total Bets,Total Cash Made,Total Cash Out,Total Cash Backs,Total Deposits,Total Withdrawals,Total Tickets +5min,0,0.00,0.00,0.00,0.00,0.00,0 + +Virtual Game Reports (Periodic) +Game Name,Number of Bets,Total Transaction Sum + +Company Reports (Periodic) +Company ID,Company Name,Total Bets,Total Cash In,Total Cash Out,Total Cash Backs + +Branch Reports (Periodic) +Branch ID,Branch Name,Company ID,Total Bets,Total Cash In,Total Cash Out,Total Cash Backs + +Total Summary +Total Bets,Total Cash In,Total Cash Out,Total Cash Backs +0,0.00,0.00,0.00 From bbd73576af273c8ba5a0e6e3189e078cd7062d03 Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Mon, 14 Jul 2025 21:00:40 +0300 Subject: [PATCH 2/4] fix: getting it ready for v1.0dev10 deployment --- internal/web_server/cron.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index 67df201..8d45a17 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -24,22 +24,22 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S spec string task func() }{ - // { - // spec: "0 0 * * * *", // Every 1 hour - // task: func() { - // if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { - // log.Printf("FetchUpcomingEvents error: %v", err) - // } - // }, - // }, - // { - // spec: "0 0 * * * *", // Every 1 hour (since its takes that long to fetch all the events) - // task: func() { - // if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { - // log.Printf("FetchNonLiveOdds error: %v", err) - // } - // }, - // }, + { + spec: "0 0 * * * *", // Every 1 hour + task: func() { + if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { + log.Printf("FetchUpcomingEvents error: %v", err) + } + }, + }, + { + spec: "0 0 * * * *", // Every 1 hour (since its takes that long to fetch all the events) + task: func() { + if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { + log.Printf("FetchNonLiveOdds error: %v", err) + } + }, + }, { spec: "0 */5 * * * *", // Every 5 Minutes task: func() { From f63e35fb4effe8fbd57280dc2c9be837f5f52c72 Mon Sep 17 00:00:00 2001 From: Asher Samuel Date: Mon, 14 Jul 2025 23:05:17 +0300 Subject: [PATCH 3/4] flag multiple bets with same bet outcomes --- db/query/bet.sql | 4 ++ gen/db/bet.sql.go | 13 ++++++ internal/repository/bet.go | 9 +++++ internal/services/bet/port.go | 1 + internal/services/bet/service.go | 50 +++++++++++++++++++----- reports/report_5min_2025-07-14_18-11.csv | 16 -------- reports/report_5min_2025-07-14_18-15.csv | 16 -------- reports/report_5min_2025-07-14_18-16.csv | 16 -------- 8 files changed, 68 insertions(+), 57 deletions(-) delete mode 100644 reports/report_5min_2025-07-14_18-11.csv delete mode 100644 reports/report_5min_2025-07-14_18-15.csv delete mode 100644 reports/report_5min_2025-07-14_18-16.csv diff --git a/db/query/bet.sql b/db/query/bet.sql index 588e3c0..fdc74d8 100644 --- a/db/query/bet.sql +++ b/db/query/bet.sql @@ -106,6 +106,10 @@ SELECT COUNT(*) FROM bets WHERE user_id = $1 AND outcomes_hash = $2; +-- name: GetBetCountByOutcomesHash :one +SELECT COUNT(*) +FROM bets +WHERE outcomes_hash = $1; -- name: UpdateCashOut :exec UPDATE bets SET cashed_out = $2, diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index 965ccdf..c8d18d5 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -282,6 +282,19 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID int64) ([]BetWithOu return items, nil } +const GetBetCountByOutcomesHash = `-- name: GetBetCountByOutcomesHash :one +SELECT COUNT(*) +FROM bets +WHERE outcomes_hash = $1 +` + +func (q *Queries) GetBetCountByOutcomesHash(ctx context.Context, outcomesHash string) (int64, error) { + row := q.db.QueryRow(ctx, GetBetCountByOutcomesHash, outcomesHash) + var count int64 + err := row.Scan(&count) + return count, err +} + const GetBetCountByUserID = `-- name: GetBetCountByUserID :one SELECT COUNT(*) FROM bets diff --git a/internal/repository/bet.go b/internal/repository/bet.go index 75714f8..f61e5c8 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -290,6 +290,15 @@ func (s *Store) GetBetCountByUserID(ctx context.Context, UserID int64, outcomesH return count, nil } +func (s *Store) GetBetCountByOutcomesHash(ctx context.Context, outcomesHash string) (int64, error) { + count, err := s.queries.GetBetCountByOutcomesHash(ctx, outcomesHash) + if err != nil { + return 0, err + } + + return count, nil +} + func (s *Store) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error { err := s.queries.UpdateCashOut(ctx, dbgen.UpdateCashOutParams{ ID: id, diff --git a/internal/services/bet/port.go b/internal/services/bet/port.go index e56cd40..6c79f0a 100644 --- a/internal/services/bet/port.go +++ b/internal/services/bet/port.go @@ -18,6 +18,7 @@ type BetStore interface { GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error) GetBetCountByUserID(ctx context.Context, userID int64, outcomesHash string) (int64, error) + GetBetCountByOutcomesHash(ctx context.Context, outcomesHash string) (int64, error) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index 81cd6bf..7a2f0a4 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -360,15 +360,15 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID case domain.RoleCustomer: // Only the customer is able to create a online bet newBet.IsShopBet = false - // err = s.DeductBetFromCustomerWallet(ctx, req.Amount, userID) - // if err != nil { - // s.mongoLogger.Error("customer wallet deduction failed", - // zap.Float32("amount", req.Amount), - // zap.Int64("user_id", userID), - // zap.Error(err), - // ) - // return domain.CreateBetRes{}, err - // } + err = s.DeductBetFromCustomerWallet(ctx, req.Amount, userID) + if err != nil { + s.mongoLogger.Error("customer wallet deduction failed", + zap.Float32("amount", req.Amount), + zap.Int64("user_id", userID), + zap.Error(err), + ) + return domain.CreateBetRes{}, err + } default: s.mongoLogger.Error("unknown role type", zap.String("role", string(role)), @@ -415,6 +415,34 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID } } + // large amount of users betting on the same bet_outcomes + total_bet_count, err := s.betStore.GetBetCountByOutcomesHash(ctx, outcomesHash) + if err != nil { + s.mongoLogger.Error("failed to get bet outcomes count", + zap.String("outcomes_hash", outcomesHash), + zap.Error(err), + ) + return domain.CreateBetRes{}, err + } + + fmt.Println("total bet count: ", total_bet_count) + + if total_bet_count > 3 { + flag := domain.CreateFlagReq{ + BetID: bet.ID, + OddID: 0, + Reason: fmt.Sprintf("too many users bet on same outcomes - (%s)", outcomesHash), + } + _, err := s.betStore.CreateFlag(ctx, flag) + + if err != nil { + s.mongoLogger.Error("failed to get bet outcomes count", + zap.String("outcomes_hash", outcomesHash), + zap.Error(err), + ) + } + } + res := domain.ConvertCreateBet(bet, rows) return res, nil @@ -820,6 +848,10 @@ func (s *Service) GetBetCountByUserID(ctx context.Context, UserID int64, outcome return s.betStore.GetBetCountByUserID(ctx, UserID, outcomesHash) } +func (s *Service) GetBetCountByOutcomesHash(ctx context.Context, outcomesHash string) (int64, error) { + return s.betStore.GetBetCountByOutcomesHash(ctx, outcomesHash) +} + func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error { return s.betStore.UpdateCashOut(ctx, id, cashedOut) } diff --git a/reports/report_5min_2025-07-14_18-11.csv b/reports/report_5min_2025-07-14_18-11.csv deleted file mode 100644 index c9ad19d..0000000 --- a/reports/report_5min_2025-07-14_18-11.csv +++ /dev/null @@ -1,16 +0,0 @@ -Sports Betting Reports (Periodic) -Period,Total Bets,Total Cash Made,Total Cash Out,Total Cash Backs,Total Deposits,Total Withdrawals,Total Tickets -5min,0,0.00,0.00,0.00,0.00,0.00,0 - -Virtual Game Reports (Periodic) -Game Name,Number of Bets,Total Transaction Sum - -Company Reports (Periodic) -Company ID,Company Name,Total Bets,Total Cash In,Total Cash Out,Total Cash Backs - -Branch Reports (Periodic) -Branch ID,Branch Name,Company ID,Total Bets,Total Cash In,Total Cash Out,Total Cash Backs - -Total Summary -Total Bets,Total Cash In,Total Cash Out,Total Cash Backs -0,0.00,0.00,0.00 diff --git a/reports/report_5min_2025-07-14_18-15.csv b/reports/report_5min_2025-07-14_18-15.csv deleted file mode 100644 index c9ad19d..0000000 --- a/reports/report_5min_2025-07-14_18-15.csv +++ /dev/null @@ -1,16 +0,0 @@ -Sports Betting Reports (Periodic) -Period,Total Bets,Total Cash Made,Total Cash Out,Total Cash Backs,Total Deposits,Total Withdrawals,Total Tickets -5min,0,0.00,0.00,0.00,0.00,0.00,0 - -Virtual Game Reports (Periodic) -Game Name,Number of Bets,Total Transaction Sum - -Company Reports (Periodic) -Company ID,Company Name,Total Bets,Total Cash In,Total Cash Out,Total Cash Backs - -Branch Reports (Periodic) -Branch ID,Branch Name,Company ID,Total Bets,Total Cash In,Total Cash Out,Total Cash Backs - -Total Summary -Total Bets,Total Cash In,Total Cash Out,Total Cash Backs -0,0.00,0.00,0.00 diff --git a/reports/report_5min_2025-07-14_18-16.csv b/reports/report_5min_2025-07-14_18-16.csv deleted file mode 100644 index c9ad19d..0000000 --- a/reports/report_5min_2025-07-14_18-16.csv +++ /dev/null @@ -1,16 +0,0 @@ -Sports Betting Reports (Periodic) -Period,Total Bets,Total Cash Made,Total Cash Out,Total Cash Backs,Total Deposits,Total Withdrawals,Total Tickets -5min,0,0.00,0.00,0.00,0.00,0.00,0 - -Virtual Game Reports (Periodic) -Game Name,Number of Bets,Total Transaction Sum - -Company Reports (Periodic) -Company ID,Company Name,Total Bets,Total Cash In,Total Cash Out,Total Cash Backs - -Branch Reports (Periodic) -Branch ID,Branch Name,Company ID,Total Bets,Total Cash In,Total Cash Out,Total Cash Backs - -Total Summary -Total Bets,Total Cash In,Total Cash Out,Total Cash Backs -0,0.00,0.00,0.00 From 1c3f57519566b6e3c68ba250ec036f2ddbba7ead Mon Sep 17 00:00:00 2001 From: Asher Samuel Date: Tue, 15 Jul 2025 15:39:47 +0300 Subject: [PATCH 4/4] flag abused odd --- db/query/bet.sql | 4 ++++ gen/db/bet.sql.go | 13 ++++++++++++ internal/repository/bet.go | 9 +++++++++ internal/services/bet/port.go | 1 + internal/services/bet/service.go | 34 +++++++++++++++++++++++++++++--- 5 files changed, 58 insertions(+), 3 deletions(-) diff --git a/db/query/bet.sql b/db/query/bet.sql index fdc74d8..47018e1 100644 --- a/db/query/bet.sql +++ b/db/query/bet.sql @@ -101,6 +101,10 @@ WHERE (event_id = $1) SELECT * FROM bet_outcomes WHERE bet_id = $1; +-- name: GetBetOutcomeCountByOddID :one +SELECT COUNT(*) +FROM bet_outcomes +WHERE odd_id = $1; -- name: GetBetCountByUserID :one SELECT COUNT(*) FROM bets diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index c8d18d5..31ca511 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -410,6 +410,19 @@ func (q *Queries) GetBetOutcomeByEventID(ctx context.Context, arg GetBetOutcomeB return items, nil } +const GetBetOutcomeCountByOddID = `-- name: GetBetOutcomeCountByOddID :one +SELECT COUNT(*) +FROM bet_outcomes +WHERE odd_id = $1 +` + +func (q *Queries) GetBetOutcomeCountByOddID(ctx context.Context, oddID int64) (int64, error) { + row := q.db.QueryRow(ctx, GetBetOutcomeCountByOddID, oddID) + var count int64 + err := row.Scan(&count) + return count, err +} + const GetBetsForCashback = `-- name: GetBetsForCashback :many SELECT id, amount, total_odds, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at, full_name, phone_number, outcomes FROM bet_with_outcomes diff --git a/internal/repository/bet.go b/internal/repository/bet.go index f61e5c8..2dee7b1 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -299,6 +299,15 @@ func (s *Store) GetBetCountByOutcomesHash(ctx context.Context, outcomesHash stri return count, nil } +func (s *Store) GetBetOutcomeCountByOddID(ctx context.Context, oddID int64) (int64, error) { + count, err := s.queries.GetBetOutcomeCountByOddID(ctx, oddID) + if err != nil { + return 0, err + } + + return count, nil +} + func (s *Store) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error { err := s.queries.UpdateCashOut(ctx, dbgen.UpdateCashOutParams{ ID: id, diff --git a/internal/services/bet/port.go b/internal/services/bet/port.go index 6c79f0a..e29b68e 100644 --- a/internal/services/bet/port.go +++ b/internal/services/bet/port.go @@ -17,6 +17,7 @@ type BetStore interface { GetBetByFastCode(ctx context.Context, fastcode string) (domain.GetBet, error) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error) + GetBetOutcomeCountByOddID(ctx context.Context, oddID int64) (int64, error) GetBetCountByUserID(ctx context.Context, userID int64, outcomesHash string) (int64, error) GetBetCountByOutcomesHash(ctx context.Context, outcomesHash string) (int64, error) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index 7a2f0a4..7804ec4 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -398,6 +398,36 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID return domain.CreateBetRes{}, err } + for i := range outcomes { + // flag odds with large amount of users betting on them + count, err := s.betStore.GetBetOutcomeCountByOddID(ctx, outcomes[i].OddID) + if err != nil { + s.mongoLogger.Error("failed to get count of bet outcome", + zap.Int64("bet_id", bet.ID), + zap.Int64("odd_id", outcomes[i].OddID), + zap.Error(err), + ) + return domain.CreateBetRes{}, err + } + + // TODO: fetch cap from settings in db + if count > 20 { + flag := domain.CreateFlagReq{ + BetID: 0, + OddID: outcomes[i].OddID, + Reason: fmt.Sprintf("too many users targeting odd - (%d)", outcomes[i].OddID), + } + + _, err := s.betStore.CreateFlag(ctx, flag) + if err != nil { + s.mongoLogger.Error("failed to create flag for bet", + zap.Int64("bet_id", bet.ID), + zap.Error(err), + ) + } + } + } + // flag bets that have more than three outcomes if len(outcomes) > 3 { flag := domain.CreateFlagReq{ @@ -425,9 +455,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID return domain.CreateBetRes{}, err } - fmt.Println("total bet count: ", total_bet_count) - - if total_bet_count > 3 { + if total_bet_count > 10 { flag := domain.CreateFlagReq{ BetID: bet.ID, OddID: 0,