adding fi

This commit is contained in:
OneTap Technologies 2025-04-13 10:55:16 +03:00
parent 6c478df2b4
commit b8d15695a4
14 changed files with 496 additions and 198 deletions

View File

@ -90,12 +90,10 @@ CREATE TABLE odds (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
event_id TEXT, event_id TEXT,
fi TEXT, fi TEXT,
raw_event_id TEXT,
market_type TEXT NOT NULL, market_type TEXT NOT NULL,
market_name TEXT, market_name TEXT,
market_category TEXT, market_category TEXT,
market_id TEXT, market_id TEXT,
header TEXT,
name TEXT, name TEXT,
handicap TEXT, handicap TEXT,
odds_value DOUBLE PRECISION, odds_value DOUBLE PRECISION,
@ -105,7 +103,7 @@ CREATE TABLE odds (
fetched_at TIMESTAMP DEFAULT now(), fetched_at TIMESTAMP DEFAULT now(),
source TEXT DEFAULT 'b365api', source TEXT DEFAULT 'b365api',
is_active BOOLEAN DEFAULT true, is_active BOOLEAN DEFAULT true,
UNIQUE (event_id, market_id, header, name, handicap) UNIQUE (event_id, market_id, name, handicap)
); );

View File

@ -2,12 +2,10 @@
INSERT INTO odds ( INSERT INTO odds (
event_id, event_id,
fi, fi,
raw_event_id,
market_type, market_type,
market_name, market_name,
market_category, market_category,
market_id, market_id,
header,
name, name,
handicap, handicap,
odds_value, odds_value,
@ -19,48 +17,21 @@ INSERT INTO odds (
fetched_at fetched_at
) VALUES ( ) VALUES (
$1, $2, $3, $4, $5, $6, $7, $1, $2, $3, $4, $5, $6, $7,
$8, $9, $10, $11, $12, $13, $14, $8, $9, $10, $11, $12, $13, $14, $15
true, 'b365api', now()
) )
ON CONFLICT (event_id, market_id, header, name, handicap) DO UPDATE SET ON CONFLICT (market_id, name, handicap) DO UPDATE SET
odds_value = EXCLUDED.odds_value, odds_value = EXCLUDED.odds_value,
raw_odds = EXCLUDED.raw_odds, raw_odds = EXCLUDED.raw_odds,
market_type = EXCLUDED.market_type, market_type = EXCLUDED.market_type,
market_name = EXCLUDED.market_name, market_name = EXCLUDED.market_name,
market_category = EXCLUDED.market_category, market_category = EXCLUDED.market_category,
fetched_at = now(), fetched_at = EXCLUDED.fetched_at,
is_active = true, is_active = EXCLUDED.is_active,
source = 'b365api', source = EXCLUDED.source,
fi = EXCLUDED.fi, fi = EXCLUDED.fi;
raw_event_id = EXCLUDED.raw_event_id;
-- name: GetPrematchOdds :many -- name: GetPrematchOdds :many
SELECT SELECT
id,
event_id,
fi,
raw_event_id,
market_type,
market_name,
market_category,
market_id,
header,
name,
handicap,
odds_value,
section,
category,
raw_odds,
fetched_at,
source,
is_active
FROM odds
WHERE event_id = $1 AND is_active = true AND source = 'b365api';
-- name: GetALLPrematchOdds :many
SELECT
id,
event_id, event_id,
fi, fi,
market_type, market_type,
@ -78,10 +49,30 @@ SELECT
is_active is_active
FROM odds FROM odds
WHERE is_active = true AND source = 'b365api'; WHERE is_active = true AND source = 'b365api';
-- name: GetALLPrematchOdds :many
SELECT
event_id,
fi,
market_type,
market_name,
market_category,
market_id,
name,
handicap,
odds_value,
section,
category,
raw_odds,
fetched_at,
source,
is_active
FROM odds
WHERE is_active = true AND source = 'b365api';
-- name: GetRawOddsByID :one -- name: GetRawOddsByID :one
SELECT SELECT
id, id,
event_id,
raw_odds, raw_odds,
fetched_at fetched_at
FROM odds FROM odds
@ -89,4 +80,30 @@ WHERE
raw_odds @> $1::jsonb AND raw_odds @> $1::jsonb AND
is_active = true AND is_active = true AND
source = 'b365api' source = 'b365api'
LIMIT 1; LIMIT 1;
-- name: GetPrematchOddsByUpcomingID :many
SELECT
o.event_id,
o.fi,
o.market_type,
o.market_name,
o.market_category,
o.market_id,
o.name,
o.handicap,
o.odds_value,
o.section,
o.category,
o.raw_odds,
o.fetched_at,
o.source,
o.is_active
FROM odds o
JOIN events e ON o.fi = e.id
WHERE e.id = $1
AND e.is_live = false
AND e.status = 'upcoming'
AND o.is_active = true
AND o.source = 'b365api'
LIMIT $2 OFFSET $3;

View File

@ -332,6 +332,65 @@ const docTemplate = `{
} }
} }
}, },
"/prematch/odds/upcoming/{upcoming_id}": {
"get": {
"description": "Retrieve prematch odds by upcoming event ID (FI from Bet365) with optional pagination",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"prematch"
],
"summary": "Retrieve prematch odds by upcoming ID (FI)",
"parameters": [
{
"type": "string",
"description": "Upcoming Event ID (FI)",
"name": "upcoming_id",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "Number of results to return (default: 10)",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"description": "Number of results to skip (default: 0)",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.Odd"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/prematch/odds/{event_id}": { "/prematch/odds/{event_id}": {
"get": { "get": {
"description": "Retrieve prematch odds for a specific event by event ID", "description": "Retrieve prematch odds for a specific event by event ID",
@ -669,9 +728,6 @@ const docTemplate = `{
"handicap": { "handicap": {
"type": "string" "type": "string"
}, },
"id": {
"type": "integer"
},
"is_active": { "is_active": {
"type": "boolean" "type": "boolean"
}, },
@ -685,11 +741,9 @@ const docTemplate = `{
"type": "string" "type": "string"
}, },
"market_type": { "market_type": {
"description": "RawEventID string ` + "`" + `json:\"raw_event_id\"` + "`" + `",
"type": "string" "type": "string"
}, },
"name": { "name": {
"description": "Header string ` + "`" + `json:\"header\"` + "`" + `",
"type": "string" "type": "string"
}, },
"odds_value": { "odds_value": {

View File

@ -324,6 +324,65 @@
} }
} }
}, },
"/prematch/odds/upcoming/{upcoming_id}": {
"get": {
"description": "Retrieve prematch odds by upcoming event ID (FI from Bet365) with optional pagination",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"prematch"
],
"summary": "Retrieve prematch odds by upcoming ID (FI)",
"parameters": [
{
"type": "string",
"description": "Upcoming Event ID (FI)",
"name": "upcoming_id",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "Number of results to return (default: 10)",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"description": "Number of results to skip (default: 0)",
"name": "offset",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.Odd"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/prematch/odds/{event_id}": { "/prematch/odds/{event_id}": {
"get": { "get": {
"description": "Retrieve prematch odds for a specific event by event ID", "description": "Retrieve prematch odds for a specific event by event ID",
@ -661,9 +720,6 @@
"handicap": { "handicap": {
"type": "string" "type": "string"
}, },
"id": {
"type": "integer"
},
"is_active": { "is_active": {
"type": "boolean" "type": "boolean"
}, },
@ -677,11 +733,9 @@
"type": "string" "type": "string"
}, },
"market_type": { "market_type": {
"description": "RawEventID string `json:\"raw_event_id\"`",
"type": "string" "type": "string"
}, },
"name": { "name": {
"description": "Header string `json:\"header\"`",
"type": "string" "type": "string"
}, },
"odds_value": { "odds_value": {

View File

@ -11,8 +11,6 @@ definitions:
type: string type: string
handicap: handicap:
type: string type: string
id:
type: integer
is_active: is_active:
type: boolean type: boolean
market_category: market_category:
@ -22,10 +20,8 @@ definitions:
market_name: market_name:
type: string type: string
market_type: market_type:
description: RawEventID string `json:"raw_event_id"`
type: string type: string
name: name:
description: Header string `json:"header"`
type: string type: string
odds_value: odds_value:
type: number type: number
@ -498,6 +494,46 @@ paths:
summary: Retrieve raw odds by ID summary: Retrieve raw odds by ID
tags: tags:
- prematch - prematch
/prematch/odds/upcoming/{upcoming_id}:
get:
consumes:
- application/json
description: Retrieve prematch odds by upcoming event ID (FI from Bet365) with
optional pagination
parameters:
- description: Upcoming Event ID (FI)
in: path
name: upcoming_id
required: true
type: string
- description: 'Number of results to return (default: 10)'
in: query
name: limit
type: integer
- description: 'Number of results to skip (default: 0)'
in: query
name: offset
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/domain.Odd'
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Retrieve prematch odds by upcoming ID (FI)
tags:
- prematch
/user/checkPhoneEmailExist: /user/checkPhoneEmailExist:
post: post:
consumes: consumes:

View File

@ -36,12 +36,10 @@ type Odd struct {
ID int32 ID int32
EventID pgtype.Text EventID pgtype.Text
Fi pgtype.Text Fi pgtype.Text
RawEventID pgtype.Text
MarketType string MarketType string
MarketName pgtype.Text MarketName pgtype.Text
MarketCategory pgtype.Text MarketCategory pgtype.Text
MarketID pgtype.Text MarketID pgtype.Text
Header pgtype.Text
Name pgtype.Text Name pgtype.Text
Handicap pgtype.Text Handicap pgtype.Text
OddsValue pgtype.Float8 OddsValue pgtype.Float8

View File

@ -13,7 +13,6 @@ import (
const GetALLPrematchOdds = `-- name: GetALLPrematchOdds :many const GetALLPrematchOdds = `-- name: GetALLPrematchOdds :many
SELECT SELECT
id,
event_id, event_id,
fi, fi,
market_type, market_type,
@ -34,7 +33,6 @@ WHERE is_active = true AND source = 'b365api'
` `
type GetALLPrematchOddsRow struct { type GetALLPrematchOddsRow struct {
ID int32
EventID pgtype.Text EventID pgtype.Text
Fi pgtype.Text Fi pgtype.Text
MarketType string MarketType string
@ -62,7 +60,6 @@ func (q *Queries) GetALLPrematchOdds(ctx context.Context) ([]GetALLPrematchOddsR
for rows.Next() { for rows.Next() {
var i GetALLPrematchOddsRow var i GetALLPrematchOddsRow
if err := rows.Scan( if err := rows.Scan(
&i.ID,
&i.EventID, &i.EventID,
&i.Fi, &i.Fi,
&i.MarketType, &i.MarketType,
@ -91,15 +88,12 @@ func (q *Queries) GetALLPrematchOdds(ctx context.Context) ([]GetALLPrematchOddsR
const GetPrematchOdds = `-- name: GetPrematchOdds :many const GetPrematchOdds = `-- name: GetPrematchOdds :many
SELECT SELECT
id,
event_id, event_id,
fi, fi,
raw_event_id,
market_type, market_type,
market_name, market_name,
market_category, market_category,
market_id, market_id,
header,
name, name,
handicap, handicap,
odds_value, odds_value,
@ -110,28 +104,130 @@ SELECT
source, source,
is_active is_active
FROM odds FROM odds
WHERE event_id = $1 AND is_active = true AND source = 'b365api' WHERE is_active = true AND source = 'b365api'
` `
func (q *Queries) GetPrematchOdds(ctx context.Context, eventID pgtype.Text) ([]Odd, error) { type GetPrematchOddsRow struct {
rows, err := q.db.Query(ctx, GetPrematchOdds, eventID) EventID pgtype.Text
Fi pgtype.Text
MarketType string
MarketName pgtype.Text
MarketCategory pgtype.Text
MarketID pgtype.Text
Name pgtype.Text
Handicap pgtype.Text
OddsValue pgtype.Float8
Section string
Category pgtype.Text
RawOdds []byte
FetchedAt pgtype.Timestamp
Source pgtype.Text
IsActive pgtype.Bool
}
func (q *Queries) GetPrematchOdds(ctx context.Context) ([]GetPrematchOddsRow, error) {
rows, err := q.db.Query(ctx, GetPrematchOdds)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
var items []Odd var items []GetPrematchOddsRow
for rows.Next() { for rows.Next() {
var i Odd var i GetPrematchOddsRow
if err := rows.Scan(
&i.EventID,
&i.Fi,
&i.MarketType,
&i.MarketName,
&i.MarketCategory,
&i.MarketID,
&i.Name,
&i.Handicap,
&i.OddsValue,
&i.Section,
&i.Category,
&i.RawOdds,
&i.FetchedAt,
&i.Source,
&i.IsActive,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const GetPrematchOddsByUpcomingID = `-- name: GetPrematchOddsByUpcomingID :many
SELECT
o.event_id,
o.fi,
o.market_type,
o.market_name,
o.market_category,
o.market_id,
o.name,
o.handicap,
o.odds_value,
o.section,
o.category,
o.raw_odds,
o.fetched_at,
o.source,
o.is_active
FROM odds o
JOIN events e ON o.fi = e.id
WHERE e.id = $1
AND e.is_live = false
AND e.status = 'upcoming'
AND o.is_active = true
AND o.source = 'b365api'
LIMIT $2 OFFSET $3
`
type GetPrematchOddsByUpcomingIDParams struct {
ID string
Limit int32
Offset int32
}
type GetPrematchOddsByUpcomingIDRow struct {
EventID pgtype.Text
Fi pgtype.Text
MarketType string
MarketName pgtype.Text
MarketCategory pgtype.Text
MarketID pgtype.Text
Name pgtype.Text
Handicap pgtype.Text
OddsValue pgtype.Float8
Section string
Category pgtype.Text
RawOdds []byte
FetchedAt pgtype.Timestamp
Source pgtype.Text
IsActive pgtype.Bool
}
func (q *Queries) GetPrematchOddsByUpcomingID(ctx context.Context, arg GetPrematchOddsByUpcomingIDParams) ([]GetPrematchOddsByUpcomingIDRow, error) {
rows, err := q.db.Query(ctx, GetPrematchOddsByUpcomingID, arg.ID, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetPrematchOddsByUpcomingIDRow
for rows.Next() {
var i GetPrematchOddsByUpcomingIDRow
if err := rows.Scan( if err := rows.Scan(
&i.ID,
&i.EventID, &i.EventID,
&i.Fi, &i.Fi,
&i.RawEventID,
&i.MarketType, &i.MarketType,
&i.MarketName, &i.MarketName,
&i.MarketCategory, &i.MarketCategory,
&i.MarketID, &i.MarketID,
&i.Header,
&i.Name, &i.Name,
&i.Handicap, &i.Handicap,
&i.OddsValue, &i.OddsValue,
@ -155,7 +251,6 @@ func (q *Queries) GetPrematchOdds(ctx context.Context, eventID pgtype.Text) ([]O
const GetRawOddsByID = `-- name: GetRawOddsByID :one const GetRawOddsByID = `-- name: GetRawOddsByID :one
SELECT SELECT
id, id,
event_id,
raw_odds, raw_odds,
fetched_at fetched_at
FROM odds FROM odds
@ -168,7 +263,6 @@ LIMIT 1
type GetRawOddsByIDRow struct { type GetRawOddsByIDRow struct {
ID int32 ID int32
EventID pgtype.Text
RawOdds []byte RawOdds []byte
FetchedAt pgtype.Timestamp FetchedAt pgtype.Timestamp
} }
@ -176,12 +270,7 @@ type GetRawOddsByIDRow struct {
func (q *Queries) GetRawOddsByID(ctx context.Context, dollar_1 []byte) (GetRawOddsByIDRow, error) { func (q *Queries) GetRawOddsByID(ctx context.Context, dollar_1 []byte) (GetRawOddsByIDRow, error) {
row := q.db.QueryRow(ctx, GetRawOddsByID, dollar_1) row := q.db.QueryRow(ctx, GetRawOddsByID, dollar_1)
var i GetRawOddsByIDRow var i GetRawOddsByIDRow
err := row.Scan( err := row.Scan(&i.ID, &i.RawOdds, &i.FetchedAt)
&i.ID,
&i.EventID,
&i.RawOdds,
&i.FetchedAt,
)
return i, err return i, err
} }
@ -189,12 +278,10 @@ const InsertNonLiveOdd = `-- name: InsertNonLiveOdd :exec
INSERT INTO odds ( INSERT INTO odds (
event_id, event_id,
fi, fi,
raw_event_id,
market_type, market_type,
market_name, market_name,
market_category, market_category,
market_id, market_id,
header,
name, name,
handicap, handicap,
odds_value, odds_value,
@ -206,55 +293,55 @@ INSERT INTO odds (
fetched_at fetched_at
) VALUES ( ) VALUES (
$1, $2, $3, $4, $5, $6, $7, $1, $2, $3, $4, $5, $6, $7,
$8, $9, $10, $11, $12, $13, $14, $8, $9, $10, $11, $12, $13, $14, $15
true, 'b365api', now()
) )
ON CONFLICT (event_id, market_id, header, name, handicap) DO UPDATE SET ON CONFLICT (market_id, name, handicap) DO UPDATE SET
odds_value = EXCLUDED.odds_value, odds_value = EXCLUDED.odds_value,
raw_odds = EXCLUDED.raw_odds, raw_odds = EXCLUDED.raw_odds,
market_type = EXCLUDED.market_type, market_type = EXCLUDED.market_type,
market_name = EXCLUDED.market_name, market_name = EXCLUDED.market_name,
market_category = EXCLUDED.market_category, market_category = EXCLUDED.market_category,
fetched_at = now(), fetched_at = EXCLUDED.fetched_at,
is_active = true, is_active = EXCLUDED.is_active,
source = 'b365api', source = EXCLUDED.source,
fi = EXCLUDED.fi, fi = EXCLUDED.fi
raw_event_id = EXCLUDED.raw_event_id
` `
type InsertNonLiveOddParams struct { type InsertNonLiveOddParams struct {
EventID pgtype.Text EventID pgtype.Text
Fi pgtype.Text Fi pgtype.Text
RawEventID pgtype.Text
MarketType string MarketType string
MarketName pgtype.Text MarketName pgtype.Text
MarketCategory pgtype.Text MarketCategory pgtype.Text
MarketID pgtype.Text MarketID pgtype.Text
Header pgtype.Text
Name pgtype.Text Name pgtype.Text
Handicap pgtype.Text Handicap pgtype.Text
OddsValue pgtype.Float8 OddsValue pgtype.Float8
Section string Section string
Category pgtype.Text Category pgtype.Text
RawOdds []byte RawOdds []byte
IsActive pgtype.Bool
Source pgtype.Text
FetchedAt pgtype.Timestamp
} }
func (q *Queries) InsertNonLiveOdd(ctx context.Context, arg InsertNonLiveOddParams) error { func (q *Queries) InsertNonLiveOdd(ctx context.Context, arg InsertNonLiveOddParams) error {
_, err := q.db.Exec(ctx, InsertNonLiveOdd, _, err := q.db.Exec(ctx, InsertNonLiveOdd,
arg.EventID, arg.EventID,
arg.Fi, arg.Fi,
arg.RawEventID,
arg.MarketType, arg.MarketType,
arg.MarketName, arg.MarketName,
arg.MarketCategory, arg.MarketCategory,
arg.MarketID, arg.MarketID,
arg.Header,
arg.Name, arg.Name,
arg.Handicap, arg.Handicap,
arg.OddsValue, arg.OddsValue,
arg.Section, arg.Section,
arg.Category, arg.Category,
arg.RawOdds, arg.RawOdds,
arg.IsActive,
arg.Source,
arg.FetchedAt,
) )
return err return err
} }

View File

@ -16,23 +16,18 @@ type Market struct {
MarketID string MarketID string
UpdatedAt time.Time UpdatedAt time.Time
Odds []json.RawMessage Odds []json.RawMessage
Header string
Name string Name string
Handicap string Handicap string
OddsVal float64 OddsVal float64
} }
type Odd struct { type Odd struct {
ID int64 `json:"id"`
EventID string `json:"event_id"` EventID string `json:"event_id"`
Fi string `json:"fi"` Fi string `json:"fi"`
// RawEventID string `json:"raw_event_id"`
MarketType string `json:"market_type"` MarketType string `json:"market_type"`
MarketName string `json:"market_name"` MarketName string `json:"market_name"`
MarketCategory string `json:"market_category"` MarketCategory string `json:"market_category"`
MarketID string `json:"market_id"` MarketID string `json:"market_id"`
// Header string `json:"header"`
Name string `json:"name"` Name string `json:"name"`
Handicap string `json:"handicap"` Handicap string `json:"handicap"`
OddsValue float64 `json:"odds_value"` OddsValue float64 `json:"odds_value"`

View File

@ -23,7 +23,6 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
continue continue
} }
header := getString(item["header"])
name := getString(item["name"]) name := getString(item["name"])
handicap := getString(item["handicap"]) handicap := getString(item["handicap"])
oddsVal := getFloat(item["odds"]) oddsVal := getFloat(item["odds"])
@ -33,23 +32,24 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
params := dbgen.InsertNonLiveOddParams{ params := dbgen.InsertNonLiveOddParams{
EventID: pgtype.Text{String: m.EventID, Valid: m.EventID != ""}, EventID: pgtype.Text{String: m.EventID, Valid: m.EventID != ""},
Fi: pgtype.Text{String: m.FI, Valid: m.FI != ""}, Fi: pgtype.Text{String: m.FI, Valid: m.FI != ""},
RawEventID: pgtype.Text{String: m.EventID, Valid: m.EventID != ""},
MarketType: m.MarketType, MarketType: m.MarketType,
MarketName: pgtype.Text{String: m.MarketName, Valid: m.MarketName != ""}, MarketName: pgtype.Text{String: m.MarketName, Valid: m.MarketName != ""},
MarketCategory: pgtype.Text{String: m.MarketCategory, Valid: m.MarketCategory != ""}, MarketCategory: pgtype.Text{String: m.MarketCategory, Valid: m.MarketCategory != ""},
MarketID: pgtype.Text{String: m.MarketID, Valid: m.MarketID != ""}, MarketID: pgtype.Text{String: m.MarketID, Valid: m.MarketID != ""},
Header: pgtype.Text{String: header, Valid: header != ""},
Name: pgtype.Text{String: name, Valid: name != ""}, Name: pgtype.Text{String: name, Valid: name != ""},
Handicap: pgtype.Text{String: handicap, Valid: handicap != ""}, Handicap: pgtype.Text{String: handicap, Valid: handicap != ""},
OddsValue: pgtype.Float8{Float64: oddsVal, Valid: oddsVal != 0}, OddsValue: pgtype.Float8{Float64: oddsVal, Valid: oddsVal != 0},
Section: m.MarketCategory, Section: m.MarketCategory,
Category: pgtype.Text{Valid: false}, Category: pgtype.Text{Valid: false},
RawOdds: rawOddsBytes, RawOdds: rawOddsBytes,
IsActive: pgtype.Bool{Bool: true, Valid: true},
Source: pgtype.Text{String: "b365api", Valid: true},
FetchedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
} }
err := s.queries.InsertNonLiveOdd(ctx, params) err := s.queries.InsertNonLiveOdd(ctx, params)
if err != nil { if err != nil {
_ = writeFailedMarketLog(m, err) _ = writeFailedMarketLog(m, err)
continue continue
} }
} }
@ -103,9 +103,7 @@ func getFloat(v interface{}) float64 {
} }
func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) { func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) {
eventIDParam := pgtype.Text{String: eventID, Valid: eventID != ""} odds, err := s.queries.GetPrematchOdds(ctx)
odds, err := s.queries.GetPrematchOdds(ctx, eventIDParam)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -113,15 +111,12 @@ func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.O
domainOdds := make([]domain.Odd, len(odds)) domainOdds := make([]domain.Odd, len(odds))
for i, odd := range odds { for i, odd := range odds {
domainOdds[i] = domain.Odd{ domainOdds[i] = domain.Odd{
ID: int64(odd.ID),
EventID: odd.EventID.String, EventID: odd.EventID.String,
Fi: odd.Fi.String, Fi: odd.Fi.String,
// RawEventID: odd.RawEventID.String,
MarketType: odd.MarketType, MarketType: odd.MarketType,
MarketName: odd.MarketName.String, MarketName: odd.MarketName.String,
MarketCategory: odd.MarketCategory.String, MarketCategory: odd.MarketCategory.String,
MarketID: odd.MarketID.String, MarketID: odd.MarketID.String,
// Header: odd.Header.String,
Name: odd.Name.String, Name: odd.Name.String,
Handicap: odd.Handicap.String, Handicap: odd.Handicap.String,
OddsValue: odd.OddsValue.Float64, OddsValue: odd.OddsValue.Float64,
@ -152,7 +147,7 @@ func (s *Store) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) {
domainOdds := make([]domain.Odd, len(rows)) domainOdds := make([]domain.Odd, len(rows))
for i, row := range rows { for i, row := range rows {
domainOdds[i] = domain.Odd{ domainOdds[i] = domain.Odd{
ID: int64(row.ID), // ID: int64(row.ID),
EventID: row.EventID.String, EventID: row.EventID.String,
Fi: row.Fi.String, Fi: row.Fi.String,
MarketType: row.MarketType, MarketType: row.MarketType,
@ -195,7 +190,6 @@ func (s *Store) GetRawOddsByID(ctx context.Context, rawOddsID string) (domain.Ra
return domain.RawOddsByID{ return domain.RawOddsByID{
ID: int64(odd.ID), ID: int64(odd.ID),
EventID: odd.EventID.String,
RawOdds: func() []domain.RawMessage { RawOdds: func() []domain.RawMessage {
converted := make([]domain.RawMessage, len(rawOdds)) converted := make([]domain.RawMessage, len(rawOdds))
for i, r := range rawOdds { for i, r := range rawOdds {
@ -205,4 +199,48 @@ func (s *Store) GetRawOddsByID(ctx context.Context, rawOddsID string) (domain.Ra
}(), }(),
FetchedAt: odd.FetchedAt.Time, FetchedAt: odd.FetchedAt.Time,
}, nil }, nil
}
func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset int32) ([]domain.Odd, error) {
// Prepare query parameters
params := dbgen.GetPrematchOddsByUpcomingIDParams{
ID: upcomingID,
Limit: limit,
Offset: offset,
}
// Execute the query
odds, err := s.queries.GetPrematchOddsByUpcomingID(ctx, params)
if err != nil {
return nil, err
}
// Map the results to domain.Odd
domainOdds := make([]domain.Odd, len(odds))
for i, odd := range odds {
var rawOdds []domain.RawMessage
if err := json.Unmarshal(odd.RawOdds, &rawOdds); err != nil {
rawOdds = nil
}
domainOdds[i] = domain.Odd{
EventID: odd.EventID.String,
Fi: odd.Fi.String,
MarketType: odd.MarketType,
MarketName: odd.MarketName.String,
MarketCategory: odd.MarketCategory.String,
MarketID: odd.MarketID.String,
Name: odd.Name.String,
Handicap: odd.Handicap.String,
OddsValue: odd.OddsValue.Float64,
Section: odd.Section,
Category: odd.Category.String,
RawOdds: rawOdds,
FetchedAt: odd.FetchedAt.Time,
Source: odd.Source.String,
IsActive: odd.IsActive.Bool,
}
}
return domainOdds, nil
} }

View File

@ -97,7 +97,7 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
} }
func (s *service) FetchUpcomingEvents(ctx context.Context) error { func (s *service) FetchUpcomingEvents(ctx context.Context) error {
sportIDs := []int{1, 13, 78, 18, 91, 16, 17, 14, 12, 3, 2, 4, 83, 15, 92, 94, 8, 19, 36, 66, 9, 75, 90, 95, 110, 107, 151, 162, 148} sportIDs := []int{1}
for _, sportID := range sportIDs { for _, sportID := range sportIDs {
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s", sportID, s.token) url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s", sportID, s.token)
resp, err := http.Get(url) resp, err := http.Get(url)

View File

@ -24,76 +24,51 @@ func New(token string, store *repository.Store) *ServiceImpl {
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error { func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
sportIDs := []int{ eventIDs, err := s.store.GetAllUpcomingEvents(ctx)
1, 13, 78, 18, 91, 16, 17, 14, 12, 3, 2, 4, if err != nil {
83, 15, 92, 94, 8, 19, 36, 66, 9, 75, 90, log.Printf("❌ Failed to fetch upcoming event IDs: %v", err)
95, 110, 107, 151, 162, 148, return err
} }
for _, sportID := range sportIDs {
upcomingURL := "https://api.b365api.com/v1/bet365/upcoming?sport_id=" + strconv.Itoa(sportID) + "&token=" + s.token for _, event := range eventIDs {
log.Printf("Fetching upcoming odds for sport ID: %d from URL: %s", sportID, upcomingURL) eventID := event.ID
resp, err := http.Get(upcomingURL) prematchURL := "https://api.b365api.com/v3/bet365/prematch?token=" + s.token + "&FI=" + eventID
log.Printf("📡 Fetching prematch odds for event ID: %s", eventID)
resp, err := http.Get(prematchURL)
if err != nil { if err != nil {
log.Printf("Error fetching upcoming odds for sport ID: %d, error: %v", sportID, err) log.Printf("❌ Failed to fetch prematch odds for event %s: %v", eventID, err)
continue continue
} }
defer resp.Body.Close() defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body) body, _ := io.ReadAll(resp.Body)
var upcomingData struct { var oddsData struct {
Success int `json:"success"` Success int `json:"success"`
Results []struct { Results []struct {
ID string `json:"id"` EventID string `json:"event_id"`
FI string `json:"FI"`
Main OddsSection `json:"main"`
} `json:"results"` } `json:"results"`
} }
if err := json.Unmarshal(body, &upcomingData); err != nil || upcomingData.Success != 1 { if err := json.Unmarshal(body, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 {
log.Printf("Failed to parse upcoming odds for sport ID: %d, error: %v", sportID, err) log.Printf("❌ Invalid prematch data for event %s", eventID)
continue continue
} }
log.Printf("Successfully fetched upcoming odds for sport ID: %d", sportID) result := oddsData.Results[0]
finalID := result.EventID
for _, ev := range upcomingData.Results { if finalID == "" {
eventID := ev.ID finalID = result.FI
prematchURL := "https://api.b365api.com/v3/bet365/prematch?token=" + s.token + "&FI=" + eventID
log.Printf("Fetching prematch odds for event ID: %s from URL: %s", eventID, prematchURL)
oddsResp, err := http.Get(prematchURL)
if err != nil {
log.Printf("Error fetching prematch odds for event ID: %s, error: %v", eventID, err)
continue
}
defer oddsResp.Body.Close()
oddsBody, _ := io.ReadAll(oddsResp.Body)
var oddsData struct {
Success int `json:"success"`
Results []struct {
EventID string `json:"event_id"`
FI string `json:"FI"`
Main OddsSection `json:"main"`
} `json:"results"`
}
if err := json.Unmarshal(oddsBody, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 {
log.Printf("Failed to parse prematch odds for event ID: %s, error: %v", eventID, err)
continue
}
result := oddsData.Results[0]
finalID := result.EventID
if finalID == "" {
finalID = result.FI
}
if finalID == "" {
log.Printf("Skipping event with missing final ID for event ID: %s", eventID)
continue
}
log.Printf("Storing odds for event ID: %s, final ID: %s", eventID, finalID)
s.storeSection(ctx, finalID, result.FI, "main", result.Main)
} }
if finalID == "" {
log.Printf("⚠️ Skipping event %s with no valid ID", eventID)
continue
}
s.storeSection(ctx, finalID, result.FI, "main", result.Main)
} }
return nil return nil
} }
func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName string, section OddsSection) { func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName string, section OddsSection) {
@ -153,4 +128,7 @@ func (s *ServiceImpl) GetRawOddsByID(ctx context.Context, rawOddsID string) ([]d
return nil, err return nil, err
} }
return []domain.RawOddsByID{rawOdds}, nil return []domain.RawOddsByID{rawOdds}, nil
} }
func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset int32) ([]domain.Odd, error) {
return s.store.GetPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset)
}

View File

@ -1,7 +1,7 @@
package httpserver package httpserver
import ( import (
"context" // "context"
"log" "log"
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
@ -17,33 +17,35 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
task func() task func()
}{ }{
{ // {
spec: "0 0 * * * *", // Every hour at minute 0 and second 0 // spec: "*/30 * * * * *", // Every 30 seconds
task: func() { // task: func() {
if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { // if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
log.Printf("FetchUpcomingEvents error: %v", err) // log.Printf("FetchUpcomingEvents error: %v", err)
} // }
}, // },
}, // },
{ // {
spec: "*/5 * * * * *", // Every 5 seconds // spec: "*/5 * * * * *", // Every 5 seconds
task: func() { // task: func() {
if err := eventService.FetchLiveEvents(context.Background()); err != nil { // if err := eventService.FetchLiveEvents(context.Background()); err != nil {
log.Printf("FetchLiveEvents error: %v", err) // log.Printf("FetchLiveEvents error: %v", err)
} // }
}, // },
}, // },
{ // {
spec: "0 0 * * * *", // Every hour at minute 0 and second 0 // spec: "*/30 * * * * *", // Every 30 seconds
task: func() { // task: func() {
if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { // if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
log.Printf("FetchNonLiveOdds error: %v", err) // log.Printf("FetchNonLiveOdds error: %v", err)
} // }
}, // },
}, // },
} }

View File

@ -1,11 +1,13 @@
package handlers package handlers
import ( import (
"github.com/gofiber/fiber/v2" "log/slog"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "strconv"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"log/slog" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
) )
// GetPrematchOdds godoc // GetPrematchOdds godoc
@ -123,3 +125,41 @@ func GetUpcomingEventByID(logger *slog.Logger, eventSvc event.Service) fiber.Han
return response.WriteJSON(c, fiber.StatusOK, "Upcoming event retrieved successfully", event, nil) return response.WriteJSON(c, fiber.StatusOK, "Upcoming event retrieved successfully", event, nil)
} }
} }
// @Summary Retrieve prematch odds by upcoming ID (FI)
// @Description Retrieve prematch odds by upcoming event ID (FI from Bet365) with optional pagination
// @Tags prematch
// @Accept json
// @Produce json
// @Param upcoming_id path string true "Upcoming Event ID (FI)"
// @Param limit query int false "Number of results to return (default: 10)"
// @Param offset query int false "Number of results to skip (default: 0)"
// @Success 200 {array} domain.Odd
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /prematch/odds/upcoming/{upcoming_id} [get]
func GetPrematchOddsByUpcomingID(logger *slog.Logger, prematchSvc *odds.ServiceImpl) fiber.Handler {
return func(c *fiber.Ctx) error {
upcomingID := c.Params("upcoming_id")
if upcomingID == "" {
return response.WriteJSON(c, fiber.StatusBadRequest, "Missing upcoming_id", nil, nil)
}
limit, err := strconv.Atoi(c.Query("limit", "10")) // Default limit is 10
if err != nil || limit <= 0 {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid limit value", nil, nil)
}
offset, err := strconv.Atoi(c.Query("offset", "0")) // Default offset is 0
if err != nil || offset < 0 {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid offset value", nil, nil)
}
odds, err := prematchSvc.GetPrematchOddsByUpcomingID(c.Context(), upcomingID, int32(limit), int32(offset))
if err != nil {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve prematch odds", nil, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "Prematch odds retrieved successfully", odds, nil)
}
}

View File

@ -33,6 +33,7 @@ func (a *App) initAppRoutes() {
a.fiber.Get("/prematch/events/:id", handlers.GetUpcomingEventByID(a.logger, a.eventSvc)) a.fiber.Get("/prematch/events/:id", handlers.GetUpcomingEventByID(a.logger, a.eventSvc))
a.fiber.Get("/prematch/events", handlers.GetAllUpcomingEvents(a.logger, a.eventSvc)) a.fiber.Get("/prematch/events", handlers.GetAllUpcomingEvents(a.logger, a.eventSvc))
a.fiber.Get("/prematch/odds/upcoming/:upcoming_id", handlers.GetPrematchOddsByUpcomingID(a.logger, a.prematchSvc))
// Swagger // Swagger
a.fiber.Get("/swagger/*", fiberSwagger.WrapHandler) a.fiber.Get("/swagger/*", fiberSwagger.WrapHandler)
} }