package enetpulse import ( "context" "encoding/json" "fmt" "io" "net/http" "strconv" "strings" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/config" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" ) type Service struct { // username string // token string cfg config.Config store *repository.Store // mongoLogger *zap.Logger httpClient *http.Client } func New(cfg config.Config, store *repository.Store) *Service { return &Service{ cfg: cfg, store: store, // mongoLogger: mongoLogger, httpClient: &http.Client{ Timeout: 15 * time.Second, }, } } func (s *Service) FetchAndStoreSports(ctx context.Context) error { // 1️⃣ Compose URL with credentials url := fmt.Sprintf( "http://eapi.enetpulse.com/sport/list/?username=%s&token=%s", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, ) // 2️⃣ Create HTTP request with context req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return fmt.Errorf("creating sport request: %w", err) } // 3️⃣ Execute request resp, err := s.httpClient.Do(req) if err != nil { return fmt.Errorf("requesting sports: %w", err) } defer resp.Body.Close() // 4️⃣ Check response status if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("failed to fetch sports (status %d): %s", resp.StatusCode, string(body)) } // 5️⃣ Decode JSON response var sportsResp struct { Sports map[string]struct { ID string `json:"id"` N string `json:"n"` // updates count Name string `json:"name"` UT string `json:"ut"` // timestamp string } `json:"sports"` } if err := json.NewDecoder(resp.Body).Decode(&sportsResp); err != nil { return fmt.Errorf("decoding sports response: %w", err) } // 6️⃣ Iterate and store each sport for _, sport := range sportsResp.Sports { // Parse updates count updatesCount := 0 if sport.N != "" { if n, err := strconv.Atoi(sport.N); err == nil { updatesCount = n } } // Parse timestamp lastUpdatedAt, err := time.Parse(time.RFC3339, sport.UT) if err != nil { // Fallback to zero time if parsing fails lastUpdatedAt = time.Time{} } // Build domain object createSport := domain.CreateEnetpulseSport{ SportID: sport.ID, Name: sport.Name, UpdatesCount: updatesCount, LastUpdatedAt: lastUpdatedAt, Status: 1, // default active } // Insert or update in DB if _, err := s.store.CreateEnetpulseSport(ctx, createSport); err != nil { // Log error but continue // s.logger.Error("failed to store sport", zap.String("sport_id", sport.ID), zap.Error(err)) continue } } // s.logger.Info("Successfully fetched and stored sports", zap.Int("count", len(sportsResp.Sports))) return nil } func (s *Service) GetAllSports(ctx context.Context) ([]domain.EnetpulseSport, error) { // 1️⃣ Fetch all sports from the store (database) sports, err := s.store.GetAllEnetpulseSports(ctx) if err != nil { return nil, fmt.Errorf("failed to fetch sports from DB: %w", err) } // 2️⃣ Optionally, you can log the count or perform other transformations // s.logger.Info("Fetched sports from DB", zap.Int("count", len(sports))) return sports, nil } func (s *Service) FetchAndStoreTournamentTemplates(ctx context.Context) error { // 1️⃣ Fetch all sports from the database sports, err := s.store.GetAllEnetpulseSports(ctx) if err != nil { return fmt.Errorf("failed to fetch sports from DB: %w", err) } // Template struct type TournamentTemplate struct { ID string `json:"id"` Name string `json:"name"` SportFK string `json:"sportFK"` Gender string `json:"gender"` N string `json:"n"` UT string `json:"ut"` } for _, sport := range sports { if sport.SportID != "1" { continue } else { // 2️⃣ Compose URL for each sport using its Enetpulse sportFK url := fmt.Sprintf( "http://eapi.enetpulse.com/tournament_template/list/?sportFK=%s&username=%s&token=%s", sport.SportID, // must be Enetpulse sportFK s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, ) fmt.Println("Fetching tournament templates:", url) // 3️⃣ Create HTTP request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return fmt.Errorf("creating tournament template request for sport %s: %w", sport.SportID, err) } // 4️⃣ Execute request resp, err := s.httpClient.Do(req) if err != nil { return fmt.Errorf("requesting tournament templates for sport %s: %w", sport.SportID, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("failed to fetch tournament templates for sport %s (status %d): %s", sport.SportID, resp.StatusCode, string(body)) } // 5️⃣ Decode JSON response flexibly var raw struct { TournamentTemplates json.RawMessage `json:"tournament_templates"` } bodyBytes, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("reading tournament templates response for sport %s: %w", sport.SportID, err) } if err := json.Unmarshal(bodyBytes, &raw); err != nil { return fmt.Errorf("unmarshalling raw tournament templates for sport %s: %w", sport.SportID, err) } // 6️⃣ Parse depending on object or array templates := map[string]TournamentTemplate{} if len(raw.TournamentTemplates) > 0 && raw.TournamentTemplates[0] == '{' { // Object (normal case) if err := json.Unmarshal(raw.TournamentTemplates, &templates); err != nil { return fmt.Errorf("decoding tournament templates (object) for sport %s: %w", sport.SportID, err) } } else { // Array or empty → skip safely fmt.Printf("No tournament templates found for sport %s\n", sport.SportID) continue } // 7️⃣ Iterate and store each tournament template for _, tmpl := range templates { updatesCount := 0 if tmpl.N != "" { if n, err := strconv.Atoi(tmpl.N); err == nil { updatesCount = n } } lastUpdatedAt, err := time.Parse(time.RFC3339, tmpl.UT) if err != nil { lastUpdatedAt = time.Time{} } // Convert sport.SportID from string to int64 sportFK, err := strconv.ParseInt(sport.SportID, 10, 64) if err != nil { fmt.Printf("failed to convert sport.SportID '%s' to int64: %v\n", sport.SportID, err) continue } createTemplate := domain.CreateEnetpulseTournamentTemplate{ TemplateID: tmpl.ID, Name: tmpl.Name, SportFK: sportFK, // use DB sport ID internally Gender: tmpl.Gender, UpdatesCount: updatesCount, LastUpdatedAt: lastUpdatedAt, Status: 1, // default active } if _, err := s.store.CreateEnetpulseTournamentTemplate(ctx, createTemplate); err != nil { fmt.Printf("failed to store tournament template %s: %v\n", tmpl.ID, err) continue } } break } } // fmt.Println("✅ Successfully fetched and stored all tournament templates") return nil } func (s *Service) GetAllTournamentTemplates(ctx context.Context) ([]domain.EnetpulseTournamentTemplate, error) { // 1️⃣ Fetch all tournament templates from the store (database) templates, err := s.store.GetAllEnetpulseTournamentTemplates(ctx) if err != nil { return nil, fmt.Errorf("failed to fetch tournament templates from DB: %w", err) } // 2️⃣ Optionally, you can log the count or perform other transformations // s.logger.Info("Fetched tournament templates from DB", zap.Int("count", len(templates))) return templates, nil } func (s *Service) FetchAndStoreTournaments(ctx context.Context) error { // 1️⃣ Fetch all tournament templates from the database templates, err := s.store.GetAllEnetpulseTournamentTemplates(ctx) if err != nil { return fmt.Errorf("failed to fetch tournament templates from DB: %w", err) } for _, tmpl := range templates { // 2️⃣ Compose URL for each tournament template url := fmt.Sprintf( "http://eapi.enetpulse.com/tournament/list/?tournament_templateFK=%s&username=%s&token=%s", tmpl.TemplateID, s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, ) // 3️⃣ Create HTTP request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return fmt.Errorf("creating tournament request for template %s: %w", tmpl.TemplateID, err) } // 4️⃣ Execute request resp, err := s.httpClient.Do(req) if err != nil { return fmt.Errorf("requesting tournaments for template %s: %w", tmpl.TemplateID, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("failed to fetch tournaments for template %s (status %d): %s", tmpl.TemplateID, resp.StatusCode, string(body)) } // 5️⃣ Decode JSON response var tournamentsResp struct { Tournaments map[string]struct { ID string `json:"id"` Name string `json:"name"` TournamentTemplateFK string `json:"tournament_templateFK"` N string `json:"n"` // updates count UT string `json:"ut"` // timestamp } `json:"tournaments"` } if err := json.NewDecoder(resp.Body).Decode(&tournamentsResp); err != nil { return fmt.Errorf("decoding tournaments for template %s: %w", tmpl.TemplateID, err) } // 6️⃣ Iterate and store each tournament for _, t := range tournamentsResp.Tournaments { updatesCount := 0 if t.N != "" { if n, err := strconv.Atoi(t.N); err == nil { updatesCount = n } } lastUpdatedAt, err := time.Parse(time.RFC3339, t.UT) if err != nil { lastUpdatedAt = time.Time{} } createTournament := domain.CreateEnetpulseTournament{ TournamentID: t.ID, Name: t.Name, TournamentTemplateFK: tmpl.TemplateID, // DB ID of template UpdatesCount: updatesCount, LastUpdatedAt: lastUpdatedAt, Status: 1, // default active } // Insert into DB if _, err := s.store.CreateEnetpulseTournament(ctx, createTournament); err != nil { // Log error but continue // s.logger.Error("failed to store tournament", zap.String("tournament_id", t.ID), zap.Error(err)) continue } } } // s.logger.Info("Successfully fetched and stored all tournaments") return nil } func (s *Service) GetAllTournaments(ctx context.Context) ([]domain.EnetpulseTournament, error) { // 1️⃣ Fetch all tournaments from the store (database) tournaments, err := s.store.GetAllEnetpulseTournaments(ctx) if err != nil { return nil, fmt.Errorf("failed to fetch tournaments from DB: %w", err) } // 2️⃣ Optionally log count or do transformations // s.logger.Info("Fetched tournaments from DB", zap.Int("count", len(tournaments))) return tournaments, nil } func (s *Service) FetchAndStoreTournamentStages(ctx context.Context) error { // 1️⃣ Get all tournaments from DB tournaments, err := s.store.GetAllEnetpulseTournaments(ctx) if err != nil { return fmt.Errorf("failed to fetch tournaments from DB: %w", err) } // 2️⃣ Loop through each tournament for _, t := range tournaments { // Compose URL for each tournament url := fmt.Sprintf( "https://eapi.enetpulse.com/tournament_stage/list/?language_typeFK=3&tz=Europe/Sofia&tournamentFK=%s&username=%s&token=%s", t.TournamentID, s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, ) // 3️⃣ Create HTTP request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { // log and skip // s.logger.Error("creating tournament stages request", zap.String("tournament_id", t.TournamentID), zap.Error(err)) continue } // 4️⃣ Execute request resp, err := s.httpClient.Do(req) if err != nil { // s.logger.Error("requesting tournament stages", zap.String("tournament_id", t.TournamentID), zap.Error(err)) continue } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) // s.logger.Error("unexpected status code fetching stages", zap.String("tournament_id", t.TournamentID), zap.Int("status", resp.StatusCode)) _ = body continue } // 5️⃣ Decode JSON response (based on EnetPulse response format) var stagesResp struct { Stages map[string]struct { ID string `json:"id"` Name string `json:"name"` TournamentFK string `json:"tournamentFK"` N string `json:"n"` // updates count UT string `json:"ut"` // timestamp } `json:"tournament_stages"` } if err := json.NewDecoder(resp.Body).Decode(&stagesResp); err != nil { // s.logger.Error("decoding tournament stages", zap.String("tournament_id", t.TournamentID), zap.Error(err)) continue } // 6️⃣ Iterate and store each stage for _, st := range stagesResp.Stages { updatesCount := 0 if st.N != "" { if n, err := strconv.Atoi(st.N); err == nil { updatesCount = n } } lastUpdatedAt, err := time.Parse(time.RFC3339, st.UT) if err != nil { lastUpdatedAt = time.Time{} } createStage := domain.CreateEnetpulseTournamentStage{ StageID: st.ID, Name: st.Name, TournamentFK: t.TournamentID, // DB ID of the tournament UpdatesCount: updatesCount, LastUpdatedAt: lastUpdatedAt, Status: 1, } // Insert into DB if _, err := s.store.CreateEnetpulseTournamentStage(ctx, createStage); err != nil { // s.logger.Error("failed to store tournament stage", zap.String("stage_id", st.ID), zap.Error(err)) continue } } } // s.logger.Info("Successfully fetched and stored all tournament stages for all tournaments") return nil } func (s *Service) GetAllTournamentStages(ctx context.Context) ([]domain.EnetpulseTournamentStage, error) { // 1️⃣ Fetch all tournament stages from the store (database) stages, err := s.store.GetAllEnetpulseTournamentStages(ctx) if err != nil { return nil, fmt.Errorf("failed to fetch tournament stages from DB: %w", err) } // 2️⃣ Optionally log count or perform transformations // s.logger.Info("Fetched tournament stages from DB", zap.Int("count", len(stages))) return stages, nil } func (s *Service) FetchAndStoreFixtures(ctx context.Context, date string) error { // 1️⃣ Fetch all sports from DB sports, err := s.store.GetAllEnetpulseSports(ctx) if err != nil { return fmt.Errorf("failed to fetch sports from DB: %w", err) } // Define API fixture struct type Fixture struct { FixtureID string `json:"id"` Name string `json:"name"` SportFK string `json:"sportFK"` TournamentFK string `json:"tournamentFK"` TournamentTemplateFK string `json:"tournament_templateFK"` TournamentStageFK string `json:"tournament_stageFK"` TournamentStageName string `json:"tournament_stage_name"` TournamentName string `json:"tournament_name"` TournamentTemplateName string `json:"tournament_template_name"` SportName string `json:"sport_name"` Gender string `json:"gender"` StartDate string `json:"startdate"` // ISO 8601 StatusType string `json:"status_type"` StatusDescFK string `json:"status_descFK"` RoundTypeFK string `json:"round_typeFK"` UpdatesCount string `json:"n"` // convert to int LastUpdatedAt string `json:"ut"` // parse to time.Time } // 2️⃣ Loop through each sport for _, sport := range sports { if sport.SportID != "1" { continue } url := fmt.Sprintf( "https://eapi.enetpulse.com/event/fixtures/?username=%s&token=%s&sportFK=%s&language_typeFK=3&date=%s", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, sport.SportID, date, ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { fmt.Printf("creating request for sport %s: %v\n", sport.SportID, err) continue } resp, err := s.httpClient.Do(req) if err != nil { fmt.Printf("requesting fixtures for sport %s: %v\n", sport.SportID, err) continue } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) fmt.Printf("failed to fetch fixtures for sport %s (status %d): %s\n", sport.SportID, resp.StatusCode, string(body)) continue } // 3️⃣ Decode API response var fixturesResp struct { Events map[string]Fixture `json:"events"` } if err := json.NewDecoder(resp.Body).Decode(&fixturesResp); err != nil { fmt.Printf("decoding fixtures for sport %s: %v\n", sport.SportID, err) continue } // 4️⃣ Iterate and upsert fixtures for _, fx := range fixturesResp.Events { // Parse StartDate and LastUpdatedAt startDate, err := time.Parse(time.RFC3339, fx.StartDate) if err != nil { fmt.Printf("invalid startDate for fixture %s: %v\n", fx.FixtureID, err) continue } lastUpdated, err := time.Parse(time.RFC3339, fx.LastUpdatedAt) if err != nil { fmt.Printf("invalid lastUpdatedAt for fixture %s: %v\n", fx.FixtureID, err) continue } // Convert UpdatesCount updatesCount, err := strconv.Atoi(fx.UpdatesCount) if err != nil { fmt.Printf("invalid updatesCount for fixture %s: %v\n", fx.FixtureID, err) updatesCount = 0 } fixture := domain.CreateEnetpulseFixture{ FixtureID: fx.FixtureID, Name: fx.Name, SportFK: fx.SportFK, TournamentFK: fx.TournamentFK, TournamentTemplateFK: fx.TournamentTemplateFK, TournamentStageFK: fx.TournamentStageFK, TournamentStageName: fx.TournamentStageName, TournamentName: fx.TournamentName, TournamentTemplateName: fx.TournamentTemplateName, SportName: fx.SportName, Gender: fx.Gender, StartDate: startDate, StatusType: fx.StatusType, StatusDescFK: fx.StatusDescFK, RoundTypeFK: fx.RoundTypeFK, UpdatesCount: updatesCount, LastUpdatedAt: lastUpdated, } // 5️⃣ Save fixture using UPSERT repository method if _, err := s.store.CreateEnetpulseFixture(ctx, fixture); err != nil { fmt.Printf("failed upserting fixture %s: %v\n", fx.FixtureID, err) continue } } fmt.Printf("✅ Successfully fetched and stored fixtures for sport %s\n", sport.SportID) break } return nil } func (s *Service) GetAllFixtures(ctx context.Context) ([]domain.EnetpulseFixture, error) { // 1️⃣ Fetch all from store fixtures, err := s.store.GetAllEnetpulseFixtures(ctx) if err != nil { return nil, fmt.Errorf("failed to fetch fixtures from DB: %w", err) } return fixtures, nil } func (s *Service) FetchAndStoreResults(ctx context.Context) error { sports, err := s.store.GetAllEnetpulseSports(ctx) if err != nil { return fmt.Errorf("failed to fetch sports from DB: %w", err) } for _, sport := range sports { if sport.SportID != "1" { continue } today := time.Now().Format("2006-01-02") url := fmt.Sprintf( "http://eapi.enetpulse.com/event/results/?sportFK=%s&date=%s&username=%s&token=%s", sport.SportID, today, s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, ) fmt.Println("Fetching results:", url) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return fmt.Errorf("creating results request for sport %s: %w", sport.SportID, err) } resp, err := s.httpClient.Do(req) if err != nil { return fmt.Errorf("requesting results for sport %s: %w", sport.SportID, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("failed to fetch results for sport %s (status %d): %s", sport.SportID, resp.StatusCode, string(body)) } var data struct { Events []struct { ID string `json:"id"` Name string `json:"name"` SportFK string `json:"sportFK"` TournamentFK string `json:"tournamentFK"` TournamentTemplateFK string `json:"tournament_templateFK"` TournamentStageFK string `json:"tournament_stageFK"` TournamentStageName string `json:"tournament_stage_name"` TournamentName string `json:"tournament_name"` TournamentTemplateName string `json:"tournament_template_name"` SportName string `json:"sport_name"` StartDate string `json:"startdate"` StatusType string `json:"status_type"` StatusDescFK string `json:"status_descFK"` RoundTypeFK string `json:"round_typeFK"` N string `json:"n"` UT string `json:"ut"` Property map[string]struct { ID string `json:"id"` Type string `json:"type"` Name string `json:"name"` Value string `json:"value"` N string `json:"n"` UT string `json:"ut"` } `json:"property"` EventParticipants map[string]struct { ID string `json:"id"` Number string `json:"number"` ParticipantFK string `json:"participantFK"` EventFK string `json:"eventFK"` Result map[string]struct { ID string `json:"id"` EventParticipantsFK string `json:"event_participantsFK"` ResultTypeFK string `json:"result_typeFK"` ResultCode string `json:"result_code"` Value string `json:"value"` N string `json:"n"` UT string `json:"ut"` } `json:"result"` Participant struct { ID string `json:"id"` Name string `json:"name"` Gender string `json:"gender"` Type string `json:"type"` CountryFK string `json:"countryFK"` CountryName string `json:"country_name"` } `json:"participant"` } `json:"event_participants"` } `json:"events"` } bodyBytes, _ := io.ReadAll(resp.Body) if err := json.Unmarshal(bodyBytes, &data); err != nil { return fmt.Errorf("decoding results failed: %w", err) } for _, event := range data.Events { // 1️⃣ Create result record lastUpdatedAt, _ := time.Parse(time.RFC3339, event.UT) startDate, _ := time.Parse(time.RFC3339, event.StartDate) createResult := domain.CreateEnetpulseResult{ ResultID: event.ID, Name: event.Name, SportFK: event.SportFK, TournamentFK: event.TournamentFK, TournamentTemplateFK: event.TournamentTemplateFK, TournamentStageName: event.TournamentStageName, TournamentName: event.TournamentName, TournamentTemplateName: event.TournamentTemplateName, SportName: event.SportName, StartDate: startDate, StatusType: event.StatusType, StatusDescFK: event.StatusDescFK, RoundTypeFK: event.RoundTypeFK, LastUpdatedAt: lastUpdatedAt, } if _, err := s.store.CreateEnetpulseResult(ctx, createResult); err != nil { fmt.Printf("❌ failed to store result %s: %v\n", event.ID, err) continue } // 2️⃣ Create referees (type == "ref:participant") for _, prop := range event.Property { if strings.HasPrefix(prop.Type, "ref:participant") { refCreatedAt, _ := time.Parse(time.RFC3339, prop.UT) ref := domain.CreateEnetpulseResultReferee{ ResultFk: event.ID, RefereeFk: prop.Value, LastUpdatedAt: refCreatedAt, } if _, err := s.store.CreateEnetpulseResultReferee(ctx, ref); err != nil { fmt.Printf("⚠️ failed to create referee %s: %v\n", prop.Name, err) } } } // 3️⃣ Create participants + their results for _, ep := range event.EventParticipants { p := domain.CreateEnetpulseResultParticipant{ ParticipantMapID: ep.ID, ResultFk: ep.EventFK, ParticipantFk: ep.ParticipantFK, Name: ep.Participant.Name, CountryFk: ep.Participant.CountryFK, CountryName: ep.Participant.CountryName, } if _, err := s.store.CreateEnetpulseResultParticipant(ctx, p); err != nil { fmt.Printf("⚠️ failed to create participant %s: %v\n", ep.Participant.Name, err) continue } } } break // stop after the first sport (football) } fmt.Println("✅ Successfully fetched and stored EnetPulse results + participants + referees") return nil } func (s *Service) GetAllResults(ctx context.Context) ([]domain.EnetpulseResult, error) { results, err := s.store.GetAllEnetpulseResults(ctx) if err != nil { return nil, fmt.Errorf("failed to fetch results from DB: %w", err) } fmt.Printf("✅ Retrieved %d results from DB\n", len(results)) return results, nil } // FetchAndStoreOutcomeTypes fetches outcome types from EnetPulse API and stores them in the DB. func (s *Service) FetchAndStoreOutcomeTypes(ctx context.Context) error { // 1️⃣ Compose EnetPulse API URL url := fmt.Sprintf( "http://eapi.enetpulse.com/static/outcome_type/?language_typeFK=3&username=%s&token=%s", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, ) // 2️⃣ Create HTTP request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return fmt.Errorf("failed to create outcome types request: %w", err) } // 3️⃣ Execute request resp, err := s.httpClient.Do(req) if err != nil { return fmt.Errorf("failed to call EnetPulse outcome_type API: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("unexpected status %d fetching outcome types: %s", resp.StatusCode, string(body)) } // 4️⃣ Decode JSON response var outcomeResp struct { OutcomeTypes map[string]struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` N string `json:"n"` UT string `json:"ut"` } `json:"outcome_type"` } if err := json.NewDecoder(resp.Body).Decode(&outcomeResp); err != nil { return fmt.Errorf("failed to decode outcome types JSON: %w", err) } // 5️⃣ Iterate and store each outcome type for _, ot := range outcomeResp.OutcomeTypes { updatesCount := 0 if ot.N != "" { if n, err := strconv.Atoi(ot.N); err == nil { updatesCount = n } } lastUpdatedAt, err := time.Parse(time.RFC3339, ot.UT) if err != nil { lastUpdatedAt = time.Time{} } createOutcome := domain.CreateEnetpulseOutcomeType{ OutcomeTypeID: ot.ID, Name: ot.Name, Description: ot.Description, UpdatesCount: int32(updatesCount), LastUpdatedAt: lastUpdatedAt, } // 6️⃣ Save to DB (upsert) if _, err := s.store.CreateEnetpulseOutcomeType(ctx, createOutcome); err != nil { // Optionally log the failure, continue to next continue } } // s.logger.Info("✅ Successfully fetched and stored all EnetPulse outcome types") return nil } // GetAllOutcomeTypes retrieves all stored outcome types from the DB. func (s *Service) GetAllOutcomeTypes(ctx context.Context) ([]domain.EnetpulseOutcomeType, error) { outcomes, err := s.store.GetAllEnetpulseOutcomeTypes(ctx) if err != nil { return nil, fmt.Errorf("failed to fetch outcome types from DB: %w", err) } // s.logger.Info("✅ Fetched outcome types from DB", zap.Int("count", len(outcomes))) return outcomes, nil } func (s *Service) FetchAndStorePreodds(ctx context.Context) error { // 1️⃣ Fetch all fixtures fixtures, err := s.store.GetAllEnetpulseFixtures(ctx) if err != nil { return fmt.Errorf("failed to fetch fixtures: %w", err) } // 2️⃣ Fetch all outcome types outcomeTypes, err := s.store.GetAllEnetpulseOutcomeTypes(ctx) if err != nil { return fmt.Errorf("failed to fetch outcome types: %w", err) } // 3️⃣ Loop through each fixture for _, fixture := range fixtures { // 4️⃣ Loop through each outcome type for _, outcome := range outcomeTypes { url := fmt.Sprintf( "http://eapi.enetpulse.com/preodds/event/?objectFK=%s&odds_providerFK=%s&outcome_typeFK=%s&username=%s&token=%s", fixture.FixtureID, s.cfg.EnetPulseConfig.ProviderID, outcome.OutcomeTypeID, s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { continue } resp, err := s.httpClient.Do(req) if err != nil { continue } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { continue } var preoddsResp struct { Preodds map[string]struct { ID string `json:"id"` OutcomeTypeFK string `json:"outcome_typeFK"` OutcomeScopeFK string `json:"outcome_scopeFK"` OutcomeSubtypeFK string `json:"outcome_subtypeFK"` EventParticipantNumber string `json:"event_participant_number"` Iparam string `json:"iparam"` Iparam2 string `json:"iparam2"` Dparam string `json:"dparam"` Dparam2 string `json:"dparam2"` Sparam string `json:"sparam"` N string `json:"n"` UT string `json:"ut"` BettingOffers []struct { ID string `json:"id"` BettingOfferStatusFK int32 `json:"bettingoffer_status_fk"` OddsProviderFK int32 `json:"odds_provider_fk"` Odds float64 `json:"odds"` OddsOld float64 `json:"odds_old"` Active string `json:"active"` CouponKey string `json:"coupon_key"` N string `json:"n"` UT string `json:"ut"` } `json:"bettingoffers"` } `json:"preodds"` } if err := json.NewDecoder(resp.Body).Decode(&preoddsResp); err != nil { continue } for _, p := range preoddsResp.Preodds { updatesCount := 0 if p.N != "" { if n, err := strconv.Atoi(p.N); err == nil { updatesCount = n } } lastUpdatedAt, _ := time.Parse(time.RFC3339, p.UT) eventParticipantNumber := int32(0) if p.EventParticipantNumber != "" { if epn, err := strconv.Atoi(p.EventParticipantNumber); err == nil { eventParticipantNumber = int32(epn) } } createPreodds := domain.CreateEnetpulsePreodds{ PreoddsID: p.ID, EventFK: fixture.FixtureID, OutcomeTypeFK: outcome.OutcomeTypeID, OutcomeScopeFK: string(p.OutcomeScopeFK), OutcomeSubtypeFK: string(p.OutcomeSubtypeFK), EventParticipantNumber: int(eventParticipantNumber), IParam: p.Iparam, IParam2: p.Iparam2, DParam: p.Dparam, DParam2: p.Dparam2, SParam: p.Sparam, UpdatesCount: int(updatesCount), LastUpdatedAt: lastUpdatedAt, } fmt.Printf("\n\nPreodds are:%v\n\n", createPreodds) storedPreodds, err := s.store.CreateEnetpulsePreodds(ctx, createPreodds) if err != nil { continue } for _, o := range p.BettingOffers { bettingUpdates := 0 if o.N != "" { if n, err := strconv.Atoi(o.N); err == nil { bettingUpdates = n } } bettingLastUpdatedAt, _ := time.Parse(time.RFC3339, o.UT) createOffer := domain.CreateEnetpulsePreoddsBettingOffer{ BettingOfferID: o.ID, PreoddsFK: storedPreodds.PreoddsID, BettingOfferStatusFK: o.BettingOfferStatusFK, OddsProviderFK: o.OddsProviderFK, Odds: o.Odds, OddsOld: o.OddsOld, Active: o.Active, CouponKey: o.CouponKey, UpdatesCount: int(bettingUpdates), LastUpdatedAt: bettingLastUpdatedAt, } _, _ = s.store.CreateEnetpulsePreoddsBettingOffer(ctx, createOffer) } } } } return nil } // helper function to parse string to int32 safely func ParseStringToInt32(s string) int32 { if s == "" { return 0 } i, _ := strconv.Atoi(s) return int32(i) } func (s *Service) GetAllPreodds(ctx context.Context) ([]domain.EnetpulsePreodds, error) { preodds, err := s.store.GetAllEnetpulsePreodds(ctx) if err != nil { return nil, fmt.Errorf("failed to fetch preodds from DB: %w", err) } fmt.Printf("\n\nFetched Preodds are:%v\n\n", preodds) return preodds, nil } // FetchAndStoreBettingOffers fetches betting offers from EnetPulse API and stores them in the DB. func (s *Service) StoreBettingOffers(ctx context.Context, preoddsID string, oddsProviderIDs []int32) error { // 1️⃣ Compose API URL providers := make([]string, len(oddsProviderIDs)) for i, p := range oddsProviderIDs { providers[i] = strconv.Itoa(int(p)) } url := fmt.Sprintf( "http://eapi.enetpulse.com/preodds_bettingoffer/?preoddsFK=%s&odds_providerFK=%s&username=%s&token=%s", preoddsID, strings.Join(providers, ","), s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, ) // 2️⃣ Create HTTP request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return fmt.Errorf("failed to create betting offer request: %w", err) } // 3️⃣ Execute request resp, err := s.httpClient.Do(req) if err != nil { return fmt.Errorf("failed to call EnetPulse betting offer API: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("unexpected status %d fetching betting offers: %s", resp.StatusCode, string(body)) } // 4️⃣ Decode JSON response var offerResp struct { BettingOffers map[string]struct { ID string `json:"id"` PreoddsFK string `json:"preodds_fk"` BettingOfferStatusFK int32 `json:"bettingoffer_status_fk"` OddsProviderFK int32 `json:"odds_provider_fk"` Odds float64 `json:"odds"` OddsOld float64 `json:"odds_old"` Active string `json:"active"` CouponKey string `json:"coupon_key"` N string `json:"n"` UT string `json:"ut"` } `json:"bettingoffer"` } if err := json.NewDecoder(resp.Body).Decode(&offerResp); err != nil { return fmt.Errorf("failed to decode betting offers JSON: %w", err) } // 5️⃣ Iterate and store each betting offer for _, o := range offerResp.BettingOffers { updatesCount := 0 if o.N != "" { if n, err := strconv.Atoi(o.N); err == nil { updatesCount = n } } lastUpdatedAt, err := time.Parse(time.RFC3339, o.UT) if err != nil { lastUpdatedAt = time.Time{} } createOffer := domain.CreateEnetpulsePreoddsBettingOffer{ BettingOfferID: o.ID, PreoddsFK: preoddsID, BettingOfferStatusFK: o.BettingOfferStatusFK, OddsProviderFK: o.OddsProviderFK, Odds: o.Odds, OddsOld: o.OddsOld, Active: o.Active, CouponKey: o.CouponKey, UpdatesCount: int(updatesCount), LastUpdatedAt: lastUpdatedAt, } // 6️⃣ Save to DB if _, err := s.store.CreateEnetpulsePreoddsBettingOffer(ctx, createOffer); err != nil { // optionally log the failure and continue continue } } return nil } // GetAllBettingOffers retrieves all stored betting offers from the DB. func (s *Service) GetAllBettingOffers(ctx context.Context) ([]domain.EnetpulsePreoddsBettingOffer, error) { offers, err := s.store.GetAllEnetpulsePreoddsBettingOffers(ctx) if err != nil { return nil, fmt.Errorf("failed to fetch betting offers from DB: %w", err) } return offers, nil } func (s *Service) GetFixturesWithPreodds(ctx context.Context) ([]domain.EnetpulseFixtureWithPreodds, error) { // 1️⃣ Fetch fixtures and their associated preodds from the repository fixtures, err := s.store.GetFixturesWithPreodds(ctx) if err != nil { return nil, fmt.Errorf("failed to fetch fixtures with preodds from DB: %w", err) } return fixtures, nil } func (s *Service) FetchTournamentTemplates(ctx context.Context) (*domain.TournamentTemplatesResponse, error) { url := fmt.Sprintf( "http://eapi.enetpulse.com/tournamenttemplate/list/?username=%s&token=%s", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating tournament template request: %w", err) } resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting tournament templates: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("failed to fetch tournament templates: %s, body: %s", resp.Status, string(body)) } var templatesResp domain.TournamentTemplatesResponse if err := json.NewDecoder(resp.Body).Decode(&templatesResp); err != nil { return nil, fmt.Errorf("decoding tournament templates response: %w", err) } // Optionally save to DB or cache return &templatesResp, nil } func (s *Service) FetchTournamentTemplateParticipants(ctx context.Context, templateID string, opts domain.ParticipantsOptions) (*domain.TournamentTemplateParticipantsResponse, error) { // Build URL with optional parameters url := fmt.Sprintf( "http://eapi.enetpulse.com/tournamenttemplate/participants/?username=%s&token=%s&id=%s", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, templateID, ) // Append optional params if set if opts.IncludeProperties { url += "&includeProperties=yes" } if opts.IncludeParticipantProperties { url += "&includeParticipantProperties=yes" } if opts.IncludeParticipantSports { url += "&includeParticipantSports=yes" } if opts.IncludeCountries { url += "&includeCountries=yes" } if opts.IncludeCountryCodes { url += "&includeCountryCodes=yes" } if opts.ParticipantType != "" { url += "&participantType=" + opts.ParticipantType } req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating tournament participants request: %w", err) } resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting tournament participants: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("failed to fetch tournament participants: %s, body: %s", resp.Status, string(body)) } var participantsResp domain.TournamentTemplateParticipantsResponse if err := json.NewDecoder(resp.Body).Decode(&participantsResp); err != nil { return nil, fmt.Errorf("decoding tournament participants response: %w", err) } // Optionally save to DB or cache return &participantsResp, nil } func (s *Service) FetchTournaments(ctx context.Context, templateID string) error { url := fmt.Sprintf( "http://eapi.enetpulse.com/tournament/list/?tournament_templateFK=%s&username=%s&token=%s", templateID, s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return fmt.Errorf("creating tournament request: %w", err) } resp, err := s.httpClient.Do(req) if err != nil { return fmt.Errorf("requesting tournaments: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("failed to fetch tournaments (status %d): %s", resp.StatusCode, string(body)) } var tournamentsResp domain.TournamentsResponse if err := json.NewDecoder(resp.Body).Decode(&tournamentsResp); err != nil { return fmt.Errorf("decoding tournaments response: %w", err) } // Example: save tournaments to store or log them for _, tournament := range tournamentsResp.Tournaments { fmt.Printf("Tournament ID=%s Name=%s TemplateFK=%s UpdatedAt=%s\n", tournament.ID, tournament.Name, tournament.TournamentTemplateFK, tournament.UT) // e.g. s.store.SaveTournament(ctx, tournament) } return nil } func (s *Service) FetchTournamentParticipants(ctx context.Context, tournamentID string) error { url := fmt.Sprintf( "http://eapi.enetpulse.com/tournament_stage/participants/?id=%s&participantType=team&username=%s&token=%s", tournamentID, s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return fmt.Errorf("creating tournament participants request: %w", err) } resp, err := s.httpClient.Do(req) if err != nil { return fmt.Errorf("requesting tournament participants: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("failed to fetch tournament participants (status %d): %s", resp.StatusCode, string(body)) } var participantsResp domain.TournamentParticipantsResponse if err := json.NewDecoder(resp.Body).Decode(&participantsResp); err != nil { return fmt.Errorf("decoding tournament participants response: %w", err) } // Example: loop participants for tid, t := range participantsResp.Tournaments { fmt.Printf("Tournament ID=%s Name=%s has %d participants\n", tid, t.Name, len(t.Participants)) // You can loop deeper into t.Participants and store } return nil } func (s *Service) FetchTournamentStages(ctx context.Context, tournamentFK string) (*domain.TournamentStagesResponse, error) { url := fmt.Sprintf( "http://eapi.enetpulse.com/tournament_stage/list/?language_typeFK=3&tz=Europe/Sofia&tournamentFK=%s&username=%s&token=%s", tournamentFK, s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating tournament stages request: %w", err) } resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting tournament stages: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(body)) } var stagesResp domain.TournamentStagesResponse if err := json.NewDecoder(resp.Body).Decode(&stagesResp); err != nil { return nil, fmt.Errorf("decoding tournament stages response: %w", err) } // optionally save to DB or cache here return &stagesResp, nil } func (s *Service) FetchTournamentStageParticipants( ctx context.Context, stageID string, includeProps, includeParticipantProps, includeCountries, includeCountryCodes bool, ) (*domain.TournamentStageParticipantsResponse, error) { url := fmt.Sprintf( "http://eapi.enetpulse.com/tournament_stage/participants/?id=%s&includeProperties=%s&includeParticipantProperties=%s&includeCountries=%s&includeCountryCodes=%s&username=%s&token=%s", stageID, boolToYesNo(includeProps), boolToYesNo(includeParticipantProps), boolToYesNo(includeCountries), boolToYesNo(includeCountryCodes), s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating participants request: %w", err) } resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting participants: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(body)) } var participantsResp domain.TournamentStageParticipantsResponse if err := json.NewDecoder(resp.Body).Decode(&participantsResp); err != nil { return nil, fmt.Errorf("decoding participants response: %w", err) } // optionally save to DB or cache here return &participantsResp, nil } // helper function to convert bool to yes/no func boolToYesNo(v bool) string { if v { return "yes" } return "no" } func (s *Service) FetchDailyEvents(ctx context.Context, req domain.DailyEventsRequest) (*domain.DailyEventsResponse, error) { baseURL := "http://eapi.enetpulse.com/event/daily/" query := fmt.Sprintf("?username=%s&token=%s&language_typeFK=3", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token) // Required at least one of sportFK / tournament_templateFK / tournament_stageFK if req.SportFK != 0 { query += fmt.Sprintf("&sportFK=%d", req.SportFK) } if req.TournamentTemplateFK != 0 { query += fmt.Sprintf("&tournament_templateFK=%d", req.TournamentTemplateFK) } // if req.TournamentStageFK != 0 { // query += fmt.Sprintf("&tournament_stageFK=%d", req.TournamentStageFK) // } // Optionals if req.Date != "" { query += fmt.Sprintf("&date=%s", req.Date) } if req.Live != "" { query += fmt.Sprintf("&live=%s", req.Live) } if req.IncludeVenue != "" { query += fmt.Sprintf("&includeVenue=%s", req.IncludeVenue) } if req.StatusType != "" { query += fmt.Sprintf("&status_type=%s", req.StatusType) } if req.IncludeEventProperties != "" { query += fmt.Sprintf("&includeEventProperties=%s", req.IncludeEventProperties) } if req.IncludeCountryCodes != "" { query += fmt.Sprintf("&includeCountryCodes=%s", req.IncludeCountryCodes) } if req.IncludeFirstLastName != "" { query += fmt.Sprintf("&includeFirstLastName=%s", req.IncludeFirstLastName) } if req.IncludeDeleted != "" { query += fmt.Sprintf("&includeDeleted=%s", req.IncludeDeleted) } fullURL := baseURL + query httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, fullURL, nil) if err != nil { return nil, err } resp, err := s.httpClient.Do(httpReq) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("failed to fetch daily events: status %d body: %s", resp.StatusCode, string(body)) } var dailyResp domain.DailyEventsResponse if err := json.NewDecoder(resp.Body).Decode(&dailyResp); err != nil { return nil, err } return &dailyResp, nil } func (s *Service) FetchResults(ctx context.Context, params domain.ResultsRequest) (*domain.ResultsResponse, error) { // Build base URL url := fmt.Sprintf("http://eapi.enetpulse.com/event/results/?username=%s&token=%s", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token) // Required filters (at least one of them) if params.SportFK != 0 { url += fmt.Sprintf("&sportFK=%d", params.SportFK) } if params.TournamentTemplateFK != 0 { url += fmt.Sprintf("&tournament_templateFK=%d", params.TournamentTemplateFK) } // if params.TournamentStageFK != 0 { // url += fmt.Sprintf("&tournament_stageFK=%d", params.TournamentStageFK) // } // Optional filters if params.LanguageTypeFK != 0 { url += fmt.Sprintf("&language_typeFK=%d", params.LanguageTypeFK) } else { url += "&language_typeFK=3" // default English } if params.Date != "" { url += fmt.Sprintf("&date=%s", params.Date) } if params.Live != "" { url += fmt.Sprintf("&live=%s", params.Live) } if params.IncludeVenue { url += "&includeVenue=yes" } if !params.IncludeEventProperties { url += "&includeEventProperties=no" } if params.IncludeCountryCodes { url += "&includeCountryCodes=yes" } if params.IncludeFirstLastName { url += "&includeFirstLastName=yes" } if params.Limit > 0 { url += fmt.Sprintf("&limit=%d", params.Limit) } if params.Offset > 0 { url += fmt.Sprintf("&offset=%d", params.Offset) } // Make request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating results request: %w", err) } resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting results: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body)) } // Decode response var resultsResp domain.ResultsResponse if err := json.NewDecoder(resp.Body).Decode(&resultsResp); err != nil { return nil, fmt.Errorf("decoding results response: %w", err) } return &resultsResp, nil } func (s *Service) FetchEventDetails(ctx context.Context, params domain.EventDetailsRequest) (*domain.EventDetailsResponse, error) { // Base URL url := fmt.Sprintf("http://eapi.enetpulse.com/event/details/?username=%s&token=%s&id=%d", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, params.ID) // Optional flags if params.IncludeLineups { url += "&includeLineups=yes" } if params.IncludeIncidents { url += "&includeIncidents=yes" } if !params.IncludeExtendedResults { url += "&includeExtendedResults=no" } if params.IncludeProperties { url += "&includeProperties=yes" } if params.IncludeLivestats { url += "&includeLivestats=yes" } if params.IncludeVenue { url += "&includeVenue=yes" } if params.IncludeCountryCodes { url += "&includeCountryCodes=yes" } if params.IncludeFirstLastName { url += "&includeFirstLastName=yes" } if params.IncludeDeleted { url += "&includeDeleted=yes" } if params.IncludeReference { url += "&includeReference=yes" } if params.IncludeObjectParticipants { url += "&includeObjectParticipants=yes" } if params.IncludeEventIncidentRelation { url += "&includeEventIncidentRelation=yes" } if params.IncludeTeamProperties { url += "&includeTeamProperties=yes" } if params.IncludeObjectRound { url += "&includeObjectRound=yes" } // Make request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating event details request: %w", err) } resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting event details: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body)) } // Decode response var detailsResp domain.EventDetailsResponse if err := json.NewDecoder(resp.Body).Decode(&detailsResp); err != nil { return nil, fmt.Errorf("decoding event details response: %w", err) } return &detailsResp, nil } func (s *Service) FetchEventList(ctx context.Context, params domain.EventListRequest) (*domain.EventListResponse, error) { // You must provide either TournamentFK or TournamentStageFK if params.TournamentFK == 0 { return nil, fmt.Errorf("either TournamentFK or TournamentStageFK is required") } // Base URL url := fmt.Sprintf("http://eapi.enetpulse.com/event/list/?username=%s&token=%s", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token) // Mandatory one of if params.TournamentFK != 0 { url += fmt.Sprintf("&tournamentFK=%d", params.TournamentFK) } // if params.TournamentStageFK != 0 { // url += fmt.Sprintf("&tournament_stageFK=%d", params.TournamentStageFK) // } // Optional parameters if !params.IncludeEventProperties { url += "&includeEventProperties=no" } if params.StatusType != "" { url += "&status_type=" + params.StatusType } if params.IncludeVenue { url += "&includeVenue=yes" } if params.IncludeDeleted { url += "&includeDeleted=yes" } if params.Limit > 0 { url += fmt.Sprintf("&limit=%d", params.Limit) } if params.Offset > 0 { url += fmt.Sprintf("&offset=%d", params.Offset) } // Make request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating event list request: %w", err) } resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting event list: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body)) } // Decode response var eventListResp domain.EventListResponse if err := json.NewDecoder(resp.Body).Decode(&eventListResp); err != nil { return nil, fmt.Errorf("decoding event list response: %w", err) } return &eventListResp, nil } func (s *Service) FetchParticipantFixtures(ctx context.Context, params domain.ParticipantFixturesRequest) (*domain.ParticipantFixturesResponse, error) { // You must provide ParticipantFK if params.ParticipantFK == 0 { return nil, fmt.Errorf("ParticipantFK is required") } // Base URL url := fmt.Sprintf("http://eapi.enetpulse.com/event/participant_fixtures/?username=%s&token=%s", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token) // Mandatory param url += fmt.Sprintf("&participantFK=%d", params.ParticipantFK) // Optionals if params.SportFK != 0 { url += fmt.Sprintf("&sportFK=%d", params.SportFK) } if params.TournamentFK != 0 { url += fmt.Sprintf("&tournamentFK=%d", params.TournamentFK) } if params.TournamentTemplateFK != 0 { url += fmt.Sprintf("&tournament_templateFK=%d", params.TournamentTemplateFK) } // if params.TournamentStageFK != 0 { // url += fmt.Sprintf("&tournament_stageFK=%d", params.TournamentStageFK) // } if params.Date != "" { url += "&date=" + params.Date } if params.Live != "" { url += "&live=" + params.Live } if params.Limit > 0 { url += fmt.Sprintf("&limit=%d", params.Limit) } if params.Offset > 0 { url += fmt.Sprintf("&offset=%d", params.Offset) } if params.IncludeVenue { url += "&includeVenue=yes" } if params.IncludeCountryCodes { url += "&includeCountryCodes=yes" } if params.IncludeFirstLastName { url += "&includeFirstLastName=yes" } if !params.IncludeEventProperties { // default yes → only append when false url += "&includeEventProperties=no" } if params.LanguageTypeFK != 0 { url += fmt.Sprintf("&language_typeFK=%d", params.LanguageTypeFK) } // Make request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating participant fixtures request: %w", err) } resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting participant fixtures: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body)) } // Decode response var fixturesResp domain.ParticipantFixturesResponse if err := json.NewDecoder(resp.Body).Decode(&fixturesResp); err != nil { return nil, fmt.Errorf("decoding participant fixtures response: %w", err) } return &fixturesResp, nil } func (s *Service) FetchParticipantResults(ctx context.Context, params domain.ParticipantResultsRequest) (*domain.ParticipantResultsResponse, error) { // Required param if params.ParticipantFK == 0 { return nil, fmt.Errorf("participantFK is required") } // Base URL url := fmt.Sprintf("http://eapi.enetpulse.com/event/participant_results/?username=%s&token=%s&participantFK=%d", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token, params.ParticipantFK) // Optional parameters if params.Limit > 0 { url += fmt.Sprintf("&limit=%d", params.Limit) } if params.Offset > 0 { url += fmt.Sprintf("&offset=%d", params.Offset) } if params.IncludeDeleted { url += "&includeDeleted=yes" } if params.IncludeVenue { url += "&includeVenue=yes" } // Make request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating participant_results request: %w", err) } resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting participant_results: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body)) } // Decode response var prResp domain.ParticipantResultsResponse if err := json.NewDecoder(resp.Body).Decode(&prResp); err != nil { return nil, fmt.Errorf("decoding participant_results response: %w", err) } return &prResp, nil } func (s *Service) FetchTeamLogo(ctx context.Context, teamFK int64) (*domain.TeamLogoResponse, error) { if teamFK == 0 { return nil, fmt.Errorf("teamFK is required") } // Build URL url := fmt.Sprintf("http://eapi.enetpulse.com/image/team_logo/?teamFK=%d&username=%s&token=%s", teamFK, s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token) // Make request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating team logo request: %w", err) } resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting team logo: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body)) } // Read image bytes data, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("reading team logo body: %w", err) } // Build response return &domain.TeamLogoResponse{ ContentType: resp.Header.Get("Content-Type"), Data: data, URL: url, // optional: you can also return the URL for reference }, nil } func (s *Service) FetchTeamShirts(ctx context.Context, teamFK int64) (*domain.TeamShirtsResponse, error) { if teamFK == 0 { return nil, fmt.Errorf("teamFK is required") } // Build URL url := fmt.Sprintf("http://eapi.enetpulse.com/image/team_shirt/?teamFK=%d&username=%s&token=%s", teamFK, s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token) // Make request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating team shirts request: %w", err) } resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting team shirts: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body)) } // Read response bytes — because shirts may come back as JSON list of URLs *or* image bytes data, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("reading team shirts body: %w", err) } // Build response — since Enetpulse typically returns a list of image URLs, // we’ll return raw bytes if only one image, or JSON if multiple images. return &domain.TeamShirtsResponse{ ContentType: resp.Header.Get("Content-Type"), RawData: data, URL: url, }, nil } func (s *Service) FetchCountryFlag(ctx context.Context, countryFK int64) (*domain.ImageResponse, error) { if countryFK == 0 { return nil, fmt.Errorf("countryFK is required") } // Build URL url := fmt.Sprintf("http://eapi.enetpulse.com/image/country_flag/?countryFK=%d&username=%s&token=%s", countryFK, s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token) // Create HTTP request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating country flag request: %w", err) } // Execute request resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting country flag: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body)) } // Read image bytes data, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("reading country flag body: %w", err) } // Return response return &domain.ImageResponse{ ContentType: resp.Header.Get("Content-Type"), RawData: data, URL: url, }, nil } func (s *Service) FetchPreMatchOdds(ctx context.Context, params domain.PreMatchOddsRequest) (*domain.PreMatchOddsResponse, error) { // Mandatory parameter check if params.ObjectFK == 0 { return nil, fmt.Errorf("objectFK (event ID) is required") } // Base URL url := fmt.Sprintf("http://eapi.enetpulse.com/preodds/event/?username=%s&token=%s", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token) // Mandatory parameters url += fmt.Sprintf("&objectFK=%d", params.ObjectFK) if len(params.OddsProviderFK) > 0 { url += "&odds_providerFK=" + joinIntSlice(params.OddsProviderFK) } if params.OutcomeTypeFK != 0 { url += fmt.Sprintf("&outcome_typeFK=%d", params.OutcomeTypeFK) } if params.OutcomeScopeFK != 0 { url += fmt.Sprintf("&outcome_scopeFK=%d", params.OutcomeScopeFK) } if params.OutcomeSubtypeFK != 0 { url += fmt.Sprintf("&outcome_subtypeFK=%d", params.OutcomeSubtypeFK) } // Optional parameters if params.Limit > 0 { url += fmt.Sprintf("&limit=%d", params.Limit) } if params.Offset > 0 { url += fmt.Sprintf("&offset=%d", params.Offset) } if params.LanguageTypeFK != 0 { url += fmt.Sprintf("&language_typeFK=%d", params.LanguageTypeFK) } // Create HTTP request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating pre-match odds request: %w", err) } // Execute request resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting pre-match odds: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body)) } // Decode response var oddsResp domain.PreMatchOddsResponse if err := json.NewDecoder(resp.Body).Decode(&oddsResp); err != nil { return nil, fmt.Errorf("decoding pre-match odds response: %w", err) } return &oddsResp, nil } // Helper function to join int slice with comma func joinIntSlice(slice []int64) string { result := "" for i, v := range slice { if i > 0 { result += "," } result += fmt.Sprintf("%d", v) } return result } func (s *Service) FetchTournamentStageOdds(ctx context.Context, params domain.TournamentStageOddsRequest) (*domain.TournamentStageOddsResponse, error) { // Mandatory parameter check if params.ObjectFK == 0 { return nil, fmt.Errorf("objectFK (tournament stage ID) is required") } if params.OutcomeTypeFK == 0 { return nil, fmt.Errorf("outcomeTypeFK is required") } // Base URL url := fmt.Sprintf("http://eapi.enetpulse.com/preodds/tournament_stage/?username=%s&token=%s", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token) // Mandatory parameters url += fmt.Sprintf("&objectFK=%d", params.ObjectFK) url += fmt.Sprintf("&outcome_typeFK=%d", params.OutcomeTypeFK) if params.OddsProviderFK != 0 { url += fmt.Sprintf("&odds_providerFK=%d", params.OddsProviderFK) } if params.OutcomeScopeFK != 0 { url += fmt.Sprintf("&outcome_scopeFK=%d", params.OutcomeScopeFK) } if params.OutcomeSubtypeFK != 0 { url += fmt.Sprintf("&outcome_subtypeFK=%d", params.OutcomeSubtypeFK) } // Optional parameters if params.Limit > 0 { url += fmt.Sprintf("&limit=%d", params.Limit) } if params.Offset > 0 { url += fmt.Sprintf("&offset=%d", params.Offset) } if params.LanguageTypeFK != 0 { url += fmt.Sprintf("&language_typeFK=%d", params.LanguageTypeFK) } // Create HTTP request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating tournament stage odds request: %w", err) } // Execute request resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting tournament stage odds: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body)) } // Decode response var oddsResp domain.TournamentStageOddsResponse if err := json.NewDecoder(resp.Body).Decode(&oddsResp); err != nil { return nil, fmt.Errorf("decoding tournament stage odds response: %w", err) } return &oddsResp, nil } func (s *Service) FetchTournamentOdds(ctx context.Context, params domain.TournamentOddsRequest) (*domain.TournamentOddsResponse, error) { // Mandatory parameter check if params.ObjectFK == 0 { return nil, fmt.Errorf("objectFK (tournament ID) is required") } if params.OutcomeTypeFK == 0 { return nil, fmt.Errorf("outcomeTypeFK is required") } // Base URL url := fmt.Sprintf("http://eapi.enetpulse.com/preodds/tournament/?username=%s&token=%s", s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token) // Mandatory parameters url += fmt.Sprintf("&objectFK=%d", params.ObjectFK) url += fmt.Sprintf("&outcome_typeFK=%d", params.OutcomeTypeFK) if params.OddsProviderFK != 0 { url += fmt.Sprintf("&odds_providerFK=%d", params.OddsProviderFK) } if params.OutcomeScopeFK != 0 { url += fmt.Sprintf("&outcome_scopeFK=%d", params.OutcomeScopeFK) } if params.OutcomeSubtypeFK != 0 { url += fmt.Sprintf("&outcome_subtypeFK=%d", params.OutcomeSubtypeFK) } // Optional parameters if params.Limit > 0 { url += fmt.Sprintf("&limit=%d", params.Limit) } if params.Offset > 0 { url += fmt.Sprintf("&offset=%d", params.Offset) } if params.LanguageTypeFK != 0 { url += fmt.Sprintf("&language_typeFK=%d", params.LanguageTypeFK) } // Create HTTP request req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("creating tournament odds request: %w", err) } // Execute request resp, err := s.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("requesting tournament odds: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body)) } // Decode response var oddsResp domain.TournamentOddsResponse if err := json.NewDecoder(resp.Body).Decode(&oddsResp); err != nil { return nil, fmt.Errorf("decoding tournament odds response: %w", err) } return &oddsResp, nil }