mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
docs(openapi): document the profile module map and the 403 module.disabled envelope
Add the resolved `modules` map to ProfileResponse (shared ModuleMap schema) so the spec matches the alias payload, and add a reusable module-disabled 403 response documenting `meta.errorCode = module.disabled` plus `meta.module` (the disabled key). Wire that 403 onto the module-gated sleep night/rhythm routes, which emit it via requireModuleEnabled. Regenerate docs/api/openapi.yaml; openapi:check is in sync.
This commit is contained in:
1 parent
f435142db5
commit
3090b742c4
4 files changed
+50
-15
No files matched your search
@@ -14,7 +14,7 @@ import {
|
||||
measurementSourceEnum,
|
||||
} from "@/lib/validations/measurement";
|
||||
import { deviceTypeEnum } from "@/lib/validations/source-priority";
|
||||
import { dataEnvelope, stdResponses } from "./shared";
|
||||
import { dataEnvelope, moduleDisabledResponse, stdResponses } from "./shared";
|
||||
|
||||
const batchEntrySchema = z
|
||||
.object({
|
||||
@@ -600,6 +600,7 @@ export const measurementPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
...moduleDisabledResponse,
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
@@ -619,6 +620,7 @@ export const measurementPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
...moduleDisabledResponse,
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -219,11 +219,15 @@ const profileResponse = z
|
||||
.string()
|
||||
.nullable()
|
||||
.describe("German KVNR, decrypted for the form prefill."),
|
||||
// v1.18.0 — resolved module enable/disable map, identical to the
|
||||
// /api/auth/me projection. The native client reads this alias, so it
|
||||
// must carry the same server-authoritative module flags.
|
||||
modules: moduleMapResolved,
|
||||
})
|
||||
.meta({
|
||||
id: "ProfileResponse",
|
||||
description:
|
||||
"Flattened profile fields for the native client (GET). The KVNR is decrypted server-side; the IKNR (v1.8.6) is plaintext at rest.",
|
||||
"Flattened profile fields for the native client (GET). The KVNR is decrypted server-side; the IKNR (v1.8.6) is plaintext at rest. The `modules` map mirrors the /api/auth/me projection so the alias carries the same module flags.",
|
||||
});
|
||||
|
||||
// The PATCH echo mirrors GET but reports KVNR presence as a boolean
|
||||
|
||||
@@ -124,3 +124,19 @@ export const consentRequiredResponse = {
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
};
|
||||
|
||||
// ── Module-disabled gate (v1.18.0) ───────────────────────────────────
|
||||
// Every module-scoped route runs `requireModuleEnabled(userId, key)`,
|
||||
// which returns a 403 when the account has the module turned off — even
|
||||
// with a valid Bearer token. The envelope carries
|
||||
// `meta.errorCode = "module.disabled"` and `meta.module` (the disabled
|
||||
// module key) so the iOS retry classifier branches on it and the client
|
||||
// can drop the whole surface. The errorEnvelope shape already declares
|
||||
// `meta.errorCode`; `meta.module` is documented here in prose.
|
||||
export const moduleDisabledResponse = {
|
||||
"403": {
|
||||
description:
|
||||
'Module disabled for this account: the user (or operator) has the module turned off. `meta.errorCode` = `module.disabled` and `meta.module` carries the disabled module key (e.g. "sleep"). Returned even for a valid Bearer token. Clients hide the whole module surface end-to-end rather than retry.',
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user