mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
feat(illness): add the condition-journal MVP behind a born-gated module
Introduce a retrospective illness/condition journal: episodes with a lifecycle (acute / chronic-ongoing / recurring / flare) and a daily symptom timeline forked structurally from the cycle log-day surface. The journal is a record, not a medical device — no prediction or diagnosis UI. - Register `illness` as a born-gated (opt-in / default-off) module. The gate resolver flips the disabled-allowlist default to off for born-gated keys, so the surface stays dark until the account opts in from the Modules hub. `isIllnessEnabled` + `requireIllnessEnabled` delegate to the module foundation (one source of truth) and re-stamp an illness errorCode. - Episode CRUD under /api/illness: list / create / get / edit / resolve / delete, plus a per-episode-day day-log upsert on (episodeId, date). Every route wraps apiHandler, Zod-validates with returnAllZodIssues, narrows userId from auth, builds data field-by-field, and gates the module. Free-text notes encrypt at rest through the shared Bytes codec. - Log-day sheet (cycle fork): ~8 Jackson/WURSS symptom chips 0-3 + a functional-impact group + fever + an encrypted note, one row per day. - Episode history page + a born-gated nav entry, neutral palette, status via a neutral Badge, retrospective-only copy. i18n across all six locales. - Cover the new routes in the module-route-gate inventory; unit tests for the day-log upsert, the gate enforcement, and the born-gated resolver. The /api/auth/me module map now projects the `illness` flag (openapi.yaml regenerated).
This commit is contained in:
1 parent
bee7295b19
commit
b079431a8d
34 files changed
+2799
-6
No files matched your search
@@ -75,6 +75,14 @@ const MODULE_ROUTE_TREES: ReadonlyArray<string> = [
|
||||
"src/app/api/workouts",
|
||||
"src/app/api/labs",
|
||||
"src/app/api/cycle",
|
||||
// v1.18.1 (W-B) — the illness/condition journal. Every `/api/illness/*`
|
||||
// route gates on the born-gated `illness` module via the thin
|
||||
// `requireIllnessEnabled(...)` wrapper (which re-stamps the
|
||||
// illness-specific errorCode over `requireModuleEnabled("illness")`),
|
||||
// recognised below as a delegated gate. Walking the tree means a NEW
|
||||
// ungated illness route fails this test BY NAME rather than leaking the
|
||||
// surface over a Bearer token when the account never opted in.
|
||||
"src/app/api/illness",
|
||||
"src/app/api/gamification",
|
||||
// v1.18.0 B3 — the legacy `/api/doctor-report` tree (JSON + server-PDF +
|
||||
// availability probe) was orphaned dead code (no production caller) and
|
||||
@@ -168,6 +176,12 @@ const EXEMPT_ROUTES: ReadonlyArray<string> = [
|
||||
const MODULE_GATE_NEEDLE = "requireModuleEnabled(";
|
||||
const CYCLE_GATE_NEEDLE = "requireCycleEnabled(";
|
||||
const COACH_GATE_NEEDLE = 'requireAssistantSurface("coach")';
|
||||
// v1.18.1 — the illness journal's thin gate wrapper. `requireIllnessEnabled`
|
||||
// delegates to `requireModuleEnabled(userId, "illness")` and re-stamps the
|
||||
// illness-specific errorCode, exactly mirroring how `cycle` delegates to
|
||||
// `requireCycleEnabled`. Recognised as a delegated gate so illness routes
|
||||
// are not flagged as ungated.
|
||||
const ILLNESS_GATE_NEEDLE = "requireIllnessEnabled(";
|
||||
// Builder aggregators that resolve `resolveModuleMap` once and exclude
|
||||
// disabled-module sections/resources at the build boundary.
|
||||
const BUILDER_GATE_NEEDLES: ReadonlyArray<string> = [
|
||||
@@ -251,6 +265,7 @@ describe("module API route gate inventory", () => {
|
||||
"workouts",
|
||||
"recovery",
|
||||
"labs",
|
||||
"illness",
|
||||
"achievements",
|
||||
"cycle",
|
||||
"coach",
|
||||
@@ -276,6 +291,7 @@ describe("module API route gate inventory", () => {
|
||||
if (fileHasCall(text, MODULE_GATE_NEEDLE)) continue;
|
||||
if (fileHasCall(text, CYCLE_GATE_NEEDLE)) continue;
|
||||
if (fileHasCall(text, COACH_GATE_NEEDLE)) continue;
|
||||
if (fileHasCall(text, ILLNESS_GATE_NEEDLE)) continue;
|
||||
if (BUILDER_GATE_NEEDLES.some((n) => fileHasCall(text, n))) continue;
|
||||
|
||||
if (exempt.has(path)) continue;
|
||||
@@ -346,6 +362,7 @@ describe("module API route gate inventory", () => {
|
||||
fileHasCall(text, MODULE_GATE_NEEDLE) ||
|
||||
fileHasCall(text, CYCLE_GATE_NEEDLE) ||
|
||||
fileHasCall(text, COACH_GATE_NEEDLE) ||
|
||||
fileHasCall(text, ILLNESS_GATE_NEEDLE) ||
|
||||
BUILDER_GATE_NEEDLES.some((n) => fileHasCall(text, n))
|
||||
) {
|
||||
stillGated.push(path);
|
||||
|
||||
Reference in new issue
Block a user