adding upcoming

This commit is contained in:
OneTap Technologies 2025-04-12 14:43:09 +03:00
parent ed0d107f1a
commit 1726cfd63b
6 changed files with 210 additions and 78 deletions

View File

@ -33,6 +33,35 @@ ON CONFLICT (id) DO UPDATE SET
is_live = EXCLUDED.is_live, is_live = EXCLUDED.is_live,
status = EXCLUDED.status, status = EXCLUDED.status,
fetched_at = now(); fetched_at = now();
-- name: InsertUpcomingEvent :exec
INSERT INTO events (
id, sport_id, match_name, home_team, away_team,
home_team_id, away_team_id, home_kit_image, away_kit_image,
league_id, league_name, league_cc, start_time,
is_live, status
) VALUES (
$1, $2, $3, $4, $5,
$6, $7, $8, $9,
$10, $11, $12, $13,
false, 'upcoming'
)
ON CONFLICT (id) DO UPDATE SET
sport_id = EXCLUDED.sport_id,
match_name = EXCLUDED.match_name,
home_team = EXCLUDED.home_team,
away_team = EXCLUDED.away_team,
home_team_id = EXCLUDED.home_team_id,
away_team_id = EXCLUDED.away_team_id,
home_kit_image = EXCLUDED.home_kit_image,
away_kit_image = EXCLUDED.away_kit_image,
league_id = EXCLUDED.league_id,
league_name = EXCLUDED.league_name,
league_cc = EXCLUDED.league_cc,
start_time = EXCLUDED.start_time,
is_live = false,
status = 'upcoming',
fetched_at = now();
-- name: ListLiveEvents :many -- name: ListLiveEvents :many
SELECT id FROM events WHERE is_live = true; SELECT id FROM events WHERE is_live = true;

View File

@ -97,6 +97,71 @@ func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) error
return err return err
} }
const InsertUpcomingEvent = `-- name: InsertUpcomingEvent :exec
INSERT INTO events (
id, sport_id, match_name, home_team, away_team,
home_team_id, away_team_id, home_kit_image, away_kit_image,
league_id, league_name, league_cc, start_time,
is_live, status
) VALUES (
$1, $2, $3, $4, $5,
$6, $7, $8, $9,
$10, $11, $12, $13,
false, 'upcoming'
)
ON CONFLICT (id) DO UPDATE SET
sport_id = EXCLUDED.sport_id,
match_name = EXCLUDED.match_name,
home_team = EXCLUDED.home_team,
away_team = EXCLUDED.away_team,
home_team_id = EXCLUDED.home_team_id,
away_team_id = EXCLUDED.away_team_id,
home_kit_image = EXCLUDED.home_kit_image,
away_kit_image = EXCLUDED.away_kit_image,
league_id = EXCLUDED.league_id,
league_name = EXCLUDED.league_name,
league_cc = EXCLUDED.league_cc,
start_time = EXCLUDED.start_time,
is_live = false,
status = 'upcoming',
fetched_at = now()
`
type InsertUpcomingEventParams struct {
ID string
SportID pgtype.Text
MatchName pgtype.Text
HomeTeam pgtype.Text
AwayTeam pgtype.Text
HomeTeamID pgtype.Text
AwayTeamID pgtype.Text
HomeKitImage pgtype.Text
AwayKitImage pgtype.Text
LeagueID pgtype.Text
LeagueName pgtype.Text
LeagueCc pgtype.Text
StartTime pgtype.Timestamp
}
func (q *Queries) InsertUpcomingEvent(ctx context.Context, arg InsertUpcomingEventParams) error {
_, err := q.db.Exec(ctx, InsertUpcomingEvent,
arg.ID,
arg.SportID,
arg.MatchName,
arg.HomeTeam,
arg.AwayTeam,
arg.HomeTeamID,
arg.AwayTeamID,
arg.HomeKitImage,
arg.AwayKitImage,
arg.LeagueID,
arg.LeagueName,
arg.LeagueCc,
arg.StartTime,
)
return err
}
const ListLiveEvents = `-- name: ListLiveEvents :many const ListLiveEvents = `-- name: ListLiveEvents :many
SELECT id FROM events WHERE is_live = true SELECT id FROM events WHERE is_live = true
` `

View File

