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

View File

@ -18,13 +18,14 @@ 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)
} }
}, },
}, },
{ {