Source: app.js

const ALARMiatorPluginManager = require('./pluginManager/pluginManager');
const express = require('express');
const session = require('express-session');
const fileUpload = require('express-fileupload');
const bodyParser = require('body-parser');
const path = require('path');
const Database = require('./models/database'); // Database Manager
const Configuration = require('./models/configuration'); // Configuration of CORE
const CoreCron = require('./models/core/core_cron.js'); // Cron Manager

const uuid = require('uuid').v1;

var config = require('./config/config.js');
var log4js = require('log4js');
var cookieParser = require('cookie-parser')
var SQLiteStore = require('connect-sqlite3')(session);
const HandlerState = require('./models/core/handlerState');
const HandlerAlarm = require('./models/core/handlerAlarm');
const HandlerExternalIp = require('./models/core/handlerExternalIp');
const { EventEmitter } = require('events');

// Printer-related objects
const Printer = require('./models/core/core_printing');
var printing = new Printer();
global.printManager = printing;


// globally used objects
const FCMTokens = require('./models/fctmtokens');

class Core extends EventEmitter {
    constructor() {
        super();

        // inititalize class propertys
        this.coreDb = null;
        this.logger = null;                         // holds logger instance
        this.plgManager = null;                     // holds instance of plgManager
        // Initialize globals
        global.coreDb = null;                       // holds database instance to coredatabase
        global.appRoot = path.resolve(__dirname);   // holds complete path to application root
        global.app = null;                          // holds instance of this app globally
        global.appPort = null;                      // holds port number for UI
        global.cron = null;                         // holds core manager after initialization

        this.app = express();
        global.app = this.app;

        this.uuid = null;                           // holds the uuid of this server instance

        /**
         * initialize defaults for command line parameters handed over with server start
         */
        this.args_development = false;              // Server rund in development mode
        
    }

    /**
     * Read all arguments from server start
     */
    getStartupArguments() {
        process.argv.forEach(function (val, index, array) {
            
            if(val === '--development')
            {
                this.args_development = true;
                console.log('CORE | running in development-mode');
            }
            else if(val.startsWith('--pm2'))
            {
                this.args_startedByPM2 = true;
                this.args_PM2_Name = val.replace('--pm2=','');

                global.serverStartedByPM2 = this.args_startedByPM2;
                global.PM2_Name = this.args_PM2_Name;
                console.log('CORE | Start by PM2 : ' + global.serverStartedByPM2);
                console.log('CORE | PM2-Name : ' + global.PM2_Name);
            }
            
        }.bind(this));
        this.emit('getStartupArguments', true);
    }


    /**
     * Starts the coredatabase connection
     */
    startCoredatabase() {
        this.coreDb = new Database();
        this.coreDb.startDatabase((successDatabase) => {
            if (successDatabase) {
                global.coreDb = this.coreDb.db;
                this.emit('startCoredatabase', true);
                // Database started, start server now
            } else {
                // Database could not be started. -> Log error to console
                console.log('CORE | Error starting up database. Server could not be started. Please check logs for more details.');
                this.emit('startCoredatabase', false);
            }
        });
    }

    /**
     * Starts the global logger(s)
     */
    startLoggers() {
        /**
         * start new Logger
         */
        // create a rolling file logger based on date/time that fires process events
        const opts = {
            logDirectory: global.appRoot + '/logs/server', // NOTE: folder must exist and be writable...
            fileNamePattern: 'ALARMiator-Server-<DATE>.log',
            dateFormat: 'YYYY.MM.DD'
        };
        this.logmanager = require('simple-node-logger');
        this.logger = this.logmanager.createRollingFileLogger(opts);
        this.logger.setLevel('all');
        // this.logger should be removed later after the whole project used global.logger
        this.app.logger = this.logger;
        global.logger = this.logger;

        // Emit event
        this.emit('startLoggers', true);
    }

    /**
     * starts the server itself
     */
    setupServer() {
        this.port = 5000;
        try {
            this.port = config.serverOptions.uiport;
            this.emit('setupServer', true);
        } catch (err) {
            this.logger.debug('CONFIG: ' + err);
            this.logger.warn(`CORE | configuration for server port not set. Please check the config.js file in config folder. Using default port 5000 for UI.`);
            this.emit('setupServer', true);
        }
        global.appPort = this.port;
       
    }

