856 lines
24 KiB
Go
856 lines
24 KiB
Go
package odds
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"log/slog"
|
|
"net/http"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
|
)
|
|
|
|
type ServiceImpl struct {
|
|
store *repository.Store
|
|
config *config.Config
|
|
logger *slog.Logger
|
|
client *http.Client
|
|
}
|
|
|
|
func New(store *repository.Store, cfg *config.Config, logger *slog.Logger) *ServiceImpl {
|
|
return &ServiceImpl{
|
|
store: store,
|
|
config: cfg,
|
|
logger: logger,
|
|
client: &http.Client{Timeout: 10 * time.Second},
|
|
}
|
|
}
|
|
|
|
// TODO Add the optimization to get 10 events at the same time
|
|
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
|
var wg sync.WaitGroup
|
|
errChan := make(chan error, 2)
|
|
// wg.Add(2)
|
|
wg.Add(1)
|
|
|
|
// go func() {
|
|
// defer wg.Done()
|
|
// if err := s.fetchBet365Odds(ctx); err != nil {
|
|
// errChan <- fmt.Errorf("bet365 odds fetching error: %w", err)
|
|
// }
|
|
// }()
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
if err := s.fetchBwinOdds(ctx); err != nil {
|
|
errChan <- fmt.Errorf("bwin odds fetching error: %w", err)
|
|
}
|
|
}()
|
|
|
|
var errs []error
|
|
for err := range errChan {
|
|
errs = append(errs, err)
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ServiceImpl) fetchBet365Odds(ctx context.Context) error {
|
|
eventIDs, err := s.store.GetAllUpcomingEvents(ctx)
|
|
if err != nil {
|
|
log.Printf("❌ Failed to fetch upcoming event IDs: %v", err)
|
|
return err
|
|
}
|
|
|
|
var errs []error
|
|
|
|
for index, event := range eventIDs {
|
|
|
|
eventID, err := strconv.ParseInt(event.ID, 10, 64)
|
|
if err != nil {
|
|
s.logger.Error("Failed to parse event id", "error", err.Error())
|
|
return err
|
|
}
|
|
|
|
url := fmt.Sprintf("https://api.b365api.com/v3/bet365/prematch?token=%s&FI=%d", s.config.Bet365Token, eventID)
|
|
|
|
log.Printf("📡 Fetching prematch odds for event ID: %d (%d/%d) ", eventID, index, len(eventIDs))
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
log.Printf("❌ Failed to create request for event %d: %v", eventID, err)
|
|
s.logger.Error("Failed to create request for event%d: %v", strconv.FormatInt(eventID, 10), err.Error())
|
|
continue
|
|
}
|
|
|
|
resp, err := s.client.Do(req)
|
|
if err != nil {
|
|
log.Printf("❌ Failed to fetch prematch odds for event %d: %v", eventID, err)
|
|
continue
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Printf("❌ Failed to read response body for event %d: %v", eventID, err)
|
|
continue
|
|
}
|
|
var oddsData domain.BaseNonLiveOddResponse
|
|
|
|
if err := json.Unmarshal(body, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 {
|
|
log.Printf("❌ Invalid prematch data for event %d", eventID)
|
|
continue
|
|
}
|
|
|
|
switch event.SportID {
|
|
case domain.FOOTBALL:
|
|
if err := s.parseFootball(ctx, oddsData.Results[0]); err != nil {
|
|
s.logger.Error("Error while inserting football odd")
|
|
errs = append(errs, err)
|
|
}
|
|
case domain.BASKETBALL:
|
|
if err := s.parseBasketball(ctx, oddsData.Results[0]); err != nil {
|
|
s.logger.Error("Error while inserting basketball odd")
|
|
errs = append(errs, err)
|
|
}
|
|
case domain.ICE_HOCKEY:
|
|
if err := s.parseIceHockey(ctx, oddsData.Results[0]); err != nil {
|
|
s.logger.Error("Error while inserting ice hockey odd")
|
|
errs = append(errs, err)
|
|
}
|
|
case domain.CRICKET:
|
|
if err := s.parseCricket(ctx, oddsData.Results[0]); err != nil {
|
|
s.logger.Error("Error while inserting cricket odd")
|
|
errs = append(errs, err)
|
|
}
|
|
case domain.VOLLEYBALL:
|
|
if err := s.parseVolleyball(ctx, oddsData.Results[0]); err != nil {
|
|
s.logger.Error("Error while inserting volleyball odd")
|
|
errs = append(errs, err)
|
|
}
|
|
case domain.DARTS:
|
|
if err := s.parseDarts(ctx, oddsData.Results[0]); err != nil {
|
|
s.logger.Error("Error while inserting darts odd")
|
|
errs = append(errs, err)
|
|
}
|
|
case domain.FUTSAL:
|
|
if err := s.parseFutsal(ctx, oddsData.Results[0]); err != nil {
|
|
s.logger.Error("Error while inserting futsal odd")
|
|
errs = append(errs, err)
|
|
}
|
|
case domain.AMERICAN_FOOTBALL:
|
|
if err := s.parseAmericanFootball(ctx, oddsData.Results[0]); err != nil {
|
|
s.logger.Error("Error while inserting american football odd")
|
|
errs = append(errs, err)
|
|
}
|
|
case domain.RUGBY_LEAGUE:
|
|
if err := s.parseRugbyLeague(ctx, oddsData.Results[0]); err != nil {
|
|
s.logger.Error("Error while inserting rugby league odd")
|
|
errs = append(errs, err)
|
|
}
|
|
case domain.RUGBY_UNION:
|
|
if err := s.parseRugbyUnion(ctx, oddsData.Results[0]); err != nil {
|
|
s.logger.Error("Error while inserting rugby union odd")
|
|
errs = append(errs, err)
|
|
}
|
|
case domain.BASEBALL:
|
|
if err := s.parseBaseball(ctx, oddsData.Results[0]); err != nil {
|
|
s.logger.Error("Error while inserting baseball odd")
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
|
|
// result := oddsData.Results[0]
|
|
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ServiceImpl) fetchBwinOdds(ctx context.Context) error {
|
|
// getting odds for a specific event is not possible for bwin, most specific we can get is fetch odds on a single sport
|
|
// so instead of having event and odds fetched separetly event will also be fetched along with the odds
|
|
sportIds := []int{4, 12, 7}
|
|
for _, sportId := range sportIds {
|
|
url := fmt.Sprintf("https://api.b365api.com/v1/bwin/prematch?sport_id=%d&token=%s", sportId, s.config.Bet365Token)
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
log.Printf("❌ Failed to create request for sportId %d: %v", sportId, err)
|
|
continue
|
|
}
|
|
|
|
resp, err := s.client.Do(req)
|
|
if err != nil {
|
|
log.Printf("❌ Failed to fetch request for sportId %d: %v", sportId, err)
|
|
continue
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Printf("❌ Failed to read response body for sportId %d: %v", sportId, err)
|
|
continue
|
|
}
|
|
|
|
var data struct {
|
|
Success int `json:"success"`
|
|
Results []map[string]interface{} `json:"results"`
|
|
}
|
|
|
|
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
|
fmt.Printf("Decode failed for sport_id=%d\nRaw: %s\n", sportId, string(body))
|
|
continue
|
|
}
|
|
|
|
for _, res := range data.Results {
|
|
if getInt(res["Id"]) == -1 {
|
|
continue
|
|
}
|
|
|
|
event := domain.Event{
|
|
ID: strconv.Itoa(getInt(res["Id"])),
|
|
SportID: int32(getInt(res["SportId"])),
|
|
LeagueID: int32(getInt(res["LeagueId"])),
|
|
LeagueName: getString(res["Leaguename"]),
|
|
HomeTeam: getString(res["HomeTeam"]),
|
|
HomeTeamID: int32(getInt(res["HomeTeamId"])),
|
|
AwayTeam: getString(res["AwayTeam"]),
|
|
AwayTeamID: int32(getInt(res["AwayTeamId"])),
|
|
StartTime: time.Now().UTC().Format(time.RFC3339),
|
|
TimerStatus: "1",
|
|
IsLive: true,
|
|
Status: "live",
|
|
Source: "bwin",
|
|
}
|
|
|
|
if err := s.store.SaveEvent(ctx, event); err != nil {
|
|
fmt.Printf("Could not store live event [id=%s]: %v\n", event.ID, err)
|
|
continue
|
|
}
|
|
|
|
for _, market := range []string{"Markets, optionMarkets"} {
|
|
for _, m := range getMapArray(res[market]) {
|
|
name := getMap(m["name"])
|
|
marketName := getString(name["value"])
|
|
|
|
market := domain.Market{
|
|
EventID: event.ID,
|
|
MarketID: getString(m["id"]),
|
|
MarketCategory: getString(m["category"]),
|
|
MarketName: marketName,
|
|
Source: "bwin",
|
|
}
|
|
|
|
results := getMapArray(m["results"])
|
|
market.Odds = results
|
|
|
|
s.store.SaveNonLiveMarket(ctx, market)
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ServiceImpl) parseFootball(ctx context.Context, res json.RawMessage) error {
|
|
var footballRes domain.FootballOddsResponse
|
|
if err := json.Unmarshal(res, &footballRes); err != nil {
|
|
s.logger.Error("Failed to unmarshal football result", "error", err)
|
|
return err
|
|
}
|
|
if footballRes.EventID == "" && footballRes.FI == "" {
|
|
s.logger.Error("Skipping football result with no valid Event ID", "eventID", footballRes.EventID, "fi", footballRes.FI)
|
|
return fmt.Errorf("Skipping football result with no valid Event ID Event ID %v", footballRes.EventID)
|
|
}
|
|
sections := map[string]domain.OddsSection{
|
|
"main": footballRes.Main,
|
|
"asian_lines": footballRes.AsianLines,
|
|
"goals": footballRes.Goals,
|
|
"half": footballRes.Half,
|
|
}
|
|
|
|
var errs []error
|
|
|
|
for oddCategory, section := range sections {
|
|
if err := s.storeSection(ctx, footballRes.EventID, footballRes.FI, oddCategory, section); err != nil {
|
|
s.logger.Error("Error storing football section", "eventID", footballRes.FI, "odd", oddCategory)
|
|
log.Printf("⚠️ Error when storing football %v", err)
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ServiceImpl) parseBasketball(ctx context.Context, res json.RawMessage) error {
|
|
var basketballRes domain.BasketballOddsResponse
|
|
if err := json.Unmarshal(res, &basketballRes); err != nil {
|
|
s.logger.Error("Failed to unmarshal basketball result", "error", err)
|
|
return err
|
|
}
|
|
if basketballRes.EventID == "" && basketballRes.FI == "" {
|
|
s.logger.Error("Skipping basketball result with no valid Event ID")
|
|
return fmt.Errorf("Skipping basketball result with no valid Event ID")
|
|
}
|
|
sections := map[string]domain.OddsSection{
|
|
"main": basketballRes.Main,
|
|
"half_props": basketballRes.HalfProps,
|
|
"quarter_props": basketballRes.QuarterProps,
|
|
"team_props": basketballRes.TeamProps,
|
|
}
|
|
|
|
var errs []error
|
|
|
|
for oddCategory, section := range sections {
|
|
if err := s.storeSection(ctx, basketballRes.EventID, basketballRes.FI, oddCategory, section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
for _, section := range basketballRes.Others {
|
|
if err := s.storeSection(ctx, basketballRes.EventID, basketballRes.FI, "others", section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
func (s *ServiceImpl) parseIceHockey(ctx context.Context, res json.RawMessage) error {
|
|
var iceHockeyRes domain.IceHockeyOddsResponse
|
|
if err := json.Unmarshal(res, &iceHockeyRes); err != nil {
|
|
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
|
|
return err
|
|
}
|
|
if iceHockeyRes.EventID == "" && iceHockeyRes.FI == "" {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
return fmt.Errorf("Skipping result with no valid Event ID")
|
|
}
|
|
sections := map[string]domain.OddsSection{
|
|
"main": iceHockeyRes.Main,
|
|
"main_2": iceHockeyRes.Main2,
|
|
"1st_period": iceHockeyRes.FirstPeriod,
|
|
}
|
|
|
|
var errs []error
|
|
|
|
for oddCategory, section := range sections {
|
|
if err := s.storeSection(ctx, iceHockeyRes.EventID, iceHockeyRes.FI, oddCategory, section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
for _, section := range iceHockeyRes.Others {
|
|
if err := s.storeSection(ctx, iceHockeyRes.EventID, iceHockeyRes.FI, "others", section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ServiceImpl) parseCricket(ctx context.Context, res json.RawMessage) error {
|
|
var cricketRes domain.CricketOddsResponse
|
|
if err := json.Unmarshal(res, &cricketRes); err != nil {
|
|
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
|
|
return err
|
|
}
|
|
if cricketRes.EventID == "" && cricketRes.FI == "" {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
return fmt.Errorf("Skipping result with no valid Event ID")
|
|
}
|
|
|
|
sections := map[string]domain.OddsSection{
|
|
"1st_over": cricketRes.Main,
|
|
"innings_1": cricketRes.First_Innings,
|
|
"main": cricketRes.Main,
|
|
"match": cricketRes.Match,
|
|
"player": cricketRes.Player,
|
|
"team": cricketRes.Team,
|
|
}
|
|
|
|
var errs []error
|
|
|
|
for oddCategory, section := range sections {
|
|
if err := s.storeSection(ctx, cricketRes.EventID, cricketRes.FI, oddCategory, section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
for _, section := range cricketRes.Others {
|
|
if err := s.storeSection(ctx, cricketRes.EventID, cricketRes.FI, "others", section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ServiceImpl) parseVolleyball(ctx context.Context, res json.RawMessage) error {
|
|
var volleyballRes domain.VolleyballOddsResponse
|
|
if err := json.Unmarshal(res, &volleyballRes); err != nil {
|
|
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
|
|
return err
|
|
}
|
|
if volleyballRes.EventID == "" && volleyballRes.FI == "" {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
return fmt.Errorf("Skipping result with no valid Event ID")
|
|
}
|
|
sections := map[string]domain.OddsSection{
|
|
"main": volleyballRes.Main,
|
|
}
|
|
|
|
var errs []error
|
|
|
|
for oddCategory, section := range sections {
|
|
if err := s.storeSection(ctx, volleyballRes.EventID, volleyballRes.FI, oddCategory, section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
for _, section := range volleyballRes.Others {
|
|
if err := s.storeSection(ctx, volleyballRes.EventID, volleyballRes.FI, "others", section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ServiceImpl) parseDarts(ctx context.Context, res json.RawMessage) error {
|
|
var dartsRes domain.DartsOddsResponse
|
|
if err := json.Unmarshal(res, &dartsRes); err != nil {
|
|
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
|
|
return err
|
|
}
|
|
if dartsRes.EventID == "" && dartsRes.FI == "" {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
return fmt.Errorf("Skipping result with no valid Event ID")
|
|
}
|
|
sections := map[string]domain.OddsSection{
|
|
"180s": dartsRes.OneEightys,
|
|
"extra": dartsRes.Extra,
|
|
"leg": dartsRes.Leg,
|
|
"main": dartsRes.Main,
|
|
}
|
|
|
|
var errs []error
|
|
|
|
for oddCategory, section := range sections {
|
|
if err := s.storeSection(ctx, dartsRes.EventID, dartsRes.FI, oddCategory, section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
for _, section := range dartsRes.Others {
|
|
if err := s.storeSection(ctx, dartsRes.EventID, dartsRes.FI, "others", section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ServiceImpl) parseFutsal(ctx context.Context, res json.RawMessage) error {
|
|
var futsalRes domain.FutsalOddsResponse
|
|
if err := json.Unmarshal(res, &futsalRes); err != nil {
|
|
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
|
|
return err
|
|
}
|
|
if futsalRes.EventID == "" && futsalRes.FI == "" {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
return fmt.Errorf("Skipping result with no valid Event ID")
|
|
}
|
|
sections := map[string]domain.OddsSection{
|
|
"main": futsalRes.Main,
|
|
"score": futsalRes.Score,
|
|
}
|
|
|
|
var errs []error
|
|
|
|
for oddCategory, section := range sections {
|
|
if err := s.storeSection(ctx, futsalRes.EventID, futsalRes.FI, oddCategory, section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
for _, section := range futsalRes.Others {
|
|
if err := s.storeSection(ctx, futsalRes.EventID, futsalRes.FI, "others", section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ServiceImpl) parseAmericanFootball(ctx context.Context, res json.RawMessage) error {
|
|
var americanFootballRes domain.AmericanFootballOddsResponse
|
|
if err := json.Unmarshal(res, &americanFootballRes); err != nil {
|
|
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
|
|
return err
|
|
}
|
|
if americanFootballRes.EventID == "" && americanFootballRes.FI == "" {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
return fmt.Errorf("Skipping result with no valid Event ID")
|
|
}
|
|
sections := map[string]domain.OddsSection{
|
|
"half_props": americanFootballRes.HalfProps,
|
|
"main": americanFootballRes.Main,
|
|
"quarter_props": americanFootballRes.QuarterProps,
|
|
}
|
|
|
|
var errs []error
|
|
|
|
for oddCategory, section := range sections {
|
|
if err := s.storeSection(ctx, americanFootballRes.EventID, americanFootballRes.FI, oddCategory, section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
for _, section := range americanFootballRes.Others {
|
|
if err := s.storeSection(ctx, americanFootballRes.EventID, americanFootballRes.FI, "others", section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ServiceImpl) parseRugbyLeague(ctx context.Context, res json.RawMessage) error {
|
|
var rugbyLeagueRes domain.RugbyLeagueOddsResponse
|
|
if err := json.Unmarshal(res, &rugbyLeagueRes); err != nil {
|
|
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
|
|
return err
|
|
}
|
|
if rugbyLeagueRes.EventID == "" && rugbyLeagueRes.FI == "" {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
return fmt.Errorf("Skipping result with no valid Event ID")
|
|
}
|
|
sections := map[string]domain.OddsSection{
|
|
"10minute": rugbyLeagueRes.TenMinute,
|
|
"main": rugbyLeagueRes.Main,
|
|
"main_2": rugbyLeagueRes.Main2,
|
|
"player": rugbyLeagueRes.Player,
|
|
"Score": rugbyLeagueRes.Score,
|
|
"Team": rugbyLeagueRes.Team,
|
|
}
|
|
|
|
var errs []error
|
|
|
|
for oddCategory, section := range sections {
|
|
if err := s.storeSection(ctx, rugbyLeagueRes.EventID, rugbyLeagueRes.FI, oddCategory, section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
for _, section := range rugbyLeagueRes.Others {
|
|
if err := s.storeSection(ctx, rugbyLeagueRes.EventID, rugbyLeagueRes.FI, "others", section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ServiceImpl) parseRugbyUnion(ctx context.Context, res json.RawMessage) error {
|
|
var rugbyUnionRes domain.RugbyUnionOddsResponse
|
|
if err := json.Unmarshal(res, &rugbyUnionRes); err != nil {
|
|
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
|
|
return err
|
|
}
|
|
if rugbyUnionRes.EventID == "" && rugbyUnionRes.FI == "" {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
return fmt.Errorf("Skipping result with no valid Event ID")
|
|
}
|
|
sections := map[string]domain.OddsSection{
|
|
"main": rugbyUnionRes.Main,
|
|
"main_2": rugbyUnionRes.Main2,
|
|
"player": rugbyUnionRes.Player,
|
|
"Score": rugbyUnionRes.Score,
|
|
"Team": rugbyUnionRes.Team,
|
|
}
|
|
|
|
var errs []error
|
|
|
|
for oddCategory, section := range sections {
|
|
if err := s.storeSection(ctx, rugbyUnionRes.EventID, rugbyUnionRes.FI, oddCategory, section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
for _, section := range rugbyUnionRes.Others {
|
|
if err := s.storeSection(ctx, rugbyUnionRes.EventID, rugbyUnionRes.FI, "others", section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ServiceImpl) parseBaseball(ctx context.Context, res json.RawMessage) error {
|
|
var baseballRes domain.BaseballOddsResponse
|
|
if err := json.Unmarshal(res, &baseballRes); err != nil {
|
|
s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
|
|
return err
|
|
}
|
|
if baseballRes.EventID == "" && baseballRes.FI == "" {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
return fmt.Errorf("Skipping result with no valid Event ID")
|
|
}
|
|
sections := map[string]domain.OddsSection{
|
|
"main": baseballRes.Main,
|
|
"mani_props": baseballRes.MainProps,
|
|
}
|
|
|
|
var errs []error
|
|
|
|
for oddCategory, section := range sections {
|
|
if err := s.storeSection(ctx, baseballRes.EventID, baseballRes.FI, oddCategory, section); err != nil {
|
|
s.logger.Error("Skipping result with no valid Event ID")
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName string, section domain.OddsSection) error {
|
|
if len(section.Sp) == 0 {
|
|
return nil
|
|
}
|
|
|
|
updatedAtUnix, _ := strconv.ParseInt(section.UpdatedAt, 10, 64)
|
|
updatedAt := time.Unix(updatedAtUnix, 0)
|
|
|
|
var errs []error
|
|
for marketType, market := range section.Sp {
|
|
if len(market.Odds) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Check if the market id is a string
|
|
var marketIDstr string
|
|
err := json.Unmarshal(market.ID, &marketIDstr)
|
|
if err != nil {
|
|
// check if its int
|
|
var marketIDint int
|
|
err := json.Unmarshal(market.ID, &marketIDint)
|
|
if err != nil {
|
|
s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name)
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
|
|
marketIDint, err := strconv.ParseInt(marketIDstr, 10, 64)
|
|
if err != nil {
|
|
s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name)
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
|
|
isSupported, ok := domain.SupportedMarkets[marketIDint]
|
|
|
|
if !ok || !isSupported {
|
|
// s.logger.Info("Unsupported market_id", "marketID", marketIDint, "marketName", market.Name)
|
|
continue
|
|
}
|
|
|
|
marketOdds, err := convertRawMessage(market.Odds)
|
|
if err != nil {
|
|
s.logger.Error("failed to conver json.RawMessage to []map[string]interface{} for market_id: ", market.ID)
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
|
|
marketRecord := domain.Market{
|
|
EventID: eventID,
|
|
FI: fi,
|
|
MarketCategory: sectionName,
|
|
MarketType: marketType,
|
|
MarketName: market.Name,
|
|
MarketID: marketIDstr,
|
|
UpdatedAt: updatedAt,
|
|
Odds: marketOdds,
|
|
// bwin won't reach this code so bet365 is hardcoded for now
|
|
Source: "bet365",
|
|
}
|
|
|
|
err = s.store.SaveNonLiveMarket(ctx, marketRecord)
|
|
if err != nil {
|
|
s.logger.Error("failed to save market", "market_id", market.ID, "error", err)
|
|
errs = append(errs, fmt.Errorf("market %s: %w", market.ID, err))
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return errors.Join(errs...)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ServiceImpl) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) {
|
|
return s.store.GetPrematchOdds(ctx, eventID)
|
|
}
|
|
|
|
func (s *ServiceImpl) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) {
|
|
return s.store.GetALLPrematchOdds(ctx)
|
|
}
|
|
|
|
func (s *ServiceImpl) GetRawOddsByMarketID(ctx context.Context, marketID string, upcomingID string) (domain.RawOddsByMarketID, error) {
|
|
rows, err := s.store.GetRawOddsByMarketID(ctx, marketID, upcomingID)
|
|
if err != nil {
|
|
return domain.RawOddsByMarketID{}, err
|
|
}
|
|
|
|
return rows, nil
|
|
}
|
|
|
|
func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string) ([]domain.Odd, error) {
|
|
return s.store.GetPrematchOddsByUpcomingID(ctx, upcomingID)
|
|
}
|
|
|
|
func (s *ServiceImpl) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset domain.ValidInt64) ([]domain.Odd, error) {
|
|
return s.store.GetPaginatedPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset)
|
|
}
|
|
|
|
func getString(v interface{}) string {
|
|
if str, ok := v.(string); ok {
|
|
return str
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func getInt(v interface{}) int {
|
|
if n, ok := v.(float64); ok {
|
|
return int(n)
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func getMap(v interface{}) map[string]interface{} {
|
|
if m, ok := v.(map[string]interface{}); ok {
|
|
return m
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getMapArray(v interface{}) []map[string]interface{} {
|
|
result := []map[string]interface{}{}
|
|
if arr, ok := v.([]interface{}); ok {
|
|
for _, item := range arr {
|
|
if m, ok := item.(map[string]interface{}); ok {
|
|
result = append(result, m)
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func convertRawMessage(rawMessages []json.RawMessage) ([]map[string]interface{}, error) {
|
|
var result []map[string]interface{}
|
|
for _, raw := range rawMessages {
|
|
var m map[string]interface{}
|
|
if err := json.Unmarshal(raw, &m); err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, m)
|
|
}
|
|
|
|
return result, nil
|
|
}
|