From e55d8618bd1ae5acc380abe9c9e93eb45fa98178 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 29 Jul 2025 11:51:06 -0700 Subject: [PATCH] convert checkModule to class for dependency injection --- server/src/config/services.js | 12 +- server/src/db/mongo/MongoDB.js | 4 +- server/src/db/mongo/modules/checkModule.js | 633 ++++++++---------- server/src/service/business/checkService.js | 16 +- server/src/service/business/monitorService.js | 2 +- .../service/infrastructure/bufferService.js | 2 +- 6 files changed, 300 insertions(+), 369 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index 73955a3b4..c2ecae200 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -32,6 +32,15 @@ import mjml2html from "mjml"; import jwt from "jsonwebtoken"; import crypto from "crypto"; +// DB Modules +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 CheckModule from "../db/mongo/modules/checkModule.js"; + export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); ServiceRegistry.instance = serviceRegistry; @@ -42,7 +51,8 @@ export const initializeServices = async ({ logger, envSettings, settingsService const stringService = new StringService(translationService); // Create DB - const db = new MongoDB({ logger, envSettings }); + const checkModule = new CheckModule({ logger, Check, HardwareCheck, PageSpeedCheck, Monitor, User }); + const db = new MongoDB({ logger, envSettings, checkModule }); await db.connect(); const networkService = new NetworkService(axios, ping, logger, http, Docker, net, stringService, settingsService); diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index d53fea928..ef7179d35 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -69,7 +69,7 @@ import * as diagnosticModule from "./modules/diagnosticModule.js"; class MongoDB { static SERVICE_NAME = "MongoDB"; - constructor({ logger, envSettings }) { + constructor({ logger, envSettings, checkModule }) { this.logger = logger; this.envSettings = envSettings; Object.assign(this, userModule); @@ -78,7 +78,7 @@ class MongoDB { Object.assign(this, monitorModule); Object.assign(this, pageSpeedCheckModule); Object.assign(this, hardwareCheckModule); - Object.assign(this, checkModule); + this.checkModule = checkModule; Object.assign(this, maintenanceWindowModule); Object.assign(this, notificationModule); Object.assign(this, settingsModule); diff --git a/server/src/db/mongo/modules/checkModule.js b/server/src/db/mongo/modules/checkModule.js index 18c3d45ec..f806b5bf5 100755 --- a/server/src/db/mongo/modules/checkModule.js +++ b/server/src/db/mongo/modules/checkModule.js @@ -1,9 +1,3 @@ -import Check from "../../models/Check.js"; -import Monitor from "../../models/Monitor.js"; -import HardwareCheck from "../../models/HardwareCheck.js"; -import PageSpeedCheck from "../../models/PageSpeedCheck.js"; -import User from "../../models/User.js"; -import { logger } from "../../../utils/logger.js"; import { ObjectId } from "mongodb"; import { buildChecksSummaryByTeamIdPipeline } from "./checkModuleQueries.js"; @@ -17,369 +11,296 @@ const dateRangeLookup = { all: undefined, }; -/** - * Create a check for a monitor - * @async - * @param {Object} checkData - * @param {string} checkData.monitorId - * @param {boolean} checkData.status - * @param {number} checkData.responseTime - * @param {number} checkData.statusCode - * @param {string} checkData.message - * @returns {Promise} - * @throws {Error} - */ - -const createCheck = async (checkData) => { - try { - const check = await new Check({ ...checkData }).save(); - return check; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createCheck"; - throw error; +class CheckModule { + constructor({ logger, Check, HardwareCheck, PageSpeedCheck, Monitor, User }) { + this.logger = logger; + this.Check = Check; + this.HardwareCheck = HardwareCheck; + this.PageSpeedCheck = PageSpeedCheck; + this.Monitor = Monitor; + this.User = User; } -}; -const createChecks = async (checks) => { - try { - await Check.insertMany(checks, { ordered: false }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createCheck"; - throw error; - } -}; - -/** - * Get all checks for a monitor - * @async - * @param {string} monitorId - * @returns {Promise>} - * @throws {Error} - */ -const getChecksByMonitor = async ({ monitorId, type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status }) => { - try { - status = status === "true" ? true : status === "false" ? false : undefined; - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - - const ackStage = ack === "true" ? { ack: true } : { $or: [{ ack: false }, { ack: { $exists: false } }] }; - - // Match - const matchStage = { - monitorId: new ObjectId(monitorId), - ...(typeof status !== "undefined" && { status }), - ...(typeof ack !== "undefined" && ackStage), - ...(dateRangeLookup[dateRange] && { - createdAt: { - $gte: dateRangeLookup[dateRange], - }, - }), - }; - - if (filter !== undefined) { - switch (filter) { - case "all": - break; - case "down": - break; - case "resolve": - matchStage.statusCode = 5000; - break; - default: - logger.warn({ - message: "invalid filter", - service: SERVICE_NAME, - method: "getChecks", - }); - break; - } + createChecks = async (checks) => { + try { + await this.Check.insertMany(checks, { ordered: false }); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createCheck"; + throw error; } + }; - //Sort - sortOrder = sortOrder === "asc" ? 1 : -1; + getChecksByMonitor = async ({ monitorId, type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status }) => { + try { + status = status === "true" ? true : status === "false" ? false : undefined; + page = parseInt(page); + rowsPerPage = parseInt(rowsPerPage); - // Pagination - let skip = 0; - if (page && rowsPerPage) { - skip = page * rowsPerPage; - } + const ackStage = ack === "true" ? { ack: true } : { $or: [{ ack: false }, { ack: { $exists: false } }] }; - const checkModels = { - http: Check, - ping: Check, - docker: Check, - port: Check, - pagespeed: PageSpeedCheck, - hardware: HardwareCheck, - }; - - const Model = checkModels[type]; - - const checks = await Model.aggregate([ - { $match: matchStage }, - { $sort: { createdAt: sortOrder } }, - { - $facet: { - summary: [{ $count: "checksCount" }], - checks: [{ $skip: skip }, { $limit: rowsPerPage }], - }, - }, - { - $project: { - checksCount: { - $ifNull: [{ $arrayElemAt: ["$summary.checksCount", 0] }, 0], + // Match + const matchStage = { + monitorId: new ObjectId(monitorId), + ...(typeof status !== "undefined" && { status }), + ...(typeof ack !== "undefined" && ackStage), + ...(dateRangeLookup[dateRange] && { + createdAt: { + $gte: dateRangeLookup[dateRange], }, - checks: { - $ifNull: ["$checks", []], + }), + }; + + if (filter !== undefined) { + switch (filter) { + case "all": + break; + case "down": + break; + case "resolve": + matchStage.statusCode = 5000; + break; + default: + this.logger.warn({ + message: "invalid filter", + service: SERVICE_NAME, + method: "getChecks", + }); + break; + } + } + + //Sort + sortOrder = sortOrder === "asc" ? 1 : -1; + + // Pagination + let skip = 0; + if (page && rowsPerPage) { + skip = page * rowsPerPage; + } + + const checkModels = { + http: this.Check, + ping: this.Check, + docker: this.Check, + port: this.Check, + pagespeed: this.PageSpeedCheck, + hardware: this.HardwareCheck, + }; + + const Model = checkModels[type]; + + const checks = await Model.aggregate([ + { $match: matchStage }, + { $sort: { createdAt: sortOrder } }, + { + $facet: { + summary: [{ $count: "checksCount" }], + checks: [{ $skip: skip }, { $limit: rowsPerPage }], }, }, - }, - ]); - return checks[0]; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getChecks"; - throw error; - } -}; - -const getChecksByTeam = async ({ sortOrder, dateRange, filter, ack, page, rowsPerPage, teamId }) => { - try { - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - - const ackStage = ack === "true" ? { ack: true } : { $or: [{ ack: false }, { ack: { $exists: false } }] }; - - const matchStage = { - teamId: new ObjectId(teamId), - status: false, - ...(typeof ack !== "undefined" && ackStage), - ...(dateRangeLookup[dateRange] && { - createdAt: { - $gte: dateRangeLookup[dateRange], + { + $project: { + checksCount: { + $ifNull: [{ $arrayElemAt: ["$summary.checksCount", 0] }, 0], + }, + checks: { + $ifNull: ["$checks", []], + }, + }, }, - }), - }; - // Add filter to match stage - if (filter !== undefined) { - switch (filter) { - case "all": - break; - case "down": - break; - case "resolve": - matchStage.statusCode = 5000; - break; - default: - logger.warn({ - message: "invalid filter", - service: SERVICE_NAME, - method: "getChecksByTeam", - }); - break; + ]); + return checks[0]; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getChecks"; + throw error; + } + }; + + getChecksByTeam = async ({ sortOrder, dateRange, filter, ack, page, rowsPerPage, teamId }) => { + try { + page = parseInt(page); + rowsPerPage = parseInt(rowsPerPage); + + const ackStage = ack === "true" ? { ack: true } : { $or: [{ ack: false }, { ack: { $exists: false } }] }; + + const matchStage = { + teamId: new ObjectId(teamId), + status: false, + ...(typeof ack !== "undefined" && ackStage), + ...(dateRangeLookup[dateRange] && { + createdAt: { + $gte: dateRangeLookup[dateRange], + }, + }), + }; + // Add filter to match stage + if (filter !== undefined) { + switch (filter) { + case "all": + break; + case "down": + break; + case "resolve": + matchStage.statusCode = 5000; + break; + default: + this.logger.warn({ + message: "invalid filter", + service: SERVICE_NAME, + method: "getChecksByTeam", + }); + break; + } } + + sortOrder = sortOrder === "asc" ? 1 : -1; + + // pagination + let skip = 0; + if (page && rowsPerPage) { + skip = page * rowsPerPage; + } + + const aggregatePipeline = [ + { $match: matchStage }, + { + $unionWith: { + coll: "hardwarechecks", + pipeline: [{ $match: matchStage }], + }, + }, + { + $unionWith: { + coll: "pagespeedchecks", + pipeline: [{ $match: matchStage }], + }, + }, + + { $sort: { createdAt: sortOrder } }, + { + $facet: { + summary: [{ $count: "checksCount" }], + checks: [{ $skip: skip }, { $limit: rowsPerPage }], + }, + }, + { + $project: { + checksCount: { $arrayElemAt: ["$summary.checksCount", 0] }, + checks: "$checks", + }, + }, + ]; + + const checks = await this.Check.aggregate(aggregatePipeline); + return checks[0]; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getChecksByTeam"; + throw error; + } + }; + + ackCheck = async (checkId, teamId, ack) => { + try { + const updatedCheck = await this.Check.findOneAndUpdate({ _id: checkId, teamId: teamId }, { $set: { ack, ackAt: new Date() } }, { new: true }); + + if (!updatedCheck) { + throw new Error("Check not found"); + } + + return updatedCheck; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "ackCheck"; + throw error; + } + }; + + ackAllChecks = async (monitorId, teamId, ack, path) => { + try { + const updatedChecks = await this.Check.updateMany(path === "monitor" ? { monitorId } : { teamId }, { $set: { ack, ackAt: new Date() } }); + return updatedChecks.modifiedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "ackAllChecks"; + throw error; + } + }; + + getChecksSummaryByTeamId = async ({ teamId }) => { + try { + const matchStage = { + teamId: new ObjectId(teamId), + }; + const checks = await this.Check.aggregate(buildChecksSummaryByTeamIdPipeline({ matchStage })); + return checks[0].summary; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getChecksSummaryByTeamId"; + throw error; + } + }; + deleteChecks = async (monitorId) => { + try { + const result = await this.Check.deleteMany({ monitorId }); + return result.deletedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteChecks"; + throw error; + } + }; + + deleteChecksByTeamId = async (teamId) => { + try { + // Find all monitor IDs for this team (only get _id field for efficiency) + const teamMonitors = await this.Monitor.find({ teamId }, { _id: 1 }); + const monitorIds = teamMonitors.map((monitor) => monitor._id); + + // Delete all checks for these monitors in one operation + const deleteResult = await this.Check.deleteMany({ monitorId: { $in: monitorIds } }); + + return deleteResult.deletedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteChecksByTeamId"; + throw error; + } + }; + + updateChecksTTL = async (teamId, ttl) => { + try { + await this.Check.collection.dropIndex("expiry_1"); + } catch (error) { + this.logger.error({ + message: error.message, + service: SERVICE_NAME, + method: "updateChecksTTL", + stack: error.stack, + }); } - sortOrder = sortOrder === "asc" ? 1 : -1; - - // pagination - let skip = 0; - if (page && rowsPerPage) { - skip = page * rowsPerPage; + try { + await this.Check.collection.createIndex( + { expiry: 1 }, + { expireAfterSeconds: ttl } // TTL in seconds, adjust as necessary + ); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateChecksTTL"; + throw error; } - - const aggregatePipeline = [ - { $match: matchStage }, - { - $unionWith: { - coll: "hardwarechecks", - pipeline: [{ $match: matchStage }], - }, - }, - { - $unionWith: { - coll: "pagespeedchecks", - pipeline: [{ $match: matchStage }], - }, - }, - - { $sort: { createdAt: sortOrder } }, - { - $facet: { - summary: [{ $count: "checksCount" }], - checks: [{ $skip: skip }, { $limit: rowsPerPage }], - }, - }, - { - $project: { - checksCount: { $arrayElemAt: ["$summary.checksCount", 0] }, - checks: "$checks", - }, - }, - ]; - - const checks = await Check.aggregate(aggregatePipeline); - return checks[0]; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getChecksByTeam"; - throw error; - } -}; - -/** - * Update the acknowledgment status of a check - * @async - * @param {string} checkId - The ID of the check to update - * @param {string} teamId - The ID of the team - * @param {boolean} ack - The acknowledgment status to set - * @returns {Promise} - * @throws {Error} - */ -const ackCheck = async (checkId, teamId, ack) => { - try { - const updatedCheck = await Check.findOneAndUpdate({ _id: checkId, teamId: teamId }, { $set: { ack, ackAt: new Date() } }, { new: true }); - - if (!updatedCheck) { - throw new Error("Check not found"); + // Update user + try { + await this.User.updateMany({ teamId: teamId }, { checkTTL: ttl }); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateChecksTTL"; + throw error; } + }; +} - return updatedCheck; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "ackCheck"; - throw error; - } -}; - -/** - * Update the acknowledgment status of all checks for a monitor or team - * @async - * @param {string} id - The monitor ID or team ID - * @param {boolean} ack - The acknowledgment status to set - * @param {string} path - The path type ('monitor' or 'team') - * @returns {Promise} - * @throws {Error} - */ -const ackAllChecks = async (monitorId, teamId, ack, path) => { - try { - const updatedChecks = await Check.updateMany(path === "monitor" ? { monitorId } : { teamId }, { $set: { ack, ackAt: new Date() } }); - return updatedChecks.modifiedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "ackAllChecks"; - throw error; - } -}; - -/** - * Get checks and summary by team ID - * @async - * @param {string} teamId - * @returns {Promise} - * @throws {Error} - */ -const getChecksSummaryByTeamId = async ({ teamId }) => { - try { - const matchStage = { - teamId: new ObjectId(teamId), - }; - const checks = await Check.aggregate(buildChecksSummaryByTeamIdPipeline({ matchStage })); - return checks[0].summary; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getChecksSummaryByTeamId"; - throw error; - } -}; - -/** - * Delete all checks for a monitor - * @async - * @param {string} monitorId - * @returns {number} - * @throws {Error} - */ - -const deleteChecks = async (monitorId) => { - try { - const result = await Check.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteChecks"; - throw error; - } -}; - -/** - * Delete all checks for a team - * @async - * @param {string} monitorId - * @returns {number} - * @throws {Error} - */ - -const deleteChecksByTeamId = async (teamId) => { - try { - // Find all monitor IDs for this team (only get _id field for efficiency) - const teamMonitors = await Monitor.find({ teamId }, { _id: 1 }); - const monitorIds = teamMonitors.map((monitor) => monitor._id); - - // Delete all checks for these monitors in one operation - const deleteResult = await Check.deleteMany({ monitorId: { $in: monitorIds } }); - - return deleteResult.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteChecksByTeamId"; - throw error; - } -}; - -const updateChecksTTL = async (teamId, ttl) => { - try { - await Check.collection.dropIndex("expiry_1"); - } catch (error) { - logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "updateChecksTTL", - stack: error.stack, - }); - } - - try { - await Check.collection.createIndex( - { expiry: 1 }, - { expireAfterSeconds: ttl } // TTL in seconds, adjust as necessary - ); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateChecksTTL"; - throw error; - } - // Update user - try { - await User.updateMany({ teamId: teamId }, { checkTTL: ttl }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateChecksTTL"; - throw error; - } -}; - -export { - createCheck, - createChecks, - getChecksByMonitor, - getChecksByTeam, - ackCheck, - ackAllChecks, - getChecksSummaryByTeamId, - deleteChecks, - deleteChecksByTeamId, - updateChecksTTL, -}; +export default CheckModule; diff --git a/server/src/service/business/checkService.js b/server/src/service/business/checkService.js index 262d8e863..c5a4df88d 100644 --- a/server/src/service/business/checkService.js +++ b/server/src/service/business/checkService.js @@ -34,7 +34,7 @@ class CheckService { } let { type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status } = query; - const result = await this.db.getChecksByMonitor({ + const result = await this.db.checkModule.getChecksByMonitor({ monitorId, type, sortOrder, @@ -55,7 +55,7 @@ class CheckService { throw this.errorService.createBadRequestError("No team ID in request"); } - const checkData = await this.db.getChecksByTeam({ + const checkData = await this.db.checkModule.getChecksByTeam({ sortOrder, dateRange, filter, @@ -72,7 +72,7 @@ class CheckService { throw this.errorService.createBadRequestError("No team ID in request"); } - const summary = await this.db.getChecksSummaryByTeamId({ teamId }); + const summary = await this.db.checkModule.getChecksSummaryByTeamId({ teamId }); return summary; }; @@ -85,7 +85,7 @@ class CheckService { throw this.errorService.createBadRequestError("No team ID in request"); } - const updatedCheck = await this.db.ackCheck(checkId, teamId, ack); + const updatedCheck = await this.db.checkModule.ackCheck(checkId, teamId, ack); return updatedCheck; }; @@ -105,7 +105,7 @@ class CheckService { } } - const updatedChecks = await this.db.ackAllChecks(monitorId, teamId, ack, path); + const updatedChecks = await this.db.checkModule.ackAllChecks(monitorId, teamId, ack, path); return updatedChecks; }; @@ -128,7 +128,7 @@ class CheckService { throw this.errorService.createAuthorizationError(); } - const deletedCount = await this.db.deleteChecks(monitorId); + const deletedCount = await this.db.checkModule.deleteChecks(monitorId); return deletedCount; }; deleteChecksByTeamId = async ({ teamId }) => { @@ -136,14 +136,14 @@ class CheckService { throw this.errorService.createBadRequestError("No team ID in request"); } - const deletedCount = await this.db.deleteChecksByTeamId(teamId); + const deletedCount = await this.db.checkModule.deleteChecksByTeamId(teamId); return deletedCount; }; updateChecksTTL = async ({ teamId, ttl }) => { const SECONDS_PER_DAY = 86400; const newTTL = parseInt(ttl, 10) * SECONDS_PER_DAY; - await this.db.updateChecksTTL(teamId, newTTL); + await this.db.checkModule.updateChecksTTL(teamId, newTTL); }; } diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.js index d307e1837..94be6984c 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -153,7 +153,7 @@ class MonitorService { monitors.map(async (monitor) => { try { await this.jobQueue.deleteJob(monitor); - await this.db.deleteChecks(monitor._id); + await this.db.checkModule.deleteChecks(monitor._id); await this.db.deletePageSpeedChecksByMonitorId(monitor._id); await this.db.deleteNotificationsByMonitorId(monitor._id); } catch (error) { diff --git a/server/src/service/infrastructure/bufferService.js b/server/src/service/infrastructure/bufferService.js index d67e917d7..e772c313d 100755 --- a/server/src/service/infrastructure/bufferService.js +++ b/server/src/service/infrastructure/bufferService.js @@ -21,7 +21,7 @@ class BufferService { hardwareChecks: [], }; this.OPERATION_MAP = { - checks: this.db.createChecks, + checks: this.db.checkModule.createChecks, pagespeedChecks: this.db.createPageSpeedChecks, hardwareChecks: this.db.createHardwareChecks, };