docs(openapi): add GET/PUT /api/auth/me/report-selection

Documents the saved doctor-report scope endpoint, reusing the real
savedReportProfileSchema so the spec's leaf cap and version literal
stay tied to the source of truth.
This commit is contained in:
Marc-André Bombeck committed 2026-07-30 20:03:26 +02:00
1 parent ec2c8a9e71
commit b7074e04b8
2 files changed
+205

No files matched your search

+131
View File
@@ -5699,6 +5699,48 @@ paths:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a3
"429": *a4
/api/auth/me/report-selection:
get:
tags:
- Auth
summary: Read the owner's saved doctor-report selection
description: "Returns the saved report-scope profile, or `profile: null` when the account has never saved one (or the
stored blob no longer parses against the current leaf catalogue). Replaces the retired `GET
/api/auth/me/doctor-report-prefs`. Auth via cookie or Bearer."
responses:
"200":
description: The saved profile, or null.
content:
application/json:
schema:
$ref: "#/components/schemas/GetReportSelectionResponseEnvelope"
"401": *a1
"422": *a3
"429": *a4
put:
tags:
- Auth
summary: Replace the owner's saved doctor-report selection
description: Replace, not merge — the body states the whole scope or it is not a scope; a leaf omitted from the body is
a leaf removed from the saved profile. An unknown leaf id is refused with a 422 that names it. The saved
selection is also what the FHIR REST face and the MCP doctor-visit surfaces replay for a caller that cannot ask
a human, so every write is audit-logged. Auth via cookie or Bearer.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SavedReportProfile"
responses:
"200":
description: The saved profile, echoed back.
content:
application/json:
schema:
$ref: "#/components/schemas/PutReportSelectionResponseEnvelope"
"401": *a1
"422": *a3
"429": *a4
/api/insights/chat:
get:
tags:
@@ -13691,6 +13733,40 @@ components:
`null` switches the alert off. `baseUpdatedAt` is the optional optimistic-concurrency token: echo it to have the
server refuse (409) a write that would silently revert a category another writer set from a fresher read (e.g.
iOS `medication.clientManaged`). Omit it for the legacy unconditional write."
SavedReportProfile:
type: object
properties:
v:
type: number
const: 2
leaves:
maxItems: 91
type: array
items:
type: string
minLength: 1
maxLength: 64
format:
type: string
enum:
- pdf
- fhir
- package
rangeDays:
type: integer
minimum: 1
maximum: 365
includeCharts:
type: boolean
required:
- v
- leaves
- format
- rangeDays
- includeCharts
additionalProperties: false
description: "A saved doctor-report scope: which leaves are included plus the export format, trailing window and chart
toggle. Replace-only — there is no partial-merge write."
FencedCoachChatRequest:
type: object
properties:
@@ -24686,6 +24762,61 @@ components:
- data
- error
additionalProperties: false
GetReportSelectionResponseEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/GetReportSelectionResponse"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
GetReportSelectionResponse:
type: object
properties:
profile:
anyOf:
- $ref: "#/components/schemas/SavedReportProfile"
- type: "null"
description: The saved profile, or null when the account has never saved one, or when the stored blob no longer parses
against the current leaf catalogue. Null is never resolved to a default scope server-side — the client
applies its own named template and shows that as a visible choice.
required:
- profile
additionalProperties: false
PutReportSelectionResponseEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/PutReportSelectionResponse"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
PutReportSelectionResponse:
type: object
properties:
profile:
$ref: "#/components/schemas/SavedReportProfile"
required:
- profile
additionalProperties: false
CoachConversationsPageResponse:
type: object
properties:
+74
View File
@@ -12,6 +12,7 @@ import { notificationPrefsSchema } from "@/lib/validations/notification-prefs";
import { sourcePrioritySchema } from "@/lib/validations/source-priority";
import { modulePrefsPatchSchema } from "@/lib/validations/modules";
import { MODULE_KEYS } from "@/lib/modules/registry";
import { savedReportProfileSchema } from "@/lib/report-selection/profile-shape";
import {
dataEnvelope,
errorEnvelope,
@@ -199,6 +200,33 @@ const coachPrefsPutRequest = coachPrefsSchema
"Coach prompt-tuning preferences to persist, plus the optional optimistic-concurrency `baseUpdatedAt` token. The guarded write covers both `coachPrefsJson` and the mirrored `insightsExcludeMetrics` column atomically. Omit the token for the legacy unconditional write.",
});
// v1.28-era — the owner's saved doctor-report selection (leaf list plus the
// render choices the export panel restores). `v` is the report-selection
// schema version (currently locked at 2); `leaves` is a closed catalogue of
// section/field ids, canonicalised to a fixed order server-side so two
// clients that chose the same scope persist the same bytes. This is the ONLY
// request body for the PUT — it replaces the stored profile whole, it never
// merges.
const savedReportProfile = savedReportProfileSchema.meta({
id: "SavedReportProfile",
description:
"A saved doctor-report scope: which leaves are included plus the export format, trailing window and chart toggle. Replace-only — there is no partial-merge write.",
});
const reportSelectionGetResponse = z
.object({
profile: savedReportProfile
.nullable()
.describe(
"The saved profile, or null when the account has never saved one, or when the stored blob no longer parses against the current leaf catalogue. Null is never resolved to a default scope server-side — the client applies its own named template and shows that as a visible choice.",
),
})
.meta({ id: "GetReportSelectionResponse" });
const reportSelectionPutResponse = z
.object({ profile: savedReportProfile })
.meta({ id: "PutReportSelectionResponse" });
// v1.8.6 — profile read/write surface for the native client. The
// runtime validation lives in `src/lib/validations/auth.ts`
// (`profileSchema`, whose transforms normalise empty → null and run the
@@ -850,4 +878,50 @@ export const profilePaths: NonNullable<ZodOpenApiObject["paths"]> = {
},
},
},
"/api/auth/me/report-selection": {
get: {
tags: ["Auth"],
summary: "Read the owner's saved doctor-report selection",
description:
"Returns the saved report-scope profile, or `profile: null` when the account has never saved one (or the stored blob no longer parses against the current leaf catalogue). Replaces the retired `GET /api/auth/me/doctor-report-prefs`. Auth via cookie or Bearer.",
responses: {
"200": {
description: "The saved profile, or null.",
content: {
"application/json": {
schema: dataEnvelope(
reportSelectionGetResponse,
"GetReportSelectionResponseEnvelope",
),
},
},
},
...stdResponses,
},
},
put: {
tags: ["Auth"],
summary: "Replace the owner's saved doctor-report selection",
description:
"Replace, not merge — the body states the whole scope or it is not a scope; a leaf omitted from the body is a leaf removed from the saved profile. An unknown leaf id is refused with a 422 that names it. The saved selection is also what the FHIR REST face and the MCP doctor-visit surfaces replay for a caller that cannot ask a human, so every write is audit-logged. Auth via cookie or Bearer.",
requestBody: {
required: true,
content: { "application/json": { schema: savedReportProfile } },
},
responses: {
"200": {
description: "The saved profile, echoed back.",
content: {
"application/json": {
schema: dataEnvelope(
reportSelectionPutResponse,
"PutReportSelectionResponseEnvelope",
),
},
},
},
...stdResponses,
},
},
},
};