favourtie game and virtual games + providers orchestration fixes

This commit is contained in:
Yared Yemane 2025-11-18 16:39:37 +03:00
parent 1c7e076be5
commit 299825e797
20 changed files with 522 additions and 174 deletions

View File

@ -71,6 +71,15 @@ CREATE TABLE IF NOT EXISTS virtual_games (
);
CREATE UNIQUE INDEX IF NOT EXISTS ux_virtual_games_provider_game ON virtual_games (provider_id, game_id);
CREATE TABLE IF NOT EXISTS virtual_game_favourites (
id BIGSERIAL PRIMARY KEY,
game_id BIGINT NOT NULL REFERENCES virtual_games(id) ON DELETE CASCADE,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
provider_id VARCHAR(100) NOT NULL REFERENCES virtual_game_providers(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (game_id, user_id)
);
CREATE TABLE IF NOT EXISTS virtual_game_reports (
id BIGSERIAL PRIMARY KEY,
game_id VARCHAR(150) NOT NULL REFERENCES virtual_games(game_id) ON DELETE CASCADE,

View File

@ -200,17 +200,28 @@ WHERE vgt.transaction_type = 'BET'
AND vgt.created_at BETWEEN $1 AND $2
GROUP BY c.name,
vg.name;
-- name: AddFavoriteGame :exec
INSERT INTO favorite_games (user_id, game_id, created_at)
VALUES ($1, $2, NOW()) ON CONFLICT (user_id, game_id) DO NOTHING;
INSERT INTO virtual_game_favourites (user_id, game_id, provider_id, created_at)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (game_id, user_id) DO NOTHING;
-- name: RemoveFavoriteGame :exec
DELETE FROM favorite_games
WHERE user_id = $1
AND game_id = $2;
-- name: ListFavoriteGames :many
SELECT game_id
FROM favorite_games
WHERE user_id = $1;
DELETE FROM virtual_game_favourites
WHERE user_id = $1 AND game_id = $2 AND provider_id = $3;
-- name: GetUserFavoriteGamesPaginated :many
SELECT
vg.*
FROM virtual_games vg
JOIN virtual_game_favourites vf
ON vf.game_id = vg.id
WHERE
vf.user_id = $1
AND ($2::varchar IS NULL OR vf.provider_id = $2)
ORDER BY vf.created_at DESC
LIMIT $3 OFFSET $4;
-- name: CreateVirtualGame :one
INSERT INTO virtual_games (
game_id,

View File

@ -8108,16 +8108,42 @@ const docTemplate = `{
"VirtualGames - Favourites"
],
"summary": "Get user's favorite games",
"parameters": [
{
"type": "string",
"description": "Filter by provider ID",
"name": "providerID",
"in": "query"
},
{
"type": "integer",
"description": "Number of results to return",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"description": "Results offset",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.GameRecommendation"
"$ref": "#/definitions/domain.UnifiedGame"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
@ -8140,7 +8166,7 @@ const docTemplate = `{
"summary": "Add game to favorites",
"parameters": [
{
"description": "Game ID to add",
"description": "Game ID and Provider ID to add",
"name": "body",
"in": "body",
"required": true,
@ -8188,13 +8214,20 @@ const docTemplate = `{
"name": "gameID",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Provider ID of the game",
"name": "providerID",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "removed",
"schema": {
"type": "string"
"$ref": "#/definitions/domain.Response"
}
},
"400": {
@ -12740,6 +12773,9 @@ const docTemplate = `{
"properties": {
"game_id": {
"type": "integer"
},
"provider_id": {
"type": "string"
}
}
},

View File

@ -8100,16 +8100,42 @@
"VirtualGames - Favourites"
],
"summary": "Get user's favorite games",
"parameters": [
{
"type": "string",
"description": "Filter by provider ID",
"name": "providerID",
"in": "query"
},
{
"type": "integer",
"description": "Number of results to return",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"description": "Results offset",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.GameRecommendation"
"$ref": "#/definitions/domain.UnifiedGame"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
@ -8132,7 +8158,7 @@
"summary": "Add game to favorites",
"parameters": [
{
"description": "Game ID to add",
"description": "Game ID and Provider ID to add",
"name": "body",
"in": "body",
"required": true,
@ -8180,13 +8206,20 @@
"name": "gameID",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Provider ID of the game",
"name": "providerID",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "removed",
"schema": {
"type": "string"
"$ref": "#/definitions/domain.Response"
}
},
"400": {
@ -12732,6 +12765,9 @@
"properties": {
"game_id": {
"type": "integer"
},
"provider_id": {
"type": "string"
}
}
},

View File

@ -1430,6 +1430,8 @@ definitions:
properties:
game_id:
type: integer
provider_id:
type: string
type: object
domain.FreeSpinRequest:
properties:
@ -9783,6 +9785,19 @@ paths:
/api/v1/virtual-game/favorites:
get:
description: Lists the games that the user marked as favorite
parameters:
- description: Filter by provider ID
in: query
name: providerID
type: string
- description: Number of results to return
in: query
name: limit
type: integer
- description: Results offset
in: query
name: offset
type: integer
produces:
- application/json
responses:
@ -9790,8 +9805,12 @@ paths:
description: OK
schema:
items:
$ref: '#/definitions/domain.GameRecommendation'
$ref: '#/definitions/domain.UnifiedGame'
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/domain.ErrorResponse'
"500":
description: Internal Server Error
schema:
@ -9804,7 +9823,7 @@ paths:
- application/json
description: Adds a game to the user's favorite games list
parameters:
- description: Game ID to add
- description: Game ID and Provider ID to add
in: body
name: body
required: true
@ -9837,13 +9856,18 @@ paths:
name: gameID
required: true
type: integer
- description: Provider ID of the game
in: query
name: providerID
required: true
type: string
produces:
- application/json
responses:
"200":
description: removed
schema:
type: string
$ref: '#/definitions/domain.Response'
"400":
description: Bad Request
schema:

View File

@ -1157,9 +1157,16 @@ type VirtualGame struct {
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type VirtualGameFavourite struct {
ID int64 `json:"id"`
GameID int64 `json:"game_id"`
UserID int64 `json:"user_id"`
ProviderID string `json:"provider_id"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type VirtualGameHistory struct {
ID int64 `json:"id"`
SessionID pgtype.Text `json:"session_id"`
UserID int64 `json:"user_id"`
CompanyID pgtype.Int8 `json:"company_id"`
Provider pgtype.Text `json:"provider"`

View File

@ -12,17 +12,19 @@ import (
)
const AddFavoriteGame = `-- name: AddFavoriteGame :exec
INSERT INTO favorite_games (user_id, game_id, created_at)
VALUES ($1, $2, NOW()) ON CONFLICT (user_id, game_id) DO NOTHING
INSERT INTO virtual_game_favourites (user_id, game_id, provider_id, created_at)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (game_id, user_id) DO NOTHING
`
type AddFavoriteGameParams struct {
UserID int64 `json:"user_id"`
GameID int64 `json:"game_id"`
ProviderID string `json:"provider_id"`
}
func (q *Queries) AddFavoriteGame(ctx context.Context, arg AddFavoriteGameParams) error {
_, err := q.db.Exec(ctx, AddFavoriteGame, arg.UserID, arg.GameID)
_, err := q.db.Exec(ctx, AddFavoriteGame, arg.UserID, arg.GameID, arg.ProviderID)
return err
}
@ -137,7 +139,7 @@ func (q *Queries) CreateVirtualGame(ctx context.Context, arg CreateVirtualGamePa
const CreateVirtualGameHistory = `-- name: CreateVirtualGameHistory :one
INSERT INTO virtual_game_histories (
session_id,
-- session_id,
user_id,
company_id,
provider,
@ -161,11 +163,11 @@ VALUES (
$8,
$9,
$10,
$11,
$12
$11
-- $12
)
RETURNING id,
session_id,
-- session_id,
user_id,
company_id,
provider,
@ -182,7 +184,6 @@ RETURNING id,
`
type CreateVirtualGameHistoryParams struct {
SessionID pgtype.Text `json:"session_id"`
UserID int64 `json:"user_id"`
CompanyID pgtype.Int8 `json:"company_id"`
Provider pgtype.Text `json:"provider"`
@ -198,7 +199,6 @@ type CreateVirtualGameHistoryParams struct {
func (q *Queries) CreateVirtualGameHistory(ctx context.Context, arg CreateVirtualGameHistoryParams) (VirtualGameHistory, error) {
row := q.db.QueryRow(ctx, CreateVirtualGameHistory,
arg.SessionID,
arg.UserID,
arg.CompanyID,
arg.Provider,
@ -214,7 +214,6 @@ func (q *Queries) CreateVirtualGameHistory(ctx context.Context, arg CreateVirtua
var i VirtualGameHistory
err := row.Scan(
&i.ID,
&i.SessionID,
&i.UserID,
&i.CompanyID,
&i.Provider,
@ -662,6 +661,67 @@ func (q *Queries) GetAllVirtualGames(ctx context.Context, arg GetAllVirtualGames
return items, nil
}
const GetUserFavoriteGamesPaginated = `-- name: GetUserFavoriteGamesPaginated :many
SELECT
vg.id, vg.game_id, vg.provider_id, vg.name, vg.category, vg.device_type, vg.volatility, vg.rtp, vg.has_demo, vg.has_free_bets, vg.bets, vg.thumbnail, vg.status, vg.created_at, vg.updated_at
FROM virtual_games vg
JOIN virtual_game_favourites vf
ON vf.game_id = vg.id
WHERE
vf.user_id = $1
AND ($2::varchar IS NULL OR vf.provider_id = $2)
ORDER BY vf.created_at DESC
LIMIT $3 OFFSET $4
`
type GetUserFavoriteGamesPaginatedParams struct {
UserID int64 `json:"user_id"`
Column2 string `json:"column_2"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) GetUserFavoriteGamesPaginated(ctx context.Context, arg GetUserFavoriteGamesPaginatedParams) ([]VirtualGame, error) {
rows, err := q.db.Query(ctx, GetUserFavoriteGamesPaginated,
arg.UserID,
arg.Column2,
arg.Limit,
arg.Offset,
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []VirtualGame
for rows.Next() {
var i VirtualGame
if err := rows.Scan(
&i.ID,
&i.GameID,
&i.ProviderID,
&i.Name,
&i.Category,
&i.DeviceType,
&i.Volatility,
&i.Rtp,
&i.HasDemo,
&i.HasFreeBets,
&i.Bets,
&i.Thumbnail,
&i.Status,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const GetVirtualGameProviderByID = `-- name: GetVirtualGameProviderByID :one
SELECT id,
provider_id,
@ -874,32 +934,6 @@ func (q *Queries) GetVirtualGameTransactionByExternalID(ctx context.Context, ext
return i, err
}
const ListFavoriteGames = `-- name: ListFavoriteGames :many
SELECT game_id
FROM favorite_games
WHERE user_id = $1
`
func (q *Queries) ListFavoriteGames(ctx context.Context, userID int64) ([]int64, error) {
rows, err := q.db.Query(ctx, ListFavoriteGames, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []int64
for rows.Next() {
var game_id int64
if err := rows.Scan(&game_id); err != nil {
return nil, err
}
items = append(items, game_id)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const ListVirtualGameProviderReportsByGamesPlayedAsc = `-- name: ListVirtualGameProviderReportsByGamesPlayedAsc :many
SELECT id, provider_id, report_date, total_games_played, total_bets, total_payouts, total_profit, total_players, report_type, created_at, updated_at
FROM virtual_game_provider_reports
@ -1025,18 +1059,18 @@ func (q *Queries) ListVirtualGameProviders(ctx context.Context, arg ListVirtualG
}
const RemoveFavoriteGame = `-- name: RemoveFavoriteGame :exec
DELETE FROM favorite_games
WHERE user_id = $1
AND game_id = $2
DELETE FROM virtual_game_favourites
WHERE user_id = $1 AND game_id = $2 AND provider_id = $3
`
type RemoveFavoriteGameParams struct {
UserID int64 `json:"user_id"`
GameID int64 `json:"game_id"`
ProviderID string `json:"provider_id"`
}
func (q *Queries) RemoveFavoriteGame(ctx context.Context, arg RemoveFavoriteGameParams) error {
_, err := q.db.Exec(ctx, RemoveFavoriteGame, arg.UserID, arg.GameID)
_, err := q.db.Exec(ctx, RemoveFavoriteGame, arg.UserID, arg.GameID, arg.ProviderID)
return err
}

View File

@ -19,9 +19,10 @@ type Response struct {
MetaData interface{} `json:"metadata"`
}
type CallbackErrorResponse struct {
Error string `json:"error,omitempty"`
}
// type CallbackErrorResponse struct {
// ErrorData interface
// Error string `json:"error,omitempty"`
// }
func CalculateWinnings(amount Currency, totalOdds float32) Currency {

View File

@ -1,5 +1,13 @@
package domain
type VeliCallbackErrorResponse struct {
ErrorStatus int `json:"errorStatus"`
ErrorData struct {
Error string `json:"error"`
Details *string `json:"details"`
} `json:"errorData"`
}
type ProviderRequest struct {
BrandID string `json:"brandId"`
ExtraData bool `json:"extraData"`

View File

@ -21,6 +21,7 @@ type FavoriteGame struct {
type FavoriteGameRequest struct {
GameID int64 `json:"game_id"`
ProviderID string `json:"provider_id"`
}
type FavoriteGameResponse struct {

View File

@ -28,10 +28,14 @@ type VirtualGameRepository interface {
GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error)
UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) error
// WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error
AddFavoriteGame(ctx context.Context, userID, gameID int64) error
RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error
ListFavoriteGames(ctx context.Context, userID int64) ([]int64, error)
AddFavoriteGame(ctx context.Context, userID, gameID int64, providerID string) error
RemoveFavoriteGame(ctx context.Context, userID, gameID int64, providerID string) error
ListFavoriteGames(
ctx context.Context,
userID int64,
providerID *string,
limit, offset int32,
) ([]dbgen.VirtualGame, error)
// GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
GetUserGameHistory(ctx context.Context, userID int64) ([]domain.VirtualGameHistory, error)
CreateVirtualGameHistory(ctx context.Context, his *domain.VirtualGameHistory) error
@ -138,24 +142,46 @@ func (r *VirtualGameRepo) UpdateVirtualGameProviderEnabled(ctx context.Context,
return r.store.queries.UpdateVirtualGameProviderEnabled(ctx, params)
}
func (r *VirtualGameRepo) AddFavoriteGame(ctx context.Context, userID, gameID int64) error {
func (r *VirtualGameRepo) AddFavoriteGame(ctx context.Context, userID, gameID int64, providerID string) error {
params := dbgen.AddFavoriteGameParams{
UserID: userID,
GameID: gameID,
ProviderID: providerID,
}
return r.store.queries.AddFavoriteGame(ctx, params)
}
func (r *VirtualGameRepo) RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error {
func (r *VirtualGameRepo) RemoveFavoriteGame(ctx context.Context, userID, gameID int64, providerID string) error {
params := dbgen.RemoveFavoriteGameParams{
UserID: userID,
GameID: gameID,
ProviderID: providerID,
}
return r.store.queries.RemoveFavoriteGame(ctx, params)
}
func (r *VirtualGameRepo) ListFavoriteGames(ctx context.Context, userID int64) ([]int64, error) {
return r.store.queries.ListFavoriteGames(ctx, userID)
func (r *VirtualGameRepo) ListFavoriteGames(
ctx context.Context,
userID int64,
providerID *string,
limit, offset int32,
) ([]dbgen.VirtualGame, error) {
params := dbgen.GetUserFavoriteGamesPaginatedParams{
UserID: userID,
Limit: limit,
Offset: offset,
}
if providerID != nil {
params.Column2 = *providerID
} else {
params.Column2 = ""
}
return r.store.queries.GetUserFavoriteGamesPaginated(ctx, params)
}
func (r *VirtualGameRepo) CountVirtualGameProviders(ctx context.Context) (int64, error) {

View File

@ -0,0 +1,71 @@
package orchestration
import (
"context"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)
func (s *Service) AddFavoriteGame(ctx context.Context, userID, gameID int64, providerID string) error {
return s.repo.AddFavoriteGame(ctx, userID, gameID, providerID)
}
func (s *Service) RemoveFavoriteGame(ctx context.Context, userID, gameID int64, providerID string) error {
return s.repo.RemoveFavoriteGame(ctx, userID, gameID, providerID)
}
func (s *Service) ListFavoriteGames(
ctx context.Context,
userID int64,
providerID *string,
limit, offset int32,
) ([]domain.UnifiedGame, error) {
// Fetch favorite games directly from repository
games, err := s.repo.ListFavoriteGames(ctx, userID, providerID, limit, offset)
if err != nil {
// s.logger.Error("Failed to list favorite games", "userID", userID, "error", err)
return nil, err
}
// If no favorites, return empty list
if len(games) == 0 {
return []domain.UnifiedGame{}, nil
}
favorites := make([]domain.UnifiedGame, 0, len(games))
for _, g := range games {
var rtpPtr *float64
if g.Rtp.Valid {
if f, err := g.Rtp.Float64Value(); err == nil {
rtpPtr = new(float64)
*rtpPtr = f.Float64
}
}
bets := make([]float64, len(g.Bets))
for i, b := range g.Bets {
bets[i] = float64(b.Exp)
}
favorites = append(favorites, domain.UnifiedGame{
GameID: g.GameID, // assuming g.GameID is string
ProviderID: g.ProviderID, // provider id
Provider: g.ProviderID, // you can map a full name if needed
Name: g.Name,
Category: g.Category.String, // nullable
DeviceType: g.DeviceType.String, // nullable
Volatility: g.Volatility.String, // nullable
RTP: rtpPtr,
HasDemo: g.HasDemo.Bool,
HasFreeBets: g.HasFreeBets.Bool,
Bets: bets,
Thumbnail: g.Thumbnail.String,
Status: int(g.Status.Int32),
// DemoURL: g..String,
})
}
return favorites, nil
}

View File

@ -0,0 +1 @@
package orchestration

View File

@ -297,7 +297,7 @@ func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.P
unified := domain.UnifiedGame{
GameID: fmt.Sprintf("%d", g.ID),
ProviderID: "popok",
Provider: "PopOK",
Provider: "Popok Gaming",
Name: g.GameName,
Category: "Crash",
Bets: g.Bets,
@ -416,6 +416,15 @@ func (s *Service) SetProviderEnabled(ctx context.Context, providerID string, ena
return domainProvider, nil
}
func (s *Service) CreateVirtualGameProviderReport(ctx context.Context, report domain.CreateVirtualGameProviderReport) (domain.VirtualGameProviderReport, error) {
// Example: logger := s.mongoLogger.With(zap.String("service", "CreateVirtualGameProviderReport"), zap.Any("Report", report))

View File

@ -25,7 +25,7 @@ type VirtualGameService interface {
// GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
ListGames(ctx context.Context, currency string) ([]domain.PopOKGame, error)
RecommendGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error)
AddFavoriteGame(ctx context.Context, userID, gameID int64) error
RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error
ListFavoriteGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error)
// AddFavoriteGame(ctx context.Context, userID, gameID int64) error
// RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error
// ListFavoriteGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error)
}

View File

@ -823,47 +823,5 @@ func toInt64Ptr(s string) *int64 {
return &id
}
func (s *service) AddFavoriteGame(ctx context.Context, userID, gameID int64) error {
return s.repo.AddFavoriteGame(ctx, userID, gameID)
}
func (s *service) RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error {
return s.repo.RemoveFavoriteGame(ctx, userID, gameID)
}
func (s *service) ListFavoriteGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error) {
gameIDs, err := s.repo.ListFavoriteGames(ctx, userID)
if err != nil {
s.logger.Error("Failed to list favorite games", "userID", userID, "error", err)
return nil, err
}
if len(gameIDs) == 0 {
return []domain.GameRecommendation{}, nil
}
allGames, err := s.ListGames(ctx, "ETB") // You can use dynamic currency if needed
if err != nil {
return nil, err
}
var favorites []domain.GameRecommendation
idMap := make(map[int64]bool)
for _, id := range gameIDs {
idMap[id] = true
}
for _, g := range allGames {
if idMap[int64(g.ID)] {
favorites = append(favorites, domain.GameRecommendation{
GameID: g.ID,
GameName: g.GameName,
Thumbnail: g.Thumbnail,
Bets: g.Bets,
Reason: "Marked as favorite",
})
}
}
return favorites, nil
}

View File

@ -148,7 +148,7 @@ type loginAdminReq struct {
}
// loginAdminRes represents the response body for the LoginAdmin endpoint.
type loginAdminRes struct {
type LoginAdminRes struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
Role string `json:"role"`

View File

@ -32,7 +32,7 @@ func (h *Handler) CreateDirectDeposit(c *fiber.Ctx) error {
// Call service
deposit, err := h.directDepositSvc.CreateDirectDeposit(c.Context(), req)
if err != nil {
h.logger.Error("CreateDirectDeposit error", err.Error())
// h.logger.Error("CreateDirectDeposit error", err.Error())
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Message: "Failed to create direct deposit",
Error: err.Error(),
@ -73,7 +73,7 @@ func (h *Handler) GetDirectDepositsByStatus(c *fiber.Ctx) error {
deposits, total, err := h.directDepositSvc.GetDirectDepositsByStatus(c.Context(), status, page, pageSize)
if err != nil {
h.logger.Error("GetDirectDepositsByStatus error", err)
// h.logger.Error("GetDirectDepositsByStatus error", err)
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Message: "Failed to fetch direct deposits",
Error: err.Error(),
@ -125,7 +125,7 @@ func (h *Handler) ApproveDirectDeposit(c *fiber.Ctx) error {
}
if err := h.directDepositSvc.ApproveDirectDeposit(c.Context(), depositID, adminID); err != nil {
h.logger.Error("ApproveDirectDeposit error", err)
// h.logger.Error("ApproveDirectDeposit error", err)
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Message: "Failed to approve direct deposit",
Error: err.Error(),
@ -178,7 +178,7 @@ func (h *Handler) RejectDirectDeposit(c *fiber.Ctx) error {
}
if err := h.directDepositSvc.RejectDirectDeposit(c.Context(), depositID, adminID, reason); err != nil {
h.logger.Error("RejectDirectDeposit error", err)
// h.logger.Error("RejectDirectDeposit error", err)
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Message: "Failed to reject direct deposit",
Error: err.Error(),
@ -215,7 +215,7 @@ func (h *Handler) GetDirectDepositByID(c *fiber.Ctx) error {
deposit, err := h.directDepositSvc.GetDirectDepositByID(c.Context(), depositID)
if err != nil {
h.logger.Error("GetDirectDepositByID error", err)
// h.logger.Error("GetDirectDepositByID error", err)
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Message: "Failed to fetch direct deposit",
Error: err.Error(),
@ -251,7 +251,7 @@ func (h *Handler) DeleteDirectDeposit(c *fiber.Ctx) error {
}
if err := h.directDepositSvc.DeleteDirectDeposit(c.Context(), depositID); err != nil {
h.logger.Error("DeleteDirectDeposit error", err)
// h.logger.Error("DeleteDirectDeposit error", err)
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Message: "Failed to delete direct deposit",
Error: err.Error(),

View File

@ -18,7 +18,7 @@ func ParseLeagueIDFromQuery(c *fiber.Ctx) (domain.ValidInt64, error) {
if leagueIDQuery != "" {
leagueIDInt, err := strconv.ParseInt(leagueIDQuery, 10, 64)
if err != nil {
return domain.ValidInt64{}, fmt.Errorf("Failed to parse league_id %v: %w", leagueIDQuery, err)
return domain.ValidInt64{}, fmt.Errorf("failed to parse league_id %v: %w", leagueIDQuery, err)
}
return domain.ValidInt64{
Value: leagueIDInt,

View File

@ -2,7 +2,6 @@ package handlers
import (
"encoding/json"
"errors"
"fmt"
"log"
"strconv"
@ -346,19 +345,40 @@ func (h *Handler) HandleBet(c *fiber.Ctx) error {
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{
return c.Status(fiber.StatusConflict).JSON(domain.VeliCallbackErrorResponse{
// Message: "Duplicate transaction",
ErrorStatus: 409,
ErrorData: struct {
Error string `json:"error"`
Details *string `json:"details"`
}{
Error: veli.ErrDuplicateTransaction.Error(),
Details: nil,
},
// ErrorData.Er: veli.ErrDuplicateTransaction.Error(),
})
} else if strings.Contains(err.Error(), veli.ErrInsufficientBalance.Error()) {
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
return c.Status(fiber.StatusConflict).JSON(domain.VeliCallbackErrorResponse{
// Message: "Wallet balance is insufficient",
ErrorStatus: 409,
ErrorData: struct {
Error string `json:"error"`
Details *string `json:"details"`
}{
Error: veli.ErrInsufficientBalance.Error(),
})
Details: nil,
}})
} else if strings.Contains(err.Error(), veli.ErrPlayerNotFound.Error()) {
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
return c.Status(fiber.StatusConflict).JSON(domain.VeliCallbackErrorResponse{
// Message: "User not found",
ErrorStatus: 409,
ErrorData: struct {
Error string `json:"error"`
Details *string `json:"details"`
}{
Error: veli.ErrPlayerNotFound.Error(),
Details: nil,
},
})
}
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
@ -472,15 +492,29 @@ func (h *Handler) HandleWin(c *fiber.Ctx) 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{
if strings.Contains(err.Error(), veli.ErrDuplicateTransaction.Error()) {
return c.Status(fiber.StatusConflict).JSON(domain.VeliCallbackErrorResponse{
// Message: "Duplicate transaction",
ErrorStatus: 409,
ErrorData: struct {
Error string `json:"error"`
Details *string `json:"details"`
}{
Error: veli.ErrDuplicateTransaction.Error(),
Details: nil,
},
})
} else if errors.Is(err, veli.ErrPlayerNotFound) {
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
} else if strings.Contains(err.Error(), veli.ErrPlayerNotFound.Error()) {
return c.Status(fiber.StatusConflict).JSON(domain.VeliCallbackErrorResponse{
// Message: "Duplicate transaction",
ErrorStatus: 409,
ErrorData: struct {
Error string `json:"error"`
Details *string `json:"details"`
}{
Error: veli.ErrPlayerNotFound.Error(),
Details: nil,
},
})
}
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
@ -550,14 +584,28 @@ func (h *Handler) HandleCancel(c *fiber.Ctx) 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{
return c.Status(fiber.StatusConflict).JSON(domain.VeliCallbackErrorResponse{
// Message: "Duplicate transaction",
ErrorStatus: 409,
ErrorData: struct {
Error string `json:"error"`
Details *string `json:"details"`
}{
Error: veli.ErrDuplicateTransaction.Error(),
Details: nil,
},
})
} else if strings.Contains(err.Error(), veli.ErrPlayerNotFound.Error()) {
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
return c.Status(fiber.StatusConflict).JSON(domain.VeliCallbackErrorResponse{
// Message: "User not found",
ErrorStatus: 409,
ErrorData: struct {
Error string `json:"error"`
Details *string `json:"details"`
}{
Error: veli.ErrPlayerNotFound.Error(),
Details: nil,
},
})
}
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
@ -696,7 +744,7 @@ func (h *Handler) HandlePromoWin(c *fiber.Ctx) error {
// @Tags VirtualGames - Favourites
// @Accept json
// @Produce json
// @Param body body domain.FavoriteGameRequest true "Game ID to add"
// @Param body body domain.FavoriteGameRequest true "Game ID and Provider ID to add"
// @Success 201 {string} domain.Response "created"
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
@ -706,23 +754,36 @@ func (h *Handler) AddFavorite(c *fiber.Ctx) error {
var req domain.FavoriteGameRequest
if err := c.BodyParser(&req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request")
// return fiber.NewError(fiber.StatusBadRequest, "Invalid request")
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Could not add favorite game",
Error: err.Error(),
})
}
err := h.virtualGameSvc.AddFavoriteGame(c.Context(), userID, req.GameID)
// Validate required fields
if req.GameID == 0 || req.ProviderID == "" {
// return fiber.NewError(fiber.StatusBadRequest, "game_id and provider_id are required")
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Could not remove favorite",
Error: "game_id and provider_id are required",
})
}
// Call service layer with providerID
err := h.orchestrationSvc.AddFavoriteGame(c.Context(), userID, req.GameID, req.ProviderID)
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
@ -731,19 +792,46 @@ func (h *Handler) AddFavorite(c *fiber.Ctx) error {
// @Tags VirtualGames - Favourites
// @Produce json
// @Param gameID path int64 true "Game ID to remove"
// @Success 200 {string} domain.Response "removed"
// @Param providerID query string true "Provider ID of the game"
// @Success 200 {object} 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")
var req domain.FavoriteGameRequest
if err := c.BodyParser(&req); err != nil {
// return fiber.NewError(fiber.StatusBadRequest, "Invalid request")
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Could not remove favorite",
Error: err.Error(),
})
}
return c.SendStatus(fiber.StatusOK)
// Parse gameID from path
if req.GameID == 0 || req.ProviderID == "" {
// return fiber.NewError(fiber.StatusBadRequest, "game_id and provider_id are required")
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Could not remove favorite",
Error: "game_id and provider_id are required",
})
}
// Call service layer
err := h.orchestrationSvc.RemoveFavoriteGame(c.Context(), userID, req.GameID, req.ProviderID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Could not remove favorite",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Game removed from favorites",
StatusCode: fiber.StatusOK,
Success: true,
})
}
// ListFavoriteGames godoc
@ -751,17 +839,45 @@ func (h *Handler) RemoveFavorite(c *fiber.Ctx) error {
// @Description Lists the games that the user marked as favorite
// @Tags VirtualGames - Favourites
// @Produce json
// @Success 200 {array} domain.GameRecommendation
// @Param providerID query string false "Filter by provider ID"
// @Param limit query int false "Number of results to return"
// @Param offset query int false "Results offset"
// @Success 200 {array} domain.UnifiedGame
// @Failure 400 {object} domain.ErrorResponse
// @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")
// Parse optional query params
providerIDQuery := c.Query("providerID")
var providerID *string
if providerIDQuery != "" {
providerID = &providerIDQuery
}
return c.Status(fiber.StatusOK).JSON(games)
limitQuery := c.QueryInt("limit", 10) // default limit 20
offsetQuery := c.QueryInt("offset", 0) // default offset 0
// Call service method
games, err := h.orchestrationSvc.ListFavoriteGames(c.Context(), userID, providerID, int32(limitQuery), int32(offsetQuery))
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Could not fetch favorites",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Favorite games retrieved successfully",
Data: games,
StatusCode: fiber.StatusOK,
Success: true,
MetaData: map[string]interface{}{
"offset": offsetQuery,
"limit": limitQuery,
},
})
}
func IdentifyBetProvider(body []byte) (string, error) {