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)
|
||||
|
||||
httpserver.StartDataFetchingCrons(eventSvc, oddsSvc, resultSvc)
|
||||
httpserver.StartTicketCrons(*ticketSvc)
|
||||
|
||||
app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{
|
||||
JwtAccessKey: cfg.JwtKey,
|
||||
|
|
|
|||
|
|
@ -340,15 +340,43 @@ INSERT INTO users (
|
|||
suspended_at,
|
||||
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 (
|
||||
'Samuel',
|
||||
'Tariku',
|
||||
'cybersamt@gmail.com',
|
||||
NULL,
|
||||
'0911111111',
|
||||
crypt('password@123', gen_salt('bf'))::bytea,
|
||||
'super_admin',
|
||||
TRUE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP,
|
||||
NULL,
|
||||
|
|
@ -372,11 +400,11 @@ VALUES (
|
|||
'Kirubel',
|
||||
'Kibru',
|
||||
'kirubeljkl679 @gmail.com',
|
||||
NULL,
|
||||
'0911111111',
|
||||
crypt('password@123', gen_salt('bf'))::bytea,
|
||||
'super_admin',
|
||||
TRUE,
|
||||
FALSE,
|
||||
TRUE,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP,
|
||||
NULL,
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ WHERE branch_id = $1;
|
|||
SELECT *
|
||||
FROM bet_outcomes
|
||||
WHERE event_id = $1;
|
||||
-- name: GetBetOutcomeByBetID :many
|
||||
SELECT *
|
||||
FROM bet_outcomes
|
||||
WHERE bet_id = $1;
|
||||
-- name: UpdateCashOut :exec
|
||||
UPDATE bets
|
||||
SET cashed_out = $2,
|
||||
|
|
@ -74,9 +78,9 @@ WHERE id = $2
|
|||
RETURNING *;
|
||||
-- name: UpdateStatus :exec
|
||||
UPDATE bets
|
||||
SET status = $2,
|
||||
SET status = $1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1;
|
||||
WHERE id = $2;
|
||||
-- name: DeleteBet :exec
|
||||
DELETE FROM bets
|
||||
WHERE id = $1;
|
||||
|
|
|
|||
|
|
@ -196,15 +196,23 @@ FROM events
|
|||
WHERE is_live = false
|
||||
AND status = 'upcoming'
|
||||
AND (
|
||||
league_id = $3
|
||||
OR $3 IS NULL
|
||||
league_id = sqlc.narg('league_id')
|
||||
OR sqlc.narg('league_id') IS NULL
|
||||
)
|
||||
AND (
|
||||
sport_id = $4
|
||||
OR $4 IS NULL
|
||||
sport_id = sqlc.narg('sport_id')
|
||||
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
|
||||
LIMIT $1 OFFSET $2;
|
||||
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
||||
-- name: GetUpcomingByID :one
|
||||
SELECT id,
|
||||
sport_id,
|
||||
|
|
|
|||
|
|
@ -94,23 +94,17 @@ WHERE market_id = $1
|
|||
AND fi = $2
|
||||
AND is_active = true
|
||||
AND source = 'b365api';
|
||||
|
||||
-- name: GetPrematchOddsByUpcomingID :many
|
||||
SELECT 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
|
||||
SELECT o.*
|
||||
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';
|
||||
-- name: GetPaginatedPrematchOddsByUpcomingID :many
|
||||
SELECT o.*
|
||||
FROM odds o
|
||||
JOIN events e ON o.fi = e.id
|
||||
WHERE e.id = $1
|
||||
|
|
@ -118,4 +112,4 @@ WHERE e.id = $1
|
|||
AND e.status = 'upcoming'
|
||||
AND o.is_active = true
|
||||
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": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -341,7 +341,7 @@ const docTemplate = `{
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.CreateBetReq"
|
||||
"$ref": "#/definitions/domain.CreateBetReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -349,7 +349,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -393,7 +393,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -437,7 +437,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -786,7 +786,7 @@ const docTemplate = `{
|
|||
"schema": {
|
||||
"type": "array",
|
||||
"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": {
|
||||
"get": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -3501,6 +3658,15 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.RandomBetReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"branch_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.RawOddsByMarketID": {
|
||||
"type": "object",
|
||||
"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": {
|
||||
"type": "object",
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@
|
|||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -333,7 +333,7 @@
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.CreateBetReq"
|
||||
"$ref": "#/definitions/domain.CreateBetReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -341,7 +341,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -385,7 +385,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -429,7 +429,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.BetRes"
|
||||
"$ref": "#/definitions/domain.BetRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -778,7 +778,7 @@
|
|||
"schema": {
|
||||
"type": "array",
|
||||
"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": {
|
||||
"get": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -3493,6 +3650,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.RandomBetReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"branch_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.RawOddsByMarketID": {
|
||||
"type": "object",
|
||||
"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": {
|
||||
"type": "object",
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,82 @@ definitions:
|
|||
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||
example: 1
|
||||
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:
|
||||
properties:
|
||||
category:
|
||||
|
|
@ -130,6 +206,12 @@ definitions:
|
|||
description: BET, WIN, REFUND, JACKPOT_WIN
|
||||
type: string
|
||||
type: object
|
||||
domain.RandomBetReq:
|
||||
properties:
|
||||
branch_id:
|
||||
example: 1
|
||||
type: integer
|
||||
type: object
|
||||
domain.RawOddsByMarketID:
|
||||
properties:
|
||||
fetched_at:
|
||||
|
|
@ -309,47 +391,6 @@ definitions:
|
|||
updated_at:
|
||||
type: string
|
||||
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:
|
||||
properties:
|
||||
branch_manager_id:
|
||||
|
|
@ -465,41 +506,6 @@ definitions:
|
|||
example: "1234567890"
|
||||
type: string
|
||||
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:
|
||||
properties:
|
||||
branch_id:
|
||||
|
|
@ -1320,7 +1326,7 @@ paths:
|
|||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/handlers.BetRes'
|
||||
$ref: '#/definitions/domain.BetRes'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
|
|
@ -1343,14 +1349,14 @@ paths:
|
|||
name: createBet
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.CreateBetReq'
|
||||
$ref: '#/definitions/domain.CreateBetReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.BetRes'
|
||||
$ref: '#/definitions/domain.BetRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -1407,7 +1413,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.BetRes'
|
||||
$ref: '#/definitions/domain.BetRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -1470,7 +1476,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.BetRes'
|
||||
$ref: '#/definitions/domain.BetRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -1639,7 +1645,7 @@ paths:
|
|||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/handlers.BetRes'
|
||||
$ref: '#/definitions/domain.BetRes'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
|
|
@ -2385,6 +2391,36 @@ paths:
|
|||
summary: Retrieve raw odds by Market ID
|
||||
tags:
|
||||
- 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:
|
||||
get:
|
||||
consumes:
|
||||
|
|
|
|||
|
|
@ -243,6 +243,48 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, 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
|
||||
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
|
||||
|
|
@ -339,17 +381,17 @@ func (q *Queries) UpdateCashOut(ctx context.Context, arg UpdateCashOutParams) er
|
|||
|
||||
const UpdateStatus = `-- name: UpdateStatus :exec
|
||||
UPDATE bets
|
||||
SET status = $2,
|
||||
SET status = $1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1
|
||||
WHERE id = $2
|
||||
`
|
||||
|
||||
type UpdateStatusParams struct {
|
||||
ID int64 `json:"id"`
|
||||
Status int32 `json:"status"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,22 +201,32 @@ FROM events
|
|||
WHERE is_live = false
|
||||
AND status = 'upcoming'
|
||||
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
|
||||
)
|
||||
AND (
|
||||
sport_id = $4
|
||||
start_time > $4
|
||||
OR $4 IS NULL
|
||||
)
|
||||
ORDER BY start_time ASC
|
||||
LIMIT $1 OFFSET $2
|
||||
LIMIT $6 OFFSET $5
|
||||
`
|
||||
|
||||
type GetPaginatedUpcomingEventsParams struct {
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
LeagueID pgtype.Text `json:"league_id"`
|
||||
SportID pgtype.Text `json:"sport_id"`
|
||||
LastStartTime pgtype.Timestamp `json:"last_start_time"`
|
||||
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
|
||||
Offset pgtype.Int4 `json:"offset"`
|
||||
Limit pgtype.Int4 `json:"limit"`
|
||||
}
|
||||
|
||||
type GetPaginatedUpcomingEventsRow struct {
|
||||
|
|
@ -240,10 +250,12 @@ type GetPaginatedUpcomingEventsRow struct {
|
|||
|
||||
func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginatedUpcomingEventsParams) ([]GetPaginatedUpcomingEventsRow, error) {
|
||||
rows, err := q.db.Query(ctx, GetPaginatedUpcomingEvents,
|
||||
arg.Limit,
|
||||
arg.Offset,
|
||||
arg.LeagueID,
|
||||
arg.SportID,
|
||||
arg.LastStartTime,
|
||||
arg.FirstStartTime,
|
||||
arg.Offset,
|
||||
arg.Limit,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -86,6 +86,61 @@ func (q *Queries) GetALLPrematchOdds(ctx context.Context) ([]GetALLPrematchOddsR
|
|||
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
|
||||
SELECT event_id,
|
||||
fi,
|
||||
|
|
@ -162,21 +217,7 @@ func (q *Queries) GetPrematchOdds(ctx context.Context) ([]GetPrematchOddsRow, er
|
|||
}
|
||||
|
||||
const GetPrematchOddsByUpcomingID = `-- name: GetPrematchOddsByUpcomingID :many
|
||||
SELECT 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
|
||||
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
|
||||
|
|
@ -184,43 +225,19 @@ WHERE e.id = $1
|
|||
AND e.status = 'upcoming'
|
||||
AND o.is_active = true
|
||||
AND o.source = 'b365api'
|
||||
LIMIT $2 OFFSET $3
|
||||
`
|
||||
|
||||
type GetPrematchOddsByUpcomingIDParams struct {
|
||||
ID string `json:"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)
|
||||
func (q *Queries) GetPrematchOddsByUpcomingID(ctx context.Context, id string) ([]Odd, error) {
|
||||
rows, err := q.db.Query(ctx, GetPrematchOddsByUpcomingID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetPrematchOddsByUpcomingIDRow
|
||||
var items []Odd
|
||||
for rows.Next() {
|
||||
var i GetPrematchOddsByUpcomingIDRow
|
||||
var i Odd
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.EventID,
|
||||
&i.Fi,
|
||||
&i.MarketType,
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ type CreateBetReq struct {
|
|||
}
|
||||
|
||||
type RandomBetReq struct {
|
||||
BranchID int64 `json:"branch_id,omitempty" example:"1"`
|
||||
BranchID int64 `json:"branch_id" validate:"required" example:"1"`
|
||||
}
|
||||
|
||||
type CreateBetRes struct {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package domain
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ValidInt64 struct {
|
||||
Value int64
|
||||
|
|
@ -11,6 +14,10 @@ type ValidString struct {
|
|||
Value string
|
||||
Valid bool
|
||||
}
|
||||
type ValidTime struct {
|
||||
Value time.Time
|
||||
Valid bool
|
||||
}
|
||||
type ValidBool struct {
|
||||
Value bool
|
||||
Valid bool
|
||||
|
|
|
|||
|
|
@ -12,9 +12,8 @@ var SupportedLeagues = []int64{
|
|||
10041957, //UEFA Europa League
|
||||
10079560, //UEFA Conference League
|
||||
10047168, // US MLS
|
||||
|
||||
10044469, // Ethiopian Premier League
|
||||
10050282, //UEFA Nations League
|
||||
10040795, //EuroLeague
|
||||
|
||||
10043156, //England FA Cup
|
||||
10042103, //France Cup
|
||||
|
|
@ -26,5 +25,12 @@ var SupportedLeagues = []int64{
|
|||
|
||||
// Basketball
|
||||
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"`
|
||||
}
|
||||
|
||||
// The Market ID for the json data can be either string / int which is causing problems when UnMarshalling
|
||||
type OddsMarket struct {
|
||||
ID json.Number `json:"id"`
|
||||
ID json.RawMessage `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Odds []json.RawMessage `json:"odds"`
|
||||
Header string `json:"header,omitempty"`
|
||||
|
|
|
|||
|
|
@ -43,4 +43,24 @@ const (
|
|||
OUTCOME_STATUS_LOSS OutcomeStatus = 2
|
||||
OUTCOME_STATUS_VOID OutcomeStatus = 3 //Give 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_ASIAN_HANDICAP FootballMarket = 938 //"asian_handicap"
|
||||
FOOTBALL_GOAL_LINE FootballMarket = 10143 //"goal_line"
|
||||
|
||||
FOOTBALL_HALF_TIME_RESULT FootballMarket = 1579 //"half_time_result"
|
||||
FOOTBALL_FIRST_HALF_ASIAN_HANDICAP FootballMarket = 50137 //"1st_half_asian_handicap"
|
||||
FOOTBALL_FIRST_HALF_GOAL_LINE FootballMarket = 50136 //"1st_half_goal_line"
|
||||
FOOTBALL_FIRST_TEAM_TO_SCORE FootballMarket = 1178 //"first_team_to_score"
|
||||
FOOTBALL_GOALS_ODD_EVEN FootballMarket = 10111 //"goals_odd_even"
|
||||
FOOTBALL_DRAW_NO_BET FootballMarket = 10544 //"draw_no_bet"
|
||||
|
||||
|
||||
)
|
||||
|
||||
type BasketBallMarket int64
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package repository
|
|||
|
||||
import (
|
||||
"context"
|
||||
|
||||
// "fmt"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
update, err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{
|
||||
Status: int32(status),
|
||||
|
|
|
|||
|
|
@ -117,7 +117,8 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcoming
|
|||
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{
|
||||
LeagueID: pgtype.Text{
|
||||
String: leagueID.Value,
|
||||
|
|
@ -127,8 +128,22 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, off
|
|||
String: sportID.Value,
|
||||
Valid: sportID.Valid,
|
||||
},
|
||||
Limit: limit,
|
||||
Offset: offset * limit,
|
||||
Limit: pgtype.Int4{
|
||||
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 {
|
||||
|
|
@ -167,7 +182,7 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, off
|
|||
return nil, 0, err
|
||||
}
|
||||
|
||||
numberOfPages := math.Ceil(float64(totalCount) / float64(limit))
|
||||
numberOfPages := math.Ceil(float64(totalCount) / float64(limit.Value))
|
||||
return upcomingEvents, int64(numberOfPages), nil
|
||||
}
|
||||
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,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset int32) ([]domain.Odd, error) {
|
||||
params := dbgen.GetPrematchOddsByUpcomingIDParams{
|
||||
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: limit,
|
||||
Offset: offset,
|
||||
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
|
||||
}
|
||||
|
||||
odds, err := s.queries.GetPrematchOddsByUpcomingID(ctx, params)
|
||||
domainOdds[i] = domain.Odd{
|
||||
EventID: odd.EventID.String,
|
||||
Fi: odd.Fi.String,
|
||||
MarketType: odd.MarketType,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ type BetStore interface {
|
|||
GetAllBets(ctx context.Context) ([]domain.GetBet, error)
|
||||
GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, 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
|
||||
UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error
|
||||
UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error)
|
||||
DeleteBet(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"log/slog"
|
||||
"math/big"
|
||||
random "math/rand"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
|
@ -20,6 +19,12 @@ import (
|
|||
"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 {
|
||||
betStore BetStore
|
||||
eventSvc event.Service
|
||||
|
|
@ -239,12 +244,12 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
|||
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 totalOdds float32 = 1
|
||||
|
||||
markets, err := s.prematchSvc.GetPrematchOdds(ctx, eventID)
|
||||
markets, err := s.prematchSvc.GetPrematchOddsByUpcomingID(ctx, eventID)
|
||||
|
||||
if err != nil {
|
||||
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 {
|
||||
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 randIndex []int = make([]int, numMarkets)
|
||||
var selectedMarkets []domain.Odd
|
||||
numMarkets = min(numMarkets, len(markets))
|
||||
for i := 0; i < numMarkets; i++ {
|
||||
// Guarantee that the odd is unique
|
||||
var newRandMarket int
|
||||
count := 0
|
||||
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++
|
||||
randomIndex := random.Intn(len(markets))
|
||||
selectedMarkets = append(selectedMarkets, markets[randomIndex])
|
||||
markets = append(markets[:randomIndex], markets[randomIndex+1:]...)
|
||||
}
|
||||
|
||||
randIndex[i] = newRandMarket
|
||||
for _, market := range selectedMarkets {
|
||||
|
||||
rawOdds := markets[i].RawOdds
|
||||
randomRawOdd := rawOdds[random.Intn(len(rawOdds))]
|
||||
randomRawOdd := market.RawOdds[random.Intn(len(market.RawOdds))]
|
||||
|
||||
type rawOddType struct {
|
||||
ID string
|
||||
|
|
@ -317,13 +310,13 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportI
|
|||
continue
|
||||
}
|
||||
|
||||
marketID, err := strconv.ParseInt(markets[i].MarketID, 10, 64)
|
||||
marketID, err := strconv.ParseInt(market.MarketID, 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get odd id", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
marketName := markets[i].MarketName
|
||||
marketName := market.MarketName
|
||||
|
||||
newOdds = append(newOdds, domain.CreateBetOutcome{
|
||||
EventID: eventID,
|
||||
|
|
@ -345,28 +338,48 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportI
|
|||
}
|
||||
|
||||
if len(newOdds) == 0 {
|
||||
s.logger.Error("Failed to generate random outcomes")
|
||||
return nil, 0, nil
|
||||
s.logger.Error("Bet Outcomes is empty for market", "selectedMarket", selectedMarkets[0].MarketName)
|
||||
return nil, 0, ErrGenerateRandomOutcome
|
||||
}
|
||||
|
||||
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
|
||||
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 {
|
||||
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
|
||||
var randomOdds []domain.CreateBetOutcome
|
||||
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 {
|
||||
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 {
|
||||
s.logger.Error("Failed to generate random outcomes")
|
||||
return domain.CreateBetRes{}, nil
|
||||
s.logger.Error("Failed to generate random any outcomes for all events")
|
||||
return domain.CreateBetRes{}, ErrGenerateRandomOutcome
|
||||
}
|
||||
|
||||
s.logger.Info("Generated Random bet Outcome", "randomOdds", len(randomOdds))
|
||||
|
||||
var cashoutID string
|
||||
|
||||
cashoutID, err = s.GenerateCashoutID()
|
||||
|
|
@ -389,13 +404,13 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64) (d
|
|||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
randomNumber := strconv.FormatInt(int64(random.Intn(10)), 10)
|
||||
randomNumber := strconv.FormatInt(int64(random.Intn(100000000000)), 10)
|
||||
newBet := domain.CreateBet{
|
||||
Amount: 123,
|
||||
Amount: domain.ToCurrency(123.5),
|
||||
TotalOdds: totalOdds,
|
||||
Status: domain.OUTCOME_STATUS_PENDING,
|
||||
FullName: "test" + randomNumber,
|
||||
PhoneNumber: randomNumber,
|
||||
PhoneNumber: "0900000000",
|
||||
CashoutID: cashoutID,
|
||||
BranchID: domain.ValidInt64{Valid: true, Value: branchID},
|
||||
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)
|
||||
}
|
||||
|
||||
func (s *Service) checkBetOutcomeForBet(ctx context.Context, eventID int64) error {
|
||||
betOutcomes, err := s.betStore.GetBetOutcomeByEventID(ctx, eventID)
|
||||
func (s *Service) CheckBetOutcomeForBet(ctx context.Context, betID int64) (domain.OutcomeStatus, error) {
|
||||
betOutcomes, err := s.betStore.GetBetOutcomeByBetID(ctx, betID)
|
||||
if err != nil {
|
||||
return err
|
||||
return domain.OUTCOME_STATUS_PENDING, err
|
||||
}
|
||||
status := domain.OUTCOME_STATUS_PENDING
|
||||
|
||||
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 {
|
||||
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
|
||||
} else if status == domain.OUTCOME_STATUS_WIN {
|
||||
status = betOutcome.Status
|
||||
} else if status == domain.OUTCOME_STATUS_LOSS {
|
||||
continue
|
||||
case domain.OUTCOME_STATUS_WIN:
|
||||
if betOutcome.Status == domain.OUTCOME_STATUS_LOSS {
|
||||
status = domain.OUTCOME_STATUS_HALF
|
||||
} 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 {
|
||||
return nil
|
||||
if status == domain.OUTCOME_STATUS_PENDING || status == domain.OUTCOME_STATUS_ERROR {
|
||||
// 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)
|
||||
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
|
||||
GetAllUpcomingEvents(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)
|
||||
// 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 {
|
||||
sportIDs := []int{1, 18}
|
||||
// sportIDs := []int{1, 18, 17}
|
||||
sportIDs := []int{18}
|
||||
|
||||
for _, sportID := range sportIDs {
|
||||
var totalPages int = 1
|
||||
var page int = 0
|
||||
var limit int = 100
|
||||
var limit int = 10
|
||||
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 page <= totalPages {
|
||||
page = page + 1
|
||||
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)
|
||||
if err != nil {
|
||||
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"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
||||
log.Printf("❌ Failed to parse json data")
|
||||
continue
|
||||
}
|
||||
skippedLeague := 0
|
||||
var skippedLeague []string
|
||||
for _, ev := range data.Results {
|
||||
startUnix, _ := strconv.ParseInt(ev.Time, 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) {
|
||||
skippedLeague++
|
||||
|
||||
skippedLeague = append(skippedLeague, ev.League.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -188,11 +190,20 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
|||
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
|
||||
}
|
||||
count++
|
||||
|
|
@ -223,8 +234,8 @@ func (s *service) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcomi
|
|||
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) {
|
||||
return s.store.GetPaginatedUpcomingEvents(ctx, limit, offset, leagueID, sportID)
|
||||
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, firstStartTime, lastStartTime)
|
||||
}
|
||||
|
||||
func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import (
|
|||
type Service interface {
|
||||
FetchNonLiveOdds(ctx context.Context) 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)
|
||||
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
|
||||
|
||||
for _, event := range eventIDs {
|
||||
// time.Sleep(3 * time.Second) //This will restrict the fetching to 1200 requests per hour
|
||||
for index, event := range eventIDs {
|
||||
|
||||
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
||||
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)
|
||||
|
||||
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 {
|
||||
log.Printf("❌ Failed to fetch prematch odds for event %d: %v", eventID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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 {
|
||||
case domain.FOOTBALL:
|
||||
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)
|
||||
}
|
||||
case domain.BASKETBALL:
|
||||
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)
|
||||
}
|
||||
case domain.ICE_HOCKEY:
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -107,8 +115,8 @@ func (s *ServiceImpl) parseFootball(ctx context.Context, res json.RawMessage) er
|
|||
return err
|
||||
}
|
||||
if footballRes.EventID == "" && footballRes.FI == "" {
|
||||
s.logger.Error("Skipping result with no valid Event ID")
|
||||
return fmt.Errorf("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 football result with no valid Event ID Event ID %v", footballRes.EventID)
|
||||
}
|
||||
sections := map[string]domain.OddsSection{
|
||||
"main": footballRes.Main,
|
||||
|
|
@ -121,7 +129,8 @@ func (s *ServiceImpl) parseFootball(ctx context.Context, res json.RawMessage) er
|
|||
|
||||
for oddCategory, section := range sections {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
var basketballRes domain.BasketballOddsResponse
|
||||
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
|
||||
}
|
||||
if basketballRes.EventID == "" && basketballRes.FI == "" {
|
||||
s.logger.Error("Skipping result with no valid Event ID")
|
||||
return fmt.Errorf("Skipping result with no valid Event ID")
|
||||
s.logger.Error("Skipping basketball result with no valid Event ID")
|
||||
return fmt.Errorf("Skipping basketball result with no valid Event ID")
|
||||
}
|
||||
sections := map[string]domain.OddsSection{
|
||||
"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 {
|
||||
var iceHockeyRes domain.IceHockeyOddsResponse
|
||||
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
|
||||
}
|
||||
if iceHockeyRes.EventID == "" && iceHockeyRes.FI == "" {
|
||||
|
|
@ -229,17 +238,30 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
|
|||
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 {
|
||||
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)
|
||||
continue
|
||||
}
|
||||
|
||||
isSupported, ok := domain.SupportedMarkets[marketID]
|
||||
isSupported, ok := domain.SupportedMarkets[marketIDint]
|
||||
|
||||
if !ok || !isSupported {
|
||||
s.logger.Info("Unsupported market_id", "marketID", marketID)
|
||||
// s.logger.Info("Unsupported market_id", "marketID", marketIDint, "marketName", market.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -249,7 +271,7 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
|
|||
MarketCategory: sectionName,
|
||||
MarketType: marketType,
|
||||
MarketName: market.Name,
|
||||
MarketID: market.ID.String(),
|
||||
MarketID: marketIDstr,
|
||||
UpdatedAt: updatedAt,
|
||||
Odds: market.Odds,
|
||||
}
|
||||
|
|
@ -285,6 +307,10 @@ func (s *ServiceImpl) GetRawOddsByMarketID(ctx context.Context, marketID string,
|
|||
return rows, nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset int32) ([]domain.Odd, error) {
|
||||
return s.store.GetPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset)
|
||||
func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string) ([]domain.Odd, error) {
|
||||
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
|
||||
|
||||
// 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) {
|
||||
switch outcome.OddName {
|
||||
case "1": // Home win
|
||||
|
|
@ -27,15 +29,16 @@ func evaluateFullTimeResult(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
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) {
|
||||
totalGoals := float64(score.Home + score.Away)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
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" {
|
||||
|
|
@ -53,9 +56,10 @@ func evaluateGoalsOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
}
|
||||
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) {
|
||||
expectedScore := fmt.Sprintf("%d-%d", score.Home, score.Away)
|
||||
if outcome.OddName == expectedScore {
|
||||
|
|
@ -64,6 +68,8 @@ func evaluateCorrectScore(outcome domain.BetOutcome, score struct{ Home, Away in
|
|||
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) {
|
||||
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
|
||||
// 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) {
|
||||
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 {
|
||||
case domain.OUTCOME_STATUS_PENDING:
|
||||
return secondOutcome, nil
|
||||
case domain.OUTCOME_STATUS_WIN:
|
||||
if secondOutcome == domain.OUTCOME_STATUS_WIN {
|
||||
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 {
|
||||
return domain.OUTCOME_STATUS_HALF, nil
|
||||
} 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:
|
||||
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
|
||||
} else if secondOutcome == domain.OUTCOME_STATUS_VOID {
|
||||
return domain.OUTCOME_STATUS_HALF, nil
|
||||
} 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:
|
||||
if secondOutcome == domain.OUTCOME_STATUS_WIN || secondOutcome == domain.OUTCOME_STATUS_LOSS {
|
||||
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 {
|
||||
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:
|
||||
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) {
|
||||
handicapList := strings.Split(outcome.OddHandicap, ",")
|
||||
newOutcome := domain.OUTCOME_STATUS_PENDING
|
||||
for _, handicapStr := range handicapList {
|
||||
handicapStr = strings.TrimSpace(handicapStr)
|
||||
handicap, err := strconv.ParseFloat(handicapStr, 64)
|
||||
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)
|
||||
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
|
||||
adjustedAwayScore += handicap
|
||||
} 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 outcome.OddHeader == "1" {
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
|
||||
if err != nil {
|
||||
fmt.Printf("multi outcome check error")
|
||||
return domain.OUTCOME_STATUS_PENDING, err
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
}
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
|
||||
if err != nil {
|
||||
fmt.Printf("multi outcome check error")
|
||||
return domain.OUTCOME_STATUS_PENDING, err
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
} else if adjustedHomeScore < adjustedAwayScore {
|
||||
if outcome.OddHeader == "2" {
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
|
||||
if err != nil {
|
||||
fmt.Printf("multi outcome check error")
|
||||
return domain.OUTCOME_STATUS_PENDING, err
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
}
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
|
||||
if err != nil {
|
||||
fmt.Printf("multi outcome check error")
|
||||
return domain.OUTCOME_STATUS_PENDING, err
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
}
|
||||
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID)
|
||||
if err != nil {
|
||||
fmt.Printf("multi outcome check error")
|
||||
return domain.OUTCOME_STATUS_PENDING, err
|
||||
return domain.OUTCOME_STATUS_ERROR, err
|
||||
}
|
||||
}
|
||||
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) {
|
||||
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) {
|
||||
for _, event := range events {
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
totalGoals := score.Home + score.Away
|
||||
isOdd := totalGoals%2 == 1
|
||||
|
|
@ -184,6 +306,7 @@ func evaluateGoalsOddEven(outcome domain.BetOutcome, score struct{ Home, Away in
|
|||
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) {
|
||||
isHomeWin := 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
|
||||
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) {
|
||||
if score.Home == score.Away {
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
switch outcome.OddName {
|
||||
case "Money Line":
|
||||
|
|
@ -235,10 +360,11 @@ func evaluateGameLines(outcome domain.BetOutcome, score struct{ Home, Away int }
|
|||
case "Total":
|
||||
return evaluateTotalOverUnder(outcome, score)
|
||||
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) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
|
|
@ -258,21 +384,22 @@ func evaluateMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }
|
|||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
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) {
|
||||
|
||||
// The handicap will be in the format "U {float}" or "O {float}"
|
||||
// U and O denoting over and under for this case
|
||||
overUnderStr := strings.Split(outcome.OddHandicap, " ")
|
||||
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)
|
||||
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
|
||||
|
|
@ -294,26 +421,28 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
}
|
||||
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) {
|
||||
|
||||
// The handicap will be in the format "U {float}" or "O {float}"
|
||||
// U and O denoting over and under for this case
|
||||
overUnderStr := strings.Split(outcome.OddHandicap, " ")
|
||||
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]
|
||||
|
||||
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)
|
||||
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
|
||||
|
|
@ -321,6 +450,10 @@ func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home < score.Away {
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
|
||||
if overUnder == "Over" && totalScore > threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} 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
|
||||
case "2":
|
||||
if score.Away < score.Home {
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
if overUnder == "Over" && totalScore > threshold {
|
||||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
} 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
|
||||
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) {
|
||||
|
||||
// The handicap will be in the format "U {float}" or "O {float}"
|
||||
// U and O denoting over and under for this case
|
||||
overUnderStr := strings.Split(outcome.OddHandicap, " ")
|
||||
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]
|
||||
|
||||
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)
|
||||
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
|
||||
|
|
@ -380,11 +518,12 @@ func evaluateTeamTotal(outcome domain.BetOutcome, score struct{ Home, Away int }
|
|||
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
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) {
|
||||
|
||||
// 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" {
|
||||
isScorePoints = false
|
||||
} 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], ""))
|
||||
|
||||
threshold, err := strconv.ParseInt(outcome.OddHeader, 10, 64)
|
||||
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 {
|
||||
|
|
@ -428,18 +567,18 @@ func evaluateResultAndBTTSX(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
}
|
||||
}
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
// 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) {
|
||||
threshold, err := strconv.ParseInt(outcome.OddName, 10, 64)
|
||||
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 {
|
||||
|
|
@ -453,12 +592,13 @@ func evaluateBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (d
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
switch outcome.OddName {
|
||||
case "1": // Home win
|
||||
|
|
@ -477,23 +617,24 @@ func evaluateMoneyLine3Way(outcome domain.BetOutcome, score struct{ Home, Away i
|
|||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
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) {
|
||||
halfWins := strings.Split(outcome.OddName, "-")
|
||||
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])
|
||||
secondHalfWinner := strings.TrimSpace(halfWins[1])
|
||||
|
||||
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" {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
|
||||
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
|
||||
}
|
||||
|
||||
switch {
|
||||
|
|
@ -517,6 +658,7 @@ func evaluateDoubleResult(outcome domain.BetOutcome, firstHalfScore struct{ Home
|
|||
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) {
|
||||
firstHalfTotal := firstScore.Home + firstScore.Away
|
||||
secondHalfTotal := secondScore.Home + secondScore.Away
|
||||
|
|
@ -534,11 +676,12 @@ func evaluateHighestScoringHalf(outcome domain.BetOutcome, firstScore struct{ Ho
|
|||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
firstQuarterTotal := firstScore.Home + firstScore.Away
|
||||
secondQuarterTotal := secondScore.Home + secondScore.Away
|
||||
|
|
@ -567,18 +710,20 @@ func evaluateHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{
|
|||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
nameSplit := strings.Split(outcome.OddName, " ")
|
||||
// Evaluate from bottom to get the threshold and find out if its over or under
|
||||
threshold, err := strconv.ParseFloat(nameSplit[len(nameSplit)-1], 10)
|
||||
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)
|
||||
overUnder := nameSplit[len(nameSplit)-2]
|
||||
|
|
@ -591,12 +736,12 @@ func evaluateHandicapAndTotal(outcome domain.BetOutcome, score struct{ Home, Awa
|
|||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
}
|
||||
} 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)
|
||||
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], ""))
|
||||
|
|
@ -618,21 +763,22 @@ func evaluateHandicapAndTotal(outcome domain.BetOutcome, score struct{ Home, Awa
|
|||
}
|
||||
return domain.OUTCOME_STATUS_LOSS, nil
|
||||
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) {
|
||||
|
||||
marginSplit := strings.Split(outcome.OddName, "")
|
||||
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)
|
||||
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
|
||||
|
|
@ -656,9 +802,10 @@ func evaluateWinningMargin(outcome domain.BetOutcome, score struct{ Home, Away i
|
|||
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) {
|
||||
firstPeriodTotal := firstScore.Home + firstScore.Away
|
||||
secondPeriodTotal := secondScore.Home + secondScore.Away
|
||||
|
|
@ -682,11 +829,12 @@ func evaluateHighestScoringPeriod(outcome domain.BetOutcome, firstScore struct{
|
|||
return domain.OUTCOME_STATUS_WIN, nil
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
totalScore := struct{ Home, Away int }{0, 0}
|
||||
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_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")
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Expired Events: %d \n", len(events))
|
||||
|
||||
for _, event := range events {
|
||||
fmt.Printf("⚠️ Expired Events: %d \n", len(events))
|
||||
for i, 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)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse event id")
|
||||
|
|
@ -59,46 +59,89 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
|||
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()) {
|
||||
isDeleted = false
|
||||
s.logger.Info("Outcome is not expired yet", "event_id", event.ID, "outcome_id", outcome.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Error("Sport ID is invalid", "event_id", outcome.EventID, "error", err)
|
||||
isDeleted = false
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
// _, err = s.repo.CreateResult(ctx, domain.CreateResult{
|
||||
// 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)
|
||||
outcome, err = s.betSvc.UpdateBetOutcomeStatus(ctx, outcome.ID, result.Status)
|
||||
if err != nil {
|
||||
isDeleted = false
|
||||
s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err)
|
||||
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
|
||||
}
|
||||
err = s.repo.DeleteEvent(ctx, event.ID)
|
||||
|
||||
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 {
|
||||
s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err)
|
||||
return err
|
||||
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))
|
||||
|
||||
}
|
||||
if isDeleted {
|
||||
// err = s.repo.DeleteEvent(ctx, event.ID)
|
||||
// if err != nil {
|
||||
// s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err)
|
||||
// return err
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -248,7 +291,7 @@ func (s *Service) parseFootball(resultRes json.RawMessage, eventID, oddID, marke
|
|||
corners := parseStats(result.Stats.Corners)
|
||||
status, err := s.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, corners, result.Events)
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
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
|
||||
|
||||
import (
|
||||
// "context"
|
||||
"context"
|
||||
|
||||
"log"
|
||||
|
||||
// "time"
|
||||
|
|
@ -10,6 +10,7 @@ import (
|
|||
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
resultsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
|
|
@ -20,14 +21,14 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
|||
spec string
|
||||
task func()
|
||||
}{
|
||||
{
|
||||
spec: "0 0 * * * *", // Every 1 hour
|
||||
task: func() {
|
||||
if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
|
||||
log.Printf("FetchUpcomingEvents error: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
// {
|
||||
// spec: "0 0 * * * *", // Every 1 hour
|
||||
// task: func() {
|
||||
// if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
|
||||
// log.Printf("FetchUpcomingEvents error: %v", err)
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
|
||||
// {
|
||||
// spec: "*/5 * * * * *", // Every 5 seconds
|
||||
|
|
@ -37,14 +38,14 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
|||
// }
|
||||
// },
|
||||
// },
|
||||
{
|
||||
spec: "0 */15 * * * *", // Every 15 minutes
|
||||
task: func() {
|
||||
if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
|
||||
log.Printf("FetchNonLiveOdds error: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
// {
|
||||
// spec: "0 */15 * * * *", // Every 15 minutes
|
||||
// task: func() {
|
||||
// if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
|
||||
// log.Printf("FetchNonLiveOdds error: %v", err)
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// spec: "0 */15 * * * *",
|
||||
// task: func() {
|
||||
|
|
@ -80,6 +81,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
@ -88,3 +90,34 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
|||
c.Start()
|
||||
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 (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"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/gofiber/fiber/v2"
|
||||
)
|
||||
|
|
@ -15,7 +17,7 @@ import (
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param createBet body domain.CreateBetReq true "Creates bet"
|
||||
// @Success 200 {object} BetRes
|
||||
// @Success 200 {object} domain.BetRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /bet [post]
|
||||
|
|
@ -54,7 +56,7 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param createBet body domain.RandomBetReq true "Create Random bet"
|
||||
// @Success 200 {object} BetRes
|
||||
// @Success 200 {object} domain.BetRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /random/bet [post]
|
||||
|
|
@ -64,6 +66,45 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
|
|||
userID := c.Locals("user_id").(int64)
|
||||
// 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
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
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")
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +137,7 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
|
|||
// @Tags bet
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {array} BetRes
|
||||
// @Success 200 {array} domain.BetRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /bet [get]
|
||||
|
|
@ -118,7 +163,7 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "Bet ID"
|
||||
// @Success 200 {object} BetRes
|
||||
// @Success 200 {object} domain.BetRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /bet/{id} [get]
|
||||
|
|
@ -149,7 +194,7 @@ func (h *Handler) GetBetByID(c *fiber.Ctx) error {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "cashout ID"
|
||||
// @Success 200 {object} BetRes
|
||||
// @Success 200 {object} domain.BetRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /bet/cashout/{id} [get]
|
||||
|
|
|
|||
|
|
@ -498,7 +498,7 @@ func (h *Handler) GetBranchOperations(c *fiber.Ctx) error {
|
|||
// @Tags branch
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {array} BetRes
|
||||
// @Success 200 {array} domain.BetRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /branch/{id}/bets [get]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package handlers
|
|||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"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)
|
||||
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,
|
||||
|
|
@ -116,7 +119,41 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
|
|||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
odds, err := h.prematchSvc.GetPrematchOddsByUpcomingID(c.Context(), upcomingID, int32(limit), int32(offset))
|
||||
odds, err := h.prematchSvc.GetPrematchOddsByUpcomingID(c.Context(), upcomingID)
|
||||
if err != nil {
|
||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve prematch odds", nil, nil)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user