From 61bd5abd00dd87a5cda0bfb1aa2d4f703ea89726 Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Fri, 23 May 2025 22:12:52 +0300 Subject: [PATCH 1/3] fix: increase event fetch limit and restore cron job scheduling --- internal/services/event/service.go | 2 +- internal/web_server/cron.go | 34 +++++++++++++++--------------- internal/web_server/routes.go | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/internal/services/event/service.go b/internal/services/event/service.go index fe51aa7..6a2176d 100644 --- a/internal/services/event/service.go +++ b/internal/services/event/service.go @@ -104,7 +104,7 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error { for _, sportID := range sportIDs { var totalPages int = 1 var page int = 0 - var limit int = 10 + var limit int = 200 var count int = 0 for page <= totalPages { page = page + 1 diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index 8e0bfdd..769d0b3 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -21,22 +21,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 */15 * * * *", // Every 15 minutes - // 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 */15 * * * *", // Every 15 minutes + task: func() { + if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { + log.Printf("FetchNonLiveOdds error: %v", err) + } + }, + }, { spec: "0 */15 * * * *", // Every 15 Minutes task: func() { @@ -52,7 +52,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S } for _, job := range schedule { - job.task() + // job.task() if _, err := c.AddFunc(job.spec, job.task); err != nil { log.Fatalf("Failed to schedule cron job: %v", err) } diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 51a9d1c..7874ef7 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -35,7 +35,7 @@ func (a *App) initAppRoutes() { a.fiber.Get("/", func(c *fiber.Ctx) error { return c.JSON(fiber.Map{ "message": "Welcome to the FortuneBet API", - "version": "1.0dev2", + "version": "1.0dev3", }) }) From d04198e08a254c1119317f0bbd78d85ec2c4a991 Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Mon, 26 May 2025 12:48:51 +0300 Subject: [PATCH 2/3] fix: remove redundant unique constraint from customer_wallets table --- db/migrations/000001_fortune.up.sql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 64f3c2d..17b5e49 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -117,8 +117,7 @@ CREATE TABLE IF NOT EXISTS customer_wallets ( regular_wallet_id BIGINT NOT NULL, static_wallet_id BIGINT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE (customer_id, company_id) + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS wallet_transfer ( id BIGSERIAL PRIMARY KEY, From 1b6fbebddf8d3cc84f76637aab29e5a07d880f5c Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Tue, 27 May 2025 14:57:35 +0300 Subject: [PATCH 3/3] fix: get bet by users for online betting --- db/query/bet.sql | 4 ++ gen/db/bet.sql.go | 41 +++++++++++++++++++ internal/domain/bet.go | 4 ++ internal/repository/bet.go | 20 +++++++++ internal/services/bet/port.go | 1 + internal/services/bet/service.go | 10 +++++ internal/services/event/service.go | 2 +- internal/services/wallet/wallet.go | 2 +- internal/web_server/cron.go | 2 +- internal/web_server/handlers/bet_handler.go | 2 +- .../handlers/transaction_handler.go | 5 ++- internal/web_server/handlers/user.go | 31 ++++++++++++++ internal/web_server/routes.go | 1 + 13 files changed, 120 insertions(+), 5 deletions(-) diff --git a/db/query/bet.sql b/db/query/bet.sql index 61a3d02..7d3eab5 100644 --- a/db/query/bet.sql +++ b/db/query/bet.sql @@ -58,6 +58,10 @@ WHERE cashout_id = $1; SELECT * FROM bet_with_outcomes WHERE branch_id = $1; +-- name: GetBetByUserID :many +SELECT * +FROM bet_with_outcomes +WHERE user_id = $1; -- name: GetBetOutcomeByEventID :many SELECT * FROM bet_outcomes diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index 823fb43..3857c40 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -243,6 +243,47 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err return i, err } +const GetBetByUserID = `-- name: GetBetByUserID :many +SELECT id, amount, total_odds, status, full_name, phone_number, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes +FROM bet_with_outcomes +WHERE user_id = $1 +` + +func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]BetWithOutcome, error) { + rows, err := q.db.Query(ctx, GetBetByUserID, userID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []BetWithOutcome + for rows.Next() { + var i BetWithOutcome + if err := rows.Scan( + &i.ID, + &i.Amount, + &i.TotalOdds, + &i.Status, + &i.FullName, + &i.PhoneNumber, + &i.BranchID, + &i.UserID, + &i.CashedOut, + &i.CashoutID, + &i.CreatedAt, + &i.UpdatedAt, + &i.IsShopBet, + &i.Outcomes, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const GetBetOutcomeByBetID = `-- name: GetBetOutcomeByBetID :many SELECT id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires FROM bet_outcomes diff --git a/internal/domain/bet.go b/internal/domain/bet.go index d681bb8..9c5b53b 100644 --- a/internal/domain/bet.go +++ b/internal/domain/bet.go @@ -52,6 +52,7 @@ type Bet struct { IsShopBet bool CashedOut bool CashoutID string + CreatedAt time.Time } type GetBet struct { @@ -67,6 +68,7 @@ type GetBet struct { CashedOut bool CashoutID string Outcomes []BetOutcome + CreatedAt time.Time } type CreateBet struct { @@ -127,6 +129,7 @@ type BetRes struct { IsShopBet bool `json:"is_shop_bet" example:"false"` CashedOut bool `json:"cashed_out" example:"false"` CashedID string `json:"cashed_id" example:"21234"` + CreatedAt time.Time `json:"created_at" example:"2025-04-08T12:00:00Z"` } func ConvertCreateBet(bet Bet, createdNumber int64) CreateBetRes { @@ -158,5 +161,6 @@ func ConvertBet(bet GetBet) BetRes { IsShopBet: bet.IsShopBet, CashedOut: bet.CashedOut, CashedID: bet.CashoutID, + CreatedAt: bet.CreatedAt, } } diff --git a/internal/repository/bet.go b/internal/repository/bet.go index 81a501c..24fe5b8 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -29,6 +29,7 @@ func convertDBBet(bet dbgen.Bet) domain.Bet { IsShopBet: bet.IsShopBet, CashedOut: bet.CashedOut, CashoutID: bet.CashoutID, + CreatedAt: bet.CreatedAt.Time, } } @@ -78,6 +79,7 @@ func convertDBBetWithOutcomes(bet dbgen.BetWithOutcome) domain.GetBet { CashedOut: bet.CashedOut, CashoutID: bet.CashoutID, Outcomes: outcomes, + CreatedAt: bet.CreatedAt.Time, } } @@ -198,6 +200,24 @@ func (s *Store) GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain. return result, nil } +func (s *Store) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) { + bets, err := s.queries.GetBetByUserID(ctx, pgtype.Int8{ + Int64: UserID, + Valid: true, + }) + + if err != nil { + return nil, err + } + + var result []domain.GetBet = make([]domain.GetBet, 0, len(bets)) + for _, bet := range bets { + result = append(result, convertDBBetWithOutcomes(bet)) + } + + return result, 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 cdd1ea0..452083a 100644 --- a/internal/services/bet/port.go +++ b/internal/services/bet/port.go @@ -13,6 +13,7 @@ type BetStore interface { GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) GetAllBets(ctx context.Context) ([]domain.GetBet, error) GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error) + GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, 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 4e3f9bf..65af3d7 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -248,6 +248,12 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID return domain.CreateBetRes{}, err } + newBet.UserID = domain.ValidInt64{ + Value: userID, + Valid: true, + } + newBet.IsShopBet = false + default: return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type") } @@ -485,6 +491,10 @@ func (s *Service) GetBetByBranchID(ctx context.Context, branchID int64) ([]domai return s.betStore.GetBetByBranchID(ctx, branchID) } +func (s *Service) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) { + return s.betStore.GetBetByUserID(ctx, UserID) +} + func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error { return s.betStore.UpdateCashOut(ctx, id, cashedOut) } diff --git a/internal/services/event/service.go b/internal/services/event/service.go index fe51aa7..dca4de6 100644 --- a/internal/services/event/service.go +++ b/internal/services/event/service.go @@ -104,7 +104,7 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error { for _, sportID := range sportIDs { var totalPages int = 1 var page int = 0 - var limit int = 10 + var limit int = 150 var count int = 0 for page <= totalPages { page = page + 1 diff --git a/internal/services/wallet/wallet.go b/internal/services/wallet/wallet.go index feb29d0..cf9cd4c 100644 --- a/internal/services/wallet/wallet.go +++ b/internal/services/wallet/wallet.go @@ -87,7 +87,7 @@ func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain. return ErrBalanceInsufficient } - return s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) + return s.walletStore.UpdateBalance(ctx, id, wallet.Balance-amount) } func (s *Service) UpdateWalletActive(ctx context.Context, id int64, isActive bool) error { diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index 8e0bfdd..e07a014 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -52,7 +52,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S } for _, job := range schedule { - job.task() + // job.task() if _, err := c.AddFunc(job.spec, job.task); err != nil { log.Fatalf("Failed to schedule cron job: %v", err) } diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index b01fbd3..b5f87ec 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -45,7 +45,7 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error { h.logger.Error("PlaceBet failed", "error", err) switch err { case bet.ErrEventHasBeenRemoved, bet.ErrEventHasNotEnded, bet.ErrRawOddInvalid, wallet.ErrBalanceInsufficient: - return fiber.NewError(fiber.StatusBadGateway, err.Error()) + return fiber.NewError(fiber.StatusBadRequest, err.Error()) } return fiber.NewError(fiber.StatusInternalServerError, "Unable to create bet") } diff --git a/internal/web_server/handlers/transaction_handler.go b/internal/web_server/handlers/transaction_handler.go index b9c5d6a..8918023 100644 --- a/internal/web_server/handlers/transaction_handler.go +++ b/internal/web_server/handlers/transaction_handler.go @@ -172,7 +172,10 @@ func (h *Handler) CreateTransaction(c *fiber.Ctx) error { } user, err := h.userSvc.GetUserByID(c.Context(), userID) - + if err != nil { + h.logger.Error("CreateTransactionReq failed, user id invalid", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "User ID invalid", err, nil) + } transaction, err := h.transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{ BranchID: branchID, CashierID: userID, diff --git a/internal/web_server/handlers/user.go b/internal/web_server/handlers/user.go index a0121f9..aabea39 100644 --- a/internal/web_server/handlers/user.go +++ b/internal/web_server/handlers/user.go @@ -591,3 +591,34 @@ func (h *Handler) UpdateUserSuspend(c *fiber.Ctx) error { } return response.WriteJSON(c, fiber.StatusOK, "User suspend status updated successfully", res, nil) } + +// GetBetByUserID godoc +// @Summary Gets user bets +// @Description Gets user bets +// @Tags user +// @Accept json +// @Produce json +// @Success 200 {array} domain.BetRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /user/bets [get] +func (h *Handler) GetBetByUserID(c *fiber.Ctx) error { + userID, ok := c.Locals("user_id").(int64) + if !ok || userID == 0 { + h.logger.Error("Invalid user ID in context") + return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification") + } + + bets, err := h.betSvc.GetBetByUserID(c.Context(), userID) + if err != nil { + h.logger.Error("Failed to get bets", "error", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets") + } + + res := make([]domain.BetRes, len(bets)) + for i, bet := range bets { + res[i] = domain.ConvertBet(bet) + } + + return response.WriteJSON(c, fiber.StatusOK, "User bets retrieved successfully", res, nil) +} diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 51a9d1c..9854342 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -79,6 +79,7 @@ func (a *App) initAppRoutes() { a.fiber.Get("/user/single/:id", a.authMiddleware, h.GetUserByID) a.fiber.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser) a.fiber.Post("/user/suspend", a.authMiddleware, h.UpdateUserSuspend) + a.fiber.Get("/user/bets", a.authMiddleware, h.GetBetByUserID) a.fiber.Get("/user/wallet", a.authMiddleware, h.GetCustomerWallet) a.fiber.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone)