855 lines
28 KiB
Go
855 lines
28 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"strconv"
|
|
|
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/veli"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
type launchVirtualGameReq struct {
|
|
GameID string `json:"game_id" validate:"required" example:"1"`
|
|
Currency string `json:"currency" validate:"required,len=3" example:"USD"`
|
|
Mode string `json:"mode" validate:"required,oneof=fun real" example:"real"`
|
|
}
|
|
|
|
type launchVirtualGameRes struct {
|
|
LaunchURL string `json:"launch_url"`
|
|
}
|
|
|
|
// ListVirtualGameProviderReportsAscHandler
|
|
// @Summary List virtual game provider reports (ascending)
|
|
// @Description Retrieves all virtual game provider reports sorted by total_games_played in ascending order
|
|
// @Tags VirtualGames - Orchestration
|
|
// @Success 200 {array} domain.VirtualGameProviderReport
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/orchestrator/virtual-game/provider-reports/asc [get]
|
|
func (h *Handler) ListVirtualGameProviderReportsAscHandler(c *fiber.Ctx) error {
|
|
reports, err := h.orchestrationSvc.ListVirtualGameProviderReportsByGamesPlayedAsc(c.Context())
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Failed to fetch virtual game provider reports ascending",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "Virtual game provider reports retrieved successfully",
|
|
Data: reports,
|
|
StatusCode: fiber.StatusOK,
|
|
Success: true,
|
|
})
|
|
}
|
|
|
|
// ListVirtualGameProviderReportsDescHandler
|
|
// @Summary List virtual game provider reports (descending)
|
|
// @Description Retrieves all virtual game provider reports sorted by total_games_played in descending order
|
|
// @Tags VirtualGames - Orchestration
|
|
// @Success 200 {array} domain.VirtualGameProviderReport
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/orchestrator/virtual-game/provider-reports/desc [get]
|
|
func (h *Handler) ListVirtualGameProviderReportsDescHandler(c *fiber.Ctx) error {
|
|
reports, err := h.orchestrationSvc.ListVirtualGameProviderReportsByGamesPlayedDesc(c.Context())
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Failed to fetch virtual game provider reports descending",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "Virtual game provider reports retrieved successfully",
|
|
Data: reports,
|
|
StatusCode: fiber.StatusOK,
|
|
Success: true,
|
|
})
|
|
}
|
|
|
|
// ListVirtualGames godoc
|
|
// @Summary List all virtual games
|
|
// @Description Returns all virtual games with optional filters (category, search, pagination)
|
|
// @Tags VirtualGames - Orchestration
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param category query string false "Filter by category"
|
|
// @Param search query string false "Search by game name"
|
|
// @Param limit query int false "Pagination limit"
|
|
// @Param offset query int false "Pagination offset"
|
|
// @Success 200 {object} domain.Response{data=[]domain.UnifiedGame}
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 502 {object} domain.ErrorResponse
|
|
// @Router /api/v1/orchestrator/virtual-games [get]
|
|
func (h *Handler) ListVirtualGames(c *fiber.Ctx) error {
|
|
// --- Parse query parameters ---
|
|
limit := c.QueryInt("limit", 100)
|
|
if limit <= 0 {
|
|
limit = 100
|
|
}
|
|
offset := c.QueryInt("offset", 0)
|
|
if offset < 0 {
|
|
offset = 0
|
|
}
|
|
category := c.Query("category", "")
|
|
search := c.Query("search", "")
|
|
providerID := c.Query("providerID", "")
|
|
|
|
params := dbgen.GetAllVirtualGamesParams{
|
|
Category: pgtype.Text{
|
|
String: category,
|
|
Valid: category != "",
|
|
},
|
|
Name: pgtype.Text{
|
|
String: search,
|
|
Valid: search != "",
|
|
},
|
|
ProviderID: pgtype.Text{
|
|
String: providerID,
|
|
Valid: providerID != "",
|
|
},
|
|
Offset: pgtype.Int4{
|
|
Int32: int32(offset),
|
|
Valid: offset != 0,
|
|
},
|
|
Limit: pgtype.Int4{
|
|
Int32: int32(limit),
|
|
Valid: limit != 0,
|
|
},
|
|
}
|
|
|
|
// --- Call service method ---
|
|
games, err := h.orchestrationSvc.GetAllVirtualGames(c.Context(), params)
|
|
if err != nil {
|
|
log.Println("ListVirtualGames error:", err)
|
|
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
|
Message: "Failed to fetch virtual games",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
// --- Return response ---
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "Virtual games fetched successfully",
|
|
Data: games,
|
|
StatusCode: fiber.StatusOK,
|
|
Success: true,
|
|
})
|
|
}
|
|
|
|
// RemoveProviderHandler
|
|
// @Summary Remove a virtual game provider
|
|
// @Description Deletes a provider by provider_id
|
|
// @Tags VirtualGames - Orchestration
|
|
// @Param provider_id path string true "Provider ID"
|
|
// @Success 200
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/virtual-game/providers/{provider_id} [delete]
|
|
func (h *Handler) RemoveProvider(c *fiber.Ctx) error {
|
|
providerID := c.Params("providerID")
|
|
if err := h.orchestrationSvc.RemoveProvider(c.Context(), providerID); err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Could not remove provider")
|
|
}
|
|
return c.SendStatus(fiber.StatusOK)
|
|
}
|
|
|
|
// GetProviderHandler
|
|
// @Summary Get a virtual game provider
|
|
// @Description Fetches a provider by provider_id
|
|
// @Tags VirtualGames - Orchestration
|
|
// @Param provider_id path string true "Provider ID"
|
|
// @Produce json
|
|
// @Success 200 {object} domain.VirtualGameProvider
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/virtual-game/providers/{provider_id} [get]
|
|
func (h *Handler) GetProviderByID(c *fiber.Ctx) error {
|
|
providerID := c.Params("providerID")
|
|
provider, err := h.orchestrationSvc.GetProviderByID(c.Context(), providerID)
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Could not fetch provider")
|
|
}
|
|
return c.Status(fiber.StatusOK).JSON(provider)
|
|
}
|
|
|
|
// ListProvidersHandler
|
|
// @Summary List virtual game providers
|
|
// @Description Lists all providers with pagination
|
|
// @Tags VirtualGames - Orchestration
|
|
// @Produce json
|
|
// @Param limit query int false "Limit"
|
|
// @Param offset query int false "Offset"
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/virtual-game/providers [get]
|
|
func (h *Handler) ListProviders(c *fiber.Ctx) error {
|
|
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
|
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
|
|
|
providers, total, err := h.orchestrationSvc.ListProviders(c.Context(), int32(limit), int32(offset))
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Could not list providers")
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
|
"total": total,
|
|
"providers": providers,
|
|
})
|
|
}
|
|
|
|
// SetProviderEnabledHandler
|
|
// @Summary Enable/Disable a provider
|
|
// @Description Sets the enabled status of a provider
|
|
// @Tags VirtualGames - Orchestration
|
|
// @Param provider_id path string true "Provider ID"
|
|
// @Param enabled query bool true "Enable or Disable"
|
|
// @Produce json
|
|
// @Success 200 {object} domain.VirtualGameProvider
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/virtual-game/orchestrator/providers/status [patch]
|
|
func (h *Handler) SetProviderEnabled(c *fiber.Ctx) error {
|
|
providerID := c.Params("providerID")
|
|
enabled, _ := strconv.ParseBool(c.Query("enabled", "true"))
|
|
|
|
provider, err := h.orchestrationSvc.SetProviderEnabled(c.Context(), providerID, enabled)
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Could not update provider status")
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(provider)
|
|
}
|
|
|
|
// LaunchVirtualGame godoc
|
|
// @Summary Launch a PopOK virtual game
|
|
// @Description Generates a URL to launch a PopOK game
|
|
// @Tags Virtual Games - PopOK
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security Bearer
|
|
// @Param launch body launchVirtualGameReq true "Game launch details"
|
|
// @Success 200 {object} launchVirtualGameRes
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 401 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /virtual-game/launch [post]
|
|
func (h *Handler) LaunchVirtualGame(c *fiber.Ctx) error {
|
|
|
|
userID, ok := c.Locals("user_id").(int64)
|
|
if !ok || userID == 0 {
|
|
h.logger.Error("Invalid user ID in context")
|
|
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
|
|
}
|
|
|
|
// companyID, ok := c.Locals("company_id").(int64)
|
|
// if !ok || companyID == 0 {
|
|
// h.logger.Error("Invalid company ID in context")
|
|
// return fiber.NewError(fiber.StatusUnauthorized, "Invalid company identification")
|
|
// }
|
|
|
|
var req launchVirtualGameReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.logger.Error("Failed to parse LaunchVirtualGame 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)
|
|
}
|
|
|
|
url, err := h.virtualGameSvc.GenerateGameLaunchURL(c.Context(), userID, req.GameID, req.Currency, req.Mode)
|
|
if err != nil {
|
|
h.logger.Error("Failed to generate game launch URL", "userID", userID, "gameID", req.GameID, "error", err)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to launch game")
|
|
}
|
|
|
|
res := launchVirtualGameRes{LaunchURL: url}
|
|
return response.WriteJSON(c, fiber.StatusOK, "Game launched successfully", res, nil)
|
|
}
|
|
|
|
// HandleVirtualGameCallback godoc
|
|
// @Summary Handle PopOK game callback
|
|
// @Description Processes callbacks from PopOK for game events
|
|
// @Tags Virtual Games - PopOK
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param callback body domain.PopOKCallback true "Callback data"
|
|
// @Success 200 {object} domain.ErrorResponse
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /virtual-game/callback [post]
|
|
func (h *Handler) HandleVirtualGameCallback(c *fiber.Ctx) error {
|
|
var callback domain.PopOKCallback
|
|
if err := c.BodyParser(&callback); err != nil {
|
|
h.logger.Error("Failed to parse callback", "error", err)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid callback data")
|
|
}
|
|
|
|
if err := h.virtualGameSvc.HandleCallback(c.Context(), &callback); err != nil {
|
|
h.logger.Error("Failed to handle callback", "transactionID", callback.TransactionID, "error", err)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to process callback")
|
|
}
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "Callback processed successfully", nil, nil)
|
|
}
|
|
|
|
func (h *Handler) HandlePlayerInfo(c *fiber.Ctx) error {
|
|
var req domain.PopOKPlayerInfoRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request")
|
|
}
|
|
|
|
resp, err := h.virtualGameSvc.GetPlayerInfo(c.Context(), &req)
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(resp)
|
|
}
|
|
|
|
func (h *Handler) HandleBet(c *fiber.Ctx) error {
|
|
// Read the raw 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",
|
|
})
|
|
}
|
|
|
|
// Identify the provider based on request structure
|
|
provider, err := IdentifyBetProvider(body)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Unrecognized request format",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
switch provider {
|
|
case "veli":
|
|
var req domain.BetRequest
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid Veli bet request",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
// req.PlayerID = fmt.Sprintf("%v", userID)
|
|
|
|
res, err := h.veliVirtualGameSvc.ProcessBet(c.Context(), req)
|
|
if err != nil {
|
|
if errors.Is(err, veli.ErrDuplicateTransaction) {
|
|
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
|
|
// Message: "Duplicate transaction",
|
|
Error: veli.ErrDuplicateTransaction.Error(),
|
|
})
|
|
} else if errors.Is(err, veli.ErrInsufficientBalance) {
|
|
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
|
|
// Message: "Wallet balance is insufficient",
|
|
Error: veli.ErrInsufficientBalance.Error(),
|
|
})
|
|
} else if errors.Is(err, veli.ErrPlayerNotFound) {
|
|
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
|
|
// Message: "User not found",
|
|
Error: veli.ErrPlayerNotFound.Error(),
|
|
})
|
|
}
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Veli bet processing failed",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
return c.JSON(res)
|
|
|
|
case "popok":
|
|
var req domain.PopOKBetRequest
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid PopOK bet request",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
// req.PlayerID = fmt.Sprintf("%v", userID)
|
|
|
|
resp, err := h.virtualGameSvc.ProcessBet(c.Context(), &req)
|
|
if err != nil {
|
|
code := fiber.StatusInternalServerError
|
|
switch err.Error() {
|
|
case "invalid token":
|
|
code = fiber.StatusUnauthorized
|
|
case "insufficient balance":
|
|
code = fiber.StatusBadRequest
|
|
}
|
|
return c.Status(code).JSON(domain.ErrorResponse{
|
|
Message: "PopOK bet processing failed",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
return c.JSON(resp)
|
|
|
|
case "atlas":
|
|
var req domain.AtlasBetRequest
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid Atlas bet request",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
// req.PlayerID = fmt.Sprintf("%v", userID)
|
|
|
|
resp, err := h.atlasVirtualGameSvc.ProcessBet(c.Context(), req)
|
|
if err != nil {
|
|
// code := fiber.StatusInternalServerError
|
|
// if errors.Is(err, ErrDuplicateTransaction) {
|
|
// code = fiber.StatusConflict
|
|
// }
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Atlas bet processing failed",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
return c.JSON(resp)
|
|
|
|
default:
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Unsupported provider",
|
|
Error: "Request format doesn't match any supported provider",
|
|
})
|
|
}
|
|
}
|
|
|
|
// identifyProvider examines the request body to determine the provider
|
|
|
|
// WinHandler godoc
|
|
// @Summary Handle win callback (Veli or PopOK)
|
|
// @Description Processes win callbacks from either Veli or PopOK providers by auto-detecting the format
|
|
// @Tags Wins
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} interface{} "Win processing result"
|
|
// @Failure 400 {object} domain.ErrorResponse "Invalid request format"
|
|
// @Failure 401 {object} domain.ErrorResponse "Authentication failed"
|
|
// @Failure 409 {object} domain.ErrorResponse "Duplicate transaction"
|
|
// @Failure 500 {object} domain.ErrorResponse "Internal server error"
|
|
// @Router /api/v1/win [post]
|
|
func (h *Handler) HandleWin(c *fiber.Ctx) error {
|
|
// Read the raw body to avoid parsing issues
|
|
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",
|
|
})
|
|
}
|
|
|
|
// Try to identify the provider based on the request structure
|
|
provider, err := identifyWinProvider(body)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Unrecognized request format",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
switch provider {
|
|
case "veli":
|
|
var req domain.WinRequest
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid Veli win request",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
res, err := h.veliVirtualGameSvc.ProcessWin(c.Context(), req)
|
|
if err != nil {
|
|
if errors.Is(err, veli.ErrDuplicateTransaction) {
|
|
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
|
|
// Message: "Duplicate transaction",
|
|
Error: veli.ErrDuplicateTransaction.Error(),
|
|
})
|
|
} else if errors.Is(err, veli.ErrPlayerNotFound) {
|
|
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
|
|
// Message: "Duplicate transaction",
|
|
Error: veli.ErrPlayerNotFound.Error(),
|
|
})
|
|
}
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Veli win processing failed",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
return c.JSON(res)
|
|
|
|
case "popok":
|
|
var req domain.PopOKWinRequest
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid PopOK win request",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
resp, err := h.virtualGameSvc.ProcessWin(c.Context(), &req)
|
|
if err != nil {
|
|
code := fiber.StatusInternalServerError
|
|
if err.Error() == "invalid token" {
|
|
code = fiber.StatusUnauthorized
|
|
}
|
|
return c.Status(code).JSON(domain.ErrorResponse{
|
|
Message: "PopOK win processing failed",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
return c.JSON(resp)
|
|
|
|
default:
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Unsupported provider",
|
|
Error: "Request format doesn't match any supported provider",
|
|
})
|
|
}
|
|
}
|
|
|
|
func (h *Handler) HandleCancel(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",
|
|
})
|
|
}
|
|
|
|
provider, err := identifyCancelProvider(body)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Unrecognized request format",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
switch provider {
|
|
case "veli":
|
|
var req domain.CancelRequest
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid Veli cancel request",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
res, err := h.veliVirtualGameSvc.ProcessCancel(c.Context(), req)
|
|
if err != nil {
|
|
if errors.Is(err, veli.ErrDuplicateTransaction) {
|
|
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
|
|
// Message: "Duplicate transaction",
|
|
Error: veli.ErrDuplicateTransaction.Error(),
|
|
})
|
|
} else if errors.Is(err, veli.ErrPlayerNotFound) {
|
|
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
|
|
// Message: "User not found",
|
|
Error: veli.ErrPlayerNotFound.Error(),
|
|
})
|
|
}
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Veli cancel processing failed",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
return c.JSON(res)
|
|
|
|
case "popok":
|
|
var req domain.PopOKCancelRequest
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid PopOK cancel request",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
resp, err := h.virtualGameSvc.ProcessCancel(c.Context(), &req)
|
|
if err != nil {
|
|
code := fiber.StatusInternalServerError
|
|
switch err.Error() {
|
|
case "invalid token":
|
|
code = fiber.StatusUnauthorized
|
|
case "original bet not found", "invalid original transaction":
|
|
code = fiber.StatusBadRequest
|
|
}
|
|
return c.Status(code).JSON(domain.ErrorResponse{
|
|
Message: "PopOK cancel processing failed",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
return c.JSON(resp)
|
|
|
|
default:
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Unsupported provider",
|
|
Error: "Request format doesn't match any supported provider",
|
|
})
|
|
}
|
|
}
|
|
|
|
// GetGameList godoc
|
|
// @Summary Get PopOK Games List
|
|
// @Description Retrieves the list of available PopOK slot games
|
|
// @Tags Virtual Games - PopOK
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param currency query string false "Currency (e.g. USD, ETB)" default(USD)
|
|
// @Success 200 {array} domain.PopOKGame
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /popok/games [get]
|
|
func (h *Handler) GetGameList(c *fiber.Ctx) error {
|
|
currency := c.Query("currency", "ETB") // fallback default
|
|
|
|
games, err := h.virtualGameSvc.ListGames(c.Context(), currency)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Falied to fetch games",
|
|
Error: err.Error(),
|
|
})
|
|
// return fiber.NewError(fiber.StatusBadGateway, "failed to fetch games")
|
|
}
|
|
return c.JSON(games)
|
|
}
|
|
|
|
// RecommendGames godoc
|
|
// @Summary Recommend virtual games
|
|
// @Description Recommends games based on user history or randomly
|
|
// @Tags Virtual Games - PopOK
|
|
// @Produce json
|
|
// @Param user_id query int true "User ID"
|
|
// @Success 200 {array} domain.GameRecommendation
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /popok/games/recommend [get]
|
|
func (h *Handler) RecommendGames(c *fiber.Ctx) error {
|
|
userIDVal := c.Locals("user_id")
|
|
userID, ok := userIDVal.(int64)
|
|
if !ok || userID == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid user ID")
|
|
}
|
|
|
|
recommendations, err := h.virtualGameSvc.RecommendGames(c.Context(), userID)
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to recommend games")
|
|
}
|
|
|
|
return c.JSON(recommendations)
|
|
}
|
|
|
|
func (h *Handler) HandleTournamentWin(c *fiber.Ctx) error {
|
|
var req domain.PopOKWinRequest
|
|
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.logger.Error("Invalid tournament win request body", "error", err)
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Invalid request body",
|
|
})
|
|
}
|
|
|
|
resp, err := h.virtualGameSvc.ProcessTournamentWin(c.Context(), &req)
|
|
if err != nil {
|
|
h.logger.Error("Failed to process tournament win", "error", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(resp)
|
|
}
|
|
|
|
func (h *Handler) HandlePromoWin(c *fiber.Ctx) error {
|
|
var req domain.PopOKWinRequest
|
|
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.logger.Error("Invalid promo win request body", "error", err)
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Invalid request body",
|
|
})
|
|
}
|
|
|
|
resp, err := h.virtualGameSvc.ProcessPromoWin(c.Context(), &req)
|
|
if err != nil {
|
|
h.logger.Error("Failed to process promo win", "error", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(resp)
|
|
}
|
|
|
|
// AddFavoriteGame godoc
|
|
// @Summary Add game to favorites
|
|
// @Description Adds a game to the user's favorite games list
|
|
// @Tags VirtualGames - Favourites
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param body body domain.FavoriteGameRequest true "Game ID to add"
|
|
// @Success 201 {string} domain.Response "created"
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/virtual-game/favorites [post]
|
|
func (h *Handler) AddFavorite(c *fiber.Ctx) error {
|
|
userID := c.Locals("user_id").(int64)
|
|
|
|
var req domain.FavoriteGameRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request")
|
|
}
|
|
|
|
err := h.virtualGameSvc.AddFavoriteGame(c.Context(), userID, req.GameID)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Could not add favorite",
|
|
Error: err.Error(),
|
|
})
|
|
// return fiber.NewError(fiber.StatusInternalServerError, "Could not add favorite")
|
|
}
|
|
return c.Status(fiber.StatusCreated).JSON(domain.Response{
|
|
Message: "Game added to favorites",
|
|
StatusCode: fiber.StatusCreated,
|
|
Success: true,
|
|
})
|
|
// return c.SendStatus(fiber.StatusCreated)
|
|
}
|
|
|
|
// RemoveFavoriteGame godoc
|
|
// @Summary Remove game from favorites
|
|
// @Description Removes a game from the user's favorites
|
|
// @Tags VirtualGames - Favourites
|
|
// @Produce json
|
|
// @Param gameID path int64 true "Game ID to remove"
|
|
// @Success 200 {string} domain.Response "removed"
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/virtual-game/favorites/{gameID} [delete]
|
|
func (h *Handler) RemoveFavorite(c *fiber.Ctx) error {
|
|
userID := c.Locals("user_id").(int64)
|
|
gameID, _ := strconv.ParseInt(c.Params("gameID"), 10, 64)
|
|
|
|
err := h.virtualGameSvc.RemoveFavoriteGame(c.Context(), userID, gameID)
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Could not remove favorite")
|
|
}
|
|
return c.SendStatus(fiber.StatusOK)
|
|
}
|
|
|
|
// ListFavoriteGames godoc
|
|
// @Summary Get user's favorite games
|
|
// @Description Lists the games that the user marked as favorite
|
|
// @Tags VirtualGames - Favourites
|
|
// @Produce json
|
|
// @Success 200 {array} domain.GameRecommendation
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/virtual-game/favorites [get]
|
|
func (h *Handler) ListFavorites(c *fiber.Ctx) error {
|
|
userID := c.Locals("user_id").(int64)
|
|
|
|
games, err := h.virtualGameSvc.ListFavoriteGames(c.Context(), userID)
|
|
if err != nil {
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Could not fetch favorites")
|
|
}
|
|
return c.Status(fiber.StatusOK).JSON(games)
|
|
}
|
|
|
|
func IdentifyBetProvider(body []byte) (string, error) {
|
|
// Check for Veli signature fields
|
|
var veliCheck struct {
|
|
SessionID string `json:"sessionId"`
|
|
BrandID string `json:"brandId"`
|
|
}
|
|
if json.Unmarshal(body, &veliCheck) == nil {
|
|
if veliCheck.SessionID != "" && veliCheck.BrandID != "" {
|
|
return "veli", nil
|
|
}
|
|
}
|
|
|
|
// Check for PopOK signature fields
|
|
var popokCheck struct {
|
|
Token string `json:"externalToken"`
|
|
}
|
|
|
|
if json.Unmarshal(body, &popokCheck) == nil {
|
|
if popokCheck.Token != "" {
|
|
return "popok", nil
|
|
}
|
|
}
|
|
|
|
var atlasCheck struct {
|
|
CasinoID string `json:"casino_id"`
|
|
SessionID string `json:"session_id"`
|
|
}
|
|
|
|
if json.Unmarshal(body, &atlasCheck) == nil {
|
|
if atlasCheck.CasinoID != "" && atlasCheck.SessionID != "" {
|
|
return "atlas", nil
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("could not identify provider from request structure")
|
|
}
|
|
|
|
// identifyWinProvider examines the request body to determine the provider for win callbacks
|
|
func identifyWinProvider(body []byte) (string, error) {
|
|
// Check for Veli signature fields
|
|
var veliCheck struct {
|
|
SessionID string `json:"sessionId"`
|
|
BrandID string `json:"brandId"`
|
|
}
|
|
if json.Unmarshal(body, &veliCheck) == nil {
|
|
if veliCheck.SessionID != "" && veliCheck.BrandID != "" {
|
|
return "veli", nil
|
|
}
|
|
}
|
|
|
|
// Check for PopOK signature fields
|
|
var popokCheck struct {
|
|
Token string `json:"externalToken"`
|
|
}
|
|
|
|
if json.Unmarshal(body, &popokCheck) == nil {
|
|
if popokCheck.Token != "" {
|
|
return "popok", nil
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("could not identify provider from request structure")
|
|
}
|
|
|
|
func identifyCancelProvider(body []byte) (string, error) {
|
|
// Check for Veli cancel signature
|
|
var veliCheck struct {
|
|
SessionID string `json:"sessionId"`
|
|
BrandID string `json:"brandId"`
|
|
}
|
|
if json.Unmarshal(body, &veliCheck) == nil {
|
|
if veliCheck.SessionID != "" && veliCheck.BrandID != "" {
|
|
return "veli", nil
|
|
}
|
|
}
|
|
|
|
// Check for PopOK cancel signature
|
|
var popokCheck struct {
|
|
Token string `json:"externalToken"`
|
|
}
|
|
|
|
if json.Unmarshal(body, &popokCheck) == nil {
|
|
if popokCheck.Token != "" {
|
|
return "popok", nil
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("could not identify cancel provider from request structure")
|
|
}
|