gitignore fix

This commit is contained in:
Yared Yemane 2025-06-13 11:14:14 +03:00
commit 49527cbf2a
75 changed files with 2023 additions and 1210 deletions

View File

@ -119,8 +119,8 @@ func main() {
branchSvc := branch.NewService(store) branchSvc := branch.NewService(store)
companySvc := company.NewService(store) companySvc := company.NewService(store)
leagueSvc := league.New(store) leagueSvc := league.New(store)
betSvc := bet.NewService(store, eventSvc, oddsSvc, *walletSvc, *branchSvc, logger, domain.MongoDBLogger) betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, logger, domain.MongoDBLogger)
resultSvc := result.NewService(store, cfg, logger, *betSvc) resultSvc := result.NewService(store, cfg, logger, *betSvc, *oddsSvc, eventSvc, leagueSvc)
referalRepo := repository.NewReferralRepository(store) referalRepo := repository.NewReferralRepository(store)
vitualGameRepo := repository.NewVirtualGameRepository(store) vitualGameRepo := repository.NewVirtualGameRepository(store)
recommendationRepo := repository.NewRecommendationRepository(store) recommendationRepo := repository.NewRecommendationRepository(store)
@ -177,8 +177,7 @@ func main() {
) )
walletMonitorSvc.Start() walletMonitorSvc.Start()
// Start other cron jobs httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc)
httpserver.StartDataFetchingCrons(eventSvc, oddsSvc, resultSvc)
httpserver.StartTicketCrons(*ticketSvc) httpserver.StartTicketCrons(*ticketSvc)
go httpserver.SetupReportCronJob(reportWorker) go httpserver.SetupReportCronJob(reportWorker)

View File