@ -1,4 +1,7 @@
package domain package domain
import "time"
type Event struct { type Event struct {
ID string ID string
SportID string SportID string
@ -21,3 +24,18 @@ type Event struct {
IsLive bool IsLive bool
Status string Status string
} }
type UpcomingEvent struct {
ID string // Event ID
SportID string // Sport ID
MatchName string // Match or event name
HomeTeam string // Home team name (if available)
AwayTeam string // Away team name (can be empty/null)
HomeTeamID string // Home team ID
AwayTeamID string // Away team ID (can be empty/null)
HomeKitImage string // Kit or image for home team (optional)
AwayKitImage string // Kit or image for away team (optional)
LeagueID string // League ID
LeagueName string // League name
LeagueCC string // League country code
StartTime time.Time // Converted from "time" field in UNIX format
}

View File

@ -38,6 +38,23 @@ func (s *Store) SaveEvent(ctx context.Context, e domain.Event) error {
Status: pgtype.Text{String: e.Status, Valid: true}, Status: pgtype.Text{String: e.Status, Valid: true},
}) })
} }
func (s *Store) SaveUpcomingEvent(ctx context.Context, e domain.UpcomingEvent) error {
return s.queries.InsertUpcomingEvent(ctx, dbgen.InsertUpcomingEventParams{
ID: e.ID,
SportID: pgtype.Text{String: e.SportID, Valid: true},
MatchName: pgtype.Text{String: e.MatchName, Valid: true},
HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true},
AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true},
HomeTeamID: pgtype.Text{String: e.HomeTeamID, Valid: true},
AwayTeamID: pgtype.Text{String: e.AwayTeamID, Valid: true},
HomeKitImage: pgtype.Text{String: e.HomeKitImage, Valid: true},
AwayKitImage: pgtype.Text{String: e.AwayKitImage, Valid: true},
LeagueID: pgtype.Text{String: e.LeagueID, Valid: true},
LeagueName: pgtype.Text{String: e.LeagueName, Valid: true},
LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true},
StartTime: pgtype.Timestamp{Time: e.StartTime, Valid: true},
})
}
func (s *Store) GetLiveEventIDs(ctx context.Context) ([]string, error) { func (s *Store) GetLiveEventIDs(ctx context.Context) ([]string, error) {
return s.queries.ListLiveEvents(ctx) return s.queries.ListLiveEvents(ctx)

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strconv"
"sync" "sync"
"time" "time"
@ -96,65 +97,66 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
func (s *service) FetchUpcomingEvents(ctx context.Context) error { func (s *service) FetchUpcomingEvents(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} 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 { for _, sportID := range sportIDs {
wg.Add(1)
go func(sportID int) {
defer wg.Done()
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s", sportID, s.token) url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s", sportID, s.token)
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
fmt.Printf(" Failed request for upcoming sport_id=%d: %v\n", sportID, err) continue
return
} }
defer resp.Body.Close() defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body) body, _ := io.ReadAll(resp.Body)
var data struct { var data struct {
Success int `json:"success"` Success int `json:"success"`
Results [][]map[string]interface{} `json:"results"` Results []struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"home"`
Away *struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"away"`
} `json:"results"`
} }
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 { if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
fmt.Printf(" Decode failed for upcoming 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 continue
} }
event := domain.Event{ for _, ev := range data.Results {
ID: getString(ev["ID"]), startUnix, _ := strconv.ParseInt(ev.Time, 10, 64)
SportID: fmt.Sprintf("%d", sportID), event := domain.UpcomingEvent{
MatchName: getString(ev["NA"]), ID: ev.ID,
HomeTeamID: getString(ev["HT"]), SportID: ev.SportID,
AwayTeamID: getString(ev["AT"]), MatchName: ev.Home.Name,
HomeKitImage: getString(ev["K1"]), HomeTeam: ev.Home.Name,
AwayKitImage: getString(ev["K2"]), AwayTeam: "", // handle nil safely
LeagueID: getString(ev["C2"]), HomeTeamID: ev.Home.ID,
LeagueName: getString(ev["CT"]), AwayTeamID: "",
LeagueCC: getString(ev["CB"]), HomeKitImage: "",
StartTime: time.Now().UTC().Format(time.RFC3339), AwayKitImage: "",
IsLive: false, LeagueID: ev.League.ID,
Status: "upcoming", LeagueName: ev.League.Name,
LeagueCC: "",
StartTime: time.Unix(startUnix, 0).UTC(),
} }
if err := s.store.SaveEvent(ctx, event); err != nil { if ev.Away != nil {
fmt.Printf(" Could not store upcoming event [id=%s]: %v\n", event.ID, err) event.AwayTeam = ev.Away.Name
} event.AwayTeamID = ev.Away.ID
} }
}
}(sportID) _ = s.store.SaveUpcomingEvent(ctx, event)
}
} }
wg.Wait()
fmt.Println(" All upcoming events fetched and stored.")
return nil return nil
} }

View File

@ -18,7 +18,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
}{ }{
{ {
spec: "0 0 * * * *", // Every hour spec: "*/5 * * * * *", // Every 5 seconds
task: func() { task: func() {
if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
log.Printf("FetchUpcomingEvents error: %v", err) log.Printf("FetchUpcomingEvents error: %v", err)
@ -27,6 +27,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
}, },
{ {
spec: "*/5 * * * * *", // Every 5 seconds spec: "*/5 * * * * *", // Every 5 seconds
task: func() { task: func() {