mirror of
https://github.com/temetro/temetro.git
synced 2026-08-29 03:46:52 +00:00
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>
This commit is contained in:
@@ -1692,6 +1692,32 @@
|
||||
"endpointPlaceholder": "https://your-clearinghouse.example/claims",
|
||||
"credentialsPlaceholder": "JSON: {\"token\":\"…\",\"submitterId\":\"…\",\"receiverId\":\"…\"}",
|
||||
"credentialsHint": "JSON مع رمز مركز المقاصة ومعرّفات المرسل/المستقبل."
|
||||
},
|
||||
"fhirServer": {
|
||||
"title": "خادم FHIR (مشاركة سجلاتك)",
|
||||
"description": "اعرض سجلات هذه العيادة للأنظمة الخارجية كخادم FHIR R4 للقراءة فقط، مع المصادقة عبر مفاتيح API خاصة بكل عيادة.",
|
||||
"baseUrl": "عنوان URL الأساسي",
|
||||
"baseUrlHint": "وجّه عميل FHIR إلى هذا العنوان. يقدّم Patient وObservation وCondition وAllergyIntolerance وMedicationRequest وEncounter وAppointment.",
|
||||
"copiedUrl": "تم نسخ العنوان الأساسي",
|
||||
"newKey": "إنشاء مفتاح API",
|
||||
"newKeyPlaceholder": "اسم المفتاح (مثل مستودع الأبحاث)",
|
||||
"create": "إنشاء",
|
||||
"creating": "جارٍ الإنشاء…",
|
||||
"createFailed": "تعذّر إنشاء المفتاح",
|
||||
"createFailedBody": "يرجى المحاولة مرة أخرى.",
|
||||
"secretTitle": "انسخ مفتاح API الآن",
|
||||
"secretHint": "هذه هي المرة الوحيدة التي يظهر فيها السر. احفظه في مكان آمن — لا يمكن استرجاعه لاحقًا.",
|
||||
"copiedSecret": "تم نسخ مفتاح API",
|
||||
"dismissSecret": "تم",
|
||||
"noKeys": "لا توجد مفاتيح API بعد. أنشئ واحدًا للسماح لعميل FHIR بالاتصال.",
|
||||
"lastUsed": "آخر استخدام {{when}}",
|
||||
"neverUsed": "لم يُستخدم قط",
|
||||
"revoke": "إبطال",
|
||||
"confirmRevoke": "إبطال المفتاح",
|
||||
"revoked": "تم الإبطال",
|
||||
"revokeFailed": "تعذّر إبطال المفتاح",
|
||||
"revokeFailedBody": "يرجى المحاولة مرة أخرى.",
|
||||
"cancel": "إلغاء"
|
||||
}
|
||||
},
|
||||
"empty": "لا شيء هنا بعد.",
|
||||
|
||||
@@ -1672,6 +1672,32 @@
|
||||
"endpointPlaceholder": "https://ihre-verrechnungsstelle.example/claims",
|
||||
"credentialsPlaceholder": "JSON: {\"token\":\"…\",\"submitterId\":\"…\",\"receiverId\":\"…\"}",
|
||||
"credentialsHint": "JSON mit Ihrem Token und Absender-/Empfänger-IDs der Verrechnungsstelle."
|
||||
},
|
||||
"fhirServer": {
|
||||
"title": "FHIR-Server (Datensätze teilen)",
|
||||
"description": "Stellen Sie die Datensätze dieser Praxis externen Systemen als schreibgeschützten FHIR-R4-Server bereit, authentifiziert über praxiseigene API-Schlüssel.",
|
||||
"baseUrl": "Basis-URL",
|
||||
"baseUrlHint": "Richten Sie einen FHIR-Client auf diese URL. Sie liefert Patient, Observation, Condition, AllergyIntolerance, MedicationRequest, Encounter und Appointment.",
|
||||
"copiedUrl": "Basis-URL kopiert",
|
||||
"newKey": "API-Schlüssel erstellen",
|
||||
"newKeyPlaceholder": "Schlüsselname (z. B. Forschungslager)",
|
||||
"create": "Erstellen",
|
||||
"creating": "Wird erstellt…",
|
||||
"createFailed": "Schlüssel konnte nicht erstellt werden",
|
||||
"createFailedBody": "Bitte erneut versuchen.",
|
||||
"secretTitle": "Kopieren Sie Ihren API-Schlüssel jetzt",
|
||||
"secretHint": "Das Geheimnis wird nur dieses eine Mal angezeigt. Bewahren Sie es sicher auf — es kann später nicht abgerufen werden.",
|
||||
"copiedSecret": "API-Schlüssel kopiert",
|
||||
"dismissSecret": "Fertig",
|
||||
"noKeys": "Noch keine API-Schlüssel. Erstellen Sie einen, damit sich ein FHIR-Client verbinden kann.",
|
||||
"lastUsed": "Zuletzt verwendet {{when}}",
|
||||
"neverUsed": "Nie verwendet",
|
||||
"revoke": "Widerrufen",
|
||||
"confirmRevoke": "Schlüssel widerrufen",
|
||||
"revoked": "Widerrufen",
|
||||
"revokeFailed": "Schlüssel konnte nicht widerrufen werden",
|
||||
"revokeFailedBody": "Bitte erneut versuchen.",
|
||||
"cancel": "Abbrechen"
|
||||
}
|
||||
},
|
||||
"empty": "Hier gibt es noch nichts.",
|
||||
|
||||
@@ -1672,6 +1672,32 @@
|
||||
"endpointPlaceholder": "https://your-clearinghouse.example/claims",
|
||||
"credentialsPlaceholder": "JSON: {\"token\":\"…\",\"submitterId\":\"…\",\"receiverId\":\"…\"}",
|
||||
"credentialsHint": "JSON with your clearinghouse token and submitter/receiver ids."
|
||||
},
|
||||
"fhirServer": {
|
||||
"title": "FHIR server (share your records)",
|
||||
"description": "Expose this clinic's records to external systems as a read-only FHIR R4 server, authenticated with per-clinic API keys.",
|
||||
"baseUrl": "Base URL",
|
||||
"baseUrlHint": "Point a FHIR client at this URL. It serves Patient, Observation, Condition, AllergyIntolerance, MedicationRequest, Encounter and Appointment.",
|
||||
"copiedUrl": "Base URL copied",
|
||||
"newKey": "Create an API key",
|
||||
"newKeyPlaceholder": "Key name (e.g. Research warehouse)",
|
||||
"create": "Create",
|
||||
"creating": "Creating…",
|
||||
"createFailed": "Couldn't create the key",
|
||||
"createFailedBody": "Please try again.",
|
||||
"secretTitle": "Copy your API key now",
|
||||
"secretHint": "This is the only time the secret is shown. Store it somewhere safe — it can't be retrieved later.",
|
||||
"copiedSecret": "API key copied",
|
||||
"dismissSecret": "Done",
|
||||
"noKeys": "No API keys yet. Create one to let a FHIR client connect.",
|
||||
"lastUsed": "Last used {{when}}",
|
||||
"neverUsed": "Never used",
|
||||
"revoke": "Revoke",
|
||||
"confirmRevoke": "Revoke key",
|
||||
"revoked": "Revoked",
|
||||
"revokeFailed": "Couldn't revoke the key",
|
||||
"revokeFailedBody": "Please try again.",
|
||||
"cancel": "Cancel"
|
||||
}
|
||||
},
|
||||
"empty": "Nothing here yet.",
|
||||
|
||||
@@ -1672,6 +1672,32 @@
|
||||
"endpointPlaceholder": "https://votre-chambre-compensation.example/claims",
|
||||
"credentialsPlaceholder": "JSON : {\"token\":\"…\",\"submitterId\":\"…\",\"receiverId\":\"…\"}",
|
||||
"credentialsHint": "JSON avec le jeton de votre chambre de compensation et les identifiants d'expéditeur/destinataire."
|
||||
},
|
||||
"fhirServer": {
|
||||
"title": "Serveur FHIR (partager vos dossiers)",
|
||||
"description": "Exposez les dossiers de cette clinique à des systèmes externes via un serveur FHIR R4 en lecture seule, authentifié par des clés d'API propres à la clinique.",
|
||||
"baseUrl": "URL de base",
|
||||
"baseUrlHint": "Pointez un client FHIR vers cette URL. Elle expose Patient, Observation, Condition, AllergyIntolerance, MedicationRequest, Encounter et Appointment.",
|
||||
"copiedUrl": "URL de base copiée",
|
||||
"newKey": "Créer une clé d'API",
|
||||
"newKeyPlaceholder": "Nom de la clé (ex. Entrepôt de recherche)",
|
||||
"create": "Créer",
|
||||
"creating": "Création…",
|
||||
"createFailed": "Impossible de créer la clé",
|
||||
"createFailedBody": "Veuillez réessayer.",
|
||||
"secretTitle": "Copiez votre clé d'API maintenant",
|
||||
"secretHint": "Le secret n'est affiché qu'une seule fois. Conservez-le en lieu sûr — il ne pourra pas être récupéré ensuite.",
|
||||
"copiedSecret": "Clé d'API copiée",
|
||||
"dismissSecret": "Terminé",
|
||||
"noKeys": "Aucune clé d'API pour l'instant. Créez-en une pour qu'un client FHIR puisse se connecter.",
|
||||
"lastUsed": "Dernière utilisation {{when}}",
|
||||
"neverUsed": "Jamais utilisée",
|
||||
"revoke": "Révoquer",
|
||||
"confirmRevoke": "Révoquer la clé",
|
||||
"revoked": "Révoquée",
|
||||
"revokeFailed": "Impossible de révoquer la clé",
|
||||
"revokeFailedBody": "Veuillez réessayer.",
|
||||
"cancel": "Annuler"
|
||||
}
|
||||
},
|
||||
"empty": "Rien ici pour le moment.",
|
||||
|
||||
@@ -1672,6 +1672,32 @@
|
||||
"endpointPlaceholder": "https://xarunta-xisaabintaada.example/claims",
|
||||
"credentialsPlaceholder": "JSON: {\"token\":\"…\",\"submitterId\":\"…\",\"receiverId\":\"…\"}",
|
||||
"credentialsHint": "JSON leh token-ka xarunta xisaabinta iyo aqoonsiyada diraha/qaataha."
|
||||
},
|
||||
"fhirServer": {
|
||||
"title": "Serfarka FHIR (la wadaag diiwaannadaada)",
|
||||
"description": "U soo bandhig diiwaannada rugtan nidaamyada dibadda ah sida serfar FHIR R4 akhris-oo-keliya, oo lagu ansixiyo furayaal API oo rug walba gaar u ah.",
|
||||
"baseUrl": "URL-ka aasaasiga ah",
|
||||
"baseUrlHint": "U tilmaam macmiil FHIR URL-kan. Wuxuu adeegaa Patient, Observation, Condition, AllergyIntolerance, MedicationRequest, Encounter iyo Appointment.",
|
||||
"copiedUrl": "URL-ka aasaasiga waa la koobiyeeyay",
|
||||
"newKey": "Samee fure API",
|
||||
"newKeyPlaceholder": "Magaca furaha (tusaale, Bakhaarka cilmi-baarista)",
|
||||
"create": "Samee",
|
||||
"creating": "Waa la samaynayaa…",
|
||||
"createFailed": "Furaha lama abuuri karin",
|
||||
"createFailedBody": "Fadlan mar kale isku day.",
|
||||
"secretTitle": "Hadda koobiyee furahaaga API",
|
||||
"secretHint": "Tanu waa markii kaliya ee sirta la muujiyo. Meel ammaan ah ku kaydi — lama soo ceshan karo dabadeed.",
|
||||
"copiedSecret": "Furaha API waa la koobiyeeyay",
|
||||
"dismissSecret": "Diyaar",
|
||||
"noKeys": "Weli ma jiraan furayaal API ah. Mid samee si macmiil FHIR u xidho.",
|
||||
"lastUsed": "Markii ugu dambeysay la isticmaalay {{when}}",
|
||||
"neverUsed": "Weligeed lama isticmaalin",
|
||||
"revoke": "Baabbi'i",
|
||||
"confirmRevoke": "Baabbi'i furaha",
|
||||
"revoked": "La baabbi'iyay",
|
||||
"revokeFailed": "Furaha lama baabbi'in karin",
|
||||
"revokeFailedBody": "Fadlan mar kale isku day.",
|
||||
"cancel": "Jooji"
|
||||
}
|
||||
},
|
||||
"empty": "Weli halkan waxba ma jiraan.",
|
||||
|
||||
@@ -64,6 +64,37 @@ export function submitInsuranceClaim(
|
||||
});
|
||||
}
|
||||
|
||||
// --- FHIR server API keys (owner/admin only) --------------------------------
|
||||
|
||||
export type FhirApiKey = {
|
||||
id: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
lastUsedAt: string | null;
|
||||
revoked: boolean;
|
||||
};
|
||||
|
||||
// A freshly created key includes the one-time plaintext secret; it is never
|
||||
// returned again.
|
||||
export type CreatedFhirApiKey = FhirApiKey & { secret: string };
|
||||
|
||||
export function listFhirKeys(): Promise<FhirApiKey[]> {
|
||||
return apiFetch<FhirApiKey[]>("/api/integrations/fhir-server/keys");
|
||||
}
|
||||
|
||||
export function createFhirKey(name: string): Promise<CreatedFhirApiKey> {
|
||||
return apiFetch<CreatedFhirApiKey>("/api/integrations/fhir-server/keys", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
}
|
||||
|
||||
export function revokeFhirKey(id: string): Promise<{ revoked: boolean }> {
|
||||
return apiFetch(`/api/integrations/fhir-server/keys/${id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
// Convenience hook-style fetch reused by the on-page sections: returns the
|
||||
// config for one type (or null while loading/absent).
|
||||
export async function getIntegration(
|
||||
|
||||
Reference in New Issue
Block a user