@ -65,6 +65,7 @@ CREATE TABLE IF NOT EXISTS tickets (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
amount BIGINT NOT NULL, amount BIGINT NOT NULL,
total_odds REAL NOT NULL, total_odds REAL NOT NULL,
IP VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); );
@ -240,6 +241,7 @@ CREATE TABLE leagues (
name TEXT NOT NULL, name TEXT NOT NULL,
country_code TEXT, country_code TEXT,
bet365_id INT, bet365_id INT,
sport_id INT NOT NULL,
is_active BOOLEAN DEFAULT true is_active BOOLEAN DEFAULT true
); );
CREATE TABLE teams ( CREATE TABLE teams (
@ -265,9 +267,11 @@ FROM companies
CREATE VIEW branch_details AS CREATE VIEW branch_details AS
SELECT branches.*, SELECT branches.*,
CONCAT(users.first_name, ' ', users.last_name) AS manager_name, CONCAT(users.first_name, ' ', users.last_name) AS manager_name,
users.phone_number AS manager_phone_number users.phone_number AS manager_phone_number,
wallets.balance
FROM branches FROM branches
LEFT JOIN users ON branches.branch_manager_id = users.id; LEFT JOIN users ON branches.branch_manager_id = users.id
LEFT JOin wallets ON wallets.id = branches.wallet_id;
CREATE TABLE IF NOT EXISTS supported_operations ( CREATE TABLE IF NOT EXISTS supported_operations (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,

View File

@ -2,14 +2,24 @@
SELECT users.* SELECT users.*
FROM branch_cashiers FROM branch_cashiers
JOIN users ON branch_cashiers.user_id = users.id JOIN users ON branch_cashiers.user_id = users.id
JOIN branches ON branches.id = branch_id
WHERE branch_cashiers.branch_id = $1; WHERE branch_cashiers.branch_id = $1;
-- name: GetAllCashiers :many -- name: GetAllCashiers :many
SELECT users.*, SELECT users.*,
branch_id branch_id,
branches.name AS branch_name,
branches.wallet_id AS branch_wallet,
branches.location As branch_location
FROM branch_cashiers FROM branch_cashiers
JOIN users ON branch_cashiers.user_id = users.id; JOIN users ON branch_cashiers.user_id = users.id
JOIN branches ON branches.id = branch_id;
-- name: GetCashierByID :one -- name: GetCashierByID :one
SELECT users.*, SELECT users.*,
branch_id branch_id,
branches.name AS branch_name,
branches.wallet_id AS branch_wallet,
branches.location As branch_location
FROM branch_cashiers FROM branch_cashiers
JOIN users ON branch_cashiers.user_id = $1; JOIN users ON branch_cashiers.user_id = users.id
JOIN branches ON branches.id = branch_id
WHERE users.id = $1;

View File

@ -144,33 +144,40 @@ SELECT id,
source, source,
fetched_at fetched_at
FROM events FROM events
WHERE is_live = false WHERE start_time > now()
AND is_live = false
AND status = 'upcoming' AND status = 'upcoming'
ORDER BY start_time ASC; ORDER BY start_time ASC;
-- name: GetExpiredUpcomingEvents :many -- name: GetExpiredUpcomingEvents :many
SELECT id, SELECT events.id,
sport_id, events.sport_id,
match_name, events.match_name,
home_team, events.home_team,
away_team, events.away_team,
home_team_id, events.home_team_id,
away_team_id, events.away_team_id,
home_kit_image, events.home_kit_image,
away_kit_image, events.away_kit_image,
league_id, events.league_id,
league_name, events.league_name,
league_cc, events.start_time,
start_time, events.is_live,
is_live, events.status,
status, events.source,
source, events.fetched_at,
fetched_at leagues.country_code as league_cc
FROM events FROM events
LEFT JOIN leagues ON leagues.id = league_id
WHERE start_time < now() WHERE start_time < now()
and (
status = sqlc.narg('status')
OR sqlc.narg('status') IS NULL
)
ORDER BY start_time ASC; ORDER BY start_time ASC;
-- name: GetTotalEvents :one -- name: GetTotalEvents :one
SELECT COUNT(*) SELECT COUNT(*)
FROM events FROM events
LEFT JOIN leagues ON leagues.id = league_id
WHERE is_live = false WHERE is_live = false
AND status = 'upcoming' AND status = 'upcoming'
AND ( AND (
@ -178,7 +185,7 @@ WHERE is_live = false
OR sqlc.narg('league_id') IS NULL OR sqlc.narg('league_id') IS NULL
) )
AND ( AND (
sport_id = sqlc.narg('sport_id') events.sport_id = sqlc.narg('sport_id')
OR sqlc.narg('sport_id') IS NULL OR sqlc.narg('sport_id') IS NULL
) )
AND ( AND (
@ -188,34 +195,40 @@ WHERE is_live = false
AND ( AND (
start_time > sqlc.narg('first_start_time') start_time > sqlc.narg('first_start_time')
OR sqlc.narg('first_start_time') IS NULL OR sqlc.narg('first_start_time') IS NULL
)
AND (
leagues.country_code = sqlc.narg('country_code')
OR sqlc.narg('country_code') IS NULL
); );
-- name: GetPaginatedUpcomingEvents :many -- name: GetPaginatedUpcomingEvents :many
SELECT id, SELECT events.id,
sport_id, events.sport_id,
match_name, events.match_name,
home_team, events.home_team,
away_team, events.away_team,
home_team_id, events.home_team_id,
away_team_id, events.away_team_id,
home_kit_image, events.home_kit_image,
away_kit_image, events.away_kit_image,
league_id, events.league_id,
league_name, events.league_name,
league_cc, events.start_time,
start_time, events.is_live,
is_live, events.status,
status, events.source,
source, events.fetched_at,
fetched_at leagues.country_code as league_cc
FROM events FROM events
WHERE is_live = false LEFT JOIN leagues ON leagues.id = league_id
WHERE start_time > now()
AND is_live = false
AND status = 'upcoming' AND status = 'upcoming'
AND ( AND (
league_id = sqlc.narg('league_id') league_id = sqlc.narg('league_id')
OR sqlc.narg('league_id') IS NULL OR sqlc.narg('league_id') IS NULL
) )
AND ( AND (
sport_id = sqlc.narg('sport_id') events.sport_id = sqlc.narg('sport_id')
OR sqlc.narg('sport_id') IS NULL OR sqlc.narg('sport_id') IS NULL
) )
AND ( AND (
@ -226,6 +239,10 @@ WHERE is_live = false
start_time > sqlc.narg('first_start_time') start_time > sqlc.narg('first_start_time')
OR sqlc.narg('first_start_time') IS NULL OR sqlc.narg('first_start_time') IS NULL
) )
AND (
leagues.country_code = sqlc.narg('country_code')
OR sqlc.narg('country_code') IS NULL
)
ORDER BY start_time ASC ORDER BY start_time ASC
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
-- name: GetUpcomingByID :one -- name: GetUpcomingByID :one

View File

@ -4,45 +4,61 @@ INSERT INTO leagues (
name, name,
country_code, country_code,
bet365_id, bet365_id,
sport_id,
is_active is_active
) VALUES ( )
$1, $2, $3, $4, $5 VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (id) DO
) UPDATE
ON CONFLICT (id) DO UPDATE
SET name = EXCLUDED.name, SET name = EXCLUDED.name,
country_code = EXCLUDED.country_code, country_code = EXCLUDED.country_code,
bet365_id = EXCLUDED.bet365_id, bet365_id = EXCLUDED.bet365_id,
is_active = EXCLUDED.is_active; is_active = EXCLUDED.is_active,
-- name: GetSupportedLeagues :many sport_id = EXCLUDED.sport_id;
SELECT id,
name,
country_code,
bet365_id,
is_active
FROM leagues
WHERE is_active = true;
-- name: GetAllLeagues :many -- name: GetAllLeagues :many
SELECT id, SELECT id,
name, name,
country_code, country_code,
bet365_id, bet365_id,
is_active is_active,
FROM leagues; sport_id
FROM leagues
WHERE (
country_code = sqlc.narg('country_code')
OR sqlc.narg('country_code') IS NULL
)
AND (
sport_id = sqlc.narg('sport_id')
OR sqlc.narg('sport_id') IS NULL
)
AND (
is_active = sqlc.narg('is_active')
OR sqlc.narg('is_active') IS NULL
)
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
-- name: CheckLeagueSupport :one -- name: CheckLeagueSupport :one
SELECT EXISTS( SELECT EXISTS(
SELECT 1 SELECT 1
FROM leagues FROM leagues
WHERE id = $1 WHERE id = $1
AND is_active = true AND is_active = true
); );
-- name: UpdateLeague :exec -- name: UpdateLeague :exec
UPDATE leagues UPDATE leagues
SET name = $1, SET name = COALESCE(sqlc.narg('name'), name),
country_code = $2, country_code = COALESCE(sqlc.narg('country_code'), country_code),
bet365_id = $3, bet365_id = COALESCE(sqlc.narg('bet365_id'), bet365_id),
is_active = $4 is_active = COALESCE(sqlc.narg('is_active'), is_active),
WHERE id = $5; sport_id = COALESCE(sqlc.narg('sport_id'), sport_id)
WHERE id = $1;
-- name: UpdateLeagueByBet365ID :exec
UPDATE leagues
SET name = COALESCE(sqlc.narg('name'), name),
id = COALESCE(sqlc.narg('id'), id),
country_code = COALESCE(sqlc.narg('country_code'), country_code),
is_active = COALESCE(sqlc.narg('is_active'), is_active),
sport_id = COALESCE(sqlc.narg('sport_id'), sport_id)
WHERE bet365_id = $1;
-- name: SetLeagueActive :exec -- name: SetLeagueActive :exec
UPDATE leagues UPDATE leagues
SET is_active = true SET is_active = $2
WHERE id = $1; WHERE id = $1;

View File

@ -63,7 +63,7 @@ SELECT event_id,
is_active is_active
FROM odds FROM odds
WHERE is_active = true WHERE is_active = true
AND source = 'b365api'; AND source = 'bet365';
-- name: GetALLPrematchOdds :many -- name: GetALLPrematchOdds :many
SELECT event_id, SELECT event_id,
fi, fi,
@ -82,7 +82,7 @@ SELECT event_id,
is_active is_active
FROM odds FROM odds
WHERE is_active = true WHERE is_active = true
AND source = 'b365api'; AND source = 'bet365';
-- name: GetRawOddsByMarketID :one -- name: GetRawOddsByMarketID :one
SELECT id, SELECT id,
market_name, market_name,
@ -93,7 +93,7 @@ FROM odds
WHERE market_id = $1 WHERE market_id = $1
AND fi = $2 AND fi = $2
AND is_active = true AND is_active = true
AND source = 'b365api'; AND source = 'bet365';
-- name: GetPrematchOddsByUpcomingID :many -- name: GetPrematchOddsByUpcomingID :many
SELECT o.* SELECT o.*
FROM odds o FROM odds o
@ -102,7 +102,7 @@ WHERE e.id = $1
AND e.is_live = false AND e.is_live = false
AND e.status = 'upcoming' AND e.status = 'upcoming'
AND o.is_active = true AND o.is_active = true
AND o.source = 'b365api'; AND o.source = 'bet365';
-- name: GetPaginatedPrematchOddsByUpcomingID :many -- name: GetPaginatedPrematchOddsByUpcomingID :many
SELECT o.* SELECT o.*
FROM odds o FROM odds o
@ -111,5 +111,8 @@ WHERE e.id = $1
AND e.is_live = false AND e.is_live = false
AND e.status = 'upcoming' AND e.status = 'upcoming'
AND o.is_active = true AND o.is_active = true
AND o.source = 'b365api' AND o.source = 'bet365'
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
-- name: DeleteOddsForEvent :exec
DELETE FROM odds
Where fi = $1;

View File

@ -1,6 +1,6 @@
-- name: CreateTicket :one -- name: CreateTicket :one
INSERT INTO tickets (amount, total_odds) INSERT INTO tickets (amount, total_odds, ip)
VALUES ($1, $2) VALUES ($1, $2, $3)
RETURNING *; RETURNING *;
-- name: CreateTicketOutcome :copyfrom -- name: CreateTicketOutcome :copyfrom
INSERT INTO ticket_outcomes ( INSERT INTO ticket_outcomes (
@ -42,6 +42,10 @@ WHERE id = $1;
SELECT * SELECT *
FROM ticket_outcomes FROM ticket_outcomes
WHERE ticket_id = $1; WHERE ticket_id = $1;
-- name: CountTicketByIP :one
SELECT count(id)
FROM tickets
WHERE IP = $1;
-- name: UpdateTicketOutcomeStatus :exec -- name: UpdateTicketOutcomeStatus :exec
UPDATE ticket_outcomes UPDATE ticket_outcomes
SET status = $1 SET status = $1

View File

@ -2850,6 +2850,53 @@ const docTemplate = `{
} }
} }
}, },
"/result/{id}": {
"get": {
"description": "Get results for an event",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"result"
],
"summary": "Get results for an event",
"parameters": [
{
"type": "string",
"description": "Event ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.BetOutcome"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/search/branch": { "/search/branch": {
"get": { "get": {
"description": "Search branches by name or location", "description": "Search branches by name or location",

View File

@ -2842,6 +2842,53 @@
} }
} }
}, },
"/result/{id}": {
"get": {
"description": "Get results for an event",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"result"
],
"summary": "Get results for an event",
"parameters": [
{
"type": "string",
"description": "Event ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.BetOutcome"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/search/branch": { "/search/branch": {
"get": { "get": {
"description": "Search branches by name or location", "description": "Search branches by name or location",

View File

@ -3318,6 +3318,37 @@ paths:
summary: Get referral statistics summary: Get referral statistics
tags: tags:
- referral - referral
/result/{id}:
get:
consumes:
- application/json
description: Get results for an event
parameters:
- description: Event ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/domain.BetOutcome'
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Get results for an event
tags:
- result
/search/branch: /search/branch:
get: get:
consumes: consumes:

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: auth.sql // source: auth.sql
package dbgen package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: bet.sql // source: bet.sql
package dbgen package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: branch.sql // source: branch.sql
package dbgen package dbgen
@ -155,7 +155,7 @@ func (q *Queries) DeleteBranchOperation(ctx context.Context, arg DeleteBranchOpe
} }
const GetAllBranches = `-- name: GetAllBranches :many const GetAllBranches = `-- name: GetAllBranches :many
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance
FROM branch_details FROM branch_details
` `
@ -181,6 +181,7 @@ func (q *Queries) GetAllBranches(ctx context.Context) ([]BranchDetail, error) {
&i.UpdatedAt, &i.UpdatedAt,
&i.ManagerName, &i.ManagerName,
&i.ManagerPhoneNumber, &i.ManagerPhoneNumber,
&i.Balance,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -243,7 +244,7 @@ func (q *Queries) GetBranchByCashier(ctx context.Context, userID int64) (Branch,
} }
const GetBranchByCompanyID = `-- name: GetBranchByCompanyID :many const GetBranchByCompanyID = `-- name: GetBranchByCompanyID :many
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance
FROM branch_details FROM branch_details
WHERE company_id = $1 WHERE company_id = $1
` `
@ -270,6 +271,7 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]
&i.UpdatedAt, &i.UpdatedAt,
&i.ManagerName, &i.ManagerName,
&i.ManagerPhoneNumber, &i.ManagerPhoneNumber,
&i.Balance,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -282,7 +284,7 @@ func (q *Queries) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]
} }
const GetBranchByID = `-- name: GetBranchByID :one const GetBranchByID = `-- name: GetBranchByID :one
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance
FROM branch_details FROM branch_details
WHERE id = $1 WHERE id = $1
` `
@ -303,12 +305,13 @@ func (q *Queries) GetBranchByID(ctx context.Context, id int64) (BranchDetail, er
&i.UpdatedAt, &i.UpdatedAt,
&i.ManagerName, &i.ManagerName,
&i.ManagerPhoneNumber, &i.ManagerPhoneNumber,
&i.Balance,
) )
return i, err return i, err
} }
const GetBranchByManagerID = `-- name: GetBranchByManagerID :many const GetBranchByManagerID = `-- name: GetBranchByManagerID :many
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance
FROM branch_details FROM branch_details
WHERE branch_manager_id = $1 WHERE branch_manager_id = $1
` `
@ -335,6 +338,7 @@ func (q *Queries) GetBranchByManagerID(ctx context.Context, branchManagerID int6
&i.UpdatedAt, &i.UpdatedAt,
&i.ManagerName, &i.ManagerName,
&i.ManagerPhoneNumber, &i.ManagerPhoneNumber,
&i.Balance,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -394,7 +398,7 @@ func (q *Queries) GetBranchOperations(ctx context.Context, branchID int64) ([]Ge
} }
const SearchBranchByName = `-- name: SearchBranchByName :many const SearchBranchByName = `-- name: SearchBranchByName :many
SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number SELECT id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at, manager_name, manager_phone_number, balance
FROM branch_details FROM branch_details
WHERE name ILIKE '%' || $1 || '%' WHERE name ILIKE '%' || $1 || '%'
` `
@ -421,6 +425,7 @@ func (q *Queries) SearchBranchByName(ctx context.Context, dollar_1 pgtype.Text)
&i.UpdatedAt, &i.UpdatedAt,
&i.ManagerName, &i.ManagerName,
&i.ManagerPhoneNumber, &i.ManagerPhoneNumber,
&i.Balance,
); err != nil { ); err != nil {
return nil, err return nil, err
} }

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: cashier.sql // source: cashier.sql
package dbgen package dbgen
@ -13,9 +13,13 @@ import (
const GetAllCashiers = `-- name: GetAllCashiers :many const GetAllCashiers = `-- name: GetAllCashiers :many
SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by, SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by,
branch_id branch_id,
branches.name AS branch_name,
branches.wallet_id AS branch_wallet,
branches.location As branch_location
FROM branch_cashiers FROM branch_cashiers
JOIN users ON branch_cashiers.user_id = users.id JOIN users ON branch_cashiers.user_id = users.id
JOIN branches ON branches.id = branch_id
` `
type GetAllCashiersRow struct { type GetAllCashiersRow struct {
@ -36,6 +40,9 @@ type GetAllCashiersRow struct {
ReferralCode pgtype.Text `json:"referral_code"` ReferralCode pgtype.Text `json:"referral_code"`
ReferredBy pgtype.Text `json:"referred_by"` ReferredBy pgtype.Text `json:"referred_by"`
BranchID int64 `json:"branch_id"` BranchID int64 `json:"branch_id"`
BranchName string `json:"branch_name"`
BranchWallet int64 `json:"branch_wallet"`
BranchLocation string `json:"branch_location"`
} }
func (q *Queries) GetAllCashiers(ctx context.Context) ([]GetAllCashiersRow, error) { func (q *Queries) GetAllCashiers(ctx context.Context) ([]GetAllCashiersRow, error) {
@ -65,6 +72,9 @@ func (q *Queries) GetAllCashiers(ctx context.Context) ([]GetAllCashiersRow, erro
&i.ReferralCode, &i.ReferralCode,
&i.ReferredBy, &i.ReferredBy,
&i.BranchID, &i.BranchID,
&i.BranchName,
&i.BranchWallet,
&i.BranchLocation,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -78,9 +88,14 @@ func (q *Queries) GetAllCashiers(ctx context.Context) ([]GetAllCashiersRow, erro
const GetCashierByID = `-- name: GetCashierByID :one const GetCashierByID = `-- name: GetCashierByID :one
SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by, SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by,
branch_id branch_id,
branches.name AS branch_name,
branches.wallet_id AS branch_wallet,
branches.location As branch_location
FROM branch_cashiers FROM branch_cashiers
JOIN users ON branch_cashiers.user_id = $1 JOIN users ON branch_cashiers.user_id = users.id
JOIN branches ON branches.id = branch_id
WHERE users.id = $1
` `
type GetCashierByIDRow struct { type GetCashierByIDRow struct {
@ -101,10 +116,13 @@ type GetCashierByIDRow struct {
ReferralCode pgtype.Text `json:"referral_code"` ReferralCode pgtype.Text `json:"referral_code"`
ReferredBy pgtype.Text `json:"referred_by"` ReferredBy pgtype.Text `json:"referred_by"`
BranchID int64 `json:"branch_id"` BranchID int64 `json:"branch_id"`
BranchName string `json:"branch_name"`
BranchWallet int64 `json:"branch_wallet"`
BranchLocation string `json:"branch_location"`
} }
func (q *Queries) GetCashierByID(ctx context.Context, userID int64) (GetCashierByIDRow, error) { func (q *Queries) GetCashierByID(ctx context.Context, id int64) (GetCashierByIDRow, error) {
row := q.db.QueryRow(ctx, GetCashierByID, userID) row := q.db.QueryRow(ctx, GetCashierByID, id)
var i GetCashierByIDRow var i GetCashierByIDRow
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
@ -124,6 +142,9 @@ func (q *Queries) GetCashierByID(ctx context.Context, userID int64) (GetCashierB
&i.ReferralCode, &i.ReferralCode,
&i.ReferredBy, &i.ReferredBy,
&i.BranchID, &i.BranchID,
&i.BranchName,
&i.BranchWallet,
&i.BranchLocation,
) )
return i, err return i, err
} }
@ -132,6 +153,7 @@ const GetCashiersByBranch = `-- name: GetCashiersByBranch :many
SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by SELECT users.id, users.first_name, users.last_name, users.email, users.phone_number, users.role, users.password, users.email_verified, users.phone_verified, users.created_at, users.updated_at, users.company_id, users.suspended_at, users.suspended, users.referral_code, users.referred_by
FROM branch_cashiers FROM branch_cashiers
JOIN users ON branch_cashiers.user_id = users.id JOIN users ON branch_cashiers.user_id = users.id
JOIN branches ON branches.id = branch_id
WHERE branch_cashiers.branch_id = $1 WHERE branch_cashiers.branch_id = $1
` `

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: company.sql // source: company.sql
package dbgen package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: copyfrom.go // source: copyfrom.go
package dbgen package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
package dbgen package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: events.sql // source: events.sql
package dbgen package dbgen
@ -40,7 +40,8 @@ SELECT id,
source, source,
fetched_at fetched_at
FROM events FROM events
WHERE is_live = false WHERE start_time > now()
AND is_live = false
AND status = 'upcoming' AND status = 'upcoming'
ORDER BY start_time ASC ORDER BY start_time ASC
` `
@ -104,25 +105,30 @@ func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]GetAllUpcomingEve
} }
const GetExpiredUpcomingEvents = `-- name: GetExpiredUpcomingEvents :many const GetExpiredUpcomingEvents = `-- name: GetExpiredUpcomingEvents :many
SELECT id, SELECT events.id,
sport_id, events.sport_id,
match_name, events.match_name,
home_team, events.home_team,
away_team, events.away_team,
home_team_id, events.home_team_id,
away_team_id, events.away_team_id,
home_kit_image, events.home_kit_image,
away_kit_image, events.away_kit_image,
league_id, events.league_id,
league_name, events.league_name,
league_cc, events.start_time,
start_time, events.is_live,
is_live, events.status,
status, events.source,
source, events.fetched_at,
fetched_at leagues.country_code as league_cc
FROM events FROM events
LEFT JOIN leagues ON leagues.id = league_id
WHERE start_time < now() WHERE start_time < now()
and (
status = $1
OR $1 IS NULL
)
ORDER BY start_time ASC ORDER BY start_time ASC
` `
@ -138,16 +144,16 @@ type GetExpiredUpcomingEventsRow struct {
AwayKitImage pgtype.Text `json:"away_kit_image"` AwayKitImage pgtype.Text `json:"away_kit_image"`
LeagueID pgtype.Int4 `json:"league_id"` LeagueID pgtype.Int4 `json:"league_id"`
LeagueName pgtype.Text `json:"league_name"` LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"` StartTime pgtype.Timestamp `json:"start_time"`
IsLive pgtype.Bool `json:"is_live"` IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
Source pgtype.Text `json:"source"` Source pgtype.Text `json:"source"`
FetchedAt pgtype.Timestamp `json:"fetched_at"` FetchedAt pgtype.Timestamp `json:"fetched_at"`
LeagueCc pgtype.Text `json:"league_cc"`
} }
func (q *Queries) GetExpiredUpcomingEvents(ctx context.Context) ([]GetExpiredUpcomingEventsRow, error) { func (q *Queries) GetExpiredUpcomingEvents(ctx context.Context, status pgtype.Text) ([]GetExpiredUpcomingEventsRow, error) {
rows, err := q.db.Query(ctx, GetExpiredUpcomingEvents) rows, err := q.db.Query(ctx, GetExpiredUpcomingEvents, status)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -167,12 +173,12 @@ func (q *Queries) GetExpiredUpcomingEvents(ctx context.Context) ([]GetExpiredUpc
&i.AwayKitImage, &i.AwayKitImage,
&i.LeagueID, &i.LeagueID,
&i.LeagueName, &i.LeagueName,
&i.LeagueCc,
&i.StartTime, &i.StartTime,
&i.IsLive, &i.IsLive,
&i.Status, &i.Status,
&i.Source, &i.Source,
&i.FetchedAt, &i.FetchedAt,
&i.LeagueCc,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -185,32 +191,34 @@ func (q *Queries) GetExpiredUpcomingEvents(ctx context.Context) ([]GetExpiredUpc
} }
const GetPaginatedUpcomingEvents = `-- name: GetPaginatedUpcomingEvents :many const GetPaginatedUpcomingEvents = `-- name: GetPaginatedUpcomingEvents :many
SELECT id, SELECT events.id,
sport_id, events.sport_id,
match_name, events.match_name,
home_team, events.home_team,
away_team, events.away_team,
home_team_id, events.home_team_id,
away_team_id, events.away_team_id,
home_kit_image, events.home_kit_image,
away_kit_image, events.away_kit_image,
league_id, events.league_id,
league_name, events.league_name,
league_cc, events.start_time,
start_time, events.is_live,
is_live, events.status,
status, events.source,
source, events.fetched_at,
fetched_at leagues.country_code as league_cc
FROM events FROM events
WHERE is_live = false LEFT JOIN leagues ON leagues.id = league_id
WHERE start_time > now()
AND is_live = false
AND status = 'upcoming' AND status = 'upcoming'
AND ( AND (
league_id = $1 league_id = $1
OR $1 IS NULL OR $1 IS NULL
) )
AND ( AND (
sport_id = $2 events.sport_id = $2
OR $2 IS NULL OR $2 IS NULL
) )
AND ( AND (
@ -221,8 +229,12 @@ WHERE is_live = false
start_time > $4 start_time > $4
OR $4 IS NULL OR $4 IS NULL
) )
AND (
leagues.country_code = $5
OR $5 IS NULL
)
ORDER BY start_time ASC ORDER BY start_time ASC
LIMIT $6 OFFSET $5 LIMIT $7 OFFSET $6
` `
type GetPaginatedUpcomingEventsParams struct { type GetPaginatedUpcomingEventsParams struct {
@ -230,6 +242,7 @@ type GetPaginatedUpcomingEventsParams struct {
SportID pgtype.Int4 `json:"sport_id"` SportID pgtype.Int4 `json:"sport_id"`
LastStartTime pgtype.Timestamp `json:"last_start_time"` LastStartTime pgtype.Timestamp `json:"last_start_time"`
FirstStartTime pgtype.Timestamp `json:"first_start_time"` FirstStartTime pgtype.Timestamp `json:"first_start_time"`
CountryCode pgtype.Text `json:"country_code"`
Offset pgtype.Int4 `json:"offset"` Offset pgtype.Int4 `json:"offset"`
Limit pgtype.Int4 `json:"limit"` Limit pgtype.Int4 `json:"limit"`
} }
@ -246,12 +259,12 @@ type GetPaginatedUpcomingEventsRow struct {
AwayKitImage pgtype.Text `json:"away_kit_image"` AwayKitImage pgtype.Text `json:"away_kit_image"`
LeagueID pgtype.Int4 `json:"league_id"` LeagueID pgtype.Int4 `json:"league_id"`
LeagueName pgtype.Text `json:"league_name"` LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"` StartTime pgtype.Timestamp `json:"start_time"`
IsLive pgtype.Bool `json:"is_live"` IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
Source pgtype.Text `json:"source"` Source pgtype.Text `json:"source"`
FetchedAt pgtype.Timestamp `json:"fetched_at"` FetchedAt pgtype.Timestamp `json:"fetched_at"`
LeagueCc pgtype.Text `json:"league_cc"`
} }
func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginatedUpcomingEventsParams) ([]GetPaginatedUpcomingEventsRow, error) { func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginatedUpcomingEventsParams) ([]GetPaginatedUpcomingEventsRow, error) {
@ -260,6 +273,7 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat
arg.SportID, arg.SportID,
arg.LastStartTime, arg.LastStartTime,
arg.FirstStartTime, arg.FirstStartTime,
arg.CountryCode,
arg.Offset, arg.Offset,
arg.Limit, arg.Limit,
) )
@ -282,12 +296,12 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat
&i.AwayKitImage, &i.AwayKitImage,
&i.LeagueID, &i.LeagueID,
&i.LeagueName, &i.LeagueName,
&i.LeagueCc,
&i.StartTime, &i.StartTime,
&i.IsLive, &i.IsLive,
&i.Status, &i.Status,
&i.Source, &i.Source,
&i.FetchedAt, &i.FetchedAt,
&i.LeagueCc,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -302,6 +316,7 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat
const GetTotalEvents = `-- name: GetTotalEvents :one const GetTotalEvents = `-- name: GetTotalEvents :one
SELECT COUNT(*) SELECT COUNT(*)
FROM events FROM events
LEFT JOIN leagues ON leagues.id = league_id
WHERE is_live = false WHERE is_live = false
AND status = 'upcoming' AND status = 'upcoming'
AND ( AND (
@ -309,7 +324,7 @@ WHERE is_live = false
OR $1 IS NULL OR $1 IS NULL
) )
AND ( AND (
sport_id = $2 events.sport_id = $2
OR $2 IS NULL OR $2 IS NULL
) )
AND ( AND (
@ -320,6 +335,10 @@ WHERE is_live = false
start_time > $4 start_time > $4
OR $4 IS NULL OR $4 IS NULL
) )
AND (
leagues.country_code = $5
OR $5 IS NULL
)
` `
type GetTotalEventsParams struct { type GetTotalEventsParams struct {
@ -327,6 +346,7 @@ type GetTotalEventsParams struct {
SportID pgtype.Int4 `json:"sport_id"` SportID pgtype.Int4 `json:"sport_id"`
LastStartTime pgtype.Timestamp `json:"last_start_time"` LastStartTime pgtype.Timestamp `json:"last_start_time"`
FirstStartTime pgtype.Timestamp `json:"first_start_time"` FirstStartTime pgtype.Timestamp `json:"first_start_time"`
CountryCode pgtype.Text `json:"country_code"`
} }
func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams) (int64, error) { func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams) (int64, error) {
@ -335,6 +355,7 @@ func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams)
arg.SportID, arg.SportID,
arg.LastStartTime, arg.LastStartTime,
arg.FirstStartTime, arg.FirstStartTime,
arg.CountryCode,
) )
var count int64 var count int64
err := row.Scan(&count) err := row.Scan(&count)

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: leagues.sql // source: leagues.sql
package dbgen package dbgen
@ -17,7 +17,7 @@ SELECT EXISTS(
FROM leagues FROM leagues
WHERE id = $1 WHERE id = $1
AND is_active = true AND is_active = true
) )
` `
func (q *Queries) CheckLeagueSupport(ctx context.Context, id int64) (bool, error) { func (q *Queries) CheckLeagueSupport(ctx context.Context, id int64) (bool, error) {
@ -32,61 +32,63 @@ SELECT id,
name, name,
country_code, country_code,
bet365_id, bet365_id,
is_active is_active,
sport_id
FROM leagues FROM leagues
WHERE (
country_code = $1
OR $1 IS NULL
)
AND (
sport_id = $2
OR $2 IS NULL
)
AND (
is_active = $3
OR $3 IS NULL
)
LIMIT $5 OFFSET $4
` `
func (q *Queries) GetAllLeagues(ctx context.Context) ([]League, error) { type GetAllLeaguesParams struct {
rows, err := q.db.Query(ctx, GetAllLeagues) CountryCode pgtype.Text `json:"country_code"`
if err != nil { SportID pgtype.Int4 `json:"sport_id"`
return nil, err IsActive pgtype.Bool `json:"is_active"`
} Offset pgtype.Int4 `json:"offset"`
defer rows.Close() Limit pgtype.Int4 `json:"limit"`
var items []League
for rows.Next() {
var i League
if err := rows.Scan(
&i.ID,
&i.Name,
&i.CountryCode,
&i.Bet365ID,
&i.IsActive,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
} }
const GetSupportedLeagues = `-- name: GetSupportedLeagues :many type GetAllLeaguesRow struct {
SELECT id, ID int64 `json:"id"`
name, Name string `json:"name"`
country_code, CountryCode pgtype.Text `json:"country_code"`
bet365_id, Bet365ID pgtype.Int4 `json:"bet365_id"`
is_active IsActive pgtype.Bool `json:"is_active"`
FROM leagues SportID int32 `json:"sport_id"`
WHERE is_active = true }
`
func (q *Queries) GetSupportedLeagues(ctx context.Context) ([]League, error) { func (q *Queries) GetAllLeagues(ctx context.Context, arg GetAllLeaguesParams) ([]GetAllLeaguesRow, error) {
rows, err := q.db.Query(ctx, GetSupportedLeagues) rows, err := q.db.Query(ctx, GetAllLeagues,
arg.CountryCode,
arg.SportID,
arg.IsActive,
arg.Offset,
arg.Limit,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
var items []League var items []GetAllLeaguesRow
for rows.Next() { for rows.Next() {
var i League var i GetAllLeaguesRow
if err := rows.Scan( if err := rows.Scan(
&i.ID, &i.ID,
&i.Name, &i.Name,
&i.CountryCode, &i.CountryCode,
&i.Bet365ID, &i.Bet365ID,
&i.IsActive, &i.IsActive,
&i.SportID,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -104,15 +106,16 @@ INSERT INTO leagues (
name, name,
country_code, country_code,
bet365_id, bet365_id,
sport_id,
is_active is_active
) VALUES ( )
$1, $2, $3, $4, $5 VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (id) DO
) UPDATE
ON CONFLICT (id) DO UPDATE
SET name = EXCLUDED.name, SET name = EXCLUDED.name,
country_code = EXCLUDED.country_code, country_code = EXCLUDED.country_code,
bet365_id = EXCLUDED.bet365_id, bet365_id = EXCLUDED.bet365_id,
is_active = EXCLUDED.is_active is_active = EXCLUDED.is_active,
sport_id = EXCLUDED.sport_id
` `
type InsertLeagueParams struct { type InsertLeagueParams struct {
@ -120,6 +123,7 @@ type InsertLeagueParams struct {
Name string `json:"name"` Name string `json:"name"`
CountryCode pgtype.Text `json:"country_code"` CountryCode pgtype.Text `json:"country_code"`
Bet365ID pgtype.Int4 `json:"bet365_id"` Bet365ID pgtype.Int4 `json:"bet365_id"`
SportID int32 `json:"sport_id"`
IsActive pgtype.Bool `json:"is_active"` IsActive pgtype.Bool `json:"is_active"`
} }
@ -129,6 +133,7 @@ func (q *Queries) InsertLeague(ctx context.Context, arg InsertLeagueParams) erro
arg.Name, arg.Name,
arg.CountryCode, arg.CountryCode,
arg.Bet365ID, arg.Bet365ID,
arg.SportID,
arg.IsActive, arg.IsActive,
) )
return err return err
@ -136,39 +141,78 @@ func (q *Queries) InsertLeague(ctx context.Context, arg InsertLeagueParams) erro
const SetLeagueActive = `-- name: SetLeagueActive :exec const SetLeagueActive = `-- name: SetLeagueActive :exec
UPDATE leagues UPDATE leagues
SET is_active = true SET is_active = $2
WHERE id = $1 WHERE id = $1
` `
func (q *Queries) SetLeagueActive(ctx context.Context, id int64) error { type SetLeagueActiveParams struct {
_, err := q.db.Exec(ctx, SetLeagueActive, id) ID int64 `json:"id"`
IsActive pgtype.Bool `json:"is_active"`
}
func (q *Queries) SetLeagueActive(ctx context.Context, arg SetLeagueActiveParams) error {
_, err := q.db.Exec(ctx, SetLeagueActive, arg.ID, arg.IsActive)
return err return err
} }
const UpdateLeague = `-- name: UpdateLeague :exec const UpdateLeague = `-- name: UpdateLeague :exec
UPDATE leagues UPDATE leagues
SET name = $1, SET name = COALESCE($2, name),
country_code = $2, country_code = COALESCE($3, country_code),
bet365_id = $3, bet365_id = COALESCE($4, bet365_id),
is_active = $4 is_active = COALESCE($5, is_active),
WHERE id = $5 sport_id = COALESCE($6, sport_id)
WHERE id = $1
` `
type UpdateLeagueParams struct { type UpdateLeagueParams struct {
Name string `json:"name"` ID int64 `json:"id"`
Name pgtype.Text `json:"name"`
CountryCode pgtype.Text `json:"country_code"` CountryCode pgtype.Text `json:"country_code"`
Bet365ID pgtype.Int4 `json:"bet365_id"` Bet365ID pgtype.Int4 `json:"bet365_id"`
IsActive pgtype.Bool `json:"is_active"` IsActive pgtype.Bool `json:"is_active"`
ID int64 `json:"id"` SportID pgtype.Int4 `json:"sport_id"`
} }
func (q *Queries) UpdateLeague(ctx context.Context, arg UpdateLeagueParams) error { func (q *Queries) UpdateLeague(ctx context.Context, arg UpdateLeagueParams) error {
_, err := q.db.Exec(ctx, UpdateLeague, _, err := q.db.Exec(ctx, UpdateLeague,
arg.ID,
arg.Name, arg.Name,
arg.CountryCode, arg.CountryCode,
arg.Bet365ID, arg.Bet365ID,
arg.IsActive, arg.IsActive,
arg.ID, arg.SportID,
)
return err
}
const UpdateLeagueByBet365ID = `-- name: UpdateLeagueByBet365ID :exec
UPDATE leagues
SET name = COALESCE($2, name),
id = COALESCE($3, id),
country_code = COALESCE($4, country_code),
is_active = COALESCE($5, is_active),
sport_id = COALESCE($6, sport_id)
WHERE bet365_id = $1
`
type UpdateLeagueByBet365IDParams struct {
Bet365ID pgtype.Int4 `json:"bet365_id"`
Name pgtype.Text `json:"name"`
ID pgtype.Int8 `json:"id"`
CountryCode pgtype.Text `json:"country_code"`
IsActive pgtype.Bool `json:"is_active"`
SportID pgtype.Int4 `json:"sport_id"`
}
func (q *Queries) UpdateLeagueByBet365ID(ctx context.Context, arg UpdateLeagueByBet365IDParams) error {
_, err := q.db.Exec(ctx, UpdateLeagueByBet365ID,
arg.Bet365ID,
arg.Name,
arg.ID,
arg.CountryCode,
arg.IsActive,
arg.SportID,
) )
return err return err
} }

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
package dbgen package dbgen
@ -140,6 +140,7 @@ type BranchDetail struct {
UpdatedAt pgtype.Timestamp `json:"updated_at"` UpdatedAt pgtype.Timestamp `json:"updated_at"`
ManagerName interface{} `json:"manager_name"` ManagerName interface{} `json:"manager_name"`
ManagerPhoneNumber pgtype.Text `json:"manager_phone_number"` ManagerPhoneNumber pgtype.Text `json:"manager_phone_number"`
Balance pgtype.Int8 `json:"balance"`
} }
type BranchOperation struct { type BranchOperation struct {
@ -208,6 +209,7 @@ type League struct {
Name string `json:"name"` Name string `json:"name"`
CountryCode pgtype.Text `json:"country_code"` CountryCode pgtype.Text `json:"country_code"`
Bet365ID pgtype.Int4 `json:"bet365_id"` Bet365ID pgtype.Int4 `json:"bet365_id"`
SportID int32 `json:"sport_id"`
IsActive pgtype.Bool `json:"is_active"` IsActive pgtype.Bool `json:"is_active"`
} }
@ -327,6 +329,7 @@ type Ticket struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"` TotalOdds float32 `json:"total_odds"`
Ip string `json:"ip"`
CreatedAt pgtype.Timestamp `json:"created_at"` CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"` UpdatedAt pgtype.Timestamp `json:"updated_at"`
} }
@ -352,6 +355,7 @@ type TicketWithOutcome struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"` TotalOdds float32 `json:"total_odds"`
Ip string `json:"ip"`
CreatedAt pgtype.Timestamp `json:"created_at"` CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"` UpdatedAt pgtype.Timestamp `json:"updated_at"`
Outcomes []TicketOutcome `json:"outcomes"` Outcomes []TicketOutcome `json:"outcomes"`

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: monitor.sql // source: monitor.sql
package dbgen package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: notification.sql // source: notification.sql
package dbgen package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: odds.sql // source: odds.sql
package dbgen package dbgen
@ -11,6 +11,16 @@ import (
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
) )
const DeleteOddsForEvent = `-- name: DeleteOddsForEvent :exec
DELETE FROM odds
Where fi = $1
`
func (q *Queries) DeleteOddsForEvent(ctx context.Context, fi pgtype.Text) error {
_, err := q.db.Exec(ctx, DeleteOddsForEvent, fi)
return err
}
const GetALLPrematchOdds = `-- name: GetALLPrematchOdds :many const GetALLPrematchOdds = `-- name: GetALLPrematchOdds :many
SELECT event_id, SELECT event_id,
fi, fi,
@ -29,7 +39,7 @@ SELECT event_id,
is_active is_active
FROM odds FROM odds
WHERE is_active = true WHERE is_active = true
AND source = 'b365api' AND source = 'bet365'
` `
type GetALLPrematchOddsRow struct { type GetALLPrematchOddsRow struct {
@ -94,7 +104,7 @@ WHERE e.id = $1
AND e.is_live = false AND e.is_live = false
AND e.status = 'upcoming' AND e.status = 'upcoming'
AND o.is_active = true AND o.is_active = true
AND o.source = 'b365api' AND o.source = 'bet365'
LIMIT $3 OFFSET $2 LIMIT $3 OFFSET $2
` `
@ -159,7 +169,7 @@ SELECT event_id,
is_active is_active
FROM odds FROM odds
WHERE is_active = true WHERE is_active = true
AND source = 'b365api' AND source = 'bet365'
` `
type GetPrematchOddsRow struct { type GetPrematchOddsRow struct {
@ -224,7 +234,7 @@ WHERE e.id = $1
AND e.is_live = false AND e.is_live = false
AND e.status = 'upcoming' AND e.status = 'upcoming'
AND o.is_active = true AND o.is_active = true
AND o.source = 'b365api' AND o.source = 'bet365'
` `
func (q *Queries) GetPrematchOddsByUpcomingID(ctx context.Context, id string) ([]Odd, error) { func (q *Queries) GetPrematchOddsByUpcomingID(ctx context.Context, id string) ([]Odd, error) {
@ -274,7 +284,7 @@ FROM odds
WHERE market_id = $1 WHERE market_id = $1
AND fi = $2 AND fi = $2
AND is_active = true AND is_active = true
AND source = 'b365api' AND source = 'bet365'
` `
type GetRawOddsByMarketIDParams struct { type GetRawOddsByMarketIDParams struct {

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: otp.sql // source: otp.sql
package dbgen package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: referal.sql // source: referal.sql
package dbgen package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: result.sql // source: result.sql
package dbgen package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: ticket.sql // source: ticket.sql
package dbgen package dbgen
@ -11,24 +11,39 @@ import (
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
) )
const CountTicketByIP = `-- name: CountTicketByIP :one
SELECT count(id)
FROM tickets
WHERE IP = $1
`
func (q *Queries) CountTicketByIP(ctx context.Context, ip string) (int64, error) {
row := q.db.QueryRow(ctx, CountTicketByIP, ip)
var count int64
err := row.Scan(&count)
return count, err
}
const CreateTicket = `-- name: CreateTicket :one const CreateTicket = `-- name: CreateTicket :one
INSERT INTO tickets (amount, total_odds) INSERT INTO tickets (amount, total_odds, ip)
VALUES ($1, $2) VALUES ($1, $2, $3)
RETURNING id, amount, total_odds, created_at, updated_at RETURNING id, amount, total_odds, ip, created_at, updated_at
` `
type CreateTicketParams struct { type CreateTicketParams struct {
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"` TotalOdds float32 `json:"total_odds"`
Ip string `json:"ip"`
} }
func (q *Queries) CreateTicket(ctx context.Context, arg CreateTicketParams) (Ticket, error) { func (q *Queries) CreateTicket(ctx context.Context, arg CreateTicketParams) (Ticket, error) {
row := q.db.QueryRow(ctx, CreateTicket, arg.Amount, arg.TotalOdds) row := q.db.QueryRow(ctx, CreateTicket, arg.Amount, arg.TotalOdds, arg.Ip)
var i Ticket var i Ticket
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.Amount, &i.Amount,
&i.TotalOdds, &i.TotalOdds,
&i.Ip,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
) )
@ -81,7 +96,7 @@ func (q *Queries) DeleteTicketOutcome(ctx context.Context, ticketID int64) error
} }
const GetAllTickets = `-- name: GetAllTickets :many const GetAllTickets = `-- name: GetAllTickets :many
SELECT id, amount, total_odds, created_at, updated_at, outcomes SELECT id, amount, total_odds, ip, created_at, updated_at, outcomes
FROM ticket_with_outcomes FROM ticket_with_outcomes
` `
@ -98,6 +113,7 @@ func (q *Queries) GetAllTickets(ctx context.Context) ([]TicketWithOutcome, error
&i.ID, &i.ID,
&i.Amount, &i.Amount,
&i.TotalOdds, &i.TotalOdds,
&i.Ip,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Outcomes, &i.Outcomes,
@ -113,7 +129,7 @@ func (q *Queries) GetAllTickets(ctx context.Context) ([]TicketWithOutcome, error
} }
const GetTicketByID = `-- name: GetTicketByID :one const GetTicketByID = `-- name: GetTicketByID :one
SELECT id, amount, total_odds, created_at, updated_at, outcomes SELECT id, amount, total_odds, ip, created_at, updated_at, outcomes
FROM ticket_with_outcomes FROM ticket_with_outcomes
WHERE id = $1 WHERE id = $1
` `
@ -125,6 +141,7 @@ func (q *Queries) GetTicketByID(ctx context.Context, id int64) (TicketWithOutcom
&i.ID, &i.ID,
&i.Amount, &i.Amount,
&i.TotalOdds, &i.TotalOdds,
&i.Ip,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Outcomes, &i.Outcomes,

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: transactions.sql // source: transactions.sql
package dbgen package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: transfer.sql // source: transfer.sql
package dbgen package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: user.sql // source: user.sql
package dbgen package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: virtual_games.sql // source: virtual_games.sql
package dbgen package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.28.0
// source: wallet.sql // source: wallet.sql
package dbgen package dbgen

View File

@ -16,6 +16,7 @@ type BranchDetail struct {
Name string Name string
Location string Location string
WalletID int64 WalletID int64
Balance Currency
BranchManagerID int64 BranchManagerID int64
CompanyID int64 CompanyID int64
IsSuspended bool IsSuspended bool

View File

@ -2,6 +2,39 @@ package domain
import "time" import "time"
// TODO: turn status into an enum
// Status represents the status of an event.
// 0 Not Started
// 1 InPlay
// 2 TO BE FIXED
// 3 Ended
// 4 Postponed
// 5 Cancelled
// 6 Walkover
// 7 Interrupted
// 8 Abandoned
// 9 Retired
// 10 Suspended
// 11 Decided by FA
// 99 Removed
type EventStatus string
const (
STATUS_PENDING EventStatus = "upcoming"
STATUS_IN_PLAY EventStatus = "in_play"
STATUS_TO_BE_FIXED EventStatus = "to_be_fixed"
STATUS_ENDED EventStatus = "ended"
STATUS_POSTPONED EventStatus = "postponed"
STATUS_CANCELLED EventStatus = "cancelled"
STATUS_WALKOVER EventStatus = "walkover"
STATUS_INTERRUPTED EventStatus = "interrupted"
STATUS_ABANDONED EventStatus = "abandoned"
STATUS_RETIRED EventStatus = "retired"
STATUS_SUSPENDED EventStatus = "suspended"
STATUS_DECIDED_BY_FA EventStatus = "decided_by_fa"
STATUS_REMOVED EventStatus = "removed"
)
type Event struct { type Event struct {
ID string ID string
SportID int32 SportID int32
@ -53,20 +86,21 @@ type BetResult struct {
} }
type UpcomingEvent struct { type UpcomingEvent struct {
ID string // Event ID ID string `json:"id"` // Event ID
SportID int32 // Sport ID SportID int32 `json:"sport_id"` // Sport ID
MatchName string // Match or event name MatchName string `json:"match_name"` // Match or event name
HomeTeam string // Home team name (if available) HomeTeam string `json:"home_team"` // Home team name (if available)
AwayTeam string // Away team name (can be empty/null) AwayTeam string `json:"away_team"` // Away team name (can be empty/null)
HomeTeamID int32 // Home team ID HomeTeamID int32 `json:"home_team_id"` // Home team ID
AwayTeamID int32 // Away team ID (can be empty/null) AwayTeamID int32 `json:"away_team_id"` // Away team ID (can be empty/null)
HomeKitImage string // Kit or image for home team (optional) HomeKitImage string `json:"home_kit_image"` // Kit or image for home team (optional)
AwayKitImage string // Kit or image for away team (optional) AwayKitImage string `json:"away_kit_image"` // Kit or image for away team (optional)
LeagueID int32 // League ID LeagueID int32 `json:"league_id"` // League ID
LeagueName string // League name LeagueName string `json:"league_name"` // League name
LeagueCC string // League country code LeagueCC string `json:"league_cc"` // League country code
StartTime time.Time // Converted from "time" field in UNIX format StartTime time.Time `json:"start_time"` // Converted from "time" field in UNIX format
Source string // bet api provider (bet365, betfair) Source string `json:"source"` // bet api provider (bet365, betfair)
Status EventStatus `json:"status"` //Match Status for event
} }
type MatchResult struct { type MatchResult struct {
EventID string EventID string
@ -83,3 +117,14 @@ type Odds struct {
Name string `json:"name"` Name string `json:"name"`
HitStatus string `json:"hit_status"` HitStatus string `json:"hit_status"`
} }
type EventFilter struct {
SportID ValidInt32
LeagueID ValidInt32
CountryCode ValidString
FirstStartTime ValidTime
LastStartTime ValidTime
Limit ValidInt64
Offset ValidInt64
MatchStatus ValidString // e.g., "upcoming", "in_play", "ended"
}

View File

@ -1,9 +1,27 @@
package domain package domain
type League struct { type League struct {
ID int64 ID int64 `json:"id" example:"1"`
Name string Name string `json:"name" example:"BPL"`
CountryCode string CountryCode string `json:"cc" example:"uk"`
Bet365ID int32 Bet365ID int32 `json:"bet365_id" example:"1121"`
IsActive bool IsActive bool `json:"is_active" example:"false"`
SportID int32 `json:"sport_id" example:"1"`
}
type UpdateLeague struct {
ID int64 `json:"id" example:"1"`
Name ValidString `json:"name" example:"BPL"`
CountryCode ValidString `json:"cc" example:"uk"`
Bet365ID ValidInt32 `json:"bet365_id" example:"1121"`
IsActive ValidBool `json:"is_active" example:"false"`
SportID ValidInt32 `json:"sport_id" example:"1"`
}
type LeagueFilter struct {
CountryCode ValidString
SportID ValidInt32
IsActive ValidBool
Limit ValidInt64
Offset ValidInt64
} }

View File

@ -12,6 +12,19 @@ type OddsSection struct {
Sp map[string]OddsMarket `json:"sp"` Sp map[string]OddsMarket `json:"sp"`
} }
type ParseOddSectionsRes struct {
Sections map[string]OddsSection
OtherRes []OddsSection
EventFI string
}
type RawOdd struct {
ID string `json:"id"`
Name string `json:"name"`
Header string `json:"header,omitempty"`
Handicap string `json:"handicap,omitempty"`
Odds string `json:"odds"`
}
// The Market ID for the json data can be either string / int which is causing problems when UnMarshalling // The Market ID for the json data can be either string / int which is causing problems when UnMarshalling
type OddsMarket struct { type OddsMarket struct {
ID json.RawMessage `json:"id"` ID json.RawMessage `json:"id"`

View File

@ -45,3 +45,5 @@ type RawOddsByMarketID struct {
RawOdds []RawMessage `json:"raw_odds"` RawOdds []RawMessage `json:"raw_odds"`
FetchedAt time.Time `json:"fetched_at"` FetchedAt time.Time `json:"fetched_at"`
} }

View File

@ -27,6 +27,17 @@ type Score struct {
Away string `json:"away"` Away string `json:"away"`
} }
type CommonResultResponse struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League LeagueRes `json:"league"`
Home Team `json:"home"`
Away Team `json:"away"`
SS string `json:"ss"`
}
type FootballResultResponse struct { type FootballResultResponse struct {
ID string `json:"id"` ID string `json:"id"`
SportID string `json:"sport_id"` SportID string `json:"sport_id"`

View File

@ -51,4 +51,5 @@ type GetTicket struct {
type CreateTicket struct { type CreateTicket struct {
Amount Currency Amount Currency
TotalOdds float32 TotalOdds float32
IP string
} }

View File

@ -86,4 +86,7 @@ type GetCashier struct {
SuspendedAt time.Time `json:"suspended_at"` SuspendedAt time.Time `json:"suspended_at"`
Suspended bool `json:"suspended"` Suspended bool `json:"suspended"`
BranchID int64 `json:"branch_id"` BranchID int64 `json:"branch_id"`
BranchName string `json:"branch_name"`
BranchWallet int64 `json:"branch_wallet"`
BranchLocation string `json:"branch_location"`
} }

View File

@ -31,6 +31,7 @@ func convertDBBranchDetail(dbBranch dbgen.BranchDetail) domain.BranchDetail {
IsSelfOwned: dbBranch.IsSelfOwned, IsSelfOwned: dbBranch.IsSelfOwned,
ManagerName: dbBranch.ManagerName.(string), ManagerName: dbBranch.ManagerName.(string),
ManagerPhoneNumber: dbBranch.ManagerPhoneNumber.String, ManagerPhoneNumber: dbBranch.ManagerPhoneNumber.String,
Balance: domain.Currency(dbBranch.Balance.Int64),
} }
} }

View File

@ -88,13 +88,17 @@ func (s *Store) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEven
LeagueCC: e.LeagueCc.String, LeagueCC: e.LeagueCc.String,
StartTime: e.StartTime.Time.UTC(), StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String, Source: e.Source.String,
Status: domain.EventStatus(e.Status.String),
} }
} }
return upcomingEvents, nil return upcomingEvents, nil
} }
func (s *Store) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) { func (s *Store) GetExpiredUpcomingEvents(ctx context.Context, filter domain.EventFilter) ([]domain.UpcomingEvent, error) {
events, err := s.queries.GetExpiredUpcomingEvents(ctx) events, err := s.queries.GetExpiredUpcomingEvents(ctx, pgtype.Text{
String: filter.MatchStatus.Value,
Valid: filter.MatchStatus.Valid,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -116,37 +120,42 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcoming
LeagueCC: e.LeagueCc.String, LeagueCC: e.LeagueCc.String,
StartTime: e.StartTime.Time.UTC(), StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String, Source: e.Source.String,
Status: domain.EventStatus(e.Status.String),
} }
} }
return upcomingEvents, nil return upcomingEvents, nil
} }
func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidInt32, sportID domain.ValidInt32, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error) { func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.EventFilter) ([]domain.UpcomingEvent, int64, error) {
events, err := s.queries.GetPaginatedUpcomingEvents(ctx, dbgen.GetPaginatedUpcomingEventsParams{ events, err := s.queries.GetPaginatedUpcomingEvents(ctx, dbgen.GetPaginatedUpcomingEventsParams{
LeagueID: pgtype.Int4{ LeagueID: pgtype.Int4{
Int32: leagueID.Value, Int32: int32(filter.LeagueID.Value),
Valid: leagueID.Valid, Valid: filter.LeagueID.Valid,
}, },
SportID: pgtype.Int4{ SportID: pgtype.Int4{
Int32: sportID.Value, Int32: int32(filter.SportID.Value),
Valid: sportID.Valid, Valid: filter.SportID.Valid,
}, },
Limit: pgtype.Int4{ Limit: pgtype.Int4{
Int32: int32(limit.Value), Int32: int32(filter.Limit.Value),
Valid: limit.Valid, Valid: filter.Limit.Valid,
}, },
Offset: pgtype.Int4{ Offset: pgtype.Int4{
Int32: int32(offset.Value * limit.Value), Int32: int32(filter.Offset.Value * filter.Limit.Value),
Valid: offset.Valid, Valid: filter.Offset.Valid,
}, },
FirstStartTime: pgtype.Timestamp{ FirstStartTime: pgtype.Timestamp{
Time: firstStartTime.Value.UTC(), Time: filter.FirstStartTime.Value.UTC(),
Valid: firstStartTime.Valid, Valid: filter.FirstStartTime.Valid,
}, },
LastStartTime: pgtype.Timestamp{ LastStartTime: pgtype.Timestamp{
Time: lastStartTime.Value.UTC(), Time: filter.LastStartTime.Value.UTC(),
Valid: lastStartTime.Valid, Valid: filter.LastStartTime.Valid,
},
CountryCode: pgtype.Text{
String: filter.CountryCode.Value,
Valid: filter.CountryCode.Valid,
}, },
}) })
@ -170,31 +179,36 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.Val
LeagueCC: e.LeagueCc.String, LeagueCC: e.LeagueCc.String,
StartTime: e.StartTime.Time.UTC(), StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String, Source: e.Source.String,
Status: domain.EventStatus(e.Status.String),
} }
} }
totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{ totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{
LeagueID: pgtype.Int4{ LeagueID: pgtype.Int4{
Int32: leagueID.Value, Int32: int32(filter.LeagueID.Value),
Valid: leagueID.Valid, Valid: filter.LeagueID.Valid,
}, },
SportID: pgtype.Int4{ SportID: pgtype.Int4{
Int32: sportID.Value, Int32: int32(filter.SportID.Value),
Valid: sportID.Valid, Valid: filter.SportID.Valid,
}, },
FirstStartTime: pgtype.Timestamp{ FirstStartTime: pgtype.Timestamp{
Time: firstStartTime.Value.UTC(), Time: filter.FirstStartTime.Value.UTC(),
Valid: firstStartTime.Valid, Valid: filter.FirstStartTime.Valid,
}, },
LastStartTime: pgtype.Timestamp{ LastStartTime: pgtype.Timestamp{
Time: lastStartTime.Value.UTC(), Time: filter.LastStartTime.Value.UTC(),
Valid: lastStartTime.Valid, Valid: filter.LastStartTime.Valid,
},
CountryCode: pgtype.Text{
String: filter.CountryCode.Value,
Valid: filter.CountryCode.Valid,
}, },
}) })
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
numberOfPages := math.Ceil(float64(totalCount) / float64(limit.Value)) numberOfPages := math.Ceil(float64(totalCount) / float64(filter.Limit.Value))
return upcomingEvents, int64(numberOfPages), nil return upcomingEvents, int64(numberOfPages), nil
} }
func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) { func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) {
@ -218,12 +232,13 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Upc
LeagueCC: event.LeagueCc.String, LeagueCC: event.LeagueCc.String,
StartTime: event.StartTime.Time.UTC(), StartTime: event.StartTime.Time.UTC(),
Source: event.Source.String, Source: event.Source.String,
Status: domain.EventStatus(event.Status.String),
}, nil }, nil
} }
func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore, status string) error { func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error {
params := dbgen.UpdateMatchResultParams{ params := dbgen.UpdateMatchResultParams{
Score: pgtype.Text{String: fullScore, Valid: true}, Score: pgtype.Text{String: fullScore, Valid: true},
Status: pgtype.Text{String: status, Valid: true}, Status: pgtype.Text{String: string(status), Valid: true},
ID: eventID, ID: eventID,
} }
@ -235,6 +250,24 @@ func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore, status
return nil return nil
} }
func (s *Store) UpdateEventStatus(ctx context.Context, eventID string, status domain.EventStatus) error {
params := dbgen.UpdateMatchResultParams{
Status: pgtype.Text{
String: string(status),
Valid: true,
},
ID: eventID,
}
err := s.queries.UpdateMatchResult(ctx, params)
if err != nil {
return err
}
return nil
}
func (s *Store) DeleteEvent(ctx context.Context, eventID string) error { func (s *Store) DeleteEvent(ctx context.Context, eventID string) error {
err := s.queries.DeleteEvent(ctx, eventID) err := s.queries.DeleteEvent(ctx, eventID)
if err != nil { if err != nil {

View File

@ -15,30 +15,33 @@ func (s *Store) SaveLeague(ctx context.Context, l domain.League) error {
CountryCode: pgtype.Text{String: l.CountryCode, Valid: true}, CountryCode: pgtype.Text{String: l.CountryCode, Valid: true},
Bet365ID: pgtype.Int4{Int32: l.Bet365ID, Valid: true}, Bet365ID: pgtype.Int4{Int32: l.Bet365ID, Valid: true},
IsActive: pgtype.Bool{Bool: l.IsActive, Valid: true}, IsActive: pgtype.Bool{Bool: l.IsActive, Valid: true},
SportID: l.SportID,
}) })
} }
func (s *Store) GetSupportedLeagues(ctx context.Context) ([]domain.League, error) { func (s *Store) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.League, error) {
leagues, err := s.queries.GetSupportedLeagues(ctx) l, err := s.queries.GetAllLeagues(ctx, dbgen.GetAllLeaguesParams{
if err != nil { CountryCode: pgtype.Text{
return nil, err String: filter.CountryCode.Value,
} Valid: filter.CountryCode.Valid,
},
supportedLeagues := make([]domain.League, len(leagues)) SportID: pgtype.Int4{
for i, league := range leagues { Int32: filter.SportID.Value,
supportedLeagues[i] = domain.League{ Valid: filter.SportID.Valid,
ID: league.ID, },
Name: league.Name, IsActive: pgtype.Bool{
CountryCode: league.CountryCode.String, Bool: filter.IsActive.Value,
Bet365ID: league.Bet365ID.Int32, Valid: filter.IsActive.Valid,
IsActive: league.IsActive.Bool, },
} Limit: pgtype.Int4{
} Int32: int32(filter.Limit.Value),
return supportedLeagues, nil Valid: filter.Limit.Valid,
} },
Offset: pgtype.Int4{
func (s *Store) GetAllLeagues(ctx context.Context) ([]domain.League, error) { Int32: int32(filter.Offset.Value * filter.Limit.Value),
l, err := s.queries.GetAllLeagues(ctx) Valid: filter.Offset.Valid,
},
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -51,6 +54,7 @@ func (s *Store) GetAllLeagues(ctx context.Context) ([]domain.League, error) {
CountryCode: league.CountryCode.String, CountryCode: league.CountryCode.String,
Bet365ID: league.Bet365ID.Int32, Bet365ID: league.Bet365ID.Int32,
IsActive: league.IsActive.Bool, IsActive: league.IsActive.Bool,
SportID: league.SportID,
} }
} }
return leagues, nil return leagues, nil
@ -60,16 +64,40 @@ func (s *Store) CheckLeagueSupport(ctx context.Context, leagueID int64) (bool, e
return s.queries.CheckLeagueSupport(ctx, leagueID) return s.queries.CheckLeagueSupport(ctx, leagueID)
} }
func (s *Store) SetLeagueActive(ctx context.Context, leagueId int64) error { func (s *Store) SetLeagueActive(ctx context.Context, leagueId int64, isActive bool) error {
return s.queries.SetLeagueActive(ctx, leagueId) return s.queries.SetLeagueActive(ctx, dbgen.SetLeagueActiveParams{
} ID: leagueId,
IsActive: pgtype.Bool{
// TODO: update based on id, no need for the entire league (same as the set active one) Bool: isActive,
func (s *Store) SetLeagueInActive(ctx context.Context, l domain.League) error { Valid: true,
return s.queries.UpdateLeague(ctx, dbgen.UpdateLeagueParams{ },
Name: l.Name,
CountryCode: pgtype.Text{String: l.CountryCode, Valid: true},
Bet365ID: pgtype.Int4{Int32: l.Bet365ID, Valid: true},
IsActive: pgtype.Bool{Bool: false, Valid: true},
}) })
} }
func (s *Store) UpdateLeague(ctx context.Context, league domain.UpdateLeague) error {
err := s.queries.UpdateLeague(ctx, dbgen.UpdateLeagueParams{
ID: league.ID,
Name: pgtype.Text{
String: league.Name.Value,
Valid: league.Name.Valid,
},
CountryCode: pgtype.Text{
String: league.CountryCode.Value,
Valid: league.CountryCode.Valid,
},
Bet365ID: pgtype.Int4{
Int32: league.Bet365ID.Value,
Valid: league.Bet365ID.Valid,
},
IsActive: pgtype.Bool{
Bool: league.IsActive.Value,
Valid: league.IsActive.Valid,
},
SportID: pgtype.Int4{
Int32: league.SportID.Value,
Valid: league.SportID.Valid,
},
})
return err
}

