diff --git a/db/integrations/strava/models.go b/db/integrations/strava/models.go index 41ccb665..c26f1012 100644 --- a/db/integrations/strava/models.go +++ b/db/integrations/strava/models.go @@ -351,6 +351,11 @@ type Photos struct { Count int `json:"count"` } +type StravaActivityPhoto struct { + UniqueID string `json:"unique_id"` + Urls Urls `json:"urls"` +} + type HighlightedKudosers struct { DestinationURL string `json:"destination_url"` DisplayName string `json:"display_name"` diff --git a/db/integrations/strava/strava.go b/db/integrations/strava/strava.go index e4bee208..fcfc468b 100644 --- a/db/integrations/strava/strava.go +++ b/db/integrations/strava/strava.go @@ -452,7 +452,7 @@ func syncTrailsWithActivities(app core.App, client meilisearch.ServiceManager, c app.Logger().Warn(fmt.Sprintf("Unable to fetch GPX for activity '%s': %v", activity.Name, err)) continue } - trailID, err := createTrailFromActivity(app, detailedActivity, gpx, user, actor.Id, i.Privacy) + trailID, err := createTrailFromActivity(app, detailedActivity, gpx, user, actor.Id, i.Privacy, accessToken) if err != nil { app.Logger().Warn(fmt.Sprintf("Unable to create trail from activity '%s': %v", activity.Name, err)) continue @@ -492,7 +492,7 @@ func fetchDetailedActivity(activity StravaActivity, accessToken string) (*Detail return &detailedActivity, nil } -func createTrailFromActivity(app core.App, activity *DetailedStravaActivity, gpx *filesystem.File, user string, actor string, privacy string) (string, error) { +func createTrailFromActivity(app core.App, activity *DetailedStravaActivity, gpx *filesystem.File, user string, actor string, privacy string, accessToken string) (string, error) { if len(activity.StartLatlng) < 2 { return "", nil } @@ -502,11 +502,19 @@ func createTrailFromActivity(app core.App, activity *DetailedStravaActivity, gpx return "", err } - var photo *filesystem.File - if len(activity.Photos.Primary.Urls.Num600) > 0 { - photo, err = fetchActivityPhoto(activity) + var photos []*filesystem.File + if activity.Photos.Count > 0 { + photos, err = fetchActivityPhotos(activity.ID, accessToken) if err != nil { - return "", err + app.Logger().Warn(fmt.Sprintf("Failed to fetch activity photos for activity %d: %v", activity.ID, err)) + } + } + + // Fallback to primary photo if no photos were fetched but primary URL is available + if len(photos) == 0 && len(activity.Photos.Primary.Urls.Num600) > 0 { + photo, err := fetchPhotoFromURL(activity.Photos.Primary.Urls.Num600) + if err == nil { + photos = []*filesystem.File{photo} } } @@ -589,8 +597,8 @@ func createTrailFromActivity(app core.App, activity *DetailedStravaActivity, gpx "author": actor, }) - if photo != nil { - record.Set("photos", photo) + if len(photos) > 0 { + record.Set("photos", photos) } if gpx != nil { @@ -607,8 +615,8 @@ func createTrailFromActivity(app core.App, activity *DetailedStravaActivity, gpx return record.Id, nil } -func fetchActivityPhoto(activity *DetailedStravaActivity) (*filesystem.File, error) { - req, err := http.NewRequest("GET", activity.Photos.Primary.Urls.Num600, nil) +func fetchPhotoFromURL(url string) (*filesystem.File, error) { + req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } @@ -638,6 +646,50 @@ func fetchActivityPhoto(activity *DetailedStravaActivity) (*filesystem.File, err return photo, nil } +func fetchActivityPhotos(activityID int64, accessToken string) ([]*filesystem.File, error) { + url := fmt.Sprintf("https://www.strava.com/api/v3/activities/%d/photos?size=600", activityID) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+accessToken) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to fetch activity photos: received status %d", resp.StatusCode) + } + + var apiPhotos []StravaActivityPhoto + if err := json.NewDecoder(resp.Body).Decode(&apiPhotos); err != nil { + return nil, err + } + + photos := make([]*filesystem.File, 0, len(apiPhotos)) + for _, apiPhoto := range apiPhotos { + photoURL := apiPhoto.Urls.Num600 + if photoURL == "" { + photoURL = apiPhoto.Urls.Num100 + } + if photoURL == "" { + continue + } + + photo, err := fetchPhotoFromURL(photoURL) + if err != nil { + continue + } + photos = append(photos, photo) + } + + return photos, nil +} + func generateActivityGPX(activity *DetailedStravaActivity, accessToken string) (*filesystem.File, error) { url := fmt.Sprintf("https://www.strava.com/api/v3/activities/%d/streams?keys=latlng,time,altitude&key_by_type=true", activity.ID)