mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +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.
18 lines
786 B
TypeScript
18 lines
786 B
TypeScript
import { z } from "zod/v4";
|
|
|
|
/**
|
|
* Per-user Strava BYO-key credentials. Strava caps every newly-created API app
|
|
* at single-player mode (athlete capacity 1), so a single shared app cannot
|
|
* serve many self-hosters — each registers their own Strava app and pastes the
|
|
* client id/secret into Settings. Stored encrypted on `User`. When unset, the
|
|
* integration falls back to the shared env-configured OAuth app.
|
|
*/
|
|
export const stravaCredentialsSchema = z.object({
|
|
// Trimmed: a trailing space or newline from the portal's copy button reaches
|
|
// Strava verbatim and answers as "invalid client".
|
|
clientId: z.string().trim().min(1).max(200),
|
|
clientSecret: z.string().trim().min(1).max(200),
|
|
});
|
|
|
|
export type StravaCredentialsInput = z.infer<typeof stravaCredentialsSchema>;
|