diff --git a/db/query/odds.sql b/db/query/odds.sql index 912b1f9..a508511 100644 --- a/db/query/odds.sql +++ b/db/query/odds.sql @@ -16,19 +16,24 @@ INSERT INTO odds ( source, fetched_at ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 ) -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, +ON CONFLICT (event_id, market_id) 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 = EXCLUDED.fetched_at, - is_active = EXCLUDED.is_active, - source = EXCLUDED.source, - fi = EXCLUDED.fi; + name = EXCLUDED.name, + handicap = EXCLUDED.handicap, + fetched_at = EXCLUDED.fetched_at, + is_active = EXCLUDED.is_active, + source = EXCLUDED.source, + fi = EXCLUDED.fi; + + + -- name: GetPrematchOdds :many SELECT @@ -70,17 +75,17 @@ SELECT FROM odds WHERE is_active = true AND source = 'b365api'; --- name: GetRawOddsByID :one +-- name: GetRawOddsByMarketID :many SELECT id, raw_odds, fetched_at FROM odds WHERE - raw_odds @> $1::jsonb AND + market_id = $1 AND is_active = true AND source = 'b365api' -LIMIT 1; +LIMIT $2 OFFSET $3; -- name: GetPrematchOddsByUpcomingID :many SELECT diff --git a/docs/docs.go b/docs/docs.go index 67e9ac0..57af447 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1354,9 +1354,9 @@ const docTemplate = `{ } } }, - "/prematch/odds/raw/{raw_odds_id}": { + "/prematch/odds/raw/{market_id}": { "get": { - "description": "Retrieve raw odds by raw odds ID", + "description": "Retrieve raw odds records using a Market ID", "consumes": [ "application/json" ], @@ -1366,12 +1366,12 @@ const docTemplate = `{ "tags": [ "prematch" ], - "summary": "Retrieve raw odds by ID", + "summary": "Retrieve raw odds by Market ID", "parameters": [ { "type": "string", - "description": "Raw Odds ID", - "name": "raw_odds_id", + "description": "Market ID", + "name": "market_id", "in": "path", "required": true } @@ -1380,7 +1380,10 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/domain.RawOddsByID" + "type": "array", + "items": { + "$ref": "#/definitions/domain.RawOddsByMarketID" + } } }, "400": { @@ -2672,12 +2675,9 @@ const docTemplate = `{ "BANK" ] }, - "domain.RawOddsByID": { + "domain.RawOddsByMarketID": { "type": "object", "properties": { - "event_id": { - "type": "string" - }, "fetched_at": { "type": "string" }, diff --git a/docs/swagger.json b/docs/swagger.json index fc559e3..55f1352 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1346,9 +1346,9 @@ } } }, - "/prematch/odds/raw/{raw_odds_id}": { + "/prematch/odds/raw/{market_id}": { "get": { - "description": "Retrieve raw odds by raw odds ID", + "description": "Retrieve raw odds records using a Market ID", "consumes": [ "application/json" ], @@ -1358,12 +1358,12 @@ "tags": [ "prematch" ], - "summary": "Retrieve raw odds by ID", + "summary": "Retrieve raw odds by Market ID", "parameters": [ { "type": "string", - "description": "Raw Odds ID", - "name": "raw_odds_id", + "description": "Market ID", + "name": "market_id", "in": "path", "required": true } @@ -1372,7 +1372,10 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/domain.RawOddsByID" + "type": "array", + "items": { + "$ref": "#/definitions/domain.RawOddsByMarketID" + } } }, "400": { @@ -2664,12 +2667,9 @@ "BANK" ] }, - "domain.RawOddsByID": { + "domain.RawOddsByMarketID": { "type": "object", "properties": { - "event_id": { - "type": "string" - }, "fetched_at": { "type": "string" }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ad5e9cd..b27f68c 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -68,10 +68,8 @@ definitions: - TELEBIRR_TRANSACTION - ARIFPAY_TRANSACTION - BANK - domain.RawOddsByID: + domain.RawOddsByMarketID: properties: - event_id: - type: string fetched_at: type: string id: @@ -1706,15 +1704,15 @@ paths: summary: Retrieve prematch odds for an event tags: - prematch - /prematch/odds/raw/{raw_odds_id}: + /prematch/odds/raw/{market_id}: get: consumes: - application/json - description: Retrieve raw odds by raw odds ID + description: Retrieve raw odds records using a Market ID parameters: - - description: Raw Odds ID + - description: Market ID in: path - name: raw_odds_id + name: market_id required: true type: string produces: @@ -1723,7 +1721,9 @@ paths: "200": description: OK schema: - $ref: '#/definitions/domain.RawOddsByID' + items: + $ref: '#/definitions/domain.RawOddsByMarketID' + type: array "400": description: Bad Request schema: @@ -1732,7 +1732,7 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/response.APIResponse' - summary: Retrieve raw odds by ID + summary: Retrieve raw odds by Market ID tags: - prematch /prematch/odds/upcoming/{upcoming_id}: diff --git a/gen/db/odds.sql.go b/gen/db/odds.sql.go index 5f966bf..d4c31c2 100644 --- a/gen/db/odds.sql.go +++ b/gen/db/odds.sql.go @@ -247,28 +247,47 @@ func (q *Queries) GetPrematchOddsByUpcomingID(ctx context.Context, arg GetPremat return items, nil } -const GetRawOddsByID = `-- name: GetRawOddsByID :one +const GetRawOddsByMarketID = `-- name: GetRawOddsByMarketID :many SELECT id, raw_odds, fetched_at FROM odds -WHERE raw_odds @> $1::jsonb +WHERE market_id = $1 AND is_active = true AND source = 'b365api' -LIMIT 1 +LIMIT $2 OFFSET $3 ` -type GetRawOddsByIDRow struct { +type GetRawOddsByMarketIDParams struct { + MarketID pgtype.Text + Limit int32 + Offset int32 +} + +type GetRawOddsByMarketIDRow struct { ID int32 `json:"id"` RawOdds []byte `json:"raw_odds"` FetchedAt pgtype.Timestamp `json:"fetched_at"` } -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.RawOdds, &i.FetchedAt) - return i, err +func (q *Queries) GetRawOddsByMarketID(ctx context.Context, arg GetRawOddsByMarketIDParams) ([]GetRawOddsByMarketIDRow, error) { + rows, err := q.db.Query(ctx, GetRawOddsByMarketID, arg.MarketID, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetRawOddsByMarketIDRow + for rows.Next() { + var i GetRawOddsByMarketIDRow + if err := rows.Scan(&i.ID, &i.RawOdds, &i.FetchedAt); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil } const InsertNonLiveOdd = `-- name: InsertNonLiveOdd :exec @@ -311,6 +330,30 @@ SET odds_value = EXCLUDED.odds_value, raw_odds = EXCLUDED.raw_odds, market_type = EXCLUDED.market_type, market_name = EXCLUDED.market_name, + event_id, + fi, + market_type, + market_name, + market_category, + market_id, + name, + handicap, + odds_value, + section, + category, + raw_odds, + is_active, + source, + fetched_at +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, + $8, $9, $10, $11, $12, $13, $14, $15 +) +ON CONFLICT (event_id, market_id) 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 = EXCLUDED.fetched_at, is_active = EXCLUDED.is_active, diff --git a/internal/domain/odds.go b/internal/domain/odds.go index df2de7e..9992490 100644 --- a/internal/domain/odds.go +++ b/internal/domain/odds.go @@ -38,9 +38,8 @@ type Odd struct { 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"` +type RawOddsByMarketID struct { + ID int64 `json:"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 a8573a3..bb41387 100644 --- a/internal/repository/odds.go +++ b/internal/repository/odds.go @@ -1,15 +1,16 @@ package repository import ( - "context" - "encoding/json" - "os" - "strconv" - "time" + "context" + "encoding/json" + "fmt" + "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 { @@ -175,21 +176,31 @@ func (s *Store) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) { 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 +func (s *Store) GetRawOddsByMarketID(ctx context.Context, rawOddsID string) (domain.RawOddsByMarketID, error) { + params := dbgen.GetRawOddsByMarketIDParams{ + MarketID: pgtype.Text{String: rawOddsID, Valid: true}, + Limit: 1, + Offset: 0, } + rows, err := s.queries.GetRawOddsByMarketID(ctx, params) + if err != nil { + return domain.RawOddsByMarketID{}, err + } + + if len(rows) == 0 { + return domain.RawOddsByMarketID{}, fmt.Errorf("no raw odds found for market_id: %s", rawOddsID) + } + + row := rows[0] + var rawOdds []json.RawMessage - if err := json.Unmarshal(odd.RawOdds, &rawOdds); err != nil { - return domain.RawOddsByID{}, err + if err := json.Unmarshal(row.RawOdds, &rawOdds); err != nil { + return domain.RawOddsByMarketID{}, err } - return domain.RawOddsByID{ - ID: int64(odd.ID), + return domain.RawOddsByMarketID{ + ID: int64(row.ID), RawOdds: func() []domain.RawMessage { converted := make([]domain.RawMessage, len(rawOdds)) for i, r := range rawOdds { @@ -197,19 +208,17 @@ func (s *Store) GetRawOddsByID(ctx context.Context, rawOddsID string) (domain.Ra } return converted }(), - FetchedAt: odd.FetchedAt.Time, + FetchedAt: row.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 diff --git a/internal/services/odds/port.go b/internal/services/odds/port.go index 8805a66..2472e99 100644 --- a/internal/services/odds/port.go +++ b/internal/services/odds/port.go @@ -10,7 +10,7 @@ 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) + GetRawOddsByMarketID(ctx context.Context, marketID string) ([]domain.RawOddsByMarketID, error) } diff --git a/internal/services/odds/service.go b/internal/services/odds/service.go index 9b31a94..d8bce34 100644 --- a/internal/services/odds/service.go +++ b/internal/services/odds/service.go @@ -122,13 +122,15 @@ func (s *ServiceImpl) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, err return s.store.GetALLPrematchOdds(ctx) } -func (s *ServiceImpl) GetRawOddsByID(ctx context.Context, rawOddsID string) ([]domain.RawOddsByID, error) { - rawOdds, err := s.store.GetRawOddsByID(ctx, rawOddsID) +func (s *ServiceImpl) GetRawOddsByMarketID(ctx context.Context, marketID string) ([]domain.RawOddsByMarketID, error) { + rows, err := s.store.GetRawOddsByMarketID(ctx, marketID) if err != nil { return nil, err } - return []domain.RawOddsByID{rawOdds}, nil + + return []domain.RawOddsByMarketID{rows}, nil } + 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 ddf9677..2ee4e69 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -37,8 +37,8 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S // }, - // { - // spec: "*/30 * * * * *", // Every 30 seconds + // { + // spec: "*/5 * * * * *", // Every 5 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 17e4de4..ea1ac59 100644 --- a/internal/web_server/handlers/prematch.go +++ b/internal/web_server/handlers/prematch.go @@ -55,26 +55,27 @@ func GetALLPrematchOdds(logger *slog.Logger, prematchSvc *odds.ServiceImpl) fibe 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 +// GetRawOddsByMarketID +// @Summary Retrieve raw odds by Market ID +// @Description Retrieve raw odds records using a Market ID // @Tags prematch // @Accept json // @Produce json -// @Param raw_odds_id path string true "Raw Odds ID" -// @Success 200 {object} domain.RawOddsByID +// @Param market_id path string true "Market ID" +// @Success 200 {array} domain.RawOddsByMarketID // @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 { +// @Router /prematch/odds/raw/{market_id} [get] +func GetRawOddsByMarketID(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) + marketID := c.Params("market_id") + if marketID == "" { + return response.WriteJSON(c, fiber.StatusBadRequest, "Missing market_id", nil, nil) } - rawOdds, err := prematchSvc.GetRawOddsByID(c.Context(), rawOddsID) + rawOdds, err := prematchSvc.GetRawOddsByMarketID(c.Context(), marketID) if err != nil { + logger.Error("failed to fetch raw odds", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve raw odds", nil, nil) } diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index b968e15..c8d75a5 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -59,7 +59,7 @@ func (a *App) initAppRoutes() { 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)) + a.fiber.Get("/prematch/odds/raw/:market_id", handlers.GetRawOddsByMarketID(a.logger, a.prematchSvc)) a.fiber.Get("/prematch/events/:id", handlers.GetUpcomingEventByID(a.logger, a.eventSvc)) a.fiber.Get("/prematch/events", handlers.GetAllUpcomingEvents(a.logger, a.eventSvc))