/* * Copyright (C) 2019 StApps * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import {SCNotFoundErrorResponse, SCUnsupportedMediaTypeErrorResponse} from '@openstapps/core'; import {Validator} from '@openstapps/core-tools/lib/validate'; import * as bodyParser from 'body-parser'; import * as config from 'config'; import * as cors from 'cors'; import * as express from 'express'; import * as morgan from 'morgan'; import {join} from 'path'; import {logger, mailer} from './common'; import {MailQueue} from './notification/MailQueue'; import {bulkAddRouter} from './routes/BulkAddRoute'; import {bulkDoneRouter} from './routes/BulkDoneRoute'; import {bulkRouter} from './routes/BulkRoute'; import {indexRouter} from './routes/IndexRoute'; import {multiSearchRouter} from './routes/MultiSearchRoute'; import {searchRouter} from './routes/SearchRoute'; import {thingUpdateRouter} from './routes/ThingUpdateRoute'; import {BulkStorage} from './storage/BulkStorage'; import {DatabaseConstructor} from './storage/Database'; import {Elasticsearch} from './storage/elasticsearch/Elasticsearch'; export const app = express(); async function configureApp() { // only accept json as content type for all requests app.use(bodyParser.json({ limit: '500kb', type: (req) => { const contentType = typeof req.headers['Content-Type'] === 'string' ? req.headers['Content-Type'] : req.headers['content-type']; if (typeof contentType === 'string' && contentType.match(/^application\/json/)) { return true; } else { throw new SCUnsupportedMediaTypeErrorResponse(process.env.NODE_ENV !== 'production'); } }, })); // 500kb should be reasonably large // use morgan as a request logger // request loggers have to be the first middleware to be set in express app.use(morgan('dev')); const databases: {[name: string]: DatabaseConstructor} = { elasticsearch: Elasticsearch, }; // validate config file const scValidator = new Validator(); await scValidator.addSchemas(join('node_modules', '@openstapps', 'core', 'lib', 'schema')); // validate the config file const configValidation = scValidator.validate(config.util.toObject(), 'SCConfigFile'); // validation failed if (configValidation.errors.length > 0) { throw new Error( 'Validation of config file failed. Errors were: ' + JSON.stringify(configValidation.errors), ); } // check if a database name was given if (!config.has('internal.database.name')) { throw new Error('You have to configure a database'); } if (typeof mailer !== 'undefined') { // set a mailQueue to use the backend mailer if (config.has('internal.monitoring')) { app.set('mailQueue', new MailQueue(mailer)); } } const database = new databases[config.get('internal.database.name')]( config.util.toObject(), app.get('mailQueue'), ); if (typeof database === 'undefined') { throw new Error('No implementation for configured database found. Please check your configuration.'); } logger.ok('Validated config file sucessfully'); // make the validator available on the app app.set('validator', scValidator); // treats /foo and /foo/ as two different routes // see http://expressjs.com/en/api.html#app.set app.set('strict routing', true); // make the bulk storage available to all http middlewares/routes app.set( 'bulk', new BulkStorage(database), ); const corsOptions = { allowedHeaders: [ 'DNT', 'Keep-Alive', 'User-Agent', 'X-Requested-With', 'If-Modified-Since', 'Cache-Control', 'Content-Type', 'X-StApps-Version', ], credentials: true, maxAge: 1728000, methods: ['GET', 'POST', 'PUT', 'OPTIONS'], optionsSuccessStatus: 204, }; // allow all origins on all routes app.use(cors(corsOptions)); // TODO: See if it can handle options request with no content-type // allow cors preflight requests on every route app.options('*', cors(corsOptions)); app.set('isProductiveEnvironment', process.env.NODE_ENV !== 'production'); // load routes before plugins // they now can be used or overwritten by any plugin app.use( bulkAddRouter, bulkDoneRouter, bulkRouter, indexRouter, multiSearchRouter, searchRouter, thingUpdateRouter, ); // add a route for a missing resource (404) app.use((_req, res) => { const errorResponse = new SCNotFoundErrorResponse(process.env.NODE_ENV !== 'production'); res.status(errorResponse.statusCode); res.json(errorResponse); }); // TODO: implement a route to register plugins } configureApp().then(() => { logger.ok('Sucessfully configured express server'); }).catch((err) => { throw err; });