mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-14 21:48:39 -05:00
Merge pull request #3131 from bluewave-labs/fix/remove-monitor-db-module
fix: remove db module
This commit is contained in:
@@ -10,8 +10,8 @@ import StatusPageController from "../controllers/statusPageController.js";
|
||||
import NotificationController from "../controllers/notificationController.js";
|
||||
import DiagnosticController from "../controllers/diagnosticController.js";
|
||||
import IncidentController from "../controllers/incidentController.js";
|
||||
|
||||
export const initializeControllers = (services: any) => {
|
||||
import type { InitializedSerivces } from "@/config/services.js";
|
||||
export const initializeControllers = (services: InitializedSerivces) => {
|
||||
const controllers: Record<string, any> = {};
|
||||
|
||||
controllers.authController = new AuthController(services.userService);
|
||||
@@ -26,7 +26,7 @@ export const initializeControllers = (services: any) => {
|
||||
controllers.queueController = new QueueController(services.jobQueue);
|
||||
controllers.logController = new LogController(services.logger);
|
||||
controllers.statusPageController = new StatusPageController(services.db);
|
||||
controllers.notificationController = new NotificationController(services.notificationService, services.db);
|
||||
controllers.notificationController = new NotificationController(services.notificationService, services.db, services.monitorsRepository);
|
||||
controllers.diagnosticController = new DiagnosticController(services.diagnosticService);
|
||||
|
||||
controllers.incidentController = new IncidentController(services.incidentService);
|
||||
|
||||
@@ -63,16 +63,61 @@ 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";
|
||||
import MonitorModule from "../db/modules/monitorModule.js";
|
||||
import NotificationModule from "../db/modules/notificationModule.js";
|
||||
import RecoveryModule from "../db/modules/recoveryModule.js";
|
||||
import SettingsModule from "../db/modules/settingsModule.js";
|
||||
import IncidentModule from "../db/modules/incidentModule.js";
|
||||
|
||||
// repositories
|
||||
import { MongoMonitorsRepository, MongoChecksRepository, MongoMonitorStatsRepository, MongoStatusPagesRepository } from "@/repositories/index.js";
|
||||
import {
|
||||
MongoMonitorsRepository,
|
||||
MongoChecksRepository,
|
||||
MongoMonitorStatsRepository,
|
||||
MongoStatusPagesRepository,
|
||||
IMonitorsRepository,
|
||||
IChecksRepository,
|
||||
IMonitorStatsRepository,
|
||||
IStatusPagesRepository,
|
||||
} from "@/repositories/index.js";
|
||||
|
||||
export const initializeServices = async ({ logger, envSettings, settingsService }: { logger: any; envSettings: any; settingsService: any }) => {
|
||||
export type InitializedSerivces = {
|
||||
//v1
|
||||
settingsService: any;
|
||||
translationService: any;
|
||||
stringService: any;
|
||||
db: any;
|
||||
networkService: any;
|
||||
emailService: any;
|
||||
bufferService: any;
|
||||
statusService: any;
|
||||
notificationService: any;
|
||||
jobQueue: any;
|
||||
userService: any;
|
||||
checkService: any;
|
||||
diagnosticService: any;
|
||||
inviteService: any;
|
||||
maintenanceWindowService: any;
|
||||
monitorService: any;
|
||||
incidentService: any;
|
||||
errorService: any;
|
||||
logger: any;
|
||||
|
||||
// Repositories
|
||||
monitorsRepository: IMonitorsRepository;
|
||||
checksRepository: IChecksRepository;
|
||||
monitorStatsRepository: IMonitorStatsRepository;
|
||||
statusPagesRepository: IStatusPagesRepository;
|
||||
};
|
||||
|
||||
export const initializeServices = async ({
|
||||
logger,
|
||||
envSettings,
|
||||
settingsService,
|
||||
}: {
|
||||
logger: any;
|
||||
envSettings: any;
|
||||
settingsService: any;
|
||||
}): Promise<InitializedSerivces> => {
|
||||
const serviceRegistry = new ServiceRegistry({ logger });
|
||||
(ServiceRegistry as any).instance = serviceRegistry;
|
||||
|
||||
@@ -87,17 +132,6 @@ export const initializeServices = async ({ logger, envSettings, settingsService
|
||||
const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService });
|
||||
const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService });
|
||||
const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow });
|
||||
const monitorModule = new MonitorModule({
|
||||
Monitor,
|
||||
MonitorStats,
|
||||
stringService,
|
||||
fs,
|
||||
path,
|
||||
fileURLToPath,
|
||||
ObjectId,
|
||||
NormalizeData,
|
||||
NormalizeDataUptimeDetails,
|
||||
});
|
||||
const notificationModule = new NotificationModule({ Notification, Monitor });
|
||||
const recoveryModule = new RecoveryModule({ User, RecoveryToken, crypto, stringService });
|
||||
const settingsModule = new SettingsModule({ AppSettings });
|
||||
@@ -111,7 +145,6 @@ export const initializeServices = async ({ logger, envSettings, settingsService
|
||||
statusPageModule,
|
||||
userModule,
|
||||
maintenanceWindowModule,
|
||||
monitorModule,
|
||||
notificationModule,
|
||||
recoveryModule,
|
||||
settingsModule,
|
||||
@@ -196,12 +229,14 @@ export const initializeServices = async ({ logger, envSettings, settingsService
|
||||
jwt,
|
||||
errorService,
|
||||
jobQueue: superSimpleQueue,
|
||||
monitorsRepository,
|
||||
});
|
||||
const checkService = new CheckService({
|
||||
db,
|
||||
settingsService,
|
||||
stringService,
|
||||
errorService,
|
||||
monitorsRepository,
|
||||
});
|
||||
const diagnosticService = new DiagnosticService();
|
||||
const inviteService = new InviteService({
|
||||
@@ -216,6 +251,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService
|
||||
settingsService,
|
||||
stringService,
|
||||
errorService,
|
||||
monitorsRepository,
|
||||
});
|
||||
const monitorService = new MonitorService({
|
||||
jobQueue: superSimpleQueue,
|
||||
@@ -252,6 +288,12 @@ export const initializeServices = async ({ logger, envSettings, settingsService
|
||||
incidentService,
|
||||
errorService,
|
||||
logger,
|
||||
|
||||
// Repositories
|
||||
monitorsRepository,
|
||||
checksRepository,
|
||||
monitorStatsRepository,
|
||||
statusPagesRepository,
|
||||
};
|
||||
|
||||
Object.values(services).forEach((service) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Request, Response, NextFunction } from "express";
|
||||
|
||||
import { createNotificationBodyValidation } from "@/validation/joi.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { IMonitorsRepository } from "@/repositories/index.js";
|
||||
|
||||
const SERVICE_NAME = "NotificationController";
|
||||
|
||||
@@ -9,9 +10,11 @@ class NotificationController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private db: any;
|
||||
private notificationService: any;
|
||||
constructor(notificationService: any, db: any) {
|
||||
private monitorsRepository: IMonitorsRepository;
|
||||
constructor(notificationService: any, db: any, monitorsRepository: IMonitorsRepository) {
|
||||
this.notificationService = notificationService;
|
||||
this.db = db;
|
||||
this.monitorsRepository = monitorsRepository;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
@@ -167,11 +170,7 @@ class NotificationController {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
|
||||
const monitor = await this.db.monitorModule.getMonitorById(monitorId);
|
||||
|
||||
if (!monitor.teamId.equals(teamId)) {
|
||||
throw new AppError({ message: "Unauthorized", status: 403 });
|
||||
}
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
|
||||
const notifications = monitor.notifications;
|
||||
if (notifications.length === 0) {
|
||||
|
||||
@@ -12,7 +12,6 @@ class MongoDB {
|
||||
statusPageModule,
|
||||
userModule,
|
||||
maintenanceWindowModule,
|
||||
monitorModule,
|
||||
notificationModule,
|
||||
recoveryModule,
|
||||
settingsModule,
|
||||
@@ -25,7 +24,6 @@ class MongoDB {
|
||||
this.recoveryModule = recoveryModule;
|
||||
this.checkModule = checkModule;
|
||||
this.maintenanceWindowModule = maintenanceWindowModule;
|
||||
this.monitorModule = monitorModule;
|
||||
this.notificationModule = notificationModule;
|
||||
this.settingsModule = settingsModule;
|
||||
this.statusPageModule = statusPageModule;
|
||||
|
||||
@@ -1,539 +0,0 @@
|
||||
import {
|
||||
buildUptimeDetailsPipeline,
|
||||
buildMonitorSummaryByTeamIdPipeline,
|
||||
buildMonitorsByTeamIdPipeline,
|
||||
buildMonitorsAndSummaryByTeamIdPipeline,
|
||||
buildMonitorsWithChecksByTeamIdPipeline,
|
||||
buildFilteredMonitorsByTeamIdPipeline,
|
||||
getHardwareStats,
|
||||
getUpChecks,
|
||||
getAggregateData,
|
||||
} from "./monitorModuleQueries.js";
|
||||
|
||||
import { CheckModel } from "@/db/models/index.js";
|
||||
|
||||
const SERVICE_NAME = "monitorModule";
|
||||
|
||||
class MonitorModule {
|
||||
constructor({ Monitor, MonitorStats, stringService, fs, path, fileURLToPath, ObjectId, NormalizeData, NormalizeDataUptimeDetails }) {
|
||||
this.Monitor = Monitor;
|
||||
this.MonitorStats = MonitorStats;
|
||||
this.stringService = stringService;
|
||||
this.fs = fs;
|
||||
this.path = path;
|
||||
this.fileURLToPath = fileURLToPath;
|
||||
this.ObjectId = ObjectId;
|
||||
this.NormalizeData = NormalizeData;
|
||||
this.NormalizeDataUptimeDetails = NormalizeDataUptimeDetails;
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
this.demoMonitorsPath = path.resolve(__dirname, "../../../utils/demoMonitors.json");
|
||||
}
|
||||
|
||||
// Helper
|
||||
calculateUptimeDuration = (checks) => {
|
||||
if (!checks || checks.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const latestCheck = new Date(checks[0].createdAt);
|
||||
let latestDownCheck = 0;
|
||||
|
||||
for (let i = checks.length - 1; i >= 0; i--) {
|
||||
if (checks[i].status === false) {
|
||||
latestDownCheck = new Date(checks[i].createdAt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no down check is found, uptime is from the last check to now
|
||||
if (latestDownCheck === 0) {
|
||||
return Date.now() - new Date(checks[checks.length - 1].createdAt);
|
||||
}
|
||||
|
||||
// Otherwise the uptime is from the last check to the last down check
|
||||
return latestCheck - latestDownCheck;
|
||||
};
|
||||
|
||||
// Helper
|
||||
getLastChecked = (checks) => {
|
||||
if (!checks || checks.length === 0) {
|
||||
return 0; // Handle case when no checks are available
|
||||
}
|
||||
// Data is sorted newest->oldest, so last check is the most recent
|
||||
return new Date() - new Date(checks[0].createdAt);
|
||||
};
|
||||
getLatestResponseTime = (checks) => {
|
||||
if (!checks || checks.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return checks[0]?.responseTime ?? 0;
|
||||
};
|
||||
|
||||
// Helper
|
||||
getAverageResponseTime = (checks) => {
|
||||
if (!checks || checks.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const validChecks = checks.filter((check) => typeof check.responseTime === "number");
|
||||
if (validChecks.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const aggResponseTime = validChecks.reduce((sum, check) => {
|
||||
return sum + check.responseTime;
|
||||
}, 0);
|
||||
return aggResponseTime / validChecks.length;
|
||||
};
|
||||
|
||||
// Helper
|
||||
getUptimePercentage = (checks) => {
|
||||
if (!checks || checks.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const upCount = checks.reduce((count, check) => {
|
||||
return check.status === true ? count + 1 : count;
|
||||
}, 0);
|
||||
return (upCount / checks.length) * 100;
|
||||
};
|
||||
|
||||
// Helper
|
||||
getIncidents = (checks) => {
|
||||
if (!checks || checks.length === 0) {
|
||||
return 0; // Handle case when no checks are available
|
||||
}
|
||||
return checks.reduce((acc, check) => {
|
||||
return check.status === false ? (acc += 1) : acc;
|
||||
}, 0);
|
||||
};
|
||||
|
||||
// Helper
|
||||
getDateRange = (dateRange) => {
|
||||
const startDates = {
|
||||
recent: new Date(new Date().setHours(new Date().getHours() - 2)),
|
||||
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: new Date(0),
|
||||
};
|
||||
return {
|
||||
start: startDates[dateRange],
|
||||
end: new Date(),
|
||||
};
|
||||
};
|
||||
|
||||
//Helper
|
||||
getMonitorChecks = async (monitorId, dateRange, sortOrder) => {
|
||||
const objectId = new this.ObjectId(monitorId);
|
||||
const indexSpec = {
|
||||
"metadata.monitorId": 1,
|
||||
updatedAt: sortOrder,
|
||||
};
|
||||
|
||||
const matchBase = { "metadata.monitorId": objectId };
|
||||
|
||||
const [checksAll, checksForDateRange] = await Promise.all([
|
||||
CheckModel.find(matchBase).sort({ createdAt: sortOrder }).hint(indexSpec).lean(),
|
||||
CheckModel.find({
|
||||
...matchBase,
|
||||
createdAt: { $gte: dateRange.start, $lte: dateRange.end },
|
||||
})
|
||||
.hint(indexSpec)
|
||||
.lean(),
|
||||
]);
|
||||
|
||||
return { checksAll, checksForDateRange };
|
||||
};
|
||||
|
||||
// Helper
|
||||
processChecksForDisplay = (normalizeData, checks, numToDisplay, normalize) => {
|
||||
let processedChecks = checks;
|
||||
if (numToDisplay && checks.length > numToDisplay) {
|
||||
const n = Math.ceil(checks.length / numToDisplay);
|
||||
processedChecks = checks.filter((_, index) => index % n === 0);
|
||||
}
|
||||
return normalize ? normalizeData(processedChecks, 1, 100) : processedChecks;
|
||||
};
|
||||
|
||||
// Helper
|
||||
groupChecksByTime = (checks, dateRange) => {
|
||||
return checks.reduce((acc, check) => {
|
||||
// Validate the date
|
||||
const checkDate = new Date(check.createdAt);
|
||||
if (Number.isNaN(checkDate.getTime()) || checkDate.getTime() === 0) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const time = dateRange === "day" ? checkDate.setMinutes(0, 0, 0) : checkDate.toISOString().split("T")[0];
|
||||
|
||||
if (!acc[time]) {
|
||||
acc[time] = { time, checks: [] };
|
||||
}
|
||||
acc[time].checks.push(check);
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
// Helper
|
||||
calculateGroupStats = (group) => {
|
||||
const totalChecks = group.checks.length;
|
||||
|
||||
const checksWithResponseTime = group.checks.filter((check) => typeof check.responseTime === "number" && !Number.isNaN(check.responseTime));
|
||||
|
||||
return {
|
||||
time: group.time,
|
||||
uptimePercentage: this.getUptimePercentage(group.checks),
|
||||
totalChecks,
|
||||
totalIncidents: group.checks.filter((check) => !check.status).length,
|
||||
avgResponseTime:
|
||||
checksWithResponseTime.length > 0
|
||||
? checksWithResponseTime.reduce((sum, check) => sum + check.responseTime, 0) / checksWithResponseTime.length
|
||||
: 0,
|
||||
};
|
||||
};
|
||||
|
||||
getMonitorById = async (monitorId) => {
|
||||
try {
|
||||
const monitor = await this.Monitor.findById(monitorId);
|
||||
if (monitor === null || monitor === undefined) {
|
||||
const error = new Error(this.stringService.getDbFindMonitorById(monitorId));
|
||||
error.status = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return monitor;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getMonitorById";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
getMonitorsByIds = async (monitorIds) => {
|
||||
try {
|
||||
const objectIds = monitorIds.map((id) => new this.ObjectId(id));
|
||||
return await this.Monitor.find({ _id: { $in: objectIds } }, { _id: 1, teamId: 1 }).lean();
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getMonitorsByIds";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
getUptimeDetailsById = async ({ monitorId, dateRange }) => {
|
||||
try {
|
||||
const dates = this.getDateRange(dateRange);
|
||||
const formatLookup = {
|
||||
recent: "%Y-%m-%dT%H:%M:00Z",
|
||||
day: "%Y-%m-%dT%H:00:00Z",
|
||||
week: "%Y-%m-%dT00:00:00Z",
|
||||
month: "%Y-%m-%dT00:00:00Z",
|
||||
};
|
||||
|
||||
const dateString = formatLookup[dateRange];
|
||||
|
||||
const results = await CheckModel.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString));
|
||||
|
||||
const monitorData = results[0];
|
||||
|
||||
monitorData.groupedUpChecks = this.NormalizeDataUptimeDetails(monitorData.groupedUpChecks, 10, 100);
|
||||
|
||||
monitorData.groupedDownChecks = this.NormalizeDataUptimeDetails(monitorData.groupedDownChecks, 10, 100);
|
||||
|
||||
const normalizedGroupChecks = this.NormalizeDataUptimeDetails(monitorData.groupedChecks, 10, 100);
|
||||
|
||||
monitorData.groupedChecks = normalizedGroupChecks;
|
||||
const monitorStats = await this.MonitorStats.findOne({ monitorId });
|
||||
return { monitorData, monitorStats };
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getUptimeDetailsById";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
getHardwareDetailsById = async ({ monitorId, dateRange }) => {
|
||||
try {
|
||||
const monitor = await this.Monitor.findById(monitorId);
|
||||
const dates = this.getDateRange(dateRange);
|
||||
|
||||
const formatLookup = {
|
||||
recent: "%Y-%m-%dT%H:%M:00Z",
|
||||
day: "%Y-%m-%dT%H:00:00Z",
|
||||
week: "%Y-%m-%dT00:00:00Z",
|
||||
month: "%Y-%m-%dT00:00:00Z",
|
||||
};
|
||||
const dateString = formatLookup[dateRange];
|
||||
|
||||
const [aggregateData, upChecksCount, metrics] = await Promise.all([
|
||||
getAggregateData(monitorId, dates),
|
||||
getUpChecks(monitorId, dates),
|
||||
getHardwareStats(monitorId, dates, dateString),
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
aggregateData: aggregateData,
|
||||
upChecks: upChecksCount,
|
||||
checks: metrics,
|
||||
};
|
||||
|
||||
return {
|
||||
...monitor.toObject(),
|
||||
stats,
|
||||
};
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getHardwareDetailsById";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
getMonitorsByTeamId = async ({ teamId, type, filter }) => {
|
||||
try {
|
||||
const matchStage = { teamId: new this.ObjectId(teamId) };
|
||||
if (type !== undefined) {
|
||||
matchStage.type = Array.isArray(type) ? { $in: type } : type;
|
||||
}
|
||||
if (filter !== undefined && filter !== null && filter !== "") {
|
||||
matchStage.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }];
|
||||
}
|
||||
const monitors = await this.Monitor.find(matchStage)
|
||||
.sort({ name: 1 })
|
||||
.select({
|
||||
_id: 1,
|
||||
name: 1,
|
||||
type: 1,
|
||||
url: 1,
|
||||
status: 1,
|
||||
isActive: 1,
|
||||
teamId: 1,
|
||||
})
|
||||
.lean();
|
||||
return monitors;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getMonitorsByTeamId";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
findMonitorsSummaryByTeamId = async ({ type, teamId, explain }) => {
|
||||
try {
|
||||
const matchStage = { teamId: new this.ObjectId(teamId) };
|
||||
if (type !== undefined) {
|
||||
matchStage.type = Array.isArray(type) ? { $in: type } : type;
|
||||
}
|
||||
|
||||
const pipeline = buildMonitorsSummaryByTeamIdPipeline({ matchStage });
|
||||
if (explain === true) {
|
||||
return this.Monitor.aggregate(pipeline).explain("executionStats");
|
||||
}
|
||||
const [summary] = await this.Monitor.aggregate(pipeline);
|
||||
return summary ?? { totalMonitors: 0, upMonitors: 0, downMonitors: 0, pausedMonitors: 0 };
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "findMonitorsSummaryByTeamId";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
getMonitorsWithChecksByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId, explain }) => {
|
||||
try {
|
||||
limit = parseInt(limit);
|
||||
page = parseInt(page);
|
||||
rowsPerPage = parseInt(rowsPerPage);
|
||||
if (field === undefined) {
|
||||
field = "name";
|
||||
order = "asc";
|
||||
}
|
||||
// Build match stage
|
||||
const matchStage = { teamId: new this.ObjectId(teamId) };
|
||||
if (type !== undefined) {
|
||||
matchStage.type = Array.isArray(type) ? { $in: type } : type;
|
||||
}
|
||||
|
||||
if (explain === true) {
|
||||
return this.Monitor.aggregate(
|
||||
buildMonitorsWithChecksByTeamIdPipeline({
|
||||
matchStage,
|
||||
filter,
|
||||
page,
|
||||
rowsPerPage,
|
||||
field,
|
||||
order,
|
||||
limit,
|
||||
type,
|
||||
})
|
||||
).explain("executionStats");
|
||||
}
|
||||
|
||||
const queryResult = await this.Monitor.aggregate(
|
||||
buildMonitorsWithChecksByTeamIdPipeline({
|
||||
matchStage,
|
||||
filter,
|
||||
page,
|
||||
rowsPerPage,
|
||||
field,
|
||||
order,
|
||||
limit,
|
||||
type,
|
||||
})
|
||||
);
|
||||
const monitors = queryResult[0]?.monitors;
|
||||
const count = queryResult[0]?.count;
|
||||
const normalizedFilteredMonitors = monitors.map((monitor) => {
|
||||
if (!monitor.checks) {
|
||||
return monitor;
|
||||
}
|
||||
monitor.checks = this.NormalizeData(monitor.checks, 10, 100);
|
||||
return monitor;
|
||||
});
|
||||
return { count, monitors: normalizedFilteredMonitors };
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getMonitorsWithChecksByTeamId";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
createMonitor = async ({ body, teamId, userId }) => {
|
||||
try {
|
||||
const monitor = new this.Monitor({ ...body, teamId, userId });
|
||||
const saved = await monitor.save();
|
||||
return saved;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "createMonitor";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
createBulkMonitors = async (req) => {
|
||||
try {
|
||||
const monitors = req.map((item) => new this.Monitor({ ...item, notifications: undefined }));
|
||||
await this.Monitor.bulkSave(monitors);
|
||||
return monitors;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "createBulkMonitors";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
deleteMonitor = async ({ teamId, monitorId }) => {
|
||||
try {
|
||||
const deletedMonitor = await this.Monitor.findOneAndDelete({ _id: monitorId, teamId });
|
||||
|
||||
if (!deletedMonitor) {
|
||||
throw new Error(this.stringService.getDbFindMonitorById(monitorId));
|
||||
}
|
||||
|
||||
return deletedMonitor;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "deleteMonitor";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
deleteAllMonitors = async (teamId) => {
|
||||
try {
|
||||
const monitors = await this.Monitor.find({ teamId });
|
||||
const { deletedCount } = await this.Monitor.deleteMany({ teamId });
|
||||
|
||||
return { monitors, deletedCount };
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "deleteAllMonitors";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
deleteMonitorsByUserId = async (userId) => {
|
||||
try {
|
||||
const result = await this.Monitor.deleteMany({ userId: userId });
|
||||
return result;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "deleteMonitorsByUserId";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
editMonitor = async ({ monitorId, body }) => {
|
||||
try {
|
||||
const editedMonitor = await this.Monitor.findByIdAndUpdate(monitorId, body, {
|
||||
new: true,
|
||||
});
|
||||
return editedMonitor;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "editMonitor";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
addDemoMonitors = async (userId, teamId) => {
|
||||
try {
|
||||
const demoMonitors = JSON.parse(this.fs.readFileSync(this.demoMonitorsPath, "utf8"));
|
||||
|
||||
const demoMonitorsToInsert = demoMonitors.map((monitor) => {
|
||||
return {
|
||||
userId,
|
||||
teamId,
|
||||
name: monitor.name,
|
||||
description: monitor.name,
|
||||
type: "http",
|
||||
url: monitor.url,
|
||||
interval: 60000,
|
||||
};
|
||||
});
|
||||
const insertedMonitors = await this.Monitor.insertMany(demoMonitorsToInsert);
|
||||
return insertedMonitors;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "addDemoMonitors";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
pauseMonitor = async ({ monitorId }) => {
|
||||
try {
|
||||
const monitor = await this.Monitor.findOneAndUpdate(
|
||||
{ _id: monitorId },
|
||||
[
|
||||
{
|
||||
$set: {
|
||||
isActive: { $not: "$isActive" },
|
||||
status: "$$REMOVE",
|
||||
},
|
||||
},
|
||||
],
|
||||
{ new: true }
|
||||
);
|
||||
|
||||
return monitor;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "pauseMonitor";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
getGroupsByTeamId = async ({ teamId }) => {
|
||||
try {
|
||||
const groups = await this.Monitor.distinct("group", {
|
||||
teamId: new this.ObjectId(teamId),
|
||||
group: { $ne: null, $ne: "" },
|
||||
});
|
||||
|
||||
return groups.filter(Boolean).sort();
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getGroupsByTeamId";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default MonitorModule;
|
||||
@@ -1,740 +0,0 @@
|
||||
import { ObjectId } from "mongodb";
|
||||
import { CheckModel } from "@/db/models/index.js";
|
||||
|
||||
const buildUptimeDetailsPipeline = (monitorId, dates, dateString) => {
|
||||
return [
|
||||
{
|
||||
$match: {
|
||||
"metadata.monitorId": new ObjectId(monitorId),
|
||||
updatedAt: { $gte: dates.start, $lte: dates.end },
|
||||
},
|
||||
},
|
||||
{
|
||||
$sort: {
|
||||
updatedAt: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
$facet: {
|
||||
// For the response time chart, should return checks for date window
|
||||
// Grouped by: {day: hour}, {week: day}, {month: day}
|
||||
uptimePercentage: [
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
upChecks: {
|
||||
$sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] },
|
||||
},
|
||||
totalChecks: { $sum: 1 },
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
percentage: {
|
||||
$cond: [{ $eq: ["$totalChecks", 0] }, 0, { $divide: ["$upChecks", "$totalChecks"] }],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
groupedAvgResponseTime: [
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
avgResponseTime: {
|
||||
$avg: "$responseTime",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
groupedChecks: [
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
$dateToString: {
|
||||
format: dateString,
|
||||
date: "$createdAt",
|
||||
},
|
||||
},
|
||||
avgResponseTime: {
|
||||
$avg: "$responseTime",
|
||||
},
|
||||
totalChecks: {
|
||||
$sum: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$sort: {
|
||||
_id: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
// Up checks grouped by: {day: hour}, {week: day}, {month: day}
|
||||
groupedUpChecks: [
|
||||
{
|
||||
$match: {
|
||||
status: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
$dateToString: {
|
||||
format: dateString,
|
||||
date: "$createdAt",
|
||||
},
|
||||
},
|
||||
totalChecks: {
|
||||
$sum: 1,
|
||||
},
|
||||
avgResponseTime: {
|
||||
$avg: "$responseTime",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$sort: { _id: 1 },
|
||||
},
|
||||
],
|
||||
// Down checks grouped by: {day: hour}, {week: day}, {month: day} for the date window
|
||||
groupedDownChecks: [
|
||||
{
|
||||
$match: {
|
||||
status: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
$dateToString: {
|
||||
format: dateString,
|
||||
date: "$createdAt",
|
||||
},
|
||||
},
|
||||
totalChecks: {
|
||||
$sum: 1,
|
||||
},
|
||||
avgResponseTime: {
|
||||
$avg: "$responseTime",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$sort: { _id: 1 },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: "monitors",
|
||||
let: { monitor_id: { $toObjectId: monitorId } },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: { $eq: ["$_id", "$$monitor_id"] },
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
teamId: 1,
|
||||
name: 1,
|
||||
status: 1,
|
||||
interval: 1,
|
||||
type: 1,
|
||||
url: 1,
|
||||
isActive: 1,
|
||||
notifications: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
as: "monitor",
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
groupedAvgResponseTime: {
|
||||
$arrayElemAt: ["$groupedAvgResponseTime.avgResponseTime", 0],
|
||||
},
|
||||
|
||||
groupedChecks: "$groupedChecks",
|
||||
groupedUpChecks: "$groupedUpChecks",
|
||||
groupedDownChecks: "$groupedDownChecks",
|
||||
groupedUptimePercentage: { $arrayElemAt: ["$uptimePercentage.percentage", 0] },
|
||||
monitor: { $arrayElemAt: ["$monitor", 0] },
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const buildMonitorStatsPipeline = (monitor) => {
|
||||
return [
|
||||
{
|
||||
$match: {
|
||||
monitorId: monitor._id,
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
avgResponseTime: 1,
|
||||
uptimePercentage: 1,
|
||||
totalChecks: 1,
|
||||
timeSinceLastCheck: {
|
||||
$subtract: [Date.now(), "$lastCheckTimestamp"],
|
||||
},
|
||||
lastCheckTimestamp: 1,
|
||||
uptBurnt: { $toString: "$uptBurnt" },
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const buildMonitorSummaryByTeamIdPipeline = ({ matchStage }) => {
|
||||
return [
|
||||
{ $match: matchStage },
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
totalMonitors: { $sum: 1 },
|
||||
upMonitors: {
|
||||
$sum: {
|
||||
$cond: [{ $eq: ["$status", true] }, 1, 0],
|
||||
},
|
||||
},
|
||||
downMonitors: {
|
||||
$sum: {
|
||||
$cond: [{ $eq: ["$status", false] }, 1, 0],
|
||||
},
|
||||
},
|
||||
pausedMonitors: {
|
||||
$sum: {
|
||||
$cond: [{ $eq: ["$isActive", false] }, 1, 0],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const buildMonitorsByTeamIdPipeline = ({ matchStage, field, order }) => {
|
||||
const sort = { [field]: order === "asc" ? 1 : -1 };
|
||||
|
||||
return [
|
||||
{ $match: matchStage },
|
||||
{ $sort: sort },
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
name: 1,
|
||||
type: 1,
|
||||
port: 1,
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const buildMonitorsAndSummaryByTeamIdPipeline = ({ matchStage }) => {
|
||||
return [
|
||||
{ $match: matchStage },
|
||||
{
|
||||
$facet: {
|
||||
summary: [
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
totalMonitors: { $sum: 1 },
|
||||
upMonitors: {
|
||||
$sum: {
|
||||
$cond: [{ $eq: ["$status", true] }, 1, 0],
|
||||
},
|
||||
},
|
||||
downMonitors: {
|
||||
$sum: {
|
||||
$cond: [{ $eq: ["$status", false] }, 1, 0],
|
||||
},
|
||||
},
|
||||
pausedMonitors: {
|
||||
$sum: {
|
||||
$cond: [{ $eq: ["$isActive", false] }, 1, 0],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
monitors: [
|
||||
{ $sort: { name: 1 } },
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
name: 1,
|
||||
type: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
summary: { $arrayElemAt: ["$summary", 0] },
|
||||
monitors: 1,
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const buildMonitorsWithChecksByTeamIdPipeline = ({ matchStage, filter, page, rowsPerPage, field, order, limit, type }) => {
|
||||
const skip = page && rowsPerPage ? page * rowsPerPage : 0;
|
||||
const sort = { [field]: order === "asc" ? 1 : -1 };
|
||||
const limitStage = rowsPerPage ? [{ $limit: rowsPerPage }] : [];
|
||||
|
||||
// Match name
|
||||
if (typeof filter !== "undefined" && field === "name") {
|
||||
matchStage.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }];
|
||||
}
|
||||
|
||||
// Match isActive
|
||||
if (typeof filter !== "undefined" && field === "isActive") {
|
||||
matchStage.isActive = filter === "true" ? true : false;
|
||||
}
|
||||
|
||||
if (typeof filter !== "undefined" && field === "status") {
|
||||
matchStage.status = filter === "true" ? true : false;
|
||||
}
|
||||
|
||||
// Match type
|
||||
if (typeof filter !== "undefined" && field === "type") {
|
||||
matchStage.type = filter;
|
||||
}
|
||||
|
||||
const monitorsPipeline = [
|
||||
{ $sort: sort },
|
||||
{ $skip: skip },
|
||||
...limitStage,
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
name: 1,
|
||||
description: 1,
|
||||
type: 1,
|
||||
url: 1,
|
||||
isActive: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
uptimePercentage: 1,
|
||||
status: 1,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Add checks
|
||||
if (limit) {
|
||||
const checksCollection = "checks";
|
||||
monitorsPipeline.push({
|
||||
$lookup: {
|
||||
from: checksCollection,
|
||||
let: { monitorId: "$_id" },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: { $eq: ["$monitorId", "$$monitorId"] },
|
||||
},
|
||||
},
|
||||
{ $sort: { updatedAt: -1 } },
|
||||
{ $limit: limit },
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
status: 1,
|
||||
responseTime: 1,
|
||||
statusCode: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
originalResponseTime: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
as: "checks",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const pipeline = [
|
||||
{ $match: matchStage },
|
||||
{
|
||||
$facet: {
|
||||
count: [{ $count: "monitorsCount" }],
|
||||
monitors: monitorsPipeline,
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
count: { $arrayElemAt: ["$count", 0] },
|
||||
monitors: 1,
|
||||
},
|
||||
},
|
||||
];
|
||||
return pipeline;
|
||||
};
|
||||
|
||||
const buildFilteredMonitorsByTeamIdPipeline = ({ matchStage, filter, page, rowsPerPage, field, order, limit, type }) => {
|
||||
const skip = page && rowsPerPage ? page * rowsPerPage : 0;
|
||||
const sort = { [field]: order === "asc" ? 1 : -1 };
|
||||
const limitStage = rowsPerPage ? [{ $limit: rowsPerPage }] : [];
|
||||
|
||||
if (typeof filter !== "undefined" && field === "name") {
|
||||
matchStage.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }];
|
||||
}
|
||||
|
||||
if (typeof filter !== "undefined" && field === "status") {
|
||||
matchStage.status = filter === "true";
|
||||
}
|
||||
|
||||
const pipeline = [{ $match: matchStage }, { $sort: sort }, { $skip: skip }, ...limitStage];
|
||||
|
||||
// Add checks
|
||||
if (limit) {
|
||||
const checksCollection = "checks";
|
||||
|
||||
pipeline.push({
|
||||
$lookup: {
|
||||
from: checksCollection,
|
||||
let: { monitorId: "$_id" },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: { $eq: ["$monitorId", "$$monitorId"] },
|
||||
},
|
||||
},
|
||||
{ $sort: { createdAt: -1 } },
|
||||
{ $limit: limit },
|
||||
],
|
||||
as: "checks",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return pipeline;
|
||||
};
|
||||
|
||||
const buildGetMonitorsByTeamIdPipeline = (req) => {
|
||||
let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
|
||||
|
||||
limit = parseInt(limit);
|
||||
page = parseInt(page);
|
||||
rowsPerPage = parseInt(rowsPerPage);
|
||||
if (field === undefined) {
|
||||
field = "name";
|
||||
order = "asc";
|
||||
}
|
||||
// Build the match stage
|
||||
const matchStage = { teamId: new ObjectId(req.params.teamId) };
|
||||
if (type !== undefined) {
|
||||
matchStage.type = Array.isArray(type) ? { $in: type } : type;
|
||||
}
|
||||
|
||||
const skip = page && rowsPerPage ? page * rowsPerPage : 0;
|
||||
const sort = { [field]: order === "asc" ? 1 : -1 };
|
||||
return [
|
||||
{ $match: matchStage },
|
||||
{
|
||||
$facet: {
|
||||
summary: [
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
totalMonitors: { $sum: 1 },
|
||||
upMonitors: {
|
||||
$sum: {
|
||||
$cond: [{ $eq: ["$status", true] }, 1, 0],
|
||||
},
|
||||
},
|
||||
downMonitors: {
|
||||
$sum: {
|
||||
$cond: [{ $eq: ["$status", false] }, 1, 0],
|
||||
},
|
||||
},
|
||||
pausedMonitors: {
|
||||
$sum: {
|
||||
$cond: [{ $eq: ["$isActive", false] }, 1, 0],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
monitors: [
|
||||
{ $sort: sort },
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
name: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
filteredMonitors: [
|
||||
...(filter !== undefined
|
||||
? [
|
||||
{
|
||||
$match: {
|
||||
$or: [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }],
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ $sort: sort },
|
||||
{ $skip: skip },
|
||||
...(rowsPerPage ? [{ $limit: rowsPerPage }] : []),
|
||||
...(limit
|
||||
? [
|
||||
{
|
||||
$lookup: {
|
||||
from: "checks",
|
||||
let: { monitorId: "$_id" },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: { $eq: ["$monitorId", "$$monitorId"] },
|
||||
},
|
||||
},
|
||||
{ $sort: { createdAt: -1 } },
|
||||
...(limit ? [{ $limit: limit }] : []),
|
||||
],
|
||||
as: "standardchecks",
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
{
|
||||
$addFields: {
|
||||
checks: {
|
||||
$switch: {
|
||||
branches: [
|
||||
{
|
||||
case: { $in: ["$type", ["http", "ping", "docker", "port", "game"]] },
|
||||
then: "$standardchecks",
|
||||
},
|
||||
],
|
||||
default: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
standardchecks: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
summary: { $arrayElemAt: ["$summary", 0] },
|
||||
filteredMonitors: 1,
|
||||
monitors: 1,
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export {
|
||||
buildUptimeDetailsPipeline,
|
||||
buildMonitorStatsPipeline,
|
||||
buildGetMonitorsByTeamIdPipeline,
|
||||
buildMonitorSummaryByTeamIdPipeline,
|
||||
buildMonitorsByTeamIdPipeline,
|
||||
buildMonitorsAndSummaryByTeamIdPipeline,
|
||||
buildMonitorsWithChecksByTeamIdPipeline,
|
||||
buildFilteredMonitorsByTeamIdPipeline,
|
||||
};
|
||||
|
||||
export const getAggregateData = async (monitorId, dates) => {
|
||||
const result = await CheckModel.aggregate([
|
||||
{
|
||||
$match: {
|
||||
"metadata.monitorId": new ObjectId(monitorId),
|
||||
"metadata.type": "hardware",
|
||||
createdAt: { $gte: dates.start, $lte: dates.end },
|
||||
},
|
||||
},
|
||||
{ $sort: { createdAt: -1 } },
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
latestCheck: { $first: "$$ROOT" },
|
||||
totalChecks: { $sum: 1 },
|
||||
},
|
||||
},
|
||||
]);
|
||||
return result[0] || { totalChecks: 0, latestCheck: null };
|
||||
};
|
||||
|
||||
export const getUpChecks = async (monitorId, dates) => {
|
||||
const count = await CheckModel.countDocuments({
|
||||
"metadata.monitorId": new ObjectId(monitorId),
|
||||
"metadata.type": "hardware",
|
||||
createdAt: { $gte: dates.start, $lte: dates.end },
|
||||
status: true,
|
||||
});
|
||||
return { totalChecks: count };
|
||||
};
|
||||
|
||||
export const getHardwareStats = async (monitorId, dates, dateString) => {
|
||||
return await CheckModel.aggregate([
|
||||
{
|
||||
$match: {
|
||||
"metadata.monitorId": new ObjectId(monitorId),
|
||||
"metadata.type": "hardware",
|
||||
createdAt: { $gte: dates.start, $lte: dates.end },
|
||||
},
|
||||
},
|
||||
{ $sort: { createdAt: 1 } },
|
||||
{
|
||||
$group: {
|
||||
_id: { $dateToString: { format: dateString, date: "$createdAt" } },
|
||||
avgCpuUsage: { $avg: "$cpu.usage_percent" },
|
||||
avgMemoryUsage: { $avg: "$memory.usage_percent" },
|
||||
avgTemperatures: { $push: { $ifNull: ["$cpu.temperature", [0]] } },
|
||||
disks: { $push: "$disk" },
|
||||
net: { $push: "$net" },
|
||||
updatedAts: { $push: "$updatedAt" },
|
||||
sampleDoc: { $first: "$$ROOT" },
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
avgCpuUsage: 1,
|
||||
avgMemoryUsage: 1,
|
||||
avgTemperature: {
|
||||
$map: {
|
||||
input: { $range: [0, { $size: { $ifNull: [{ $arrayElemAt: ["$avgTemperatures", 0] }, [0]] } }] },
|
||||
as: "idx",
|
||||
in: { $avg: { $map: { input: "$avgTemperatures", as: "t", in: { $arrayElemAt: ["$$t", "$$idx"] } } } },
|
||||
},
|
||||
},
|
||||
disks: {
|
||||
$map: {
|
||||
input: { $range: [0, { $size: { $ifNull: ["$sampleDoc.disk", []] } }] },
|
||||
as: "dIdx",
|
||||
in: {
|
||||
name: { $concat: ["disk", { $toString: "$$dIdx" }] },
|
||||
readSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.read_speed_bytes", "$$dIdx"] } } } },
|
||||
writeSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.write_speed_bytes", "$$dIdx"] } } } },
|
||||
totalBytes: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.total_bytes", "$$dIdx"] } } } },
|
||||
freeBytes: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.free_bytes", "$$dIdx"] } } } },
|
||||
usagePercent: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.usage_percent", "$$dIdx"] } } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
net: {
|
||||
$map: {
|
||||
input: { $range: [0, { $size: { $ifNull: ["$sampleDoc.net", []] } }] },
|
||||
as: "nIdx",
|
||||
in: {
|
||||
name: { $arrayElemAt: ["$sampleDoc.net.name", "$$nIdx"] },
|
||||
bytesSentPerSecond: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.bytes_sent" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.bytes_sent" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
deltaBytesRecv: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.bytes_recv" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.bytes_recv" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
deltaPacketsSent: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.packets_sent" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.packets_sent" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
deltaPacketsRecv: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.packets_recv" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.packets_recv" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
deltaErrIn: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.err_in" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.err_in" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
deltaErrOut: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.err_out" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.err_out" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
deltaDropIn: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.drop_in" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.drop_in" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
deltaDropOut: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.drop_out" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.drop_out" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ $sort: { _id: 1 } },
|
||||
]);
|
||||
};
|
||||
@@ -15,13 +15,11 @@ import type {
|
||||
} from "@/types/index.js";
|
||||
import { CheckModel, type CheckDocument } from "@/db/models/index.js";
|
||||
import mongoose from "mongoose";
|
||||
import {
|
||||
getAggregateData as getHardwareAggregateData,
|
||||
getHardwareStats,
|
||||
getUpChecks as getHardwareUpChecks,
|
||||
} from "@/db/modules/monitorModuleQueries.js";
|
||||
|
||||
export type LatestChecksMap = Record<string, Check[]>;
|
||||
type DateRange = { start: Date; end: Date };
|
||||
type HardwareAggregateData = { latestCheck: CheckDocument | null; totalChecks: number };
|
||||
type HardwareUpChecks = { totalChecks: number };
|
||||
|
||||
class MongoChecksRepository implements IChecksRepository {
|
||||
private toEntity = (doc: CheckDocument): Check => {
|
||||
@@ -323,9 +321,9 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
const monitorId = monitorObjectId.toHexString();
|
||||
const dates = { start: startDate, end: endDate };
|
||||
const [aggregateDataDoc, upChecksDoc, hardwareMetrics] = await Promise.all([
|
||||
getHardwareAggregateData(monitorId, dates),
|
||||
getHardwareUpChecks(monitorId, dates),
|
||||
getHardwareStats(monitorId, dates, dateString),
|
||||
this.getHardwareAggregateData(monitorId, dates),
|
||||
this.getHardwareUpChecks(monitorId, dates),
|
||||
this.getHardwareStats(monitorId, dates, dateString),
|
||||
]);
|
||||
|
||||
const aggregateData = {
|
||||
@@ -387,9 +385,183 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
};
|
||||
|
||||
deleteByMonitorId = async (monitorId: string): Promise<number> => {
|
||||
const result = await CheckModel.deleteMany({ "metadata.monitorId": monitorId });
|
||||
const result = await CheckModel.deleteMany({ "metadata.monitorId": new mongoose.Types.ObjectId(monitorId) });
|
||||
return result.deletedCount;
|
||||
};
|
||||
|
||||
private getHardwareAggregateData = async (monitorId: string, dates: DateRange): Promise<HardwareAggregateData> => {
|
||||
const result = await CheckModel.aggregate([
|
||||
{
|
||||
$match: {
|
||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||
"metadata.type": "hardware",
|
||||
createdAt: { $gte: dates.start, $lte: dates.end },
|
||||
},
|
||||
},
|
||||
{ $sort: { createdAt: -1 } },
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
latestCheck: { $first: "$$ROOT" },
|
||||
totalChecks: { $sum: 1 },
|
||||
},
|
||||
},
|
||||
]);
|
||||
return result[0] || { totalChecks: 0, latestCheck: null };
|
||||
};
|
||||
|
||||
private getHardwareUpChecks = async (monitorId: string, dates: DateRange): Promise<HardwareUpChecks> => {
|
||||
const count = await CheckModel.countDocuments({
|
||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||
"metadata.type": "hardware",
|
||||
createdAt: { $gte: dates.start, $lte: dates.end },
|
||||
status: true,
|
||||
});
|
||||
return { totalChecks: count };
|
||||
};
|
||||
|
||||
private getHardwareStats = async (monitorId: string, dates: DateRange, dateString: string) => {
|
||||
return await CheckModel.aggregate([
|
||||
{
|
||||
$match: {
|
||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||
"metadata.type": "hardware",
|
||||
createdAt: { $gte: dates.start, $lte: dates.end },
|
||||
},
|
||||
},
|
||||
{ $sort: { createdAt: 1 } },
|
||||
{
|
||||
$group: {
|
||||
_id: { $dateToString: { format: dateString, date: "$createdAt" } },
|
||||
avgCpuUsage: { $avg: "$cpu.usage_percent" },
|
||||
avgMemoryUsage: { $avg: "$memory.usage_percent" },
|
||||
avgTemperatures: { $push: { $ifNull: ["$cpu.temperature", [0]] } },
|
||||
disks: { $push: "$disk" },
|
||||
net: { $push: "$net" },
|
||||
updatedAts: { $push: "$updatedAt" },
|
||||
sampleDoc: { $first: "$$ROOT" },
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
avgCpuUsage: 1,
|
||||
avgMemoryUsage: 1,
|
||||
avgTemperature: {
|
||||
$map: {
|
||||
input: { $range: [0, { $size: { $ifNull: [{ $arrayElemAt: ["$avgTemperatures", 0] }, [0]] } }] },
|
||||
as: "idx",
|
||||
in: { $avg: { $map: { input: "$avgTemperatures", as: "t", in: { $arrayElemAt: ["$$t", "$$idx"] } } } },
|
||||
},
|
||||
},
|
||||
disks: {
|
||||
$map: {
|
||||
input: { $range: [0, { $size: { $ifNull: ["$sampleDoc.disk", []] } }] },
|
||||
as: "dIdx",
|
||||
in: {
|
||||
name: { $concat: ["disk", { $toString: "$$dIdx" }] },
|
||||
readSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.read_speed_bytes", "$$dIdx"] } } } },
|
||||
writeSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.write_speed_bytes", "$$dIdx"] } } } },
|
||||
totalBytes: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.total_bytes", "$$dIdx"] } } } },
|
||||
freeBytes: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.free_bytes", "$$dIdx"] } } } },
|
||||
usagePercent: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.usage_percent", "$$dIdx"] } } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
net: {
|
||||
$map: {
|
||||
input: { $range: [0, { $size: { $ifNull: ["$sampleDoc.net", []] } }] },
|
||||
as: "nIdx",
|
||||
in: {
|
||||
name: { $arrayElemAt: ["$sampleDoc.net.name", "$$nIdx"] },
|
||||
bytesSentPerSecond: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.bytes_sent" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.bytes_sent" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
deltaBytesRecv: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.bytes_recv" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.bytes_recv" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
deltaPacketsSent: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.packets_sent" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.packets_sent" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
deltaPacketsRecv: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.packets_recv" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.packets_recv" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
deltaErrIn: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.err_in" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.err_in" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
deltaErrOut: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.err_out" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.err_out" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
deltaDropIn: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.drop_in" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.drop_in" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
deltaDropOut: {
|
||||
$let: {
|
||||
vars: {
|
||||
tDiff: { $divide: [{ $subtract: [{ $last: "$updatedAts" }, { $first: "$updatedAts" }] }, 1000] },
|
||||
f: { $arrayElemAt: [{ $map: { input: { $first: "$net" }, as: "i", in: "$$i.drop_out" } }, "$$nIdx"] },
|
||||
l: { $arrayElemAt: [{ $map: { input: { $last: "$net" }, as: "i", in: "$$i.drop_out" } }, "$$nIdx"] },
|
||||
},
|
||||
in: { $cond: [{ $gt: ["$$tDiff", 0] }, { $divide: [{ $subtract: ["$$l", "$$f"] }, "$$tDiff"] }, 0] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ $sort: { _id: 1 } },
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
export default MongoChecksRepository;
|
||||
|
||||
@@ -19,11 +19,12 @@ export interface IMonitorsRepository {
|
||||
create(monitor: Monitor, teamId: string, userId: string): Promise<Monitor | null>;
|
||||
createBulkMonitors(monitors: Monitor[]): Promise<Monitor[]>;
|
||||
// single fetch
|
||||
findById(monitorId: string, teamId?: string): Promise<Monitor | null>;
|
||||
findById(monitorId: string, teamId: string): Promise<Monitor>;
|
||||
|
||||
// collection fetch
|
||||
findAll(): Promise<Monitor[] | null>;
|
||||
findByTeamId(teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null>;
|
||||
findByIds(monitorIds: string[]): Promise<Monitor[]>;
|
||||
|
||||
// update
|
||||
updateById(monitorId: string, teamId: string, updates: Partial<Monitor>): Promise<Monitor>;
|
||||
|
||||
@@ -21,19 +21,11 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
||||
return this.mapDocuments(inserted);
|
||||
};
|
||||
|
||||
findById = async (monitorId: string, teamId?: string): Promise<Monitor> => {
|
||||
const match: { _id: string; teamId?: string } = { _id: monitorId };
|
||||
if (teamId) {
|
||||
match.teamId = teamId;
|
||||
}
|
||||
findById = async (monitorId: string, teamId: string): Promise<Monitor> => {
|
||||
const match: { _id: string; teamId: string } = { _id: monitorId, teamId };
|
||||
const monitor = await MonitorModel.findOne(match);
|
||||
if (!monitor) {
|
||||
if (monitor === null || monitor === undefined) {
|
||||
throw new AppError({
|
||||
message: `Monitor with ID ${monitorId} not found.`,
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
throw new AppError({ message: `Monitor with ID ${monitorId} not found`, status: 404 });
|
||||
}
|
||||
return this.toEntity(monitor);
|
||||
};
|
||||
@@ -81,6 +73,12 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
||||
return this.mapDocuments(documents);
|
||||
};
|
||||
|
||||
findByIds = async (monitorIds: string[]): Promise<Monitor[]> => {
|
||||
const objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id));
|
||||
const monitors = await MonitorModel.find({ _id: { $in: objectIds } });
|
||||
return this.mapDocuments(monitors);
|
||||
};
|
||||
|
||||
findMonitorCountByTeamIdAndType = async (teamId: string, config?: TeamQueryConfig): Promise<number> => {
|
||||
const { type } = config ?? {};
|
||||
|
||||
|
||||
@@ -1,47 +1,60 @@
|
||||
import { IStatusPagesRepository } from "@/repositories/index.js";
|
||||
import { type StatusPageDocument, StatusPageModel } from "@/db/models/StatusPage.js";
|
||||
import type { StatusPage } from "@/types/statusPage.js";
|
||||
import type { StatusPage, StatusPageLogo } 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() ?? "";
|
||||
};
|
||||
private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value);
|
||||
};
|
||||
|
||||
const toDateString = (value: Date | string): string => {
|
||||
return value instanceof Date ? value.toISOString() : value;
|
||||
};
|
||||
private toDateString = (value?: Date | string | null): string => {
|
||||
if (!value) {
|
||||
return new Date(0).toISOString();
|
||||
}
|
||||
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
||||
};
|
||||
|
||||
const mapIdArray = (values?: mongoose.Types.ObjectId[]): string[] => {
|
||||
return values?.map((value) => toStringId(value)) ?? [];
|
||||
};
|
||||
private mapIdArray = (values?: Array<mongoose.Types.ObjectId | string>): string[] => {
|
||||
return values?.map((value) => this.toStringId(value)) ?? [];
|
||||
};
|
||||
|
||||
private mapLogo = (logo?: StatusPageLogo | null): StatusPageLogo | undefined => {
|
||||
if (!logo) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
id: toStringId(doc._id),
|
||||
userId: toStringId(doc.userId),
|
||||
teamId: toStringId(doc.teamId),
|
||||
data: logo.data,
|
||||
contentType: logo.contentType,
|
||||
};
|
||||
};
|
||||
|
||||
private toEntity = (doc: StatusPageDocument): StatusPage => {
|
||||
return {
|
||||
id: this.toStringId(doc._id),
|
||||
userId: this.toStringId(doc.userId),
|
||||
teamId: this.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,
|
||||
monitors: this.mapIdArray(doc.monitors),
|
||||
subMonitors: this.mapIdArray(doc.subMonitors),
|
||||
originalMonitors: this.mapIdArray(doc.originalMonitors),
|
||||
logo: this.mapLogo(doc.logo),
|
||||
isPublished: doc.isPublished,
|
||||
showCharts: doc.showCharts,
|
||||
showUptimePercentage: doc.showUptimePercentage,
|
||||
showAdminLoginLink: doc.showAdminLoginLink,
|
||||
customCSS: doc.customCSS,
|
||||
createdAt: toDateString(doc.createdAt),
|
||||
updatedAt: toDateString(doc.updatedAt),
|
||||
createdAt: this.toDateString(doc.createdAt),
|
||||
updatedAt: this.toDateString(doc.updatedAt),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
removeMonitorFromStatusPages = async (monitorId: string): Promise<number> => {
|
||||
const res = await StatusPageModel.updateMany({ monitors: monitorId }, { $pull: { monitors: monitorId } });
|
||||
|
||||
+36
-35
@@ -1,20 +1,41 @@
|
||||
import { IMonitorsRepository } from "@/repositories/index.js";
|
||||
|
||||
const SERVICE_NAME = "checkService";
|
||||
|
||||
class CheckService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor({ db, settingsService, stringService, errorService }) {
|
||||
private db: any;
|
||||
private settingsService: any;
|
||||
private stringService: any;
|
||||
private errorService: any;
|
||||
private monitorsRepository: IMonitorsRepository;
|
||||
|
||||
constructor({
|
||||
db,
|
||||
settingsService,
|
||||
stringService,
|
||||
errorService,
|
||||
monitorsRepository,
|
||||
}: {
|
||||
db: any;
|
||||
settingsService: any;
|
||||
stringService: any;
|
||||
errorService: any;
|
||||
monitorsRepository: IMonitorsRepository;
|
||||
}) {
|
||||
this.db = db;
|
||||
this.settingsService = settingsService;
|
||||
this.stringService = stringService;
|
||||
this.errorService = errorService;
|
||||
this.monitorsRepository = monitorsRepository;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return CheckService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getChecksByMonitor = async ({ monitorId, query, teamId }) => {
|
||||
getChecksByMonitor = async ({ monitorId, query, teamId }: { monitorId: string; query: any; teamId: string }) => {
|
||||
if (!monitorId) {
|
||||
throw this.errorService.createBadRequestError("No monitor ID in request");
|
||||
}
|
||||
@@ -23,15 +44,8 @@ class CheckService {
|
||||
throw this.errorService.createBadRequestError("No team ID in request");
|
||||
}
|
||||
|
||||
const monitor = await this.db.monitorModule.getMonitorById(monitorId);
|
||||
|
||||
if (!monitor) {
|
||||
throw this.errorService.createNotFoundError("Monitor not found");
|
||||
}
|
||||
|
||||
if (!monitor.teamId.equals(teamId)) {
|
||||
throw this.errorService.createAuthorizationError();
|
||||
}
|
||||
// For verificaiton, throws an error if monitor doesn't belong to team
|
||||
await this.monitorsRepository.findById(monitorId, teamId);
|
||||
|
||||
let { sortOrder, dateRange, filter, ack, page, rowsPerPage, status } = query;
|
||||
const result = await this.db.checkModule.getChecksByMonitor({
|
||||
@@ -47,7 +61,7 @@ class CheckService {
|
||||
return result;
|
||||
};
|
||||
|
||||
getChecksByTeam = async ({ teamId, query }) => {
|
||||
getChecksByTeam = async ({ teamId, query }: { teamId: string; query: any }) => {
|
||||
let { sortOrder, dateRange, filter, ack, page, rowsPerPage } = query;
|
||||
|
||||
if (!teamId) {
|
||||
@@ -66,7 +80,7 @@ class CheckService {
|
||||
return checkData;
|
||||
};
|
||||
|
||||
getChecksSummaryByTeamId = async ({ teamId }) => {
|
||||
getChecksSummaryByTeamId = async ({ teamId }: { teamId: string }) => {
|
||||
if (!teamId) {
|
||||
throw this.errorService.createBadRequestError("No team ID in request");
|
||||
}
|
||||
@@ -75,7 +89,7 @@ class CheckService {
|
||||
return summary;
|
||||
};
|
||||
|
||||
ackCheck = async ({ checkId, teamId, ack }) => {
|
||||
ackCheck = async ({ checkId, teamId, ack }: { checkId: string; teamId: string; ack: any }) => {
|
||||
if (!checkId) {
|
||||
throw this.errorService.createBadRequestError("No check ID in request");
|
||||
}
|
||||
@@ -88,27 +102,21 @@ class CheckService {
|
||||
return updatedCheck;
|
||||
};
|
||||
|
||||
ackAllChecks = async ({ monitorId, path, teamId, ack }) => {
|
||||
ackAllChecks = async ({ monitorId, path, teamId, ack }: { monitorId: string; path: string; teamId: string; ack: any }) => {
|
||||
if (path === "monitor") {
|
||||
if (!monitorId) {
|
||||
throw this.errorService.createBadRequestError("No monitor ID in request");
|
||||
}
|
||||
|
||||
const monitor = await this.db.monitorModule.getMonitorById(monitorId);
|
||||
if (!monitor) {
|
||||
throw this.errorService.createNotFoundError("Monitor not found");
|
||||
}
|
||||
|
||||
if (!monitor.teamId.equals(teamId)) {
|
||||
throw this.errorService.createAuthorizationError();
|
||||
}
|
||||
// For verificaiton, throws an error if monitor doesn't belong to team
|
||||
await this.monitorsRepository.findById(monitorId, teamId);
|
||||
}
|
||||
|
||||
const updatedChecks = await this.db.checkModule.ackAllChecks(monitorId, teamId, ack, path);
|
||||
return updatedChecks;
|
||||
};
|
||||
|
||||
deleteChecks = async ({ monitorId, teamId }) => {
|
||||
deleteChecks = async ({ monitorId, teamId }: { monitorId: string; teamId: string }) => {
|
||||
if (!monitorId) {
|
||||
throw this.errorService.createBadRequestError("No monitor ID in request");
|
||||
}
|
||||
@@ -117,20 +125,13 @@ class CheckService {
|
||||
throw this.errorService.createBadRequestError("No team ID in request");
|
||||
}
|
||||
|
||||
const monitor = await this.db.monitorModule.getMonitorById(monitorId);
|
||||
|
||||
if (!monitor) {
|
||||
throw this.errorService.createNotFoundError("Monitor not found");
|
||||
}
|
||||
|
||||
if (!monitor.teamId.equals(teamId)) {
|
||||
throw this.errorService.createAuthorizationError();
|
||||
}
|
||||
// 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);
|
||||
return deletedCount;
|
||||
};
|
||||
deleteChecksByTeamId = async ({ teamId }) => {
|
||||
deleteChecksByTeamId = async ({ teamId }: { teamId: string }) => {
|
||||
if (!teamId) {
|
||||
throw this.errorService.createBadRequestError("No team ID in request");
|
||||
}
|
||||
@@ -139,7 +140,7 @@ class CheckService {
|
||||
return deletedCount;
|
||||
};
|
||||
|
||||
updateChecksTTL = async ({ teamId, ttl }) => {
|
||||
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);
|
||||
+30
-10
@@ -1,30 +1,50 @@
|
||||
import { IMonitorsRepository } from "@/repositories/index.js";
|
||||
|
||||
const SERVICE_NAME = "maintenanceWindowService";
|
||||
|
||||
class MaintenanceWindowService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private db: any;
|
||||
private settingsService: any;
|
||||
private stringService: any;
|
||||
private errorService: any;
|
||||
private monitorsRepository: IMonitorsRepository;
|
||||
|
||||
constructor({ db, settingsService, stringService, errorService }) {
|
||||
constructor({
|
||||
db,
|
||||
settingsService,
|
||||
stringService,
|
||||
errorService,
|
||||
monitorsRepository,
|
||||
}: {
|
||||
db: any;
|
||||
settingsService: any;
|
||||
stringService: any;
|
||||
errorService: any;
|
||||
monitorsRepository: IMonitorsRepository;
|
||||
}) {
|
||||
this.db = db;
|
||||
this.settingsService = settingsService;
|
||||
this.stringService = stringService;
|
||||
this.errorService = errorService;
|
||||
this.monitorsRepository = monitorsRepository;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return MaintenanceWindowService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
createMaintenanceWindow = async ({ teamId, body }) => {
|
||||
createMaintenanceWindow = async ({ teamId, body }: { teamId: string; body: any }) => {
|
||||
const monitorIds = body.monitors;
|
||||
const monitors = await this.db.monitorModule.getMonitorsByIds(monitorIds);
|
||||
const monitors = await this.monitorsRepository.findByIds(monitorIds);
|
||||
|
||||
const unauthorizedMonitors = monitors.filter((monitor) => !monitor.teamId.equals(teamId));
|
||||
const unauthorizedMonitors = monitors.filter((monitor) => monitor.teamId !== teamId);
|
||||
|
||||
if (unauthorizedMonitors.length > 0) {
|
||||
throw this.errorService.createAuthorizationError();
|
||||
}
|
||||
|
||||
const dbTransactions = monitorIds.map((monitorId) => {
|
||||
const dbTransactions = monitorIds.map((monitorId: string) => {
|
||||
return this.db.maintenanceWindowModule.createMaintenanceWindow({
|
||||
teamId,
|
||||
monitorId,
|
||||
@@ -38,26 +58,26 @@ class MaintenanceWindowService {
|
||||
await Promise.all(dbTransactions);
|
||||
};
|
||||
|
||||
getMaintenanceWindowById = async ({ id, teamId }) => {
|
||||
getMaintenanceWindowById = async ({ id, teamId }: { id: string; teamId: string }) => {
|
||||
const maintenanceWindow = await this.db.maintenanceWindowModule.getMaintenanceWindowById({ id, teamId });
|
||||
return maintenanceWindow;
|
||||
};
|
||||
|
||||
getMaintenanceWindowsByTeamId = async ({ teamId, query }) => {
|
||||
getMaintenanceWindowsByTeamId = async ({ teamId, query }: { teamId: string; query: any }) => {
|
||||
const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByTeamId(teamId, query);
|
||||
return maintenanceWindows;
|
||||
};
|
||||
|
||||
getMaintenanceWindowsByMonitorId = async ({ monitorId, teamId }) => {
|
||||
getMaintenanceWindowsByMonitorId = async ({ monitorId, teamId }: { monitorId: string; teamId: string }) => {
|
||||
const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId({ monitorId, teamId });
|
||||
return maintenanceWindows;
|
||||
};
|
||||
|
||||
deleteMaintenanceWindow = async ({ id, teamId }) => {
|
||||
deleteMaintenanceWindow = async ({ id, teamId }: { id: string; teamId: string }) => {
|
||||
await this.db.maintenanceWindowModule.deleteMaintenanceWindowById({ id, teamId });
|
||||
};
|
||||
|
||||
editMaintenanceWindow = async ({ id, teamId, body }) => {
|
||||
editMaintenanceWindow = async ({ id, teamId, body }: { id: string; teamId: string; body: any }) => {
|
||||
const editedMaintenanceWindow = await this.db.maintenanceWindowModule.editMaintenanceWindowById({ id, body, teamId });
|
||||
return editedMaintenanceWindow;
|
||||
};
|
||||
+54
-19
@@ -1,9 +1,44 @@
|
||||
import { IMonitorsRepository } from "@/repositories/index.js";
|
||||
|
||||
const SERVICE_NAME = "userService";
|
||||
|
||||
class UserService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor({ crypto, db, emailService, settingsService, logger, stringService, jwt, errorService, jobQueue }) {
|
||||
private db: any;
|
||||
private emailService: any;
|
||||
private settingsService: any;
|
||||
private logger: any;
|
||||
private stringService: any;
|
||||
private jwt: any;
|
||||
private errorService: any;
|
||||
private jobQueue: any;
|
||||
private crypto: any;
|
||||
private monitorsRepository: IMonitorsRepository;
|
||||
|
||||
constructor({
|
||||
crypto,
|
||||
db,
|
||||
emailService,
|
||||
settingsService,
|
||||
logger,
|
||||
stringService,
|
||||
jwt,
|
||||
errorService,
|
||||
jobQueue,
|
||||
monitorsRepository,
|
||||
}: {
|
||||
crypto: any;
|
||||
db: any;
|
||||
emailService: any;
|
||||
settingsService: any;
|
||||
logger: any;
|
||||
stringService: any;
|
||||
jwt: any;
|
||||
errorService: any;
|
||||
jobQueue: any;
|
||||
monitorsRepository: IMonitorsRepository;
|
||||
}) {
|
||||
this.db = db;
|
||||
this.emailService = emailService;
|
||||
this.settingsService = settingsService;
|
||||
@@ -13,20 +48,21 @@ class UserService {
|
||||
this.errorService = errorService;
|
||||
this.jobQueue = jobQueue;
|
||||
this.crypto = crypto;
|
||||
this.monitorsRepository = monitorsRepository;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return UserService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
issueToken = (payload, appSettings) => {
|
||||
issueToken = (payload: any, appSettings: any) => {
|
||||
const tokenTTL = appSettings?.jwtTTL ?? "2h";
|
||||
const tokenSecret = appSettings?.jwtSecret;
|
||||
const payloadData = payload;
|
||||
return this.jwt.sign(payloadData, tokenSecret, { expiresIn: tokenTTL });
|
||||
};
|
||||
|
||||
registerUser = async (user, file) => {
|
||||
registerUser = async (user: any, file: any) => {
|
||||
// Create a new user
|
||||
// If superAdmin exists, a token should be attached to all further register requests
|
||||
const superAdminExists = await this.db.userModule.checkSuperadmin();
|
||||
@@ -61,7 +97,7 @@ class UserService {
|
||||
const html = await this.emailService.buildEmail("welcomeEmailTemplate", {
|
||||
name: newUser.firstName,
|
||||
});
|
||||
this.emailService.sendEmail(newUser.email, "Welcome to Uptime Monitor", html).catch((error) => {
|
||||
this.emailService.sendEmail(newUser.email, "Welcome to Uptime Monitor", html).catch((error: any) => {
|
||||
this.logger.warn({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
@@ -69,7 +105,7 @@ class UserService {
|
||||
stack: error.stack,
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
this.logger.warn({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
@@ -81,7 +117,7 @@ class UserService {
|
||||
return { user: newUser, token };
|
||||
};
|
||||
|
||||
loginUser = async (email, password) => {
|
||||
loginUser = async (email: string, password: string) => {
|
||||
// Check if user exists
|
||||
const user = await this.db.userModule.getUserByEmail(email);
|
||||
// Compare password
|
||||
@@ -103,7 +139,7 @@ class UserService {
|
||||
return { user: userWithoutPassword, token };
|
||||
};
|
||||
|
||||
editUser = async (updates, file, currentUser) => {
|
||||
editUser = async (updates: any, file: any, currentUser: any) => {
|
||||
// Change Password check
|
||||
if (updates?.password && updates?.newPassword) {
|
||||
// Get user's email
|
||||
@@ -131,7 +167,7 @@ class UserService {
|
||||
return superAdminExists;
|
||||
};
|
||||
|
||||
requestRecovery = async (email) => {
|
||||
requestRecovery = async (email: string) => {
|
||||
const user = await this.db.userModule.getUserByEmail(email);
|
||||
const recoveryToken = await this.db.recoveryModule.requestRecoveryToken(email);
|
||||
const name = user.firstName;
|
||||
@@ -147,18 +183,18 @@ class UserService {
|
||||
return msgId;
|
||||
};
|
||||
|
||||
validateRecovery = async (recoveryToken) => {
|
||||
validateRecovery = async (recoveryToken: string) => {
|
||||
await this.db.recoveryModule.validateRecoveryToken(recoveryToken);
|
||||
};
|
||||
|
||||
resetPassword = async (password, recoveryToken) => {
|
||||
resetPassword = async (password: string, recoveryToken: string) => {
|
||||
const user = await this.db.recoveryModule.resetPassword(password, recoveryToken);
|
||||
const appSettings = await this.settingsService.getSettings();
|
||||
const token = this.issueToken(user._doc, appSettings);
|
||||
return { user, token };
|
||||
};
|
||||
|
||||
deleteUser = async (user) => {
|
||||
deleteUser = async (user: any) => {
|
||||
const email = user?.email;
|
||||
if (!email) {
|
||||
throw this.errorService.createBadRequestError("No email in request");
|
||||
@@ -181,15 +217,14 @@ class UserService {
|
||||
}
|
||||
|
||||
// 1. Find all the monitors associated with the team ID if superadmin
|
||||
const result = await this.db.monitorModule.getMonitorsByTeamId({
|
||||
teamId: teamId,
|
||||
});
|
||||
const res = await this.monitorsRepository.findByTeamId(teamId, {});
|
||||
|
||||
if (roles.includes("superadmin")) {
|
||||
// 2. Remove all jobs, delete checks and alerts
|
||||
result?.monitors.length > 0 &&
|
||||
res &&
|
||||
res?.length > 0 &&
|
||||
(await Promise.all(
|
||||
result.monitors.map(async (monitor) => {
|
||||
res.map(async (monitor) => {
|
||||
await this.jobQueue.deleteJob(monitor);
|
||||
})
|
||||
));
|
||||
@@ -203,15 +238,15 @@ class UserService {
|
||||
return users;
|
||||
};
|
||||
|
||||
getUserById = async (roles, userId) => {
|
||||
getUserById = async (roles: any, userId: any) => {
|
||||
const user = await this.db.userModule.getUserById(roles, userId);
|
||||
return user;
|
||||
};
|
||||
|
||||
editUserById = async (userId, user) => {
|
||||
editUserById = async (userId: any, user: any) => {
|
||||
await this.db.userModule.editUserById(userId, user);
|
||||
};
|
||||
setPasswordByUserId = async (userId, password) => {
|
||||
setPasswordByUserId = async (userId: any, password: string) => {
|
||||
const updatedUser = await this.db.userModule.updateUser({ userId: userId, user: { password: password }, file: null });
|
||||
return updatedUser;
|
||||
};
|
||||
@@ -167,9 +167,8 @@ class StatusService {
|
||||
const check = this.buildCheck(networkResponse);
|
||||
await this.insertCheck(check);
|
||||
try {
|
||||
const { monitorId, status, code } = networkResponse;
|
||||
|
||||
const monitor = await this.monitorsRepository.findById(monitorId);
|
||||
const { monitorId, teamId, status, code } = networkResponse;
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
|
||||
// Update running stats
|
||||
this.updateRunningStats({ monitor, networkResponse });
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { jest } from "@jest/globals";
|
||||
import { MonitorService } from "../src/service/business/monitorService.ts";
|
||||
import type { IMonitorsRepository, IChecksRepository } from "../src/repositories/index.ts";
|
||||
import type { IChecksRepository, IMonitorStatsRepository, IMonitorsRepository, IStatusPagesRepository } from "../src/repositories/index.ts";
|
||||
|
||||
const createMonitorsRepositoryMock = () =>
|
||||
({
|
||||
findMonitorCountByTeamIdAndType: jest.fn(),
|
||||
findByTeamId: jest.fn(),
|
||||
findById: jest.fn(),
|
||||
findMonitorsSummaryByTeamId: jest.fn(),
|
||||
findGroupsByTeamId: jest.fn(),
|
||||
create: jest.fn(),
|
||||
createBulkMonitors: jest.fn(),
|
||||
deleteByTeamId: jest.fn(),
|
||||
@@ -18,32 +20,34 @@ const createChecksRepositoryMock = () =>
|
||||
findDateRangeChecksByMonitor: jest.fn(),
|
||||
}) as unknown as IChecksRepository;
|
||||
|
||||
const createMonitorStatsRepositoryMock = () =>
|
||||
({
|
||||
findByMonitorId: jest.fn(),
|
||||
deleteByMonitorId: jest.fn(),
|
||||
}) as unknown as IMonitorStatsRepository;
|
||||
|
||||
const createStatusPagesRepositoryMock = () =>
|
||||
({
|
||||
removeMonitorFromStatusPages: jest.fn(),
|
||||
}) as unknown as IStatusPagesRepository;
|
||||
|
||||
const createService = ({
|
||||
monitorsRepository = createMonitorsRepositoryMock(),
|
||||
checksRepository = createChecksRepositoryMock(),
|
||||
monitorStatsRepository = { findByMonitorId: jest.fn() },
|
||||
monitorModuleOverrides = {},
|
||||
monitorStatsRepository = createMonitorStatsRepositoryMock(),
|
||||
statusPagesRepository = createStatusPagesRepositoryMock(),
|
||||
}: {
|
||||
monitorsRepository?: IMonitorsRepository;
|
||||
checksRepository?: IChecksRepository;
|
||||
monitorStatsRepository?: { findByMonitorId: jest.Mock };
|
||||
monitorModuleOverrides?: Record<string, unknown>;
|
||||
monitorStatsRepository?: IMonitorStatsRepository;
|
||||
statusPagesRepository?: IStatusPagesRepository;
|
||||
} = {}) => {
|
||||
const monitorModule = {
|
||||
getMonitorById: jest.fn().mockResolvedValue({ teamId: { equals: () => true } }),
|
||||
getMonitorStatsById: jest.fn().mockResolvedValue({ latest: {} }),
|
||||
getMonitorsByTeamId: jest.fn().mockResolvedValue([]),
|
||||
getMonitorsAndSummaryByTeamId: jest.fn().mockResolvedValue({ monitors: [], summary: {} }),
|
||||
...monitorModuleOverrides,
|
||||
};
|
||||
|
||||
return new MonitorService({
|
||||
db: {
|
||||
monitorModule,
|
||||
statusPageModule: { deleteStatusPagesByMonitorId: jest.fn() },
|
||||
checkModule: { deleteChecks: jest.fn() },
|
||||
statusPageModule: { deleteStatusPagesByMonitorId: jest.fn() },
|
||||
pageSpeedCheckModule: { deletePageSpeedChecksByMonitorId: jest.fn() },
|
||||
notificationsModule: { deleteNotificationsByMonitorId: jest.fn() },
|
||||
notificationModule: { deleteNotificationsByMonitorId: jest.fn() },
|
||||
},
|
||||
jobQueue: {
|
||||
addJob: jest.fn(),
|
||||
@@ -66,6 +70,7 @@ const createService = ({
|
||||
monitorsRepository,
|
||||
checksRepository,
|
||||
monitorStatsRepository,
|
||||
statusPagesRepository,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -106,29 +111,41 @@ describe("MonitorService", () => {
|
||||
});
|
||||
|
||||
describe("getMonitorsByTeamId", () => {
|
||||
it("returns monitors array from db module", async () => {
|
||||
const monitorsPayload = [
|
||||
it("returns monitors array from repository", async () => {
|
||||
const monitorsRepository = createMonitorsRepositoryMock();
|
||||
(monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue([
|
||||
{ id: "m1", name: "Monitor 1" },
|
||||
{ id: "m2", name: "Monitor 2" },
|
||||
];
|
||||
const monitorModuleOverrides = {
|
||||
getMonitorsByTeamId: jest.fn().mockResolvedValue(monitorsPayload),
|
||||
};
|
||||
const service = createService({ monitorModuleOverrides });
|
||||
]);
|
||||
const service = createService({ monitorsRepository });
|
||||
const result = await service.getMonitorsByTeamId({ teamId: "team" } as any);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toHaveProperty("id", "m1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMonitorsAndSummaryByTeamId", () => {
|
||||
it("returns monitors with summary block", async () => {
|
||||
const monitorModuleOverrides = {
|
||||
getMonitorsAndSummaryByTeamId: jest.fn().mockResolvedValue({ monitors: [{ id: "m1" }], summary: { total: 1, uptime: 0.99 } }),
|
||||
};
|
||||
const service = createService({ monitorModuleOverrides });
|
||||
const result = await service.getMonitorsAndSummaryByTeamId({ teamId: "team" });
|
||||
expect(result).toEqual({ monitors: [{ id: "m1" }], summary: { total: 1, uptime: 0.99 } });
|
||||
describe("getMonitorsWithChecksByTeamId summary", () => {
|
||||
it("includes summary and monitors with checks", async () => {
|
||||
const monitorsRepository = createMonitorsRepositoryMock();
|
||||
(monitorsRepository.findMonitorCountByTeamIdAndType as jest.Mock).mockResolvedValue(1);
|
||||
(monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue([{ id: "m1", type: "http" }]);
|
||||
(monitorsRepository.findMonitorsSummaryByTeamId as jest.Mock).mockResolvedValue({
|
||||
totalMonitors: 1,
|
||||
upMonitors: 1,
|
||||
downMonitors: 0,
|
||||
pausedMonitors: 0,
|
||||
});
|
||||
|
||||
const checksRepository = createChecksRepositoryMock();
|
||||
(checksRepository.findLatestChecksByMonitorIds as jest.Mock).mockResolvedValue({ m1: [] });
|
||||
|
||||
const service = createService({ monitorsRepository, checksRepository });
|
||||
const result = await service.getMonitorsWithChecksByTeamId({ teamId: "team" });
|
||||
expect(result).toEqual({
|
||||
summary: { totalMonitors: 1, upMonitors: 1, downMonitors: 0, pausedMonitors: 0 },
|
||||
count: 1,
|
||||
monitors: [{ id: "m1", type: "http", checks: [] }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -163,7 +180,7 @@ describe("MonitorService", () => {
|
||||
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
|
||||
const checksRepository = createChecksRepositoryMock();
|
||||
(checksRepository.findDateRangeChecksByMonitor as jest.Mock).mockResolvedValue({
|
||||
monitorType: "uptime",
|
||||
monitorType: "http",
|
||||
groupedChecks: [{ _id: "2024-01-01", avgResponseTime: 100, totalChecks: 2 }],
|
||||
groupedUpChecks: [{ _id: "2024-01-01", totalChecks: 2, avgResponseTime: 90 }],
|
||||
groupedDownChecks: [{ _id: "2024-01-01", totalChecks: 0, avgResponseTime: 0 }],
|
||||
@@ -171,27 +188,23 @@ describe("MonitorService", () => {
|
||||
avgResponseTime: 95,
|
||||
});
|
||||
|
||||
const monitorStatsRepository = {
|
||||
findByMonitorId: jest.fn().mockResolvedValue({
|
||||
id: "stats-1",
|
||||
monitorId: monitor.id,
|
||||
avgResponseTime: 90,
|
||||
totalChecks: 10,
|
||||
totalUpChecks: 9,
|
||||
totalDownChecks: 1,
|
||||
uptimePercentage: 0.9,
|
||||
lastCheckTimestamp: 123456789,
|
||||
lastResponseTime: 80,
|
||||
timeOfLastFailure: 123456700,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
}),
|
||||
} as any;
|
||||
const monitorModuleOverrides = {
|
||||
getMonitorById: jest.fn().mockResolvedValue({ teamId: { equals: (value: string) => value === TEAM_ID } }),
|
||||
};
|
||||
const monitorStatsRepository = createMonitorStatsRepositoryMock();
|
||||
(monitorStatsRepository.findByMonitorId as jest.Mock).mockResolvedValue({
|
||||
id: "stats-1",
|
||||
monitorId: monitor.id,
|
||||
avgResponseTime: 90,
|
||||
totalChecks: 10,
|
||||
totalUpChecks: 9,
|
||||
totalDownChecks: 1,
|
||||
uptimePercentage: 0.9,
|
||||
lastCheckTimestamp: 123456789,
|
||||
lastResponseTime: 80,
|
||||
timeOfLastFailure: 123456700,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
const service = createService({ monitorsRepository, checksRepository, monitorModuleOverrides, monitorStatsRepository });
|
||||
const service = createService({ monitorsRepository, checksRepository, monitorStatsRepository });
|
||||
const result = await service.getUptimeDetailsById({ teamId: TEAM_ID, monitorId: "monitor-1", dateRange: "recent" });
|
||||
|
||||
expect(result).toHaveProperty("monitorData");
|
||||
|
||||
Reference in New Issue
Block a user