Controller.js

/**
 * Import vendor modules
 * @ignore
 */
const {Router} = require('express');

/**
 * Import own modules
 * @ignore
 */
const Logger = require('./Logger');
const Status = require('./Status');

class Controller {
    /**
     * Controller name used for internal logs
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @type {string}
     */
    name = '';

    /**
     * Express router instance used by the Express server
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @type {Router}
     */
    router = Router();

    /**
     * Array of routes which to implement within Express
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @type {array.<object>}
     */
    routes = [];

    /**
     * Controller class
     *
     * @class Controller
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} name - Name of the Controller
     *
     * @example
     * const {Controller} = require('@neobeach/core');
     * const controller = new Controller('IndexController');
     *
     * controller.get('/', [], (req, res) => {
     *     res.json(1000, {
     *         hello: 'world!'
     *     });
     * });
     */
    constructor(name) {
        // Check if a name string is given
        if(typeof name !== 'string' && name !== '') {
            throw new Error(`A Controller must be named. Got: ${name}`);
        }

        // Set the Controller name
        this.name = name;
    }

    /**
     * Internal override functions of the Express Response Object
     *
     * @access private
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param res - Express Response Object
     * @return {{redirect: redirect, res, badRequest: badRequest, created: (function(): *), forbidden: forbidden, paymentRequired: paymentRequired, xml: xml, unauthorized: unauthorized, json: json, html: html, notFound: notFound, tooManyRequests: tooManyRequests, text: text, render: render, conflict: conflict}}
     */
    #response(res) {
        return {
            res: res,
            render: (view, params = {}) => {
                res.render(view, params);
            },
            redirect: (statusCode, url) => {
                res.redirect(statusCode, url);
            },
            text: (text) => {
                res.set('Content-Type', 'text/plain');
                res.status(200).send(text);
            },
            html: (html) => {
                res.set('Content-Type', 'text/html');
                res.status(200).send(html);
            },
            json: (code, json) => {
                res.set('Content-Type', 'application/json');
                res.status(200).send(JSON.stringify({
                    status: Status(code),
                    data: json
                }));
            },
            xml: (xml) => {
                res.set('Content-Type', 'application/xml');
                res.status(200).send(xml);
            },
            created: () => {
                return res.sendStatus(201);
            },
            badRequest: () => {
                res.set('Content-Type', 'text/plain');
                res.status(400).send('Bad Request');
            },
            unauthorized: () => {
                res.set('Content-Type', 'text/plain');
                res.status(401).send('Unauthorized');
            },
            paymentRequired: () => {
                res.set('Content-Type', 'text/plain');
                res.status(402).send('Payment Required');
            },
            forbidden: () => {
                res.set('Content-Type', 'text/plain');
                res.status(403).send('Forbidden');
            },
            notFound: () => {
                res.set('Content-Type', 'text/plain');
                res.status(404).send('Not Found');
            },
            conflict: () => {
                res.set('Content-Type', 'text/plain');
                res.status(409).send('Conflict');
            },
            tooManyRequests: () => {
                res.set('Content-Type', 'text/plain');
                res.status(429).send('Too Many Requests');
            }
        }
    }

    /**
     * Add a new middleware to the Internal Controller's Express Router
     *
     * @access private
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} path - A path to bind the middleware function to
     * @param {function(*, *, *)} middleware - An Express middleware function
     */
    #addMiddleware(path, middleware) {
        this.router.use(path, middleware);
    }

    /**
     * Add a new route to the Internal Controller's Express Router
     *
     * @access private
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} method - The HTTP method used by Express
     * @param {string} path - A path to bind the handler function to
     * @param {function(*, *)} handler - A handler function that handles the incoming HTTP request
     */
    #addRoute(method, path, handler) {
        this.router[method](path, (req, res) => handler(req, this.#response(res)));
    }

    /**
     * Add a new route and middlewares to the controller
     *
     * @access private
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} method - The HTTP method used by Express
     * @param {string} path - A path to bind the handler function to
     * @param {function(*, *)} handler - A handler function that handles the incoming HTTP request
     * @param {array.<function(*, *, *)>} middlewares - An array of Middlewares
     */
    #add(method, path, handler, middlewares) {
        // Check if we have a method string
        if(typeof method !== "string") {
            throw new Error(`Missing method string!`);
        }

        // Check if we have a path string
        if(typeof path !== "string") {
            throw new Error(`Missing path string!`);
        }

        // Check if we have a handler function
        if(typeof handler !== "function") {
            throw new Error(`Missing handler function!`);
        }

        // Check if we have a middleware array
        if(!Array.isArray(middlewares)) {
            throw new Error(`Missing middleware array!`);
        }

        // Add all middlewares to the controller
        middlewares.forEach((middleware) => {
            this.#addMiddleware(path, middleware);
        });

        // Add the route to the controller
        this.#addRoute(method, path, handler);

        // Add the route itself for reference to the routes array
        this.routes.push({
            path,
            method,
            handler,
            middlewares
        });
    }

    /**
     * Get the internal Express Router
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} routerName - Parent Router name. Used to logging
     * @ignore
     */
    getRouter(routerName) {
        // Check if we have routes available
        if(this.routes.length === 0) {
            Logger.warn(`[CONTROLLER] ${routerName}/${this.name} is initialized without routes!`);
        }

        // Return internal Express Router
        return this.router;
    }

    /**
     * Add a GET route to the Controller
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} path - A path to bind the handler function to
     * @param {array.<function(*, *, *)>} middlewares - An array of Middlewares
     * @param {function(*, *)} handler - A handler function that handles the incoming HTTP request
     *
     * @example
     * const {Controller} = require('@neobeach/core');
     * const controller = new Controller('IndexController');
     *
     * controller.get('/', [], (req, res) => {
     *     // Custom handler code here
     * });
     */
    get(path, middlewares, handler) {
        this.#add('get', path, handler, middlewares);
    }

    /**
     * Add a POST route to the Controller
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} path - A path to bind the handler function to
     * @param {array.<function(*, *, *)>} middlewares - An array of Middlewares
     * @param {function(*, *)} handler - A handler function that handles the incoming HTTP request
     *
     * @example
     * const {Controller} = require('@neobeach/core');
     * const controller = new Controller('IndexController');
     *
     * controller.post('/', [], (req, res) => {
     *     // Custom handler code here
     * });
     */
    post(path, middlewares, handler) {
        this.#add('post', path, handler, middlewares);
    }

    /**
     * Add a PUT route to the Controller
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} path - A path to bind the handler function to
     * @param {array.<function(*, *, *)>} middlewares - An array of Middlewares
     * @param {function(*, *)} handler - A handler function that handles the incoming HTTP request
     *
     * @example
     * const {Controller} = require('@neobeach/core');
     * const controller = new Controller('IndexController');
     *
     * controller.put('/', [], (req, res) => {
     *     // Custom handler code here
     * });
     */
    put(path, middlewares, handler) {
        this.#add('put', path, handler, middlewares);
    }

    /**
     * Add a PATCH route to the Controller
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} path - A path to bind the handler function to
     * @param {array.<function(*, *, *)>} middlewares - An array of Middlewares
     * @param {function(*, *)} handler - A handler function that handles the incoming HTTP request
     *
     * @example
     * const {Controller} = require('@neobeach/core');
     * const controller = new Controller('IndexController');
     *
     * controller.patch('/', [], (req, res) => {
     *     // Custom handler code here
     * });
     */
    patch(path, middlewares, handler) {
        this.#add('patch', path, handler, middlewares);
    }

    /**
     * Add a DELETE route to the Controller
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} path - A path to bind the handler function to
     * @param {array.<function(*, *, *)>} middlewares - An array of Middlewares
     * @param {function(*, *)} handler - A handler function that handles the incoming HTTP request
     *
     * @example
     * const {Controller} = require('@neobeach/core');
     * const controller = new Controller('IndexController');
     *
     * controller.delete('/', [], (req, res) => {
     *     // Custom handler code here
     * });
     */
    delete(path, middlewares, handler) {
        this.#add('delete', path, handler, middlewares);
    }

    /**
     * Add a COPY route to the Controller
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} path - A path to bind the handler function to
     * @param {array.<function(*, *, *)>} middlewares - An array of Middlewares
     * @param {function(*, *)} handler - A handler function that handles the incoming HTTP request
     *
     * @example
     * const {Controller} = require('@neobeach/core');
     * const controller = new Controller('IndexController');
     *
     * controller.copy('/', [], (req, res) => {
     *     // Custom handler code here
     * });
     */
    copy(path, middlewares, handler) {
        this.#add('copy', path, handler, middlewares);
    }

    /**
     * Add a HEAD route to the Controller
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} path - A path to bind the handler function to
     * @param {array.<function(*, *, *)>} middlewares - An array of Middlewares
     * @param {function(*, *)} handler - A handler function that handles the incoming HTTP request
     *
     * @example
     * const {Controller} = require('@neobeach/core');
     * const controller = new Controller('IndexController');
     *
     * controller.head('/', [], (req, res) => {
     *     // Custom handler code here
     * });
     */
    head(path, middlewares, handler) {
        this.#add('head', path, handler, middlewares);
    }

    /**
     * Add a OPTIONS route to the Controller
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} path - A path to bind the handler function to
     * @param {array.<function(*, *, *)>} middlewares - An array of Middlewares
     * @param {function(*, *)} handler - A handler function that handles the incoming HTTP request
     *
     * @example
     * const {Controller} = require('@neobeach/core');
     * const controller = new Controller('IndexController');
     *
     * controller.options('/', [], (req, res) => {
     *     // Custom handler code here
     * });
     */
    options(path, middlewares, handler) {
        this.#add('options', path, handler, middlewares);
    }

    /**
     * Add a PURGE route to the Controller
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} path - A path to bind the handler function to
     * @param {array.<function(*, *, *)>} middlewares - An array of Middlewares
     * @param {function(*, *)} handler - A handler function that handles the incoming HTTP request
     *
     * @example
     * const {Controller} = require('@neobeach/core');
     * const controller = new Controller('IndexController');
     *
     * controller.purge('/', [], (req, res) => {
     *     // Custom handler code here
     * });
     */
    purge(path, middlewares, handler) {
        this.#add('purge', path, handler, middlewares);
    }

    /**
     * Add a LOCK route to the Controller
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} path - A path to bind the handler function to
     * @param {array.<function(*, *, *)>} middlewares - An array of Middlewares
     * @param {function(*, *)} handler - A handler function that handles the incoming HTTP request
     *
     * @example
     * const {Controller} = require('@neobeach/core');
     * const controller = new Controller('IndexController');
     *
     * controller.lock('/', [], (req, res) => {
     *     // Custom handler code here
     * });
     */
    lock(path, middlewares, handler) {
        this.#add('lock', path, handler, middlewares);
    }

    /**
     * Add a UNLOCK route to the Controller
     *
     * @access public
     * @since 1.0.0
     * @author Glenn de Haan
     * @copyright MIT
     *
     * @param {string} path - A path to bind the handler function to
     * @param {array.<function(*, *, *)>} middlewares - An array of Middlewares
     * @param {function(*, *)} handler - A handler function that handles the incoming HTTP request
     *
     * @example
     * const {Controller} = require('@neobeach/core');
     * const controller = new Controller('IndexController');
     *
     * controller.unlock('/', [], (req, res) => {
     *     // Custom handler code here
     * });
     */
    unlock(path, middlewares, handler) {
        this.#add('unlock', path, handler, middlewares);
    }
}

/**
 * Export the Controller class
 * @ignore
 */
module.exports = Controller;