package handlers import ( "fmt" "strconv" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/gofiber/fiber/v2" "go.uber.org/zap" ) // CreateBet godoc // @Summary Create a bet // @Description Creates a bet // @Tags bet // @Accept json // @Produce json // @Param createBet body domain.CreateBetReq true "Creates bet" // @Success 200 {object} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @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) var req domain.CreateBetReq 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:"+err.Error()) } res, err := h.CreateBetInternal(c, req, userID, role, companyID.Value) if err != nil { switch err { case bet.ErrEventHasBeenRemoved, bet.ErrEventHasNotEnded, bet.ErrRawOddInvalid, bet.ErrTotalBalanceNotEnough: h.mongoLoggerSvc.Info("PlaceBet failed", zap.Int("status_code", fiber.StatusBadRequest), zap.Int64("userID", userID), zap.Int64("companyID", companyID.Value), zap.String("role", string(role)), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, err.Error()) } h.mongoLoggerSvc.Error("Failed to create bet", zap.Int("status_code", fiber.StatusInternalServerError), zap.Int64("user_id", userID), zap.String("role", string(role)), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to create bet:"+err.Error()) } 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.CreateBetWithFastCodeReq true "Creates bet" // @Success 200 {object} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @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) var req domain.CreateBetWithFastCodeReq 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:"+err.Error()) } bet, err := h.betSvc.GetBetByFastCode(c.Context(), req.FastCode) if err != nil { h.mongoLoggerSvc.Info("failed to get bet with fast code", zap.String("fast_code", req.FastCode), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "failed to get bet with fast code:"+err.Error()) } if bet.UserID == userID { h.mongoLoggerSvc.Info("User cannot refer himself", zap.Int64("bet_id", bet.ID), zap.Int("status_code", fiber.StatusBadRequest), zap.Time("timestamp", time.Now()), zap.Error(err), ) return fiber.NewError(fiber.StatusBadRequest, "User cannot use his own referral code") } outcomes, err := h.betSvc.GetBetOutcomeByBetID(c.Context(), bet.ID) if err != nil { h.mongoLoggerSvc.Info("failed to get BetOutcomes by BetID", zap.Int64("bet_id", bet.ID), zap.Int("status_code", fiber.StatusBadRequest), zap.Time("timestamp", time.Now()), zap.Error(err), ) return fiber.NewError(fiber.StatusBadRequest, "failed to get BetOutcomes by BetID:"+err.Error()) } bet_outcomes := []domain.CreateBetOutcomeReq{} for _, outcome := range outcomes { bet_outcomes = append(bet_outcomes, domain.CreateBetOutcomeReq{ EventID: outcome.EventID, OddID: outcome.OddID, MarketID: outcome.MarketID, }) } // 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) // then the branchID is null newReq := domain.CreateBetReq{ Amount: req.Amount, Outcomes: bet_outcomes, BranchID: req.BranchID, } 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), zap.Int64("user_id", userID), zap.String("role", string(role)), zap.Any("newReq", newReq), zap.Time("timestamp", time.Now()), zap.Error(err), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to create bet:"+err.Error()) } wallet, _ := h.walletSvc.GetCustomerWallet(c.Context(), bet.UserID) // amount added for fast code owner can be fetched from settings in db settingList, _ := h.settingSvc.GetOverrideSettingsList(c.Context(), companyID.Value) amount := settingList.AmountForBetReferral _, err = h.walletSvc.AddToWallet(c.Context(), wallet.StaticID, amount, domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, fmt.Sprintf("Added %v to static wallet by referring using fast_code", amount.Float32())) if err != nil { h.mongoLoggerSvc.Error("Failed to add reward to static bet", zap.Int("status_code", fiber.StatusBadRequest), zap.Int64("user_id", userID), zap.Float32("amount", amount.Float32()), zap.Int64("static wallet_id", wallet.StaticID), zap.Time("timestamp", time.Now()), zap.Error(err), ) return fiber.NewError(fiber.StatusBadRequest, "Failed to add reward to static bet:"+err.Error()) } 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, companyID int64) (domain.CreateBetRes, error) { valErrs, ok := h.validator.Validate(c, req) if !ok { h.mongoLoggerSvc.Error("CreateBet validation failed", zap.Int("status_code", fiber.StatusBadRequest), zap.Any("validation_errors", valErrs), zap.Time("timestamp", time.Now()), ) return domain.CreateBetRes{}, fmt.Errorf("%s", valErrs) } res, err := h.betSvc.PlaceBet(c.Context(), req, userID, role, companyID) if err != nil { switch err { case bet.ErrEventHasBeenRemoved, bet.ErrEventHasNotEnded, bet.ErrRawOddInvalid, bet.ErrTotalBalanceNotEnough: h.mongoLoggerSvc.Info("PlaceBet failed", zap.Int("status_code", fiber.StatusBadRequest), zap.Int64("userID", userID), zap.Int64("companyID", companyID), zap.String("role", string(role)), zap.Error(err), zap.Time("timestamp", time.Now()), ) return domain.CreateBetRes{}, err } h.mongoLoggerSvc.Error("PlaceBet failed", zap.Int("status_code", fiber.StatusInternalServerError), zap.Int64("userID", userID), zap.Int64("companyID", companyID), zap.String("role", string(role)), zap.Error(err), zap.Time("timestamp", time.Now()), ) return domain.CreateBetRes{}, err } // create raffle tickets raffles, err := h.raffleSvc.GetRafflesOfCompany(c.Context(), int32(companyID)) if err != nil { h.mongoLoggerSvc.Error("Failed to fetch raffle of company", zap.Int("status_code", fiber.StatusInternalServerError), zap.Int64("userID", userID), zap.Int64("companyID", companyID), zap.String("role", string(role)), zap.Error(err), zap.Time("timestamp", time.Now()), ) } sportAndLeagueIDs := [][]int64{} for _, outcome := range req.Outcomes { ids, err := h.eventSvc.GetSportAndLeagueIDs(c.Context(), outcome.EventID) if err != nil { continue } sportAndLeagueIDs = append(sportAndLeagueIDs, ids) } fmt.Println("sportAndLeagueIDs: ", sportAndLeagueIDs) for _, raffle := range raffles { // TODO: only fetch pending raffles from db if raffle.Status == "completed" { continue } // only require one sport and league combo to be valide to make the raffle ticket foundValid := false for _, sportAndLeagueID := range sportAndLeagueIDs { res, err := h.raffleSvc.CheckValidSportRaffleFilter(c.Context(), raffle.ID, sportAndLeagueID[0], sportAndLeagueID[1]) if err != nil { continue } fmt.Println(sportAndLeagueID, res) foundValid = foundValid || res } if !foundValid { continue } raffleTicket := domain.CreateRaffleTicket{ RaffleID: raffle.ID, UserID: int32(userID), } _, err := h.raffleSvc.CreateRaffleTicket(c.Context(), raffleTicket) if err != nil { h.mongoLoggerSvc.Error("Failed to create raffle ticket", zap.Int("status_code", fiber.StatusInternalServerError), zap.Int64("raffleID", int64(raffle.ID)), zap.Int64("userID", userID), zap.Int64("companyID", companyID), zap.String("role", string(role)), zap.Error(err), zap.Time("timestamp", time.Now()), ) } } return res, nil } // RandomBet godoc // @Summary Generate a random bet // @Description Generate a random bet // @Tags bet // @Accept json // @Produce json // @Param createBet body domain.RandomBetReq true "Create Random bet" // @Success 200 {object} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @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.ParseInt(c.Query("league_id"), 10, 64) if err != nil { h.mongoLoggerSvc.Info("invalid league id", zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "invalid league id") } sportIDQuery, err := strconv.Atoi(c.Query("sport_id")) if err != nil { h.mongoLoggerSvc.Info("invalid sport id", zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "invalid sport id") } firstStartTimeQuery := c.Query("first_start_time") lastStartTimeQuery := c.Query("last_start_time") leagueID := domain.ValidInt64{ Value: leagueIDQuery, Valid: leagueIDQuery != 0, } sportID := domain.ValidInt32{ Value: int32(sportIDQuery), Valid: sportIDQuery != 0, } var firstStartTime domain.ValidTime if firstStartTimeQuery != "" { firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery) if err != nil { h.mongoLoggerSvc.Info("invalid first_start_time format", zap.String("first_start_time", firstStartTimeQuery), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid first_start_time format") } firstStartTime = domain.ValidTime{ Value: firstStartTimeParsed, Valid: true, } } var lastStartTime domain.ValidTime if lastStartTimeQuery != "" { lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery) if err != nil { h.mongoLoggerSvc.Info("invalid last_start_time format", zap.String("last_start_time", lastStartTimeQuery), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid last_start_time format") } lastStartTime = domain.ValidTime{ Value: lastStartTimeParsed, Valid: true, } } var req domain.RandomBetReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Info("Failed to parse RandomBet request", zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error()) } 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.Error("RandomBet validation failed", zap.Int("status_code", fiber.StatusBadRequest), zap.Any("validation_errors", valErrs), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, errMsg) } var res domain.CreateBetRes for i := 0; i < int(req.NumberOfBets); i++ { res, err = h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID, companyID.Value, leagueID, sportID, firstStartTime, lastStartTime) if err != nil { switch err { case bet.ErrNoEventsAvailable: return fiber.NewError(fiber.StatusNotFound, "No events found") } h.mongoLoggerSvc.Error("Random Bet failed place random bet", zap.Int64("userID", userID), zap.Int64("branch_id", req.BranchID), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Unable to create random bet:"+err.Error()) } } h.mongoLoggerSvc.Info("Random bet(s) 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) } // GetAllBet godoc // @Summary Gets all bets // @Description Gets all the bets // @Tags bet // @Accept json // @Produce json // @Success 200 {array} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/{tenant_slug}/sport/bet [get] func (h *Handler) GetAllBet(c *fiber.Ctx) error { role := c.Locals("role").(domain.Role) page := c.QueryInt("page", 1) pageSize := c.QueryInt("page_size", 10) limit := domain.ValidInt32{ Value: int32(pageSize), Valid: true, } offset := domain.ValidInt32{ Value: int32(page - 1), Valid: true, } var isShopBet domain.ValidBool isShopBetQuery := c.Query("is_shop") if isShopBetQuery != "" && role == domain.RoleSuperAdmin { isShopBetParse, err := strconv.ParseBool(isShopBetQuery) if err != nil { h.mongoLoggerSvc.Info("failed to parse is_shop_bet", zap.Int("status_code", fiber.StatusBadRequest), zap.String("is_shop", isShopBetQuery), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "failed to parse is_shop_bet") } isShopBet = domain.ValidBool{ Value: isShopBetParse, Valid: true, } } searchQuery := c.Query("query") searchString := domain.ValidString{ Value: searchQuery, Valid: searchQuery != "", } createdBeforeQuery := c.Query("created_before") var createdBefore domain.ValidTime if createdBeforeQuery != "" { createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) if err != nil { h.mongoLoggerSvc.Info("invalid created_before format", zap.String("time", createdBeforeQuery), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid created_before format") } createdBefore = domain.ValidTime{ Value: createdBeforeParsed, Valid: true, } } createdAfterQuery := c.Query("created_after") var createdAfter domain.ValidTime if createdAfterQuery != "" { createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) if err != nil { h.mongoLoggerSvc.Info("invalid created_after format", zap.String("created_after", createdAfterQuery), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format") } createdAfter = domain.ValidTime{ Value: createdAfterParsed, Valid: true, } } var statusFilter domain.ValidOutcomeStatus statusQuery := c.Query("status") if statusQuery != "" { statusParsed, err := strconv.ParseInt(statusQuery, 10, 32) if err != nil { h.mongoLoggerSvc.Info("invalid status format", zap.String("status", statusQuery), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid status format") } statusFilter = domain.ValidOutcomeStatus{ Value: domain.OutcomeStatus(statusParsed), Valid: true, } } bets, total, err := h.betSvc.GetAllBets(c.Context(), domain.BetFilter{ IsShopBet: isShopBet, Query: searchString, CreatedBefore: createdBefore, CreatedAfter: createdAfter, Status: statusFilter, Limit: limit, Offset: offset, }) if err != nil { h.mongoLoggerSvc.Error("Failed to get all bets", zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets፡"+err.Error()) } res := make([]domain.BetRes, len(bets)) for i, bet := range bets { res[i] = domain.ConvertBet(bet) } return response.WritePaginatedJSON(c, fiber.StatusOK, "All bets retrieved successfully", res, nil, page, int(total)) } // GetAllTenants godoc // @Summary Gets all bets // @Description Gets all the bets // @Tags bet // @Accept json // @Produce json // @Success 200 {array} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/{tenant_slug}/sport/bet [get] func (h *Handler) GetAllTenantBets(c *fiber.Ctx) error { companyID := c.Locals("company_id").(domain.ValidInt64) if !companyID.Valid { h.BadRequestLogger().Error("invalid company id", zap.Any("company_id", companyID)) return fiber.NewError(fiber.StatusBadRequest, "invalid company id") } role := c.Locals("role").(domain.Role) page := c.QueryInt("page", 1) pageSize := c.QueryInt("page_size", 10) limit := domain.ValidInt32{ Value: int32(pageSize), Valid: true, } offset := domain.ValidInt32{ Value: int32(page - 1), Valid: true, } var isShopBet domain.ValidBool isShopBetQuery := c.Query("is_shop") if isShopBetQuery != "" && role == domain.RoleSuperAdmin { isShopBetParse, err := strconv.ParseBool(isShopBetQuery) if err != nil { h.mongoLoggerSvc.Info("failed to parse is_shop_bet", zap.Int("status_code", fiber.StatusBadRequest), zap.String("is_shop", isShopBetQuery), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "failed to parse is_shop_bet") } isShopBet = domain.ValidBool{ Value: isShopBetParse, Valid: true, } } searchQuery := c.Query("query") searchString := domain.ValidString{ Value: searchQuery, Valid: searchQuery != "", } createdBeforeQuery := c.Query("created_before") var createdBefore domain.ValidTime if createdBeforeQuery != "" { createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) if err != nil { h.mongoLoggerSvc.Info("invalid created_before format", zap.String("time", createdBeforeQuery), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid created_before format") } createdBefore = domain.ValidTime{ Value: createdBeforeParsed, Valid: true, } } createdAfterQuery := c.Query("created_after") var createdAfter domain.ValidTime if createdAfterQuery != "" { createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) if err != nil { h.mongoLoggerSvc.Info("invalid created_after format", zap.String("created_after", createdAfterQuery), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format") } createdAfter = domain.ValidTime{ Value: createdAfterParsed, Valid: true, } } var statusFilter domain.ValidOutcomeStatus statusQuery := c.Query("status") if statusQuery != "" { statusParsed, err := strconv.ParseInt(statusQuery, 10, 32) if err != nil { h.mongoLoggerSvc.Info("invalid status format", zap.String("status", statusQuery), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid status format") } statusFilter = domain.ValidOutcomeStatus{ Value: domain.OutcomeStatus(statusParsed), Valid: true, } } bets, total, err := h.betSvc.GetAllBets(c.Context(), domain.BetFilter{ CompanyID: companyID, IsShopBet: isShopBet, Query: searchString, CreatedBefore: createdBefore, CreatedAfter: createdAfter, Status: statusFilter, Limit: limit, Offset: offset, }) if err != nil { h.mongoLoggerSvc.Error("Failed to get all bets", zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets፡"+err.Error()) } res := make([]domain.BetRes, len(bets)) for i, bet := range bets { res[i] = domain.ConvertBet(bet) } return response.WritePaginatedJSON(c, fiber.StatusOK, "All bets retrieved successfully", res, nil, page, int(total)) } // GetBetByID godoc // @Summary Gets bet by id // @Description Gets a single bet by id // @Tags bet // @Accept json // @Produce json // @Param id path int true "Bet ID" // @Success 200 {object} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/sport/bet/{id} [get] func (h *Handler) GetBetByID(c *fiber.Ctx) error { betID := c.Params("id") id, err := strconv.ParseInt(betID, 10, 64) if err != nil { h.mongoLoggerSvc.Info("Invalid bet ID", zap.String("betID", betID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID") } bet, err := h.betSvc.GetBetByID(c.Context(), id) if err != nil { h.mongoLoggerSvc.Info("Failed to get bet by ID", 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", // zap.Int64("betID", id), // zap.Int("status_code", fiber.StatusOK), // zap.Time("timestamp", time.Now()), // ) return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil) } // GetTenantBetByID godoc // @Summary Gets bet by id // @Description Gets a single bet by id // @Tags bet // @Accept json // @Produce json // @Param id path int true "Bet ID" // @Success 200 {object} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/{tenant_slug}/sport/bet/{id} [get] func (h *Handler) GetTenantBetByID(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 { h.mongoLoggerSvc.Info("Invalid bet ID", zap.String("betID", betID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID") } bet, err := h.betSvc.GetBetByID(c.Context(), id) if err != nil { h.mongoLoggerSvc.Info("Failed to get bet by ID", 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") } res := domain.ConvertBet(bet) // h.mongoLoggerSvc.Info("Bet retrieved successfully", // zap.Int64("betID", id), // zap.Int("status_code", fiber.StatusOK), // zap.Time("timestamp", time.Now()), // ) return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil) } // GetBetByFastCode godoc // @Summary Gets bet by fast_code // @Description Gets a single bet by fast_code // @Tags bet // @Accept json // @Produce json // @Param fast_code path int true "Bet ID" // @Success 200 {object} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @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) if err != nil { h.mongoLoggerSvc.Info("Failed to get bet by fast code", 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 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", // zap.Int64("betID", id), // zap.Int("status_code", fiber.StatusOK), // zap.Time("timestamp", time.Now()), // ) return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil) } type UpdateCashOutReq struct { CashedOut bool } // UpdateCashOut godoc // @Summary Updates the cashed out field // @Description Updates the cashed out field // @Tags bet // @Accept json // @Produce json // @Param id path int true "Bet ID" // @Param updateCashOut body UpdateCashOutReq true "Updates Cashed Out" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @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"` } betID := c.Params("id") id, err := strconv.ParseInt(betID, 10, 64) if err != nil { h.mongoLoggerSvc.Error("Invalid bet ID", zap.String("betID", betID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID") } var req UpdateCashOutReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Error("Failed to parse UpdateCashOut request", zap.Int64("betID", id), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "failed to parse request body:"+err.Error()) } if valErrs, ok := h.validator.Validate(c, req); !ok { var errMsg string for field, msg := range valErrs { errMsg += fmt.Sprintf("%s: %s; ", field, msg) } 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", zap.Int64("betID", id), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to update cash out bet") } h.mongoLoggerSvc.Info("Bet updated successfully", zap.Int64("betID", id), zap.Int("status_code", fiber.StatusOK), zap.Time("timestamp", time.Now()), ) return response.WriteJSON(c, fiber.StatusOK, "Bet updated successfully", nil, nil) } // DeleteBet godoc // @Summary Deletes bet by id // @Description Deletes bet by id // @Tags bet // @Accept json // @Produce json // @Param id path int true "Bet ID" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/sport/bet/{id} [delete] func (h *Handler) DeleteBet(c *fiber.Ctx) error { betID := c.Params("id") id, err := strconv.ParseInt(betID, 10, 64) if err != nil { h.mongoLoggerSvc.Error("Invalid bet ID", zap.String("betID", betID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID") } err = h.betSvc.SetBetToRemoved(c.Context(), id) if err != nil { h.mongoLoggerSvc.Error("Failed to delete bet by ID", zap.Int64("betID", id), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete bet:"+err.Error()) } h.mongoLoggerSvc.Info("Bet removed successfully", zap.Int64("betID", id), zap.Int("status_code", fiber.StatusOK), zap.Time("timestamp", time.Now()), ) return response.WriteJSON(c, fiber.StatusOK, "Bet removed successfully", nil, nil) } // DeleteTenantBet godoc // @Summary Deletes bet by id // @Description Deletes bet by id // @Tags bet // @Accept json // @Produce json // @Param id path int true "Bet ID" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/{tenant_slug}/sport/bet/{id} [delete] func (h *Handler) DeleteTenantBet(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 { h.mongoLoggerSvc.Error("Invalid bet ID", zap.String("betID", betID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) 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", zap.Int64("betID", id), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete bet:"+err.Error()) } h.mongoLoggerSvc.Info("Bet removed successfully", zap.Int64("betID", id), zap.Int("status_code", fiber.StatusOK), zap.Time("timestamp", time.Now()), ) return response.WriteJSON(c, fiber.StatusOK, "Bet removed successfully", nil, nil) }