Files
temetro/backend/src/services/fhir-server/capability.ts
T
Khalid Abdi 0d2494d67a feat: read-only FHIR R4 server (share records over /fhir)
Expose temetro's own records as a read-only FHIR R4 server at /fhir,
authenticated with per-clinic API keys (tmf_… bearer tokens, SHA-256
hashed, shown once). Serves Patient, Observation (labs + vitals),
AllergyIntolerance, Condition, MedicationRequest, Encounter and
Appointment as text-only CodeableConcepts (temetro stores free-text
clinical values); CapabilityStatement at /fhir/metadata (unauth).
Searchset Bundles with _count/_offset pagination and self/next/prev
links; every request is org-scoped and written to the activity log.
Keys are created/revoked under Settings → Integrations (owner/admin).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 01:27:32 +03:00

115 lines
3.4 KiB
TypeScript

// A static CapabilityStatement describing exactly what this read-only FHIR R4
// server supports. It is intentionally honest: only the resources and search
// params implemented below are listed, everything is `read`/`search-type` only,
// and clinical concepts are text-only (no SNOMED/LOINC coding).
type ResourceCapability = {
type: string;
interaction: { code: "read" | "search-type" }[];
searchParam?: { name: string; type: "token" | "string" | "reference" }[];
};
const RESOURCES: ResourceCapability[] = [
{
type: "Patient",
interaction: [{ code: "read" }, { code: "search-type" }],
searchParam: [
{ name: "identifier", type: "token" },
{ name: "name", type: "string" },
],
},
{
type: "Observation",
interaction: [{ code: "read" }, { code: "search-type" }],
searchParam: [
{ name: "patient", type: "reference" },
{ name: "patient.identifier", type: "token" },
{ name: "category", type: "token" },
],
},
{
type: "AllergyIntolerance",
interaction: [{ code: "read" }, { code: "search-type" }],
searchParam: [
{ name: "patient", type: "reference" },
{ name: "patient.identifier", type: "token" },
],
},
{
type: "Condition",
interaction: [{ code: "read" }, { code: "search-type" }],
searchParam: [
{ name: "patient", type: "reference" },
{ name: "patient.identifier", type: "token" },
],
},
{
type: "MedicationRequest",
interaction: [{ code: "read" }, { code: "search-type" }],
searchParam: [
{ name: "patient", type: "reference" },
{ name: "patient.identifier", type: "token" },
],
},
{
type: "Encounter",
interaction: [{ code: "read" }, { code: "search-type" }],
searchParam: [
{ name: "patient", type: "reference" },
{ name: "patient.identifier", type: "token" },
],
},
{
type: "Appointment",
interaction: [{ code: "read" }, { code: "search-type" }],
searchParam: [
{ name: "patient", type: "reference" },
{ name: "patient.identifier", type: "token" },
],
},
];
export function capabilityStatement(
baseUrl: string,
version: string,
): Record<string, unknown> {
return {
resourceType: "CapabilityStatement",
status: "active",
date: new Date().toISOString(),
publisher: "temetro",
kind: "instance",
implementation: { description: "temetro FHIR server", url: baseUrl },
software: { name: "temetro", version },
fhirVersion: "4.0.1",
format: ["application/fhir+json", "json"],
rest: [
{
mode: "server",
documentation:
"Read-only FHIR R4 server. Authenticate with a per-clinic API key: " +
"Authorization: Bearer tmf_…. Clinical values are text-only " +
"CodeableConcepts (no SNOMED/LOINC). Patients expose age (extension), " +
"not birthDate. Pagination via _count (default 50, max 200) and _offset.",
security: {
description: "Bearer token (per-organization API key, tmf_ prefix).",
service: [
{
coding: [
{
system:
"http://terminology.hl7.org/CodeSystem/restful-security-service",
code: "OAuth",
display: "OAuth",
},
],
text: "API key bearer token",
},
],
},
resource: RESOURCES,
},
],
};
}