From 11a70ec1dc99444fe12a7775d68fa23e1d1900be Mon Sep 17 00:00:00 2001 From: Asher Samuel Date: Fri, 23 May 2025 13:09:44 +0300 Subject: [PATCH] fetch events from betfair --- .vscode/settings.json | 4 +- cmd/main.go | 8 ++++ db/migrations/000001_fortune.up.sql | 3 +- db/query/events.sql | 18 +++++++-- gen/db/auth.sql.go | 2 +- gen/db/bet.sql.go | 2 +- gen/db/branch.sql.go | 2 +- gen/db/company.sql.go | 2 +- gen/db/copyfrom.go | 2 +- gen/db/db.go | 2 +- gen/db/events.sql.go | 32 +++++++++++++--- gen/db/models.go | 3 +- gen/db/notification.sql.go | 2 +- gen/db/odds.sql.go | 2 +- gen/db/otp.sql.go | 2 +- gen/db/referal.sql.go | 2 +- gen/db/result.sql.go | 2 +- gen/db/ticket.sql.go | 2 +- gen/db/transactions.sql.go | 2 +- gen/db/transfer.sql.go | 2 +- gen/db/user.sql.go | 2 +- gen/db/virtual_games.sql.go | 2 +- gen/db/wallet.sql.go | 2 +- internal/domain/event.go | 28 ++++++++++++++ internal/repository/event.go | 5 +++ internal/services/event/service.go | 57 ++++++++++++++--------------- 26 files changed, 134 insertions(+), 58 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index aa78ae8..b6d50b3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,7 @@ "Cashout", "narg", "sqlc" - ] + ], + "postman.settings.dotenv-detection-notification-visibility": false, + "makefile.configureOnOpen": false } \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index f6fd907..0b77b9e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -2,6 +2,7 @@ package main import ( // "context" + "context" "fmt" "log/slog" "os" @@ -69,6 +70,13 @@ func main() { userSvc := user.NewService(store, store, mockSms, mockEmail) eventSvc := event.New(cfg.Bet365Token, store) + // test start + logger.Info("test fetching...") + if err := eventSvc.FetchUpcomingEvents(context.TODO()); err != nil { + panic(err) + } + // test end + oddsSvc := odds.New(cfg.Bet365Token, store) resultSvc := result.NewService(store, cfg, logger) ticketSvc := ticket.NewService(store) diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 3cf6d3e..e34d21a 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -204,7 +204,8 @@ CREATE TABLE events ( match_period INT, is_live BOOLEAN, status TEXT, - fetched_at TIMESTAMP DEFAULT now() + fetched_at TIMESTAMP DEFAULT now(), + source TEXT DEFAULT 'b365api' ); CREATE TABLE odds ( id SERIAL PRIMARY KEY, diff --git a/db/query/events.sql b/db/query/events.sql index 4109c44..0a74840 100644 --- a/db/query/events.sql +++ b/db/query/events.sql @@ -19,7 +19,8 @@ INSERT INTO events ( added_time, match_period, is_live, - status + status, + source ) VALUES ( $1, @@ -41,7 +42,8 @@ VALUES ( $17, $18, $19, - $20 + $20, + $21 ) ON CONFLICT (id) DO UPDATE SET sport_id = EXCLUDED.sport_id, @@ -63,6 +65,7 @@ SET sport_id = EXCLUDED.sport_id, match_period = EXCLUDED.match_period, is_live = EXCLUDED.is_live, status = EXCLUDED.status, + source = EXCLUDED.source, fetched_at = now(); -- name: InsertUpcomingEvent :exec INSERT INTO events ( @@ -80,7 +83,8 @@ INSERT INTO events ( league_cc, start_time, is_live, - status + status, + source ) VALUES ( $1, @@ -97,7 +101,8 @@ VALUES ( $12, $13, false, - 'upcoming' + 'upcoming', + $14 ) ON CONFLICT (id) DO UPDATE SET sport_id = EXCLUDED.sport_id, @@ -114,6 +119,7 @@ SET sport_id = EXCLUDED.sport_id, start_time = EXCLUDED.start_time, is_live = false, status = 'upcoming', + source = EXCLUDED.source, fetched_at = now(); -- name: ListLiveEvents :many SELECT id @@ -135,6 +141,7 @@ SELECT id, start_time, is_live, status, + source, fetched_at FROM events WHERE is_live = false @@ -156,6 +163,7 @@ SELECT id, start_time, is_live, status, + source, fetched_at FROM events WHERE is_live = false @@ -191,6 +199,7 @@ SELECT id, start_time, is_live, status, + source, fetched_at FROM events WHERE is_live = false @@ -221,6 +230,7 @@ SELECT id, start_time, is_live, status, + source, fetched_at FROM events WHERE id = $1 diff --git a/gen/db/auth.sql.go b/gen/db/auth.sql.go index 527f25c..9c55b29 100644 --- a/gen/db/auth.sql.go +++ b/gen/db/auth.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: auth.sql package dbgen diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index e236690..84936ac 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: bet.sql package dbgen diff --git a/gen/db/branch.sql.go b/gen/db/branch.sql.go index cf16465..2be193a 100644 --- a/gen/db/branch.sql.go +++ b/gen/db/branch.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: branch.sql package dbgen diff --git a/gen/db/company.sql.go b/gen/db/company.sql.go index 13a1940..d0ea4e9 100644 --- a/gen/db/company.sql.go +++ b/gen/db/company.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: company.sql package dbgen diff --git a/gen/db/copyfrom.go b/gen/db/copyfrom.go index 900af58..1212253 100644 --- a/gen/db/copyfrom.go +++ b/gen/db/copyfrom.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: copyfrom.go package dbgen diff --git a/gen/db/db.go b/gen/db/db.go index d892683..84de07c 100644 --- a/gen/db/db.go +++ b/gen/db/db.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 package dbgen diff --git a/gen/db/events.sql.go b/gen/db/events.sql.go index 94315a7..63fe1cb 100644 --- a/gen/db/events.sql.go +++ b/gen/db/events.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: events.sql package dbgen @@ -37,6 +37,7 @@ SELECT id, start_time, is_live, status, + source, fetched_at FROM events WHERE is_live = false @@ -60,6 +61,7 @@ type GetAllUpcomingEventsRow struct { StartTime pgtype.Timestamp `json:"start_time"` IsLive pgtype.Bool `json:"is_live"` Status pgtype.Text `json:"status"` + Source pgtype.Text `json:"source"` FetchedAt pgtype.Timestamp `json:"fetched_at"` } @@ -88,6 +90,7 @@ func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]GetAllUpcomingEve &i.StartTime, &i.IsLive, &i.Status, + &i.Source, &i.FetchedAt, ); err != nil { return nil, err @@ -116,6 +119,7 @@ SELECT id, start_time, is_live, status, + source, fetched_at FROM events WHERE is_live = false @@ -140,6 +144,7 @@ type GetExpiredUpcomingEventsRow struct { StartTime pgtype.Timestamp `json:"start_time"` IsLive pgtype.Bool `json:"is_live"` Status pgtype.Text `json:"status"` + Source pgtype.Text `json:"source"` FetchedAt pgtype.Timestamp `json:"fetched_at"` } @@ -168,6 +173,7 @@ func (q *Queries) GetExpiredUpcomingEvents(ctx context.Context) ([]GetExpiredUpc &i.StartTime, &i.IsLive, &i.Status, + &i.Source, &i.FetchedAt, ); err != nil { return nil, err @@ -196,6 +202,7 @@ SELECT id, start_time, is_live, status, + source, fetched_at FROM events WHERE is_live = false @@ -235,6 +242,7 @@ type GetPaginatedUpcomingEventsRow struct { StartTime pgtype.Timestamp `json:"start_time"` IsLive pgtype.Bool `json:"is_live"` Status pgtype.Text `json:"status"` + Source pgtype.Text `json:"source"` FetchedAt pgtype.Timestamp `json:"fetched_at"` } @@ -268,6 +276,7 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat &i.StartTime, &i.IsLive, &i.Status, + &i.Source, &i.FetchedAt, ); err != nil { return nil, err @@ -323,6 +332,7 @@ SELECT id, start_time, is_live, status, + source, fetched_at FROM events WHERE id = $1 @@ -347,6 +357,7 @@ type GetUpcomingByIDRow struct { StartTime pgtype.Timestamp `json:"start_time"` IsLive pgtype.Bool `json:"is_live"` Status pgtype.Text `json:"status"` + Source pgtype.Text `json:"source"` FetchedAt pgtype.Timestamp `json:"fetched_at"` } @@ -369,6 +380,7 @@ func (q *Queries) GetUpcomingByID(ctx context.Context, id string) (GetUpcomingBy &i.StartTime, &i.IsLive, &i.Status, + &i.Source, &i.FetchedAt, ) return i, err @@ -395,7 +407,8 @@ INSERT INTO events ( added_time, match_period, is_live, - status + status, + source ) VALUES ( $1, @@ -417,7 +430,8 @@ VALUES ( $17, $18, $19, - $20 + $20, + $21 ) ON CONFLICT (id) DO UPDATE SET sport_id = EXCLUDED.sport_id, @@ -439,6 +453,7 @@ SET sport_id = EXCLUDED.sport_id, match_period = EXCLUDED.match_period, is_live = EXCLUDED.is_live, status = EXCLUDED.status, + source = EXCLUDED.source, fetched_at = now() ` @@ -463,6 +478,7 @@ type InsertEventParams struct { MatchPeriod pgtype.Int4 `json:"match_period"` IsLive pgtype.Bool `json:"is_live"` Status pgtype.Text `json:"status"` + Source pgtype.Text `json:"source"` } func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) error { @@ -487,6 +503,7 @@ func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) error arg.MatchPeriod, arg.IsLive, arg.Status, + arg.Source, ) return err } @@ -507,7 +524,8 @@ INSERT INTO events ( league_cc, start_time, is_live, - status + status, + source ) VALUES ( $1, @@ -524,7 +542,8 @@ VALUES ( $12, $13, false, - 'upcoming' + 'upcoming', + $14 ) ON CONFLICT (id) DO UPDATE SET sport_id = EXCLUDED.sport_id, @@ -541,6 +560,7 @@ SET sport_id = EXCLUDED.sport_id, start_time = EXCLUDED.start_time, is_live = false, status = 'upcoming', + source = EXCLUDED.source, fetched_at = now() ` @@ -558,6 +578,7 @@ type InsertUpcomingEventParams struct { LeagueName pgtype.Text `json:"league_name"` LeagueCc pgtype.Text `json:"league_cc"` StartTime pgtype.Timestamp `json:"start_time"` + Source pgtype.Text `json:"source"` } func (q *Queries) InsertUpcomingEvent(ctx context.Context, arg InsertUpcomingEventParams) error { @@ -575,6 +596,7 @@ func (q *Queries) InsertUpcomingEvent(ctx context.Context, arg InsertUpcomingEve arg.LeagueName, arg.LeagueCc, arg.StartTime, + arg.Source, ) return err } diff --git a/gen/db/models.go b/gen/db/models.go index 0cc5956..0460bec 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 package dbgen @@ -194,6 +194,7 @@ type Event struct { IsLive pgtype.Bool `json:"is_live"` Status pgtype.Text `json:"status"` FetchedAt pgtype.Timestamp `json:"fetched_at"` + Source pgtype.Text `json:"source"` } type Notification struct { diff --git a/gen/db/notification.sql.go b/gen/db/notification.sql.go index 5bfedd6..8b7a99c 100644 --- a/gen/db/notification.sql.go +++ b/gen/db/notification.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: notification.sql package dbgen diff --git a/gen/db/odds.sql.go b/gen/db/odds.sql.go index 3f920f4..e3f7e58 100644 --- a/gen/db/odds.sql.go +++ b/gen/db/odds.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: odds.sql package dbgen diff --git a/gen/db/otp.sql.go b/gen/db/otp.sql.go index 99cdd4c..7dba175 100644 --- a/gen/db/otp.sql.go +++ b/gen/db/otp.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: otp.sql package dbgen diff --git a/gen/db/referal.sql.go b/gen/db/referal.sql.go index d0ab21e..3a7f337 100644 --- a/gen/db/referal.sql.go +++ b/gen/db/referal.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: referal.sql package dbgen diff --git a/gen/db/result.sql.go b/gen/db/result.sql.go index cb3fdd8..bff7b1e 100644 --- a/gen/db/result.sql.go +++ b/gen/db/result.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: result.sql package dbgen diff --git a/gen/db/ticket.sql.go b/gen/db/ticket.sql.go index 054372d..8718bce 100644 --- a/gen/db/ticket.sql.go +++ b/gen/db/ticket.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: ticket.sql package dbgen diff --git a/gen/db/transactions.sql.go b/gen/db/transactions.sql.go index d3a7418..7d33130 100644 --- a/gen/db/transactions.sql.go +++ b/gen/db/transactions.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: transactions.sql package dbgen diff --git a/gen/db/transfer.sql.go b/gen/db/transfer.sql.go index 9bbf333..b9d2797 100644 --- a/gen/db/transfer.sql.go +++ b/gen/db/transfer.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: transfer.sql package dbgen diff --git a/gen/db/user.sql.go b/gen/db/user.sql.go index a595372..175e49f 100644 --- a/gen/db/user.sql.go +++ b/gen/db/user.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: user.sql package dbgen diff --git a/gen/db/virtual_games.sql.go b/gen/db/virtual_games.sql.go index eb832e7..16034ee 100644 --- a/gen/db/virtual_games.sql.go +++ b/gen/db/virtual_games.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: virtual_games.sql package dbgen diff --git a/gen/db/wallet.sql.go b/gen/db/wallet.sql.go index b3637f8..6deff3c 100644 --- a/gen/db/wallet.sql.go +++ b/gen/db/wallet.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: wallet.sql package dbgen diff --git a/internal/domain/event.go b/internal/domain/event.go index 2a10da5..53dc4d9 100644 --- a/internal/domain/event.go +++ b/internal/domain/event.go @@ -24,6 +24,33 @@ type Event struct { IsLive bool Status string } + +type BetResult struct { + Success int `json:"success"` + Pager struct { + Page int `json:"page"` + PerPage int `json:"per_page"` + Total int `json:"total"` + } + 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"` +} + type UpcomingEvent struct { ID string // Event ID SportID string // Sport ID @@ -38,6 +65,7 @@ type UpcomingEvent struct { LeagueName string // League name LeagueCC string // League country code StartTime time.Time // Converted from "time" field in UNIX format + Source string // bet api provider (bet365, betfair) } type MatchResult struct { EventID string diff --git a/internal/repository/event.go b/internal/repository/event.go index 630cd39..d09f3f1 100644 --- a/internal/repository/event.go +++ b/internal/repository/event.go @@ -57,6 +57,7 @@ func (s *Store) SaveUpcomingEvent(ctx context.Context, e domain.UpcomingEvent) e LeagueName: pgtype.Text{String: e.LeagueName, Valid: true}, LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true}, StartTime: pgtype.Timestamp{Time: e.StartTime, Valid: true}, + Source: pgtype.Text{String: e.Source, Valid: true}, }) } @@ -85,6 +86,7 @@ func (s *Store) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEven LeagueName: e.LeagueName.String, LeagueCC: e.LeagueCc.String, StartTime: e.StartTime.Time.UTC(), + Source: e.Source.String, } } return upcomingEvents, nil @@ -112,6 +114,7 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcoming LeagueName: e.LeagueName.String, LeagueCC: e.LeagueCc.String, StartTime: e.StartTime.Time.UTC(), + Source: e.Source.String, } } return upcomingEvents, nil @@ -151,6 +154,7 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit int32, off LeagueName: e.LeagueName.String, LeagueCC: e.LeagueCc.String, StartTime: e.StartTime.Time.UTC(), + Source: e.Source.String, } } totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{ @@ -190,6 +194,7 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Upc LeagueName: event.LeagueName.String, LeagueCC: event.LeagueCc.String, StartTime: event.StartTime.Time.UTC(), + Source: event.Source.String, }, nil } func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore, status string) error { diff --git a/internal/services/event/service.go b/internal/services/event/service.go index 70b4f98..5112834 100644 --- a/internal/services/event/service.go +++ b/internal/services/event/service.go @@ -99,6 +99,29 @@ func (s *service) FetchLiveEvents(ctx context.Context) error { } 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 @@ -109,8 +132,8 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error { time.Sleep(3 * time.Second) //This will restrict the fetching to 1200 requests per hour page = page + 1 - url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", sportID, s.token, page) - log.Printf("📡 Fetching data for event data page %d", page) + 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) @@ -119,31 +142,8 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error { defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) - var data struct { - Success int `json:"success"` - Pager struct { - Page int `json:"page"` - PerPage int `json:"per_page"` - Total int `json:"total"` - } - 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"` - } + var data domain.BetResult + if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 { continue } @@ -181,6 +181,7 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error { LeagueName: ev.League.Name, LeagueCC: "", StartTime: time.Unix(startUnix, 0).UTC(), + Source: source, } if ev.Away != nil { @@ -198,8 +199,6 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error { count++ } } - - return nil } func getString(v interface{}) string {