267 lines
7.1 KiB
Go
267 lines
7.1 KiB
Go
package event
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"slices"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
|
// "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
|
)
|
|
|
|
type service struct {
|
|
token string
|
|
store *repository.Store
|
|
}
|
|
|
|
func New(token string, store *repository.Store) Service {
|
|
return &service{
|
|
token: token,
|
|
store: store,
|
|
}
|
|
}
|
|
|
|
func (s *service) FetchLiveEvents(ctx context.Context) error {
|
|
sportIDs := []int{1, 13, 78, 18, 91, 16, 17, 14, 12, 3, 2, 4, 83, 15, 92, 94, 8, 19, 36, 66, 9, 75, 90, 95, 110, 107, 151, 162, 148}
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
for _, sportID := range sportIDs {
|
|
wg.Add(1)
|
|
go func(sportID int) {
|
|
defer wg.Done()
|
|
|
|
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/inplay?sport_id=%d&token=%s", sportID, s.token)
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
fmt.Printf(" Failed request for sport_id=%d: %v\n", sportID, err)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
|
|
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))
|
|
return
|
|
}
|
|
|
|
for _, group := range data.Results {
|
|
for _, ev := range group {
|
|
if getString(ev["type"]) != "EV" {
|
|
continue
|
|
}
|
|
|
|
event := domain.Event{
|
|
ID: getString(ev["ID"]),
|
|
SportID: fmt.Sprintf("%d", sportID),
|
|
MatchName: getString(ev["NA"]),
|
|
Score: getString(ev["SS"]),
|
|
MatchMinute: getInt(ev["TM"]),
|
|
TimerStatus: getString(ev["TT"]),
|
|
HomeTeamID: getString(ev["HT"]),
|
|
AwayTeamID: getString(ev["AT"]),
|
|
HomeKitImage: getString(ev["K1"]),
|
|
AwayKitImage: getString(ev["K2"]),
|
|
LeagueName: getString(ev["CT"]),
|
|
LeagueID: getString(ev["C2"]),
|
|
LeagueCC: getString(ev["CB"]),
|
|
StartTime: time.Now().UTC().Format(time.RFC3339),
|
|
IsLive: true,
|
|
Status: "live",
|
|
MatchPeriod: getInt(ev["MD"]),
|
|
AddedTime: getInt(ev["TA"]),
|
|
}
|
|
|
|
if err := s.store.SaveEvent(ctx, event); err != nil {
|
|
fmt.Printf("Could not store live event [id=%s]: %v\n", event.ID, err)
|
|
}
|
|
}
|
|
}
|
|
}(sportID)
|
|
}
|
|
|
|
wg.Wait()
|
|
fmt.Println("All live events fetched and stored.")
|
|
return nil
|
|
}
|
|
|
|
func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
|
var wg sync.WaitGroup
|
|
urls := []struct {
|
|
name string
|
|
source string
|
|
}{
|
|
{"https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", "bet365"},
|
|
{"https://api.b365api.com/v1/betfair/sb/upcoming?sport_id=%d&token=%s&page=%d", "betfair"},
|
|
}
|
|
|
|
for _, url := range urls {
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
s.fetchUpcomingEventsFromProvider(ctx, url.name, url.source)
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
return nil
|
|
}
|
|
|
|
func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, source string) {
|
|
sportIDs := []int{1, 18}
|
|
var totalPages int = 1
|
|
var page int = 0
|
|
var limit int = 100
|
|
var count int = 0
|
|
for _, sportID := range sportIDs {
|
|
for page != totalPages {
|
|
time.Sleep(3 * time.Second) //This will restrict the fetching to 1200 requests per hour
|
|
|
|
page = page + 1
|
|
url := fmt.Sprintf(url, sportID, s.token, page)
|
|
log.Printf("📡 Fetching event data from %s, data page %d", source, page)
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
log.Printf("❌ Failed to fetch event data for page %d: %v", page, err)
|
|
continue
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
var data domain.BetResult
|
|
|
|
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
|
continue
|
|
}
|
|
skippedLeague := 0
|
|
for _, ev := range data.Results {
|
|
startUnix, _ := strconv.ParseInt(ev.Time, 10, 64)
|
|
// eventID, err := strconv.ParseInt(ev.ID, 10, 64)
|
|
// if err != nil {
|
|
// log.Panicf("❌ Invalid event id, eventID %v", ev.ID)
|
|
// continue
|
|
// }
|
|
|
|
leagueID, err := strconv.ParseInt(ev.League.ID, 10, 64)
|
|
if err != nil {
|
|
log.Printf("❌ Invalid league id, leagueID %v", ev.League.ID)
|
|
continue
|
|
}
|
|
|
|
if !slices.Contains(domain.SupportedLeagues, leagueID) {
|
|
skippedLeague++
|
|
continue
|
|
}
|
|
|
|
event := domain.UpcomingEvent{
|
|
ID: ev.ID,
|
|
SportID: ev.SportID,
|
|
MatchName: ev.Home.Name,
|
|
HomeTeam: ev.Home.Name,
|
|
AwayTeam: "", // handle nil safely
|
|
HomeTeamID: ev.Home.ID,
|
|
AwayTeamID: "",
|
|
HomeKitImage: "",
|
|
AwayKitImage: "",
|
|
LeagueID: ev.League.ID,
|
|
LeagueName: ev.League.Name,
|
|
LeagueCC: "",
|
|
StartTime: time.Unix(startUnix, 0).UTC(),
|
|
Source: source,
|
|
}
|
|
|
|
if ev.Away != nil {
|
|
event.AwayTeam = ev.Away.Name
|
|
event.AwayTeamID = ev.Away.ID
|
|
}
|
|
|
|
_ = s.store.SaveUpcomingEvent(ctx, event)
|
|
}
|
|
totalPages = data.Pager.Total
|
|
|
|
if count > limit {
|
|
break
|
|
}
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
|
|
func getString(v interface{}) string {
|
|
if str, ok := v.(string); ok {
|
|
return str
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func getInt(v interface{}) int {
|
|
if f, ok := v.(float64); ok {
|
|
return int(f)
|
|
}
|
|
return 0
|
|
}
|
|
func (s *service) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) {
|
|
return s.store.GetAllUpcomingEvents(ctx)
|
|
}
|
|
|
|
func (s *service) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) {
|
|
return s.store.GetExpiredUpcomingEvents(ctx)
|
|
}
|
|
|
|
func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, offset int32, leagueID domain.ValidString, sportID domain.ValidString) ([]domain.UpcomingEvent, int64, error) {
|
|
return s.store.GetPaginatedUpcomingEvents(ctx, limit, offset, leagueID, sportID)
|
|
}
|
|
|
|
func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) {
|
|
return s.store.GetUpcomingEventByID(ctx, ID)
|
|
}
|
|
|
|
// func (s *service) GetAndStoreMatchResult(ctx context.Context, eventID string) error {
|
|
// url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%s", s.token, eventID)
|
|
|
|
// resp, err := http.Get(url)
|
|
// if err != nil {
|
|
// return fmt.Errorf("failed to fetch result: %w", err)
|
|
// }
|
|
// defer resp.Body.Close()
|
|
|
|
// body, _ := io.ReadAll(resp.Body)
|
|
|
|
// // Parse the API response
|
|
// var apiResp struct {
|
|
// Results []struct {
|
|
// ID string `json:"id"`
|
|
// Ss string `json:"ss"` // Full-time score
|
|
// Status string `json:"time_status"`
|
|
// } `json:"results"`
|
|
// }
|
|
|
|
// err = json.Unmarshal(body, &apiResp)
|
|
// if err != nil || len(apiResp.Results) == 0 {
|
|
// return fmt.Errorf("invalid response or no results found")
|
|
// }
|
|
|
|
// result := apiResp.Results[0]
|
|
|
|
// err = s.store.UpdateFinalScore(ctx, result.ID, result.Ss, result.Status)
|
|
// if err != nil {
|
|
// return fmt.Errorf("failed to update final score in database: %w", err)
|
|
// }
|
|
|
|
// return nil
|
|
// }
|