Merge branch 'main' into ticket-bet

This commit is contained in:
Samuel Tariku 2025-08-27 21:37:15 +03:00
commit 42788e6f9f
20 changed files with 1092 additions and 49 deletions

View File

@ -153,7 +153,7 @@ func main() {
virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, cfg, logger)
aleaService := alea.NewAleaPlayService(vitualGameRepo, *walletSvc, cfg, logger)
veliCLient := veli.NewClient(cfg, walletSvc)
veliVirtualGameService := veli.New(veliCLient, walletSvc, wallet.TransferStore(store), cfg)
veliVirtualGameService := veli.New(vitualGameRepo, veliCLient, walletSvc, wallet.TransferStore(store), cfg)
recommendationSvc := recommendation.NewService(recommendationRepo)
chapaClient := chapa.NewClient(cfg.CHAPA_BASE_URL, cfg.CHAPA_SECRET_KEY)
@ -187,7 +187,7 @@ func main() {
logger,
)
go httpserver.SetupReportCronJobs(context.Background(), reportSvc, "C:/Users/User/Desktop")
go httpserver.SetupReportandVirtualGameCronJobs(context.Background(), reportSvc, veliVirtualGameService, "C:/Users/User/Desktop")
go httpserver.ProcessBetCashback(context.TODO(), betSvc)
bankRepository := repository.NewBankRepository(store)

View File

@ -1,3 +1,4 @@
DROP TABLE IF EXISTS virtual_game_providers;
-- Drop tables that depend on service_type_setting
DROP TABLE IF EXISTS service_type_setting;
-- Drop product-related tables and types

View File

