Files
Checkmate/server/index.js
2025-06-09 10:49:52 +08:00

391 lines
12 KiB
JavaScript
Executable File

import path from "path";
import fs from "fs";
import swaggerUi from "swagger-ui-express";
import express from "express";
import helmet from "helmet";
import cors from "cors";
import compression from "compression";
import logger from "./utils/logger.js";
import { verifyJWT } from "./middleware/verifyJWT.js";
import { handleErrors } from "./middleware/handleErrors.js";
import { responseHandler } from "./middleware/responseHandler.js";
import { fileURLToPath } from "url";
import AuthRoutes from "./routes/authRoute.js";
import AuthController from "./controllers/authController.js";
import InviteRoutes from "./routes/inviteRoute.js";
import InviteController from "./controllers/inviteController.js";
import MonitorRoutes from "./routes/monitorRoute.js";
import MonitorController from "./controllers/monitorController.js";
import CheckRoutes from "./routes/checkRoute.js";
import CheckController from "./controllers/checkController.js";
import MaintenanceWindowRoutes from "./routes/maintenanceWindowRoute.js";
import MaintenanceWindowController from "./controllers/maintenanceWindowController.js";
import SettingsRoutes from "./routes/settingsRoute.js";
import SettingsController from "./controllers/settingsController.js";
import StatusPageRoutes from "./routes/statusPageRoute.js";
import StatusPageController from "./controllers/statusPageController.js";
import QueueRoutes from "./routes/queueRoute.js";
import QueueController from "./controllers/queueController.js";
import NotificationRoutes from "./routes/notificationRoute.js";
import NotificationController from "./controllers/notificationController.js";
import DiagnosticRoutes from "./routes/diagnosticRoute.js";
import DiagnosticController from "./controllers/diagnosticController.js";
//JobQueue service and dependencies
import JobQueue from "./service/JobQueue/JobQueue.js";
import JobQueueHelper from "./service/JobQueue/JobQueueHelper.js";
import { Queue, Worker } from "bullmq";
import PulseQueue from "./service/PulseQueue/PulseQueue.js";
import PulseQueueHelper from "./service/PulseQueue/PulseQueueHelper.js";
//Network service and dependencies
import NetworkService from "./service/networkService.js";
import axios from "axios";
import ping from "ping";
import http from "http";
import Docker from "dockerode";
import net from "net";
// Email service and dependencies
import EmailService from "./service/emailService.js";
import nodemailer from "nodemailer";
import pkg from "handlebars";
const { compile } = pkg;
import mjml2html from "mjml";
// Settings Service and dependencies
import SettingsService from "./service/settingsService.js";
import AppSettings from "./db/models/AppSettings.js";
// Status Service and dependencies
import StatusService from "./service/statusService.js";
// Notification Service and dependencies
import NotificationService from "./service/notificationService.js";
// Buffer Service and dependencies
import BufferService from "./service/bufferService.js";
// Service Registry
import ServiceRegistry from "./service/serviceRegistry.js";
import MongoDB from "./db/mongo/MongoDB.js";
// Redis Service and dependencies
import IORedis from "ioredis";
import RedisService from "./service/redisService.js";
import TranslationService from "./service/translationService.js";
import languageMiddleware from "./middleware/languageMiddleware.js";
import StringService from "./service/stringService.js";
const SERVICE_NAME = "Server";
const SHUTDOWN_TIMEOUT = 1000;
let isShuttingDown = false;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const openApiSpec = JSON.parse(
fs.readFileSync(path.join(__dirname, "openapi.json"), "utf8")
);
const frontendPath = path.join(__dirname, "public");
let server;
const shutdown = async () => {
if (isShuttingDown) {
return;
}
isShuttingDown = true;
logger.info({ message: "Attempting graceful shutdown" });
setTimeout(async () => {
logger.error({
message: "Could not shut down in time, forcing shutdown",
service: SERVICE_NAME,
method: "shutdown",
});
await ServiceRegistry.get(RedisService.SERVICE_NAME).flushRedis();
process.exit(1);
}, SHUTDOWN_TIMEOUT);
try {
server.close();
await ServiceRegistry.get(JobQueue.SERVICE_NAME).shutdown();
await ServiceRegistry.get(MongoDB.SERVICE_NAME).disconnect();
logger.info({ message: "Graceful shutdown complete" });
process.exit(0);
} catch (error) {
logger.error({
message: error.message,
service: SERVICE_NAME,
method: "shutdown",
stack: error.stack,
});
}
};
// Need to wrap server setup in a function to handle async nature of JobQueue
const startApp = async () => {
const app = express();
// Create and Register Primary services
const translationService = new TranslationService(logger);
const stringService = new StringService(translationService);
ServiceRegistry.register(StringService.SERVICE_NAME, stringService);
// Create services
const settingsService = new SettingsService(AppSettings);
const appSettings = settingsService.loadSettings();
// Create DB
const db = new MongoDB({ appSettings });
await db.connect();
// Set allowed origin
const allowedOrigin = appSettings.clientHost;
const networkService = new NetworkService(
axios,
ping,
logger,
http,
Docker,
net,
stringService,
settingsService
);
const emailService = new EmailService(
settingsService,
fs,
path,
compile,
mjml2html,
nodemailer,
logger
);
const bufferService = new BufferService({ db, logger });
const statusService = new StatusService({ db, logger, buffer: bufferService });
const notificationService = new NotificationService(
emailService,
db,
logger,
networkService,
stringService
);
const redisService = new RedisService({ Redis: IORedis, logger });
// const jobQueueHelper = new JobQueueHelper({
// redisService,
// Queue,
// Worker,
// logger,
// db,
// networkService,
// statusService,
// notificationService,
// });
// const jobQueue = await JobQueue.create({
// db,
// jobQueueHelper,
// logger,
// stringService,
// });
const pulseQueueHelper = new PulseQueueHelper({
db,
logger,
networkService,
statusService,
notificationService,
});
const pulseQueue = await PulseQueue.create({
appSettings,
db,
pulseQueueHelper,
logger,
});
// Register services
// ServiceRegistry.register(JobQueue.SERVICE_NAME, jobQueue);
ServiceRegistry.register(JobQueue.SERVICE_NAME, pulseQueue);
ServiceRegistry.register(MongoDB.SERVICE_NAME, db);
ServiceRegistry.register(SettingsService.SERVICE_NAME, settingsService);
ServiceRegistry.register(EmailService.SERVICE_NAME, emailService);
ServiceRegistry.register(NetworkService.SERVICE_NAME, networkService);
ServiceRegistry.register(BufferService.SERVICE_NAME, bufferService);
ServiceRegistry.register(StatusService.SERVICE_NAME, statusService);
ServiceRegistry.register(NotificationService.SERVICE_NAME, notificationService);
ServiceRegistry.register(TranslationService.SERVICE_NAME, translationService);
ServiceRegistry.register(RedisService.SERVICE_NAME, redisService);
await translationService.initialize();
const port = appSettings.port || 52345;
server = app.listen(port, () => {
logger.info({ message: `Server started on port:${port}` });
});
process.on("SIGUSR2", shutdown);
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
//Create controllers
const authController = new AuthController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME),
ServiceRegistry.get(EmailService.SERVICE_NAME),
ServiceRegistry.get(JobQueue.SERVICE_NAME),
ServiceRegistry.get(StringService.SERVICE_NAME)
);
const monitorController = new MonitorController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME),
ServiceRegistry.get(JobQueue.SERVICE_NAME),
ServiceRegistry.get(StringService.SERVICE_NAME),
ServiceRegistry.get(EmailService.SERVICE_NAME)
);
const settingsController = new SettingsController({
db: ServiceRegistry.get(MongoDB.SERVICE_NAME),
settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME),
stringService: ServiceRegistry.get(StringService.SERVICE_NAME),
emailService: ServiceRegistry.get(EmailService.SERVICE_NAME),
});
const checkController = new CheckController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME),
ServiceRegistry.get(StringService.SERVICE_NAME)
);
const inviteController = new InviteController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME),
ServiceRegistry.get(EmailService.SERVICE_NAME),
ServiceRegistry.get(StringService.SERVICE_NAME)
);
const maintenanceWindowController = new MaintenanceWindowController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME),
ServiceRegistry.get(StringService.SERVICE_NAME)
);
const queueController = new QueueController(
ServiceRegistry.get(JobQueue.SERVICE_NAME),
ServiceRegistry.get(StringService.SERVICE_NAME)
);
const statusPageController = new StatusPageController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(StringService.SERVICE_NAME)
);
const notificationController = new NotificationController({
notificationService: ServiceRegistry.get(NotificationService.SERVICE_NAME),
stringService: ServiceRegistry.get(StringService.SERVICE_NAME),
statusService: ServiceRegistry.get(StatusService.SERVICE_NAME),
db: ServiceRegistry.get(MongoDB.SERVICE_NAME),
});
const diagnosticController = new DiagnosticController(
ServiceRegistry.get(MongoDB.SERVICE_NAME)
);
//Create routes
const authRoutes = new AuthRoutes(authController);
const monitorRoutes = new MonitorRoutes(monitorController);
const settingsRoutes = new SettingsRoutes(settingsController);
const checkRoutes = new CheckRoutes(checkController);
const inviteRoutes = new InviteRoutes(inviteController);
const maintenanceWindowRoutes = new MaintenanceWindowRoutes(
maintenanceWindowController
);
const queueRoutes = new QueueRoutes(queueController);
const statusPageRoutes = new StatusPageRoutes(statusPageController);
const notificationRoutes = new NotificationRoutes(notificationController);
const diagnosticRoutes = new DiagnosticRoutes(diagnosticController);
// Middleware
app.use(express.static(frontendPath));
app.use(responseHandler);
app.use(
cors({
origin: allowedOrigin,
methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS",
allowedHeaders: "*",
credentials: true,
})
);
app.use(express.json());
app.use(
helmet({
hsts: false,
contentSecurityPolicy: {
useDefaults: true,
directives: {
upgradeInsecureRequests: null,
},
},
})
);
app.use(
compression({
level: 6,
threshold: 1024,
filter: (req, res) => {
if (req.headers["x-no-compression"]) {
return false;
}
return compression.filter(req, res);
},
})
);
app.use(languageMiddleware(stringService, translationService, settingsService));
// Swagger UI
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec));
//routes
app.use("/api/v1/auth", authRoutes.getRouter());
app.use("/api/v1/settings", verifyJWT, settingsRoutes.getRouter());
app.use("/api/v1/invite", inviteRoutes.getRouter());
app.use("/api/v1/monitors", verifyJWT, monitorRoutes.getRouter());
app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter());
app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter());
app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter());
app.use("/api/v1/status-page", statusPageRoutes.getRouter());
app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter());
app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter());
app.use("/api/v1/health", (req, res) => {
res.json({
status: "OK",
});
});
// FE routes
app.get("*", (req, res) => {
res.sendFile(path.join(frontendPath, "index.html"));
});
app.use(handleErrors);
};
startApp().catch((error) => {
logger.error({
message: error.message,
service: SERVICE_NAME,
method: "startApp",
stack: error.stack,
});
process.exit(1);
});