Merge pull request #2688 from bluewave-labs/feat/server-refactor
feat: server refactor
This commit is contained in:
127 files changed
+657
-666
No files matched your search
@@ -10,4 +10,4 @@ COPY ./server/ ./
|
||||
|
||||
EXPOSE 52345
|
||||
|
||||
CMD ["node", "index.js"]
|
||||
CMD ["node", "src/index.js"]
|
||||
@@ -22,4 +22,4 @@ RUN chmod +x ./scripts/inject-vars.sh
|
||||
|
||||
EXPOSE 52345
|
||||
|
||||
CMD ./scripts/inject-vars.sh && node ./index.js
|
||||
CMD ./scripts/inject-vars.sh && node ./src/index.js
|
||||
@@ -22,4 +22,4 @@ RUN chmod +x ./scripts/inject-vars.sh
|
||||
|
||||
EXPOSE 52345
|
||||
|
||||
CMD ./scripts/inject-vars.sh && node ./index.js
|
||||
CMD ./scripts/inject-vars.sh && node ./src/index.js
|
||||
Vendored
+2
-2
@@ -12,10 +12,10 @@ declare -A services=(
|
||||
)
|
||||
|
||||
for service in "${!services[@]}"; do
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
docker build \
|
||||
-f "${services[$service]}" \
|
||||
-t "$service" \
|
||||
.
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error building $service image. Exiting..."
|
||||
|
||||
Vendored
+1
-1
@@ -10,4 +10,4 @@ COPY ./server/ ./
|
||||
|
||||
EXPOSE 52345
|
||||
|
||||
CMD ["node", "index.js"]
|
||||
CMD ["node", "src/index.js"]
|
||||
@@ -12,4 +12,4 @@ COPY ./server ./
|
||||
|
||||
EXPOSE 52345
|
||||
|
||||
CMD ["node", "index.js"]
|
||||
CMD ["node", "src/index.js"]
|
||||
@@ -12,4 +12,4 @@ COPY ./server ./
|
||||
|
||||
EXPOSE 52345
|
||||
|
||||
CMD ["node", "index.js"]
|
||||
CMD ["node", "src/index.js"]
|
||||
Executable → Regular
+24
-427
@@ -1,450 +1,47 @@
|
||||
import { initializeServices } from "./src/config/services.js";
|
||||
import { initializeControllers } from "./src/config/controllers.js";
|
||||
import { createApp } from "./src/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";
|
||||
import SettingsService from "./src/service/system/settingsService.js";
|
||||
import AppSettings from "./src/db/models/AppSettings.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) => {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"ignore": ["locales/*", "*.log", "node_modules/*"],
|
||||
"watch": ["*.js", "*.json"],
|
||||
"ignore": ["src/locales/*", "*.log", "node_modules/*"],
|
||||
"watch": ["src/**/*.js", "*.json"],
|
||||
"ext": "js,json"
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "c8 mocha",
|
||||
"dev": "nodemon index.js",
|
||||
"dev": "nodemon src/index.js",
|
||||
"lint": "eslint .",
|
||||
"lint-fix": "eslint --fix .",
|
||||
"format": "prettier --write .",
|
||||
|
||||
@@ -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,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.errorService, services.logger, 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,155 @@
|
||||
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";
|
||||
import crypto from "crypto";
|
||||
|
||||
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({
|
||||
crypto,
|
||||
db,
|
||||
emailService,
|
||||
settingsService,
|
||||
logger,
|
||||
stringService,
|
||||
jwt,
|
||||
errorService,
|
||||
jobQueue: superSimpleQueue,
|
||||
});
|
||||
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;
|
||||
};
|
||||
+6
@@ -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.
|
||||
*
|
||||
File renamed without changes.
+4
@@ -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();
|
||||
+5
@@ -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)) {
|
||||
+5
@@ -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
@@ -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;
|
||||
+5
@@ -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);
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,5 @@
|
||||
import mongoose from "mongoose";
|
||||
import { BaseCheckSchema } from "./Check.js";
|
||||
import logger from "../../utils/logger.js";
|
||||
import { time } from "console";
|
||||
const AuditSchema = mongoose.Schema({
|
||||
id: { type: String, required: true },
|
||||
title: { type: String, required: true },
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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";
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Executable
+55
@@ -0,0 +1,55 @@
|
||||
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 SettingsService from "./service/system/settingsService.js";
|
||||
import AppSettings from "./db/models/AppSettings.js";
|
||||
|
||||
const SERVICE_NAME = "Server";
|
||||
|
||||
const startApp = async () => {
|
||||
// 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();
|
||||
|
||||
// Initialize services
|
||||
const services = await initializeServices(appSettings, settingsService);
|
||||
|
||||
// Initialize controllers
|
||||
const controllers = initializeControllers(services);
|
||||
|
||||
const app = createApp({
|
||||
services,
|
||||
controllers,
|
||||
appSettings,
|
||||
frontendPath,
|
||||
openApiSpec,
|
||||
});
|
||||
|
||||
const port = appSettings.port || 52345;
|
||||
const server = app.listen(port, () => {
|
||||
logger.info({ message: `Server started on port:${port}` });
|
||||
});
|
||||
|
||||
initShutdownListener(server, services);
|
||||
};
|
||||
|
||||
startApp().catch((error) => {
|
||||
logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "startApp",
|
||||
stack: error.stack,
|
||||
});
|
||||
process.exit(1);
|
||||
});
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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
@@ -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
|
||||
+4
@@ -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);
|
||||
+5
@@ -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);
|
||||
+5
-1
@@ -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,7 +2,8 @@ const SERVICE_NAME = "userService";
|
||||
|
||||
class UserService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor({ db, emailService, settingsService, logger, stringService, jwt, errorService }) {
|
||||
|
||||
constructor({ crypto, db, emailService, settingsService, logger, stringService, jwt, errorService, jobQueue }) {
|
||||
this.db = db;
|
||||
this.emailService = emailService;
|
||||
this.settingsService = settingsService;
|
||||
@@ -10,6 +11,12 @@ class UserService {
|
||||
this.stringService = stringService;
|
||||
this.jwt = jwt;
|
||||
this.errorService = errorService;
|
||||
this.jobQueue = jobQueue;
|
||||
this.crypto = crypto;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return UserService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
issueToken = (payload, appSettings) => {
|
||||
@@ -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) => {
|
||||
+4
@@ -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();
|
||||
+6
@@ -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, {
|
||||
+5
-1
@@ -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();
|
||||
+6
@@ -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 {
|
||||
+5
-1
@@ -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();
|
||||
+6
-1
@@ -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 {
|
||||
+5
-1
@@ -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 {
|
||||
+5
-1
@@ -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) {
|
||||
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user