    /**
     * Sets up the middleware (express)
     */
    setupMiddelware() {
        this.app.use(require('express-status-monitor')());
        this.app.set('port', process.env.port || this.port); // set express to use this port
        this.app.set('views', __dirname + '/views'); // set express to look in this folder to render our view
        this.app.set('view engine', 'ejs'); // configure template engine
        this.app.use(bodyParser.urlencoded({ extended: true }));
        this.app.use(bodyParser.json()); // parse form data client
        this.app.use(express.static(path.join(__dirname, 'public'))); // configure express to use public folder
        this.app.use(fileUpload()); // configure fileupload
    
        // Configure Session and Authentication handling (SQLite-Version)
        this.app.use(cookieParser());
        this.app.use(session({
            store: new SQLiteStore({
                table: 'sessions',
                db: 'sessions.db',
                dir: global.appRoot + '/store'
            }),
            secret: '9xa&aVPKe18XV;f',
            cookie: { maxAge: 2 * 24 * 60 * 60 * 1000 }, // 1 week
            resave: false,
            saveUninitialized: true,
            key: 'alarmiator',
            secret: 'ALRMiator#2019'
        }));
        this.emit('setupMiddleware', true);
    }

    /**
     * registers core handlers
     */
    startCoreHandlers() {
        this.handlerState = new HandlerState();
        this.handlerAlarm = new HandlerAlarm()
        this.handlerExternalIp = new HandlerExternalIp();

        this.emit('startCoreHandlers', true);
    }

    /**
     * Starts the plugin manager and makes it globally available
     */
    startPluginManager() {
        this.plgManager = new ALARMiatorPluginManager();
        global.plgManager = this.plgManager;
        // Read inbound plugins and initialize plugins
        this.plgManager.readInboundPluginList();
        this.plgManager.readOutboundPluginList();
        // reinitialize Plugins in plugin store
        this.plgManager.registerPluginsInStore(this.plgManager.pluginList);
        // Load configuration of plugins from database
        this.plgManager.initializeConfigFromStore((success) => {
            if (success) {
                this.plgManager.initializePlugins();
                this.emit('startPluginManager', true);
            } else {
                // Config could not be loaded from database
                tthis.emit('startPluginManager', false);
    
            }
        })
    }

    /**
     * Registers core to plugin manager events
     */
    registerPluginManagerEvents() {

        this.plgManager.on('event_new_zvei_alarm', (zveiInfo) => {
            // a new zvei alarm has been fired
            console.log('APP | New ZVEI Alarm Event fired with the following data:');
            console.log(zveiInfo);
        })
    
        this.plgManager.on('event_new_alarm', (alarmInfo) => {
            // a new zvei alarm has been fired
            console.log('APP | New Alarm Event fired with the following data:');
            this.handlerAlarm.persistNewAlarmInformation(alarmInfo);
        })
    
        this.plgManager.on('event_new_state', (stateInfo) => {
            // a new zvei alarm has been fired
            console.log('APP | New State Event fired with the following data:' + stateInfo.radioStatusHumanReadable + ' (' + stateInfo.issi + ')');
            this.handlerState.persistNewStateInformation(stateInfo);
        })
    
        this.plgManager.on('event_refresh_extip', (ip) => {
            // external ip has been refetched
            // console.log('APP | New external ip address state fired with the following ip: ' + ip);
            global.externalIP = ip;
            this.handlerExternalIp.persistNewIPInformation(ip);
        })

        this.plgManager.on('event_print_pdf', (pathToPDF) => {
            // PDF Document should be sent to default printer
            try {
                global.printManager.printPDFToDefaultPrinter(pathToPDF, (resultPrint) => {
                    if (resultPrint) {
                        console.log('CORE | PRINTING | Successfully finished print job for document ' + pathToPDF);
                    } else {
                        console.log('CORE | PRINTING | Error printing document: ' + pathToPDF);
                    }
                })
            } catch (err) {
                console.log('CORE | PRINTING | Error while trying to print PDF Document: ' + err.message);
            }
        })

        this.emit('registerPluginManagerEvents', true);
    }

    /**
     * Sets up authentication
     */
    setupAuthentication() {
        // Authentication
        const { authenticate, changePassword } = require('./routes/auth');
        const { getLoginPage } = require('./routes/auth');
        const { endSession } = require('./routes/auth');
        const { getChangePasswordPage } = require('./routes/auth');
        const { setNewPasswordFromStartPassword } = require('./routes/auth');
        const { getLockedAccountPage } = require('./routes/auth');


        // routes for the app
        // Login
        this.app.get('/', getLoginPage);
        this.app.post('/auth', authenticate);
        this.app.get('/changepassword', getChangePasswordPage);
        this.app.post('/changepassword', setNewPasswordFromStartPassword);
        this.app.get('/auth/logoff', endSession);
        this.app.get('/lockedaccount', getLockedAccountPage);
        this.emit('setupAuthentication', true);
    }

