fix: result service evaluation issues
This commit is contained in:
parent
b7b17fa8d2
commit
95fb33c9d4
|
|
@ -86,6 +86,7 @@ func main() {
|
||||||
virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, cfg, logger)
|
virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, cfg, logger)
|
||||||
|
|
||||||
httpserver.StartDataFetchingCrons(eventSvc, oddsSvc, resultSvc)
|
httpserver.StartDataFetchingCrons(eventSvc, oddsSvc, resultSvc)
|
||||||
|
httpserver.StartTicketCrons(*ticketSvc)
|
||||||
|
|
||||||
app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{
|
app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{
|
||||||
JwtAccessKey: cfg.JwtKey,
|
JwtAccessKey: cfg.JwtKey,
|
||||||
|
|
|
||||||
|
|
@ -340,15 +340,43 @@ INSERT INTO users (
|
||||||
suspended_at,
|
suspended_at,
|
||||||
suspended
|
suspended
|
||||||
)
|
)
|
||||||
|
VALUES (
|
||||||
|
'Test',
|
||||||
|
'Admin',
|
||||||
|
'test.admin@gmail.com',
|
||||||
|
'0911111111',
|
||||||
|
crypt('password123', gen_salt('bf'))::bytea,
|
||||||
|
'admin',
|
||||||
|
TRUE,
|
||||||
|
TRUE,
|
||||||
|
CURRENT_TIMESTAMP,
|
||||||
|
CURRENT_TIMESTAMP,
|
||||||
|
NULL,
|
||||||
|
FALSE
|
||||||
|
);
|
||||||
|
INSERT INTO users (
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
email,
|
||||||
|
phone_number,
|
||||||
|
password,
|
||||||
|
role,
|
||||||
|
email_verified,
|
||||||
|
phone_verified,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
suspended_at,
|
||||||
|
suspended
|
||||||
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
'Samuel',
|
'Samuel',
|
||||||
'Tariku',
|
'Tariku',
|
||||||
'cybersamt@gmail.com',
|
'cybersamt@gmail.com',
|
||||||
NULL,
|
'0911111111',
|
||||||
crypt('password@123', gen_salt('bf'))::bytea,
|
crypt('password@123', gen_salt('bf'))::bytea,
|
||||||
'super_admin',
|
'super_admin',
|
||||||
TRUE,
|
TRUE,
|
||||||
FALSE,
|
TRUE,
|
||||||
CURRENT_TIMESTAMP,
|
CURRENT_TIMESTAMP,
|
||||||
CURRENT_TIMESTAMP,
|
CURRENT_TIMESTAMP,
|
||||||
NULL,
|
NULL,
|
||||||
|
|
@ -372,11 +400,11 @@ VALUES (
|
||||||
'Kirubel',
|
'Kirubel',
|
||||||
'Kibru',
|
'Kibru',
|
||||||
'kirubeljkl679 @gmail.com',
|
'kirubeljkl679 @gmail.com',
|
||||||
NULL,
|
'0911111111',
|
||||||
crypt('password@123', gen_salt('bf'))::bytea,
|
crypt('password@123', gen_salt('bf'))::bytea,
|
||||||
'super_admin',
|
'super_admin',
|
||||||
TRUE,
|
TRUE,
|
||||||
FALSE,
|
TRUE,
|
||||||
CURRENT_TIMESTAMP,
|
CURRENT_TIMESTAMP,
|
||||||
CURRENT_TIMESTAMP,
|
CURRENT_TIMESTAMP,
|
||||||
NULL,
|
NULL,
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,10 @@ WHERE branch_id = $1;
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM bet_outcomes
|
FROM bet_outcomes
|
||||||
WHERE event_id = $1;
|
WHERE event_id = $1;
|
||||||
|
-- name: GetBetOutcomeByBetID :many
|
||||||
|
SELECT *
|
||||||
|
FROM bet_outcomes
|
||||||
|
WHERE bet_id = $1;
|
||||||
-- name: UpdateCashOut :exec
|
-- name: UpdateCashOut :exec
|
||||||
UPDATE bets
|
UPDATE bets
|
||||||
SET cashed_out = $2,
|
SET cashed_out = $2,
|
||||||
|
|
@ -74,9 +78,9 @@ WHERE id = $2
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
-- name: UpdateStatus :exec
|
-- name: UpdateStatus :exec
|
||||||
UPDATE bets
|
UPDATE bets
|
||||||
SET status = $2,
|
SET status = $1,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $1;
|
WHERE id = $2;
|
||||||
-- name: DeleteBet :exec
|
-- name: DeleteBet :exec
|
||||||
DELETE FROM bets
|
DELETE FROM bets
|
||||||
WHERE id = $1;
|
WHERE id = $1;
|
||||||
|
|
|
||||||
|
|
@ -196,15 +196,23 @@ FROM events
|
||||||
WHERE is_live = false
|
WHERE is_live = false
|
||||||
AND status = 'upcoming'
|
AND status = 'upcoming'
|
||||||
AND (
|
AND (
|
||||||
league_id = $3
|
league_id = sqlc.narg('league_id')
|
||||||
OR $3 IS NULL
|
OR sqlc.narg('league_id') IS NULL
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
sport_id = $4
|
sport_id = sqlc.narg('sport_id')
|
||||||
OR $4 IS NULL
|
OR sqlc.narg('sport_id') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
start_time < sqlc.narg('last_start_time')
|
||||||
|
OR sqlc.narg('last_start_time') IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
start_time > sqlc.narg('first_start_time')
|
||||||
|
OR sqlc.narg('first_start_time') IS NULL
|
||||||
)
|
)
|
||||||
ORDER BY start_time ASC
|
ORDER BY start_time ASC
|
||||||
LIMIT $1 OFFSET $2;
|
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
||||||
-- name: GetUpcomingByID :one
|
-- name: GetUpcomingByID :one
|
||||||
SELECT id,
|
SELECT id,
|
||||||
sport_id,
|
sport_id,
|
||||||
|
|
|
||||||
|
|
@ -94,23 +94,17 @@ WHERE market_id = $1
|
||||||
AND fi = $2
|
AND fi = $2
|
||||||
AND is_active = true
|
AND is_active = true
|
||||||
AND source = 'b365api';
|
AND source = 'b365api';
|
||||||
|
|
||||||
-- name: GetPrematchOddsByUpcomingID :many
|
-- name: GetPrematchOddsByUpcomingID :many
|
||||||
SELECT o.event_id,
|
SELECT o.*
|
||||||
o.fi,
|
FROM odds o
|
||||||
o.market_type,
|
JOIN events e ON o.fi = e.id
|
||||||
o.market_name,
|
WHERE e.id = $1
|
||||||
o.market_category,
|
AND e.is_live = false
|
||||||
o.market_id,
|
AND e.status = 'upcoming'
|
||||||
o.name,
|
AND o.is_active = true
|
||||||
o.handicap,
|
AND o.source = 'b365api';
|
||||||
o.odds_value,
|
-- name: GetPaginatedPrematchOddsByUpcomingID :many
|
||||||
o.section,
|
SELECT o.*
|
||||||
o.category,
|
|
||||||
o.raw_odds,
|
|
||||||
o.fetched_at,
|
|
||||||
o.source,
|
|
||||||
o.is_active
|
|
||||||
FROM odds o
|
FROM odds o
|
||||||
JOIN events e ON o.fi = e.id
|
JOIN events e ON o.fi = e.id
|
||||||
WHERE e.id = $1
|
WHERE e.id = $1
|
||||||
|
|
@ -118,4 +112,4 @@ WHERE e.id = $1
|
||||||
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 = 'b365api'
|
||||||
LIMIT $2 OFFSET $3;
|
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
||||||
289
docs/docs.go
289
docs/docs.go
|
|
@ -304,7 +304,7 @@ const docTemplate = `{
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/handlers.BetRes"
|
"$ref": "#/definitions/domain.BetRes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -341,7 +341,7 @@ const docTemplate = `{
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.CreateBetReq"
|
"$ref": "#/definitions/domain.CreateBetReq"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -349,7 +349,7 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.BetRes"
|
"$ref": "#/definitions/domain.BetRes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -393,7 +393,7 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.BetRes"
|
"$ref": "#/definitions/domain.BetRes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -437,7 +437,7 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.BetRes"
|
"$ref": "#/definitions/domain.BetRes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -786,7 +786,7 @@ const docTemplate = `{
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/handlers.BetRes"
|
"$ref": "#/definitions/domain.BetRes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1915,6 +1915,52 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/random/bet": {
|
||||||
|
"post": {
|
||||||
|
"description": "Generate a random bet",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"bet"
|
||||||
|
],
|
||||||
|
"summary": "Generate a random bet",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Create Random bet",
|
||||||
|
"name": "createBet",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.RandomBetReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.BetRes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/referral/settings": {
|
"/referral/settings": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -3386,6 +3432,117 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.BetRes": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"amount": {
|
||||||
|
"type": "number",
|
||||||
|
"example": 100
|
||||||
|
},
|
||||||
|
"branch_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 2
|
||||||
|
},
|
||||||
|
"cashed_id": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "21234"
|
||||||
|
},
|
||||||
|
"cashed_out": {
|
||||||
|
"type": "boolean",
|
||||||
|
"example": false
|
||||||
|
},
|
||||||
|
"full_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "John"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"is_shop_bet": {
|
||||||
|
"type": "boolean",
|
||||||
|
"example": false
|
||||||
|
},
|
||||||
|
"outcomes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/domain.BetOutcome"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "1234567890"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/domain.OutcomeStatus"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"total_odds": {
|
||||||
|
"type": "number",
|
||||||
|
"example": 4.22
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.CreateBetOutcomeReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"event_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"market_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"odd_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.CreateBetReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"amount": {
|
||||||
|
"type": "number",
|
||||||
|
"example": 100
|
||||||
|
},
|
||||||
|
"branch_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"full_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "John"
|
||||||
|
},
|
||||||
|
"outcomes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/domain.CreateBetOutcomeReq"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "1234567890"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/domain.OutcomeStatus"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.Odd": {
|
"domain.Odd": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -3501,6 +3658,15 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.RandomBetReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"branch_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.RawOddsByMarketID": {
|
"domain.RawOddsByMarketID": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -3757,65 +3923,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"handlers.BetRes": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"type": "number",
|
|
||||||
"example": 100
|
|
||||||
},
|
|
||||||
"branch_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 2
|
|
||||||
},
|
|
||||||
"cashed_id": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "21234"
|
|
||||||
},
|
|
||||||
"cashed_out": {
|
|
||||||
"type": "boolean",
|
|
||||||
"example": false
|
|
||||||
},
|
|
||||||
"full_name": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "John"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 1
|
|
||||||
},
|
|
||||||
"is_shop_bet": {
|
|
||||||
"type": "boolean",
|
|
||||||
"example": false
|
|
||||||
},
|
|
||||||
"outcomes": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/domain.BetOutcome"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone_number": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "1234567890"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/domain.OutcomeStatus"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"example": 1
|
|
||||||
},
|
|
||||||
"total_odds": {
|
|
||||||
"type": "number",
|
|
||||||
"example": 4.22
|
|
||||||
},
|
|
||||||
"user_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"handlers.BranchDetailRes": {
|
"handlers.BranchDetailRes": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -3977,58 +4084,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"handlers.CreateBetOutcomeReq": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"event_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 1
|
|
||||||
},
|
|
||||||
"market_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 1
|
|
||||||
},
|
|
||||||
"odd_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"handlers.CreateBetReq": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"type": "number",
|
|
||||||
"example": 100
|
|
||||||
},
|
|
||||||
"branch_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 1
|
|
||||||
},
|
|
||||||
"full_name": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "John"
|
|
||||||
},
|
|
||||||
"outcomes": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/handlers.CreateBetOutcomeReq"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone_number": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "1234567890"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/domain.OutcomeStatus"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"example": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"handlers.CreateBranchOperationReq": {
|
"handlers.CreateBranchOperationReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -296,7 +296,7 @@
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/handlers.BetRes"
|
"$ref": "#/definitions/domain.BetRes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -333,7 +333,7 @@
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.CreateBetReq"
|
"$ref": "#/definitions/domain.CreateBetReq"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -341,7 +341,7 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.BetRes"
|
"$ref": "#/definitions/domain.BetRes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -385,7 +385,7 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.BetRes"
|
"$ref": "#/definitions/domain.BetRes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -429,7 +429,7 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.BetRes"
|
"$ref": "#/definitions/domain.BetRes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -778,7 +778,7 @@
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/handlers.BetRes"
|
"$ref": "#/definitions/domain.BetRes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1907,6 +1907,52 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/random/bet": {
|
||||||
|
"post": {
|
||||||
|
"description": "Generate a random bet",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"bet"
|
||||||
|
],
|
||||||
|
"summary": "Generate a random bet",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Create Random bet",
|
||||||
|
"name": "createBet",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.RandomBetReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.BetRes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/referral/settings": {
|
"/referral/settings": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -3378,6 +3424,117 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.BetRes": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"amount": {
|
||||||
|
"type": "number",
|
||||||
|
"example": 100
|
||||||
|
},
|
||||||
|
"branch_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 2
|
||||||
|
},
|
||||||
|
"cashed_id": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "21234"
|
||||||
|
},
|
||||||
|
"cashed_out": {
|
||||||
|
"type": "boolean",
|
||||||
|
"example": false
|
||||||
|
},
|
||||||
|
"full_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "John"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"is_shop_bet": {
|
||||||
|
"type": "boolean",
|
||||||
|
"example": false
|
||||||
|
},
|
||||||
|
"outcomes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/domain.BetOutcome"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "1234567890"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/domain.OutcomeStatus"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"total_odds": {
|
||||||
|
"type": "number",
|
||||||
|
"example": 4.22
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.CreateBetOutcomeReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"event_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"market_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"odd_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.CreateBetReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"amount": {
|
||||||
|
"type": "number",
|
||||||
|
"example": 100
|
||||||
|
},
|
||||||
|
"branch_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"full_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "John"
|
||||||
|
},
|
||||||
|
"outcomes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/domain.CreateBetOutcomeReq"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "1234567890"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/domain.OutcomeStatus"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.Odd": {
|
"domain.Odd": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -3493,6 +3650,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.RandomBetReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"branch_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.RawOddsByMarketID": {
|
"domain.RawOddsByMarketID": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -3749,65 +3915,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"handlers.BetRes": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"type": "number",
|
|
||||||
"example": 100
|
|
||||||
},
|
|
||||||
"branch_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 2
|
|
||||||
},
|
|
||||||
"cashed_id": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "21234"
|
|
||||||
},
|
|
||||||
"cashed_out": {
|
|
||||||
"type": "boolean",
|
|
||||||
"example": false
|
|
||||||
},
|
|
||||||
"full_name": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "John"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 1
|
|
||||||
},
|
|
||||||
"is_shop_bet": {
|
|
||||||
"type": "boolean",
|
|
||||||
"example": false
|
|
||||||
},
|
|
||||||
"outcomes": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/domain.BetOutcome"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone_number": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "1234567890"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/domain.OutcomeStatus"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"example": 1
|
|
||||||
},
|
|
||||||
"total_odds": {
|
|
||||||
"type": "number",
|
|
||||||
"example": 4.22
|
|
||||||
},
|
|
||||||
"user_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"handlers.BranchDetailRes": {
|
"handlers.BranchDetailRes": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -3969,58 +4076,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"handlers.CreateBetOutcomeReq": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"event_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 1
|
|
||||||
},
|
|
||||||
"market_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 1
|
|
||||||
},
|
|
||||||
"odd_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"handlers.CreateBetReq": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"type": "number",
|
|
||||||
"example": 100
|
|
||||||
},
|
|
||||||
"branch_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"example": 1
|
|
||||||
},
|
|
||||||
"full_name": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "John"
|
|
||||||
},
|
|
||||||
"outcomes": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/handlers.CreateBetOutcomeReq"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"phone_number": {
|
|
||||||
"type": "string",
|
|
||||||
"example": "1234567890"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/domain.OutcomeStatus"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"example": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"handlers.CreateBranchOperationReq": {
|
"handlers.CreateBranchOperationReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,82 @@ definitions:
|
||||||
- $ref: '#/definitions/domain.OutcomeStatus'
|
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||||
example: 1
|
example: 1
|
||||||
type: object
|
type: object
|
||||||
|
domain.BetRes:
|
||||||
|
properties:
|
||||||
|
amount:
|
||||||
|
example: 100
|
||||||
|
type: number
|
||||||
|
branch_id:
|
||||||
|
example: 2
|
||||||
|
type: integer
|
||||||
|
cashed_id:
|
||||||
|
example: "21234"
|
||||||
|
type: string
|
||||||
|
cashed_out:
|
||||||
|
example: false
|
||||||
|
type: boolean
|
||||||
|
full_name:
|
||||||
|
example: John
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
example: 1
|
||||||
|
type: integer
|
||||||
|
is_shop_bet:
|
||||||
|
example: false
|
||||||
|
type: boolean
|
||||||
|
outcomes:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/domain.BetOutcome'
|
||||||
|
type: array
|
||||||
|
phone_number:
|
||||||
|
example: "1234567890"
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||||
|
example: 1
|
||||||
|
total_odds:
|
||||||
|
example: 4.22
|
||||||
|
type: number
|
||||||
|
user_id:
|
||||||
|
example: 2
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
domain.CreateBetOutcomeReq:
|
||||||
|
properties:
|
||||||
|
event_id:
|
||||||
|
example: 1
|
||||||
|
type: integer
|
||||||
|
market_id:
|
||||||
|
example: 1
|
||||||
|
type: integer
|
||||||
|
odd_id:
|
||||||
|
example: 1
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
domain.CreateBetReq:
|
||||||
|
properties:
|
||||||
|
amount:
|
||||||
|
example: 100
|
||||||
|
type: number
|
||||||
|
branch_id:
|
||||||
|
example: 1
|
||||||
|
type: integer
|
||||||
|
full_name:
|
||||||
|
example: John
|
||||||
|
type: string
|
||||||
|
outcomes:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/domain.CreateBetOutcomeReq'
|
||||||
|
type: array
|
||||||
|
phone_number:
|
||||||
|
example: "1234567890"
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||||
|
example: 1
|
||||||
|
type: object
|
||||||
domain.Odd:
|
domain.Odd:
|
||||||
properties:
|
properties:
|
||||||
category:
|
category:
|
||||||
|
|
@ -130,6 +206,12 @@ definitions:
|
||||||
description: BET, WIN, REFUND, JACKPOT_WIN
|
description: BET, WIN, REFUND, JACKPOT_WIN
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
domain.RandomBetReq:
|
||||||
|
properties:
|
||||||
|
branch_id:
|
||||||
|
example: 1
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
domain.RawOddsByMarketID:
|
domain.RawOddsByMarketID:
|
||||||
properties:
|
properties:
|
||||||
fetched_at:
|
fetched_at:
|
||||||
|
|
@ -309,47 +391,6 @@ definitions:
|
||||||
updated_at:
|
updated_at:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
handlers.BetRes:
|
|
||||||
properties:
|
|
||||||
amount:
|
|
||||||
example: 100
|
|
||||||
type: number
|
|
||||||
branch_id:
|
|
||||||
example: 2
|
|
||||||
type: integer
|
|
||||||
cashed_id:
|
|
||||||
example: "21234"
|
|
||||||
type: string
|
|
||||||
cashed_out:
|
|
||||||
example: false
|
|
||||||
type: boolean
|
|
||||||
full_name:
|
|
||||||
example: John
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
example: 1
|
|
||||||
type: integer
|
|
||||||
is_shop_bet:
|
|
||||||
example: false
|
|
||||||
type: boolean
|
|
||||||
outcomes:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/domain.BetOutcome'
|
|
||||||
type: array
|
|
||||||
phone_number:
|
|
||||||
example: "1234567890"
|
|
||||||
type: string
|
|
||||||
status:
|
|
||||||
allOf:
|
|
||||||
- $ref: '#/definitions/domain.OutcomeStatus'
|
|
||||||
example: 1
|
|
||||||
total_odds:
|
|
||||||
example: 4.22
|
|
||||||
type: number
|
|
||||||
user_id:
|
|
||||||
example: 2
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
handlers.BranchDetailRes:
|
handlers.BranchDetailRes:
|
||||||
properties:
|
properties:
|
||||||
branch_manager_id:
|
branch_manager_id:
|
||||||
|
|
@ -465,41 +506,6 @@ definitions:
|
||||||
example: "1234567890"
|
example: "1234567890"
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
handlers.CreateBetOutcomeReq:
|
|
||||||
properties:
|
|
||||||
event_id:
|
|
||||||
example: 1
|
|
||||||
type: integer
|
|
||||||
market_id:
|
|
||||||
example: 1
|
|
||||||
type: integer
|
|
||||||
odd_id:
|
|
||||||
example: 1
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
handlers.CreateBetReq:
|
|
||||||
properties:
|
|
||||||
amount:
|
|
||||||
example: 100
|
|
||||||
type: number
|
|
||||||
branch_id:
|
|
||||||
example: 1
|
|
||||||
type: integer
|
|
||||||
full_name:
|
|
||||||
example: John
|
|
||||||
type: string
|
|
||||||
outcomes:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/handlers.CreateBetOutcomeReq'
|
|
||||||
type: array
|
|
||||||
phone_number:
|
|
||||||
example: "1234567890"
|
|
||||||
type: string
|
|
||||||
status:
|
|
||||||
allOf:
|
|
||||||
- $ref: '#/definitions/domain.OutcomeStatus'
|
|
||||||
example: 1
|
|
||||||
type: object
|
|
||||||
handlers.CreateBranchOperationReq:
|
handlers.CreateBranchOperationReq:
|
||||||
properties:
|
properties:
|
||||||
branch_id:
|
branch_id:
|
||||||
|
|
@ -1320,7 +1326,7 @@ paths:
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/handlers.BetRes'
|
$ref: '#/definitions/domain.BetRes'
|
||||||
type: array
|
type: array
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
|
|
@ -1343,14 +1349,14 @@ paths:
|
||||||
name: createBet
|
name: createBet
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/handlers.CreateBetReq'
|
$ref: '#/definitions/domain.CreateBetReq'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/handlers.BetRes'
|
$ref: '#/definitions/domain.BetRes'
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
|
|
@ -1407,7 +1413,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/handlers.BetRes'
|
$ref: '#/definitions/domain.BetRes'
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
|
|
@ -1470,7 +1476,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/handlers.BetRes'
|
$ref: '#/definitions/domain.BetRes'
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
|
|
@ -1639,7 +1645,7 @@ paths:
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/handlers.BetRes'
|
$ref: '#/definitions/domain.BetRes'
|
||||||
type: array
|
type: array
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
|
|
@ -2385,6 +2391,36 @@ paths:
|
||||||
summary: Retrieve raw odds by Market ID
|
summary: Retrieve raw odds by Market ID
|
||||||
tags:
|
tags:
|
||||||
- prematch
|
- prematch
|
||||||
|
/random/bet:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Generate a random bet
|
||||||
|
parameters:
|
||||||
|
- description: Create Random bet
|
||||||
|
in: body
|
||||||
|
name: createBet
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.RandomBetReq'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.BetRes'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
summary: Generate a random bet
|
||||||
|
tags:
|
||||||
|
- bet
|
||||||
/referral/settings:
|
/referral/settings:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,48 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GetBetOutcomeByBetID = `-- name: GetBetOutcomeByBetID :many
|
||||||
|
SELECT id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires
|
||||||
|
FROM bet_outcomes
|
||||||
|
WHERE bet_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]BetOutcome, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetBetOutcomeByBetID, betID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []BetOutcome
|
||||||
|
for rows.Next() {
|
||||||
|
var i BetOutcome
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.BetID,
|
||||||
|
&i.SportID,
|
||||||
|
&i.EventID,
|
||||||
|
&i.OddID,
|
||||||
|
&i.HomeTeamName,
|
||||||
|
&i.AwayTeamName,
|
||||||
|
&i.MarketID,
|
||||||
|
&i.MarketName,
|
||||||
|
&i.Odd,
|
||||||
|
&i.OddName,
|
||||||
|
&i.OddHeader,
|
||||||
|
&i.OddHandicap,
|
||||||
|
&i.Status,
|
||||||
|
&i.Expires,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const GetBetOutcomeByEventID = `-- name: GetBetOutcomeByEventID :many
|
const GetBetOutcomeByEventID = `-- name: GetBetOutcomeByEventID :many
|
||||||
SELECT id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires
|
SELECT id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires
|
||||||
FROM bet_outcomes
|
FROM bet_outcomes
|
||||||
|
|
@ -339,17 +381,17 @@ func (q *Queries) UpdateCashOut(ctx context.Context, arg UpdateCashOutParams) er
|
||||||
|
|
||||||
const UpdateStatus = `-- name: UpdateStatus :exec
|
const UpdateStatus = `-- name: UpdateStatus :exec
|
||||||
UPDATE bets
|
UPDATE bets
|
||||||
SET status = $2,
|
SET status = $1,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $1
|
WHERE id = $2
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateStatusParams struct {
|
type UpdateStatusParams struct {
|
||||||
ID int64 `json:"id"`
|
|
||||||
Status int32 `json:"status"`
|
Status int32 `json:"status"`
|
||||||
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateStatus(ctx context.Context, arg UpdateStatusParams) error {
|
func (q *Queries) UpdateStatus(ctx context.Context, arg UpdateStatusParams) error {
|
||||||
_, err := q.db.Exec(ctx, UpdateStatus, arg.ID, arg.Status)
|
_, err := q.db.Exec(ctx, UpdateStatus, arg.Status, arg.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -201,22 +201,32 @@ FROM events
|
||||||
WHERE is_live = false
|
WHERE is_live = false
|
||||||
AND status = 'upcoming'
|
AND status = 'upcoming'
|
||||||
AND (
|
AND (
|
||||||
league_id = $3
|
league_id = $1
|
||||||
|
OR $1 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
sport_id = $2
|
||||||
|
OR $2 IS NULL
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
start_time < $3
|
||||||
OR $3 IS NULL
|
OR $3 IS NULL
|
||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
sport_id = $4
|
start_time > $4
|
||||||
OR $4 IS NULL
|
OR $4 IS NULL
|
||||||
)
|
)
|
||||||
ORDER BY start_time ASC
|
ORDER BY start_time ASC
|
||||||
LIMIT $1 OFFSET $2
|
LIMIT $6 OFFSET $5
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetPaginatedUpcomingEventsParams struct {
|
type GetPaginatedUpcomingEventsParams struct {
|
||||||
Limit int32 `json:"limit"`
|
LeagueID pgtype.Text `json:"league_id"`
|
||||||
Offset int32 `json:"offset"`
|
SportID pgtype.Text `json:"sport_id"`
|
||||||
LeagueID pgtype.Text `json:"league_id"`
|
LastStartTime pgtype.Timestamp `json:"last_start_time"`
|
||||||
SportID pgtype.Text `json:"sport_id"`
|
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
|
||||||
|
Offset pgtype.Int4 `json:"offset"`
|
||||||
|
Limit pgtype.Int4 `json:"limit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetPaginatedUpcomingEventsRow struct {
|
type GetPaginatedUpcomingEventsRow struct {
|
||||||
|
|
@ -240,10 +250,12 @@ type GetPaginatedUpcomingEventsRow struct {
|
||||||
|
|
||||||
func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginatedUpcomingEventsParams) ([]GetPaginatedUpcomingEventsRow, error) {
|
func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginatedUpcomingEventsParams) ([]GetPaginatedUpcomingEventsRow, error) {
|
||||||
rows, err := q.db.Query(ctx, GetPaginatedUpcomingEvents,
|
rows, err := q.db.Query(ctx, GetPaginatedUpcomingEvents,
|
||||||
arg.Limit,
|
|
||||||
arg.Offset,
|
|
||||||
arg.LeagueID,
|
arg.LeagueID,
|
||||||
arg.SportID,
|
arg.SportID,
|
||||||
|
arg.LastStartTime,
|
||||||
|
arg.FirstStartTime,
|
||||||
|
arg.Offset,
|
||||||
|
arg.Limit,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,61 @@ func (q *Queries) GetALLPrematchOdds(ctx context.Context) ([]GetALLPrematchOddsR
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GetPaginatedPrematchOddsByUpcomingID = `-- name: GetPaginatedPrematchOddsByUpcomingID :many
|
||||||
|
SELECT o.id, o.event_id, o.fi, o.market_type, o.market_name, o.market_category, o.market_id, o.name, o.handicap, o.odds_value, o.section, o.category, o.raw_odds, o.fetched_at, o.source, o.is_active
|
||||||
|
FROM odds o
|
||||||
|
JOIN events e ON o.fi = e.id
|
||||||
|
WHERE e.id = $1
|
||||||
|
AND e.is_live = false
|
||||||
|
AND e.status = 'upcoming'
|
||||||
|
AND o.is_active = true
|
||||||
|
AND o.source = 'b365api'
|
||||||
|
LIMIT $3 OFFSET $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetPaginatedPrematchOddsByUpcomingIDParams struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Offset pgtype.Int4 `json:"offset"`
|
||||||
|
Limit pgtype.Int4 `json:"limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, arg GetPaginatedPrematchOddsByUpcomingIDParams) ([]Odd, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetPaginatedPrematchOddsByUpcomingID, arg.ID, arg.Offset, arg.Limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Odd
|
||||||
|
for rows.Next() {
|
||||||
|
var i Odd
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.EventID,
|
||||||
|
&i.Fi,
|
||||||
|
&i.MarketType,
|
||||||
|
&i.MarketName,
|
||||||
|
&i.MarketCategory,
|
||||||
|
&i.MarketID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Handicap,
|
||||||
|
&i.OddsValue,
|
||||||
|
&i.Section,
|
||||||
|
&i.Category,
|
||||||
|
&i.RawOdds,
|
||||||
|
&i.FetchedAt,
|
||||||
|
&i.Source,
|
||||||
|
&i.IsActive,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const GetPrematchOdds = `-- name: GetPrematchOdds :many
|
const GetPrematchOdds = `-- name: GetPrematchOdds :many
|
||||||
SELECT event_id,
|
SELECT event_id,
|
||||||
fi,
|
fi,
|
||||||
|
|
@ -162,21 +217,7 @@ func (q *Queries) GetPrematchOdds(ctx context.Context) ([]GetPrematchOddsRow, er
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetPrematchOddsByUpcomingID = `-- name: GetPrematchOddsByUpcomingID :many
|
const GetPrematchOddsByUpcomingID = `-- name: GetPrematchOddsByUpcomingID :many
|
||||||
SELECT o.event_id,
|
SELECT o.id, o.event_id, o.fi, o.market_type, o.market_name, o.market_category, o.market_id, o.name, o.handicap, o.odds_value, o.section, o.category, o.raw_odds, o.fetched_at, o.source, o.is_active
|
||||||
o.fi,
|
|
||||||
o.market_type,
|
|
||||||
o.market_name,
|
|
||||||
o.market_category,
|
|
||||||
o.market_id,
|
|
||||||
o.name,
|
|
||||||
o.handicap,
|
|
||||||
o.odds_value,
|
|
||||||
o.section,
|
|
||||||
o.category,
|
|
||||||
o.raw_odds,
|
|
||||||
o.fetched_at,
|
|
||||||
o.source,
|
|
||||||
o.is_active
|
|
||||||
FROM odds o
|
FROM odds o
|
||||||
JOIN events e ON o.fi = e.id
|
JOIN events e ON o.fi = e.id
|
||||||
WHERE e.id = $1
|
WHERE e.id = $1
|
||||||
|
|
@ -184,43 +225,19 @@ WHERE e.id = $1
|
||||||
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 = 'b365api'
|
||||||
LIMIT $2 OFFSET $3
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetPrematchOddsByUpcomingIDParams struct {
|
func (q *Queries) GetPrematchOddsByUpcomingID(ctx context.Context, id string) ([]Odd, error) {
|
||||||
ID string `json:"id"`
|
rows, err := q.db.Query(ctx, GetPrematchOddsByUpcomingID, id)
|
||||||
Limit int32 `json:"limit"`
|
|
||||||
Offset int32 `json:"offset"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetPrematchOddsByUpcomingIDRow struct {
|
|
||||||
EventID pgtype.Text `json:"event_id"`
|
|
||||||
Fi pgtype.Text `json:"fi"`
|
|
||||||
MarketType string `json:"market_type"`
|
|
||||||
MarketName pgtype.Text `json:"market_name"`
|
|
||||||
MarketCategory pgtype.Text `json:"market_category"`
|
|
||||||
MarketID pgtype.Text `json:"market_id"`
|
|
||||||
Name pgtype.Text `json:"name"`
|
|
||||||
Handicap pgtype.Text `json:"handicap"`
|
|
||||||
OddsValue pgtype.Float8 `json:"odds_value"`
|
|
||||||
Section string `json:"section"`
|
|
||||||
Category pgtype.Text `json:"category"`
|
|
||||||
RawOdds []byte `json:"raw_odds"`
|
|
||||||
FetchedAt pgtype.Timestamp `json:"fetched_at"`
|
|
||||||
Source pgtype.Text `json:"source"`
|
|
||||||
IsActive pgtype.Bool `json:"is_active"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetPrematchOddsByUpcomingID(ctx context.Context, arg GetPrematchOddsByUpcomingIDParams) ([]GetPrematchOddsByUpcomingIDRow, error) {
|
|
||||||
rows, err := q.db.Query(ctx, GetPrematchOddsByUpcomingID, arg.ID, arg.Limit, arg.Offset)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var items []GetPrematchOddsByUpcomingIDRow
|
var items []Odd
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var i GetPrematchOddsByUpcomingIDRow
|
var i Odd
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
&i.EventID,
|
&i.EventID,
|
||||||
&i.Fi,
|
&i.Fi,
|
||||||
&i.MarketType,
|
&i.MarketType,
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ type CreateBetReq struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RandomBetReq struct {
|
type RandomBetReq struct {
|
||||||
BranchID int64 `json:"branch_id,omitempty" example:"1"`
|
BranchID int64 `json:"branch_id" validate:"required" example:"1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateBetRes struct {
|
type CreateBetRes struct {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type ValidInt64 struct {
|
type ValidInt64 struct {
|
||||||
Value int64
|
Value int64
|
||||||
|
|
@ -11,6 +14,10 @@ type ValidString struct {
|
||||||
Value string
|
Value string
|
||||||
Valid bool
|
Valid bool
|
||||||
}
|
}
|
||||||
|
type ValidTime struct {
|
||||||
|
Value time.Time
|
||||||
|
Valid bool
|
||||||
|
}
|
||||||
type ValidBool struct {
|
type ValidBool struct {
|
||||||
Value bool
|
Value bool
|
||||||
Valid bool
|
Valid bool
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,8 @@ var SupportedLeagues = []int64{
|
||||||
10041957, //UEFA Europa League
|
10041957, //UEFA Europa League
|
||||||
10079560, //UEFA Conference League
|
10079560, //UEFA Conference League
|
||||||
10047168, // US MLS
|
10047168, // US MLS
|
||||||
|
10044469, // Ethiopian Premier League
|
||||||
10050282, //UEFA Nations League
|
10050282, //UEFA Nations League
|
||||||
10040795, //EuroLeague
|
|
||||||
|
|
||||||
10043156, //England FA Cup
|
10043156, //England FA Cup
|
||||||
10042103, //France Cup
|
10042103, //France Cup
|
||||||
|
|
@ -26,5 +25,12 @@ var SupportedLeagues = []int64{
|
||||||
|
|
||||||
// Basketball
|
// Basketball
|
||||||
173998768, //NBA
|
173998768, //NBA
|
||||||
|
10041830, //NBA
|
||||||
|
|
||||||
|
// Ice Hockey
|
||||||
|
10037477, //NHL
|
||||||
|
10037447, //AHL
|
||||||
|
10069385, //IIHF World Championship
|
||||||
|
10040795, //EuroLeague
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,9 @@ type OddsSection struct {
|
||||||
Sp map[string]OddsMarket `json:"sp"`
|
Sp map[string]OddsMarket `json:"sp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.Number `json:"id"`
|
ID json.RawMessage `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Odds []json.RawMessage `json:"odds"`
|
Odds []json.RawMessage `json:"odds"`
|
||||||
Header string `json:"header,omitempty"`
|
Header string `json:"header,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -43,4 +43,24 @@ const (
|
||||||
OUTCOME_STATUS_LOSS OutcomeStatus = 2
|
OUTCOME_STATUS_LOSS OutcomeStatus = 2
|
||||||
OUTCOME_STATUS_VOID OutcomeStatus = 3 //Give Back
|
OUTCOME_STATUS_VOID OutcomeStatus = 3 //Give Back
|
||||||
OUTCOME_STATUS_HALF OutcomeStatus = 4 //Half Win and Half Given Back
|
OUTCOME_STATUS_HALF OutcomeStatus = 4 //Half Win and Half Given Back
|
||||||
|
OUTCOME_STATUS_ERROR OutcomeStatus = 5 //Half Win and Half Given Back
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (o *OutcomeStatus) String() string {
|
||||||
|
switch *o {
|
||||||
|
case OUTCOME_STATUS_PENDING:
|
||||||
|
return "PENDING"
|
||||||
|
case OUTCOME_STATUS_WIN:
|
||||||
|
return "WIN"
|
||||||
|
case OUTCOME_STATUS_LOSS:
|
||||||
|
return "LOSS"
|
||||||
|
case OUTCOME_STATUS_VOID:
|
||||||
|
return "VOID"
|
||||||
|
case OUTCOME_STATUS_HALF:
|
||||||
|
return "HALF"
|
||||||
|
case OUTCOME_STATUS_ERROR:
|
||||||
|
return "ERROR"
|
||||||
|
default:
|
||||||
|
return "UNKNOWN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,15 @@ const (
|
||||||
FOOTBALL_CORRECT_SCORE FootballMarket = 43 //"correct_score"
|
FOOTBALL_CORRECT_SCORE FootballMarket = 43 //"correct_score"
|
||||||
FOOTBALL_ASIAN_HANDICAP FootballMarket = 938 //"asian_handicap"
|
FOOTBALL_ASIAN_HANDICAP FootballMarket = 938 //"asian_handicap"
|
||||||
FOOTBALL_GOAL_LINE FootballMarket = 10143 //"goal_line"
|
FOOTBALL_GOAL_LINE FootballMarket = 10143 //"goal_line"
|
||||||
|
|
||||||
FOOTBALL_HALF_TIME_RESULT FootballMarket = 1579 //"half_time_result"
|
FOOTBALL_HALF_TIME_RESULT FootballMarket = 1579 //"half_time_result"
|
||||||
FOOTBALL_FIRST_HALF_ASIAN_HANDICAP FootballMarket = 50137 //"1st_half_asian_handicap"
|
FOOTBALL_FIRST_HALF_ASIAN_HANDICAP FootballMarket = 50137 //"1st_half_asian_handicap"
|
||||||
FOOTBALL_FIRST_HALF_GOAL_LINE FootballMarket = 50136 //"1st_half_goal_line"
|
FOOTBALL_FIRST_HALF_GOAL_LINE FootballMarket = 50136 //"1st_half_goal_line"
|
||||||
FOOTBALL_FIRST_TEAM_TO_SCORE FootballMarket = 1178 //"first_team_to_score"
|
FOOTBALL_FIRST_TEAM_TO_SCORE FootballMarket = 1178 //"first_team_to_score"
|
||||||
FOOTBALL_GOALS_ODD_EVEN FootballMarket = 10111 //"goals_odd_even"
|
FOOTBALL_GOALS_ODD_EVEN FootballMarket = 10111 //"goals_odd_even"
|
||||||
FOOTBALL_DRAW_NO_BET FootballMarket = 10544 //"draw_no_bet"
|
FOOTBALL_DRAW_NO_BET FootballMarket = 10544 //"draw_no_bet"
|
||||||
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BasketBallMarket int64
|
type BasketBallMarket int64
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
// "fmt"
|
// "fmt"
|
||||||
|
|
||||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
|
@ -225,6 +226,19 @@ func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]do
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error) {
|
||||||
|
outcomes, err := s.queries.GetBetOutcomeByBetID(ctx, betID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes))
|
||||||
|
|
||||||
|
for _, outcome := range outcomes {
|
||||||
|
result = append(result, convertDBBetOutcomes(outcome))
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) {
|
func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) {
|
||||||
update, err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{
|
update, err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{
|
||||||
Status: int32(status),
|
Status: int32(status),
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,8 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcoming
|
||||||
return upcomingEvents, nil
|
return upcomingEvents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32, leagueID domain.ValidString, sportID domain.ValidString) ([]domain.UpcomingEvent, int64, error) {
|
func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidString, sportID domain.ValidString, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error) {
|
||||||
|
|
||||||
events, err := s.queries.GetPaginatedUpcomingEvents(ctx, dbgen.GetPaginatedUpcomingEventsParams{
|
events, err := s.queries.GetPaginatedUpcomingEvents(ctx, dbgen.GetPaginatedUpcomingEventsParams{
|
||||||
LeagueID: pgtype.Text{
|
LeagueID: pgtype.Text{
|
||||||
String: leagueID.Value,
|
String: leagueID.Value,
|
||||||
|
|
@ -127,8 +128,22 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, off
|
||||||
String: sportID.Value,
|
String: sportID.Value,
|
||||||
Valid: sportID.Valid,
|
Valid: sportID.Valid,
|
||||||
},
|
},
|
||||||
Limit: limit,
|
Limit: pgtype.Int4{
|
||||||
Offset: offset * limit,
|
Int32: int32(limit.Value),
|
||||||
|
Valid: limit.Valid,
|
||||||
|
},
|
||||||
|
Offset: pgtype.Int4{
|
||||||
|
Int32: int32(offset.Value),
|
||||||
|
Valid: offset.Valid,
|
||||||
|
},
|
||||||
|
FirstStartTime: pgtype.Timestamp{
|
||||||
|
Time: firstStartTime.Value.UTC(),
|
||||||
|
Valid: firstStartTime.Valid,
|
||||||
|
},
|
||||||
|
LastStartTime: pgtype.Timestamp{
|
||||||
|
Time: lastStartTime.Value.UTC(),
|
||||||
|
Valid: lastStartTime.Valid,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -167,7 +182,7 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, off
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
numberOfPages := math.Ceil(float64(totalCount) / float64(limit))
|
numberOfPages := math.Ceil(float64(totalCount) / float64(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) {
|
||||||
|
|
|
||||||
|
|
@ -205,15 +205,54 @@ func (s *Store) GetRawOddsByMarketID(ctx context.Context, rawOddsID string, upco
|
||||||
FetchedAt: odds.FetchedAt.Time,
|
FetchedAt: odds.FetchedAt.Time,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
func (s *Store) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit domain.ValidInt64, offset domain.ValidInt64) ([]domain.Odd, error) {
|
||||||
|
odds, err := s.queries.GetPaginatedPrematchOddsByUpcomingID(ctx, dbgen.GetPaginatedPrematchOddsByUpcomingIDParams{
|
||||||
|
ID: upcomingID,
|
||||||
|
Limit: pgtype.Int4{
|
||||||
|
Int32: int32(limit.Value),
|
||||||
|
Valid: limit.Valid,
|
||||||
|
},
|
||||||
|
Offset: pgtype.Int4{
|
||||||
|
Int32: int32(offset.Value),
|
||||||
|
Valid: offset.Valid,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Map the results to domain.Odd
|
||||||
|
domainOdds := make([]domain.Odd, len(odds))
|
||||||
|
for i, odd := range odds {
|
||||||
|
var rawOdds []domain.RawMessage
|
||||||
|
if err := json.Unmarshal(odd.RawOdds, &rawOdds); err != nil {
|
||||||
|
rawOdds = nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset int32) ([]domain.Odd, error) {
|
domainOdds[i] = domain.Odd{
|
||||||
params := dbgen.GetPrematchOddsByUpcomingIDParams{
|
EventID: odd.EventID.String,
|
||||||
ID: upcomingID,
|
Fi: odd.Fi.String,
|
||||||
Limit: limit,
|
MarketType: odd.MarketType,
|
||||||
Offset: offset,
|
MarketName: odd.MarketName.String,
|
||||||
|
MarketCategory: odd.MarketCategory.String,
|
||||||
|
MarketID: odd.MarketID.String,
|
||||||
|
Name: odd.Name.String,
|
||||||
|
Handicap: odd.Handicap.String,
|
||||||
|
OddsValue: odd.OddsValue.Float64,
|
||||||
|
Section: odd.Section,
|
||||||
|
Category: odd.Category.String,
|
||||||
|
RawOdds: rawOdds,
|
||||||
|
FetchedAt: odd.FetchedAt.Time,
|
||||||
|
Source: odd.Source.String,
|
||||||
|
IsActive: odd.IsActive.Bool,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
odds, err := s.queries.GetPrematchOddsByUpcomingID(ctx, params)
|
return domainOdds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string) ([]domain.Odd, error) {
|
||||||
|
|
||||||
|
odds, err := s.queries.GetPrematchOddsByUpcomingID(ctx, upcomingID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@ type BetStore interface {
|
||||||
GetAllBets(ctx context.Context) ([]domain.GetBet, error)
|
GetAllBets(ctx context.Context) ([]domain.GetBet, error)
|
||||||
GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error)
|
GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error)
|
||||||
GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error)
|
GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error)
|
||||||
|
GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error)
|
||||||
UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error
|
UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error
|
||||||
UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error
|
UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error
|
||||||
UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error)
|
UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error)
|
||||||
DeleteBet(ctx context.Context, id int64) error
|
DeleteBet(ctx context.Context, id int64) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"math/big"
|
"math/big"
|
||||||
random "math/rand"
|
random "math/rand"
|
||||||
"slices"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -20,6 +19,12 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoEventsAvailable = errors.New("Not enough events available with the given filters")
|
||||||
|
ErrGenerateRandomOutcome = errors.New("Failed to generate any random outcome for events")
|
||||||
|
ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending")
|
||||||
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
betStore BetStore
|
betStore BetStore
|
||||||
eventSvc event.Service
|
eventSvc event.Service
|
||||||
|
|
@ -239,12 +244,12 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportID, HomeTeam, AwayTeam string, StartTime time.Time) ([]domain.CreateBetOutcome, float32, error) {
|
func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportID, HomeTeam, AwayTeam string, StartTime time.Time, numMarkets int) ([]domain.CreateBetOutcome, float32, error) {
|
||||||
|
|
||||||
var newOdds []domain.CreateBetOutcome
|
var newOdds []domain.CreateBetOutcome
|
||||||
var totalOdds float32 = 1
|
var totalOdds float32 = 1
|
||||||
|
|
||||||
markets, err := s.prematchSvc.GetPrematchOdds(ctx, eventID)
|
markets, err := s.prematchSvc.GetPrematchOddsByUpcomingID(ctx, eventID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("failed to get odds for event", "event id", eventID, "error", err)
|
s.logger.Error("failed to get odds for event", "event id", eventID, "error", err)
|
||||||
|
|
@ -253,32 +258,20 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportI
|
||||||
|
|
||||||
if len(markets) == 0 {
|
if len(markets) == 0 {
|
||||||
s.logger.Error("empty odds for event", "event id", eventID)
|
s.logger.Error("empty odds for event", "event id", eventID)
|
||||||
return nil, 0, fmt.Errorf("empty odds or event", "event id", eventID)
|
return nil, 0, fmt.Errorf("empty odds or event %v", eventID)
|
||||||
}
|
}
|
||||||
|
|
||||||
var numMarkets = min(5, len(markets))
|
var selectedMarkets []domain.Odd
|
||||||
var randIndex []int = make([]int, numMarkets)
|
numMarkets = min(numMarkets, len(markets))
|
||||||
for i := 0; i < numMarkets; i++ {
|
for i := 0; i < numMarkets; i++ {
|
||||||
// Guarantee that the odd is unique
|
randomIndex := random.Intn(len(markets))
|
||||||
var newRandMarket int
|
selectedMarkets = append(selectedMarkets, markets[randomIndex])
|
||||||
count := 0
|
markets = append(markets[:randomIndex], markets[randomIndex+1:]...)
|
||||||
for {
|
}
|
||||||
newRandMarket = random.Intn(len(markets))
|
|
||||||
if !slices.Contains(randIndex, newRandMarket) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// just in case
|
|
||||||
if count >= 5 {
|
|
||||||
s.logger.Warn("market overload", "event id", eventID)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
|
|
||||||
randIndex[i] = newRandMarket
|
for _, market := range selectedMarkets {
|
||||||
|
|
||||||
rawOdds := markets[i].RawOdds
|
randomRawOdd := market.RawOdds[random.Intn(len(market.RawOdds))]
|
||||||
randomRawOdd := rawOdds[random.Intn(len(rawOdds))]
|
|
||||||
|
|
||||||
type rawOddType struct {
|
type rawOddType struct {
|
||||||
ID string
|
ID string
|
||||||
|
|
@ -317,13 +310,13 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportI
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
marketID, err := strconv.ParseInt(markets[i].MarketID, 10, 64)
|
marketID, err := strconv.ParseInt(market.MarketID, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get odd id", "error", err)
|
s.logger.Error("Failed to get odd id", "error", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
marketName := markets[i].MarketName
|
marketName := market.MarketName
|
||||||
|
|
||||||
newOdds = append(newOdds, domain.CreateBetOutcome{
|
newOdds = append(newOdds, domain.CreateBetOutcome{
|
||||||
EventID: eventID,
|
EventID: eventID,
|
||||||
|
|
@ -345,28 +338,48 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportI
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(newOdds) == 0 {
|
if len(newOdds) == 0 {
|
||||||
s.logger.Error("Failed to generate random outcomes")
|
s.logger.Error("Bet Outcomes is empty for market", "selectedMarket", selectedMarkets[0].MarketName)
|
||||||
return nil, 0, nil
|
return nil, 0, ErrGenerateRandomOutcome
|
||||||
}
|
}
|
||||||
|
|
||||||
return newOdds, totalOdds, nil
|
return newOdds, totalOdds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64) (domain.CreateBetRes, error) {
|
func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, leagueID, sportID domain.ValidString, firstStartTime, lastStartTime domain.ValidTime) (domain.CreateBetRes, error) {
|
||||||
|
|
||||||
// Get a unexpired event id
|
// Get a unexpired event id
|
||||||
events, _, err := s.eventSvc.GetPaginatedUpcomingEvents(ctx, 5, 0, domain.ValidString{}, domain.ValidString{})
|
|
||||||
|
events, _, err := s.eventSvc.GetPaginatedUpcomingEvents(ctx,
|
||||||
|
domain.ValidInt64{}, domain.ValidInt64{}, leagueID, sportID, firstStartTime, lastStartTime)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.CreateBetRes{}, err
|
return domain.CreateBetRes{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(events) == 0 {
|
||||||
|
return domain.CreateBetRes{}, ErrNoEventsAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add the option of passing number of created events
|
||||||
|
var selectedUpcomingEvents []domain.UpcomingEvent
|
||||||
|
numEventsPerBet := random.Intn(4) + 1 //Eliminate the option of 0
|
||||||
|
|
||||||
|
for i := 0; i < int(numEventsPerBet); i++ {
|
||||||
|
randomIndex := random.Intn(len(events))
|
||||||
|
selectedUpcomingEvents = append(selectedUpcomingEvents, events[randomIndex])
|
||||||
|
events = append(events[:randomIndex], events[randomIndex+1:]...)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("Generating random bet events", "selectedUpcomingEvents", len(selectedUpcomingEvents))
|
||||||
|
|
||||||
// Get market and odds for that
|
// Get market and odds for that
|
||||||
var randomOdds []domain.CreateBetOutcome
|
var randomOdds []domain.CreateBetOutcome
|
||||||
var totalOdds float32 = 1
|
var totalOdds float32 = 1
|
||||||
for _, event := range events {
|
numMarketsPerBet := random.Intn(2) + 1
|
||||||
|
for _, event := range selectedUpcomingEvents {
|
||||||
|
|
||||||
newOdds, total, err := s.GenerateRandomBetOutcomes(ctx, event.ID, event.SportID, event.HomeTeam, event.AwayTeam, event.StartTime)
|
newOdds, total, err := s.GenerateRandomBetOutcomes(ctx, event.ID, event.SportID, event.HomeTeam, event.AwayTeam, event.StartTime, numMarketsPerBet)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("failed to generate random bet outcome", "event id", event.ID, "error", err)
|
s.logger.Error("failed to generate random bet outcome", "event id", event.ID, "error", err)
|
||||||
|
|
@ -378,10 +391,12 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64) (d
|
||||||
|
|
||||||
}
|
}
|
||||||
if len(randomOdds) == 0 {
|
if len(randomOdds) == 0 {
|
||||||
s.logger.Error("Failed to generate random outcomes")
|
s.logger.Error("Failed to generate random any outcomes for all events")
|
||||||
return domain.CreateBetRes{}, nil
|
return domain.CreateBetRes{}, ErrGenerateRandomOutcome
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.logger.Info("Generated Random bet Outcome", "randomOdds", len(randomOdds))
|
||||||
|
|
||||||
var cashoutID string
|
var cashoutID string
|
||||||
|
|
||||||
cashoutID, err = s.GenerateCashoutID()
|
cashoutID, err = s.GenerateCashoutID()
|
||||||
|
|
@ -389,13 +404,13 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64) (d
|
||||||
return domain.CreateBetRes{}, err
|
return domain.CreateBetRes{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
randomNumber := strconv.FormatInt(int64(random.Intn(10)), 10)
|
randomNumber := strconv.FormatInt(int64(random.Intn(100000000000)), 10)
|
||||||
newBet := domain.CreateBet{
|
newBet := domain.CreateBet{
|
||||||
Amount: 123,
|
Amount: domain.ToCurrency(123.5),
|
||||||
TotalOdds: totalOdds,
|
TotalOdds: totalOdds,
|
||||||
Status: domain.OUTCOME_STATUS_PENDING,
|
Status: domain.OUTCOME_STATUS_PENDING,
|
||||||
FullName: "test" + randomNumber,
|
FullName: "test" + randomNumber,
|
||||||
PhoneNumber: randomNumber,
|
PhoneNumber: "0900000000",
|
||||||
CashoutID: cashoutID,
|
CashoutID: cashoutID,
|
||||||
BranchID: domain.ValidInt64{Valid: true, Value: branchID},
|
BranchID: domain.ValidInt64{Valid: true, Value: branchID},
|
||||||
UserID: domain.ValidInt64{Valid: true, Value: userID},
|
UserID: domain.ValidInt64{Valid: true, Value: userID},
|
||||||
|
|
@ -450,42 +465,97 @@ func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.Outc
|
||||||
return s.betStore.UpdateStatus(ctx, id, status)
|
return s.betStore.UpdateStatus(ctx, id, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) checkBetOutcomeForBet(ctx context.Context, eventID int64) error {
|
func (s *Service) CheckBetOutcomeForBet(ctx context.Context, betID int64) (domain.OutcomeStatus, error) {
|
||||||
betOutcomes, err := s.betStore.GetBetOutcomeByEventID(ctx, eventID)
|
betOutcomes, err := s.betStore.GetBetOutcomeByBetID(ctx, betID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return domain.OUTCOME_STATUS_PENDING, err
|
||||||
}
|
}
|
||||||
status := domain.OUTCOME_STATUS_PENDING
|
status := domain.OUTCOME_STATUS_PENDING
|
||||||
|
|
||||||
for _, betOutcome := range betOutcomes {
|
for _, betOutcome := range betOutcomes {
|
||||||
// Check if any of them are pending
|
// If any of the bet outcomes are pending return
|
||||||
if betOutcome.Status == domain.OUTCOME_STATUS_PENDING {
|
if betOutcome.Status == domain.OUTCOME_STATUS_PENDING {
|
||||||
return nil
|
return domain.OUTCOME_STATUS_PENDING, ErrOutcomesNotCompleted
|
||||||
}
|
}
|
||||||
|
|
||||||
if status == domain.OUTCOME_STATUS_PENDING {
|
if betOutcome.Status == domain.OUTCOME_STATUS_ERROR {
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The bet status can only be updated if its not lost or error
|
||||||
|
// If all the bet outcomes are a win, then set the bet status to win
|
||||||
|
// If even one of the bet outcomes is a loss then set the bet status to loss
|
||||||
|
// If even one of the bet outcomes is an error, then set the bet status to error
|
||||||
|
switch status {
|
||||||
|
case domain.OUTCOME_STATUS_PENDING:
|
||||||
status = betOutcome.Status
|
status = betOutcome.Status
|
||||||
} else if status == domain.OUTCOME_STATUS_WIN {
|
case domain.OUTCOME_STATUS_WIN:
|
||||||
status = betOutcome.Status
|
if betOutcome.Status == domain.OUTCOME_STATUS_LOSS {
|
||||||
} else if status == domain.OUTCOME_STATUS_LOSS {
|
status = domain.OUTCOME_STATUS_HALF
|
||||||
continue
|
} else if betOutcome.Status == domain.OUTCOME_STATUS_HALF {
|
||||||
|
status = domain.OUTCOME_STATUS_VOID
|
||||||
|
} else if betOutcome.Status == domain.OUTCOME_STATUS_WIN {
|
||||||
|
status = domain.OUTCOME_STATUS_WIN
|
||||||
|
} else if betOutcome.Status == domain.OUTCOME_STATUS_VOID {
|
||||||
|
status = domain.OUTCOME_STATUS_VOID
|
||||||
|
} else {
|
||||||
|
status = domain.OUTCOME_STATUS_ERROR
|
||||||
|
}
|
||||||
|
case domain.OUTCOME_STATUS_LOSS:
|
||||||
|
if betOutcome.Status == domain.OUTCOME_STATUS_LOSS {
|
||||||
|
status = domain.OUTCOME_STATUS_LOSS
|
||||||
|
} else if betOutcome.Status == domain.OUTCOME_STATUS_HALF {
|
||||||
|
status = domain.OUTCOME_STATUS_LOSS
|
||||||
|
} else if betOutcome.Status == domain.OUTCOME_STATUS_WIN {
|
||||||
|
status = domain.OUTCOME_STATUS_LOSS
|
||||||
|
} else if betOutcome.Status == domain.OUTCOME_STATUS_VOID {
|
||||||
|
status = domain.OUTCOME_STATUS_VOID
|
||||||
|
} else {
|
||||||
|
status = domain.OUTCOME_STATUS_ERROR
|
||||||
|
}
|
||||||
|
case domain.OUTCOME_STATUS_VOID:
|
||||||
|
if betOutcome.Status == domain.OUTCOME_STATUS_VOID ||
|
||||||
|
betOutcome.Status == domain.OUTCOME_STATUS_WIN ||
|
||||||
|
betOutcome.Status == domain.OUTCOME_STATUS_LOSS ||
|
||||||
|
betOutcome.Status == domain.OUTCOME_STATUS_HALF {
|
||||||
|
status = domain.OUTCOME_STATUS_VOID
|
||||||
|
} else {
|
||||||
|
status = domain.OUTCOME_STATUS_ERROR
|
||||||
|
}
|
||||||
|
case domain.OUTCOME_STATUS_HALF:
|
||||||
|
if betOutcome.Status == domain.OUTCOME_STATUS_HALF ||
|
||||||
|
betOutcome.Status == domain.OUTCOME_STATUS_WIN {
|
||||||
|
status = domain.OUTCOME_STATUS_HALF
|
||||||
|
} else if betOutcome.Status == domain.OUTCOME_STATUS_LOSS {
|
||||||
|
status = domain.OUTCOME_STATUS_LOSS
|
||||||
|
} else if betOutcome.Status == domain.OUTCOME_STATUS_VOID {
|
||||||
|
status = domain.OUTCOME_STATUS_VOID
|
||||||
|
} else {
|
||||||
|
status = domain.OUTCOME_STATUS_ERROR
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// If the status is not pending, win, loss or error, then set the status to error
|
||||||
|
status = domain.OUTCOME_STATUS_ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if status != domain.OUTCOME_STATUS_PENDING {
|
if status == domain.OUTCOME_STATUS_PENDING || status == domain.OUTCOME_STATUS_ERROR {
|
||||||
return nil
|
// If the status is pending or error, then we don't need to update the bet
|
||||||
|
s.logger.Info("bet not updated", "bet id", betID, "status", status)
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("Error when processing bet outcomes")
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.UpdateStatus(ctx, eventID, status)
|
return status, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error {
|
func (s *Service) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) {
|
||||||
betOutcome, err := s.betStore.UpdateBetOutcomeStatus(ctx, id, status)
|
betOutcome, err := s.betStore.UpdateBetOutcomeStatus(ctx, id, status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return domain.BetOutcome{}, err
|
||||||
}
|
}
|
||||||
return s.checkBetOutcomeForBet(ctx, betOutcome.EventID)
|
|
||||||
|
return betOutcome, err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ type Service interface {
|
||||||
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) ([]domain.UpcomingEvent, error)
|
||||||
GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32, leagueID domain.ValidString, sportID domain.ValidString) ([]domain.UpcomingEvent, int64, error)
|
GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidString, sportID domain.ValidString, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,18 +99,18 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
||||||
sportIDs := []int{1, 18}
|
// sportIDs := []int{1, 18, 17}
|
||||||
var totalPages int = 1
|
sportIDs := []int{18}
|
||||||
var page int = 0
|
|
||||||
var limit int = 100
|
|
||||||
var count int = 0
|
|
||||||
for _, sportID := range sportIDs {
|
|
||||||
for page != totalPages {
|
|
||||||
time.Sleep(3 * time.Second) //This will restrict the fetching to 1200 requests per hour
|
|
||||||
|
|
||||||
|
for _, sportID := range sportIDs {
|
||||||
|
var totalPages int = 1
|
||||||
|
var page int = 0
|
||||||
|
var limit int = 10
|
||||||
|
var count int = 0
|
||||||
|
for page <= totalPages {
|
||||||
page = page + 1
|
page = page + 1
|
||||||
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", sportID, s.token, page)
|
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", sportID, s.token, page)
|
||||||
log.Printf("📡 Fetching data for event data page %d", page)
|
log.Printf("📡 Fetching data for sport %d event data page %d/%d", sportID, page, min(limit, 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)
|
||||||
|
|
@ -145,9 +145,10 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
||||||
} `json:"results"`
|
} `json:"results"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
||||||
|
log.Printf("❌ Failed to parse json data")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
skippedLeague := 0
|
var skippedLeague []string
|
||||||
for _, ev := range data.Results {
|
for _, ev := range data.Results {
|
||||||
startUnix, _ := strconv.ParseInt(ev.Time, 10, 64)
|
startUnix, _ := strconv.ParseInt(ev.Time, 10, 64)
|
||||||
// eventID, err := strconv.ParseInt(ev.ID, 10, 64)
|
// eventID, err := strconv.ParseInt(ev.ID, 10, 64)
|
||||||
|
|
@ -163,7 +164,8 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !slices.Contains(domain.SupportedLeagues, leagueID) {
|
if !slices.Contains(domain.SupportedLeagues, leagueID) {
|
||||||
skippedLeague++
|
|
||||||
|
skippedLeague = append(skippedLeague, ev.League.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,11 +190,20 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
||||||
event.AwayTeamID = ev.Away.ID
|
event.AwayTeamID = ev.Away.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = s.store.SaveUpcomingEvent(ctx, event)
|
err = s.store.SaveUpcomingEvent(ctx, event)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ Failed to save upcoming event %s", event.ID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
totalPages = data.Pager.Total
|
|
||||||
|
|
||||||
if count > limit {
|
log.Printf("⚠️ Skipped leagues %v", len(skippedLeague))
|
||||||
|
// log.Printf("⚠️ Total pages %v", data.Pager.Total)
|
||||||
|
totalPages = data.Pager.Total / data.Pager.PerPage
|
||||||
|
|
||||||
|
if count >= limit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if page > totalPages {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
count++
|
count++
|
||||||
|
|
@ -223,8 +234,8 @@ func (s *service) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcomi
|
||||||
return s.store.GetExpiredUpcomingEvents(ctx)
|
return s.store.GetExpiredUpcomingEvents(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32, leagueID domain.ValidString, sportID domain.ValidString) ([]domain.UpcomingEvent, int64, error) {
|
func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidString, sportID domain.ValidString, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error){
|
||||||
return s.store.GetPaginatedUpcomingEvents(ctx, limit, offset, leagueID, sportID)
|
return s.store.GetPaginatedUpcomingEvents(ctx, limit, offset, leagueID, sportID, firstStartTime, lastStartTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) {
|
func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import (
|
||||||
type Service interface {
|
type Service interface {
|
||||||
FetchNonLiveOdds(ctx context.Context) error
|
FetchNonLiveOdds(ctx context.Context) 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)
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,7 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||||
|
|
||||||
var errs []error
|
var errs []error
|
||||||
|
|
||||||
for _, event := range eventIDs {
|
for index, event := range eventIDs {
|
||||||
// time.Sleep(3 * time.Second) //This will restrict the fetching to 1200 requests per hour
|
|
||||||
|
|
||||||
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -54,17 +53,26 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||||
|
|
||||||
url := fmt.Sprintf("https://api.b365api.com/v3/bet365/prematch?token=%s&FI=%d", s.config.Bet365Token, eventID)
|
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", eventID)
|
log.Printf("📡 Fetching prematch odds for event ID: %d (%d/%d) ", eventID, index, len(eventIDs))
|
||||||
|
|
||||||
resp, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ Failed to create request for event %d: %v", eventID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ Failed to fetch prematch odds for event %d: %v", eventID, err)
|
log.Printf("❌ Failed to fetch prematch odds for event %d: %v", eventID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
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
|
var oddsData domain.BaseNonLiveOddResponse
|
||||||
|
|
||||||
if err := json.Unmarshal(body, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 {
|
if err := json.Unmarshal(body, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 {
|
||||||
|
|
@ -77,17 +85,17 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||||
switch sportID {
|
switch sportID {
|
||||||
case domain.FOOTBALL:
|
case domain.FOOTBALL:
|
||||||
if err := s.parseFootball(ctx, oddsData.Results[0]); err != nil {
|
if err := s.parseFootball(ctx, oddsData.Results[0]); err != nil {
|
||||||
s.logger.Error("Failed to insert football odd")
|
s.logger.Error("Error while inserting football odd")
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
case domain.BASKETBALL:
|
case domain.BASKETBALL:
|
||||||
if err := s.parseBasketball(ctx, oddsData.Results[0]); err != nil {
|
if err := s.parseBasketball(ctx, oddsData.Results[0]); err != nil {
|
||||||
s.logger.Error("Failed to insert basketball odd")
|
s.logger.Error("Error while inserting basketball odd")
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
case domain.ICE_HOCKEY:
|
case domain.ICE_HOCKEY:
|
||||||
if err := s.parseIceHockey(ctx, oddsData.Results[0]); err != nil {
|
if err := s.parseIceHockey(ctx, oddsData.Results[0]); err != nil {
|
||||||
s.logger.Error("Failed to insert ice hockey odd")
|
s.logger.Error("Error while inserting ice hockey odd")
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,8 +115,8 @@ func (s *ServiceImpl) parseFootball(ctx context.Context, res json.RawMessage) er
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if footballRes.EventID == "" && footballRes.FI == "" {
|
if footballRes.EventID == "" && footballRes.FI == "" {
|
||||||
s.logger.Error("Skipping result with no valid Event ID")
|
s.logger.Error("Skipping football result with no valid Event ID", "eventID", footballRes.EventID, "fi", footballRes.FI)
|
||||||
return fmt.Errorf("Skipping result with no valid Event ID")
|
return fmt.Errorf("Skipping football result with no valid Event ID Event ID %v", footballRes.EventID)
|
||||||
}
|
}
|
||||||
sections := map[string]domain.OddsSection{
|
sections := map[string]domain.OddsSection{
|
||||||
"main": footballRes.Main,
|
"main": footballRes.Main,
|
||||||
|
|
@ -121,7 +129,8 @@ func (s *ServiceImpl) parseFootball(ctx context.Context, res json.RawMessage) er
|
||||||
|
|
||||||
for oddCategory, section := range sections {
|
for oddCategory, section := range sections {
|
||||||
if err := s.storeSection(ctx, footballRes.EventID, footballRes.FI, oddCategory, section); err != nil {
|
if err := s.storeSection(ctx, footballRes.EventID, footballRes.FI, oddCategory, section); err != nil {
|
||||||
s.logger.Error("Skipping result with no valid Event ID")
|
s.logger.Error("Error storing football section", "eventID", footballRes.FI, "odd", oddCategory)
|
||||||
|
log.Printf("⚠️ Error when storing football %v", err)
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -136,12 +145,12 @@ func (s *ServiceImpl) parseFootball(ctx context.Context, res json.RawMessage) er
|
||||||
func (s *ServiceImpl) parseBasketball(ctx context.Context, res json.RawMessage) error {
|
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 football result", "error", err)
|
s.logger.Error("Failed to unmarshal basketball result", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if basketballRes.EventID == "" && basketballRes.FI == "" {
|
if basketballRes.EventID == "" && basketballRes.FI == "" {
|
||||||
s.logger.Error("Skipping result with no valid Event ID")
|
s.logger.Error("Skipping basketball result with no valid Event ID")
|
||||||
return fmt.Errorf("Skipping result with no valid Event ID")
|
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,
|
||||||
|
|
@ -177,7 +186,7 @@ func (s *ServiceImpl) parseBasketball(ctx context.Context, res json.RawMessage)
|
||||||
func (s *ServiceImpl) parseIceHockey(ctx context.Context, res json.RawMessage) error {
|
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 football result", "error", err)
|
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if iceHockeyRes.EventID == "" && iceHockeyRes.FI == "" {
|
if iceHockeyRes.EventID == "" && iceHockeyRes.FI == "" {
|
||||||
|
|
@ -229,17 +238,30 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
marketID, err := market.ID.Int64()
|
// Check if the market id is a string
|
||||||
|
var marketIDstr string
|
||||||
|
err := json.Unmarshal(market.ID, &marketIDstr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Invalid market id", "marketID", marketID)
|
// check if its int
|
||||||
|
var marketIDint int
|
||||||
|
err := json.Unmarshal(market.ID, &marketIDint)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Invalid market id")
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
marketIDint, err := strconv.ParseInt(marketIDstr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name)
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
isSupported, ok := domain.SupportedMarkets[marketID]
|
isSupported, ok := domain.SupportedMarkets[marketIDint]
|
||||||
|
|
||||||
if !ok || !isSupported {
|
if !ok || !isSupported {
|
||||||
s.logger.Info("Unsupported market_id", "marketID", marketID)
|
// s.logger.Info("Unsupported market_id", "marketID", marketIDint, "marketName", market.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,7 +271,7 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
|
||||||
MarketCategory: sectionName,
|
MarketCategory: sectionName,
|
||||||
MarketType: marketType,
|
MarketType: marketType,
|
||||||
MarketName: market.Name,
|
MarketName: market.Name,
|
||||||
MarketID: market.ID.String(),
|
MarketID: marketIDstr,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
Odds: market.Odds,
|
Odds: market.Odds,
|
||||||
}
|
}
|
||||||
|
|
@ -285,6 +307,10 @@ func (s *ServiceImpl) GetRawOddsByMarketID(ctx context.Context, marketID string,
|
||||||
return rows, nil
|
return rows, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset int32) ([]domain.Odd, error) {
|
func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string) ([]domain.Odd, error) {
|
||||||
return s.store.GetPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset)
|
return s.store.GetPrematchOddsByUpcomingID(ctx, upcomingID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceImpl) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset domain.ValidInt64) ([]domain.Odd, error) {
|
||||||
|
return s.store.GetPaginatedPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Football evaluations
|
// Football evaluations
|
||||||
|
|
||||||
|
// Full Time Result betting is a type of bet where the bettor predicts the outcome of a match at the end of the full 90 minutes of play.
|
||||||
func evaluateFullTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateFullTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
switch outcome.OddName {
|
switch outcome.OddName {
|
||||||
case "1": // Home win
|
case "1": // Home win
|
||||||
|
|
@ -27,15 +29,16 @@ func evaluateFullTimeResult(outcome domain.BetOutcome, score struct{ Home, Away
|
||||||
}
|
}
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Over/Under betting is a type of bet where the bettor predicts whether the total number of goals scored in a match will be over or under a specified number.
|
||||||
func evaluateGoalsOverUnder(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateGoalsOverUnder(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
totalGoals := float64(score.Home + score.Away)
|
totalGoals := float64(score.Home + score.Away)
|
||||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if outcome.OddHeader == "Over" {
|
if outcome.OddHeader == "Over" {
|
||||||
|
|
@ -53,9 +56,10 @@ func evaluateGoalsOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
|
||||||
}
|
}
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
}
|
}
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Correct Score betting is a type of bet where the bettor predicts the exact final score of a match.
|
||||||
func evaluateCorrectScore(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateCorrectScore(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
expectedScore := fmt.Sprintf("%d-%d", score.Home, score.Away)
|
expectedScore := fmt.Sprintf("%d-%d", score.Home, score.Away)
|
||||||
if outcome.OddName == expectedScore {
|
if outcome.OddName == expectedScore {
|
||||||
|
|
@ -64,6 +68,8 @@ func evaluateCorrectScore(outcome domain.BetOutcome, score struct{ Home, Away in
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Half Time Result betting is a type of bet where the bettor predicts the outcome of a match at the end of the first half.
|
||||||
|
// This is the same as the full time result but only for the first half of the game
|
||||||
func evaluateHalfTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateHalfTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
return evaluateFullTimeResult(outcome, score)
|
return evaluateFullTimeResult(outcome, score)
|
||||||
}
|
}
|
||||||
|
|
@ -71,43 +77,90 @@ func evaluateHalfTimeResult(outcome domain.BetOutcome, score struct{ Home, Away
|
||||||
// This is a multiple outcome checker for the asian handicap and other kinds of bets
|
// This is a multiple outcome checker for the asian handicap and other kinds of bets
|
||||||
// The only outcome that are allowed are "Both Bets win", "Both Bets Lose", "Half Win and Half Void"
|
// The only outcome that are allowed are "Both Bets win", "Both Bets Lose", "Half Win and Half Void"
|
||||||
func checkMultiOutcome(outcome domain.OutcomeStatus, secondOutcome domain.OutcomeStatus) (domain.OutcomeStatus, error) {
|
func checkMultiOutcome(outcome domain.OutcomeStatus, secondOutcome domain.OutcomeStatus) (domain.OutcomeStatus, error) {
|
||||||
|
if secondOutcome == domain.OUTCOME_STATUS_PENDING {
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("cannot check pending outcome")
|
||||||
|
}
|
||||||
|
|
||||||
|
if outcome == domain.OUTCOME_STATUS_ERROR || secondOutcome == domain.OUTCOME_STATUS_ERROR {
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("❌ mutli outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
|
||||||
|
}
|
||||||
|
|
||||||
switch outcome {
|
switch outcome {
|
||||||
case domain.OUTCOME_STATUS_PENDING:
|
case domain.OUTCOME_STATUS_PENDING:
|
||||||
return secondOutcome, nil
|
return secondOutcome, nil
|
||||||
case domain.OUTCOME_STATUS_WIN:
|
case domain.OUTCOME_STATUS_WIN:
|
||||||
if secondOutcome == domain.OUTCOME_STATUS_WIN {
|
if secondOutcome == domain.OUTCOME_STATUS_WIN {
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
} else if secondOutcome == domain.OUTCOME_STATUS_LOSS {
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
} else if secondOutcome == domain.OUTCOME_STATUS_HALF {
|
||||||
|
return domain.OUTCOME_STATUS_HALF, nil
|
||||||
} else if secondOutcome == domain.OUTCOME_STATUS_VOID {
|
} else if secondOutcome == domain.OUTCOME_STATUS_VOID {
|
||||||
return domain.OUTCOME_STATUS_HALF, nil
|
return domain.OUTCOME_STATUS_HALF, nil
|
||||||
} else {
|
} else {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid multi outcome")
|
fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
|
||||||
}
|
}
|
||||||
case domain.OUTCOME_STATUS_LOSS:
|
case domain.OUTCOME_STATUS_LOSS:
|
||||||
if secondOutcome == domain.OUTCOME_STATUS_LOSS {
|
if secondOutcome == domain.OUTCOME_STATUS_LOSS ||
|
||||||
|
secondOutcome == domain.OUTCOME_STATUS_WIN ||
|
||||||
|
secondOutcome == domain.OUTCOME_STATUS_HALF {
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
} else if secondOutcome == domain.OUTCOME_STATUS_VOID {
|
} else if secondOutcome == domain.OUTCOME_STATUS_VOID {
|
||||||
return domain.OUTCOME_STATUS_HALF, nil
|
return domain.OUTCOME_STATUS_HALF, nil
|
||||||
} else {
|
} else {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid multi outcome")
|
fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
|
||||||
}
|
}
|
||||||
case domain.OUTCOME_STATUS_VOID:
|
case domain.OUTCOME_STATUS_VOID:
|
||||||
if secondOutcome == domain.OUTCOME_STATUS_WIN || secondOutcome == domain.OUTCOME_STATUS_LOSS {
|
if secondOutcome == domain.OUTCOME_STATUS_WIN || secondOutcome == domain.OUTCOME_STATUS_LOSS {
|
||||||
return domain.OUTCOME_STATUS_HALF, nil
|
return domain.OUTCOME_STATUS_HALF, nil
|
||||||
|
} else if secondOutcome == domain.OUTCOME_STATUS_VOID || secondOutcome == domain.OUTCOME_STATUS_HALF {
|
||||||
|
return domain.OUTCOME_STATUS_VOID, nil
|
||||||
} else {
|
} else {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid multi outcome")
|
fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
|
||||||
|
}
|
||||||
|
case domain.OUTCOME_STATUS_HALF:
|
||||||
|
if secondOutcome == domain.OUTCOME_STATUS_WIN || secondOutcome == domain.OUTCOME_STATUS_HALF {
|
||||||
|
return domain.OUTCOME_STATUS_HALF, nil
|
||||||
|
} else if secondOutcome == domain.OUTCOME_STATUS_LOSS {
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
} else if secondOutcome == domain.OUTCOME_STATUS_VOID {
|
||||||
|
return domain.OUTCOME_STATUS_VOID, nil
|
||||||
|
} else {
|
||||||
|
fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid multi outcome")
|
fmt.Printf("❌ multi outcome: %v -> %v \n", outcome.String(), secondOutcome.String())
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid multi outcome")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Asian Handicap betting is a type of betting that eliminates the possibility of a draw by giving one team a virtual advantage or disadvantage.
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "id": "548319135",
|
||||||
|
// "odds": "1.750",
|
||||||
|
// "header": "1",
|
||||||
|
// "handicap": "+0.5, +1.0"
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "id": "548319139",
|
||||||
|
// "odds": "1.950",
|
||||||
|
// "header": "2",
|
||||||
|
// "handicap": "-0.5, -1.0"
|
||||||
|
// }
|
||||||
func evaluateAsianHandicap(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateAsianHandicap(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
handicapList := strings.Split(outcome.OddHandicap, ",")
|
handicapList := strings.Split(outcome.OddHandicap, ",")
|
||||||
newOutcome := domain.OUTCOME_STATUS_PENDING
|
newOutcome := domain.OUTCOME_STATUS_PENDING
|
||||||
for _, handicapStr := range handicapList {
|
for _, handicapStr := range handicapList {
|
||||||
|
handicapStr = strings.TrimSpace(handicapStr)
|
||||||
handicap, err := strconv.ParseFloat(handicapStr, 64)
|
handicap, err := strconv.ParseFloat(handicapStr, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||||
}
|
}
|
||||||
adjustedHomeScore := float64(score.Home)
|
adjustedHomeScore := float64(score.Home)
|
||||||
adjustedAwayScore := float64(score.Away)
|
adjustedAwayScore := float64(score.Away)
|
||||||
|
|
@ -116,49 +169,117 @@ func evaluateAsianHandicap(outcome domain.BetOutcome, score struct{ Home, Away i
|
||||||
} else if outcome.OddHeader == "2" { // Away team
|
} else if outcome.OddHeader == "2" { // Away team
|
||||||
adjustedAwayScore += handicap
|
adjustedAwayScore += handicap
|
||||||
} else {
|
} else {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
if adjustedHomeScore > adjustedAwayScore {
|
if adjustedHomeScore > adjustedAwayScore {
|
||||||
if outcome.OddHeader == "1" {
|
if outcome.OddHeader == "1" {
|
||||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
|
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("multi outcome check error")
|
return domain.OUTCOME_STATUS_ERROR, err
|
||||||
return domain.OUTCOME_STATUS_PENDING, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
|
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("multi outcome check error")
|
return domain.OUTCOME_STATUS_ERROR, err
|
||||||
return domain.OUTCOME_STATUS_PENDING, err
|
|
||||||
}
|
}
|
||||||
} else if adjustedHomeScore < adjustedAwayScore {
|
} else if adjustedHomeScore < adjustedAwayScore {
|
||||||
if outcome.OddHeader == "2" {
|
if outcome.OddHeader == "2" {
|
||||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
|
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("multi outcome check error")
|
return domain.OUTCOME_STATUS_ERROR, err
|
||||||
return domain.OUTCOME_STATUS_PENDING, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
|
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("multi outcome check error")
|
return domain.OUTCOME_STATUS_ERROR, err
|
||||||
return domain.OUTCOME_STATUS_PENDING, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID)
|
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("multi outcome check error")
|
return domain.OUTCOME_STATUS_ERROR, err
|
||||||
return domain.OUTCOME_STATUS_PENDING, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newOutcome, nil
|
return newOutcome, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Goal Line betting, also known as Over/Under betting,
|
||||||
|
// involves predicting the total number of goals scored in a match, regardless of which team wins.
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "id": "548319141",
|
||||||
|
// "odds": "1.800",
|
||||||
|
// "header": "Over",
|
||||||
|
// "name": "1.5, 2.0"
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "id": "548319146",
|
||||||
|
// "odds": "1.900",
|
||||||
|
// "header": "Under",
|
||||||
|
// "name": "1.5, 2.0"
|
||||||
|
// }
|
||||||
func evaluateGoalLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateGoalLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
return evaluateGoalsOverUnder(outcome, score)
|
|
||||||
|
totalGoals := float64(score.Home + score.Away)
|
||||||
|
thresholdList := strings.Split(outcome.OddName, ",")
|
||||||
|
|
||||||
|
newOutcome := domain.OUTCOME_STATUS_PENDING
|
||||||
|
for _, thresholdStr := range thresholdList {
|
||||||
|
thresholdStr = strings.TrimSpace(thresholdStr)
|
||||||
|
threshold, err := strconv.ParseFloat(thresholdStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: '%s', %v", thresholdStr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
oddHeader := strings.TrimSpace(outcome.OddHeader)
|
||||||
|
if oddHeader == "Over" {
|
||||||
|
if totalGoals > threshold {
|
||||||
|
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
|
||||||
|
if err != nil {
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, err
|
||||||
|
}
|
||||||
|
} else if totalGoals == threshold {
|
||||||
|
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
|
||||||
|
if err != nil {
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, err
|
||||||
|
}
|
||||||
|
} else if oddHeader == "Under" {
|
||||||
|
if totalGoals < threshold {
|
||||||
|
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, err
|
||||||
|
}
|
||||||
|
} else if totalGoals == threshold {
|
||||||
|
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
|
||||||
|
if err != nil {
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, err
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: '%s'", oddHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOutcome, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First Team To Score betting is a type of bet where the bettor predicts which team will score first in a match.
|
||||||
|
// We can get this from the "events" field on the result json
|
||||||
func evaluateFirstTeamToScore(outcome domain.BetOutcome, events []map[string]string) (domain.OutcomeStatus, error) {
|
func evaluateFirstTeamToScore(outcome domain.BetOutcome, events []map[string]string) (domain.OutcomeStatus, error) {
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
if strings.Contains(event["text"], "1st Goal") || strings.Contains(event["text"], "Goal 1") {
|
if strings.Contains(event["text"], "1st Goal") || strings.Contains(event["text"], "Goal 1") {
|
||||||
|
|
@ -173,6 +294,7 @@ func evaluateFirstTeamToScore(outcome domain.BetOutcome, events []map[string]str
|
||||||
return domain.OUTCOME_STATUS_VOID, nil // No goals scored
|
return domain.OUTCOME_STATUS_VOID, nil // No goals scored
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Goals Odd/Even betting is a type of bet where the bettor predicts whether the total number of goals scored in a match will be odd or even.
|
||||||
func evaluateGoalsOddEven(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateGoalsOddEven(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
totalGoals := score.Home + score.Away
|
totalGoals := score.Home + score.Away
|
||||||
isOdd := totalGoals%2 == 1
|
isOdd := totalGoals%2 == 1
|
||||||
|
|
@ -184,6 +306,7 @@ func evaluateGoalsOddEven(outcome domain.BetOutcome, score struct{ Home, Away in
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Double Chance betting is a type of bet where the bettor predicts two of the three possible outcomes of a match.
|
||||||
func evaluateDoubleChance(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateDoubleChance(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
isHomeWin := score.Home > score.Away
|
isHomeWin := score.Home > score.Away
|
||||||
isDraw := score.Home == score.Away
|
isDraw := score.Home == score.Away
|
||||||
|
|
@ -206,10 +329,11 @@ func evaluateDoubleChance(outcome domain.BetOutcome, score struct{ Home, Away in
|
||||||
}
|
}
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw No Bet betting is a type of bet where the bettor predicts the outcome of a match, but if the match ends in a draw, the bet is voided.
|
||||||
func evaluateDrawNoBet(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateDrawNoBet(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
if score.Home == score.Away {
|
if score.Home == score.Away {
|
||||||
return domain.OUTCOME_STATUS_VOID, nil
|
return domain.OUTCOME_STATUS_VOID, nil
|
||||||
|
|
@ -222,8 +346,9 @@ func evaluateDrawNoBet(outcome domain.BetOutcome, score struct{ Home, Away int }
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// basketball evaluations
|
// Basketball evaluations
|
||||||
|
|
||||||
|
// Game Lines is an aggregate of money line, spread and total betting markets in one
|
||||||
func evaluateGameLines(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateGameLines(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
switch outcome.OddName {
|
switch outcome.OddName {
|
||||||
case "Money Line":
|
case "Money Line":
|
||||||
|
|
@ -235,10 +360,11 @@ func evaluateGameLines(outcome domain.BetOutcome, score struct{ Home, Away int }
|
||||||
case "Total":
|
case "Total":
|
||||||
return evaluateTotalOverUnder(outcome, score)
|
return evaluateTotalOverUnder(outcome, score)
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Money Line betting is a type of bet where the bettor predicts the outcome of a match without any point spread.
|
||||||
func evaluateMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
switch outcome.OddHeader {
|
switch outcome.OddHeader {
|
||||||
case "1":
|
case "1":
|
||||||
|
|
@ -258,21 +384,22 @@ func evaluateMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }
|
||||||
}
|
}
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Total Over/Under betting is a type of bet where the bettor predicts whether the total number of points scored in a match will be over or under a specified number.
|
||||||
func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
|
||||||
// The handicap will be in the format "U {float}" or "O {float}"
|
// The handicap will be in the format "U {float}" or "O {float}"
|
||||||
// U and O denoting over and under for this case
|
// U and O denoting over and under for this case
|
||||||
overUnderStr := strings.Split(outcome.OddHandicap, " ")
|
overUnderStr := strings.Split(outcome.OddHandicap, " ")
|
||||||
if len(overUnderStr) != 2 {
|
if len(overUnderStr) != 2 {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
threshold, err := strconv.ParseFloat(overUnderStr[1], 64)
|
threshold, err := strconv.ParseFloat(overUnderStr[1], 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet
|
// Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet
|
||||||
|
|
@ -294,26 +421,28 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
|
||||||
}
|
}
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
}
|
}
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Result and Total betting is a type of bet where the bettor predicts
|
||||||
|
// the outcome of a match and whether the total number of points scored will be over or under a specified number.
|
||||||
func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
|
||||||
// The handicap will be in the format "U {float}" or "O {float}"
|
// The handicap will be in the format "U {float}" or "O {float}"
|
||||||
// U and O denoting over and under for this case
|
// U and O denoting over and under for this case
|
||||||
overUnderStr := strings.Split(outcome.OddHandicap, " ")
|
overUnderStr := strings.Split(outcome.OddHandicap, " ")
|
||||||
if len(overUnderStr) != 2 {
|
if len(overUnderStr) != 2 {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
|
|
||||||
overUnder := overUnderStr[0]
|
overUnder := overUnderStr[0]
|
||||||
|
|
||||||
if overUnder != "Over" && overUnder != "Under" {
|
if overUnder != "Over" && overUnder != "Under" {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed parsing over under: %s", outcome.OddHeader)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing over under: %s", outcome.OddHeader)
|
||||||
}
|
}
|
||||||
threshold, err := strconv.ParseFloat(overUnderStr[1], 64)
|
threshold, err := strconv.ParseFloat(overUnderStr[1], 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet
|
// Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet
|
||||||
|
|
@ -321,6 +450,10 @@ func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away
|
||||||
|
|
||||||
switch outcome.OddHeader {
|
switch outcome.OddHeader {
|
||||||
case "1":
|
case "1":
|
||||||
|
if score.Home < score.Away {
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
|
|
||||||
if overUnder == "Over" && totalScore > threshold {
|
if overUnder == "Over" && totalScore > threshold {
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
} else if overUnder == "Under" && totalScore < threshold {
|
} else if overUnder == "Under" && totalScore < threshold {
|
||||||
|
|
@ -328,6 +461,9 @@ func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away
|
||||||
}
|
}
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
case "2":
|
case "2":
|
||||||
|
if score.Away < score.Home {
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
if overUnder == "Over" && totalScore > threshold {
|
if overUnder == "Over" && totalScore > threshold {
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
} else if overUnder == "Under" && totalScore < threshold {
|
} else if overUnder == "Under" && totalScore < threshold {
|
||||||
|
|
@ -336,27 +472,29 @@ func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away
|
||||||
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed to parse over and under: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed to parse over and under: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Team Total betting is a type of bet where the bettor predicts the total number of points scored by a specific team in a match
|
||||||
|
// is over or under a specified number.
|
||||||
func evaluateTeamTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateTeamTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
|
||||||
// The handicap will be in the format "U {float}" or "O {float}"
|
// The handicap will be in the format "U {float}" or "O {float}"
|
||||||
// U and O denoting over and under for this case
|
// U and O denoting over and under for this case
|
||||||
overUnderStr := strings.Split(outcome.OddHandicap, " ")
|
overUnderStr := strings.Split(outcome.OddHandicap, " ")
|
||||||
if len(overUnderStr) != 2 {
|
if len(overUnderStr) != 2 {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddHandicap)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddHandicap)
|
||||||
}
|
}
|
||||||
|
|
||||||
overUnder := overUnderStr[0]
|
overUnder := overUnderStr[0]
|
||||||
|
|
||||||
if overUnder != "Over" && overUnder != "Under" {
|
if overUnder != "Over" && overUnder != "Under" {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed parsing over under: %s", outcome.OddHeader)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing over under: %s", outcome.OddHeader)
|
||||||
}
|
}
|
||||||
threshold, err := strconv.ParseFloat(overUnderStr[1], 64)
|
threshold, err := strconv.ParseFloat(overUnderStr[1], 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddHandicap)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddHandicap)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet
|
// Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet
|
||||||
|
|
@ -380,11 +518,12 @@ func evaluateTeamTotal(outcome domain.BetOutcome, score struct{ Home, Away int }
|
||||||
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed to parse over and under: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed to parse over and under: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate Result and Both Teams To Score X Points
|
// Result and Both Teams To Score X Points is a type of bet where the bettor predicts whether both teams will score a certain number of points
|
||||||
|
// and also the result fo the match
|
||||||
func evaluateResultAndBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateResultAndBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
|
||||||
// The name parameter will hold value "name": "{team_name} and {Yes | No}"
|
// The name parameter will hold value "name": "{team_name} and {Yes | No}"
|
||||||
|
|
@ -400,14 +539,14 @@ func evaluateResultAndBTTSX(outcome domain.BetOutcome, score struct{ Home, Away
|
||||||
} else if scoreCheckSplit == "No" {
|
} else if scoreCheckSplit == "No" {
|
||||||
isScorePoints = false
|
isScorePoints = false
|
||||||
} else {
|
} else {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
|
|
||||||
teamName := strings.TrimSpace(strings.Join(oddNameSplit[:len(oddNameSplit)-2], ""))
|
teamName := strings.TrimSpace(strings.Join(oddNameSplit[:len(oddNameSplit)-2], ""))
|
||||||
|
|
||||||
threshold, err := strconv.ParseInt(outcome.OddHeader, 10, 64)
|
threshold, err := strconv.ParseInt(outcome.OddHeader, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddHeader)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch teamName {
|
switch teamName {
|
||||||
|
|
@ -428,18 +567,18 @@ func evaluateResultAndBTTSX(outcome domain.BetOutcome, score struct{ Home, Away
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("team name error: %s", teamName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("team name error: %s", teamName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Both Teams To Score X Points
|
// Both Teams To Score X Points is a type of bet where the bettor predicts whether both teams will score a certain number of points.
|
||||||
func evaluateBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
threshold, err := strconv.ParseInt(outcome.OddName, 10, 64)
|
threshold, err := strconv.ParseInt(outcome.OddName, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch outcome.OddHeader {
|
switch outcome.OddHeader {
|
||||||
|
|
@ -453,12 +592,13 @@ func evaluateBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (d
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Money Line 3 Way betting is a type of bet where the bettor predicts the outcome of a match with three possible outcomes: home win, away win, or draw.
|
||||||
func evaluateMoneyLine3Way(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateMoneyLine3Way(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
switch outcome.OddName {
|
switch outcome.OddName {
|
||||||
case "1": // Home win
|
case "1": // Home win
|
||||||
|
|
@ -477,23 +617,24 @@ func evaluateMoneyLine3Way(outcome domain.BetOutcome, score struct{ Home, Away i
|
||||||
}
|
}
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Double Result betting is a type of bet where the bettor predicts the outcome of a match at both half-time and full-time.
|
||||||
func evaluateDoubleResult(outcome domain.BetOutcome, firstHalfScore struct{ Home, Away int }, secondHalfScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateDoubleResult(outcome domain.BetOutcome, firstHalfScore struct{ Home, Away int }, secondHalfScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
halfWins := strings.Split(outcome.OddName, "-")
|
halfWins := strings.Split(outcome.OddName, "-")
|
||||||
if len(halfWins) != 2 {
|
if len(halfWins) != 2 {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
firstHalfWinner := strings.TrimSpace(halfWins[0])
|
firstHalfWinner := strings.TrimSpace(halfWins[0])
|
||||||
secondHalfWinner := strings.TrimSpace(halfWins[1])
|
secondHalfWinner := strings.TrimSpace(halfWins[1])
|
||||||
|
|
||||||
if firstHalfWinner != outcome.HomeTeamName && firstHalfWinner != outcome.AwayTeamName && firstHalfWinner != "Tie" {
|
if firstHalfWinner != outcome.HomeTeamName && firstHalfWinner != outcome.AwayTeamName && firstHalfWinner != "Tie" {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
|
||||||
}
|
}
|
||||||
if secondHalfWinner != outcome.HomeTeamName && secondHalfWinner != outcome.AwayTeamName && secondHalfWinner != "Tie" {
|
if secondHalfWinner != outcome.HomeTeamName && secondHalfWinner != outcome.AwayTeamName && secondHalfWinner != "Tie" {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|
@ -517,6 +658,7 @@ func evaluateDoubleResult(outcome domain.BetOutcome, firstHalfScore struct{ Home
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Highest Scoring Half betting is a type of bet where the bettor predicts which half of the match will have the highest total score.
|
||||||
func evaluateHighestScoringHalf(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateHighestScoringHalf(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
firstHalfTotal := firstScore.Home + firstScore.Away
|
firstHalfTotal := firstScore.Home + firstScore.Away
|
||||||
secondHalfTotal := secondScore.Home + secondScore.Away
|
secondHalfTotal := secondScore.Home + secondScore.Away
|
||||||
|
|
@ -534,11 +676,12 @@ func evaluateHighestScoringHalf(outcome domain.BetOutcome, firstScore struct{ Ho
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Highest Scoring Quarter betting is a type of bet where the bettor predicts which quarter of the match will have the highest score.
|
||||||
func evaluateHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }, fourthScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }, fourthScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
firstQuarterTotal := firstScore.Home + firstScore.Away
|
firstQuarterTotal := firstScore.Home + firstScore.Away
|
||||||
secondQuarterTotal := secondScore.Home + secondScore.Away
|
secondQuarterTotal := secondScore.Home + secondScore.Away
|
||||||
|
|
@ -567,18 +710,20 @@ func evaluateHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handicap and Total betting is a combination of spread betting and total points betting
|
||||||
|
// where the bettor predicts the outcome of a match with a point spread and the total number of points scored is over or under a specified number.
|
||||||
func evaluateHandicapAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateHandicapAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
|
||||||
nameSplit := strings.Split(outcome.OddName, " ")
|
nameSplit := strings.Split(outcome.OddName, " ")
|
||||||
// Evaluate from bottom to get the threshold and find out if its over or under
|
// Evaluate from bottom to get the threshold and find out if its over or under
|
||||||
threshold, err := strconv.ParseFloat(nameSplit[len(nameSplit)-1], 10)
|
threshold, err := strconv.ParseFloat(nameSplit[len(nameSplit)-1], 10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed parsing threshold: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing threshold: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
total := float64(score.Home + score.Away)
|
total := float64(score.Home + score.Away)
|
||||||
overUnder := nameSplit[len(nameSplit)-2]
|
overUnder := nameSplit[len(nameSplit)-2]
|
||||||
|
|
@ -591,12 +736,12 @@ func evaluateHandicapAndTotal(outcome domain.BetOutcome, score struct{ Home, Awa
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed parsing over and under: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing over and under: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
|
|
||||||
handicap, err := strconv.ParseFloat(nameSplit[len(nameSplit)-4], 10)
|
handicap, err := strconv.ParseFloat(nameSplit[len(nameSplit)-4], 10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed parsing handicap: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing handicap: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
|
|
||||||
teamName := strings.TrimSpace(strings.Join(nameSplit[:len(nameSplit)-4], ""))
|
teamName := strings.TrimSpace(strings.Join(nameSplit[:len(nameSplit)-4], ""))
|
||||||
|
|
@ -618,21 +763,22 @@ func evaluateHandicapAndTotal(outcome domain.BetOutcome, score struct{ Home, Awa
|
||||||
}
|
}
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed parsing team name: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing team name: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Winning Margin betting is a type of bet where the bettor predicts the margin of victory in a match.
|
||||||
func evaluateWinningMargin(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateWinningMargin(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
|
||||||
marginSplit := strings.Split(outcome.OddName, "")
|
marginSplit := strings.Split(outcome.OddName, "")
|
||||||
if len(marginSplit) < 1 {
|
if len(marginSplit) < 1 {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
|
|
||||||
margin, err := strconv.ParseInt(marginSplit[0], 10, 64)
|
margin, err := strconv.ParseInt(marginSplit[0], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
|
|
||||||
isGtr := false
|
isGtr := false
|
||||||
|
|
@ -656,9 +802,10 @@ func evaluateWinningMargin(outcome domain.BetOutcome, score struct{ Home, Away i
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddheader: %s", outcome.OddHeader)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddheader: %s", outcome.OddHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Highest Scoring Period betting is a type of bet where the bettor predicts which period of the match will have the highest total score.
|
||||||
func evaluateHighestScoringPeriod(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateHighestScoringPeriod(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
firstPeriodTotal := firstScore.Home + firstScore.Away
|
firstPeriodTotal := firstScore.Home + firstScore.Away
|
||||||
secondPeriodTotal := secondScore.Home + secondScore.Away
|
secondPeriodTotal := secondScore.Home + secondScore.Away
|
||||||
|
|
@ -682,11 +829,12 @@ func evaluateHighestScoringPeriod(outcome domain.BetOutcome, firstScore struct{
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tied After Regulation is a type of bet where the bettor predicts whether the match will end in a tie after regulation time.
|
||||||
func evaluateTiedAfterRegulation(outcome domain.BetOutcome, scores []struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func evaluateTiedAfterRegulation(outcome domain.BetOutcome, scores []struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
totalScore := struct{ Home, Away int }{0, 0}
|
totalScore := struct{ Home, Away int }{0, 0}
|
||||||
for _, score := range scores {
|
for _, score := range scores {
|
||||||
|
|
@ -706,6 +854,5 @@ func evaluateTiedAfterRegulation(outcome domain.BetOutcome, scores []struct{ Hom
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
30
internal/services/result/football_test.go
Normal file
30
internal/services/result/football_test.go
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
package result
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEvaluateFullTimeResult(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
outcome domain.BetOutcome
|
||||||
|
score struct{ Home, Away int }
|
||||||
|
expected domain.OutcomeStatus
|
||||||
|
}{
|
||||||
|
{"Home win", domain.BetOutcome{OddName: "1"}, struct{ Home, Away int }{2, 1}, domain.OUTCOME_STATUS_WIN},
|
||||||
|
{"Away win", domain.BetOutcome{OddName: "2"}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_WIN},
|
||||||
|
{"Draw", domain.BetOutcome{OddName: "Draw"}, struct{ Home, Away int }{1, 1}, domain.OUTCOME_STATUS_WIN},
|
||||||
|
{"Home selected, but Draw", domain.BetOutcome{OddName: "1"}, struct{ Home, Away int }{1, 1}, domain.OUTCOME_STATUS_LOSS},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
status, _ := evaluateFullTimeResult(tt.outcome, tt.score)
|
||||||
|
if status != tt.expected {
|
||||||
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -45,9 +45,9 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
||||||
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))
|
||||||
|
for i, event := range events {
|
||||||
for _, event := range events {
|
fmt.Printf("🕛 Checking if event has bets placed on it %v (%d/%d) \n", event.ID, i+1, len(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")
|
||||||
|
|
@ -59,46 +59,89 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, outcome := range outcomes {
|
if len(outcomes) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
isDeleted := true
|
||||||
|
for j, outcome := range outcomes {
|
||||||
|
fmt.Printf("⚙️ Processing 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
|
||||||
|
outcome.MarketName,
|
||||||
|
event.HomeTeam+" "+event.AwayTeam, event.ID,
|
||||||
|
j+1, len(outcomes))
|
||||||
|
|
||||||
if outcome.Expires.After(time.Now()) {
|
if outcome.Expires.After(time.Now()) {
|
||||||
|
isDeleted = false
|
||||||
|
s.logger.Info("Outcome is not expired yet", "event_id", event.ID, "outcome_id", outcome.ID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
|
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Sport ID is invalid", "event_id", outcome.EventID, "error", err)
|
s.logger.Error("Sport ID is invalid", "event_id", outcome.EventID, "error", err)
|
||||||
|
isDeleted = false
|
||||||
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
|
||||||
result, err := s.fetchResult(ctx, outcome.EventID, outcome.OddID, outcome.MarketID, sportID, outcome)
|
result, err := s.fetchResult(ctx, outcome.EventID, outcome.OddID, outcome.MarketID, sportID, outcome)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to fetch result", "event_id", outcome.EventID, "error", err)
|
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
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// _, err = s.repo.CreateResult(ctx, domain.CreateResult{
|
outcome, err = s.betSvc.UpdateBetOutcomeStatus(ctx, outcome.ID, result.Status)
|
||||||
// BetOutcomeID: outcome.ID,
|
|
||||||
// EventID: outcome.EventID,
|
|
||||||
// OddID: outcome.OddID,
|
|
||||||
// MarketID: outcome.MarketID,
|
|
||||||
// Status: result.Status,
|
|
||||||
// Score: result.Score,
|
|
||||||
// })
|
|
||||||
// if err != nil {
|
|
||||||
// s.logger.Error("Failed to store result", "bet_outcome_id", outcome.ID, "error", err)
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
|
|
||||||
_, err = s.repo.UpdateBetOutcomeStatus(ctx, outcome.ID, result.Status)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
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)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if outcome.Status == domain.OUTCOME_STATUS_ERROR || outcome.Status == domain.OUTCOME_STATUS_PENDING {
|
||||||
|
fmt.Printf("❌ Error while updating 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
|
||||||
|
outcome.MarketName,
|
||||||
|
event.HomeTeam+" "+event.AwayTeam, event.ID,
|
||||||
|
j+1, len(outcomes))
|
||||||
|
|
||||||
|
s.logger.Error("Outcome is pending or error", "event_id", outcome.EventID, "outcome_id", outcome.ID)
|
||||||
|
isDeleted = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("✅ Successfully updated 🎲 outcomes '%v' for event %v(%v) (%d/%d) \n",
|
||||||
|
outcome.MarketName,
|
||||||
|
event.HomeTeam+" "+event.AwayTeam, event.ID,
|
||||||
|
j+1, len(outcomes))
|
||||||
|
|
||||||
|
status, err := s.betSvc.CheckBetOutcomeForBet(ctx, outcome.BetID)
|
||||||
|
if err != nil {
|
||||||
|
if err != bet.ErrOutcomesNotCompleted {
|
||||||
|
s.logger.Error("Failed to check bet outcome for bet", "event_id", outcome.EventID, "error", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("🧾 Updating bet status for event %v (%d/%d) to %v\n", event.ID, j+1, len(outcomes), status.String())
|
||||||
|
err = s.betSvc.UpdateStatus(ctx, outcome.BetID, status)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to update bet status", "event id", outcome.EventID, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("✅ Successfully updated 🎫 Bet for event %v(%v) (%d/%d) \n",
|
||||||
|
event.HomeTeam+" "+event.AwayTeam, event.ID,
|
||||||
|
j+1, len(outcomes))
|
||||||
|
|
||||||
}
|
}
|
||||||
err = s.repo.DeleteEvent(ctx, event.ID)
|
if isDeleted {
|
||||||
if err != nil {
|
// err = s.repo.DeleteEvent(ctx, event.ID)
|
||||||
s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err)
|
// if err != nil {
|
||||||
return err
|
// s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err)
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -248,7 +291,7 @@ func (s *Service) parseFootball(resultRes json.RawMessage, eventID, oddID, marke
|
||||||
corners := parseStats(result.Stats.Corners)
|
corners := parseStats(result.Stats.Corners)
|
||||||
status, err := s.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, corners, result.Events)
|
status, err := s.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, corners, result.Events)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
|
s.logger.Error("Failed to evaluate football outcome", "event_id", eventID, "market_id", marketID, "error", err)
|
||||||
return domain.CreateResult{}, err
|
return domain.CreateResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package result
|
|
||||||
|
|
@ -37,3 +37,7 @@ func (s *Service) UpdateTicketOutcomeStatus(ctx context.Context, id int64, statu
|
||||||
func (s *Service) DeleteTicket(ctx context.Context, id int64) error {
|
func (s *Service) DeleteTicket(ctx context.Context, id int64) error {
|
||||||
return s.ticketStore.DeleteTicket(ctx, id)
|
return s.ticketStore.DeleteTicket(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeleteOldTickets(ctx context.Context) error {
|
||||||
|
return s.ticketStore.DeleteOldTickets(ctx)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package httpserver
|
package httpserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// "context"
|
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
// "time"
|
// "time"
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
resultsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
resultsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -20,14 +21,14 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
||||||
spec string
|
spec string
|
||||||
task func()
|
task func()
|
||||||
}{
|
}{
|
||||||
{
|
// {
|
||||||
spec: "0 0 * * * *", // Every 1 hour
|
// spec: "0 0 * * * *", // Every 1 hour
|
||||||
task: func() {
|
// task: func() {
|
||||||
if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
|
// if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
|
||||||
log.Printf("FetchUpcomingEvents error: %v", err)
|
// log.Printf("FetchUpcomingEvents error: %v", err)
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
|
|
||||||
// {
|
// {
|
||||||
// spec: "*/5 * * * * *", // Every 5 seconds
|
// spec: "*/5 * * * * *", // Every 5 seconds
|
||||||
|
|
@ -37,14 +38,14 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
{
|
// {
|
||||||
spec: "0 */15 * * * *", // Every 15 minutes
|
// spec: "0 */15 * * * *", // Every 15 minutes
|
||||||
task: func() {
|
// task: func() {
|
||||||
if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
|
// if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
|
||||||
log.Printf("FetchNonLiveOdds error: %v", err)
|
// log.Printf("FetchNonLiveOdds error: %v", err)
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
// {
|
// {
|
||||||
// spec: "0 */15 * * * *",
|
// spec: "0 */15 * * * *",
|
||||||
// task: func() {
|
// task: func() {
|
||||||
|
|
@ -80,6 +81,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, job := range schedule {
|
for _, job := range schedule {
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
@ -88,3 +90,34 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
||||||
c.Start()
|
c.Start()
|
||||||
log.Println("Cron jobs started for event and odds services")
|
log.Println("Cron jobs started for event and odds services")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StartTicketCrons(ticketService ticket.Service) {
|
||||||
|
c := cron.New(cron.WithSeconds())
|
||||||
|
|
||||||
|
schedule := []struct {
|
||||||
|
spec string
|
||||||
|
task func()
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
spec: "0 0 * * * *", // Every hour
|
||||||
|
task: func() {
|
||||||
|
log.Println("Deleting old tickets...")
|
||||||
|
if err := ticketService.DeleteOldTickets(context.Background()); err != nil {
|
||||||
|
log.Printf("Failed to remove old ticket: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Successfully deleted old tickets")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, job := range schedule {
|
||||||
|
job.task()
|
||||||
|
if _, err := c.AddFunc(job.spec, job.task); err != nil {
|
||||||
|
log.Fatalf("Failed to schedule cron job: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Start()
|
||||||
|
log.Println("Cron jobs started for ticket service")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
@ -15,7 +17,7 @@ import (
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param createBet body domain.CreateBetReq true "Creates bet"
|
// @Param createBet body domain.CreateBetReq true "Creates bet"
|
||||||
// @Success 200 {object} BetRes
|
// @Success 200 {object} domain.BetRes
|
||||||
// @Failure 400 {object} response.APIResponse
|
// @Failure 400 {object} response.APIResponse
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /bet [post]
|
// @Router /bet [post]
|
||||||
|
|
@ -54,7 +56,7 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param createBet body domain.RandomBetReq true "Create Random bet"
|
// @Param createBet body domain.RandomBetReq true "Create Random bet"
|
||||||
// @Success 200 {object} BetRes
|
// @Success 200 {object} domain.BetRes
|
||||||
// @Failure 400 {object} response.APIResponse
|
// @Failure 400 {object} response.APIResponse
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /random/bet [post]
|
// @Router /random/bet [post]
|
||||||
|
|
@ -64,6 +66,45 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
|
||||||
userID := c.Locals("user_id").(int64)
|
userID := c.Locals("user_id").(int64)
|
||||||
// role := c.Locals("role").(domain.Role)
|
// role := c.Locals("role").(domain.Role)
|
||||||
|
|
||||||
|
leagueIDQuery := c.Query("league_id")
|
||||||
|
sportIDQuery := c.Query("sport_id")
|
||||||
|
firstStartTimeQuery := c.Query("first_start_time")
|
||||||
|
lastStartTimeQuery := c.Query("last_start_time")
|
||||||
|
|
||||||
|
leagueID := domain.ValidString{
|
||||||
|
Value: leagueIDQuery,
|
||||||
|
Valid: leagueIDQuery != "",
|
||||||
|
}
|
||||||
|
sportID := domain.ValidString{
|
||||||
|
Value: sportIDQuery,
|
||||||
|
Valid: sportIDQuery != "",
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstStartTime domain.ValidTime
|
||||||
|
if firstStartTimeQuery != "" {
|
||||||
|
firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("invalid start_time format", "error", err)
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
|
||||||
|
}
|
||||||
|
firstStartTime = domain.ValidTime{
|
||||||
|
Value: firstStartTimeParsed,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var lastStartTime domain.ValidTime
|
||||||
|
if lastStartTimeQuery != "" {
|
||||||
|
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("invalid start_time format", "error", err)
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
|
||||||
|
}
|
||||||
|
lastStartTime = domain.ValidTime{
|
||||||
|
Value: lastStartTimeParsed,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var req domain.RandomBetReq
|
var req domain.RandomBetReq
|
||||||
if err := c.BodyParser(&req); err != nil {
|
if err := c.BodyParser(&req); err != nil {
|
||||||
h.logger.Error("Failed to parse RandomBet request", "error", err)
|
h.logger.Error("Failed to parse RandomBet request", "error", err)
|
||||||
|
|
@ -75,10 +116,14 @@ func (h *Handler) RandomBet(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.PlaceRandomBet(c.Context(), userID, req.BranchID)
|
res, err := h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID, leagueID, sportID, firstStartTime, lastStartTime)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error("Random Bet failed", "error", err)
|
h.logger.Error("Random Bet failed", "error", err)
|
||||||
|
switch err {
|
||||||
|
case bet.ErrNoEventsAvailable:
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "No events found")
|
||||||
|
}
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Unable to create random bet")
|
return fiber.NewError(fiber.StatusInternalServerError, "Unable to create random bet")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,7 +137,7 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
|
||||||
// @Tags bet
|
// @Tags bet
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {array} BetRes
|
// @Success 200 {array} domain.BetRes
|
||||||
// @Failure 400 {object} response.APIResponse
|
// @Failure 400 {object} response.APIResponse
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /bet [get]
|
// @Router /bet [get]
|
||||||
|
|
@ -118,7 +163,7 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path int true "Bet ID"
|
// @Param id path int true "Bet ID"
|
||||||
// @Success 200 {object} BetRes
|
// @Success 200 {object} domain.BetRes
|
||||||
// @Failure 400 {object} response.APIResponse
|
// @Failure 400 {object} response.APIResponse
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /bet/{id} [get]
|
// @Router /bet/{id} [get]
|
||||||
|
|
@ -149,7 +194,7 @@ func (h *Handler) GetBetByID(c *fiber.Ctx) error {
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "cashout ID"
|
// @Param id path string true "cashout ID"
|
||||||
// @Success 200 {object} BetRes
|
// @Success 200 {object} domain.BetRes
|
||||||
// @Failure 400 {object} response.APIResponse
|
// @Failure 400 {object} response.APIResponse
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /bet/cashout/{id} [get]
|
// @Router /bet/cashout/{id} [get]
|
||||||
|
|
|
||||||
|
|
@ -498,7 +498,7 @@ func (h *Handler) GetBranchOperations(c *fiber.Ctx) error {
|
||||||
// @Tags branch
|
// @Tags branch
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {array} BetRes
|
// @Success 200 {array} domain.BetRes
|
||||||
// @Failure 400 {object} response.APIResponse
|
// @Failure 400 {object} response.APIResponse
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
// @Router /branch/{id}/bets [get]
|
// @Router /branch/{id}/bets [get]
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"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"
|
||||||
|
|
@ -106,6 +107,8 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
|
||||||
pageSize := c.QueryInt("page_size", 10)
|
pageSize := c.QueryInt("page_size", 10)
|
||||||
leagueIDQuery := c.Query("league_id")
|
leagueIDQuery := c.Query("league_id")
|
||||||
sportIDQuery := c.Query("sport_id")
|
sportIDQuery := c.Query("sport_id")
|
||||||
|
firstStartTimeQuery := c.Query("first_start_time")
|
||||||
|
lastStartTimeQuery := c.Query("last_start_time")
|
||||||
|
|
||||||
leagueID := domain.ValidString{
|
leagueID := domain.ValidString{
|
||||||
Value: leagueIDQuery,
|
Value: leagueIDQuery,
|
||||||
|
|
@ -116,7 +119,41 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
|
||||||
Valid: sportIDQuery != "",
|
Valid: sportIDQuery != "",
|
||||||
}
|
}
|
||||||
|
|
||||||
events, total, err := h.eventSvc.GetPaginatedUpcomingEvents(c.Context(), int32(pageSize), int32(page)-1, leagueID, sportID)
|
var firstStartTime domain.ValidTime
|
||||||
|
if firstStartTimeQuery != "" {
|
||||||
|
firstStartTimeParsed, err := time.Parse(time.RFC3339, firstStartTimeQuery)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("invalid start_time format", "error", err)
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
|
||||||
|
}
|
||||||
|
firstStartTime = domain.ValidTime{
|
||||||
|
Value: firstStartTimeParsed,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var lastStartTime domain.ValidTime
|
||||||
|
if lastStartTimeQuery != "" {
|
||||||
|
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("invalid start_time format", "error", err)
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
|
||||||
|
}
|
||||||
|
lastStartTime = domain.ValidTime{
|
||||||
|
Value: lastStartTimeParsed,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := domain.ValidInt64{
|
||||||
|
Value: int64(pageSize),
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
offset := domain.ValidInt64{
|
||||||
|
Value: int64(page - 1),
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
events, total, err := h.eventSvc.GetPaginatedUpcomingEvents(
|
||||||
|
c.Context(), limit, offset, leagueID, sportID, firstStartTime, lastStartTime)
|
||||||
|
|
||||||
// fmt.Printf("League ID: %v", leagueID)
|
// fmt.Printf("League ID: %v", leagueID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -183,7 +220,7 @@ func (h *Handler) GetPrematchOddsByUpcomingID(c *fiber.Ctx) error {
|
||||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid offset value", nil, nil)
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid offset value", nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
odds, err := h.prematchSvc.GetPrematchOddsByUpcomingID(c.Context(), upcomingID, int32(limit), int32(offset))
|
odds, err := h.prematchSvc.GetPrematchOddsByUpcomingID(c.Context(), upcomingID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve prematch odds", nil, nil)
|
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve prematch odds", nil, nil)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user