adding prematchodd
This commit is contained in:
parent
a282080133
commit
b90fd84aba
|
|
@ -67,7 +67,7 @@ func main() {
|
||||||
app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{
|
app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{
|
||||||
JwtAccessKey: cfg.JwtKey,
|
JwtAccessKey: cfg.JwtKey,
|
||||||
JwtAccessExpiry: cfg.AccessExpiry,
|
JwtAccessExpiry: cfg.AccessExpiry,
|
||||||
}, userSvc)
|
}, userSvc, oddsSvc)
|
||||||
|
|
||||||
logger.Info("Starting server", "port", cfg.Port)
|
logger.Info("Starting server", "port", cfg.Port)
|
||||||
if err := app.Run(); err != nil {
|
if err := app.Run(); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,25 @@ ON CONFLICT (event_id, market_id, header, name, handicap) DO UPDATE SET
|
||||||
raw_event_id = EXCLUDED.raw_event_id;
|
raw_event_id = EXCLUDED.raw_event_id;
|
||||||
|
|
||||||
|
|
||||||
-- name: GetUpcomingEventIDs :many
|
-- name: GetPrematchOdds :many
|
||||||
SELECT id FROM events
|
SELECT
|
||||||
WHERE is_live = false;
|
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';
|
||||||
115
docs/docs.go
115
docs/docs.go
|
|
@ -180,6 +180,53 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/prematch/odds/{event_id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Retrieve prematch odds for a specific event by event ID",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"prematch"
|
||||||
|
],
|
||||||
|
"summary": "Retrieve prematch odds for an event",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Event ID",
|
||||||
|
"name": "event_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/user/checkPhoneEmailExist": {
|
"/user/checkPhoneEmailExist": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Check if phone number or email exist",
|
"description": "Check if phone number or email exist",
|
||||||
|
|
@ -452,20 +499,80 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
"domain.Odd": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"category": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"event_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"fetched_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"fi": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"handicap": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"is_active": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"market_category": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"market_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"market_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"market_type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"odds_value": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"raw_event_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"raw_odds": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {}
|
||||||
|
},
|
||||||
|
"section": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.Role": {
|
"domain.Role": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"admin",
|
|
||||||
"customer",
|
|
||||||
"super_admin",
|
"super_admin",
|
||||||
|
"admin",
|
||||||
"branch_manager",
|
"branch_manager",
|
||||||
|
"customer",
|
||||||
"cashier"
|
"cashier"
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"RoleAdmin",
|
|
||||||
"RoleCustomer",
|
|
||||||
"RoleSuperAdmin",
|
"RoleSuperAdmin",
|
||||||
|
"RoleAdmin",
|
||||||
"RoleBranchManager",
|
"RoleBranchManager",
|
||||||
|
"RoleCustomer",
|
||||||
"RoleCashier"
|
"RoleCashier"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,53 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/prematch/odds/{event_id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Retrieve prematch odds for a specific event by event ID",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"prematch"
|
||||||
|
],
|
||||||
|
"summary": "Retrieve prematch odds for an event",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Event ID",
|
||||||
|
"name": "event_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/user/checkPhoneEmailExist": {
|
"/user/checkPhoneEmailExist": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Check if phone number or email exist",
|
"description": "Check if phone number or email exist",
|
||||||
|
|
@ -444,20 +491,80 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
"domain.Odd": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"category": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"event_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"fetched_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"fi": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"handicap": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"is_active": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"market_category": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"market_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"market_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"market_type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"odds_value": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"raw_event_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"raw_odds": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {}
|
||||||
|
},
|
||||||
|
"section": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.Role": {
|
"domain.Role": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"admin",
|
|
||||||
"customer",
|
|
||||||
"super_admin",
|
"super_admin",
|
||||||
|
"admin",
|
||||||
"branch_manager",
|
"branch_manager",
|
||||||
|
"customer",
|
||||||
"cashier"
|
"cashier"
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"RoleAdmin",
|
|
||||||
"RoleCustomer",
|
|
||||||
"RoleSuperAdmin",
|
"RoleSuperAdmin",
|
||||||
|
"RoleAdmin",
|
||||||
"RoleBranchManager",
|
"RoleBranchManager",
|
||||||
|
"RoleCustomer",
|
||||||
"RoleCashier"
|
"RoleCashier"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,57 @@
|
||||||
definitions:
|
definitions:
|
||||||
|
domain.Odd:
|
||||||
|
properties:
|
||||||
|
category:
|
||||||
|
type: string
|
||||||
|
event_id:
|
||||||
|
type: string
|
||||||
|
fetched_at:
|
||||||
|
type: string
|
||||||
|
fi:
|
||||||
|
type: string
|
||||||
|
handicap:
|
||||||
|
type: string
|
||||||
|
header:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
is_active:
|
||||||
|
type: boolean
|
||||||
|
market_category:
|
||||||
|
type: string
|
||||||
|
market_id:
|
||||||
|
type: string
|
||||||
|
market_name:
|
||||||
|
type: string
|
||||||
|
market_type:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
odds_value:
|
||||||
|
type: number
|
||||||
|
raw_event_id:
|
||||||
|
type: string
|
||||||
|
raw_odds:
|
||||||
|
items: {}
|
||||||
|
type: array
|
||||||
|
section:
|
||||||
|
type: string
|
||||||
|
source:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
domain.Role:
|
domain.Role:
|
||||||
enum:
|
enum:
|
||||||
- admin
|
|
||||||
- customer
|
|
||||||
- super_admin
|
- super_admin
|
||||||
|
- admin
|
||||||
- branch_manager
|
- branch_manager
|
||||||
|
- customer
|
||||||
- cashier
|
- cashier
|
||||||
type: string
|
type: string
|
||||||
x-enum-varnames:
|
x-enum-varnames:
|
||||||
- RoleAdmin
|
|
||||||
- RoleCustomer
|
|
||||||
- RoleSuperAdmin
|
- RoleSuperAdmin
|
||||||
|
- RoleAdmin
|
||||||
- RoleBranchManager
|
- RoleBranchManager
|
||||||
|
- RoleCustomer
|
||||||
- RoleCashier
|
- RoleCashier
|
||||||
handlers.CheckPhoneEmailExistReq:
|
handlers.CheckPhoneEmailExistReq:
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -275,6 +315,37 @@ paths:
|
||||||
summary: Refresh token
|
summary: Refresh token
|
||||||
tags:
|
tags:
|
||||||
- auth
|
- auth
|
||||||
|
/prematch/odds/{event_id}:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Retrieve prematch odds for a specific event by event ID
|
||||||
|
parameters:
|
||||||
|
- description: Event ID
|
||||||
|
in: path
|
||||||
|
name: event_id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
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 for an event
|
||||||
|
tags:
|
||||||
|
- prematch
|
||||||
/user/checkPhoneEmailExist:
|
/user/checkPhoneEmailExist:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
|
||||||
|
|
@ -11,24 +11,62 @@ import (
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
const GetUpcomingEventIDs = `-- name: GetUpcomingEventIDs :many
|
const GetPrematchOdds = `-- name: GetPrematchOdds :many
|
||||||
SELECT id FROM events
|
SELECT
|
||||||
WHERE is_live = false
|
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'
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetUpcomingEventIDs(ctx context.Context) ([]string, error) {
|
func (q *Queries) GetPrematchOdds(ctx context.Context, eventID pgtype.Text) ([]Odd, error) {
|
||||||
rows, err := q.db.Query(ctx, GetUpcomingEventIDs)
|
rows, err := q.db.Query(ctx, GetPrematchOdds, eventID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var items []string
|
var items []Odd
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var id string
|
var i Odd
|
||||||
if err := rows.Scan(&id); err != nil {
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.EventID,
|
||||||
|
&i.Fi,
|
||||||
|
&i.RawEventID,
|
||||||
|
&i.MarketType,
|
||||||
|
&i.MarketName,
|
||||||
|
&i.MarketCategory,
|
||||||
|
&i.MarketID,
|
||||||
|
&i.Header,
|
||||||
|
&i.Name,
|
||||||
|
&i.Handicap,
|
||||||
|
&i.OddsValue,
|
||||||
|
&i.Section,
|
||||||
|
&i.Category,
|
||||||
|
&i.RawOdds,
|
||||||
|
&i.FetchedAt,
|
||||||
|
&i.Source,
|
||||||
|
&i.IsActive,
|
||||||
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
items = append(items, id)
|
items = append(items, i)
|
||||||
}
|
}
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ package domain
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
type RawMessage interface{} // Change from json.RawMessage to interface{}
|
||||||
|
|
||||||
type Market struct {
|
type Market struct {
|
||||||
EventID string
|
EventID string
|
||||||
|
|
@ -15,9 +17,29 @@ type Market struct {
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
Odds []json.RawMessage
|
Odds []json.RawMessage
|
||||||
|
|
||||||
// Optional breakdown (extracted from odds)
|
Header string
|
||||||
Header string // only if processing one odd at a time
|
|
||||||
Name string
|
Name string
|
||||||
Handicap string
|
Handicap string
|
||||||
OddsVal float64
|
OddsVal float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Odd struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
EventID string `json:"event_id"`
|
||||||
|
Fi string `json:"fi"`
|
||||||
|
RawEventID string `json:"raw_event_id"`
|
||||||
|
MarketType string `json:"market_type"`
|
||||||
|
MarketName string `json:"market_name"`
|
||||||
|
MarketCategory string `json:"market_category"`
|
||||||
|
MarketID string `json:"market_id"`
|
||||||
|
Header string `json:"header"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Handicap string `json:"handicap"`
|
||||||
|
OddsValue float64 `json:"odds_value"`
|
||||||
|
Section string `json:"section"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
RawOdds []RawMessage `json:"raw_odds"`
|
||||||
|
FetchedAt time.Time `json:"fetched_at"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
@ -15,14 +15,14 @@ import (
|
||||||
|
|
||||||
func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
|
func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
|
||||||
if len(m.Odds) == 0 {
|
if len(m.Odds) == 0 {
|
||||||
fmt.Printf("⚠️ Market has no odds: %s (%s)\n", m.MarketType, m.EventID)
|
fmt.Printf(" Market has no odds: %s (%s)\n", m.MarketType, m.EventID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, raw := range m.Odds {
|
for _, raw := range m.Odds {
|
||||||
var item map[string]interface{}
|
var item map[string]interface{}
|
||||||
if err := json.Unmarshal(raw, &item); err != nil {
|
if err := json.Unmarshal(raw, &item); err != nil {
|
||||||
fmt.Printf("❌ Invalid odd JSON for %s (%s): %v\n", m.MarketType, m.EventID, err)
|
fmt.Printf(" Invalid odd JSON for %s (%s): %v\n", m.MarketType, m.EventID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,7 +31,6 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
|
||||||
handicap := getString(item["handicap"])
|
handicap := getString(item["handicap"])
|
||||||
oddsVal := getFloat(item["odds"])
|
oddsVal := getFloat(item["odds"])
|
||||||
|
|
||||||
// Marshal the full list of odds for reference (if needed)
|
|
||||||
rawOddsBytes, _ := json.Marshal(m.Odds)
|
rawOddsBytes, _ := json.Marshal(m.Odds)
|
||||||
|
|
||||||
params := dbgen.InsertNonLiveOddParams{
|
params := dbgen.InsertNonLiveOddParams{
|
||||||
|
|
@ -53,12 +52,12 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
|
||||||
|
|
||||||
err := s.queries.InsertNonLiveOdd(ctx, params)
|
err := s.queries.InsertNonLiveOdd(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("❌ Failed to insert odd for market %s (%s): %v\n", m.MarketType, m.EventID, err)
|
fmt.Printf(" Failed to insert odd for market %s (%s): %v\n", m.MarketType, m.EventID, err)
|
||||||
_ = writeFailedMarketLog(m, err)
|
_ = writeFailedMarketLog(m, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("✅ Inserted odd: %s | type=%s | header=%s | name=%s\n", m.EventID, m.MarketType, header, name)
|
fmt.Printf("Inserted odd: %s | type=%s | header=%s | name=%s\n", m.EventID, m.MarketType, header, name)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -110,6 +109,43 @@ func getFloat(v interface{}) float64 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) GetUpcomingEventIDs(ctx context.Context) ([]string, error) {
|
func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) {
|
||||||
return s.queries.GetUpcomingEventIDs(ctx)
|
eventIDParam := pgtype.Text{String: eventID, Valid: eventID != ""}
|
||||||
|
|
||||||
|
odds, err := s.queries.GetPrematchOdds(ctx, eventIDParam)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
domainOdds := make([]domain.Odd, len(odds))
|
||||||
|
for i, odd := range odds {
|
||||||
|
domainOdds[i] = domain.Odd{
|
||||||
|
ID: int64(odd.ID), // Cast int32 to int64
|
||||||
|
EventID: odd.EventID.String, // Extract the String value
|
||||||
|
Fi: odd.Fi.String, // Extract the String value
|
||||||
|
RawEventID: odd.RawEventID.String, // Extract the String value
|
||||||
|
MarketType: odd.MarketType, // Direct assignment
|
||||||
|
MarketName: odd.MarketName.String, // Extract the String value
|
||||||
|
MarketCategory: odd.MarketCategory.String, // Extract the String value
|
||||||
|
MarketID: odd.MarketID.String, // Extract the String value
|
||||||
|
Header: odd.Header.String, // Extract the String value
|
||||||
|
Name: odd.Name.String, // Extract the String value
|
||||||
|
Handicap: odd.Handicap.String, // Extract the String value
|
||||||
|
OddsValue: odd.OddsValue.Float64, // Extract the Float64 value
|
||||||
|
Section: odd.Section, // Direct assignment
|
||||||
|
Category: odd.Category.String, // Extract the String value
|
||||||
|
RawOdds: func() []domain.RawMessage {
|
||||||
|
var rawOdds []domain.RawMessage
|
||||||
|
if err := json.Unmarshal(odd.RawOdds, &rawOdds); err != nil {
|
||||||
|
rawOdds = nil
|
||||||
|
}
|
||||||
|
return rawOdds
|
||||||
|
}(),
|
||||||
|
FetchedAt: odd.FetchedAt.Time, // Extract the Time value
|
||||||
|
Source: odd.Source.String, // Extract the String value
|
||||||
|
IsActive: odd.IsActive.Bool, // Extract the Bool value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return domainOdds, nil
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
package odds
|
package odds
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
FetchNonLiveOdds(ctx context.Context) error
|
FetchNonLiveOdds(ctx context.Context) error
|
||||||
|
GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,13 @@ func New(token string, store *repository.Store) *ServiceImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||||
fmt.Println("🔄 Starting FetchNonLiveOdds...")
|
fmt.Println("Starting FetchNonLiveOdds...")
|
||||||
|
|
||||||
sportID := 1
|
sportID := 1
|
||||||
upcomingURL := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s", sportID, s.token)
|
upcomingURL := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s", sportID, s.token)
|
||||||
resp, err := http.Get(upcomingURL)
|
resp, err := http.Get(upcomingURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("❌ Failed to fetch upcoming: %v\n", err)
|
fmt.Printf("Failed to fetch upcoming: %v\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
@ -42,23 +42,23 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||||
} `json:"results"`
|
} `json:"results"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(body, &upcomingData); err != nil || upcomingData.Success != 1 {
|
if err := json.Unmarshal(body, &upcomingData); err != nil || upcomingData.Success != 1 {
|
||||||
fmt.Printf("❌ Failed to decode upcoming response\nRaw: %s\n", string(body))
|
fmt.Printf("Failed to decode upcoming response\nRaw: %s\n", string(body))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ev := range upcomingData.Results {
|
for _, ev := range upcomingData.Results {
|
||||||
eventID := ev.ID
|
eventID := ev.ID
|
||||||
fmt.Printf("📦 Fetching prematch odds for event_id=%s\n", eventID)
|
fmt.Printf("Fetching prematch odds for event_id=%s\n", eventID)
|
||||||
prematchURL := fmt.Sprintf("https://api.b365api.com/v3/bet365/prematch?token=%s&FI=%s", s.token, eventID)
|
prematchURL := fmt.Sprintf("https://api.b365api.com/v3/bet365/prematch?token=%s&FI=%s", s.token, eventID)
|
||||||
oddsResp, err := http.Get(prematchURL)
|
oddsResp, err := http.Get(prematchURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("❌ Odds fetch failed for event_id=%s: %v\n", eventID, err)
|
fmt.Printf(" Odds fetch failed for event_id=%s: %v\n", eventID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
defer oddsResp.Body.Close()
|
defer oddsResp.Body.Close()
|
||||||
|
|
||||||
oddsBody, _ := io.ReadAll(oddsResp.Body)
|
oddsBody, _ := io.ReadAll(oddsResp.Body)
|
||||||
fmt.Printf("📩 Raw odds response for event_id=%s: %.300s...\n", eventID, string(oddsBody))
|
fmt.Printf(" Raw odds response for event_id=%s: %.300s...\n", eventID, string(oddsBody))
|
||||||
|
|
||||||
var oddsData struct {
|
var oddsData struct {
|
||||||
Success int `json:"success"`
|
Success int `json:"success"`
|
||||||
|
|
@ -69,7 +69,7 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||||
} `json:"results"`
|
} `json:"results"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(oddsBody, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 {
|
if err := json.Unmarshal(oddsBody, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 {
|
||||||
fmt.Printf("❌ Failed odds decode for event_id=%s\nRaw: %s\n", eventID, string(oddsBody))
|
fmt.Printf(" Failed odds decode for event_id=%s\nRaw: %s\n", eventID, string(oddsBody))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,23 +79,23 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||||
finalID = result.FI
|
finalID = result.FI
|
||||||
}
|
}
|
||||||
if finalID == "" {
|
if finalID == "" {
|
||||||
fmt.Println("⚠️ Skipping event with missing final ID.")
|
fmt.Println(" Skipping event with missing final ID.")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("🗂 Saving prematch odds for event_id=%s\n", finalID)
|
fmt.Printf("🗂 Saving prematch odds for event_id=%s\n", finalID)
|
||||||
s.storeSection(ctx, finalID, result.FI, "main", result.Main)
|
s.storeSection(ctx, finalID, result.FI, "main", result.Main)
|
||||||
fmt.Printf("✅ Finished storing prematch odds for event_id=%s\n", finalID)
|
fmt.Printf(" Finished storing prematch odds for event_id=%s\n", finalID)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("✅ All prematch odds fetched and stored.")
|
fmt.Println(" All prematch odds fetched and stored.")
|
||||||
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) {
|
||||||
fmt.Printf("📂 Processing section '%s' for event_id=%s\n", sectionName, eventID)
|
fmt.Printf(" Processing section '%s' for event_id=%s\n", sectionName, eventID)
|
||||||
if len(section.Sp) == 0 {
|
if len(section.Sp) == 0 {
|
||||||
fmt.Printf("⚠️ No odds in section '%s' for event_id=%s\n", sectionName, eventID)
|
fmt.Printf(" No odds in section '%s' for event_id=%s\n", sectionName, eventID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,9 +103,9 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
|
||||||
updatedAt := time.Unix(updatedAtUnix, 0)
|
updatedAt := time.Unix(updatedAtUnix, 0)
|
||||||
|
|
||||||
for marketType, market := range section.Sp {
|
for marketType, market := range section.Sp {
|
||||||
fmt.Printf("🔍 Processing market: %s (%s)\n", marketType, market.ID)
|
fmt.Printf(" Processing market: %s (%s)\n", marketType, market.ID)
|
||||||
if len(market.Odds) == 0 {
|
if len(market.Odds) == 0 {
|
||||||
fmt.Printf("⚠️ Empty odds for marketType=%s in section=%s\n", marketType, sectionName)
|
fmt.Printf(" Empty odds for marketType=%s in section=%s\n", marketType, sectionName)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,17 +120,16 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
|
||||||
Odds: market.Odds,
|
Odds: market.Odds,
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("📦 Saving market to DB: %s (%s)\n", marketType, market.ID)
|
fmt.Printf(" Saving market to DB: %s (%s)\n", marketType, market.ID)
|
||||||
err := s.store.SaveNonLiveMarket(ctx, marketRecord)
|
err := s.store.SaveNonLiveMarket(ctx, marketRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("❌ Save failed for market %s (%s): %v\n", marketType, eventID, err)
|
fmt.Printf(" Save failed for market %s (%s): %v\n", marketType, eventID, err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("✅ Successfully stored market: %s (%s)\n", marketType, eventID)
|
fmt.Printf(" Successfully stored market: %s (%s)\n", marketType, eventID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Odds structures
|
|
||||||
|
|
||||||
type OddsMarket struct {
|
type OddsMarket struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
|
@ -145,10 +144,22 @@ type OddsSection struct {
|
||||||
Sp map[string]OddsMarket `json:"sp"`
|
Sp map[string]OddsMarket `json:"sp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility
|
|
||||||
func getString(v interface{}) string {
|
func getString(v interface{}) string {
|
||||||
if str, ok := v.(string); ok {
|
if str, ok := v.(string); ok {
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServiceImpl) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) {
|
||||||
|
fmt.Printf("Retrieving prematch odds for event_id=%s\n", eventID)
|
||||||
|
|
||||||
|
odds, err := s.store.GetPrematchOdds(ctx, eventID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf(" Failed to retrieve odds for event_id=%s: %v\n", eventID, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(" Retrieved %d odds entries for event_id=%s\n", len(odds), eventID)
|
||||||
|
return odds, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ type Service struct {
|
||||||
otpStore OtpStore
|
otpStore OtpStore
|
||||||
smsGateway SmsGateway
|
smsGateway SmsGateway
|
||||||
emailGateway EmailGateway
|
emailGateway EmailGateway
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(
|
func NewService(
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,60 @@
|
||||||
package httpserver
|
package httpserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||||
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||||
"github.com/bytedance/sonic"
|
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
|
"github.com/bytedance/sonic"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
fiber *fiber.App
|
fiber *fiber.App
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
port int
|
port int
|
||||||
authSvc *authentication.Service
|
authSvc *authentication.Service
|
||||||
userSvc *user.Service
|
userSvc *user.Service
|
||||||
validator *customvalidator.CustomValidator
|
validator *customvalidator.CustomValidator
|
||||||
JwtConfig jwtutil.JwtConfig
|
JwtConfig jwtutil.JwtConfig
|
||||||
|
prematchSvc *odds.ServiceImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp(
|
func NewApp(
|
||||||
port int, validator *customvalidator.CustomValidator,
|
port int, validator *customvalidator.CustomValidator,
|
||||||
authSvc *authentication.Service,
|
authSvc *authentication.Service,
|
||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
JwtConfig jwtutil.JwtConfig,
|
JwtConfig jwtutil.JwtConfig,
|
||||||
userSvc *user.Service,
|
userSvc *user.Service,
|
||||||
|
prematchSvc *odds.ServiceImpl,
|
||||||
) *App {
|
) *App {
|
||||||
app := fiber.New(fiber.Config{
|
app := fiber.New(fiber.Config{
|
||||||
CaseSensitive: true,
|
CaseSensitive: true,
|
||||||
DisableHeaderNormalizing: true,
|
DisableHeaderNormalizing: true,
|
||||||
JSONEncoder: sonic.Marshal,
|
JSONEncoder: sonic.Marshal,
|
||||||
JSONDecoder: sonic.Unmarshal,
|
JSONDecoder: sonic.Unmarshal,
|
||||||
})
|
})
|
||||||
s := &App{
|
s := &App{
|
||||||
fiber: app,
|
fiber: app,
|
||||||
port: port,
|
port: port,
|
||||||
authSvc: authSvc,
|
authSvc: authSvc,
|
||||||
validator: validator,
|
validator: validator,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
JwtConfig: JwtConfig,
|
JwtConfig: JwtConfig,
|
||||||
userSvc: userSvc,
|
userSvc: userSvc,
|
||||||
}
|
prematchSvc: prematchSvc,
|
||||||
|
}
|
||||||
|
|
||||||
s.initAppRoutes()
|
s.initAppRoutes()
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Run() error {
|
func (a *App) Run() error {
|
||||||
return a.fiber.Listen(fmt.Sprintf(":%d", a.port))
|
return a.fiber.Listen(fmt.Sprintf(":%d", a.port))
|
||||||
}
|
}
|
||||||
|
|
@ -24,14 +24,14 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// 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: "*/5 * * * * *", // Every 5 seconds
|
spec: "*/5 * * * * *", // Every 5 seconds
|
||||||
task: func() {
|
task: func() {
|
||||||
|
|
|
||||||
37
internal/web_server/handlers/prematch.go
Normal file
37
internal/web_server/handlers/prematch.go
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPrematchOdds godoc
|
||||||
|
// @Summary Retrieve prematch odds for an event
|
||||||
|
// @Description Retrieve prematch odds for a specific event by event ID
|
||||||
|
// @Tags prematch
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param event_id path string true "Event ID"
|
||||||
|
// @Success 200 {array} domain.Odd
|
||||||
|
// @Failure 400 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /prematch/odds/{event_id} [get]
|
||||||
|
func GetPrematchOdds(logger *slog.Logger, prematchSvc *odds.ServiceImpl) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
eventID := c.Params("event_id")
|
||||||
|
if eventID == "" {
|
||||||
|
logger.Error("GetPrematchOdds failed: missing event_id")
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Missing event_id", nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
odds, err := prematchSvc.GetPrematchOdds(c.Context(), eventID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("GetPrematchOdds failed", "error", err)
|
||||||
|
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve odds", nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.WriteJSON(c, fiber.StatusOK, "Prematch odds retrieved successfully", odds, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,9 @@ func (a *App) initAppRoutes() {
|
||||||
a.fiber.Post("/user/sendRegisterCode", handlers.SendRegisterCode(a.logger, a.userSvc, a.validator))
|
a.fiber.Post("/user/sendRegisterCode", handlers.SendRegisterCode(a.logger, a.userSvc, a.validator))
|
||||||
a.fiber.Post("/user/checkPhoneEmailExist", handlers.CheckPhoneEmailExist(a.logger, a.userSvc, a.validator))
|
a.fiber.Post("/user/checkPhoneEmailExist", handlers.CheckPhoneEmailExist(a.logger, a.userSvc, a.validator))
|
||||||
a.fiber.Get("/user/profile", a.authMiddleware, handlers.UserProfile(a.logger, a.userSvc))
|
a.fiber.Get("/user/profile", a.authMiddleware, handlers.UserProfile(a.logger, a.userSvc))
|
||||||
|
|
||||||
|
a.fiber.Get("/prematch/odds/:event_id", handlers.GetPrematchOdds(a.logger, a.prematchSvc))
|
||||||
|
|
||||||
// Swagger
|
// Swagger
|
||||||
a.fiber.Get("/swagger/*", fiberSwagger.WrapHandler)
|
a.fiber.Get("/swagger/*", fiberSwagger.WrapHandler)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user