TryGhost/Ghost

View on GitHub
ghost/api-framework/lib/http.js

Summary

Maintainability
C
1 day
Test Coverage
const url = require('url');
const debug = require('@tryghost/debug')('http');

const Frame = require('./Frame');
const headers = require('./headers');

/**
 * @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 {import('@tryghost/api-framework').Controller} apiImpl - Pipeline wrapper, which executes the target ctrl function.
 * @return {import('express').RequestHandler}
 */
const http = (apiImpl) => {
    /**
     * @param {import('express').Request} req - Express request object.
     * @param {import('express').Response} res - Express response object.
     * @param {import('express').NextFunction} next - Express next function.
     * @returns {Promise<import('express').RequestHandler>}
     */
    return async function Http(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')
            };
        }

        if (req.user?.id) {
            user = req.user.id;
        }

        const frame = new 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
        });

        try {
            const result = await apiImpl(frame);

            debug(`External API request to ${frame.docName}.${frame.method}`);

            // 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
            const apiHeaders = await headers.get(result, apiImpl.headers, frame) || {};
            res.set(apiHeaders);

            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;