    /**
     * Sets up all needed express - routes
     */
    setupRoutes() {
        // Load Route Modules
        var routeBasedata = require('./routes/basedata');
        var routeAdmin = require('./routes/admin');
        var routeTraining = require('./routes/training');
        var routeAssets = require('./routes/assets');
        var routeWallboard = require('./routes/wallboard');
        var routeReports = require('./routes/reports');
        var routesOperations = require('./routes/operations');
        var routesApi = require('./routes/restapi-v1');
        var logtail = require('./routes/admin/logtail');
        var routesAlarm = require('./routes/alarm');
        var routesHome = require('./routes/home');
        var routesRespiratory = require('./routes/respiratory');


        const { isAuthenticated } = require('./routes/auth');
        const { changePassword } = require('./routes/auth');
        const { getUserGroups } = require('./routes/auth');

        this.app.use('/home', isAuthenticated, getUserGroups, routesHome);
        this.app.use('/basedata', isAuthenticated, getUserGroups, routeBasedata);
        this.app.use('/admin', isAuthenticated, getUserGroups, routeAdmin);
        this.app.use('/training', isAuthenticated,getUserGroups, routeTraining);
        this.app.use('/assets', isAuthenticated,getUserGroups, routeAssets);
        this.app.use('/reports', isAuthenticated,getUserGroups, routeReports);
        this.app.use('/operations', isAuthenticated,getUserGroups, routesOperations);
        this.app.use('/alarm', isAuthenticated,getUserGroups, routesAlarm);
        this.app.use('/respiratory', isAuthenticated, getUserGroups, routesRespiratory);
        this.app.use('/auth/changePassword', isAuthenticated, getUserGroups, changePassword);
        
        if (this.args_development) {
            this.startAPIDocumentation();
        }

        // Static Route to wallboard client in plugins directory
        this.app.use('/wallboard', express.static('plugins/outbound/wallboard/static'));

        this.emit('setupRoutes', true);
    }

    /**
     * Checks for initial Settings like server UUID etc.
     */
    runServer() {

        // read uuid of this instance and publish globally (serverUUID)
        global.configuration.getValueForKeyword('serveruuid', (uuidFromDb) => {
            if (uuidFromDb === null) {
                var uuidFromDb = uuid();
                global.configuration.addKeywordAndValue('serveruuid', uuidFromDb, (successUUID) => {
                    if (successUUID) {
                        this.logger.info('APP | Set UUID of this Server to ' + uuidFromDb);
                        this.uuid = uuidFromDb;
                    } else {
                        this.logger.error('APP | Error setting UUID of this Server.');
                    }
                });
            } else {             
                this.uuid = uuidFromDb;
            }
            global.serverUUID = this.uuid;
        })

        // read serverinstance and publish globally
        global.configuration.getValueForKeyword('instancename', (instanceNameFromDb) => {
            if (instanceNameFromDb === null) {
                this.instancename = 'ALARMiator';
            } else {
                this.instancename = instanceNameFromDb;
            }
            
            global.serverInstanceName = this.instancename;
        })

        // read (external ip) of this server and publish it globally (wanIP)
        global.configuration.getValueForKeyword('externalip', (externalip) => {
            global.externalIP = externalip;
        })

        // set the app to listen on the port
        this.app.listen(this.port, () => {
            this.logger.info(`CORE | ALARMiator Server is running on port: ${this.port}`);
            this.app.listenPort = this.port;
        });

        this.emit('runServer', true);
    }

    /**
     * Place to initialize all global objects and properties
     */
    initializeGlobalObjects() {
        // Distribute the FCMTokens Class globally
        global.fcmtokens = new FCMTokens(global.logger);
        global.configuration = new Configuration(global.logger);

        this.emit('initializeGlobalObjects', true);
    }

    /**
     * Starts the cron manager and makes it available globally
     */
    startCron() {
        this.cron = new CoreCron(global.logger);
        global.cron = this.cron;
        global.cron.startCronManager();
        // start cron joba
        // maintenance
        global.cron.startJob('maintenance', '30 23 * * *', function() { this.emit('event_cron_maintenance', 'maintenance'); }, null);
        // hourly
        global.cron.startJob('hourly', '0 * * * *', function() { this.emit('event_cron_hourly', 'hourly'); }, null);
        // every 5 minutes
        global.cron.startJob('every5Minutes', '*/5 * * * *', function () { this.emit('event_cron_every_5_minutes', 'every5Minutes'); }, null);
        global.cron.on('event_cron_maintenance', (title) => {
            console.log('CORE | CRON | event fired: event_cron_maintenance from job ' + title);
        })
        global.cron.on('event_cron_hourly', (title) => {
            console.log('CORE | CRON | event fired: event_cron_hourly from job ' + title);
        })
        global.cron.on('event_cron_every_5_minutes', (title) => {
            console.log('CORE | CRON | event fired: event_cron_every_5_minutes from job ' + title);
        })
        this.emit('startCron', true);
    }

