/* 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'), { 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'))); require('./helpers/db/connect.js')(); 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(); await setupRoutes(); // Start Job Importer queue consumer const jobQueuer = require('./helpers/job_queue').getInstance(); jobQueuer.start(); console.log(`Server is running on port ${port}`) debug(`HTTPS-v2 Agmission Server listening on port ${port}`); } catch (error) { onAppErr(error); } });