rutilus-analytics-node-js
Version:
Provides a GUI web app that allows users to examine their data in detail. Includes CSV export functionality.
238 lines (200 loc) • 8.45 kB
JavaScript
/**
* Rutilus
*
* @homepage https://gmrutilus.github.io
* @license Apache-2.0
*/
/** @module */
/**
* @param {RawConfig} config
* @param {Function} cb callback
* @constructor
*/
function AnalyticsApi(config, cb) {
// Node modules
const fs = require('fs');
const http = require('http');
const https = require('https');
// Classes
const Env = require('./lib/env');
const ResultStreamer = require('./lib/resultStreamer');
const Emailer = require('./lib/emailer');
const RateLimit = require('./lib/rateLimit');
const Logger = require('./lib/logger');
const ErrorHandler = require('./lib/errorHandler');
const RecalculationLock = require('./lib/recalculationLock');
const JsonToMongo = require('./lib/jsonToMongo');
const Parsing = require('./lib/parsing');
const Validation = require('./lib/validation');
const QuerySchedule = require('./lib/querySchedule');
const Random = require('./lib/random');
// Settings
/** @type {ServerSettings} */
const serverSettings = require('./lib/serverSettings')(config);
// API version]
/** @type {String} */
const version = require('./package.json').version;
/**
* @type {ResultStreamer}
*/
const resultStreamer = new ResultStreamer(serverSettings.resultFilePath);
// Environment variables
const env = new Env();
const inProduction = /** @type {Boolean} */ env.check('node_env', 'production');
// Koa stuff
const app = /** @type {Application} */ new (require('koa'))();
const serve = require('koa-static');
const router = /** @type {{ routes: Function }} */ new (require('koa-router'))();
const bodyParser = require('koa-bodyparser');
const helmet = require('koa-helmet');
const session = require('koa-session');
// Passport
const passport = /** @type {Object<String, Function>} */ require('koa-passport');
// Presets and queries
const presets = require('./lib/presets/index');
const createDefaultQueries = require('./lib/createDefaultQueries');
const defaultIndexes = require('./lib/joins/defaultIndexes')(serverSettings.extraIndexes);
/** @type {Array<Object>} */
const defaultQueries = require('./lib/presets/defaultQueries');
// Models
const mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
// Static files
app.use(serve(__dirname + '/public'));
// Security
const rateLimitConfig = serverSettings.rateLimit;
if (!rateLimitConfig.handler) { // Default handler for rate limit
rateLimitConfig.handler = function (/** @type {{
_matchedRoute: String,
originalUrl: String}}
*/ self) {
logger.error({
errorCode: 429,
error: '429 Too many requests',
route: self._matchedRoute,
url: self.originalUrl
});
self.body = '429 Too many requests';
self.status = 429;
}
}
const rateLimit = new RateLimit(serverSettings.rateLimit);
app.use(helmet());
// Building the library of joins
const joinsLib = require('./lib/joins')(serverSettings.extraInformation);
// Other includes
const logger = new Logger(serverSettings.loggerConfig);
const errorHandler = new ErrorHandler(logger);
const checkConflictingExtraInformation = require('./lib/checkConflictingExtraInformation');
const emailer = new Emailer(serverSettings.transporter, serverSettings.addresses.analytics, logger);
const recalculationLock = new RecalculationLock();
const jsonToMongo = new JsonToMongo(joinsLib);
const querySchedule = new QuerySchedule();
// Checking for conflicting extra information
if (!checkConflictingExtraInformation(logger, serverSettings.extraInformation)) {
process.exit(ErrorHandler.SYS.CONFLICTING_EXTRA_INFORMATION.code);
}
// Packing dashboard before starting the API
if (config.skipPackingDashboard) {
start();
}
else {
require('./lib/packDashboard')({
inProduction,
rootDirectory: __dirname,
analyticsLocation: serverSettings.addresses.analytics,
logger,
extraInformation: serverSettings.extraInformation,
joinsLib,
}, start);
}
/**
* Starts the server
* @param {String} [dashboardSourceCode]
*/
function start(dashboardSourceCode = '') {
// Creating the schema for Mongoose and stores for queries
//------------------------------------------------------------------
const schemas = /** @type {Object<String, Object>} */ {
Queries: require('./lib/schema/queries')(defaultIndexes.queries || []),
Visits: require('./lib/schema/visits')(serverSettings.extraInformation, defaultIndexes.visits || [], defaultIndexes.actions || []),
Accounts: require('./lib/schema/accounts')(defaultIndexes.accounts || []),
UatCache: require('./lib/schema/uatCache')(serverSettings.extraInformation, defaultIndexes.uatCache || []),
};
// Configuring passport
//------------------------------------------------------------------
require('./lib/passport')(passport, schemas.Accounts);
app.keys = [Math.random().toString(36).substr(2)];
app.use(session(app));
app.use(passport.initialize());
app.use(passport.session());
const auth = require('./lib/auth').Auth(schemas.Accounts, errorHandler.dbErrorCatcher, env);
// Creating the server
//------------------------------------------------------------------
if (!inProduction) {
logger.warn(`MONGODB will connect to ${serverSettings.addresses.database}`);
}
else {
logger.warn(`MONGODB will connect`);
}
mongoose.connect(serverSettings.addresses.database);
const db = mongoose.connection;
// Opening Mongo connection and then, when open, loading rest of app
// -----------------------------------------------------------------
db.once('open', () => {
if (!inProduction) {
logger.info(`MONGODB connected to ${serverSettings.addresses.database}`);
}
else {
logger.info(`MONGODB connected`);
}
// Checking if we have the default queries in the DB. If not,
// we create them
//------------------------------------------------------------------
createDefaultQueries(schemas.Queries, errorHandler, logger, presets, defaultQueries);
// Setting up router
// -----------------
require('./lib/setupRouter')({
router,
presets,
schemas,
logger,
resultStreamer,
extraInformation: serverSettings.extraInformation,
bodyParser,
auth,
rateLimit,
errorHandler,
emailer,
affinityTool: serverSettings.affinityTool,
algorithms: serverSettings.algorithms,
dashboardSourceCode,
passport,
recalculationLock,
jsonToMongo,
querySchedule,
serverSettings,
db,
});
// Binding router routes to app
// ----------------------------
app.use(router.routes());
app.use(router.allowedMethods());
// Creating the server
// -------------------
const envLabel = (env.check('node_env') + '').toUpperCase();
const protocol = serverSettings.useHttps ? 'HTTPS' : 'HTTP';
const server = serverSettings.useHttps
? https.createServer(serverSettings.httpsKeys, app.callback())
: http.createServer(app.callback());
server.listen(serverSettings.port, () => {
logger.info(`${envLabel} ANALYTICS ${version} | ${protocol} on port ${serverSettings.port}`);
if (cb) { cb(); }
});
});
db.on('error', () => {
logger.error(`Mongoose connection refused`);
});
}
}
module.exports = AnalyticsApi;