    /**
     * Starts the REST API documentation if server is in development mode
     */
    startAPIDocumentation() {
        this.swaggerActice = true;
        this.swaggerUi = require('swagger-ui-express');
        this.YAML = require('yamljs');
        this.swaggerDocument = this.YAML.load('./api/v1/api/swagger/swagger.yaml');
        this.app.use('/api-docs', this.swaggerUi.serve, this.swaggerUi.setup(this.swaggerDocument));
        console.log('CORE | successful | startAPIDumentation');
    }

}

// GENERIC Error Handling
process.on('unhandledRejection', (reason, p) => {
    console.log('CORE | Unhandled Rejection at: Promise', p, 'reason:', reason.stack);
    // application specific logging, throwing an error, or other logic here
});

// Start Core-System
const core = new Core();

core.on('getStartupArguments', (state) => {

    if (state) {
        // Database could be started successfully
        console.log('CORE | successful | getStartupArguments');
        core.startCoredatabase();
    } else {
        // Database could not be started successfully
        console.log('CORE | ERROR reading passed startup arguments. Please check logs for more details');
    }
})

core.on('startCoredatabase', (state) => {
    if (state) {
        // Database could be started successfully
        console.log('CORE | successful | startCoredatabase');
        core.startLoggers();
    } else {
        // Database could not be started successfully
        console.log('CORE | ERROR starting Database. Please check logs for more details');
    }
})

core.on('startLoggers', (state) => {
    if (state) {
        console.log('CORE | successful | startLoggers');
        // Loggers have been successfully started
        core.initializeGlobalObjects();
    } else {
        console.log('CORE | ERROR starting Loggers. Please check logs for more details.');
    }
})

core.on('initializeGlobalObjects', (state) => {
    if (state) {
        console.log('CORE | successful | initializeGlobalObjects');
        core.setupServer();
    } else {
        console.log('CORE | ERROR initializing global objects. Please check logs for more details.');
    }
})

core.on('setupServer', (state) => {
    if (state) {
        // Server config successfully done
        console.log('CORE | successful | setupServer');
        core.setupMiddelware();
    } else {
        console.log('CORE | ERROR setting Up Server. Please check logs for more details.');
    }
})

core.on('setupMiddleware', (state) => {
    if (state) {
        // Middleware Setup successfully done
        console.log('CORE | successful | startMiddleware');
        core.startCoreHandlers();
    } else {
        console.log('CORE | ERROR setting Up Middleware. Please check logs for more details.');
    }
})

core.on('startCoreHandlers', (state) => {
    if (state) {
        // Core Handlers Setup successfully done
        console.log('CORE | successful | startCoreHandlers');
        core.startPluginManager();
    } else {
        console.log('CORE | ERROR setting Up Core handlers. Please check logs for more details.');
    }
})

core.on('startPluginManager', (state) => {
    if (state) {
        // Core Handlers Setup successfully done
        console.log('CORE | successful | startPluginManager');
        core.registerPluginManagerEvents();
    } else {
        console.log('CORE | ERROR starting PluginManager. Please check logs for more details.');
    }
})

core.on('registerPluginManagerEvents', (state) => {
    if (state) {
        // Core Handlers Setup successfully done
        console.log('CORE | successful | registerPluginManagerEvents');
        core.setupAuthentication();
    } else {
        console.log('CORE | ERROR registering Plugin Manager Events. Please check logs for more details.');
    }
})

core.on('setupAuthentication', (state) => {
    if (state) {
        // Core Handlers Setup successfully done
        console.log('CORE | successful | setupAuthentication');
        core.setupRoutes();
    } else {
        console.log('CORE | ERROR setting up Authentication. Please check logs for more details.');
    }
})

core.on('setupRoutes', (state) => {
    if (state) {
        // Core Handlers Setup successfully done
        console.log('CORE | successful | setupRoutes');
        core.startCron();
    } else {
        console.log('CORE | ERROR setting up Routes. Please check logs for more details.');
    }
})

core.on('startCron', (state) => {
    if (state) {
        // Core Handlers Setup successfully done
        console.log('CORE | successful | setupCron');
        core.runServer();
    } else {
        console.log('CORE | ERROR setting up Cron. Please check logs for more details.');
    }
})

core.on('runServer', (state) => {
    if (state) {
        // Core Handlers Setup successfully done
        console.log('CORE | ALARMiator Server started successfully');
    } else {
        console.log('CORE | ERROR starting ALARMiator Server. Please check logs for more details.');
    }
})


// Starting now
core.getStartupArguments();