435 lines
14 KiB
Go
435 lines
14 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type CreateBetOutcomeReq struct {
|
|
EventID int64 `json:"event_id" example:"1"`
|
|
OddID int64 `json:"odd_id" example:"1"`
|
|
MarketID int64 `json:"market_id" example:"1"`
|
|
}
|
|
|
|
type CreateBetReq struct {
|
|
Outcomes []CreateBetOutcomeReq `json:"outcomes"`
|
|
Amount float32 `json:"amount" example:"100.0"`
|
|
TotalOdds float32 `json:"total_odds" example:"4.22"`
|
|
Status domain.OutcomeStatus `json:"status" example:"1"`
|
|
FullName string `json:"full_name" example:"John"`
|
|
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
|
IsShopBet bool `json:"is_shop_bet" example:"false"`
|
|
}
|
|
|
|
type CreateBetRes struct {
|
|
ID int64 `json:"id" example:"1"`
|
|
Amount float32 `json:"amount" example:"100.0"`
|
|
TotalOdds float32 `json:"total_odds" example:"4.22"`
|
|
Status domain.OutcomeStatus `json:"status" example:"1"`
|
|
FullName string `json:"full_name" example:"John"`
|
|
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
|
BranchID int64 `json:"branch_id" example:"2"`
|
|
UserID int64 `json:"user_id" example:"2"`
|
|
IsShopBet bool `json:"is_shop_bet" example:"false"`
|
|
CreatedNumber int64 `json:"created_number" example:"2"`
|
|
CashedID string `json:"cashed_id" example:"21234"`
|
|
}
|
|
type BetRes struct {
|
|
ID int64 `json:"id" example:"1"`
|
|
Outcomes []domain.BetOutcome `json:"outcomes"`
|
|
Amount float32 `json:"amount" example:"100.0"`
|
|
TotalOdds float32 `json:"total_odds" example:"4.22"`
|
|
Status domain.OutcomeStatus `json:"status" example:"1"`
|
|
FullName string `json:"full_name" example:"John"`
|
|
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
|
BranchID int64 `json:"branch_id" example:"2"`
|
|
UserID int64 `json:"user_id" example:"2"`
|
|
IsShopBet bool `json:"is_shop_bet" example:"false"`
|
|
CashedOut bool `json:"cashed_out" example:"false"`
|
|
CashedID string `json:"cashed_id" example:"21234"`
|
|
}
|
|
|
|
func convertCreateBet(bet domain.Bet, createdNumber int64) CreateBetRes {
|
|
return CreateBetRes{
|
|
ID: bet.ID,
|
|
Amount: bet.Amount.Float64(),
|
|
TotalOdds: bet.TotalOdds,
|
|
Status: bet.Status,
|
|
FullName: bet.FullName,
|
|
PhoneNumber: bet.PhoneNumber,
|
|
BranchID: bet.BranchID.Value,
|
|
UserID: bet.UserID.Value,
|
|
CreatedNumber: createdNumber,
|
|
CashedID: bet.CashoutID,
|
|
}
|
|
}
|
|
|
|
func convertBet(bet domain.GetBet) BetRes {
|
|
return BetRes{
|
|
ID: bet.ID,
|
|
Amount: bet.Amount.Float64(),
|
|
TotalOdds: bet.TotalOdds,
|
|
Status: bet.Status,
|
|
FullName: bet.FullName,
|
|
PhoneNumber: bet.PhoneNumber,
|
|
BranchID: bet.BranchID.Value,
|
|
UserID: bet.UserID.Value,
|
|
Outcomes: bet.Outcomes,
|
|
IsShopBet: bet.IsShopBet,
|
|
CashedOut: bet.CashedOut,
|
|
CashedID: bet.CashoutID,
|
|
}
|
|
}
|
|
|
|
// CreateBet godoc
|
|
// @Summary Create a bet
|
|
// @Description Creates a bet
|
|
// @Tags bet
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param createBet body CreateBetReq true "Creates bet"
|
|
// @Success 200 {object} BetRes
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /bet [post]
|
|
func (h *Handler) CreateBet(c *fiber.Ctx) error {
|
|
|
|
// Get user_id from middleware
|
|
userID := c.Locals("user_id").(int64)
|
|
|
|
var req CreateBetReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.logger.Error("Failed to parse CreateBet request", "error", err)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
|
}
|
|
|
|
valErrs, ok := h.validator.Validate(c, req)
|
|
if !ok {
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
|
}
|
|
|
|
// Validating user by role
|
|
// Differentiating between offline and online bets
|
|
user, err := h.userSvc.GetUserByID(c.Context(), userID)
|
|
cashoutUUID := uuid.New()
|
|
var bet domain.Bet
|
|
if user.Role == domain.RoleCashier {
|
|
|
|
// Get the branch from the branch ID
|
|
branch, err := h.branchSvc.GetBranchByCashier(c.Context(), user.ID)
|
|
if err != nil {
|
|
h.logger.Error("CreateBetReq failed, branch id invalid")
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
|
|
}
|
|
|
|
// Deduct a percentage of the amount
|
|
// TODO move to service layer. Make it fetch dynamically from company
|
|
var deductedAmount = req.Amount / 10
|
|
err = h.walletSvc.DeductFromWallet(c.Context(), branch.WalletID, domain.ToCurrency(deductedAmount))
|
|
|
|
if err != nil {
|
|
h.logger.Error("CreateBetReq failed, unable to deduct from WalletID")
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
|
|
}
|
|
|
|
bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{
|
|
Amount: domain.ToCurrency(req.Amount),
|
|
TotalOdds: req.TotalOdds,
|
|
Status: req.Status,
|
|
FullName: req.FullName,
|
|
PhoneNumber: req.PhoneNumber,
|
|
|
|
BranchID: domain.ValidInt64{
|
|
Value: branch.ID,
|
|
Valid: true,
|
|
},
|
|
UserID: domain.ValidInt64{
|
|
Value: userID,
|
|
Valid: false,
|
|
},
|
|
IsShopBet: req.IsShopBet,
|
|
CashoutID: cashoutUUID.String(),
|
|
})
|
|
} else {
|
|
// TODO if user is customer, get id from the token then get the wallet id from there and reduce the amount
|
|
bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{
|
|
Amount: domain.ToCurrency(req.Amount),
|
|
TotalOdds: req.TotalOdds,
|
|
Status: req.Status,
|
|
FullName: req.FullName,
|
|
PhoneNumber: req.PhoneNumber,
|
|
|
|
BranchID: domain.ValidInt64{
|
|
Value: 0,
|
|
Valid: false,
|
|
},
|
|
UserID: domain.ValidInt64{
|
|
Value: userID,
|
|
Valid: true,
|
|
},
|
|
IsShopBet: req.IsShopBet,
|
|
CashoutID: cashoutUUID.String(),
|
|
})
|
|
}
|
|
|
|
if err != nil {
|
|
h.logger.Error("CreateBetReq failed", "error", err)
|
|
return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil)
|
|
}
|
|
|
|
//
|
|
// TODO Validate Outcomes Here and make sure they didn't expire
|
|
// Validation for creating tickets
|
|
if len(req.Outcomes) > 30 {
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil)
|
|
}
|
|
var outcomes []domain.CreateBetOutcome = make([]domain.CreateBetOutcome, 0, len(req.Outcomes))
|
|
for _, outcome := range req.Outcomes {
|
|
eventIDStr := strconv.FormatInt(outcome.EventID, 10)
|
|
marketIDStr := strconv.FormatInt(outcome.MarketID, 10)
|
|
oddIDStr := strconv.FormatInt(outcome.OddID, 10)
|
|
event, err := h.eventSvc.GetUpcomingEventByID(c.Context(), eventIDStr)
|
|
if err != nil {
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid event id", err, nil)
|
|
}
|
|
|
|
// Checking to make sure the event hasn't already started
|
|
currentTime := time.Now()
|
|
if event.StartTime.Before(currentTime) {
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil)
|
|
}
|
|
|
|
odds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr)
|
|
|
|
if err != nil {
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil)
|
|
}
|
|
type rawOddType struct {
|
|
ID string
|
|
Name string
|
|
Odds string
|
|
Header string
|
|
Handicap string
|
|
}
|
|
var selectedOdd rawOddType
|
|
var isOddFound bool = false
|
|
for _, raw := range odds.RawOdds {
|
|
var rawOdd rawOddType
|
|
rawBytes, err := json.Marshal(raw)
|
|
err = json.Unmarshal(rawBytes, &rawOdd)
|
|
if err != nil {
|
|
h.logger.Error("Failed to unmarshal raw odd:", err)
|
|
continue
|
|
}
|
|
if rawOdd.ID == oddIDStr {
|
|
selectedOdd = rawOdd
|
|
isOddFound = true
|
|
}
|
|
}
|
|
|
|
if !isOddFound {
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil)
|
|
}
|
|
|
|
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
|
|
|
|
outcomes = append(outcomes, domain.CreateBetOutcome{
|
|
BetID: bet.ID,
|
|
EventID: outcome.EventID,
|
|
OddID: outcome.OddID,
|
|
MarketID: outcome.MarketID,
|
|
HomeTeamName: event.HomeTeam,
|
|
AwayTeamName: event.AwayTeam,
|
|
MarketName: odds.MarketName,
|
|
Odd: float32(parsedOdd),
|
|
OddName: selectedOdd.Name,
|
|
OddHeader: selectedOdd.Header,
|
|
OddHandicap: selectedOdd.Handicap,
|
|
Expires: event.StartTime,
|
|
})
|
|
}
|
|
|
|
rows, err := h.betSvc.CreateBetOutcome(c.Context(), outcomes)
|
|
|
|
if err != nil {
|
|
h.logger.Error("CreateBetReq failed to create outcomes", "error", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": "Internal server error",
|
|
})
|
|
}
|
|
|
|
res := convertCreateBet(bet, rows)
|
|
|
|
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} BetRes
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /bet [get]
|
|
func (h *Handler) GetAllBet(c *fiber.Ctx) error {
|
|
bets, err := h.betSvc.GetAllBets(c.Context())
|
|
if err != nil {
|
|
h.logger.Error("Failed to get bets", "error", err)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets")
|
|
}
|
|
|
|
res := make([]BetRes, len(bets))
|
|
for i, bet := range bets {
|
|
res[i] = convertBet(bet)
|
|
}
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "All bets retrieved successfully", res, nil)
|
|
}
|
|
|
|
// 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} BetRes
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /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.logger.Error("Invalid bet ID", "betID", betID, "error", err)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID")
|
|
}
|
|
|
|
bet, err := h.betSvc.GetBetByID(c.Context(), id)
|
|
if err != nil {
|
|
h.logger.Error("Failed to get bet by ID", "betID", id, "error", err)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bet")
|
|
}
|
|
|
|
res := convertBet(bet)
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
|
|
|
|
}
|
|
|
|
// GetBetByCashoutID godoc
|
|
// @Summary Gets bet by cashout id
|
|
// @Description Gets a single bet by cashout id
|
|
// @Tags bet
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "cashout ID"
|
|
// @Success 200 {object} BetRes
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /bet/cashout/{id} [get]
|
|
func (h *Handler) GetBetByCashoutID(c *fiber.Ctx) error {
|
|
cashoutID := c.Params("id")
|
|
// id, err := strconv.ParseInt(cashoutID, 10, 64)
|
|
|
|
// if err != nil {
|
|
// logger.Error("Invalid cashout ID", "cashoutID", cashoutID, "error", err)
|
|
// return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid cashout ID", err, nil)
|
|
// }
|
|
|
|
bet, err := h.betSvc.GetBetByCashoutID(c.Context(), cashoutID)
|
|
if err != nil {
|
|
h.logger.Error("Failed to get bet by ID", "cashoutID", cashoutID, "error", err)
|
|
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bet", err, nil)
|
|
}
|
|
|
|
res := convertBet(bet)
|
|
|
|
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 /bet/{id} [patch]
|
|
func (h *Handler) UpdateCashOut(c *fiber.Ctx) error {
|
|
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.logger.Error("Invalid bet ID", "betID", betID, "error", err)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID")
|
|
}
|
|
|
|
var req UpdateCashOutReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.logger.Error("Failed to parse UpdateCashOut request", "error", err)
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request body", err, nil)
|
|
}
|
|
|
|
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
|
}
|
|
|
|
err = h.betSvc.UpdateCashOut(c.Context(), id, req.CashedOut)
|
|
if err != nil {
|
|
h.logger.Error("Failed to update cash out bet", "betID", id, "error", err)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update cash out bet")
|
|
}
|
|
|
|
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 /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.logger.Error("Invalid bet ID", "betID", betID, "error", err)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid bet ID")
|
|
}
|
|
|
|
err = h.betSvc.DeleteBet(c.Context(), id)
|
|
if err != nil {
|
|
h.logger.Error("Failed to delete bet by ID", "betID", id, "error", err)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete bet")
|
|
}
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "Bet removed successfully", nil, nil)
|
|
}
|