@coko/server
Version:
Reusable server for use by Coko's projects
244 lines • 10.9 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.shutdownFn = exports.startServer = void 0;
const express_1 = __importDefault(require("express"));
const util_1 = require("util");
const http_1 = __importDefault(require("http"));
const passport_1 = __importDefault(require("passport"));
const cookie_parser_1 = __importDefault(require("cookie-parser"));
const helmet_1 = __importDefault(require("helmet"));
const Sentry = __importStar(require("@sentry/node"));
const pino_http_1 = __importDefault(require("pino-http"));
const internals_1 = __importDefault(require("./logger/internals"));
const db_1 = require("./db");
const pubsub_1 = __importDefault(require("./graphql/pubsub"));
const healthcheck_1 = __importDefault(require("./healthcheck"));
const filesystem_1 = require("./utils/filesystem");
const errorStatuses_1 = __importDefault(require("./startup/errorStatuses"));
const config_1 = __importDefault(require("./configManager/config"));
const fileStorage_1 = __importDefault(require("./fileStorage"));
const urls_1 = require("./utils/urls");
const checkConnections_1 = require("./startup/checkConnections");
const seedGlobalTeams_1 = __importDefault(require("./startup/seedGlobalTeams"));
const seedAdminUser_1 = __importDefault(require("./startup/seedAdminUser"));
const customScripts_1 = require("./startup/customScripts");
const cors_1 = __importDefault(require("./startup/cors"));
const authentication_1 = __importDefault(require("./authentication"));
const static_1 = __importDefault(require("./startup/static"));
const registerComponents_1 = __importDefault(require("./startup/registerComponents"));
const setup_1 = __importDefault(require("./graphql/setup"));
const jobManager_1 = require("./jobManager");
const env_1 = require("./utils/env");
let server;
const nodeEnv = (0, env_1.env)('NODE_ENV');
const isDevelopment = nodeEnv === 'development';
/**
* startServer is run with no parameters, but we allow a testConfig so that
* tests can run the server in a specified setup.
*/
const startServer = async (testConfig) => {
if (server)
return server;
const startTime = performance.now();
internals_1.default.init('Coko server init tasks', { newLineBefore: true });
internals_1.default.section('Load config');
await config_1.default.init(testConfig);
internals_1.default.success('Configuration valid!');
internals_1.default.section(`Ensure tmp folder exists`);
await (0, filesystem_1.ensureTempFolderExists)();
internals_1.default.success(`tmp folder now exists`);
db_1.db.init();
pubsub_1.default.init();
fileStorage_1.default.init();
(0, urls_1.initUrls)();
await (0, checkConnections_1.checkConnections)();
await db_1.migrationManager.migrate();
await (0, seedGlobalTeams_1.default)();
await (0, seedAdminUser_1.default)(config_1.default.get('adminUser'));
await (0, customScripts_1.runCustomStartupScripts)();
const app = (0, express_1.default)();
const port = config_1.default.get('port') || 3000;
app.set('port', port);
const httpServer = http_1.default.createServer(app);
app.use(express_1.default.json({ limit: '50mb' }));
app.use(express_1.default.urlencoded({ extended: false }));
app.use((0, cookie_parser_1.default)());
/**
* Perhaps in the future, we can add a config option to make this 'same-site'
* in some cases. (eg. client running at myapp.com and server running at
* server.myapp.com can use a stricter 'same-site' policy without issues.)
* Or maybe someone is not mounting static folders at all and they want to
* restrict even further to 'same-origin'.
*/
let helmetConfig = {
crossOriginResourcePolicy: { policy: 'cross-origin' },
};
/**
* This makes apollo explorer work in development
* See https://docs.nestjs.com/security/helmet#use-with-express-default
*/
if (isDevelopment && config_1.default.get('useGraphQLServer')) {
helmetConfig = {
...helmetConfig,
crossOriginEmbedderPolicy: false,
contentSecurityPolicy: {
directives: {
imgSrc: [
`'self'`,
'data:',
'apollo-server-landing-page.cdn.apollographql.com',
],
scriptSrc: [`'self'`, `https: 'unsafe-inline'`],
manifestSrc: [
`'self'`,
'apollo-server-landing-page.cdn.apollographql.com',
],
frameSrc: [`'self'`, 'sandbox.embed.apollographql.com'],
},
},
};
}
app.use((0, helmet_1.default)(helmetConfig));
app.use((0, cors_1.default)());
app.use((0, pino_http_1.default)({
transport: isDevelopment
? {
target: 'pino-pretty',
options: {
colorize: true,
messageFormat: '{req.method} {req.url} {res.statusCode} - {responseTime}ms - request ID: {req.id}',
ignore: 'req,res,err,responseTime',
},
}
: undefined,
customProps: (req, _res) => {
if (!(req.url === '/graphql' && req.body))
return {};
const { variables, operationName } = req.body;
return {
gql: {
operation: operationName || 'unnamed',
// Do not log variables in production due to PII/GDPR etc.
// Maybe at some point we could have a whitelist for variables that are explictly OK to log
variables: isDevelopment ? variables : undefined,
},
};
},
redact: {
paths: [
'req.headers.cookie',
'req.headers.authorization',
'gql.variables.token',
'gql.variables.email',
'gql.variables.password',
'gql.variables.currentPassword',
'gql.variables.newPassword',
'gql.variables.input.token',
'gql.variables.input.email',
'gql.variables.input.password',
'gql.variables.input.currentPassword',
'gql.variables.input.newPassword',
],
censor: '*****',
},
}));
// @ts-ignore
app.use(passport_1.default.initialize());
passport_1.default.use('bearer', authentication_1.default.strategies.bearer);
passport_1.default.use('anonymous', authentication_1.default.strategies.anonymous);
passport_1.default.use('local', authentication_1.default.strategies.local);
app.get('/healthcheck', healthcheck_1.default);
(0, static_1.default)(app);
await (0, registerComponents_1.default)(app);
if (config_1.default.get('useGraphQLServer')) {
await (0, setup_1.default)(httpServer, app, passport_1.default);
}
await jobManager_1.jobManager.init();
internals_1.default.section(`Starting HTTP server`);
const sentryDsn = config_1.default.get('sentry.dsn');
if (sentryDsn) {
Sentry.setupExpressErrorHandler(app);
internals_1.default.success('Sentry initialized');
}
else {
internals_1.default.warn('Skipping sentry initialization: no dsn key found in config');
}
(0, errorStatuses_1.default)(app);
const startListening = (0, util_1.promisify)(httpServer.listen).bind(httpServer);
await startListening(port);
internals_1.default.point(`App is listening on port ${port}`);
server = httpServer;
const endTime = performance.now();
const durationInSeconds = (endTime - startTime) / 1000; // Convert to seconds
internals_1.default.init(`Coko server init finished in ${durationInSeconds.toFixed(4)} seconds`, {
newLineBefore: true,
newLineAfter: true,
});
return server;
};
exports.startServer = startServer;
const shutdownFn = async () => {
await (0, customScripts_1.runCustomShutdownScripts)();
internals_1.default.section('Shut down http server');
server.close();
server = undefined;
internals_1.default.success('Http server successfully shut down');
await jobManager_1.jobManager.stop();
if (config_1.default.get('useGraphQLServer')) {
internals_1.default.section('Shut down subscription client');
await pubsub_1.default.client.end();
internals_1.default.success('Subscription client successfully shut down');
}
internals_1.default.section('Shut down database connection');
await db_1.db.destroy();
internals_1.default.success('Database connection successfully shut down');
};
exports.shutdownFn = shutdownFn;
const shutdown = async (signal) => {
internals_1.default.init(`Coko server graceful shutdown after receiving signal ${signal}`);
const startTime = performance.now();
await (0, exports.shutdownFn)();
const endTime = performance.now();
const durationInSeconds = (endTime - startTime) / 1000; // Convert to seconds
internals_1.default.init(`Coko server graceful shutdown finished in ${durationInSeconds.toFixed(4)} seconds`);
process.exit();
};
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));
//# sourceMappingURL=startServer.js.map