package repository import ( "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" ) func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error { if len(m.Odds) == 0 { return nil } for _, item := range m.Odds { var name string var oddsVal float64 if m.Source == "bwin" { nameValue := getMap(item["name"]) name = getString(nameValue["value"]) oddsVal = getFloat(item["odds"]) } else { name = getString(item["name"]) oddsVal = getConvertedFloat(item["odds"]) } handicap := getString(item["handicap"]) 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 != ""}, 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 != ""}, 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, IsActive: pgtype.Bool{Bool: true, Valid: true}, Source: pgtype.Text{String: m.Source, Valid: true}, FetchedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, } 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" 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() 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 } func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) { odds, err := s.queries.GetPrematchOdds(ctx) if err != nil { return nil, err } domainOdds := make([]domain.Odd, len(odds)) for i, odd := range odds { 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: 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) GetRawOddsByMarketID(ctx context.Context, rawOddsID string, upcomingID string) (domain.RawOddsByMarketID, error) { params := dbgen.GetRawOddsByMarketIDParams{ MarketID: pgtype.Text{String: rawOddsID, Valid: true}, Fi: pgtype.Text{String: upcomingID, Valid: true}, } odds, err := s.queries.GetRawOddsByMarketID(ctx, params) if err != nil { return domain.RawOddsByMarketID{}, err } var rawOdds []json.RawMessage if err := json.Unmarshal(odds.RawOdds, &rawOdds); err != nil { return domain.RawOddsByMarketID{}, err } return domain.RawOddsByMarketID{ ID: int64(odds.ID), MarketName: odds.MarketName.String, Handicap: odds.Handicap.String, RawOdds: func() []domain.RawMessage { converted := make([]domain.RawMessage, len(rawOdds)) for i, r := range rawOdds { converted[i] = domain.RawMessage(r) } return converted }(), FetchedAt: odds.FetchedAt.Time, }, nil } func (s *Store) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit domain.ValidInt64, offset domain.ValidInt64) ([]domain.Odd, error) { odds, err := s.queries.GetPaginatedPrematchOddsByUpcomingID(ctx, dbgen.GetPaginatedPrematchOddsByUpcomingIDParams{ ID: upcomingID, Limit: pgtype.Int4{ Int32: int32(limit.Value), Valid: limit.Valid, }, Offset: pgtype.Int4{ Int32: int32(offset.Value), Valid: offset.Valid, }, }) 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 } func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string) ([]domain.Odd, error) { odds, err := s.queries.GetPrematchOddsByUpcomingID(ctx, upcomingID) 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 } func (s *Store) DeleteOddsForEvent(ctx context.Context, eventID string) error { return s.queries.DeleteOddsForEvent(ctx, pgtype.Text{ String: eventID, Valid: true, }) } func getString(v interface{}) string { if s, ok := v.(string); ok { return s } return "" } func getConvertedFloat(v interface{}) float64 { if s, ok := v.(string); ok { f, err := strconv.ParseFloat(s, 64) if err == nil { return f } } return 0 } func getFloat(v interface{}) float64 { if n, ok := v.(float64); ok { return n } return 0 } func getMap(v interface{}) map[string]interface{} { if m, ok := v.(map[string]interface{}); ok { return m } return nil }