remove mongo middleware

This commit is contained in:
Alex Holliday committed 2026-01-15 19:36:41 +00:00
1 parent 74a7f0a701
commit 68559741ba
19 files changed
+247 -141

No files matched your search

+3 -2
View File
@@ -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 = {
@@ -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 {
-29
View File
@@ -171,35 +171,6 @@ const MonitorSchema = new Schema<MonitorDocument>(
}
);
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;
-85
View File
@@ -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);
+112
View File
@@ -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<StatusPage, "id" | "userId" | "teamId" | "monitors" | "subMonitors" | "originalMonitors" | "createdAt" | "updatedAt"> & {
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<StatusPageLogo & { data: Buffer }>(
{
data: { type: Buffer },
contentType: { type: String },
},
{ _id: false }
);
const StatusPageSchema = new Schema<StatusPageDocument>(
{
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<StatusPageDocument>("StatusPage", StatusPageSchema);
export type { StatusPageDocument };
export { StatusPageModel };
export default StatusPageModel;
+3
View File
@@ -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";
@@ -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";
@@ -54,6 +54,9 @@ export interface UptimeChecksResult {
}
export interface IChecksRepository {
// create
// single fetch
// collection fetch
findLatestChecksByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap>;
findDateRangeChecksByMonitor(
monitorId: string,
@@ -62,4 +65,7 @@ export interface IChecksRepository {
dateString: string,
options?: { type?: MonitorType }
): Promise<UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult>;
// update
//delete
deleteByMonitorId(monitorId: string): Promise<number>;
}
@@ -385,6 +385,11 @@ class MongoChecksRepository implements IChecksRepository {
checks: checks.map((doc) => this.toEntity(doc)),
};
};
deleteByMonitorId = async (monitorId: string): Promise<number> => {
const result = await CheckModel.deleteMany({ "metadata.monitorId": monitorId });
return result.deletedCount;
};
}
export default MongoChecksRepository;
+3
View File
@@ -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";
@@ -5,5 +5,6 @@ export interface IMonitorStatsRepository {
findByMonitorId(monitorId: string): Promise<MonitorStats>;
// update
// delete
deleteByMonitorId(monitorId: string): Promise<MonitorStats>;
// other
}
@@ -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;
@@ -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";
@@ -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<number>;
}
@@ -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<number> => {
const res = await StatusPageModel.updateMany({ monitors: monitorId }, { $pull: { monitors: monitorId } });
return res.modifiedCount;
};
}
export default MongoStatusPagesRepository;
-1
View File
@@ -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);
+11 -8
View File
@@ -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<Monitor> => {
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}`,
+1
View File
@@ -1,3 +1,4 @@
export * from "@/types/check.js";
export * from "@/types/monitor.js";
export * from "@/types/monitorStats.js";
export * from "@/types/statusPage.js";
+29
View File
@@ -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;
}