View File

@ -3,6 +3,7 @@ package repository
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"os" "os"
"strconv" "strconv"
"time" "time"
@ -274,6 +275,13 @@ func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID stri
return domainOdds, nil return domainOdds, nil
} }
func (s *Store) DeleteOddsForEvent(ctx context.Context, eventID string) error {
return s.queries.DeleteOddsForEvent(ctx, pgtype.Text{
String: eventID,
Valid: true,
})
}
func getString(v interface{}) string { func getString(v interface{}) string {
if s, ok := v.(string); ok { if s, ok := v.(string); ok {
return s return s

View File

@ -3,6 +3,7 @@ package repository
import ( import (
"context" "context"
"database/sql" "database/sql"
"fmt"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
@ -32,6 +33,7 @@ func (s *Store) GetOtp(ctx context.Context, sentTo string, sentfor domain.OtpFor
OtpFor: string(sentfor), OtpFor: string(sentfor),
}) })
if err != nil { if err != nil {
fmt.Printf("OTP REPO error: %v sentTo: %v, medium: %v, otpFor: %v\n", err, sentTo, medium, sentfor)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return domain.Otp{}, domain.ErrOtpNotFound return domain.Otp{}, domain.ErrOtpNotFound
} }

View File

@ -70,6 +70,7 @@ func convertCreateTicket(ticket domain.CreateTicket) dbgen.CreateTicketParams {
return dbgen.CreateTicketParams{ return dbgen.CreateTicketParams{
Amount: int64(ticket.Amount), Amount: int64(ticket.Amount),
TotalOdds: ticket.TotalOdds, TotalOdds: ticket.TotalOdds,
Ip: ticket.IP,
} }
} }
@ -123,6 +124,16 @@ func (s *Store) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) {
return result, nil return result, nil
} }
func (s *Store) CountTicketByIP(ctx context.Context, IP string) (int64, error) {
count, err := s.queries.CountTicketByIP(ctx, IP)
if err != nil {
return 0, err
}
return count, err
}
func (s *Store) UpdateTicketOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { func (s *Store) UpdateTicketOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error {
err := s.queries.UpdateTicketOutcomeStatus(ctx, dbgen.UpdateTicketOutcomeStatusParams{ err := s.queries.UpdateTicketOutcomeStatus(ctx, dbgen.UpdateTicketOutcomeStatusParams{
Status: int32(status), Status: int32(status),

View File

@ -82,6 +82,10 @@ func (s *Store) GetUserByID(ctx context.Context, id int64) (domain.User, error)
UpdatedAt: user.UpdatedAt.Time, UpdatedAt: user.UpdatedAt.Time,
SuspendedAt: user.SuspendedAt.Time, SuspendedAt: user.SuspendedAt.Time,
Suspended: user.Suspended, Suspended: user.Suspended,
CompanyID: domain.ValidInt64{
Value: user.CompanyID.Int64,
Valid: user.CompanyID.Valid,
},
}, nil }, nil
} }
func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.User, int64, error) { func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.User, int64, error) {
@ -118,6 +122,10 @@ func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.U
UpdatedAt: user.UpdatedAt.Time, UpdatedAt: user.UpdatedAt.Time,
SuspendedAt: user.SuspendedAt.Time, SuspendedAt: user.SuspendedAt.Time,
Suspended: user.Suspended, Suspended: user.Suspended,
CompanyID: domain.ValidInt64{
Value: user.CompanyID.Int64,
Valid: user.CompanyID.Valid,
},
} }
} }
totalCount, err := s.queries.GetTotalUsers(ctx, dbgen.GetTotalUsersParams{ totalCount, err := s.queries.GetTotalUsers(ctx, dbgen.GetTotalUsersParams{
@ -130,10 +138,10 @@ func (s *Store) GetAllUsers(ctx context.Context, filter user.Filter) ([]domain.U
return userList, totalCount, nil return userList, totalCount, nil
} }
func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.GetCashier, error) { func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.GetCashier, int64, error) {
users, err := s.queries.GetAllCashiers(ctx) users, err := s.queries.GetAllCashiers(ctx)
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
userList := make([]domain.GetCashier, len(users)) userList := make([]domain.GetCashier, len(users))
for i, user := range users { for i, user := range users {
@ -150,9 +158,16 @@ func (s *Store) GetAllCashiers(ctx context.Context) ([]domain.GetCashier, error)
UpdatedAt: user.UpdatedAt.Time, UpdatedAt: user.UpdatedAt.Time,
SuspendedAt: user.SuspendedAt.Time, SuspendedAt: user.SuspendedAt.Time,
Suspended: user.Suspended, Suspended: user.Suspended,
BranchID: user.BranchID,
BranchName: user.BranchName,
BranchWallet: user.BranchWallet,
BranchLocation: user.BranchLocation,
} }
} }
return userList, nil totalCount, err := s.queries.GetTotalUsers(ctx, dbgen.GetTotalUsersParams{
Role: string(domain.RoleCashier),
})
return userList, totalCount, nil
} }
func (s *Store) GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error) { func (s *Store) GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error) {
@ -174,6 +189,9 @@ func (s *Store) GetCashierByID(ctx context.Context, cashierID int64) (domain.Get
SuspendedAt: user.SuspendedAt.Time, SuspendedAt: user.SuspendedAt.Time,
Suspended: user.Suspended, Suspended: user.Suspended,
BranchID: user.BranchID, BranchID: user.BranchID,
BranchName: user.BranchName,
BranchWallet: user.BranchWallet,
BranchLocation: user.BranchLocation,
}, nil }, nil
} }

