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 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 ( CREATE TABLE IF NOT EXISTS virtual_game_reports (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
game_id VARCHAR(150) NOT NULL REFERENCES virtual_games(game_id) ON DELETE CASCADE, 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 AND vgt.created_at BETWEEN $1 AND $2
GROUP BY c.name, GROUP BY c.name,
vg.name; vg.name;
-- name: AddFavoriteGame :exec -- name: AddFavoriteGame :exec
INSERT INTO favorite_games (user_id, game_id, created_at) INSERT INTO virtual_game_favourites (user_id, game_id, provider_id, created_at)
VALUES ($1, $2, NOW()) ON CONFLICT (user_id, game_id) DO NOTHING; VALUES ($1, $2, $3, NOW())
ON CONFLICT (game_id, user_id) DO NOTHING;
-- name: RemoveFavoriteGame :exec -- name: RemoveFavoriteGame :exec
DELETE FROM favorite_games DELETE FROM virtual_game_favourites
WHERE user_id = $1 WHERE user_id = $1 AND game_id = $2 AND provider_id = $3;
AND game_id = $2;
-- name: ListFavoriteGames :many -- name: GetUserFavoriteGamesPaginated :many
SELECT game_id SELECT
FROM favorite_games vg.*
WHERE user_id = $1; 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 -- name: CreateVirtualGame :one
INSERT INTO virtual_games ( INSERT INTO virtual_games (
game_id, game_id,

View File

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

View File

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

View File

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

View File

@ -1157,9 +1157,16 @@ type VirtualGame struct {
UpdatedAt pgtype.Timestamptz `json:"updated_at"` 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 { type VirtualGameHistory struct {
ID int64 `json:"id"` ID int64 `json:"id"`
SessionID pgtype.Text `json:"session_id"`
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
CompanyID pgtype.Int8 `json:"company_id"` CompanyID pgtype.Int8 `json:"company_id"`
Provider pgtype.Text `json:"provider"` Provider pgtype.Text `json:"provider"`

View File

@ -12,17 +12,19 @@ import (
) )
const AddFavoriteGame = `-- name: AddFavoriteGame :exec const AddFavoriteGame = `-- name: AddFavoriteGame :exec
INSERT INTO favorite_games (user_id, game_id, created_at) INSERT INTO virtual_game_favourites (user_id, game_id, provider_id, created_at)
VALUES ($1, $2, NOW()) ON CONFLICT (user_id, game_id) DO NOTHING VALUES ($1, $2, $3, NOW())
ON CONFLICT (game_id, user_id) DO NOTHING
` `
type AddFavoriteGameParams struct { type AddFavoriteGameParams struct {
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
GameID int64 `json:"game_id"` GameID int64 `json:"game_id"`
ProviderID string `json:"provider_id"`
} }
func (q *Queries) AddFavoriteGame(ctx context.Context, arg AddFavoriteGameParams) error { 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 return err
} }
@ -137,7 +139,7 @@ func (q *Queries) CreateVirtualGame(ctx context.Context, arg CreateVirtualGamePa
const CreateVirtualGameHistory = `-- name: CreateVirtualGameHistory :one const CreateVirtualGameHistory = `-- name: CreateVirtualGameHistory :one
INSERT INTO virtual_game_histories ( INSERT INTO virtual_game_histories (
session_id, -- session_id,
user_id, user_id,
company_id, company_id,
provider, provider,
@ -161,11 +163,11 @@ VALUES (
$8, $8,
$9, $9,
$10, $10,
$11, $11
$12 -- $12
) )
RETURNING id, RETURNING id,
session_id, -- session_id,
user_id, user_id,
company_id, company_id,
provider, provider,
@ -182,7 +184,6 @@ RETURNING id,
` `
type CreateVirtualGameHistoryParams struct { type CreateVirtualGameHistoryParams struct {
SessionID pgtype.Text `json:"session_id"`
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
CompanyID pgtype.Int8 `json:"company_id"` CompanyID pgtype.Int8 `json:"company_id"`
Provider pgtype.Text `json:"provider"` Provider pgtype.Text `json:"provider"`
@ -198,7 +199,6 @@ type CreateVirtualGameHistoryParams struct {
func (q *Queries) CreateVirtualGameHistory(ctx context.Context, arg CreateVirtualGameHistoryParams) (VirtualGameHistory, error) { func (q *Queries) CreateVirtualGameHistory(ctx context.Context, arg CreateVirtualGameHistoryParams) (VirtualGameHistory, error) {
row := q.db.QueryRow(ctx, CreateVirtualGameHistory, row := q.db.QueryRow(ctx, CreateVirtualGameHistory,
arg.SessionID,
arg.UserID, arg.UserID,
arg.CompanyID, arg.CompanyID,
arg.Provider, arg.Provider,
@ -214,7 +214,6 @@ func (q *Queries) CreateVirtualGameHistory(ctx context.Context, arg CreateVirtua
var i VirtualGameHistory var i VirtualGameHistory
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.SessionID,
&i.UserID, &i.UserID,
&i.CompanyID, &i.CompanyID,
&i.Provider, &i.Provider,
@ -662,6 +661,67 @@ func (q *Queries) GetAllVirtualGames(ctx context.Context, arg GetAllVirtualGames
return items, nil 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 const GetVirtualGameProviderByID = `-- name: GetVirtualGameProviderByID :one
SELECT id, SELECT id,
provider_id, provider_id,
@ -874,32 +934,6 @@ func (q *Queries) GetVirtualGameTransactionByExternalID(ctx context.Context, ext
return i, err 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 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 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 FROM virtual_game_provider_reports
@ -1025,18 +1059,18 @@ func (q *Queries) ListVirtualGameProviders(ctx context.Context, arg ListVirtualG
} }
const RemoveFavoriteGame = `-- name: RemoveFavoriteGame :exec const RemoveFavoriteGame = `-- name: RemoveFavoriteGame :exec
DELETE FROM favorite_games DELETE FROM virtual_game_favourites
WHERE user_id = $1 WHERE user_id = $1 AND game_id = $2 AND provider_id = $3
AND game_id = $2
` `
type RemoveFavoriteGameParams struct { type RemoveFavoriteGameParams struct {
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
GameID int64 `json:"game_id"` GameID int64 `json:"game_id"`
ProviderID string `json:"provider_id"`
} }
func (q *Queries) RemoveFavoriteGame(ctx context.Context, arg RemoveFavoriteGameParams) error { 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 return err
} }

View File

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

View File

@ -1,5 +1,13 @@
package domain package domain
type VeliCallbackErrorResponse struct {
ErrorStatus int `json:"errorStatus"`
ErrorData struct {
Error string `json:"error"`
Details *string `json:"details"`
} `json:"errorData"`
}
type ProviderRequest struct { type ProviderRequest struct {
BrandID string `json:"brandId"` BrandID string `json:"brandId"`
ExtraData bool `json:"extraData"` ExtraData bool `json:"extraData"`
@ -43,8 +51,8 @@ type AtlasGameEntity struct {
Category string `json:"type"` Category string `json:"type"`
HasDemoMode bool `json:"has_demo"` HasDemoMode bool `json:"has_demo"`
HasFreeBets bool `json:"hasFreeBets"` HasFreeBets bool `json:"hasFreeBets"`
Thumbnail string `json:"thumbnail_img_url"` // ✅ new field Thumbnail string `json:"thumbnail_img_url"` // ✅ new field
DemoURL string `json:"demo_url"` // ✅ new field DemoURL string `json:"demo_url"` // ✅ new field
} }
type GameStartRequest struct { type GameStartRequest struct {

View File

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

View File

@ -28,10 +28,14 @@ type VirtualGameRepository interface {
GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error) GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error)
UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) error UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) error
// WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error // WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error
AddFavoriteGame(ctx context.Context, userID, gameID int64) error AddFavoriteGame(ctx context.Context, userID, gameID int64, providerID string) error
RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error RemoveFavoriteGame(ctx context.Context, userID, gameID int64, providerID string) error
ListFavoriteGames(ctx context.Context, userID int64) ([]int64, 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) // GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
GetUserGameHistory(ctx context.Context, userID int64) ([]domain.VirtualGameHistory, error) GetUserGameHistory(ctx context.Context, userID int64) ([]domain.VirtualGameHistory, error)
CreateVirtualGameHistory(ctx context.Context, his *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) 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{ params := dbgen.AddFavoriteGameParams{
UserID: userID, UserID: userID,
GameID: gameID, GameID: gameID,
ProviderID: providerID,
} }
return r.store.queries.AddFavoriteGame(ctx, params) 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{ params := dbgen.RemoveFavoriteGameParams{
UserID: userID, UserID: userID,
GameID: gameID, GameID: gameID,
ProviderID: providerID,
} }
return r.store.queries.RemoveFavoriteGame(ctx, params) return r.store.queries.RemoveFavoriteGame(ctx, params)
} }
func (r *VirtualGameRepo) ListFavoriteGames(ctx context.Context, userID int64) ([]int64, error) { func (r *VirtualGameRepo) ListFavoriteGames(
return r.store.queries.ListFavoriteGames(ctx, userID) 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) { func (r *VirtualGameRepo) CountVirtualGameProviders(ctx context.Context) (int64, error) {
@ -240,7 +266,7 @@ func (r *VirtualGameRepo) CreateVirtualGameTransaction(ctx context.Context, tx *
func (r *VirtualGameRepo) CreateVirtualGameHistory(ctx context.Context, his *domain.VirtualGameHistory) error { func (r *VirtualGameRepo) CreateVirtualGameHistory(ctx context.Context, his *domain.VirtualGameHistory) error {
params := dbgen.CreateVirtualGameHistoryParams{ params := dbgen.CreateVirtualGameHistoryParams{
// SessionID: pgtype.Text{String: his.SessionID, Valid: true}, // SessionID: pgtype.Text{String: his.SessionID, Valid: true},
UserID: his.UserID, UserID: his.UserID,
// WalletID: pgtype.Int8{Int64: *his.WalletID, Valid: true}, // WalletID: pgtype.Int8{Int64: *his.WalletID, Valid: true},
TransactionType: his.TransactionType, TransactionType: his.TransactionType,
Amount: his.Amount, Amount: his.Amount,
@ -261,7 +287,7 @@ func (r *VirtualGameRepo) GetVirtualGameTransactionByExternalID(ctx context.Cont
return nil, err return nil, err
} }
return &domain.VirtualGameTransaction{ return &domain.VirtualGameTransaction{
ID: dbTx.ID, ID: dbTx.ID,
// SessionID: dbTx.SessionID, // SessionID: dbTx.SessionID,
UserID: dbTx.UserID, UserID: dbTx.UserID,
WalletID: dbTx.WalletID, WalletID: dbTx.WalletID,

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{ unified := domain.UnifiedGame{
GameID: fmt.Sprintf("%d", g.ID), GameID: fmt.Sprintf("%d", g.ID),
ProviderID: "popok", ProviderID: "popok",
Provider: "PopOK", Provider: "Popok Gaming",
Name: g.GameName, Name: g.GameName,
Category: "Crash", Category: "Crash",
Bets: g.Bets, Bets: g.Bets,
@ -416,6 +416,15 @@ func (s *Service) SetProviderEnabled(ctx context.Context, providerID string, ena
return domainProvider, nil return domainProvider, nil
} }
func (s *Service) CreateVirtualGameProviderReport(ctx context.Context, report domain.CreateVirtualGameProviderReport) (domain.VirtualGameProviderReport, error) { 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)) // 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) // GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
ListGames(ctx context.Context, currency string) ([]domain.PopOKGame, error) ListGames(ctx context.Context, currency string) ([]domain.PopOKGame, error)
RecommendGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error) RecommendGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error)
AddFavoriteGame(ctx context.Context, userID, gameID int64) error // AddFavoriteGame(ctx context.Context, userID, gameID int64) error
RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error // RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error
ListFavoriteGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error) // ListFavoriteGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error)
} }

View File

@ -823,47 +823,5 @@ func toInt64Ptr(s string) *int64 {
return &id 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. // loginAdminRes represents the response body for the LoginAdmin endpoint.
type loginAdminRes struct { type LoginAdminRes struct {
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"` RefreshToken string `json:"refresh_token"`
Role string `json:"role"` Role string `json:"role"`

View File

@ -32,7 +32,7 @@ func (h *Handler) CreateDirectDeposit(c *fiber.Ctx) error {
// Call service // Call service
deposit, err := h.directDepositSvc.CreateDirectDeposit(c.Context(), req) deposit, err := h.directDepositSvc.CreateDirectDeposit(c.Context(), req)
if err != nil { 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{ return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Message: "Failed to create direct deposit", Message: "Failed to create direct deposit",
Error: err.Error(), 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) deposits, total, err := h.directDepositSvc.GetDirectDepositsByStatus(c.Context(), status, page, pageSize)
if err != nil { if err != nil {
h.logger.Error("GetDirectDepositsByStatus error", err) // h.logger.Error("GetDirectDepositsByStatus error", err)
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Message: "Failed to fetch direct deposits", Message: "Failed to fetch direct deposits",
Error: err.Error(), 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 { 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{ return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Message: "Failed to approve direct deposit", Message: "Failed to approve direct deposit",
Error: err.Error(), 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 { 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{ return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Message: "Failed to reject direct deposit", Message: "Failed to reject direct deposit",
Error: err.Error(), Error: err.Error(),
@ -215,7 +215,7 @@ func (h *Handler) GetDirectDepositByID(c *fiber.Ctx) error {
deposit, err := h.directDepositSvc.GetDirectDepositByID(c.Context(), depositID) deposit, err := h.directDepositSvc.GetDirectDepositByID(c.Context(), depositID)
if err != nil { if err != nil {
h.logger.Error("GetDirectDepositByID error", err) // h.logger.Error("GetDirectDepositByID error", err)
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Message: "Failed to fetch direct deposit", Message: "Failed to fetch direct deposit",
Error: err.Error(), 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 { 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{ return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Message: "Failed to delete direct deposit", Message: "Failed to delete direct deposit",
Error: err.Error(), Error: err.Error(),

View File

@ -18,7 +18,7 @@ func ParseLeagueIDFromQuery(c *fiber.Ctx) (domain.ValidInt64, error) {
if leagueIDQuery != "" { if leagueIDQuery != "" {
leagueIDInt, err := strconv.ParseInt(leagueIDQuery, 10, 64) leagueIDInt, err := strconv.ParseInt(leagueIDQuery, 10, 64)
if err != nil { 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{ return domain.ValidInt64{
Value: leagueIDInt, Value: leagueIDInt,

View File

@ -2,7 +2,6 @@ package handlers
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"log" "log"
"strconv" "strconv"
@ -346,19 +345,40 @@ func (h *Handler) HandleBet(c *fiber.Ctx) error {
res, err := h.veliVirtualGameSvc.ProcessBet(c.Context(), req) res, err := h.veliVirtualGameSvc.ProcessBet(c.Context(), req)
if err != nil { if err != nil {
if strings.Contains(err.Error(), veli.ErrDuplicateTransaction.Error()) { 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", // Message: "Duplicate transaction",
Error: veli.ErrDuplicateTransaction.Error(), 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()) { } 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", // Message: "Wallet balance is insufficient",
Error: veli.ErrInsufficientBalance.Error(), 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()) { } 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", // Message: "User not found",
Error: veli.ErrPlayerNotFound.Error(), 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{ 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) res, err := h.veliVirtualGameSvc.ProcessWin(c.Context(), req)
if err != nil { if err != nil {
if errors.Is(err, veli.ErrDuplicateTransaction) { 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", // Message: "Duplicate transaction",
Error: veli.ErrDuplicateTransaction.Error(), 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) { } 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: "Duplicate transaction", // Message: "Duplicate transaction",
Error: veli.ErrPlayerNotFound.Error(), 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{ 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) res, err := h.veliVirtualGameSvc.ProcessCancel(c.Context(), req)
if err != nil { if err != nil {
if strings.Contains(err.Error(), veli.ErrDuplicateTransaction.Error()) { 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", // Message: "Duplicate transaction",
Error: veli.ErrDuplicateTransaction.Error(), 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()) { } 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", // Message: "User not found",
Error: veli.ErrPlayerNotFound.Error(), 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{ return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
@ -696,7 +744,7 @@ func (h *Handler) HandlePromoWin(c *fiber.Ctx) error {
// @Tags VirtualGames - Favourites // @Tags VirtualGames - Favourites
// @Accept json // @Accept json
// @Produce 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" // @Success 201 {string} domain.Response "created"
// @Failure 400 {object} domain.ErrorResponse // @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse
@ -706,23 +754,36 @@ func (h *Handler) AddFavorite(c *fiber.Ctx) error {
var req domain.FavoriteGameRequest var req domain.FavoriteGameRequest
if err := c.BodyParser(&req); err != nil { 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 { if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Could not add favorite", Message: "Could not add favorite",
Error: err.Error(), Error: err.Error(),
}) })
// return fiber.NewError(fiber.StatusInternalServerError, "Could not add favorite")
} }
return c.Status(fiber.StatusCreated).JSON(domain.Response{ return c.Status(fiber.StatusCreated).JSON(domain.Response{
Message: "Game added to favorites", Message: "Game added to favorites",
StatusCode: fiber.StatusCreated, StatusCode: fiber.StatusCreated,
Success: true, Success: true,
}) })
// return c.SendStatus(fiber.StatusCreated)
} }
// RemoveFavoriteGame godoc // RemoveFavoriteGame godoc
@ -730,20 +791,47 @@ func (h *Handler) AddFavorite(c *fiber.Ctx) error {
// @Description Removes a game from the user's favorites // @Description Removes a game from the user's favorites
// @Tags VirtualGames - Favourites // @Tags VirtualGames - Favourites
// @Produce json // @Produce json
// @Param gameID path int64 true "Game ID to remove" // @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"
// @Failure 400 {object} domain.ErrorResponse // @Success 200 {object} domain.Response "removed"
// @Failure 500 {object} domain.ErrorResponse // @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/virtual-game/favorites/{gameID} [delete] // @Router /api/v1/virtual-game/favorites/{gameID} [delete]
func (h *Handler) RemoveFavorite(c *fiber.Ctx) error { func (h *Handler) RemoveFavorite(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
gameID, _ := strconv.ParseInt(c.Params("gameID"), 10, 64)
err := h.virtualGameSvc.RemoveFavoriteGame(c.Context(), userID, gameID) var req domain.FavoriteGameRequest
if err != nil { if err := c.BodyParser(&req); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Could not remove favorite") // 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 // ListFavoriteGames godoc
@ -751,17 +839,45 @@ func (h *Handler) RemoveFavorite(c *fiber.Ctx) error {
// @Description Lists the games that the user marked as favorite // @Description Lists the games that the user marked as favorite
// @Tags VirtualGames - Favourites // @Tags VirtualGames - Favourites
// @Produce json // @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 // @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/virtual-game/favorites [get] // @Router /api/v1/virtual-game/favorites [get]
func (h *Handler) ListFavorites(c *fiber.Ctx) error { func (h *Handler) ListFavorites(c *fiber.Ctx) error {
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
games, err := h.virtualGameSvc.ListFavoriteGames(c.Context(), userID) // Parse optional query params
if err != nil { providerIDQuery := c.Query("providerID")
return fiber.NewError(fiber.StatusInternalServerError, "Could not fetch favorites") 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) { func IdentifyBetProvider(body []byte) (string, error) {