mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-24 11:59:39 -05:00
Merge pull request #3157 from bluewave-labs/feat-checks-repository
Feat checks repository
This commit is contained in:
@@ -64,7 +64,6 @@ import AppSettings from "../db/models/AppSettings.js";
|
||||
import Incident from "../db/models/Incident.js";
|
||||
|
||||
import InviteModule from "../db/modules/inviteModule.js";
|
||||
import CheckModule from "../db/modules/checkModule.js";
|
||||
import StatusPageModule from "../db/modules/statusPageModule.js";
|
||||
import UserModule from "../db/modules/userModule.js";
|
||||
import MaintenanceWindowModule from "../db/modules/maintenanceWindowModule.js";
|
||||
@@ -150,7 +149,6 @@ export const initializeServices = async ({
|
||||
const stringService = new StringService(translationService);
|
||||
|
||||
// Create DB
|
||||
const checkModule = new CheckModule({ logger, Monitor, User });
|
||||
const inviteModule = new InviteModule({ InviteToken, crypto, stringService });
|
||||
const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService, AppSettings });
|
||||
const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService });
|
||||
@@ -163,7 +161,6 @@ export const initializeServices = async ({
|
||||
const db = new MongoDB({
|
||||
logger,
|
||||
envSettings,
|
||||
checkModule,
|
||||
inviteModule,
|
||||
statusPageModule,
|
||||
userModule,
|
||||
@@ -213,9 +210,6 @@ export const initializeServices = async ({
|
||||
});
|
||||
|
||||
const checkService = new CheckService({
|
||||
db,
|
||||
settingsService,
|
||||
stringService,
|
||||
errorService,
|
||||
monitorsRepository,
|
||||
logger,
|
||||
|
||||
@@ -7,7 +7,6 @@ class MongoDB {
|
||||
constructor({
|
||||
logger,
|
||||
envSettings,
|
||||
checkModule,
|
||||
inviteModule,
|
||||
statusPageModule,
|
||||
userModule,
|
||||
@@ -22,7 +21,6 @@ class MongoDB {
|
||||
this.userModule = userModule;
|
||||
this.inviteModule = inviteModule;
|
||||
this.recoveryModule = recoveryModule;
|
||||
this.checkModule = checkModule;
|
||||
this.maintenanceWindowModule = maintenanceWindowModule;
|
||||
this.notificationModule = notificationModule;
|
||||
this.settingsModule = settingsModule;
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
import { ObjectId } from "mongodb";
|
||||
import mongoose from "mongoose";
|
||||
import { CheckModel } from "@/db/models/index.js";
|
||||
import { buildChecksSummaryByTeamIdPipeline } from "./checkModuleQueries.js";
|
||||
|
||||
const SERVICE_NAME = "checkModule";
|
||||
const dateRangeLookup = {
|
||||
recent: new Date(new Date().setDate(new Date().getDate() - 2)),
|
||||
hour: new Date(new Date().setHours(new Date().getHours() - 1)),
|
||||
day: new Date(new Date().setDate(new Date().getDate() - 1)),
|
||||
week: new Date(new Date().setDate(new Date().getDate() - 7)),
|
||||
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
|
||||
all: undefined,
|
||||
};
|
||||
|
||||
class CheckModule {
|
||||
constructor({ logger, Monitor, User }) {
|
||||
this.logger = logger;
|
||||
this.Monitor = Monitor;
|
||||
this.User = User;
|
||||
}
|
||||
|
||||
createChecks = async (checks) => {
|
||||
try {
|
||||
await CheckModel.insertMany(checks, { ordered: false, runValidators: true });
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "createCheck";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
getChecksByMonitor = async ({ monitorId, sortOrder, dateRange, filter, ack, page, rowsPerPage, status }) => {
|
||||
try {
|
||||
status = status === "true" ? true : status === "false" ? false : undefined;
|
||||
page = parseInt(page);
|
||||
rowsPerPage = parseInt(rowsPerPage);
|
||||
|
||||
const ackStage = ack === "true" ? { ack: true } : { $or: [{ ack: false }, { ack: { $exists: false } }] };
|
||||
|
||||
// Match
|
||||
const matchStage = {
|
||||
"metadata.monitorId": new ObjectId(monitorId),
|
||||
...(typeof status !== "undefined" && { status }),
|
||||
...(typeof ack !== "undefined" && ackStage),
|
||||
...(dateRangeLookup[dateRange] && {
|
||||
createdAt: {
|
||||
$gte: dateRangeLookup[dateRange],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
if (filter !== undefined) {
|
||||
switch (filter) {
|
||||
case "all":
|
||||
break;
|
||||
case "down":
|
||||
break;
|
||||
case "resolve":
|
||||
matchStage.statusCode = 5000;
|
||||
break;
|
||||
default:
|
||||
this.logger.warn({
|
||||
message: "invalid filter",
|
||||
service: SERVICE_NAME,
|
||||
method: "getChecks",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Sort
|
||||
sortOrder = sortOrder === "asc" ? 1 : -1;
|
||||
|
||||
// Pagination
|
||||
let skip = 0;
|
||||
if (page && rowsPerPage) {
|
||||
skip = page * rowsPerPage;
|
||||
}
|
||||
|
||||
const checks = await CheckModel.aggregate([
|
||||
{ $match: matchStage },
|
||||
{ $sort: { createdAt: sortOrder } },
|
||||
{
|
||||
$facet: {
|
||||
summary: [{ $count: "checksCount" }],
|
||||
checks: [{ $skip: skip }, { $limit: rowsPerPage }],
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
checksCount: {
|
||||
$ifNull: [{ $arrayElemAt: ["$summary.checksCount", 0] }, 0],
|
||||
},
|
||||
checks: {
|
||||
$ifNull: ["$checks", []],
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
return checks[0];
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getChecks";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
getChecksByTeam = async ({ sortOrder, dateRange, filter, ack, page, rowsPerPage, teamId }) => {
|
||||
try {
|
||||
page = parseInt(page);
|
||||
rowsPerPage = parseInt(rowsPerPage);
|
||||
|
||||
const ackStage = ack === "true" ? { ack: true } : { $or: [{ ack: false }, { ack: { $exists: false } }] };
|
||||
|
||||
const matchStage = {
|
||||
"metadata.teamId": new ObjectId(teamId),
|
||||
status: false,
|
||||
...(typeof ack !== "undefined" && ackStage),
|
||||
...(dateRangeLookup[dateRange] && {
|
||||
createdAt: {
|
||||
$gte: dateRangeLookup[dateRange],
|
||||
},
|
||||
}),
|
||||
};
|
||||
// Add filter to match stage
|
||||
if (filter !== undefined) {
|
||||
switch (filter) {
|
||||
case "all":
|
||||
break;
|
||||
case "down":
|
||||
break;
|
||||
case "resolve":
|
||||
matchStage.statusCode = 5000;
|
||||
break;
|
||||
default:
|
||||
this.logger.warn({
|
||||
message: "invalid filter",
|
||||
service: SERVICE_NAME,
|
||||
method: "getChecksByTeam",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sortOrder = sortOrder === "asc" ? 1 : -1;
|
||||
|
||||
// pagination
|
||||
let skip = 0;
|
||||
if (page && rowsPerPage) {
|
||||
skip = page * rowsPerPage;
|
||||
}
|
||||
|
||||
const aggregatePipeline = [
|
||||
{ $match: matchStage },
|
||||
|
||||
{ $sort: { createdAt: sortOrder } },
|
||||
{
|
||||
$facet: {
|
||||
summary: [{ $count: "checksCount" }],
|
||||
checks: [{ $skip: skip }, { $limit: rowsPerPage }],
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
checksCount: { $arrayElemAt: ["$summary.checksCount", 0] },
|
||||
checks: "$checks",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const checks = await CheckModel.aggregate(aggregatePipeline);
|
||||
return checks[0];
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getChecksByTeam";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
getChecksSummaryByTeamId = async ({ teamId }) => {
|
||||
try {
|
||||
const matchStage = {
|
||||
"metadata.teamId": new ObjectId(teamId),
|
||||
};
|
||||
const checks = await CheckModel.aggregate(buildChecksSummaryByTeamIdPipeline({ matchStage }));
|
||||
return checks[0].summary;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getChecksSummaryByTeamId";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
deleteChecks = async (monitorId) => {
|
||||
try {
|
||||
const result = await CheckModel.deleteMany({ "metadata.monitorId": monitorId });
|
||||
return result.deletedCount;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "deleteChecks";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
deleteChecksByTeamId = async (teamId) => {
|
||||
try {
|
||||
// Find all monitor IDs for this team (only get _id field for efficiency)
|
||||
const teamMonitors = await this.Monitor.find({ teamId }, { _id: 1 });
|
||||
const monitorIds = teamMonitors.map((monitor) => monitor._id);
|
||||
|
||||
// Delete all checks for these monitors in one operation
|
||||
const deleteResult = await CheckModel.deleteMany({ "metadata.monitorId": { $in: monitorIds } });
|
||||
|
||||
return deleteResult.deletedCount;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "deleteChecksByTeamId";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
updateChecksTTL = async (teamId, ttl) => {
|
||||
try {
|
||||
await CheckModel.collection.dropIndex("expiry_1");
|
||||
} catch (error) {
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "updateChecksTTL",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await CheckModel.collection.createIndex(
|
||||
{ expiry: 1 },
|
||||
{ expireAfterSeconds: ttl, partialFilterExpression: { "metadata.mode": { $exists: true } } }
|
||||
);
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "updateChecksTTL";
|
||||
throw error;
|
||||
}
|
||||
// Update user
|
||||
try {
|
||||
await this.User.updateMany({ teamId: teamId }, { checkTTL: ttl });
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "updateChecksTTL";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default CheckModule;
|
||||
@@ -1,44 +0,0 @@
|
||||
const buildChecksSummaryByTeamIdPipeline = ({ matchStage }) => {
|
||||
return [
|
||||
{ $match: matchStage },
|
||||
{
|
||||
$facet: {
|
||||
summary: [
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
totalChecks: { $sum: { $cond: [{ $eq: ["$status", false] }, 1, 0] } },
|
||||
resolvedChecks: {
|
||||
$sum: {
|
||||
$cond: [{ $and: [{ $eq: ["$ack", true] }, { $eq: ["$status", false] }] }, 1, 0],
|
||||
},
|
||||
},
|
||||
downChecks: {
|
||||
$sum: {
|
||||
$cond: [{ $and: [{ $eq: ["$ack", false] }, { $eq: ["$status", false] }] }, 1, 0],
|
||||
},
|
||||
},
|
||||
cannotResolveChecks: {
|
||||
$sum: {
|
||||
$cond: [{ $eq: ["$statusCode", 5000] }, 1, 0],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
summary: { $arrayElemAt: ["$summary", 0] },
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export { buildChecksSummaryByTeamIdPipeline };
|
||||
@@ -1,73 +1,41 @@
|
||||
import type { Check, MonitorType } from "@/types/index.js";
|
||||
import type {
|
||||
Check,
|
||||
ChecksQueryResult,
|
||||
ChecksSummary,
|
||||
MonitorType,
|
||||
PageSpeedChecksResult,
|
||||
HardwareChecksResult,
|
||||
UptimeChecksResult,
|
||||
} from "@/types/index.js";
|
||||
import type { LatestChecksMap } from "@/repositories/checks/MongoChecksRepistory.js";
|
||||
|
||||
export interface PageSpeedChecksResult {
|
||||
monitorType: "pagespeed";
|
||||
checks: Check[];
|
||||
}
|
||||
|
||||
export interface HardwareChecksResult {
|
||||
monitorType: "hardware";
|
||||
aggregateData: {
|
||||
latestCheck: Check | null;
|
||||
totalChecks: number;
|
||||
};
|
||||
upChecks: {
|
||||
totalChecks: number;
|
||||
};
|
||||
checks: Array<{
|
||||
_id: string;
|
||||
avgCpuUsage: number;
|
||||
avgMemoryUsage: number;
|
||||
avgTemperature: number[];
|
||||
disks: Array<{
|
||||
name: string;
|
||||
readSpeed: number;
|
||||
writeSpeed: number;
|
||||
totalBytes: number;
|
||||
freeBytes: number;
|
||||
usagePercent: number;
|
||||
}>;
|
||||
net: Array<{
|
||||
name: string;
|
||||
bytesSentPerSecond: number;
|
||||
deltaBytesRecv: number;
|
||||
deltaPacketsSent: number;
|
||||
deltaPacketsRecv: number;
|
||||
deltaErrIn: number;
|
||||
deltaErrOut: number;
|
||||
deltaDropIn: number;
|
||||
deltaDropOut: number;
|
||||
deltaFifoIn: number;
|
||||
deltaFifoOut: number;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface UptimeChecksResult {
|
||||
monitorType: Exclude<MonitorType, "hardware" | "pagespeed">;
|
||||
groupedChecks: Array<{ _id: string; avgResponseTime: number; totalChecks: number }>;
|
||||
groupedUpChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>;
|
||||
groupedDownChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>;
|
||||
uptimePercentage: number;
|
||||
avgResponseTime: number;
|
||||
}
|
||||
|
||||
export interface IChecksRepository {
|
||||
// create
|
||||
createChecks(checks: Check[]): Promise<Check[]>;
|
||||
|
||||
// single fetch
|
||||
// collection fetch
|
||||
findLatestChecksByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap>;
|
||||
findDateRangeChecksByMonitor(
|
||||
findByMonitorId(
|
||||
monitorId: string,
|
||||
sortOrder: string,
|
||||
dateRange: string,
|
||||
filter: string,
|
||||
page: number,
|
||||
rowsPerPage: number,
|
||||
status: boolean | undefined
|
||||
): Promise<ChecksQueryResult>;
|
||||
findByTeamId(sortOrder: string, dateRange: string, filter: string, page: number, rowsPerPage: number, teamId: string): Promise<ChecksQueryResult>;
|
||||
findLatestByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap>;
|
||||
findByDateRangeAndMonitorId(
|
||||
monitorId: string,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
dateString: string,
|
||||
options?: { type?: MonitorType }
|
||||
): Promise<UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult>;
|
||||
findSummaryByTeamId(teamId: string): Promise<ChecksSummary>;
|
||||
// update
|
||||
//delete
|
||||
deleteByMonitorId(monitorId: string): Promise<number>;
|
||||
deleteByTeamId(teamId: string): Promise<number>;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,15 @@ import mongoose from "mongoose";
|
||||
|
||||
const SERVICE_NAME = "StatusService";
|
||||
|
||||
const dateRangeLookup: Record<string, Date | undefined> = {
|
||||
recent: new Date(new Date().setDate(new Date().getDate() - 2)),
|
||||
hour: new Date(new Date().setHours(new Date().getHours() - 1)),
|
||||
day: new Date(new Date().setDate(new Date().getDate() - 1)),
|
||||
week: new Date(new Date().setDate(new Date().getDate() - 7)),
|
||||
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
|
||||
all: undefined,
|
||||
};
|
||||
|
||||
export type LatestChecksMap = Record<string, Check[]>;
|
||||
type DateRange = { start: Date; end: Date };
|
||||
type HardwareAggregateData = { latestCheck: CheckDocument | null; totalChecks: number };
|
||||
@@ -189,7 +198,138 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
return await CheckModel.insertMany(checks);
|
||||
};
|
||||
|
||||
findLatestChecksByMonitorIds = async (monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap> => {
|
||||
findByMonitorId = async (
|
||||
monitorId: string,
|
||||
sortOrder: string,
|
||||
dateRange: string,
|
||||
filter: string,
|
||||
page: number,
|
||||
rowsPerPage: number,
|
||||
status: boolean | undefined
|
||||
) => {
|
||||
// Match
|
||||
const matchStage: Record<string, any> = {
|
||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||
...(typeof status !== "undefined" && { status }),
|
||||
...(dateRangeLookup[dateRange] && {
|
||||
createdAt: {
|
||||
$gte: dateRangeLookup[dateRange],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
if (filter !== undefined) {
|
||||
switch (filter) {
|
||||
case "all":
|
||||
break;
|
||||
case "down":
|
||||
break;
|
||||
case "resolve":
|
||||
matchStage.statusCode = 5000;
|
||||
break;
|
||||
default:
|
||||
this.logger.warn({
|
||||
message: "invalid filter",
|
||||
service: SERVICE_NAME,
|
||||
method: "getChecks",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Sort
|
||||
const convertedSortOrder = sortOrder === "asc" ? 1 : -1;
|
||||
|
||||
// Pagination
|
||||
let skip = 0;
|
||||
if (page && rowsPerPage) {
|
||||
skip = page * rowsPerPage;
|
||||
}
|
||||
|
||||
const checks = await CheckModel.aggregate([
|
||||
{ $match: matchStage },
|
||||
{ $sort: { createdAt: convertedSortOrder } },
|
||||
{
|
||||
$facet: {
|
||||
summary: [{ $count: "checksCount" }],
|
||||
checks: [{ $skip: skip }, { $limit: rowsPerPage }],
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
checksCount: {
|
||||
$ifNull: [{ $arrayElemAt: ["$summary.checksCount", 0] }, 0],
|
||||
},
|
||||
checks: {
|
||||
$ifNull: ["$checks", []],
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
return checks[0];
|
||||
};
|
||||
|
||||
findByTeamId = async (sortOrder: string, dateRange: string, filter: string, page: number, rowsPerPage: number, teamId: string) => {
|
||||
const matchStage: Record<string, any> = {
|
||||
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
||||
status: false,
|
||||
...(dateRangeLookup[dateRange] && {
|
||||
createdAt: {
|
||||
$gte: dateRangeLookup[dateRange],
|
||||
},
|
||||
}),
|
||||
};
|
||||
// Add filter to match stage
|
||||
if (filter !== undefined) {
|
||||
switch (filter) {
|
||||
case "all":
|
||||
break;
|
||||
case "down":
|
||||
break;
|
||||
case "resolve":
|
||||
matchStage.statusCode = 5000;
|
||||
break;
|
||||
default:
|
||||
this.logger.warn({
|
||||
message: "invalid filter",
|
||||
service: SERVICE_NAME,
|
||||
method: "getChecksByTeam",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const parsedSortOrder = sortOrder === "asc" ? 1 : -1;
|
||||
|
||||
// pagination
|
||||
let skip = 0;
|
||||
if (page && rowsPerPage) {
|
||||
skip = page * rowsPerPage;
|
||||
}
|
||||
|
||||
const aggregatePipeline: any = [
|
||||
{ $match: matchStage },
|
||||
|
||||
{ $sort: { createdAt: parsedSortOrder } },
|
||||
{
|
||||
$facet: {
|
||||
summary: [{ $count: "checksCount" }],
|
||||
checks: [{ $skip: skip }, { $limit: rowsPerPage }],
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
checksCount: { $arrayElemAt: ["$summary.checksCount", 0] },
|
||||
checks: "$checks",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const checks = await CheckModel.aggregate(aggregatePipeline);
|
||||
return checks[0];
|
||||
};
|
||||
|
||||
findLatestByMonitorIds = async (monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap> => {
|
||||
if (monitorIds.length === 0) {
|
||||
return {};
|
||||
}
|
||||
@@ -223,7 +363,7 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
}, {});
|
||||
};
|
||||
|
||||
findDateRangeChecksByMonitor = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: MonitorType }) => {
|
||||
findByDateRangeAndMonitorId = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: MonitorType }) => {
|
||||
const monitorObjectId = new mongoose.Types.ObjectId(monitorId);
|
||||
if (options?.type === "hardware") {
|
||||
return this.findHardwareDateRangeChecks(monitorObjectId, startDate, endDate, dateString);
|
||||
@@ -234,6 +374,63 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, startDate, endDate, dateString);
|
||||
};
|
||||
|
||||
findSummaryByTeamId = async (teamId: string) => {
|
||||
const matchStage = {
|
||||
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
||||
};
|
||||
const checks = await CheckModel.aggregate([
|
||||
{ $match: matchStage },
|
||||
{
|
||||
$facet: {
|
||||
summary: [
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
totalChecks: { $sum: { $cond: [{ $eq: ["$status", false] }, 1, 0] } },
|
||||
resolvedChecks: {
|
||||
$sum: {
|
||||
$cond: [{ $and: [{ $eq: ["$ack", true] }, { $eq: ["$status", false] }] }, 1, 0],
|
||||
},
|
||||
},
|
||||
downChecks: {
|
||||
$sum: {
|
||||
$cond: [{ $and: [{ $eq: ["$ack", false] }, { $eq: ["$status", false] }] }, 1, 0],
|
||||
},
|
||||
},
|
||||
cannotResolveChecks: {
|
||||
$sum: {
|
||||
$cond: [{ $eq: ["$statusCode", 5000] }, 1, 0],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
summary: { $arrayElemAt: ["$summary", 0] },
|
||||
},
|
||||
},
|
||||
]);
|
||||
return checks[0].summary;
|
||||
};
|
||||
|
||||
deleteByMonitorId = async (monitorId: string): Promise<number> => {
|
||||
const result = await CheckModel.deleteMany({ "metadata.monitorId": new mongoose.Types.ObjectId(monitorId) });
|
||||
return result.deletedCount;
|
||||
};
|
||||
|
||||
deleteByTeamId = async (teamId: string) => {
|
||||
const deleteResult = await CheckModel.deleteMany({ "metadata.teamId": teamId });
|
||||
return deleteResult.deletedCount;
|
||||
};
|
||||
|
||||
private findUptimeDateRangeChecks = async (
|
||||
monitorType: Exclude<MonitorType, "hardware" | "pagespeed">,
|
||||
monitorObjectId: mongoose.Types.ObjectId,
|
||||
@@ -397,11 +594,6 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
};
|
||||
};
|
||||
|
||||
deleteByMonitorId = async (monitorId: string): Promise<number> => {
|
||||
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([
|
||||
{
|
||||
|
||||
@@ -1,39 +1,29 @@
|
||||
import { IChecksRepository, IMonitorsRepository } from "@/repositories/index.js";
|
||||
import type { MonitorType, MonitorStatusResponse, CheckErrorInfo, Check } from "@/types/index.js";
|
||||
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { ParseBoolean } from "@/utils/utils.js";
|
||||
|
||||
const SERVICE_NAME = "checkService";
|
||||
|
||||
class CheckService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private db: any;
|
||||
private settingsService: any;
|
||||
private stringService: any;
|
||||
private errorService: any;
|
||||
private monitorsRepository: IMonitorsRepository;
|
||||
private checksRepository: IChecksRepository;
|
||||
private logger: any;
|
||||
constructor({
|
||||
db,
|
||||
settingsService,
|
||||
stringService,
|
||||
errorService,
|
||||
monitorsRepository,
|
||||
logger,
|
||||
checksRepository,
|
||||
}: {
|
||||
db: any;
|
||||
settingsService: any;
|
||||
stringService: any;
|
||||
errorService: any;
|
||||
monitorsRepository: IMonitorsRepository;
|
||||
logger: any;
|
||||
checksRepository: IChecksRepository;
|
||||
}) {
|
||||
this.db = db;
|
||||
this.settingsService = settingsService;
|
||||
this.stringService = stringService;
|
||||
this.errorService = errorService;
|
||||
this.monitorsRepository = monitorsRepository;
|
||||
this.logger = logger;
|
||||
@@ -132,36 +122,27 @@ class CheckService {
|
||||
// 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({
|
||||
monitorId,
|
||||
sortOrder,
|
||||
dateRange,
|
||||
filter,
|
||||
ack,
|
||||
page,
|
||||
rowsPerPage,
|
||||
status,
|
||||
});
|
||||
let { sortOrder, dateRange, filter, page, rowsPerPage, status } = query;
|
||||
const parsedStatus = typeof status === "undefined" ? status : ParseBoolean(status);
|
||||
const parsedPage = page ? parseInt(page) : page;
|
||||
const parsedRowsPerPage = rowsPerPage ? parseInt(rowsPerPage) : rowsPerPage;
|
||||
|
||||
const result = await this.checksRepository.findByMonitorId(monitorId, sortOrder, dateRange, filter, parsedPage, parsedRowsPerPage, parsedStatus);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
getChecksByTeam = async ({ teamId, query }: { teamId: string; query: any }) => {
|
||||
let { sortOrder, dateRange, filter, ack, page, rowsPerPage } = query;
|
||||
let { sortOrder, dateRange, filter, page, rowsPerPage } = query;
|
||||
|
||||
if (!teamId) {
|
||||
throw this.errorService.createBadRequestError("No team ID in request");
|
||||
}
|
||||
|
||||
const checkData = await this.db.checkModule.getChecksByTeam({
|
||||
sortOrder,
|
||||
dateRange,
|
||||
filter,
|
||||
ack,
|
||||
page,
|
||||
rowsPerPage,
|
||||
teamId,
|
||||
});
|
||||
const parsedPage = page ? parseInt(page) : page;
|
||||
const parsedRowsPerPage = rowsPerPage ? parseInt(rowsPerPage) : rowsPerPage;
|
||||
|
||||
const checkData = await this.checksRepository.findByTeamId(sortOrder, dateRange, filter, parsedPage, parsedRowsPerPage, teamId);
|
||||
return checkData;
|
||||
};
|
||||
|
||||
@@ -169,8 +150,7 @@ class CheckService {
|
||||
if (!teamId) {
|
||||
throw this.errorService.createBadRequestError("No team ID in request");
|
||||
}
|
||||
|
||||
const summary = await this.db.checkModule.getChecksSummaryByTeamId({ teamId });
|
||||
const summary = await this.checksRepository.findSummaryByTeamId(teamId);
|
||||
return summary;
|
||||
};
|
||||
|
||||
@@ -186,7 +166,7 @@ class CheckService {
|
||||
// For verificaiton, throws an error if monitor doesn't belong to team
|
||||
await this.monitorsRepository.findById(monitorId, teamId);
|
||||
|
||||
const deletedCount = await this.db.checkModule.deleteChecks(monitorId);
|
||||
const deletedCount = await this.checksRepository.deleteByMonitorId(monitorId);
|
||||
return deletedCount;
|
||||
};
|
||||
deleteChecksByTeamId = async ({ teamId }: { teamId: string }) => {
|
||||
@@ -194,14 +174,12 @@ class CheckService {
|
||||
throw this.errorService.createBadRequestError("No team ID in request");
|
||||
}
|
||||
|
||||
const deletedCount = await this.db.checkModule.deleteChecksByTeamId(teamId);
|
||||
const deletedCount = await this.checksRepository.deleteByTeamId(teamId);
|
||||
return deletedCount;
|
||||
};
|
||||
|
||||
updateChecksTTL = async ({ teamId, ttl }: { teamId: string; ttl: string }) => {
|
||||
const SECONDS_PER_DAY = 86400;
|
||||
const newTTL = parseInt(ttl, 10) * SECONDS_PER_DAY;
|
||||
await this.db.checkModule.updateChecksTTL(teamId, newTTL);
|
||||
throw new AppError({ message: "Not implemented", service: SERVICE_NAME, method: "updateChecksTTL", status: 500 });
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
const SERVICE_NAME = "inviteService";
|
||||
|
||||
class InviteService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor({ db, settingsService, emailService, stringService, errorService }) {
|
||||
this.db = db;
|
||||
this.settingsService = settingsService;
|
||||
this.emailService = emailService;
|
||||
this.stringService = stringService;
|
||||
this.errorService = errorService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return InviteService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getInviteToken = async ({ invite, teamId }) => {
|
||||
invite.teamId = teamId;
|
||||
const inviteToken = await this.db.inviteModule.requestInviteToken(invite);
|
||||
return inviteToken;
|
||||
};
|
||||
|
||||
sendInviteEmail = async ({ inviteRequest, firstName }) => {
|
||||
const inviteToken = await this.db.inviteModule.requestInviteToken({ ...inviteRequest });
|
||||
const { clientHost } = this.settingsService.getSettings();
|
||||
|
||||
const html = await this.emailService.buildEmail("employeeActivationTemplate", {
|
||||
name: firstName,
|
||||
link: `${clientHost}/register/${inviteToken.token}`,
|
||||
});
|
||||
const result = await this.emailService.sendEmail(inviteRequest.email, "Welcome to Uptime Monitor", html);
|
||||
if (!result) {
|
||||
throw this.errorService.createServerError("Failed to send invite e-mail... Please verify your settings.");
|
||||
}
|
||||
};
|
||||
|
||||
verifyInviteToken = async ({ inviteToken }) => {
|
||||
const invite = await this.db.inviteModule.getInviteToken(inviteToken);
|
||||
return invite;
|
||||
};
|
||||
}
|
||||
|
||||
export default InviteService;
|
||||
@@ -253,7 +253,7 @@ export class MonitorService implements IMonitorService {
|
||||
}
|
||||
const rangeKey = (dateRange as DateRangeKey) ?? "recent";
|
||||
const { start, end } = this.getDateRange(rangeKey);
|
||||
const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), {
|
||||
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), {
|
||||
type: monitor.type,
|
||||
});
|
||||
const monitorStats = await this.monitorStatsRepository.findByMonitorId(monitor.id);
|
||||
@@ -292,7 +292,7 @@ export class MonitorService implements IMonitorService {
|
||||
|
||||
const rangeKey = (dateRange as DateRangeKey) ?? "recent";
|
||||
const { start, end } = this.getDateRange(rangeKey);
|
||||
const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), {
|
||||
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), {
|
||||
type: monitor.type,
|
||||
});
|
||||
|
||||
@@ -323,7 +323,7 @@ export class MonitorService implements IMonitorService {
|
||||
|
||||
const rangeKey = (dateRange as DateRangeKey) ?? "recent";
|
||||
const { start, end } = this.getDateRange(rangeKey);
|
||||
const checksData = await this.checksRepository.findDateRangeChecksByMonitor(monitor.id, start, end, this.getDateFormat(rangeKey), {
|
||||
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), {
|
||||
type: monitor.type,
|
||||
});
|
||||
|
||||
@@ -394,7 +394,7 @@ export class MonitorService implements IMonitorService {
|
||||
requestedTypes.length > 0 && requestedTypes.every((requestedType) => snapshotTypes.includes(requestedType as MonitorType));
|
||||
|
||||
const limitPerMonitor = snapshotOnlyRequest ? 1 : 25;
|
||||
const checksMap = await this.checksRepository.findLatestChecksByMonitorIds(
|
||||
const checksMap = await this.checksRepository.findLatestByMonitorIds(
|
||||
monitorsList.map((monitor) => monitor.id),
|
||||
{ limitPerMonitor }
|
||||
);
|
||||
|
||||
@@ -108,3 +108,66 @@ export interface Check {
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
export interface ChecksQueryResult {
|
||||
checksCount: number;
|
||||
checks: Check[];
|
||||
}
|
||||
|
||||
export interface PageSpeedChecksResult {
|
||||
monitorType: "pagespeed";
|
||||
checks: Check[];
|
||||
}
|
||||
|
||||
export interface HardwareChecksResult {
|
||||
monitorType: "hardware";
|
||||
aggregateData: {
|
||||
latestCheck: Check | null;
|
||||
totalChecks: number;
|
||||
};
|
||||
upChecks: {
|
||||
totalChecks: number;
|
||||
};
|
||||
checks: Array<{
|
||||
_id: string;
|
||||
avgCpuUsage: number;
|
||||
avgMemoryUsage: number;
|
||||
avgTemperature: number[];
|
||||
disks: Array<{
|
||||
name: string;
|
||||
readSpeed: number;
|
||||
writeSpeed: number;
|
||||
totalBytes: number;
|
||||
freeBytes: number;
|
||||
usagePercent: number;
|
||||
}>;
|
||||
net: Array<{
|
||||
name: string;
|
||||
bytesSentPerSecond: number;
|
||||
deltaBytesRecv: number;
|
||||
deltaPacketsSent: number;
|
||||
deltaPacketsRecv: number;
|
||||
deltaErrIn: number;
|
||||
deltaErrOut: number;
|
||||
deltaDropIn: number;
|
||||
deltaDropOut: number;
|
||||
deltaFifoIn: number;
|
||||
deltaFifoOut: number;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface UptimeChecksResult {
|
||||
monitorType: Exclude<MonitorType, "hardware" | "pagespeed">;
|
||||
groupedChecks: Array<{ _id: string; avgResponseTime: number; totalChecks: number }>;
|
||||
groupedUpChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>;
|
||||
groupedDownChecks: Array<{ _id: string; totalChecks: number; avgResponseTime: number }>;
|
||||
uptimePercentage: number;
|
||||
avgResponseTime: number;
|
||||
}
|
||||
|
||||
export interface ChecksSummary {
|
||||
totalChecks: number;
|
||||
resolvedChecks: number;
|
||||
downChecks: number;
|
||||
cannotResolveChecks: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user