basketball and fixes
This commit is contained in:
parent
ae571d51a6
commit
b4e20a274d
|
|
@ -70,6 +70,7 @@ CREATE TABLE IF NOT EXISTS tickets (
|
||||||
CREATE TABLE IF NOT EXISTS bet_outcomes (
|
CREATE TABLE IF NOT EXISTS bet_outcomes (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
bet_id BIGINT NOT NULL,
|
bet_id BIGINT NOT NULL,
|
||||||
|
sport_id BIGINT NOT NULL,
|
||||||
event_id BIGINT NOT null,
|
event_id BIGINT NOT null,
|
||||||
odd_id BIGINT NOT NULL,
|
odd_id BIGINT NOT NULL,
|
||||||
home_team_name VARCHAR(255) NOT NULL,
|
home_team_name VARCHAR(255) NOT NULL,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ RETURNING *;
|
||||||
-- name: CreateBetOutcome :copyfrom
|
-- name: CreateBetOutcome :copyfrom
|
||||||
INSERT INTO bet_outcomes (
|
INSERT INTO bet_outcomes (
|
||||||
bet_id,
|
bet_id,
|
||||||
|
sport_id,
|
||||||
event_id,
|
event_id,
|
||||||
odd_id,
|
odd_id,
|
||||||
home_team_name,
|
home_team_name,
|
||||||
|
|
@ -39,7 +40,8 @@ VALUES (
|
||||||
$9,
|
$9,
|
||||||
$10,
|
$10,
|
||||||
$11,
|
$11,
|
||||||
$12
|
$12,
|
||||||
|
$13
|
||||||
);
|
);
|
||||||
-- name: GetAllBets :many
|
-- name: GetAllBets :many
|
||||||
SELECT *
|
SELECT *
|
||||||
|
|
@ -56,6 +58,11 @@ WHERE cashout_id = $1;
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM bet_with_outcomes
|
FROM bet_with_outcomes
|
||||||
WHERE branch_id = $1;
|
WHERE branch_id = $1;
|
||||||
|
-- name: GetBetOutcomeByEventID :many
|
||||||
|
SELECT *
|
||||||
|
FROM bet_outcomes
|
||||||
|
WHERE event_id = $1;
|
||||||
|
|
||||||
-- name: UpdateCashOut :exec
|
-- name: UpdateCashOut :exec
|
||||||
UPDATE bets
|
UPDATE bets
|
||||||
SET cashed_out = $2,
|
SET cashed_out = $2,
|
||||||
|
|
|
||||||
|
|
@ -233,3 +233,6 @@ SET score = $1,
|
||||||
status = $2,
|
status = $2,
|
||||||
fetched_at = NOW()
|
fetched_at = NOW()
|
||||||
WHERE id = $3;
|
WHERE id = $3;
|
||||||
|
-- name: DeleteEvent :exec
|
||||||
|
DELETE FROM events
|
||||||
|
WHERE id = $1;
|
||||||
59
docs/docs.go
59
docs/docs.go
|
|
@ -1256,7 +1256,7 @@ const docTemplate = `{
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.CreateCompanyReq"
|
"$ref": "#/definitions/handlers.UpdateCompanyReq"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -3372,6 +3372,10 @@ const docTemplate = `{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "1"
|
"example": "1"
|
||||||
},
|
},
|
||||||
|
"sport_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
|
|
@ -3439,13 +3443,19 @@ const docTemplate = `{
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
3
|
3,
|
||||||
|
4
|
||||||
],
|
],
|
||||||
|
"x-enum-comments": {
|
||||||
|
"OUTCOME_STATUS_HALF": "Half Win and Half Given Back",
|
||||||
|
"OUTCOME_STATUS_VOID": "Give Back"
|
||||||
|
},
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"OUTCOME_STATUS_PENDING",
|
"OUTCOME_STATUS_PENDING",
|
||||||
"OUTCOME_STATUS_WIN",
|
"OUTCOME_STATUS_WIN",
|
||||||
"OUTCOME_STATUS_LOSS",
|
"OUTCOME_STATUS_LOSS",
|
||||||
"OUTCOME_STATUS_VOID"
|
"OUTCOME_STATUS_VOID",
|
||||||
|
"OUTCOME_STATUS_HALF"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"domain.PaymentOption": {
|
"domain.PaymentOption": {
|
||||||
|
|
@ -4034,6 +4044,12 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"handlers.CreateBranchReq": {
|
"handlers.CreateBranchReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"branch_manager_id",
|
||||||
|
"location",
|
||||||
|
"name",
|
||||||
|
"operations"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"branch_manager_id": {
|
"branch_manager_id": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
|
@ -4049,10 +4065,14 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"maxLength": 100,
|
||||||
|
"minLength": 3,
|
||||||
"example": "Addis Ababa"
|
"example": "Addis Ababa"
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"maxLength": 100,
|
||||||
|
"minLength": 3,
|
||||||
"example": "4-kilo Branch"
|
"example": "4-kilo Branch"
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
|
|
@ -4502,6 +4522,10 @@ const docTemplate = `{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
},
|
},
|
||||||
|
"approver_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "John Smith"
|
||||||
|
},
|
||||||
"bank_code": {
|
"bank_code": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -4516,10 +4540,26 @@ const docTemplate = `{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
},
|
},
|
||||||
|
"branch_location": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Branch Location"
|
||||||
|
},
|
||||||
|
"branch_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Branch Name"
|
||||||
|
},
|
||||||
"cashier_id": {
|
"cashier_id": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
},
|
},
|
||||||
|
"cashier_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "John Smith"
|
||||||
|
},
|
||||||
|
"company_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -4616,6 +4656,19 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"handlers.UpdateCompanyReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"admin_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "CompanyName"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"handlers.UpdateTransactionVerifiedReq": {
|
"handlers.UpdateTransactionVerifiedReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -1248,7 +1248,7 @@
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/handlers.CreateCompanyReq"
|
"$ref": "#/definitions/handlers.UpdateCompanyReq"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -3364,6 +3364,10 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "1"
|
"example": "1"
|
||||||
},
|
},
|
||||||
|
"sport_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
|
|
@ -3431,13 +3435,19 @@
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
3
|
3,
|
||||||
|
4
|
||||||
],
|
],
|
||||||
|
"x-enum-comments": {
|
||||||
|
"OUTCOME_STATUS_HALF": "Half Win and Half Given Back",
|
||||||
|
"OUTCOME_STATUS_VOID": "Give Back"
|
||||||
|
},
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"OUTCOME_STATUS_PENDING",
|
"OUTCOME_STATUS_PENDING",
|
||||||
"OUTCOME_STATUS_WIN",
|
"OUTCOME_STATUS_WIN",
|
||||||
"OUTCOME_STATUS_LOSS",
|
"OUTCOME_STATUS_LOSS",
|
||||||
"OUTCOME_STATUS_VOID"
|
"OUTCOME_STATUS_VOID",
|
||||||
|
"OUTCOME_STATUS_HALF"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"domain.PaymentOption": {
|
"domain.PaymentOption": {
|
||||||
|
|
@ -4026,6 +4036,12 @@
|
||||||
},
|
},
|
||||||
"handlers.CreateBranchReq": {
|
"handlers.CreateBranchReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"branch_manager_id",
|
||||||
|
"location",
|
||||||
|
"name",
|
||||||
|
"operations"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"branch_manager_id": {
|
"branch_manager_id": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
|
@ -4041,10 +4057,14 @@
|
||||||
},
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"maxLength": 100,
|
||||||
|
"minLength": 3,
|
||||||
"example": "Addis Ababa"
|
"example": "Addis Ababa"
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"maxLength": 100,
|
||||||
|
"minLength": 3,
|
||||||
"example": "4-kilo Branch"
|
"example": "4-kilo Branch"
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
|
|
@ -4494,6 +4514,10 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
},
|
},
|
||||||
|
"approver_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "John Smith"
|
||||||
|
},
|
||||||
"bank_code": {
|
"bank_code": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -4508,10 +4532,26 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
},
|
},
|
||||||
|
"branch_location": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Branch Location"
|
||||||
|
},
|
||||||
|
"branch_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Branch Name"
|
||||||
|
},
|
||||||
"cashier_id": {
|
"cashier_id": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
},
|
},
|
||||||
|
"cashier_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "John Smith"
|
||||||
|
},
|
||||||
|
"company_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -4608,6 +4648,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"handlers.UpdateCompanyReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"admin_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "CompanyName"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"handlers.UpdateTransactionVerifiedReq": {
|
"handlers.UpdateTransactionVerifiedReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,9 @@ definitions:
|
||||||
odd_name:
|
odd_name:
|
||||||
example: "1"
|
example: "1"
|
||||||
type: string
|
type: string
|
||||||
|
sport_id:
|
||||||
|
example: 1
|
||||||
|
type: integer
|
||||||
status:
|
status:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/definitions/domain.OutcomeStatus'
|
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||||
|
|
@ -85,12 +88,17 @@ definitions:
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 2
|
||||||
- 3
|
- 3
|
||||||
|
- 4
|
||||||
type: integer
|
type: integer
|
||||||
|
x-enum-comments:
|
||||||
|
OUTCOME_STATUS_HALF: Half Win and Half Given Back
|
||||||
|
OUTCOME_STATUS_VOID: Give Back
|
||||||
x-enum-varnames:
|
x-enum-varnames:
|
||||||
- OUTCOME_STATUS_PENDING
|
- OUTCOME_STATUS_PENDING
|
||||||
- OUTCOME_STATUS_WIN
|
- OUTCOME_STATUS_WIN
|
||||||
- OUTCOME_STATUS_LOSS
|
- OUTCOME_STATUS_LOSS
|
||||||
- OUTCOME_STATUS_VOID
|
- OUTCOME_STATUS_VOID
|
||||||
|
- OUTCOME_STATUS_HALF
|
||||||
domain.PaymentOption:
|
domain.PaymentOption:
|
||||||
enum:
|
enum:
|
||||||
- 0
|
- 0
|
||||||
|
|
@ -514,14 +522,23 @@ definitions:
|
||||||
type: boolean
|
type: boolean
|
||||||
location:
|
location:
|
||||||
example: Addis Ababa
|
example: Addis Ababa
|
||||||
|
maxLength: 100
|
||||||
|
minLength: 3
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
example: 4-kilo Branch
|
example: 4-kilo Branch
|
||||||
|
maxLength: 100
|
||||||
|
minLength: 3
|
||||||
type: string
|
type: string
|
||||||
operations:
|
operations:
|
||||||
items:
|
items:
|
||||||
type: integer
|
type: integer
|
||||||
type: array
|
type: array
|
||||||
|
required:
|
||||||
|
- branch_manager_id
|
||||||
|
- location
|
||||||
|
- name
|
||||||
|
- operations
|
||||||
type: object
|
type: object
|
||||||
handlers.CreateCashierReq:
|
handlers.CreateCashierReq:
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -830,6 +847,9 @@ definitions:
|
||||||
approved_by:
|
approved_by:
|
||||||
example: 1
|
example: 1
|
||||||
type: integer
|
type: integer
|
||||||
|
approver_name:
|
||||||
|
example: John Smith
|
||||||
|
type: string
|
||||||
bank_code:
|
bank_code:
|
||||||
type: string
|
type: string
|
||||||
beneficiary_name:
|
beneficiary_name:
|
||||||
|
|
@ -840,9 +860,21 @@ definitions:
|
||||||
branch_id:
|
branch_id:
|
||||||
example: 1
|
example: 1
|
||||||
type: integer
|
type: integer
|
||||||
|
branch_location:
|
||||||
|
example: Branch Location
|
||||||
|
type: string
|
||||||
|
branch_name:
|
||||||
|
example: Branch Name
|
||||||
|
type: string
|
||||||
cashier_id:
|
cashier_id:
|
||||||
example: 1
|
example: 1
|
||||||
type: integer
|
type: integer
|
||||||
|
cashier_name:
|
||||||
|
example: John Smith
|
||||||
|
type: string
|
||||||
|
company_id:
|
||||||
|
example: 1
|
||||||
|
type: integer
|
||||||
created_at:
|
created_at:
|
||||||
type: string
|
type: string
|
||||||
full_name:
|
full_name:
|
||||||
|
|
@ -910,6 +942,15 @@ definitions:
|
||||||
cashedOut:
|
cashedOut:
|
||||||
type: boolean
|
type: boolean
|
||||||
type: object
|
type: object
|
||||||
|
handlers.UpdateCompanyReq:
|
||||||
|
properties:
|
||||||
|
admin_id:
|
||||||
|
example: 1
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
example: CompanyName
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
handlers.UpdateTransactionVerifiedReq:
|
handlers.UpdateTransactionVerifiedReq:
|
||||||
properties:
|
properties:
|
||||||
verified:
|
verified:
|
||||||
|
|
@ -1935,7 +1976,7 @@ paths:
|
||||||
name: updateCompany
|
name: updateCompany
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/handlers.CreateCompanyReq'
|
$ref: '#/definitions/handlers.UpdateCompanyReq'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
|
||||||
|
|
||||||
type CreateBetOutcomeParams struct {
|
type CreateBetOutcomeParams struct {
|
||||||
BetID int64 `json:"bet_id"`
|
BetID int64 `json:"bet_id"`
|
||||||
|
SportID int64 `json:"sport_id"`
|
||||||
EventID int64 `json:"event_id"`
|
EventID int64 `json:"event_id"`
|
||||||
OddID int64 `json:"odd_id"`
|
OddID int64 `json:"odd_id"`
|
||||||
HomeTeamName string `json:"home_team_name"`
|
HomeTeamName string `json:"home_team_name"`
|
||||||
|
|
@ -242,6 +243,48 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
WHERE event_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]BetOutcome, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetBetOutcomeByEventID, eventID)
|
||||||
|
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 UpdateBetOutcomeStatus = `-- name: UpdateBetOutcomeStatus :exec
|
const UpdateBetOutcomeStatus = `-- name: UpdateBetOutcomeStatus :exec
|
||||||
UPDATE bet_outcomes
|
UPDATE bet_outcomes
|
||||||
SET status = $1
|
SET status = $1
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ func (r *iteratorForCreateBetOutcome) Next() bool {
|
||||||
func (r iteratorForCreateBetOutcome) Values() ([]interface{}, error) {
|
func (r iteratorForCreateBetOutcome) Values() ([]interface{}, error) {
|
||||||
return []interface{}{
|
return []interface{}{
|
||||||
r.rows[0].BetID,
|
r.rows[0].BetID,
|
||||||
|
r.rows[0].SportID,
|
||||||
r.rows[0].EventID,
|
r.rows[0].EventID,
|
||||||
r.rows[0].OddID,
|
r.rows[0].OddID,
|
||||||
r.rows[0].HomeTeamName,
|
r.rows[0].HomeTeamName,
|
||||||
|
|
@ -49,7 +50,7 @@ func (r iteratorForCreateBetOutcome) Err() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateBetOutcome(ctx context.Context, arg []CreateBetOutcomeParams) (int64, error) {
|
func (q *Queries) CreateBetOutcome(ctx context.Context, arg []CreateBetOutcomeParams) (int64, error) {
|
||||||
return q.db.CopyFrom(ctx, []string{"bet_outcomes"}, []string{"bet_id", "event_id", "odd_id", "home_team_name", "away_team_name", "market_id", "market_name", "odd", "odd_name", "odd_header", "odd_handicap", "expires"}, &iteratorForCreateBetOutcome{rows: arg})
|
return q.db.CopyFrom(ctx, []string{"bet_outcomes"}, []string{"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", "expires"}, &iteratorForCreateBetOutcome{rows: arg})
|
||||||
}
|
}
|
||||||
|
|
||||||
// iteratorForCreateTicketOutcome implements pgx.CopyFromSource.
|
// iteratorForCreateTicketOutcome implements pgx.CopyFromSource.
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,16 @@ import (
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const DeleteEvent = `-- name: DeleteEvent :exec
|
||||||
|
DELETE FROM events
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteEvent(ctx context.Context, id string) error {
|
||||||
|
_, err := q.db.Exec(ctx, DeleteEvent, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const GetAllUpcomingEvents = `-- name: GetAllUpcomingEvents :many
|
const GetAllUpcomingEvents = `-- name: GetAllUpcomingEvents :many
|
||||||
SELECT id,
|
SELECT id,
|
||||||
sport_id,
|
sport_id,
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ type Bet struct {
|
||||||
type BetOutcome struct {
|
type BetOutcome struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
BetID int64 `json:"bet_id"`
|
BetID int64 `json:"bet_id"`
|
||||||
|
SportID int64 `json:"sport_id"`
|
||||||
EventID int64 `json:"event_id"`
|
EventID int64 `json:"event_id"`
|
||||||
OddID int64 `json:"odd_id"`
|
OddID int64 `json:"odd_id"`
|
||||||
HomeTeamName string `json:"home_team_name"`
|
HomeTeamName string `json:"home_team_name"`
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ func (q *Queries) CreateResult(ctx context.Context, arg CreateResultParams) (Res
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetPendingBetOutcomes = `-- name: GetPendingBetOutcomes :many
|
const GetPendingBetOutcomes = `-- name: GetPendingBetOutcomes :many
|
||||||
SELECT id, bet_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 status = 0 AND expires <= CURRENT_TIMESTAMP
|
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 status = 0 AND expires <= CURRENT_TIMESTAMP
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetPendingBetOutcomes(ctx context.Context) ([]BetOutcome, error) {
|
func (q *Queries) GetPendingBetOutcomes(ctx context.Context) ([]BetOutcome, error) {
|
||||||
|
|
@ -85,6 +85,7 @@ func (q *Queries) GetPendingBetOutcomes(ctx context.Context) ([]BetOutcome, erro
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.BetID,
|
&i.BetID,
|
||||||
|
&i.SportID,
|
||||||
&i.EventID,
|
&i.EventID,
|
||||||
&i.OddID,
|
&i.OddID,
|
||||||
&i.HomeTeamName,
|
&i.HomeTeamName,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ type BetOutcome struct {
|
||||||
BetID int64 `json:"bet_id" example:"1"`
|
BetID int64 `json:"bet_id" example:"1"`
|
||||||
EventID int64 `json:"event_id" example:"1"`
|
EventID int64 `json:"event_id" example:"1"`
|
||||||
OddID int64 `json:"odd_id" example:"1"`
|
OddID int64 `json:"odd_id" example:"1"`
|
||||||
|
SportID int64 `json:"sport_id" example:"1"`
|
||||||
HomeTeamName string `json:"home_team_name" example:"Manchester"`
|
HomeTeamName string `json:"home_team_name" example:"Manchester"`
|
||||||
AwayTeamName string `json:"away_team_name" example:"Liverpool"`
|
AwayTeamName string `json:"away_team_name" example:"Liverpool"`
|
||||||
MarketID int64 `json:"market_id" example:"1"`
|
MarketID int64 `json:"market_id" example:"1"`
|
||||||
|
|
@ -25,6 +26,7 @@ type CreateBetOutcome struct {
|
||||||
BetID int64 `json:"bet_id" example:"1"`
|
BetID int64 `json:"bet_id" example:"1"`
|
||||||
EventID int64 `json:"event_id" example:"1"`
|
EventID int64 `json:"event_id" example:"1"`
|
||||||
OddID int64 `json:"odd_id" example:"1"`
|
OddID int64 `json:"odd_id" example:"1"`
|
||||||
|
SportID int64 `json:"sport_id" example:"1"`
|
||||||
HomeTeamName string `json:"home_team_name" example:"Manchester"`
|
HomeTeamName string `json:"home_team_name" example:"Manchester"`
|
||||||
AwayTeamName string `json:"away_team_name" example:"Liverpool"`
|
AwayTeamName string `json:"away_team_name" example:"Liverpool"`
|
||||||
MarketID int64 `json:"market_id" example:"1"`
|
MarketID int64 `json:"market_id" example:"1"`
|
||||||
|
|
@ -78,4 +80,3 @@ type CreateBet struct {
|
||||||
IsShopBet bool
|
IsShopBet bool
|
||||||
CashoutID string
|
CashoutID string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
30
internal/domain/league.go
Normal file
30
internal/domain/league.go
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
// TODO Will make this dynamic by moving into the database
|
||||||
|
|
||||||
|
var SupportedLeagues = []int64{
|
||||||
|
// Football
|
||||||
|
10041282, //Premier League
|
||||||
|
10083364, //La Liga
|
||||||
|
10041095, //German Bundesliga
|
||||||
|
10041100, //Ligue 1
|
||||||
|
10041809, //UEFA Champions League
|
||||||
|
10041957, //UEFA Europa League
|
||||||
|
10079560, //UEFA Conference League
|
||||||
|
10047168, // US MLS
|
||||||
|
|
||||||
|
10050282, //UEFA Nations League
|
||||||
|
10040795, //EuroLeague
|
||||||
|
|
||||||
|
10043156, //England FA Cup
|
||||||
|
10042103, //France Cup
|
||||||
|
10041088, //Premier League 2
|
||||||
|
10084250, //Turkiye Super League
|
||||||
|
10041187, //Kenya Super League
|
||||||
|
10041315, //Italian Serie A
|
||||||
|
10041391, //Netherlands Eredivisie
|
||||||
|
|
||||||
|
// Basketball
|
||||||
|
173998768, //NBA
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,62 +1,126 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResultResponse struct {
|
type BaseResultResponse struct {
|
||||||
Success int `json:"success"`
|
Success int `json:"success"`
|
||||||
Results []struct {
|
Results []json.RawMessage `json:"results"`
|
||||||
ID string `json:"id"`
|
}
|
||||||
SportID string `json:"sport_id"`
|
type FootballResultResponse struct {
|
||||||
Time string `json:"time"`
|
ID string `json:"id"`
|
||||||
TimeStatus string `json:"time_status"`
|
SportID string `json:"sport_id"`
|
||||||
League struct {
|
Time string `json:"time"`
|
||||||
ID string `json:"id"`
|
TimeStatus string `json:"time_status"`
|
||||||
Name string `json:"name"`
|
League struct {
|
||||||
CC string `json:"cc"`
|
ID string `json:"id"`
|
||||||
} `json:"league"`
|
Name string `json:"name"`
|
||||||
Home struct {
|
CC string `json:"cc"`
|
||||||
ID string `json:"id"`
|
} `json:"league"`
|
||||||
Name string `json:"name"`
|
Home struct {
|
||||||
ImageID string `json:"image_id"`
|
ID string `json:"id"`
|
||||||
CC string `json:"cc"`
|
Name string `json:"name"`
|
||||||
} `json:"home"`
|
ImageID string `json:"image_id"`
|
||||||
Away struct {
|
CC string `json:"cc"`
|
||||||
ID string `json:"id"`
|
} `json:"home"`
|
||||||
Name string `json:"name"`
|
Away struct {
|
||||||
ImageID string `json:"image_id"`
|
ID string `json:"id"`
|
||||||
CC string `json:"cc"`
|
Name string `json:"name"`
|
||||||
} `json:"away"`
|
ImageID string `json:"image_id"`
|
||||||
SS string `json:"ss"`
|
CC string `json:"cc"`
|
||||||
Scores struct {
|
} `json:"away"`
|
||||||
FirstHalf Score `json:"1"`
|
SS string `json:"ss"`
|
||||||
SecondHalf Score `json:"2"`
|
Scores struct {
|
||||||
} `json:"scores"`
|
FirstHalf Score `json:"1"`
|
||||||
Stats struct {
|
SecondHalf Score `json:"2"`
|
||||||
Attacks []string `json:"attacks"`
|
} `json:"scores"`
|
||||||
Corners []string `json:"corners"`
|
Stats struct {
|
||||||
DangerousAttacks []string `json:"dangerous_attacks"`
|
Attacks []string `json:"attacks"`
|
||||||
Goals []string `json:"goals"`
|
Corners []string `json:"corners"`
|
||||||
OffTarget []string `json:"off_target"`
|
DangerousAttacks []string `json:"dangerous_attacks"`
|
||||||
OnTarget []string `json:"on_target"`
|
Goals []string `json:"goals"`
|
||||||
Penalties []string `json:"penalties"`
|
OffTarget []string `json:"off_target"`
|
||||||
PossessionRT []string `json:"possession_rt"`
|
OnTarget []string `json:"on_target"`
|
||||||
RedCards []string `json:"redcards"`
|
Penalties []string `json:"penalties"`
|
||||||
Substitutions []string `json:"substitutions"`
|
PossessionRT []string `json:"possession_rt"`
|
||||||
YellowCards []string `json:"yellowcards"`
|
RedCards []string `json:"redcards"`
|
||||||
} `json:"stats"`
|
Substitutions []string `json:"substitutions"`
|
||||||
Extra struct {
|
YellowCards []string `json:"yellowcards"`
|
||||||
HomePos string `json:"home_pos"`
|
} `json:"stats"`
|
||||||
AwayPos string `json:"away_pos"`
|
Extra struct {
|
||||||
StadiumData map[string]string `json:"stadium_data"`
|
HomePos string `json:"home_pos"`
|
||||||
Round string `json:"round"`
|
AwayPos string `json:"away_pos"`
|
||||||
} `json:"extra"`
|
StadiumData map[string]string `json:"stadium_data"`
|
||||||
Events []map[string]string `json:"events"`
|
Round string `json:"round"`
|
||||||
HasLineup int `json:"has_lineup"`
|
} `json:"extra"`
|
||||||
ConfirmedAt string `json:"confirmed_at"`
|
Events []map[string]string `json:"events"`
|
||||||
Bet365ID string `json:"bet365_id"`
|
HasLineup int `json:"has_lineup"`
|
||||||
} `json:"results"`
|
ConfirmedAt string `json:"confirmed_at"`
|
||||||
|
Bet365ID string `json:"bet365_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasketballResultResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
SportID string `json:"sport_id"`
|
||||||
|
Time string `json:"time"`
|
||||||
|
TimeStatus string `json:"time_status"`
|
||||||
|
League struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
CC string `json:"cc"`
|
||||||
|
} `json:"league"`
|
||||||
|
Home struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ImageID string `json:"image_id"`
|
||||||
|
CC string `json:"cc"`
|
||||||
|
} `json:"home"`
|
||||||
|
Away struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ImageID string `json:"image_id"`
|
||||||
|
CC string `json:"cc"`
|
||||||
|
} `json:"away"`
|
||||||
|
SS string `json:"ss"`
|
||||||
|
Scores struct {
|
||||||
|
FirstQuarter Score `json:"1"`
|
||||||
|
SecondQuarter Score `json:"2"`
|
||||||
|
FirstHalf Score `json:"3"`
|
||||||
|
ThirdQuarter Score `json:"4"`
|
||||||
|
FourthQuarter Score `json:"5"`
|
||||||
|
TotalScore Score `json:"7"`
|
||||||
|
} `json:"scores"`
|
||||||
|
Stats struct {
|
||||||
|
TwoPoints []string `json:"2points"`
|
||||||
|
ThreePoints []string `json:"3points"`
|
||||||
|
BiggestLead []string `json:"biggest_lead"`
|
||||||
|
Fouls []string `json:"fouls"`
|
||||||
|
FreeThrows []string `json:"free_throws"`
|
||||||
|
FreeThrowRate []string `json:"free_throws_rate"`
|
||||||
|
LeadChanges []string `json:"lead_changes"`
|
||||||
|
MaxpointsInarow []string `json:"maxpoints_inarow"`
|
||||||
|
Possession []string `json:"possession"`
|
||||||
|
SuccessAttempts []string `json:"success_attempts"`
|
||||||
|
TimeSpendInLead []string `json:"timespent_inlead"`
|
||||||
|
Timeuts []string `json:"time_outs"`
|
||||||
|
} `json:"stats"`
|
||||||
|
Extra struct {
|
||||||
|
HomePos string `json:"home_pos"`
|
||||||
|
AwayPos string `json:"away_pos"`
|
||||||
|
AwayManager map[string]string `json:"away_manager"`
|
||||||
|
HomeManager map[string]string `json:"home_manager"`
|
||||||
|
NumberOfPeriods string `json:"numberofperiods"`
|
||||||
|
PeriodLength string `json:"periodlength"`
|
||||||
|
StadiumData map[string]string `json:"stadium_data"`
|
||||||
|
Length string `json:"length"`
|
||||||
|
Round string `json:"round"`
|
||||||
|
} `json:"extra"`
|
||||||
|
Events []map[string]string `json:"events"`
|
||||||
|
HasLineup int `json:"has_lineup"`
|
||||||
|
ConfirmedAt string `json:"confirmed_at"`
|
||||||
|
Bet365ID string `json:"bet365_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Score struct {
|
type Score struct {
|
||||||
|
|
@ -67,7 +131,7 @@ type Score struct {
|
||||||
type MarketConfig struct {
|
type MarketConfig struct {
|
||||||
Sport string
|
Sport string
|
||||||
MarketCategories map[string]bool
|
MarketCategories map[string]bool
|
||||||
MarketTypes map[string]bool
|
MarketTypes map[int64]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
|
|
@ -101,5 +165,6 @@ const (
|
||||||
OUTCOME_STATUS_PENDING OutcomeStatus = 0
|
OUTCOME_STATUS_PENDING OutcomeStatus = 0
|
||||||
OUTCOME_STATUS_WIN OutcomeStatus = 1
|
OUTCOME_STATUS_WIN OutcomeStatus = 1
|
||||||
OUTCOME_STATUS_LOSS OutcomeStatus = 2
|
OUTCOME_STATUS_LOSS OutcomeStatus = 2
|
||||||
OUTCOME_STATUS_VOID OutcomeStatus = 3
|
OUTCOME_STATUS_VOID OutcomeStatus = 3 //Give Back
|
||||||
|
OUTCOME_STATUS_HALF OutcomeStatus = 4 //Half Win and Half Given Back
|
||||||
)
|
)
|
||||||
|
|
|
||||||
34
internal/domain/sport.go
Normal file
34
internal/domain/sport.go
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
type Sport int64
|
||||||
|
|
||||||
|
const (
|
||||||
|
FOOTBALL = 1
|
||||||
|
BASKETBALL = 18
|
||||||
|
VOLLEYBALL = 91
|
||||||
|
HANDBALL = 78
|
||||||
|
BASEBALL = 16
|
||||||
|
HORSE_RACING = 2
|
||||||
|
GREYHOUNDS = 4
|
||||||
|
ICE_HOCKEY = 17
|
||||||
|
SNOOKER = 14
|
||||||
|
AMERICAN_FOOTBALL = 12
|
||||||
|
CRICKET = 3
|
||||||
|
FUTSAL = 83
|
||||||
|
DARTS = 15
|
||||||
|
TABLE_TENNIS = 92
|
||||||
|
BADMINTON = 94
|
||||||
|
RUGBY_UNION = 8
|
||||||
|
RUGBY_LEAGUE = 19
|
||||||
|
AUSTRALIAN_RULES = 36
|
||||||
|
BOWLS = 66
|
||||||
|
BOXING = 9
|
||||||
|
GAELIC_SPORTS = 75
|
||||||
|
FLOORBALL = 90
|
||||||
|
BEACH_VOLLEYBALL = 95
|
||||||
|
WATER_POLO = 110
|
||||||
|
SQUASH = 107
|
||||||
|
E_SPORTS = 151
|
||||||
|
MMA = 162
|
||||||
|
SURFING = 148
|
||||||
|
)
|
||||||
147
internal/domain/sportmarket.go
Normal file
147
internal/domain/sportmarket.go
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
type FootballMarket int64
|
||||||
|
|
||||||
|
const (
|
||||||
|
FOOTBALL_FULL_TIME_RESULT FootballMarket = 40 //"full_time_result"
|
||||||
|
FOOTBALL_DOUBLE_CHANCE FootballMarket = 10114 //"double_chance"
|
||||||
|
FOOTBALL_GOALS_OVER_UNDER FootballMarket = 981 //"goals_over_under"
|
||||||
|
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
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Main
|
||||||
|
BASKETBALL_GAME_LINES BasketBallMarket = 1453 //"game_lines"
|
||||||
|
BASKETBALL_FIRST_HALF BasketBallMarket = 928 //"1st_half"
|
||||||
|
BASKETBALL_FIRST_QUARTER BasketBallMarket = 941 //"1st_quarter"
|
||||||
|
|
||||||
|
// Main Props
|
||||||
|
BASKETBALL_RESULT_AND_BOTH_TEAMS_TO_SCORE_X_POINTS BasketBallMarket = 181273 //"result_and_both_teams_to_score_'x'_points"
|
||||||
|
BASKETBALL_DOUBLE_RESULT BasketBallMarket = 1517 //"double_result"
|
||||||
|
BASKETBALL_MATCH_RESULT_AND_TOTAL BasketBallMarket = 181125 //"match_result_and_total"
|
||||||
|
BASKETBALL_MATCH_HANDICAP_AND_TOTAL BasketBallMarket = 181126 //"match_handicap_and_total"
|
||||||
|
|
||||||
|
// Half Props
|
||||||
|
BASKETBALL_FIRST_HALF_TEAM_TOTALS BasketBallMarket = 181159 //"1st_half_team_totals"
|
||||||
|
BASKETBALL_FIRST_HALF_WINNING_MARGIN BasketBallMarket = 181185 //"1st_half_winning_margin"
|
||||||
|
BASKETBALL_FIRST_HALF_HANDICAP_AND_TOTAL BasketBallMarket = 181182 //"1st_half_handicap_and_total"
|
||||||
|
BASKETBALL_FIRST_HALF_BOTH_TEAMS_TO_SCORE_X_POINTS BasketBallMarket = 181195 //"1st_half_both_teams_to_score_x_points"
|
||||||
|
BASKETBALL_FIRST_HALF_MONEY_LINE_3_WAY BasketBallMarket = 181183 //"1st_half_money_line_3_way"
|
||||||
|
|
||||||
|
// Others
|
||||||
|
BASKETBALL_GAME_TOTAL_ODD_EVEN BasketBallMarket = 180013 //"game_total_odd_even"
|
||||||
|
BASKETBALL_FIRST_QUARTER_TOTAL_ODD_EVEN BasketBallMarket = 180170 //"1st_quarter_total_odd_even"
|
||||||
|
BASKETBALL_HIGHEST_SCORING_HALF BasketBallMarket = 181131 //"highest_scoring_half"
|
||||||
|
BASKETBALL_HIGHEST_SCORING_QUARTER BasketBallMarket = 181132 //"highest_scoring_quarter"
|
||||||
|
BASKETBALL_FIRST_HALF_DOUBLE_CHANCE BasketBallMarket = 181184 //"1st_half_double_chance"
|
||||||
|
BASKETBALL_FIRST_HALF_TOTAL_ODD_EVEN BasketBallMarket = 181204 //"1st_half_total_odd_even"
|
||||||
|
BASKETBALL_FIRST_QUARTER_HANDICAP_AND_TOTAL BasketBallMarket = 181243 //"1st_quarter_handicap_and_total"
|
||||||
|
BASKETBALL_FIRST_QUARTER_DOUBLE_CHANCE BasketBallMarket = 181245 //"1st_quarter_double_chance"
|
||||||
|
|
||||||
|
// Quarter Props
|
||||||
|
BASKETBALL_FIRST_QUARTER_TEAM_TOTALS BasketBallMarket = 181220 //"1st_quarter_team_totals"
|
||||||
|
BASKETBALL_FIRST_QUARTER_WINNING_MARGIN BasketBallMarket = 181247 //"1st_quarter_winning_margin"
|
||||||
|
|
||||||
|
// Team Props
|
||||||
|
BASKETBALL_TEAM_WITH_HIGHEST_SCORING_QUARTER BasketBallMarket = 181377 //"team_with_highest_scoring_quarter"
|
||||||
|
BASKETBALL_TEAM_TOTALS BasketBallMarket = 181335 //"team_totals"
|
||||||
|
|
||||||
|
BASKETBALL_TEAM_TOTAL_ODD_EVEN BasketBallMarket = 1731 //"team_total_odd_even"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IceHockeyMarket int64
|
||||||
|
|
||||||
|
const (
|
||||||
|
ICE_HOCKEY_FIRST_PERIOD IceHockeyMarket = 1531
|
||||||
|
ICE_HOCKEY_GAME_LINES IceHockeyMarket = 972
|
||||||
|
ICE_HOCKEY_THREE_WAY IceHockeyMarket = 170008
|
||||||
|
ICE_HOCKEY_DRAW_NO_BET IceHockeyMarket = 170447
|
||||||
|
ICE_HOCKEY_DOUBLE_CHANCE IceHockeyMarket = 170038
|
||||||
|
ICE_HOCKEY_WINNING_MARGIN IceHockeyMarket = 1556
|
||||||
|
ICE_HOCKEY_HIGHEST_SCORING_PERIOD IceHockeyMarket = 1557
|
||||||
|
ICE_HOCKEY_TIED_AFTER_REGULATION IceHockeyMarket = 170479
|
||||||
|
ICE_HOCKEY_WHEN_WILL_MATCH_END IceHockeyMarket = 170481
|
||||||
|
ICE_HOCKEY_GAME_TOTAL_ODD_EVEN IceHockeyMarket = 170013
|
||||||
|
|
||||||
|
ICE_HOCKEY_ALTERNATIVE_PUCK_LINE_TWO_WAY IceHockeyMarket = 170226
|
||||||
|
ICE_HOCKEY_ALTERNATIVE_TOTAL_TWO_WAY IceHockeyMarket = 170240
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Move this into the database so that it can be modified dynamically
|
||||||
|
var SupportedMarkets = map[string]MarketConfig{
|
||||||
|
"football": {
|
||||||
|
Sport: "football",
|
||||||
|
MarketCategories: map[string]bool{
|
||||||
|
"main": true,
|
||||||
|
"asian_lines": true,
|
||||||
|
"goals": true,
|
||||||
|
"half": true,
|
||||||
|
},
|
||||||
|
MarketTypes: map[int64]bool{
|
||||||
|
int64(FOOTBALL_FULL_TIME_RESULT): true, //"full_time_result"
|
||||||
|
int64(FOOTBALL_DOUBLE_CHANCE): true, //"double_chance"
|
||||||
|
int64(FOOTBALL_GOALS_OVER_UNDER): true, //"goals_over_under"
|
||||||
|
int64(FOOTBALL_CORRECT_SCORE): true, //"correct_score"
|
||||||
|
int64(FOOTBALL_ASIAN_HANDICAP): true, //"asian_handicap"
|
||||||
|
int64(FOOTBALL_GOAL_LINE): true, //"goal_line"
|
||||||
|
int64(FOOTBALL_HALF_TIME_RESULT): true, //"half_time_result"
|
||||||
|
int64(FOOTBALL_FIRST_HALF_ASIAN_HANDICAP): true, //"1st_half_asian_handicap"
|
||||||
|
int64(FOOTBALL_FIRST_HALF_GOAL_LINE): true, //"1st_half_goal_line"
|
||||||
|
int64(FOOTBALL_FIRST_TEAM_TO_SCORE): true, //"first_team_to_score"
|
||||||
|
int64(FOOTBALL_GOALS_ODD_EVEN): true, //"goals_odd_even"
|
||||||
|
int64(FOOTBALL_DRAW_NO_BET): true, //"draw_no_bet"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"basketball": {
|
||||||
|
Sport: "basketball",
|
||||||
|
MarketCategories: map[string]bool{
|
||||||
|
"main": true,
|
||||||
|
"main_props": true,
|
||||||
|
"others": true,
|
||||||
|
"quarter_props": true,
|
||||||
|
"team_props": true,
|
||||||
|
"half_props": true,
|
||||||
|
},
|
||||||
|
MarketTypes: map[int64]bool{
|
||||||
|
|
||||||
|
int64(BASKETBALL_GAME_LINES): true,
|
||||||
|
int64(BASKETBALL_FIRST_HALF): true,
|
||||||
|
int64(BASKETBALL_FIRST_QUARTER): true,
|
||||||
|
int64(BASKETBALL_RESULT_AND_BOTH_TEAMS_TO_SCORE_X_POINTS): true,
|
||||||
|
int64(BASKETBALL_DOUBLE_RESULT): true,
|
||||||
|
int64(BASKETBALL_MATCH_RESULT_AND_TOTAL): true,
|
||||||
|
int64(BASKETBALL_MATCH_HANDICAP_AND_TOTAL): false,
|
||||||
|
int64(BASKETBALL_GAME_TOTAL_ODD_EVEN): true,
|
||||||
|
int64(BASKETBALL_TEAM_TOTALS): true,
|
||||||
|
int64(BASKETBALL_TEAM_TOTAL_ODD_EVEN): true,
|
||||||
|
|
||||||
|
int64(BASKETBALL_FIRST_HALF_TEAM_TOTALS): true,
|
||||||
|
int64(BASKETBALL_FIRST_HALF_WINNING_MARGIN): false,
|
||||||
|
int64(BASKETBALL_FIRST_HALF_HANDICAP_AND_TOTAL): true,
|
||||||
|
int64(BASKETBALL_FIRST_HALF_BOTH_TEAMS_TO_SCORE_X_POINTS): true,
|
||||||
|
int64(BASKETBALL_FIRST_HALF_MONEY_LINE_3_WAY): true,
|
||||||
|
int64(BASKETBALL_FIRST_HALF_DOUBLE_CHANCE): true,
|
||||||
|
int64(BASKETBALL_FIRST_HALF_TOTAL_ODD_EVEN): true,
|
||||||
|
int64(BASKETBALL_HIGHEST_SCORING_HALF): true,
|
||||||
|
|
||||||
|
int64(BASKETBALL_FIRST_QUARTER_HANDICAP_AND_TOTAL): false,
|
||||||
|
int64(BASKETBALL_FIRST_QUARTER_DOUBLE_CHANCE): true,
|
||||||
|
int64(BASKETBALL_FIRST_QUARTER_TEAM_TOTALS): true,
|
||||||
|
int64(BASKETBALL_FIRST_QUARTER_WINNING_MARGIN): true,
|
||||||
|
int64(BASKETBALL_FIRST_QUARTER_TOTAL_ODD_EVEN): true,
|
||||||
|
int64(BASKETBALL_HIGHEST_SCORING_QUARTER): true,
|
||||||
|
int64(BASKETBALL_TEAM_WITH_HIGHEST_SCORING_QUARTER): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -31,27 +31,33 @@ func convertDBBet(bet dbgen.Bet) domain.Bet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertDBBetOutcomes(bet dbgen.BetWithOutcome) domain.GetBet {
|
func convertDBBetOutcomes(outcome dbgen.BetOutcome) domain.BetOutcome {
|
||||||
|
return domain.BetOutcome{
|
||||||
|
ID: outcome.ID,
|
||||||
|
BetID: outcome.BetID,
|
||||||
|
SportID: outcome.SportID,
|
||||||
|
EventID: outcome.EventID,
|
||||||
|
OddID: outcome.OddID,
|
||||||
|
HomeTeamName: outcome.HomeTeamName,
|
||||||
|
AwayTeamName: outcome.AwayTeamName,
|
||||||
|
MarketID: outcome.MarketID,
|
||||||
|
MarketName: outcome.MarketName,
|
||||||
|
Odd: outcome.Odd,
|
||||||
|
OddName: outcome.OddName,
|
||||||
|
OddHeader: outcome.OddHeader,
|
||||||
|
OddHandicap: outcome.OddHandicap,
|
||||||
|
Status: domain.OutcomeStatus(outcome.Status),
|
||||||
|
Expires: outcome.Expires.Time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertDBBetWithOutcomes(bet dbgen.BetWithOutcome) domain.GetBet {
|
||||||
var outcomes []domain.BetOutcome = make([]domain.BetOutcome, 0, len(bet.Outcomes))
|
var outcomes []domain.BetOutcome = make([]domain.BetOutcome, 0, len(bet.Outcomes))
|
||||||
|
|
||||||
for _, outcome := range bet.Outcomes {
|
for _, outcome := range bet.Outcomes {
|
||||||
outcomes = append(outcomes, domain.BetOutcome{
|
outcomes = append(outcomes, convertDBBetOutcomes(outcome))
|
||||||
ID: outcome.ID,
|
|
||||||
BetID: outcome.BetID,
|
|
||||||
EventID: outcome.EventID,
|
|
||||||
OddID: outcome.OddID,
|
|
||||||
HomeTeamName: outcome.HomeTeamName,
|
|
||||||
AwayTeamName: outcome.AwayTeamName,
|
|
||||||
MarketID: outcome.MarketID,
|
|
||||||
MarketName: outcome.MarketName,
|
|
||||||
Odd: outcome.Odd,
|
|
||||||
OddName: outcome.OddName,
|
|
||||||
OddHeader: outcome.OddHeader,
|
|
||||||
OddHandicap: outcome.OddHandicap,
|
|
||||||
Status: domain.OutcomeStatus(outcome.Status),
|
|
||||||
Expires: outcome.Expires.Time,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return domain.GetBet{
|
return domain.GetBet{
|
||||||
ID: bet.ID,
|
ID: bet.ID,
|
||||||
Amount: domain.Currency(bet.Amount),
|
Amount: domain.Currency(bet.Amount),
|
||||||
|
|
@ -78,6 +84,7 @@ func convertDBCreateBetOutcome(betOutcome domain.CreateBetOutcome) dbgen.CreateB
|
||||||
return dbgen.CreateBetOutcomeParams{
|
return dbgen.CreateBetOutcomeParams{
|
||||||
BetID: betOutcome.BetID,
|
BetID: betOutcome.BetID,
|
||||||
EventID: betOutcome.EventID,
|
EventID: betOutcome.EventID,
|
||||||
|
SportID: betOutcome.SportID,
|
||||||
OddID: betOutcome.OddID,
|
OddID: betOutcome.OddID,
|
||||||
HomeTeamName: betOutcome.HomeTeamName,
|
HomeTeamName: betOutcome.HomeTeamName,
|
||||||
AwayTeamName: betOutcome.AwayTeamName,
|
AwayTeamName: betOutcome.AwayTeamName,
|
||||||
|
|
@ -145,7 +152,7 @@ func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error)
|
||||||
return domain.GetBet{}, err
|
return domain.GetBet{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return convertDBBetOutcomes(bet), nil
|
return convertDBBetWithOutcomes(bet), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error) {
|
func (s *Store) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error) {
|
||||||
|
|
@ -155,7 +162,7 @@ func (s *Store) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet
|
||||||
return domain.GetBet{}, err
|
return domain.GetBet{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return convertDBBetOutcomes(bet), nil
|
return convertDBBetWithOutcomes(bet), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) GetAllBets(ctx context.Context) ([]domain.GetBet, error) {
|
func (s *Store) GetAllBets(ctx context.Context) ([]domain.GetBet, error) {
|
||||||
|
|
@ -166,7 +173,7 @@ func (s *Store) GetAllBets(ctx context.Context) ([]domain.GetBet, error) {
|
||||||
|
|
||||||
var result []domain.GetBet = make([]domain.GetBet, 0, len(bets))
|
var result []domain.GetBet = make([]domain.GetBet, 0, len(bets))
|
||||||
for _, bet := range bets {
|
for _, bet := range bets {
|
||||||
result = append(result, convertDBBetOutcomes(bet))
|
result = append(result, convertDBBetWithOutcomes(bet))
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|
@ -184,7 +191,7 @@ func (s *Store) GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.
|
||||||
|
|
||||||
var result []domain.GetBet = make([]domain.GetBet, 0, len(bets))
|
var result []domain.GetBet = make([]domain.GetBet, 0, len(bets))
|
||||||
for _, bet := range bets {
|
for _, bet := range bets {
|
||||||
result = append(result, convertDBBetOutcomes(bet))
|
result = append(result, convertDBBetWithOutcomes(bet))
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|
@ -206,6 +213,18 @@ func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.Outcom
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error) {
|
||||||
|
outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, eventID)
|
||||||
|
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) error {
|
func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error {
|
||||||
err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{
|
err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{
|
||||||
Status: int32(status),
|
Status: int32(status),
|
||||||
|
|
|
||||||
|
|
@ -206,3 +206,11 @@ func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore, status
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) DeleteEvent(ctx context.Context, eventID string) error {
|
||||||
|
err := s.queries.DeleteEvent(ctx, eventID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -98,7 +99,7 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
||||||
sportIDs := []int{1}
|
sportIDs := []int{1, 18}
|
||||||
|
|
||||||
for _, sportID := range sportIDs {
|
for _, sportID := range sportIDs {
|
||||||
var totalPages int = 1
|
var totalPages int = 1
|
||||||
|
|
@ -106,7 +107,7 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
||||||
var limit int = 100
|
var limit int = 100
|
||||||
var count int = 0
|
var count int = 0
|
||||||
for page != totalPages {
|
for page != totalPages {
|
||||||
time.Sleep(3 * time.Second) //This will restrict the fetching to 1200 requests per hour
|
// time.Sleep(1 * time.Second) //This will restrict the fetching to 1200 requests per hour
|
||||||
|
|
||||||
page = page + 1
|
page = page + 1
|
||||||
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", sportID, s.token, page)
|
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", sportID, s.token, page)
|
||||||
|
|
@ -147,9 +148,27 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
||||||
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
skippedLeague := 0
|
||||||
for _, ev := range data.Results {
|
for _, ev := range data.Results {
|
||||||
|
|
||||||
startUnix, _ := strconv.ParseInt(ev.Time, 10, 64)
|
startUnix, _ := strconv.ParseInt(ev.Time, 10, 64)
|
||||||
|
// eventID, err := strconv.ParseInt(ev.ID, 10, 64)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Panicf("❌ Invalid event id, eventID %v", ev.ID)
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
leagueID, err := strconv.ParseInt(ev.League.ID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ Invalid league id, leagueID %v", ev.League.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !slices.Contains(domain.SupportedLeagues, leagueID) {
|
||||||
|
skippedLeague++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
event := domain.UpcomingEvent{
|
event := domain.UpcomingEvent{
|
||||||
ID: ev.ID,
|
ID: ev.ID,
|
||||||
SportID: ev.SportID,
|
SportID: ev.SportID,
|
||||||
|
|
@ -179,6 +198,7 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
count++
|
count++
|
||||||
|
log.Printf("Skipped leagues %d", skippedLeague)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ func New(token string, store *repository.Store) *ServiceImpl {
|
||||||
return &ServiceImpl{token: token, store: store}
|
return &ServiceImpl{token: token, store: store}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO this is only getting the main odds, this must be fixed
|
||||||
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||||
eventIDs, err := s.store.GetAllUpcomingEvents(ctx)
|
eventIDs, err := s.store.GetAllUpcomingEvents(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -30,7 +31,7 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, event := range eventIDs {
|
for _, event := range eventIDs {
|
||||||
time.Sleep(3 * time.Second) //This will restrict the fetching to 1200 requests per hour
|
// time.Sleep(3 * time.Second) //This will restrict the fetching to 1200 requests per hour
|
||||||
|
|
||||||
eventID := event.ID
|
eventID := event.ID
|
||||||
prematchURL := "https://api.b365api.com/v3/bet365/prematch?token=" + s.token + "&FI=" + eventID
|
prematchURL := "https://api.b365api.com/v3/bet365/prematch?token=" + s.token + "&FI=" + eventID
|
||||||
|
|
@ -65,7 +66,6 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||||
log.Printf("⚠️ Skipping event %s with no valid ID", eventID)
|
log.Printf("⚠️ Skipping event %s with no valid ID", eventID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
s.storeSection(ctx, finalID, result.FI, "main", result.Main)
|
s.storeSection(ctx, finalID, result.FI, "main", result.Main)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
614
internal/services/result/eval.go
Normal file
614
internal/services/result/eval.go
Normal file
|
|
@ -0,0 +1,614 @@
|
||||||
|
package result
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Football evaluations
|
||||||
|
func evaluateFullTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
switch outcome.OddName {
|
||||||
|
case "1": // Home win
|
||||||
|
if score.Home > score.Away {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
case "Draw":
|
||||||
|
if score.Home == score.Away {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
case "2": // Away win
|
||||||
|
if score.Away > score.Home {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
default:
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if outcome.OddHeader == "Over" {
|
||||||
|
if totalGoals > threshold {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
} else if totalGoals == threshold {
|
||||||
|
return domain.OUTCOME_STATUS_VOID, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
} else if outcome.OddHeader == "Under" {
|
||||||
|
if totalGoals < threshold {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
} else if totalGoals == threshold {
|
||||||
|
return domain.OUTCOME_STATUS_VOID, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluateHalfTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
return evaluateFullTimeResult(outcome, score)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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_VOID {
|
||||||
|
return domain.OUTCOME_STATUS_HALF, nil
|
||||||
|
} else {
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid multi outcome")
|
||||||
|
}
|
||||||
|
case domain.OUTCOME_STATUS_LOSS:
|
||||||
|
if secondOutcome == domain.OUTCOME_STATUS_LOSS {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
case domain.OUTCOME_STATUS_VOID:
|
||||||
|
if secondOutcome == domain.OUTCOME_STATUS_WIN || secondOutcome == domain.OUTCOME_STATUS_LOSS {
|
||||||
|
return domain.OUTCOME_STATUS_HALF, nil
|
||||||
|
} else {
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid multi outcome")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid multi outcome")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
handicap, err := strconv.ParseFloat(handicapStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||||
|
}
|
||||||
|
adjustedHomeScore := float64(score.Home)
|
||||||
|
adjustedAwayScore := float64(score.Away)
|
||||||
|
if outcome.OddHeader == "1" { // Home team
|
||||||
|
adjustedHomeScore += handicap
|
||||||
|
} else if outcome.OddHeader == "2" { // Away team
|
||||||
|
adjustedAwayScore += handicap
|
||||||
|
} else {
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("multi outcome check error")
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("multi outcome check error")
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, 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 newOutcome, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluateGoalLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
return evaluateGoalsOverUnder(outcome, score)
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluateFirstTeamToScore(outcome domain.BetOutcome, events []map[string]string) (domain.OutcomeStatus, error) {
|
||||||
|
for _, event := range events {
|
||||||
|
if strings.Contains(event["text"], "1st Goal") {
|
||||||
|
if strings.Contains(event["text"], outcome.HomeTeamName) && outcome.OddName == "1" {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
} else if strings.Contains(event["text"], outcome.AwayTeamName) && outcome.OddName == "2" {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_VOID, nil // No goals scored
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluateGoalsOddEven(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
totalGoals := score.Home + score.Away
|
||||||
|
isOdd := totalGoals%2 == 1
|
||||||
|
if outcome.OddName == "Odd" && isOdd {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
} else if outcome.OddName == "Even" && !isOdd {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluateDoubleChance(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
isHomeWin := score.Home > score.Away
|
||||||
|
isDraw := score.Home == score.Away
|
||||||
|
isAwayWin := score.Away > score.Home
|
||||||
|
|
||||||
|
switch outcome.OddName {
|
||||||
|
case "1 or Draw", (outcome.HomeTeamName + " or " + "Draw"):
|
||||||
|
if isHomeWin || isDraw {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
case "Draw or 2", ("Draw" + " or " + outcome.AwayTeamName):
|
||||||
|
if isDraw || isAwayWin {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
case "1 or 2", (outcome.HomeTeamName + " or " + outcome.AwayTeamName):
|
||||||
|
if isHomeWin || isAwayWin {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
default:
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluateDrawNoBet(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
if score.Home == score.Away {
|
||||||
|
return domain.OUTCOME_STATUS_VOID, nil
|
||||||
|
}
|
||||||
|
if outcome.OddName == "1" && score.Home > score.Away {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
} else if outcome.OddName == "2" && score.Away > score.Home {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// basketball evaluations
|
||||||
|
|
||||||
|
func evaluateGameLines(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
switch outcome.OddName {
|
||||||
|
case "Money Line":
|
||||||
|
return evaluateMoneyLine(outcome, score)
|
||||||
|
case "Spread":
|
||||||
|
// Since Spread betting is essentially the same thing
|
||||||
|
return evaluateAsianHandicap(outcome, score)
|
||||||
|
case "Total":
|
||||||
|
return evaluateTotalOverUnder(outcome, score)
|
||||||
|
default:
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluateMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
switch outcome.OddHeader {
|
||||||
|
case "1":
|
||||||
|
if score.Home > score.Away {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
|
||||||
|
case "2":
|
||||||
|
if score.Home < score.Away {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
threshold, err := strconv.ParseFloat(overUnderStr[1], 64)
|
||||||
|
if err != nil {
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, 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
|
||||||
|
totalScore := float64(score.Home + score.Away)
|
||||||
|
|
||||||
|
if overUnderStr[0] == "O" {
|
||||||
|
if totalScore > threshold {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
} else if overUnderStr[0] == "U" {
|
||||||
|
if totalScore < threshold {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
overUnder := overUnderStr[0]
|
||||||
|
|
||||||
|
if overUnder != "Over" && overUnder != "Under" {
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet
|
||||||
|
totalScore := float64(score.Home + score.Away)
|
||||||
|
|
||||||
|
switch outcome.OddHeader {
|
||||||
|
case "1":
|
||||||
|
if overUnder == "Over" && totalScore > threshold {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
} else if overUnder == "Under" && totalScore < threshold {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
case "2":
|
||||||
|
if overUnder == "Over" && totalScore > threshold {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
} else if overUnder == "Under" && totalScore < threshold {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
default:
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed to parse over and under: %s", outcome.OddName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
overUnder := overUnderStr[0]
|
||||||
|
|
||||||
|
if overUnder != "Over" && overUnder != "Under" {
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the threshold will come in a xx.5 format, there is no VOID for this kind of bet
|
||||||
|
HomeScore := float64(score.Home)
|
||||||
|
AwayScore := float64(score.Away)
|
||||||
|
|
||||||
|
switch outcome.OddHeader {
|
||||||
|
case "1":
|
||||||
|
if overUnder == "Over" && HomeScore > threshold {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
} else if overUnder == "Under" && HomeScore < threshold {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
case "2":
|
||||||
|
if overUnder == "Over" && AwayScore > threshold {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
} else if overUnder == "Under" && AwayScore < threshold {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
default:
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed to parse over and under: %s", outcome.OddName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate Result and Both Teams To Score X Points
|
||||||
|
func evaluateResultAndBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
|
||||||
|
// The name parameter will hold value "name": "{team_name} and {Yes | No}"
|
||||||
|
// The best way to do this is to evaluate backwards since there might be
|
||||||
|
// teams with 'and' in their name
|
||||||
|
// We know that there is going to be "Yes" and "No "
|
||||||
|
oddNameSplit := strings.Split(outcome.OddName, " ")
|
||||||
|
|
||||||
|
scoreCheckSplit := oddNameSplit[len(oddNameSplit)-1]
|
||||||
|
var isScorePoints bool
|
||||||
|
if scoreCheckSplit == "Yes" {
|
||||||
|
isScorePoints = true
|
||||||
|
} else if scoreCheckSplit == "No" {
|
||||||
|
isScorePoints = false
|
||||||
|
} else {
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch teamName {
|
||||||
|
case outcome.HomeTeamName:
|
||||||
|
if score.Home > score.Away {
|
||||||
|
if isScorePoints && score.Home >= int(threshold) && score.Away >= int(threshold) {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
} else if !isScorePoints && score.Home < int(threshold) && score.Away < int(threshold) {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case outcome.AwayTeamName:
|
||||||
|
if score.Away > score.Home {
|
||||||
|
if isScorePoints && score.Home >= int(threshold) && score.Away >= int(threshold) {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
} else if !isScorePoints && score.Home < int(threshold) && score.Away < int(threshold) {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("team name error: %s", teamName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both Teams To Score X 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch outcome.OddHeader {
|
||||||
|
case "Yes":
|
||||||
|
if score.Home >= int(threshold) && score.Away >= int(threshold) {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
case "No":
|
||||||
|
if score.Home < int(threshold) && score.Away < int(threshold) {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluateMoneyLine3Way(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
|
switch outcome.OddName {
|
||||||
|
case "1": // Home win
|
||||||
|
if score.Home > score.Away {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
case "Tie":
|
||||||
|
if score.Home == score.Away {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
case "2": // Away win
|
||||||
|
if score.Away > score.Home {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
default:
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if secondHalfWinner != outcome.HomeTeamName && secondHalfWinner != outcome.AwayTeamName && secondHalfWinner != "Tie" {
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case firstHalfWinner == outcome.HomeTeamName && firstHalfScore.Home < firstHalfScore.Away:
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
case firstHalfWinner == outcome.AwayTeamName && firstHalfScore.Away < firstHalfScore.Home:
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
case firstHalfWinner == "Tie" && firstHalfScore.Home != firstHalfScore.Away:
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case secondHalfWinner == outcome.HomeTeamName && firstHalfScore.Home < firstHalfScore.Away:
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
case secondHalfWinner == outcome.AwayTeamName && firstHalfScore.Away < firstHalfScore.Home:
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
case secondHalfWinner == "Tie" && firstHalfScore.Home != firstHalfScore.Away:
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
switch outcome.OddName {
|
||||||
|
case "1st Half":
|
||||||
|
if firstHalfTotal > secondHalfTotal {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
case "2nd Half":
|
||||||
|
if firstHalfTotal < secondHalfTotal {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
case "Tie":
|
||||||
|
if firstHalfTotal == secondHalfTotal {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
thirdQuarterTotal := thirdScore.Home + thirdScore.Away
|
||||||
|
fourthQuarterTotal := fourthScore.Home + fourthScore.Away
|
||||||
|
|
||||||
|
switch outcome.OddName {
|
||||||
|
case "1st Quarter":
|
||||||
|
if firstQuarterTotal > secondQuarterTotal && firstQuarterTotal > thirdQuarterTotal && firstQuarterTotal > fourthQuarterTotal {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
case "2nd Quarter":
|
||||||
|
if secondQuarterTotal > firstQuarterTotal && secondQuarterTotal > thirdQuarterTotal && secondQuarterTotal > fourthQuarterTotal {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
case "3rd Quarter":
|
||||||
|
if thirdQuarterTotal > firstQuarterTotal && thirdQuarterTotal > secondQuarterTotal && thirdQuarterTotal > fourthQuarterTotal {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
case "4th Quarter":
|
||||||
|
if fourthQuarterTotal > firstQuarterTotal && fourthQuarterTotal > secondQuarterTotal && fourthQuarterTotal > thirdQuarterTotal {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
case "Tie":
|
||||||
|
if fourthQuarterTotal == firstQuarterTotal && fourthQuarterTotal == secondQuarterTotal && fourthQuarterTotal == thirdQuarterTotal {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
total := float64(score.Home + score.Away)
|
||||||
|
overUnder := nameSplit[len(nameSplit)-2]
|
||||||
|
if overUnder == "Over" {
|
||||||
|
if total < threshold {
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
|
} else if overUnder == "Under" {
|
||||||
|
if total > threshold {
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
teamName := strings.TrimSpace(strings.Join(nameSplit[:len(nameSplit)-4], ""))
|
||||||
|
|
||||||
|
adjustedHomeScore := float64(score.Home)
|
||||||
|
adjustedAwayScore := float64(score.Away)
|
||||||
|
|
||||||
|
switch teamName {
|
||||||
|
case outcome.HomeTeamName:
|
||||||
|
adjustedHomeScore += handicap
|
||||||
|
if adjustedHomeScore > adjustedAwayScore {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
case outcome.AwayTeamName:
|
||||||
|
adjustedAwayScore += handicap
|
||||||
|
if adjustedAwayScore > adjustedHomeScore {
|
||||||
|
return domain.OUTCOME_STATUS_WIN, nil
|
||||||
|
}
|
||||||
|
return domain.OUTCOME_STATUS_LOSS, nil
|
||||||
|
default:
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("failed parsing team name: %s", outcome.OddName)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -31,139 +31,142 @@ func NewService(repo *repository.Store, cfg *config.Config, logger *slog.Logger)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var supportedMarkets = map[string]domain.MarketConfig{
|
type ResultCheck struct {
|
||||||
"football": {
|
|
||||||
Sport: "football",
|
|
||||||
MarketCategories: map[string]bool{
|
|
||||||
"main": true,
|
|
||||||
"asian_lines": true,
|
|
||||||
"goals": true,
|
|
||||||
"half": true,
|
|
||||||
},
|
|
||||||
MarketTypes: map[string]bool{
|
|
||||||
"full_time_result": true,
|
|
||||||
"double_chance": true,
|
|
||||||
"goals_over_under": true,
|
|
||||||
"correct_score": true,
|
|
||||||
"asian_handicap": true,
|
|
||||||
"goal_line": true,
|
|
||||||
"half_time_result": true,
|
|
||||||
"1st_half_asian_handicap": true,
|
|
||||||
"1st_half_goal_line": true,
|
|
||||||
"first_team_to_score": true,
|
|
||||||
"goals_odd_even": true,
|
|
||||||
"draw_no_bet": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
||||||
outcomes, err := s.repo.GetPendingBetOutcomes(ctx)
|
// TODO: Optimize this because there could be many bet outcomes for the same odd
|
||||||
|
// Take market id and match result as param and update all the bet outcomes at the same time
|
||||||
|
events, err := s.repo.GetExpiredUpcomingEvents(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get pending bet outcomes", "error", err)
|
s.logger.Error("Failed to fetch events")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, outcome := range outcomes {
|
for _, event := range events {
|
||||||
if outcome.Expires.After(time.Now()) {
|
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
||||||
continue
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to parse event id")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outcomes, err := s.repo.GetBetOutcomeByEventID(ctx, eventID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get pending bet outcomes", "error", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := s.fetchResult(ctx, outcome.EventID, outcome.OddID, outcome.MarketID, outcome)
|
for _, outcome := range outcomes {
|
||||||
if err != nil {
|
if outcome.Expires.After(time.Now()) {
|
||||||
s.logger.Error("Failed to fetch result", "event_id", outcome.EventID, "error", err)
|
continue
|
||||||
continue
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.repo.CreateResult(ctx, domain.CreateResult{
|
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
|
||||||
BetOutcomeID: outcome.ID,
|
if err != nil {
|
||||||
EventID: outcome.EventID,
|
s.logger.Error("Sport ID is invalid", "event_id", outcome.EventID, "error", err)
|
||||||
OddID: outcome.OddID,
|
continue
|
||||||
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)
|
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)
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = s.repo.DeleteEvent(ctx, event.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err)
|
s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err)
|
||||||
continue
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) FetchAndStoreResult(ctx context.Context, eventID string) error {
|
// func (s *Service) FetchAndStoreResult(ctx context.Context, eventID string) error {
|
||||||
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%s", s.config.Bet365Token, eventID)
|
// url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%s", s.config.Bet365Token, eventID)
|
||||||
|
|
||||||
res, err := s.client.Get(url)
|
// res, err := s.client.Get(url)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
s.logger.Error("Failed to fetch result", "event_id", eventID, "error", err)
|
// s.logger.Error("Failed to fetch result", "event_id", eventID, "error", err)
|
||||||
return fmt.Errorf("failed to fetch result: %w", err)
|
// return fmt.Errorf("failed to fetch result: %w", err)
|
||||||
}
|
// }
|
||||||
defer res.Body.Close()
|
// defer res.Body.Close()
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
// if res.StatusCode != http.StatusOK {
|
||||||
s.logger.Error("Unexpected status code", "event_id", eventID, "status_code", res.StatusCode)
|
// s.logger.Error("Unexpected status code", "event_id", eventID, "status_code", res.StatusCode)
|
||||||
return fmt.Errorf("unexpected status code: %d", res.StatusCode)
|
// return fmt.Errorf("unexpected status code: %d", res.StatusCode)
|
||||||
}
|
// }
|
||||||
|
|
||||||
var apiResp domain.ResultResponse
|
// var apiResp domain.BaseResultResponse
|
||||||
if err := json.NewDecoder(res.Body).Decode(&apiResp); err != nil {
|
// if err := json.NewDecoder(res.Body).Decode(&apiResp); err != nil {
|
||||||
s.logger.Error("Failed to decode result", "event_id", eventID, "error", err)
|
// s.logger.Error("Failed to decode result", "event_id", eventID, "error", err)
|
||||||
return fmt.Errorf("failed to decode result: %w", err)
|
// return fmt.Errorf("failed to decode result: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
if apiResp.Success != 1 || len(apiResp.Results) == 0 {
|
// if apiResp.Success != 1 || len(apiResp.Results) == 0 {
|
||||||
s.logger.Error("Invalid API response", "event_id", eventID)
|
// s.logger.Error("Invalid API response", "event_id", eventID)
|
||||||
return fmt.Errorf("no result returned from API")
|
// return fmt.Errorf("no result returned from API")
|
||||||
}
|
// }
|
||||||
|
|
||||||
r := apiResp.Results[0]
|
// r := apiResp.Results[0]
|
||||||
if r.TimeStatus != "3" {
|
// if r.TimeStatus != "3" {
|
||||||
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
// s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||||
return fmt.Errorf("match not yet completed")
|
// return fmt.Errorf("match not yet completed")
|
||||||
}
|
// }
|
||||||
|
|
||||||
eventIDInt, err := strconv.ParseInt(eventID, 10, 64)
|
// eventIDInt, err := strconv.ParseInt(eventID, 10, 64)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
s.logger.Error("Failed to parse event_id", "event_id", eventID, "error", err)
|
// s.logger.Error("Failed to parse event_id", "event_id", eventID, "error", err)
|
||||||
return fmt.Errorf("failed to parse event_id: %w", err)
|
// return fmt.Errorf("failed to parse event_id: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
halfScore := ""
|
// halfScore := ""
|
||||||
if r.Scores.FirstHalf.Home != "" {
|
// if r.Scores.FirstHalf.Home != "" {
|
||||||
halfScore = fmt.Sprintf("%s-%s", r.Scores.FirstHalf.Home, r.Scores.FirstHalf.Away)
|
// halfScore = fmt.Sprintf("%s-%s", r.Scores.FirstHalf.Home, r.Scores.FirstHalf.Away)
|
||||||
}
|
// }
|
||||||
|
|
||||||
result := domain.Result{
|
// result := domain.Result{
|
||||||
EventID: eventIDInt,
|
// EventID: eventIDInt,
|
||||||
Status: domain.OUTCOME_STATUS_PENDING,
|
// Status: domain.OUTCOME_STATUS_PENDING,
|
||||||
Score: r.SS,
|
// Score: r.SS,
|
||||||
FullTimeScore: r.SS,
|
// FullTimeScore: r.SS,
|
||||||
HalfTimeScore: halfScore,
|
// HalfTimeScore: halfScore,
|
||||||
SS: r.SS,
|
// SS: r.SS,
|
||||||
Scores: make(map[string]domain.Score),
|
// Scores: make(map[string]domain.Score),
|
||||||
}
|
// }
|
||||||
for k, v := range map[string]domain.Score{
|
// for k, v := range map[string]domain.Score{
|
||||||
"1": r.Scores.FirstHalf,
|
// "1": r.Scores.FirstHalf,
|
||||||
"2": r.Scores.SecondHalf,
|
// "2": r.Scores.SecondHalf,
|
||||||
} {
|
// } {
|
||||||
result.Scores[k] = domain.Score{
|
// result.Scores[k] = domain.Score{
|
||||||
Home: v.Home,
|
// Home: v.Home,
|
||||||
Away: v.Away,
|
// Away: v.Away,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return s.repo.InsertResult(ctx, result)
|
// return s.repo.InsertResult(ctx, result)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID, sportID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
||||||
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%d", s.config.Bet365Token, eventID)
|
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%d", s.config.Bet365Token, eventID)
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -183,7 +186,7 @@ func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID int6
|
||||||
return domain.CreateResult{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
return domain.CreateResult{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultResp domain.ResultResponse
|
var resultResp domain.BaseResultResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&resultResp); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&resultResp); err != nil {
|
||||||
s.logger.Error("Failed to decode result", "event_id", eventID, "error", err)
|
s.logger.Error("Failed to decode result", "event_id", eventID, "error", err)
|
||||||
return domain.CreateResult{}, err
|
return domain.CreateResult{}, err
|
||||||
|
|
@ -194,17 +197,47 @@ func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID int6
|
||||||
return domain.CreateResult{}, fmt.Errorf("invalid API response")
|
return domain.CreateResult{}, fmt.Errorf("invalid API response")
|
||||||
}
|
}
|
||||||
|
|
||||||
result := resultResp.Results[0]
|
var result domain.CreateResult
|
||||||
|
switch sportID {
|
||||||
|
case domain.FOOTBALL:
|
||||||
|
result, err = s.parseFootball(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to parse football", "event_id", eventID, "market_id", marketID, "error", err)
|
||||||
|
return domain.CreateResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case domain.BASKETBALL:
|
||||||
|
result, err = s.parseBasketball(resultResp.Results[0], eventID, oddID, marketID, outcome)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to parse basketball", "event_id", eventID, "market_id", marketID, "error", err)
|
||||||
|
return domain.CreateResult{}, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
s.logger.Error("Unsupported sport", "sport", sportID)
|
||||||
|
return domain.CreateResult{}, fmt.Errorf("unsupported sport: %v", sportID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) parseFootball(resultRes json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
||||||
|
var fbResp domain.FootballResultResponse
|
||||||
|
if err := json.Unmarshal(resultRes, &fbResp); err != nil {
|
||||||
|
s.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
|
||||||
|
return domain.CreateResult{}, err
|
||||||
|
}
|
||||||
|
result := fbResp
|
||||||
if result.TimeStatus != "3" {
|
if result.TimeStatus != "3" {
|
||||||
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||||
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
||||||
}
|
}
|
||||||
|
|
||||||
finalScore := parseScore(result.SS)
|
finalScore := parseSS(result.SS)
|
||||||
firstHalfScore := parseScore(fmt.Sprintf("%s-%s", result.Scores.FirstHalf.Home, result.Scores.FirstHalf.Away))
|
firstHalfScore := parseSS(fmt.Sprintf("%s-%s", result.Scores.FirstHalf.Home, result.Scores.FirstHalf.Away))
|
||||||
|
|
||||||
corners := parseStats(result.Stats.Corners)
|
corners := parseStats(result.Stats.Corners)
|
||||||
status, err := s.evaluateOutcome(outcome, finalScore, firstHalfScore, corners, result.Events)
|
status, err := s.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, corners, result.Events)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
|
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
|
||||||
return domain.CreateResult{}, err
|
return domain.CreateResult{}, err
|
||||||
|
|
@ -218,9 +251,44 @@ func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID int6
|
||||||
Status: status,
|
Status: status,
|
||||||
Score: result.SS,
|
Score: result.SS,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseScore(scoreStr string) struct{ Home, Away int } {
|
func (s *Service) parseBasketball(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
|
||||||
|
var basketBallRes domain.BasketballResultResponse
|
||||||
|
if err := json.Unmarshal(response, &basketBallRes); err != nil {
|
||||||
|
s.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
|
||||||
|
return domain.CreateResult{}, err
|
||||||
|
}
|
||||||
|
if basketBallRes.TimeStatus != "3" {
|
||||||
|
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||||
|
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := s.evaluateBasketballOutcome(outcome, basketBallRes)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
|
||||||
|
return domain.CreateResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.CreateResult{
|
||||||
|
BetOutcomeID: 0,
|
||||||
|
EventID: eventID,
|
||||||
|
OddID: oddID,
|
||||||
|
MarketID: marketID,
|
||||||
|
Status: status,
|
||||||
|
Score: basketBallRes.SS,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseScore(home string, away string) struct{ Home, Away int } {
|
||||||
|
homeVal, _ := strconv.Atoi(strings.TrimSpace(home))
|
||||||
|
awaVal, _ := strconv.Atoi(strings.TrimSpace(away))
|
||||||
|
return struct{ Home, Away int }{Home: homeVal, Away: awaVal}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSS(scoreStr string) struct{ Home, Away int } {
|
||||||
parts := strings.Split(scoreStr, "-")
|
parts := strings.Split(scoreStr, "-")
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return struct{ Home, Away int }{0, 0}
|
return struct{ Home, Away int }{0, 0}
|
||||||
|
|
@ -240,37 +308,38 @@ func parseStats(stats []string) struct{ Home, Away int } {
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluateOutcome determines the outcome status based on market type and odd
|
// evaluateOutcome determines the outcome status based on market type and odd
|
||||||
func (s *Service) evaluateOutcome(outcome domain.BetOutcome, finalScore, firstHalfScore struct{ Home, Away int }, corners struct{ Home, Away int }, events []map[string]string) (domain.OutcomeStatus, error) {
|
func (s *Service) evaluateFootballOutcome(outcome domain.BetOutcome, finalScore, firstHalfScore struct{ Home, Away int }, corners struct{ Home, Away int }, events []map[string]string) (domain.OutcomeStatus, error) {
|
||||||
marketConfig := supportedMarkets["football"]
|
|
||||||
if !marketConfig.MarketTypes[outcome.MarketName] {
|
marketConfig := domain.SupportedMarkets["football"]
|
||||||
|
if !marketConfig.MarketTypes[outcome.MarketID] {
|
||||||
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
|
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch outcome.MarketName {
|
switch outcome.MarketID {
|
||||||
case "full_time_result":
|
case int64(domain.FOOTBALL_FULL_TIME_RESULT):
|
||||||
return evaluateFullTimeResult(outcome, finalScore)
|
return evaluateFullTimeResult(outcome, finalScore)
|
||||||
case "goals_over_under":
|
case int64(domain.FOOTBALL_GOALS_OVER_UNDER):
|
||||||
return evaluateGoalsOverUnder(outcome, finalScore)
|
return evaluateGoalsOverUnder(outcome, finalScore)
|
||||||
case "correct_score":
|
case int64(domain.FOOTBALL_CORRECT_SCORE):
|
||||||
return evaluateCorrectScore(outcome, finalScore)
|
return evaluateCorrectScore(outcome, finalScore)
|
||||||
case "half_time_result":
|
case int64(domain.FOOTBALL_HALF_TIME_RESULT):
|
||||||
return evaluateHalfTimeResult(outcome, firstHalfScore)
|
return evaluateHalfTimeResult(outcome, firstHalfScore)
|
||||||
case "asian_handicap":
|
case int64(domain.FOOTBALL_ASIAN_HANDICAP):
|
||||||
return evaluateAsianHandicap(outcome, finalScore)
|
return evaluateAsianHandicap(outcome, finalScore)
|
||||||
case "goal_line":
|
case int64(domain.FOOTBALL_GOAL_LINE):
|
||||||
return evaluateGoalLine(outcome, finalScore)
|
return evaluateGoalLine(outcome, finalScore)
|
||||||
case "1st_half_asian_handicap":
|
case int64(domain.FOOTBALL_FIRST_HALF_ASIAN_HANDICAP):
|
||||||
return evaluateAsianHandicap(outcome, firstHalfScore)
|
return evaluateAsianHandicap(outcome, firstHalfScore)
|
||||||
case "1st_half_goal_line":
|
case int64(domain.FOOTBALL_FIRST_HALF_GOAL_LINE):
|
||||||
return evaluateGoalLine(outcome, firstHalfScore)
|
return evaluateGoalLine(outcome, firstHalfScore)
|
||||||
case "first_team_to_score":
|
case int64(domain.FOOTBALL_FIRST_TEAM_TO_SCORE):
|
||||||
return evaluateFirstTeamToScore(outcome, events)
|
return evaluateFirstTeamToScore(outcome, events)
|
||||||
case "goals_odd_even":
|
case int64(domain.FOOTBALL_GOALS_ODD_EVEN):
|
||||||
return evaluateGoalsOddEven(outcome, finalScore)
|
return evaluateGoalsOddEven(outcome, finalScore)
|
||||||
case "double_chance":
|
case int64(domain.FOOTBALL_DOUBLE_CHANCE):
|
||||||
return evaluateDoubleChance(outcome, finalScore)
|
return evaluateDoubleChance(outcome, finalScore)
|
||||||
case "draw_no_bet":
|
case int64(domain.FOOTBALL_DRAW_NO_BET):
|
||||||
return evaluateDrawNoBet(outcome, finalScore)
|
return evaluateDrawNoBet(outcome, finalScore)
|
||||||
default:
|
default:
|
||||||
s.logger.Warn("Market type not implemented", "market_name", outcome.MarketName)
|
s.logger.Warn("Market type not implemented", "market_name", outcome.MarketName)
|
||||||
|
|
@ -278,159 +347,71 @@ func (s *Service) evaluateOutcome(outcome domain.BetOutcome, finalScore, firstHa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateFullTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func (s *Service) evaluateBasketballOutcome(outcome domain.BetOutcome, res domain.BasketballResultResponse) (domain.OutcomeStatus, error) {
|
||||||
switch outcome.OddName {
|
marketConfig := domain.SupportedMarkets["basketball"]
|
||||||
case "1": // Home win
|
if !marketConfig.MarketTypes[outcome.MarketID] {
|
||||||
if score.Home > score.Away {
|
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
||||||
}
|
}
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
|
||||||
case "Draw":
|
finalScore := parseSS(res.SS)
|
||||||
if score.Home == score.Away {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
firstHalfScore := parseScore(res.Scores.FirstHalf.Home, res.Scores.FirstHalf.Away)
|
||||||
}
|
secondHalfScore := struct{ Home, Away int }{Home: finalScore.Home - firstHalfScore.Home, Away: finalScore.Away - firstHalfScore.Away}
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
|
||||||
case "2": // Away win
|
firstQuarter := parseScore(res.Scores.FirstQuarter.Home, res.Scores.FirstQuarter.Away)
|
||||||
if score.Away > score.Home {
|
secondQuarter := parseScore(res.Scores.SecondQuarter.Home, res.Scores.SecondQuarter.Away)
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
thirdQuarter := parseScore(res.Scores.ThirdQuarter.Home, res.Scores.ThirdQuarter.Away)
|
||||||
}
|
fourthQuarter := parseScore(res.Scores.FourthQuarter.Home, res.Scores.FourthQuarter.Away)
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
|
||||||
|
switch outcome.MarketID {
|
||||||
|
case int64(domain.BASKETBALL_GAME_LINES):
|
||||||
|
return evaluateGameLines(outcome, finalScore)
|
||||||
|
case int64(domain.BASKETBALL_RESULT_AND_BOTH_TEAMS_TO_SCORE_X_POINTS):
|
||||||
|
return evaluateResultAndBTTSX(outcome, finalScore)
|
||||||
|
case int64(domain.BASKETBALL_DOUBLE_RESULT):
|
||||||
|
return evaluateDoubleResult(outcome, firstHalfScore, secondHalfScore)
|
||||||
|
case int64(domain.BASKETBALL_MATCH_RESULT_AND_TOTAL):
|
||||||
|
return evaluateResultAndTotal(outcome, finalScore)
|
||||||
|
case int64(domain.BASKETBALL_MATCH_HANDICAP_AND_TOTAL):
|
||||||
|
return evaluateHandicapAndTotal(outcome, finalScore)
|
||||||
|
case int64(domain.BASKETBALL_GAME_TOTAL_ODD_EVEN):
|
||||||
|
return evaluateGoalsOddEven(outcome, finalScore)
|
||||||
|
case int64(domain.BASKETBALL_TEAM_TOTALS):
|
||||||
|
return evaluateGoalsOddEven(outcome, finalScore)
|
||||||
|
|
||||||
|
case int64(domain.BASKETBALL_FIRST_HALF):
|
||||||
|
return evaluateGameLines(outcome, firstHalfScore)
|
||||||
|
case int64(domain.BASKETBALL_FIRST_HALF_TEAM_TOTALS):
|
||||||
|
return evaluateTeamTotal(outcome, firstHalfScore)
|
||||||
|
case int64(domain.BASKETBALL_FIRST_HALF_HANDICAP_AND_TOTAL):
|
||||||
|
return evaluateHandicapAndTotal(outcome, firstHalfScore)
|
||||||
|
case int64(domain.BASKETBALL_FIRST_HALF_BOTH_TEAMS_TO_SCORE_X_POINTS):
|
||||||
|
return evaluateBTTSX(outcome, firstHalfScore)
|
||||||
|
case int64(domain.BASKETBALL_FIRST_HALF_DOUBLE_CHANCE):
|
||||||
|
return evaluateDoubleChance(outcome, firstHalfScore)
|
||||||
|
case int64(domain.BASKETBALL_FIRST_HALF_TOTAL_ODD_EVEN):
|
||||||
|
return evaluateGoalsOddEven(outcome, firstHalfScore)
|
||||||
|
case int64(domain.BASKETBALL_FIRST_HALF_MONEY_LINE_3_WAY):
|
||||||
|
return evaluateMoneyLine3Way(outcome, firstHalfScore)
|
||||||
|
case int64(domain.BASKETBALL_HIGHEST_SCORING_HALF):
|
||||||
|
return evaluateHighestScoringHalf(outcome, firstHalfScore, secondHalfScore)
|
||||||
|
|
||||||
|
case int64(domain.BASKETBALL_FIRST_QUARTER):
|
||||||
|
return evaluateGameLines(outcome, firstQuarter)
|
||||||
|
case int64(domain.BASKETBALL_FIRST_QUARTER_TEAM_TOTALS):
|
||||||
|
return evaluateTeamTotal(outcome, firstQuarter)
|
||||||
|
case int64(domain.BASKETBALL_FIRST_QUARTER_TOTAL_ODD_EVEN):
|
||||||
|
return evaluateGoalsOddEven(outcome, firstQuarter)
|
||||||
|
case int64(domain.BASKETBALL_FIRST_QUARTER_HANDICAP_AND_TOTAL):
|
||||||
|
return evaluateHandicapAndTotal(outcome, firstQuarter)
|
||||||
|
case int64(domain.BASKETBALL_FIRST_QUARTER_DOUBLE_CHANCE):
|
||||||
|
return evaluateDoubleChance(outcome, firstQuarter)
|
||||||
|
case int64(domain.BASKETBALL_HIGHEST_SCORING_QUARTER):
|
||||||
|
return evaluateHighestScoringQuarter(outcome, firstQuarter, secondQuarter, thirdQuarter, fourthQuarter)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
s.logger.Warn("Market type not implemented", "market_name", outcome.MarketName)
|
||||||
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("market type not implemented: %s", outcome.MarketName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if outcome.OddHeader == "Over" {
|
|
||||||
if totalGoals > threshold {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
|
||||||
} else if totalGoals == threshold {
|
|
||||||
return domain.OUTCOME_STATUS_VOID, nil
|
|
||||||
}
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
|
||||||
} else if outcome.OddHeader == "Under" {
|
|
||||||
if totalGoals < threshold {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
|
||||||
} else if totalGoals == threshold {
|
|
||||||
return domain.OUTCOME_STATUS_VOID, nil
|
|
||||||
}
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
|
||||||
}
|
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
|
||||||
}
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func evaluateHalfTimeResult(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
|
||||||
return evaluateFullTimeResult(outcome, score)
|
|
||||||
}
|
|
||||||
|
|
||||||
func evaluateAsianHandicap(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
|
||||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
|
||||||
if err != nil {
|
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
|
||||||
}
|
|
||||||
|
|
||||||
adjustedHomeScore := float64(score.Home)
|
|
||||||
adjustedAwayScore := float64(score.Away)
|
|
||||||
|
|
||||||
if outcome.OddHeader == "1" { // Home team
|
|
||||||
adjustedHomeScore += handicap
|
|
||||||
} else if outcome.OddHeader == "2" { // Away team
|
|
||||||
adjustedAwayScore += handicap
|
|
||||||
} else {
|
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
|
||||||
}
|
|
||||||
|
|
||||||
if adjustedHomeScore > adjustedAwayScore {
|
|
||||||
if outcome.OddHeader == "1" {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
|
||||||
}
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
|
||||||
} else if adjustedHomeScore < adjustedAwayScore {
|
|
||||||
if outcome.OddHeader == "2" {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
|
||||||
}
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
|
||||||
}
|
|
||||||
return domain.OUTCOME_STATUS_VOID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func evaluateGoalLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
|
||||||
return evaluateGoalsOverUnder(outcome, score)
|
|
||||||
}
|
|
||||||
|
|
||||||
func evaluateFirstTeamToScore(outcome domain.BetOutcome, events []map[string]string) (domain.OutcomeStatus, error) {
|
|
||||||
for _, event := range events {
|
|
||||||
if strings.Contains(event["text"], "1st Goal") {
|
|
||||||
if strings.Contains(event["text"], outcome.HomeTeamName) && outcome.OddName == "1" {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
|
||||||
} else if strings.Contains(event["text"], outcome.AwayTeamName) && outcome.OddName == "2" {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
|
||||||
}
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return domain.OUTCOME_STATUS_VOID, nil // No goals scored
|
|
||||||
}
|
|
||||||
|
|
||||||
func evaluateGoalsOddEven(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
|
||||||
totalGoals := score.Home + score.Away
|
|
||||||
isOdd := totalGoals%2 == 1
|
|
||||||
if outcome.OddName == "Odd" && isOdd {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
|
||||||
} else if outcome.OddName == "Even" && !isOdd {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
|
||||||
}
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func evaluateDoubleChance(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
|
||||||
isHomeWin := score.Home > score.Away
|
|
||||||
isDraw := score.Home == score.Away
|
|
||||||
isAwayWin := score.Away > score.Home
|
|
||||||
|
|
||||||
switch outcome.OddName {
|
|
||||||
case "1 or Draw":
|
|
||||||
if isHomeWin || isDraw {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
|
||||||
}
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
|
||||||
case "Draw or 2":
|
|
||||||
if isDraw || isAwayWin {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
|
||||||
}
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
|
||||||
case "1 or 2":
|
|
||||||
if isHomeWin || isAwayWin {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
|
||||||
}
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
|
||||||
default:
|
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func evaluateDrawNoBet(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
|
||||||
if score.Home == score.Away {
|
|
||||||
return domain.OUTCOME_STATUS_VOID, nil
|
|
||||||
}
|
|
||||||
if outcome.OddName == "1" && score.Home > score.Away {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
|
||||||
} else if outcome.OddName == "2" && score.Away > score.Home {
|
|
||||||
return domain.OUTCOME_STATUS_WIN, nil
|
|
||||||
}
|
|
||||||
return domain.OUTCOME_STATUS_LOSS, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
1
internal/services/result/service_test.go
Normal file
1
internal/services/result/service_test.go
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
package result
|
||||||
|
|
@ -2,7 +2,6 @@ package httpserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// "context"
|
// "context"
|
||||||
|
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
|
@ -67,6 +66,18 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
|
{
|
||||||
|
spec: "0 */15 * * * *",
|
||||||
|
task: func() {
|
||||||
|
log.Println("Fetching results for upcoming events...")
|
||||||
|
|
||||||
|
if err := resultService.FetchAndProcessResults(context.Background()); err != nil {
|
||||||
|
log.Printf("Failed to process result: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Successfully processed all outcomes")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, job := range schedule {
|
for _, job := range schedule {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log/slog"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
|
@ -169,10 +170,18 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
|
||||||
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
|
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
|
||||||
totalOdds = totalOdds * float32(parsedOdd)
|
totalOdds = totalOdds * float32(parsedOdd)
|
||||||
|
|
||||||
|
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid sport id", nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("Create Bet", slog.Int64("sportId", sportID))
|
||||||
|
|
||||||
outcomes = append(outcomes, domain.CreateBetOutcome{
|
outcomes = append(outcomes, domain.CreateBetOutcome{
|
||||||
EventID: outcome.EventID,
|
EventID: outcome.EventID,
|
||||||
OddID: outcome.OddID,
|
OddID: outcome.OddID,
|
||||||
MarketID: outcome.MarketID,
|
MarketID: outcome.MarketID,
|
||||||
|
SportID: sportID,
|
||||||
HomeTeamName: event.HomeTeam,
|
HomeTeamName: event.HomeTeam,
|
||||||
AwayTeamName: event.AwayTeam,
|
AwayTeamName: event.AwayTeam,
|
||||||
MarketName: odds.MarketName,
|
MarketName: odds.MarketName,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user