mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
Add Strava as a per-user BYO-OAuth workout source, mirroring the shipped WHOOP/Oura/Polar pattern. Strava caps every new app at single-player mode, so each self-hoster registers their own app and pastes the client id/secret into Settings (falling back to a shared env app). Strava is a workout source only — it exposes no daily metrics and no training-depth analytics are derived. - Schema (migration 0233): per-user encrypted client id/secret + access/refresh tokens, plaintext athlete id, incremental cursor, and backfill marker on `User`; `STRAVA` added to `MeasurementSource`. - Token model follows Oura's reactive-refresh-on-401 with a compare-and-set on the stored refresh ciphertext, because Strava rotates its refresh token on every refresh. - Client over safeFetch: authorization-code + refresh, activity list + budget- aware per-activity detail (for calories/HR), honouring the 200/15min cap; each activity maps to one `Workout` row. Activity name/description stay in metadata as data, never surfaced or interpolated. - Dedup reuses the existing source-agnostic two-layer picker: `STRAVA` slots into the workout ladder below the device-native wearables and above MANUAL/IMPORT, so an Apple-Watch + Strava twin collapses to one canonical row. - Poll-cohort hourly sync (:17) + self-converging boot backfill, wired into the integration-sync registrar. Webhook deferred. - OAuth routes (connect/callback/credentials/disconnect/status/test) with signed state, cross-account re-auth rejection, and a surgical `www.strava.com` connect-src gated to the Strava surfaces. - Settings card + consolidated status envelope entry; env + compose whitelist; i18n across all six locales. - Garmin: no direct connector is possible (business-partner-only). Add docs/integrations/garmin.md documenting the Apple Health / Google Health Connect path and Garmin's withheld proprietary scores, cross-linked from both platform docs, plus a non-connector info note in the Connections panel. Regenerate docs/api/openapi.yaml for the new `STRAVA` MeasurementSource value.
75 lines
3.6 KiB
Markdown
75 lines
3.6 KiB
Markdown
# Strava integration
|
|
|
|
HealthLog reads your Strava activities and folds them into the same workout
|
|
history as every other source. Strava is a **workout source only** — its API is
|
|
activity-centric and exposes no sleep, recovery, body-composition or glucose
|
|
data, so connecting it adds runs, rides, swims and the rest to your workout
|
|
surface and nothing else.
|
|
|
|
The OAuth app is brought **per user**: each self-hoster registers their own
|
|
Strava API application and pastes the client id/secret into Settings. Strava
|
|
caps every newly-created app at "single-player mode" (an athlete capacity of 1)
|
|
until you request a capacity increase — perfectly fine for a personal
|
|
self-host, and the reason a single shared app cannot serve many operators.
|
|
|
|
## What you get — and what you don't
|
|
|
|
**Covered** (one `Workout` row per activity): sport type, start and end time,
|
|
moving duration, distance, elevation gain, average and maximum heart rate (when
|
|
the activity recorded it), and calories. Power, cadence, the "trainer"/"commute"
|
|
flags and the activity name are kept on the row's metadata for provenance but
|
|
are **not** surfaced as training analytics — HealthLog treats Strava as another
|
|
workout source, not a training-load platform.
|
|
|
|
**Not covered**: Strava exposes no daily metrics (steps, sleep, resting heart
|
|
rate, weight, …). Those come from your wearable's own integration or from Apple
|
|
Health / Google Health Connect.
|
|
|
|
## Duplicate activities are collapsed automatically
|
|
|
|
If you record a run on an Apple Watch **and** it also lands in Strava, the same
|
|
run arrives twice — once via Apple Health, once via Strava. HealthLog keeps one
|
|
canonical row per logical workout using your **source-priority ladder**: by
|
|
default the device-native capture (Apple Watch, WHOOP, Withings) wins over the
|
|
Strava copy, because a Strava activity is often a re-upload with lower-fidelity
|
|
or missing heart-rate data. A run you record **only** on Strava (phone GPS)
|
|
survives untouched. You can promote Strava above the wearables in
|
|
**Settings → Sources** if you prefer the Strava copy.
|
|
|
|
## 1. Register a Strava API application
|
|
|
|
1. Sign in at <https://www.strava.com/settings/api>.
|
|
2. Create an application. For **Authorization Callback Domain** enter the host
|
|
of your HealthLog instance (the domain only, no scheme or path).
|
|
3. Copy the **Client ID** and **Client Secret**.
|
|
|
|
## 2. Paste the credentials into HealthLog
|
|
|
|
1. Open **Settings → Integrations** and find the **Strava** card.
|
|
2. Paste the Client ID and Client Secret into the **API Credentials** fields and
|
|
save. (An operator can instead configure a shared app via the
|
|
`STRAVA_CLIENT_ID` / `STRAVA_CLIENT_SECRET` environment variables; per-user
|
|
credentials always take precedence.)
|
|
3. Click **Connect Strava** and approve the access request. HealthLog requests
|
|
the `activity:read_all` scope so it can read every activity, including those
|
|
you marked "only me".
|
|
|
|
## 3. First sync
|
|
|
|
The first history import runs in the background shortly after you connect;
|
|
afterwards an hourly poll pulls new activities. Strava rotates its refresh token
|
|
on every refresh, and HealthLog persists the rotated token each time, so the
|
|
connection stays healthy without you re-authorising.
|
|
|
|
## Disconnecting
|
|
|
|
Disconnecting revokes the grant at Strava and clears the stored tokens.
|
|
Activities already imported stay in your history; reconnecting resumes the sync.
|
|
|
|
## Related
|
|
|
|
- Wearable that syncs to Strava but not directly to HealthLog? See the device's
|
|
own page, or ingest via [Apple Health](./apple-health.md) /
|
|
[Google Health Connect](./google-health.md).
|
|
- **Garmin owners:** Garmin has no direct connector — see [garmin.md](./garmin.md).
|