Files
HealthLog/src/lib/openapi/routes/index.ts
T
Marc-André Bombeck ab03bc96f1 feat(concurrency): guard layout and about-me writes against a concurrent overwrite
Extract the dashboard-layout optimistic-concurrency mechanism (issue #581)
into a shared server helper (takeBaseToken plus guardedUserUpdate /
guardedRowUpdate) and a client helper (readUpdatedAtToken, withBaseToken,
isConflict). Retrofit the dashboard-widgets route onto the helper and apply the
same guard to the insights layout, medications layout, and coach about-me write
endpoints.

A request that omits the base token keeps the byte-identical unconditional
write, so older clients are unaffected. Document the contract in the OpenAPI
registry, including the dashboard-widgets path that was previously absent.
2026-07-24 16:26:58 +02:00

143 lines
5.1 KiB
TypeScript

/**
* OpenAPI route table — thin aggregation index.
*
* The route table is split into domain modules under this directory;
* this index assembles them into the single `openApiPaths` object the
* registry consumes. The spread order below is load-bearing: the YAML
* emitter runs with `sortMapEntries: false` (see
* `scripts/generate-openapi.ts`), so the key order of this object is
* the path order of the committed `docs/api/openapi.yaml`. Keep the
* spread sequence stable when adding a module — append inside the
* domain module, not by reordering here.
*
* Schemas come from `src/lib/validations/*` so the wire contract stays
* single-source-of-truth. The `.meta()` annotations on each schema land
* the title + description in `components.schemas.*` automatically.
*
* Routes are intentionally registered as inline operation objects
* rather than via a wrapping helper — `zod-openapi`'s `createDocument`
* type-checks the route table directly, which catches schema-shape
* regressions at typecheck time.
*/
import type { ZodOpenApiObject } from "zod-openapi";
import { adminDiagnosticPaths, adminInvitePaths } from "./admin";
import { allergyPaths } from "./allergies";
import { authPaths } from "./auth";
import {
coachFeedbackPaths,
coachPaths,
coachReminderSuggestionPaths,
coachReminderPaths,
coachSuggestedActionPaths,
} from "./coach";
import { consentPaths } from "./consent";
import { customMetricPaths } from "./custom-metrics";
import { dailyPaths } from "./daily";
import { cyclePaths } from "./cycle";
import { dashboardWidgetPaths } from "./dashboard";
import { biomarkerPaths } from "./biomarkers";
import { inboundDocumentPaths } from "./documents";
import { devicePaths } from "./devices";
import { exportPaths } from "./export";
import { familyHistoryPaths } from "./family-history";
import { healthRecordPaths } from "./health-record";
import { illnessPaths } from "./illness";
import { importPaths } from "./import";
import { insightsPaths } from "./insights";
import { integrationPaths } from "./integrations";
import { insightsSignalPaths } from "./insights-signals";
import { labsPaths } from "./labs";
import { ocrPaths } from "./ocr";
import { measurementPaths } from "./measurements";
import { measurementReminderPaths } from "./measurement-reminders";
import { mentalHealthPaths } from "./mental-health";
import { medicationPaths, medicationResource } from "./medications";
import { metaPaths } from "./meta";
import { moodPaths } from "./mood";
import { nutrientPaths } from "./nutrients";
import { onboardingPaths } from "./onboarding";
import { profilePaths } from "./profile";
import { settingsPaths } from "./settings";
import { syncPaths } from "./sync";
import { workoutPaths } from "./workouts";
export const openApiPaths: NonNullable<ZodOpenApiObject["paths"]> = {
...cyclePaths,
...metaPaths,
...healthRecordPaths,
...exportPaths,
...authPaths,
...syncPaths,
...measurementPaths,
...workoutPaths,
...devicePaths,
...medicationPaths,
...profilePaths,
...coachPaths,
...coachReminderSuggestionPaths,
...coachReminderPaths,
...coachSuggestedActionPaths,
...adminInvitePaths,
...coachFeedbackPaths,
...adminDiagnosticPaths,
...insightsPaths,
...insightsSignalPaths,
...moodPaths,
...settingsPaths,
...consentPaths,
...importPaths,
...measurementReminderPaths,
...labsPaths,
...ocrPaths,
...biomarkerPaths,
...illnessPaths,
...mentalHealthPaths,
...allergyPaths,
...familyHistoryPaths,
...inboundDocumentPaths,
...customMetricPaths,
...onboardingPaths,
// v1.28 — nutrient-intake sync (appended; spread order is load-bearing).
...nutrientPaths,
// v1.28 — HealthKit integration config (appended; spread order is
// load-bearing).
...integrationPaths,
// v1.28 — the unified daily digest (P3 spine; appended, spread order is
// load-bearing).
...dailyPaths,
// v1.32.21 (R5a) — dashboard widget layout: previously absent from the
// registry entirely, now documented as the reference optimistic-concurrency
// contract (appended, spread order is load-bearing).
...dashboardWidgetPaths,
};
export const openApiComponents: NonNullable<ZodOpenApiObject["components"]> = {
// Schemas listed here are forced into `components.schemas` even when no
// route references them directly. `Medication` lives in this slot
// because its only consumers are the `MedicationListEntry` /
// `MedicationDetail` variants which extend it — `.extend()` inlines
// the base shape into the derived schema, so without an explicit
// registration the standalone `Medication` component would never
// emit. The iOS codegen reads from `Medication` directly for the
// shared Swift struct backing both variants.
schemas: {
Medication: medicationResource,
},
securitySchemes: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "hlk_<64hex>",
description:
"Native-client API token. Use the `token` field returned by /api/auth/login on a native client.",
},
cookieAuth: {
type: "apiKey",
in: "cookie",
name: "healthlog_session",
description: "Browser session cookie — set by /api/auth/login.",
},
},
};