From d9f7cde114ef1a6f315c444af479a614ac488f7e Mon Sep 17 00:00:00 2001 From: Asher Samuel Date: Wed, 2 Jul 2025 16:57:36 +0300 Subject: [PATCH] create bet with fast code --- db/query/bet.sql | 5 + gen/db/bet.sql.go | 32 ++++ internal/repository/bet.go | 10 ++ internal/services/bet/port.go | 1 + internal/services/bet/service.go | 8 + internal/web_server/handlers/bet_handler.go | 172 ++++++++++++++++++-- internal/web_server/routes.go | 1 + 7 files changed, 219 insertions(+), 10 deletions(-) diff --git a/db/query/bet.sql b/db/query/bet.sql index 9ee10ad..badb2a7 100644 --- a/db/query/bet.sql +++ b/db/query/bet.sql @@ -94,6 +94,11 @@ WHERE branch_id = $1; SELECT * FROM bet_with_outcomes WHERE user_id = $1; +-- name: GetBetByFastCode :one +SELECT * +FROM bet_with_outcomes +WHERE fast_code = $1 +LIMIT 1; -- name: GetBetOutcomeByEventID :many SELECT * FROM bet_outcomes diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index 401290d..00b4bad 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -283,6 +283,38 @@ func (q *Queries) GetBetByCashoutID(ctx context.Context, cashoutID string) (BetW return i, err } +const GetBetByFastCode = `-- name: GetBetByFastCode :one +SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash, fast_code, outcomes +FROM bet_with_outcomes +WHERE fast_code = $1 +LIMIT 1 +` + +func (q *Queries) GetBetByFastCode(ctx context.Context, fastCode string) (BetWithOutcome, error) { + row := q.db.QueryRow(ctx, GetBetByFastCode, fastCode) + var i BetWithOutcome + err := row.Scan( + &i.ID, + &i.Amount, + &i.TotalOdds, + &i.Status, + &i.FullName, + &i.PhoneNumber, + &i.CompanyID, + &i.BranchID, + &i.UserID, + &i.CashedOut, + &i.CashoutID, + &i.CreatedAt, + &i.UpdatedAt, + &i.IsShopBet, + &i.OutcomesHash, + &i.FastCode, + &i.Outcomes, + ) + return i, err +} + const GetBetByID = `-- name: GetBetByID :one SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash, fast_code, outcomes FROM bet_with_outcomes diff --git a/internal/repository/bet.go b/internal/repository/bet.go index dfebf37..e6a0eaa 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -282,6 +282,16 @@ func (s *Store) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetB return result, nil } +func (s *Store) GetBetByFastCode(ctx context.Context, fastcode string) (domain.GetBet, error) { + bet, err := s.queries.GetBetByFastCode(ctx, fastcode) + + if err != nil { + return domain.GetBet{}, err + } + + return convertDBBetWithOutcomes(bet), nil +} + func (s *Store) GetBetCount(ctx context.Context, UserID int64, outcomesHash string) (int64, error) { count, err := s.queries.GetBetCount(ctx, dbgen.GetBetCountParams{ UserID: pgtype.Int8{Int64: UserID}, diff --git a/internal/services/bet/port.go b/internal/services/bet/port.go index 8756667..58a5610 100644 --- a/internal/services/bet/port.go +++ b/internal/services/bet/port.go @@ -15,6 +15,7 @@ type BetStore interface { GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error) GetBetByBranchID(ctx context.Context, BranchID int64) ([]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) diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index 3de60a0..3d3ba37 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -711,6 +711,14 @@ func (s *Service) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.Ge return s.betStore.GetBetByUserID(ctx, UserID) } +func (s *Service) GetBetOutcomeByBetID(ctx context.Context, UserID int64) ([]domain.BetOutcome, error) { + return s.betStore.GetBetOutcomeByBetID(ctx, UserID) +} + +func (s *Service) GetBetByFastCode(ctx context.Context, fastcode string) (domain.GetBet, error) { + 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) } diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index 77950e2..890b1f6 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -1,6 +1,7 @@ package handlers import ( + "fmt" "strconv" "time" @@ -37,6 +38,163 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } + res, err := h.CreateBetInternal(c, req, userID, role) + if err != nil { + h.mongoLoggerSvc.Error("Failed to create bet", + zap.Int("status_code", fiber.StatusOK), + zap.Int64("user_id", userID), + zap.Time("timestamp", time.Now()), + ) + } + + h.mongoLoggerSvc.Info("Bet created successfully", + zap.Int("status_code", fiber.StatusOK), + zap.Int64("user_id", userID), + zap.Time("timestamp", time.Now()), + ) + + return response.WriteJSON(c, fiber.StatusOK, "Bet Created", res, nil) +} + +// CreateBetWithFastCode godoc +// @Summary Create a bet with fast code +// @Description Creates a bet with fast code +// @Tags bet +// @Accept json +// @Produce json +// @Param createBetWithFastCode body domain.CreateBetReq true "Creates bet" +// @Success 200 {object} domain.BetRes +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /sport/bet/fastcode [post] +func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error { + userID := c.Locals("user_id").(int64) + role := c.Locals("role").(domain.Role) + + var req struct { + FastCode string `json:"fast_code"` + } + + if err := c.BodyParser(&req); err != nil { + h.mongoLoggerSvc.Error("Failed to parse CreateBet request", + zap.Int("status_code", fiber.StatusBadRequest), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + bet, err := h.betSvc.GetBetByFastCode(c.Context(), req.FastCode) + if err != nil { + h.mongoLoggerSvc.Error("falied to get bet with fast code", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "falied to get bet with fast code") + } + + outcomes, err := h.betSvc.GetBetOutcomeByBetID(c.Context(), bet.ID) + if err != nil { + h.mongoLoggerSvc.Error("falied to get BetOutcomes by BetID", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "falied to get BetOutcomes by BetID") + } + + bet_outcomes := []domain.CreateBetOutcomeReq{} + for _, outcome := range outcomes { + bet_outcomes = append(bet_outcomes, domain.CreateBetOutcomeReq{ + EventID: outcome.EventID, + OddID: outcome.OddID, + MarketID: outcome.MarketID, + }) + } + + user, err := h.userSvc.GetUserByID(c.Context(), userID) + if err != nil { + h.mongoLoggerSvc.Error("falied to get user information", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "falied to get user information") + } + + branch, err := h.branchSvc.GetBranchByID(c.Context(), user.CompanyID.Value) + if err != nil { + h.mongoLoggerSvc.Error("falied to get branch of user", + zap.Int("status_code", fiber.StatusInternalServerError), + zap.Error(err), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "falied to get branch of user") + } + + newReq := domain.CreateBetReq{ + Amount: float32(bet.Amount), + Status: bet.Status, + Outcomes: bet_outcomes, + BranchID: &branch.ID, + FullName: user.FirstName, + PhoneNumber: user.PhoneNumber, + } + + res, err := h.CreateBetInternal(c, newReq, userID, role) + if err != nil { + h.mongoLoggerSvc.Error("Failed to create bet", + zap.Int("status_code", fiber.StatusOK), + zap.Int64("user_id", userID), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "Failed to create bet") + } + + wallets, err := h.walletSvc.GetWalletsByUser(c.Context(), bet.UserID.Value) + var staticWallet domain.Wallet + var staticFound bool + for _, wallet := range wallets { + if !wallet.IsWithdraw { + staticWallet = wallet + staticFound = true + break + } + } + + if err != nil || staticFound == false { + fmt.Println("wallet error: ", err) + h.mongoLoggerSvc.Error("Failed to get static wallet of user", + zap.Int("status_code", fiber.StatusOK), + zap.Int64("user_id", user.ID), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "Failed to get wallets of user") + } + + // amount added for fast code owner can be fetched from settings in db + amount := domain.Currency(100) + + _, err = h.walletSvc.AddToWallet(c.Context(), staticWallet.ID, amount, domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}) + if err != nil { + h.mongoLoggerSvc.Error("Failed to add reward to static bet", + zap.Int("status_code", fiber.StatusOK), + zap.Int64("user_id", userID), + zap.Time("timestamp", time.Now()), + ) + return fiber.NewError(fiber.StatusBadRequest, "Failed to add reward to static bet") + } + + h.mongoLoggerSvc.Info("Bet created successfully", + zap.Int("status_code", fiber.StatusOK), + zap.Int64("user_id", userID), + zap.Time("timestamp", time.Now()), + ) + 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) (domain.CreateBetRes, error) { valErrs, ok := h.validator.Validate(c, req) if !ok { h.mongoLoggerSvc.Error("CreateBet validation failed", @@ -44,7 +202,7 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error { zap.Any("validation_errors", valErrs), zap.Time("timestamp", time.Now()), ) - return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + return domain.CreateBetRes{}, fmt.Errorf("%s", valErrs) } res, err := h.betSvc.PlaceBet(c.Context(), req, userID, role) @@ -57,19 +215,13 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error { switch err { case bet.ErrEventHasBeenRemoved, bet.ErrEventHasNotEnded, bet.ErrRawOddInvalid, wallet.ErrBalanceInsufficient: - return fiber.NewError(fiber.StatusBadRequest, err.Error()) + return domain.CreateBetRes{}, fiber.NewError(fiber.StatusBadRequest, err.Error()) } - return fiber.NewError(fiber.StatusInternalServerError, "Unable to create bet") + return domain.CreateBetRes{}, fiber.NewError(fiber.StatusInternalServerError, "Unable to create bet") } - h.mongoLoggerSvc.Info("Bet created successfully", - zap.Int("status_code", fiber.StatusOK), - zap.Int64("user_id", userID), - zap.Time("timestamp", time.Now()), - ) - - return response.WriteJSON(c, fiber.StatusOK, "Bet Created", res, nil) + return res, nil } // RandomBet godoc diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 48d6208..a32bb2e 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -192,6 +192,7 @@ func (a *App) initAppRoutes() { // Bet Routes a.fiber.Post("/sport/bet", a.authMiddleware, h.CreateBet) + a.fiber.Post("/sport/bet/fastcode", a.authMiddleware, h.CreateBetWithFastCode) a.fiber.Get("/sport/bet", a.authMiddleware, h.GetAllBet) a.fiber.Get("/sport/bet/:id", h.GetBetByID) a.fiber.Get("/sport/bet/cashout/:id", a.authMiddleware, h.GetBetByCashoutID)