Source: app.js

'use strict';

/**
 * @module AppServer
 * Main entry point.
 * @license MIT
 * @author Kai KRETSCHMANN <kai@kretschmann.consulting>
 */

// Sentry
const Sentry = require('@sentry/node');
const sentryDSN = process.env.SENTRY_DSN || process.env.SENTRY;
Sentry.init({
  dsn: sentryDSN,
  environment: process.env.NODE_ENV || /* istanbul ignore next */ 'production',
  sendDefaultPii: true,
  debug: false,
  release: "demodata@" + process.env.npm_package_version,
  tracesSampleRate: 1.0,
});

const fs = require('fs'),
    path = require('path'),
    http = require('http');

require('custom-env').env(true);

const log4js = require("log4js");
const logger = log4js.getLogger();
logger.level = process.env.LOGLEVEL || "warn";

logger.info(`SENTRY dsn=${sentryDSN}`);

const app = require('connect')();
const favicon = require('serve-favicon');
const serveStatic = require('serve-static');
const swaggerTools = require('swagger-tools');
const jsyaml = require('js-yaml');

// get git revision
const gitrevFilename = path.join(__dirname, '.gitrevision');
let gitrevision = "";
try {
    fs.accessSync(gitrevFilename, fs.constants.R_OK);
    gitrevision = fs.readFileSync(gitrevFilename, 'utf8');
    gitrevision = gitrevision.trim();
    process.env.VERSION = gitrevision;
} catch (err) {
    logger.warn("gitrevision file not found at: " + gitrevFilename);
}

const myEnvironment = process.env.NODE_ENV || "production";

// Feature Flag support
const unleash = require('unleash-client');
if(process.env.FF_URL) {
    logger.info("Add FeatureFlag support for " + myEnvironment);
    unleash.initialize({
        url: process.env.FF_URL,
        appName: myEnvironment,
        instanceId: process.env.FF_IID,
        environment: myEnvironment
    });
} // if

// Prometheus support
const client = require('prom-client');
const defaultLabels = {NODE_APP_INSTANCE: process.env.NODE_APP_INSTANCE || "vapp"};
const Registry = client.Registry;
const register = new Registry();
register.setDefaultLabels(defaultLabels);
const collectDefaultMetrics = client.collectDefaultMetrics;
const appCounter = new client.Counter({
    name: 'demodata_app_requests_counter',
    help: 'all api requests',
    registers: [register],
});

const pj = require('./package.json');
const myversion = pj.version;
const versionCounter = new client.Gauge({
  name: 'demodata_app_version',
  help: 'source code version',
  registers: [register],
  labelNames: ['version']
});
versionCounter.set({version: myversion}, 1);

collectDefaultMetrics({prefix: 'demodata_', register});

const emitter = require('events').EventEmitter;
const eventEmitter = require('./utils/eventer').em;
require('./subscribers/matomo');
require('./subscribers/prometheus.js');

// swaggerRouter configuration
const options = {
    swaggerUi: path.join(__dirname, '/swagger.json'),
    controllers: path.join(__dirname, './controllers'),
    useStubs: process.env.NODE_ENV === 'development' // Conditionally turn on stubs (mock mode)
};


// The Swagger document (require it, build it programmatically, fetch it from a URL, ...)
const spec = fs.readFileSync(path.join(__dirname, 'api/swagger.yaml'), 'utf8');
const swaggerDoc = jsyaml.load(spec);

// Initialize the Swagger middleware
swaggerTools.initializeMiddleware(swaggerDoc, function(middleware) {

    // Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain
    app.use(middleware.swaggerMetadata());

    // Add Sentry to app object
    app.use(function(req, _res, next) {
		let realip = req.ip;
        if (typeof req.headers['x-real-ip'] !== 'undefined') {
          realip = req.headers['x-real-ip'];
        }
        Sentry.setUser({
		  ip_address: realip
		});
        if (typeof req.headers['geoip-country-code'] !== 'undefined') {
          Sentry.setContext('GeoIP', {
            country: req.headers['geoip-country-code'],
            city: req.headers['geoip-city-name'],
            zip: req.headers['geoip-zip'],
            statecode: req.headers['geoip-state-code']
          });
        } // if
        req.sentry = Sentry;
        req.realip = realip;
        next();
    });

    // Add FF object to request
    app.use(function(req, _res, next) {
        const clientip = req.headers['x-real-ip'] || '127.0.0.1';
        const context = {
            remoteAddress: clientip
        };
        req.unleash = unleash;
        req.unleashCtx = context;
        next();
    });

    // Add appCounter for prometheus
    app.use(function(req, _res, next) {
        req.appCounter = appCounter;
        next();
    });

    app.use(favicon(path.join(__dirname, 'static', 'favicon.ico')));

    app.use(serveStatic(path.join(__dirname, 'static')));

    // Validate Swagger requests
    app.use(middleware.swaggerValidator());

    // Route validated requests to appropriate controller
    app.use(middleware.swaggerRouter(options));

    // Serve the Swagger documents and Swagger UI
    app.use(middleware.swaggerUi());

    // Attach Sentry to express app
    Sentry.setupExpressErrorHandler(app);

    /**
     * Start the server 
     * @see DDATA-server-backend-002
     */
    const serverPort = process.env.BIND_PORT || 8080;
    const serverHost = process.env.BIND_HOST || "0.0.0.0";
    logger.info("Bind to %s : %d", serverHost, serverPort);
    http.createServer(app).listen(serverPort, serverHost, function() {
        logger.info('Your server is listening on port %d (http://%s:%d)', serverPort, serverHost, serverPort);
        logger.info('Swagger-ui is available on http://%s:%d/docs', serverHost, serverPort);
    });

    app.use('/metrics', async(req, res) => {
        res.end(await register.metrics());
    });

    // Redirect root to docs UI
    app.use('/', function doRedir(req, res, next) {
        if (req.url != '/') {
            next();
        } else {
            res.writeHead(301, {
                Location: '/docs/'
            });
            res.end();
        }
    });

});

module.exports = {
	server: app,
	version: gitrevision
};