From 753a904c947cf99f0664ca64c64a08490558af40 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 30 Jul 2025 10:20:33 -0700 Subject: [PATCH] refacotr status page module into class --- server/src/config/services.js | 7 +- .../src/controllers/statusPageController.js | 10 +- server/src/db/mongo/MongoDB.js | 9 +- .../src/db/mongo/modules/statusPageModule.js | 541 +++++++++--------- server/src/service/business/monitorService.js | 2 +- 5 files changed, 282 insertions(+), 287 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index ae3a4d27b..f5c9b3066 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -33,15 +33,19 @@ import jwt from "jsonwebtoken"; import crypto from "crypto"; // DB Modules +import { NormalizeData } from "../utils/dataUtils.js"; + import Check from "../db/models/Check.js"; import HardwareCheck from "../db/models/HardwareCheck.js"; import PageSpeedCheck from "../db/models/PageSpeedCheck.js"; import Monitor from "../db/models/Monitor.js"; import User from "../db/models/User.js"; import InviteToken from "../db/models/InviteToken.js"; +import StatusPage from "../db/models/StatusPage.js"; import InviteModule from "../db/mongo/modules/inviteModule.js"; import CheckModule from "../db/mongo/modules/checkModule.js"; +import StatusPageModule from "../db/mongo/modules/statusPageModule.js"; export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); @@ -55,7 +59,8 @@ export const initializeServices = async ({ logger, envSettings, settingsService // Create DB const checkModule = new CheckModule({ logger, Check, HardwareCheck, PageSpeedCheck, Monitor, User }); const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); - const db = new MongoDB({ logger, envSettings, checkModule, inviteModule }); + const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService }); + const db = new MongoDB({ logger, envSettings, checkModule, inviteModule, statusPageModule }); await db.connect(); const networkService = new NetworkService(axios, ping, logger, http, Docker, net, stringService, settingsService); diff --git a/server/src/controllers/statusPageController.js b/server/src/controllers/statusPageController.js index 85211c7ff..1cbcb49a2 100755 --- a/server/src/controllers/statusPageController.js +++ b/server/src/controllers/statusPageController.js @@ -19,7 +19,7 @@ class StatusPageController extends BaseController { await imageValidation.validateAsync(req.file); const { _id, teamId } = req.user; - const statusPage = await this.db.createStatusPage({ + const statusPage = await this.db.statusPageModule.createStatusPage({ statusPageData: req.body, image: req.file, userId: _id, @@ -39,7 +39,7 @@ class StatusPageController extends BaseController { await createStatusPageBodyValidation.validateAsync(req.body); await imageValidation.validateAsync(req.file); - const statusPage = await this.db.updateStatusPage(req.body, req.file); + const statusPage = await this.db.statusPageModule.updateStatusPage(req.body, req.file); if (statusPage === null) { throw this.errorService.createNotFoundError(this.stringService.statusPageNotFound); } @@ -54,7 +54,7 @@ class StatusPageController extends BaseController { getStatusPage = this.asyncHandler( async (req, res) => { - const statusPage = await this.db.getStatusPage(); + const statusPage = await this.db.statusPageModule.getStatusPage(); return res.success({ msg: this.stringService.statusPageByUrl, data: statusPage, @@ -69,7 +69,7 @@ class StatusPageController extends BaseController { await getStatusPageParamValidation.validateAsync(req.params); await getStatusPageQueryValidation.validateAsync(req.query); - const statusPage = await this.db.getStatusPageByUrl(req.params.url, req.query.type); + const statusPage = await this.db.statusPageModule.getStatusPageByUrl(req.params.url); return res.success({ msg: this.stringService.statusPageByUrl, data: statusPage, @@ -82,7 +82,7 @@ class StatusPageController extends BaseController { getStatusPagesByTeamId = this.asyncHandler( async (req, res) => { const teamId = req.user.teamId; - const statusPages = await this.db.getStatusPagesByTeamId(teamId); + const statusPages = await this.db.statusPageModule.getStatusPagesByTeamId(teamId); return res.success({ msg: this.stringService.statusPageByTeamId, diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index 1126b5ca6..9235892a8 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -44,11 +44,6 @@ import * as notificationModule from "./modules/notificationModule.js"; //**************************************** import * as settingsModule from "./modules/settingsModule.js"; -//**************************************** -// Status Page -//**************************************** -import * as statusPageModule from "./modules/statusPageModule.js"; - //**************************************** // Diagnostic //**************************************** @@ -57,7 +52,7 @@ import * as diagnosticModule from "./modules/diagnosticModule.js"; class MongoDB { static SERVICE_NAME = "MongoDB"; - constructor({ logger, envSettings, checkModule, inviteModule }) { + constructor({ logger, envSettings, checkModule, inviteModule, statusPageModule }) { this.logger = logger; this.envSettings = envSettings; Object.assign(this, userModule); @@ -70,7 +65,7 @@ class MongoDB { Object.assign(this, maintenanceWindowModule); Object.assign(this, notificationModule); Object.assign(this, settingsModule); - Object.assign(this, statusPageModule); + this.statusPageModule = statusPageModule; Object.assign(this, diagnosticModule); } diff --git a/server/src/db/mongo/modules/statusPageModule.js b/server/src/db/mongo/modules/statusPageModule.js index e814c20f5..10710d0f7 100755 --- a/server/src/db/mongo/modules/statusPageModule.js +++ b/server/src/db/mongo/modules/statusPageModule.js @@ -1,292 +1,287 @@ -import StatusPage from "../../models/StatusPage.js"; -import { NormalizeData } from "../../../utils/dataUtils.js"; -import ServiceRegistry from "../../../service/system/serviceRegistry.js"; -import StringService from "../../../service/system/stringService.js"; +// import StatusPage from "../../models/StatusPage.js"; +// import { NormalizeData } from "../../../utils/dataUtils.js"; +// import ServiceRegistry from "../../../service/system/serviceRegistry.js"; +// import StringService from "../../../service/system/stringService.js"; const SERVICE_NAME = "statusPageModule"; -const createStatusPage = async ({ statusPageData, image, userId, teamId }) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - const statusPage = new StatusPage({ - ...statusPageData, - userId, - teamId, - }); - if (image) { - statusPage.logo = { - data: image.buffer, - contentType: image.mimetype, - }; - } - await statusPage.save(); - return statusPage; - } catch (error) { - if (error?.code === 11000) { - // Handle duplicate URL errors - error.status = 400; - error.message = stringService.statusPageUrlNotUnique; - } - error.service = SERVICE_NAME; - error.method = "createStatusPage"; - throw error; +class StatusPageModule { + constructor({ StatusPage, NormalizeData, stringService }) { + this.StatusPage = StatusPage; + this.NormalizeData = NormalizeData; + this.stringService = stringService; } -}; -const updateStatusPage = async (statusPageData, image) => { - try { - if (image) { - statusPageData.logo = { - data: image.buffer, - contentType: image.mimetype, - }; - } else { - statusPageData.logo = null; - } - - if (statusPageData.deleteSubmonitors === "true") { - statusPageData.subMonitors = []; - } - const statusPage = await StatusPage.findOneAndUpdate({ url: statusPageData.url }, statusPageData, { - new: true, - }); - - return statusPage; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateStatusPage"; - throw error; - } -}; - -const getStatusPageByUrl = async (url, type) => { - // TODO This is deprecated, can remove and have controller call getStatusPage - try { - return getStatusPage(url); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getStatusPageByUrl"; - throw error; - } -}; - -const getStatusPagesByTeamId = async (teamId) => { - try { - const statusPages = await StatusPage.find({ teamId }); - return statusPages; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getStatusPagesByTeamId"; - throw error; - } -}; - -const getStatusPage = async (url) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - const preliminaryStatusPage = await StatusPage.findOne({ url }); - if (!preliminaryStatusPage) { - const error = new Error(stringService.statusPageNotFound); - error.status = 404; + createStatusPage = async ({ statusPageData, image, userId, teamId }) => { + try { + const statusPage = new this.StatusPage({ + ...statusPageData, + userId, + teamId, + }); + if (image) { + statusPage.logo = { + data: image.buffer, + contentType: image.mimetype, + }; + } + await statusPage.save(); + return statusPage; + } catch (error) { + if (error?.code === 11000) { + // Handle duplicate URL errors + error.status = 400; + error.message = this.stringService.statusPageUrlNotUnique; + } + error.service = SERVICE_NAME; + error.method = "createStatusPage"; throw error; } + }; - if (!preliminaryStatusPage.monitors || preliminaryStatusPage.monitors.length === 0) { - const { _id, color, companyName, isPublished, logo, originalMonitors, showCharts, showUptimePercentage, timezone, showAdminLoginLink, url } = - preliminaryStatusPage; - return { - statusPage: { - _id, - color, - companyName, - isPublished, - logo, - originalMonitors, - showCharts, - showUptimePercentage, - timezone, - showAdminLoginLink, - url, - }, - monitors: [], - }; + updateStatusPage = async (statusPageData, image) => { + try { + if (image) { + statusPageData.logo = { + data: image.buffer, + contentType: image.mimetype, + }; + } else { + statusPageData.logo = null; + } + + if (statusPageData.deleteSubmonitors === "true") { + statusPageData.subMonitors = []; + } + const statusPage = await this.StatusPage.findOneAndUpdate({ url: statusPageData.url }, statusPageData, { + new: true, + }); + + return statusPage; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateStatusPage"; + throw error; } + }; - const statusPageQuery = await StatusPage.aggregate([ - { $match: { url: url } }, - { - $set: { - originalMonitors: "$monitors", - }, - }, - { - $lookup: { - from: "monitors", - localField: "monitors", - foreignField: "_id", - as: "monitors", - }, - }, - { - $unwind: { - path: "$monitors", - preserveNullAndEmptyArrays: true, - }, - }, - { - $lookup: { - from: "maintenancewindows", - let: { monitorId: "$monitors._id" }, - pipeline: [{ $match: { $expr: { $eq: ["$monitorId", "$$monitorId"] } } }], - as: "monitors.maintenanceWindows", - }, - }, - { - $lookup: { - from: "checks", - let: { monitorId: "$monitors._id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - }, - }, - { $sort: { createdAt: -1 } }, - { $limit: 25 }, - ], - as: "monitors.checks", - }, - }, - { - $addFields: { - "monitors.orderIndex": { - $indexOfArray: ["$originalMonitors", "$monitors._id"], - }, - "monitors.isMaintenance": { - $reduce: { - input: "$monitors.maintenanceWindows", - initialValue: false, - in: { - $or: [ - "$$value", - { - $and: [{ $eq: ["$$this.active", true] }, { $lte: ["$$this.start", "$$NOW"] }, { $gte: ["$$this.end", "$$NOW"] }], - }, - ], - }, - }, - }, - }, - }, - { $match: { "monitors.orderIndex": { $ne: -1 } } }, - { $sort: { "monitors.orderIndex": 1 } }, + getStatusPageByUrl = async (url) => { + // TODO This is deprecated, can remove and have controller call getStatusPage + try { + return this.getStatusPage(url); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getStatusPageByUrl"; + throw error; + } + }; - { - $group: { - _id: "$_id", - statusPage: { $first: "$$ROOT" }, - monitors: { $push: "$monitors" }, - }, - }, - { - $project: { + getStatusPagesByTeamId = async (teamId) => { + try { + const statusPages = await this.StatusPage.find({ teamId }); + return statusPages; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getStatusPagesByTeamId"; + throw error; + } + }; + + getStatusPage = async (url) => { + try { + const preliminaryStatusPage = await this.StatusPage.findOne({ url }); + if (!preliminaryStatusPage) { + const error = new Error(this.stringService.statusPageNotFound); + error.status = 404; + throw error; + } + + if (!preliminaryStatusPage.monitors || preliminaryStatusPage.monitors.length === 0) { + const { _id, color, companyName, isPublished, logo, originalMonitors, showCharts, showUptimePercentage, timezone, showAdminLoginLink, url } = + preliminaryStatusPage; + return { statusPage: { - _id: 1, - color: 1, - companyName: 1, - isPublished: 1, - logo: 1, - originalMonitors: 1, - showCharts: 1, - showUptimePercentage: 1, - timezone: 1, - showAdminLoginLink: 1, - url: 1, + _id, + color, + companyName, + isPublished, + logo, + originalMonitors, + showCharts, + showUptimePercentage, + timezone, + showAdminLoginLink, + url, }, - monitors: { - _id: 1, - userId: 1, - teamId: 1, - name: 1, - description: 1, - status: 1, - type: 1, - ignoreTlsErrors: 1, - jsonPath: 1, - expectedValue: 1, - matchMethod: 1, - url: 1, - port: 1, - isActive: 1, - interval: 1, - uptimePercentage: 1, - notifications: 1, - secret: 1, - thresholds: 1, - alertThreshold: 1, - cpuAlertThreshold: 1, - memoryAlertThreshold: 1, - diskAlertThreshold: 1, - tempAlertThreshold: 1, - checks: 1, - isMaintenance: 1, - createdAt: 1, - updatedAt: 1, + monitors: [], + }; + } + + const statusPageQuery = await this.StatusPage.aggregate([ + { $match: { url: url } }, + { + $set: { + originalMonitors: "$monitors", }, }, - }, - ]); - if (!statusPageQuery.length) { - const error = new Error(stringService.statusPageNotFound); - error.status = 404; + { + $lookup: { + from: "monitors", + localField: "monitors", + foreignField: "_id", + as: "monitors", + }, + }, + { + $unwind: { + path: "$monitors", + preserveNullAndEmptyArrays: true, + }, + }, + { + $lookup: { + from: "maintenancewindows", + let: { monitorId: "$monitors._id" }, + pipeline: [{ $match: { $expr: { $eq: ["$monitorId", "$$monitorId"] } } }], + as: "monitors.maintenanceWindows", + }, + }, + { + $lookup: { + from: "checks", + let: { monitorId: "$monitors._id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + { $limit: 25 }, + ], + as: "monitors.checks", + }, + }, + { + $addFields: { + "monitors.orderIndex": { + $indexOfArray: ["$originalMonitors", "$monitors._id"], + }, + "monitors.isMaintenance": { + $reduce: { + input: "$monitors.maintenanceWindows", + initialValue: false, + in: { + $or: [ + "$$value", + { + $and: [{ $eq: ["$$this.active", true] }, { $lte: ["$$this.start", "$$NOW"] }, { $gte: ["$$this.end", "$$NOW"] }], + }, + ], + }, + }, + }, + }, + }, + { $match: { "monitors.orderIndex": { $ne: -1 } } }, + { $sort: { "monitors.orderIndex": 1 } }, + + { + $group: { + _id: "$_id", + statusPage: { $first: "$$ROOT" }, + monitors: { $push: "$monitors" }, + }, + }, + { + $project: { + statusPage: { + _id: 1, + color: 1, + companyName: 1, + isPublished: 1, + logo: 1, + originalMonitors: 1, + showCharts: 1, + showUptimePercentage: 1, + timezone: 1, + showAdminLoginLink: 1, + url: 1, + }, + monitors: { + _id: 1, + userId: 1, + teamId: 1, + name: 1, + description: 1, + status: 1, + type: 1, + ignoreTlsErrors: 1, + jsonPath: 1, + expectedValue: 1, + matchMethod: 1, + url: 1, + port: 1, + isActive: 1, + interval: 1, + uptimePercentage: 1, + notifications: 1, + secret: 1, + thresholds: 1, + alertThreshold: 1, + cpuAlertThreshold: 1, + memoryAlertThreshold: 1, + diskAlertThreshold: 1, + tempAlertThreshold: 1, + checks: 1, + isMaintenance: 1, + createdAt: 1, + updatedAt: 1, + }, + }, + }, + ]); + if (!statusPageQuery.length) { + const error = new Error(this.stringService.statusPageNotFound); + error.status = 404; + throw error; + } + + const { statusPage, monitors } = statusPageQuery[0]; + + const normalizedMonitors = monitors.map((monitor) => { + return { + ...monitor, + checks: this.NormalizeData(monitor.checks, 10, 100), + }; + }); + + return { statusPage, monitors: normalizedMonitors }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getStatusPageByUrl"; throw error; } + }; - const { statusPage, monitors } = statusPageQuery[0]; + deleteStatusPage = async (url) => { + try { + await this.StatusPage.deleteOne({ url }); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteStatusPage"; + throw error; + } + }; + deleteStatusPagesByMonitorId = async (monitorId) => { + try { + await this.StatusPage.deleteMany({ monitors: { $in: [monitorId] } }); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteStatusPageByMonitorId"; + throw error; + } + }; +} - const normalizedMonitors = monitors.map((monitor) => { - return { - ...monitor, - checks: NormalizeData(monitor.checks, 10, 100), - }; - }); - - return { statusPage, monitors: normalizedMonitors }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getStatusPageByUrl"; - throw error; - } -}; - -const deleteStatusPage = async (url) => { - try { - await StatusPage.deleteOne({ url }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteStatusPage"; - throw error; - } -}; - -const deleteStatusPagesByMonitorId = async (monitorId) => { - try { - await StatusPage.deleteMany({ monitors: { $in: [monitorId] } }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteStatusPageByMonitorId"; - throw error; - } -}; - -export { - createStatusPage, - updateStatusPage, - getStatusPagesByTeamId, - getStatusPage, - getStatusPageByUrl, - deleteStatusPage, - deleteStatusPagesByMonitorId, -}; +export default StatusPageModule; diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.js index 94be6984c..f90c21470 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -143,7 +143,7 @@ class MonitorService { await this.verifyTeamAccess({ teamId, monitorId }); const monitor = await this.db.deleteMonitor({ teamId, monitorId }); await this.jobQueue.deleteJob(monitor); - await this.db.deleteStatusPagesByMonitorId(monitor._id); + await this.db.statusPageModule.deleteStatusPagesByMonitorId(monitor._id); return monitor; };