Files
HealthLog/src/lib/query-keys/auth.ts
T
Marc-André Bombeck 5078867203 feat: let an account choose the unit its glucose is shown in
`User.glucoseUnit` has existed since v1.2 and around thirty surfaces read
it — the dashboard tiles, the glucose page, the targets panel, the CSV and
FHIR exports, the doctor report, the Coach snapshot, the low-reading push
copy. Nothing could ever write it. No route, no schema, no field on any
form, so every account resolved to the mg/dL default and the mmol/L half
of the app was unreachable code. Anyone who reads glucose in mmol/L
converted in their head on every screen.

This is the missing end: a `GET`/`PATCH /api/auth/me/glucose-unit` scalar
on the pattern the unit-preference endpoint next door established, and a
dropdown in the profile form beside it. Deliberately its own control
rather than a branch of metric/imperial: metric countries are split on
which unit they read glucose in, so one dropdown would get half of them
wrong.

Storage is untouched and stays untouched. Every reading is written in
mg/dL by every ingest path and the unit only selects a display branch, so
nothing already recorded is reinterpreted. The entry form moved with the
preference, which is the half that could have hurt: it asks in the chosen
unit and inverts through `toCanonicalMgdl` on submit, so a 5.3 typed by an
mmol/L reader is filed as 95 mg/dL rather than as 5.3 — a value the
plausibility band accepts in silence and every surface reads back as a
severe hypo.

The contract gains the endpoint and nothing else; the account payload
already carried the field.
2026-09-03 06:05:58 +02:00

96 lines
4.1 KiB
TypeScript

/**
* Query keys — auth session, passkeys, and per-user account preferences.
* Part of the centralized factory; aggregated in `./index.ts`.
*/
export const authKeys = {
auth: () => ["auth"] as const,
/**
* v1.4.40 W-RSC — the `useAuth` hook's `["auth", "me"]` shape was a
* recurring source of factory drift (audit H1 — "`useAuth` uses
* `["auth", "me"]` but `queryKeys.auth()` returns `["auth"]`"). Both
* shapes share the `["auth"]` prefix so existing prefix-invalidations
* still match, but the centralised name makes the call site
* obviously factory-routed.
*/
authMe: () => ["auth", "me"] as const,
authRegistrationStatus: () => ["auth", "registration-status"] as const,
authOidcStatus: () => ["auth", "oidc", "status"] as const,
/**
* v1.4.40 W-RSC — Settings → AI surfaces and the targets editor
* subscribe to the user-thresholds API. Centralise the key so a
* future rename (e.g. `["user","limits"]`) doesn't drift across the
* three call sites (settings/thresholds-editor-section,
* settings/ai-section, targets/target-edit-sheet).
*/
userThresholds: () => ["user", "thresholds"] as const,
/**
* v1.7.0 — Settings → Display metric/imperial control reads its
* current value from `GET /api/auth/me/unit-preference`. The PATCH
* mutation also invalidates `authMe()` so `useAuth().unitPreference`
* (and every chart display transform that keys off it) re-renders
* without a manual reload.
*/
userUnitPreference: () => ["user", "unit-preference"] as const,
/**
* The mg/dL vs mmol/L glucose display control behind
* `GET /api/auth/me/glucose-unit`. A separate axis from the
* metric/imperial preference above — metric countries are split on
* which one they read glucose in — so it carries its own key. The
* PATCH also invalidates `authMe()`, since every glucose surface
* resolves the unit from the account payload rather than from here.
*/
userGlucoseUnit: () => ["user", "glucose-unit"] as const,
passkeys: () => ["passkeys"] as const,
/**
* v1.23 — the user-facing active-session list behind
* `GET /api/auth/me/sessions`. Revoking a single session or signing out
* everywhere invalidates this key so the surface re-reads the live set.
*/
sessions: () => ["auth", "sessions"] as const,
/**
* v1.23 — the shared security-activity feed behind
* `GET /api/auth/me/security-activity` (logins, MFA, password change,
* session revoke, exports, deletions). Consumed by the account-security
* surface and the privacy dashboard.
*/
securityActivity: () => ["auth", "security-activity"] as const,
/**
* v1.23 — the security settings hub reads second-factor status (TOTP
* enabled, recovery codes remaining, registered security keys, passkey-nudge
* dismissal) from `GET /api/auth/me/mfa`. Mutations that change a factor
* invalidate this key so the hub reflects the new state immediately.
*/
mfaStatus: () => ["auth", "mfa", "status"] as const,
/**
* v1.23 — the "remember this device" trusted-device list behind
* `GET /api/auth/me/trusted-devices`. Revoking a device (or all of them)
* invalidates this key so the surface re-reads the live set.
*/
trustedDevices: () => ["auth", "trusted-devices"] as const,
userAiProvider: () => ["user", "ai-provider"] as const,
userProfile: () => ["user", "profile"] as const,
/**
* v1.7.0 — the roaming notification prefs blob behind
* `GET/PATCH /api/auth/me/notification-prefs` (medication delivery
* default + mood reminder hour). Distinct from
* `notificationsPreferences()` (the per-event push toggles on the
* `/notifications` page) so the two never collide in the cache.
*/
authNotificationPrefs: () => ["auth", "me", "notification-prefs"] as const,
/**
* v1.4.25 W5e — per-user, per-metric-class source priority. The
* Settings → Sources surface reads + writes this key; saving
* invalidates `analytics()` because the cumulative-metric aggregator
* folds the new priority into the SLEEP_DURATION daily total
* immediately.
*/
sourcePriority: () => ["auth", "source-priority"] as const,
};