From 858fd6ce2440c4e2987423d5d58f8a15c9a88b18 Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Tue, 26 Aug 2025 21:07:12 +0300 Subject: [PATCH] virtual game orchestration --- cmd/main.go | 4 +- db/migrations/000001_fortune.down.sql | 1 + db/migrations/000001_fortune.up.sql | 11 ++ db/query/virtual_games.sql | 33 ++++ docs/docs.go | 172 ++++++++++++++++++ docs/swagger.json | 172 ++++++++++++++++++ docs/swagger.yaml | 114 ++++++++++++ gen/db/models.go | 11 ++ gen/db/virtual_games.sql.go | 152 ++++++++++++++++ internal/domain/virtual_game.go | 33 ++-- internal/repository/virtual_game.go | 80 ++++++++ .../virtualGame/game_orchestration.go | 69 +++++++ internal/services/virtualGame/port.go | 7 + internal/services/virtualGame/service.go | 3 +- .../virtualGame/veli/game_orchestration.go | 55 ++++++ internal/services/virtualGame/veli/port.go | 1 + internal/services/virtualGame/veli/service.go | 35 ++-- internal/web_server/cron.go | 43 +++-- .../handlers/virtual_games_hadlers.go | 81 +++++++++ internal/web_server/routes.go | 5 + 20 files changed, 1038 insertions(+), 44 deletions(-) create mode 100644 internal/services/virtualGame/game_orchestration.go create mode 100644 internal/services/virtualGame/veli/game_orchestration.go diff --git a/cmd/main.go b/cmd/main.go index 354626e..a12baf3 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -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) diff --git a/db/migrations/000001_fortune.down.sql b/db/migrations/000001_fortune.down.sql index d854608..0870ac6 100644 --- a/db/migrations/000001_fortune.down.sql +++ b/db/migrations/000001_fortune.down.sql @@ -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 diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 71519c4..9dbc921 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -20,6 +20,17 @@ CREATE TABLE IF NOT EXISTS users ( ) ); +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, diff --git a/db/query/virtual_games.sql b/db/query/virtual_games.sql index 68f2fca..213923a 100644 --- a/db/query/virtual_games.sql +++ b/db/query/virtual_games.sql @@ -1,3 +1,36 @@ +-- 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: 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 diff --git a/docs/docs.go b/docs/docs.go index 8add105..1173278 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -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": { diff --git a/docs/swagger.json b/docs/swagger.json index 4201f9e..ee0556a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -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": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 5637a01..60cf75b 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -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: diff --git a/gen/db/models.go b/gen/db/models.go index 7138ff5..f117e6c 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -652,6 +652,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"` diff --git a/gen/db/virtual_games.sql.go b/gen/db/virtual_games.sql.go index c05d582..710b5e6 100644 --- a/gen/db/virtual_games.sql.go +++ b/gen/db/virtual_games.sql.go @@ -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,38 @@ func (q *Queries) CreateVirtualGameTransaction(ctx context.Context, arg CreateVi return i, 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 +447,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 +503,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 diff --git a/internal/domain/virtual_game.go b/internal/domain/virtual_game.go index 39177ba..947fc3d 100644 --- a/internal/domain/virtual_game.go +++ b/internal/domain/virtual_game.go @@ -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"` +} \ No newline at end of file diff --git a/internal/repository/virtual_game.go b/internal/repository/virtual_game.go index b4c8e06..82b7d64 100644 --- a/internal/repository/virtual_game.go +++ b/internal/repository/virtual_game.go @@ -12,6 +12,12 @@ 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 + 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 +43,80 @@ 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) 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 +137,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, diff --git a/internal/services/virtualGame/game_orchestration.go b/internal/services/virtualGame/game_orchestration.go new file mode 100644 index 0000000..69a463c --- /dev/null +++ b/internal/services/virtualGame/game_orchestration.go @@ -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 +} diff --git a/internal/services/virtualGame/port.go b/internal/services/virtualGame/port.go index 36f57de..ca320e1 100644 --- a/internal/services/virtualGame/port.go +++ b/internal/services/virtualGame/port.go @@ -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) diff --git a/internal/services/virtualGame/service.go b/internal/services/virtualGame/service.go index ebc5311..3cd1fb8 100644 --- a/internal/services/virtualGame/service.go +++ b/internal/services/virtualGame/service.go @@ -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) { diff --git a/internal/services/virtualGame/veli/game_orchestration.go b/internal/services/virtualGame/veli/game_orchestration.go new file mode 100644 index 0000000..59d8d2f --- /dev/null +++ b/internal/services/virtualGame/veli/game_orchestration.go @@ -0,0 +1,55 @@ +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) { + // 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, + } + + _, err := s.repo.CreateVirtualGameProvider(ctx, createParams) + if err != nil { + // Log error but continue with other providers + return nil, err + } + } + + return &res, nil +} diff --git a/internal/services/virtualGame/veli/port.go b/internal/services/virtualGame/veli/port.go index 22c78ba..d1af627 100644 --- a/internal/services/virtualGame/veli/port.go +++ b/internal/services/virtualGame/veli/port.go @@ -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) diff --git a/internal/services/virtualGame/veli/service.go b/internal/services/virtualGame/veli/service.go index e0fa68b..c8da008 100644 --- a/internal/services/virtualGame/veli/service.go +++ b/internal/services/virtualGame/veli/service.go @@ -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, diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index 59a4f13..4734886 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -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" ) @@ -128,17 +131,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 0 0 * * *", // 1 Minute + period: "1 minute", + }, { spec: "0 0 0 * * *", // Daily at midnight period: "daily", @@ -161,14 +169,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()) @@ -182,15 +186,32 @@ func SetupReportCronJobs(ctx context.Context, reportService *report.Service, out return } + // --- Generate Reports --- 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) } + + // --- Fetch and Add Virtual Game Providers (daily only) --- + if period == "1 minute" { + log.Println("Fetching and adding virtual game providers...") + req := domain.ProviderRequest{ + BrandID: os.Getenv("VELI_BRAND_ID"), // adjust to your actual brand ID + ExtraData: true, + Size: 1000, // or any default value + Page: 1, // start from first page + } + + if _, err := virtualGameService.AddProviders(ctx, req); err != nil { + log.Printf("Error adding virtual game providers: %v", err) + } else { + log.Println("Successfully added virtual game providers") + } + } }); 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) } } diff --git a/internal/web_server/handlers/virtual_games_hadlers.go b/internal/web_server/handlers/virtual_games_hadlers.go index 5351fee..054b3a8 100644 --- a/internal/web_server/handlers/virtual_games_hadlers.go +++ b/internal/web_server/handlers/virtual_games_hadlers.go @@ -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 diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index d4f00a6..8945e0b 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -341,6 +341,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)