From 68559741ba6c4933b7c8edc9368c3f45c01721df Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 15 Jan 2026 19:36:41 +0000 Subject: [PATCH] remove mongo middleware --- server/src/config/services.ts | 5 +- server/src/controllers/monitorController.ts | 15 --- server/src/db/models/Monitor.ts | 29 ----- server/src/db/models/StatusPage.js | 85 ------------- server/src/db/models/StatusPage.ts | 112 ++++++++++++++++++ server/src/db/models/index.ts | 3 + server/src/db/modules/statusPageModule.js | 1 + .../repositories/checks/IChecksRepository.ts | 6 + .../checks/MongoChecksRepistory.ts | 5 + server/src/repositories/index.ts | 3 + .../monitor-stats/IMonitorStatsRepository.ts | 1 + .../MongoMonitorStatsRepository.ts | 8 ++ .../monitors/MongoMonitorsRepository.ts | 2 +- .../status-pages/IStatusPagesRepository.ts | 11 ++ .../MongoStatusPagesRepository.ts | 52 ++++++++ server/src/routes/v1/monitorRoute.ts | 1 - server/src/service/business/monitorService.ts | 19 +-- server/src/types/index.ts | 1 + server/src/types/statusPage.ts | 29 +++++ 19 files changed, 247 insertions(+), 141 deletions(-) delete mode 100755 server/src/db/models/StatusPage.js create mode 100644 server/src/db/models/StatusPage.ts create mode 100644 server/src/repositories/status-pages/IStatusPagesRepository.ts create mode 100644 server/src/repositories/status-pages/MongoStatusPagesRepository.ts create mode 100644 server/src/types/statusPage.ts diff --git a/server/src/config/services.ts b/server/src/config/services.ts index 74c68c86e..990dc63b3 100644 --- a/server/src/config/services.ts +++ b/server/src/config/services.ts @@ -70,7 +70,7 @@ import SettingsModule from "../db/modules/settingsModule.js"; import IncidentModule from "../db/modules/incidentModule.js"; // repositories -import { MongoMonitorsRepository, MongoChecksRepository, MongoMonitorStatsRepository } from "@/repositories/index.js"; +import { MongoMonitorsRepository, MongoChecksRepository, MongoMonitorStatsRepository, MongoStatusPagesRepository } from "@/repositories/index.js"; export const initializeServices = async ({ logger, envSettings, settingsService }: { logger: any; envSettings: any; settingsService: any }) => { const serviceRegistry = new ServiceRegistry({ logger }); @@ -124,6 +124,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService const monitorsRepository = new MongoMonitorsRepository(); const checksRepository = new MongoChecksRepository(); const monitorStatsRepository = new MongoMonitorStatsRepository(); + const statusPagesRepository = new MongoStatusPagesRepository(); const networkService = new NetworkService({ axios, @@ -217,7 +218,6 @@ export const initializeServices = async ({ logger, envSettings, settingsService errorService, }); const monitorService = new MonitorService({ - db, jobQueue: superSimpleQueue, stringService, emailService, @@ -228,6 +228,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService monitorsRepository, checksRepository, monitorStatsRepository, + statusPagesRepository, }); const services = { diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts index 859a8fa4b..d46845026 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -374,21 +374,6 @@ class MonitorController { next(error); } }; - exportMonitorsToCSV = async (req: Request, res: Response, next: NextFunction) => { - try { - const teamId = req?.user?.teamId; - if (!teamId) { - throw new AppError({ message: "Team ID is required", status: 400 }); - } - - const csv = await this.monitorService.exportMonitorsToCSV({ teamId }); - res.setHeader("Content-Type", "text/csv"); - res.setHeader("Content-Disposition", "attachment; filename=monitors.csv"); - return res.send(csv); - } catch (error) { - next(error); - } - }; exportMonitorsToJSON = async (req: Request, res: Response, next: NextFunction) => { try { diff --git a/server/src/db/models/Monitor.ts b/server/src/db/models/Monitor.ts index f6f9d4371..17565a2e4 100644 --- a/server/src/db/models/Monitor.ts +++ b/server/src/db/models/Monitor.ts @@ -171,35 +171,6 @@ const MonitorSchema = new Schema( } ); -MonitorSchema.pre("findOneAndDelete", async function (next) { - try { - const doc = await this.model.findOne(this.getFilter()); - - if (!doc) { - throw new Error("Monitor not found"); - } - - await Check.deleteMany({ monitorId: doc._id }); - await StatusPage.updateMany({ monitors: doc?._id }, { $pull: { monitors: doc?._id } }); - await MonitorStats.deleteMany({ monitorId: doc?._id.toString() }); - next(); - } catch (error) { - next(error as Error); - } -}); - -MonitorSchema.pre("deleteMany", async function (next) { - const filter = this.getFilter(); - const monitors = (await this.model.find(filter).select(["_id", "type"]).lean()) as { _id: Types.ObjectId }[]; - - for (const monitor of monitors) { - await Check.deleteMany({ monitorId: monitor._id }); - await StatusPage.updateMany({ monitors: monitor._id }, { $pull: { monitors: monitor._id } }); - await MonitorStats.deleteMany({ monitorId: monitor._id.toString() }); - } - next(); -}); - MonitorSchema.pre("save", function (next) { if (!this.cpuAlertThreshold || this.isModified("alertThreshold")) { this.cpuAlertThreshold = this.alertThreshold; diff --git a/server/src/db/models/StatusPage.js b/server/src/db/models/StatusPage.js deleted file mode 100755 index 621749fb8..000000000 --- a/server/src/db/models/StatusPage.js +++ /dev/null @@ -1,85 +0,0 @@ -import mongoose from "mongoose"; - -const StatusPageSchema = mongoose.Schema( - { - userId: { - type: mongoose.Schema.Types.ObjectId, - ref: "User", - immutable: true, - required: true, - }, - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - required: true, - }, - type: { - type: String, - required: true, - default: "uptime", - enum: ["uptime"], - }, - companyName: { - type: String, - required: true, - default: "", - }, - url: { - type: String, - unique: true, - required: true, - default: "", - }, - timezone: { - type: String, - required: false, - }, - color: { - type: String, - required: false, - default: "#4169E1", - }, - monitors: [ - { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - required: true, - }, - ], - subMonitors: [ - { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - required: true, - }, - ], - logo: { - data: Buffer, - contentType: String, - }, - isPublished: { - type: Boolean, - default: false, - }, - showCharts: { - type: Boolean, - default: true, - }, - showUptimePercentage: { - type: Boolean, - default: true, - }, - showAdminLoginLink: { - type: Boolean, - default: false, - }, - customCSS: { - type: String, - default: "", - }, - }, - { timestamps: true } -); - -export default mongoose.model("StatusPage", StatusPageSchema); diff --git a/server/src/db/models/StatusPage.ts b/server/src/db/models/StatusPage.ts new file mode 100644 index 000000000..6b6e7721c --- /dev/null +++ b/server/src/db/models/StatusPage.ts @@ -0,0 +1,112 @@ +import { Schema, model, type Types } from "mongoose"; +import type { StatusPage, StatusPageLogo } from "@/types/statusPage.js"; +import { StatusPageTypes } from "@/types/statusPage.js"; + +type StatusPageDocumentBase = Omit & { + monitors: Types.ObjectId[]; + subMonitors: Types.ObjectId[]; + originalMonitors?: Types.ObjectId[]; + logo?: StatusPageLogo | null; +}; + +interface StatusPageDocument extends StatusPageDocumentBase { + _id: Types.ObjectId; + userId: Types.ObjectId; + teamId: Types.ObjectId; + createdAt: Date; + updatedAt: Date; +} + +const logoSchema = new Schema( + { + data: { type: Buffer }, + contentType: { type: String }, + }, + { _id: false } +); + +const StatusPageSchema = new Schema( + { + userId: { + type: Schema.Types.ObjectId, + ref: "User", + immutable: true, + required: true, + }, + teamId: { + type: Schema.Types.ObjectId, + ref: "Team", + immutable: true, + required: true, + }, + type: { + type: String, + required: true, + default: "uptime", + enum: StatusPageTypes, + }, + companyName: { + type: String, + required: true, + default: "", + }, + url: { + type: String, + unique: true, + required: true, + default: "", + }, + timezone: { + type: String, + }, + color: { + type: String, + default: "#4169E1", + }, + monitors: [ + { + type: Schema.Types.ObjectId, + ref: "Monitor", + required: true, + }, + ], + subMonitors: [ + { + type: Schema.Types.ObjectId, + ref: "Monitor", + required: true, + }, + ], + logo: { + type: logoSchema, + default: null, + }, + isPublished: { + type: Boolean, + default: false, + }, + showCharts: { + type: Boolean, + default: true, + }, + showUptimePercentage: { + type: Boolean, + default: true, + }, + showAdminLoginLink: { + type: Boolean, + default: false, + }, + customCSS: { + type: String, + default: "", + }, + }, + { timestamps: true } +); + +const StatusPageModel = model("StatusPage", StatusPageSchema); + +export type { StatusPageDocument }; +export { StatusPageModel }; +export default StatusPageModel; diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts index 3ac99a390..b92ae18b7 100644 --- a/server/src/db/models/index.ts +++ b/server/src/db/models/index.ts @@ -6,3 +6,6 @@ export { default as CheckModel } from "@/db/models/Check.js"; export * from "@/db/models/MonitorStats.js"; export { default as MonitorStatsModel } from "@/db/models/MonitorStats.js"; + +export * from "@/db/models/StatusPage.js"; +export { default as StatusPageModel } from "@/db/models/StatusPage.js"; diff --git a/server/src/db/modules/statusPageModule.js b/server/src/db/modules/statusPageModule.js index 999e0bdc9..081d3bea5 100755 --- a/server/src/db/modules/statusPageModule.js +++ b/server/src/db/modules/statusPageModule.js @@ -287,6 +287,7 @@ class StatusPageModule { deleteStatusPagesByMonitorId = async (monitorId) => { try { await this.StatusPage.deleteMany({ monitors: { $in: [monitorId] } }); + await this.StatusPage.deleteMany({ subMonitors: { $in: [monitorId] } }); } catch (error) { error.service = SERVICE_NAME; error.method = "deleteStatusPageByMonitorId"; diff --git a/server/src/repositories/checks/IChecksRepository.ts b/server/src/repositories/checks/IChecksRepository.ts index cedea8d86..19faa0159 100644 --- a/server/src/repositories/checks/IChecksRepository.ts +++ b/server/src/repositories/checks/IChecksRepository.ts @@ -54,6 +54,9 @@ export interface UptimeChecksResult { } export interface IChecksRepository { + // create + // single fetch + // collection fetch findLatestChecksByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise; findDateRangeChecksByMonitor( monitorId: string, @@ -62,4 +65,7 @@ export interface IChecksRepository { dateString: string, options?: { type?: MonitorType } ): Promise; + // update + //delete + deleteByMonitorId(monitorId: string): Promise; } diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts index b5489309d..615754933 100644 --- a/server/src/repositories/checks/MongoChecksRepistory.ts +++ b/server/src/repositories/checks/MongoChecksRepistory.ts @@ -385,6 +385,11 @@ class MongoChecksRepository implements IChecksRepository { checks: checks.map((doc) => this.toEntity(doc)), }; }; + + deleteByMonitorId = async (monitorId: string): Promise => { + const result = await CheckModel.deleteMany({ "metadata.monitorId": monitorId }); + return result.deletedCount; + }; } export default MongoChecksRepository; diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index 44d64f5bb..2c27591f1 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -6,3 +6,6 @@ export { default as MongoChecksRepository } from "@/repositories/checks/MongoChe export * from "@/repositories/monitor-stats/IMonitorStatsRepository.js"; export { default as MongoMonitorStatsRepository } from "@/repositories/monitor-stats/MongoMonitorStatsRepository.js"; + +export * from "@/repositories/status-pages/IStatusPagesRepository.js"; +export { default as MongoStatusPagesRepository } from "@/repositories/status-pages/MongoStatusPagesRepository.js"; diff --git a/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts b/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts index a0720619f..08309b4ee 100644 --- a/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts +++ b/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts @@ -5,5 +5,6 @@ export interface IMonitorStatsRepository { findByMonitorId(monitorId: string): Promise; // update // delete + deleteByMonitorId(monitorId: string): Promise; // other } diff --git a/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts b/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts index d1d6a4825..52dbe8aa5 100644 --- a/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts +++ b/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts @@ -39,6 +39,14 @@ class MongoMonitorStatsRepository implements IMonitorStatsRepository { } return this.toEntity(monitorStats); }; + + deleteByMonitorId = async (monitorId: string) => { + const deleted = await MonitorStatsModel.findOneAndDelete({ monitorId: new mongoose.Types.ObjectId(monitorId) }); + if (!deleted) { + throw new AppError({ message: "Monitor stats not found", status: 404 }); + } + return this.toEntity(deleted); + }; } export default MongoMonitorStatsRepository; diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index fe5958c2a..52f423154 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -1,6 +1,6 @@ import { MonitorModel } from "@/db/models/index.js"; import type { MonitorDocument } from "@/db/models/index.js"; -import type { Monitor, MonitorType } from "@/types/index.js"; +import type { Monitor } from "@/types/index.js"; import mongoose, { type FilterQuery } from "mongoose"; import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "./IMonitorsRepository.js"; import { AppError } from "@/utils/AppError.js"; diff --git a/server/src/repositories/status-pages/IStatusPagesRepository.ts b/server/src/repositories/status-pages/IStatusPagesRepository.ts new file mode 100644 index 000000000..1b9f9303b --- /dev/null +++ b/server/src/repositories/status-pages/IStatusPagesRepository.ts @@ -0,0 +1,11 @@ +import type { StatusPage } from "@/types/statusPage.js"; + +export interface IStatusPagesRepository { + // create + // single fetch + // collection fetch + // update + // delete + // other + removeMonitorFromStatusPages(monitorId: string): Promise; +} diff --git a/server/src/repositories/status-pages/MongoStatusPagesRepository.ts b/server/src/repositories/status-pages/MongoStatusPagesRepository.ts new file mode 100644 index 000000000..6beb23f0e --- /dev/null +++ b/server/src/repositories/status-pages/MongoStatusPagesRepository.ts @@ -0,0 +1,52 @@ +import { IStatusPagesRepository } from "@/repositories/index.js"; +import { type StatusPageDocument, StatusPageModel } from "@/db/models/StatusPage.js"; +import type { StatusPage } from "@/types/statusPage.js"; +import mongoose from "mongoose"; + +class MongoStatusPagesRepository implements IStatusPagesRepository { + private toEntity(doc: StatusPageDocument): StatusPage { + const toStringId = (value: unknown): string => { + if (value instanceof mongoose.Types.ObjectId) { + return value.toString(); + } + return value?.toString() ?? ""; + }; + + const toDateString = (value: Date | string): string => { + return value instanceof Date ? value.toISOString() : value; + }; + + const mapIdArray = (values?: mongoose.Types.ObjectId[]): string[] => { + return values?.map((value) => toStringId(value)) ?? []; + }; + + return { + id: toStringId(doc._id), + userId: toStringId(doc.userId), + teamId: toStringId(doc.teamId), + type: doc.type, + companyName: doc.companyName, + url: doc.url, + timezone: doc.timezone ?? undefined, + color: doc.color, + monitors: mapIdArray(doc.monitors), + subMonitors: mapIdArray(doc.subMonitors), + originalMonitors: doc.originalMonitors?.map((value) => toStringId(value)), + logo: doc.logo ?? undefined, + isPublished: doc.isPublished, + showCharts: doc.showCharts, + showUptimePercentage: doc.showUptimePercentage, + showAdminLoginLink: doc.showAdminLoginLink, + customCSS: doc.customCSS, + createdAt: toDateString(doc.createdAt), + updatedAt: toDateString(doc.updatedAt), + }; + } + + removeMonitorFromStatusPages = async (monitorId: string): Promise => { + const res = await StatusPageModel.updateMany({ monitors: monitorId }, { $pull: { monitors: monitorId } }); + return res.modifiedCount; + }; +} + +export default MongoStatusPagesRepository; diff --git a/server/src/routes/v1/monitorRoute.ts b/server/src/routes/v1/monitorRoute.ts index ab6b0c98e..2e6db630d 100755 --- a/server/src/routes/v1/monitorRoute.ts +++ b/server/src/routes/v1/monitorRoute.ts @@ -43,7 +43,6 @@ class MonitorRoutes { // Other static routes this.router.post("/demo", isAllowed(["admin", "superadmin"]), this.monitorController.addDemoMonitors); - this.router.get("/export", isAllowed(["admin", "superadmin"]), this.monitorController.exportMonitorsToCSV); this.router.get("/export/json", isAllowed(["admin", "superadmin"]), this.monitorController.exportMonitorsToJSON); this.router.post("/bulk", isAllowed(["admin", "superadmin"]), upload.single("csvFile"), this.monitorController.createBulkMonitors); this.router.post("/test-email", isAllowed(["admin", "superadmin"]), this.monitorController.sendTestEmail); diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index ff79259eb..ecdf8ca02 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -2,7 +2,7 @@ import { createMonitorsBodyValidation } from "@/validation/joi.js"; import { NormalizeData, NormalizeDataUptimeDetails } from "@/utils/dataUtils.js"; import { type Monitor } from "@/types/index.js"; import type { MonitorType } from "@/types/monitor.js"; -import type { IChecksRepository, IMonitorsRepository, IMonitorStatsRepository } from "@/repositories/index.js"; +import type { IChecksRepository, IMonitorsRepository, IMonitorStatsRepository, IStatusPagesRepository } from "@/repositories/index.js"; import fs from "fs"; import { fileURLToPath } from "url"; import path from "path"; @@ -76,9 +76,9 @@ export class MonitorService implements IMonitorService { private monitorsRepository: IMonitorsRepository; private checksRepository: IChecksRepository; private monitorStatsRepository: IMonitorStatsRepository; + private statusPagesRepository: IStatusPagesRepository; constructor({ - db, jobQueue, stringService, emailService, @@ -89,8 +89,8 @@ export class MonitorService implements IMonitorService { monitorsRepository, checksRepository, monitorStatsRepository, + statusPagesRepository, }: { - db: any; jobQueue: any; stringService: any; emailService: any; @@ -101,8 +101,8 @@ export class MonitorService implements IMonitorService { monitorsRepository: IMonitorsRepository; checksRepository: IChecksRepository; monitorStatsRepository: IMonitorStatsRepository; + statusPagesRepository: IStatusPagesRepository; }) { - this.db = db; this.jobQueue = jobQueue; this.stringService = stringService; this.emailService = emailService; @@ -113,6 +113,7 @@ export class MonitorService implements IMonitorService { this.monitorsRepository = monitorsRepository; this.checksRepository = checksRepository; this.monitorStatsRepository = monitorStatsRepository; + this.statusPagesRepository = statusPagesRepository; } get serviceName(): string { @@ -428,8 +429,10 @@ export class MonitorService implements IMonitorService { deleteMonitor = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise => { const monitor = await this.monitorsRepository.deleteById(monitorId, teamId); + await this.monitorStatsRepository.deleteByMonitorId(monitor.id); + await this.checksRepository.deleteByMonitorId(monitor.id); + await this.statusPagesRepository.removeMonitorFromStatusPages(monitor.id); await this.jobQueue.deleteJob(monitor); - await this.db.statusPageModule.deleteStatusPagesByMonitorId(monitor.id); return monitor; }; @@ -439,9 +442,9 @@ export class MonitorService implements IMonitorService { monitors.map(async (monitor) => { try { await this.jobQueue.deleteJob(monitor); - await this.db.checkModule.deleteChecks(monitor.id); - await this.db.pageSpeedCheckModule.deletePageSpeedChecksByMonitorId(monitor.id); - await this.db.notificationsModule.deleteNotificationsByMonitorId(monitor.id); + await this.checksRepository.deleteByMonitorId(monitor.id); + await this.statusPagesRepository.removeMonitorFromStatusPages(monitor.id); + await this.monitorStatsRepository.deleteByMonitorId(monitor.id); } catch (error: any) { this.logger.warn({ message: `Error deleting associated records for monitor ${monitor.id} with name ${monitor.name}`, diff --git a/server/src/types/index.ts b/server/src/types/index.ts index 2367f1c1c..81620da03 100644 --- a/server/src/types/index.ts +++ b/server/src/types/index.ts @@ -1,3 +1,4 @@ export * from "@/types/check.js"; export * from "@/types/monitor.js"; export * from "@/types/monitorStats.js"; +export * from "@/types/statusPage.js"; diff --git a/server/src/types/statusPage.ts b/server/src/types/statusPage.ts new file mode 100644 index 000000000..64a5ac50b --- /dev/null +++ b/server/src/types/statusPage.ts @@ -0,0 +1,29 @@ +export const StatusPageTypes = ["uptime"] as const; +export type StatusPageType = (typeof StatusPageTypes)[number]; + +export interface StatusPageLogo { + data: Buffer; + contentType: string; +} + +export interface StatusPage { + id: string; + userId: string; + teamId: string; + type: StatusPageType; + companyName: string; + url: string; + timezone?: string; + color: string; + monitors: string[]; + subMonitors: string[]; + originalMonitors?: string[]; + logo?: StatusPageLogo | null; + isPublished: boolean; + showCharts: boolean; + showUptimePercentage: boolean; + showAdminLoginLink: boolean; + customCSS: string; + createdAt: string; + updatedAt: string; +}