168 lines
5.4 KiB
JavaScript
168 lines
5.4 KiB
JavaScript
/* eslint-disable no-unused-vars */
|
|
'use strict';
|
|
|
|
// Ref: http://javascript.tutorialhorizon.com/2014/09/20/organizing-your-expressjs-routes-in-separate-files/
|
|
|
|
const express = require('express');
|
|
require('express-async-errors');
|
|
const rateLimit = require('express-rate-limit')
|
|
|
|
const debug = require('debug')('agm:server'),
|
|
compression = require('compression'),
|
|
path = require('path'),
|
|
fs = require('fs-extra'),
|
|
http = require('http'),
|
|
https = require('node:http2'), // Use Node's https for HTTP2 support instead of 3rd party spdy (for Node <=v14)
|
|
app = express(),
|
|
env = require('./helpers/env'),
|
|
dbConnect = require('./helpers/db/connect.js'),
|
|
{ checkUser } = require('./middlewares/app_validator.js'),
|
|
{ ErrorHandler } = require('./middlewares/error_handler.js'),
|
|
errorHandler = require('error-handler').errorHandler;
|
|
|
|
http.globalAgent.maxSockets = Infinity;
|
|
const MAX_REQ_BDY_MB = env.MAX_REQ_BDY_MB || '150mb'
|
|
|
|
// Load ENV vars specified in the .env file (within the same folder)
|
|
debug("Is in Production: ", env.PRODUCTION);
|
|
app.isProd = env.PRODUCTION;
|
|
|
|
process.setMaxListeners(0);
|
|
errorHandler && (errorHandler.registerUnCaughtProcessErrorsHandler(process, path.join(__dirname, 'agm_server.rlog')));
|
|
|
|
app.set('trust proxy', env.APP_RATE_TRUST_PROXIES /* number of proxies between user and server */);
|
|
|
|
if (!env.PRODUCTION && (env.INV_IMG_VIR_DIR && env.INV_UPLOAD_DIR)) {
|
|
// Map static resources, cached for 8 hours
|
|
app.use(env.INV_IMG_VIR_DIR, express.static(env.INV_UPLOAD_DIR, { maxAge: 31557600 }));
|
|
}
|
|
|
|
// for parsing application/x-www-form-urlencoded
|
|
app.use(express.urlencoded({ limit: MAX_REQ_BDY_MB, extended: true, parameterLimit: 100000 }));
|
|
|
|
// Rate limiting middleware. Define a rate limiter
|
|
const apiLimiter = rateLimit({
|
|
windowMs: (env.APP_RATE_MINS || 15) * 60 * 1000, // 15 minutes
|
|
max: env.APP_RATE_REQS || 100, // Limit each IP to 100 requests per windowMs
|
|
skipFailedRequests: env.APP_RATE_SKIPFAIL || true, // Skip failed requests
|
|
message: {
|
|
error: "Too many requests, please try again later."
|
|
}
|
|
});
|
|
|
|
// Apply the rate limiter to all routes in this router
|
|
app.use(apiLimiter);
|
|
|
|
// Handle webhook events from Stripe
|
|
require('./routes/subscription_webhooks')(express, app);
|
|
|
|
function shouldCompress(req, res) {
|
|
if (req.headers['x-no-compression']) {
|
|
return false; // don't compress responses with this request header
|
|
}
|
|
// fallback to standard filter function
|
|
return compression.filter(req, res);
|
|
}
|
|
// Enable the compression middleware
|
|
app.use(compression({ filter: shouldCompress }));
|
|
|
|
// Parsers for POST JSON data
|
|
app.use(express.json({ limit: MAX_REQ_BDY_MB }));
|
|
|
|
// Allow all CORS requests
|
|
// Ref at http://restlet.com/company/blog/2015/12/15/understanding-and-using-cors
|
|
app.use(function (req, res, next) {
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('Access-Control-Allow-Headers', 'Origin, Methods, X-Requested-With, Content-Type, Content-Disposition, Accept');
|
|
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, PATCH, DELETE, OPTIONS');
|
|
next();
|
|
});
|
|
|
|
async function setupRoutes() {
|
|
// const resLogger = async (req, res, next) => {
|
|
// debug('Request:', req.url);
|
|
// next();
|
|
// };
|
|
|
|
// Handle routes with middelware functions
|
|
app.use(checkUser/*, resLogger*/);
|
|
|
|
// REGISTER OUR ROUTES
|
|
require('./routes')(app);
|
|
|
|
app.use(ErrorHandler);
|
|
|
|
app.get('/*', (req, res) => {
|
|
// console.log(req.path);
|
|
res.status(404).send({ status: 404, error: 'Not found' }).end();
|
|
});
|
|
|
|
// Avoid logging aborted requests
|
|
// Ref: https://github.com/nodejs/help/issues/2155
|
|
app.use((err, req, res, next) => {
|
|
if (err && err.code === 'ECONNABORTED') {
|
|
res.status(400).end(); // Don't process this error any further to avoid its logging
|
|
} else
|
|
next(err);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Preload some common libs, ussually written in ESM modules that require asynchronously loaded or import..
|
|
* instead of using requires in CommonJS ones.
|
|
*/
|
|
async function preloadLibs() {
|
|
let mod = await import('@mickeyjohn/geodesy/utm.js');
|
|
app.locals.UTM = mod.default;
|
|
app.locals.LatLonUTM = mod.LatLon; // More accuracy, for converting from ll to utm
|
|
app.locals.Dms = mod.Dms;
|
|
|
|
mod = await import('@mickeyjohn/geodesy/latlon-spherical.js');
|
|
app.locals.LatLonSP = mod.default;
|
|
}
|
|
|
|
async function ensureFolders() {
|
|
try {
|
|
await fs.ensureDir(env.INV_UPLOAD_DIR);
|
|
await fs.ensureDir(env.UNZIP_DIR);
|
|
await fs.ensureDir(env.REPORT_DIR);
|
|
await fs.ensureDir(env.TEMP_DIR);
|
|
await fs.ensureDir(env.AREAS_UPLOAD_DIR);
|
|
} catch (error) {
|
|
debug(error);
|
|
}
|
|
}
|
|
|
|
const port = env.AGM_PORT || '4000';
|
|
|
|
// Create a secure HTTPS - HTTP2 server
|
|
https.createSecureServer({ key: fs.readFileSync(env.SSL_KEY), cert: fs.readFileSync(env.SSL_CERT), allowHTTP1: true }, app)
|
|
.listen(port, async (error) => {
|
|
const onAppErr = (err) => {
|
|
debug(error);
|
|
process.exit(1);
|
|
}
|
|
if (error) return onAppErr(error);
|
|
|
|
try {
|
|
await ensureFolders();
|
|
await preloadLibs();
|
|
|
|
const conn = await dbConnect();
|
|
// Set up error handler for future connection issues
|
|
conn.on('error', onAppErr);
|
|
// Since connection is already established, execute the setup directly
|
|
debug('✅-> MongoDB connected');
|
|
|
|
await setupRoutes();
|
|
|
|
// Start Job Importer queue consumer
|
|
const jobQueuer = require('./helpers/job_queue').getInstance();
|
|
jobQueuer.start();
|
|
|
|
debug(`HTTPS-v2 AgMission Server is listening on port ${port}`);
|
|
|
|
} catch (error) {
|
|
onAppErr(error);
|
|
}
|
|
}); |