feat(fhir): read-only FHIR R4 REST search routes

GET /api/fhir/{metadata,Patient,Observation,MedicationStatement,
MedicationAdministration} and $everything, returning type:"searchset"
Bundles (total + self/next links, offset paging via _count clamped to 200)
as application/fhir+json, with an OperationOutcome on error. The per-resource
emitters are reused from src/lib/fhir/resources.ts so the document export and
the REST face share one coding source and can never drift. Auth is a narrow
fhir:read scope via requireAuth (cookie sessions also pass); userId is
narrowed from auth. Read-only — no write handlers. Rate-limited per user.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.8 committed 2026-06-04 01:05:06 +02:00
1 parent 6c3b8be099
commit 57b8fd8ad9
10 files changed
+809

No files matched your search

+144
View File
@@ -2102,6 +2102,150 @@ export const openApiPaths: NonNullable<ZodOpenApiObject["paths"]> = {
},
},
},
"/api/fhir/metadata": {
get: {
tags: ["FHIR"],
summary: "FHIR R4 CapabilityStatement (v1.11.0)",
description:
"Read-only FHIR R4 capability statement for the REST face. Declares the served resource types (Patient, Observation, MedicationStatement, MedicationAdministration), the `$everything` operation, and the `application/fhir+json` format. Auth: `fhir:read` scope (cookie sessions also pass).",
responses: {
"200": {
description: "CapabilityStatement (application/fhir+json).",
content: {
"application/fhir+json": {
schema: z.string().meta({ format: "binary" }),
},
},
},
...stdResponses,
},
},
},
"/api/fhir/Patient": {
get: {
tags: ["FHIR"],
summary: "FHIR R4 Patient search (v1.11.0)",
description:
"Read-only `searchset` Bundle of the caller's own Patient resource. Auth: `fhir:read` scope. Offset paging via `_count` (clamped ≤200) / `_offset`. `userId` is narrowed from auth.",
requestParams: {
query: z.object({
_count: z.coerce.number().optional(),
_offset: z.coerce.number().optional(),
}),
},
responses: {
"200": {
description: "searchset Bundle (application/fhir+json).",
content: {
"application/fhir+json": {
schema: z.string().meta({ format: "binary" }),
},
},
},
...stdResponses,
},
},
},
"/api/fhir/Observation": {
get: {
tags: ["FHIR"],
summary: "FHIR R4 Observation search (v1.11.0)",
description:
"Read-only `searchset` Bundle of the caller's own Observations (vitals / activity / lab / survey). Auth: `fhir:read` scope. Offset paging via `_count` (clamped ≤200) / `_offset`.",
requestParams: {
query: z.object({
_count: z.coerce.number().optional(),
_offset: z.coerce.number().optional(),
}),
},
responses: {
"200": {
description: "searchset Bundle (application/fhir+json).",
content: {
"application/fhir+json": {
schema: z.string().meta({ format: "binary" }),
},
},
},
...stdResponses,
},
},
},
"/api/fhir/MedicationStatement": {
get: {
tags: ["FHIR"],
summary: "FHIR R4 MedicationStatement search (v1.11.0)",
description:
"Read-only `searchset` Bundle of the caller's own active-medication statements. Auth: `fhir:read` scope. Offset paging via `_count` (≤200) / `_offset`.",
requestParams: {
query: z.object({
_count: z.coerce.number().optional(),
_offset: z.coerce.number().optional(),
}),
},
responses: {
"200": {
description: "searchset Bundle (application/fhir+json).",
content: {
"application/fhir+json": {
schema: z.string().meta({ format: "binary" }),
},
},
},
...stdResponses,
},
},
},
"/api/fhir/MedicationAdministration": {
get: {
tags: ["FHIR"],
summary: "FHIR R4 MedicationAdministration search (v1.11.0)",
description:
"Read-only `searchset` Bundle of the caller's own acted intakes (completed / not-done). Auth: `fhir:read` scope. Offset paging via `_count` (≤200) / `_offset`.",
requestParams: {
query: z.object({
_count: z.coerce.number().optional(),
_offset: z.coerce.number().optional(),
}),
},
responses: {
"200": {
description: "searchset Bundle (application/fhir+json).",
content: {
"application/fhir+json": {
schema: z.string().meta({ format: "binary" }),
},
},
},
...stdResponses,
},
},
},
"/api/fhir/$everything": {
get: {
tags: ["FHIR"],
summary: "FHIR R4 $everything (v1.11.0)",
description:
"Read-only `$everything` operation: every resource in the caller's own record (Patient, Coverage, Observations, MedicationStatements, MedicationAdministrations) in one `searchset` Bundle. Auth: `fhir:read` scope. Offset paging via `_count` (≤200) / `_offset`.",
requestParams: {
query: z.object({
_count: z.coerce.number().optional(),
_offset: z.coerce.number().optional(),
}),
},
responses: {
"200": {
description: "searchset Bundle (application/fhir+json).",
content: {
"application/fhir+json": {
schema: z.string().meta({ format: "binary" }),
},
},
},
...stdResponses,
},
},
},
"/api/auth/login": {
post: {
tags: ["Auth"],