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 (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
bet_id BIGINT NOT NULL,
|
||||
sport_id BIGINT NOT NULL,
|
||||
event_id BIGINT NOT null,
|
||||
odd_id BIGINT NOT NULL,
|
||||
home_team_name VARCHAR(255) NOT NULL,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ RETURNING *;
|
|||
-- name: CreateBetOutcome :copyfrom
|
||||
INSERT INTO bet_outcomes (
|
||||
bet_id,
|
||||
sport_id,
|
||||
event_id,
|
||||
odd_id,
|
||||
home_team_name,
|
||||
|
|
@ -39,7 +40,8 @@ VALUES (
|
|||
$9,
|
||||
$10,
|
||||
$11,
|
||||
$12
|
||||
$12,
|
||||
$13
|
||||
);
|
||||
-- name: GetAllBets :many
|
||||
SELECT *
|
||||
|
|
@ -56,6 +58,11 @@ WHERE cashout_id = $1;
|
|||
SELECT *
|
||||
FROM bet_with_outcomes
|
||||
WHERE branch_id = $1;
|
||||
-- name: GetBetOutcomeByEventID :many
|
||||
SELECT *
|
||||
FROM bet_outcomes
|
||||
WHERE event_id = $1;
|
||||
|
||||
-- name: UpdateCashOut :exec
|
||||
UPDATE bets
|
||||
SET cashed_out = $2,
|
||||
|
|
|
|||
|
|
@ -233,3 +233,6 @@ SET score = $1,
|
|||
status = $2,
|
||||
fetched_at = NOW()
|
||||
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",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.CreateCompanyReq"
|
||||
"$ref": "#/definitions/handlers.UpdateCompanyReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -3372,6 +3372,10 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"example": "1"
|
||||
},
|
||||
"sport_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
|
|
@ -3439,13 +3443,19 @@ const docTemplate = `{
|
|||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
3,
|
||||
4
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"OUTCOME_STATUS_HALF": "Half Win and Half Given Back",
|
||||
"OUTCOME_STATUS_VOID": "Give Back"
|
||||
},
|
||||
"x-enum-varnames": [
|
||||
"OUTCOME_STATUS_PENDING",
|
||||
"OUTCOME_STATUS_WIN",
|
||||
"OUTCOME_STATUS_LOSS",
|
||||
"OUTCOME_STATUS_VOID"
|
||||
"OUTCOME_STATUS_VOID",
|
||||
"OUTCOME_STATUS_HALF"
|
||||
]
|
||||
},
|
||||
"domain.PaymentOption": {
|
||||
|
|
@ -4034,6 +4044,12 @@ const docTemplate = `{
|
|||
},
|
||||
"handlers.CreateBranchReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"branch_manager_id",
|
||||
"location",
|
||||
"name",
|
||||
"operations"
|
||||
],
|
||||
"properties": {
|
||||
"branch_manager_id": {
|
||||
"type": "integer",
|
||||
|
|
@ -4049,10 +4065,14 @@ const docTemplate = `{
|
|||
},
|
||||
"location": {
|
||||
"type": "string",
|
||||
"maxLength": 100,
|
||||
"minLength": 3,
|
||||
"example": "Addis Ababa"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 100,
|
||||
"minLength": 3,
|
||||
"example": "4-kilo Branch"
|
||||
},
|
||||
"operations": {
|
||||
|
|
@ -4502,6 +4522,10 @@ const docTemplate = `{
|
|||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"approver_name": {
|
||||
"type": "string",
|
||||
"example": "John Smith"
|
||||
},
|
||||
"bank_code": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -4516,10 +4540,26 @@ const docTemplate = `{
|
|||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"branch_location": {
|
||||
"type": "string",
|
||||
"example": "Branch Location"
|
||||
},
|
||||
"branch_name": {
|
||||
"type": "string",
|
||||
"example": "Branch Name"
|
||||
},
|
||||
"cashier_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"cashier_name": {
|
||||
"type": "string",
|
||||
"example": "John Smith"
|
||||
},
|
||||
"company_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"created_at": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -1248,7 +1248,7 @@
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handlers.CreateCompanyReq"
|
||||
"$ref": "#/definitions/handlers.UpdateCompanyReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -3364,6 +3364,10 @@
|
|||
"type": "string",
|
||||
"example": "1"
|
||||
},
|
||||
"sport_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
|
|
@ -3431,13 +3435,19 @@
|
|||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
3,
|
||||
4
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"OUTCOME_STATUS_HALF": "Half Win and Half Given Back",
|
||||
"OUTCOME_STATUS_VOID": "Give Back"
|
||||
},
|
||||
"x-enum-varnames": [
|
||||
"OUTCOME_STATUS_PENDING",
|
||||
"OUTCOME_STATUS_WIN",
|
||||
"OUTCOME_STATUS_LOSS",
|
||||
"OUTCOME_STATUS_VOID"
|
||||
"OUTCOME_STATUS_VOID",
|
||||
"OUTCOME_STATUS_HALF"
|
||||
]
|
||||
},
|
||||
"domain.PaymentOption": {
|
||||
|
|
@ -4026,6 +4036,12 @@
|
|||
},
|
||||
"handlers.CreateBranchReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"branch_manager_id",
|
||||
"location",
|
||||
"name",
|
||||
"operations"
|
||||
],
|
||||
"properties": {
|
||||
"branch_manager_id": {
|
||||
"type": "integer",
|
||||
|
|
@ -4041,10 +4057,14 @@
|
|||
},
|
||||
"location": {
|
||||
"type": "string",
|
||||
"maxLength": 100,
|
||||
"minLength": 3,
|
||||
"example": "Addis Ababa"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 100,
|
||||
"minLength": 3,
|
||||
"example": "4-kilo Branch"
|
||||
},
|
||||
"operations": {
|
||||
|
|
@ -4494,6 +4514,10 @@
|
|||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"approver_name": {
|
||||
"type": "string",
|
||||
"example": "John Smith"
|
||||
},
|
||||
"bank_code": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -4508,10 +4532,26 @@
|
|||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"branch_location": {
|
||||
"type": "string",
|
||||
"example": "Branch Location"
|
||||
},
|
||||
"branch_name": {
|
||||
"type": "string",
|
||||
"example": "Branch Name"
|
||||
},
|
||||
"cashier_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"cashier_name": {
|
||||
"type": "string",
|
||||
"example": "John Smith"
|
||||
},
|
||||
"company_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -4608,6 +4648,19 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.UpdateCompanyReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"admin_id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "CompanyName"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.UpdateTransactionVerifiedReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ definitions:
|
|||
odd_name:
|
||||
example: "1"
|
||||
type: string
|
||||
sport_id:
|
||||
example: 1
|
||||
type: integer
|
||||
status:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||
|
|
@ -85,12 +88,17 @@ definitions:
|
|||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
type: integer
|
||||
x-enum-comments:
|
||||
OUTCOME_STATUS_HALF: Half Win and Half Given Back
|
||||
OUTCOME_STATUS_VOID: Give Back
|
||||
x-enum-varnames:
|
||||
- OUTCOME_STATUS_PENDING
|
||||
- OUTCOME_STATUS_WIN
|
||||
- OUTCOME_STATUS_LOSS
|
||||
- OUTCOME_STATUS_VOID
|
||||
- OUTCOME_STATUS_HALF
|
||||
domain.PaymentOption:
|
||||
enum:
|
||||
- 0
|
||||
|
|
@ -514,14 +522,23 @@ definitions:
|
|||
type: boolean
|
||||
location:
|
||||
example: Addis Ababa
|
||||
maxLength: 100
|
||||
minLength: 3
|
||||
type: string
|
||||
name:
|
||||
example: 4-kilo Branch
|
||||
maxLength: 100
|
||||
minLength: 3
|
||||
type: string
|
||||
operations:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
required:
|
||||
- branch_manager_id
|
||||
- location
|
||||
- name
|
||||
- operations
|
||||
type: object
|
||||
handlers.CreateCashierReq:
|
||||
properties:
|
||||
|
|
@ -830,6 +847,9 @@ definitions:
|
|||
approved_by:
|
||||
example: 1
|
||||
type: integer
|
||||
approver_name:
|
||||
example: John Smith
|
||||
type: string
|
||||
bank_code:
|
||||
type: string
|
||||
beneficiary_name:
|
||||
|
|
@ -840,9 +860,21 @@ definitions:
|
|||
branch_id:
|
||||
example: 1
|
||||
type: integer
|
||||
branch_location:
|
||||
example: Branch Location
|
||||
type: string
|
||||
branch_name:
|
||||
example: Branch Name
|
||||
type: string
|
||||
cashier_id:
|
||||
example: 1
|
||||
type: integer
|
||||
cashier_name:
|
||||
example: John Smith
|
||||
type: string
|
||||
company_id:
|
||||
example: 1
|
||||
type: integer
|
||||
created_at:
|
||||
type: string
|
||||
full_name:
|
||||
|
|
@ -910,6 +942,15 @@ definitions:
|
|||
cashedOut:
|
||||
type: boolean
|
||||
type: object
|
||||
handlers.UpdateCompanyReq:
|
||||
properties:
|
||||
admin_id:
|
||||
example: 1
|
||||
type: integer
|
||||
name:
|
||||
example: CompanyName
|
||||
type: string
|
||||
type: object
|
||||
handlers.UpdateTransactionVerifiedReq:
|
||||
properties:
|
||||
verified:
|
||||
|
|
@ -1935,7 +1976,7 @@ paths:
|
|||
name: updateCompany
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.CreateCompanyReq'
|
||||
$ref: '#/definitions/handlers.UpdateCompanyReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
|
|||
|
||||
type CreateBetOutcomeParams struct {
|
||||
BetID int64 `json:"bet_id"`
|
||||
SportID int64 `json:"sport_id"`
|
||||
EventID int64 `json:"event_id"`
|
||||
OddID int64 `json:"odd_id"`
|
||||
HomeTeamName string `json:"home_team_name"`
|
||||
|
|
@ -242,6 +243,48 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, 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
|
||||
UPDATE bet_outcomes
|
||||
SET status = $1
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ func (r *iteratorForCreateBetOutcome) Next() bool {
|
|||
func (r iteratorForCreateBetOutcome) Values() ([]interface{}, error) {
|
||||
return []interface{}{
|
||||
r.rows[0].BetID,
|
||||
r.rows[0].SportID,
|
||||
r.rows[0].EventID,
|
||||
r.rows[0].OddID,
|
||||
r.rows[0].HomeTeamName,
|
||||
|
|
@ -49,7 +50,7 @@ func (r iteratorForCreateBetOutcome) Err() 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.
|
||||
|
|
|
|||
|
|
@ -11,6 +11,16 @@ import (
|
|||
"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
|
||||
SELECT id,
|
||||
sport_id,
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ type Bet struct {
|
|||
type BetOutcome struct {
|
||||
ID int64 `json:"id"`
|
||||
BetID int64 `json:"bet_id"`
|
||||
SportID int64 `json:"sport_id"`
|
||||
EventID int64 `json:"event_id"`
|
||||
OddID int64 `json:"odd_id"`
|
||||
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
|
||||
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) {
|
||||
|
|
@ -85,6 +85,7 @@ func (q *Queries) GetPendingBetOutcomes(ctx context.Context) ([]BetOutcome, erro
|
|||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.BetID,
|
||||
&i.SportID,
|
||||
&i.EventID,
|
||||
&i.OddID,
|
||||
&i.HomeTeamName,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ type BetOutcome struct {
|
|||
BetID int64 `json:"bet_id" example:"1"`
|
||||
EventID int64 `json:"event_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"`
|
||||
AwayTeamName string `json:"away_team_name" example:"Liverpool"`
|
||||
MarketID int64 `json:"market_id" example:"1"`
|
||||
|
|
@ -25,6 +26,7 @@ type CreateBetOutcome struct {
|
|||
BetID int64 `json:"bet_id" example:"1"`
|
||||
EventID int64 `json:"event_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"`
|
||||
AwayTeamName string `json:"away_team_name" example:"Liverpool"`
|
||||
MarketID int64 `json:"market_id" example:"1"`
|
||||
|
|
@ -78,4 +80,3 @@ type CreateBet struct {
|
|||
IsShopBet bool
|
||||
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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ResultResponse struct {
|
||||
Success int `json:"success"`
|
||||
Results []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 {
|
||||
FirstHalf Score `json:"1"`
|
||||
SecondHalf Score `json:"2"`
|
||||
} `json:"scores"`
|
||||
Stats struct {
|
||||
Attacks []string `json:"attacks"`
|
||||
Corners []string `json:"corners"`
|
||||
DangerousAttacks []string `json:"dangerous_attacks"`
|
||||
Goals []string `json:"goals"`
|
||||
OffTarget []string `json:"off_target"`
|
||||
OnTarget []string `json:"on_target"`
|
||||
Penalties []string `json:"penalties"`
|
||||
PossessionRT []string `json:"possession_rt"`
|
||||
RedCards []string `json:"redcards"`
|
||||
Substitutions []string `json:"substitutions"`
|
||||
YellowCards []string `json:"yellowcards"`
|
||||
} `json:"stats"`
|
||||
Extra struct {
|
||||
HomePos string `json:"home_pos"`
|
||||
AwayPos string `json:"away_pos"`
|
||||
StadiumData map[string]string `json:"stadium_data"`
|
||||
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"`
|
||||
} `json:"results"`
|
||||
type BaseResultResponse struct {
|
||||
Success int `json:"success"`
|
||||
Results []json.RawMessage `json:"results"`
|
||||
}
|
||||
type FootballResultResponse 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 {
|
||||
FirstHalf Score `json:"1"`
|
||||
SecondHalf Score `json:"2"`
|
||||
} `json:"scores"`
|
||||
Stats struct {
|
||||
Attacks []string `json:"attacks"`
|
||||
Corners []string `json:"corners"`
|
||||
DangerousAttacks []string `json:"dangerous_attacks"`
|
||||
Goals []string `json:"goals"`
|
||||
OffTarget []string `json:"off_target"`
|
||||
OnTarget []string `json:"on_target"`
|
||||
Penalties []string `json:"penalties"`
|
||||
PossessionRT []string `json:"possession_rt"`
|
||||
RedCards []string `json:"redcards"`
|
||||
Substitutions []string `json:"substitutions"`
|
||||
YellowCards []string `json:"yellowcards"`
|
||||
} `json:"stats"`
|
||||
Extra struct {
|
||||
HomePos string `json:"home_pos"`
|
||||
AwayPos string `json:"away_pos"`
|
||||
StadiumData map[string]string `json:"stadium_data"`
|
||||
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 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 {
|
||||
|
|
@ -67,7 +131,7 @@ type Score struct {
|
|||
type MarketConfig struct {
|
||||
Sport string
|
||||
MarketCategories map[string]bool
|
||||
MarketTypes map[string]bool
|
||||
MarketTypes map[int64]bool
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
|
|
@ -101,5 +165,6 @@ const (
|
|||
OUTCOME_STATUS_PENDING OutcomeStatus = 0
|
||||
OUTCOME_STATUS_WIN OutcomeStatus = 1
|
||||
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))
|
||||
|
||||
for _, outcome := range bet.Outcomes {
|
||||
outcomes = append(outcomes, domain.BetOutcome{
|
||||
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,
|
||||
})
|
||||
outcomes = append(outcomes, convertDBBetOutcomes(outcome))
|
||||
}
|
||||
|
||||
return domain.GetBet{
|
||||
ID: bet.ID,
|
||||
Amount: domain.Currency(bet.Amount),
|
||||
|
|
@ -78,6 +84,7 @@ func convertDBCreateBetOutcome(betOutcome domain.CreateBetOutcome) dbgen.CreateB
|
|||
return dbgen.CreateBetOutcomeParams{
|
||||
BetID: betOutcome.BetID,
|
||||
EventID: betOutcome.EventID,
|
||||
SportID: betOutcome.SportID,
|
||||
OddID: betOutcome.OddID,
|
||||
HomeTeamName: betOutcome.HomeTeamName,
|
||||
AwayTeamName: betOutcome.AwayTeamName,
|
||||
|
|
@ -145,7 +152,7 @@ func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error)
|
|||
return domain.GetBet{}, err
|
||||
}
|
||||
|
||||
return convertDBBetOutcomes(bet), nil
|
||||
return convertDBBetWithOutcomes(bet), nil
|
||||
}
|
||||
|
||||
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 convertDBBetOutcomes(bet), nil
|
||||
return convertDBBetWithOutcomes(bet), nil
|
||||
}
|
||||
|
||||
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))
|
||||
for _, bet := range bets {
|
||||
result = append(result, convertDBBetOutcomes(bet))
|
||||
result = append(result, convertDBBetWithOutcomes(bet))
|
||||
}
|
||||
|
||||
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))
|
||||
for _, bet := range bets {
|
||||
result = append(result, convertDBBetOutcomes(bet))
|
||||
result = append(result, convertDBBetWithOutcomes(bet))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
|
@ -206,6 +213,18 @@ func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.Outcom
|
|||
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 {
|
||||
err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{
|
||||
Status: int32(status),
|
||||
|
|
|
|||
|
|
@ -206,3 +206,11 @@ func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore, status
|
|||
|
||||
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"
|
||||
"log"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -98,7 +99,7 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
||||
sportIDs := []int{1}
|
||||
sportIDs := []int{1, 18}
|
||||
|
||||
for _, sportID := range sportIDs {
|
||||
var totalPages int = 1
|
||||
|
|
@ -106,7 +107,7 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
|||
var limit int = 100
|
||||
var count int = 0
|
||||
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
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
|
||||
skippedLeague := 0
|
||||
for _, ev := range data.Results {
|
||||
|
||||
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{
|
||||
ID: ev.ID,
|
||||
SportID: ev.SportID,
|
||||
|
|
@ -179,6 +198,7 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
|||
break
|
||||
}
|
||||
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}
|
||||
}
|
||||
|
||||
// TODO this is only getting the main odds, this must be fixed
|
||||
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||
eventIDs, err := s.store.GetAllUpcomingEvents(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -30,7 +31,7 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
|||
}
|
||||
|
||||
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
|
||||
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)
|
||||
continue
|
||||
}
|
||||
|
||||
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{
|
||||
"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,
|
||||
},
|
||||
},
|
||||
type ResultCheck struct {
|
||||
}
|
||||
|
||||
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 {
|
||||
s.logger.Error("Failed to get pending bet outcomes", "error", err)
|
||||
s.logger.Error("Failed to fetch events")
|
||||
return err
|
||||
}
|
||||
|
||||
for _, outcome := range outcomes {
|
||||
if outcome.Expires.After(time.Now()) {
|
||||
continue
|
||||
for _, event := range events {
|
||||
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
||||
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)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to fetch result", "event_id", outcome.EventID, "error", err)
|
||||
continue
|
||||
}
|
||||
for _, outcome := range outcomes {
|
||||
if outcome.Expires.After(time.Now()) {
|
||||
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
|
||||
}
|
||||
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Error("Sport ID is invalid", "event_id", outcome.EventID, "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 {
|
||||
s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err)
|
||||
continue
|
||||
s.logger.Error("Failed to remove event", "event_id", event.ID, "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
// 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)
|
||||
|
||||
res, err := s.client.Get(url)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to fetch result", "event_id", eventID, "error", err)
|
||||
return fmt.Errorf("failed to fetch result: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
// res, err := s.client.Get(url)
|
||||
// if err != nil {
|
||||
// s.logger.Error("Failed to fetch result", "event_id", eventID, "error", err)
|
||||
// return fmt.Errorf("failed to fetch result: %w", err)
|
||||
// }
|
||||
// defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
s.logger.Error("Unexpected status code", "event_id", eventID, "status_code", res.StatusCode)
|
||||
return fmt.Errorf("unexpected status code: %d", res.StatusCode)
|
||||
}
|
||||
// if res.StatusCode != http.StatusOK {
|
||||
// s.logger.Error("Unexpected status code", "event_id", eventID, "status_code", res.StatusCode)
|
||||
// return fmt.Errorf("unexpected status code: %d", res.StatusCode)
|
||||
// }
|
||||
|
||||
var apiResp domain.ResultResponse
|
||||
if err := json.NewDecoder(res.Body).Decode(&apiResp); err != nil {
|
||||
s.logger.Error("Failed to decode result", "event_id", eventID, "error", err)
|
||||
return fmt.Errorf("failed to decode result: %w", err)
|
||||
}
|
||||
// var apiResp domain.BaseResultResponse
|
||||
// if err := json.NewDecoder(res.Body).Decode(&apiResp); err != nil {
|
||||
// s.logger.Error("Failed to decode result", "event_id", eventID, "error", err)
|
||||
// return fmt.Errorf("failed to decode result: %w", err)
|
||||
// }
|
||||
|
||||
if apiResp.Success != 1 || len(apiResp.Results) == 0 {
|
||||
s.logger.Error("Invalid API response", "event_id", eventID)
|
||||
return fmt.Errorf("no result returned from API")
|
||||
}
|
||||
// if apiResp.Success != 1 || len(apiResp.Results) == 0 {
|
||||
// s.logger.Error("Invalid API response", "event_id", eventID)
|
||||
// return fmt.Errorf("no result returned from API")
|
||||
// }
|
||||
|
||||
r := apiResp.Results[0]
|
||||
if r.TimeStatus != "3" {
|
||||
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||
return fmt.Errorf("match not yet completed")
|
||||
}
|
||||
// r := apiResp.Results[0]
|
||||
// if r.TimeStatus != "3" {
|
||||
// s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||
// return fmt.Errorf("match not yet completed")
|
||||
// }
|
||||
|
||||
eventIDInt, err := strconv.ParseInt(eventID, 10, 64)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to parse event_id", "event_id", eventID, "error", err)
|
||||
return fmt.Errorf("failed to parse event_id: %w", err)
|
||||
}
|
||||
// eventIDInt, err := strconv.ParseInt(eventID, 10, 64)
|
||||
// if err != nil {
|
||||
// s.logger.Error("Failed to parse event_id", "event_id", eventID, "error", err)
|
||||
// return fmt.Errorf("failed to parse event_id: %w", err)
|
||||
// }
|
||||
|
||||
halfScore := ""
|
||||
if r.Scores.FirstHalf.Home != "" {
|
||||
halfScore = fmt.Sprintf("%s-%s", r.Scores.FirstHalf.Home, r.Scores.FirstHalf.Away)
|
||||
}
|
||||
// halfScore := ""
|
||||
// if r.Scores.FirstHalf.Home != "" {
|
||||
// halfScore = fmt.Sprintf("%s-%s", r.Scores.FirstHalf.Home, r.Scores.FirstHalf.Away)
|
||||
// }
|
||||
|
||||
result := domain.Result{
|
||||
EventID: eventIDInt,
|
||||
Status: domain.OUTCOME_STATUS_PENDING,
|
||||
Score: r.SS,
|
||||
FullTimeScore: r.SS,
|
||||
HalfTimeScore: halfScore,
|
||||
SS: r.SS,
|
||||
Scores: make(map[string]domain.Score),
|
||||
}
|
||||
for k, v := range map[string]domain.Score{
|
||||
"1": r.Scores.FirstHalf,
|
||||
"2": r.Scores.SecondHalf,
|
||||
} {
|
||||
result.Scores[k] = domain.Score{
|
||||
Home: v.Home,
|
||||
Away: v.Away,
|
||||
}
|
||||
}
|
||||
// result := domain.Result{
|
||||
// EventID: eventIDInt,
|
||||
// Status: domain.OUTCOME_STATUS_PENDING,
|
||||
// Score: r.SS,
|
||||
// FullTimeScore: r.SS,
|
||||
// HalfTimeScore: halfScore,
|
||||
// SS: r.SS,
|
||||
// Scores: make(map[string]domain.Score),
|
||||
// }
|
||||
// for k, v := range map[string]domain.Score{
|
||||
// "1": r.Scores.FirstHalf,
|
||||
// "2": r.Scores.SecondHalf,
|
||||
// } {
|
||||
// result.Scores[k] = domain.Score{
|
||||
// Home: v.Home,
|
||||
// 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)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, 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)
|
||||
}
|
||||
|
||||
var resultResp domain.ResultResponse
|
||||
var resultResp domain.BaseResultResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&resultResp); err != nil {
|
||||
s.logger.Error("Failed to decode result", "event_id", eventID, "error", 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")
|
||||
}
|
||||
|
||||
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" {
|
||||
s.logger.Warn("Match not yet completed", "event_id", eventID)
|
||||
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
|
||||
}
|
||||
|
||||
finalScore := parseScore(result.SS)
|
||||
firstHalfScore := parseScore(fmt.Sprintf("%s-%s", result.Scores.FirstHalf.Home, result.Scores.FirstHalf.Away))
|
||||
finalScore := parseSS(result.SS)
|
||||
firstHalfScore := parseSS(fmt.Sprintf("%s-%s", result.Scores.FirstHalf.Home, result.Scores.FirstHalf.Away))
|
||||
|
||||
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 {
|
||||
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
|
||||
return domain.CreateResult{}, err
|
||||
|
|
@ -218,9 +251,44 @@ func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID int6
|
|||
Status: status,
|
||||
Score: result.SS,
|
||||
}, 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, "-")
|
||||
if len(parts) != 2 {
|
||||
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
|
||||
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) {
|
||||
marketConfig := supportedMarkets["football"]
|
||||
if !marketConfig.MarketTypes[outcome.MarketName] {
|
||||
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 := domain.SupportedMarkets["football"]
|
||||
if !marketConfig.MarketTypes[outcome.MarketID] {
|
||||
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
||||
}
|
||||
|
||||
switch outcome.MarketName {
|
||||
case "full_time_result":
|
||||
switch outcome.MarketID {
|
||||
case int64(domain.FOOTBALL_FULL_TIME_RESULT):
|
||||
return evaluateFullTimeResult(outcome, finalScore)
|
||||
case "goals_over_under":
|
||||
case int64(domain.FOOTBALL_GOALS_OVER_UNDER):
|
||||
return evaluateGoalsOverUnder(outcome, finalScore)
|
||||
case "correct_score":
|
||||
case int64(domain.FOOTBALL_CORRECT_SCORE):
|
||||
return evaluateCorrectScore(outcome, finalScore)
|
||||
case "half_time_result":
|
||||
case int64(domain.FOOTBALL_HALF_TIME_RESULT):
|
||||
return evaluateHalfTimeResult(outcome, firstHalfScore)
|
||||
case "asian_handicap":
|
||||
case int64(domain.FOOTBALL_ASIAN_HANDICAP):
|
||||
return evaluateAsianHandicap(outcome, finalScore)
|
||||
case "goal_line":
|
||||
case int64(domain.FOOTBALL_GOAL_LINE):
|
||||
return evaluateGoalLine(outcome, finalScore)
|
||||
case "1st_half_asian_handicap":
|
||||
case int64(domain.FOOTBALL_FIRST_HALF_ASIAN_HANDICAP):
|
||||
return evaluateAsianHandicap(outcome, firstHalfScore)
|
||||
case "1st_half_goal_line":
|
||||
case int64(domain.FOOTBALL_FIRST_HALF_GOAL_LINE):
|
||||
return evaluateGoalLine(outcome, firstHalfScore)
|
||||
case "first_team_to_score":
|
||||
case int64(domain.FOOTBALL_FIRST_TEAM_TO_SCORE):
|
||||
return evaluateFirstTeamToScore(outcome, events)
|
||||
case "goals_odd_even":
|
||||
case int64(domain.FOOTBALL_GOALS_ODD_EVEN):
|
||||
return evaluateGoalsOddEven(outcome, finalScore)
|
||||
case "double_chance":
|
||||
case int64(domain.FOOTBALL_DOUBLE_CHANCE):
|
||||
return evaluateDoubleChance(outcome, finalScore)
|
||||
case "draw_no_bet":
|
||||
case int64(domain.FOOTBALL_DRAW_NO_BET):
|
||||
return evaluateDrawNoBet(outcome, finalScore)
|
||||
default:
|
||||
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) {
|
||||
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
|
||||
func (s *Service) evaluateBasketballOutcome(outcome domain.BetOutcome, res domain.BasketballResultResponse) (domain.OutcomeStatus, error) {
|
||||
marketConfig := domain.SupportedMarkets["basketball"]
|
||||
if !marketConfig.MarketTypes[outcome.MarketID] {
|
||||
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
|
||||
}
|
||||
|
||||
finalScore := parseSS(res.SS)
|
||||
|
||||
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}
|
||||
|
||||
firstQuarter := parseScore(res.Scores.FirstQuarter.Home, res.Scores.FirstQuarter.Away)
|
||||
secondQuarter := parseScore(res.Scores.SecondQuarter.Home, res.Scores.SecondQuarter.Away)
|
||||
thirdQuarter := parseScore(res.Scores.ThirdQuarter.Home, res.Scores.ThirdQuarter.Away)
|
||||
fourthQuarter := parseScore(res.Scores.FourthQuarter.Home, res.Scores.FourthQuarter.Away)
|
||||
|
||||
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:
|
||||
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 (
|
||||
// "context"
|
||||
|
||||
"context"
|
||||
"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 {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package handlers
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
|
||||
"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)
|
||||
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{
|
||||
EventID: outcome.EventID,
|
||||
OddID: outcome.OddID,
|
||||
MarketID: outcome.MarketID,
|
||||
SportID: sportID,
|
||||
HomeTeamName: event.HomeTeam,
|
||||
AwayTeamName: event.AwayTeam,
|
||||
MarketName: odds.MarketName,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user