This commit is contained in:
Alex Holliday
2025-07-21 10:34:16 -07:00
parent 846564a17e
commit bfa6832beb
83 changed files with 338 additions and 1249 deletions

View File

@@ -91,16 +91,14 @@ class AuthController {
const html = await this.emailService.buildEmail("welcomeEmailTemplate", {
name: newUser.firstName,
});
this.emailService
.sendEmail(newUser.email, "Welcome to Uptime Monitor", html)
.catch((error) => {
this.logger.warn({
message: error.message,
service: SERVICE_NAME,
method: "registerUser",
stack: error.stack,
});
this.emailService.sendEmail(newUser.email, "Welcome to Uptime Monitor", html).catch((error) => {
this.logger.warn({
message: error.message,
service: SERVICE_NAME,
method: "registerUser",
stack: error.stack,
});
});
} catch (error) {
this.logger.warn({
message: error.message,
@@ -274,11 +272,7 @@ class AuthController {
email,
url,
});
const msgId = await this.emailService.sendEmail(
email,
"Checkmate Password Reset",
html
);
const msgId = await this.emailService.sendEmail(email, "Checkmate Password Reset", html);
return res.success({
msg: this.stringService.authCreateRecoveryToken,

View File

@@ -46,8 +46,7 @@ class CheckController {
await getChecksQueryValidation.validateAsync(req.query);
const { monitorId } = req.params;
let { type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status } =
req.query;
let { type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status } = req.query;
const result = await this.db.getChecksByMonitor({
monitorId,
type,

View File

@@ -12,8 +12,7 @@ obs.observe({ entryTypes: ["measure"] });
class DiagnosticController {
constructor(db) {
this.db = db;
this.getMonitorsByTeamIdExecutionStats =
this.getMonitorsByTeamIdExecutionStats.bind(this);
this.getMonitorsByTeamIdExecutionStats = this.getMonitorsByTeamIdExecutionStats.bind(this);
this.getDbStats = this.getDbStats.bind(this);
}

View File

@@ -1,8 +1,4 @@
import {
inviteRoleValidation,
inviteBodyValidation,
inviteVerificationBodyValidation,
} from "../validation/joi.js";
import { inviteRoleValidation, inviteBodyValidation, inviteVerificationBodyValidation } from "../validation/joi.js";
import jwt from "jsonwebtoken";
import { getTokenFromHeaders } from "../utils/utils.js";
import { asyncHandler, createServerError } from "../utils/errorUtils.js";
@@ -65,15 +61,9 @@ class InviteController {
name: firstname,
link: `${clientHost}/register/${inviteToken.token}`,
});
const result = await this.emailService.sendEmail(
req.body.email,
"Welcome to Uptime Monitor",
html
);
const result = await this.emailService.sendEmail(req.body.email, "Welcome to Uptime Monitor", html);
if (!result) {
throw createServerError(
"Failed to send invite e-mail... Please verify your settings."
);
throw createServerError("Failed to send invite e-mail... Please verify your settings.");
}
return res.success({

View File

@@ -63,10 +63,7 @@ class MaintenanceWindowController {
await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query);
const { teamId } = req.user;
const maintenanceWindows = await this.db.getMaintenanceWindowsByTeamId(
teamId,
req.query
);
const maintenanceWindows = await this.db.getMaintenanceWindowsByTeamId(teamId, req.query);
return res.success({
msg: this.stringService.maintenanceWindowGetByTeam,
@@ -81,9 +78,7 @@ class MaintenanceWindowController {
async (req, res, next) => {
await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params);
const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId(
req.params.monitorId
);
const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId(req.params.monitorId);
return res.success({
msg: this.stringService.maintenanceWindowGetByUser,
@@ -110,10 +105,7 @@ class MaintenanceWindowController {
async (req, res, next) => {
await editMaintenanceWindowByIdParamValidation.validateAsync(req.params);
await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body);
const editedMaintenanceWindow = await this.db.editMaintenanceWindowById(
req.params.id,
req.body
);
const editedMaintenanceWindow = await this.db.editMaintenanceWindowById(req.params.id, req.body);
return res.success({
msg: this.stringService.maintenanceWindowEdit,
data: editedMaintenanceWindow,

View File

@@ -463,14 +463,10 @@ class MonitorController {
const monitorId = req.params.monitorId;
const monitor = await this.db.pauseMonitor({ monitorId });
monitor.isActive === true
? await this.jobQueue.resumeJob(monitor._id, monitor)
: await this.jobQueue.pauseJob(monitor);
monitor.isActive === true ? await this.jobQueue.resumeJob(monitor._id, monitor) : await this.jobQueue.pauseJob(monitor);
return res.success({
msg: monitor.isActive
? this.stringService.monitorResume
: this.stringService.monitorPause,
msg: monitor.isActive ? this.stringService.monitorResume : this.stringService.monitorPause,
data: monitor,
});
},
@@ -493,9 +489,7 @@ class MonitorController {
async (req, res, next) => {
const { _id, teamId } = req.user;
const demoMonitors = await this.db.addDemoMonitors(_id, teamId);
await Promise.all(
demoMonitors.map((monitor) => this.jobQueue.addJob(monitor._id, monitor))
);
await Promise.all(demoMonitors.map((monitor) => this.jobQueue.addJob(monitor._id, monitor)));
return res.success({
msg: this.stringService.monitorDemoAdded,

View File

@@ -1,9 +1,4 @@
import {
createStatusPageBodyValidation,
getStatusPageParamValidation,
getStatusPageQueryValidation,
imageValidation,
} from "../validation/joi.js";
import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "../validation/joi.js";
import { asyncHandler } from "../utils/errorUtils.js";
const SERVICE_NAME = "statusPageController";

View File

@@ -156,10 +156,7 @@ MonitorSchema.pre("deleteMany", async function (next) {
} else {
await Check.deleteMany({ monitorId: monitor._id });
}
await StatusPage.updateMany(
{ monitors: monitor._id },
{ $pull: { monitors: monitor._id } }
);
await StatusPage.updateMany({ monitors: monitor._id }, { $pull: { monitors: monitor._id } });
await MonitorStats.deleteMany({ monitorId: monitor._id.toString() });
}
next();

View File

@@ -89,8 +89,7 @@ class MongoDB {
connect = async () => {
try {
const connectionString =
this.appSettings.dbConnectionString || "mongodb://localhost:27017/uptime_db";
const connectionString = this.appSettings.dbConnectionString || "mongodb://localhost:27017/uptime_db";
await mongoose.connect(connectionString);
// If there are no AppSettings, create one
await AppSettings.findOneAndUpdate(

View File

@@ -58,26 +58,13 @@ const createChecks = async (checks) => {
* @returns {Promise<Array<Check>>}
* @throws {Error}
*/
const getChecksByMonitor = async ({
monitorId,
type,
sortOrder,
dateRange,
filter,
ack,
page,
rowsPerPage,
status,
}) => {
const getChecksByMonitor = async ({ monitorId, type, 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 } }] };
const ackStage = ack === "true" ? { ack: true } : { $or: [{ ack: false }, { ack: { $exists: false } }] };
// Match
const matchStage = {
@@ -158,23 +145,12 @@ const getChecksByMonitor = async ({
}
};
const getChecksByTeam = async ({
sortOrder,
dateRange,
filter,
ack,
page,
rowsPerPage,
teamId,
}) => {
const 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 ackStage = ack === "true" ? { ack: true } : { $or: [{ ack: false }, { ack: { $exists: false } }] };
const matchStage = {
teamId: new ObjectId(teamId),
@@ -264,11 +240,7 @@ const getChecksByTeam = async ({
*/
const ackCheck = async (checkId, teamId, ack) => {
try {
const updatedCheck = await Check.findOneAndUpdate(
{ _id: checkId, teamId: teamId },
{ $set: { ack, ackAt: new Date() } },
{ new: true }
);
const updatedCheck = await Check.findOneAndUpdate({ _id: checkId, teamId: teamId }, { $set: { ack, ackAt: new Date() } }, { new: true });
if (!updatedCheck) {
throw new Error("Check not found");
@@ -293,10 +265,7 @@ const ackCheck = async (checkId, teamId, ack) => {
*/
const ackAllChecks = async (monitorId, teamId, ack, path) => {
try {
const updatedChecks = await Check.updateMany(
path === "monitor" ? { monitorId } : { teamId },
{ $set: { ack, ackAt: new Date() } }
);
const updatedChecks = await Check.updateMany(path === "monitor" ? { monitorId } : { teamId }, { $set: { ack, ackAt: new Date() } });
return updatedChecks.modifiedCount;
} catch (error) {
error.service = SERVICE_NAME;
@@ -317,9 +286,7 @@ const getChecksSummaryByTeamId = async ({ teamId }) => {
const matchStage = {
teamId: new ObjectId(teamId),
};
const checks = await Check.aggregate(
buildChecksSummaryByTeamIdPipeline({ matchStage })
);
const checks = await Check.aggregate(buildChecksSummaryByTeamIdPipeline({ matchStage }));
return checks[0].summary;
} catch (error) {
error.service = SERVICE_NAME;

View File

@@ -10,20 +10,12 @@ const buildChecksSummaryByTeamIdPipeline = ({ matchStage }) => {
totalChecks: { $sum: { $cond: [{ $eq: ["$status", false] }, 1, 0] } },
resolvedChecks: {
$sum: {
$cond: [
{ $and: [{ $eq: ["$ack", true] }, { $eq: ["$status", false] }] },
1,
0,
],
$cond: [{ $and: [{ $eq: ["$ack", true] }, { $eq: ["$status", false] }] }, 1, 0],
},
},
downChecks: {
$sum: {
$cond: [
{ $and: [{ $eq: ["$ack", false] }, { $eq: ["$status", false] }] },
1,
0,
],
$cond: [{ $and: [{ $eq: ["$ack", false] }, { $eq: ["$status", false] }] }, 1, 0],
},
},
cannotResolveChecks: {

View File

@@ -2,11 +2,7 @@ import Monitor from "../../models/Monitor.js";
import { ObjectId } from "mongodb";
const SERVICE_NAME = "diagnosticModule";
import {
buildMonitorSummaryByTeamIdPipeline,
buildMonitorsByTeamIdPipeline,
buildFilteredMonitorsByTeamIdPipeline,
} from "./monitorModuleQueries.js";
import { buildMonitorSummaryByTeamIdPipeline, buildMonitorsByTeamIdPipeline, buildFilteredMonitorsByTeamIdPipeline } from "./monitorModuleQueries.js";
const getMonitorsByTeamIdExecutionStats = async (req) => {
try {
@@ -24,13 +20,9 @@ const getMonitorsByTeamIdExecutionStats = async (req) => {
matchStage.type = Array.isArray(type) ? { $in: type } : type;
}
const summary = await Monitor.aggregate(
buildMonitorSummaryByTeamIdPipeline({ matchStage })
).explain("executionStats");
const summary = await Monitor.aggregate(buildMonitorSummaryByTeamIdPipeline({ matchStage })).explain("executionStats");
const monitors = await Monitor.aggregate(
buildMonitorsByTeamIdPipeline({ matchStage, field, order })
).explain("executionStats");
const monitors = await Monitor.aggregate(buildMonitorsByTeamIdPipeline({ matchStage, field, order })).explain("executionStats");
const filteredMonitors = await Monitor.aggregate(
buildFilteredMonitorsByTeamIdPipeline({

View File

@@ -23,14 +23,10 @@ const createHardwareCheck = async (hardwareCheckData) => {
if (monitor.uptimePercentage === undefined) {
newUptimePercentage = status === true ? 1 : 0;
} else {
newUptimePercentage =
(monitor.uptimePercentage * (n - 1) + (status === true ? 1 : 0)) / n;
newUptimePercentage = (monitor.uptimePercentage * (n - 1) + (status === true ? 1 : 0)) / n;
}
await Monitor.findOneAndUpdate(
{ _id: monitorId },
{ uptimePercentage: newUptimePercentage }
);
await Monitor.findOneAndUpdate({ _id: monitorId }, { uptimePercentage: newUptimePercentage });
const hardwareCheck = await new HardwareCheck({
...hardwareCheckData,

View File

@@ -63,8 +63,7 @@ const getMaintenanceWindowsByTeamId = async (teamId, query) => {
if (active !== undefined) maintenanceQuery.active = active;
const maintenanceWindowCount =
await MaintenanceWindow.countDocuments(maintenanceQuery);
const maintenanceWindowCount = await MaintenanceWindow.countDocuments(maintenanceQuery);
// Pagination
let skip = 0;
@@ -78,10 +77,7 @@ const getMaintenanceWindowsByTeamId = async (teamId, query) => {
sort[field] = order === "asc" ? 1 : -1;
}
const maintenanceWindows = await MaintenanceWindow.find(maintenanceQuery)
.skip(skip)
.limit(rowsPerPage)
.sort(sort);
const maintenanceWindows = await MaintenanceWindow.find(maintenanceQuery).skip(skip).limit(rowsPerPage).sort(sort);
return { maintenanceWindows, maintenanceWindowCount };
} catch (error) {
@@ -122,8 +118,7 @@ const getMaintenanceWindowsByMonitorId = async (monitorId) => {
*/
const deleteMaintenanceWindowById = async (maintenanceWindowId) => {
try {
const maintenanceWindow =
await MaintenanceWindow.findByIdAndDelete(maintenanceWindowId);
const maintenanceWindow = await MaintenanceWindow.findByIdAndDelete(maintenanceWindowId);
return maintenanceWindow;
} catch (error) {
error.service = SERVICE_NAME;
@@ -174,11 +169,7 @@ const deleteMaintenanceWindowByUserId = async (userId) => {
const editMaintenanceWindowById = async (maintenanceWindowId, maintenanceWindowData) => {
try {
const editedMaintenanceWindow = await MaintenanceWindow.findByIdAndUpdate(
maintenanceWindowId,
maintenanceWindowData,
{ new: true }
);
const editedMaintenanceWindow = await MaintenanceWindow.findByIdAndUpdate(maintenanceWindowId, maintenanceWindowData, { new: true });
return editedMaintenanceWindow;
} catch (error) {
error.service = SERVICE_NAME;

View File

@@ -285,10 +285,7 @@ const groupChecksByTime = (checks, dateRange) => {
return acc;
}
const time =
dateRange === "day"
? checkDate.setMinutes(0, 0, 0)
: checkDate.toISOString().split("T")[0];
const time = dateRange === "day" ? checkDate.setMinutes(0, 0, 0) : checkDate.toISOString().split("T")[0];
if (!acc[time]) {
acc[time] = { time, checks: [] };
@@ -306,9 +303,7 @@ const groupChecksByTime = (checks, dateRange) => {
const calculateGroupStats = (group) => {
const totalChecks = group.checks.length;
const checksWithResponseTime = group.checks.filter(
(check) => typeof check.responseTime === "number" && !Number.isNaN(check.responseTime)
);
const checksWithResponseTime = group.checks.filter((check) => typeof check.responseTime === "number" && !Number.isNaN(check.responseTime));
return {
time: group.time,
@@ -317,8 +312,7 @@ const calculateGroupStats = (group) => {
totalIncidents: group.checks.filter((check) => !check.status).length,
avgResponseTime:
checksWithResponseTime.length > 0
? checksWithResponseTime.reduce((sum, check) => sum + check.responseTime, 0) /
checksWithResponseTime.length
? checksWithResponseTime.reduce((sum, check) => sum + check.responseTime, 0) / checksWithResponseTime.length
: 0,
};
};
@@ -344,29 +338,15 @@ const getUptimeDetailsById = async ({ monitorId, dateRange, normalize }) => {
const dateString = formatLookup[dateRange];
const results = await Check.aggregate(
buildUptimeDetailsPipeline(monitorId, dates, dateString)
);
const results = await Check.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString));
const monitorData = results[0];
monitorData.groupedUpChecks = NormalizeDataUptimeDetails(
monitorData.groupedUpChecks,
10,
100
);
monitorData.groupedUpChecks = NormalizeDataUptimeDetails(monitorData.groupedUpChecks, 10, 100);
monitorData.groupedDownChecks = NormalizeDataUptimeDetails(
monitorData.groupedDownChecks,
10,
100
);
monitorData.groupedDownChecks = NormalizeDataUptimeDetails(monitorData.groupedDownChecks, 10, 100);
const normalizedGroupChecks = NormalizeDataUptimeDetails(
monitorData.groupedChecks,
10,
100
);
const normalizedGroupChecks = NormalizeDataUptimeDetails(monitorData.groupedChecks, 10, 100);
monitorData.groupedChecks = normalizedGroupChecks;
const monitorStats = await MonitorStats.findOne({ monitorId });
@@ -386,14 +366,7 @@ const getUptimeDetailsById = async ({ monitorId, dateRange, normalize }) => {
* @returns {Promise<Monitor>}
* @throws {Error}
*/
const getMonitorStatsById = async ({
monitorId,
limit,
sortOrder,
dateRange,
numToDisplay,
normalize,
}) => {
const getMonitorStatsById = async ({ monitorId, limit, sortOrder, dateRange, numToDisplay, normalize }) => {
const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
try {
// Get monitor, if we can't find it, abort with error
@@ -408,12 +381,7 @@ const getMonitorStatsById = async ({
// Get Checks for monitor in date range requested
const model = CHECK_MODEL_LOOKUP[monitor.type];
const dates = getDateRange(dateRange);
const { checksAll, checksForDateRange } = await getMonitorChecks(
monitorId,
model,
dates,
sort
);
const { checksAll, checksForDateRange } = await getMonitorChecks(monitorId, model, dates, sort);
// Build monitor stats
const monitorStats = {
@@ -423,20 +391,10 @@ const getMonitorStatsById = async ({
latestResponseTime: getLatestResponseTime(checksAll),
periodIncidents: getIncidents(checksForDateRange),
periodTotalChecks: checksForDateRange.length,
checks: processChecksForDisplay(
NormalizeData,
checksForDateRange,
numToDisplay,
normalize
),
checks: processChecksForDisplay(NormalizeData, checksForDateRange, numToDisplay, normalize),
};
if (
monitor.type === "http" ||
monitor.type === "ping" ||
monitor.type === "docker" ||
monitor.type === "port"
) {
if (monitor.type === "http" || monitor.type === "ping" || monitor.type === "docker" || monitor.type === "port") {
// HTTP/PING Specific stats
monitorStats.periodAvgResponseTime = getAverageResponseTime(checksForDateRange);
monitorStats.periodUptime = getUptimePercentage(checksForDateRange);
@@ -463,9 +421,7 @@ const getHardwareDetailsById = async ({ monitorId, dateRange }) => {
month: "%Y-%m-%dT00:00:00Z",
};
const dateString = formatLookup[dateRange];
const hardwareStats = await HardwareCheck.aggregate(
buildHardwareDetailsPipeline(monitor, dates, dateString)
);
const hardwareStats = await HardwareCheck.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString));
const monitorStats = {
...monitor.toObject(),
@@ -505,16 +461,7 @@ const getMonitorById = async (monitorId) => {
}
};
const getMonitorsByTeamId = async ({
limit,
type,
page,
rowsPerPage,
filter,
field,
order,
teamId,
}) => {
const getMonitorsByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId }) => {
limit = parseInt(limit);
page = parseInt(page);
rowsPerPage = parseInt(rowsPerPage);
@@ -528,14 +475,10 @@ const getMonitorsByTeamId = async ({
matchStage.type = Array.isArray(type) ? { $in: type } : type;
}
const summaryResult = await Monitor.aggregate(
buildMonitorSummaryByTeamIdPipeline({ matchStage })
);
const summaryResult = await Monitor.aggregate(buildMonitorSummaryByTeamIdPipeline({ matchStage }));
const summary = summaryResult[0];
const monitors = await Monitor.aggregate(
buildMonitorsByTeamIdPipeline({ matchStage, field, order })
);
const monitors = await Monitor.aggregate(buildMonitorsByTeamIdPipeline({ matchStage, field, order }));
const filteredMonitors = await Monitor.aggregate(
buildFilteredMonitorsByTeamIdPipeline({
@@ -569,14 +512,10 @@ const getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => {
}
if (explain === true) {
return Monitor.aggregate(
buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })
).explain("executionStats");
return Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })).explain("executionStats");
}
const queryResult = await Monitor.aggregate(
buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })
);
const queryResult = await Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage }));
const { monitors, summary } = queryResult?.[0] ?? {};
return { monitors, summary };
} catch (error) {
@@ -586,17 +525,7 @@ const getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => {
}
};
const getMonitorsWithChecksByTeamId = async ({
limit,
type,
page,
rowsPerPage,
filter,
field,
order,
teamId,
explain,
}) => {
const getMonitorsWithChecksByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId, explain }) => {
try {
limit = parseInt(limit);
page = parseInt(page);
@@ -684,9 +613,7 @@ const createMonitor = async ({ body, teamId, userId }) => {
*/
const createBulkMonitors = async (req) => {
try {
const monitors = req.map(
(item) => new Monitor({ ...item, notifications: undefined })
);
const monitors = req.map((item) => new Monitor({ ...item, notifications: undefined }));
await Monitor.bulkSave(monitors);
return monitors;
} catch (error) {

View File

@@ -31,11 +31,7 @@ const buildUptimeDetailsPipeline = (monitorId, dates, dateString) => {
$project: {
_id: 0,
percentage: {
$cond: [
{ $eq: ["$totalChecks", 0] },
0,
{ $divide: ["$upChecks", "$totalChecks"] },
],
$cond: [{ $eq: ["$totalChecks", 0] }, 0, { $divide: ["$upChecks", "$totalChecks"] }],
},
},
},
@@ -235,11 +231,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
{
$match: {
$expr: {
$and: [
{ $eq: ["$monitorId", monitor._id] },
{ $gte: ["$createdAt", dates.start] },
{ $lte: ["$createdAt", dates.end] },
],
$and: [{ $eq: ["$monitorId", monitor._id] }, { $gte: ["$createdAt", dates.start] }, { $lte: ["$createdAt", dates.end] }],
},
},
},
@@ -326,10 +318,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
input: "$disks",
as: "diskArray",
in: {
$arrayElemAt: [
"$$diskArray.read_speed_bytes",
"$$diskIndex",
],
$arrayElemAt: ["$$diskArray.read_speed_bytes", "$$diskIndex"],
},
},
},
@@ -340,10 +329,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
input: "$disks",
as: "diskArray",
in: {
$arrayElemAt: [
"$$diskArray.write_speed_bytes",
"$$diskIndex",
],
$arrayElemAt: ["$$diskArray.write_speed_bytes", "$$diskIndex"],
},
},
},
@@ -354,10 +340,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
input: "$disks",
as: "diskArray",
in: {
$arrayElemAt: [
"$$diskArray.total_bytes",
"$$diskIndex",
],
$arrayElemAt: ["$$diskArray.total_bytes", "$$diskIndex"],
},
},
},
@@ -379,10 +362,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
input: "$disks",
as: "diskArray",
in: {
$arrayElemAt: [
"$$diskArray.usage_percent",
"$$diskIndex",
],
$arrayElemAt: ["$$diskArray.usage_percent", "$$diskIndex"],
},
},
},
@@ -556,26 +536,14 @@ const buildMonitorsAndSummaryByTeamIdPipeline = ({ matchStage }) => {
];
};
const buildMonitorsWithChecksByTeamIdPipeline = ({
matchStage,
filter,
page,
rowsPerPage,
field,
order,
limit,
type,
}) => {
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" } },
];
matchStage.$or = [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }];
}
// Match isActive
@@ -667,37 +635,20 @@ const buildMonitorsWithChecksByTeamIdPipeline = ({
return pipeline;
};
const buildFilteredMonitorsByTeamIdPipeline = ({
matchStage,
filter,
page,
rowsPerPage,
field,
order,
limit,
type,
}) => {
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" } },
];
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,
];
const pipeline = [{ $match: matchStage }, { $sort: sort }, { $skip: skip }, ...limitStage];
// Add checks
if (limit) {
@@ -792,10 +743,7 @@ const buildGetMonitorsByTeamIdPipeline = (req) => {
? [
{
$match: {
$or: [
{ name: { $regex: filter, $options: "i" } },
{ url: { $regex: filter, $options: "i" } },
],
$or: [{ name: { $regex: filter, $options: "i" } }, { url: { $regex: filter, $options: "i" } }],
},
},
]

View File

@@ -32,9 +32,7 @@ const createNetworkCheck = async (networkCheckData) => {
*/
const getNetworkChecksByMonitorId = async (monitorId, limit = 100) => {
try {
const networkChecks = await NetworkCheck.find({ monitorId })
.sort({ createdAt: -1 })
.limit(limit);
const networkChecks = await NetworkCheck.find({ monitorId }).sort({ createdAt: -1 }).limit(limit);
return networkChecks;
} catch (error) {
error.service = SERVICE_NAME;

View File

@@ -29,9 +29,7 @@ const updateAppSettings = async (newSettings) => {
await AppSettings.findOneAndUpdate({}, update, {
upsert: true,
});
const settings = await AppSettings.findOne()
.select("-__v -_id -createdAt -updatedAt -singleton")
.lean();
const settings = await AppSettings.findOne().select("-__v -_id -createdAt -updatedAt -singleton").lean();
return settings;
} catch (error) {
error.service = SERVICE_NAME;

View File

@@ -48,13 +48,9 @@ const updateStatusPage = async (statusPageData, image) => {
if (statusPageData.deleteSubmonitors === "true") {
statusPageData.subMonitors = [];
}
const statusPage = await StatusPage.findOneAndUpdate(
{ url: statusPageData.url },
statusPageData,
{
new: true,
}
);
const statusPage = await StatusPage.findOneAndUpdate({ url: statusPageData.url }, statusPageData, {
new: true,
});
return statusPage;
} catch (error) {
@@ -98,19 +94,8 @@ const getStatusPage = async (url) => {
}
if (!preliminaryStatusPage.monitors || preliminaryStatusPage.monitors.length === 0) {
const {
_id,
color,
companyName,
isPublished,
logo,
originalMonitors,
showCharts,
showUptimePercentage,
timezone,
showAdminLoginLink,
url,
} = preliminaryStatusPage;
const { _id, color, companyName, isPublished, logo, originalMonitors, showCharts, showUptimePercentage, timezone, showAdminLoginLink, url } =
preliminaryStatusPage;
return {
statusPage: {
_id,

View File

@@ -16,11 +16,7 @@ const SERVICE_NAME = "userModule";
* @returns {Promise<UserModel>}
* @throws {Error}
*/
const insertUser = async (
userData,
imageFile,
generateAvatarImage = GenerateAvatarImage
) => {
const insertUser = async (userData, imageFile, generateAvatarImage = GenerateAvatarImage) => {
const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
try {
if (imageFile) {
@@ -47,9 +43,7 @@ const insertUser = async (
const newUser = new UserModel(userData);
await newUser.save();
return await UserModel.findOne({ _id: newUser._id })
.select("-password")
.select("-profileImage"); // .select() doesn't work with create, need to save then find
return await UserModel.findOne({ _id: newUser._id }).select("-password").select("-profileImage"); // .select() doesn't work with create, need to save then find
} catch (error) {
if (error.code === DUPLICATE_KEY_CODE) {
error.message = stringService.dbUserExists;
@@ -98,12 +92,7 @@ const getUserByEmail = async (email) => {
* @throws {Error}
*/
const updateUser = async (
req,
res,
parseBoolean = ParseBoolean,
generateAvatarImage = GenerateAvatarImage
) => {
const updateUser = async (req, res, parseBoolean = ParseBoolean, generateAvatarImage = GenerateAvatarImage) => {
const candidateUserId = req.params.userId;
try {
const candidateUser = { ...req.body };
@@ -220,13 +209,4 @@ const logoutUser = async (userId) => {
}
};
export {
insertUser,
getUserByEmail,
updateUser,
deleteUser,
deleteTeam,
deleteAllOtherUsers,
getAllUsers,
logoutUser,
};
export { insertUser, getUserByEmail, updateUser, deleteUser, deleteTeam, deleteAllOtherUsers, getAllUsers, logoutUser };

View File

@@ -99,9 +99,7 @@ const SHUTDOWN_TIMEOUT = 1000;
let isShuttingDown = false;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const openApiSpec = JSON.parse(
fs.readFileSync(path.join(__dirname, "openapi.json"), "utf8")
);
const openApiSpec = JSON.parse(fs.readFileSync(path.join(__dirname, "openapi.json"), "utf8"));
const frontendPath = path.join(__dirname, "public");
@@ -156,25 +154,8 @@ const startApp = async () => {
// Set allowed origin
const allowedOrigin = appSettings.clientHost;
const networkService = new NetworkService(
axios,
ping,
logger,
http,
Docker,
net,
stringService,
settingsService
);
const emailService = new EmailService(
settingsService,
fs,
path,
compile,
mjml2html,
nodemailer,
logger
);
const networkService = new NetworkService(axios, ping, logger, http, Docker, net, stringService, settingsService);
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
const bufferService = new BufferService({ db, logger });
const statusService = new StatusService({ db, logger, buffer: bufferService });
const notificationUtils = new NotificationUtils({
@@ -292,17 +273,11 @@ const startApp = async () => {
ServiceRegistry.get(StringService.SERVICE_NAME)
);
const queueController = new QueueController(
ServiceRegistry.get(JobQueue.SERVICE_NAME),
ServiceRegistry.get(StringService.SERVICE_NAME)
);
const queueController = new QueueController(ServiceRegistry.get(JobQueue.SERVICE_NAME), ServiceRegistry.get(StringService.SERVICE_NAME));
const logController = new LogController(logger);
const statusPageController = new StatusPageController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(StringService.SERVICE_NAME)
);
const statusPageController = new StatusPageController(ServiceRegistry.get(MongoDB.SERVICE_NAME), ServiceRegistry.get(StringService.SERVICE_NAME));
const notificationController = new NotificationController({
notificationService: ServiceRegistry.get(NotificationService.SERVICE_NAME),
@@ -311,9 +286,7 @@ const startApp = async () => {
db: ServiceRegistry.get(MongoDB.SERVICE_NAME),
});
const diagnosticController = new DiagnosticController(
ServiceRegistry.get(MongoDB.SERVICE_NAME)
);
const diagnosticController = new DiagnosticController(ServiceRegistry.get(MongoDB.SERVICE_NAME));
//Create routes
const authRoutes = new AuthRoutes(authController);
@@ -321,9 +294,7 @@ const startApp = async () => {
const settingsRoutes = new SettingsRoutes(settingsController);
const checkRoutes = new CheckRoutes(checkController);
const inviteRoutes = new InviteRoutes(inviteController);
const maintenanceWindowRoutes = new MaintenanceWindowRoutes(
maintenanceWindowController
);
const maintenanceWindowRoutes = new MaintenanceWindowRoutes(maintenanceWindowController);
const queueRoutes = new QueueRoutes(queueController);
const logRoutes = new LogRoutes(logController);
const statusPageRoutes = new StatusPageRoutes(statusPageController);

View File

@@ -29,9 +29,7 @@ const isAllowed = (allowedRoles) => {
// Parse the token
try {
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
const { jwtSecret } = ServiceRegistry.get(
SettingsService.SERVICE_NAME
).getSettings();
const { jwtSecret } = ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings();
var decoded = jwt.verify(parsedToken, jwtSecret);
const userRoles = decoded.role;

View File

@@ -1,28 +1,27 @@
import logger from "../utils/logger.js";
const languageMiddleware =
(stringService, translationService) => async (req, res, next) => {
try {
const acceptLanguage = req.headers["accept-language"] || "en";
const language = acceptLanguage.split(",")[0].slice(0, 2).toLowerCase();
const languageMiddleware = (stringService, translationService) => async (req, res, next) => {
try {
const acceptLanguage = req.headers["accept-language"] || "en";
const language = acceptLanguage.split(",")[0].slice(0, 2).toLowerCase();
translationService.setLanguage(language);
stringService.setLanguage(language);
translationService.setLanguage(language);
stringService.setLanguage(language);
next();
} catch (error) {
logger.error({
message: error.message,
service: "languageMiddleware",
});
const acceptLanguage = req.headers["accept-language"] || "en";
const language = acceptLanguage.split(",")[0].slice(0, 2).toLowerCase();
next();
} catch (error) {
logger.error({
message: error.message,
service: "languageMiddleware",
});
const acceptLanguage = req.headers["accept-language"] || "en";
const language = acceptLanguage.split(",")[0].slice(0, 2).toLowerCase();
translationService.setLanguage(language);
stringService.setLanguage(language);
translationService.setLanguage(language);
stringService.setLanguage(language);
next();
}
};
next();
}
};
export default languageMiddleware;

View File

@@ -39,10 +39,7 @@ const verifyJWT = (req, res, next) => {
const { jwtSecret } = ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings();
jwt.verify(parsedToken, jwtSecret, (err, decoded) => {
if (err) {
const errorMessage =
err.name === "TokenExpiredError"
? stringService.expiredAuthToken
: stringService.invalidAuthToken;
const errorMessage = err.name === "TokenExpiredError" ? stringService.expiredAuthToken : stringService.invalidAuthToken;
err.details = { msg: errorMessage };
err.status = 401;
err.service = SERVICE_NAME;

View File

@@ -47,9 +47,7 @@ const verifySuperAdmin = (req, res, next) => {
stack: err.stack,
details: stringService.invalidAuthToken,
});
return res
.status(401)
.json({ success: false, msg: stringService.invalidAuthToken });
return res.status(401).json({ success: false, msg: stringService.invalidAuthToken });
}
if (decoded.role.includes("superadmin") === false) {

View File

@@ -100,14 +100,7 @@
"application/json": {
"schema": {
"type": "object",
"required": [
"firstName",
"lastName",
"email",
"password",
"role",
"teamId"
],
"required": ["firstName", "lastName", "email", "password", "role", "teamId"],
"properties": {
"firstName": {
"type": "string"

View File

@@ -15,12 +15,7 @@ class AnnouncementRoutes {
* @desc Create a new announcement
* @access Private (Requires JWT verification)
*/
this.router.post(
"/",
verifyJWT,
isAllowed(["admin", "superadmin"]),
this.announcementController.createAnnouncement
);
this.router.post("/", verifyJWT, isAllowed(["admin", "superadmin"]), this.announcementController.createAnnouncement);
/**
* @route GET /

View File

@@ -15,34 +15,14 @@ class CheckRoutes {
initRoutes() {
this.router.get("/team/summary", this.checkController.getChecksSummaryByTeamId);
this.router.get("/team", this.checkController.getChecksByTeam);
this.router.put(
"/team/ttl",
isAllowed(["admin", "superadmin"]),
this.checkController.updateChecksTTL
);
this.router.delete(
"/team",
isAllowed(["admin", "superadmin"]),
this.checkController.deleteChecksByTeamId
);
this.router.put("/team/ttl", isAllowed(["admin", "superadmin"]), this.checkController.updateChecksTTL);
this.router.delete("/team", isAllowed(["admin", "superadmin"]), this.checkController.deleteChecksByTeamId);
this.router.put(
"/check/:checkId",
verifyTeamAccess(Check, "checkId"),
this.checkController.ackCheck
);
this.router.put("/check/:checkId", verifyTeamAccess(Check, "checkId"), this.checkController.ackCheck);
this.router.get("/:monitorId", this.checkController.getChecksByMonitor);
this.router.post(
"/:monitorId",
verifyOwnership(Monitor, "monitorId"),
this.checkController.createCheck
);
this.router.delete(
"/:monitorId",
verifyOwnership(Monitor, "monitorId"),
this.checkController.deleteChecks
);
this.router.post("/:monitorId", verifyOwnership(Monitor, "monitorId"), this.checkController.createCheck);
this.router.delete("/:monitorId", verifyOwnership(Monitor, "monitorId"), this.checkController.deleteChecks);
this.router.put("/:path/:monitorId?", this.checkController.ackAllChecks);
}

View File

@@ -9,10 +9,7 @@ class DiagnosticRoutes {
initRoutes() {
this.router.post("/db/stats", this.diagnosticController.getDbStats);
this.router.get("/system", this.diagnosticController.getSystemStats);
this.router.get(
"/db/get-monitors-by-team-id/:teamId",
this.diagnosticController.getMonitorsByTeamIdExecutionStats
);
this.router.get("/db/get-monitors-by-team-id/:teamId", this.diagnosticController.getMonitorsByTeamIdExecutionStats);
}
getRouter() {

View File

@@ -12,12 +12,7 @@ class InviteRoutes {
initRoutes() {
this.router.post("/send", this.inviteController.sendInviteEmail);
this.router.post("/verify", this.inviteController.inviteVerifyController);
this.router.post(
"/",
verifyJWT,
isAllowed(["admin", "superadmin"]),
this.inviteController.getInviteToken
);
this.router.post("/", verifyJWT, isAllowed(["admin", "superadmin"]), this.inviteController.getInviteToken);
}
getRouter() {

View File

@@ -13,23 +13,11 @@ class MaintenanceWindowRoutes {
this.router.post("/", this.mwController.createMaintenanceWindows);
this.router.get("/team/", this.mwController.getMaintenanceWindowsByTeamId);
this.router.get(
"/monitor/:monitorId",
verifyOwnership(Monitor, "monitorId"),
this.mwController.getMaintenanceWindowsByMonitorId
);
this.router.get("/monitor/:monitorId", verifyOwnership(Monitor, "monitorId"), this.mwController.getMaintenanceWindowsByMonitorId);
this.router.get("/:id", this.mwController.getMaintenanceWindowById);
this.router.put(
"/:id",
verifyTeamAccess(MaintenanceWindow, "id"),
this.mwController.editMaintenanceWindow
);
this.router.delete(
"/:id",
verifyTeamAccess(MaintenanceWindow, "id"),
this.mwController.deleteMaintenanceWindow
);
this.router.put("/:id", verifyTeamAccess(MaintenanceWindow, "id"), this.mwController.editMaintenanceWindow);
this.router.delete("/:id", verifyTeamAccess(MaintenanceWindow, "id"), this.mwController.deleteMaintenanceWindow);
}
getRouter() {

View File

@@ -20,17 +20,9 @@ class NotificationRoutes {
this.router.get("/team", this.notificationController.getNotificationsByTeamId);
this.router.delete(
"/:id",
verifyOwnership(Notification, "id"),
this.notificationController.deleteNotification
);
this.router.delete("/:id", verifyOwnership(Notification, "id"), this.notificationController.deleteNotification);
this.router.get("/:id", this.notificationController.getNotificationById);
this.router.put(
"/:id",
verifyTeamAccess(Notification, "id"),
this.notificationController.editNotification
);
this.router.put("/:id", verifyTeamAccess(Notification, "id"), this.notificationController.editNotification);
}
getRouter() {

View File

@@ -7,37 +7,13 @@ class QueueRoutes {
this.initRoutes();
}
initRoutes() {
this.router.get(
"/jobs",
isAllowed(["admin", "superadmin"]),
this.queueController.getJobs
);
this.router.post(
"/jobs",
isAllowed(["admin", "superadmin"]),
this.queueController.addJob
);
this.router.get("/jobs", isAllowed(["admin", "superadmin"]), this.queueController.getJobs);
this.router.post("/jobs", isAllowed(["admin", "superadmin"]), this.queueController.addJob);
this.router.get(
"/metrics",
isAllowed(["admin", "superadmin"]),
this.queueController.getMetrics
);
this.router.get(
"/health",
isAllowed(["admin", "superadmin"]),
this.queueController.checkQueueHealth
);
this.router.get(
"/all-metrics",
isAllowed(["admin", "superadmin"]),
this.queueController.getAllMetrics
);
this.router.post(
"/flush",
isAllowed(["admin", "superadmin"]),
this.queueController.flushQueue
);
this.router.get("/metrics", isAllowed(["admin", "superadmin"]), this.queueController.getMetrics);
this.router.get("/health", isAllowed(["admin", "superadmin"]), this.queueController.checkQueueHealth);
this.router.get("/all-metrics", isAllowed(["admin", "superadmin"]), this.queueController.getAllMetrics);
this.router.post("/flush", isAllowed(["admin", "superadmin"]), this.queueController.flushQueue);
}
getRouter() {

View File

@@ -14,18 +14,8 @@ class StatusPageRoutes {
this.router.get("/", this.statusPageController.getStatusPage);
this.router.get("/team", verifyJWT, this.statusPageController.getStatusPagesByTeamId);
this.router.post(
"/",
upload.single("logo"),
verifyJWT,
this.statusPageController.createStatusPage
);
this.router.put(
"/",
upload.single("logo"),
verifyJWT,
this.statusPageController.updateStatusPage
);
this.router.post("/", upload.single("logo"), verifyJWT, this.statusPageController.createStatusPage);
this.router.put("/", upload.single("logo"), verifyJWT, this.statusPageController.updateStatusPage);
this.router.get("/:url", this.statusPageController.getStatusPageByUrl);
this.router.delete("/:url(*)", verifyJWT, this.statusPageController.deleteStatusPage);

View File

@@ -150,11 +150,7 @@ class JobQueue {
};
const schedulerId = getSchedulerId(monitor);
await queue.upsertJobScheduler(
schedulerId,
{ every: monitor?.interval ?? 60000 },
jobTemplate
);
await queue.upsertJobScheduler(schedulerId, { every: monitor?.interval ?? 60000 }, jobTemplate);
}
async deleteJob(monitor) {

View File

@@ -1,16 +1,7 @@
const SERVICE_NAME = "JobQueueHelper";
class JobQueueHelper {
constructor({
redisService,
Queue,
Worker,
logger,
db,
networkService,
statusService,
notificationService,
}) {
constructor({ redisService, Queue, Worker, logger, db, networkService, statusService, notificationService }) {
this.db = db;
this.redisService = redisService;
this.Queue = Queue;
@@ -265,11 +256,7 @@ class JobQueueHelper {
// Handle status change
await job.updateProgress(60);
const {
monitor: updatedMonitor,
statusChanged,
prevStatus,
} = await this.statusService.updateStatus(networkResponse);
const { monitor: updatedMonitor, statusChanged, prevStatus } = await this.statusService.updateStatus(networkResponse);
// Handle notifications
await job.updateProgress(80);
this.notificationService

View File

@@ -22,8 +22,7 @@ class PulseQueue {
// ****************************************
init = async () => {
try {
const mongoConnectionString =
this.appSettings.dbConnectionString || "mongodb://localhost:27017/uptime_db";
const mongoConnectionString = this.appSettings.dbConnectionString || "mongodb://localhost:27017/uptime_db";
this.pulse = new Pulse({ db: { address: mongoConnectionString } });
await this.pulse.start();
this.pulse.define("monitor-job", this.pulseQueueHelper.getMonitorJob(), {});
@@ -186,9 +185,7 @@ class PulseQueue {
failReason: job.attrs.failReason,
lastRunAt: job.attrs.lastRunAt,
lastFinishedAt: job.attrs.lastFinishedAt,
lastRunTook: job.attrs.lockedAt
? null
: job.attrs.lastFinishedAt - job.attrs.lastRunAt,
lastRunTook: job.attrs.lockedAt ? null : job.attrs.lastFinishedAt - job.attrs.lastRunAt,
lastFailedAt: job.attrs.failedAt,
};
});

View File

@@ -34,11 +34,7 @@ class PulseQueueHelper {
throw new Error("No network response");
}
const {
monitor: updatedMonitor,
statusChanged,
prevStatus,
} = await this.statusService.updateStatus(networkResponse);
const { monitor: updatedMonitor, statusChanged, prevStatus } = await this.statusService.updateStatus(networkResponse);
this.notificationService
.handleNotifications({

View File

@@ -42,10 +42,7 @@ class EmailService {
*/
this.loadTemplate = (templateName) => {
try {
const templatePath = this.path.join(
__dirname,
`../templates/${templateName}.mjml`
);
const templatePath = this.path.join(__dirname, `../templates/${templateName}.mjml`);
const templateContent = this.fs.readFileSync(templatePath, "utf8");
return this.compile(templateContent);
} catch (error) {

View File

@@ -78,9 +78,7 @@ class NetworkService {
async requestPing(monitor) {
try {
const url = monitor.url;
const { response, responseTime, error } = await this.timeRequest(() =>
this.ping.promise.probe(url)
);
const { response, responseTime, error } = await this.timeRequest(() => this.ping.promise.probe(url));
const pingResponse = {
monitorId: monitor._id,
@@ -133,18 +131,7 @@ class NetworkService {
*/
async requestHttp(monitor) {
try {
const {
url,
secret,
_id,
name,
teamId,
type,
ignoreTlsErrors,
jsonPath,
matchMethod,
expectedValue,
} = monitor;
const { url, secret, _id, name, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor;
const config = {};
secret !== undefined && (config.headers = { Authorization: `Bearer ${secret}` });
@@ -155,9 +142,7 @@ class NetworkService {
});
}
const { response, responseTime, error } = await this.timeRequest(() =>
this.axios.get(url, config)
);
const { response, responseTime, error } = await this.timeRequest(() => this.axios.get(url, config));
const httpResponse = {
monitorId: _id,
@@ -171,8 +156,7 @@ class NetworkService {
const code = error.response?.status || this.NETWORK_ERROR;
httpResponse.code = code;
httpResponse.status = false;
httpResponse.message =
this.http.STATUS_CODES[code] || this.stringService.httpNetworkError;
httpResponse.message = this.http.STATUS_CODES[code] || this.stringService.httpNetworkError;
return httpResponse;
}
@@ -227,9 +211,7 @@ class NetworkService {
else match = result === expectedValue;
httpResponse.status = match;
httpResponse.message = match
? this.stringService.httpMatchSuccess
: this.stringService.httpMatchFail;
httpResponse.message = match ? this.stringService.httpMatchSuccess : this.stringService.httpMatchFail;
return httpResponse;
} catch (error) {
error.service = this.SERVICE_NAME;
@@ -320,9 +302,7 @@ class NetworkService {
}
const container = docker.getContainer(monitor.url);
const { response, responseTime, error } = await this.timeRequest(() =>
container.inspect()
);
const { response, responseTime, error } = await this.timeRequest(() => container.inspect());
const dockerResponse = {
monitorId: monitor._id,
@@ -333,8 +313,7 @@ class NetworkService {
if (error) {
dockerResponse.status = false;
dockerResponse.code = error.statusCode || this.NETWORK_ERROR;
dockerResponse.message =
error.reason || "Failed to fetch Docker container information";
dockerResponse.message = error.reason || "Failed to fetch Docker container information";
return dockerResponse;
}
dockerResponse.status = response?.State?.Status === "running" ? true : false;

View File

@@ -3,14 +3,7 @@ const SERVICE_NAME = "NotificationService";
class NotificationService {
static SERVICE_NAME = SERVICE_NAME;
constructor({
emailService,
db,
logger,
networkService,
stringService,
notificationUtils,
}) {
constructor({ emailService, db, logger, networkService, stringService, notificationUtils }) {
this.emailService = emailService;
this.db = db;
this.logger = logger;
@@ -69,20 +62,15 @@ class NotificationService {
const alerts = await this.notificationUtils.buildHardwareAlerts(networkResponse);
if (alerts.length === 0) return false;
const { subject, html } = await this.notificationUtils.buildHardwareEmail(
networkResponse,
alerts
);
const content =
await this.notificationUtils.buildHardwareNotificationMessage(alerts);
const { subject, html } = await this.notificationUtils.buildHardwareEmail(networkResponse, alerts);
const content = await this.notificationUtils.buildHardwareNotificationMessage(alerts);
const success = await this.notifyAll({ notificationIDs, subject, html, content });
return success;
}
// Status monitors
const { subject, html } =
await this.notificationUtils.buildStatusEmail(networkResponse);
const { subject, html } = await this.notificationUtils.buildStatusEmail(networkResponse);
const content = await this.notificationUtils.buildWebhookMessage(networkResponse);
const success = this.notifyAll({ notificationIDs, subject, html, content });
return success;

View File

@@ -26,10 +26,7 @@ class NotificationUtils {
const date = new Date(timestamp);
// Get timezone abbreviation and format the date
const timeZoneAbbr = date
.toLocaleTimeString("en-US", { timeZoneName: "short" })
.split(" ")
.pop();
const timeZoneAbbr = date.toLocaleTimeString("en-US", { timeZoneName: "short" }).split(" ").pop();
// Format the date with readable format
return (
@@ -50,9 +47,7 @@ class NotificationUtils {
};
// Get formatted time
const formattedTime = timestamp
? formatTime(timestamp)
: formatTime(new Date().getTime());
const formattedTime = timestamp ? formatTime(timestamp) : formatTime(new Date().getTime());
// Create different messages based on status with extra spacing
let messageText;
@@ -73,29 +68,15 @@ class NotificationUtils {
buildHardwareAlerts = async (networkResponse) => {
const monitor = networkResponse?.monitor;
const thresholds = networkResponse?.monitor?.thresholds;
const {
usage_cpu: cpuThreshold = -1,
usage_memory: memoryThreshold = -1,
usage_disk: diskThreshold = -1,
} = thresholds;
const { usage_cpu: cpuThreshold = -1, usage_memory: memoryThreshold = -1, usage_disk: diskThreshold = -1 } = thresholds;
const metrics = networkResponse?.payload?.data;
const {
cpu: { usage_percent: cpuUsage = -1 } = {},
memory: { usage_percent: memoryUsage = -1 } = {},
disk = [],
} = metrics;
const { cpu: { usage_percent: cpuUsage = -1 } = {}, memory: { usage_percent: memoryUsage = -1 } = {}, disk = [] } = metrics;
const alerts = {
cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold ? true : false,
memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold ? true : false,
disk:
disk?.some(
(d) =>
diskThreshold !== -1 &&
typeof d?.usage_percent === "number" &&
d?.usage_percent > diskThreshold
) ?? false,
disk: disk?.some((d) => diskThreshold !== -1 && typeof d?.usage_percent === "number" && d?.usage_percent > diskThreshold) ?? false,
};
const alertsToSend = [];
@@ -110,16 +91,13 @@ class NotificationUtils {
monitor[`${type}AlertThreshold`] = monitor.alertThreshold;
const formatAlert = {
cpu: () =>
`Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`,
cpu: () => `Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`,
memory: () =>
`Your current memory usage (${(memoryUsage * 100).toFixed(0)}%) is above your threshold (${(memoryThreshold * 100).toFixed(0)}%)`,
disk: () =>
`Your current disk usage: ${disk
.map((d, idx) => `(Disk${idx}: ${(d.usage_percent * 100).toFixed(0)}%)`)
.join(
", "
)} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`,
.join(", ")} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`,
};
alertsToSend.push(formatAlert[type]());
}

View File

@@ -62,14 +62,10 @@ class SettingsService {
// Remove any old settings
await this.AppSettings.deleteMany({ version: { $exists: false } });
let settings = await this.AppSettings.findOne({ singleton: true })
.select("-__v -_id -createdAt -updatedAt -singleton")
.lean();
let settings = await this.AppSettings.findOne({ singleton: true }).select("-__v -_id -createdAt -updatedAt -singleton").lean();
if (settings === null) {
await this.AppSettings.create({});
settings = await this.AppSettings.findOne({ singleton: true })
.select("-__v -_id -createdAt -updatedAt -singleton")
.lean();
settings = await this.AppSettings.findOne({ singleton: true }).select("-__v -_id -createdAt -updatedAt -singleton").lean();
}
return settings;
}

View File

@@ -48,9 +48,7 @@ class StatusService {
if (avgResponseTime === 0) {
avgResponseTime = responseTime;
} else {
avgResponseTime =
(avgResponseTime * (stats.totalChecks - 1) + responseTime) /
stats.totalChecks;
avgResponseTime = (avgResponseTime * (stats.totalChecks - 1) + responseTime) / stats.totalChecks;
}
}
stats.avgResponseTime = avgResponseTime;
@@ -138,9 +136,7 @@ class StatusService {
// Monitor status changed, save prev status and update monitor
this.logger.info({
service: this.SERVICE_NAME,
message: `${monitor.name} went from ${this.getStatusString(
monitor.status
)} to ${this.getStatusString(status)}`,
message: `${monitor.name} went from ${this.getStatusString(monitor.status)} to ${this.getStatusString(status)}`,
prevStatus: monitor.status,
newStatus: status,
});
@@ -279,9 +275,7 @@ class StatusService {
message: error.message,
service: error.service || this.SERVICE_NAME,
method: error.method || "insertCheck",
details:
error.details ||
`Error inserting check for monitor: ${networkResponse?.monitorId}`,
details: error.details || `Error inserting check for monitor: ${networkResponse?.monitorId}`,
stack: error.stack,
});
}

View File

@@ -211,23 +211,16 @@ class StringService {
}
getWebhookUnsupportedPlatform(platform) {
return this.translationService
.getTranslation("webhookUnsupportedPlatform")
.replace("{platform}", platform);
return this.translationService.getTranslation("webhookUnsupportedPlatform").replace("{platform}", platform);
}
getWebhookSendError(platform) {
return this.translationService
.getTranslation("webhookSendError")
.replace("{platform}", platform);
return this.translationService.getTranslation("webhookSendError").replace("{platform}", platform);
}
getMonitorStatus(name, status, url) {
const translationKey = status === true ? "monitorStatusUp" : "monitorStatusDown";
return this.translationService
.getTranslation(translationKey)
.replace("{name}", name)
.replace("{url}", url);
return this.translationService.getTranslation(translationKey).replace("{name}", name).replace("{url}", url);
}
// Error Messages
@@ -378,9 +371,7 @@ class StringService {
}
getDeletedCount(count) {
return this.translationService
.getTranslation("deletedCount")
.replace("{count}", count);
return this.translationService.getTranslation("deletedCount").replace("{count}", count);
}
get pingSuccess() {
@@ -424,9 +415,7 @@ class StringService {
}
getDbFindMonitorById(monitorId) {
return this.translationService
.getTranslation("dbFindMonitorById")
.replace("${monitorId}", monitorId);
return this.translationService.getTranslation("dbFindMonitorById").replace("${monitorId}", monitorId);
}
get dbUserExists() {

View File

@@ -38,9 +38,7 @@ class TranslationService {
return false;
}
const files = fs
.readdirSync(this.localesDir)
.filter((file) => file.endsWith(".json"));
const files = fs.readdirSync(this.localesDir).filter((file) => file.endsWith(".json"));
if (files.length === 0) {
return false;

View File

@@ -32,20 +32,15 @@
<mj-text>
<p>Hello {{ name }}!</p>
<p>
We hope youre finding Checkmate helpful in monitoring your infrastructure.
Your support means a lot to us, and we <b>truly appreciate</b> having you as
part of our community.
We hope youre finding Checkmate helpful in monitoring your infrastructure. Your support means a lot to us, and we
<b>truly appreciate</b> having you as part of our community.
</p>
<p>
If youre happy with Checkmate, wed love to hear about your experience!
Leaving a review on G2 helps others discover Checkmate and supports our
ongoing improvements.
If youre happy with Checkmate, wed love to hear about your experience! Leaving a review on G2 helps others discover Checkmate and
supports our ongoing improvements.
</p>
G2 Link: TBD
<p>
Thank you for taking the time to share your thoughts - we greatly appreciate
it!
</p>
<p>Thank you for taking the time to share your thoughts - we greatly appreciate it!</p>
Checkmate Team
</mj-text>
</mj-column>

View File

@@ -33,8 +33,8 @@
<mj-text>
<p>Hello {{ name }}!</p>
<p>
You are receiving this email because a password reset request has been made
for {{ email }}. Please use the link below on the site to reset your password.
You are receiving this email because a password reset request has been made for {{ email }}. Please use the link below on the site to
reset your password.
</p>
<a href="{{url}}">Reset Password</a>
<p>If you didn't request this, please ignore this email.</p>

View File

@@ -48,10 +48,7 @@
<mj-column width="100%">
<mj-text>
<p>Hello {{ name }}!</p>
<p>
We detected an incident on one of your monitors. Your service is currently
down. We'll send a message to you once it is up again.
</p>
<p>We detected an incident on one of your monitors. Your service is currently down. We'll send a message to you once it is up again.</p>
<p><b>Monitor name:</b> {{ monitor }}</p>
<p><b>URL:</b> {{ url }}</p>
</mj-text>

View File

@@ -31,14 +31,8 @@
<mj-column width="100%">
<mj-text>
<p>Hello {{ name }}!</p>
<p>
Thank you for trying out Checkmate! We developed it with great care to meet
our own needs, and we're excited to share it with you.
</p>
<p>
Checkmate is an automated way of checking whether a service such as a website
or an application is available or not.
</p>
<p>Thank you for trying out Checkmate! We developed it with great care to meet our own needs, and we're excited to share it with you.</p>
<p>Checkmate is an automated way of checking whether a service such as a website or an application is available or not.</p>
<p>We hope you find our service as valuable as we do.</p>
<p>Thank you.</p>
</mj-text>

View File

@@ -28,9 +28,7 @@ describe("Auth Controller - issueToken", function () {
stub = sinon.stub(jwt, "sign").throws(error);
const payload = { id: "123" };
const appSettings = { jwtSecret: "my_secret" };
expect(() => issueToken(payload, tokenType.ACCESS_TOKEN, appSettings)).to.throw(
error
);
expect(() => issueToken(payload, tokenType.ACCESS_TOKEN, appSettings)).to.throw(error);
});
it("should return a token if jwt.sign is successful and appSettings.jwtTTL is not defined", function () {
@@ -163,9 +161,7 @@ describe("Auth Controller - registerUser", function () {
req.db.checkSuperadmin.resolves(false);
req.db.updateAppSettings.resolves();
req.db.insertUser.resolves({ _id: "123" });
req.settingsService.getSettings.rejects(
new Error("settingsService.getSettings error")
);
req.settingsService.getSettings.rejects(new Error("settingsService.getSettings error"));
await registerUser(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("settingsService.getSettings error");
@@ -304,9 +300,7 @@ describe("Auth Controller - loginUser", function () {
user.comparePassword.resolves(false);
await loginUser(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal(
errorMessages.AUTH_INCORRECT_PASSWORD
);
expect(next.firstCall.args[0].message).to.equal(errorMessages.AUTH_INCORRECT_PASSWORD);
});
});
@@ -370,9 +364,7 @@ describe("Auth Controller - refreshAuthToken", function () {
});
it("should reject if settingsService.getSettings fails", async function () {
req.settingsService.getSettings.rejects(
new Error("settingsService.getSettings error")
);
req.settingsService.getSettings.rejects(new Error("settingsService.getSettings error"));
await refreshAuthToken(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
@@ -475,9 +467,7 @@ describe("Auth Controller - editUser", function () {
await editUser(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(401);
expect(next.firstCall.args[0].message).to.equal(
errorMessages.AUTH_INCORRECT_PASSWORD
);
expect(next.firstCall.args[0].message).to.equal(errorMessages.AUTH_INCORRECT_PASSWORD);
});
it("should edit a user if it receives a proper request", async function () {
@@ -863,8 +853,7 @@ describe("Auth Controller - deleteUser", function () {
).to.be.true;
expect(req.jobQueue.deleteJob.calledOnceWith(monitors[0])).to.be.true;
expect(req.db.deleteChecks.calledOnceWith("monitor_id")).to.be.true;
expect(req.db.deletePageSpeedChecksByMonitorId.calledOnceWith("monitor_id")).to.be
.true;
expect(req.db.deletePageSpeedChecksByMonitorId.calledOnceWith("monitor_id")).to.be.true;
expect(req.db.deleteNotificationsByMonitorId.calledOnceWith("monitor_id")).to.be.true;
expect(req.db.deleteTeam.calledOnceWith("team_id")).to.be.true;
expect(req.db.deleteAllOtherUsers.calledOnce).to.be.true;

View File

@@ -1,11 +1,4 @@
import {
createCheck,
getChecks,
getTeamChecks,
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
} from "../../controllers/checkController.js";
import { createCheck, getChecks, getTeamChecks, deleteChecks, deleteChecksByTeamId, updateChecksTTL } from "../../controllers/checkController.js";
import jwt from "jsonwebtoken";
import { errorMessages, successMessages } from "../../utils/messages.js";
import sinon from "sinon";

View File

@@ -1,10 +1,6 @@
import sinon from "sinon";
import {
handleValidationError,
handleError,
fetchMonitorCertificate,
} from "../../controllers/controllerUtils.js";
import { handleValidationError, handleError, fetchMonitorCertificate } from "../../controllers/controllerUtils.js";
import { expect } from "chai";
import sslChecker from "ssl-checker";
import { afterEach } from "node:test";

View File

@@ -1,7 +1,4 @@
import {
issueInvitation,
inviteVerifyController,
} from "../../controllers/inviteController.js";
import { issueInvitation, inviteVerifyController } from "../../controllers/inviteController.js";
import jwt from "jsonwebtoken";
import sinon from "sinon";
import joi from "joi";

View File

@@ -623,9 +623,7 @@ describe("Monitor Controller - deleteMonitor", function () {
req.jobQueue.deleteJob.rejects(error);
await deleteMonitor(req, res, next);
expect(logger.error.calledOnce).to.be.true;
expect(logger.error.firstCall.args[0].message).to.equal(
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
);
expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`);
});
it("should log an error if deleteChecks throws an error", async function () {
@@ -635,9 +633,7 @@ describe("Monitor Controller - deleteMonitor", function () {
req.db.deleteChecks.rejects(error);
await deleteMonitor(req, res, next);
expect(logger.error.calledOnce).to.be.true;
expect(logger.error.firstCall.args[0].message).to.equal(
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
);
expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`);
});
it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async function () {
@@ -647,9 +643,7 @@ describe("Monitor Controller - deleteMonitor", function () {
req.db.deletePageSpeedChecksByMonitorId.rejects(error);
await deleteMonitor(req, res, next);
expect(logger.error.calledOnce).to.be.true;
expect(logger.error.firstCall.args[0].message).to.equal(
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
);
expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`);
});
it("should log an error if deleteNotificationsByMonitorId throws an error", async function () {
@@ -659,9 +653,7 @@ describe("Monitor Controller - deleteMonitor", function () {
req.db.deleteNotificationsByMonitorId.rejects(error);
await deleteMonitor(req, res, next);
expect(logger.error.calledOnce).to.be.true;
expect(logger.error.firstCall.args[0].message).to.equal(
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
);
expect(logger.error.firstCall.args[0].message).to.equal(`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`);
});
it("should return success message if all operations succeed", async function () {

View File

@@ -1,10 +1,5 @@
import { afterEach } from "node:test";
import {
getMetrics,
getJobs,
addJob,
obliterateQueue,
} from "../../controllers/queueController.js";
import { getMetrics, getJobs, addJob, obliterateQueue } from "../../controllers/queueController.js";
import { successMessages } from "../../utils/messages.js";
import sinon from "sinon";

View File

@@ -1,8 +1,5 @@
import { afterEach } from "node:test";
import {
getAppSettings,
updateAppSettings,
} from "../../controllers/settingsController.js";
import { getAppSettings, updateAppSettings } from "../../controllers/settingsController.js";
import { successMessages } from "../../utils/messages.js";
import sinon from "sinon";

View File

@@ -1,8 +1,5 @@
import sinon from "sinon";
import {
createStatusPage,
getStatusPageByUrl,
} from "../../controllers/statusPageController.js";
import { createStatusPage, getStatusPageByUrl } from "../../controllers/statusPageController.js";
describe("statusPageController", function () {
let req, res, next;

View File

@@ -216,11 +216,7 @@ describe("checkModule", function () {
const result = await getChecksCount(req);
expect(result).to.equal(4);
expect(checkCountDocumentStub.firstCall.args[0]).to.have.all.keys(
"monitorId",
"createdAt",
"status"
);
expect(checkCountDocumentStub.firstCall.args[0]).to.have.all.keys("monitorId", "createdAt", "status");
});
});

View File

@@ -52,10 +52,7 @@ const mockMonitor = {
};
describe("HardwareCheckModule", function () {
let hardwareCheckSaveStub,
hardwareCheckCountDocumentsStub,
monitorFindByIdStub,
loggerStub;
let hardwareCheckSaveStub, hardwareCheckCountDocumentsStub, monitorFindByIdStub, loggerStub;
beforeEach(function () {
loggerStub = sinon.stub(logger, "error");

View File

@@ -1,10 +1,6 @@
import sinon from "sinon";
import InviteToken from "../../db/models/InviteToken.js";
import {
requestInviteToken,
getInviteToken,
getInviteTokenAndDelete,
} from "../../db/mongo/modules/inviteModule.js";
import { requestInviteToken, getInviteToken, getInviteTokenAndDelete } from "../../db/mongo/modules/inviteModule.js";
import { errorMessages } from "../../utils/messages.js";
describe("Invite Module", function () {
@@ -15,10 +11,7 @@ describe("Invite Module", function () {
token: "123",
};
const mockInviteToken = { _id: 123, time: 123 };
let inviteTokenDeleteManyStub,
inviteTokenSaveStub,
inviteTokenFindOneStub,
inviteTokenFindOneAndDeleteStub;
let inviteTokenDeleteManyStub, inviteTokenSaveStub, inviteTokenFindOneStub, inviteTokenFindOneAndDeleteStub;
beforeEach(function () {
inviteTokenDeleteManyStub = sinon.stub(InviteToken, "deleteMany");

View File

@@ -40,15 +40,9 @@ describe("MaintenanceWindow Module", function () {
}),
}),
});
maintenanceWindowFindByIdAndDeleteStub = sinon.stub(
MaintenanceWindow,
"findByIdAndDelete"
);
maintenanceWindowFindByIdAndDeleteStub = sinon.stub(MaintenanceWindow, "findByIdAndDelete");
maintenanceWindowDeleteManyStub = sinon.stub(MaintenanceWindow, "deleteMany");
maintenanceWindowFindByIdAndUpdateStub = sinon.stub(
MaintenanceWindow,
"findByIdAndUpdate"
);
maintenanceWindowFindByIdAndUpdateStub = sinon.stub(MaintenanceWindow, "findByIdAndUpdate");
});
afterEach(function () {
@@ -110,10 +104,7 @@ describe("MaintenanceWindow Module", function () {
it("should return a list of maintenance windows and count", async function () {
maintenanceWindowCountDocumentsStub.resolves(1);
const result = await getMaintenanceWindowsByTeamId(
mockMaintenanceWindow.teamId,
query
);
const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query);
expect(result).to.deep.equal({
maintenanceWindows: mockMaintenanceWindows,
maintenanceWindowCount: 1,
@@ -123,10 +114,7 @@ describe("MaintenanceWindow Module", function () {
it("should return a list of maintenance windows and count with empty query", async function () {
query = undefined;
maintenanceWindowCountDocumentsStub.resolves(1);
const result = await getMaintenanceWindowsByTeamId(
mockMaintenanceWindow.teamId,
query
);
const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query);
expect(result).to.deep.equal({
maintenanceWindows: mockMaintenanceWindows,
maintenanceWindowCount: 1,
@@ -137,10 +125,7 @@ describe("MaintenanceWindow Module", function () {
query.page = undefined;
query.rowsPerPage = undefined;
maintenanceWindowCountDocumentsStub.resolves(1);
const result = await getMaintenanceWindowsByTeamId(
mockMaintenanceWindow.teamId,
query
);
const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query);
expect(result).to.deep.equal({
maintenanceWindows: mockMaintenanceWindows,
maintenanceWindowCount: 1,
@@ -150,10 +135,7 @@ describe("MaintenanceWindow Module", function () {
it("should return a list of maintenance windows and count with field and desc order", async function () {
query.order = "desc";
maintenanceWindowCountDocumentsStub.resolves(1);
const result = await getMaintenanceWindowsByTeamId(
mockMaintenanceWindow.teamId,
query
);
const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query);
expect(result).to.deep.equal({
maintenanceWindows: mockMaintenanceWindows,
maintenanceWindowCount: 1,
@@ -163,10 +145,7 @@ describe("MaintenanceWindow Module", function () {
it("should return a list of maintenance windows and count no field", async function () {
query.field = undefined;
maintenanceWindowCountDocumentsStub.resolves(1);
const result = await getMaintenanceWindowsByTeamId(
mockMaintenanceWindow.teamId,
query
);
const result = await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query);
expect(result).to.deep.equal({
maintenanceWindows: mockMaintenanceWindows,
maintenanceWindowCount: 1,
@@ -187,9 +166,7 @@ describe("MaintenanceWindow Module", function () {
describe("getMaintenanceWindowsByMonitorId", function () {
it("should return a list of maintenance windows", async function () {
maintenanceWindowFindStub.resolves(mockMaintenanceWindows);
const result = await getMaintenanceWindowsByMonitorId(
mockMaintenanceWindow.monitorId
);
const result = await getMaintenanceWindowsByMonitorId(mockMaintenanceWindow.monitorId);
expect(result).to.deep.equal(mockMaintenanceWindows);
});
@@ -225,9 +202,7 @@ describe("MaintenanceWindow Module", function () {
describe("deleteMaintenanceWindowByMonitorId", function () {
it("should return the number of documents deleted", async function () {
maintenanceWindowDeleteManyStub.resolves({ deletedCount: 1 });
const result = await deleteMaintenanceWindowByMonitorId(
mockMaintenanceWindow.monitorId
);
const result = await deleteMaintenanceWindowByMonitorId(mockMaintenanceWindow.monitorId);
expect(result).to.deep.equal({ deletedCount: 1 });
});
@@ -263,10 +238,7 @@ describe("MaintenanceWindow Module", function () {
describe("editMaintenanceWindowById", function () {
it("should return the updated maintenance window", async function () {
maintenanceWindowFindByIdAndUpdateStub.resolves(mockMaintenanceWindow);
const result = await editMaintenanceWindowById(
mockMaintenanceWindow.id,
mockMaintenanceWindow
);
const result = await editMaintenanceWindowById(mockMaintenanceWindow.id, mockMaintenanceWindow);
expect(result).to.deep.equal(mockMaintenanceWindow);
});

View File

@@ -120,12 +120,7 @@ describe("monitorModule", function () {
},
];
const mockChecks = [
{ status: true },
{ status: true },
{ status: false },
{ status: true },
];
const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }];
monitorFindStub.resolves(mockMonitors);
checkFindStub.resolves(mockChecks);
@@ -166,12 +161,7 @@ describe("monitorModule", function () {
},
];
const mockChecks = [
{ status: true },
{ status: true },
{ status: false },
{ status: true },
];
const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }];
monitorFindStub.resolves(mockMonitors);
pageSpeedCheckFindStub.resolves(mockChecks);
@@ -212,12 +202,7 @@ describe("monitorModule", function () {
},
];
const mockChecks = [
{ status: true },
{ status: true },
{ status: false },
{ status: true },
];
const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }];
monitorFindStub.resolves(mockMonitors);
hardwareCheckFindStub.resolves(mockChecks);
@@ -400,10 +385,7 @@ describe("monitorModule", function () {
});
it("should handle missing responseTime in checks", function () {
const checks = [
{ createdAt: "2024-01-01T11:30:00Z" },
{ responseTime: 200, createdAt: "2024-01-01T11:00:00Z" },
];
const checks = [{ createdAt: "2024-01-01T11:30:00Z" }, { responseTime: 200, createdAt: "2024-01-01T11:00:00Z" }];
expect(getLatestResponseTime(checks)).to.equal(0);
});
@@ -441,10 +423,7 @@ describe("monitorModule", function () {
});
it("should return 0 when no checks have responseTime", function () {
const checks = [
{ createdAt: "2024-01-01T11:30:00Z" },
{ createdAt: "2024-01-01T11:00:00Z" },
];
const checks = [{ createdAt: "2024-01-01T11:30:00Z" }, { createdAt: "2024-01-01T11:00:00Z" }];
expect(getAverageResponseTime(checks)).to.equal(0);
});
@@ -470,12 +449,7 @@ describe("monitorModule", function () {
});
it("should calculate correct percentage for mixed status checks", function () {
const checks = [
{ status: true },
{ status: false },
{ status: true },
{ status: true },
];
const checks = [{ status: true }, { status: false }, { status: true }, { status: true }];
// 3 up out of 4 total = 75%
expect(getUptimePercentage(checks)).to.equal(75);
});
@@ -507,23 +481,12 @@ describe("monitorModule", function () {
});
it("should count correct number of incidents for mixed status checks", function () {
const checks = [
{ status: true },
{ status: false },
{ status: true },
{ status: false },
{ status: true },
];
const checks = [{ status: true }, { status: false }, { status: true }, { status: false }, { status: true }];
expect(getIncidents(checks)).to.equal(2);
});
it("should handle undefined status values", function () {
const checks = [
{ status: true },
{ status: undefined },
{ status: false },
{ status: false },
];
const checks = [{ status: true }, { status: undefined }, { status: false }, { status: false }];
// Only counts explicit false values
expect(getIncidents(checks)).to.equal(2);
});
@@ -628,12 +591,8 @@ describe("monitorModule", function () {
);
// Assert
expect(result.checksAll[0].createdAt).to.be.greaterThan(
result.checksAll[1].createdAt
);
expect(result.checksForDateRange[0].createdAt).to.be.greaterThan(
result.checksForDateRange[1].createdAt
);
expect(result.checksAll[0].createdAt).to.be.greaterThan(result.checksAll[1].createdAt);
expect(result.checksForDateRange[0].createdAt).to.be.greaterThan(result.checksForDateRange[1].createdAt);
});
});
@@ -753,10 +712,7 @@ describe("monitorModule", function () {
});
it("should handle checks in same time group", function () {
const checksInSameHour = [
{ createdAt: "2024-01-15T10:15:00Z" },
{ createdAt: "2024-01-15T10:45:00Z" },
];
const checksInSameHour = [{ createdAt: "2024-01-15T10:15:00Z" }, { createdAt: "2024-01-15T10:45:00Z" }];
const result = groupChecksByTime(checksInSameHour, "day");
@@ -816,11 +772,7 @@ describe("monitorModule", function () {
it("should handle missing responseTime values", function () {
const mockGroup = {
time: "2024-01-15",
checks: [
{ status: true },
{ status: false, responseTime: 200 },
{ status: true, responseTime: undefined },
],
checks: [{ status: true }, { status: false, responseTime: 200 }, { status: true, responseTime: undefined }],
};
const result = calculateGroupStats(mockGroup, uptimePercentageStub);
@@ -1413,10 +1365,7 @@ describe("monitorModule", function () {
expect(Monitor.find.firstCall.args[0]).to.deep.equal({
teamId: "team123",
$or: [
{ name: { $regex: "search", $options: "i" } },
{ url: { $regex: "search", $options: "i" } },
],
$or: [{ name: { $regex: "search", $options: "i" } }, { url: { $regex: "search", $options: "i" } }],
});
});
@@ -1816,14 +1765,9 @@ describe("monitorModule", function () {
// Assert
expect(result).to.deep.equal(mockUpdatedMonitor);
sinon.assert.calledWith(
monitorFindByIdAndUpdateStub,
candidateId,
expectedUpdateData,
{
new: true,
}
);
sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, expectedUpdateData, {
new: true,
});
});
it("should return null when monitor not found", async function () {
@@ -1840,12 +1784,7 @@ describe("monitorModule", function () {
// Assert
expect(result).to.be.null;
sinon.assert.calledWith(
monitorFindByIdAndUpdateStub,
candidateId,
{ name: "Updated Monitor", notifications: undefined },
{ new: true }
);
sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, { name: "Updated Monitor", notifications: undefined }, { new: true });
});
it("should remove notifications from update data", async function () {
@@ -1870,14 +1809,9 @@ describe("monitorModule", function () {
await editMonitor(candidateId, candidateMonitor);
// Assert
sinon.assert.calledWith(
monitorFindByIdAndUpdateStub,
candidateId,
expectedUpdateData,
{
new: true,
}
);
sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, expectedUpdateData, {
new: true,
});
});
it("should handle database errors", async function () {

View File

@@ -1,10 +1,6 @@
import sinon from "sinon";
import Notification from "../../db/models/Notification.js";
import {
createNotification,
getNotificationsByMonitorId,
deleteNotificationsByMonitorId,
} from "../../db/mongo/modules/notificationModule.js";
import { createNotification, getNotificationsByMonitorId, deleteNotificationsByMonitorId } from "../../db/mongo/modules/notificationModule.js";
describe("notificationModule", function () {
const mockNotification = {

View File

@@ -1,9 +1,6 @@
import sinon from "sinon";
import PageSpeedCheck from "../../db/models/PageSpeedCheck.js";
import {
createPageSpeedCheck,
deletePageSpeedChecksByMonitorId,
} from "../../db/mongo/modules/pageSpeedCheckModule.js";
import { createPageSpeedCheck, deletePageSpeedChecksByMonitorId } from "../../db/mongo/modules/pageSpeedCheckModule.js";
const mockPageSpeedCheck = {
monitorId: "monitorId",

View File

@@ -1,11 +1,7 @@
import sinon from "sinon";
import RecoveryToken from "../../db/models/RecoveryToken.js";
import User from "../../db/models/User.js";
import {
requestRecoveryToken,
validateRecoveryToken,
resetPassword,
} from "../../db/mongo/modules/recoveryModule.js";
import { requestRecoveryToken, validateRecoveryToken, resetPassword } from "../../db/mongo/modules/recoveryModule.js";
import { errorMessages } from "../../utils/messages.js";
const mockRecoveryToken = {
@@ -44,12 +40,7 @@ const createQueryChain = (finalResult, comparePasswordResult = false) => ({
});
describe("recoveryModule", function () {
let deleteManyStub,
saveStub,
findOneStub,
userCompareStub,
userSaveStub,
userFindOneStub;
let deleteManyStub, saveStub, findOneStub, userCompareStub, userSaveStub, userFindOneStub;
let req, res;
beforeEach(function () {
@@ -153,9 +144,7 @@ describe("recoveryModule", function () {
it("should throw an error if the passwords match", async function () {
findOneStub.resolves(mockRecoveryToken);
saveStub.resolves();
userFindOneStub = sinon
.stub(User, "findOne")
.returns(createQueryChain(mockUser, true));
userFindOneStub = sinon.stub(User, "findOne").returns(createQueryChain(mockUser, true));
try {
await resetPassword(req, res);
} catch (error) {

View File

@@ -1,8 +1,5 @@
import sinon from "sinon";
import {
getAppSettings,
updateAppSettings,
} from "../../db/mongo/modules/settingsModule.js";
import { getAppSettings, updateAppSettings } from "../../db/mongo/modules/settingsModule.js";
import AppSettings from "../../db/models/AppSettings.js";
const mockAppSettings = {

View File

@@ -1,8 +1,5 @@
import sinon from "sinon";
import {
createStatusPage,
getStatusPageByUrl,
} from "../../db/mongo/modules/statusPageModule.js";
import { createStatusPage, getStatusPageByUrl } from "../../db/mongo/modules/statusPageModule.js";
import StatusPage from "../../db/models/StatusPage.js";
import { errorMessages } from "../../utils/messages.js";

View File

@@ -157,12 +157,7 @@ describe("userModule", function () {
select: sinon.stub().resolves(mockUser),
}),
});
const result = await updateUser(
req,
res,
parseBooleanStub,
generateAvatarImageStub
);
const result = await updateUser(req, res, parseBooleanStub, generateAvatarImageStub);
expect(result).to.deep.equal(mockUser);
});
@@ -173,12 +168,7 @@ describe("userModule", function () {
select: sinon.stub().resolves(mockUser),
}),
});
const result = await updateUser(
req,
res,
parseBooleanStub,
generateAvatarImageStub
);
const result = await updateUser(req, res, parseBooleanStub, generateAvatarImageStub);
expect(result).to.deep.equal(mockUser);
});

View File

@@ -48,15 +48,7 @@ describe("EmailService - Constructor", function () {
});
it("should initialize template loaders and email transporter", function () {
const emailService = new EmailService(
settingsServiceMock,
fsMock,
pathMock,
compileMock,
mjml2htmlMock,
nodemailerMock,
loggerMock
);
const emailService = new EmailService(settingsServiceMock, fsMock, pathMock, compileMock, mjml2htmlMock, nodemailerMock, loggerMock);
// Verify that the settingsService is assigned correctly
expect(emailService.settingsService).to.equal(settingsServiceMock);
@@ -83,15 +75,7 @@ describe("EmailService - Constructor", function () {
fsMock = {
readFileSync: sinon.stub().throws(new Error("File read error")),
};
const emailService = new EmailService(
settingsServiceMock,
fsMock,
pathMock,
compileMock,
mjml2htmlMock,
nodemailerMock,
loggerMock
);
const emailService = new EmailService(settingsServiceMock, fsMock, pathMock, compileMock, mjml2htmlMock, nodemailerMock, loggerMock);
expect(loggerMock.error.called).to.be.true;
expect(loggerMock.error.firstCall.args[0].message).to.equal("File read error");
});
@@ -139,15 +123,7 @@ describe("EmailService - buildAndSendEmail", function () {
error: sinon.stub(),
};
emailService = new EmailService(
settingsServiceMock,
fsMock,
pathMock,
compileMock,
mjml2htmlMock,
nodemailerMock,
loggerMock
);
emailService = new EmailService(settingsServiceMock, fsMock, pathMock, compileMock, mjml2htmlMock, nodemailerMock, loggerMock);
});
afterEach(function () {
@@ -155,12 +131,7 @@ describe("EmailService - buildAndSendEmail", function () {
});
it("should build and send email successfully", async function () {
const messageId = await emailService.buildAndSendEmail(
"welcomeEmailTemplate",
{},
"recipient@example.com",
"Welcome"
);
const messageId = await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome");
expect(messageId).to.equal("12345");
expect(nodemailerMock.createTransport().sendMail.calledOnce).to.be.true;
@@ -169,24 +140,14 @@ describe("EmailService - buildAndSendEmail", function () {
it("should log error if building HTML fails", async function () {
mjml2htmlMock.throws(new Error("MJML error"));
const messageId = await emailService.buildAndSendEmail(
"welcomeEmailTemplate",
{},
"recipient@example.com",
"Welcome"
);
const messageId = await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome");
expect(loggerMock.error.calledOnce).to.be.true;
expect(loggerMock.error.getCall(0).args[0].message).to.equal("MJML error");
});
it("should log error if sending email fails", async function () {
nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error"));
await emailService.buildAndSendEmail(
"welcomeEmailTemplate",
{},
"recipient@example.com",
"Welcome"
);
await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome");
expect(loggerMock.error.calledOnce).to.be.true;
expect(loggerMock.error.getCall(0).args[0].message).to.equal("SMTP error");
});
@@ -195,12 +156,7 @@ describe("EmailService - buildAndSendEmail", function () {
mjml2htmlMock.throws(new Error("MJML error"));
nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error"));
const messageId = await emailService.buildAndSendEmail(
"welcomeEmailTemplate",
{},
"recipient@example.com",
"Welcome"
);
const messageId = await emailService.buildAndSendEmail("welcomeEmailTemplate", {}, "recipient@example.com", "Welcome");
expect(messageId).to.be.undefined;
expect(loggerMock.error.calledTwice).to.be.true;

View File

@@ -52,13 +52,7 @@ class WorkerStub {
}
describe("JobQueue", function () {
let settingsService,
logger,
db,
networkService,
statusService,
notificationService,
jobQueue;
let settingsService, logger, db, networkService, statusService, notificationService, jobQueue;
beforeEach(async function () {
settingsService = { getSettings: sinon.stub() };
@@ -71,16 +65,7 @@ describe("JobQueue", function () {
getMaintenanceWindowsByMonitorId: sinon.stub().returns([]),
};
networkService = { getStatus: sinon.stub() };
jobQueue = await JobQueue.createJobQueue(
db,
networkService,
statusService,
notificationService,
settingsService,
logger,
QueueStub,
WorkerStub
);
jobQueue = await JobQueue.createJobQueue(db, networkService, statusService, notificationService, settingsService, logger, QueueStub, WorkerStub);
});
afterEach(function () {
@@ -327,9 +312,7 @@ describe("JobQueue", function () {
const handler = jobQueue.createJobHandler();
await handler({ data: { _id: 1 } });
expect(logger.info.calledOnce).to.be.true;
expect(logger.info.firstCall.args[0].message).to.equal(
"Monitor 1 is in maintenance window"
);
expect(logger.info.firstCall.args[0].message).to.equal("Monitor 1 is in maintenance window");
});
it("should return if status has not changed", async function () {
@@ -362,9 +345,7 @@ describe("JobQueue", function () {
WorkerStub
);
jobQueue.isInMaintenanceWindow = sinon.stub().returns(false);
statusService.updateStatus = sinon
.stub()
.returns({ statusChanged: true, prevStatus: undefined });
statusService.updateStatus = sinon.stub().returns({ statusChanged: true, prevStatus: undefined });
const handler = jobQueue.createJobHandler();
await handler({ data: { _id: 1 } });
expect(jobQueue.notificationService.handleNotifications.notCalled).to.be.true;
@@ -382,9 +363,7 @@ describe("JobQueue", function () {
WorkerStub
);
jobQueue.isInMaintenanceWindow = sinon.stub().returns(false);
statusService.updateStatus = sinon
.stub()
.returns({ statusChanged: true, prevStatus: false });
statusService.updateStatus = sinon.stub().returns({ statusChanged: true, prevStatus: false });
const handler = jobQueue.createJobHandler();
await handler({ data: { _id: 1 } });
expect(jobQueue.notificationService.handleNotifications.calledOnce).to.be.true;

View File

@@ -26,9 +26,7 @@ describe("Network Service", function () {
};
ping = {
promise: {
probe: sinon
.stub()
.resolves({ response: { alive: true }, responseTime: 100, alive: true }),
probe: sinon.stub().resolves({ response: { alive: true }, responseTime: 100, alive: true }),
},
};
logger = { error: sinon.stub() };
@@ -73,9 +71,7 @@ describe("Network Service", function () {
it("should return a response object if ping unsuccessful", async function () {
const error = new Error("Test error");
networkService.timeRequest = sinon
.stub()
.resolves({ response: null, responseTime: 1, error });
networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error });
const pingResult = await networkService.requestPing({
data: { url: "http://test.com", _id: "123" },
});
@@ -113,9 +109,7 @@ describe("Network Service", function () {
it("should return a response object if http unsuccessful", async function () {
const error = new Error("Test error");
error.response = { status: 404 };
networkService.timeRequest = sinon
.stub()
.resolves({ response: null, responseTime: 1, error });
networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error });
const job = { data: { url: "http://test.com", _id: "123", type: "http" } };
const httpResult = await networkService.requestHttp(job);
expect(httpResult.monitorId).to.equal("123");
@@ -128,9 +122,7 @@ describe("Network Service", function () {
it("should return a response object if http unsuccessful with unknown code", async function () {
const error = new Error("Test error");
error.response = {};
networkService.timeRequest = sinon
.stub()
.resolves({ response: null, responseTime: 1, error });
networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error });
const job = { data: { url: "http://test.com", _id: "123", type: "http" } };
const httpResult = await networkService.requestHttp(job);
expect(httpResult.monitorId).to.equal("123");
@@ -167,9 +159,7 @@ describe("Network Service", function () {
it("should return a response object if pagespeed unsuccessful", async function () {
const error = new Error("Test error");
error.response = { status: 404 };
networkService.timeRequest = sinon
.stub()
.resolves({ response: null, responseTime: 1, error });
networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error });
const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } };
const pagespeedResult = await networkService.requestPagespeed(job);
expect(pagespeedResult.monitorId).to.equal("123");
@@ -182,9 +172,7 @@ describe("Network Service", function () {
it("should return a response object if pagespeed unsuccessful with an unknown code", async function () {
const error = new Error("Test error");
error.response = {};
networkService.timeRequest = sinon
.stub()
.resolves({ response: null, responseTime: 1, error });
networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error });
const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } };
const pagespeedResult = await networkService.requestPagespeed(job);
expect(pagespeedResult.monitorId).to.equal("123");
@@ -237,9 +225,7 @@ describe("Network Service", function () {
it("should return a response object if hardware unsuccessful", async function () {
const error = new Error("Test error");
error.response = { status: 404 };
networkService.timeRequest = sinon
.stub()
.resolves({ response: null, responseTime: 1, error });
networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error });
const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } };
const httpResult = await networkService.requestHardware(job);
expect(httpResult.monitorId).to.equal("123");
@@ -252,9 +238,7 @@ describe("Network Service", function () {
it("should return a response object if hardware unsuccessful with unknown code", async function () {
const error = new Error("Test error");
error.response = {};
networkService.timeRequest = sinon
.stub()
.resolves({ response: null, responseTime: 1, error });
networkService.timeRequest = sinon.stub().resolves({ response: null, responseTime: 1, error });
const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } };
const httpResult = await networkService.requestHardware(job);
expect(httpResult.monitorId).to.equal("123");

View File

@@ -43,10 +43,7 @@ describe("NotificationService", function () {
await notificationService.sendEmail(networkResponse, address);
expect(notificationService.emailService.buildAndSendEmail.calledOnce).to.be.true;
expect(
notificationService.emailService.buildAndSendEmail.calledWith(
"serverIsUpTemplate",
{ monitor: "Test Monitor", url: "http://test.com" }
)
notificationService.emailService.buildAndSendEmail.calledWith("serverIsUpTemplate", { monitor: "Test Monitor", url: "http://test.com" })
);
});
@@ -132,22 +129,14 @@ describe("NotificationService", function () {
it("should send an email notification with Hardware Template", async function () {
emailService.buildAndSendEmail.resolves(true);
const res = await notificationService.sendHardwareEmail(
networkResponse,
address,
alerts
);
const res = await notificationService.sendHardwareEmail(networkResponse, address, alerts);
expect(res).to.be.true;
});
it("should return false if no alerts are provided", async function () {
alerts = [];
emailService.buildAndSendEmail.resolves(true);
const res = await notificationService.sendHardwareEmail(
networkResponse,
address,
alerts
);
const res = await notificationService.sendHardwareEmail(networkResponse, address, alerts);
expect(res).to.be.false;
});
});
@@ -172,9 +161,7 @@ describe("NotificationService", function () {
});
it("should handle status notifications", async function () {
db.getNotificationsByMonitorId.resolves([
{ type: "email", address: "test@test.com" },
]);
db.getNotificationsByMonitorId.resolves([{ type: "email", address: "test@test.com" }]);
const res = await notificationService.handleStatusNotifications(networkResponse);
expect(res).to.be.true;
});
@@ -247,15 +234,13 @@ describe("NotificationService", function () {
describe("it should return false if no thresholds are set", function () {
it("should return false if no thresholds are set", async function () {
networkResponse.monitor.thresholds = undefined;
const res =
await notificationService.handleHardwareNotifications(networkResponse);
const res = await notificationService.handleHardwareNotifications(networkResponse);
expect(res).to.be.false;
});
it("should return false if metrics are null", async function () {
networkResponse.payload.data = null;
const res =
await notificationService.handleHardwareNotifications(networkResponse);
const res = await notificationService.handleHardwareNotifications(networkResponse);
expect(res).to.be.false;
});
@@ -271,8 +256,7 @@ describe("NotificationService", function () {
save: sinon.stub().resolves(),
},
]);
const res =
await notificationService.handleHardwareNotifications(networkResponse);
const res = await notificationService.handleHardwareNotifications(networkResponse);
expect(res).to.be.true;
});
@@ -293,8 +277,7 @@ describe("NotificationService", function () {
usage_memory: 0.01,
usage_disk: 0.01,
};
const res =
await notificationService.handleHardwareNotifications(networkResponse);
const res = await notificationService.handleHardwareNotifications(networkResponse);
expect(res).to.be.true;
});
});

View File

@@ -13,9 +13,7 @@ describe("SettingsService", function () {
sandbox.stub(process.env, "JWT_SECRET").value("secret");
sandbox.stub(process.env, "REFRESH_TOKEN_SECRET").value("refreshSecret");
sandbox.stub(process.env, "DB_TYPE").value("postgres");
sandbox
.stub(process.env, "DB_CONNECTION_STRING")
.value("postgres://user:pass@localhost/db");
sandbox.stub(process.env, "DB_CONNECTION_STRING").value("postgres://user:pass@localhost/db");
sandbox.stub(process.env, "REDIS_HOST").value("localhost");
sandbox.stub(process.env, "REDIS_PORT").value("6379");
sandbox.stub(process.env, "TOKEN_TTL").value("3600");

View File

@@ -73,9 +73,7 @@ describe("StatusService", () => {
});
it("should return {statusChanged: true} if status has changed from down to up", async function () {
statusService.db.getMonitorById = sinon
.stub()
.returns({ status: false, save: sinon.stub() });
statusService.db.getMonitorById = sinon.stub().returns({ status: false, save: sinon.stub() });
const result = await statusService.updateStatus({
monitorId: "test",
status: true,
@@ -87,9 +85,7 @@ describe("StatusService", () => {
});
it("should return {statusChanged: true} if status has changed from up to down", async function () {
statusService.db.getMonitorById = sinon
.stub()
.returns({ status: true, save: sinon.stub() });
statusService.db.getMonitorById = sinon.stub().returns({ status: true, save: sinon.stub() });
const result = await statusService.updateStatus({
monitorId: "test",
status: false,

View File

@@ -51,13 +51,7 @@ describe("NormalizeData", function () {
describe("calculatePercentile", function () {
it("should return the lower value when upper is greater than or equal to the length of the sorted array", function () {
const checks = [
{ responseTime: 10 },
{ responseTime: 20 },
{ responseTime: 30 },
{ responseTime: 40 },
{ responseTime: 50 },
];
const checks = [{ responseTime: 10 }, { responseTime: 20 }, { responseTime: 30 }, { responseTime: 40 }, { responseTime: 50 }];
const percentile = 100;
const result = calculatePercentile(checks, percentile);

View File

@@ -12,9 +12,7 @@ describe("imageProcessing - GenerateAvatarImage", function () {
// Stub the sharp function
const toBufferStub = sinon.stub().resolves(Buffer.from("resized image buffer"));
const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub });
const sharpStub = sinon
.stub(sharp.prototype, "resize")
.returns({ toBuffer: toBufferStub });
const sharpStub = sinon.stub(sharp.prototype, "resize").returns({ toBuffer: toBufferStub });
const result = await GenerateAvatarImage(file);
@@ -38,9 +36,7 @@ describe("imageProcessing - GenerateAvatarImage", function () {
// Stub the sharp function to throw an error
const toBufferStub = sinon.stub().rejects(new Error("Resizing failed"));
const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub });
const sharpStub = sinon
.stub(sharp.prototype, "resize")
.returns({ toBuffer: toBufferStub });
const sharpStub = sinon.stub(sharp.prototype, "resize").returns({ toBuffer: toBufferStub });
try {
await GenerateAvatarImage(file);

View File

@@ -3,25 +3,19 @@ describe("Messages", function () {
describe("messages - errorMessages", function () {
it("should have a DB_FIND_MONITOR_BY_ID function", function () {
const monitorId = "12345";
expect(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)).to.equal(
`Monitor with id ${monitorId} not found`
);
expect(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)).to.equal(`Monitor with id ${monitorId} not found`);
});
it("should have a DB_DELETE_CHECKS function", function () {
const monitorId = "12345";
expect(errorMessages.DB_DELETE_CHECKS(monitorId)).to.equal(
`No checks found for monitor with id ${monitorId}`
);
expect(errorMessages.DB_DELETE_CHECKS(monitorId)).to.equal(`No checks found for monitor with id ${monitorId}`);
});
});
describe("messages - successMessages", function () {
it("should have a MONITOR_GET_BY_USER_ID function", function () {
const userId = "12345";
expect(successMessages.MONITOR_GET_BY_USER_ID(userId)).to.equal(
`Got monitor for ${userId} successfully"`
);
expect(successMessages.MONITOR_GET_BY_USER_ID(userId)).to.equal(`Got monitor for ${userId} successfully"`);
});
// Add more tests for other success messages as needed

View File

@@ -15,9 +15,7 @@ const calculatePercentileUptimeDetails = (arr, percentile) => {
const upper = lower + 1;
const weight = index % 1;
if (upper >= sorted.length) return sorted[lower].avgResponseTime;
return (
sorted[lower].avgResponseTime * (1 - weight) + sorted[upper].avgResponseTime * weight
);
return sorted[lower].avgResponseTime * (1 - weight) + sorted[upper].avgResponseTime * weight;
};
const NormalizeData = (checks, rangeMin, rangeMax) => {
@@ -28,15 +26,11 @@ const NormalizeData = (checks, rangeMin, rangeMax) => {
const normalizedChecks = checks.map((check) => {
const originalResponseTime = check.responseTime;
// Normalize the response time between 1 and 100
let normalizedResponseTime =
rangeMin + ((check.responseTime - min) * (rangeMax - rangeMin)) / (max - min);
let normalizedResponseTime = rangeMin + ((check.responseTime - min) * (rangeMax - rangeMin)) / (max - min);
// Put a floor on the response times so we don't have extreme outliers
// Better visuals
normalizedResponseTime = Math.max(
rangeMin,
Math.min(rangeMax, normalizedResponseTime)
);
normalizedResponseTime = Math.max(rangeMin, Math.min(rangeMax, normalizedResponseTime));
return {
...check,
responseTime: normalizedResponseTime,
@@ -60,15 +54,11 @@ const NormalizeDataUptimeDetails = (checks, rangeMin, rangeMax) => {
const normalizedChecks = checks.map((check) => {
const originalResponseTime = check.avgResponseTime;
// Normalize the response time between 1 and 100
let normalizedResponseTime =
rangeMin + ((check.avgResponseTime - min) * (rangeMax - rangeMin)) / (max - min);
let normalizedResponseTime = rangeMin + ((check.avgResponseTime - min) * (rangeMax - rangeMin)) / (max - min);
// Put a floor on the response times so we don't have extreme outliers
// Better visuals
normalizedResponseTime = Math.max(
rangeMin,
Math.min(rangeMax, normalizedResponseTime)
);
normalizedResponseTime = Math.max(rangeMin, Math.min(rangeMax, normalizedResponseTime));
return {
...check,
avgResponseTime: normalizedResponseTime,
@@ -107,10 +97,4 @@ const safelyParseFloat = (value, defaultValue = 0) => {
return parsedValue;
};
export {
safelyParseFloat,
calculatePercentile,
NormalizeData,
calculatePercentileUptimeDetails,
NormalizeDataUptimeDetails,
};
export { safelyParseFloat, calculatePercentile, NormalizeData, calculatePercentileUptimeDetails, NormalizeDataUptimeDetails };

View File

@@ -10,67 +10,31 @@ class AppError extends Error {
}
}
export const createError = (
message,
status = 500,
service = null,
method = null,
details = null
) => {
export const createError = (message, status = 500, service = null, method = null, details = null) => {
return new AppError(message, status, service, method, details);
};
export const createValidationError = (
message,
details = null,
service = null,
method = null
) => {
export const createValidationError = (message, details = null, service = null, method = null) => {
return createError(message, 422, service, method, details);
};
export const createAuthError = (
message,
details = null,
service = null,
method = null
) => {
export const createAuthError = (message, details = null, service = null, method = null) => {
return createError(message, 401, service, method, details);
};
export const createForbiddenError = (
message,
details = null,
service = null,
method = null
) => {
export const createForbiddenError = (message, details = null, service = null, method = null) => {
return createError(message, 403, service, method, details);
};
export const createNotFoundError = (
message,
details = null,
service = null,
method = null
) => {
export const createNotFoundError = (message, details = null, service = null, method = null) => {
return createError(message, 404, service, method, details);
};
export const createConflictError = (
message,
details = null,
service = null,
method = null
) => {
export const createConflictError = (message, details = null, service = null, method = null) => {
return createError(message, 409, service, method, details);
};
export const createServerError = (
message,
details = null,
service = null,
method = null
) => {
export const createServerError = (message, details = null, service = null, method = null) => {
return createError(message, 500, service, method, details);
};
@@ -81,12 +45,7 @@ export const asyncHandler = (fn, serviceName, methodName) => {
} catch (error) {
// Handle validation errors
if (error.isJoi || error.name === "ValidationError") {
const validationError = createValidationError(
error.message,
error.details,
serviceName,
methodName
);
const validationError = createValidationError(error.message, error.details, serviceName, methodName);
return next(validationError);
}
@@ -107,10 +66,7 @@ export const asyncHandler = (fn, serviceName, methodName) => {
}
// For unknown errors, create a server error
const appError = createServerError(
error.message || "An unexpected error occurred",
{ originalError: error.message, stack: error.stack }
);
const appError = createServerError(error.message || "An unexpected error occurred", { originalError: error.message, stack: error.stack });
appError.service = serviceName;
appError.method = methodName;
appError.stack = error.stack; // Preserve original stack

View File

@@ -6,43 +6,41 @@ class Logger {
constructor() {
this.logCache = [];
this.maxCacheSize = 1000;
const consoleFormat = format.printf(
({ level, message, service, method, details, timestamp, stack }) => {
if (message instanceof Object) {
message = JSON.stringify(message, null, 2);
}
if (details instanceof Object) {
details = JSON.stringify(details, null, 2);
}
let msg = `${timestamp} ${level}:`;
service && (msg += ` [${service}]`);
method && (msg += `(${method})`);
message && (msg += ` ${message}`);
details && (msg += ` (details: ${details})`);
if (typeof stack !== "undefined") {
const stackTrace = stack
?.split("\n")
.slice(1) // Remove first line (error message)
.map((line) => {
const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
if (match) {
return {
function: match[1],
file: match[2],
line: parseInt(match[3]),
column: parseInt(match[4]),
};
}
return line.trim();
});
stack && (msg += ` (stack: ${JSON.stringify(stackTrace, null, 2)})`);
}
return msg;
const consoleFormat = format.printf(({ level, message, service, method, details, timestamp, stack }) => {
if (message instanceof Object) {
message = JSON.stringify(message, null, 2);
}
);
if (details instanceof Object) {
details = JSON.stringify(details, null, 2);
}
let msg = `${timestamp} ${level}:`;
service && (msg += ` [${service}]`);
method && (msg += `(${method})`);
message && (msg += ` ${message}`);
details && (msg += ` (details: ${details})`);
if (typeof stack !== "undefined") {
const stackTrace = stack
?.split("\n")
.slice(1) // Remove first line (error message)
.map((line) => {
const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
if (match) {
return {
function: match[1],
file: match[2],
line: parseInt(match[3]),
column: parseInt(match[4]),
};
}
return line.trim();
});
stack && (msg += ` (stack: ${JSON.stringify(stackTrace, null, 2)})`);
}
return msg;
});
const logLevel = process.env.LOG_LEVEL || "info";
@@ -51,12 +49,7 @@ class Logger {
format: format.combine(format.timestamp()),
transports: [
new transports.Console({
format: format.combine(
format.colorize(),
format.prettyPrint(),
format.json(),
consoleFormat
),
format: format.combine(format.colorize(), format.prettyPrint(), format.json(), consoleFormat),
}),
new transports.File({
format: format.combine(format.json()),

View File

@@ -6,12 +6,7 @@
const ParseBoolean = (value) => {
if (value === true || value === "true") {
return true;
} else if (
value === false ||
value === "false" ||
value === null ||
value === undefined
) {
} else if (value === false || value === "false" || value === null || value === undefined) {
return false;
}
};
@@ -21,8 +16,7 @@ const getTokenFromHeaders = (headers) => {
if (!authorizationHeader) throw new Error("No auth headers");
const parts = authorizationHeader.split(" ");
if (parts.length !== 2 || parts[0] !== "Bearer")
throw new Error("Invalid auth headers");
if (parts.length !== 2 || parts[0] !== "Bearer") throw new Error("Invalid auth headers");
return parts[1];
};

View File

@@ -7,9 +7,7 @@ import joi from "joi";
const roleValidatior = (role) => (value, helpers) => {
const hasRole = role.some((role) => value.includes(role));
if (!hasRole) {
throw new joi.ValidationError(
`You do not have the required authorization. Required roles: ${role.join(", ")}`
);
throw new joi.ValidationError(`You do not have the required authorization. Required roles: ${role.join(", ")}`);
}
return value;
};
@@ -134,11 +132,7 @@ const getMonitorsByTeamIdQueryValidation = joi.object({
.alternatives()
.try(
joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port"),
joi
.array()
.items(
joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port")
)
joi.array().items(joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port"))
),
page: joi.number(),
rowsPerPage: joi.number(),
@@ -454,8 +448,7 @@ const createStatusPageBodyValidation = joi.object({
.pattern(/^[a-zA-Z0-9_-]+$/) // Only allow alphanumeric, underscore, and hyphen
.required()
.messages({
"string.pattern.base":
"URL can only contain letters, numbers, underscores, and hyphens",
"string.pattern.base": "URL can only contain letters, numbers, underscores, and hyphens",
}),
timezone: joi.string().optional(),
color: joi.string().optional(),
@@ -485,13 +478,9 @@ const imageValidation = joi
fieldname: joi.string().required(),
originalname: joi.string().required(),
encoding: joi.string().required(),
mimetype: joi
.string()
.valid("image/jpeg", "image/png", "image/jpg")
.required()
.messages({
"string.valid": "File must be a valid image (jpeg, jpg, or png)",
}),
mimetype: joi.string().valid("image/jpeg", "image/png", "image/jpg").required().messages({
"string.valid": "File must be a valid image (jpeg, jpg, or png)",
}),
size: joi.number().max(3145728).required().messages({
"number.max": "File size must be less than 3MB",
}),
@@ -578,15 +567,11 @@ const createNotificationBodyValidation = joi.object({
"any.required": "Notification name is required",
}),
type: joi
.string()
.valid("email", "webhook", "slack", "discord", "pager_duty")
.required()
.messages({
"string.empty": "Notification type is required",
"any.required": "Notification type is required",
"any.only": "Notification type must be email, webhook, or pager_duty",
}),
type: joi.string().valid("email", "webhook", "slack", "discord", "pager_duty").required().messages({
"string.empty": "Notification type is required",
"any.required": "Notification type is required",
"any.only": "Notification type must be email, webhook, or pager_duty",
}),
address: joi.when("type", {
is: "email",