mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
`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.
61 lines
2.2 KiB
TypeScript
61 lines
2.2 KiB
TypeScript
/**
|
|
* Request schemas for the per-user scalar preference endpoints under
|
|
* `/api/auth/me/*`. Single-sourced here so the runtime `safeParse` and the
|
|
* OpenAPI registry (`src/lib/openapi/routes/profile.ts`) describe the identical
|
|
* wire contract — the same reason `mfa.ts` exists next door.
|
|
*
|
|
* They all follow the same shape: a one-field PATCH that hard-sets the value
|
|
* and answers with the resolved next state, so a client can replace its
|
|
* optimistic update rather than re-read.
|
|
*/
|
|
import { z } from "zod/v4";
|
|
|
|
import {
|
|
injectionSiteEnum,
|
|
INJECTION_SITE_VALUES,
|
|
} from "@/lib/validations/medication";
|
|
|
|
/** `PATCH /api/auth/me/unit-preference` — metric/imperial display branch. */
|
|
export const unitPreferencePatchSchema = z.object({
|
|
unitPreference: z.enum(["metric", "imperial"]),
|
|
});
|
|
|
|
/**
|
|
* `PATCH /api/auth/me/glucose-unit` — mg/dL vs mmol/L display branch.
|
|
*
|
|
* Not the metric/imperial preference: the two split along different lines.
|
|
* Germany is metric and reads glucose in mg/dL; the UK and the Nordics read
|
|
* it in mmol/L. Canonical storage stays mg/dL either way.
|
|
*/
|
|
export const glucoseUnitPatchSchema = z.object({
|
|
glucoseUnit: z.enum(["mg/dL", "mmol/L"]),
|
|
});
|
|
|
|
/** `PATCH /api/auth/me/documents-auto-ai-read` — the auto-read opt-in. */
|
|
export const documentsAutoAiReadPatchSchema = z.object({
|
|
documentsAutoAiRead: z.boolean(),
|
|
});
|
|
|
|
/** `PATCH /api/auth/me/labs-local-ocr` — browser-side OCR opt-in. */
|
|
export const labsLocalOcrPatchSchema = z.object({
|
|
labsLocalOcrEnabled: z.boolean(),
|
|
});
|
|
|
|
/** `PATCH /api/auth/me/use-central-codex` — the operator's shared Codex. */
|
|
export const useCentralCodexPatchSchema = z.object({
|
|
useCentralCodex: z.boolean(),
|
|
});
|
|
|
|
/** `PATCH /api/auth/me/injection-site-prefs` — the user-level deny-list. */
|
|
export const injectionSitePrefsPatchSchema = z.object({
|
|
// Dedup at write time; cap at the full enum size. An empty array
|
|
// clears the exclusion.
|
|
globalExcludedInjectionSites: z
|
|
.array(injectionSiteEnum)
|
|
.max(INJECTION_SITE_VALUES.length)
|
|
.meta({
|
|
description:
|
|
"User-level injection-site deny-list. Sites here are never offered for any medication and rejected at intake (deny wins). Empty array clears the exclusion.",
|
|
}),
|
|
});
|