UNPKG

enketo-transformer

Version:

Library that transforms ODK-compliant XForms into a format that Enketo can consume

1 lines 8.02 kB
{"version":3,"file":"api.cjs","sources":["../../src/api.ts"],"sourcesContent":["/**\n * @module api\n *\n * @description This is not a robust, secure web API. It is just a quick starting point.\n * This repo is not used in production as a web API (only as a library).\n *\n * See inventory of work to be done here: https://github.com/enketo/enketo-transformer/labels/web-api-only.\n *\n * PRs are very welcome!\n */\n\nimport express from 'express';\nimport { readFileSync } from 'fs';\nimport { dirname, resolve } from 'path';\nimport { request } from 'undici';\nimport { fileURLToPath } from 'url';\nimport { transform } from './transformer';\n\nconst router = express.Router();\n\nclass FetchError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.status = status;\n }\n}\n\nconst getXForm = async (req: express.Request) => {\n const { xform } = req.query;\n\n try {\n if (typeof xform !== 'string') {\n throw new FetchError(\n 400,\n 'An `xform` query parameter is required.'\n );\n }\n\n if (!xform.includes('/')) {\n const basePath = resolve(\n dirname(fileURLToPath(import.meta.url)),\n '../'\n );\n const paths = [\n resolve(basePath, './test/forms', xform),\n resolve(\n basePath,\n './test/external-fixtures/enketo-core',\n xform\n ),\n ];\n\n for (const path of paths) {\n try {\n return readFileSync(path, 'utf-8');\n } catch {\n // Try next path...\n }\n }\n\n throw new FetchError(400, `Could not find XForm: ${xform}`);\n }\n\n const response = await request(xform, {\n headers: {\n 'X-OpenRosa-Version': '1.0',\n },\n });\n const { statusCode } = response;\n\n if (statusCode === 401) {\n throw new FetchError(\n statusCode,\n 'Forbidden. Authorization Required.'\n );\n }\n\n if (statusCode < 200 || statusCode >= 300) {\n throw new FetchError(statusCode, `Request to ${xform} failed.`);\n }\n\n return response.body.text();\n } catch (error) {\n console.error(`Error occurred when requesting ${xform}`, error);\n\n if (error instanceof Error && !(error instanceof FetchError)) {\n throw new FetchError(500, error.message ?? 'Unknown error.');\n }\n\n throw error;\n }\n};\n\nconst getTransformedSurvey = async (req: express.Request) => {\n const isPost = req.method === 'POST';\n const payload = isPost ? req.body : req.query;\n const { markdown, openclinica, theme } = payload;\n const media = isPost ? payload.media : {};\n const xform = req.method === 'POST' ? payload.xform : await getXForm(req);\n\n return transform({\n xform,\n markdown: markdown !== 'false',\n openclinica: openclinica === 'true',\n media,\n theme,\n });\n};\n\nrouter\n .all('/', (req, res, next) => {\n try {\n if (req.app.get('secure')) {\n throw new FetchError(405, 'Not Allowed.');\n }\n\n const payload = req.method === 'POST' ? req.body : req.query;\n\n if (payload.xform == null) {\n throw new FetchError(400, 'Bad Request.');\n }\n\n // Allow requests from anywhere\n res.set('Access-Control-Allow-Origin', '*');\n next();\n } catch (error) {\n next(error);\n }\n })\n .get('/', async (req, res, next) => {\n try {\n const survey = await getTransformedSurvey(req);\n\n res.json(survey);\n } catch (error) {\n next(error);\n }\n })\n .post('/', async (req, res, next) => {\n try {\n const survey = await getTransformedSurvey(req);\n\n res.json(survey);\n } catch (error) {\n next(error);\n }\n })\n // for development purposes, to return HTML that can be easily inspected in the developer console\n .get('/htmlform', async (req, res, next) => {\n try {\n const { form } = await getTransformedSurvey(req);\n\n res.set('Content-Type', 'text/html');\n res.end(form);\n } catch (error) {\n next(error);\n }\n });\n\nconst errorHandler: express.ErrorRequestHandler = (\n error: unknown,\n _req,\n res\n) => {\n if (error instanceof FetchError) {\n res.status(error.status).send(\n `${error.message} (stack: ${error.stack})`\n );\n } else {\n res.status(500).send(`Unknown error: ${error}`);\n }\n};\n\n/** @package */\nexport const api = (app: express.Application) => {\n app.use('/transform', router, errorHandler);\n};\n\n/**\n * Exported for backwards compatibility, prefer named imports.\n */\nexport default api;\n"],"names":["resolve","dirname","fileURLToPath","path","readFileSync","request","transform"],"mappings":"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"}