libflitter/middleware/MiddlewareUnit.js

/**
 * @module libflitter/middleware/MiddlewareUnit
 */

const rra = require('recursive-readdir-async')
const path = require('path')
const Unit = require('../Unit')

/**
 * The middleware unit is responsible for parsing and loading middleware
 * defined in the specified directory. It then stores it such that it can
 * be accessed using the global mw() function.
 *
 * @extends module:libflitter/Unit~Unit
 */
class MiddlewareUnit extends Unit {

    /**
     * Instantiate the unit. Resolves the paths to th middleware directory and the global middleware list.
     * @param {string} [mw_path = './app/routing/middleware'] - path to the directory containing the middleware definition files
     * @param {string} [global_definitions_file = './app/routing/Middleware.js'] - path to the file containing a list of middleware to be applied to all routes
     */
    constructor(mw_path = './app/routing/middleware', global_definitions_file = './app/routing/Middleware.js') {
        super()

        /**
         * The fully-qualified path to the folder of the middleware definition files.
         * @name MiddlewareUnit#directory
         * @type {string}
         */
        this.directory = path.resolve(mw_path)

        /**
         * The fully-qualified path to the file containing a list of middleware to be applied to all routes.
         * @name MiddlewareUnit#globals_file
         * @type {string}
         */
        this.globals_file = path.resolve(global_definitions_file)
    }

    /**
     * Loads the unit. For each middleware definition file in {@link module:libflitter/middleware/MiddlewareUnit~MiddlewareUnit#directory},
     * store it in an object by its Flitter canonical name. Then, bind the daemon and helper functions to the appropriate contexts.
     * @param {module:libflitter/app/FlitterApp~FlitterApp} app - the Flitter app
     * @param {module:libflitter/Context~Context} context - the unit's context
     * @returns {Promise<void>}
     */
    async go(app, context){

        /*
         * Get the middleware files, recursively.
         */
        const files = await rra.list(this.directory)

        app.d.utility.log("Middleware directory: "+this.directory, 3)
        const middleware = {}

        /*
         * Parse the middleware name from the folder
         * and file name.
         * e.g. "auth/RedirectIfGuest.middleware.js" becomes "auth:RedirectIfGuest"
         */
        for ( let key in files ){
            if ( files[key].fullname.endsWith('.middleware.js') ) {
                let name = files[key].fullname.replace(this.directory, '')
                    .replace(/.middleware.js/g, '')
                    .replace(/\//g, ':').substr(1)

                /*
                 * Create and store instances of each middleware class.
                 */
                let mw_class = require(files[key].fullname)
                middleware[name] = new mw_class()
            }
        }

        context.bind('daemon', middleware)

        app.d.utility.log("Middleware definitions loaded!", 2)
        app.d.utility.log(middleware, 4)

        context.bind('mw', this.mw)
        app.global.bind('mw', this.mw)

        const globals = require(this.globals_file)
        context.bind('globals', globals)

        app.d.utility.log('Loaded Global Middleware Definitions!', 4)
        app.d.utility.log(globals, 4)

        /*
         * Tell the underlying express app to apply each of these
         * middlewares before every request.
         */
        for ( let i in globals ){
            app.express.use( middleware[globals[i]].test )
        }
    }

    /**
     * A helper function to return the Express middleware function for a registered middleware, using its Flitter canonical name.
     * This is usually bound to the {@link module:libflitter/app/FlitterApp~FlitterApp} context.
     * @param {string} name - the Flitter canonical name of the middleware whose handler should be returned
     * @param {*} args - An argument or arguments to be passed to the middleware function as the 4th argument.
     * @returns {function} - the Express middleware
     */
    mw(name, args){
        const self = this
        if ( typeof args !== "undefined" ){
            return (req, res, next) => {
                return self.d.middleware.daemon[name].test(req, res, next, args)
            }
        }
        return this.d.middleware.daemon[name].test
    }

    /**
     * Get the name of the unit.
     * @returns {string} "middleware"
     */
    name(){
        return "middleware"
    }

    /**
     * Get the directories provided by this unit.
     * {@link module:libflitter/middleware/MiddlewareUnit~MiddlewareUnit#directory} as "middleware".
     * @returns {{middleware: string}}
     */
    directories() {
        return {
            middleware: this.directory,
            global_middleware: this.globals_file,
        }
    }

    /**
     * Get the templates provided by this unit.
     * Currently, "middleware" template, using the generator {@link module:libflitter/templates/middleware}.
     * @returns {{middleware: {template: (middleware|(function(string): string)|*), extension: string, directory: string}}}
     */
    templates(){
        return {
            middleware: {
                template: require('libflitter/templates/middleware'),
                directory: this.directory,
                extension: '.middleware.js'
            }
        }
    }

    /**
     * Get the fully-qualified path to the migrations provided by this unit.
     * @returns {string}
     */
    migrations(){
        return path.resolve(__dirname, 'migrations')
    }
}

module.exports = MiddlewareUnit