fetch company and branch by wallet ID methods
This commit is contained in:
parent
bdf057e01d
commit
25230e3fcf
19
cmd/main.go
19
cmd/main.go
|
|
@ -199,6 +199,25 @@ func main() {
|
|||
|
||||
httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc)
|
||||
httpserver.StartTicketCrons(*ticketSvc)
|
||||
|
||||
// Fetch companies and branches for live wallet metrics update
|
||||
ctx := context.Background()
|
||||
|
||||
companies := []domain.GetCompany{
|
||||
{ID: 1, Name: "Company A", WalletBalance: 1000.0},
|
||||
}
|
||||
|
||||
branches := []domain.BranchWallet{
|
||||
{ID: 10, Name: "Branch Z", CompanyID: 1, Balance: 500.0},
|
||||
}
|
||||
|
||||
notificationSvc.UpdateLiveWalletMetrics(ctx, companies, branches)
|
||||
if err != nil {
|
||||
log.Println("Failed to update live metrics:", err)
|
||||
} else {
|
||||
log.Println("Live metrics broadcasted successfully")
|
||||
}
|
||||
|
||||
// go httpserver.SetupReportCronJob(reportWorker)
|
||||
|
||||
// Initialize and start HTTP server
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ CREATE TABLE virtual_game_transactions (
|
|||
id BIGSERIAL PRIMARY KEY,
|
||||
session_id BIGINT NOT NULL REFERENCES virtual_game_sessions(id),
|
||||
user_id BIGINT NOT NULL REFERENCES users(id),
|
||||
company_id BIGINT,
|
||||
provider VARCHAR(100),
|
||||
game_id VARCHAR(100),
|
||||
wallet_id BIGINT NOT NULL REFERENCES wallets(id),
|
||||
transaction_type VARCHAR(20) NOT NULL,
|
||||
amount BIGINT NOT NULL,
|
||||
|
|
@ -44,6 +47,8 @@ CREATE TABLE virtual_game_histories (
|
|||
id BIGSERIAL PRIMARY KEY,
|
||||
session_id VARCHAR(100), -- nullable
|
||||
user_id BIGINT NOT NULL,
|
||||
company_id BIGINT,
|
||||
provider VARCHAR(100),
|
||||
wallet_id BIGINT, -- nullable
|
||||
game_id BIGINT, -- nullable
|
||||
transaction_type VARCHAR(20) NOT NULL, -- e.g., BET, WIN, CANCEL
|
||||
|
|
@ -56,6 +61,13 @@ CREATE TABLE virtual_game_histories (
|
|||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS favorite_games (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
game_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Optional: Indexes for performance
|
||||
CREATE INDEX idx_virtual_game_user_id ON virtual_game_histories(user_id);
|
||||
CREATE INDEX idx_virtual_game_transaction_type ON virtual_game_histories(transaction_type);
|
||||
|
|
@ -65,3 +77,7 @@ CREATE INDEX idx_virtual_game_external_transaction_id ON virtual_game_histories(
|
|||
CREATE INDEX idx_virtual_game_sessions_user_id ON virtual_game_sessions(user_id);
|
||||
CREATE INDEX idx_virtual_game_transactions_session_id ON virtual_game_transactions(session_id);
|
||||
CREATE INDEX idx_virtual_game_transactions_user_id ON virtual_game_transactions(user_id);
|
||||
|
||||
ALTER TABLE favorite_games
|
||||
ADD CONSTRAINT unique_user_game_favorite UNIQUE (user_id, game_id);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +1,44 @@
|
|||
-- name: GetTotalBetsMadeInRange :one
|
||||
SELECT COUNT(*) AS total_bets
|
||||
FROM bets
|
||||
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
|
||||
AND (
|
||||
company_id = sqlc.narg('company_id')
|
||||
OR sqlc.narg('company_id') IS NULL
|
||||
);
|
||||
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to');
|
||||
-- name: GetTotalCashMadeInRange :one
|
||||
SELECT COALESCE(SUM(amount), 0) AS total_cash_made
|
||||
FROM bets
|
||||
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
|
||||
AND (
|
||||
company_id = sqlc.narg('company_id')
|
||||
OR sqlc.narg('company_id') IS NULL
|
||||
);
|
||||
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to');
|
||||
-- name: GetTotalCashOutInRange :one
|
||||
SELECT COALESCE(SUM(amount), 0) AS total_cash_out
|
||||
FROM bets
|
||||
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
|
||||
AND cashed_out = true
|
||||
AND (
|
||||
company_id = sqlc.narg('company_id')
|
||||
OR sqlc.narg('company_id') IS NULL
|
||||
);
|
||||
AND cashed_out = true;
|
||||
-- name: GetTotalCashBacksInRange :one
|
||||
SELECT COALESCE(SUM(amount), 0) AS total_cash_backs
|
||||
FROM bets
|
||||
WHERE created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
|
||||
AND status = 5
|
||||
AND (
|
||||
company_id = sqlc.narg('company_id')
|
||||
OR sqlc.narg('company_id') IS NULL
|
||||
);
|
||||
AND status = 5;
|
||||
-- name: GetCompanyWiseReport :many
|
||||
SELECT
|
||||
b.company_id,
|
||||
c.name AS company_name,
|
||||
COUNT(*) AS total_bets,
|
||||
COALESCE(SUM(b.amount), 0) AS total_cash_made,
|
||||
COALESCE(SUM(CASE WHEN b.cashed_out THEN b.amount ELSE 0 END), 0) AS total_cash_out,
|
||||
COALESCE(SUM(CASE WHEN b.status = 5 THEN b.amount ELSE 0 END), 0) AS total_cash_backs
|
||||
FROM bets b
|
||||
JOIN companies c ON b.company_id = c.id
|
||||
WHERE b.created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
|
||||
GROUP BY b.company_id, c.name;
|
||||
-- name: GetBranchWiseReport :many
|
||||
SELECT
|
||||
b.branch_id,
|
||||
br.name AS branch_name,
|
||||
br.company_id,
|
||||
COUNT(*) AS total_bets,
|
||||
COALESCE(SUM(b.amount), 0) AS total_cash_made,
|
||||
COALESCE(SUM(CASE WHEN b.cashed_out THEN b.amount ELSE 0 END), 0) AS total_cash_out,
|
||||
COALESCE(SUM(CASE WHEN b.status = 5 THEN b.amount ELSE 0 END), 0) AS total_cash_backs
|
||||
FROM bets b
|
||||
JOIN branches br ON b.branch_id = br.id
|
||||
WHERE b.created_at BETWEEN sqlc.arg('from') AND sqlc.arg('to')
|
||||
GROUP BY b.branch_id, br.name, br.company_id;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,28 +4,26 @@ INSERT INTO virtual_game_sessions (
|
|||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6
|
||||
) RETURNING id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at;
|
||||
|
||||
-- name: GetVirtualGameSessionByToken :one
|
||||
SELECT id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at
|
||||
FROM virtual_game_sessions
|
||||
WHERE session_token = $1;
|
||||
|
||||
-- name: UpdateVirtualGameSessionStatus :exec
|
||||
UPDATE virtual_game_sessions
|
||||
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: CreateVirtualGameTransaction :one
|
||||
INSERT INTO virtual_game_transactions (
|
||||
session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status
|
||||
session_id, user_id, company_id, provider, wallet_id, transaction_type, amount, currency, external_transaction_id, status
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8
|
||||
) RETURNING id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at;
|
||||
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||
) RETURNING id, session_id, user_id, company_id, provider, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at;
|
||||
-- name: CreateVirtualGameHistory :one
|
||||
INSERT INTO virtual_game_histories (
|
||||
session_id,
|
||||
user_id,
|
||||
company_id,
|
||||
provider,
|
||||
wallet_id,
|
||||
game_id,
|
||||
transaction_type,
|
||||
|
|
@ -35,11 +33,13 @@ INSERT INTO virtual_game_histories (
|
|||
reference_transaction_id,
|
||||
status
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12
|
||||
) RETURNING
|
||||
id,
|
||||
session_id,
|
||||
user_id,
|
||||
company_id,
|
||||
provider,
|
||||
wallet_id,
|
||||
game_id,
|
||||
transaction_type,
|
||||
|
|
@ -50,25 +50,39 @@ INSERT INTO virtual_game_histories (
|
|||
status,
|
||||
created_at,
|
||||
updated_at;
|
||||
|
||||
|
||||
-- name: GetVirtualGameTransactionByExternalID :one
|
||||
SELECT id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
||||
FROM virtual_game_transactions
|
||||
WHERE external_transaction_id = $1;
|
||||
|
||||
-- name: UpdateVirtualGameTransactionStatus :exec
|
||||
UPDATE virtual_game_transactions
|
||||
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: GetVirtualGameSummaryInRange :many
|
||||
SELECT
|
||||
c.name AS company_name,
|
||||
vg.name AS game_name,
|
||||
COUNT(vgh.id) AS number_of_bets,
|
||||
COALESCE(SUM(vgh.amount), 0) AS total_transaction_sum
|
||||
FROM virtual_game_histories vgh
|
||||
JOIN virtual_games vg ON vgh.game_id = vg.id
|
||||
WHERE vgh.transaction_type = 'BET'
|
||||
AND vgh.created_at BETWEEN $1 AND $2
|
||||
GROUP BY vg.name;
|
||||
COUNT(vgt.id) AS number_of_bets,
|
||||
COALESCE(SUM(vgt.amount), 0) AS total_transaction_sum
|
||||
FROM virtual_game_transactions vgt
|
||||
JOIN virtual_game_sessions vgs ON vgt.session_id = vgs.id
|
||||
JOIN virtual_games vg ON vgs.game_id = vg.id
|
||||
JOIN companies c ON vgt.company_id = c.id
|
||||
WHERE vgt.transaction_type = 'BET'
|
||||
AND vgt.created_at BETWEEN $1 AND $2
|
||||
GROUP BY c.name, vg.name;
|
||||
-- name: AddFavoriteGame :exec
|
||||
INSERT INTO favorite_games (
|
||||
user_id,
|
||||
game_id,
|
||||
created_at
|
||||
) VALUES ($1, $2, NOW())
|
||||
ON CONFLICT (user_id, game_id) DO NOTHING;
|
||||
-- name: RemoveFavoriteGame :exec
|
||||
DELETE FROM favorite_games
|
||||
WHERE user_id = $1 AND game_id = $2;
|
||||
-- name: ListFavoriteGames :many
|
||||
SELECT game_id
|
||||
FROM favorite_games
|
||||
WHERE user_id = $1;
|
||||
|
||||
|
|
|
|||
|
|
@ -63,3 +63,13 @@ UPDATE wallets
|
|||
SET is_active = $1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $2;
|
||||
-- name: GetCompanyByWalletID :one
|
||||
SELECT id, name, admin_id, wallet_id
|
||||
FROM companies
|
||||
WHERE wallet_id = $1
|
||||
LIMIT 1;
|
||||
-- name: GetBranchByWalletID :one
|
||||
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at
|
||||
FROM branches
|
||||
WHERE wallet_id = $1
|
||||
LIMIT 1;
|
||||
136
docs/docs.go
136
docs/docs.go
|
|
@ -1052,6 +1052,122 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/virtual-game/favorites": {
|
||||
"post": {
|
||||
"description": "Adds a game to the user's favorite games list",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"VirtualGames - Favourites"
|
||||
],
|
||||
"summary": "Add game to favorites",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Game ID to add",
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.FavoriteGameRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "created",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/virtual-games/favorites": {
|
||||
"get": {
|
||||
"description": "Lists the games that the user marked as favorite",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"VirtualGames - Favourites"
|
||||
],
|
||||
"summary": "Get user's favorite games",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.GameRecommendation"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/virtual-games/favorites/{gameID}": {
|
||||
"delete": {
|
||||
"description": "Removes a game from the user's favorites",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"VirtualGames - Favourites"
|
||||
],
|
||||
"summary": "Remove game from favorites",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Game ID to remove",
|
||||
"name": "gameID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "removed",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/webhooks/alea": {
|
||||
"post": {
|
||||
"description": "Handles webhook callbacks from Alea Play virtual games for bet settlement",
|
||||
|
|
@ -4701,19 +4817,19 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4758,19 +4874,19 @@ const docTemplate = `{
|
|||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5479,6 +5595,14 @@ const docTemplate = `{
|
|||
"STATUS_REMOVED"
|
||||
]
|
||||
},
|
||||
"domain.FavoriteGameRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"game_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.GameRecommendation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -1044,6 +1044,122 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/virtual-game/favorites": {
|
||||
"post": {
|
||||
"description": "Adds a game to the user's favorite games list",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"VirtualGames - Favourites"
|
||||
],
|
||||
"summary": "Add game to favorites",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Game ID to add",
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.FavoriteGameRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "created",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/virtual-games/favorites": {
|
||||
"get": {
|
||||
"description": "Lists the games that the user marked as favorite",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"VirtualGames - Favourites"
|
||||
],
|
||||
"summary": "Get user's favorite games",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.GameRecommendation"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/virtual-games/favorites/{gameID}": {
|
||||
"delete": {
|
||||
"description": "Removes a game from the user's favorites",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"VirtualGames - Favourites"
|
||||
],
|
||||
"summary": "Remove game from favorites",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Game ID to remove",
|
||||
"name": "gameID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "removed",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/webhooks/alea": {
|
||||
"post": {
|
||||
"description": "Handles webhook callbacks from Alea Play virtual games for bet settlement",
|
||||
|
|
@ -4693,19 +4809,19 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4750,19 +4866,19 @@
|
|||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5471,6 +5587,14 @@
|
|||
"STATUS_REMOVED"
|
||||
]
|
||||
},
|
||||
"domain.FavoriteGameRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"game_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.GameRecommendation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -391,6 +391,11 @@ definitions:
|
|||
- STATUS_SUSPENDED
|
||||
- STATUS_DECIDED_BY_FA
|
||||
- STATUS_REMOVED
|
||||
domain.FavoriteGameRequest:
|
||||
properties:
|
||||
game_id:
|
||||
type: integer
|
||||
type: object
|
||||
domain.GameRecommendation:
|
||||
properties:
|
||||
bets:
|
||||
|
|
@ -2268,6 +2273,82 @@ paths:
|
|||
summary: Get dashboard report
|
||||
tags:
|
||||
- Reports
|
||||
/api/v1/virtual-game/favorites:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Adds a game to the user's favorite games list
|
||||
parameters:
|
||||
- description: Game ID to add
|
||||
in: body
|
||||
name: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.FavoriteGameRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: created
|
||||
schema:
|
||||
type: string
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Add game to favorites
|
||||
tags:
|
||||
- VirtualGames - Favourites
|
||||
/api/v1/virtual-games/favorites:
|
||||
get:
|
||||
description: Lists the games that the user marked as favorite
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/domain.GameRecommendation'
|
||||
type: array
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Get user's favorite games
|
||||
tags:
|
||||
- VirtualGames - Favourites
|
||||
/api/v1/virtual-games/favorites/{gameID}:
|
||||
delete:
|
||||
description: Removes a game from the user's favorites
|
||||
parameters:
|
||||
- description: Game ID to remove
|
||||
in: path
|
||||
name: gameID
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: removed
|
||||
schema:
|
||||
type: string
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Remove game from favorites
|
||||
tags:
|
||||
- VirtualGames - Favourites
|
||||
/api/v1/webhooks/alea:
|
||||
post:
|
||||
consumes:
|
||||
|
|
@ -4663,15 +4744,15 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Handle PopOK game callback
|
||||
tags:
|
||||
- Virtual Games - PopOK
|
||||
|
|
@ -4697,15 +4778,15 @@ paths:
|
|||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Launch a PopOK virtual game
|
||||
|
|
|
|||
|
|
@ -233,6 +233,13 @@ type ExchangeRate struct {
|
|||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
}
|
||||
|
||||
type FavoriteGame struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
GameID int64 `json:"game_id"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
}
|
||||
|
||||
type League struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
|
@ -476,6 +483,8 @@ type VirtualGameHistory struct {
|
|||
ID int64 `json:"id"`
|
||||
SessionID pgtype.Text `json:"session_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
Provider pgtype.Text `json:"provider"`
|
||||
WalletID pgtype.Int8 `json:"wallet_id"`
|
||||
GameID pgtype.Int8 `json:"game_id"`
|
||||
TransactionType string `json:"transaction_type"`
|
||||
|
|
@ -504,6 +513,9 @@ type VirtualGameTransaction struct {
|
|||
ID int64 `json:"id"`
|
||||
SessionID int64 `json:"session_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
Provider pgtype.Text `json:"provider"`
|
||||
GameID pgtype.Text `json:"game_id"`
|
||||
WalletID int64 `json:"wallet_id"`
|
||||
TransactionType string `json:"transaction_type"`
|
||||
Amount int64 `json:"amount"`
|
||||
|
|
|
|||
|
|
@ -11,24 +11,132 @@ import (
|
|||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const GetBranchWiseReport = `-- name: GetBranchWiseReport :many
|
||||
SELECT
|
||||
b.branch_id,
|
||||
br.name AS branch_name,
|
||||
br.company_id,
|
||||
COUNT(*) AS total_bets,
|
||||
COALESCE(SUM(b.amount), 0) AS total_cash_made,
|
||||
COALESCE(SUM(CASE WHEN b.cashed_out THEN b.amount ELSE 0 END), 0) AS total_cash_out,
|
||||
COALESCE(SUM(CASE WHEN b.status = 5 THEN b.amount ELSE 0 END), 0) AS total_cash_backs
|
||||
FROM bets b
|
||||
JOIN branches br ON b.branch_id = br.id
|
||||
WHERE b.created_at BETWEEN $1 AND $2
|
||||
GROUP BY b.branch_id, br.name, br.company_id
|
||||
`
|
||||
|
||||
type GetBranchWiseReportParams struct {
|
||||
From pgtype.Timestamp `json:"from"`
|
||||
To pgtype.Timestamp `json:"to"`
|
||||
}
|
||||
|
||||
type GetBranchWiseReportRow struct {
|
||||
BranchID pgtype.Int8 `json:"branch_id"`
|
||||
BranchName string `json:"branch_name"`
|
||||
CompanyID int64 `json:"company_id"`
|
||||
TotalBets int64 `json:"total_bets"`
|
||||
TotalCashMade interface{} `json:"total_cash_made"`
|
||||
TotalCashOut interface{} `json:"total_cash_out"`
|
||||
TotalCashBacks interface{} `json:"total_cash_backs"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetBranchWiseReport(ctx context.Context, arg GetBranchWiseReportParams) ([]GetBranchWiseReportRow, error) {
|
||||
rows, err := q.db.Query(ctx, GetBranchWiseReport, arg.From, arg.To)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetBranchWiseReportRow
|
||||
for rows.Next() {
|
||||
var i GetBranchWiseReportRow
|
||||
if err := rows.Scan(
|
||||
&i.BranchID,
|
||||
&i.BranchName,
|
||||
&i.CompanyID,
|
||||
&i.TotalBets,
|
||||
&i.TotalCashMade,
|
||||
&i.TotalCashOut,
|
||||
&i.TotalCashBacks,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const GetCompanyWiseReport = `-- name: GetCompanyWiseReport :many
|
||||
SELECT
|
||||
b.company_id,
|
||||
c.name AS company_name,
|
||||
COUNT(*) AS total_bets,
|
||||
COALESCE(SUM(b.amount), 0) AS total_cash_made,
|
||||
COALESCE(SUM(CASE WHEN b.cashed_out THEN b.amount ELSE 0 END), 0) AS total_cash_out,
|
||||
COALESCE(SUM(CASE WHEN b.status = 5 THEN b.amount ELSE 0 END), 0) AS total_cash_backs
|
||||
FROM bets b
|
||||
JOIN companies c ON b.company_id = c.id
|
||||
WHERE b.created_at BETWEEN $1 AND $2
|
||||
GROUP BY b.company_id, c.name
|
||||
`
|
||||
|
||||
type GetCompanyWiseReportParams struct {
|
||||
From pgtype.Timestamp `json:"from"`
|
||||
To pgtype.Timestamp `json:"to"`
|
||||
}
|
||||
|
||||
type GetCompanyWiseReportRow struct {
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
CompanyName string `json:"company_name"`
|
||||
TotalBets int64 `json:"total_bets"`
|
||||
TotalCashMade interface{} `json:"total_cash_made"`
|
||||
TotalCashOut interface{} `json:"total_cash_out"`
|
||||
TotalCashBacks interface{} `json:"total_cash_backs"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetCompanyWiseReport(ctx context.Context, arg GetCompanyWiseReportParams) ([]GetCompanyWiseReportRow, error) {
|
||||
rows, err := q.db.Query(ctx, GetCompanyWiseReport, arg.From, arg.To)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetCompanyWiseReportRow
|
||||
for rows.Next() {
|
||||
var i GetCompanyWiseReportRow
|
||||
if err := rows.Scan(
|
||||
&i.CompanyID,
|
||||
&i.CompanyName,
|
||||
&i.TotalBets,
|
||||
&i.TotalCashMade,
|
||||
&i.TotalCashOut,
|
||||
&i.TotalCashBacks,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const GetTotalBetsMadeInRange = `-- name: GetTotalBetsMadeInRange :one
|
||||
SELECT COUNT(*) AS total_bets
|
||||
FROM bets
|
||||
WHERE created_at BETWEEN $1 AND $2
|
||||
AND (
|
||||
company_id = $3
|
||||
OR $3 IS NULL
|
||||
)
|
||||
`
|
||||
|
||||
type GetTotalBetsMadeInRangeParams struct {
|
||||
From pgtype.Timestamp `json:"from"`
|
||||
To pgtype.Timestamp `json:"to"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetTotalBetsMadeInRange(ctx context.Context, arg GetTotalBetsMadeInRangeParams) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, GetTotalBetsMadeInRange, arg.From, arg.To, arg.CompanyID)
|
||||
row := q.db.QueryRow(ctx, GetTotalBetsMadeInRange, arg.From, arg.To)
|
||||
var total_bets int64
|
||||
err := row.Scan(&total_bets)
|
||||
return total_bets, err
|
||||
|
|
@ -39,20 +147,15 @@ SELECT COALESCE(SUM(amount), 0) AS total_cash_backs
|
|||
FROM bets
|
||||
WHERE created_at BETWEEN $1 AND $2
|
||||
AND status = 5
|
||||
AND (
|
||||
company_id = $3
|
||||
OR $3 IS NULL
|
||||
)
|
||||
`
|
||||
|
||||
type GetTotalCashBacksInRangeParams struct {
|
||||
From pgtype.Timestamp `json:"from"`
|
||||
To pgtype.Timestamp `json:"to"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetTotalCashBacksInRange(ctx context.Context, arg GetTotalCashBacksInRangeParams) (interface{}, error) {
|
||||
row := q.db.QueryRow(ctx, GetTotalCashBacksInRange, arg.From, arg.To, arg.CompanyID)
|
||||
row := q.db.QueryRow(ctx, GetTotalCashBacksInRange, arg.From, arg.To)
|
||||
var total_cash_backs interface{}
|
||||
err := row.Scan(&total_cash_backs)
|
||||
return total_cash_backs, err
|
||||
|
|
@ -62,20 +165,15 @@ const GetTotalCashMadeInRange = `-- name: GetTotalCashMadeInRange :one
|
|||
SELECT COALESCE(SUM(amount), 0) AS total_cash_made
|
||||
FROM bets
|
||||
WHERE created_at BETWEEN $1 AND $2
|
||||
AND (
|
||||
company_id = $3
|
||||
OR $3 IS NULL
|
||||
)
|
||||
`
|
||||
|
||||
type GetTotalCashMadeInRangeParams struct {
|
||||
From pgtype.Timestamp `json:"from"`
|
||||
To pgtype.Timestamp `json:"to"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetTotalCashMadeInRange(ctx context.Context, arg GetTotalCashMadeInRangeParams) (interface{}, error) {
|
||||
row := q.db.QueryRow(ctx, GetTotalCashMadeInRange, arg.From, arg.To, arg.CompanyID)
|
||||
row := q.db.QueryRow(ctx, GetTotalCashMadeInRange, arg.From, arg.To)
|
||||
var total_cash_made interface{}
|
||||
err := row.Scan(&total_cash_made)
|
||||
return total_cash_made, err
|
||||
|
|
@ -86,20 +184,15 @@ SELECT COALESCE(SUM(amount), 0) AS total_cash_out
|
|||
FROM bets
|
||||
WHERE created_at BETWEEN $1 AND $2
|
||||
AND cashed_out = true
|
||||
AND (
|
||||
company_id = $3
|
||||
OR $3 IS NULL
|
||||
)
|
||||
`
|
||||
|
||||
type GetTotalCashOutInRangeParams struct {
|
||||
From pgtype.Timestamp `json:"from"`
|
||||
To pgtype.Timestamp `json:"to"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetTotalCashOutInRange(ctx context.Context, arg GetTotalCashOutInRangeParams) (interface{}, error) {
|
||||
row := q.db.QueryRow(ctx, GetTotalCashOutInRange, arg.From, arg.To, arg.CompanyID)
|
||||
row := q.db.QueryRow(ctx, GetTotalCashOutInRange, arg.From, arg.To)
|
||||
var total_cash_out interface{}
|
||||
err := row.Scan(&total_cash_out)
|
||||
return total_cash_out, err
|
||||
|
|
|
|||
|
|
@ -11,10 +11,31 @@ import (
|
|||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const AddFavoriteGame = `-- name: AddFavoriteGame :exec
|
||||
INSERT INTO favorite_games (
|
||||
user_id,
|
||||
game_id,
|
||||
created_at
|
||||
) VALUES ($1, $2, NOW())
|
||||
ON CONFLICT (user_id, game_id) DO NOTHING
|
||||
`
|
||||
|
||||
type AddFavoriteGameParams struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
GameID int64 `json:"game_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) AddFavoriteGame(ctx context.Context, arg AddFavoriteGameParams) error {
|
||||
_, err := q.db.Exec(ctx, AddFavoriteGame, arg.UserID, arg.GameID)
|
||||
return err
|
||||
}
|
||||
|
||||
const CreateVirtualGameHistory = `-- name: CreateVirtualGameHistory :one
|
||||
INSERT INTO virtual_game_histories (
|
||||
session_id,
|
||||
user_id,
|
||||
company_id,
|
||||
provider,
|
||||
wallet_id,
|
||||
game_id,
|
||||
transaction_type,
|
||||
|
|
@ -24,11 +45,13 @@ INSERT INTO virtual_game_histories (
|
|||
reference_transaction_id,
|
||||
status
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12
|
||||
) RETURNING
|
||||
id,
|
||||
session_id,
|
||||
user_id,
|
||||
company_id,
|
||||
provider,
|
||||
wallet_id,
|
||||
game_id,
|
||||
transaction_type,
|
||||
|
|
@ -44,6 +67,8 @@ INSERT INTO virtual_game_histories (
|
|||
type CreateVirtualGameHistoryParams struct {
|
||||
SessionID pgtype.Text `json:"session_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
Provider pgtype.Text `json:"provider"`
|
||||
WalletID pgtype.Int8 `json:"wallet_id"`
|
||||
GameID pgtype.Int8 `json:"game_id"`
|
||||
TransactionType string `json:"transaction_type"`
|
||||
|
|
@ -58,6 +83,8 @@ func (q *Queries) CreateVirtualGameHistory(ctx context.Context, arg CreateVirtua
|
|||
row := q.db.QueryRow(ctx, CreateVirtualGameHistory,
|
||||
arg.SessionID,
|
||||
arg.UserID,
|
||||
arg.CompanyID,
|
||||
arg.Provider,
|
||||
arg.WalletID,
|
||||
arg.GameID,
|
||||
arg.TransactionType,
|
||||
|
|
@ -72,6 +99,8 @@ func (q *Queries) CreateVirtualGameHistory(ctx context.Context, arg CreateVirtua
|
|||
&i.ID,
|
||||
&i.SessionID,
|
||||
&i.UserID,
|
||||
&i.CompanyID,
|
||||
&i.Provider,
|
||||
&i.WalletID,
|
||||
&i.GameID,
|
||||
&i.TransactionType,
|
||||
|
|
@ -129,15 +158,17 @@ func (q *Queries) CreateVirtualGameSession(ctx context.Context, arg CreateVirtua
|
|||
|
||||
const CreateVirtualGameTransaction = `-- name: CreateVirtualGameTransaction :one
|
||||
INSERT INTO virtual_game_transactions (
|
||||
session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status
|
||||
session_id, user_id, company_id, provider, wallet_id, transaction_type, amount, currency, external_transaction_id, status
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8
|
||||
) RETURNING id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||
) RETURNING id, session_id, user_id, company_id, provider, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
||||
`
|
||||
|
||||
type CreateVirtualGameTransactionParams struct {
|
||||
SessionID int64 `json:"session_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
Provider pgtype.Text `json:"provider"`
|
||||
WalletID int64 `json:"wallet_id"`
|
||||
TransactionType string `json:"transaction_type"`
|
||||
Amount int64 `json:"amount"`
|
||||
|
|
@ -146,10 +177,28 @@ type CreateVirtualGameTransactionParams struct {
|
|||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateVirtualGameTransaction(ctx context.Context, arg CreateVirtualGameTransactionParams) (VirtualGameTransaction, error) {
|
||||
type CreateVirtualGameTransactionRow struct {
|
||||
ID int64 `json:"id"`
|
||||
SessionID int64 `json:"session_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
Provider pgtype.Text `json:"provider"`
|
||||
WalletID int64 `json:"wallet_id"`
|
||||
TransactionType string `json:"transaction_type"`
|
||||
Amount int64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
ExternalTransactionID string `json:"external_transaction_id"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateVirtualGameTransaction(ctx context.Context, arg CreateVirtualGameTransactionParams) (CreateVirtualGameTransactionRow, error) {
|
||||
row := q.db.QueryRow(ctx, CreateVirtualGameTransaction,
|
||||
arg.SessionID,
|
||||
arg.UserID,
|
||||
arg.CompanyID,
|
||||
arg.Provider,
|
||||
arg.WalletID,
|
||||
arg.TransactionType,
|
||||
arg.Amount,
|
||||
|
|
@ -157,11 +206,13 @@ func (q *Queries) CreateVirtualGameTransaction(ctx context.Context, arg CreateVi
|
|||
arg.ExternalTransactionID,
|
||||
arg.Status,
|
||||
)
|
||||
var i VirtualGameTransaction
|
||||
var i CreateVirtualGameTransactionRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.SessionID,
|
||||
&i.UserID,
|
||||
&i.CompanyID,
|
||||
&i.Provider,
|
||||
&i.WalletID,
|
||||
&i.TransactionType,
|
||||
&i.Amount,
|
||||
|
|
@ -199,22 +250,26 @@ func (q *Queries) GetVirtualGameSessionByToken(ctx context.Context, sessionToken
|
|||
|
||||
const GetVirtualGameSummaryInRange = `-- name: GetVirtualGameSummaryInRange :many
|
||||
SELECT
|
||||
c.name AS company_name,
|
||||
vg.name AS game_name,
|
||||
COUNT(vgh.id) AS number_of_bets,
|
||||
COALESCE(SUM(vgh.amount), 0) AS total_transaction_sum
|
||||
FROM virtual_game_histories vgh
|
||||
JOIN virtual_games vg ON vgh.game_id = vg.id
|
||||
WHERE vgh.transaction_type = 'BET'
|
||||
AND vgh.created_at BETWEEN $1 AND $2
|
||||
GROUP BY vg.name
|
||||
COUNT(vgt.id) AS number_of_bets,
|
||||
COALESCE(SUM(vgt.amount), 0) AS total_transaction_sum
|
||||
FROM virtual_game_transactions vgt
|
||||
JOIN virtual_game_sessions vgs ON vgt.session_id = vgs.id
|
||||
JOIN virtual_games vg ON vgs.game_id = vg.id
|
||||
JOIN companies c ON vgt.company_id = c.id
|
||||
WHERE vgt.transaction_type = 'BET'
|
||||
AND vgt.created_at BETWEEN $1 AND $2
|
||||
GROUP BY c.name, vg.name
|
||||
`
|
||||
|
||||
type GetVirtualGameSummaryInRangeParams struct {
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
CreatedAt_2 pgtype.Timestamp `json:"created_at_2"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
CreatedAt_2 pgtype.Timestamptz `json:"created_at_2"`
|
||||
}
|
||||
|
||||
type GetVirtualGameSummaryInRangeRow struct {
|
||||
CompanyName string `json:"company_name"`
|
||||
GameName string `json:"game_name"`
|
||||
NumberOfBets int64 `json:"number_of_bets"`
|
||||
TotalTransactionSum interface{} `json:"total_transaction_sum"`
|
||||
|
|
@ -229,7 +284,12 @@ func (q *Queries) GetVirtualGameSummaryInRange(ctx context.Context, arg GetVirtu
|
|||
var items []GetVirtualGameSummaryInRangeRow
|
||||
for rows.Next() {
|
||||
var i GetVirtualGameSummaryInRangeRow
|
||||
if err := rows.Scan(&i.GameName, &i.NumberOfBets, &i.TotalTransactionSum); err != nil {
|
||||
if err := rows.Scan(
|
||||
&i.CompanyName,
|
||||
&i.GameName,
|
||||
&i.NumberOfBets,
|
||||
&i.TotalTransactionSum,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
|
|
@ -246,9 +306,23 @@ FROM virtual_game_transactions
|
|||
WHERE external_transaction_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetVirtualGameTransactionByExternalID(ctx context.Context, externalTransactionID string) (VirtualGameTransaction, error) {
|
||||
type GetVirtualGameTransactionByExternalIDRow 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"`
|
||||
Amount int64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
ExternalTransactionID string `json:"external_transaction_id"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetVirtualGameTransactionByExternalID(ctx context.Context, externalTransactionID string) (GetVirtualGameTransactionByExternalIDRow, error) {
|
||||
row := q.db.QueryRow(ctx, GetVirtualGameTransactionByExternalID, externalTransactionID)
|
||||
var i VirtualGameTransaction
|
||||
var i GetVirtualGameTransactionByExternalIDRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.SessionID,
|
||||
|
|
@ -265,6 +339,47 @@ func (q *Queries) GetVirtualGameTransactionByExternalID(ctx context.Context, ext
|
|||
return i, err
|
||||
}
|
||||
|
||||
const ListFavoriteGames = `-- name: ListFavoriteGames :many
|
||||
SELECT game_id
|
||||
FROM favorite_games
|
||||
WHERE user_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) ListFavoriteGames(ctx context.Context, userID int64) ([]int64, error) {
|
||||
rows, err := q.db.Query(ctx, ListFavoriteGames, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []int64
|
||||
for rows.Next() {
|
||||
var game_id int64
|
||||
if err := rows.Scan(&game_id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, game_id)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const RemoveFavoriteGame = `-- name: RemoveFavoriteGame :exec
|
||||
DELETE FROM favorite_games
|
||||
WHERE user_id = $1 AND game_id = $2
|
||||
`
|
||||
|
||||
type RemoveFavoriteGameParams struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
GameID int64 `json:"game_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) RemoveFavoriteGame(ctx context.Context, arg RemoveFavoriteGameParams) error {
|
||||
_, err := q.db.Exec(ctx, RemoveFavoriteGame, arg.UserID, arg.GameID)
|
||||
return err
|
||||
}
|
||||
|
||||
const UpdateVirtualGameSessionStatus = `-- name: UpdateVirtualGameSessionStatus :exec
|
||||
UPDATE virtual_game_sessions
|
||||
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
||||
|
|
|
|||
|
|
@ -181,6 +181,50 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) {
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const GetBranchByWalletID = `-- name: GetBranchByWalletID :one
|
||||
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at
|
||||
FROM branches
|
||||
WHERE wallet_id = $1
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetBranchByWalletID(ctx context.Context, walletID int64) (Branch, error) {
|
||||
row := q.db.QueryRow(ctx, GetBranchByWalletID, walletID)
|
||||
var i Branch
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Location,
|
||||
&i.IsActive,
|
||||
&i.WalletID,
|
||||
&i.BranchManagerID,
|
||||
&i.CompanyID,
|
||||
&i.IsSelfOwned,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetCompanyByWalletID = `-- name: GetCompanyByWalletID :one
|
||||
SELECT id, name, admin_id, wallet_id
|
||||
FROM companies
|
||||
WHERE wallet_id = $1
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetCompanyByWalletID(ctx context.Context, walletID int64) (Company, error) {
|
||||
row := q.db.QueryRow(ctx, GetCompanyByWalletID, walletID)
|
||||
var i Company
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.AdminID,
|
||||
&i.WalletID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetCustomerWallet = `-- name: GetCustomerWallet :one
|
||||
SELECT cw.id,
|
||||
cw.customer_id,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ type ReportData struct {
|
|||
Deposits float64
|
||||
TotalTickets int64
|
||||
VirtualGameStats []VirtualGameStat
|
||||
CompanyReports []CompanyReport
|
||||
BranchReports []BranchReport
|
||||
}
|
||||
|
||||
type VirtualGameStat struct {
|
||||
|
|
@ -366,3 +368,41 @@ type CashierPerformance struct {
|
|||
LastActivity time.Time `json:"last_activity"`
|
||||
ActiveDays int `json:"active_days"`
|
||||
}
|
||||
|
||||
type CompanyWalletBalance struct {
|
||||
CompanyID int64 `json:"company_id"`
|
||||
CompanyName string `json:"company_name"`
|
||||
Balance float64 `json:"balance"`
|
||||
}
|
||||
|
||||
type BranchWalletBalance struct {
|
||||
BranchID int64 `json:"branch_id"`
|
||||
BranchName string `json:"branch_name"`
|
||||
CompanyID int64 `json:"company_id"`
|
||||
Balance float64 `json:"balance"`
|
||||
}
|
||||
|
||||
type LiveWalletMetrics struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
CompanyBalances []CompanyWalletBalance `json:"company_balances"`
|
||||
BranchBalances []BranchWalletBalance `json:"branch_balances"`
|
||||
}
|
||||
|
||||
type CompanyReport struct {
|
||||
CompanyID int64
|
||||
CompanyName string
|
||||
TotalBets int64
|
||||
TotalCashIn float64
|
||||
TotalCashOut float64
|
||||
TotalCashBacks float64
|
||||
}
|
||||
|
||||
type BranchReport struct {
|
||||
BranchID int64
|
||||
BranchName string
|
||||
CompanyID int64
|
||||
TotalBets int64
|
||||
TotalCashIn float64
|
||||
TotalCashOut float64
|
||||
TotalCashBacks float64
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,30 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
type Provider string
|
||||
|
||||
const (
|
||||
PROVIDER_POPOK Provider = "PopOk"
|
||||
PROVIDER_ALEA_PLAY Provider = "AleaPlay"
|
||||
PROVIDER_VELI_GAMES Provider = "VeliGames"
|
||||
)
|
||||
|
||||
type FavoriteGame struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
GameID int64 `json:"game_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type FavoriteGameRequest struct {
|
||||
GameID int64 `json:"game_id"`
|
||||
}
|
||||
|
||||
type FavoriteGameResponse struct {
|
||||
GameID int64 `json:"game_id"`
|
||||
GameName string `json:"game_name"`
|
||||
}
|
||||
|
||||
type VirtualGame struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
|
@ -42,6 +66,8 @@ type VirtualGameHistory struct {
|
|||
ID int64 `json:"id"`
|
||||
SessionID string `json:"session_id,omitempty"` // Optional, if session tracking is used
|
||||
UserID int64 `json:"user_id"`
|
||||
CompanyID int64 `json:"company_id"`
|
||||
Provider string `json:"provider"`
|
||||
WalletID *int64 `json:"wallet_id,omitempty"` // Optional if wallet detail is needed
|
||||
GameID *int64 `json:"game_id,omitempty"` // Optional for game-level analysis
|
||||
TransactionType string `json:"transaction_type"` // BET, WIN, CANCEL, etc.
|
||||
|
|
@ -58,6 +84,9 @@ type VirtualGameTransaction struct {
|
|||
ID int64 `json:"id"`
|
||||
SessionID int64 `json:"session_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
CompanyID int64 `json:"company_id"`
|
||||
Provider string `json:"provider"`
|
||||
GameID string `json:"game_id"`
|
||||
WalletID int64 `json:"wallet_id"`
|
||||
TransactionType string `json:"transaction_type"` // BET, WIN, REFUND, CASHOUT, etc.
|
||||
Amount int64 `json:"amount"` // Always in cents
|
||||
|
|
|
|||
|
|
@ -317,6 +317,40 @@ func (s *Store) CountUnreadNotifications(ctx context.Context, userID int64) (int
|
|||
return count, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error) {
|
||||
dbCompany, err := s.queries.GetCompanyByWalletID(ctx, walletID)
|
||||
if err != nil {
|
||||
return domain.Company{}, err
|
||||
}
|
||||
|
||||
return domain.Company{
|
||||
ID: dbCompany.ID,
|
||||
Name: dbCompany.Name,
|
||||
AdminID: dbCompany.AdminID,
|
||||
WalletID: dbCompany.WalletID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error) {
|
||||
dbBranch, err := s.queries.GetBranchByWalletID(ctx, walletID)
|
||||
if err != nil {
|
||||
return domain.Branch{}, err
|
||||
}
|
||||
|
||||
return domain.Branch{
|
||||
ID: dbBranch.ID,
|
||||
Name: dbBranch.Name,
|
||||
Location: dbBranch.Location,
|
||||
IsSuspended: dbBranch.IsActive,
|
||||
WalletID: dbBranch.WalletID,
|
||||
BranchManagerID: dbBranch.BranchManagerID,
|
||||
CompanyID: dbBranch.CompanyID,
|
||||
IsSelfOwned: dbBranch.IsSelfOwned,
|
||||
// Creat: dbBranch.CreatedAt.Time,
|
||||
// UpdatedAt: dbBranch.UpdatedAt.Time,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// func (s *Store) GetAllNotifications(ctx context.Context, limit, offset int) ([]domain.Notification, error) {
|
||||
// dbNotifications, err := s.queries.GetAllNotifications(ctx, dbgen.GetAllNotificationsParams{
|
||||
// Limit: int32(limit),
|
||||
|
|
|
|||
|
|
@ -15,13 +15,15 @@ type ReportRepository interface {
|
|||
SaveReport(report *domain.Report) error
|
||||
FindReportsByTimeFrame(timeFrame domain.TimeFrame, limit int) ([]*domain.Report, error)
|
||||
|
||||
GetTotalCashOutInRange(ctx context.Context, from, to time.Time, companyID int64) (float64, error)
|
||||
GetTotalCashMadeInRange(ctx context.Context, from, to time.Time, companyID int64) (float64, error)
|
||||
GetTotalCashBacksInRange(ctx context.Context, from, to time.Time, companyID int64) (float64, error)
|
||||
GetTotalBetsMadeInRange(ctx context.Context, from, to time.Time, companyID int64) (int64, error)
|
||||
GetTotalCashOutInRange(ctx context.Context, from, to time.Time) (float64, error)
|
||||
GetTotalCashMadeInRange(ctx context.Context, from, to time.Time) (float64, error)
|
||||
GetTotalCashBacksInRange(ctx context.Context, from, to time.Time) (float64, error)
|
||||
GetTotalBetsMadeInRange(ctx context.Context, from, to time.Time) (int64, error)
|
||||
GetVirtualGameSummaryInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetVirtualGameSummaryInRangeRow, error)
|
||||
GetAllTicketsInRange(ctx context.Context, from, to time.Time) (dbgen.GetAllTicketsInRangeRow, error)
|
||||
GetWalletTransactionsInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetWalletTransactionsInRangeRow, error)
|
||||
GetCompanyWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetCompanyWiseReportRow, error)
|
||||
GetBranchWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetBranchWiseReportRow, error)
|
||||
}
|
||||
|
||||
type ReportRepo struct {
|
||||
|
|
@ -117,20 +119,18 @@ func (r *ReportRepo) FindReportsByTimeFrame(timeFrame domain.TimeFrame, limit in
|
|||
return reports, nil
|
||||
}
|
||||
|
||||
func (r *ReportRepo) GetTotalBetsMadeInRange(ctx context.Context, from, to time.Time, companyID int64) (int64, error) {
|
||||
func (r *ReportRepo) GetTotalBetsMadeInRange(ctx context.Context, from, to time.Time) (int64, error) {
|
||||
params := dbgen.GetTotalBetsMadeInRangeParams{
|
||||
From: ToPgTimestamp(from),
|
||||
To: ToPgTimestamp(to),
|
||||
CompanyID: ToPgInt8(companyID),
|
||||
}
|
||||
return r.store.queries.GetTotalBetsMadeInRange(ctx, params)
|
||||
}
|
||||
|
||||
func (r *ReportRepo) GetTotalCashBacksInRange(ctx context.Context, from, to time.Time, companyID int64) (float64, error) {
|
||||
func (r *ReportRepo) GetTotalCashBacksInRange(ctx context.Context, from, to time.Time) (float64, error) {
|
||||
params := dbgen.GetTotalCashBacksInRangeParams{
|
||||
From: ToPgTimestamp(from),
|
||||
To: ToPgTimestamp(to),
|
||||
CompanyID: ToPgInt8(companyID),
|
||||
}
|
||||
value, err := r.store.queries.GetTotalCashBacksInRange(ctx, params)
|
||||
if err != nil {
|
||||
|
|
@ -139,11 +139,10 @@ func (r *ReportRepo) GetTotalCashBacksInRange(ctx context.Context, from, to time
|
|||
return parseFloat(value)
|
||||
}
|
||||
|
||||
func (r *ReportRepo) GetTotalCashMadeInRange(ctx context.Context, from, to time.Time, companyID int64) (float64, error) {
|
||||
func (r *ReportRepo) GetTotalCashMadeInRange(ctx context.Context, from, to time.Time) (float64, error) {
|
||||
params := dbgen.GetTotalCashMadeInRangeParams{
|
||||
From: ToPgTimestamp(from),
|
||||
To: ToPgTimestamp(to),
|
||||
CompanyID: ToPgInt8(companyID),
|
||||
}
|
||||
value, err := r.store.queries.GetTotalCashMadeInRange(ctx, params)
|
||||
if err != nil {
|
||||
|
|
@ -152,11 +151,10 @@ func (r *ReportRepo) GetTotalCashMadeInRange(ctx context.Context, from, to time.
|
|||
return parseFloat(value)
|
||||
}
|
||||
|
||||
func (r *ReportRepo) GetTotalCashOutInRange(ctx context.Context, from, to time.Time, companyID int64) (float64, error) {
|
||||
func (r *ReportRepo) GetTotalCashOutInRange(ctx context.Context, from, to time.Time) (float64, error) {
|
||||
params := dbgen.GetTotalCashOutInRangeParams{
|
||||
From: ToPgTimestamp(from),
|
||||
To: ToPgTimestamp(to),
|
||||
CompanyID: ToPgInt8(companyID),
|
||||
}
|
||||
value, err := r.store.queries.GetTotalCashOutInRange(ctx, params)
|
||||
if err != nil {
|
||||
|
|
@ -183,8 +181,8 @@ func (r *ReportRepo) GetAllTicketsInRange(ctx context.Context, from, to time.Tim
|
|||
|
||||
func (r *ReportRepo) GetVirtualGameSummaryInRange(ctx context.Context, from, to time.Time) ([]dbgen.GetVirtualGameSummaryInRangeRow, error) {
|
||||
params := dbgen.GetVirtualGameSummaryInRangeParams{
|
||||
CreatedAt: ToPgTimestamp(from),
|
||||
CreatedAt_2: ToPgTimestamp(to),
|
||||
CreatedAt: ToPgTimestamptz(from),
|
||||
CreatedAt_2: ToPgTimestamptz(to),
|
||||
}
|
||||
return r.store.queries.GetVirtualGameSummaryInRange(ctx, params)
|
||||
}
|
||||
|
|
@ -193,8 +191,8 @@ func ToPgTimestamp(t time.Time) pgtype.Timestamp {
|
|||
return pgtype.Timestamp{Time: t, Valid: true}
|
||||
}
|
||||
|
||||
func ToPgInt8(i int64) pgtype.Int8 {
|
||||
return pgtype.Int8{Int64: i, Valid: true}
|
||||
func ToPgTimestamptz(t time.Time) pgtype.Timestamptz {
|
||||
return pgtype.Timestamptz{Time: t, Valid: true}
|
||||
}
|
||||
|
||||
func parseFloat(value interface{}) (float64, error) {
|
||||
|
|
@ -218,3 +216,19 @@ func parseFloat(value interface{}) (float64, error) {
|
|||
return 0, fmt.Errorf("unexpected type %T for value: %+v", v, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReportRepo) GetCompanyWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetCompanyWiseReportRow, error) {
|
||||
params := dbgen.GetCompanyWiseReportParams{
|
||||
From: ToPgTimestamp(from),
|
||||
To: ToPgTimestamp(to),
|
||||
}
|
||||
return r.store.queries.GetCompanyWiseReport(ctx, params)
|
||||
}
|
||||
|
||||
func (r *ReportRepo) GetBranchWiseReport(ctx context.Context, from, to time.Time) ([]dbgen.GetBranchWiseReportRow, error) {
|
||||
params := dbgen.GetBranchWiseReportParams{
|
||||
From: ToPgTimestamp(from),
|
||||
To: ToPgTimestamp(to),
|
||||
}
|
||||
return r.store.queries.GetBranchWiseReport(ctx, params)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ type VirtualGameRepository interface {
|
|||
GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error)
|
||||
UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) error
|
||||
// WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error
|
||||
AddFavoriteGame(ctx context.Context, userID, gameID int64) error
|
||||
RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error
|
||||
ListFavoriteGames(ctx context.Context, userID int64) ([]int64, error)
|
||||
|
||||
GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
||||
GetUserGameHistory(ctx context.Context, userID int64) ([]domain.VirtualGameHistory, error)
|
||||
|
|
@ -38,6 +41,26 @@ func NewVirtualGameRepository(store *Store) VirtualGameRepository {
|
|||
return &VirtualGameRepo{store: store}
|
||||
}
|
||||
|
||||
func (r *VirtualGameRepo) AddFavoriteGame(ctx context.Context, userID, gameID int64) error {
|
||||
params := dbgen.AddFavoriteGameParams{
|
||||
UserID: userID,
|
||||
GameID: gameID,
|
||||
}
|
||||
return r.store.queries.AddFavoriteGame(ctx, params)
|
||||
}
|
||||
|
||||
func (r *VirtualGameRepo) RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error {
|
||||
params := dbgen.RemoveFavoriteGameParams{
|
||||
UserID: userID,
|
||||
GameID: gameID,
|
||||
}
|
||||
return r.store.queries.RemoveFavoriteGame(ctx, params)
|
||||
}
|
||||
|
||||
func (r *VirtualGameRepo) ListFavoriteGames(ctx context.Context, userID int64) ([]int64, error) {
|
||||
return r.store.queries.ListFavoriteGames(ctx, userID)
|
||||
}
|
||||
|
||||
func (r *VirtualGameRepo) CreateVirtualGameSession(ctx context.Context, session *domain.VirtualGameSession) error {
|
||||
params := dbgen.CreateVirtualGameSessionParams{
|
||||
UserID: session.UserID,
|
||||
|
|
|
|||
|
|
@ -257,3 +257,4 @@ func (s *Store) GetTotalWallets(ctx context.Context, filter domain.ReportFilter)
|
|||
|
||||
return total, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import (
|
|||
)
|
||||
|
||||
type NotificationStore interface {
|
||||
GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error)
|
||||
GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error)
|
||||
SendNotification(ctx context.Context, notification *domain.Notification) error
|
||||
MarkAsRead(ctx context.Context, notificationID string, recipientID int64) error
|
||||
ListNotifications(ctx context.Context, recipientID int64, limit, offset int) ([]domain.Notification, error)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
|
||||
// "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws"
|
||||
afro "github.com/amanuelabay/afrosms-go"
|
||||
"github.com/gorilla/websocket"
|
||||
|
|
@ -21,6 +23,7 @@ import (
|
|||
type Service struct {
|
||||
repo repository.NotificationRepository
|
||||
Hub *ws.NotificationHub
|
||||
notificationStore NotificationStore
|
||||
connections sync.Map
|
||||
notificationCh chan *domain.Notification
|
||||
stopCh chan struct{}
|
||||
|
|
@ -32,7 +35,7 @@ type Service struct {
|
|||
func New(repo repository.NotificationRepository, logger *slog.Logger, cfg *config.Config) *Service {
|
||||
hub := ws.NewNotificationHub()
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: cfg.RedisAddr, // e.g., “redis:6379”
|
||||
Addr: cfg.RedisAddr, // e.g., "redis:6379"
|
||||
})
|
||||
|
||||
svc := &Service{
|
||||
|
|
@ -264,7 +267,8 @@ func (s *Service) retryFailedNotifications() {
|
|||
go func(notification *domain.Notification) {
|
||||
for attempt := 0; attempt < 3; attempt++ {
|
||||
time.Sleep(time.Duration(attempt) * time.Second)
|
||||
if notification.DeliveryChannel == domain.DeliveryChannelSMS {
|
||||
switch notification.DeliveryChannel {
|
||||
case domain.DeliveryChannelSMS:
|
||||
if err := s.SendSMS(ctx, notification.RecipientID, notification.Payload.Message); err == nil {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
|
||||
|
|
@ -273,7 +277,7 @@ func (s *Service) retryFailedNotifications() {
|
|||
s.logger.Info("[NotificationSvc.RetryFailedNotifications] Successfully retried notification", "id", notification.ID)
|
||||
return
|
||||
}
|
||||
} else if notification.DeliveryChannel == domain.DeliveryChannelEmail {
|
||||
case domain.DeliveryChannelEmail:
|
||||
if err := s.SendEmail(ctx, notification.RecipientID, notification.Payload.Headline, notification.Payload.Message); err == nil {
|
||||
notification.DeliveryStatus = domain.DeliveryStatusSent
|
||||
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil {
|
||||
|
|
@ -303,52 +307,60 @@ func (s *Service) RunRedisSubscriber(ctx context.Context) {
|
|||
|
||||
ch := pubsub.Channel()
|
||||
for msg := range ch {
|
||||
var payload domain.LiveMetric
|
||||
if err := json.Unmarshal([]byte(msg.Payload), &payload); err != nil {
|
||||
s.logger.Error("[NotificationSvc.runRedisSubscriber] failed unmarshal metric", "error", err)
|
||||
var parsed map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(msg.Payload), &parsed); err != nil {
|
||||
s.logger.Error("invalid Redis message format", "payload", msg.Payload, "error", err)
|
||||
continue
|
||||
}
|
||||
// Broadcast via WebSocket Hub
|
||||
s.Hub.Broadcast <- map[string]interface{}{
|
||||
"type": "LIVE_METRIC_UPDATE",
|
||||
|
||||
eventType, _ := parsed["type"].(string)
|
||||
payload := parsed["payload"]
|
||||
recipientID, hasRecipient := parsed["recipient_id"]
|
||||
recipientType, _ := parsed["recipient_type"].(string)
|
||||
|
||||
message := map[string]interface{}{
|
||||
"type": eventType,
|
||||
"payload": payload,
|
||||
}
|
||||
|
||||
if hasRecipient {
|
||||
message["recipient_id"] = recipientID
|
||||
message["recipient_type"] = recipientType
|
||||
}
|
||||
|
||||
s.Hub.Broadcast <- message
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) UpdateLiveMetrics(ctx context.Context, updates domain.MetricUpdates) error {
|
||||
func (s *Service) UpdateLiveWalletMetrics(ctx context.Context, companies []domain.GetCompany, branches []domain.BranchWallet) error {
|
||||
const key = "live_metrics"
|
||||
|
||||
val, err := s.redisClient.Get(ctx, key).Result()
|
||||
var metric domain.LiveMetric
|
||||
if err == redis.Nil {
|
||||
metric = domain.LiveMetric{}
|
||||
} else if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if err := json.Unmarshal([]byte(val), &metric); err != nil {
|
||||
return err
|
||||
}
|
||||
companyBalances := make([]domain.CompanyWalletBalance, 0, len(companies))
|
||||
for _, c := range companies {
|
||||
companyBalances = append(companyBalances, domain.CompanyWalletBalance{
|
||||
CompanyID: c.ID,
|
||||
CompanyName: c.Name,
|
||||
Balance: float64(c.WalletBalance.Float32()),
|
||||
})
|
||||
}
|
||||
|
||||
// Apply increments if provided
|
||||
if updates.TotalCashSportsbookDelta != nil {
|
||||
metric.TotalCashSportsbook += *updates.TotalCashSportsbookDelta
|
||||
}
|
||||
if updates.TotalCashSportGamesDelta != nil {
|
||||
metric.TotalCashSportGames += *updates.TotalCashSportGamesDelta
|
||||
}
|
||||
if updates.TotalLiveTicketsDelta != nil {
|
||||
metric.TotalLiveTickets += *updates.TotalLiveTicketsDelta
|
||||
}
|
||||
if updates.TotalUnsettledCashDelta != nil {
|
||||
metric.TotalUnsettledCash += *updates.TotalUnsettledCashDelta
|
||||
}
|
||||
if updates.TotalGamesDelta != nil {
|
||||
metric.TotalGames += *updates.TotalGamesDelta
|
||||
branchBalances := make([]domain.BranchWalletBalance, 0, len(branches))
|
||||
for _, b := range branches {
|
||||
branchBalances = append(branchBalances, domain.BranchWalletBalance{
|
||||
BranchID: b.ID,
|
||||
BranchName: b.Name,
|
||||
CompanyID: b.CompanyID,
|
||||
Balance: float64(b.Balance.Float32()),
|
||||
})
|
||||
}
|
||||
|
||||
updatedData, err := json.Marshal(metric)
|
||||
payload := domain.LiveWalletMetrics{
|
||||
Timestamp: time.Now(),
|
||||
CompanyBalances: companyBalances,
|
||||
BranchBalances: branchBalances,
|
||||
}
|
||||
|
||||
updatedData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -357,11 +369,9 @@ func (s *Service) UpdateLiveMetrics(ctx context.Context, updates domain.MetricUp
|
|||
return err
|
||||
}
|
||||
|
||||
if err := s.redisClient.Publish(ctx, "live_metrics", updatedData).Err(); err != nil {
|
||||
if err := s.redisClient.Publish(ctx, key, updatedData).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Info("[NotificationSvc.UpdateLiveMetrics] Live metrics updated and broadcasted")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -383,3 +393,83 @@ func (s *Service) GetLiveMetrics(ctx context.Context) (domain.LiveMetric, error)
|
|||
|
||||
return metric, nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateLiveWalletMetricForWallet(ctx context.Context, wallet domain.Wallet) {
|
||||
var (
|
||||
payload domain.LiveWalletMetrics
|
||||
event map[string]interface{}
|
||||
key = "live_metrics"
|
||||
)
|
||||
|
||||
// Try company first
|
||||
company, companyErr := s.notificationStore.GetCompanyByWalletID(ctx, wallet.ID)
|
||||
if companyErr == nil {
|
||||
payload = domain.LiveWalletMetrics{
|
||||
Timestamp: time.Now(),
|
||||
CompanyBalances: []domain.CompanyWalletBalance{{
|
||||
CompanyID: company.ID,
|
||||
CompanyName: company.Name,
|
||||
Balance: float64(wallet.Balance),
|
||||
}},
|
||||
BranchBalances: []domain.BranchWalletBalance{},
|
||||
}
|
||||
|
||||
event = map[string]interface{}{
|
||||
"type": "LIVE_WALLET_METRICS_UPDATE",
|
||||
"recipient_id": company.ID,
|
||||
"recipient_type": "company",
|
||||
"payload": payload,
|
||||
}
|
||||
} else {
|
||||
// Try branch next
|
||||
branch, branchErr := s.notificationStore.GetBranchByWalletID(ctx, wallet.ID)
|
||||
if branchErr == nil {
|
||||
payload = domain.LiveWalletMetrics{
|
||||
Timestamp: time.Now(),
|
||||
CompanyBalances: []domain.CompanyWalletBalance{},
|
||||
BranchBalances: []domain.BranchWalletBalance{{
|
||||
BranchID: branch.ID,
|
||||
BranchName: branch.Name,
|
||||
CompanyID: branch.CompanyID,
|
||||
Balance: float64(wallet.Balance),
|
||||
}},
|
||||
}
|
||||
|
||||
event = map[string]interface{}{
|
||||
"type": "LIVE_WALLET_METRICS_UPDATE",
|
||||
"recipient_id": branch.ID,
|
||||
"recipient_type": "branch",
|
||||
"payload": payload,
|
||||
}
|
||||
} else {
|
||||
// Neither company nor branch matched this wallet
|
||||
s.logger.Warn("wallet not linked to any company or branch", "walletID", wallet.ID)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Save latest metric to Redis
|
||||
if jsonBytes, err := json.Marshal(payload); err == nil {
|
||||
s.redisClient.Set(ctx, key, jsonBytes, 0)
|
||||
} else {
|
||||
s.logger.Error("failed to marshal wallet metrics payload", "walletID", wallet.ID, "err", err)
|
||||
}
|
||||
|
||||
// Publish via Redis
|
||||
if jsonEvent, err := json.Marshal(event); err == nil {
|
||||
s.redisClient.Publish(ctx, key, jsonEvent)
|
||||
} else {
|
||||
s.logger.Error("failed to marshal event payload", "walletID", wallet.ID, "err", err)
|
||||
}
|
||||
|
||||
// Broadcast over WebSocket
|
||||
s.Hub.Broadcast <- event
|
||||
}
|
||||
|
||||
func (s *Service) GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error) {
|
||||
return s.notificationStore.GetCompanyByWalletID(ctx, walletID)
|
||||
}
|
||||
|
||||
func (s *Service) GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error) {
|
||||
return s.notificationStore.GetBranchByWalletID(ctx, walletID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -476,6 +476,7 @@ func (s *Service) GenerateReport(ctx context.Context, period string) error {
|
|||
defer writer.Flush()
|
||||
|
||||
// Summary section
|
||||
writer.Write([]string{"Sports Betting Reports (Periodic)"})
|
||||
writer.Write([]string{"Period", "Total Bets", "Total Cash Made", "Total Cash Out", "Total Cash Backs", "Total Deposits", "Total Withdrawals", "Total Tickets"})
|
||||
writer.Write([]string{
|
||||
period,
|
||||
|
|
@ -491,6 +492,7 @@ func (s *Service) GenerateReport(ctx context.Context, period string) error {
|
|||
writer.Write([]string{}) // Empty line for spacing
|
||||
|
||||
// Virtual Game Summary section
|
||||
writer.Write([]string{"Virtual Game Reports (Periodic)"})
|
||||
writer.Write([]string{"Game Name", "Number of Bets", "Total Transaction Sum"})
|
||||
for _, row := range data.VirtualGameStats {
|
||||
writer.Write([]string{
|
||||
|
|
@ -500,18 +502,66 @@ func (s *Service) GenerateReport(ctx context.Context, period string) error {
|
|||
})
|
||||
}
|
||||
|
||||
writer.Write([]string{}) // Empty line
|
||||
writer.Write([]string{"Company Reports (Periodic)"})
|
||||
writer.Write([]string{"Company ID", "Company Name", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||
for _, cr := range data.CompanyReports {
|
||||
writer.Write([]string{
|
||||
fmt.Sprintf("%d", cr.CompanyID),
|
||||
cr.CompanyName,
|
||||
fmt.Sprintf("%d", cr.TotalBets),
|
||||
fmt.Sprintf("%.2f", cr.TotalCashIn),
|
||||
fmt.Sprintf("%.2f", cr.TotalCashOut),
|
||||
fmt.Sprintf("%.2f", cr.TotalCashBacks),
|
||||
})
|
||||
}
|
||||
|
||||
writer.Write([]string{}) // Empty line
|
||||
writer.Write([]string{"Branch Reports (Periodic)"})
|
||||
writer.Write([]string{"Branch ID", "Branch Name", "Company ID", "Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||
for _, br := range data.BranchReports {
|
||||
writer.Write([]string{
|
||||
fmt.Sprintf("%d", br.BranchID),
|
||||
br.BranchName,
|
||||
fmt.Sprintf("%d", br.CompanyID),
|
||||
fmt.Sprintf("%d", br.TotalBets),
|
||||
fmt.Sprintf("%.2f", br.TotalCashIn),
|
||||
fmt.Sprintf("%.2f", br.TotalCashOut),
|
||||
fmt.Sprintf("%.2f", br.TotalCashBacks),
|
||||
})
|
||||
}
|
||||
|
||||
var totalBets int64
|
||||
var totalCashIn, totalCashOut, totalCashBacks float64
|
||||
for _, cr := range data.CompanyReports {
|
||||
totalBets += cr.TotalBets
|
||||
totalCashIn += cr.TotalCashIn
|
||||
totalCashOut += cr.TotalCashOut
|
||||
totalCashBacks += cr.TotalCashBacks
|
||||
}
|
||||
|
||||
writer.Write([]string{})
|
||||
writer.Write([]string{"Total Summary"})
|
||||
writer.Write([]string{"Total Bets", "Total Cash In", "Total Cash Out", "Total Cash Backs"})
|
||||
writer.Write([]string{
|
||||
fmt.Sprintf("%d", totalBets),
|
||||
fmt.Sprintf("%.2f", totalCashIn),
|
||||
fmt.Sprintf("%.2f", totalCashOut),
|
||||
fmt.Sprintf("%.2f", totalCashBacks),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) fetchReportData(ctx context.Context, period string) (domain.ReportData, error) {
|
||||
from, to := getTimeRange(period)
|
||||
companyID := int64(0)
|
||||
// companyID := int64(0)
|
||||
|
||||
// Basic metrics
|
||||
totalBets, _ := s.repo.GetTotalBetsMadeInRange(ctx, from, to, companyID)
|
||||
cashIn, _ := s.repo.GetTotalCashMadeInRange(ctx, from, to, companyID)
|
||||
cashOut, _ := s.repo.GetTotalCashOutInRange(ctx, from, to, companyID)
|
||||
cashBacks, _ := s.repo.GetTotalCashBacksInRange(ctx, from, to, companyID)
|
||||
totalBets, _ := s.repo.GetTotalBetsMadeInRange(ctx, from, to)
|
||||
cashIn, _ := s.repo.GetTotalCashMadeInRange(ctx, from, to)
|
||||
cashOut, _ := s.repo.GetTotalCashOutInRange(ctx, from, to)
|
||||
cashBacks, _ := s.repo.GetTotalCashBacksInRange(ctx, from, to)
|
||||
|
||||
// Wallet Transactions
|
||||
transactions, _ := s.repo.GetWalletTransactionsInRange(ctx, from, to)
|
||||
|
|
@ -555,6 +605,113 @@ func (s *Service) fetchReportData(ctx context.Context, period string) (domain.Re
|
|||
})
|
||||
}
|
||||
|
||||
companyRows, _ := s.repo.GetCompanyWiseReport(ctx, from, to)
|
||||
var companyReports []domain.CompanyReport
|
||||
for _, row := range companyRows {
|
||||
var totalCashIn, totalCashOut, totalCashBacks float64
|
||||
switch v := row.TotalCashMade.(type) {
|
||||
case string:
|
||||
val, err := strconv.ParseFloat(v, 64)
|
||||
if err == nil {
|
||||
totalCashIn = val
|
||||
}
|
||||
case float64:
|
||||
totalCashIn = v
|
||||
case int:
|
||||
totalCashIn = float64(v)
|
||||
default:
|
||||
totalCashIn = 0
|
||||
}
|
||||
switch v := row.TotalCashOut.(type) {
|
||||
case string:
|
||||
val, err := strconv.ParseFloat(v, 64)
|
||||
if err == nil {
|
||||
totalCashOut = val
|
||||
}
|
||||
case float64:
|
||||
totalCashOut = v
|
||||
case int:
|
||||
totalCashOut = float64(v)
|
||||
default:
|
||||
totalCashOut = 0
|
||||
}
|
||||
switch v := row.TotalCashBacks.(type) {
|
||||
case string:
|
||||
val, err := strconv.ParseFloat(v, 64)
|
||||
if err == nil {
|
||||
totalCashBacks = val
|
||||
}
|
||||
case float64:
|
||||
totalCashBacks = v
|
||||
case int:
|
||||
totalCashBacks = float64(v)
|
||||
default:
|
||||
totalCashBacks = 0
|
||||
}
|
||||
companyReports = append(companyReports, domain.CompanyReport{
|
||||
CompanyID: row.CompanyID.Int64,
|
||||
CompanyName: row.CompanyName,
|
||||
TotalBets: row.TotalBets,
|
||||
TotalCashIn: totalCashIn,
|
||||
TotalCashOut: totalCashOut,
|
||||
TotalCashBacks: totalCashBacks,
|
||||
})
|
||||
}
|
||||
|
||||
branchRows, _ := s.repo.GetBranchWiseReport(ctx, from, to)
|
||||
var branchReports []domain.BranchReport
|
||||
for _, row := range branchRows {
|
||||
var totalCashIn, totalCashOut, totalCashBacks float64
|
||||
switch v := row.TotalCashMade.(type) {
|
||||
case string:
|
||||
val, err := strconv.ParseFloat(v, 64)
|
||||
if err == nil {
|
||||
totalCashIn = val
|
||||
}
|
||||
case float64:
|
||||
totalCashIn = v
|
||||
case int:
|
||||
totalCashIn = float64(v)
|
||||
default:
|
||||
totalCashIn = 0
|
||||
}
|
||||
switch v := row.TotalCashOut.(type) {
|
||||
case string:
|
||||
val, err := strconv.ParseFloat(v, 64)
|
||||
if err == nil {
|
||||
totalCashOut = val
|
||||
}
|
||||
case float64:
|
||||
totalCashOut = v
|
||||
case int:
|
||||
totalCashOut = float64(v)
|
||||
default:
|
||||
totalCashOut = 0
|
||||
}
|
||||
switch v := row.TotalCashBacks.(type) {
|
||||
case string:
|
||||
val, err := strconv.ParseFloat(v, 64)
|
||||
if err == nil {
|
||||
totalCashBacks = val
|
||||
}
|
||||
case float64:
|
||||
totalCashBacks = v
|
||||
case int:
|
||||
totalCashBacks = float64(v)
|
||||
default:
|
||||
totalCashBacks = 0
|
||||
}
|
||||
branchReports = append(branchReports, domain.BranchReport{
|
||||
BranchID: row.BranchID.Int64,
|
||||
BranchName: row.BranchName,
|
||||
CompanyID: row.CompanyID,
|
||||
TotalBets: row.TotalBets,
|
||||
TotalCashIn: totalCashIn,
|
||||
TotalCashOut: totalCashOut,
|
||||
TotalCashBacks: totalCashBacks,
|
||||
})
|
||||
}
|
||||
|
||||
return domain.ReportData{
|
||||
TotalBets: totalBets,
|
||||
TotalCashIn: cashIn,
|
||||
|
|
@ -564,6 +721,8 @@ func (s *Service) fetchReportData(ctx context.Context, period string) (domain.Re
|
|||
Withdrawals: totalWithdrawals,
|
||||
TotalTickets: totalTickets.TotalTickets,
|
||||
VirtualGameStats: virtualGameStatsDomain,
|
||||
CompanyReports: companyReports,
|
||||
BranchReports: branchReports,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -595,8 +754,6 @@ func getTimeRange(period string) (time.Time, time.Time) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// func (s *Service) GetCompanyPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CompanyPerformance, error) {
|
||||
// // Get company bet activity
|
||||
// companyBets, err := s.betStore.GetCompanyBetActivity(ctx, filter)
|
||||
|
|
|
|||
|
|
@ -226,13 +226,13 @@ func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq,
|
|||
return domain.Ticket{}, rows, err
|
||||
}
|
||||
|
||||
updates := domain.MetricUpdates{
|
||||
TotalLiveTicketsDelta: domain.PtrInt64(1),
|
||||
}
|
||||
// updates := domain.MetricUpdates{
|
||||
// TotalLiveTicketsDelta: domain.PtrInt64(1),
|
||||
// }
|
||||
|
||||
if err := s.notificationSvc.UpdateLiveMetrics(ctx, updates); err != nil {
|
||||
// handle error
|
||||
}
|
||||
// if err := s.notificationSvc.UpdateLiveMetrics(ctx, updates); err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
|
||||
return ticket, rows, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,4 +19,7 @@ type VirtualGameService interface {
|
|||
GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
||||
ListGames(ctx context.Context, currency string) ([]domain.PopOKGame, error)
|
||||
RecommendGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error)
|
||||
AddFavoriteGame(ctx context.Context, userID, gameID int64) error
|
||||
RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error
|
||||
ListFavoriteGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameI
|
|||
sessionId := fmt.Sprintf("%d-%s-%d", userID, gameID, time.Now().UnixNano())
|
||||
token, err := jwtutil.CreatePopOKJwt(
|
||||
userID,
|
||||
user.CompanyID,
|
||||
user.FirstName,
|
||||
currency,
|
||||
"en",
|
||||
|
|
@ -69,6 +70,8 @@ func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameI
|
|||
tx := &domain.VirtualGameHistory{
|
||||
SessionID: sessionId, // Optional: populate if session tracking is implemented
|
||||
UserID: userID,
|
||||
CompanyID: user.CompanyID.Value,
|
||||
Provider: string(domain.PROVIDER_POPOK),
|
||||
GameID: toInt64Ptr(gameID),
|
||||
TransactionType: "LAUNCH",
|
||||
Amount: 0,
|
||||
|
|
@ -211,8 +214,11 @@ func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) (
|
|||
// Create transaction record
|
||||
tx := &domain.VirtualGameTransaction{
|
||||
UserID: claims.UserID,
|
||||
CompanyID: claims.CompanyID.Value,
|
||||
Provider: string(domain.PROVIDER_POPOK),
|
||||
GameID: req.GameID,
|
||||
TransactionType: "BET",
|
||||
Amount: -amountCents, // Negative for bets
|
||||
Amount: amountCents, // Negative for bets
|
||||
Currency: req.Currency,
|
||||
ExternalTransactionID: req.TransactionID,
|
||||
Status: "COMPLETED",
|
||||
|
|
@ -279,6 +285,9 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
|
|||
// 5. Create transaction record
|
||||
tx := &domain.VirtualGameTransaction{
|
||||
UserID: claims.UserID,
|
||||
CompanyID: claims.CompanyID.Value,
|
||||
Provider: string(domain.PROVIDER_POPOK),
|
||||
GameID: req.GameID,
|
||||
TransactionType: "WIN",
|
||||
Amount: amountCents,
|
||||
Currency: req.Currency,
|
||||
|
|
@ -641,7 +650,7 @@ func (s *service) ListGames(ctx context.Context, currency string) ([]domain.PopO
|
|||
|
||||
func (s *service) RecommendGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error) {
|
||||
// Fetch all available games
|
||||
games, err := s.ListGames(ctx, "ETB") // currency can be dynamic
|
||||
games, err := s.ListGames(ctx, "ETB")
|
||||
if err != nil || len(games) == 0 {
|
||||
return nil, fmt.Errorf("could not fetch games")
|
||||
}
|
||||
|
|
@ -705,3 +714,48 @@ func toInt64Ptr(s string) *int64 {
|
|||
}
|
||||
return &id
|
||||
}
|
||||
|
||||
func (s *service) AddFavoriteGame(ctx context.Context, userID, gameID int64) error {
|
||||
return s.repo.AddFavoriteGame(ctx, userID, gameID)
|
||||
}
|
||||
|
||||
func (s *service) RemoveFavoriteGame(ctx context.Context, userID, gameID int64) error {
|
||||
return s.repo.RemoveFavoriteGame(ctx, userID, gameID)
|
||||
}
|
||||
|
||||
func (s *service) ListFavoriteGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error) {
|
||||
gameIDs, err := s.repo.ListFavoriteGames(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to list favorite games", "userID", userID, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(gameIDs) == 0 {
|
||||
return []domain.GameRecommendation{}, nil
|
||||
}
|
||||
|
||||
allGames, err := s.ListGames(ctx, "ETB") // You can use dynamic currency if needed
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var favorites []domain.GameRecommendation
|
||||
idMap := make(map[int64]bool)
|
||||
for _, id := range gameIDs {
|
||||
idMap[id] = true
|
||||
}
|
||||
|
||||
for _, g := range allGames {
|
||||
if idMap[int64(g.ID)] {
|
||||
favorites = append(favorites, domain.GameRecommendation{
|
||||
GameID: g.ID,
|
||||
GameName: g.GameName,
|
||||
Thumbnail: g.Thumbnail,
|
||||
Bets: g.Bets,
|
||||
Reason: "Marked as favorite",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return favorites, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import (
|
|||
)
|
||||
|
||||
type WalletStore interface {
|
||||
// GetCompanyByWalletID(ctx context.Context, walletID int64) (domain.Company, error)
|
||||
// GetBranchByWalletID(ctx context.Context, walletID int64) (domain.Branch, error)
|
||||
CreateWallet(ctx context.Context, wallet domain.CreateWallet) (domain.Wallet, error)
|
||||
CreateCustomerWallet(ctx context.Context, customerWallet domain.CreateCustomerWallet) (domain.CustomerWallet, error)
|
||||
GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ type Service struct {
|
|||
walletStore WalletStore
|
||||
transferStore TransferStore
|
||||
notificationStore notificationservice.NotificationStore
|
||||
notificationSvc *notificationservice.Service
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,11 +66,22 @@ func (s *Service) GetAllBranchWallets(ctx context.Context) ([]domain.BranchWalle
|
|||
}
|
||||
|
||||
func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error {
|
||||
return s.walletStore.UpdateBalance(ctx, id, balance)
|
||||
err := s.walletStore.UpdateBalance(ctx, id, balance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wallet, err := s.GetWalletByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) AddToWallet(
|
||||
ctx context.Context, id int64, amount domain.Currency, cashierID domain.ValidInt64, paymentMethod domain.PaymentMethod, paymentDetails domain. PaymentDetails) (domain.Transfer, error) {
|
||||
ctx context.Context, id int64, amount domain.Currency, cashierID domain.ValidInt64, paymentMethod domain.PaymentMethod, paymentDetails domain.PaymentDetails) (domain.Transfer, error) {
|
||||
wallet, err := s.GetWalletByID(ctx, id)
|
||||
if err != nil {
|
||||
return domain.Transfer{}, err
|
||||
|
|
@ -81,6 +92,8 @@ func (s *Service) AddToWallet(
|
|||
return domain.Transfer{}, err
|
||||
}
|
||||
|
||||
go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet)
|
||||
|
||||
// Log the transfer here for reference
|
||||
newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{
|
||||
Amount: amount,
|
||||
|
|
@ -118,6 +131,8 @@ func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain.
|
|||
return domain.Transfer{}, nil
|
||||
}
|
||||
|
||||
go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet)
|
||||
|
||||
// Log the transfer here for reference
|
||||
newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{
|
||||
Amount: amount,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
|
@ -25,9 +27,9 @@ type launchVirtualGameRes struct {
|
|||
// @Security Bearer
|
||||
// @Param launch body launchVirtualGameReq true "Game launch details"
|
||||
// @Success 200 {object} launchVirtualGameRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 401 {object} domain.ErrorResponse
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /virtual-game/launch [post]
|
||||
func (h *Handler) LaunchVirtualGame(c *fiber.Ctx) error {
|
||||
|
||||
|
|
@ -37,6 +39,12 @@ func (h *Handler) LaunchVirtualGame(c *fiber.Ctx) error {
|
|||
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
|
||||
}
|
||||
|
||||
// companyID, ok := c.Locals("company_id").(int64)
|
||||
// if !ok || companyID == 0 {
|
||||
// h.logger.Error("Invalid company ID in context")
|
||||
// return fiber.NewError(fiber.StatusUnauthorized, "Invalid company identification")
|
||||
// }
|
||||
|
||||
var req launchVirtualGameReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("Failed to parse LaunchVirtualGame request", "error", err)
|
||||
|
|
@ -64,9 +72,9 @@ func (h *Handler) LaunchVirtualGame(c *fiber.Ctx) error {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param callback body domain.PopOKCallback true "Callback data"
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Success 200 {object} domain.ErrorResponse
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /virtual-game/callback [post]
|
||||
func (h *Handler) HandleVirtualGameCallback(c *fiber.Ctx) error {
|
||||
var callback domain.PopOKCallback
|
||||
|
|
@ -241,3 +249,77 @@ func (h *Handler) HandlePromoWin(c *fiber.Ctx) error {
|
|||
|
||||
return c.JSON(resp)
|
||||
}
|
||||
|
||||
// AddFavoriteGame godoc
|
||||
// @Summary Add game to favorites
|
||||
// @Description Adds a game to the user's favorite games list
|
||||
// @Tags VirtualGames - Favourites
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body domain.FavoriteGameRequest true "Game ID to add"
|
||||
// @Success 201 {string} domain.Response "created"
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/virtual-game/favorites [post]
|
||||
func (h *Handler) AddFavorite(c *fiber.Ctx) error {
|
||||
userID := c.Locals("user_id").(int64)
|
||||
|
||||
var req domain.FavoriteGameRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request")
|
||||
}
|
||||
|
||||
err := h.virtualGameSvc.AddFavoriteGame(c.Context(), userID, req.GameID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Could not add favorite",
|
||||
Error: err.Error(),
|
||||
})
|
||||
// return fiber.NewError(fiber.StatusInternalServerError, "Could not add favorite")
|
||||
}
|
||||
return c.Status(fiber.StatusCreated).JSON(domain.Response{
|
||||
Message: "Game added to favorites",
|
||||
StatusCode: fiber.StatusCreated,
|
||||
Success: true,
|
||||
})
|
||||
// return c.SendStatus(fiber.StatusCreated)
|
||||
}
|
||||
|
||||
// RemoveFavoriteGame godoc
|
||||
// @Summary Remove game from favorites
|
||||
// @Description Removes a game from the user's favorites
|
||||
// @Tags VirtualGames - Favourites
|
||||
// @Produce json
|
||||
// @Param gameID path int64 true "Game ID to remove"
|
||||
// @Success 200 {string} domain.Response "removed"
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/virtual-game/favorites/{gameID} [delete]
|
||||
func (h *Handler) RemoveFavorite(c *fiber.Ctx) error {
|
||||
userID := c.Locals("user_id").(int64)
|
||||
gameID, _ := strconv.ParseInt(c.Params("gameID"), 10, 64)
|
||||
|
||||
err := h.virtualGameSvc.RemoveFavoriteGame(c.Context(), userID, gameID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Could not remove favorite")
|
||||
}
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
// ListFavoriteGames godoc
|
||||
// @Summary Get user's favorite games
|
||||
// @Description Lists the games that the user marked as favorite
|
||||
// @Tags VirtualGames - Favourites
|
||||
// @Produce json
|
||||
// @Success 200 {array} domain.GameRecommendation
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/virtual-game/favorites [get]
|
||||
func (h *Handler) ListFavorites(c *fiber.Ctx) error {
|
||||
userID := c.Locals("user_id").(int64)
|
||||
|
||||
games, err := h.virtualGameSvc.ListFavoriteGames(c.Context(), userID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Could not fetch favorites")
|
||||
}
|
||||
return c.Status(fiber.StatusOK).JSON(games)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type PopOKClaim struct {
|
|||
Lang string `json:"lang"`
|
||||
Mode string `json:"mode"`
|
||||
SessionID string `json:"session_id"`
|
||||
CompanyID domain.ValidInt64 `json:"company_id"`
|
||||
}
|
||||
|
||||
type JwtConfig struct {
|
||||
|
|
@ -54,7 +55,7 @@ func CreateJwt(userId int64, Role domain.Role, CompanyID domain.ValidInt64, key
|
|||
return jwtToken, err
|
||||
}
|
||||
|
||||
func CreatePopOKJwt(userID int64, username, currency, lang, mode, sessionID, key string, expiry time.Duration) (string, error) {
|
||||
func CreatePopOKJwt(userID int64, CompanyID domain.ValidInt64, username, currency, lang, mode, sessionID, key string, expiry time.Duration) (string, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, PopOKClaim{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Issuer: "fortune-bet",
|
||||
|
|
@ -69,6 +70,7 @@ func CreatePopOKJwt(userID int64, username, currency, lang, mode, sessionID, key
|
|||
Lang: lang,
|
||||
Mode: mode,
|
||||
SessionID: sessionID,
|
||||
CompanyID: CompanyID,
|
||||
})
|
||||
return token.SignedString([]byte(key))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -279,7 +279,9 @@ func (a *App) initAppRoutes() {
|
|||
a.fiber.Post("/tournamentWin ", h.HandleTournamentWin)
|
||||
a.fiber.Get("/popok/games", h.GetGameList)
|
||||
a.fiber.Get("/popok/games/recommend", a.authMiddleware, h.RecommendGames)
|
||||
|
||||
group.Post("/virtual-game/favorites", a.authMiddleware, h.AddFavorite)
|
||||
group.Delete("/virtual-game/favorites/:gameID", a.authMiddleware, h.RemoveFavorite)
|
||||
group.Get("/virtual-game/favorites", a.authMiddleware, h.ListFavorites)
|
||||
}
|
||||
|
||||
///user/profile get
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user