From 9be8780edfb6e4db3af71f204d6a71f2b84b7eb7 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Auad Date: Thu, 30 Apr 2026 16:01:16 +0000 Subject: [PATCH] Ajustadas las rutas de los servicios --- bus-gateway/app.js | 7 +- bus-gateway/controllers/iti65.js | 200 ++++++++++++++++++++++++------- bus-gateway/controllers/iti68.js | 35 ++---- bus-gateway/routes/iti104.js | 5 +- bus-gateway/routes/iti65.js | 10 +- bus-gateway/routes/iti67.js | 3 +- bus-gateway/routes/iti68.js | 4 +- bus-gateway/routes/iti78.js | 5 +- nginx/http.conf | 21 +++- nginx/https.conf | 22 +++- 10 files changed, 215 insertions(+), 97 deletions(-) diff --git a/bus-gateway/app.js b/bus-gateway/app.js index 649b7e9..18dfbdc 100644 --- a/bus-gateway/app.js +++ b/bus-gateway/app.js @@ -16,16 +16,17 @@ var app = express(); app.use(logger('dev')); app.use(express.json({ type: ['application/json', 'application/fhir+json'] })); -// ITI-65: Provide Document Bundle → POST /fhir +// ITI-65: Provide Document Bundle → POST /fhir/IPSTransaction (transaction Bundle) +// POST /fhir/Bundle (IPS document Bundle) app.use('/fhir', iti65Router); // ITI-67: Find Document References → GET /fhir/DocumentReference app.use('/fhir', iti67Router); -// ITI-68: Retrieve Document → GET /fhir/Binary?url=[URL_Directa] +// ITI-68: Retrieve Document → GET /fhir/Bundle?url=[URL_Directa] app.use('/fhir', iti68Router); -// ITI-78: Patient Demographics → GET /fhir/Patient[/:id] +// ITI-78: Patient Demographics → GET /fhir/Patient, GET /fhir/Patient/:id app.use('/fhir', iti78Router); // ITI-104: Patient Identity Feed → POST /fhir/Patient, PUT /fhir/Patient/:id diff --git a/bus-gateway/controllers/iti65.js b/bus-gateway/controllers/iti65.js index 5e7b586..e0001f9 100644 --- a/bus-gateway/controllers/iti65.js +++ b/bus-gateway/controllers/iti65.js @@ -70,26 +70,141 @@ function generateDocumentReferenceResource(subjectReference, bundleUrl) { async function getResourcesFromTransactionResponse(transactionResponse) { const promises = transactionResponse.entry.map(async (e) => { - const resource = getResourceByUrl(`${config.fhir.url}/${e.response.location}`); + const resource = await getResourceByUrl(`${config.fhir.url}/${e.response.location}`); return resource; }); return Promise.all(promises); } /** - * ITI-65: Provide Document Bundle (MHD) - * - * POST /fhir/Bundle - * - * Flow: - * 1. Store the IPS Bundle in HAPI FHIR. - * 2. Resolve the patient national identifier via Bus $match. - * 3. Register a DocumentReference in the Bus pointing to the stored Bundle. - * - * Required header: - * x-custodian-id — Efector identifier value in the Federador MSAL system. + * Construye un Bundle de tipo transaction a partir de un Bundle de tipo document (IPS). + * Genera Patient, Bundle (IPS), DocumentReference y List (SubmissionSet MHD). + * Usa urn:uuid: como fullUrl para que HAPI FHIR resuelva las referencias internas. */ -async function provideDocumentBundle(req, res, next) { +function buildTransactionFromIPSDocument(ipsBundle) { + if (ipsBundle.type !== 'document') { + throw createError(400, 'Bundle must be of type document'); + } + const patientEntry = (ipsBundle.entry || []).find( + e => e.resource && e.resource.resourceType === 'Patient' + ); + if (!patientEntry) { + throw createError(400, 'IPS Bundle must contain a Patient resource'); + } + + const patientFullUrl = `urn:uuid:${uuidv4()}`; + const bundleFullUrl = `urn:uuid:${uuidv4()}`; + const documentReferenceFullUrl = `urn:uuid:${uuidv4()}`; + + const now = new Date().toISOString(); + + const localDocumentReference = { + resourceType: 'DocumentReference', + status: 'current', + masterIdentifier: { + use: 'usual', + system: MASTER_ID_SYSTEM, + value: `urn:uuid:${uuidv4()}` + }, + type: { + coding: [{ + system: 'http://loinc.org', + code: '60591-5', + display: 'Patient Summary Document', + }] + }, + date: now, + subject: { reference: patientFullUrl }, + custodian: { + identifier: { system: CUSTODIAN_ID_SYSTEM, value: config.bus.issuer } + }, + content: [{ + attachment: { + url: bundleFullUrl, + contentType: 'application/fhir+json' + } + }] + }; + + const submissionSetList = { + resourceType: 'List', + status: 'current', + mode: 'working', + code: { + coding: [{ + system: 'https://profiles.ihe.net/ITI/MHD/CodeSystem/MHDlistTypes', + code: 'submissionset' + }] + }, + date: now, + subject: { reference: patientFullUrl }, + source: { + identifier: { system: CUSTODIAN_ID_SYSTEM, value: config.bus.issuer } + }, + entry: [ + { item: { reference: documentReferenceFullUrl } }, + { item: { reference: bundleFullUrl } } + ] + }; + + return { + resourceType: 'Bundle', + type: 'transaction', + entry: [ + { + fullUrl: patientFullUrl, + resource: patientEntry.resource, + request: { method: 'POST', url: 'Patient' } + }, + { + fullUrl: bundleFullUrl, + resource: ipsBundle, + request: { method: 'POST', url: 'Bundle' } + }, + { + fullUrl: documentReferenceFullUrl, + resource: localDocumentReference, + request: { method: 'POST', url: 'DocumentReference' } + }, + { + resource: submissionSetList, + request: { method: 'POST', url: 'List' } + } + ] + }; +} + +/** + * Lógica central de ITI-65: persiste la transacción en HAPI FHIR, resuelve el + * paciente en el Bus y registra un DocumentReference apuntando al Bundle guardado. + */ +async function executeITI65(transaction, token) { + const transactionResponse = await processDocumentBundleTransaction(transaction); + const resources = await getResourcesFromTransactionResponse(transactionResponse); + + const localPatient = extractResource(resources, PATIENT_RESOURCE_TYPE); + const localIPSDocument = extractResource(resources, IPS_DOCUMENT_RESOURCE_TYPE); + const localPatientIdentifier = extractLocalIdentifier(localPatient); + + const patientSearchset = await findPatient(token, { identifier: `${localPatientIdentifier.system}|${localPatientIdentifier.value}` }); + if (patientSearchset.total == 0) { + throw createError(404, 'Patient does not exists'); + } + const nationalPatientId = patientSearchset.entry[0].fullUrl; + const bundleReference = `${config.baseURL}/fhir/Bundle/${localIPSDocument.id}`; + + const documentReference = generateDocumentReferenceResource(nationalPatientId, bundleReference); + return createDocumentReference(token, documentReference); +} + +/** + * ITI-65: Provide Document Bundle (MHD) — variante transacción + * + * POST /fhir/iti65 + * + * Espera un Bundle de tipo transaction construido por el cliente. + */ +async function provideIPSTransaction(req, res, next) { try { const transaction = req.body; const token = await getBusToken( @@ -98,36 +213,39 @@ async function provideDocumentBundle(req, res, next) { config.bus.issuer, [config.bus.mpiScope, config.bus.documentRegistryScope].join(',') ); - - // Paso 1: Ejecuto la transcción en el servidor hapi subyacente - const transactionResponse = await processDocumentBundleTransaction(transaction); - - const resources = await getResourcesFromTransactionResponse(transactionResponse); - - // Paso 2: Obtengo el paciente local (tal como se guardo en la trasnaccion) - const localPatient = extractResource(resources, PATIENT_RESOURCE_TYPE); - // Paso 3: Obtengo el documento IPS local (tal como se guardo en la transacción) - const localIPSDocument = extractResource(resources, IPS_DOCUMENT_RESOURCE_TYPE) - - const localPatientIdentifier = extractLocalIdentifier(localPatient); - - const patientSearchset = await findPatient(token, { identifier: `${localPatientIdentifier.system}|${localPatientIdentifier.value}` }); - if (patientSearchset.total == 0) { - throw createError(404, 'Patient does not exists'); - } - const nationalPatientId = patientSearchset.entry[0].fullUrl; - const bundleReference = `${config.baseURL}/fhir/Bundle/${localIPSDocument.id}`; - - - const documentReference = generateDocumentReferenceResource(nationalPatientId, bundleReference); - - const createdDocumentReference = await createDocumentReference(token, documentReference); - - return res.status(200).json(createdDocumentReference); - + const result = await executeITI65(transaction, token); + return res.status(200).json(result); } catch (err) { next(err); } } -module.exports = { provideDocumentBundle }; +/** + * ITI-65: Provide Document Bundle (MHD) — variante IPS document + * + * POST /fhir/Bundle + * + * Espera un Bundle de tipo document (IPS). Genera internamente el Bundle + * transaction y ejecuta el mismo flujo que provideDocumentBundle. + */ +async function provideIPSDocumentBundle(req, res, next) { + try { + const ipsBundle = req.body; + if (!ipsBundle || ipsBundle.resourceType !== 'Bundle') { + throw createError(400, 'Request body must be a FHIR Bundle resource'); + } + const transaction = buildTransactionFromIPSDocument(ipsBundle); + const token = await getBusToken( + config.bus.url, + config.bus.jwtSecret, + config.bus.issuer, + [config.bus.mpiScope, config.bus.documentRegistryScope].join(',') + ); + const result = await executeITI65(transaction, token); + return res.status(200).json(result); + } catch (err) { + next(err); + } +} + +module.exports = { provideIPSTransaction, provideIPSDocumentBundle }; diff --git a/bus-gateway/controllers/iti68.js b/bus-gateway/controllers/iti68.js index eb872cf..f79a44e 100644 --- a/bus-gateway/controllers/iti68.js +++ b/bus-gateway/controllers/iti68.js @@ -1,43 +1,22 @@ const createError = require('http-errors'); -const axios = require('axios'); - - +const config = require('../config'); +const { getResourceByUrl } = require('../services/fhir'); /** * ITI-68: Retrieve Document (MHD) * - * GET /fhir/Binary?url=[URL_Directa] + * GET /fhir/Bundle/:id * - * Flow (diagram iti67.mmd, step 21-24): - * 1. HIS_A supplies the direct document URL obtained from a prior ITI-67 response. - * 2. This gateway performs a P2P GET to that URL, bypassing the Bus. - * 3. The raw document (PDF, XML, etc.) is forwarded back to HIS_A. + * Proxy transparente al servidor HAPI FHIR subyacente. */ async function getBundleById(req, res, next) { try { - const url = req.query.url; - if (!url) { - throw createError(400, 'Missing required query parameter: url'); - } - let parsedUrl; - try { - parsedUrl = new URL(url); - } catch { - throw createError(400, 'Invalid document URL'); - } - const response = await getDocumentBundleByUrl(url); - - const contentType = response.headers['content-type']; - if (contentType) res.setHeader('Content-Type', contentType); - - const contentDisposition = response.headers['content-disposition']; - if (contentDisposition) res.setHeader('Content-Disposition', contentDisposition); - - res.status(200).send(response.data); + const bundle = await getResourceByUrl(`${config.fhir.url}/Bundle/${req.params.id}`); + res.status(200).json(bundle); } catch (err) { if (err.response) { - return next(createError(err.response.status, `Document source returned ${err.response.status}`)); + return next(createError(err.response.status, `HAPI FHIR returned ${err.response.status}`)); } next(err); } diff --git a/bus-gateway/routes/iti104.js b/bus-gateway/routes/iti104.js index 0f1507a..420da6b 100644 --- a/bus-gateway/routes/iti104.js +++ b/bus-gateway/routes/iti104.js @@ -3,8 +3,7 @@ const router = express.Router(); const { createPatient, updatePatient } = require('../controllers/iti104'); // ITI-104: Patient Identity Feed FHIR -// TODO: Cambiar ruta por ITI104 -router.post('/iti104', createPatient); -router.put('/iti104/:id', updatePatient); +router.post('/Patient', createPatient); +router.put('/Patient/:id', updatePatient); module.exports = router; diff --git a/bus-gateway/routes/iti65.js b/bus-gateway/routes/iti65.js index 6c9531d..0387cd7 100644 --- a/bus-gateway/routes/iti65.js +++ b/bus-gateway/routes/iti65.js @@ -1,9 +1,11 @@ const express = require('express'); const router = express.Router(); -const { provideDocumentBundle } = require('../controllers/iti65'); +const { provideIPSTransaction, provideIPSDocumentBundle } = require('../controllers/iti65'); -// ITI-65: Provide Document Bundle -// TODO: Cambiar la ruta de acceso para que no se confunda con el recurso Bundle cambiar por iti65 -router.post('/iti65', provideDocumentBundle); +// ITI-65: Provide Document Bundle — variante transaction Bundle +router.post('/IPSTransaction', provideIPSTransaction); + +// ITI-65: Provide Document Bundle — variante IPS document Bundle (type: document) +router.post('/Bundle', provideIPSDocumentBundle); module.exports = router; diff --git a/bus-gateway/routes/iti67.js b/bus-gateway/routes/iti67.js index e704822..03ca41d 100644 --- a/bus-gateway/routes/iti67.js +++ b/bus-gateway/routes/iti67.js @@ -3,7 +3,6 @@ const router = express.Router(); const { listDocumentReference } = require('../controllers/iti67'); // ITI-67: Find Document References -// TODO: Cambiar ruta por ITI67 -router.get('/iti67', listDocumentReference); +router.get('/DocumentReference', listDocumentReference); module.exports = router; diff --git a/bus-gateway/routes/iti68.js b/bus-gateway/routes/iti68.js index fd2046c..e97c645 100644 --- a/bus-gateway/routes/iti68.js +++ b/bus-gateway/routes/iti68.js @@ -2,7 +2,7 @@ const express = require('express'); const router = express.Router(); const { getBundleById } = require('../controllers/iti68'); -// ITI-68: Retrieve Document → GET /fhir/Binary?url=[URL_Directa] -router.get('/Bundle', getBundleById); +// ITI-68: Retrieve Document → GET /fhir/Bundle/:id +router.get('/Bundle/:id', getBundleById); module.exports = router; diff --git a/bus-gateway/routes/iti78.js b/bus-gateway/routes/iti78.js index 001093f..e1fdd75 100644 --- a/bus-gateway/routes/iti78.js +++ b/bus-gateway/routes/iti78.js @@ -3,8 +3,7 @@ const router = express.Router(); const { listPatient, getPatientById } = require('../controllers/iti78'); // ITI-78: Mobile Patient Demographics Query -// TOOD: Cambiar ruta por ITI78 -router.get('/iti78', listPatient); -router.get('/iti78/:id', getPatientById); +router.get('/Patient', listPatient); +router.get('/Patient/:id', getPatientById); module.exports = router; diff --git a/nginx/http.conf b/nginx/http.conf index aae77e3..18084ba 100644 --- a/nginx/http.conf +++ b/nginx/http.conf @@ -14,7 +14,9 @@ http { server_name _; # Rutas del bus-gateway (prefijo más específico gana sobre /fhir/) - location /fhir/iti65 { + + # ITI-65: Provide Document Bundle — transaction Bundle + location /fhir/IPSTransaction { proxy_pass http://bus_gateway; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -23,7 +25,9 @@ http { proxy_read_timeout 90s; } - location /fhir/iti67 { + # ITI-65: Provide Document Bundle — IPS document Bundle + # ITI-68: Retrieve Document + location /fhir/Bundle { proxy_pass http://bus_gateway; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -32,7 +36,8 @@ http { proxy_read_timeout 90s; } - location /fhir/iti78 { + # ITI-67: Find Document References + location /fhir/DocumentReference { proxy_pass http://bus_gateway; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -40,7 +45,10 @@ http { proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 90s; } - location /fhir/iti104 { + + # ITI-78: Mobile Patient Demographics Query + # ITI-104: Patient Identity Feed FHIR + location /fhir/Patient { proxy_pass http://bus_gateway; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -48,14 +56,16 @@ http { proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 90s; } + location /vhl/ { - proxy_pass http://bus_gateway; + proxy_pass http://bus_gateway; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 90s; } + # Resto de /fhir/* va a hapi-fhir location /fhir/ { proxy_pass http://hapi_fhir; @@ -74,6 +84,7 @@ http { proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 90s; } + # Todo lo demás va a hapi-fhir location / { proxy_pass http://hapi_fhir; diff --git a/nginx/https.conf b/nginx/https.conf index 9b895e7..3ddefc8 100644 --- a/nginx/https.conf +++ b/nginx/https.conf @@ -30,8 +30,9 @@ http { ssl_session_timeout 10m; # Rutas del bus-gateway (prefijo más específico gana sobre /fhir/) - # Rutas del bus-gateway (prefijo más específico gana sobre /fhir/) - location /fhir/iti65 { + + # ITI-65: Provide Document Bundle — transaction Bundle + location /fhir/IPSTransaction { proxy_pass http://bus_gateway; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -40,7 +41,9 @@ http { proxy_read_timeout 90s; } - location /fhir/iti67 { + # ITI-65: Provide Document Bundle — IPS document Bundle + # ITI-68: Retrieve Document + location /fhir/Bundle { proxy_pass http://bus_gateway; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -49,7 +52,8 @@ http { proxy_read_timeout 90s; } - location /fhir/iti78 { + # ITI-67: Find Document References + location /fhir/DocumentReference { proxy_pass http://bus_gateway; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -57,7 +61,10 @@ http { proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 90s; } - location /fhir/iti104 { + + # ITI-78: Mobile Patient Demographics Query + # ITI-104: Patient Identity Feed FHIR + location /fhir/Patient { proxy_pass http://bus_gateway; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -65,14 +72,16 @@ http { proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 90s; } + location /vhl/ { - proxy_pass http://bus_gateway; + proxy_pass http://bus_gateway; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 90s; } + # Resto de /fhir/* va a hapi-fhir location /fhir/ { proxy_pass http://hapi_fhir; @@ -91,6 +100,7 @@ http { proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 90s; } + # Todo lo demás va a hapi-fhir location / { proxy_pass http://hapi_fhir;