@ -21,6 +21,17 @@ CREATE TABLE IF NOT EXISTS users (
UNIQUE(email, company_id),
UNIQUE (phone_number, company_id)
);
CREATE TABLE IF NOT EXISTS virtual_game_providers (
id BIGSERIAL PRIMARY KEY,
provider_id VARCHAR(100) UNIQUE NOT NULL, -- providerId from Veli Games
provider_name VARCHAR(255) NOT NULL, -- providerName
logo_dark TEXT, -- logoForDark (URL)
logo_light TEXT, -- logoForLight (URL)
enabled BOOLEAN NOT NULL DEFAULT TRUE, -- allow enabling/disabling providers
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ
);
CREATE TABLE IF NOT EXISTS wallets (
id BIGSERIAL PRIMARY KEY,
balance BIGINT NOT NULL DEFAULT 0,

View File

@ -1,3 +1,39 @@
-- name: CreateVirtualGameProvider :one
INSERT INTO virtual_game_providers (
provider_id, provider_name, logo_dark, logo_light, enabled
) VALUES (
$1, $2, $3, $4, $5
) RETURNING id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at;
-- name: DeleteVirtualGameProvider :exec
DELETE FROM virtual_game_providers
WHERE provider_id = $1;
-- name: DeleteAllVirtualGameProviders :exec
DELETE FROM virtual_game_providers;
-- name: GetVirtualGameProviderByID :one
SELECT id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at
FROM virtual_game_providers
WHERE provider_id = $1;
-- name: ListVirtualGameProviders :many
SELECT id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at
FROM virtual_game_providers
ORDER BY created_at DESC
LIMIT $1 OFFSET $2;
-- name: CountVirtualGameProviders :one
SELECT COUNT(*) AS total
FROM virtual_game_providers;
-- name: UpdateVirtualGameProviderEnabled :one
UPDATE virtual_game_providers
SET enabled = $2,
updated_at = CURRENT_TIMESTAMP
WHERE provider_id = $1
RETURNING id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at;
-- name: CreateVirtualGameSession :one
INSERT INTO virtual_game_sessions (
user_id, game_id, session_token, currency, status, expires_at

View File

@ -7011,6 +7011,151 @@ const docTemplate = `{
}
}
},
"/api/v1/virtual-game/orchestrator/providers/status": {
"patch": {
"description": "Sets the enabled status of a provider",
"produces": [
"application/json"
],
"tags": [
"VirtualGames - Orchestration"
],
"summary": "Enable/Disable a provider",
"parameters": [
{
"type": "string",
"description": "Provider ID",
"name": "provider_id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Enable or Disable",
"name": "enabled",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.VirtualGameProvider"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/virtual-game/providers": {
"get": {
"description": "Lists all providers with pagination",
"produces": [
"application/json"
],
"tags": [
"VirtualGames - Orchestration"
],
"summary": "List virtual game providers",
"parameters": [
{
"type": "integer",
"description": "Limit",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"description": "Offset",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/virtual-game/providers/{provider_id}": {
"get": {
"description": "Fetches a provider by provider_id",
"produces": [
"application/json"
],
"tags": [
"VirtualGames - Orchestration"
],
"summary": "Get a virtual game provider",
"parameters": [
{
"type": "string",
"description": "Provider ID",
"name": "provider_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.VirtualGameProvider"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
},
"delete": {
"description": "Deletes a provider by provider_id",
"tags": [
"VirtualGames - Orchestration"
],
"summary": "Remove a virtual game provider",
"parameters": [
{
"type": "string",
"description": "Provider ID",
"name": "provider_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/wallet": {
"get": {
"description": "Retrieve all wallets",
@ -9950,6 +10095,33 @@ const docTemplate = `{
}
}
},
"domain.VirtualGameProvider": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"enabled": {
"type": "boolean"
},
"logo_dark": {
"type": "string"
},
"logo_light": {
"type": "string"
},
"provider_id": {
"description": "ID int64 ` + "`" + `json:\"id\" db:\"id\"` + "`" + `",
"type": "string"
},
"provider_name": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"domain.WebhookRequest": {
"type": "object",
"properties": {

View File

@ -7003,6 +7003,151 @@
}
}
},
"/api/v1/virtual-game/orchestrator/providers/status": {
"patch": {
"description": "Sets the enabled status of a provider",
"produces": [
"application/json"
],
"tags": [
"VirtualGames - Orchestration"
],
"summary": "Enable/Disable a provider",
"parameters": [
{
"type": "string",
"description": "Provider ID",
"name": "provider_id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Enable or Disable",
"name": "enabled",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.VirtualGameProvider"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/virtual-game/providers": {
"get": {
"description": "Lists all providers with pagination",
"produces": [
"application/json"
],
"tags": [
"VirtualGames - Orchestration"
],
"summary": "List virtual game providers",
"parameters": [
{
"type": "integer",
"description": "Limit",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"description": "Offset",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/virtual-game/providers/{provider_id}": {
"get": {
"description": "Fetches a provider by provider_id",
"produces": [
"application/json"
],
"tags": [
"VirtualGames - Orchestration"
],
"summary": "Get a virtual game provider",
"parameters": [
{
"type": "string",
"description": "Provider ID",
"name": "provider_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.VirtualGameProvider"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
},
"delete": {
"description": "Deletes a provider by provider_id",
"tags": [
"VirtualGames - Orchestration"
],
"summary": "Remove a virtual game provider",
"parameters": [
{
"type": "string",
"description": "Provider ID",
"name": "provider_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/domain.ErrorResponse"
}
}
}
}
},
"/api/v1/wallet": {
"get": {
"description": "Retrieve all wallets",
@ -9942,6 +10087,33 @@
}
}
},
"domain.VirtualGameProvider": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"enabled": {
"type": "boolean"
},
"logo_dark": {
"type": "string"
},
"logo_light": {
"type": "string"
},
"provider_id": {
"description": "ID int64 `json:\"id\" db:\"id\"`",
"type": "string"
},
"provider_name": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"domain.WebhookRequest": {
"type": "object",
"properties": {

View File

@ -1743,6 +1743,24 @@ definitions:
- deposit_id
- is_verified
type: object
domain.VirtualGameProvider:
properties:
created_at:
type: string
enabled:
type: boolean
logo_dark:
type: string
logo_light:
type: string
provider_id:
description: ID int64 `json:"id" db:"id"`
type: string
provider_name:
type: string
updated_at:
type: string
type: object
domain.WebhookRequest:
properties:
nonce:
@ -7072,6 +7090,102 @@ paths:
summary: Remove game from favorites
tags:
- VirtualGames - Favourites
/api/v1/virtual-game/orchestrator/providers/status:
patch:
description: Sets the enabled status of a provider
parameters:
- description: Provider ID
in: path
name: provider_id
required: true
type: string
- description: Enable or Disable
in: query
name: enabled
required: true
type: boolean
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.VirtualGameProvider'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Enable/Disable a provider
tags:
- VirtualGames - Orchestration
/api/v1/virtual-game/providers:
get:
description: Lists all providers with pagination
parameters:
- description: Limit
in: query
name: limit
type: integer
- description: Offset
in: query
name: offset
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties: true
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: List virtual game providers
tags:
- VirtualGames - Orchestration
/api/v1/virtual-game/providers/{provider_id}:
delete:
description: Deletes a provider by provider_id
parameters:
- description: Provider ID
in: path
name: provider_id
required: true
type: string
responses:
"200":
description: OK
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Remove a virtual game provider
tags:
- VirtualGames - Orchestration
get:
description: Fetches a provider by provider_id
parameters:
- description: Provider ID
in: path
name: provider_id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.VirtualGameProvider'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/domain.ErrorResponse'
summary: Get a virtual game provider
tags:
- VirtualGames - Orchestration
/api/v1/wallet:
get:
consumes:

View File

@ -843,6 +843,17 @@ type VirtualGameHistory struct {
UpdatedAt pgtype.Timestamp `json:"updated_at"`
}
type VirtualGameProvider struct {
ID int64 `json:"id"`
ProviderID string `json:"provider_id"`
ProviderName string `json:"provider_name"`
LogoDark pgtype.Text `json:"logo_dark"`
LogoLight pgtype.Text `json:"logo_light"`
Enabled bool `json:"enabled"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type VirtualGameSession struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`

View File

@ -30,6 +30,18 @@ func (q *Queries) AddFavoriteGame(ctx context.Context, arg AddFavoriteGameParams
return err
}
const CountVirtualGameProviders = `-- name: CountVirtualGameProviders :one
SELECT COUNT(*) AS total
FROM virtual_game_providers
`
func (q *Queries) CountVirtualGameProviders(ctx context.Context) (int64, error) {
row := q.db.QueryRow(ctx, CountVirtualGameProviders)
var total int64
err := row.Scan(&total)
return total, err
}
const CreateVirtualGameHistory = `-- name: CreateVirtualGameHistory :one
INSERT INTO virtual_game_histories (
session_id,
@ -115,6 +127,44 @@ func (q *Queries) CreateVirtualGameHistory(ctx context.Context, arg CreateVirtua
return i, err
}
const CreateVirtualGameProvider = `-- name: CreateVirtualGameProvider :one
INSERT INTO virtual_game_providers (
provider_id, provider_name, logo_dark, logo_light, enabled
) VALUES (
$1, $2, $3, $4, $5
) RETURNING id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at
`
type CreateVirtualGameProviderParams struct {
ProviderID string `json:"provider_id"`
ProviderName string `json:"provider_name"`
LogoDark pgtype.Text `json:"logo_dark"`
LogoLight pgtype.Text `json:"logo_light"`
Enabled bool `json:"enabled"`
}
func (q *Queries) CreateVirtualGameProvider(ctx context.Context, arg CreateVirtualGameProviderParams) (VirtualGameProvider, error) {
row := q.db.QueryRow(ctx, CreateVirtualGameProvider,
arg.ProviderID,
arg.ProviderName,
arg.LogoDark,
arg.LogoLight,
arg.Enabled,
)
var i VirtualGameProvider
err := row.Scan(
&i.ID,
&i.ProviderID,
&i.ProviderName,
&i.LogoDark,
&i.LogoLight,
&i.Enabled,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const CreateVirtualGameSession = `-- name: CreateVirtualGameSession :one
INSERT INTO virtual_game_sessions (
user_id, game_id, session_token, currency, status, expires_at
@ -225,6 +275,47 @@ func (q *Queries) CreateVirtualGameTransaction(ctx context.Context, arg CreateVi
return i, err
}
const DeleteAllVirtualGameProviders = `-- name: DeleteAllVirtualGameProviders :exec
DELETE FROM virtual_game_providers
`
func (q *Queries) DeleteAllVirtualGameProviders(ctx context.Context) error {
_, err := q.db.Exec(ctx, DeleteAllVirtualGameProviders)
return err
}
const DeleteVirtualGameProvider = `-- name: DeleteVirtualGameProvider :exec
DELETE FROM virtual_game_providers
WHERE provider_id = $1
`
func (q *Queries) DeleteVirtualGameProvider(ctx context.Context, providerID string) error {
_, err := q.db.Exec(ctx, DeleteVirtualGameProvider, providerID)
return err
}
const GetVirtualGameProviderByID = `-- name: GetVirtualGameProviderByID :one
SELECT id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at
FROM virtual_game_providers
WHERE provider_id = $1
`
func (q *Queries) GetVirtualGameProviderByID(ctx context.Context, providerID string) (VirtualGameProvider, error) {
row := q.db.QueryRow(ctx, GetVirtualGameProviderByID, providerID)
var i VirtualGameProvider
err := row.Scan(
&i.ID,
&i.ProviderID,
&i.ProviderName,
&i.LogoDark,
&i.LogoLight,
&i.Enabled,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const GetVirtualGameSessionByToken = `-- name: GetVirtualGameSessionByToken :one
SELECT id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at
FROM virtual_game_sessions
@ -365,6 +456,47 @@ func (q *Queries) ListFavoriteGames(ctx context.Context, userID int64) ([]int64,
return items, nil
}
const ListVirtualGameProviders = `-- name: ListVirtualGameProviders :many
SELECT id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at
FROM virtual_game_providers
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`
type ListVirtualGameProvidersParams struct {
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) ListVirtualGameProviders(ctx context.Context, arg ListVirtualGameProvidersParams) ([]VirtualGameProvider, error) {
rows, err := q.db.Query(ctx, ListVirtualGameProviders, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []VirtualGameProvider
for rows.Next() {
var i VirtualGameProvider
if err := rows.Scan(
&i.ID,
&i.ProviderID,
&i.ProviderName,
&i.LogoDark,
&i.LogoLight,
&i.Enabled,
&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 RemoveFavoriteGame = `-- name: RemoveFavoriteGame :exec
DELETE FROM favorite_games
WHERE user_id = $1 AND game_id = $2
@ -380,6 +512,35 @@ func (q *Queries) RemoveFavoriteGame(ctx context.Context, arg RemoveFavoriteGame
return err
}
const UpdateVirtualGameProviderEnabled = `-- name: UpdateVirtualGameProviderEnabled :one
UPDATE virtual_game_providers
SET enabled = $2,
updated_at = CURRENT_TIMESTAMP
WHERE provider_id = $1
RETURNING id, provider_id, provider_name, logo_dark, logo_light, enabled, created_at, updated_at
`
type UpdateVirtualGameProviderEnabledParams struct {
ProviderID string `json:"provider_id"`
Enabled bool `json:"enabled"`
}
func (q *Queries) UpdateVirtualGameProviderEnabled(ctx context.Context, arg UpdateVirtualGameProviderEnabledParams) (VirtualGameProvider, error) {
row := q.db.QueryRow(ctx, UpdateVirtualGameProviderEnabled, arg.ProviderID, arg.Enabled)
var i VirtualGameProvider
err := row.Scan(
&i.ID,
&i.ProviderID,
&i.ProviderName,
&i.LogoDark,
&i.LogoLight,
&i.Enabled,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const UpdateVirtualGameSessionStatus = `-- name: UpdateVirtualGameSessionStatus :exec
UPDATE virtual_game_sessions
SET status = $2, updated_at = CURRENT_TIMESTAMP

View File

@ -107,20 +107,6 @@ type VirtualGameTransaction struct {
GameSpecificData GameSpecificData `json:"game_specific_data"`
}
// type VirtualGameTransaction struct {
// ID int64 `json:"id"`
// SessionID int64 `json:"session_id"`
// UserID int64 `json:"user_id"`
// WalletID int64 `json:"wallet_id"`
// TransactionType string `json:"transaction_type"` // BET, WIN, REFUND, JACKPOT_WIN
// Amount int64 `json:"amount"`
// Currency string `json:"currency"`
// ExternalTransactionID string `json:"external_transaction_id"`
// Status string `json:"status"` // PENDING, COMPLETED, FAILED
// CreatedAt time.Time `json:"created_at"`
// UpdatedAt time.Time `json:"updated_at"`
// }
type CreateVirtualGameSession struct {
UserID int64
GameID string
@ -294,3 +280,22 @@ type PopokLaunchResponse struct {
LauncherURL string `json:"launcherURL"`
} `json:"data"`
}
type VirtualGameProvider struct {
// ID int64 `json:"id" db:"id"`
ProviderID string `json:"provider_id" db:"provider_id"`
ProviderName string `json:"provider_name" db:"provider_name"`
LogoDark *string `json:"logo_dark,omitempty" db:"logo_dark"`
LogoLight *string `json:"logo_light,omitempty" db:"logo_light"`
Enabled bool `json:"enabled" db:"enabled"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt *time.Time `json:"updated_at,omitempty" db:"updated_at"`
}
// VirtualGameProviderPagination is used when returning paginated results
type VirtualGameProviderPagination struct {
Providers []VirtualGameProvider `json:"providers"`
TotalCount int64 `json:"total_count"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}

View File

@ -12,6 +12,13 @@ import (
)
type VirtualGameRepository interface {
CountVirtualGameProviders(ctx context.Context) (int64, error)
CreateVirtualGameProvider(ctx context.Context, arg dbgen.CreateVirtualGameProviderParams) (dbgen.VirtualGameProvider, error)
DeleteVirtualGameProvider(ctx context.Context, providerID string) error
DeleteAllVirtualGameProviders(ctx context.Context) error
GetVirtualGameProviderByID(ctx context.Context, providerID string) (dbgen.VirtualGameProvider, error)
ListVirtualGameProviders(ctx context.Context, limit, offset int32) ([]dbgen.VirtualGameProvider, error)
UpdateVirtualGameProviderEnabled(ctx context.Context, providerID string, enabled bool) (dbgen.VirtualGameProvider, error)
CreateVirtualGameSession(ctx context.Context, session *domain.VirtualGameSession) error
GetVirtualGameSessionByToken(ctx context.Context, token string) (*domain.VirtualGameSession, error)
UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error
@ -37,10 +44,88 @@ type VirtualGameRepo struct {
// panic("unimplemented")
// }
// func convertDBVirtualGameProvider(p dbgen.VirtualGameProvider) domain.VirtualGameProvider {
// var logoDark *string
// if p.LogoDark.Valid {
// logoDark = &p.LogoDark.String
// }
// var logoLight *string
// if p.LogoLight.Valid {
// logoLight = &p.LogoLight.String
// }
// return domain.VirtualGameProvider{
// // ID: p.ID,
// ProviderID: p.ProviderID,
// ProviderName: p.ProviderName,
// LogoDark: logoDark,
// LogoLight: logoLight,
// Enabled: p.Enabled,
// CreatedAt: p.CreatedAt.Time,
// UpdatedAt: &p.UpdatedAt.Time,
// }
// }
func ConvertCreateVirtualGameProvider(p domain.VirtualGameProvider) dbgen.CreateVirtualGameProviderParams {
return dbgen.CreateVirtualGameProviderParams{
ProviderID: p.ProviderID,
ProviderName: p.ProviderName,
LogoDark: pgtype.Text{String: func() string {
if p.LogoDark != nil {
return *p.LogoDark
}
return ""
}(), Valid: p.LogoDark != nil},
LogoLight: pgtype.Text{String: func() string {
if p.LogoLight != nil {
return *p.LogoLight
}
return ""
}(), Valid: p.LogoLight != nil},
Enabled: p.Enabled,
// CreatedAt: time.Now(),
}
}
func NewVirtualGameRepository(store *Store) VirtualGameRepository {
return &VirtualGameRepo{store: store}
}
func (r *VirtualGameRepo) CreateVirtualGameProvider(ctx context.Context, arg dbgen.CreateVirtualGameProviderParams) (dbgen.VirtualGameProvider, error) {
return r.store.queries.CreateVirtualGameProvider(ctx, arg)
}
func (r *VirtualGameRepo) RemoveVirtualGameProvider(ctx context.Context, arg dbgen.CreateVirtualGameProviderParams) (dbgen.VirtualGameProvider, error) {
return r.store.queries.CreateVirtualGameProvider(ctx, arg)
}
func (r *VirtualGameRepo) DeleteAllVirtualGameProviders(ctx context.Context) error {
return r.store.queries.DeleteAllVirtualGameProviders(ctx)
}
func (r *VirtualGameRepo) DeleteVirtualGameProvider(ctx context.Context, providerID string) error {
return r.store.queries.DeleteVirtualGameProvider(ctx, providerID)
}
func (r *VirtualGameRepo) GetVirtualGameProviderByID(ctx context.Context, providerID string) (dbgen.VirtualGameProvider, error) {
return r.store.queries.GetVirtualGameProviderByID(ctx, providerID)
}
func (r *VirtualGameRepo) ListVirtualGameProviders(ctx context.Context, limit, offset int32) ([]dbgen.VirtualGameProvider, error) {
args := dbgen.ListVirtualGameProvidersParams{
Limit: limit,
Offset: offset,
}
return r.store.queries.ListVirtualGameProviders(ctx, args)
}
func (r *VirtualGameRepo) UpdateVirtualGameProviderEnabled(ctx context.Context, providerID string, enabled bool) (dbgen.VirtualGameProvider, error) {
params := dbgen.UpdateVirtualGameProviderEnabledParams{
ProviderID: providerID,
Enabled: enabled,
}
return r.store.queries.UpdateVirtualGameProviderEnabled(ctx, params)
}
func (r *VirtualGameRepo) AddFavoriteGame(ctx context.Context, userID, gameID int64) error {
params := dbgen.AddFavoriteGameParams{
UserID: userID,
@ -61,6 +146,10 @@ func (r *VirtualGameRepo) ListFavoriteGames(ctx context.Context, userID int64) (
return r.store.queries.ListFavoriteGames(ctx, userID)
}
func (r *VirtualGameRepo) CountVirtualGameProviders(ctx context.Context) (int64, error) {
return r.store.queries.CountVirtualGameProviders(ctx)
}
func (r *VirtualGameRepo) CreateVirtualGameSession(ctx context.Context, session *domain.VirtualGameSession) error {
params := dbgen.CreateVirtualGameSessionParams{
UserID: session.UserID,

View File

@ -0,0 +1,69 @@
package virtualgameservice
import (
"context"
"time"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)
// Remove a provider by provider_id
func (s *service) RemoveProvider(ctx context.Context, providerID string) error {
return s.repo.DeleteVirtualGameProvider(ctx, providerID)
}
// Fetch provider by provider_id
func (s *service) GetProviderByID(ctx context.Context, providerID string) (dbgen.VirtualGameProvider, error) {
return s.repo.GetVirtualGameProviderByID(ctx, providerID)
}
// List providers with pagination
func (s *service) ListProviders(ctx context.Context, limit, offset int32) ([]domain.VirtualGameProvider, int64, error) {
providers, err := s.repo.ListVirtualGameProviders(ctx, limit, offset)
if err != nil {
return nil, 0, err
}
total, err := s.repo.CountVirtualGameProviders(ctx)
if err != nil {
return nil, 0, err
}
// Convert []dbgen.VirtualGameProvider to []domain.VirtualGameProvider
domainProviders := make([]domain.VirtualGameProvider, len(providers))
for i, p := range providers {
domainProviders[i] = domain.VirtualGameProvider{
ProviderID: p.ProviderID,
ProviderName: p.ProviderName,
Enabled: p.Enabled,
CreatedAt: p.CreatedAt.Time,
UpdatedAt: &p.UpdatedAt.Time,
// Add other fields as needed
}
}
return domainProviders, total, nil
}
// Enable/Disable a provider
func (s *service) SetProviderEnabled(ctx context.Context, providerID string, enabled bool) (*domain.VirtualGameProvider, error) {
provider, err := s.repo.UpdateVirtualGameProviderEnabled(ctx, providerID, enabled)
if err != nil {
s.logger.Error("Failed to update provider enabled status", "provider_id", providerID, "enabled", enabled, "error", err)
return nil, err
}
now := time.Now()
provider.UpdatedAt.Time = now
domainProvider := &domain.VirtualGameProvider{
ProviderID: provider.ProviderID,
ProviderName: provider.ProviderName,
Enabled: provider.Enabled,
CreatedAt: provider.CreatedAt.Time,
UpdatedAt: &provider.UpdatedAt.Time,
// Add other fields as needed
}
return domainProvider, nil
}

View File

@ -3,10 +3,17 @@ package virtualgameservice
import (
"context"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)
type VirtualGameService interface {
// AddProvider(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error)
RemoveProvider(ctx context.Context, providerID string) error
GetProviderByID(ctx context.Context, providerID string) (dbgen.VirtualGameProvider, error)
ListProviders(ctx context.Context, limit, offset int32) ([]domain.VirtualGameProvider, int64, error)
SetProviderEnabled(ctx context.Context, providerID string, enabled bool) (*domain.VirtualGameProvider, error)
GenerateGameLaunchURL(ctx context.Context, userID int64, gameID, currency, mode string) (string, error)
HandleCallback(ctx context.Context, callback *domain.PopOKCallback) error
ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) (*domain.PopOKBetResponse, error)

View File

@ -39,7 +39,8 @@ func New(repo repository.VirtualGameRepository, walletSvc wallet.Service, store
walletSvc: walletSvc,
store: store,
config: cfg,
logger: logger}
logger: logger,
}
}
func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameID, currency, mode string) (string, error) {

View File

@ -0,0 +1,59 @@
package veli
import (
"context"
"fmt"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/jackc/pgx/v5/pgtype"
)
func (s *Service) AddProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error) {
// 0. Remove all existing providers first
if err := s.repo.DeleteAllVirtualGameProviders(ctx); err != nil {
return nil, fmt.Errorf("failed to clear existing providers: %w", err)
}
// 1. Prepare signature parameters
sigParams := map[string]any{
"brandId": req.BrandID,
}
// Optional fields
if req.Size > 0 {
sigParams["size"] = fmt.Sprintf("%d", req.Size)
} else {
sigParams["size"] = ""
}
if req.Page > 0 {
sigParams["page"] = fmt.Sprintf("%d", req.Page)
} else {
sigParams["page"] = ""
}
// 2. Call external API
var res domain.ProviderResponse
if err := s.client.post(ctx, "/game-lists/public/providers", req, sigParams, &res); err != nil {
return nil, fmt.Errorf("failed to fetch providers: %w", err)
}
// 3. Loop through fetched providers and insert into DB
for _, p := range res.Items {
createParams := dbgen.CreateVirtualGameProviderParams{
ProviderID: p.ProviderID,
ProviderName: p.ProviderName,
LogoDark: pgtype.Text{String: p.LogoForDark, Valid: true},
LogoLight: pgtype.Text{String: p.LogoForLight, Valid: true},
Enabled: true,
}
if _, err := s.repo.CreateVirtualGameProvider(ctx, createParams); err != nil {
// Log error but continue with other providers
return nil, fmt.Errorf("failed to add provider %s: %w", p.ProviderID, err)
}
}
return &res, nil
}

View File

@ -8,6 +8,7 @@ import (
)
type VeliVirtualGameService interface {
AddProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error)
GetProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error)
GetGames(ctx context.Context, req domain.GameListRequest) ([]domain.GameEntity, error)
StartGame(ctx context.Context, req domain.GameStartRequest) (*domain.GameStartResponse, error)

View File

@ -8,6 +8,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
)
@ -18,23 +19,25 @@ var (
ErrDuplicateTransaction = errors.New("DUPLICATE_TRANSACTION")
)
type service struct {
type Service struct {
repo repository.VirtualGameRepository
client *Client
walletSvc *wallet.Service
transfetStore wallet.TransferStore
cfg *config.Config
}
func New(client *Client, walletSvc *wallet.Service, transferStore wallet.TransferStore, cfg *config.Config) VeliVirtualGameService {
return &service{
func New(repo repository.VirtualGameRepository,client *Client, walletSvc *wallet.Service, transferStore wallet.TransferStore, cfg *config.Config) *Service {
return &Service{
repo: repo,
client: client,
walletSvc: walletSvc,
transfetStore: transferStore,
cfg: cfg,
}
}
}
func (s *service) GetProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error) {
func (s *Service) GetProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error) {
// Always mirror request body fields into sigParams
sigParams := map[string]any{
"brandId": req.BrandID,
@ -58,7 +61,7 @@ func (s *service) GetProviders(ctx context.Context, req domain.ProviderRequest)
return &res, err
}
func (s *service) GetGames(ctx context.Context, req domain.GameListRequest) ([]domain.GameEntity, error) {
func (s *Service) GetGames(ctx context.Context, req domain.GameListRequest) ([]domain.GameEntity, error) {
sigParams := map[string]any{
"brandId": req.BrandID, "providerId": req.ProviderID,
}
@ -69,7 +72,7 @@ func (s *service) GetGames(ctx context.Context, req domain.GameListRequest) ([]d
return res.Items, err
}
func (s *service) StartGame(ctx context.Context, req domain.GameStartRequest) (*domain.GameStartResponse, error) {
func (s *Service) StartGame(ctx context.Context, req domain.GameStartRequest) (*domain.GameStartResponse, error) {
sigParams := map[string]any{
"sessionId": req.SessionID, "providerId": req.ProviderID,
"gameId": req.GameID, "language": req.Language, "playerId": req.PlayerID,
@ -81,7 +84,7 @@ func (s *service) StartGame(ctx context.Context, req domain.GameStartRequest) (*
return &res, err
}
func (s *service) StartDemoGame(ctx context.Context, req domain.DemoGameRequest) (*domain.GameStartResponse, error) {
func (s *Service) StartDemoGame(ctx context.Context, req domain.DemoGameRequest) (*domain.GameStartResponse, error) {
sigParams := map[string]any{
"providerId": req.ProviderID, "gameId": req.GameID,
"language": req.Language, "deviceType": req.DeviceType,
@ -92,8 +95,8 @@ func (s *service) StartDemoGame(ctx context.Context, req domain.DemoGameRequest)
return &res, err
}
func (s *service) GetBalance(ctx context.Context, req domain.BalanceRequest) (*domain.BalanceResponse, error) {
// Retrieve player's real balance from wallet service
func (s *Service) GetBalance(ctx context.Context, req domain.BalanceRequest) (*domain.BalanceResponse, error) {
// Retrieve player's real balance from wallet Service
playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid PlayerID: %w", err)
@ -140,7 +143,7 @@ func (s *service) GetBalance(ctx context.Context, req domain.BalanceRequest) (*d
return res, nil
}
func (s *service) ProcessBet(ctx context.Context, req domain.BetRequest) (*domain.BetResponse, error) {
func (s *Service) ProcessBet(ctx context.Context, req domain.BetRequest) (*domain.BetResponse, error) {
// --- 1. Validate PlayerID ---
playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64)
if err != nil {
@ -257,7 +260,7 @@ func (s *service) ProcessBet(ctx context.Context, req domain.BetRequest) (*domai
return res, nil
}
func (s *service) ProcessWin(ctx context.Context, req domain.WinRequest) (*domain.WinResponse, error) {
func (s *Service) ProcessWin(ctx context.Context, req domain.WinRequest) (*domain.WinResponse, error) {
// --- 1. Validate PlayerID ---
playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64)
if err != nil {
@ -336,7 +339,7 @@ func (s *service) ProcessWin(ctx context.Context, req domain.WinRequest) (*domai
return res, nil
}
func (s *service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (*domain.CancelResponse, error) {
func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (*domain.CancelResponse, error) {
// --- 1. Validate PlayerID ---
playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64)
if err != nil {
@ -434,12 +437,12 @@ func (s *service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
}
// Example helper to fetch original bet
// func (s *service) getOriginalBet(ctx context.Context, transactionID string) (*domain.BetRecord, error) {
// func (s *Service) getOriginalBet(ctx context.Context, transactionID string) (*domain.BetRecord, error) {
// // TODO: implement actual lookup
// return &domain.BetRecord{Amount: 50}, nil
// }
func (s *service) GetGamingActivity(ctx context.Context, req domain.GamingActivityRequest) (*domain.GamingActivityResponse, error) {
func (s *Service) GetGamingActivity(ctx context.Context, req domain.GamingActivityRequest) (*domain.GamingActivityResponse, error) {
// --- Signature Params (flattened strings for signing) ---
sigParams := map[string]any{
"fromDate": req.FromDate,
@ -487,7 +490,7 @@ func (s *service) GetGamingActivity(ctx context.Context, req domain.GamingActivi
return &res, nil
}
func (s *service) GetHugeWins(ctx context.Context, req domain.HugeWinsRequest) (*domain.HugeWinsResponse, error) {
func (s *Service) GetHugeWins(ctx context.Context, req domain.HugeWinsRequest) (*domain.HugeWinsResponse, error) {
// --- Signature Params (flattened strings for signing) ---
sigParams := map[string]any{
"fromDate": req.FromDate,

View File

@ -2,18 +2,21 @@ package httpserver
import (
"context"
"os"
"time"
"log"
// "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
betSvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
resultsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/veli"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
)
@ -141,17 +144,22 @@ func StartTicketCrons(ticketService ticket.Service, mongoLogger *zap.Logger) {
}
// SetupReportCronJobs schedules periodic report generation
func SetupReportCronJobs(ctx context.Context, reportService *report.Service, outputDir string) {
func SetupReportandVirtualGameCronJobs(
ctx context.Context,
reportService *report.Service,
virtualGameService *veli.Service, // inject your virtual game service
outputDir string,
) {
c := cron.New(cron.WithSeconds()) // use WithSeconds for testing
schedule := []struct {
spec string
period string
}{
// {
// spec: "*/60 * * * * *", // Every 5 minutes
// period: "5min",
// },
{
spec: "*/60 * * * * *", // Every 1 minute for testing
period: "test",
},
{
spec: "0 0 0 * * *", // Daily at midnight
period: "daily",
@ -174,14 +182,10 @@ func SetupReportCronJobs(ctx context.Context, reportService *report.Service, out
var from, to time.Time
switch period {
// case "5min":
// from = now.Add(-5 * time.Minute)
// to = now
case "daily":
from = time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, now.Location())
to = time.Date(now.Year(), now.Month(), now.Day()-1, 23, 59, 59, 0, now.Location())
case "weekly":
// last Sunday -> Saturday
weekday := int(now.Weekday())
daysSinceSunday := (weekday + 7) % 7
from = time.Date(now.Year(), now.Month(), now.Day()-daysSinceSunday-7, 0, 0, 0, 0, now.Location())
@ -192,18 +196,57 @@ func SetupReportCronJobs(ctx context.Context, reportService *report.Service, out
to = firstOfMonth.Add(-time.Second)
default:
log.Printf("Unknown period: %s", period)
return
// return
}
log.Printf("Running %s report for period %s -> %s", period, from.Format(time.RFC3339), to.Format(time.RFC3339))
// --- Generate Reports (skip for test) ---
if period != "test" {
log.Printf("Running %s report for period %s -> %s", period, from.Format(time.RFC3339), to.Format(time.RFC3339))
if err := reportService.GenerateReport(ctx, from, to); err != nil {
log.Printf("Error generating %s report: %v", period, err)
} else {
log.Printf("Successfully generated %s report", period)
}
}
if err := reportService.GenerateReport(ctx, from, to); err != nil {
log.Printf("Error generating %s report: %v", period, err)
} else {
log.Printf("Successfully generated %s report", period)
// --- Fetch and Add Virtual Game Providers (daily + test) ---
if period == "daily" || period == "test" {
log.Printf("Fetching and adding virtual game providers (%s)...", period)
brandID := os.Getenv("VELI_BRAND_ID")
if brandID == "" {
log.Println("VELI_BRAND_ID not set, skipping provider sync")
return
}
page := 1
size := 1000
for {
req := domain.ProviderRequest{
BrandID: brandID,
ExtraData: true,
Size: int(size),
Page: int(page),
}
res, err := virtualGameService.AddProviders(ctx, req)
if err != nil {
log.Printf("Error adding virtual game providers on page %d: %v", page, err)
break
}
log.Printf("[%s] Successfully processed page %d: %d providers", period, page, len(res.Items))
if len(res.Items) < size {
// Last page reached
break
}
page++
}
log.Printf("[%s] Finished fetching and adding virtual game providers", period)
}
}); err != nil {
log.Fatalf("Failed to schedule %s report cron job: %v", period, err)
log.Fatalf("Failed to schedule %s cron job: %v", period, err)
}
}
@ -211,6 +254,8 @@ func SetupReportCronJobs(ctx context.Context, reportService *report.Service, out
log.Printf("Cron jobs started. Reports will be saved to: %s", outputDir)
}
func ProcessBetCashback(ctx context.Context, betService *betSvc.Service) {
c := cron.New(cron.WithSeconds())

View File

@ -22,6 +22,87 @@ type launchVirtualGameRes struct {
LaunchURL string `json:"launch_url"`
}
// 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.virtualGameSvc.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.virtualGameSvc.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.virtualGameSvc.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.virtualGameSvc.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

View File

@ -356,6 +356,11 @@ func (a *App) initAppRoutes() {
groupV1.Delete("/virtual-game/favorites/:gameID", a.authMiddleware, h.RemoveFavorite)
groupV1.Get("/virtual-game/favorites", a.authMiddleware, h.ListFavorites)
groupV1.Delete("/virtual-game/orchestrator/providers/:provideID", a.authMiddleware, h.RemoveProvider)
groupV1.Get("/virtual-game/orchestrator/providers/:provideID", a.authMiddleware, h.GetProviderByID)
groupV1.Get("/virtual-game/orchestrator/providers", a.authMiddleware, h.ListProviders)
groupV1.Patch("/virtual-game/orchestrator/providers/:provideID/status", a.authMiddleware, h.SetProviderEnabled)
//Issue Reporting Routes
groupV1.Post("/issues", a.authMiddleware, h.CreateIssue) //anyone who has logged can report a
groupV1.Get("/issues/customer/:customer_id", a.authMiddleware, a.OnlyAdminAndAbove, h.GetUserIssues)