server/index.js
Rama master · réplica de lectura del repositorio original.
// set up the encryption keys first, then load .env file
const setUpEncryptionKeys = require("./modules/setUpEncryptionKeys"); // eslint-disable-line
setUpEncryptionKeys();
const path = require("path");
require("dotenv").config({ path: path.join(__dirname, "..", ".env") });
const express = require("express");
const methodOverride = require("method-override");
const { urlencoded, json } = require("body-parser");
const cookieParser = require("cookie-parser");
const cors = require("cors");
const _ = require("lodash");
const morgan = require("morgan");
const securityHeaders = require("./middlewares/securityHeaders");
const fs = require("fs");
const busboy = require("connect-busboy");
const settings = require("./settings");
const routes = require("./api");
const appsRoutes = require("./apps");
const cleanChartCache = require("./modules/CleanChartCache");
const cleanAuthCache = require("./modules/CleanAuthCache");
const parseQueryParams = require("./middlewares/parseQueryParams");
const chartImageBodyError = require("./modules/chartImageBodyError");
const { isChartImageRequest } = chartImageBodyError;
const { IMAGE_RENDER_LIMITS } = require("./modules/chartImage/imageLimits");
const dataApiBodyError = require("./modules/dataApiBodyError");
const { getDataApiLimits } = require("./modules/dataApiLimits");
const db = require("./models/models");
const packageJson = require("./package.json");
const cleanGhostChartsCron = require("./modules/cleanGhostChartsCron");
const updateAuditRetention = require("./modules/updateAuditRetention");
const observationDigestScheduler = require("./modules/observations/digestScheduler");
const periodEvaluationScheduler = require("./modules/observations/periodEvaluationScheduler");
const { checkEncryptionKeys } = require("./modules/cbCrypto");
const { setUpQueues } = require("./setUpQueues");
const socketManager = require("./modules/socketManager");
const { shouldMigrateOnStartup } = require("./modules/databaseMigrations");
const {
refreshPlatformSettings,
startPlatformSettingsRefresh,
} = require("./modules/platformSettings/runtime");
const uploadsDirectory = path.resolve(__dirname, "uploads");
const legacyUploadsDirectory = path.resolve(process.cwd(), "uploads");
const setUploadHeaders = (res) => {
// Uploaded assets should never be script-executable even if a bad file slips through.
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader(
"Content-Security-Policy",
"default-src 'none'; script-src 'none'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'none'; img-src 'self' data:;"
);
};
// check if the encryption keys are valid 32-byte hex strings
checkEncryptionKeys();
// set up folders
fs.mkdir(".cache", () => { });
fs.mkdir(uploadsDirectory, { recursive: true }, () => { });
fs.mkdir(path.join(uploadsDirectory, "snapshots"), { recursive: true }, () => { });
const app = express();
app.settings = settings;
app.set("trust proxy", 1);
app.use(busboy());
if (process.env.NODE_ENV !== "production") {
app.set("trust proxy", true);
app.use(morgan("dev"));
}
app.use(cookieParser());
app.use(urlencoded({
extended: true,
verify: (req, res, buf, encoding) => {
const url = req.originalUrl;
// Save raw body for Slack signature verification (URL-encoded requests)
if (req.headers["content-type"]?.includes("application/x-www-form-urlencoded") && url.includes("/apps/slack/")) {
req.rawBody = buf.toString(encoding || "utf8");
}
},
}));
app.set("query parser", "simple");
const captureJsonBody = (req, res, buf, encoding) => {
// Save raw body for Slack signature verification and bounded request checks.
if (req.headers["content-type"]?.includes("application/json")) {
req.rawBody = buf.toString(encoding || "utf8");
}
};
const defaultJsonParser = json({
limit: Math.max(100 * 1024, getDataApiLimits().maxRequestBytes),
verify: captureJsonBody,
});
const chartImageJsonParser = json({
limit: IMAGE_RENDER_LIMITS.bodyBytes,
verify: captureJsonBody,
});
app.use((req, res, next) => {
const parser = isChartImageRequest(req) ? chartImageJsonParser : defaultJsonParser;
return parser(req, res, next);
});
app.use(chartImageBodyError);
app.use(dataApiBodyError);
app.use(methodOverride("X-HTTP-Method-Override"));
app.use(securityHeaders);
app.use(cors());
//---------------------------------------
app.get("/", (req, res) => {
return res.send("Welcome to chartBrew server API");
});
app.use("/uploads", express.static(uploadsDirectory, {
setHeaders: setUploadHeaders,
}));
// Backwards-compatible fallback for files previously written relative to process.cwd().
if (legacyUploadsDirectory !== uploadsDirectory) {
app.use("/uploads", express.static(legacyUploadsDirectory, {
setHeaders: setUploadHeaders,
}));
}
// load middlewares
app.use(parseQueryParams);
// Load the routes
_.each(routes, (controller, route) => {
app.use(route, controller(app));
});
// Load the apps routes
_.each(appsRoutes, (controller, route) => {
app.use(route, controller(app));
});
const port = process.env.PORT || app.settings.port || 4019;
const migrateOnStartup = shouldMigrateOnStartup();
if (!migrateOnStartup) {
console.info("Skipping automatic database migrations in development. Run npm run db:migrate when needed."); // eslint-disable-line
}
const databasePreparation = migrateOnStartup ? db.migrate() : Promise.resolve([]);
databasePreparation
.then(async (data) => {
if (data && data.length > 0) {
console.info("Updated database schema to the latest version!"); // eslint-disable-line
}
// create an instance ID and record the current version
try {
const appData = await db.App.findAll();
if (!appData || appData.length === 0) {
db.App.create({ version: packageJson.version });
} else if (appData && appData[0]) {
db.App.update({ version: packageJson.version }, { where: { id: appData[0].id } });
}
} catch (e) {
// continue
}
await refreshPlatformSettings();
startPlatformSettingsRefresh();
const server = app.listen(port, app.settings.api, async () => {
// Initialize Socket.IO
await socketManager.initialize(server);
// Check if this is the main cluster and run the cron jobs if it is
const isMainCluster = parseInt(process.env.NODE_APP_INSTANCE, 10) === 0;
if (isMainCluster || !process.env.NODE_APP_INSTANCE) {
// start CronJob, making sure the database is populated for the first time
setTimeout(() => {
setUpQueues(app);
cleanChartCache();
cleanAuthCache();
cleanGhostChartsCron();
updateAuditRetention();
observationDigestScheduler();
periodEvaluationScheduler();
}, 5000);
}
console.log(`Running server on port ${port}`); // eslint-disable-line
});
})
.catch((err) => {
console.error(err); // eslint-disable-line
console.error("Migrations failed, could not run the server app."); // eslint-disable-line
});