diff --git a/cmd/main.go b/cmd/main.go index 584e821..31a728e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -38,13 +38,13 @@ import ( func main() { cfg, err := config.NewConfig() if err != nil { - slog.Error("❌ Config error:", "err", err) + slog.Error(" Config error:", "err", err) os.Exit(1) } db, _, err := repository.OpenDB(cfg.DbUrl) if err != nil { - fmt.Println("❌ Database error:", err) + fmt.Println(" Database error:", err) os.Exit(1) } diff --git a/db/query/odds.sql b/db/query/odds.sql index 07e1c99..5158ee0 100644 --- a/db/query/odds.sql +++ b/db/query/odds.sql @@ -56,4 +56,37 @@ SELECT source, is_active FROM odds -WHERE event_id = $1 AND is_active = true AND source = 'b365api'; \ No newline at end of file +WHERE event_id = $1 AND is_active = true AND source = 'b365api'; + +-- name: GetALLPrematchOdds :many +SELECT + id, + 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 +WHERE + raw_odds @> $1::jsonb AND + is_active = true AND + source = 'b365api' +LIMIT 1; \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index 6448575..6143af4 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -180,6 +180,82 @@ const docTemplate = `{ } } }, + "/prematch/odds": { + "get": { + "description": "Retrieve all prematch odds from the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve all prematch odds", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Odd" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/prematch/odds/raw/{raw_odds_id}": { + "get": { + "description": "Retrieve raw odds by raw odds ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve raw odds by ID", + "parameters": [ + { + "type": "string", + "description": "Raw Odds ID", + "name": "raw_odds_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.RawOddsByID" + } + }, + "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", @@ -517,9 +593,6 @@ const docTemplate = `{ "handicap": { "type": "string" }, - "header": { - "type": "string" - }, "id": { "type": "integer" }, @@ -536,17 +609,16 @@ 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": { "type": "number" }, - "raw_event_id": { - "type": "string" - }, "raw_odds": { "type": "array", "items": {} @@ -559,6 +631,24 @@ const docTemplate = `{ } } }, + "domain.RawOddsByID": { + "type": "object", + "properties": { + "event_id": { + "type": "string" + }, + "fetched_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "raw_odds": { + "type": "array", + "items": {} + } + } + }, "domain.Role": { "type": "string", "enum": [ diff --git a/docs/swagger.json b/docs/swagger.json index 414256f..8692c31 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -172,6 +172,82 @@ } } }, + "/prematch/odds": { + "get": { + "description": "Retrieve all prematch odds from the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve all prematch odds", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Odd" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/prematch/odds/raw/{raw_odds_id}": { + "get": { + "description": "Retrieve raw odds by raw odds ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "prematch" + ], + "summary": "Retrieve raw odds by ID", + "parameters": [ + { + "type": "string", + "description": "Raw Odds ID", + "name": "raw_odds_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.RawOddsByID" + } + }, + "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", @@ -509,9 +585,6 @@ "handicap": { "type": "string" }, - "header": { - "type": "string" - }, "id": { "type": "integer" }, @@ -528,17 +601,16 @@ "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" }, - "raw_event_id": { - "type": "string" - }, "raw_odds": { "type": "array", "items": {} @@ -551,6 +623,24 @@ } } }, + "domain.RawOddsByID": { + "type": "object", + "properties": { + "event_id": { + "type": "string" + }, + "fetched_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "raw_odds": { + "type": "array", + "items": {} + } + } + }, "domain.Role": { "type": "string", "enum": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 00333d0..7bfdfa4 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -11,8 +11,6 @@ definitions: type: string handicap: type: string - header: - type: string id: type: integer is_active: @@ -24,13 +22,13 @@ 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 - raw_event_id: - type: string raw_odds: items: {} type: array @@ -39,6 +37,18 @@ definitions: source: type: string type: object + domain.RawOddsByID: + properties: + event_id: + type: string + fetched_at: + type: string + id: + type: integer + raw_odds: + items: {} + type: array + type: object domain.Role: enum: - super_admin @@ -315,6 +325,27 @@ paths: summary: Refresh token tags: - auth + /prematch/odds: + get: + consumes: + - application/json + description: Retrieve all prematch odds from the database + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/domain.Odd' + type: array + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Retrieve all prematch odds + tags: + - prematch /prematch/odds/{event_id}: get: consumes: @@ -346,6 +377,35 @@ paths: summary: Retrieve prematch odds for an event tags: - prematch + /prematch/odds/raw/{raw_odds_id}: + get: + consumes: + - application/json + description: Retrieve raw odds by raw odds ID + parameters: + - description: Raw Odds ID + in: path + name: raw_odds_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.RawOddsByID' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Retrieve raw odds by ID + tags: + - prematch /user/checkPhoneEmailExist: post: consumes: diff --git a/gen/db/odds.sql.go b/gen/db/odds.sql.go index 003be80..2cc76c6 100644 --- a/gen/db/odds.sql.go +++ b/gen/db/odds.sql.go @@ -11,6 +11,84 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +const GetALLPrematchOdds = `-- name: GetALLPrematchOdds :many +SELECT + id, + 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' +` + +type GetALLPrematchOddsRow struct { + ID int32 + 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) GetALLPrematchOdds(ctx context.Context) ([]GetALLPrematchOddsRow, error) { + rows, err := q.db.Query(ctx, GetALLPrematchOdds) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetALLPrematchOddsRow + for rows.Next() { + var i GetALLPrematchOddsRow + if err := rows.Scan( + &i.ID, + &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 GetPrematchOdds = `-- name: GetPrematchOdds :many SELECT id, @@ -74,6 +152,39 @@ func (q *Queries) GetPrematchOdds(ctx context.Context, eventID pgtype.Text) ([]O return items, nil } +const GetRawOddsByID = `-- name: GetRawOddsByID :one +SELECT + id, + event_id, + raw_odds, + fetched_at +FROM odds +WHERE + raw_odds @> $1::jsonb AND + is_active = true AND + source = 'b365api' +LIMIT 1 +` + +type GetRawOddsByIDRow struct { + ID int32 + EventID pgtype.Text + RawOdds []byte + FetchedAt pgtype.Timestamp +} + +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, + ) + return i, err +} + const InsertNonLiveOdd = `-- name: InsertNonLiveOdd :exec INSERT INTO odds ( event_id, diff --git a/internal/domain/odds.go b/internal/domain/odds.go index 418b33d..1d47b85 100644 --- a/internal/domain/odds.go +++ b/internal/domain/odds.go @@ -5,7 +5,7 @@ import ( "time" ) -type RawMessage interface{} // Change from json.RawMessage to interface{} +type RawMessage interface{} type Market struct { EventID string @@ -27,12 +27,12 @@ type Odd struct { ID int64 `json:"id"` EventID string `json:"event_id"` Fi string `json:"fi"` - RawEventID string `json:"raw_event_id"` + // 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"` + // Header string `json:"header"` Name string `json:"name"` Handicap string `json:"handicap"` OddsValue float64 `json:"odds_value"` @@ -42,4 +42,10 @@ type Odd struct { FetchedAt time.Time `json:"fetched_at"` Source string `json:"source"` IsActive bool `json:"is_active"` +} +type RawOddsByID struct { + ID int64 `json:"id"` + EventID string `json:"event_id"` + RawOdds []RawMessage `json:"raw_odds"` + FetchedAt time.Time `json:"fetched_at"` } \ No newline at end of file diff --git a/internal/repository/odds.go b/internal/repository/odds.go index a21c729..ae510e7 100644 --- a/internal/repository/odds.go +++ b/internal/repository/odds.go @@ -1,112 +1,105 @@ package repository import ( - "context" - "encoding/json" - "fmt" - "os" - "strconv" - "time" + "context" + "encoding/json" + "os" + "strconv" + "time" - dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" - "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" - "github.com/jackc/pgx/v5/pgtype" + dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/jackc/pgx/v5/pgtype" ) func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error { - if len(m.Odds) == 0 { - fmt.Printf(" Market has no odds: %s (%s)\n", m.MarketType, m.EventID) - return nil - } + if len(m.Odds) == 0 { + return nil + } - for _, raw := range m.Odds { - var item map[string]interface{} - if err := json.Unmarshal(raw, &item); err != nil { - fmt.Printf(" Invalid odd JSON for %s (%s): %v\n", m.MarketType, m.EventID, err) - continue - } + for _, raw := range m.Odds { + var item map[string]interface{} + if err := json.Unmarshal(raw, &item); err != nil { + continue + } - header := getString(item["header"]) - name := getString(item["name"]) - handicap := getString(item["handicap"]) - oddsVal := getFloat(item["odds"]) + header := getString(item["header"]) + name := getString(item["name"]) + handicap := getString(item["handicap"]) + oddsVal := getFloat(item["odds"]) - rawOddsBytes, _ := json.Marshal(m.Odds) + rawOddsBytes, _ := json.Marshal(m.Odds) - 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}, - RawOdds: rawOddsBytes, - } + 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}, + RawOdds: rawOddsBytes, + } - err := s.queries.InsertNonLiveOdd(ctx, params) - if err != nil { - fmt.Printf(" Failed to insert odd for market %s (%s): %v\n", m.MarketType, m.EventID, err) - _ = writeFailedMarketLog(m, err) - continue - } - - fmt.Printf("Inserted odd: %s | type=%s | header=%s | name=%s\n", m.EventID, m.MarketType, header, name) - } - return nil + err := s.queries.InsertNonLiveOdd(ctx, params) + if err != nil { + _ = writeFailedMarketLog(m, err) + continue + } + } + return nil } - func writeFailedMarketLog(m domain.Market, err error) error { - logDir := "logs" - logFile := logDir + "/failed_markets.log" + logDir := "logs" + logFile := logDir + "/failed_markets.log" - if mkErr := os.MkdirAll(logDir, 0755); mkErr != nil { - return mkErr - } + if mkErr := os.MkdirAll(logDir, 0755); mkErr != nil { + return mkErr + } - f, fileErr := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if fileErr != nil { - return fileErr - } - defer f.Close() + f, fileErr := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if fileErr != nil { + return fileErr + } + defer f.Close() - entry := struct { - Time string `json:"time"` - Error string `json:"error"` - Record domain.Market `json:"record"` - }{ - Time: time.Now().Format(time.RFC3339), - Error: err.Error(), - Record: m, - } + entry := struct { + Time string `json:"time"` + Error string `json:"error"` + Record domain.Market `json:"record"` + }{ + Time: time.Now().Format(time.RFC3339), + Error: err.Error(), + Record: m, + } - jsonData, _ := json.MarshalIndent(entry, "", " ") - _, writeErr := f.WriteString(string(jsonData) + "\n\n") - return writeErr + jsonData, _ := json.MarshalIndent(entry, "", " ") + _, writeErr := f.WriteString(string(jsonData) + "\n\n") + return writeErr } func getString(v interface{}) string { - if s, ok := v.(string); ok { - return s - } - return "" + if s, ok := v.(string); ok { + return s + } + return "" } func getFloat(v interface{}) float64 { - if s, ok := v.(string); ok { - f, err := strconv.ParseFloat(s, 64) - if err == nil { - return f - } - } - return 0 + if s, ok := v.(string); ok { + f, err := strconv.ParseFloat(s, 64) + if err == nil { + return f + } + } + return 0 } func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) { @@ -120,32 +113,96 @@ 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), // 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 + 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, + Section: odd.Section, + Category: odd.Category.String, + 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, + Source: odd.Source.String, + IsActive: odd.IsActive.Bool, } } return domainOdds, nil +} + +func (s *Store) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) { + rows, err := s.queries.GetALLPrematchOdds(ctx) + if err != nil { + return nil, err + } + + domainOdds := make([]domain.Odd, len(rows)) + for i, row := range rows { + domainOdds[i] = domain.Odd{ + ID: int64(row.ID), + EventID: row.EventID.String, + Fi: row.Fi.String, + MarketType: row.MarketType, + MarketName: row.MarketName.String, + MarketCategory: row.MarketCategory.String, + MarketID: row.MarketID.String, + Name: row.Name.String, + Handicap: row.Handicap.String, + OddsValue: row.OddsValue.Float64, + Section: row.Section, + Category: row.Category.String, + RawOdds: func() []domain.RawMessage { + var rawOdds []domain.RawMessage + if err := json.Unmarshal(row.RawOdds, &rawOdds); err != nil { + rawOdds = nil + } + return rawOdds + }(), + FetchedAt: row.FetchedAt.Time, + Source: row.Source.String, + IsActive: row.IsActive.Bool, + } + } + + return domainOdds, nil +} + +func (s *Store) GetRawOddsByID(ctx context.Context, rawOddsID string) (domain.RawOddsByID, error) { + jsonFilter := `[{"id":"` + rawOddsID + `"}]` + + odd, err := s.queries.GetRawOddsByID(ctx, []byte(jsonFilter)) + if err != nil { + return domain.RawOddsByID{}, err + } + + var rawOdds []json.RawMessage + if err := json.Unmarshal(odd.RawOdds, &rawOdds); err != nil { + return domain.RawOddsByID{}, err + } + + 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 { + converted[i] = domain.RawMessage(r) + } + return converted + }(), + FetchedAt: odd.FetchedAt.Time, + }, nil } \ No newline at end of file diff --git a/internal/services/event/service.go b/internal/services/event/service.go index 3d54d7a..24207ca 100644 --- a/internal/services/event/service.go +++ b/internal/services/event/service.go @@ -170,4 +170,4 @@ func getInt(v interface{}) int { return int(f) } return 0 -} +} \ No newline at end of file diff --git a/internal/services/odds/port.go b/internal/services/odds/port.go index d50a8af..8805a66 100644 --- a/internal/services/odds/port.go +++ b/internal/services/odds/port.go @@ -9,6 +9,8 @@ import ( type Service interface { FetchNonLiveOdds(ctx context.Context) error GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) + GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) + GetRawOddsByID(ctx context.Context, rawOddsID string) ([]domain.RawOddsByID, error) } diff --git a/internal/services/odds/service.go b/internal/services/odds/service.go index 2f3245d..09a32fa 100644 --- a/internal/services/odds/service.go +++ b/internal/services/odds/service.go @@ -3,7 +3,6 @@ package odds import ( "context" "encoding/json" - "fmt" "io" "net/http" "strconv" @@ -23,79 +22,70 @@ func New(token string, store *repository.Store) *ServiceImpl { } func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error { - fmt.Println("Starting FetchNonLiveOdds...") - - sportID := 1 - upcomingURL := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s", sportID, s.token) - resp, err := http.Get(upcomingURL) - if err != nil { - fmt.Printf("Failed to fetch upcoming: %v\n", err) - return err + 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, } - defer resp.Body.Close() - - body, _ := io.ReadAll(resp.Body) - var upcomingData struct { - Success int `json:"success"` - Results []struct { - ID string `json:"id"` - } `json:"results"` - } - if err := json.Unmarshal(body, &upcomingData); err != nil || upcomingData.Success != 1 { - fmt.Printf("Failed to decode upcoming response\nRaw: %s\n", string(body)) - return err - } - - for _, ev := range upcomingData.Results { - eventID := ev.ID - 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) - oddsResp, err := http.Get(prematchURL) + for _, sportID := range sportIDs { + upcomingURL := "https://api.b365api.com/v1/bet365/upcoming?sport_id=" + strconv.Itoa(sportID) + "&token=" + s.token + resp, err := http.Get(upcomingURL) if err != nil { - fmt.Printf(" Odds fetch failed for event_id=%s: %v\n", eventID, err) continue } - defer oddsResp.Body.Close() + defer resp.Body.Close() - oddsBody, _ := io.ReadAll(oddsResp.Body) - fmt.Printf(" Raw odds response for event_id=%s: %.300s...\n", eventID, string(oddsBody)) - - var oddsData struct { + body, _ := io.ReadAll(resp.Body) + var upcomingData struct { Success int `json:"success"` Results []struct { - EventID string `json:"event_id"` - FI string `json:"FI"` - Main OddsSection `json:"main"` + ID string `json:"id"` } `json:"results"` } - 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)) + if err := json.Unmarshal(body, &upcomingData); err != nil || upcomingData.Success != 1 { continue } - result := oddsData.Results[0] - finalID := result.EventID - if finalID == "" { - finalID = result.FI - } - if finalID == "" { - fmt.Println(" Skipping event with missing final ID.") - continue - } + for _, ev := range upcomingData.Results { + eventID := ev.ID + prematchURL := "https://api.b365api.com/v3/bet365/prematch?token=" + s.token + "&FI=" + eventID + oddsResp, err := http.Get(prematchURL) + if err != nil { + continue + } + defer oddsResp.Body.Close() - fmt.Printf("🗂 Saving prematch odds for event_id=%s\n", finalID) - s.storeSection(ctx, finalID, result.FI, "main", result.Main) - fmt.Printf(" Finished storing prematch odds for event_id=%s\n", finalID) + 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 { + continue + } + + result := oddsData.Results[0] + finalID := result.EventID + if finalID == "" { + finalID = result.FI + } + if finalID == "" { + continue + } + + s.storeSection(ctx, finalID, result.FI, "main", result.Main) + } } - fmt.Println(" All prematch odds fetched and stored.") return nil } 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) if len(section.Sp) == 0 { - fmt.Printf(" No odds in section '%s' for event_id=%s\n", sectionName, eventID) return } @@ -103,9 +93,7 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName updatedAt := time.Unix(updatedAtUnix, 0) for marketType, market := range section.Sp { - fmt.Printf(" Processing market: %s (%s)\n", marketType, market.ID) if len(market.Odds) == 0 { - fmt.Printf(" Empty odds for marketType=%s in section=%s\n", marketType, sectionName) continue } @@ -120,23 +108,16 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName Odds: market.Odds, } - fmt.Printf(" Saving market to DB: %s (%s)\n", marketType, market.ID) - err := s.store.SaveNonLiveMarket(ctx, marketRecord) - if err != nil { - fmt.Printf(" Save failed for market %s (%s): %v\n", marketType, eventID, err) - } else { - fmt.Printf(" Successfully stored market: %s (%s)\n", marketType, eventID) - } + _ = s.store.SaveNonLiveMarket(ctx, marketRecord) } } - type OddsMarket struct { - ID string `json:"id"` - Name string `json:"name"` - Odds []json.RawMessage `json:"odds"` - Header string `json:"header,omitempty"` - Handicap string `json:"handicap,omitempty"` + ID string `json:"id"` + Name string `json:"name"` + Odds []json.RawMessage `json:"odds"` + Header string `json:"header,omitempty"` + Handicap string `json:"handicap,omitempty"` } type OddsSection struct { @@ -144,22 +125,20 @@ type OddsSection struct { Sp map[string]OddsMarket `json:"sp"` } -func getString(v interface{}) string { - if str, ok := v.(string); ok { - return str - } - return "" -} + func (s *ServiceImpl) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) { - fmt.Printf("Retrieving prematch odds for event_id=%s\n", eventID) + return s.store.GetPrematchOdds(ctx, eventID) +} - odds, err := s.store.GetPrematchOdds(ctx, eventID) +func (s *ServiceImpl) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) { + return s.store.GetALLPrematchOdds(ctx) +} + +func (s *ServiceImpl) GetRawOddsByID(ctx context.Context, rawOddsID string) ([]domain.RawOddsByID, error) { + rawOdds, err := s.store.GetRawOddsByID(ctx, rawOddsID) 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 -} + return []domain.RawOddsByID{rawOdds}, nil +} \ No newline at end of file diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index 68ddcf2..71cb9b4 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -33,7 +33,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S }, }, { - spec: "*/5 * * * * *", // Every 5 seconds + spec: "0 */5 * * * *", // Every 5 minutes task: func() { if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { log.Printf(" FetchNonLiveOdds error: %v", err) @@ -41,6 +41,8 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S }, }, + + } for _, job := range schedule { diff --git a/internal/web_server/handlers/prematch.go b/internal/web_server/handlers/prematch.go index cc48b10..5e08966 100644 --- a/internal/web_server/handlers/prematch.go +++ b/internal/web_server/handlers/prematch.go @@ -22,16 +22,59 @@ func GetPrematchOdds(logger *slog.Logger, prematchSvc *odds.ServiceImpl) fiber.H 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) } +} +//GetALLPrematchOdds +// @Summary Retrieve all prematch odds +// @Description Retrieve all prematch odds from the database +// @Tags prematch +// @Accept json +// @Produce json +// @Success 200 {array} domain.Odd +// @Failure 500 {object} response.APIResponse +// @Router /prematch/odds [get] +func GetALLPrematchOdds(logger *slog.Logger, prematchSvc *odds.ServiceImpl) fiber.Handler { + return func(c *fiber.Ctx) error { + odds, err := prematchSvc.GetALLPrematchOdds(c.Context()) + if err != nil { + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve all prematch odds", nil, nil) + } + + return response.WriteJSON(c, fiber.StatusOK, "All prematch odds retrieved successfully", odds, nil) + } +} +// GetRawOddsByID +// @Summary Retrieve raw odds by ID +// @Description Retrieve raw odds by raw odds ID +// @Tags prematch +// @Accept json +// @Produce json +// @Param raw_odds_id path string true "Raw Odds ID" +// @Success 200 {object} domain.RawOddsByID +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /prematch/odds/raw/{raw_odds_id} [get] +func GetRawOddsByID(logger *slog.Logger, prematchSvc *odds.ServiceImpl) fiber.Handler { + return func(c *fiber.Ctx) error { + rawOddsID := c.Params("raw_odds_id") + if rawOddsID == "" { + return response.WriteJSON(c, fiber.StatusBadRequest, "Missing raw_odds_id", nil, nil) + } + + rawOdds, err := prematchSvc.GetRawOddsByID(c.Context(), rawOddsID) + if err != nil { + return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve raw odds", nil, nil) + } + + return response.WriteJSON(c, fiber.StatusOK, "Raw odds retrieved successfully", rawOdds, nil) + } } \ No newline at end of file diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 22ebb93..6bd82f5 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -28,7 +28,8 @@ func (a *App) initAppRoutes() { 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)) - + a.fiber.Get("/prematch/odds", handlers.GetALLPrematchOdds(a.logger, a.prematchSvc)) + a.fiber.Get("/prematch/odds/raw/:raw_odds_id", handlers.GetRawOddsByID(a.logger, a.prematchSvc)) // Swagger a.fiber.Get("/swagger/*", fiberSwagger.WrapHandler) }