Yimaru-BackEnd/internal/services/odds/service.go
OneTap Technologies 92250d61a8 event and odd data
2025-04-10 16:42:26 +03:00

153 lines
4.0 KiB
Go

package odds
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"sync"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
)
type ServiceImpl struct {
token string
store *repository.Store
}
func New(token string, store *repository.Store) *ServiceImpl {
return &ServiceImpl{token: token, store: store}
}
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
eventIDs, err := s.store.GetUpcomingEventIDs(ctx)
if err != nil {
return fmt.Errorf("fetch upcoming event IDs: %w", err)
}
type OddsMarket struct {
ID string `json:"id"`
Name string `json:"name"`
Odds []struct {
ID string `json:"id"`
Odds string `json:"odds"`
Header string `json:"header,omitempty"`
Name string `json:"name,omitempty"`
Handicap string `json:"handicap,omitempty"`
} `json:"odds"`
}
type OddsSection struct {
UpdatedAt string `json:"updated_at"`
Sp map[string]OddsMarket `json:"sp"`
}
type StructuredOddsResponse struct {
Success int `json:"success"`
Results []struct {
EventID string `json:"event_id"`
FI string `json:"FI"`
AsianLines OddsSection `json:"asian_lines"`
Goals OddsSection `json:"goals"`
} `json:"results"`
}
var wg sync.WaitGroup
sem := make(chan struct{}, 5)
for _, eventID := range eventIDs {
if eventID == "" || len(eventID) < 5 {
continue
}
wg.Add(1)
go func(originalID string) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/odds?token=%s&event_id=%s", s.token, originalID)
resp, err := http.Get(url)
if err != nil {
fmt.Printf(" Failed HTTP request for event_id=%s: %v\n", originalID, err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf(" Failed to read response body for event_id=%s: %v\n", originalID, err)
return
}
var data StructuredOddsResponse
if err := json.Unmarshal(body, &data); err != nil {
fmt.Printf(" JSON unmarshal failed for event_id=%s. Response: %s\n", originalID, string(body))
return
}
if data.Success != 1 || len(data.Results) == 0 {
fmt.Printf(" API response error or no results for event_id=%s\nBody: %s\n", originalID, string(body))
return
}
result := data.Results[0]
finalEventID := result.EventID
if finalEventID == "" {
finalEventID = result.FI
}
if finalEventID == "" {
fmt.Printf(" Skipping event_id=%s due to missing both event_id and FI\n", originalID)
return
}
saveOdds := func(sectionName string, section OddsSection) {
for marketType, market := range section.Sp {
for _, odd := range market.Odds {
val, err := strconv.ParseFloat(odd.Odds, 64)
if err != nil {
fmt.Printf(" Skipping invalid odds for market=%s, event_id=%s\n", marketType, finalEventID)
continue
}
record := domain.OddsRecord{
EventID: finalEventID,
MarketType: marketType,
Header: odd.Header,
Name: odd.Name,
Handicap: odd.Handicap,
OddsValue: val,
Section: sectionName,
Category: market.ID,
MarketID: odd.ID,
RawEventID: originalID,
}
fmt.Printf("🟡 Preparing to store: event_id=%s | market=%s | header=%s | name=%s | odds=%.3f\n",
finalEventID, marketType, odd.Header, odd.Name, val)
err = s.store.SaveNonLiveOdd(ctx, record)
if err != nil {
fmt.Printf("❌ DB save error for market=%s, event_id=%s: %v\nRecord: %+v\n", marketType, finalEventID, err, record)
} else {
fmt.Printf("✅ Stored odd: event_id=%s | market=%s | odds=%.3f\n", finalEventID, marketType, val)
}
}
}
}
saveOdds("asian_lines", result.AsianLines)
saveOdds("goals", result.Goals)
}(eventID)
}
wg.Wait()
fmt.Println("✅ All non-live odds fetched and stored.")
return nil
}