View File

@ -30,14 +30,14 @@ var (
type Service struct { type Service struct {
betStore BetStore betStore BetStore
eventSvc event.Service eventSvc event.Service
prematchSvc odds.Service prematchSvc odds.ServiceImpl
walletSvc wallet.Service walletSvc wallet.Service
branchSvc branch.Service branchSvc branch.Service
logger *slog.Logger logger *slog.Logger
mongoLogger *zap.Logger mongoLogger *zap.Logger
} }
func NewService(betStore BetStore, eventSvc event.Service, prematchSvc odds.Service, walletSvc wallet.Service, branchSvc branch.Service, logger *slog.Logger, mongoLogger *zap.Logger) *Service { func NewService(betStore BetStore, eventSvc event.Service, prematchSvc odds.ServiceImpl, walletSvc wallet.Service, branchSvc branch.Service, logger *slog.Logger, mongoLogger *zap.Logger) *Service {
return &Service{ return &Service{
betStore: betStore, betStore: betStore,
eventSvc: eventSvc, eventSvc: eventSvc,
@ -490,7 +490,12 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le
// Get a unexpired event id // Get a unexpired event id
events, _, err := s.eventSvc.GetPaginatedUpcomingEvents(ctx, events, _, err := s.eventSvc.GetPaginatedUpcomingEvents(ctx,
domain.ValidInt64{}, domain.ValidInt64{}, leagueID, sportID, firstStartTime, lastStartTime) domain.EventFilter{
SportID: sportID,
LeagueID: leagueID,
FirstStartTime: firstStartTime,
LastStartTime: lastStartTime,
})
if err != nil { if err != nil {
s.mongoLogger.Error("failed to get paginated upcoming events", s.mongoLogger.Error("failed to get paginated upcoming events",

View File

@ -10,9 +10,10 @@ type Service interface {
FetchLiveEvents(ctx context.Context) error FetchLiveEvents(ctx context.Context) error
FetchUpcomingEvents(ctx context.Context) error FetchUpcomingEvents(ctx context.Context) error
GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error)
GetExpiredUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) GetExpiredUpcomingEvents(ctx context.Context, filter domain.EventFilter) ([]domain.UpcomingEvent, error)
GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidInt32, sportID domain.ValidInt32, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.EventFilter) ([]domain.UpcomingEvent, int64, error)
GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error)
// GetAndStoreMatchResult(ctx context.Context, eventID string) error // GetAndStoreMatchResult(ctx context.Context, eventID string) error
UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error
UpdateEventStatus(ctx context.Context, eventID string, status domain.EventStatus) error
} }

View File

@ -209,16 +209,17 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
// log.Printf("❌ Failed to open leagues file %v", err) // log.Printf("❌ Failed to open leagues file %v", err)
// return // return
// } // }
for _, sportID := range sportIDs { for sportIndex, sportID := range sportIDs {
var totalPages int = 1 var totalPages int = 1
var page int = 0 var page int = 0
var limit int = 100 var limit int = 200
var count int = 0 var count int = 0
log.Printf("Sport ID %d", sportID) log.Printf("Sport ID %d", sportID)
for page <= totalPages { for page <= totalPages {
page = page + 1 page = page + 1
url := fmt.Sprintf(url, sportID, s.token, page) url := fmt.Sprintf(url, sportID, s.token, page)
log.Printf("📡 Fetching data from %s - sport %d, for event data page %d", source, sportID, page) log.Printf("📡 Fetching data from %s - sport %d (%d/%d), for event data page (%d/%d)",
source, sportID, sportIndex+1, len(sportIDs), page, totalPages)
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
log.Printf("❌ Failed to fetch event data for page %d: %v", page, err) log.Printf("❌ Failed to fetch event data for page %d: %v", page, err)
@ -249,13 +250,23 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
} }
// doesn't make sense to save and check back to back, but for now it can be here // doesn't make sense to save and check back to back, but for now it can be here
s.store.SaveLeague(ctx, domain.League{ // no this its fine to keep it here
// but change the league id to bet365 id later
err = s.store.SaveLeague(ctx, domain.League{
ID: leagueID, ID: leagueID,
Name: ev.League.Name, Name: ev.League.Name,
IsActive: true, IsActive: true,
SportID: convertInt32(ev.SportID),
}) })
if err != nil {
log.Printf("❌ Error Saving League on %v", ev.League.Name)
log.Printf("err:%v", err)
continue
}
if supported, err := s.store.CheckLeagueSupport(ctx, leagueID); !supported || err != nil { if supported, err := s.store.CheckLeagueSupport(ctx, leagueID); !supported || err != nil {
log.Printf("Skipping league %v", ev.League.Name)
skippedLeague = append(skippedLeague, ev.League.Name) skippedLeague = append(skippedLeague, ev.League.Name)
continue continue
} }
@ -335,18 +346,25 @@ func (s *service) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEv
return s.store.GetAllUpcomingEvents(ctx) return s.store.GetAllUpcomingEvents(ctx)
} }
func (s *service) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) { func (s *service) GetExpiredUpcomingEvents(ctx context.Context, filter domain.EventFilter) ([]domain.UpcomingEvent, error) {
return s.store.GetExpiredUpcomingEvents(ctx) return s.store.GetExpiredUpcomingEvents(ctx, filter)
} }
func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidInt32, sportID domain.ValidInt32, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error) { func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.EventFilter) ([]domain.UpcomingEvent, int64, error) {
return s.store.GetPaginatedUpcomingEvents(ctx, limit, offset, leagueID, sportID, firstStartTime, lastStartTime) return s.store.GetPaginatedUpcomingEvents(ctx, filter)
} }
func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) { func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) {
return s.store.GetUpcomingEventByID(ctx, ID) return s.store.GetUpcomingEventByID(ctx, ID)
} }
func (s *service) UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error {
return s.store.UpdateFinalScore(ctx, eventID, fullScore, status)
}
func (s *service) UpdateEventStatus(ctx context.Context, eventID string, status domain.EventStatus) error {
return s.store.UpdateEventStatus(ctx, eventID, status)
}
// func (s *service) GetAndStoreMatchResult(ctx context.Context, eventID string) error { // func (s *service) GetAndStoreMatchResult(ctx context.Context, eventID string) error {
// url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%s", s.token, eventID) // url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%s", s.token, eventID)

View File

@ -7,6 +7,8 @@ import (
) )
type Service interface { type Service interface {
GetAllLeagues(ctx context.Context) ([]domain.League, error) SaveLeague(ctx context.Context, l domain.League) error
SetLeagueActive(ctx context.Context, leagueId int64) error GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.League, error)
SetLeagueActive(ctx context.Context, leagueId int64, isActive bool) error
UpdateLeague(ctx context.Context, league domain.UpdateLeague) error
} }

View File

@ -17,10 +17,18 @@ func New(store *repository.Store) Service {
} }
} }
func (s *service) GetAllLeagues(ctx context.Context) ([]domain.League, error) { func (s *service) SaveLeague(ctx context.Context, l domain.League) error {
return s.store.GetAllLeagues(ctx) return s.store.SaveLeague(ctx, l)
} }
func (s *service) SetLeagueActive(ctx context.Context, leagueId int64) error { func (s *service) GetAllLeagues(ctx context.Context, filter domain.LeagueFilter) ([]domain.League, error) {
return s.store.SetLeagueActive(ctx, leagueId) return s.store.GetAllLeagues(ctx, filter)
}
func (s *service) SetLeagueActive(ctx context.Context, leagueId int64, isActive bool) error {
return s.store.SetLeagueActive(ctx, leagueId, isActive)
}
func (s *service) UpdateLeague(ctx context.Context, league domain.UpdateLeague) error {
return s.store.UpdateLeague(ctx, league)
} }

View File

@ -2,15 +2,19 @@ package odds
import ( import (
"context" "context"
"encoding/json"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
) )
type Service interface { type Service interface {
FetchNonLiveOdds(ctx context.Context) error FetchNonLiveOdds(ctx context.Context) error
FetchNonLiveOddsByEventID(ctx context.Context, eventIDStr string) (domain.BaseNonLiveOddResponse, error)
ParseOddSections(ctx context.Context, res json.RawMessage, sportID int64) (domain.ParseOddSectionsRes, error)
GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error)
GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string) ([]domain.Odd, error) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string) ([]domain.Odd, error)
GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit domain.ValidInt64, offset domain.ValidInt64) ([]domain.Odd, error) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit domain.ValidInt64, offset domain.ValidInt64) ([]domain.Odd, error)
GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error)
GetRawOddsByMarketID(ctx context.Context, marketID string, upcomingID string) (domain.RawOddsByMarketID, error) GetRawOddsByMarketID(ctx context.Context, marketID string, upcomingID string) (domain.RawOddsByMarketID, error)
DeleteOddsForEvent(ctx context.Context, eventID string) error
} }

View File

