const url = require('url'); const debug = require('ghost-ignition').debug('api:shared:http'); const shared = require('../shared'); const models = require('../../models'); /** * @description HTTP wrapper. * * This wrapper is used in the routes definition (see web/). * The wrapper receives the express request, prepares the frame and forwards the request to the pipeline. * * @param {Function} apiImpl - Pipeline wrapper, which executes the target ctrl function. * @return {Function} */ const http = (apiImpl) => { return (req, res, next) => { debug(`External API request to ${req.url}`); let apiKey = null; let integration = null; let user = null; if (req.api_key) { apiKey = { id: req.api_key.get('id'), type: req.api_key.get('type') }; integration = { id: req.api_key.get('integration_id') }; } // NOTE: "external user" is only used in the subscriber app. External user is ID "0". if ((req.user && req.user.id) || (req.user && models.User.isExternalUser(req.user.id))) { user = req.user.id; } const frame = new shared.Frame({ body: req.body, file: req.file, files: req.files, query: req.query, params: req.params, user: req.user, session: req.session, url: { host: req.vhost ? req.vhost.host : req.get('host'), pathname: url.parse(req.originalUrl || req.url).pathname, secure: req.secure }, context: { api_key: apiKey, user: user, integration: integration, member: (req.member || null) } }); frame.configure({ options: apiImpl.options, data: apiImpl.data }); apiImpl(frame) .then((result) => { debug(`External API request to ${frame.docName}.${frame.method}`); return shared.headers.get(result, apiImpl.headers, frame) .then(headers => ({result, headers})); }) .then(({result, headers}) => { // CASE: api ctrl wants to handle the express response (e.g. streams) if (typeof result === 'function') { debug('ctrl function call'); return result(req, res, next); } let statusCode = 200; if (typeof apiImpl.statusCode === 'function') { statusCode = apiImpl.statusCode(result); } else if (apiImpl.statusCode) { statusCode = apiImpl.statusCode; } res.status(statusCode); // CASE: generate headers based on the api ctrl configuration res.set(headers); const send = (format) => { if (format === 'plain') { debug('plain text response'); return res.send(result); } debug('json response'); res.json(result || {}); }; let responseFormat; if (apiImpl.response){ if (typeof apiImpl.response.format === 'function') { const apiResponseFormat = apiImpl.response.format(); if (apiResponseFormat.then) { // is promise return apiResponseFormat.then((formatName) => { send(formatName); }); } else { responseFormat = apiResponseFormat; } } else { responseFormat = apiImpl.response.format; } } send(responseFormat); }) .catch((err) => { req.frameOptions = { docName: frame.docName, method: frame.method }; next(err); }); }; }; module.exports = http;