odds and events fetch for bwin (together)
This commit is contained in:
parent
ec02497f97
commit
84bbe53bb7
|
|
@ -1,7 +1,6 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -15,10 +14,11 @@ type Market struct {
|
|||
MarketName string
|
||||
MarketID string
|
||||
UpdatedAt time.Time
|
||||
Odds []json.RawMessage
|
||||
Odds []map[string]interface{}
|
||||
Name string
|
||||
Handicap string
|
||||
OddsVal float64
|
||||
Source string
|
||||
}
|
||||
|
||||
type Odd struct {
|
||||
|
|
|
|||
|
|
@ -17,15 +17,19 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
for _, raw := range m.Odds {
|
||||
var item map[string]interface{}
|
||||
if err := json.Unmarshal(raw, &item); err != nil {
|
||||
continue
|
||||
}
|
||||
for _, item := range m.Odds {
|
||||
var name string
|
||||
var oddsVal float64
|
||||
|
||||
name := getString(item["name"])
|
||||
if m.Source == "bwin" {
|
||||
nameValue := getMap(item["name"])
|
||||
name = getString(nameValue["value"])
|
||||
oddsVal = getFloat(item["odds"])
|
||||
} else {
|
||||
name = getString(item["name"])
|
||||
oddsVal = getConvertedFloat(item["odds"])
|
||||
}
|
||||
handicap := getString(item["handicap"])
|
||||
oddsVal := getFloat(item["odds"])
|
||||
|
||||
rawOddsBytes, _ := json.Marshal(m.Odds)
|
||||
|
||||
|
|
@ -43,7 +47,7 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
|
|||
Category: pgtype.Text{Valid: false},
|
||||
RawOdds: rawOddsBytes,
|
||||
IsActive: pgtype.Bool{Bool: true, Valid: true},
|
||||
Source: pgtype.Text{String: "b365api", Valid: true},
|
||||
Source: pgtype.Text{String: m.Source, Valid: true},
|
||||
FetchedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
|
||||
}
|
||||
|
||||
|
|
@ -85,23 +89,6 @@ func writeFailedMarketLog(m domain.Market, err error) error {
|
|||
return writeErr
|
||||
}
|
||||
|
||||
func getString(v interface{}) string {
|
||||
if s, ok := v.(string); ok {
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getFloat(v interface{}) float64 {
|
||||
if s, ok := v.(string); ok {
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err == nil {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) {
|
||||
odds, err := s.queries.GetPrematchOdds(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -286,3 +273,34 @@ func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID stri
|
|||
|
||||
return domainOdds, nil
|
||||
}
|
||||
|
||||
func getString(v interface{}) string {
|
||||
if s, ok := v.(string); ok {
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getConvertedFloat(v interface{}) float64 {
|
||||
if s, ok := v.(string); ok {
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err == nil {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func getFloat(v interface{}) float64 {
|
||||
if n, ok := v.(float64); ok {
|
||||
return n
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func getMap(v interface{}) map[string]interface{} {
|
||||
if m, ok := v.(map[string]interface{}); ok {
|
||||
return m
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
|
|||
{"https://api.b365api.com/v1/bet365/inplay?sport_id=%d&&token=%s", "bet365"},
|
||||
{"https://api.b365api.com/v1/betfair/sb/inplay?sport_id=%d&token=%s", "betfair"},
|
||||
{"https://api.b365api.com/v1/1xbet/inplay?sport_id=%d&token=%s", "1xbet"},
|
||||
{"https://api.b365api.com/v1/bwin/inplay?sport_id=%d&token=%s", "bwin"},
|
||||
}
|
||||
|
||||
for _, url := range urls {
|
||||
|
|
@ -83,8 +82,6 @@ func (s *service) fetchLiveEvents(ctx context.Context, url, source string) error
|
|||
case "1xbet":
|
||||
// betfair and 1xbet have the same result structure
|
||||
events = handleBetfairprematch(body, sportID, source)
|
||||
case "bwin":
|
||||
events = handleBwinprematch(body, sportID, source)
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
|
|
@ -185,42 +182,6 @@ func handleBetfairprematch(body []byte, sportID int, source string) []domain.Eve
|
|||
return events
|
||||
}
|
||||
|
||||
func handleBwinprematch(body []byte, sportID int, source string) []domain.Event {
|
||||
var data struct {
|
||||
Success int `json:"success"`
|
||||
Results []map[string]interface{} `json:"results"`
|
||||
}
|
||||
|
||||
events := []domain.Event{}
|
||||
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
||||
fmt.Printf("%s: Decode failed for sport_id=%d\nRaw: %s\n", source, sportID, string(body))
|
||||
return events
|
||||
}
|
||||
|
||||
for _, ev := range data.Results {
|
||||
homeTeam := getString(ev["HomeTeam"])
|
||||
awayTeam := getString(ev["HomeTeam"])
|
||||
|
||||
event := domain.Event{
|
||||
ID: getString(ev["Id"]),
|
||||
SportID: fmt.Sprintf("%d", sportID),
|
||||
TimerStatus: "1",
|
||||
HomeTeam: homeTeam,
|
||||
AwayTeam: awayTeam,
|
||||
StartTime: time.Now().UTC().Format(time.RFC3339),
|
||||
LeagueID: getString(ev["LeagueId"]),
|
||||
LeagueName: getString(ev["LeagueName"]),
|
||||
IsLive: true,
|
||||
Status: "live",
|
||||
Source: source,
|
||||
}
|
||||
|
||||
events = append(events, event)
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
func (s *service) FetchUpcomingEvents(ctx context.Context) error {
|
||||
var wg sync.WaitGroup
|
||||
urls := []struct {
|
||||
|
|
@ -288,7 +249,7 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
|
|||
if !slices.Contains(domain.SupportedLeagues, leagueID) {
|
||||
// fmt.Printf("⚠️ Skipping league %s (%d) as it is not supported\n", ev.League.Name, leagueID)
|
||||
skippedLeague = append(skippedLeague, ev.League.Name)
|
||||
// continue
|
||||
continue
|
||||
}
|
||||
|
||||
event := domain.UpcomingEvent{
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
|
|
@ -35,6 +36,37 @@ func New(store *repository.Store, cfg *config.Config, logger *slog.Logger) *Serv
|
|||
|
||||
// TODO Add the optimization to get 10 events at the same time
|
||||
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||
var wg sync.WaitGroup
|
||||
errChan := make(chan error, 2)
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := s.fetchBet365Odds(ctx); err != nil {
|
||||
errChan <- fmt.Errorf("bet365 odds fetching error: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := s.fetchBwinOdds(ctx); err != nil {
|
||||
errChan <- fmt.Errorf("bwin odds fetching error: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
var errs []error
|
||||
for err := range errChan {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) fetchBet365Odds(ctx context.Context) error {
|
||||
eventIDs, err := s.store.GetAllUpcomingEvents(ctx)
|
||||
if err != nil {
|
||||
log.Printf("❌ Failed to fetch upcoming event IDs: %v", err)
|
||||
|
|
@ -107,6 +139,91 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) fetchBwinOdds(ctx context.Context) error {
|
||||
// getting odds for a specific event is not possible for bwin, most specific we can get is fetch odds on a single sport
|
||||
// so instead of having event and odds fetched separetly event will also be fetched along with the odds
|
||||
sportIds := []int{12, 7}
|
||||
for _, sportId := range sportIds {
|
||||
url := fmt.Sprintf("https://api.b365api.com/v1/bwin/prematch?sport_id=%d&token=%s", sportId, s.config.Bet365Token)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
log.Printf("❌ Failed to create request for sportId %d: %v", sportId, err)
|
||||
continue
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("❌ Failed to fetch request for sportId %d: %v", sportId, err)
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("❌ Failed to read response body for sportId %d: %v", sportId, err)
|
||||
continue
|
||||
}
|
||||
|
||||
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))
|
||||
continue
|
||||
}
|
||||
|
||||
for _, res := range data.Results {
|
||||
if getInt(res["Id"]) == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
event := domain.Event{
|
||||
ID: strconv.Itoa(getInt(res["Id"])),
|
||||
SportID: strconv.Itoa(getInt(res["SportId"])),
|
||||
LeagueID: strconv.Itoa(getInt(res["LeagueId"])),
|
||||
LeagueName: getString(res["Leaguename"]),
|
||||
HomeTeam: getString(res["HomeTeam"]),
|
||||
HomeTeamID: strconv.Itoa(getInt(res["HomeTeamId"])),
|
||||
AwayTeam: getString(res["AwayTeam"]),
|
||||
AwayTeamID: strconv.Itoa(getInt(res["AwayTeamId"])),
|
||||
StartTime: time.Now().UTC().Format(time.RFC3339),
|
||||
TimerStatus: "1",
|
||||
IsLive: true,
|
||||
Status: "live",
|
||||
Source: "bwin",
|
||||
}
|
||||
|
||||
if err := s.store.SaveEvent(ctx, event); err != nil {
|
||||
fmt.Printf("Could not store live event [id=%s]: %v\n", event.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, m := range getMapArray(res["Markets"]) {
|
||||
name := getMap(m["name"])
|
||||
marketName := getString(name["value"])
|
||||
|
||||
market := domain.Market{
|
||||
EventID: event.ID,
|
||||
MarketID: getString(m["id"]),
|
||||
MarketCategory: getString(m["category"]),
|
||||
MarketName: marketName,
|
||||
Source: "bwin",
|
||||
}
|
||||
|
||||
results := getMapArray(m["results"])
|
||||
market.Odds = results
|
||||
|
||||
s.store.SaveNonLiveMarket(ctx, market)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) parseFootball(ctx context.Context, res json.RawMessage) error {
|
||||
var footballRes domain.FootballOddsResponse
|
||||
if err := json.Unmarshal(res, &footballRes); err != nil {
|
||||
|
|
@ -264,6 +381,13 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
|
|||
continue
|
||||
}
|
||||
|
||||
marketOdds, err := convertRawMessage(market.Odds)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to conver json.RawMessage to []map[string]interface{} for market_id: ", market.ID)
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
marketRecord := domain.Market{
|
||||
EventID: eventID,
|
||||
FI: fi,
|
||||
|
|
@ -272,7 +396,9 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
|
|||
MarketName: market.Name,
|
||||
MarketID: marketIDstr,
|
||||
UpdatedAt: updatedAt,
|
||||
Odds: market.Odds,
|
||||
Odds: marketOdds,
|
||||
// bwin won't reach this code so bet365 is hardcoded for now
|
||||
Source: "bet365",
|
||||
}
|
||||
|
||||
err = s.store.SaveNonLiveMarket(ctx, marketRecord)
|
||||
|
|
@ -313,3 +439,49 @@ func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingI
|
|||
func (s *ServiceImpl) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset domain.ValidInt64) ([]domain.Odd, error) {
|
||||
return s.store.GetPaginatedPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset)
|
||||
}
|
||||
|
||||
func getString(v interface{}) string {
|
||||
if str, ok := v.(string); ok {
|
||||
return str
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getInt(v interface{}) int {
|
||||
if n, ok := v.(float64); ok {
|
||||
return int(n)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func getMap(v interface{}) map[string]interface{} {
|
||||
if m, ok := v.(map[string]interface{}); ok {
|
||||
return m
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMapArray(v interface{}) []map[string]interface{} {
|
||||
result := []map[string]interface{}{}
|
||||
if arr, ok := v.([]interface{}); ok {
|
||||
for _, item := range arr {
|
||||
if m, ok := item.(map[string]interface{}); ok {
|
||||
result = append(result, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func convertRawMessage(rawMessages []json.RawMessage) ([]map[string]interface{}, error) {
|
||||
var result []map[string]interface{}
|
||||
for _, raw := range rawMessages {
|
||||
var m map[string]interface{}
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, m)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user