refactor server
This commit is contained in:
1 parent
6cb538ce69
commit
acc910cbc0
48 files changed
+584
-650
No files matched your search
@@ -0,0 +1,73 @@
|
||||
import express from "express";
|
||||
import path from "path";
|
||||
import { responseHandler } from "./middleware/responseHandler.js";
|
||||
import cors from "cors";
|
||||
import helmet from "helmet";
|
||||
import compression from "compression";
|
||||
import languageMiddleware from "./middleware/languageMiddleware.js";
|
||||
import swaggerUi from "swagger-ui-express";
|
||||
import { handleErrors } from "./middleware/handleErrors.js";
|
||||
import { setupRoutes } from "./config/routes.js";
|
||||
export const createApp = ({ services, controllers, appSettings, frontendPath, openApiSpec }) => {
|
||||
const allowedOrigin = appSettings.clientHost;
|
||||
|
||||
const app = express();
|
||||
|
||||
// Static files
|
||||
app.use(express.static(frontendPath));
|
||||
|
||||
// Response handler
|
||||
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(services.stringService, services.translationService, services.settingsService));
|
||||
// Swagger UI
|
||||
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec));
|
||||
|
||||
app.use("/api/v1/health", (req, res) => {
|
||||
res.json({
|
||||
status: "OK",
|
||||
});
|
||||
});
|
||||
|
||||
// Main app routes
|
||||
setupRoutes(app, controllers);
|
||||
|
||||
// FE routes
|
||||
app.get("*", (req, res) => {
|
||||
res.sendFile(path.join(frontendPath, "index.html"));
|
||||
});
|
||||
app.use(handleErrors);
|
||||
return app;
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
import { createCommonDependencies } from "../controllers/baseController.js";
|
||||
|
||||
// Services
|
||||
|
||||
// Controllers
|
||||
import MonitorController from "../controllers/monitorController.js";
|
||||
import AuthController from "../controllers/authController.js";
|
||||
import SettingsController from "../controllers/settingsController.js";
|
||||
import CheckController from "../controllers/checkController.js";
|
||||
import InviteController from "../controllers/inviteController.js";
|
||||
import MaintenanceWindowController from "../controllers/maintenanceWindowController.js";
|
||||
import QueueController from "../controllers/queueController.js";
|
||||
import LogController from "../controllers/logController.js";
|
||||
import StatusPageController from "../controllers/statusPageController.js";
|
||||
import NotificationController from "../controllers/notificationController.js";
|
||||
import DiagnosticController from "../controllers/diagnosticController.js";
|
||||
|
||||
export const initializeControllers = (services) => {
|
||||
const controllers = {};
|
||||
const commonDependencies = createCommonDependencies(services.db, services.logger, services.errorService, services.stringService);
|
||||
|
||||
controllers.authController = new AuthController(commonDependencies, {
|
||||
settingsService: services.settingsService,
|
||||
emailService: services.emailService,
|
||||
jobQueue: services.jobQueue,
|
||||
userService: services.userService,
|
||||
});
|
||||
|
||||
controllers.monitorController = new MonitorController(commonDependencies, {
|
||||
settingsService: services.settingsService,
|
||||
jobQueue: services.jobQueue,
|
||||
emailService: services.emailService,
|
||||
monitorService: services.monitorService,
|
||||
});
|
||||
|
||||
controllers.settingsController = new SettingsController(commonDependencies, {
|
||||
settingsService: services.settingsService,
|
||||
emailService: services.emailService,
|
||||
});
|
||||
controllers.checkController = new CheckController(commonDependencies, {
|
||||
settingsService: services.settingsService,
|
||||
checkService: services.checkService,
|
||||
});
|
||||
controllers.inviteController = new InviteController(commonDependencies, {
|
||||
inviteService: services.inviteService,
|
||||
});
|
||||
|
||||
controllers.maintenanceWindowController = new MaintenanceWindowController(commonDependencies, {
|
||||
settingsService: services.settingsService,
|
||||
maintenanceWindowService: services.maintenanceWindowService,
|
||||
});
|
||||
controllers.queueController = new QueueController(commonDependencies, {
|
||||
jobQueue: services.jobQueue,
|
||||
});
|
||||
controllers.logController = new LogController(commonDependencies);
|
||||
controllers.statusPageController = new StatusPageController(commonDependencies);
|
||||
controllers.notificationController = new NotificationController(commonDependencies, {
|
||||
notificationService: services.notificationService,
|
||||
statusService: services.statusService,
|
||||
});
|
||||
controllers.diagnosticController = new DiagnosticController(commonDependencies, {
|
||||
diagnosticService: services.diagnosticService,
|
||||
});
|
||||
|
||||
return controllers;
|
||||
};
|
||||
Whitespace-only changes.
@@ -0,0 +1,39 @@
|
||||
import { verifyJWT } from "../middleware/verifyJWT.js";
|
||||
|
||||
import AuthRoutes from "../routes/authRoute.js";
|
||||
import InviteRoutes from "../routes/inviteRoute.js";
|
||||
import MonitorRoutes from "../routes/monitorRoute.js";
|
||||
import CheckRoutes from "../routes/checkRoute.js";
|
||||
import SettingsRoutes from "../routes/settingsRoute.js";
|
||||
import MaintenanceWindowRoutes from "../routes/maintenanceWindowRoute.js";
|
||||
import StatusPageRoutes from "../routes/statusPageRoute.js";
|
||||
import QueueRoutes from "../routes/queueRoute.js";
|
||||
import LogRoutes from "../routes/logRoutes.js";
|
||||
import DiagnosticRoutes from "../routes/diagnosticRoute.js";
|
||||
import NotificationRoutes from "../routes/notificationRoute.js";
|
||||
|
||||
export const setupRoutes = (app, controllers) => {
|
||||
const authRoutes = new AuthRoutes(controllers.authController);
|
||||
const monitorRoutes = new MonitorRoutes(controllers.monitorController);
|
||||
const settingsRoutes = new SettingsRoutes(controllers.settingsController);
|
||||
const checkRoutes = new CheckRoutes(controllers.checkController);
|
||||
const inviteRoutes = new InviteRoutes(controllers.inviteController);
|
||||
const maintenanceWindowRoutes = new MaintenanceWindowRoutes(controllers.maintenanceWindowController);
|
||||
const queueRoutes = new QueueRoutes(controllers.queueController);
|
||||
const logRoutes = new LogRoutes(controllers.logController);
|
||||
const statusPageRoutes = new StatusPageRoutes(controllers.statusPageController);
|
||||
const notificationRoutes = new NotificationRoutes(controllers.notificationController);
|
||||
const diagnosticRoutes = new DiagnosticRoutes(controllers.diagnosticController);
|
||||
|
||||
app.use("/api/v1/auth", authRoutes.getRouter());
|
||||
app.use("/api/v1/monitors", verifyJWT, monitorRoutes.getRouter());
|
||||
app.use("/api/v1/settings", verifyJWT, settingsRoutes.getRouter());
|
||||
app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter());
|
||||
app.use("/api/v1/invite", inviteRoutes.getRouter());
|
||||
app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter());
|
||||
app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter());
|
||||
app.use("/api/v1/logs", verifyJWT, logRoutes.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());
|
||||
};
|
||||
@@ -0,0 +1,152 @@
|
||||
import ServiceRegistry from "../service/system/serviceRegistry.js";
|
||||
import logger from "../utils/logger.js";
|
||||
import TranslationService from "../service/system/translationService.js";
|
||||
import StringService from "../service/system/stringService.js";
|
||||
import MongoDB from "../db/mongo/MongoDB.js";
|
||||
import NetworkService from "../service/infrastructure/networkService.js";
|
||||
import EmailService from "../service/infrastructure/emailService.js";
|
||||
import BufferService from "../service/infrastructure/bufferService.js";
|
||||
import StatusService from "../service/infrastructure/statusService.js";
|
||||
import NotificationUtils from "../service/infrastructure/notificationUtils.js";
|
||||
import NotificationService from "../service/infrastructure/notificationService.js";
|
||||
import RedisService from "../service/data/redisService.js";
|
||||
import ErrorService from "../service/infrastructure/errorService.js";
|
||||
import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
|
||||
import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
|
||||
import UserService from "../service/business/userService.js";
|
||||
import CheckService from "../service/business/checkService.js";
|
||||
import DiagnosticService from "../service/business/diagnosticService.js";
|
||||
import InviteService from "../service/business/inviteService.js";
|
||||
import MaintenanceWindowService from "../service/business/maintenanceWindowService.js";
|
||||
import MonitorService from "../service/business/monitorService.js";
|
||||
import IORedis from "ioredis";
|
||||
import papaparse from "papaparse";
|
||||
import axios from "axios";
|
||||
import ping from "ping";
|
||||
import http from "http";
|
||||
import Docker from "dockerode";
|
||||
import net from "net";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import nodemailer from "nodemailer";
|
||||
import pkg from "handlebars";
|
||||
const { compile } = pkg;
|
||||
import mjml2html from "mjml";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
export const initializeServices = async (appSettings, settingsService) => {
|
||||
const translationService = new TranslationService(logger);
|
||||
await translationService.initialize();
|
||||
|
||||
const stringService = new StringService(translationService);
|
||||
|
||||
// Create DB
|
||||
const db = new MongoDB({ appSettings });
|
||||
await db.connect();
|
||||
|
||||
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 notificationUtils = new NotificationUtils({
|
||||
stringService,
|
||||
emailService,
|
||||
});
|
||||
|
||||
const notificationService = new NotificationService({
|
||||
emailService,
|
||||
db,
|
||||
logger,
|
||||
networkService,
|
||||
stringService,
|
||||
notificationUtils,
|
||||
});
|
||||
|
||||
const redisService = new RedisService({ Redis: IORedis, logger });
|
||||
const errorService = new ErrorService();
|
||||
|
||||
const superSimpleQueueHelper = new SuperSimpleQueueHelper({
|
||||
db,
|
||||
logger,
|
||||
networkService,
|
||||
statusService,
|
||||
notificationService,
|
||||
});
|
||||
|
||||
const superSimpleQueue = await SuperSimpleQueue.create({
|
||||
appSettings,
|
||||
db,
|
||||
logger,
|
||||
helper: superSimpleQueueHelper,
|
||||
});
|
||||
|
||||
// Business services
|
||||
const userService = new UserService({
|
||||
db,
|
||||
emailService,
|
||||
settingsService,
|
||||
logger,
|
||||
stringService,
|
||||
jwt,
|
||||
errorService,
|
||||
});
|
||||
const checkService = new CheckService({
|
||||
db,
|
||||
settingsService,
|
||||
stringService,
|
||||
errorService,
|
||||
});
|
||||
const diagnosticService = new DiagnosticService();
|
||||
const inviteService = new InviteService({
|
||||
db,
|
||||
settingsService,
|
||||
emailService,
|
||||
stringService,
|
||||
errorService,
|
||||
});
|
||||
const maintenanceWindowService = new MaintenanceWindowService({
|
||||
db,
|
||||
settingsService,
|
||||
stringService,
|
||||
errorService,
|
||||
});
|
||||
const monitorService = new MonitorService({
|
||||
db,
|
||||
settingsService,
|
||||
jobQueue: superSimpleQueue,
|
||||
stringService,
|
||||
emailService,
|
||||
papaparse,
|
||||
logger,
|
||||
errorService,
|
||||
});
|
||||
|
||||
const services = {
|
||||
settingsService,
|
||||
translationService,
|
||||
stringService,
|
||||
db,
|
||||
networkService,
|
||||
emailService,
|
||||
bufferService,
|
||||
statusService,
|
||||
notificationService,
|
||||
redisService,
|
||||
jobQueue: superSimpleQueue,
|
||||
userService,
|
||||
checkService,
|
||||
diagnosticService,
|
||||
inviteService,
|
||||
maintenanceWindowService,
|
||||
monitorService,
|
||||
errorService,
|
||||
logger,
|
||||
};
|
||||
|
||||
Object.values(services).forEach((service) => {
|
||||
ServiceRegistry.register(service.serviceName, service);
|
||||
});
|
||||
|
||||
return services;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createAnnouncementValidation } from "../validation/joi.js";
|
||||
import BaseController from "./baseController.js";
|
||||
|
||||
const SERVICE_NAME = "announcementController";
|
||||
|
||||
@@ -10,12 +11,17 @@ const SERVICE_NAME = "announcementController";
|
||||
*/
|
||||
|
||||
class AnnouncementController extends BaseController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor(commonDependencies) {
|
||||
super(commonDependencies);
|
||||
this.createAnnouncement = this.createAnnouncement.bind(this);
|
||||
this.getAnnouncement = this.getAnnouncement.bind(this);
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return AnnouncementController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the creation of a new announcement.
|
||||
*
|
||||
|
||||
@@ -24,6 +24,7 @@ const SERVICE_NAME = "authController";
|
||||
* @description Manages user authentication and authorization operations
|
||||
*/
|
||||
class AuthController extends BaseController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
/**
|
||||
* Creates an instance of AuthController.
|
||||
*
|
||||
@@ -42,6 +43,10 @@ class AuthController extends BaseController {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return AuthController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new user in the system.
|
||||
*
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { AppError } from "../service/infrastructure/errorService.js";
|
||||
|
||||
export const createCommonDependencies = (serviceRegistry, dbServiceName, loggerServiceName, errorServiceName, stringServiceName) => {
|
||||
export const createCommonDependencies = (db, errorService, logger, stringService) => {
|
||||
return {
|
||||
db: serviceRegistry.get(dbServiceName),
|
||||
errorService: serviceRegistry.get(errorServiceName),
|
||||
logger: serviceRegistry.get(loggerServiceName),
|
||||
stringService: serviceRegistry.get(stringServiceName),
|
||||
db,
|
||||
errorService,
|
||||
logger,
|
||||
stringService,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ const SERVICE_NAME = "checkController";
|
||||
* @description Manages check operations and monitoring data
|
||||
*/
|
||||
class CheckController extends BaseController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
/**
|
||||
* Creates an instance of CheckController.
|
||||
*
|
||||
@@ -37,6 +38,10 @@ class CheckController extends BaseController {
|
||||
this.checkService = checkService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return CheckController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves checks for a specific monitor with filtering and pagination.
|
||||
*
|
||||
|
||||
@@ -10,6 +10,7 @@ import BaseController from "./baseController.js";
|
||||
* @description Manages system diagnostics and performance monitoring
|
||||
*/
|
||||
class DiagnosticController extends BaseController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
/**
|
||||
* Creates an instance of DiagnosticController.
|
||||
* @param {Object} commonDependencies - Common dependencies injected into the controller
|
||||
@@ -21,6 +22,9 @@ class DiagnosticController extends BaseController {
|
||||
this.diagnosticService = diagnosticService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return DiagnosticController.SERVICE_NAME;
|
||||
}
|
||||
/**
|
||||
* Retrieves comprehensive system statistics and performance metrics.
|
||||
*
|
||||
|
||||
@@ -7,6 +7,7 @@ const SERVICE_NAME = "inviteController";
|
||||
* Manages invite token generation, email sending, and token verification
|
||||
*/
|
||||
class InviteController extends BaseController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
/**
|
||||
* Creates a new InviteController instance
|
||||
* @param {Object} commonDependencies - Common dependencies injected into the controller
|
||||
@@ -17,6 +18,10 @@ class InviteController extends BaseController {
|
||||
this.inviteService = inviteService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return InviteController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an invite token for a user invitation
|
||||
* @param {Object} req - Express request object
|
||||
|
||||
@@ -2,10 +2,15 @@ import BaseController from "./baseController.js";
|
||||
const SERVICE_NAME = "LogController";
|
||||
|
||||
class LogController extends BaseController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor(commonDependencies) {
|
||||
super(commonDependencies);
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return LogController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getLogs = this.asyncHandler(
|
||||
async (req, res) => {
|
||||
const logs = await this.logger.getLogs();
|
||||
|
||||
@@ -12,12 +12,17 @@ import BaseController from "./baseController.js";
|
||||
const SERVICE_NAME = "maintenanceWindowController";
|
||||
|
||||
class MaintenanceWindowController extends BaseController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor(commonDependencies, { settingsService, maintenanceWindowService }) {
|
||||
super(commonDependencies);
|
||||
this.settingsService = settingsService;
|
||||
this.maintenanceWindowService = maintenanceWindowService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return MaintenanceWindowController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
createMaintenanceWindows = this.asyncHandler(
|
||||
async (req, res) => {
|
||||
await createMaintenanceWindowBodyValidation.validateAsync(req.body);
|
||||
|
||||
@@ -18,6 +18,7 @@ import BaseController from "./baseController.js";
|
||||
|
||||
const SERVICE_NAME = "monitorController";
|
||||
class MonitorController extends BaseController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor(commonDependencies, { settingsService, jobQueue, emailService, monitorService }) {
|
||||
super(commonDependencies);
|
||||
this.settingsService = settingsService;
|
||||
@@ -26,6 +27,10 @@ class MonitorController extends BaseController {
|
||||
this.monitorService = monitorService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return MonitorController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
async verifyTeamAccess(teamId, monitorId) {
|
||||
const monitor = await this.db.getMonitorById(monitorId);
|
||||
if (!monitor.teamId.equals(teamId)) {
|
||||
|
||||
@@ -4,12 +4,17 @@ import BaseController from "./baseController.js";
|
||||
const SERVICE_NAME = "NotificationController";
|
||||
|
||||
class NotificationController extends BaseController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor(commonDependencies, { notificationService, statusService }) {
|
||||
super(commonDependencies);
|
||||
this.notificationService = notificationService;
|
||||
this.statusService = statusService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return NotificationController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
testNotification = this.asyncHandler(
|
||||
async (req, res) => {
|
||||
const notification = req.body;
|
||||
|
||||
@@ -2,11 +2,16 @@ import BaseController from "./baseController.js";
|
||||
const SERVICE_NAME = "JobQueueController";
|
||||
|
||||
class JobQueueController extends BaseController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor(commonDependencies, { jobQueue }) {
|
||||
super(commonDependencies);
|
||||
this.jobQueue = jobQueue;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return JobQueueController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getMetrics = this.asyncHandler(
|
||||
async (req, res) => {
|
||||
const metrics = await this.jobQueue.getMetrics();
|
||||
|
||||
@@ -5,12 +5,17 @@ import BaseController from "./baseController.js";
|
||||
const SERVICE_NAME = "SettingsController";
|
||||
|
||||
class SettingsController extends BaseController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor(commonDependencies, { settingsService, emailService }) {
|
||||
super(commonDependencies);
|
||||
this.settingsService = settingsService;
|
||||
this.emailService = emailService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return SettingsController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
buildAppSettings = (dbSettings) => {
|
||||
const sanitizedSettings = { ...dbSettings };
|
||||
delete sanitizedSettings.version;
|
||||
|
||||
@@ -4,10 +4,15 @@ import BaseController from "./baseController.js";
|
||||
const SERVICE_NAME = "statusPageController";
|
||||
|
||||
class StatusPageController extends BaseController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor(commonDependencies) {
|
||||
super(commonDependencies);
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return StatusPageController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
createStatusPage = this.asyncHandler(
|
||||
async (req, res) => {
|
||||
await createStatusPageBodyValidation.validateAsync(req.body);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import mongoose from "mongoose";
|
||||
import UserModel from "../models/User.js";
|
||||
import AppSettings from "../models/AppSettings.js";
|
||||
import logger from "../../utils/logger.js";
|
||||
|
||||
@@ -87,6 +86,10 @@ class MongoDB {
|
||||
Object.assign(this, diagnosticModule);
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return MongoDB.SERVICE_NAME;
|
||||
}
|
||||
|
||||
connect = async () => {
|
||||
try {
|
||||
const connectionString = this.appSettings.dbConnectionString || "mongodb://localhost:27017/uptime_db";
|
||||
|
||||
+22
-425
@@ -1,450 +1,47 @@
|
||||
import { initializeServices } from "./config/services.js";
|
||||
import { initializeControllers } from "./config/controllers.js";
|
||||
import { createApp } from "./app.js";
|
||||
import { initShutdownListener } from "./shutdown.js";
|
||||
import logger from "./utils/logger.js";
|
||||
import { fileURLToPath } from "url";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import swaggerUi from "swagger-ui-express";
|
||||
import jwt from "jsonwebtoken";
|
||||
import papaparse from "papaparse";
|
||||
import express from "express";
|
||||
import helmet from "helmet";
|
||||
import cors from "cors";
|
||||
import compression from "compression";
|
||||
import { Logger } from "./utils/logger.js";
|
||||
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 { createCommonDependencies } from "./controllers/baseController.js";
|
||||
|
||||
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 LogRoutes from "./routes/logRoutes.js";
|
||||
import LogController from "./controllers/logController.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/infrastructure/JobQueue/JobQueue.js";
|
||||
import JobQueueHelper from "./service/infrastructure/JobQueue/JobQueueHelper.js";
|
||||
import { Queue, Worker } from "bullmq";
|
||||
|
||||
import PulseQueue from "./service/infrastructure/PulseQueue/PulseQueue.js";
|
||||
import PulseQueueHelper from "./service/infrastructure/PulseQueue/PulseQueueHelper.js";
|
||||
|
||||
import SuperSimpleQueue from "./service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
|
||||
import SuperSimpleQueueHelper from "./service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
|
||||
|
||||
import ErrorService from "./service/infrastructure/errorService.js";
|
||||
|
||||
// Business services
|
||||
import UserService from "./service/business/userService.js";
|
||||
import CheckService from "./service/business/checkService.js";
|
||||
import DiagnosticService from "./service/business/diagnosticService.js";
|
||||
import InviteService from "./service/business/inviteService.js";
|
||||
import MaintenanceWindowService from "./service/business/maintenanceWindowService.js";
|
||||
import MonitorService from "./service/business/monitorService.js";
|
||||
|
||||
//Network service and dependencies
|
||||
import NetworkService from "./service/infrastructure/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/infrastructure/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/system/settingsService.js";
|
||||
import AppSettings from "./db/models/AppSettings.js";
|
||||
|
||||
// Status Service and dependencies
|
||||
import StatusService from "./service/infrastructure/statusService.js";
|
||||
|
||||
// Notification Service and dependencies
|
||||
import NotificationService from "./service/infrastructure/notificationService.js";
|
||||
import NotificationUtils from "./service/infrastructure/notificationUtils.js";
|
||||
|
||||
// Buffer Service and dependencies
|
||||
import BufferService from "./service/infrastructure/bufferService.js";
|
||||
|
||||
// Service Registry
|
||||
import ServiceRegistry from "./service/system/serviceRegistry.js";
|
||||
|
||||
import MongoDB from "./db/mongo/MongoDB.js";
|
||||
|
||||
// Redis Service and dependencies
|
||||
import IORedis from "ioredis";
|
||||
import RedisService from "./service/data/redisService.js";
|
||||
|
||||
import TranslationService from "./service/system/translationService.js";
|
||||
import languageMiddleware from "./middleware/languageMiddleware.js";
|
||||
import StringService from "./service/system/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);
|
||||
|
||||
// FE path
|
||||
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");
|
||||
// Create services
|
||||
const settingsService = new SettingsService(AppSettings);
|
||||
const appSettings = settingsService.loadSettings();
|
||||
|
||||
// Create DB
|
||||
const db = new MongoDB({ appSettings });
|
||||
await db.connect();
|
||||
// Initialize services
|
||||
const services = await initializeServices(appSettings, settingsService);
|
||||
|
||||
// Set allowed origin
|
||||
const allowedOrigin = appSettings.clientHost;
|
||||
// Initialize controllers
|
||||
const controllers = initializeControllers(services);
|
||||
|
||||
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 notificationUtils = new NotificationUtils({
|
||||
stringService,
|
||||
emailService,
|
||||
});
|
||||
|
||||
const notificationService = new NotificationService({
|
||||
emailService,
|
||||
db,
|
||||
logger,
|
||||
networkService,
|
||||
stringService,
|
||||
notificationUtils,
|
||||
});
|
||||
|
||||
const redisService = new RedisService({ Redis: IORedis, logger });
|
||||
const errorService = new ErrorService();
|
||||
|
||||
// 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,
|
||||
// });
|
||||
const superSimpleQueueHelper = new SuperSimpleQueueHelper({
|
||||
db,
|
||||
logger,
|
||||
networkService,
|
||||
statusService,
|
||||
notificationService,
|
||||
});
|
||||
|
||||
const superSimpleQueue = await SuperSimpleQueue.create({
|
||||
const app = createApp({
|
||||
services,
|
||||
controllers,
|
||||
appSettings,
|
||||
db,
|
||||
logger,
|
||||
helper: superSimpleQueueHelper,
|
||||
frontendPath,
|
||||
openApiSpec,
|
||||
});
|
||||
|
||||
// Business services
|
||||
const userService = new UserService({
|
||||
db,
|
||||
emailService,
|
||||
settingsService,
|
||||
logger,
|
||||
stringService,
|
||||
jwt,
|
||||
errorService,
|
||||
});
|
||||
const checkService = new CheckService({
|
||||
db,
|
||||
settingsService,
|
||||
stringService,
|
||||
errorService,
|
||||
});
|
||||
const diagnosticService = new DiagnosticService();
|
||||
const inviteService = new InviteService({
|
||||
db,
|
||||
settingsService,
|
||||
emailService,
|
||||
stringService,
|
||||
errorService,
|
||||
});
|
||||
const maintenanceWindowService = new MaintenanceWindowService({
|
||||
db,
|
||||
settingsService,
|
||||
stringService,
|
||||
errorService,
|
||||
});
|
||||
const monitorService = new MonitorService({
|
||||
db,
|
||||
settingsService,
|
||||
jobQueue: superSimpleQueue,
|
||||
stringService,
|
||||
emailService,
|
||||
papaparse,
|
||||
logger,
|
||||
errorService,
|
||||
});
|
||||
// Register services
|
||||
// ServiceRegistry.register(JobQueue.SERVICE_NAME, jobQueue);
|
||||
// ServiceRegistry.register(JobQueue.SERVICE_NAME, pulseQueue);
|
||||
ServiceRegistry.register(Logger.SERVICE_NAME, logger);
|
||||
ServiceRegistry.register(JobQueue.SERVICE_NAME, superSimpleQueue);
|
||||
ServiceRegistry.register(ErrorService.SERVICE_NAME, errorService);
|
||||
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);
|
||||
ServiceRegistry.register(UserService.SERVICE_NAME, userService);
|
||||
ServiceRegistry.register(CheckService.SERVICE_NAME, checkService);
|
||||
ServiceRegistry.register(InviteService.SERVICE_NAME, inviteService);
|
||||
ServiceRegistry.register(MaintenanceWindowService.SERVICE_NAME, maintenanceWindowService);
|
||||
ServiceRegistry.register(MonitorService.SERVICE_NAME, monitorService);
|
||||
ServiceRegistry.register(DiagnosticService.SERVICE_NAME, diagnosticService);
|
||||
|
||||
await translationService.initialize();
|
||||
|
||||
const port = appSettings.port || 52345;
|
||||
server = app.listen(port, () => {
|
||||
const 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 commonDependencies = createCommonDependencies(
|
||||
ServiceRegistry,
|
||||
MongoDB.SERVICE_NAME,
|
||||
Logger.SERVICE_NAME,
|
||||
ErrorService.SERVICE_NAME,
|
||||
StringService.SERVICE_NAME
|
||||
);
|
||||
|
||||
const authController = new AuthController(commonDependencies, {
|
||||
settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME),
|
||||
emailService: ServiceRegistry.get(EmailService.SERVICE_NAME),
|
||||
jobQueue: ServiceRegistry.get(JobQueue.SERVICE_NAME),
|
||||
userService: ServiceRegistry.get(UserService.SERVICE_NAME),
|
||||
});
|
||||
|
||||
const monitorController = new MonitorController(commonDependencies, {
|
||||
settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME),
|
||||
jobQueue: ServiceRegistry.get(JobQueue.SERVICE_NAME),
|
||||
emailService: ServiceRegistry.get(EmailService.SERVICE_NAME),
|
||||
monitorService: ServiceRegistry.get(MonitorService.SERVICE_NAME),
|
||||
});
|
||||
|
||||
const settingsController = new SettingsController(commonDependencies, {
|
||||
settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME),
|
||||
emailService: ServiceRegistry.get(EmailService.SERVICE_NAME),
|
||||
});
|
||||
|
||||
const checkController = new CheckController(commonDependencies, {
|
||||
settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME),
|
||||
checkService: ServiceRegistry.get(CheckService.SERVICE_NAME),
|
||||
});
|
||||
|
||||
const inviteController = new InviteController(commonDependencies, {
|
||||
inviteService: ServiceRegistry.get(InviteService.SERVICE_NAME),
|
||||
});
|
||||
|
||||
const maintenanceWindowController = new MaintenanceWindowController(commonDependencies, {
|
||||
settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME),
|
||||
maintenanceWindowService: ServiceRegistry.get(MaintenanceWindowService.SERVICE_NAME),
|
||||
});
|
||||
|
||||
const queueController = new QueueController(commonDependencies, {
|
||||
jobQueue: ServiceRegistry.get(JobQueue.SERVICE_NAME),
|
||||
});
|
||||
|
||||
const logController = new LogController(commonDependencies);
|
||||
|
||||
const statusPageController = new StatusPageController(commonDependencies);
|
||||
|
||||
const notificationController = new NotificationController(commonDependencies, {
|
||||
notificationService: ServiceRegistry.get(NotificationService.SERVICE_NAME),
|
||||
statusService: ServiceRegistry.get(StatusService.SERVICE_NAME),
|
||||
});
|
||||
|
||||
const diagnosticController = new DiagnosticController(commonDependencies, {
|
||||
diagnosticService: ServiceRegistry.get(DiagnosticService.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 logRoutes = new LogRoutes(logController);
|
||||
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/monitors", verifyJWT, monitorRoutes.getRouter());
|
||||
app.use("/api/v1/settings", verifyJWT, settingsRoutes.getRouter());
|
||||
app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter());
|
||||
app.use("/api/v1/invite", inviteRoutes.getRouter());
|
||||
app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter());
|
||||
app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter());
|
||||
app.use("/api/v1/logs", verifyJWT, logRoutes.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);
|
||||
initShutdownListener(server, services);
|
||||
};
|
||||
|
||||
startApp().catch((error) => {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const startServer = () => {};
|
||||
@@ -10,6 +10,10 @@ class CheckService {
|
||||
this.errorService = errorService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return CheckService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getChecksByMonitor = async ({ monitorId, query, teamId }) => {
|
||||
if (!monitorId) {
|
||||
throw this.errorService.createBadRequestError("No monitor ID in request");
|
||||
|
||||
@@ -5,6 +5,7 @@ const SERVICE_NAME = "diagnosticService";
|
||||
|
||||
class DiagnosticService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor() {
|
||||
/**
|
||||
* Performance Observer for monitoring system performance metrics.
|
||||
@@ -18,6 +19,10 @@ class DiagnosticService {
|
||||
obs.observe({ entryTypes: ["measure"] });
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return DiagnosticService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getCPUUsage = async () => {
|
||||
const startUsage = process.cpuUsage();
|
||||
const timingPeriod = 1000; // measured in ms
|
||||
|
||||
@@ -11,6 +11,10 @@ class InviteService {
|
||||
this.errorService = errorService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return InviteService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getInviteToken = async ({ invite, teamId }) => {
|
||||
invite.teamId = teamId;
|
||||
const inviteToken = await this.db.requestInviteToken(invite);
|
||||
|
||||
@@ -2,6 +2,7 @@ const SERVICE_NAME = "maintenanceWindowService";
|
||||
|
||||
class MaintenanceWindowService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor({ db, settingsService, stringService, errorService }) {
|
||||
this.db = db;
|
||||
this.settingsService = settingsService;
|
||||
@@ -9,6 +10,10 @@ class MaintenanceWindowService {
|
||||
this.errorService = errorService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return MaintenanceWindowService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
createMaintenanceWindow = async ({ teamId, body }) => {
|
||||
const monitorIds = body.monitors;
|
||||
const monitors = await this.db.getMonitorsByIds(monitorIds);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createMonitorsBodyValidation } from "../../validation/joi.js";
|
||||
|
||||
const SERVICE_NAME = "MonitorService";
|
||||
class MonitorService {
|
||||
SERVICE_NAME = SERVICE_NAME;
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor({ db, settingsService, jobQueue, stringService, emailService, papaparse, logger, errorService }) {
|
||||
this.db = db;
|
||||
@@ -15,6 +15,10 @@ class MonitorService {
|
||||
this.errorService = errorService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return MonitorService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
verifyTeamAccess = async ({ teamId, monitorId }) => {
|
||||
const monitor = await this.db.getMonitorById(monitorId);
|
||||
if (!monitor?.teamId?.equals(teamId)) {
|
||||
|
||||
@@ -2,6 +2,7 @@ const SERVICE_NAME = "userService";
|
||||
|
||||
class UserService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor({ db, emailService, settingsService, logger, stringService, jwt, errorService }) {
|
||||
this.db = db;
|
||||
this.emailService = emailService;
|
||||
@@ -12,6 +13,10 @@ class UserService {
|
||||
this.errorService = errorService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return UserService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
issueToken = (payload, appSettings) => {
|
||||
const tokenTTL = appSettings?.jwtTTL ?? "2h";
|
||||
const tokenSecret = appSettings?.jwtSecret;
|
||||
|
||||
@@ -2,12 +2,17 @@ const SERVICE_NAME = "RedisService";
|
||||
|
||||
class RedisService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor({ Redis, logger }) {
|
||||
this.Redis = Redis;
|
||||
this.connections = new Set();
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return RedisService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getNewConnection(options = {}) {
|
||||
const connection = new this.Redis(process.env.REDIS_URL, {
|
||||
retryStrategy: (times) => {
|
||||
|
||||
@@ -23,6 +23,10 @@ class JobQueue {
|
||||
this.workers = [];
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return JobQueue.SERVICE_NAME;
|
||||
}
|
||||
|
||||
static async create({ db, jobQueueHelper, logger, stringService }) {
|
||||
const instance = new JobQueue({ db, jobQueueHelper, logger, stringService });
|
||||
await instance.init();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const SERVICE_NAME = "JobQueueHelper";
|
||||
|
||||
class JobQueueHelper {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor({ redisService, Queue, Worker, logger, db, networkService, statusService, notificationService }) {
|
||||
this.db = db;
|
||||
this.redisService = redisService;
|
||||
@@ -12,6 +14,10 @@ class JobQueueHelper {
|
||||
this.notificationService = notificationService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return JobQueueHelper.SERVICE_NAME;
|
||||
}
|
||||
|
||||
createQueue(queueName) {
|
||||
const connection = this.redisService.getNewConnection();
|
||||
const q = new this.Queue(queueName, {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Pulse } from "@pulsecron/pulse";
|
||||
|
||||
const SERVICE_NAME = "PulseQueue";
|
||||
const SERVICE_NAME = "JobQueue";
|
||||
class PulseQueue {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
@@ -11,6 +11,10 @@ class PulseQueue {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return PulseQueue.SERVICE_NAME;
|
||||
}
|
||||
|
||||
static async create({ appSettings, db, pulseQueueHelper, logger }) {
|
||||
const instance = new PulseQueue({ appSettings, db, pulseQueueHelper, logger });
|
||||
await instance.init();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const SERVICE_NAME = "PulseQueueHelper";
|
||||
|
||||
class PulseQueueHelper {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor({ db, logger, networkService, statusService, notificationService }) {
|
||||
this.db = db;
|
||||
this.logger = logger;
|
||||
@@ -9,6 +11,10 @@ class PulseQueueHelper {
|
||||
this.notificationService = notificationService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return PulseQueueHelper.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getMonitorJob = () => {
|
||||
return async (job) => {
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Scheduler from "super-simple-scheduler";
|
||||
const SERVICE_NAME = "SuperSimpleQueue";
|
||||
const SERVICE_NAME = "JobQueue";
|
||||
|
||||
class SuperSimpleQueue {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
@@ -11,6 +11,10 @@ class SuperSimpleQueue {
|
||||
this.helper = helper;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return SuperSimpleQueue.SERVICE_NAME;
|
||||
}
|
||||
|
||||
static async create({ appSettings, db, logger, helper }) {
|
||||
const instance = new SuperSimpleQueue({ appSettings, db, logger, helper });
|
||||
await instance.init();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const SERVICE_NAME = "SuperSimpleQueueHelper";
|
||||
const SERVICE_NAME = "JobQueueHelper";
|
||||
|
||||
class SuperSimpleQueueHelper {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor({ db, logger, networkService, statusService, notificationService }) {
|
||||
this.db = db;
|
||||
this.logger = logger;
|
||||
@@ -9,6 +10,10 @@ class SuperSimpleQueueHelper {
|
||||
this.notificationService = notificationService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return SuperSimpleQueueHelper.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getMonitorJob = () => {
|
||||
return async (monitor) => {
|
||||
try {
|
||||
|
||||
@@ -10,6 +10,7 @@ const TYPE_MAP = {
|
||||
};
|
||||
|
||||
class BufferService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor({ db, logger }) {
|
||||
this.db = db;
|
||||
this.logger = logger;
|
||||
@@ -32,7 +33,10 @@ class BufferService {
|
||||
method: "constructor",
|
||||
});
|
||||
}
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
get serviceName() {
|
||||
return BufferService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
addToBuffer({ check, type }) {
|
||||
try {
|
||||
|
||||
@@ -33,6 +33,10 @@ class EmailService {
|
||||
this.init();
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return EmailService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
init = async () => {
|
||||
/**
|
||||
* Loads an email template from the filesystem.
|
||||
@@ -42,7 +46,7 @@ class EmailService {
|
||||
*/
|
||||
this.loadTemplate = (templateName) => {
|
||||
try {
|
||||
const templatePath = this.path.join(__dirname, `../templates/${templateName}.mjml`);
|
||||
const templatePath = this.path.join(__dirname, `../../templates/${templateName}.mjml`);
|
||||
const templateContent = this.fs.readFileSync(templatePath, "utf8");
|
||||
return this.compile(templateContent);
|
||||
} catch (error) {
|
||||
|
||||
@@ -55,9 +55,12 @@ class BadRequestError extends AppError {
|
||||
const SERVICE_NAME = "ErrorService";
|
||||
class ErrorService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor() {}
|
||||
|
||||
get serviceName() {
|
||||
return ErrorService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
createError = (message, status = 500, service = null, method = null, details = null) => {
|
||||
return new AppError(message, status, service, method, details);
|
||||
};
|
||||
|
||||
@@ -36,6 +36,10 @@ class NetworkService {
|
||||
this.settingsService = settingsService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return NetworkService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Times the execution of an asynchronous operation.
|
||||
*
|
||||
|
||||
@@ -12,6 +12,10 @@ class NotificationService {
|
||||
this.notificationUtils = notificationUtils;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return NotificationService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
sendNotification = async ({ notification, subject, content, html }) => {
|
||||
const { type, address } = notification;
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
const SERVICE_NAME = "NotificationUtils";
|
||||
|
||||
class NotificationUtils {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor({ stringService, emailService }) {
|
||||
this.stringService = stringService;
|
||||
this.emailService = emailService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return NotificationUtils.SERVICE_NAME;
|
||||
}
|
||||
|
||||
buildTestEmail = async () => {
|
||||
const context = { testName: "Monitoring System" };
|
||||
const html = await this.emailService.buildEmail("testEmailTemplate", context);
|
||||
|
||||
@@ -4,6 +4,7 @@ const SERVICE_NAME = "StatusService";
|
||||
|
||||
class StatusService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
/**
|
||||
* Creates an instance of StatusService.
|
||||
*
|
||||
@@ -14,7 +15,10 @@ class StatusService {
|
||||
this.db = db;
|
||||
this.logger = logger;
|
||||
this.buffer = buffer;
|
||||
this.SERVICE_NAME = SERVICE_NAME;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return StatusService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
async updateRunningStats({ monitor, networkResponse }) {
|
||||
|
||||
@@ -6,6 +6,10 @@ class ServiceRegistry {
|
||||
this.services = {};
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return ServiceRegistry.SERVICE_NAME;
|
||||
}
|
||||
|
||||
register(name, service) {
|
||||
logger.info({
|
||||
message: `Registering service ${name}`,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const SERVICE_NAME = "SettingsService";
|
||||
|
||||
const envConfig = {
|
||||
nodeEnv: process.env.NODE_ENV,
|
||||
logLevel: process.env.LOG_LEVEL,
|
||||
@@ -23,7 +24,8 @@ const envConfig = {
|
||||
* This service is responsible for loading and managing the application settings.
|
||||
*/
|
||||
class SettingsService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
static SERVICE_NAME = "SettingsService";
|
||||
|
||||
/**
|
||||
* Constructs a new SettingsService
|
||||
* @constructor
|
||||
@@ -32,6 +34,11 @@ class SettingsService {
|
||||
this.AppSettings = AppSettings;
|
||||
this.settings = { ...envConfig };
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return SettingsService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load settings from env settings
|
||||
* @returns {Object>} The settings.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const SERVICE_NAME = "StringService";
|
||||
|
||||
class StringService {
|
||||
static SERVICE_NAME = "StringService";
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor(translationService) {
|
||||
if (StringService.instance) {
|
||||
@@ -11,6 +13,10 @@ class StringService {
|
||||
StringService.instance = this;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return StringService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
setLanguage(language) {
|
||||
this._language = language;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import path from "path";
|
||||
|
||||
class TranslationService {
|
||||
static SERVICE_NAME = "TranslationService";
|
||||
|
||||
constructor(logger) {
|
||||
this.logger = logger;
|
||||
this.translations = {};
|
||||
@@ -11,6 +10,10 @@ class TranslationService {
|
||||
this.localesDir = path.join(process.cwd(), "locales");
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return TranslationService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
setLanguage(language) {
|
||||
this._language = language;
|
||||
}
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
const SERVICE_NAME = "userService";
|
||||
import { createAuthError, createError } from "../utils/errorUtils.js";
|
||||
|
||||
class UserService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor({ db, emailService, settingsService, logger, stringService, jwt }) {
|
||||
this.db = db;
|
||||
this.emailService = emailService;
|
||||
this.settingsService = settingsService;
|
||||
this.logger = logger;
|
||||
this.stringService = stringService;
|
||||
this.jwt = jwt;
|
||||
}
|
||||
|
||||
issueToken = (payload, appSettings) => {
|
||||
const tokenTTL = appSettings?.jwtTTL ?? "2h";
|
||||
const tokenSecret = appSettings?.jwtSecret;
|
||||
const payloadData = payload;
|
||||
return this.jwt.sign(payloadData, tokenSecret, { expiresIn: tokenTTL });
|
||||
};
|
||||
|
||||
registerUser = async (user, file) => {
|
||||
// Create a new user
|
||||
// If superAdmin exists, a token should be attached to all further register requests
|
||||
const superAdminExists = await this.db.checkSuperadmin();
|
||||
if (superAdminExists) {
|
||||
const invitedUser = await this.db.getInviteTokenAndDelete(user.inviteToken);
|
||||
user.role = invitedUser.role;
|
||||
user.teamId = invitedUser.teamId;
|
||||
} else {
|
||||
// This is the first account, create JWT secret to use if one is not supplied by env
|
||||
const jwtSecret = crypto.randomBytes(64).toString("hex");
|
||||
await this.db.updateAppSettings({ jwtSecret });
|
||||
}
|
||||
|
||||
const newUser = await this.db.insertUser({ ...user }, file);
|
||||
|
||||
this.logger.debug({
|
||||
message: "New user created",
|
||||
service: SERVICE_NAME,
|
||||
method: "registerUser",
|
||||
details: newUser._id,
|
||||
});
|
||||
|
||||
const userForToken = { ...newUser._doc };
|
||||
delete userForToken.profileImage;
|
||||
delete userForToken.avatarImage;
|
||||
|
||||
const appSettings = await this.settingsService.getSettings();
|
||||
|
||||
const token = this.issueToken(userForToken, appSettings);
|
||||
|
||||
try {
|
||||
const html = await this.emailService.buildEmail("welcomeEmailTemplate", {
|
||||
name: newUser.firstName,
|
||||
});
|
||||
this.emailService.sendEmail(newUser.email, "Welcome to Uptime Monitor", html).catch((error) => {
|
||||
this.logger.warn({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "registerUser",
|
||||
stack: error.stack,
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.warn({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "registerUser",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
|
||||
return { user: newUser, token };
|
||||
};
|
||||
|
||||
loginUser = async (email, password) => {
|
||||
// Check if user exists
|
||||
const user = await this.db.getUserByEmail(email);
|
||||
// Compare password
|
||||
const match = await user.comparePassword(password);
|
||||
if (match !== true) {
|
||||
throw createAuthError(this.stringService.authIncorrectPassword);
|
||||
}
|
||||
|
||||
// Remove password from user object. Should this be abstracted to DB layer?
|
||||
const userWithoutPassword = { ...user._doc };
|
||||
delete userWithoutPassword.password;
|
||||
delete userWithoutPassword.avatarImage;
|
||||
|
||||
// Happy path, return token
|
||||
const appSettings = await this.settingsService.getSettings();
|
||||
const token = this.issueToken(userWithoutPassword, appSettings);
|
||||
// reset avatar image
|
||||
userWithoutPassword.avatarImage = user.avatarImage;
|
||||
return { user: userWithoutPassword, token };
|
||||
};
|
||||
|
||||
editUser = async (updates, file, currentUser) => {
|
||||
// Change Password check
|
||||
if (updates?.password && updates?.newPassword) {
|
||||
// Get user's email
|
||||
// Add user email to body for DB operation
|
||||
updates.email = currentUser.email;
|
||||
// Get user
|
||||
const user = await this.db.getUserByEmail(currentUser.email);
|
||||
// Compare passwords
|
||||
const match = await user.comparePassword(updates?.password);
|
||||
// If not a match, throw a 403
|
||||
// 403 instead of 401 to avoid triggering axios interceptor
|
||||
if (!match) {
|
||||
throw createError(this.stringService.authIncorrectPassword, 403);
|
||||
}
|
||||
// If a match, update the password
|
||||
updates.password = updates.newPassword;
|
||||
}
|
||||
|
||||
const updatedUser = await this.db.updateUser({ userId: currentUser?._id, user: updates, file: file });
|
||||
return updatedUser;
|
||||
};
|
||||
|
||||
checkSuperadminExists = async () => {
|
||||
const superAdminExists = await this.db.checkSuperadmin();
|
||||
return superAdminExists;
|
||||
};
|
||||
|
||||
requestRecovery = async (email) => {
|
||||
const user = await this.db.getUserByEmail(email);
|
||||
const recoveryToken = await this.db.requestRecoveryToken(email);
|
||||
const name = user.firstName;
|
||||
const { clientHost } = this.settingsService.getSettings();
|
||||
const url = `${clientHost}/set-new-password/${recoveryToken.token}`;
|
||||
|
||||
const html = await this.emailService.buildEmail("passwordResetTemplate", {
|
||||
name,
|
||||
email,
|
||||
url,
|
||||
});
|
||||
const msgId = await this.emailService.sendEmail(email, "Checkmate Password Reset", html);
|
||||
return msgId;
|
||||
};
|
||||
|
||||
validateRecovery = async (recoveryToken) => {
|
||||
await this.db.validateRecoveryToken(recoveryToken);
|
||||
};
|
||||
|
||||
resetPassword = async (password, recoveryToken) => {
|
||||
const user = await this.db.resetPassword(password, recoveryToken);
|
||||
const appSettings = await this.settingsService.getSettings();
|
||||
const token = this.issueToken(user._doc, appSettings);
|
||||
return { user, token };
|
||||
};
|
||||
|
||||
deleteUser = async (user) => {
|
||||
const email = user?.email;
|
||||
if (!email) {
|
||||
throw new Error("No email in request");
|
||||
}
|
||||
|
||||
const teamId = user?.teamId;
|
||||
const userId = user?._id;
|
||||
|
||||
if (!teamId) {
|
||||
throw new Error("No team ID in request");
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
throw new Error("No user ID in request");
|
||||
}
|
||||
|
||||
const roles = user?.role;
|
||||
if (roles.includes("demo")) {
|
||||
throw new Error("Demo user cannot be deleted");
|
||||
}
|
||||
|
||||
// 1. Find all the monitors associated with the team ID if superadmin
|
||||
const result = await this.db.getMonitorsByTeamId({
|
||||
teamId: teamId,
|
||||
});
|
||||
|
||||
if (roles.includes("superadmin")) {
|
||||
// 2. Remove all jobs, delete checks and alerts
|
||||
result?.monitors.length > 0 &&
|
||||
(await Promise.all(
|
||||
result.monitors.map(async (monitor) => {
|
||||
await this.jobQueue.deleteJob(monitor);
|
||||
})
|
||||
));
|
||||
}
|
||||
// 6. Delete the user by id
|
||||
await this.db.deleteUser(userId);
|
||||
};
|
||||
|
||||
getAllUsers = async () => {
|
||||
const users = await this.db.getAllUsers();
|
||||
return users;
|
||||
};
|
||||
|
||||
getUserById = async (roles, userId) => {
|
||||
const user = await this.db.getUserById(roles, userId);
|
||||
return user;
|
||||
};
|
||||
|
||||
editUserById = async (userId, user) => {
|
||||
await this.db.editUserById(userId, user);
|
||||
};
|
||||
}
|
||||
export default UserService;
|
||||
@@ -0,0 +1,34 @@
|
||||
import logger from "./utils/logger.js";
|
||||
|
||||
export const initShutdownListener = (server, services) => {
|
||||
const SERVICE_NAME = "Server";
|
||||
|
||||
let isShuttingDown = false;
|
||||
|
||||
const shutdown = async () => {
|
||||
if (isShuttingDown) {
|
||||
return;
|
||||
}
|
||||
isShuttingDown = true;
|
||||
logger.info({ message: "Attempting graceful shutdown" });
|
||||
|
||||
try {
|
||||
server.close();
|
||||
await services.jobQueue.shutdown();
|
||||
await services.db.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,
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
process.on("SIGUSR2", shutdown);
|
||||
process.on("SIGINT", shutdown);
|
||||
process.on("SIGTERM", shutdown);
|
||||
};
|
||||
@@ -61,6 +61,11 @@ class Logger {
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return Logger.SERVICE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an informational message.
|
||||
* @param {Object} config - The configuration object.
|
||||
|
||||
Reference in new issue
Block a user