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" ) type CreateTicketOutcomeReq struct { // TicketID int64 `json:"ticket_id" example:"1"` EventID int64 `json:"event_id" example:"1"` OddID int64 `json:"odd_id" example:"1"` MarketID int64 `json:"market_id" example:"1"` // HomeTeamName string `json:"home_team_name" example:"Manchester"` // AwayTeamName string `json:"away_team_name" example:"Liverpool"` // MarketName string `json:"market_name" example:"Fulltime Result"` // Odd float32 `json:"odd" example:"1.5"` // OddName string `json:"odd_name" example:"1"` // Expires time.Time `json:"expires" example:"2025-04-08T12:00:00Z"` } type CreateTicketReq struct { Outcomes []CreateTicketOutcomeReq `json:"outcomes"` Amount float32 `json:"amount" example:"100.0"` } type CreateTicketRes struct { FastCode int64 `json:"fast_code" example:"1234"` CreatedNumber int64 `json:"created_number" example:"3"` } type TicketRes struct { ID int64 `json:"id" example:"1"` Outcomes []domain.TicketOutcome `json:"outcomes"` Amount float32 `json:"amount" example:"100.0"` TotalOdds float32 `json:"total_odds" example:"4.22"` } // CreateTicket godoc // @Summary Create a temporary ticket // @Description Creates a temporary ticket // @Tags ticket // @Accept json // @Produce json // @Param createTicket body CreateTicketReq true "Creates ticket" // @Success 200 {object} CreateTicketRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /ticket [post] func (h *Handler) CreateTicket(c *fiber.Ctx) error { var req CreateTicketReq if err := c.BodyParser(&req); err != nil { h.logger.Error("Failed to parse CreateTicket request", "error", err) return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } if valErrs, ok := h.validator.Validate(c, req); !ok { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, 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) } if req.Amount > 100000 { return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with an amount above 100,000 birr", nil, nil) } clientIP := c.IP() count, err := h.ticketSvc.CountTicketByIP(c.Context(), clientIP) if err != nil { return response.WriteJSON(c, fiber.StatusInternalServerError, "Error fetching user info", nil, nil) } if count > 50 { return response.WriteJSON(c, fiber.StatusBadRequest, "Ticket Limit reached", nil, nil) } var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes)) var totalOdds float32 = 1 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:", "error", 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) totalOdds = totalOdds * float32(parsedOdd) outcomes = append(outcomes, domain.CreateTicketOutcome{ 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, }) } totalWinnings := req.Amount * totalOdds if totalWinnings > 1000000 { return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with 1,000,000 winnings", nil, nil) } ticket, err := h.ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{ Amount: domain.ToCurrency(req.Amount), TotalOdds: totalOdds, IP: clientIP, }) if err != nil { h.logger.Error("CreateTicketReq failed", "error", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Internal server error", }) } // Add the ticket id now that it has fetched from the database for index := range outcomes { outcomes[index].TicketID = ticket.ID } rows, err := h.ticketSvc.CreateTicketOutcome(c.Context(), outcomes) if err != nil { h.logger.Error("CreateTicketReq failed to create outcomes", "error", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Internal server error", }) } res := CreateTicketRes{ FastCode: ticket.ID, CreatedNumber: rows, } return response.WriteJSON(c, fiber.StatusOK, "Ticket Created", res, nil) } // GetTicketByID godoc // @Summary Get ticket by ID // @Description Retrieve ticket details by ticket ID // @Tags ticket // @Accept json // @Produce json // @Param id path int true "Ticket ID" // @Success 200 {object} TicketRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /ticket/{id} [get] func (h *Handler) GetTicketByID(c *fiber.Ctx) error { ticketID := c.Params("id") id, err := strconv.ParseInt(ticketID, 10, 64) if err != nil { h.logger.Error("Invalid ticket ID", "ticketID", ticketID, "error", err) return fiber.NewError(fiber.StatusBadRequest, "Invalid ticket ID") } ticket, err := h.ticketSvc.GetTicketByID(c.Context(), id) if err != nil { h.logger.Error("Failed to get ticket by ID", "ticketID", id, "error", err) return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve ticket") } res := TicketRes{ ID: ticket.ID, Outcomes: ticket.Outcomes, Amount: ticket.Amount.Float32(), TotalOdds: ticket.TotalOdds, } return response.WriteJSON(c, fiber.StatusOK, "Ticket retrieved successfully", res, nil) } // GetAllTickets godoc // @Summary Get all tickets // @Description Retrieve all tickets // @Tags ticket // @Accept json // @Produce json // @Success 200 {array} TicketRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /ticket [get] func (h *Handler) GetAllTickets(c *fiber.Ctx) error { tickets, err := h.ticketSvc.GetAllTickets(c.Context()) if err != nil { h.logger.Error("Failed to get tickets", "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve tickets") } res := make([]TicketRes, len(tickets)) for i, ticket := range tickets { res[i] = TicketRes{ ID: ticket.ID, Outcomes: ticket.Outcomes, Amount: ticket.Amount.Float32(), TotalOdds: ticket.TotalOdds, } } return response.WriteJSON(c, fiber.StatusOK, "All tickets retrieved successfully", res, nil) }