Source: app.js

"use strict";

const cluster = require("cluster");
const express = require("express");
const bodyParser = require("body-parser");
const morgan = require("morgan");
const cors = require("cors");
const winston = require("winston");
const bluebird = require("bluebird");

/**
 * This is the application module.
 *
 * @module app
 */

/**
 * Starts the application and returns it.
 *
 * Please note that this module is designed to be a singleton. Any subsequent loading
 * (require("app")) will return the already started application if any.
 *
 * If `environment.debug === true` it doesn't create any workers, thus a single process.
 * Otherwise, it creates a worker for each CPU.
 *
 * Dead workers will be replaced with new ones.
 *
 * Please note that `startedCallback` is called for each worker.
 *
 * @alias module:app
 * @param {module:environment} environment  - environment to use
 * @param {Function} startedCallback        - callback to call once application is started. Receives the `app` instance.
 * @return {module:app} the app that is created
 */
module.exports = function startApp(environment, startedCallback) {
// IMPL note: about `startedCallback is called for each worker`:
//            it was very hard to cluster master to get notified.
//            when workers started and then calling this callback.
//            it doesn't make sense sense anyway since workers die
//            and get replaced.


    // this definition must come before require('./routes')
    const app = express();

    // let's export the Express app as the module exports.
    // that might be useful if something here needs to be accessible by another module
    // see http://stackoverflow.com/questions/14822584/express-accessing-app-set-settings-in-routes
    // we override the export so that any subsequent loading of this module
    // returns the same app. this approached is used in so many places.
    module.exports = app;

    // Bring Mongoose into the app.
    // see http://theholmesoffice.com/mongoose-connection-best-practice/
    // Bluebird suggests that it is better to promisify Mongoose
    // than using Mongoose's
    // see http://bluebirdjs.com/docs/working-with-callbacks.html#mongoose
    const mongoose = bluebird.promisifyAll(require("mongoose"));

    /**
     * Promisified Mongoose instance is exported so that we don't need to
     * do it in submodules as well.
     * @member mongoose
     * @instance mongoose
     */
    app.mongoose = mongoose;

    const constants = require("./constants");
    const numCPUs = require("os").cpus().length;

    // clustering
    // we create a worker for each CPU if no debug env
    if (!environment.debug && cluster.isMaster) {
        // fork based on number of CPUs
        for (let i = 0; i < numCPUs; i++) {
            cluster.fork();
        }

        // Listen for dying workers
        cluster.on("exit", function (worker) {
            winston.info(`worker ${worker.process.pid} died`);
            // Replace the dead worker, we're not sentimental
            cluster.fork();
        });
    }
    else {
        // if we're in a worker: start the application
        // if we're in master but we have env.debug===true: start the application
        startApplication(startedCallback);
    }

    let server;

    /**
     * Stops the application for current worker.
     * @method shutdown
     * @instance shutdown
     * @param {Function} callback - Called when application is shut down.
     */
    app.shutdown = function (callback) {
        winston.info("Shutting down...");
        mongoose.connection.close(function () {
            server.close(function () {
                module.exports = startApp;
                callback();
            });
        });
    };

    return app;

    /////////////////////////////////////////////////////

    function startApplication(startedCallback) {
        // Workers can share any TCP connection
        // In this case it is an HTTP server

        // When successfully connected to DB
        mongoose.connection.on("connected", function () {
            winston.info("Mongoose default connection open to %s", environment.databaseUrl);

            enableCORS();
            addMiddlewares();
            enableRoutes();
            registerErrorHandlers();

            // start server when DB connection is successful
            server = app.listen(constants.listenPort, constants.listenHost, function () {
                const host = server.address().address;
                const port = server.address().port;

                winston.info("App listening at http://%s:%s", host, port);
                if (startedCallback) {
                    startedCallback(app);
                }
            });
        });

        // If the DB connection throws an error
        mongoose.connection.on("error", function (err) {
            winston.error("Mongoose default connection error", err);
        });

        // When the DB connection is disconnected
        mongoose.connection.on("disconnected", function () {
            winston.info("Mongoose default connection disconnected");
        });

        // If the Node process ends, close the Mongoose connection
        //noinspection ES6ModulesDependencies
        process
            .on("SIGINT", gracefulExit)
            .on("SIGTERM", gracefulExit);

        try {
            // register models
            require("./models");
            mongoose.connect(environment.databaseUrl);
            winston.info("Trying to connect to DB : %s", environment.databaseUrl);
        } catch (err) {
            winston.error("Sever initialization failed ", err.message);
        }
    }

    function gracefulExit() {
        mongoose.disconnect(function () {
            winston.info("Mongoose default connection disconnected through app termination");
            //noinspection ES6ModulesDependencies
            process.exit(0);
        });
    }

    function enableCORS() {
        // enable CORS and enable CORS preflight
        app.use(cors());
        app.options("*", cors()); // this must be included before other routes
    }

    function addMiddlewares() {
        app.use(morgan("dev"));
        app.use(bodyParser.json());

        // auth middleware: this will check if the passed access token is valid
        // only the requests that start with /api/v1/auth/* will be checked for the token.
        const authenticateV1 = require("./middlewares/v1/authenticate");
        app.all("/api/v1/auth/*", [authenticateV1]);
    }

    function enableRoutes() {
        // ok, time to enable all routes
        const routes = require("./routes");
        app.use("/", routes);
    }

    function registerErrorHandlers() {
        // register a 404 handler
        app.use(function (req, res) {
            res.status(404).json("Sorry cant find that!");
        });

        // register a 500 handler
        app.use(function (err, req, res) {
            winston.error(err.stack);
            res.status(500).json("Something broke!");
        });
    }
};