Skip to main content
Glama
bulkdata.ts3.27 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { ContentType, flatMapFilter, isNotFound, notFound, OperationOutcomeError } from '@medplum/core'; import type { AsyncJob, BulkDataExport } from '@medplum/fhirtypes'; import type { Request, Response } from 'express'; import { Router } from 'express'; import { getAuthenticatedContext } from '../context'; import { rewriteAttachments, RewriteMode } from './rewrite'; // Bulk Data API // https://hl7.org/fhir/uv/bulkdata/STU2/ // https://hl7.org/fhir/uv/bulkdata/STU2/OperationDefinition-group-export.html // https://hl7.org/fhir/R4/async.html // https://hl7.org/fhir/smart-app-launch/backend-services.html export const bulkDataRouter = Router(); async function getExportResource(id: string): Promise<AsyncJob | BulkDataExport> { const { repo } = getAuthenticatedContext(); let resource: AsyncJob | BulkDataExport; try { resource = await repo.readResource<AsyncJob>('AsyncJob', id); } catch (err: unknown) { if (err instanceof OperationOutcomeError && isNotFound(err.outcome)) { resource = await repo.readResource<BulkDataExport>('BulkDataExport', id); } else { throw err; } } return resource; } bulkDataRouter.get('/export/:id', async (req: Request, res: Response) => { const ctx = getAuthenticatedContext(); const { id } = req.params; const bulkDataExport = await getExportResource(id); if (bulkDataExport.status === 'cancelled') { res.status(404).json(notFound); return; } else if (bulkDataExport.status !== 'completed') { res.status(202).end(); return; } const json = await rewriteAttachments(RewriteMode.PRESIGNED_URL, ctx.repo, { transactionTime: bulkDataExport.transactionTime, request: bulkDataExport.request, requiresAccessToken: false, // Rewritten attachments use presigned S3 URLs and do not require the access token output: extractOutputParameters(bulkDataExport, 'output'), error: extractOutputParameters(bulkDataExport, 'error'), deleted: extractOutputParameters(bulkDataExport, 'deleted'), }); res.status(200).type(ContentType.JSON).json(json); }); bulkDataRouter.delete('/export/:id', async (req: Request, res: Response) => { const { id } = req.params; const bulkDataExport = await getExportResource(id); if (bulkDataExport.status !== 'cancelled') { bulkDataExport.status = 'cancelled'; const { repo } = getAuthenticatedContext(); await repo.updateResource(bulkDataExport); } res.sendStatus(202); }); function extractOutputParameters(input: BulkDataExport | AsyncJob | undefined, name: string): unknown[] { if (input?.resourceType === 'BulkDataExport') { switch (name) { case 'output': return input.output ?? []; case 'error': return input.error ?? []; case 'deleted': return input.deleted ?? []; default: return []; } } return flatMapFilter(input?.output?.parameter, (param) => { if (param.name === name) { return { type: param.part?.find((part) => part.name === 'type')?.valueCode, url: param.part?.find((part) => part.name === 'url')?.valueUri, }; } else { return undefined; } }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server