mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
feat(strava): add per-user BYO-OAuth workout source + Garmin indirect-path docs
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.
This commit is contained in:
1 parent
89634e20ee
commit
75eb0637e6
49 files changed
+2683
-15
No files matched your search
@@ -0,0 +1,52 @@
|
||||
# Garmin (via Apple Health or Google Health Connect)
|
||||
|
||||
**There is no direct Garmin connector for a self-hosted HealthLog instance, and
|
||||
there cannot be one.** Garmin's data-sharing program is business-partner-only:
|
||||
it is approval-gated, requires a public callback endpoint, and is not the
|
||||
"register your own app, paste a key" model the wearable integrations here rely
|
||||
on. So instead of a broken half-integration, HealthLog reads Garmin data the
|
||||
honest way — through the phone health platform your Garmin already feeds.
|
||||
|
||||
## The path that works
|
||||
|
||||
```
|
||||
Garmin device → Garmin Connect → Apple Health (iOS) ─┐
|
||||
→ Google Health Connect ├─→ HealthLog
|
||||
(Android) ─┘
|
||||
```
|
||||
|
||||
- **iOS:** Garmin Connect writes to Apple Health automatically. Import it with
|
||||
the existing [Apple Health](./apple-health.md) path (the `export.zip` upload or
|
||||
the native client's background sync).
|
||||
- **Android:** since Garmin Connect 5.14.1 (July 2025) the app writes to **Google
|
||||
Health Connect**. Connect HealthLog through the existing
|
||||
[Google Health](./google-health.md) integration.
|
||||
|
||||
No extra code, no Garmin developer account, no approval queue — if your Garmin
|
||||
already syncs to your phone's health platform, HealthLog already reads it.
|
||||
|
||||
## What comes through
|
||||
|
||||
Everything Garmin shares with Apple Health / Health Connect flows in: steps,
|
||||
heart rate (including beat-to-beat variability), sleep stages (REM / deep /
|
||||
light), calories (active and total), distance, weight and body composition,
|
||||
cadence, and — partially — blood-oxygen saturation.
|
||||
|
||||
## What Garmin withholds
|
||||
|
||||
These are Garmin's **proprietary** scores. Garmin does **not** share them with
|
||||
Apple Health or Health Connect, so no self-hosted integration — and no amount of
|
||||
configuration — can obtain them. They are only available inside Garmin Connect
|
||||
itself:
|
||||
|
||||
- Body Battery
|
||||
- HRV Status
|
||||
- VO₂ max
|
||||
- Training Load and Training Effect
|
||||
- Recovery Time
|
||||
- Intensity Minutes
|
||||
- Stress score
|
||||
- Running dynamics
|
||||
|
||||
If you rely on those specific numbers, keep the Garmin Connect app for them;
|
||||
everything else lands in HealthLog through the platform path above.
|
||||
@@ -0,0 +1,74 @@
|
||||
# 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).
|
||||
Reference in new issue
Block a user