From b8d15695a4498c5b93d0cd1d1363cc4a77555af3 Mon Sep 17 00:00:00 2001 From: OneTap Technologies Date: Sun, 13 Apr 2025 10:55:16 +0300 Subject: [PATCH] adding fi --- db/migrations/000001_fortune.up.sql | 4 +- db/query/odds.sql | 91 +++++++------ docs/docs.go | 64 ++++++++- docs/swagger.json | 64 ++++++++- docs/swagger.yaml | 44 ++++++- gen/db/models.go | 2 - gen/db/odds.sql.go | 159 ++++++++++++++++++----- internal/domain/odds.go | 5 - internal/repository/odds.go | 64 +++++++-- internal/services/event/service.go | 2 +- internal/services/odds/service.go | 92 +++++-------- internal/web_server/cron.go | 52 ++++---- internal/web_server/handlers/prematch.go | 50 ++++++- internal/web_server/routes.go | 1 + 14 files changed, 496 insertions(+), 198 deletions(-) diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 602a6ae..5d3ee99 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -90,12 +90,10 @@ CREATE TABLE odds ( id SERIAL PRIMARY KEY, event_id TEXT, fi TEXT, - raw_event_id TEXT, market_type TEXT NOT NULL, market_name TEXT, market_category TEXT, market_id TEXT, - header TEXT, name TEXT, handicap TEXT, odds_value DOUBLE PRECISION, @@ -105,7 +103,7 @@ CREATE TABLE odds ( fetched_at TIMESTAMP DEFAULT now(), source TEXT DEFAULT 'b365api', is_active BOOLEAN DEFAULT true, - UNIQUE (event_id, market_id, header, name, handicap) + UNIQUE (event_id, market_id, name, handicap) ); diff --git a/db/query/odds.sql b/db/query/odds.sql index 5158ee0..912b1f9 100644 --- a/db/query/odds.sql +++ b/db/query/odds.sql @@ -2,12 +2,10 @@ INSERT INTO odds ( event_id, fi, - raw_event_id, market_type, market_name, market_category, market_id, - header, name, handicap, odds_value, @@ -19,48 +17,21 @@ INSERT INTO odds ( fetched_at ) VALUES ( $1, $2, $3, $4, $5, $6, $7, - $8, $9, $10, $11, $12, $13, $14, - true, 'b365api', now() + $8, $9, $10, $11, $12, $13, $14, $15 ) -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, raw_odds = EXCLUDED.raw_odds, market_type = EXCLUDED.market_type, market_name = EXCLUDED.market_name, market_category = EXCLUDED.market_category, - fetched_at = now(), - is_active = true, - source = 'b365api', - fi = EXCLUDED.fi, - raw_event_id = EXCLUDED.raw_event_id; - + fetched_at = EXCLUDED.fetched_at, + is_active = EXCLUDED.is_active, + source = EXCLUDED.source, + fi = EXCLUDED.fi; -- name: GetPrematchOdds :many 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, fi, market_type, @@ -78,10 +49,30 @@ SELECT is_active FROM odds 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 SELECT id, - event_id, raw_odds, fetched_at FROM odds @@ -89,4 +80,30 @@ WHERE raw_odds @> $1::jsonb AND is_active = true AND source = 'b365api' -LIMIT 1; \ No newline at end of file +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; \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index b815188..1c7970b 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -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}": { "get": { "description": "Retrieve prematch odds for a specific event by event ID", @@ -669,9 +728,6 @@ const docTemplate = `{ "handicap": { "type": "string" }, - "id": { - "type": "integer" - }, "is_active": { "type": "boolean" }, @@ -685,11 +741,9 @@ const docTemplate = `{ "type": "string" }, "market_type": { - "description": "RawEventID string ` + "`" + `json:\"raw_event_id\"` + "`" + `", "type": "string" }, "name": { - "description": "Header string ` + "`" + `json:\"header\"` + "`" + `", "type": "string" }, "odds_value": { diff --git a/docs/swagger.json b/docs/swagger.json index 061eb47..118ca95 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -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}": { "get": { "description": "Retrieve prematch odds for a specific event by event ID", @@ -661,9 +720,6 @@ "handicap": { "type": "string" }, - "id": { - "type": "integer" - }, "is_active": { "type": "boolean" }, @@ -677,11 +733,9 @@ "type": "string" }, "market_type": { - "description": "RawEventID string `json:\"raw_event_id\"`", "type": "string" }, "name": { - "description": "Header string `json:\"header\"`", "type": "string" }, "odds_value": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 55be753..b458113 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -11,8 +11,6 @@ definitions: type: string handicap: type: string - id: - type: integer is_active: type: boolean market_category: @@ -22,10 +20,8 @@ definitions: market_name: type: string market_type: - description: RawEventID string `json:"raw_event_id"` type: string name: - description: Header string `json:"header"` type: string odds_value: type: number @@ -498,6 +494,46 @@ paths: summary: Retrieve raw odds by ID tags: - 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: post: consumes: diff --git a/gen/db/models.go b/gen/db/models.go index 071fe68..66ca59c 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -36,12 +36,10 @@ type Odd struct { ID int32 EventID pgtype.Text Fi pgtype.Text - RawEventID pgtype.Text MarketType string MarketName pgtype.Text MarketCategory pgtype.Text MarketID pgtype.Text - Header pgtype.Text Name pgtype.Text Handicap pgtype.Text OddsValue pgtype.Float8 diff --git a/gen/db/odds.sql.go b/gen/db/odds.sql.go index 2cc76c6..66c4078 100644 --- a/gen/db/odds.sql.go +++ b/gen/db/odds.sql.go @@ -13,7 +13,6 @@ import ( const GetALLPrematchOdds = `-- name: GetALLPrematchOdds :many SELECT - id, event_id, fi, market_type, @@ -34,7 +33,6 @@ WHERE is_active = true AND source = 'b365api' ` type GetALLPrematchOddsRow struct { - ID int32 EventID pgtype.Text Fi pgtype.Text MarketType string @@ -62,7 +60,6 @@ func (q *Queries) GetALLPrematchOdds(ctx context.Context) ([]GetALLPrematchOddsR for rows.Next() { var i GetALLPrematchOddsRow if err := rows.Scan( - &i.ID, &i.EventID, &i.Fi, &i.MarketType, @@ -91,15 +88,12 @@ func (q *Queries) GetALLPrematchOdds(ctx context.Context) ([]GetALLPrematchOddsR const GetPrematchOdds = `-- name: GetPrematchOdds :many SELECT - id, event_id, fi, - raw_event_id, market_type, market_name, market_category, market_id, - header, name, handicap, odds_value, @@ -110,28 +104,130 @@ SELECT source, is_active 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) { - rows, err := q.db.Query(ctx, GetPrematchOdds, eventID) +type GetPrematchOddsRow 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) GetPrematchOdds(ctx context.Context) ([]GetPrematchOddsRow, error) { + rows, err := q.db.Query(ctx, GetPrematchOdds) if err != nil { return nil, err } defer rows.Close() - var items []Odd + var items []GetPrematchOddsRow 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( - &i.ID, &i.EventID, &i.Fi, - &i.RawEventID, &i.MarketType, &i.MarketName, &i.MarketCategory, &i.MarketID, - &i.Header, &i.Name, &i.Handicap, &i.OddsValue, @@ -155,7 +251,6 @@ func (q *Queries) GetPrematchOdds(ctx context.Context, eventID pgtype.Text) ([]O const GetRawOddsByID = `-- name: GetRawOddsByID :one SELECT id, - event_id, raw_odds, fetched_at FROM odds @@ -168,7 +263,6 @@ LIMIT 1 type GetRawOddsByIDRow struct { ID int32 - EventID pgtype.Text RawOdds []byte FetchedAt pgtype.Timestamp } @@ -176,12 +270,7 @@ type GetRawOddsByIDRow struct { func (q *Queries) GetRawOddsByID(ctx context.Context, dollar_1 []byte) (GetRawOddsByIDRow, error) { row := q.db.QueryRow(ctx, GetRawOddsByID, dollar_1) var i GetRawOddsByIDRow - err := row.Scan( - &i.ID, - &i.EventID, - &i.RawOdds, - &i.FetchedAt, - ) + err := row.Scan(&i.ID, &i.RawOdds, &i.FetchedAt) return i, err } @@ -189,12 +278,10 @@ const InsertNonLiveOdd = `-- name: InsertNonLiveOdd :exec INSERT INTO odds ( event_id, fi, - raw_event_id, market_type, market_name, market_category, market_id, - header, name, handicap, odds_value, @@ -206,55 +293,55 @@ INSERT INTO odds ( fetched_at ) VALUES ( $1, $2, $3, $4, $5, $6, $7, - $8, $9, $10, $11, $12, $13, $14, - true, 'b365api', now() + $8, $9, $10, $11, $12, $13, $14, $15 ) -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, raw_odds = EXCLUDED.raw_odds, market_type = EXCLUDED.market_type, market_name = EXCLUDED.market_name, market_category = EXCLUDED.market_category, - fetched_at = now(), - is_active = true, - source = 'b365api', - fi = EXCLUDED.fi, - raw_event_id = EXCLUDED.raw_event_id + fetched_at = EXCLUDED.fetched_at, + is_active = EXCLUDED.is_active, + source = EXCLUDED.source, + fi = EXCLUDED.fi ` type InsertNonLiveOddParams struct { EventID pgtype.Text Fi pgtype.Text - RawEventID pgtype.Text MarketType string MarketName pgtype.Text MarketCategory pgtype.Text MarketID pgtype.Text - Header pgtype.Text Name pgtype.Text Handicap pgtype.Text OddsValue pgtype.Float8 Section string Category pgtype.Text RawOdds []byte + IsActive pgtype.Bool + Source pgtype.Text + FetchedAt pgtype.Timestamp } func (q *Queries) InsertNonLiveOdd(ctx context.Context, arg InsertNonLiveOddParams) error { _, err := q.db.Exec(ctx, InsertNonLiveOdd, arg.EventID, arg.Fi, - arg.RawEventID, arg.MarketType, arg.MarketName, arg.MarketCategory, arg.MarketID, - arg.Header, arg.Name, arg.Handicap, arg.OddsValue, arg.Section, arg.Category, arg.RawOdds, + arg.IsActive, + arg.Source, + arg.FetchedAt, ) return err } diff --git a/internal/domain/odds.go b/internal/domain/odds.go index 1d47b85..df2de7e 100644 --- a/internal/domain/odds.go +++ b/internal/domain/odds.go @@ -16,23 +16,18 @@ type Market struct { MarketID string UpdatedAt time.Time Odds []json.RawMessage - - Header string Name string Handicap string 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"` diff --git a/internal/repository/odds.go b/internal/repository/odds.go index ae510e7..a8573a3 100644 --- a/internal/repository/odds.go +++ b/internal/repository/odds.go @@ -23,7 +23,6 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error { continue } - header := getString(item["header"]) name := getString(item["name"]) handicap := getString(item["handicap"]) oddsVal := getFloat(item["odds"]) @@ -33,23 +32,24 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error { params := dbgen.InsertNonLiveOddParams{ EventID: pgtype.Text{String: m.EventID, Valid: m.EventID != ""}, Fi: pgtype.Text{String: m.FI, Valid: m.FI != ""}, - RawEventID: pgtype.Text{String: m.EventID, Valid: m.EventID != ""}, MarketType: m.MarketType, MarketName: pgtype.Text{String: m.MarketName, Valid: m.MarketName != ""}, MarketCategory: pgtype.Text{String: m.MarketCategory, Valid: m.MarketCategory != ""}, MarketID: pgtype.Text{String: m.MarketID, Valid: m.MarketID != ""}, - Header: pgtype.Text{String: header, Valid: header != ""}, Name: pgtype.Text{String: name, Valid: name != ""}, Handicap: pgtype.Text{String: handicap, Valid: handicap != ""}, OddsValue: pgtype.Float8{Float64: oddsVal, Valid: oddsVal != 0}, Section: m.MarketCategory, - Category: pgtype.Text{Valid: false}, + Category: pgtype.Text{Valid: false}, 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) if err != nil { - _ = writeFailedMarketLog(m, err) + _ = writeFailedMarketLog(m, err) continue } } @@ -103,9 +103,7 @@ func getFloat(v interface{}) float64 { } 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, eventIDParam) + odds, err := s.queries.GetPrematchOdds(ctx) if err != nil { return nil, err } @@ -113,15 +111,12 @@ func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.O domainOdds := make([]domain.Odd, len(odds)) for i, odd := range odds { domainOdds[i] = domain.Odd{ - ID: int64(odd.ID), EventID: odd.EventID.String, Fi: odd.Fi.String, - // RawEventID: odd.RawEventID.String, MarketType: odd.MarketType, MarketName: odd.MarketName.String, MarketCategory: odd.MarketCategory.String, MarketID: odd.MarketID.String, - // Header: odd.Header.String, Name: odd.Name.String, Handicap: odd.Handicap.String, OddsValue: odd.OddsValue.Float64, @@ -152,7 +147,7 @@ func (s *Store) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) { domainOdds := make([]domain.Odd, len(rows)) for i, row := range rows { domainOdds[i] = domain.Odd{ - ID: int64(row.ID), + // ID: int64(row.ID), EventID: row.EventID.String, Fi: row.Fi.String, MarketType: row.MarketType, @@ -195,7 +190,6 @@ func (s *Store) GetRawOddsByID(ctx context.Context, rawOddsID string) (domain.Ra return domain.RawOddsByID{ ID: int64(odd.ID), - EventID: odd.EventID.String, RawOdds: func() []domain.RawMessage { converted := make([]domain.RawMessage, len(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, }, 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 } \ No newline at end of file diff --git a/internal/services/event/service.go b/internal/services/event/service.go index 350c8cf..41a2d1d 100644 --- a/internal/services/event/service.go +++ b/internal/services/event/service.go @@ -97,7 +97,7 @@ func (s *service) FetchLiveEvents(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 { url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s", sportID, s.token) resp, err := http.Get(url) diff --git a/internal/services/odds/service.go b/internal/services/odds/service.go index 467117b..9b31a94 100644 --- a/internal/services/odds/service.go +++ b/internal/services/odds/service.go @@ -24,76 +24,51 @@ func New(token string, store *repository.Store) *ServiceImpl { func (s *ServiceImpl) FetchNonLiveOdds(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, - } - for _, sportID := range sportIDs { - upcomingURL := "https://api.b365api.com/v1/bet365/upcoming?sport_id=" + strconv.Itoa(sportID) + "&token=" + s.token - log.Printf("Fetching upcoming odds for sport ID: %d from URL: %s", sportID, upcomingURL) - resp, err := http.Get(upcomingURL) + eventIDs, err := s.store.GetAllUpcomingEvents(ctx) + if err != nil { + log.Printf("❌ Failed to fetch upcoming event IDs: %v", err) + return err + } + + for _, event := range eventIDs { + eventID := event.ID + 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 { - 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 } defer resp.Body.Close() - + body, _ := io.ReadAll(resp.Body) - var upcomingData struct { + var oddsData struct { Success int `json:"success"` Results []struct { - ID string `json:"id"` + EventID string `json:"event_id"` + FI string `json:"FI"` + Main OddsSection `json:"main"` } `json:"results"` } - if err := json.Unmarshal(body, &upcomingData); err != nil || upcomingData.Success != 1 { - log.Printf("Failed to parse upcoming odds for sport ID: %d, error: %v", sportID, err) + if err := json.Unmarshal(body, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 { + log.Printf("❌ Invalid prematch data for event %s", eventID) continue } - - log.Printf("Successfully fetched upcoming odds for sport ID: %d", sportID) - - for _, ev := range upcomingData.Results { - eventID := ev.ID - 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) + + result := oddsData.Results[0] + finalID := result.EventID + if finalID == "" { + finalID = result.FI } + 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) { @@ -153,4 +128,7 @@ func (s *ServiceImpl) GetRawOddsByID(ctx context.Context, rawOddsID string) ([]d return nil, err } return []domain.RawOddsByID{rawOdds}, nil -} \ No newline at end of file +} +func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset int32) ([]domain.Odd, error) { + return s.store.GetPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset) +} diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index afbccc9..ddf9677 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -1,7 +1,7 @@ package httpserver import ( - "context" + // "context" "log" eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" @@ -17,33 +17,35 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S task func() }{ - { - spec: "0 0 * * * *", // Every hour at minute 0 and second 0 - task: func() { - if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { - log.Printf("FetchUpcomingEvents error: %v", err) - } - }, - }, + // { + // spec: "*/30 * * * * *", // Every 30 seconds + // task: func() { + // if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { + // log.Printf("FetchUpcomingEvents error: %v", err) + // } + // }, + // }, + - { - spec: "*/5 * * * * *", // Every 5 seconds - task: func() { - if err := eventService.FetchLiveEvents(context.Background()); err != nil { - log.Printf("FetchLiveEvents error: %v", err) - } - }, - }, + // { + // spec: "*/5 * * * * *", // Every 5 seconds + // task: func() { + // if err := eventService.FetchLiveEvents(context.Background()); err != nil { + // log.Printf("FetchLiveEvents error: %v", err) + // } + // }, + // }, - { - spec: "0 0 * * * *", // Every hour at minute 0 and second 0 - task: func() { - if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { - log.Printf("FetchNonLiveOdds error: %v", err) - } - }, - }, + // { + // spec: "*/30 * * * * *", // Every 30 seconds + // task: func() { + // if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { + // log.Printf("FetchNonLiveOdds error: %v", err) + // } + // }, + // }, + } diff --git a/internal/web_server/handlers/prematch.go b/internal/web_server/handlers/prematch.go index 4fea8a7..17e4de4 100644 --- a/internal/web_server/handlers/prematch.go +++ b/internal/web_server/handlers/prematch.go @@ -1,11 +1,13 @@ package handlers import ( - "github.com/gofiber/fiber/v2" - "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" - "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" - "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" - "log/slog" + "log/slog" + "strconv" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" + "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" + "github.com/gofiber/fiber/v2" ) // 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) } } +// @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) + } +} + diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 5841fbb..f18aa50 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -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", handlers.GetAllUpcomingEvents(a.logger, a.eventSvc)) + a.fiber.Get("/prematch/odds/upcoming/:upcoming_id", handlers.GetPrematchOddsByUpcomingID(a.logger, a.prematchSvc)) // Swagger a.fiber.Get("/swagger/*", fiberSwagger.WrapHandler) }