Files
Checkmate/server/src/app.ts
T

130 lines
4.0 KiB
TypeScript
Raw Normal View History

2025-07-25 15:15:14 -07:00
import express from "express";
import path from "path";
import cors, { type CorsOptions } from "cors";
2025-07-25 15:15:14 -07:00
import helmet from "helmet";
import compression from "compression";
2025-08-11 17:47:09 -04:00
import cookieParser from "cookie-parser";
2026-03-20 12:40:27 -07:00
import swaggerUi, { type JsonObject } from "swagger-ui-express";
2026-06-09 12:34:15 -07:00
import { handleErrors } from "@/api/middleware/handleErrors.js";
import { generalApiLimiter } from "@/api/middleware/rateLimiter.js";
import { sanitizeBody, sanitizeQuery } from "@/api/middleware/sanitization.js";
2026-01-21 11:18:13 -08:00
import { setupRoutes } from "@/config/routes.js";
import { InitializedControllers } from "@/config/controllers.js";
2026-06-09 12:34:15 -07:00
import { EnvConfig } from "@/domain/app-settings/app-settings.service.js";
import { createStatusPageCorsOrigin } from "@/api/middleware/statusPageCorsOrigin.js";
import { isPublicStatusPageApiPath } from "@/api/middleware/statusPagePublicApiPath.js";
import { createStatusPageDocumentCsp } from "@/api/middleware/statusPageDocumentCsp.js";
2026-06-24 10:30:21 -07:00
import { ApiServices } from "@/config/services.api.js";
2025-07-28 12:00:09 -07:00
2026-01-21 11:18:13 -08:00
export const createApp = ({
2026-06-24 10:30:21 -07:00
apiServices: services,
2026-01-21 11:18:13 -08:00
controllers,
envSettings,
frontendPath,
openApiSpec,
}: {
2026-06-24 10:30:21 -07:00
apiServices: ApiServices;
2026-01-21 11:18:13 -08:00
controllers: InitializedControllers;
envSettings: EnvConfig;
frontendPath: string;
2026-03-20 12:40:27 -07:00
openApiSpec: JsonObject;
2026-01-21 11:18:13 -08:00
}) => {
2025-07-28 14:37:18 -07:00
const allowedOrigin = envSettings.clientHost;
2025-07-25 15:15:14 -07:00
const app = express();
const defaultCorsOptions: CorsOptions = {
origin: allowedOrigin,
methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS",
allowedHeaders: ["Content-Type", "Authorization", "Accept-Language"],
credentials: true,
};
const publicStatusPageCorsOrigin = createStatusPageCorsOrigin(allowedOrigin, services.statusPagesRepository);
2026-01-30 19:41:41 +00:00
2026-06-30 09:08:20 -07:00
const devMode = envSettings.nodeEnv === "development";
app.use(generalApiLimiter(devMode));
2025-07-25 15:15:14 -07:00
app.use((req, res, next) => {
const corsOptions = isPublicStatusPageApiPath(req.method, req.path)
? { ...defaultCorsOptions, origin: publicStatusPageCorsOrigin }
: defaultCorsOptions;
return cors(corsOptions)(req, res, next);
});
2026-01-30 19:41:41 +00:00
app.use(createStatusPageDocumentCsp(allowedOrigin));
// Client runtime config; registered before express.static so it shadows the
// fallback config.js in the client build output
const clientConfigScript = `window.__CHECKMATE_CONFIG__ = ${JSON.stringify(envSettings.clientConfig).replace(/</g, "\\u003c")};`;
app.get("/config.js", (req, res) => {
res.set("Cache-Control", "no-cache").type("application/javascript").send(clientConfigScript);
});
2026-01-30 19:41:41 +00:00
app.use(express.static(frontendPath));
app.use("/api/v1/monitors/import/json", express.json({ limit: "10mb" }));
app.use(express.json());
2025-08-11 17:47:09 -04:00
app.use(cookieParser());
2025-08-11 17:39:08 -04:00
app.use(sanitizeBody());
app.use(sanitizeQuery());
2025-08-11 17:39:08 -04:00
2025-07-25 15:15:14 -07:00
app.use(
helmet({
hsts: false,
contentSecurityPolicy: {
useDefaults: true,
directives: {
upgradeInsecureRequests: null,
"script-src": ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
"img-src": ["'self'", "data:", "blob:", "https://img.shields.io"],
"object-src": ["'none'"],
"base-uri": ["'self'"],
2025-07-25 15:15:14 -07:00
},
},
})
);
app.use(
compression({
level: 6,
threshold: 1024,
filter: (req, res) => {
if (req.headers["x-no-compression"]) {
return false;
}
return compression.filter(req, res);
},
})
);
// Swagger UI — dynamically set server URL from request
app.use("/api-docs", swaggerUi.serve, (req: express.Request, res: express.Response, next: express.NextFunction) => {
const protocol = req.protocol;
const host = req.get("host");
const dynamicSpec = {
...openApiSpec,
servers: [
{
url: `${protocol}://${host}/api/v1`,
description: "Current Server",
},
...openApiSpec.servers,
],
};
swaggerUi.setup(dynamicSpec)(req, res, next);
});
2025-07-25 15:15:14 -07:00
app.use("/api/v1/health", (req, res) => {
res.json({
status: "OK",
});
});
// Main app routes
setupRoutes(app, controllers, services);
2025-07-25 15:15:14 -07:00
// FE routes
app.get("*", (req, res) => {
res.sendFile(path.join(frontendPath, "index.html"));
});
2026-06-24 10:53:03 -07:00
app.use(handleErrors(services.logger));
2025-07-25 15:15:14 -07:00
return app;
};