Merge remote-tracking branch 'refs/remotes/origin/auth' into auth

This commit is contained in:
Samuel Tariku 2025-04-13 13:37:13 +03:00
commit 98cb576873
14 changed files with 508 additions and 210 deletions

View File

@ -204,12 +204,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,
@ -219,7 +217,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

@ -1398,6 +1398,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",
@ -2565,9 +2624,6 @@ const docTemplate = `{
"handicap": { "handicap": {
"type": "string" "type": "string"
}, },
"id": {
"type": "integer"
},
"is_active": { "is_active": {
"type": "boolean" "type": "boolean"
}, },
@ -2581,11 +2637,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

@ -1390,6 +1390,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",
@ -2557,9 +2616,6 @@
"handicap": { "handicap": {
"type": "string" "type": "string"
}, },
"id": {
"type": "integer"
},
"is_active": { "is_active": {
"type": "boolean" "type": "boolean"
}, },
@ -2573,11 +2629,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

@ -34,8 +34,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:
@ -45,10 +43,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
@ -1739,6 +1735,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
/search/branch: /search/branch:
get: get:
consumes: consumes:

View File

@ -143,12 +143,10 @@ type Odd struct {
ID int32 `json:"id"` ID int32 `json:"id"`
EventID pgtype.Text `json:"event_id"` EventID pgtype.Text `json:"event_id"`
Fi pgtype.Text `json:"fi"` Fi pgtype.Text `json:"fi"`
RawEventID pgtype.Text `json:"raw_event_id"`
MarketType string `json:"market_type"` MarketType string `json:"market_type"`
MarketName pgtype.Text `json:"market_name"` MarketName pgtype.Text `json:"market_name"`
MarketCategory pgtype.Text `json:"market_category"` MarketCategory pgtype.Text `json:"market_category"`
MarketID pgtype.Text `json:"market_id"` MarketID pgtype.Text `json:"market_id"`
Header pgtype.Text `json:"header"`
Name pgtype.Text `json:"name"` Name pgtype.Text `json:"name"`
Handicap pgtype.Text `json:"handicap"` Handicap pgtype.Text `json:"handicap"`
OddsValue pgtype.Float8 `json:"odds_value"` OddsValue pgtype.Float8 `json:"odds_value"`

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 `json:"id"`
EventID pgtype.Text `json:"event_id"` EventID pgtype.Text `json:"event_id"`
Fi pgtype.Text `json:"fi"` Fi pgtype.Text `json:"fi"`
MarketType string `json:"market_type"` MarketType string `json:"market_type"`
@ -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 `json:"event_id"`
Fi pgtype.Text `json:"fi"`
MarketType string `json:"market_type"`
MarketName pgtype.Text `json:"market_name"`
MarketCategory pgtype.Text `json:"market_category"`
MarketID pgtype.Text `json:"market_id"`
Name pgtype.Text `json:"name"`
Handicap pgtype.Text `json:"handicap"`
OddsValue pgtype.Float8 `json:"odds_value"`
Section string `json:"section"`
Category pgtype.Text `json:"category"`
RawOdds []byte `json:"raw_odds"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
Source pgtype.Text `json:"source"`
IsActive pgtype.Bool `json:"is_active"`
}
func (q *Queries) 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 `json:"id"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
type GetPrematchOddsByUpcomingIDRow struct {
EventID pgtype.Text `json:"event_id"`
Fi pgtype.Text `json:"fi"`
MarketType string `json:"market_type"`
MarketName pgtype.Text `json:"market_name"`
MarketCategory pgtype.Text `json:"market_category"`
MarketID pgtype.Text `json:"market_id"`
Name pgtype.Text `json:"name"`
Handicap pgtype.Text `json:"handicap"`
OddsValue pgtype.Float8 `json:"odds_value"`
Section string `json:"section"`
Category pgtype.Text `json:"category"`
RawOdds []byte `json:"raw_odds"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
Source pgtype.Text `json:"source"`
IsActive pgtype.Bool `json:"is_active"`
}
func (q *Queries) GetPrematchOddsByUpcomingID(ctx context.Context, arg GetPrematchOddsByUpcomingIDParams) ([]GetPrematchOddsByUpcomingIDRow, error) {
rows, err := q.db.Query(ctx, GetPrematchOddsByUpcomingID, arg.ID, arg.Limit, arg.Offset)
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 `json:"id"` ID int32 `json:"id"`
EventID pgtype.Text `json:"event_id"`
RawOdds []byte `json:"raw_odds"` RawOdds []byte `json:"raw_odds"`
FetchedAt pgtype.Timestamp `json:"fetched_at"` FetchedAt pgtype.Timestamp `json:"fetched_at"`
} }
@ -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 `json:"event_id"` EventID pgtype.Text `json:"event_id"`
Fi pgtype.Text `json:"fi"` Fi pgtype.Text `json:"fi"`
RawEventID pgtype.Text `json:"raw_event_id"` MarketType string `json:"market_type"`
MarketType string `json:"market_type"` MarketName pgtype.Text `json:"market_name"`
MarketName pgtype.Text `json:"market_name"` MarketCategory pgtype.Text `json:"market_category"`
MarketCategory pgtype.Text `json:"market_category"` MarketID pgtype.Text `json:"market_id"`
MarketID pgtype.Text `json:"market_id"` Name pgtype.Text `json:"name"`
Header pgtype.Text `json:"header"` Handicap pgtype.Text `json:"handicap"`
Name pgtype.Text `json:"name"` OddsValue pgtype.Float8 `json:"odds_value"`
Handicap pgtype.Text `json:"handicap"` Section string `json:"section"`
OddsValue pgtype.Float8 `json:"odds_value"` Category pgtype.Text `json:"category"`
Section string `json:"section"` RawOdds []byte `json:"raw_odds"`
Category pgtype.Text `json:"category"` IsActive pgtype.Bool `json:"is_active"`
RawOdds []byte `json:"raw_odds"` Source pgtype.Text `json:"source"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
} }
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

@ -63,6 +63,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.FiberWrapHandler()) a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler())