const config = require('../config'); const { createBusRequest } = require('../utils/busAuth'); const { default: axios } = require('axios'); const INDICE_ATENCION_URL = config.bus.documentRegistryUrl; const LOCAL_URL = "https://federador.qa-bus-interoperabilidad.svc.cluster.local:8080/fhir/DocumentReference"; function maskPrivateURL(patientId) { return patientId.replace(LOCAL_URL, INDICE_ATENCION_URL); } async function findDocumentReferenceById(token, id) { const request = createBusRequest(token); const response = await request.get( URL.parse(`${INDICE_ATENCION_URL}/${id}`) ); return response.data; } async function findDocumentReferenceByUrl(token, id) { const request = createBusRequest(token); const response = await request.get( URL.parse(id, INDICE_ATENCION_URL) ); return response.data; } /** * Posts a DocumentReference to the document registry * (POST /fhir/DocumentReference). * @param {string} documentRegistryUrl - Base URL of the document registry service. * @param {string} token - Bearer access token. * @param {object} documentReference - FHIR DocumentReference resource to register. * @returns {Promise} The created DocumentReference resource returned by the registry. * @todo Agregar system http://federador.msal.gob.ar/uri para armar el identificador del custodian: http://federador.msal.gob.ar/uri| */ async function createDocumentReference(token, documentReference) { const request = createBusRequest(token); const response = await request.post( URL.parse(INDICE_ATENCION_URL), documentReference ); const location = maskPrivateURL(response.headers['location']); return findDocumentReferenceByUrl(token, location); } /** * Searches DocumentReferences in the document registry by patient subject identifier * (GET /fhir/DocumentReference?subject=system|value). * @param {string} documentRegistryUrl - Base URL of the document registry service. * @param {string} token - Bearer access token. * @param {string} subjectSystem - Identifier system of the patient. * @param {string} subjectValue - Identifier value of the patient. * @returns {Promise} FHIR Bundle (searchset) with matching DocumentReferences. */ async function findDocumentReferenceBySubject(token, subjectSystem, subjectValue) { const request = createBusRequest(token); const response = await request.get( URL.parse(INDICE_ATENCION_URL), { params: { subject: `${subjectSystem}|${subjectValue}` }, } ); return response.data; } /** * Parses a FHIR token search value (`system|code`, `|code` o `code`) into its parts. * `system === null` indica la forma `|code` (exige coding sin system). * `system === undefined` indica la forma `code` (sin restricción de system). * @param {string} token * @returns {{ system: string|null|undefined, code: string }} */ function parseTypeToken(token) { const pipeIndex = token.indexOf('|'); if (pipeIndex === -1) { return { system: undefined, code: token }; } const system = token.slice(0, pipeIndex); return { system: system === '' ? null : system, code: token.slice(pipeIndex + 1) }; } /** * Indica si el CodeableConcept `type` de un DocumentReference matchea el token FHIR dado, * replicando la semántica estándar de búsqueda por token (`system|code`, `|code` o `code`) * que el índice de documentos aplicaría del lado del servidor. * @param {object} resource - DocumentReference resource. * @param {string} token - Token FHIR de DocumentReference.type. * @returns {boolean} */ function matchesTypeToken(resource, token) { const { system, code } = parseTypeToken(token); const codings = resource?.type?.coding ?? []; return codings.some(coding => { if (system === null) { return !coding.system && coding.code === code; } if (system !== undefined) { return coding.system === system && coding.code === code; } return coding.code === code; }); } /** * Filtra las entries de un Bundle searchset de DocumentReference por type, * ajustando `total` a la cantidad de entries resultantes. * @param {object} bundle - FHIR Bundle (searchset). * @param {string} type - Token FHIR de DocumentReference.type. * @returns {object} Bundle filtrado. */ function filterBundleByType(bundle, type) { if (!bundle || !Array.isArray(bundle.entry)) { return bundle; } const entry = bundle.entry.filter(e => matchesTypeToken(e.resource, type)); return { ...bundle, entry, total: entry.length }; } /** * Searches DocumentReferences in the document registry by patient internal ID, * opcionalmente filtrando por tipo de documento * (GET /fhir/DocumentReference?patient=value[&type=system|code]). * * Si `config.bus.filterDocumentTypeLocally` está activo y se pidió `type`, el parámetro * `type` no se envía al índice de documentos: se piden todos los DocumentReference del * subject y el filtrado por tipo se hace localmente sobre el Bundle resultante. * @param {string} documentRegistryUrl - Base URL of the document registry service. * @param {string} token - Bearer access token. * @param {string} patientId - Internal patient ID in the document registry. * @param {string} [type] - Token FHIR (`system|code` o `code`) de DocumentReference.type, ej: `http://loinc.org|60591-5`. * @returns {Promise} FHIR Bundle (searchset) with matching DocumentReferences. */ async function findDocumentReferenceByPatient(token, patientId, type) { const request = createBusRequest(token); const filterLocally = Boolean(type) && config.bus.filterDocumentTypeLocally; const params = { subject: patientId }; if (type && !filterLocally) { params.type = type; } const response = await request.get( URL.parse(INDICE_ATENCION_URL), { params } ); return filterLocally ? filterBundleByType(response.data, type) : response.data; } /** * Searches DocumentReferences in the document registry filtered by subject, custodian and type * (GET /fhir/DocumentReference?subject=...&custodian=...&type=...). * @param {string} documentRegistryUrl - Base URL of the document registry service. * @param {string} token - Bearer access token. * @param {{ system: string, value: string }} subject - Patient identifier (TokenParam). * @param {{ system: string, value: string }} custodian - Custodian identifier (TokenParam). * @param {{ system: string, value: string }} type - Document type (TokenParam, e.g. { system: 'http://loinc.org', value: '60591-5' }). * @returns {Promise} FHIR Bundle (searchset) with matching DocumentReferences. */ async function searchDocumentReference(token, subject, custodian, type) { const request = createBusRequest(token); const response = await request.get( URL.parse(INDICE_ATENCION_URL), { params: { subject: `${subject.system}|${subject.value}`, custodian: `${custodian.system}|${custodian.value}`, type: `${type.system}|${type.value}`, }, }); return response.data; } module.exports = { findDocumentReferenceById, createDocumentReference, findDocumentReferenceBySubject, findDocumentReferenceByPatient, searchDocumentReference };