453 lines
14 KiB
Go
453 lines
14 KiB
Go
package handlers
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"log"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||
"github.com/gofiber/fiber/v2"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// GetAtlasVGames godoc
|
||
// @Summary List Atlas virtual games
|
||
// @Description Retrieves available Atlas virtual games from the provider
|
||
// @Tags Virtual Games - Atlas
|
||
// @Produce json
|
||
// @Success 200 {object} domain.Response{data=[]domain.AtlasGameEntity}
|
||
// @Failure 502 {object} domain.ErrorResponse
|
||
// @Router /api/v1/atlas/games [get]
|
||
func (h *Handler) GetAtlasVGames(c *fiber.Ctx) error {
|
||
// Call the service
|
||
games, err := h.veliVirtualGameSvc.GetAtlasVGames(c.Context())
|
||
if err != nil {
|
||
log.Println("GetAtlasVGames error:", err)
|
||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||
Message: "Failed to fetch Atlas virtual games",
|
||
Error: err.Error(),
|
||
})
|
||
}
|
||
|
||
// Return the list of games
|
||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||
Message: "Atlas virtual games retrieved successfully",
|
||
Data: games,
|
||
StatusCode: fiber.StatusOK,
|
||
Success: true,
|
||
})
|
||
}
|
||
|
||
// InitAtlasGame godoc
|
||
// @Summary Start an Atlas virtual game session
|
||
// @Description Initializes a game session for the given player using Atlas virtual game provider
|
||
// @Tags Virtual Games - Atlas
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param request body domain.AtlasGameInitRequest true "Start game input"
|
||
// @Success 200 {object} domain.Response{data=domain.AtlasGameInitResponse}
|
||
// @Failure 400 {object} domain.ErrorResponse
|
||
// @Failure 502 {object} domain.ErrorResponse
|
||
// @Router /api/v1/atlas/init-game [post]
|
||
func (h *Handler) InitAtlasGame(c *fiber.Ctx) error {
|
||
// 1️⃣ Retrieve user ID from context
|
||
userId, ok := c.Locals("user_id").(int64)
|
||
if !ok {
|
||
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||
Error: "missing user id",
|
||
Message: "Unauthorized",
|
||
})
|
||
}
|
||
|
||
// 2️⃣ Parse request body
|
||
var req domain.AtlasGameInitRequest
|
||
if err := c.BodyParser(&req); err != nil {
|
||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||
Message: "Invalid request body",
|
||
Error: err.Error(),
|
||
})
|
||
}
|
||
|
||
// 3️⃣ Attach user ID to request
|
||
req.PlayerID = fmt.Sprintf("%d", userId)
|
||
|
||
// 4️⃣ Set defaults if not provided
|
||
if req.Language == "" {
|
||
req.Language = "en"
|
||
}
|
||
if req.Currency == "" {
|
||
req.Currency = "USD"
|
||
}
|
||
|
||
// 5️⃣ Call the Atlas service
|
||
res, err := h.atlasVirtualGameSvc.InitGame(context.Background(), req)
|
||
if err != nil {
|
||
log.Println("InitAtlasGame error:", err)
|
||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||
Message: "Failed to initialize Atlas game",
|
||
Error: err.Error(),
|
||
})
|
||
}
|
||
|
||
// 6️⃣ Update provider report: increment total_games_played
|
||
go func() {
|
||
ctx := context.Background()
|
||
reportDate := time.Now().Truncate(24 * time.Hour)
|
||
reportType := "daily"
|
||
providerID := "atlas" // all Atlas games belong to this provider
|
||
|
||
err := h.orchestrationSvc.UpdateVirtualGameProviderReportByDate(
|
||
ctx,
|
||
providerID,
|
||
reportDate,
|
||
reportType,
|
||
1, // increment total_games_played by 1
|
||
0, // total_bets (no change)
|
||
0, // total_payouts (no change)
|
||
1, // total_players (no change)
|
||
)
|
||
if err != nil {
|
||
h.InternalServerErrorLogger().Error("Failed to update total_games_played for Atlas game",
|
||
zap.String("provider_id", providerID),
|
||
zap.Error(err),
|
||
)
|
||
}
|
||
}()
|
||
|
||
// 7️⃣ Return response to user
|
||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||
Message: "Game initialized successfully",
|
||
Data: res,
|
||
StatusCode: fiber.StatusOK,
|
||
Success: true,
|
||
})
|
||
}
|
||
|
||
// AtlasGetUserDataCallback godoc
|
||
// @Summary Atlas Get User Data callback
|
||
// @Description Callback endpoint for Atlas game server to fetch player balance
|
||
// @Tags Virtual Games - Atlas
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param request body domain.AtlasGetUserDataRequest true "Get user data input"
|
||
// @Success 200 {object} domain.AtlasGetUserDataResponse
|
||
// @Failure 400 {object} domain.ErrorResponse
|
||
// @Failure 502 {object} domain.ErrorResponse
|
||
// @Router /account [post]
|
||
func (h *Handler) AtlasGetUserDataCallback(c *fiber.Ctx) error {
|
||
var req domain.AtlasGetUserDataRequest
|
||
if err := c.BodyParser(&req); err != nil {
|
||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||
Message: "Invalid request body",
|
||
Error: err.Error(),
|
||
})
|
||
}
|
||
|
||
// Optional: validate casino_id matches your configured Atlas casino
|
||
if req.CasinoID != h.Cfg.Atlas.CasinoID {
|
||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||
Message: "Invalid casino_id",
|
||
Error: "unauthorized request",
|
||
})
|
||
}
|
||
|
||
// Call service to get player data
|
||
res, err := h.atlasVirtualGameSvc.GetUserData(c.Context(), req)
|
||
if err != nil {
|
||
log.Println("AtlasGetUserDataCallback error:", err)
|
||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||
Message: "Failed to fetch user data",
|
||
Error: err.Error(),
|
||
})
|
||
}
|
||
|
||
// Return Atlas expected response
|
||
return c.JSON(res)
|
||
}
|
||
|
||
// HandleAtlasBetWin godoc
|
||
// @Summary Atlas BetWin callback
|
||
// @Description Processes a Bet and Win request from Atlas provider
|
||
// @Tags Virtual Games - Atlas
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param request body domain.AtlasBetWinRequest true "Atlas BetWin input"
|
||
// @Success 200 {object} domain.AtlasBetWinResponse
|
||
// @Failure 400 {object} domain.ErrorResponse
|
||
// @Failure 502 {object} domain.ErrorResponse
|
||
// @Router /betwin [post]
|
||
func (h *Handler) HandleAtlasBetWin(c *fiber.Ctx) error {
|
||
body := c.Body()
|
||
if len(body) == 0 {
|
||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||
Message: "Empty request body",
|
||
Error: "Request body cannot be empty",
|
||
})
|
||
}
|
||
|
||
var req domain.AtlasBetWinRequest
|
||
if err := json.Unmarshal(body, &req); err != nil {
|
||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||
Message: "Invalid Atlas BetWin request",
|
||
Error: err.Error(),
|
||
})
|
||
}
|
||
|
||
res, err := h.atlasVirtualGameSvc.ProcessBetWin(c.Context(), req)
|
||
if err != nil {
|
||
// Handle known errors specifically
|
||
code := fiber.StatusInternalServerError
|
||
errMsg := err.Error()
|
||
|
||
switch {
|
||
case errors.Is(err, domain.ErrInsufficientBalance):
|
||
code = fiber.StatusBadRequest
|
||
errMsg = "INSUFFICIENT_BALANCE"
|
||
case strings.Contains(err.Error(), "invalid casino_id"):
|
||
code = fiber.StatusBadRequest
|
||
case strings.Contains(err.Error(), "invalid playerID"):
|
||
code = fiber.StatusBadRequest
|
||
}
|
||
|
||
return c.Status(code).JSON(domain.ErrorResponse{
|
||
Message: "Failed to process Atlas BetWin",
|
||
Error: errMsg,
|
||
})
|
||
}
|
||
|
||
return c.Status(fiber.StatusOK).JSON(res)
|
||
}
|
||
|
||
// HandleRoundResult godoc
|
||
// @Summary Atlas Round Result callback
|
||
// @Description Processes a round result from Atlas or other providers
|
||
// @Tags Virtual Games - Atlas
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param request body domain.RoundResultRequest true "Round result input"
|
||
// @Success 200 {object} domain.RoundResultResponse
|
||
// @Failure 400 {object} domain.ErrorResponse
|
||
// @Failure 502 {object} domain.ErrorResponse
|
||
// @Router /result [post]
|
||
func (h *Handler) HandleRoundResult(c *fiber.Ctx) error {
|
||
body := c.Body()
|
||
if len(body) == 0 {
|
||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||
Message: "Empty request body",
|
||
Error: "Request body cannot be empty",
|
||
})
|
||
}
|
||
|
||
var req domain.RoundResultRequest
|
||
if err := json.Unmarshal(body, &req); err != nil {
|
||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||
Message: "Invalid RoundResult request",
|
||
Error: err.Error(),
|
||
})
|
||
}
|
||
|
||
res, err := h.atlasVirtualGameSvc.ProcessRoundResult(c.Context(), req)
|
||
if err != nil {
|
||
code := fiber.StatusInternalServerError
|
||
errMsg := err.Error()
|
||
|
||
// Validation errors
|
||
if strings.Contains(err.Error(), "missing player_id") || strings.Contains(err.Error(), "missing transaction_id") {
|
||
code = fiber.StatusBadRequest
|
||
}
|
||
|
||
return c.Status(code).JSON(domain.ErrorResponse{
|
||
Message: "Failed to process round result",
|
||
Error: errMsg,
|
||
})
|
||
}
|
||
|
||
return c.Status(fiber.StatusOK).JSON(res)
|
||
}
|
||
|
||
// HandleRollback godoc
|
||
// @Summary Atlas Rollback callback
|
||
// @Description Processes a rollback request from Atlas or other providers
|
||
// @Tags Virtual Games - Atlas
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param request body domain.RollbackRequest true "Rollback request input"
|
||
// @Success 200 {object} domain.RollbackResponse
|
||
// @Failure 400 {object} domain.ErrorResponse
|
||
// @Failure 502 {object} domain.ErrorResponse
|
||
// @Router /rollback [post]
|
||
func (h *Handler) HandleRollback(c *fiber.Ctx) error {
|
||
body := c.Body()
|
||
if len(body) == 0 {
|
||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||
Message: "Empty request body",
|
||
Error: "Request body cannot be empty",
|
||
})
|
||
}
|
||
|
||
var req domain.RollbackRequest
|
||
if err := json.Unmarshal(body, &req); err != nil {
|
||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||
Message: "Invalid Rollback request",
|
||
Error: err.Error(),
|
||
})
|
||
}
|
||
|
||
res, err := h.atlasVirtualGameSvc.ProcessRollBack(c.Context(), req)
|
||
if err != nil {
|
||
code := fiber.StatusInternalServerError
|
||
errMsg := err.Error()
|
||
|
||
// Validation errors
|
||
if strings.Contains(err.Error(), "missing player_id") || strings.Contains(err.Error(), "missing transaction_id") {
|
||
code = fiber.StatusBadRequest
|
||
}
|
||
|
||
return c.Status(code).JSON(domain.ErrorResponse{
|
||
Message: "Failed to process rollback",
|
||
Error: errMsg,
|
||
})
|
||
}
|
||
|
||
return c.Status(fiber.StatusOK).JSON(res)
|
||
}
|
||
|
||
// CreateFreeSpin godoc
|
||
// @Summary Create free spins for a player
|
||
// @Description Sends a request to Atlas to create free spins/bets for a given player
|
||
// @Tags Virtual Games - Atlas
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param request body domain.FreeSpinRequest true "Free spin input"
|
||
// @Success 200 {object} domain.Response{data=domain.FreeSpinResponse}
|
||
// @Failure 400 {object} domain.ErrorResponse
|
||
// @Failure 502 {object} domain.ErrorResponse
|
||
// @Router /api/v1/atlas/freespin [post]
|
||
func (h *Handler) CreateFreeSpin(c *fiber.Ctx) error {
|
||
// Get the authenticated user ID
|
||
userId, ok := c.Locals("user_id").(int64)
|
||
if !ok {
|
||
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||
Error: "missing user id",
|
||
Message: "Unauthorized",
|
||
})
|
||
}
|
||
|
||
var req domain.FreeSpinRequest
|
||
if err := c.BodyParser(&req); err != nil {
|
||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||
Message: "Invalid request body",
|
||
Error: err.Error(),
|
||
})
|
||
}
|
||
|
||
// Attach player ID from authenticated user
|
||
req.PlayerID = fmt.Sprintf("%d", userId)
|
||
|
||
res, err := h.atlasVirtualGameSvc.CreateFreeSpin(c.Context(), req)
|
||
if err != nil {
|
||
log.Println("CreateFreeSpin error:", err)
|
||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||
Message: "Failed to create free spins",
|
||
Error: err.Error(),
|
||
})
|
||
}
|
||
|
||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||
Message: "Free spins created successfully",
|
||
Data: res,
|
||
StatusCode: fiber.StatusOK,
|
||
Success: true,
|
||
})
|
||
}
|
||
|
||
// FreeSpinResultCallback godoc
|
||
// @Summary Free Spin/Bet result callback
|
||
// @Description Handles the result of a free spin/bet from the game server
|
||
// @Tags Virtual Games - Atlas
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param request body domain.FreeSpinResultRequest true "Free spin result input"
|
||
// @Success 200 {object} domain.FreeSpinResultResponse
|
||
// @Failure 400 {object} domain.ErrorResponse
|
||
// @Failure 502 {object} domain.ErrorResponse
|
||
// @Router /freespin [post]
|
||
func (h *Handler) FreeSpinResultCallback(c *fiber.Ctx) error {
|
||
// Read raw request body
|
||
body := c.Body()
|
||
if len(body) == 0 {
|
||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||
Message: "Empty request body",
|
||
Error: "Request body cannot be empty",
|
||
})
|
||
}
|
||
|
||
// Unmarshal into FreeSpinResultRequest
|
||
var req domain.FreeSpinResultRequest
|
||
if err := json.Unmarshal(body, &req); err != nil {
|
||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||
Message: "Invalid free spin result request",
|
||
Error: err.Error(),
|
||
})
|
||
}
|
||
|
||
// Process the free spin result
|
||
res, err := h.atlasVirtualGameSvc.ProcessFreeSpinResult(c.Context(), req)
|
||
if err != nil {
|
||
log.Println("FreeSpinResultCallback error:", err)
|
||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||
Message: "Failed to process free spin result",
|
||
Error: err.Error(),
|
||
})
|
||
}
|
||
|
||
return c.Status(fiber.StatusOK).JSON(res)
|
||
}
|
||
|
||
// JackpotCallback godoc
|
||
// @Summary Jackpot result callback
|
||
// @Description Handles the jackpot result from the game server
|
||
// @Tags Virtual Games - Atlas
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param request body domain.JackpotRequest true "Jackpot result input"
|
||
// @Success 200 {object} domain.JackpotResponse
|
||
// @Failure 400 {object} domain.ErrorResponse
|
||
// @Failure 502 {object} domain.ErrorResponse
|
||
// @Router /jackpot [post]
|
||
func (h *Handler) JackpotCallback(c *fiber.Ctx) error {
|
||
// Read raw request body
|
||
body := c.Body()
|
||
if len(body) == 0 {
|
||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||
Message: "Empty request body",
|
||
Error: "Request body cannot be empty",
|
||
})
|
||
}
|
||
|
||
// Unmarshal into JackpotRequest
|
||
var req domain.JackpotRequest
|
||
if err := json.Unmarshal(body, &req); err != nil {
|
||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||
Message: "Invalid jackpot request",
|
||
Error: err.Error(),
|
||
})
|
||
}
|
||
|
||
// Process the jackpot
|
||
res, err := h.atlasVirtualGameSvc.ProcessJackPot(c.Context(), req)
|
||
if err != nil {
|
||
log.Println("JackpotCallback error:", err)
|
||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||
Message: "Failed to process jackpot",
|
||
Error: err.Error(),
|
||
})
|
||
}
|
||
|
||
return c.Status(fiber.StatusOK).JSON(res)
|
||
}
|