fix: disabling odds

This commit is contained in:
Samuel Tariku 2025-08-13 14:48:35 +03:00
parent 5615c93fbb
commit 5331798009
8 changed files with 90 additions and 59 deletions

View File

@ -265,6 +265,7 @@ CREATE TABLE events (
source TEXT DEFAULT 'b365api',
is_featured BOOLEAN NOT NULL DEFAULT FALSE,
is_monitored BOOLEAN NOT NULL DEFAULT FALSE,
winning_upper_limit INT NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE event_history (
@ -288,6 +289,7 @@ CREATE TABLE odds (
category TEXT,
raw_odds JSONB,
fetched_at TIMESTAMP DEFAULT now(),
expires_at TIMESTAMP NOT NULL,
source TEXT DEFAULT 'b365api',
is_active BOOLEAN DEFAULT true,
UNIQUE (market_id, name, handicap),
@ -303,6 +305,23 @@ CREATE TABLE odd_history (
odd_value DOUBLE PRECISION NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE custom_odd (
id BIGSERIAL PRIMARY KEY,
odd_id BIGINT NOT NULL,
raw_odd_id BIGINT NOT NULL,
market_id TEXT NOT NULL,
event_id TEXT NOT NULL,
odd_value DOUBLE PRECISION NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE disabled_odd (
id BIGSERIAL PRIMARY KEY,
odd_id BIGINT NOT NULL,
raw_odd_id BIGINT NOT NULL,
market_id TEXT NOT NULL,
event_id TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE result_log (
id BIGSERIAL PRIMARY KEY,
status_not_finished_count INT NOT NULL,

View File

@ -14,7 +14,8 @@ INSERT INTO odds (
raw_odds,
is_active,
source,
fetched_at
fetched_at,
expires_at
)
VALUES (
$1,
@ -31,7 +32,8 @@ VALUES (
$12,
$13,
$14,
$15
$15,
$16
) ON CONFLICT (event_id, market_id) DO
UPDATE
SET odds_value = EXCLUDED.odds_value,

View File

@ -351,6 +351,7 @@ type Odd struct {
Category pgtype.Text `json:"category"`
RawOdds []byte `json:"raw_odds"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
ExpiresAt pgtype.Timestamp `json:"expires_at"`
Source pgtype.Text `json:"source"`
IsActive pgtype.Bool `json:"is_active"`
}

View File

@ -22,7 +22,7 @@ func (q *Queries) DeleteOddsForEvent(ctx context.Context, fi pgtype.Text) error
}
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
SELECT id, event_id, fi, market_type, market_name, market_category, market_id, name, handicap, odds_value, section, category, raw_odds, fetched_at, expires_at, source, is_active
FROM odds
WHERE is_active = true
AND source = 'bet365'
@ -52,6 +52,7 @@ func (q *Queries) GetALLPrematchOdds(ctx context.Context) ([]Odd, error) {
&i.Category,
&i.RawOdds,
&i.FetchedAt,
&i.ExpiresAt,
&i.Source,
&i.IsActive,
); err != nil {
@ -66,7 +67,7 @@ func (q *Queries) GetALLPrematchOdds(ctx context.Context) ([]Odd, error) {
}
const GetOddsByMarketID = `-- name: GetOddsByMarketID :one
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
SELECT id, event_id, fi, market_type, market_name, market_category, market_id, name, handicap, odds_value, section, category, raw_odds, fetched_at, expires_at, source, is_active
FROM odds
WHERE market_id = $1
AND fi = $2
@ -97,6 +98,7 @@ func (q *Queries) GetOddsByMarketID(ctx context.Context, arg GetOddsByMarketIDPa
&i.Category,
&i.RawOdds,
&i.FetchedAt,
&i.ExpiresAt,
&i.Source,
&i.IsActive,
)
@ -104,7 +106,7 @@ func (q *Queries) GetOddsByMarketID(ctx context.Context, arg GetOddsByMarketIDPa
}
const GetPaginatedPrematchOddsByUpcomingID = `-- name: GetPaginatedPrematchOddsByUpcomingID :many
SELECT o.id, 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
SELECT o.id, 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.expires_at, o.source, o.is_active
FROM odds o
JOIN events e ON o.fi = e.id
WHERE e.id = $1
@ -145,6 +147,7 @@ func (q *Queries) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, arg
&i.Category,
&i.RawOdds,
&i.FetchedAt,
&i.ExpiresAt,
&i.Source,
&i.IsActive,
); err != nil {
@ -159,7 +162,7 @@ func (q *Queries) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, arg
}
const GetPrematchOdds = `-- name: GetPrematchOdds :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
SELECT id, event_id, fi, market_type, market_name, market_category, market_id, name, handicap, odds_value, section, category, raw_odds, fetched_at, expires_at, source, is_active
FROM odds
WHERE is_active = true
AND source = 'bet365'
@ -189,6 +192,7 @@ func (q *Queries) GetPrematchOdds(ctx context.Context) ([]Odd, error) {
&i.Category,
&i.RawOdds,
&i.FetchedAt,
&i.ExpiresAt,
&i.Source,
&i.IsActive,
); err != nil {
@ -203,7 +207,7 @@ func (q *Queries) GetPrematchOdds(ctx context.Context) ([]Odd, error) {
}
const GetPrematchOddsByUpcomingID = `-- name: GetPrematchOddsByUpcomingID :many
SELECT o.id, 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
SELECT o.id, 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.expires_at, o.source, o.is_active
FROM odds o
JOIN events e ON o.fi = e.id
WHERE e.id = $1
@ -237,6 +241,7 @@ func (q *Queries) GetPrematchOddsByUpcomingID(ctx context.Context, id string) ([
&i.Category,
&i.RawOdds,
&i.FetchedAt,
&i.ExpiresAt,
&i.Source,
&i.IsActive,
); err != nil {
@ -266,7 +271,8 @@ INSERT INTO odds (
raw_odds,
is_active,
source,
fetched_at
fetched_at,
expires_at
)
VALUES (
$1,
@ -283,7 +289,8 @@ VALUES (
$12,
$13,
$14,
$15
$15,
$16
) ON CONFLICT (event_id, market_id) DO
UPDATE
SET odds_value = EXCLUDED.odds_value,
@ -315,6 +322,7 @@ type InsertNonLiveOddParams struct {
IsActive pgtype.Bool `json:"is_active"`
Source pgtype.Text `json:"source"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
ExpiresAt pgtype.Timestamp `json:"expires_at"`
}
func (q *Queries) InsertNonLiveOdd(ctx context.Context, arg InsertNonLiveOddParams) error {
@ -334,6 +342,7 @@ func (q *Queries) InsertNonLiveOdd(ctx context.Context, arg InsertNonLiveOddPara
arg.IsActive,
arg.Source,
arg.FetchedAt,
arg.ExpiresAt,
)
return err
}

View File

@ -0,0 +1,17 @@
package domain
import (
"time"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
var BadRequestZapFields = []zap.Field{
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
}
var InternalServerErrorZapFields = []zap.Field{
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
}

View File

@ -35,6 +35,7 @@ type Odd struct {
Category string `json:"category"`
RawOdds []json.RawMessage `json:"raw_odds"`
FetchedAt time.Time `json:"fetched_at"`
ExpiresAt time.Time `json:"expires_at"`
Source string `json:"source"`
IsActive bool `json:"is_active"`
}
@ -44,4 +45,5 @@ type RawOddsByMarketID struct {
Handicap string `json:"handicap"`
RawOdds []json.RawMessage `json:"raw_odds"`
FetchedAt time.Time `json:"fetched_at"`
ExpiresAt time.Time `json:"expires_at"`
}

View File

@ -51,6 +51,7 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
IsActive: pgtype.Bool{Bool: true, Valid: true},
Source: pgtype.Text{String: m.Source, Valid: true},
FetchedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
ExpiresAt: pgtype.Timestamp{Time: (time.Now()).Add(time.Hour), Valid: true},
}
err := s.queries.InsertNonLiveOdd(ctx, params)
@ -120,6 +121,7 @@ func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.O
return rawOdds
}(),
FetchedAt: odd.FetchedAt.Time,
ExpiresAt: odd.ExpiresAt.Time,
Source: odd.Source.String,
IsActive: odd.IsActive.Bool,
}
@ -157,6 +159,7 @@ func (s *Store) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) {
return rawOdds
}(),
FetchedAt: row.FetchedAt.Time,
ExpiresAt: row.ExpiresAt.Time,
Source: row.Source.String,
IsActive: row.IsActive.Bool,
}
@ -193,6 +196,7 @@ func (s *Store) GetRawOddsByMarketID(ctx context.Context, marketID string, upcom
return converted
}(),
FetchedAt: odds.FetchedAt.Time,
ExpiresAt: odds.ExpiresAt.Time,
}, nil
}
@ -227,6 +231,7 @@ func (s *Store) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomi
Category: odd.Category.String,
RawOdds: rawOdds,
FetchedAt: odd.FetchedAt.Time,
ExpiresAt: odd.ExpiresAt.Time,
Source: odd.Source.String,
IsActive: odd.IsActive.Bool,
}
@ -264,6 +269,7 @@ func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID stri
Category: odd.Category.String,
RawOdds: rawOdds,
FetchedAt: odd.FetchedAt.Time,
ExpiresAt: odd.ExpiresAt.Time,
Source: odd.Source.String,
IsActive: odd.IsActive.Bool,
}

View File

@ -1,12 +1,11 @@
package handlers
import (
"strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
"strconv"
)
// GetALLPrematchOdds
@ -19,19 +18,15 @@ import (
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/odds [get]
func (h *Handler) GetALLPrematchOdds(c *fiber.Ctx) error {
odds, err := h.prematchSvc.GetALLPrematchOdds(c.Context())
if err != nil {
h.mongoLoggerSvc.Error("Failed to retrieve all prematch odds",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
logFields := append([]zap.Field{}, domain.InternalServerErrorZapFields...)
logFields = append(logFields, zap.Error(err))
h.mongoLoggerSvc.Error("Failed to retrieve all prematch odds", logFields...)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
return response.WriteJSON(c, fiber.StatusOK, "All prematch odds retrieved successfully", odds, nil)
}
// GetRawOddsByMarketID
@ -47,37 +42,28 @@ func (h *Handler) GetALLPrematchOdds(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/odds/upcoming/{upcoming_id}/market/{market_id} [get]
func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error {
logFields := []zap.Field{
zap.String("market_id", c.Params("market_id")),
zap.String("upcoming_id", c.Params("upcoming_id")),
}
marketID := c.Params("market_id")
if marketID == "" {
h.mongoLoggerSvc.Info("Missing market_id",
zap.String("market_id", marketID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
h.mongoLoggerSvc.Info("Missing market_id", append(logFields, domain.BadRequestZapFields...)...)
return fiber.NewError(fiber.StatusBadRequest, "Missing market_id")
}
upcomingID := c.Params("upcoming_id")
if upcomingID == "" {
h.mongoLoggerSvc.Info("Missing upcoming_id",
zap.String("upcoming", upcomingID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
h.mongoLoggerSvc.Info("Missing upcoming_id", append(logFields, domain.BadRequestZapFields...)...)
return fiber.NewError(fiber.StatusBadRequest, "Missing upcoming_id")
}
rawOdds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketID, upcomingID)
if err != nil {
// Lets turn this into a warn because this is constantly going off
h.mongoLoggerSvc.Warn("Failed to get raw odds by market ID",
zap.String("marketID", marketID),
zap.String("upcomingID", upcomingID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
logFields = append(logFields, zap.Error(err))
h.mongoLoggerSvc.Warn("Failed to get raw odds by market ID", append(logFields, domain.InternalServerErrorZapFields...)...)
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
@ -98,47 +84,36 @@ func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error {
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/odds/upcoming/{upcoming_id} [get]
func (h *Handler) GetOddsByUpcomingID(c *fiber.Ctx) error {
logFields := []zap.Field{
zap.String("upcoming_id", c.Params("upcoming_id")),
zap.String("limit_param", c.Query("limit", "10")),
zap.String("offset_param", c.Query("offset", "0")),
}
upcomingID := c.Params("upcoming_id")
if upcomingID == "" {
h.mongoLoggerSvc.Info("Missing upcoming_id",
zap.String("upcoming", upcomingID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
)
h.mongoLoggerSvc.Info("Missing upcoming_id", append(logFields, domain.BadRequestZapFields...)...)
return fiber.NewError(fiber.StatusBadRequest, "Missing upcoming_id")
}
limit, err := strconv.Atoi(c.Query("limit", "10")) // Default limit is 10
if err != nil || limit <= 0 {
h.mongoLoggerSvc.Info("Invalid limit value",
zap.Int("limit", limit),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
logFields = append(logFields, zap.Error(err))
h.mongoLoggerSvc.Info("Invalid limit value", append(logFields, domain.BadRequestZapFields...)...)
return fiber.NewError(fiber.StatusBadRequest, "Invalid limit value")
}
offset, err := strconv.Atoi(c.Query("offset", "0")) // Default offset is 0
if err != nil || offset < 0 {
h.mongoLoggerSvc.Info("Invalid offset value",
zap.Int("offset", offset),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
logFields = append(logFields, zap.Error(err))
h.mongoLoggerSvc.Info("Invalid offset value", append(logFields, domain.BadRequestZapFields...)...)
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
odds, err := h.prematchSvc.GetPrematchOddsByUpcomingID(c.Context(), upcomingID)
if err != nil {
h.mongoLoggerSvc.Error("Failed to retrieve prematch odds",
zap.String("upcoming", upcomingID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
logFields = append(logFields, zap.Error(err))
h.mongoLoggerSvc.Error("Failed to retrieve prematch odds", append(logFields, domain.InternalServerErrorZapFields...)...)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve prematch odds"+err.Error())
}