From 27dea6a08e766cdde8b554f1c4857aeebf18edc0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 20 Jan 2026 21:18:26 +0000 Subject: [PATCH] implement remaining check methods --- server/src/config/services.ts | 4 - server/src/db/MongoDB.js | 2 - server/src/db/modules/checkModule.js | 247 ------------------ server/src/db/modules/checkModuleQueries.js | 44 ---- .../repositories/checks/IChecksRepository.ts | 7 +- .../checks/MongoChecksRepistory.ts | 128 ++++++++- server/src/service/business/checkService.ts | 37 +-- server/src/service/business/monitorService.ts | 8 +- 8 files changed, 138 insertions(+), 339 deletions(-) delete mode 100755 server/src/db/modules/checkModule.js delete mode 100644 server/src/db/modules/checkModuleQueries.js diff --git a/server/src/config/services.ts b/server/src/config/services.ts index 0fb78da25..e3587c662 100644 --- a/server/src/config/services.ts +++ b/server/src/config/services.ts @@ -64,7 +64,6 @@ import AppSettings from "../db/models/AppSettings.js"; import Incident from "../db/models/Incident.js"; import InviteModule from "../db/modules/inviteModule.js"; -import CheckModule from "../db/modules/checkModule.js"; import StatusPageModule from "../db/modules/statusPageModule.js"; import UserModule from "../db/modules/userModule.js"; import MaintenanceWindowModule from "../db/modules/maintenanceWindowModule.js"; @@ -150,7 +149,6 @@ export const initializeServices = async ({ const stringService = new StringService(translationService); // Create DB - const checkModule = new CheckModule({ logger, Monitor, User }); const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService, AppSettings }); const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }); @@ -163,7 +161,6 @@ export const initializeServices = async ({ const db = new MongoDB({ logger, envSettings, - checkModule, inviteModule, statusPageModule, userModule, @@ -213,7 +210,6 @@ export const initializeServices = async ({ }); const checkService = new CheckService({ - db, errorService, monitorsRepository, logger, diff --git a/server/src/db/MongoDB.js b/server/src/db/MongoDB.js index f7e58f34e..a0c580f1d 100755 --- a/server/src/db/MongoDB.js +++ b/server/src/db/MongoDB.js @@ -7,7 +7,6 @@ class MongoDB { constructor({ logger, envSettings, - checkModule, inviteModule, statusPageModule, userModule, @@ -22,7 +21,6 @@ class MongoDB { this.userModule = userModule; this.inviteModule = inviteModule; this.recoveryModule = recoveryModule; - this.checkModule = checkModule; this.maintenanceWindowModule = maintenanceWindowModule; this.notificationModule = notificationModule; this.settingsModule = settingsModule; diff --git a/server/src/db/modules/checkModule.js b/server/src/db/modules/checkModule.js deleted file mode 100755 index 4fdf10678..000000000 --- a/server/src/db/modules/checkModule.js +++ /dev/null @@ -1,247 +0,0 @@ -import { ObjectId } from "mongodb"; -import mongoose from "mongoose"; -import { CheckModel } from "@/db/models/index.js"; -import { buildChecksSummaryByTeamIdPipeline } from "./checkModuleQueries.js"; - -const SERVICE_NAME = "checkModule"; -const dateRangeLookup = { - recent: new Date(new Date().setDate(new Date().getDate() - 2)), - hour: new Date(new Date().setHours(new Date().getHours() - 1)), - day: new Date(new Date().setDate(new Date().getDate() - 1)), - week: new Date(new Date().setDate(new Date().getDate() - 7)), - month: new Date(new Date().setMonth(new Date().getMonth() - 1)), - all: undefined, -}; - -class CheckModule { - constructor({ logger, Monitor, User }) { - this.logger = logger; - this.Monitor = Monitor; - this.User = User; - } - - getChecksByMonitor = async ({ monitorId, sortOrder, dateRange, filter, page, rowsPerPage, status }) => { - try { - status = status === "true" ? true : status === "false" ? false : undefined; - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - - // Match - const matchStage = { - "metadata.monitorId": new ObjectId(monitorId), - ...(typeof status !== "undefined" && { status }), - ...(dateRangeLookup[dateRange] && { - createdAt: { - $gte: dateRangeLookup[dateRange], - }, - }), - }; - - 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 checks = await CheckModel.aggregate([ - { $match: matchStage }, - { $sort: { createdAt: sortOrder } }, - { - $facet: { - summary: [{ $count: "checksCount" }], - checks: [{ $skip: skip }, { $limit: rowsPerPage }], - }, - }, - { - $project: { - checksCount: { - $ifNull: [{ $arrayElemAt: ["$summary.checksCount", 0] }, 0], - }, - checks: { - $ifNull: ["$checks", []], - }, - }, - }, - ]); - return checks[0]; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getChecks"; - throw error; - } - }; - - getChecksByTeam = async ({ sortOrder, dateRange, filter, page, rowsPerPage, teamId }) => { - try { - console.log({ - sortOrder, - dateRange, - filter, - page, - rowsPerPage, - teamId, - }); - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - - const matchStage = { - "metadata.teamId": new ObjectId(teamId), - status: false, - ...(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 }, - - { $sort: { createdAt: sortOrder } }, - { - $facet: { - summary: [{ $count: "checksCount" }], - checks: [{ $skip: skip }, { $limit: rowsPerPage }], - }, - }, - { - $project: { - checksCount: { $arrayElemAt: ["$summary.checksCount", 0] }, - checks: "$checks", - }, - }, - ]; - - const checks = await CheckModel.aggregate(aggregatePipeline); - return checks[0]; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getChecksByTeam"; - throw error; - } - }; - - getChecksSummaryByTeamId = async ({ teamId }) => { - try { - const matchStage = { - "metadata.teamId": new ObjectId(teamId), - }; - const checks = await CheckModel.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 CheckModel.deleteMany({ "metadata.monitorId": 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 CheckModel.deleteMany({ "metadata.monitorId": { $in: monitorIds } }); - - return deleteResult.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteChecksByTeamId"; - throw error; - } - }; - - updateChecksTTL = async (teamId, ttl) => { - try { - await CheckModel.collection.dropIndex("expiry_1"); - } catch (error) { - this.logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "updateChecksTTL", - stack: error.stack, - }); - } - - try { - await CheckModel.collection.createIndex( - { expiry: 1 }, - { expireAfterSeconds: ttl, partialFilterExpression: { "metadata.mode": { $exists: true } } } - ); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateChecksTTL"; - throw error; - } - // Update user - try { - await this.User.updateMany({ teamId: teamId }, { checkTTL: ttl }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateChecksTTL"; - throw error; - } - }; -} - -export default CheckModule; diff --git a/server/src/db/modules/checkModuleQueries.js b/server/src/db/modules/checkModuleQueries.js deleted file mode 100644 index f2ad2a922..000000000 --- a/server/src/db/modules/checkModuleQueries.js +++ /dev/null @@ -1,44 +0,0 @@ -const buildChecksSummaryByTeamIdPipeline = ({ matchStage }) => { - return [ - { $match: matchStage }, - { - $facet: { - summary: [ - { - $group: { - _id: null, - totalChecks: { $sum: { $cond: [{ $eq: ["$status", false] }, 1, 0] } }, - resolvedChecks: { - $sum: { - $cond: [{ $and: [{ $eq: ["$ack", true] }, { $eq: ["$status", false] }] }, 1, 0], - }, - }, - downChecks: { - $sum: { - $cond: [{ $and: [{ $eq: ["$ack", false] }, { $eq: ["$status", false] }] }, 1, 0], - }, - }, - cannotResolveChecks: { - $sum: { - $cond: [{ $eq: ["$statusCode", 5000] }, 1, 0], - }, - }, - }, - }, - { - $project: { - _id: 0, - }, - }, - ], - }, - }, - { - $project: { - summary: { $arrayElemAt: ["$summary", 0] }, - }, - }, - ]; -}; - -export { buildChecksSummaryByTeamIdPipeline }; diff --git a/server/src/repositories/checks/IChecksRepository.ts b/server/src/repositories/checks/IChecksRepository.ts index 72f7e632d..1e0c478b9 100644 --- a/server/src/repositories/checks/IChecksRepository.ts +++ b/server/src/repositories/checks/IChecksRepository.ts @@ -68,15 +68,18 @@ export interface IChecksRepository { rowsPerPage: number, status: boolean | undefined ): Promise; - findLatestChecksByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise; - findDateRangeChecksByMonitor( + findByTeamId(sortOrder: string, dateRange: string, filter: string, page: number, rowsPerPage: number, teamId: string): Promise; + findLatestByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise; + findByDateRangeAndMonitorId( monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: MonitorType } ): Promise; + findSummaryByTeamId(teamId: string): Promise; // update //delete deleteByMonitorId(monitorId: string): Promise; + deleteByTeamId(teamId: string): Promise; } diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts index f89e2318a..b9cf2041f 100644 --- a/server/src/repositories/checks/MongoChecksRepistory.ts +++ b/server/src/repositories/checks/MongoChecksRepistory.ts @@ -18,7 +18,7 @@ import mongoose from "mongoose"; const SERVICE_NAME = "StatusService"; -const dateRangeLookup: Record = { +const dateRangeLookup: Record = { recent: new Date(new Date().setDate(new Date().getDate() - 2)), hour: new Date(new Date().setHours(new Date().getHours() - 1)), day: new Date(new Date().setDate(new Date().getDate() - 1)), @@ -269,7 +269,67 @@ class MongoChecksRepository implements IChecksRepository { return checks[0]; }; - findLatestChecksByMonitorIds = async (monitorIds: string[], options?: { limitPerMonitor?: number }): Promise => { + findByTeamId = async (sortOrder: string, dateRange: string, filter: string, page: number, rowsPerPage: number, teamId: string) => { + const matchStage: Record = { + "metadata.teamId": new mongoose.Types.ObjectId(teamId), + status: false, + ...(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; + } + } + + const parsedSortOrder = sortOrder === "asc" ? 1 : -1; + + // pagination + let skip = 0; + if (page && rowsPerPage) { + skip = page * rowsPerPage; + } + + const aggregatePipeline: any = [ + { $match: matchStage }, + + { $sort: { createdAt: parsedSortOrder } }, + { + $facet: { + summary: [{ $count: "checksCount" }], + checks: [{ $skip: skip }, { $limit: rowsPerPage }], + }, + }, + { + $project: { + checksCount: { $arrayElemAt: ["$summary.checksCount", 0] }, + checks: "$checks", + }, + }, + ]; + + const checks = await CheckModel.aggregate(aggregatePipeline); + return checks[0]; + }; + + findLatestByMonitorIds = async (monitorIds: string[], options?: { limitPerMonitor?: number }): Promise => { if (monitorIds.length === 0) { return {}; } @@ -303,7 +363,7 @@ class MongoChecksRepository implements IChecksRepository { }, {}); }; - findDateRangeChecksByMonitor = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: MonitorType }) => { + findByDateRangeAndMonitorId = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: MonitorType }) => { const monitorObjectId = new mongoose.Types.ObjectId(monitorId); if (options?.type === "hardware") { return this.findHardwareDateRangeChecks(monitorObjectId, startDate, endDate, dateString); @@ -314,6 +374,63 @@ class MongoChecksRepository implements IChecksRepository { return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, startDate, endDate, dateString); }; + findSummaryByTeamId = async (teamId: string) => { + const matchStage = { + "metadata.teamId": new mongoose.Types.ObjectId(teamId), + }; + const checks = await CheckModel.aggregate([ + { $match: matchStage }, + { + $facet: { + summary: [ + { + $group: { + _id: null, + totalChecks: { $sum: { $cond: [{ $eq: ["$status", false] }, 1, 0] } }, + resolvedChecks: { + $sum: { + $cond: [{ $and: [{ $eq: ["$ack", true] }, { $eq: ["$status", false] }] }, 1, 0], + }, + }, + downChecks: { + $sum: { + $cond: [{ $and: [{ $eq: ["$ack", false] }, { $eq: ["$status", false] }] }, 1, 0], + }, + }, + cannotResolveChecks: { + $sum: { + $cond: [{ $eq: ["$statusCode", 5000] }, 1, 0], + }, + }, + }, + }, + { + $project: { + _id: 0, + }, + }, + ], + }, + }, + { + $project: { + summary: { $arrayElemAt: ["$summary", 0] }, + }, + }, + ]); + return checks[0].summary; + }; + + deleteByMonitorId = async (monitorId: string): Promise => { + const result = await CheckModel.deleteMany({ "metadata.monitorId": new mongoose.Types.ObjectId(monitorId) }); + return result.deletedCount; + }; + + deleteByTeamId = async (teamId: string) => { + const deleteResult = await CheckModel.deleteMany({ "metadata.teamId": teamId }); + return deleteResult.deletedCount; + }; + private findUptimeDateRangeChecks = async ( monitorType: Exclude, monitorObjectId: mongoose.Types.ObjectId, @@ -477,11 +594,6 @@ class MongoChecksRepository implements IChecksRepository { }; }; - deleteByMonitorId = async (monitorId: string): Promise => { - const result = await CheckModel.deleteMany({ "metadata.monitorId": new mongoose.Types.ObjectId(monitorId) }); - return result.deletedCount; - }; - private getHardwareAggregateData = async (monitorId: string, dates: DateRange): Promise => { const result = await CheckModel.aggregate([ { diff --git a/server/src/service/business/checkService.ts b/server/src/service/business/checkService.ts index 3a1a5fb18..3a932ed3f 100644 --- a/server/src/service/business/checkService.ts +++ b/server/src/service/business/checkService.ts @@ -1,6 +1,7 @@ import { IChecksRepository, IMonitorsRepository } from "@/repositories/index.js"; import type { MonitorType, MonitorStatusResponse, CheckErrorInfo, Check } from "@/types/index.js"; import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js"; +import { AppError } from "@/utils/AppError.js"; import { ParseBoolean } from "@/utils/utils.js"; const SERVICE_NAME = "checkService"; @@ -8,25 +9,21 @@ const SERVICE_NAME = "checkService"; class CheckService { static SERVICE_NAME = SERVICE_NAME; - private db: any; private errorService: any; private monitorsRepository: IMonitorsRepository; private checksRepository: IChecksRepository; private logger: any; constructor({ - db, errorService, monitorsRepository, logger, checksRepository, }: { - db: any; errorService: any; monitorsRepository: IMonitorsRepository; logger: any; checksRepository: IChecksRepository; }) { - this.db = db; this.errorService = errorService; this.monitorsRepository = monitorsRepository; this.logger = logger; @@ -132,15 +129,6 @@ class CheckService { const result = await this.checksRepository.findByMonitorId(monitorId, sortOrder, dateRange, filter, parsedPage, parsedRowsPerPage, parsedStatus); - // const result = await this.db.checkModule.getChecksByMonitor({ - // monitorId, - // sortOrder, - // dateRange, - // filter, - // page, - // rowsPerPage, - // status, - // }); return result; }; @@ -151,14 +139,10 @@ class CheckService { throw this.errorService.createBadRequestError("No team ID in request"); } - const checkData = await this.db.checkModule.getChecksByTeam({ - sortOrder, - dateRange, - filter, - page, - rowsPerPage, - teamId, - }); + const parsedPage = page ? parseInt(page) : page; + const parsedRowsPerPage = rowsPerPage ? parseInt(rowsPerPage) : rowsPerPage; + + const checkData = await this.checksRepository.findByTeamId(sortOrder, dateRange, filter, parsedPage, parsedRowsPerPage, teamId); return checkData; }; @@ -166,8 +150,7 @@ class CheckService { if (!teamId) { throw this.errorService.createBadRequestError("No team ID in request"); } - - const summary = await this.db.checkModule.getChecksSummaryByTeamId({ teamId }); + const summary = await this.checksRepository.findSummaryByTeamId(teamId); return summary; }; @@ -183,7 +166,7 @@ class CheckService { // For verificaiton, throws an error if monitor doesn't belong to team await this.monitorsRepository.findById(monitorId, teamId); - const deletedCount = await this.db.checkModule.deleteChecks(monitorId); + const deletedCount = await this.checksRepository.deleteByMonitorId(monitorId); return deletedCount; }; deleteChecksByTeamId = async ({ teamId }: { teamId: string }) => { @@ -191,14 +174,12 @@ class CheckService { throw this.errorService.createBadRequestError("No team ID in request"); } - const deletedCount = await this.db.checkModule.deleteChecksByTeamId(teamId); + const deletedCount = await this.checksRepository.deleteByTeamId(teamId); return deletedCount; }; updateChecksTTL = async ({ teamId, ttl }: { teamId: string; ttl: string }) => { - const SECONDS_PER_DAY = 86400; - const newTTL = parseInt(ttl, 10) * SECONDS_PER_DAY; - await this.db.checkModule.updateChecksTTL(teamId, newTTL); + throw new AppError({ message: "Not implemented", service: SERVICE_NAME, method: "updateChecksTTL", status: 500 }); }; } diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index 7c8ad199e..fee3a9ffc 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -253,7 +253,7 @@ export class MonitorService implements IMonitorService { } const rangeKey = (dateRange as DateRangeKey) ?? "recent"; const { start, end } = this.getDateRange(rangeKey); - const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), { + const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), { type: monitor.type, }); const monitorStats = await this.monitorStatsRepository.findByMonitorId(monitor.id); @@ -292,7 +292,7 @@ export class MonitorService implements IMonitorService { const rangeKey = (dateRange as DateRangeKey) ?? "recent"; const { start, end } = this.getDateRange(rangeKey); - const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), { + const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), { type: monitor.type, }); @@ -323,7 +323,7 @@ export class MonitorService implements IMonitorService { const rangeKey = (dateRange as DateRangeKey) ?? "recent"; const { start, end } = this.getDateRange(rangeKey); - const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), { + const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), { type: monitor.type, }); @@ -394,7 +394,7 @@ export class MonitorService implements IMonitorService { requestedTypes.length > 0 && requestedTypes.every((requestedType) => snapshotTypes.includes(requestedType as MonitorType)); const limitPerMonitor = snapshotOnlyRequest ? 1 : 25; - const checksMap = await this.checksRepository.findLatestChecksByMonitorIds( + const checksMap = await this.checksRepository.findLatestByMonitorIds( monitorsList.map((monitor) => monitor.id), { limitPerMonitor } );