@ -41,18 +41,23 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
// wg.Add(2) // wg.Add(2)
wg.Add(1) wg.Add(1)
go func() {
defer wg.Done()
if err := s.fetchBet365Odds(ctx); err != nil {
errChan <- fmt.Errorf("bet365 odds fetching error: %w", err)
}
}()
// go func() { // go func() {
// defer wg.Done() // defer wg.Done()
// if err := s.fetchBet365Odds(ctx); err != nil { // if err := s.fetchBwinOdds(ctx); err != nil {
// errChan <- fmt.Errorf("bet365 odds fetching error: %w", err) // errChan <- fmt.Errorf("bwin odds fetching error: %w", err)
// } // }
// }() // }()
go func() { go func() {
defer wg.Done() wg.Wait()
if err := s.fetchBwinOdds(ctx); err != nil { close(errChan)
errChan <- fmt.Errorf("bwin odds fetching error: %w", err)
}
}() }()
var errs []error var errs []error
@ -77,98 +82,40 @@ func (s *ServiceImpl) fetchBet365Odds(ctx context.Context) error {
var errs []error var errs []error
for index, event := range eventIDs { for index, event := range eventIDs {
log.Printf("📡 Fetching prematch odds for event ID: %v (%d/%d) ", event.ID, index, len(eventIDs))
eventID, err := strconv.ParseInt(event.ID, 10, 64) oddsData, err := s.FetchNonLiveOddsByEventID(ctx, event.ID)
if err != nil { if err != nil {
s.logger.Error("Failed to parse event id", "error", err.Error()) s.logger.Error("Failed to fetch prematch odds", "eventID", event.ID, "error", err)
return err errs = append(errs, fmt.Errorf("failed to fetch prematch odds for event %v: %w", event.ID, err))
}
url := fmt.Sprintf("https://api.b365api.com/v3/bet365/prematch?token=%s&FI=%d", s.config.Bet365Token, eventID)
log.Printf("📡 Fetching prematch odds for event ID: %d (%d/%d) ", eventID, index, len(eventIDs))
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
log.Printf("❌ Failed to create request for event %d: %v", eventID, err)
s.logger.Error("Failed to create request for event%d: %v", strconv.FormatInt(eventID, 10), err.Error())
continue continue
} }
resp, err := s.client.Do(req) parsedOddSections, err := s.ParseOddSections(ctx, oddsData.Results[0], event.SportID)
if err != nil { if err != nil {
log.Printf("❌ Failed to fetch prematch odds for event %d: %v", eventID, err) s.logger.Error("Failed to parse odd section", "error", err)
continue errs = append(errs, fmt.Errorf("failed to parse odd section for event %v: %w", event.ID, err))
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("❌ Failed to read response body for event %d: %v", eventID, err)
continue
}
var oddsData domain.BaseNonLiveOddResponse
if err := json.Unmarshal(body, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 {
log.Printf("❌ Invalid prematch data for event %d", eventID)
continue continue
} }
switch event.SportID { if parsedOddSections.EventFI == "" {
case domain.FOOTBALL: s.logger.Error("Skipping result with no valid Event FI field", "fi", parsedOddSections.EventFI)
if err := s.parseFootball(ctx, oddsData.Results[0]); err != nil { errs = append(errs, errors.New("event FI is empty"))
s.logger.Error("Error while inserting football odd") continue
}
for oddCategory, section := range parsedOddSections.Sections {
if err := s.storeSection(ctx, event.ID, parsedOddSections.EventFI, oddCategory, section); err != nil {
s.logger.Error("Error storing odd section", "eventID", event.ID, "odd", oddCategory)
log.Printf("⚠️ Error when storing %v", err)
errs = append(errs, err) errs = append(errs, err)
} }
case domain.BASKETBALL:
if err := s.parseBasketball(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting basketball odd")
errs = append(errs, err)
} }
case domain.ICE_HOCKEY: for _, section := range parsedOddSections.OtherRes {
if err := s.parseIceHockey(ctx, oddsData.Results[0]); err != nil { if err := s.storeSection(ctx, event.ID, parsedOddSections.EventFI, "others", section); err != nil {
s.logger.Error("Error while inserting ice hockey odd") s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
}
case domain.CRICKET:
if err := s.parseCricket(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting cricket odd")
errs = append(errs, err)
}
case domain.VOLLEYBALL:
if err := s.parseVolleyball(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting volleyball odd")
errs = append(errs, err)
}
case domain.DARTS:
if err := s.parseDarts(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting darts odd")
errs = append(errs, err)
}
case domain.FUTSAL:
if err := s.parseFutsal(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting futsal odd")
errs = append(errs, err)
}
case domain.AMERICAN_FOOTBALL:
if err := s.parseAmericanFootball(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting american football odd")
errs = append(errs, err)
}
case domain.RUGBY_LEAGUE:
if err := s.parseRugbyLeague(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting rugby league odd")
errs = append(errs, err)
}
case domain.RUGBY_UNION:
if err := s.parseRugbyUnion(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting rugby union odd")
errs = append(errs, err)
}
case domain.BASEBALL:
if err := s.parseBaseball(ctx, oddsData.Results[0]); err != nil {
s.logger.Error("Error while inserting baseball odd")
errs = append(errs, err) errs = append(errs, err)
continue
} }
} }
@ -176,6 +123,10 @@ func (s *ServiceImpl) fetchBet365Odds(ctx context.Context) error {
} }
for err := range errs {
log.Printf("❌ Error: %v", err)
}
return nil return nil
} }
@ -266,134 +217,99 @@ func (s *ServiceImpl) fetchBwinOdds(ctx context.Context) error {
return nil return nil
} }
func (s *ServiceImpl) parseFootball(ctx context.Context, res json.RawMessage) error { func (s *ServiceImpl) FetchNonLiveOddsByEventID(ctx context.Context, eventIDStr string) (domain.BaseNonLiveOddResponse, error) {
eventID, err := strconv.ParseInt(eventIDStr, 10, 64)
if err != nil {
s.logger.Error("Failed to parse event id")
return domain.BaseNonLiveOddResponse{}, err
}
url := fmt.Sprintf("https://api.b365api.com/v3/bet365/prematch?token=%s&FI=%d", s.config.Bet365Token, eventID)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
log.Printf("❌ Failed to create request for event %d: %v", eventID, err)
}
resp, err := s.client.Do(req)
if err != nil {
log.Printf("❌ Failed to fetch prematch odds for event %d: %v", eventID, err)
return domain.BaseNonLiveOddResponse{}, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("❌ Failed to read response body for event %d: %v", eventID, err)
return domain.BaseNonLiveOddResponse{}, err
}
var oddsData domain.BaseNonLiveOddResponse
if err := json.Unmarshal(body, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 {
log.Printf("❌ Invalid prematch data for event %d", eventID)
return domain.BaseNonLiveOddResponse{}, err
}
return oddsData, nil
}
func (s *ServiceImpl) ParseOddSections(ctx context.Context, res json.RawMessage, sportID int32) (domain.ParseOddSectionsRes, error) {
var sections map[string]domain.OddsSection
var OtherRes []domain.OddsSection
var eventFI string
switch sportID {
case domain.FOOTBALL:
var footballRes domain.FootballOddsResponse var footballRes domain.FootballOddsResponse
if err := json.Unmarshal(res, &footballRes); err != nil { if err := json.Unmarshal(res, &footballRes); err != nil {
s.logger.Error("Failed to unmarshal football result", "error", err) s.logger.Error("Failed to unmarshal football result", "error", err)
return err return domain.ParseOddSectionsRes{}, err
} }
if footballRes.EventID == "" && footballRes.FI == "" { eventFI = footballRes.FI
s.logger.Error("Skipping football result with no valid Event ID", "eventID", footballRes.EventID, "fi", footballRes.FI) sections = map[string]domain.OddsSection{
return fmt.Errorf("Skipping football result with no valid Event ID Event ID %v", footballRes.EventID)
}
sections := map[string]domain.OddsSection{
"main": footballRes.Main, "main": footballRes.Main,
"asian_lines": footballRes.AsianLines, "asian_lines": footballRes.AsianLines,
"goals": footballRes.Goals, "goals": footballRes.Goals,
"half": footballRes.Half, "half": footballRes.Half,
} }
case domain.BASKETBALL:
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, footballRes.EventID, footballRes.FI, oddCategory, section); err != nil {
s.logger.Error("Error storing football section", "eventID", footballRes.FI, "odd", oddCategory)
log.Printf("⚠️ Error when storing football %v", err)
errs = append(errs, err)
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseBasketball(ctx context.Context, res json.RawMessage) error {
var basketballRes domain.BasketballOddsResponse var basketballRes domain.BasketballOddsResponse
if err := json.Unmarshal(res, &basketballRes); err != nil { if err := json.Unmarshal(res, &basketballRes); err != nil {
s.logger.Error("Failed to unmarshal basketball result", "error", err) s.logger.Error("Failed to unmarshal basketball result", "error", err)
return err return domain.ParseOddSectionsRes{}, err
} }
if basketballRes.EventID == "" && basketballRes.FI == "" { eventFI = basketballRes.FI
s.logger.Error("Skipping basketball result with no valid Event ID") OtherRes = basketballRes.Others
return fmt.Errorf("Skipping basketball result with no valid Event ID") sections = map[string]domain.OddsSection{
}
sections := map[string]domain.OddsSection{
"main": basketballRes.Main, "main": basketballRes.Main,
"half_props": basketballRes.HalfProps, "half_props": basketballRes.HalfProps,
"quarter_props": basketballRes.QuarterProps, "quarter_props": basketballRes.QuarterProps,
"team_props": basketballRes.TeamProps, "team_props": basketballRes.TeamProps,
} }
var errs []error case domain.ICE_HOCKEY:
for oddCategory, section := range sections {
if err := s.storeSection(ctx, basketballRes.EventID, basketballRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range basketballRes.Others {
if err := s.storeSection(ctx, basketballRes.EventID, basketballRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseIceHockey(ctx context.Context, res json.RawMessage) error {
var iceHockeyRes domain.IceHockeyOddsResponse var iceHockeyRes domain.IceHockeyOddsResponse
if err := json.Unmarshal(res, &iceHockeyRes); err != nil { if err := json.Unmarshal(res, &iceHockeyRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err) s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err return domain.ParseOddSectionsRes{}, err
} }
if iceHockeyRes.EventID == "" && iceHockeyRes.FI == "" { eventFI = iceHockeyRes.FI
s.logger.Error("Skipping result with no valid Event ID") OtherRes = iceHockeyRes.Others
return fmt.Errorf("Skipping result with no valid Event ID") sections = map[string]domain.OddsSection{
}
sections := map[string]domain.OddsSection{
"main": iceHockeyRes.Main, "main": iceHockeyRes.Main,
"main_2": iceHockeyRes.Main2, "main_2": iceHockeyRes.Main2,
"1st_period": iceHockeyRes.FirstPeriod, "1st_period": iceHockeyRes.FirstPeriod,
} }
case domain.CRICKET:
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, iceHockeyRes.EventID, iceHockeyRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range iceHockeyRes.Others {
if err := s.storeSection(ctx, iceHockeyRes.EventID, iceHockeyRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseCricket(ctx context.Context, res json.RawMessage) error {
var cricketRes domain.CricketOddsResponse var cricketRes domain.CricketOddsResponse
if err := json.Unmarshal(res, &cricketRes); err != nil { if err := json.Unmarshal(res, &cricketRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err) s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err return domain.ParseOddSectionsRes{}, err
} }
if cricketRes.EventID == "" && cricketRes.FI == "" { eventFI = cricketRes.FI
s.logger.Error("Skipping result with no valid Event ID") OtherRes = cricketRes.Others
return fmt.Errorf("Skipping result with no valid Event ID") sections = map[string]domain.OddsSection{
}
sections := map[string]domain.OddsSection{
"1st_over": cricketRes.Main, "1st_over": cricketRes.Main,
"innings_1": cricketRes.First_Innings, "innings_1": cricketRes.First_Innings,
"main": cricketRes.Main, "main": cricketRes.Main,
@ -401,205 +317,65 @@ func (s *ServiceImpl) parseCricket(ctx context.Context, res json.RawMessage) err
"player": cricketRes.Player, "player": cricketRes.Player,
"team": cricketRes.Team, "team": cricketRes.Team,
} }
case domain.VOLLEYBALL:
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, cricketRes.EventID, cricketRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range cricketRes.Others {
if err := s.storeSection(ctx, cricketRes.EventID, cricketRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseVolleyball(ctx context.Context, res json.RawMessage) error {
var volleyballRes domain.VolleyballOddsResponse var volleyballRes domain.VolleyballOddsResponse
if err := json.Unmarshal(res, &volleyballRes); err != nil { if err := json.Unmarshal(res, &volleyballRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err) s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err return domain.ParseOddSectionsRes{}, err
} }
if volleyballRes.EventID == "" && volleyballRes.FI == "" { eventFI = volleyballRes.FI
s.logger.Error("Skipping result with no valid Event ID") OtherRes = volleyballRes.Others
return fmt.Errorf("Skipping result with no valid Event ID") sections = map[string]domain.OddsSection{
}
sections := map[string]domain.OddsSection{
"main": volleyballRes.Main, "main": volleyballRes.Main,
} }
case domain.DARTS:
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, volleyballRes.EventID, volleyballRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range volleyballRes.Others {
if err := s.storeSection(ctx, volleyballRes.EventID, volleyballRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseDarts(ctx context.Context, res json.RawMessage) error {
var dartsRes domain.DartsOddsResponse var dartsRes domain.DartsOddsResponse
if err := json.Unmarshal(res, &dartsRes); err != nil { if err := json.Unmarshal(res, &dartsRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err) s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err return domain.ParseOddSectionsRes{}, err
} }
if dartsRes.EventID == "" && dartsRes.FI == "" { eventFI = dartsRes.FI
s.logger.Error("Skipping result with no valid Event ID") OtherRes = dartsRes.Others
return fmt.Errorf("Skipping result with no valid Event ID") sections = map[string]domain.OddsSection{
}
sections := map[string]domain.OddsSection{
"180s": dartsRes.OneEightys, "180s": dartsRes.OneEightys,
"extra": dartsRes.Extra, "extra": dartsRes.Extra,
"leg": dartsRes.Leg, "leg": dartsRes.Leg,
"main": dartsRes.Main, "main": dartsRes.Main,
} }
case domain.FUTSAL:
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, dartsRes.EventID, dartsRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range dartsRes.Others {
if err := s.storeSection(ctx, dartsRes.EventID, dartsRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseFutsal(ctx context.Context, res json.RawMessage) error {
var futsalRes domain.FutsalOddsResponse var futsalRes domain.FutsalOddsResponse
if err := json.Unmarshal(res, &futsalRes); err != nil { if err := json.Unmarshal(res, &futsalRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err) s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err return domain.ParseOddSectionsRes{}, err
} }
if futsalRes.EventID == "" && futsalRes.FI == "" { eventFI = futsalRes.FI
s.logger.Error("Skipping result with no valid Event ID") OtherRes = futsalRes.Others
return fmt.Errorf("Skipping result with no valid Event ID") sections = map[string]domain.OddsSection{
}
sections := map[string]domain.OddsSection{
"main": futsalRes.Main, "main": futsalRes.Main,
"score": futsalRes.Score, "score": futsalRes.Score,
} }
case domain.AMERICAN_FOOTBALL:
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, futsalRes.EventID, futsalRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range futsalRes.Others {
if err := s.storeSection(ctx, futsalRes.EventID, futsalRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseAmericanFootball(ctx context.Context, res json.RawMessage) error {
var americanFootballRes domain.AmericanFootballOddsResponse var americanFootballRes domain.AmericanFootballOddsResponse
if err := json.Unmarshal(res, &americanFootballRes); err != nil { if err := json.Unmarshal(res, &americanFootballRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err) s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err return domain.ParseOddSectionsRes{}, err
} }
if americanFootballRes.EventID == "" && americanFootballRes.FI == "" { eventFI = americanFootballRes.FI
s.logger.Error("Skipping result with no valid Event ID") OtherRes = americanFootballRes.Others
return fmt.Errorf("Skipping result with no valid Event ID") sections = map[string]domain.OddsSection{
}
sections := map[string]domain.OddsSection{
"half_props": americanFootballRes.HalfProps, "half_props": americanFootballRes.HalfProps,
"main": americanFootballRes.Main, "main": americanFootballRes.Main,
"quarter_props": americanFootballRes.QuarterProps, "quarter_props": americanFootballRes.QuarterProps,
} }
case domain.RUGBY_LEAGUE:
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, americanFootballRes.EventID, americanFootballRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range americanFootballRes.Others {
if err := s.storeSection(ctx, americanFootballRes.EventID, americanFootballRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseRugbyLeague(ctx context.Context, res json.RawMessage) error {
var rugbyLeagueRes domain.RugbyLeagueOddsResponse var rugbyLeagueRes domain.RugbyLeagueOddsResponse
if err := json.Unmarshal(res, &rugbyLeagueRes); err != nil { if err := json.Unmarshal(res, &rugbyLeagueRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err) s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err return domain.ParseOddSectionsRes{}, err
} }
if rugbyLeagueRes.EventID == "" && rugbyLeagueRes.FI == "" { eventFI = rugbyLeagueRes.FI
s.logger.Error("Skipping result with no valid Event ID") OtherRes = rugbyLeagueRes.Others
return fmt.Errorf("Skipping result with no valid Event ID") sections = map[string]domain.OddsSection{
}
sections := map[string]domain.OddsSection{
"10minute": rugbyLeagueRes.TenMinute, "10minute": rugbyLeagueRes.TenMinute,
"main": rugbyLeagueRes.Main, "main": rugbyLeagueRes.Main,
"main_2": rugbyLeagueRes.Main2, "main_2": rugbyLeagueRes.Main2,
@ -607,105 +383,39 @@ func (s *ServiceImpl) parseRugbyLeague(ctx context.Context, res json.RawMessage)
"Score": rugbyLeagueRes.Score, "Score": rugbyLeagueRes.Score,
"Team": rugbyLeagueRes.Team, "Team": rugbyLeagueRes.Team,
} }
case domain.RUGBY_UNION:
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, rugbyLeagueRes.EventID, rugbyLeagueRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range rugbyLeagueRes.Others {
if err := s.storeSection(ctx, rugbyLeagueRes.EventID, rugbyLeagueRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseRugbyUnion(ctx context.Context, res json.RawMessage) error {
var rugbyUnionRes domain.RugbyUnionOddsResponse var rugbyUnionRes domain.RugbyUnionOddsResponse
if err := json.Unmarshal(res, &rugbyUnionRes); err != nil { if err := json.Unmarshal(res, &rugbyUnionRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err) s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err return domain.ParseOddSectionsRes{}, err
} }
if rugbyUnionRes.EventID == "" && rugbyUnionRes.FI == "" { eventFI = rugbyUnionRes.FI
s.logger.Error("Skipping result with no valid Event ID") OtherRes = rugbyUnionRes.Others
return fmt.Errorf("Skipping result with no valid Event ID") sections = map[string]domain.OddsSection{
}
sections := map[string]domain.OddsSection{
"main": rugbyUnionRes.Main, "main": rugbyUnionRes.Main,
"main_2": rugbyUnionRes.Main2, "main_2": rugbyUnionRes.Main2,
"player": rugbyUnionRes.Player, "player": rugbyUnionRes.Player,
"Score": rugbyUnionRes.Score, "Score": rugbyUnionRes.Score,
"Team": rugbyUnionRes.Team, "Team": rugbyUnionRes.Team,
} }
case domain.BASEBALL:
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, rugbyUnionRes.EventID, rugbyUnionRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
for _, section := range rugbyUnionRes.Others {
if err := s.storeSection(ctx, rugbyUnionRes.EventID, rugbyUnionRes.FI, "others", section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (s *ServiceImpl) parseBaseball(ctx context.Context, res json.RawMessage) error {
var baseballRes domain.BaseballOddsResponse var baseballRes domain.BaseballOddsResponse
if err := json.Unmarshal(res, &baseballRes); err != nil { if err := json.Unmarshal(res, &baseballRes); err != nil {
s.logger.Error("Failed to unmarshal ice hockey result", "error", err) s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
return err return domain.ParseOddSectionsRes{}, err
} }
if baseballRes.EventID == "" && baseballRes.FI == "" { eventFI = baseballRes.FI
s.logger.Error("Skipping result with no valid Event ID") sections = map[string]domain.OddsSection{
return fmt.Errorf("Skipping result with no valid Event ID")
}
sections := map[string]domain.OddsSection{
"main": baseballRes.Main, "main": baseballRes.Main,
"mani_props": baseballRes.MainProps, "mani_props": baseballRes.MainProps,
} }
var errs []error
for oddCategory, section := range sections {
if err := s.storeSection(ctx, baseballRes.EventID, baseballRes.FI, oddCategory, section); err != nil {
s.logger.Error("Skipping result with no valid Event ID")
errs = append(errs, err)
continue
}
} }
if len(errs) > 0 { return domain.ParseOddSectionsRes{
return errors.Join(errs...) Sections: sections,
} OtherRes: OtherRes,
EventFI: eventFI,
return nil }, nil
} }
func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName string, section domain.OddsSection) error { func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName string, section domain.OddsSection) error {
@ -725,22 +435,21 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
// Check if the market id is a string // Check if the market id is a string
var marketIDstr string var marketIDstr string
err := json.Unmarshal(market.ID, &marketIDstr) err := json.Unmarshal(market.ID, &marketIDstr)
var marketIDint int64
if err != nil { if err != nil {
// check if its int // check if its int
var marketIDint int
err := json.Unmarshal(market.ID, &marketIDint) err := json.Unmarshal(market.ID, &marketIDint)
if err != nil { if err != nil {
s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name) s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name)
errs = append(errs, err) continue
} }
} } else {
marketIDint, err = strconv.ParseInt(marketIDstr, 10, 64)
marketIDint, err := strconv.ParseInt(marketIDstr, 10, 64)
if err != nil { if err != nil {
s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name) s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name)
errs = append(errs, err)
continue continue
} }
}
isSupported, ok := domain.SupportedMarkets[marketIDint] isSupported, ok := domain.SupportedMarkets[marketIDint]
@ -808,6 +517,10 @@ func (s *ServiceImpl) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context,
return s.store.GetPaginatedPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset) return s.store.GetPaginatedPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset)
} }
func (s *ServiceImpl) DeleteOddsForEvent(ctx context.Context, eventID string) error {
return s.store.DeleteOddsForEvent(ctx, eventID)
}
func getString(v interface{}) string { func getString(v interface{}) string {
if str, ok := v.(string); ok { if str, ok := v.(string); ok {
return str return str

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"log/slog" "log/slog"
"net/http" "net/http"
"strconv" "strconv"
@ -14,6 +15,9 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
) )
type Service struct { type Service struct {
@ -22,43 +26,52 @@ type Service struct {
logger *slog.Logger logger *slog.Logger
client *http.Client client *http.Client
betSvc bet.Service betSvc bet.Service
oddSvc odds.ServiceImpl
eventSvc event.Service
leagueSvc league.Service
} }
func NewService(repo *repository.Store, cfg *config.Config, logger *slog.Logger, betSvc bet.Service) *Service { func NewService(repo *repository.Store, cfg *config.Config, logger *slog.Logger, betSvc bet.Service, oddSvc odds.ServiceImpl, eventSvc event.Service, leagueSvc league.Service) *Service {
return &Service{ return &Service{
repo: repo, repo: repo,
config: cfg, config: cfg,
logger: logger, logger: logger,
client: &http.Client{Timeout: 10 * time.Second}, client: &http.Client{Timeout: 10 * time.Second},
betSvc: betSvc, betSvc: betSvc,
oddSvc: oddSvc,
eventSvc: eventSvc,
leagueSvc: leagueSvc,
} }
} }
var ( var (
ErrEventIsNotActive = fmt.Errorf("Event has been cancelled or postponed") ErrEventIsNotActive = fmt.Errorf("event has been cancelled or postponed")
) )
func (s *Service) FetchAndProcessResults(ctx context.Context) error { func (s *Service) FetchAndProcessResults(ctx context.Context) error {
// TODO: Optimize this because there could be many bet outcomes for the same odd // TODO: Optimize this because there could be many bet outcomes for the same odd
// Take market id and match result as param and update all the bet outcomes at the same time // Take market id and match result as param and update all the bet outcomes at the same time
events, err := s.repo.GetExpiredUpcomingEvents(ctx) events, err := s.repo.GetExpiredUpcomingEvents(ctx, domain.EventFilter{})
if err != nil { if err != nil {
s.logger.Error("Failed to fetch events") s.logger.Error("Failed to fetch events")
return err return err
} }
fmt.Printf("⚠️ Expired Events: %d \n", len(events)) fmt.Printf("⚠️ Expired Events: %d \n", len(events))
removed := 0 removed := 0
errs := make([]error, 0, len(events))
for i, event := range events { for i, event := range events {
eventID, err := strconv.ParseInt(event.ID, 10, 64) eventID, err := strconv.ParseInt(event.ID, 10, 64)
if err != nil { if err != nil {
s.logger.Error("Failed to parse event id") s.logger.Error("Failed to parse event id")
return err errs = append(errs, fmt.Errorf("failed to parse event id %s: %w", event.ID, err))
continue
} }
outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID) outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID)
if err != nil { if err != nil {
s.logger.Error("Failed to get pending bet outcomes", "error", err) s.logger.Error("Failed to get pending bet outcomes", "error", err)
return err errs = append(errs, fmt.Errorf("failed to get pending bet outcomes for event %d: %w", eventID, err))
continue
} }
if len(outcomes) == 0 { if len(outcomes) == 0 {
@ -68,6 +81,40 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
} }
isDeleted := true isDeleted := true
result, err := s.fetchResult(ctx, eventID)
if err != nil {
if err == ErrEventIsNotActive {
s.logger.Warn("Event is not active", "event_id", eventID, "error", err)
continue
}
s.logger.Error("Failed to fetch result", "event_id", eventID, "error", err)
continue
}
var commonResp domain.CommonResultResponse
if err := json.Unmarshal(result.Results[0], &commonResp); err != nil {
s.logger.Error("Failed to unmarshal common result", "event_id", eventID, "error", err)
continue
}
sportID, err := strconv.ParseInt(commonResp.SportID, 10, 64)
if err != nil {
s.logger.Error("Failed to parse sport id", "event_id", eventID, "error", err)
continue
}
timeStatusParsed, err := strconv.ParseInt(strings.TrimSpace(commonResp.TimeStatus), 10, 64)
if err != nil {
s.logger.Error("Failed to parse time status", "time_status", commonResp.TimeStatus, "error", err)
continue
}
// TODO: Figure out what to do with the events that have been cancelled or postponed, etc...
if timeStatusParsed != int64(domain.TIME_STATUS_ENDED) {
s.logger.Warn("Event is not ended yet", "event_id", eventID, "time_status", commonResp.TimeStatus)
fmt.Printf("⚠️ Event %v is not ended yet (%d/%d) \n", event.ID, i+1, len(events))
isDeleted = false
continue
}
for j, outcome := range outcomes { for j, outcome := range outcomes {
fmt.Printf("⚙️ Processing 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n", fmt.Printf("⚙️ Processing 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
outcome.MarketName, outcome.MarketName,
@ -80,23 +127,14 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
continue continue
} }
// TODO: optimize this because the result is being fetched for each outcome which will have the same event id but different market id parseResult, err := s.parseResult(ctx, result.Results[0], outcome, sportID)
result, err := s.fetchResult(ctx, outcome.EventID, outcome.OddID, outcome.MarketID, int64(event.SportID), outcome)
if err != nil { if err != nil {
if err == ErrEventIsNotActive {
s.logger.Warn("Event is not active", "event_id", outcome.EventID, "error", err)
continue
}
fmt.Printf("❌ failed to parse 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
outcome.MarketName,
event.HomeTeam+" "+event.AwayTeam, event.ID,
j+1, len(outcomes))
s.logger.Error("Failed to fetch result", "event_id", outcome.EventID, "outcome_id", outcome.ID, "market_id", outcome.MarketID, "market", outcome.MarketName, "error", err)
isDeleted = false isDeleted = false
s.logger.Error("Failed to parse result", "event_id", outcome.EventID, "outcome_id", outcome.ID, "error", err)
errs = append(errs, fmt.Errorf("failed to parse result for event %d: %w", outcome.EventID, err))
continue continue
} }
outcome, err = s.betSvc.UpdateBetOutcomeStatus(ctx, outcome.ID, parseResult.Status)
outcome, err = s.betSvc.UpdateBetOutcomeStatus(ctx, outcome.ID, result.Status)
if err != nil { if err != nil {
isDeleted = false isDeleted = false
s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err) s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err)
@ -147,113 +185,389 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err) s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err)
return err return err
} }
err = s.repo.DeleteOddsForEvent(ctx, event.ID)
if err != nil {
s.logger.Error("Failed to remove odds for event", "event_id", event.ID, "error", err)
return err
}
} }
} }
fmt.Printf("🗑️ Removed Events: %d \n", removed) fmt.Printf("🗑️ Removed Events: %d \n", removed)
if len(errs) > 0 {
s.logger.Error("Errors occurred while processing results", "errors", errs)
for _, err := range errs {
fmt.Println("Error:", err)
}
return fmt.Errorf("errors occurred while processing results: %v", errs)
}
s.logger.Info("Successfully processed results", "removed_events", removed, "total_events", len(events))
return nil return nil
} }
func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID, sportID int64, outcome domain.BetOutcome) (domain.CreateResult, error) { func (s *Service) CheckAndUpdateExpiredEvents(ctx context.Context) (int64, error) {
// url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%d", s.config.Bet365Token, eventID) events, err := s.repo.GetExpiredUpcomingEvents(ctx, domain.EventFilter{})
url := fmt.Sprintf("https://api.b365api.com/v1/event/view?token=%s&event_id=%d", s.config.Bet365Token, eventID) if err != nil {
s.logger.Error("Failed to fetch events")
return 0, err
}
fmt.Printf("⚠️ Expired Events: %d \n", len(events))
updated := 0
for i, event := range events {
fmt.Printf("⚙️ Processing event %v (%d/%d) \n", event.ID, i+1, len(events))
eventID, err := strconv.ParseInt(event.ID, 10, 64)
if err != nil {
s.logger.Error("Failed to parse event id")
continue
}
if event.Status == domain.STATUS_REMOVED {
s.logger.Info("Skipping updating removed event")
continue
}
result, err := s.fetchResult(ctx, eventID)
if err != nil {
s.logger.Error("Failed to fetch result", "event_id", eventID, "error", err)
continue
}
if result.Success != 1 || len(result.Results) == 0 {
s.logger.Error("Invalid API response", "event_id", eventID)
fmt.Printf("⚠️ Invalid API response for event %v \n", result)
continue
}
var commonResp domain.CommonResultResponse
if err := json.Unmarshal(result.Results[0], &commonResp); err != nil {
s.logger.Error("Failed to unmarshal common result", "event_id", eventID, "error", err)
continue
}
var eventStatus domain.EventStatus
// TODO Change event status to int64 enum
timeStatus, err := strconv.ParseInt(strings.TrimSpace(commonResp.TimeStatus), 10, 64)
switch timeStatus {
case int64(domain.TIME_STATUS_NOT_STARTED):
eventStatus = domain.STATUS_PENDING
case int64(domain.TIME_STATUS_IN_PLAY):
eventStatus = domain.STATUS_IN_PLAY
case int64(domain.TIME_STATUS_TO_BE_FIXED):
eventStatus = domain.STATUS_TO_BE_FIXED
case int64(domain.TIME_STATUS_ENDED):
eventStatus = domain.STATUS_ENDED
case int64(domain.TIME_STATUS_POSTPONED):
eventStatus = domain.STATUS_POSTPONED
case int64(domain.TIME_STATUS_CANCELLED):
eventStatus = domain.STATUS_CANCELLED
case int64(domain.TIME_STATUS_WALKOVER):
eventStatus = domain.STATUS_WALKOVER
case int64(domain.TIME_STATUS_INTERRUPTED):
eventStatus = domain.STATUS_INTERRUPTED
case int64(domain.TIME_STATUS_ABANDONED):
eventStatus = domain.STATUS_ABANDONED
case int64(domain.TIME_STATUS_RETIRED):
eventStatus = domain.STATUS_RETIRED
case int64(domain.TIME_STATUS_SUSPENDED):
eventStatus = domain.STATUS_SUSPENDED
case int64(domain.TIME_STATUS_DECIDED_BY_FA):
eventStatus = domain.STATUS_DECIDED_BY_FA
case int64(domain.TIME_STATUS_REMOVED):
eventStatus = domain.STATUS_REMOVED
default:
s.logger.Error("Invalid time status", "time_status", commonResp.TimeStatus, "event_id", eventID)
}
err = s.eventSvc.UpdateFinalScore(ctx, strconv.FormatInt(eventID, 10), commonResp.SS, eventStatus)
if err != nil {
s.logger.Error("Failed to update final score", "event_id", eventID, "error", err)
continue
}
updated++
fmt.Printf("✅ Successfully updated event %v to %v (%d/%d) \n", event.ID, eventStatus, i+1, len(events))
// Update the league because the league country code is only found on the result response
leagueID, err := strconv.ParseInt(commonResp.League.ID, 10, 64)
if err != nil {
log.Printf("❌ Invalid league id, leagueID %v", commonResp.League.ID)
continue
}
err = s.leagueSvc.UpdateLeague(ctx, domain.UpdateLeague{
ID: int64(event.LeagueID),
CountryCode: domain.ValidString{
Value: commonResp.League.CC,
Valid: true,
},
Bet365ID: domain.ValidInt32{
Value: int32(leagueID),
Valid: true,
},
})
if err != nil {
log.Printf("❌ Error Updating League %v", commonResp.League.Name)
log.Printf("err:%v", err)
continue
}
fmt.Printf("✅ Updated League %v with country code %v \n", leagueID, commonResp.League.CC)
}
if updated == 0 {
s.logger.Info("No events were updated")
return 0, nil
}
s.logger.Info("Successfully updated live events", "updated_events", updated, "total_events", len(events))
return int64(updated), nil
}
func (s *Service) GetResultsForEvent(ctx context.Context, eventID string) (json.RawMessage, []domain.BetOutcome, error) {
id, err := strconv.ParseInt(eventID, 10, 64)
if err != nil {
s.logger.Error("Failed to parse event id")
return json.RawMessage{}, nil, err
}
result, err := s.fetchResult(ctx, id)
if err != nil {
s.logger.Error("Failed to fetch result", "event_id", id, "error", err)
}
if result.Success != 1 || len(result.Results) == 0 {
fmt.Printf("⚠️ Invalid API response for event %v \n", result)
s.logger.Error("Invalid API response", "event_id", id)
return json.RawMessage{}, nil, fmt.Errorf("invalid API response for event %d", id)
}
var commonResp domain.CommonResultResponse
if err := json.Unmarshal(result.Results[0], &commonResp); err != nil {
s.logger.Error("Failed to unmarshal common result", "event_id", eventID, "error", err)
return json.RawMessage{}, nil, err
}
sportID, err := strconv.ParseInt(commonResp.SportID, 10, 32)
if err != nil {
s.logger.Error("Failed to parse sport id", "event_id", eventID, "error", err)
return json.RawMessage{}, nil, fmt.Errorf("failed to parse sport id: %w", err)
}
expireUnix, err := strconv.ParseInt(commonResp.Time, 10, 64)
if err != nil {
s.logger.Error("Failed to parse expire time", "event_id", eventID, "error", err)
return json.RawMessage{}, nil, fmt.Errorf("Failed to parse expire time for event %s: %w", eventID, err)
}
expires := time.Unix(expireUnix, 0)
odds, err := s.oddSvc.FetchNonLiveOddsByEventID(ctx, eventID)
if err != nil {
s.logger.Error("Failed to fetch non-live odds by event ID", "event_id", eventID, "error", err)
return json.RawMessage{}, nil, fmt.Errorf("failed to fetch non-live odds for event %s: %w", eventID, err)
}
parsedOddSections, err := s.oddSvc.ParseOddSections(ctx, odds.Results[0], int32(sportID))
if err != nil {
s.logger.Error("Failed to parse odd section", "error", err)
return json.RawMessage{}, nil, fmt.Errorf("failed to parse odd section for event %v: %w", eventID, err)
}
outcomes := make([]domain.BetOutcome, 0)
for _, section := range parsedOddSections.Sections {
// TODO: Remove repeat code here, same as in odds service
for _, market := range section.Sp {
var marketIDstr string
err := json.Unmarshal(market.ID, &marketIDstr)
var marketIDint int64
if err != nil {
// check if its int
err := json.Unmarshal(market.ID, &marketIDint)
if err != nil {
s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name)
continue
}
} else {
marketIDint, err = strconv.ParseInt(marketIDstr, 10, 64)
if err != nil {
s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name)
continue
}
}
isSupported, ok := domain.SupportedMarkets[marketIDint]
if !ok || !isSupported {
// s.logger.Info("Unsupported market_id", "marketID", marketIDint, "marketName", market.Name)
continue
}
for _, oddRes := range market.Odds {
var odd domain.RawOdd
if err := json.Unmarshal(oddRes, &odd); err != nil {
s.logger.Error("Failed to unmarshal odd", "error", err)
continue
}
oddID, err := strconv.ParseInt(odd.ID, 10, 64)
if err != nil {
s.logger.Error("Failed to parse odd id", "odd_id", odd.ID, "error", err)
continue
}
oddValue, err := strconv.ParseFloat(odd.Odds, 64)
if err != nil {
s.logger.Error("Failed to parse odd value", "odd_value", odd.Odds, "error", err)
continue
}
outcome := domain.BetOutcome{
EventID: id,
MarketID: marketIDint,
OddID: oddID,
MarketName: market.Name,
OddHeader: odd.Header,
OddHandicap: odd.Handicap,
OddName: odd.Name,
Odd: float32(oddValue),
SportID: sportID,
HomeTeamName: commonResp.Home.Name,
AwayTeamName: commonResp.Away.Name,
Status: domain.OUTCOME_STATUS_PENDING,
Expires: expires,
BetID: 0, // This won't be set
}
outcomes = append(outcomes, outcome)
}
}
}
if len(outcomes) == 0 {
s.logger.Warn("No outcomes found for event", "event_id", eventID)
return json.RawMessage{}, nil, fmt.Errorf("no outcomes found for event %s", eventID)
}
s.logger.Info("Successfully fetched outcomes for event", "event_id", eventID, "outcomes_count", len(outcomes))
// Get results for outcome
for i, outcome := range outcomes {
// Parse the result based on sport type
parsedResult, err := s.parseResult(ctx, result.Results[0], outcome, sportID)
if err != nil {
s.logger.Error("Failed to parse result for outcome", "event_id", outcome.EventID, "error", err)
return json.RawMessage{}, nil, fmt.Errorf("failed to parse result for outcome %d: %w", i, err)
}
outcomes[i].Status = parsedResult.Status
}
return result.Results[0], outcomes, err
}
func (s *Service) fetchResult(ctx context.Context, eventID int64) (domain.BaseResultResponse, error) {
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%d", s.config.Bet365Token, eventID)
// url := fmt.Sprintf("https://api.b365api.com/v1/event/view?token=%s&event_id=%d", s.config.Bet365Token, eventID)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { if err != nil {
s.logger.Error("Failed to create request", "event_id", eventID, "error", err) s.logger.Error("Failed to create request", "event_id", eventID, "error", err)
return domain.CreateResult{}, err return domain.BaseResultResponse{}, err
} }
resp, err := s.client.Do(req) resp, err := s.client.Do(req)
if err != nil { if err != nil {
s.logger.Error("Failed to fetch result", "event_id", eventID, "error", err) s.logger.Error("Failed to fetch result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err return domain.BaseResultResponse{}, err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
s.logger.Error("Unexpected status code", "event_id", eventID, "status_code", resp.StatusCode) s.logger.Error("Unexpected status code", "event_id", eventID, "status_code", resp.StatusCode)
return domain.CreateResult{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode) return domain.BaseResultResponse{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
} }
var resultResp domain.BaseResultResponse var resultResp domain.BaseResultResponse
if err := json.NewDecoder(resp.Body).Decode(&resultResp); err != nil { if err := json.NewDecoder(resp.Body).Decode(&resultResp); err != nil {
s.logger.Error("Failed to decode result", "event_id", eventID, "error", err) s.logger.Error("Failed to decode result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err return domain.BaseResultResponse{}, err
} }
if resultResp.Success != 1 || len(resultResp.Results) == 0 { if resultResp.Success != 1 || len(resultResp.Results) == 0 {
s.logger.Error("Invalid API response", "event_id", eventID) s.logger.Error("Invalid API response", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("invalid API response") fmt.Printf("⚠️ Invalid API response for event %v \n", resultResp)
return domain.BaseResultResponse{}, fmt.Errorf("invalid API response")
} }
return resultResp, nil
}
func (s *Service) parseResult(ctx context.Context, resultResp json.RawMessage, outcome domain.BetOutcome, sportID int64) (domain.CreateResult, error) {
var result domain.CreateResult var result domain.CreateResult
var err error
switch sportID { switch sportID {
case domain.FOOTBALL: case domain.FOOTBALL:
result, err = s.parseFootball(resultResp.Results[0], eventID, oddID, marketID, outcome) result, err = s.parseFootball(resultResp, outcome)
if err != nil { if err != nil {
s.logger.Error("Failed to parse football", "event id", eventID, "market_id", marketID, "error", err) s.logger.Error("Failed to parse football", "event id", outcome.EventID, "market_id", outcome.MarketID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
case domain.BASKETBALL: case domain.BASKETBALL:
result, err = s.parseBasketball(resultResp.Results[0], eventID, oddID, marketID, outcome) result, err = s.parseBasketball(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
if err != nil { if err != nil {
s.logger.Error("Failed to parse basketball", "event id", eventID, "market_id", marketID, "error", err) s.logger.Error("Failed to parse basketball", "event id", outcome.EventID, "market_id", outcome.MarketID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
case domain.ICE_HOCKEY: case domain.ICE_HOCKEY:
result, err = s.parseIceHockey(resultResp.Results[0], eventID, oddID, marketID, outcome) result, err = s.parseIceHockey(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
if err != nil { if err != nil {
s.logger.Error("Failed to parse ice hockey", "event id", eventID, "market_id", marketID, "error", err) s.logger.Error("Failed to parse ice hockey", "event id", outcome.EventID, "market_id", outcome.MarketID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
case domain.CRICKET: case domain.CRICKET:
result, err = s.parseCricket(resultResp.Results[0], eventID, oddID, marketID, outcome) result, err = s.parseCricket(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
if err != nil { if err != nil {
s.logger.Error("Failed to parse cricket", "event id", eventID, "market_id", marketID, "error", err) s.logger.Error("Failed to parse cricket", "event id", outcome.EventID, "market_id", outcome.MarketID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
case domain.VOLLEYBALL: case domain.VOLLEYBALL:
result, err = s.parseVolleyball(resultResp.Results[0], eventID, oddID, marketID, outcome) result, err = s.parseVolleyball(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
if err != nil { if err != nil {
s.logger.Error("Failed to parse volleyball", "event id", eventID, "market_id", marketID, "error", err) s.logger.Error("Failed to parse volleyball", "event id", outcome.EventID, "market_id", outcome.MarketID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
case domain.DARTS: case domain.DARTS:
result, err = s.parseDarts(resultResp.Results[0], eventID, oddID, marketID, outcome) result, err = s.parseDarts(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
if err != nil { if err != nil {
s.logger.Error("Failed to parse darts", "event id", eventID, "market_id", marketID, "error", err) s.logger.Error("Failed to parse darts", "event id", outcome.EventID, "market_id", outcome.MarketID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
case domain.FUTSAL: case domain.FUTSAL:
result, err = s.parseFutsal(resultResp.Results[0], eventID, oddID, marketID, outcome) result, err = s.parseFutsal(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
if err != nil { if err != nil {
s.logger.Error("Failed to parse futsal", "event id", eventID, "market_id", marketID, "error", err) s.logger.Error("Failed to parse futsal", "event id", outcome.EventID, "market_id", outcome.MarketID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
case domain.AMERICAN_FOOTBALL: case domain.AMERICAN_FOOTBALL:
result, err = s.parseNFL(resultResp.Results[0], eventID, oddID, marketID, outcome) result, err = s.parseNFL(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
if err != nil { if err != nil {
s.logger.Error("Failed to parse american football", "event id", eventID, "market_id", marketID, "error", err) s.logger.Error("Failed to parse american football", "event id", outcome.EventID, "market_id", outcome.MarketID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
case domain.RUGBY_UNION: case domain.RUGBY_UNION:
result, err = s.parseRugbyUnion(resultResp.Results[0], eventID, oddID, marketID, outcome) result, err = s.parseRugbyUnion(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
if err != nil { if err != nil {
s.logger.Error("Failed to parse rugby_union", "event id", eventID, "market_id", marketID, "error", err) s.logger.Error("Failed to parse rugby_union", "event id", outcome.EventID, "market_id", outcome.MarketID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
case domain.RUGBY_LEAGUE: case domain.RUGBY_LEAGUE:
result, err = s.parseRugbyLeague(resultResp.Results[0], eventID, oddID, marketID, outcome) result, err = s.parseRugbyLeague(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
if err != nil { if err != nil {
s.logger.Error("Failed to parse rugby_league", "event id", eventID, "market_id", marketID, "error", err) s.logger.Error("Failed to parse rugby_league", "event id", outcome.EventID, "market_id", outcome.MarketID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
case domain.BASEBALL: case domain.BASEBALL:
result, err = s.parseBaseball(resultResp.Results[0], eventID, oddID, marketID, outcome) result, err = s.parseBaseball(resultResp, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
if err != nil { if err != nil {
s.logger.Error("Failed to parse baseball", "event id", eventID, "market_id", marketID, "error", err) s.logger.Error("Failed to parse baseball", "event id", outcome.EventID, "market_id", outcome.MarketID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
default: default:
@ -264,52 +578,14 @@ func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID, spo
return result, nil return result, nil
} }
func (s *Service) parseTimeStatus(timeStatusStr string) (bool, error) { func (s *Service) parseFootball(resultRes json.RawMessage, outcome domain.BetOutcome) (domain.CreateResult, error) {
timeStatusParsed, err := strconv.ParseInt(strings.TrimSpace(timeStatusStr), 10, 64)
if err != nil {
s.logger.Error("Failed to parse time status", "time_status", timeStatusStr, "error", err)
return false, fmt.Errorf("failed to parse time status: %w", err)
}
timeStatus := domain.TimeStatus(timeStatusParsed)
switch timeStatus {
case domain.TIME_STATUS_NOT_STARTED, domain.TIME_STATUS_IN_PLAY, domain.TIME_STATUS_TO_BE_FIXED, domain.TIME_STATUS_ENDED:
return true, nil
case domain.TIME_STATUS_POSTPONED,
domain.TIME_STATUS_CANCELLED,
domain.TIME_STATUS_WALKOVER,
domain.TIME_STATUS_INTERRUPTED,
domain.TIME_STATUS_ABANDONED,
domain.TIME_STATUS_RETIRED,
domain.TIME_STATUS_SUSPENDED,
domain.TIME_STATUS_DECIDED_BY_FA,
domain.TIME_STATUS_REMOVED:
return false, nil
default:
s.logger.Error("Invalid time status", "time_status", timeStatus)
return false, fmt.Errorf("invalid time status: %d", timeStatus)
}
}
func (s *Service) parseFootball(resultRes json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
var fbResp domain.FootballResultResponse var fbResp domain.FootballResultResponse
if err := json.Unmarshal(resultRes, &fbResp); err != nil { if err := json.Unmarshal(resultRes, &fbResp); err != nil {
s.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err) s.logger.Error("Failed to unmarshal football result", "event_id", outcome.EventID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
result := fbResp result := fbResp
isEventActive, err := s.parseTimeStatus(result.TimeStatus)
if err != nil {
s.logger.Error("Failed to parse time status", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if !isEventActive {
s.logger.Warn("Event is not active", "event_id", eventID)
return domain.CreateResult{}, ErrEventIsNotActive
}
finalScore := parseSS(result.SS) finalScore := parseSS(result.SS)
firstHalfScore := parseScore(result.Scores.FirstHalf.Home, result.Scores.FirstHalf.Away) firstHalfScore := parseScore(result.Scores.FirstHalf.Home, result.Scores.FirstHalf.Away)
secondHalfScore := parseScore(result.Scores.SecondHalf.Home, result.Scores.SecondHalf.Away) secondHalfScore := parseScore(result.Scores.SecondHalf.Home, result.Scores.SecondHalf.Away)
@ -318,15 +594,15 @@ func (s *Service) parseFootball(resultRes json.RawMessage, eventID, oddID, marke
halfTimeCorners := parseStats(result.Stats.HalfTimeCorners) halfTimeCorners := parseStats(result.Stats.HalfTimeCorners)
status, err := s.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, secondHalfScore, corners, halfTimeCorners, result.Events) status, err := s.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, secondHalfScore, corners, halfTimeCorners, result.Events)
if err != nil { if err != nil {
s.logger.Error("Failed to evaluate football outcome", "event_id", eventID, "market_id", marketID, "error", err) s.logger.Error("Failed to evaluate football outcome", "event_id", outcome.EventID, "market_id", outcome.MarketID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
return domain.CreateResult{ return domain.CreateResult{
BetOutcomeID: 0, BetOutcomeID: 0,
EventID: eventID, EventID: outcome.EventID,
OddID: oddID, OddID: outcome.OddID,
MarketID: marketID, MarketID: outcome.MarketID,
Status: status, Status: status,
Score: result.SS, Score: result.SS,
}, nil }, nil
@ -340,15 +616,6 @@ func (s *Service) parseBasketball(response json.RawMessage, eventID, oddID, mark
s.logger.Error("Failed to unmarshal basketball result", "event_id", eventID, "error", err) s.logger.Error("Failed to unmarshal basketball result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
isEventActive, err := s.parseTimeStatus(basketBallRes.TimeStatus)
if err != nil {
s.logger.Error("Failed to parse time status", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if !isEventActive {
s.logger.Warn("Event is not active", "event_id", eventID)
return domain.CreateResult{}, ErrEventIsNotActive
}
status, err := s.evaluateBasketballOutcome(outcome, basketBallRes) status, err := s.evaluateBasketballOutcome(outcome, basketBallRes)
if err != nil { if err != nil {
@ -373,15 +640,6 @@ func (s *Service) parseIceHockey(response json.RawMessage, eventID, oddID, marke
s.logger.Error("Failed to unmarshal ice hockey result", "event_id", eventID, "error", err) s.logger.Error("Failed to unmarshal ice hockey result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err return domain.CreateResult{}, err
} }
isEventActive, err := s.parseTimeStatus(iceHockeyRes.TimeStatus)
if err != nil {
s.logger.Error("Failed to parse time status", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if !isEventActive {
s.logger.Warn("Event is not active", "event_id", eventID)
return domain.CreateResult{}, ErrEventIsNotActive
}
status, err := s.evaluateIceHockeyOutcome(outcome, iceHockeyRes) status, err := s.evaluateIceHockeyOutcome(outcome, iceHockeyRes)
if err != nil { if err != nil {

View File

@ -11,6 +11,7 @@ type TicketStore interface {
CreateTicketOutcome(ctx context.Context, outcomes []domain.CreateTicketOutcome) (int64, error) CreateTicketOutcome(ctx context.Context, outcomes []domain.CreateTicketOutcome) (int64, error)
GetTicketByID(ctx context.Context, id int64) (domain.GetTicket, error) GetTicketByID(ctx context.Context, id int64) (domain.GetTicket, error)
GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error)
CountTicketByIP(ctx context.Context, IP string) (int64, error)
UpdateTicketOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error UpdateTicketOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error
DeleteOldTickets(ctx context.Context) error DeleteOldTickets(ctx context.Context) error
DeleteTicket(ctx context.Context, id int64) error DeleteTicket(ctx context.Context, id int64) error

View File

@ -31,6 +31,10 @@ func (s *Service) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error)
return s.ticketStore.GetAllTickets(ctx) return s.ticketStore.GetAllTickets(ctx)
} }
func (s *Service) CountTicketByIP(ctx context.Context, IP string) (int64, error) {
return s.ticketStore.CountTicketByIP(ctx, IP)
}
func (s *Service) UpdateTicketOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { func (s *Service) UpdateTicketOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error {
return s.ticketStore.UpdateTicketOutcomeStatus(ctx, id, status) return s.ticketStore.UpdateTicketOutcomeStatus(ctx, id, status)
} }

View File

@ -71,7 +71,7 @@ func (s *Service) GetCashiersByBranch(ctx context.Context, branchID int64) ([]do
return s.userStore.GetCashiersByBranch(ctx, branchID) return s.userStore.GetCashiersByBranch(ctx, branchID)
} }
func (s *Service) GetAllCashiers(ctx context.Context) ([]domain.GetCashier, error) { func (s *Service) GetAllCashiers(ctx context.Context) ([]domain.GetCashier, int64, error){
return s.userStore.GetAllCashiers(ctx) return s.userStore.GetAllCashiers(ctx)
} }

View File

@ -11,7 +11,7 @@ type UserStore interface {
CreateUserWithoutOtp(ctx context.Context, user domain.User, is_company bool) (domain.User, error) CreateUserWithoutOtp(ctx context.Context, user domain.User, is_company bool) (domain.User, error)
GetUserByID(ctx context.Context, id int64) (domain.User, error) GetUserByID(ctx context.Context, id int64) (domain.User, error)
GetAllUsers(ctx context.Context, filter Filter) ([]domain.User, int64, error) GetAllUsers(ctx context.Context, filter Filter) ([]domain.User, int64, error)
GetAllCashiers(ctx context.Context) ([]domain.GetCashier, error) GetAllCashiers(ctx context.Context) ([]domain.GetCashier, int64, error)
GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error) GetCashierByID(ctx context.Context, cashierID int64) (domain.GetCashier, error)
GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error) GetCashiersByBranch(ctx context.Context, branchID int64) ([]domain.User, error)
UpdateUser(ctx context.Context, user domain.UpdateUserReq) error UpdateUser(ctx context.Context, user domain.UpdateUserReq) error

View File

@ -2,6 +2,7 @@ package user
import ( import (
"context" "context"
"time" "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
@ -33,6 +34,7 @@ func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswo
} else { } else {
sentTo = resetReq.PhoneNumber sentTo = resetReq.PhoneNumber
} }
otp, err := s.otpStore.GetOtp( otp, err := s.otpStore.GetOtp(
ctx, sentTo, ctx, sentTo,
domain.OtpReset, resetReq.OtpMedium) domain.OtpReset, resetReq.OtpMedium)
@ -55,6 +57,7 @@ func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswo
return err return err
} }
// reset pass and mark otp as used // reset pass and mark otp as used
err = s.userStore.UpdatePassword(ctx, sentTo, hashedPassword, otp.ID) err = s.userStore.UpdatePassword(ctx, sentTo, hashedPassword, otp.ID)
if err != nil { if err != nil {
return err return err

View File

@ -39,26 +39,38 @@ func SetupReportCronJob(reportWorker *worker.ReportWorker) {
s.StartAsync() s.StartAsync()
} }
func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.Service, resultService *resultsvc.Service) { func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.ServiceImpl, resultService *resultsvc.Service) {
c := cron.New(cron.WithSeconds()) c := cron.New(cron.WithSeconds())
schedule := []struct { schedule := []struct {
spec string spec string
task func() task func()
}{ }{
// {
// spec: "0 0 * * * *", // Every 1 hour
// task: func() {
// if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
// log.Printf("FetchUpcomingEvents error: %v", err)
// }
// },
// },
// {
// spec: "0 0 * * * *", // Every 15 minutes
// task: func() {
// if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
// log.Printf("FetchNonLiveOdds error: %v", err)
// }
// },
// },
{ {
spec: "0 0 * * * *", // Every 1 hour spec: "0 */5 * * * *", // Every 5 Minutes
task: func() { task: func() {
if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { log.Println("Updating expired events status...")
log.Printf("FetchUpcomingEvents error: %v", err)
} if _, err := resultService.CheckAndUpdateExpiredEvents(context.Background()); err != nil {
}, log.Printf("Failed to update events: %v", err)
}, } else {
{ log.Printf("Successfully updated expired events")
spec: "0 */15 * * * *", // Every 15 minutes
task: func() {
if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
log.Printf("FetchNonLiveOdds error: %v", err)
} }
}, },
}, },
@ -77,7 +89,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
} }
for _, job := range schedule { for _, job := range schedule {
// job.task() job.task()
if _, err := c.AddFunc(job.spec, job.task); err != nil { if _, err := c.AddFunc(job.spec, job.task); err != nil {
log.Fatalf("Failed to schedule cron job: %v", err) log.Fatalf("Failed to schedule cron job: %v", err)
} }

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"fmt"
"strconv" "strconv"
"time" "time"
@ -23,7 +24,7 @@ import (
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /bet [post] // @Router /bet [post]
func (h *Handler) CreateBet(c *fiber.Ctx) error { func (h *Handler) CreateBet(c *fiber.Ctx) error {
fmt.Printf("Calling leagues")
// Get user_id from middleware // Get user_id from middleware
userID := c.Locals("user_id").(int64) userID := c.Locals("user_id").(int64)
role := c.Locals("role").(domain.Role) role := c.Locals("role").(domain.Role)
@ -39,7 +40,8 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
} }
res, err := h.betSvc.PlaceBet(c.Context(), req, userID, role) res, err := h.betSvc.
PlaceBet(c.Context(), req, userID, role)
if err != nil { if err != nil {
h.logger.Error("PlaceBet failed", "error", err) h.logger.Error("PlaceBet failed", "error", err)

View File

@ -67,6 +67,7 @@ type BranchDetailRes struct {
IsSelfOwned bool `json:"is_self_owned" example:"false"` IsSelfOwned bool `json:"is_self_owned" example:"false"`
ManagerName string `json:"manager_name" example:"John Smith"` ManagerName string `json:"manager_name" example:"John Smith"`
ManagerPhoneNumber string `json:"manager_phone_number" example:"0911111111"` ManagerPhoneNumber string `json:"manager_phone_number" example:"0911111111"`
Balance float32 `json:"balance" example:"100.5"`
} }
func convertBranch(branch domain.Branch) BranchRes { func convertBranch(branch domain.Branch) BranchRes {
@ -92,6 +93,7 @@ func convertBranchDetail(branch domain.BranchDetail) BranchDetailRes {
IsSelfOwned: branch.IsSelfOwned, IsSelfOwned: branch.IsSelfOwned,
ManagerName: branch.ManagerName, ManagerName: branch.ManagerName,
ManagerPhoneNumber: branch.ManagerPhoneNumber, ManagerPhoneNumber: branch.ManagerPhoneNumber,
Balance: branch.Balance.Float32(),
} }
} }
@ -552,6 +554,50 @@ func (h *Handler) GetBranchCashiers(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Branch Cashiers retrieved successfully", result, nil) return response.WriteJSON(c, fiber.StatusOK, "Branch Cashiers retrieved successfully", result, nil)
} }
// GetBranchForCashier godoc
// @Summary Gets branch for cahier
// @Description Gets branch for cahier
// @Tags branch
// @Accept json
// @Produce json
// @Param id path int true "Branch ID"
// @Success 200 {object} BranchDetailRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /branchCashier [get]
func (h *Handler) GetBranchForCashier(c *fiber.Ctx) error {
cashierID, ok := c.Locals("user_id").(int64)
if !ok {
h.logger.Error("Invalid cashier ID in context")
return response.WriteJSON(c, fiber.StatusUnauthorized, "Invalid cashier identification", nil, nil)
}
role, ok := c.Locals("role").(domain.Role)
if !ok || role != domain.RoleCashier {
h.logger.Error("Unauthorized access", "cashierID", cashierID, "role", role)
return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil)
}
branchID, ok := c.Locals("branch_id").(domain.ValidInt64)
if !ok || !branchID.Valid {
h.logger.Error("Invalid branch ID in context", "cashierID", cashierID)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid branch ID", nil, nil)
}
branch, err := h.branchSvc.GetBranchByID(c.Context(), branchID.Value)
if err != nil {
h.logger.Error("Failed to get branch by ID", "branchID", branchID.Value, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve branch", err, nil)
}
res := convertBranchDetail(branch)
return response.WriteJSON(c, fiber.StatusOK, "Branch retrieved successfully", res, nil)
}
// GetBetByBranchID godoc // GetBetByBranchID godoc
// @Summary Gets bets by its branch id // @Summary Gets bets by its branch id
// @Description Gets bets by its branch id // @Description Gets bets by its branch id

View File

@ -99,6 +99,9 @@ type GetCashierRes struct {
Suspended bool `json:"suspended"` Suspended bool `json:"suspended"`
LastLogin time.Time `json:"last_login"` LastLogin time.Time `json:"last_login"`
BranchID int64 `json:"branch_id"` BranchID int64 `json:"branch_id"`
BranchName string `json:"branch_name"`
BranchWallet int64 `json:"branch_wallet"`
BranchLocation string `json:"branch_location"`
} }
// GetAllCashiers godoc // GetAllCashiers godoc
@ -139,7 +142,7 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
} }
cashiers, total, err := h.userSvc.GetAllUsers(c.Context(), filter) cashiers, total, err := h.userSvc.GetAllCashiers(c.Context())
if err != nil { if err != nil {
h.logger.Error("GetAllCashiers failed", "error", err) h.logger.Error("GetAllCashiers failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil)
@ -172,6 +175,10 @@ func (h *Handler) GetAllCashiers(c *fiber.Ctx) error {
SuspendedAt: cashier.SuspendedAt, SuspendedAt: cashier.SuspendedAt,
Suspended: cashier.Suspended, Suspended: cashier.Suspended,
LastLogin: *lastLogin, LastLogin: *lastLogin,
BranchID: cashier.BranchID,
BranchName: cashier.BranchName,
BranchWallet: cashier.BranchWallet,
BranchLocation: cashier.BranchLocation,
}) })
} }
@ -215,6 +222,7 @@ func (h *Handler) GetCashierByID(c *fiber.Ctx) error {
} }
user, err := h.userSvc.GetCashierByID(c.Context(), cashierID) user, err := h.userSvc.GetCashierByID(c.Context(), cashierID)
if err != nil { if err != nil {
h.logger.Error("Get User By ID failed", "error", err) h.logger.Error("Get User By ID failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get cashiers", nil, nil)
@ -244,6 +252,9 @@ func (h *Handler) GetCashierByID(c *fiber.Ctx) error {
Suspended: user.Suspended, Suspended: user.Suspended,
LastLogin: *lastLogin, LastLogin: *lastLogin,
BranchID: user.BranchID, BranchID: user.BranchID,
BranchName: user.BranchName,
BranchWallet: user.BranchWallet,
BranchLocation: user.BranchLocation,
} }
return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil)
} }

View File

@ -15,6 +15,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
@ -48,6 +49,7 @@ type Handler struct {
veliVirtualGameSvc veli.VeliVirtualGameService veliVirtualGameSvc veli.VeliVirtualGameService
recommendationSvc recommendation.RecommendationService recommendationSvc recommendation.RecommendationService
authSvc *authentication.Service authSvc *authentication.Service
resultSvc result.Service
jwtConfig jwtutil.JwtConfig jwtConfig jwtutil.JwtConfig
validator *customvalidator.CustomValidator validator *customvalidator.CustomValidator
Cfg *config.Config Cfg *config.Config
@ -76,6 +78,7 @@ func New(
prematchSvc *odds.ServiceImpl, prematchSvc *odds.ServiceImpl,
eventSvc event.Service, eventSvc event.Service,
leagueSvc league.Service, leagueSvc league.Service,
resultSvc result.Service,
cfg *config.Config, cfg *config.Config,
) *Handler { ) *Handler {
return &Handler{ return &Handler{
@ -100,6 +103,7 @@ func New(
veliVirtualGameSvc: veliVirtualGameSvc, veliVirtualGameSvc: veliVirtualGameSvc,
recommendationSvc: recommendationSvc, recommendationSvc: recommendationSvc,
authSvc: authSvc, authSvc: authSvc,
resultSvc: resultSvc,
jwtConfig: jwtConfig, jwtConfig: jwtConfig,
Cfg: cfg, Cfg: cfg,
} }

View File

@ -1,22 +1,85 @@
package handlers package handlers
import ( import (
"fmt"
"strconv" "strconv"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
// GetAllLeagues godoc
// @Summary Gets all leagues
// @Description Gets all leagues
// @Tags leagues
// @Accept json
// @Produce json
// @Success 200 {array} domain.League
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /leagues [get]
func (h *Handler) GetAllLeagues(c *fiber.Ctx) error { func (h *Handler) GetAllLeagues(c *fiber.Ctx) error {
leagues, err := h.leagueSvc.GetAllLeagues(c.Context()) page := c.QueryInt("page", 1)
if err != nil { pageSize := c.QueryInt("page_size", 10)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get leagues", err, nil)
limit := domain.ValidInt64{
Value: int64(pageSize),
Valid: pageSize == 0,
}
offset := domain.ValidInt64{
Value: int64(page - 1),
Valid: true,
} }
return response.WriteJSON(c, fiber.StatusOK, "All leagues retrived", leagues, nil) countryCodeQuery := c.Query("cc")
countryCode := domain.ValidString{
Value: countryCodeQuery,
Valid: countryCodeQuery != "",
}
isActiveQuery := c.QueryBool("is_active", false)
isActiveFilter := c.QueryBool("is_active_filter", false)
isActive := domain.ValidBool{
Value: isActiveQuery,
Valid: isActiveFilter,
}
sportIDQuery := c.Query("sport_id")
var sportID domain.ValidInt32
if sportIDQuery != "" {
sportIDint, err := strconv.Atoi(sportIDQuery)
if err != nil {
h.logger.Error("invalid sport id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid sport id", nil, nil)
}
sportID = domain.ValidInt32{
Value: int32(sportIDint),
Valid: true,
}
}
leagues, err := h.leagueSvc.GetAllLeagues(c.Context(), domain.LeagueFilter{
CountryCode: countryCode,
IsActive: isActive,
SportID: sportID,
Limit: limit,
Offset: offset,
})
if err != nil {
fmt.Printf("Error fetching league %v \n", err)
h.logger.Error("Failed to get leagues", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get leagues", err, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "All leagues retrieved", leagues, nil)
}
type SetLeagueActiveReq struct {
IsActive bool `json:"is_active"`
} }
func (h *Handler) SetLeagueActive(c *fiber.Ctx) error { func (h *Handler) SetLeagueActive(c *fiber.Ctx) error {
fmt.Printf("Set Active Leagues")
leagueIdStr := c.Params("id") leagueIdStr := c.Params("id")
if leagueIdStr == "" { if leagueIdStr == "" {
response.WriteJSON(c, fiber.StatusBadRequest, "Missing league id", nil, nil) response.WriteJSON(c, fiber.StatusBadRequest, "Missing league id", nil, nil)
@ -26,7 +89,18 @@ func (h *Handler) SetLeagueActive(c *fiber.Ctx) error {
response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil) response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil)
} }
if err := h.leagueSvc.SetLeagueActive(c.Context(), int64(leagueId)); err != nil { var req SetLeagueActiveReq
if err := c.BodyParser(&req); err != nil {
h.logger.Error("SetLeagueReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Failed to parse request", err, nil)
}
valErrs, ok := h.validator.Validate(c, req)
if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
}
if err := h.leagueSvc.SetLeagueActive(c.Context(), int64(leagueId), req.IsActive); err != nil {
response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update league", err, nil) response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update league", err, nil)
} }

View File

@ -78,11 +78,10 @@ func (h *Handler) ConnectSocket(c *fiber.Ctx) error {
} }
h.notificationSvc.Hub.Register <- client h.notificationSvc.Hub.Register <- client
h.logger.Info("WebSocket connection established", "userID", userID) // h.logger.Info("WebSocket connection established", "userID", userID)
defer func() { defer func() {
h.notificationSvc.Hub.Unregister <- client h.notificationSvc.Hub.Unregister <- client
h.logger.Info("WebSocket connection closed", "userID", userID)
conn.Close() conn.Close()
}() }()

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"fmt"
"strconv" "strconv"
"time" "time"
@ -9,33 +10,6 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
// GetPrematchOdds godoc
// @Summary Retrieve prematch odds for an event
// @Description Retrieve prematch odds for a specific event by event ID
// @Tags prematch
// @Accept json
// @Produce json
// @Param event_id path string true "Event ID"
// @Success 200 {array} domain.Odd
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /prematch/odds/{event_id} [get]
func (h *Handler) GetPrematchOdds(c *fiber.Ctx) error {
eventID := c.Params("event_id")
if eventID == "" {
return response.WriteJSON(c, fiber.StatusBadRequest, "Missing event_id", nil, nil)
}
odds, err := h.prematchSvc.GetPrematchOdds(c.Context(), eventID)
if err != nil {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve odds", nil, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "Prematch odds retrieved successfully", odds, nil)
}
// GetALLPrematchOdds // GetALLPrematchOdds
// @Summary Retrieve all prematch odds // @Summary Retrieve all prematch odds
// @Description Retrieve all prematch odds from the database // @Description Retrieve all prematch odds from the database
@ -44,7 +18,7 @@ func (h *Handler) GetPrematchOdds(c *fiber.Ctx) error {
// @Produce json // @Produce json
// @Success 200 {array} domain.Odd // @Success 200 {array} domain.Odd
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /prematch/odds [get] // @Router /odds [get]
func (h *Handler) GetALLPrematchOdds(c *fiber.Ctx) error { func (h *Handler) GetALLPrematchOdds(c *fiber.Ctx) error {
odds, err := h.prematchSvc.GetALLPrematchOdds(c.Context()) odds, err := h.prematchSvc.GetALLPrematchOdds(c.Context())
@ -67,7 +41,7 @@ func (h *Handler) GetALLPrematchOdds(c *fiber.Ctx) error {
// @Success 200 {array} domain.RawOddsByMarketID // @Success 200 {array} domain.RawOddsByMarketID
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /prematch/odds/upcoming/{upcoming_id}/market/{market_id} [get] // @Router /odds/upcoming/{upcoming_id}/market/{market_id} [get]
func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error { func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error {
marketID := c.Params("market_id") marketID := c.Params("market_id")
@ -82,7 +56,8 @@ func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error {
rawOdds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketID, upcomingID) rawOdds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketID, upcomingID)
if err != nil { if err != nil {
// h.logger.Error("failed to fetch raw odds", "error", err) fmt.Printf("Failed to fetch raw odds: %v market_id:%v upcomingID:%v\n", err, marketID, upcomingID)
h.logger.Error("failed to fetch raw odds", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve raw odds", err, nil) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve raw odds", err, nil)
} }
@ -99,37 +74,51 @@ func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error {
// @Param page_size query int false "Page size" // @Param page_size query int false "Page size"
// @Param league_id query string false "League ID Filter" // @Param league_id query string false "League ID Filter"
// @Param sport_id query string false "Sport ID Filter" // @Param sport_id query string false "Sport ID Filter"
// @Param cc query string false "Country Code Filter"
// @Param first_start_time query string false "Start Time" // @Param first_start_time query string false "Start Time"
// @Param last_start_time query string false "End Time" // @Param last_start_time query string false "End Time"
// @Success 200 {array} domain.UpcomingEvent // @Success 200 {array} domain.UpcomingEvent
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /prematch/events [get] // @Router /events [get]
func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error { func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
page := c.QueryInt("page", 1) page := c.QueryInt("page", 1)
pageSize := c.QueryInt("page_size", 10) pageSize := c.QueryInt("page_size", 10)
leagueIDQuery, err := strconv.Atoi(c.Query("league_id")) limit := domain.ValidInt64{
Value: int64(pageSize),
Valid: true,
}
offset := domain.ValidInt64{
Value: int64(page - 1),
Valid: true,
}
leagueIDQuery := c.Query("league_id")
var leagueID domain.ValidInt32
if leagueIDQuery != "" {
leagueIDInt, err := strconv.Atoi(leagueIDQuery)
if err != nil { if err != nil {
h.logger.Error("invalid league id", "error", err) h.logger.Error("invalid league id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil)
} }
leagueID = domain.ValidInt32{
sportIDQuery, err := strconv.Atoi(c.Query("sport_id")) Value: int32(leagueIDInt),
Valid: true,
}
}
sportIDQuery := c.Query("sport_id")
var sportID domain.ValidInt32
if sportIDQuery != "" {
sportIDint, err := strconv.Atoi(sportIDQuery)
if err != nil { if err != nil {
h.logger.Error("invalid sport id", "error", err) h.logger.Error("invalid sport id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid sport id", nil, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "invalid sport id", nil, nil)
} }
sportID = domain.ValidInt32{
Value: int32(sportIDint),
Valid: true,
}
}
firstStartTimeQuery := c.Query("first_start_time") firstStartTimeQuery := c.Query("first_start_time")
lastStartTimeQuery := c.Query("last_start_time")
leagueID := domain.ValidInt32{
Value: int32(leagueIDQuery),
Valid: leagueIDQuery != 0,
}
sportID := domain.ValidInt32{
Value: int32(sportIDQuery),
Valid: sportIDQuery != 0,
}
var firstStartTime domain.ValidTime var firstStartTime domain.ValidTime
if firstStartTimeQuery != "" { if firstStartTimeQuery != "" {
firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery) firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery)
@ -142,6 +131,8 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
Valid: true, Valid: true,
} }
} }
lastStartTimeQuery := c.Query("last_start_time")
var lastStartTime domain.ValidTime var lastStartTime domain.ValidTime
if lastStartTimeQuery != "" { if lastStartTimeQuery != "" {
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery) lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
@ -155,17 +146,21 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
} }
} }
limit := domain.ValidInt64{ countryCodeQuery := c.Query("cc")
Value: int64(pageSize), countryCode := domain.ValidString{
Valid: true, Value: countryCodeQuery,
Valid: countryCodeQuery != "",
} }
offset := domain.ValidInt64{
Value: int64(page - 1),
Valid: true,
}
events, total, err := h.eventSvc.GetPaginatedUpcomingEvents( events, total, err := h.eventSvc.GetPaginatedUpcomingEvents(
c.Context(), limit, offset, leagueID, sportID, firstStartTime, lastStartTime) c.Context(), domain.EventFilter{
SportID: sportID,
LeagueID: leagueID,
FirstStartTime: firstStartTime,
LastStartTime: lastStartTime,
Limit: limit,
Offset: offset,
CountryCode: countryCode,
})
// fmt.Printf("League ID: %v", leagueID) // fmt.Printf("League ID: %v", leagueID)
if err != nil { if err != nil {
@ -186,7 +181,7 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
// @Success 200 {object} domain.UpcomingEvent // @Success 200 {object} domain.UpcomingEvent
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /prematch/events/{id} [get] // @Router /events/{id} [get]
func (h *Handler) GetUpcomingEventByID(c *fiber.Ctx) error { func (h *Handler) GetUpcomingEventByID(c *fiber.Ctx) error {
id := c.Params("id") id := c.Params("id")
@ -214,8 +209,8 @@ func (h *Handler) GetUpcomingEventByID(c *fiber.Ctx) error {
// @Success 200 {array} domain.Odd // @Success 200 {array} domain.Odd
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /prematch/odds/upcoming/{upcoming_id} [get] // @Router /odds/upcoming/{upcoming_id} [get]
func (h *Handler) GetPrematchOddsByUpcomingID(c *fiber.Ctx) error { func (h *Handler) GetOddsByUpcomingID(c *fiber.Ctx) error {
upcomingID := c.Params("upcoming_id") upcomingID := c.Params("upcoming_id")
if upcomingID == "" { if upcomingID == "" {
@ -240,3 +235,29 @@ func (h *Handler) GetPrematchOddsByUpcomingID(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Prematch odds retrieved successfully", odds, nil) return response.WriteJSON(c, fiber.StatusOK, "Prematch odds retrieved successfully", odds, nil)
} }
type UpdateEventStatusReq struct {
}
// SetEventStatusToRemoved godoc
// @Summary Set the event status to removed
// @Description Set the event status to removed
// @Tags event
// @Accept json
// @Produce json
// @Param id path int true "Event ID"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /events/{id} [delete]
func (h *Handler) SetEventStatusToRemoved(c *fiber.Ctx) error {
eventID := c.Params("id")
err := h.eventSvc.UpdateEventStatus(c.Context(), eventID, domain.STATUS_REMOVED)
if err != nil {
h.logger.Error("Failed to update event status", "eventID", eventID, "error", err)
}
return response.WriteJSON(c, fiber.StatusOK, "Event updated successfully", nil, nil)
}

View File

@ -0,0 +1,47 @@
package handlers
import (
"encoding/json"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
)
type ResultRes struct {
ResultData json.RawMessage `json:"result_data"`
Outcomes []domain.BetOutcome `json:"outcomes"`
}
// This will take an event ID and return the success results for
// all the odds for that event.
// @Summary Get results for an event
// @Description Get results for an event
// @Tags result
// @Accept json
// @Produce json
// @Param id path string true "Event ID"
// @Success 200 {array} ResultRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /result/{id} [get]
func (h *Handler) GetResultsByEventID(c *fiber.Ctx) error {
eventID := c.Params("id")
if eventID == "" {
h.logger.Error("Event ID is required")
return fiber.NewError(fiber.StatusBadRequest, "Event ID is required")
}
results, outcomes, err := h.resultSvc.GetResultsForEvent(c.Context(), eventID)
if err != nil {
h.logger.Error("Failed to get results by Event ID", "eventID", eventID, "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve results")
}
resultRes := ResultRes{
ResultData: results,
Outcomes: outcomes,
}
return response.WriteJSON(c, fiber.StatusOK, "Results retrieved successfully", resultRes, nil)
}

View File

@ -65,6 +65,20 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
if len(req.Outcomes) > 30 { if len(req.Outcomes) > 30 {
return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil)
} }
if req.Amount > 100000 {
return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with an amount above 100,000 birr", nil, nil)
}
clientIP := c.IP()
count, err := h.ticketSvc.CountTicketByIP(c.Context(), clientIP)
if err != nil {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Error fetching user info", nil, nil)
}
if count > 50 {
return response.WriteJSON(c, fiber.StatusBadRequest, "Ticket Limit reached", nil, nil)
}
var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes)) var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes))
var totalOdds float32 = 1 var totalOdds float32 = 1
for _, outcome := range req.Outcomes { for _, outcome := range req.Outcomes {
@ -129,10 +143,16 @@ func (h *Handler) CreateTicket(c *fiber.Ctx) error {
OddHandicap: selectedOdd.Handicap, OddHandicap: selectedOdd.Handicap,
Expires: event.StartTime, Expires: event.StartTime,
}) })
}
totalWinnings := req.Amount * totalOdds
if totalWinnings > 1000000 {
return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with 1,000,000 winnings", nil, nil)
} }
ticket, err := h.ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{ ticket, err := h.ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{
Amount: domain.ToCurrency(req.Amount), Amount: domain.ToCurrency(req.Amount),
TotalOdds: totalOdds, TotalOdds: totalOdds,
IP: clientIP,
}) })
if err != nil { if err != nil {
h.logger.Error("CreateTicketReq failed", "error", err) h.logger.Error("CreateTicketReq failed", "error", err)

View File

@ -253,7 +253,7 @@ func (h *Handler) GetCustomerWallet(c *fiber.Ctx) error {
// return fiber.NewError(fiber.StatusBadRequest, "Invalid company_id") // return fiber.NewError(fiber.StatusBadRequest, "Invalid company_id")
// } // }
h.logger.Info("Fetching customer wallet", "userID", userID) // h.logger.Info("Fetching customer wallet", "userID", userID)
wallet, err := h.walletSvc.GetWalletsByUser(c.Context(), userID) wallet, err := h.walletSvc.GetWalletsByUser(c.Context(), userID)
if err != nil { if err != nil {
@ -265,3 +265,64 @@ func (h *Handler) GetCustomerWallet(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil)
} }
// GetWalletForCashier godoc
// @Summary Get wallet for cashier
// @Description Get wallet for cashier
// @Tags cashier
// @Accept json
// @Produce json
// @Param id path int true "User ID"
// @Success 200 {object} UserProfileRes
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /cashierWallet [get]
func (h *Handler) GetWalletForCashier(c *fiber.Ctx) error {
cashierID, ok := c.Locals("user_id").(int64)
if !ok || cashierID == 0 {
h.logger.Error("Invalid cashier ID in context")
return response.WriteJSON(c, fiber.StatusUnauthorized, "Invalid cashier identification", nil, nil)
}
role, ok := c.Locals("role").(domain.Role)
if !ok || role != domain.RoleCashier {
h.logger.Error("Unauthorized access", "cashierID", cashierID, "role", role)
return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil)
}
branchID, ok := c.Locals("branch_id").(domain.ValidInt64)
if !ok || !branchID.Valid {
h.logger.Error("Invalid branch ID in context", "cashierID", cashierID)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid branch ID", nil, nil)
}
branch, err := h.branchSvc.GetBranchByID(c.Context(), branchID.Value)
if err != nil {
h.logger.Error("Failed to get branch by ID", "branchID", branchID.Value, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve branch", err, nil)
}
wallet, err := h.walletSvc.GetWalletByID(c.Context(), branch.WalletID)
if err != nil {
h.logger.Error("Failed to get wallet for cashier", "cashierID", cashierID, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve wallet", err, nil)
}
res := WalletRes{
ID: wallet.ID,
Balance: wallet.Balance.Float32(),
IsWithdraw: wallet.IsWithdraw,
IsBettable: wallet.IsBettable,
IsTransferable: wallet.IsTransferable,
IsActive: wallet.IsActive,
UserID: wallet.UserID,
UpdatedAt: wallet.UpdatedAt,
CreatedAt: wallet.CreatedAt,
}
return response.WriteJSON(c, fiber.StatusOK, "Wallet retrieved successfully", res, nil)
}

View File

@ -14,7 +14,7 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
authHeader := c.Get("Authorization") authHeader := c.Get("Authorization")
if authHeader == "" { if authHeader == "" {
fmt.Println("Auth Header Missing") // fmt.Println("Auth Header Missing")
return fiber.NewError(fiber.StatusUnauthorized, "Authorization header missing") return fiber.NewError(fiber.StatusUnauthorized, "Authorization header missing")
} }
@ -39,7 +39,6 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
if refreshToken == "" { if refreshToken == "" {
// refreshToken = c.Cookies("refresh_token", "") // refreshToken = c.Cookies("refresh_token", "")
// return fiber.NewError(fiber.StatusUnauthorized, "Refresh token missing") // return fiber.NewError(fiber.StatusUnauthorized, "Refresh token missing")
} }

View File

@ -42,6 +42,7 @@ func (a *App) initAppRoutes() {
a.prematchSvc, a.prematchSvc,
a.eventSvc, a.eventSvc,
a.leagueSvc, a.leagueSvc,
*a.resultSvc,
a.cfg, a.cfg,
) )
@ -121,17 +122,19 @@ func (a *App) initAppRoutes() {
a.fiber.Put("/managers/:id", a.authMiddleware, h.UpdateManagers) a.fiber.Put("/managers/:id", a.authMiddleware, h.UpdateManagers)
a.fiber.Get("/manager/:id/branch", a.authMiddleware, h.GetBranchByManagerID) a.fiber.Get("/manager/:id/branch", a.authMiddleware, h.GetBranchByManagerID)
a.fiber.Get("/events/odds/:event_id", h.GetPrematchOdds) a.fiber.Get("/odds", h.GetALLPrematchOdds)
a.fiber.Get("/events/odds", h.GetALLPrematchOdds) a.fiber.Get("/odds/upcoming/:upcoming_id", h.GetOddsByUpcomingID)
a.fiber.Get("/events/odds/upcoming/:upcoming_id/market/:market_id", h.GetRawOddsByMarketID) a.fiber.Get("/odds/upcoming/:upcoming_id/market/:market_id", h.GetRawOddsByMarketID)
a.fiber.Get("/events/:id", h.GetUpcomingEventByID)
a.fiber.Get("/events", h.GetAllUpcomingEvents) a.fiber.Get("/events", h.GetAllUpcomingEvents)
a.fiber.Get("/events/odds/upcoming/:upcoming_id", h.GetPrematchOddsByUpcomingID) a.fiber.Get("/events/:id", h.GetUpcomingEventByID)
a.fiber.Delete("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.SetEventStatusToRemoved)
// Leagues // Leagues
a.fiber.Get("/leagues", h.GetAllLeagues) a.fiber.Get("/leagues", h.GetAllLeagues)
a.fiber.Get("/leagues/:id/set-active", h.SetLeagueActive) a.fiber.Put("/leagues/:id/set-active", h.SetLeagueActive)
a.fiber.Get("/result/:id", h.GetResultsByEventID)
// Swagger // Swagger
a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler()) a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler())
@ -147,6 +150,7 @@ func (a *App) initAppRoutes() {
// /branch/search // /branch/search
// branch/wallet // branch/wallet
a.fiber.Get("/branch/:id/cashiers", a.authMiddleware, h.GetBranchCashiers) a.fiber.Get("/branch/:id/cashiers", a.authMiddleware, h.GetBranchCashiers)
a.fiber.Get("/branchCashier", a.authMiddleware, h.GetBranchForCashier)
// Branch Operation // Branch Operation
a.fiber.Get("/supportedOperation", a.authMiddleware, h.GetAllSupportedOperations) a.fiber.Get("/supportedOperation", a.authMiddleware, h.GetAllSupportedOperations)
@ -185,6 +189,7 @@ func (a *App) initAppRoutes() {
a.fiber.Get("/wallet/:id", h.GetWalletByID) a.fiber.Get("/wallet/:id", h.GetWalletByID)
a.fiber.Put("/wallet/:id", h.UpdateWalletActive) a.fiber.Put("/wallet/:id", h.UpdateWalletActive)
a.fiber.Get("/branchWallet", a.authMiddleware, h.GetAllBranchWallets) a.fiber.Get("/branchWallet", a.authMiddleware, h.GetAllBranchWallets)
a.fiber.Get("/cashierWallet", a.authMiddleware, h.GetWalletForCashier)
// Transfer // Transfer
// /transfer/wallet - transfer from one wallet to another wallet // /transfer/wallet - transfer from one wallet to another wallet

View File

@ -1,7 +1,6 @@
package ws package ws
import ( import (
"log"
"net/http" "net/http"
"sync" "sync"
@ -37,7 +36,7 @@ func (h *NotificationHub) Run() {
h.mu.Lock() h.mu.Lock()
h.Clients[client] = true h.Clients[client] = true
h.mu.Unlock() h.mu.Unlock()
log.Printf("Client registered: %d", client.RecipientID) // log.Printf("Client registered: %d", client.RecipientID)
case client := <-h.Unregister: case client := <-h.Unregister:
h.mu.Lock() h.mu.Lock()
if _, ok := h.Clients[client]; ok { if _, ok := h.Clients[client]; ok {
@ -45,7 +44,7 @@ func (h *NotificationHub) Run() {
client.Conn.Close() client.Conn.Close()
} }
h.mu.Unlock() h.mu.Unlock()
log.Printf("Client unregistered: %d", client.RecipientID) // log.Printf("Client unregistered: %d", client.RecipientID)
case message := <-h.Broadcast: case message := <-h.Broadcast:
h.mu.Lock() h.mu.Lock()
for client := range h.Clients { for client := range h.Clients {

View File

@ -50,7 +50,7 @@ swagger:
.PHONY: db-up .PHONY: db-up
db-up: db-up:
@docker compose up -d postgres migrate @docker compose up -d postgres migrate mongo
.PHONY: db-down .PHONY: db-down
db-down: db-down: