Commit Graph
2 Commits
Author SHA1 Message Date
Marc-André Bombeck 38c7604ce4 feat(integrations): per-user Polar and Oura OAuth credentials
Polar and Oura resolved their OAuth client id/secret from the server
environment only, forcing every user on a deploy to share one app. Both
now read per-user BYO keys first, falling back to the shared env app when
unset, mirroring the WHOOP and Fitbit model.

- Add DB-first-then-env resolvers in src/lib/{polar,oura}/credentials.ts,
  encrypting the client id/secret at rest on User.
- Repoint the connect, callback, status routes and the Oura refresh path
  to the user-scoped resolver.
- Add /api/{polar,oura}/credentials (GET/PUT/DELETE) with Zod-validated
  input and userId narrowed from the session.
- Render an optional 'your OAuth app credentials' form on the Polar and
  Oura cards; the status read exposes hasOwnCredentials for the saved
  placeholder. Existing env-only deploys keep working unchanged.
- Cover both resolvers (DB hit, env fallback, no creds) and the card form
  with unit tests; add the new strings across all six locales.
2026-06-14 23:19:47 +02:00
Marc-André Bombeck 88139fb981 feat(integrations): add Oura Cloud v2 OAuth sync
Connect an Oura account over the Cloud v2 API: an authorization-code OAuth
flow (shared app from OURA_CLIENT_ID / OURA_CLIENT_SECRET) on the same signed
state machinery as Polar. Unlike Polar, Oura issues refresh tokens; the sync
refreshes reactively on a 401, rotating and re-encrypting both tokens, and a
403 stays a hard reauth rather than a refresh.

An hourly poll-cohort job maps daily readiness to RECOVERY_SCORE, sleep to
per-stage durations plus efficiency / HRV / resting heart rate / respiratory
rate, and daily activity to steps and active energy — all tagged source OURA,
idempotent on (userId, type, source, externalId). Readiness flows through the
canonical recovery ladder above the computed proxy. Settings gains a connect /
disconnect card. Tokens are never logged or returned; every egress goes
through safeFetch.
2026-06-14 20:10:35 +02:00