Yimaru-BackEnd/internal/web_server/handlers/virtual_games_hadlers.go

856 lines
28 KiB
Go

package handlers
import (
"encoding/json"
"errors"
"fmt"
"log"
"strconv"
"strings"
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 strings.Contains(err.Error(), veli.ErrDuplicateTransaction.Error()) {
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
// Message: "Duplicate transaction",
Error: veli.ErrDuplicateTransaction.Error(),
})
} else if strings.Contains(err.Error(), veli.ErrInsufficientBalance.Error()) {
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
// Message: "Wallet balance is insufficient",
Error: veli.ErrInsufficientBalance.Error(),
})
} else if strings.Contains(err.Error(), veli.ErrPlayerNotFound.Error()) {
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
// 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 /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.CallbackErrorResponse{
// Message: "Duplicate transaction",
Error: veli.ErrDuplicateTransaction.Error(),
})
} else if errors.Is(err, veli.ErrPlayerNotFound) {
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
// 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 strings.Contains(err.Error(), veli.ErrDuplicateTransaction.Error()) {
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
// Message: "Duplicate transaction",
Error: veli.ErrDuplicateTransaction.Error(),
})
} else if strings.Contains(err.Error(), veli.ErrPlayerNotFound.Error()) {
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
// 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")
}