Merge pull request #978 from bluewave-labs/fix/be/prettier-baseline

Format all files on BE with perttier config
This commit is contained in:
Alexander Holliday
2024-10-18 10:03:18 +08:00
committed by GitHub
57 changed files with 8344 additions and 8352 deletions

View File

@@ -1,15 +1,15 @@
const PORT = 5000;
const connectDbAndRunServer = async (app, db) => {
try {
await db.connect();
app.listen(PORT, () => {
console.log(`server started on port:${PORT}`);
});
} catch (error) {
console.log("Failed to connect to DB");
console.error(error);
}
try {
await db.connect();
app.listen(PORT, () => {
console.log(`server started on port:${PORT}`);
});
} catch (error) {
console.log("Failed to connect to DB");
console.error(error);
}
};
export { connectDbAndRunServer };

View File

@@ -1,13 +1,13 @@
import {
createCheckParamValidation,
createCheckBodyValidation,
getChecksParamValidation,
getChecksQueryValidation,
getTeamChecksParamValidation,
getTeamChecksQueryValidation,
deleteChecksParamValidation,
deleteChecksByTeamIdParamValidation,
updateChecksTTLBodyValidation,
createCheckParamValidation,
createCheckBodyValidation,
getChecksParamValidation,
getChecksQueryValidation,
getTeamChecksParamValidation,
getTeamChecksQueryValidation,
deleteChecksParamValidation,
deleteChecksByTeamIdParamValidation,
updateChecksTTLBodyValidation,
} from "../validation/joi.js";
import { successMessages } from "../utils/messages.js";
import jwt from "jsonwebtoken";
@@ -17,138 +17,138 @@ import { handleValidationError, handleError } from "./controllerUtils.js";
const SERVICE_NAME = "checkController";
const createCheck = async (req, res, next) => {
try {
await createCheckParamValidation.validateAsync(req.params);
await createCheckBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await createCheckParamValidation.validateAsync(req.params);
await createCheckBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const checkData = { ...req.body };
const check = await req.db.createCheck(checkData);
return res
.status(200)
.json({ success: true, msg: successMessages.CHECK_CREATE, data: check });
} catch (error) {
next(handleError(error, SERVICE_NAME, "createCheck"));
}
try {
const checkData = { ...req.body };
const check = await req.db.createCheck(checkData);
return res
.status(200)
.json({ success: true, msg: successMessages.CHECK_CREATE, data: check });
} catch (error) {
next(handleError(error, SERVICE_NAME, "createCheck"));
}
};
const getChecks = async (req, res, next) => {
try {
await getChecksParamValidation.validateAsync(req.params);
await getChecksQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await getChecksParamValidation.validateAsync(req.params);
await getChecksQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const checks = await req.db.getChecks(req);
const checksCount = await req.db.getChecksCount(req);
return res.status(200).json({
success: true,
msg: successMessages.CHECK_GET,
data: { checksCount, checks },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getChecks"));
}
try {
const checks = await req.db.getChecks(req);
const checksCount = await req.db.getChecksCount(req);
return res.status(200).json({
success: true,
msg: successMessages.CHECK_GET,
data: { checksCount, checks },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getChecks"));
}
};
const getTeamChecks = async (req, res, next) => {
try {
await getTeamChecksParamValidation.validateAsync(req.params);
await getTeamChecksQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const checkData = await req.db.getTeamChecks(req);
return res.status(200).json({
success: true,
msg: successMessages.CHECK_GET,
data: checkData,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getTeamChecks"));
}
try {
await getTeamChecksParamValidation.validateAsync(req.params);
await getTeamChecksQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const checkData = await req.db.getTeamChecks(req);
return res.status(200).json({
success: true,
msg: successMessages.CHECK_GET,
data: checkData,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getTeamChecks"));
}
};
const deleteChecks = async (req, res, next) => {
try {
await deleteChecksParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await deleteChecksParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const deletedCount = await req.db.deleteChecks(req.params.monitorId);
return res.status(200).json({
success: true,
msg: successMessages.CHECK_DELETE,
data: { deletedCount },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteChecks"));
}
try {
const deletedCount = await req.db.deleteChecks(req.params.monitorId);
return res.status(200).json({
success: true,
msg: successMessages.CHECK_DELETE,
data: { deletedCount },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteChecks"));
}
};
const deleteChecksByTeamId = async (req, res, next) => {
try {
await deleteChecksByTeamIdParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await deleteChecksByTeamIdParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const deletedCount = await req.db.deleteChecksByTeamId(req.params.teamId);
return res.status(200).json({
success: true,
msg: successMessages.CHECK_DELETE,
data: { deletedCount },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteChecksByTeamId"));
}
try {
const deletedCount = await req.db.deleteChecksByTeamId(req.params.teamId);
return res.status(200).json({
success: true,
msg: successMessages.CHECK_DELETE,
data: { deletedCount },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteChecksByTeamId"));
}
};
const updateChecksTTL = async (req, res, next) => {
const SECONDS_PER_DAY = 86400;
const SECONDS_PER_DAY = 86400;
try {
await updateChecksTTLBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await updateChecksTTLBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
// Get user's teamId
const token = getTokenFromHeaders(req.headers);
const { jwtSecret } = req.settingsService.getSettings();
const { teamId } = jwt.verify(token, jwtSecret);
const ttl = parseInt(req.body.ttl, 10) * SECONDS_PER_DAY;
await req.db.updateChecksTTL(teamId, ttl);
return res.status(200).json({
success: true,
msg: successMessages.CHECK_UPDATE_TTL,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "updateTTL"));
}
try {
// Get user's teamId
const token = getTokenFromHeaders(req.headers);
const { jwtSecret } = req.settingsService.getSettings();
const { teamId } = jwt.verify(token, jwtSecret);
const ttl = parseInt(req.body.ttl, 10) * SECONDS_PER_DAY;
await req.db.updateChecksTTL(teamId, ttl);
return res.status(200).json({
success: true,
msg: successMessages.CHECK_UPDATE_TTL,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "updateTTL"));
}
};
export {
createCheck,
getChecks,
getTeamChecks,
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
createCheck,
getChecks,
getTeamChecks,
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
};

View File

@@ -1,7 +1,7 @@
import {
inviteRoleValidation,
inviteBodyValidation,
inviteVerificationBodyValidation,
inviteRoleValidation,
inviteBodyValidation,
inviteVerificationBodyValidation,
} from "../validation/joi.js";
import logger from "../utils/logger.js";
import dotenv from "dotenv";
@@ -27,62 +27,58 @@ const SERVICE_NAME = "inviteController";
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
*/
const issueInvitation = async (req, res, next) => {
try {
// Only admins can invite
const token = getTokenFromHeaders(req.headers);
const { role, firstname, teamId } = jwt.decode(token);
req.body.teamId = teamId;
try {
await inviteRoleValidation.validateAsync({ roles: role });
await inviteBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
// Only admins can invite
const token = getTokenFromHeaders(req.headers);
const { role, firstname, teamId } = jwt.decode(token);
req.body.teamId = teamId;
try {
await inviteRoleValidation.validateAsync({ roles: role });
await inviteBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
const inviteToken = await req.db.requestInviteToken({ ...req.body });
const { clientHost } = req.settingsService.getSettings();
req.emailService
.buildAndSendEmail(
"employeeActivationTemplate",
{
name: firstname,
link: `${clientHost}/register/${inviteToken.token}`,
},
req.body.email,
"Welcome to Uptime Monitor"
)
.catch((error) => {
logger.error("Error sending invite email", {
service: SERVICE_NAME,
error: error.message,
});
});
const inviteToken = await req.db.requestInviteToken({ ...req.body });
const { clientHost } = req.settingsService.getSettings();
req.emailService
.buildAndSendEmail(
"employeeActivationTemplate",
{
name: firstname,
link: `${clientHost}/register/${inviteToken.token}`,
},
req.body.email,
"Welcome to Uptime Monitor"
)
.catch((error) => {
logger.error("Error sending invite email", {
service: SERVICE_NAME,
error: error.message,
});
});
return res
.status(200)
.json({ success: true, msg: "Invite sent", data: inviteToken });
} catch (error) {
next(handleError(error, SERVICE_NAME, "inviteController"));
}
return res.status(200).json({ success: true, msg: "Invite sent", data: inviteToken });
} catch (error) {
next(handleError(error, SERVICE_NAME, "inviteController"));
}
};
const inviteVerifyController = async (req, res, next) => {
try {
await inviteVerificationBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await inviteVerificationBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const invite = await req.db.getInviteToken(req.body.token);
res
.status(200)
.json({ status: "success", msg: "Invite verified", data: invite });
} catch (error) {
next(handleError(error, SERVICE_NAME, "inviteVerifyController"));
}
try {
const invite = await req.db.getInviteToken(req.body.token);
res.status(200).json({ status: "success", msg: "Invite verified", data: invite });
} catch (error) {
next(handleError(error, SERVICE_NAME, "inviteVerifyController"));
}
};
export { issueInvitation, inviteVerifyController };

View File

@@ -1,11 +1,11 @@
import {
createMaintenanceWindowBodyValidation,
editMaintenanceWindowByIdParamValidation,
editMaintenanceByIdWindowBodyValidation,
getMaintenanceWindowByIdParamValidation,
getMaintenanceWindowsByMonitorIdParamValidation,
getMaintenanceWindowsByTeamIdQueryValidation,
deleteMaintenanceWindowByIdParamValidation,
createMaintenanceWindowBodyValidation,
editMaintenanceWindowByIdParamValidation,
editMaintenanceByIdWindowBodyValidation,
getMaintenanceWindowByIdParamValidation,
getMaintenanceWindowsByMonitorIdParamValidation,
getMaintenanceWindowsByTeamIdQueryValidation,
deleteMaintenanceWindowByIdParamValidation,
} from "../validation/joi.js";
import jwt from "jsonwebtoken";
import { getTokenFromHeaders } from "../utils/utils.js";
@@ -15,157 +15,153 @@ import { handleValidationError, handleError } from "./controllerUtils.js";
const SERVICE_NAME = "maintenanceWindowController";
const createMaintenanceWindows = async (req, res, next) => {
try {
await createMaintenanceWindowBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const token = getTokenFromHeaders(req.headers);
const { jwtSecret } = req.settingsService.getSettings();
const { teamId } = jwt.verify(token, jwtSecret);
const monitorIds = req.body.monitors;
const dbTransactions = monitorIds.map((monitorId) => {
return req.db.createMaintenanceWindow({
teamId,
monitorId,
name: req.body.name,
active: req.body.active ? req.body.active : true,
repeat: req.body.repeat,
start: req.body.start,
end: req.body.end,
});
});
await Promise.all(dbTransactions);
return res.status(201).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_CREATE,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "createMaintenanceWindow"));
}
try {
await createMaintenanceWindowBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const token = getTokenFromHeaders(req.headers);
const { jwtSecret } = req.settingsService.getSettings();
const { teamId } = jwt.verify(token, jwtSecret);
const monitorIds = req.body.monitors;
const dbTransactions = monitorIds.map((monitorId) => {
return req.db.createMaintenanceWindow({
teamId,
monitorId,
name: req.body.name,
active: req.body.active ? req.body.active : true,
repeat: req.body.repeat,
start: req.body.start,
end: req.body.end,
});
});
await Promise.all(dbTransactions);
return res.status(201).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_CREATE,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "createMaintenanceWindow"));
}
};
const getMaintenanceWindowById = async (req, res, next) => {
try {
await getMaintenanceWindowByIdParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const maintenanceWindow = await req.db.getMaintenanceWindowById(
req.params.id
);
return res.status(200).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID,
data: maintenanceWindow,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMaintenanceWindowById"));
}
try {
await getMaintenanceWindowByIdParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const maintenanceWindow = await req.db.getMaintenanceWindowById(req.params.id);
return res.status(200).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID,
data: maintenanceWindow,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMaintenanceWindowById"));
}
};
const getMaintenanceWindowsByTeamId = async (req, res, next) => {
try {
await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const token = getTokenFromHeaders(req.headers);
const { jwtSecret } = req.settingsService.getSettings();
const { teamId } = jwt.verify(token, jwtSecret);
const maintenanceWindows = await req.db.getMaintenanceWindowsByTeamId(
teamId,
req.query
);
try {
const token = getTokenFromHeaders(req.headers);
const { jwtSecret } = req.settingsService.getSettings();
const { teamId } = jwt.verify(token, jwtSecret);
const maintenanceWindows = await req.db.getMaintenanceWindowsByTeamId(
teamId,
req.query
);
return res.status(200).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM,
data: maintenanceWindows,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMaintenanceWindowsByUserId"));
}
return res.status(200).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM,
data: maintenanceWindows,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMaintenanceWindowsByUserId"));
}
};
const getMaintenanceWindowsByMonitorId = async (req, res, next) => {
try {
await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(
req.params
);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const maintenanceWindows = await req.db.getMaintenanceWindowsByMonitorId(
req.params.monitorId
);
try {
const maintenanceWindows = await req.db.getMaintenanceWindowsByMonitorId(
req.params.monitorId
);
return res.status(200).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_USER,
data: maintenanceWindows,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMaintenanceWindowsByMonitorId"));
}
return res.status(200).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_USER,
data: maintenanceWindows,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMaintenanceWindowsByMonitorId"));
}
};
const deleteMaintenanceWindow = async (req, res, next) => {
try {
await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await req.db.deleteMaintenanceWindowById(req.params.id);
return res.status(200).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_DELETE,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteMaintenanceWindow"));
}
try {
await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await req.db.deleteMaintenanceWindowById(req.params.id);
return res.status(200).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_DELETE,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteMaintenanceWindow"));
}
};
const editMaintenanceWindow = async (req, res, next) => {
try {
await editMaintenanceWindowByIdParamValidation.validateAsync(req.params);
await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const editedMaintenanceWindow = await req.db.editMaintenanceWindowById(
req.params.id,
req.body
);
return res.status(200).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_EDIT,
data: editedMaintenanceWindow,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "editMaintenanceWindow"));
}
try {
await editMaintenanceWindowByIdParamValidation.validateAsync(req.params);
await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const editedMaintenanceWindow = await req.db.editMaintenanceWindowById(
req.params.id,
req.body
);
return res.status(200).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_EDIT,
data: editedMaintenanceWindow,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "editMaintenanceWindow"));
}
};
export {
createMaintenanceWindows,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindow,
editMaintenanceWindow,
createMaintenanceWindows,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindow,
editMaintenanceWindow,
};

View File

@@ -4,56 +4,54 @@ import { errorMessages, successMessages } from "../utils/messages.js";
const SERVICE_NAME = "JobQueueController";
const getMetrics = async (req, res, next) => {
try {
const metrics = await req.jobQueue.getMetrics();
res.status(200).json({
success: true,
msg: successMessages.QUEUE_GET_METRICS,
data: metrics,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMetrics"));
return;
}
try {
const metrics = await req.jobQueue.getMetrics();
res.status(200).json({
success: true,
msg: successMessages.QUEUE_GET_METRICS,
data: metrics,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMetrics"));
return;
}
};
const getJobs = async (req, res, next) => {
try {
const jobs = await req.jobQueue.getJobStats();
return res.status(200).json({
success: true,
msg: successMessages.QUEUE_GET_METRICS,
data: jobs,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getJobs"));
return;
}
try {
const jobs = await req.jobQueue.getJobStats();
return res.status(200).json({
success: true,
msg: successMessages.QUEUE_GET_METRICS,
data: jobs,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getJobs"));
return;
}
};
const addJob = async (req, res, next) => {
try {
await req.jobQueue.addJob(Math.random().toString(36).substring(7));
return res.status(200).json({
success: true,
msg: successMessages.QUEUE_ADD_JOB,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "addJob"));
return;
}
try {
await req.jobQueue.addJob(Math.random().toString(36).substring(7));
return res.status(200).json({
success: true,
msg: successMessages.QUEUE_ADD_JOB,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "addJob"));
return;
}
};
const obliterateQueue = async (req, res, next) => {
try {
await req.jobQueue.obliterate();
return res
.status(200)
.json({ success: true, msg: successMessages.QUEUE_OBLITERATE });
} catch (error) {
next(handleError(error, SERVICE_NAME, "obliterateQueue"));
return;
}
try {
await req.jobQueue.obliterate();
return res.status(200).json({ success: true, msg: successMessages.QUEUE_OBLITERATE });
} catch (error) {
next(handleError(error, SERVICE_NAME, "obliterateQueue"));
return;
}
};
export { getMetrics, getJobs, addJob, obliterateQueue };

View File

@@ -4,39 +4,39 @@ import { handleValidationError, handleError } from "./controllerUtils.js";
const SERVICE_NAME = "SettingsController";
const getAppSettings = async (req, res, next) => {
try {
const settings = { ...(await req.settingsService.getSettings()) };
delete settings.jwtSecret;
return res.status(200).json({
success: true,
msg: successMessages.GET_APP_SETTINGS,
data: settings,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getAppSettings"));
}
try {
const settings = { ...(await req.settingsService.getSettings()) };
delete settings.jwtSecret;
return res.status(200).json({
success: true,
msg: successMessages.GET_APP_SETTINGS,
data: settings,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getAppSettings"));
}
};
const updateAppSettings = async (req, res, next) => {
try {
await updateAppSettingsBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await updateAppSettingsBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await req.db.updateAppSettings(req.body);
const updatedSettings = { ...(await req.settingsService.reloadSettings()) };
delete updatedSettings.jwtSecret;
return res.status(200).json({
success: true,
msg: successMessages.UPDATE_APP_SETTINGS,
data: updatedSettings,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "updateAppSettings"));
}
try {
await req.db.updateAppSettings(req.body);
const updatedSettings = { ...(await req.settingsService.reloadSettings()) };
delete updatedSettings.jwtSecret;
return res.status(200).json({
success: true,
msg: successMessages.UPDATE_APP_SETTINGS,
data: updatedSettings,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "updateAppSettings"));
}
};
export { getAppSettings, updateAppSettings };

View File

@@ -28,111 +28,111 @@ let FAKE_MONITOR_DATA = [];
const USERS = [];
const connect = async () => {
try {
await console.log("Connected to FakeDB");
} catch (error) {
console.error(error);
}
try {
await console.log("Connected to FakeDB");
} catch (error) {
console.error(error);
}
};
const insertUser = async (req, res) => {
try {
const newUser = new UserModel({ ...req.body });
const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait
newUser.password = await bcrypt.hash(newUser.password, salt); // hash is also async, need to eitehr await or use hashSync
USERS.push(newUser);
const userToReturn = { ...newUser._doc };
delete userToReturn.password;
return userToReturn;
} catch (error) {
throw error;
}
try {
const newUser = new UserModel({ ...req.body });
const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait
newUser.password = await bcrypt.hash(newUser.password, salt); // hash is also async, need to eitehr await or use hashSync
USERS.push(newUser);
const userToReturn = { ...newUser._doc };
delete userToReturn.password;
return userToReturn;
} catch (error) {
throw error;
}
};
const getUserByEmail = async (req, res) => {
const email = req.body.email;
try {
const idx = USERS.findIndex((user) => {
return user.email === email;
});
if (idx === -1) {
return null;
}
return USERS[idx];
} catch (error) {
throw new Error(`User with email ${email} not found`);
}
const email = req.body.email;
try {
const idx = USERS.findIndex((user) => {
return user.email === email;
});
if (idx === -1) {
return null;
}
return USERS[idx];
} catch (error) {
throw new Error(`User with email ${email} not found`);
}
};
const getAllMonitors = async () => {
return FAKE_MONITOR_DATA;
return FAKE_MONITOR_DATA;
};
const getMonitorById = async (monitorId) => {
const idx = FAKE_MONITOR_DATA.findIndex((monitor) => {
return monitor.id === monitorId;
});
if (idx === -1) {
throw new Error(`Monitor with id ${monitorId} not found`);
}
return FAKE_MONITOR_DATA[idx];
const idx = FAKE_MONITOR_DATA.findIndex((monitor) => {
return monitor.id === monitorId;
});
if (idx === -1) {
throw new Error(`Monitor with id ${monitorId} not found`);
}
return FAKE_MONITOR_DATA[idx];
};
const getMonitorsByUserId = async (userId) => {
const userMonitors = FAKE_MONITOR_DATA.filter((monitor) => {
return monitor.userId === userId;
});
const userMonitors = FAKE_MONITOR_DATA.filter((monitor) => {
return monitor.userId === userId;
});
if (userMonitors.length === 0) {
throw new Error(`Monitors for user ${userId} not found`);
}
return userMonitors;
if (userMonitors.length === 0) {
throw new Error(`Monitors for user ${userId} not found`);
}
return userMonitors;
};
const createMonitor = async (req, res) => {
const monitor = new Monitor(req.body);
monitor.createdAt = Date.now();
monitor.updatedAt = Date.now();
FAKE_MONITOR_DATA.push(monitor);
return monitor;
const monitor = new Monitor(req.body);
monitor.createdAt = Date.now();
monitor.updatedAt = Date.now();
FAKE_MONITOR_DATA.push(monitor);
return monitor;
};
const deleteMonitor = async (req, res) => {
const monitorId = req.params.monitorId;
try {
const monitor = getMonitorById(monitorId);
FAKE_MONITOR_DATA = FAKE_MONITOR_DATA.filter((monitor) => {
return monitor.id !== monitorId;
});
return monitor;
} catch (error) {
throw error;
}
const monitorId = req.params.monitorId;
try {
const monitor = getMonitorById(monitorId);
FAKE_MONITOR_DATA = FAKE_MONITOR_DATA.filter((monitor) => {
return monitor.id !== monitorId;
});
return monitor;
} catch (error) {
throw error;
}
};
const editMonitor = async (req, res) => {
const monitorId = req.params.monitorId;
const idx = FAKE_MONITOR_DATA.findIndex((monitor) => {
return monitor._id.toString() === monitorId;
});
const oldMonitor = FAKE_MONITOR_DATA[idx];
const editedMonitor = new Monitor({ ...req.body });
editedMonitor._id = oldMonitor._id;
editedMonitor.userId = oldMonitor.userId;
editedMonitor.updatedAt = Date.now();
editedMonitor.createdAt = oldMonitor.createdAt;
FAKE_MONITOR_DATA[idx] = editedMonitor;
return FAKE_MONITOR_DATA[idx];
const monitorId = req.params.monitorId;
const idx = FAKE_MONITOR_DATA.findIndex((monitor) => {
return monitor._id.toString() === monitorId;
});
const oldMonitor = FAKE_MONITOR_DATA[idx];
const editedMonitor = new Monitor({ ...req.body });
editedMonitor._id = oldMonitor._id;
editedMonitor.userId = oldMonitor.userId;
editedMonitor.updatedAt = Date.now();
editedMonitor.createdAt = oldMonitor.createdAt;
FAKE_MONITOR_DATA[idx] = editedMonitor;
return FAKE_MONITOR_DATA[idx];
};
module.exports = {
connect,
insertUser,
getUserByEmail,
getAllMonitors,
getMonitorById,
getMonitorsByUserId,
createMonitor,
deleteMonitor,
editMonitor,
connect,
insertUser,
getUserByEmail,
getAllMonitors,
getMonitorById,
getMonitorsByUserId,
createMonitor,
deleteMonitor,
editMonitor,
};

View File

@@ -1,91 +1,91 @@
import mongoose from "mongoose";
const AppSettingsSchema = mongoose.Schema(
{
apiBaseUrl: {
type: String,
required: true,
default: "http://localhost:5000/api/v1",
},
logLevel: {
type: String,
default: "debug",
enum: ["debug", "none", "error", "warn"],
},
clientHost: {
type: String,
required: true,
default: "http://localhost:5173",
},
jwtSecret: {
type: String,
required: true,
default: "my_secret",
},
refreshTokenSecret: {
type: String,
required: true,
default: "my_refresh_secret",
},
dbType: {
type: String,
required: true,
default: "MongoDB",
},
dbConnectionString: {
type: String,
required: true,
default: "mongodb://localhost:27017/uptime_db",
},
redisHost: {
type: String,
required: true,
default: "127.0.0.1",
},
redisPort: {
type: Number,
default: "6379",
},
jwtTTL: {
type: String,
required: true,
default: "2h",
},
refreshTokenTTL: {
type: String,
required: true,
default: "7d",
},
pagespeedApiKey: {
type: String,
default: "",
},
systemEmailHost: {
type: String,
default: "smtp.gmail.com",
},
systemEmailPort: {
type: Number,
default: 465,
},
systemEmailAddress: {
type: String,
default: "",
},
systemEmailPassword: {
type: String,
default: "",
},
singleton: {
type: Boolean,
required: true,
unique: true,
default: true,
},
},
{
timestamps: true,
}
{
apiBaseUrl: {
type: String,
required: true,
default: "http://localhost:5000/api/v1",
},
logLevel: {
type: String,
default: "debug",
enum: ["debug", "none", "error", "warn"],
},
clientHost: {
type: String,
required: true,
default: "http://localhost:5173",
},
jwtSecret: {
type: String,
required: true,
default: "my_secret",
},
refreshTokenSecret: {
type: String,
required: true,
default: "my_refresh_secret",
},
dbType: {
type: String,
required: true,
default: "MongoDB",
},
dbConnectionString: {
type: String,
required: true,
default: "mongodb://localhost:27017/uptime_db",
},
redisHost: {
type: String,
required: true,
default: "127.0.0.1",
},
redisPort: {
type: Number,
default: "6379",
},
jwtTTL: {
type: String,
required: true,
default: "2h",
},
refreshTokenTTL: {
type: String,
required: true,
default: "7d",
},
pagespeedApiKey: {
type: String,
default: "",
},
systemEmailHost: {
type: String,
default: "smtp.gmail.com",
},
systemEmailPort: {
type: Number,
default: 465,
},
systemEmailAddress: {
type: String,
default: "",
},
systemEmailPassword: {
type: String,
default: "",
},
singleton: {
type: Boolean,
required: true,
unique: true,
default: true,
},
},
{
timestamps: true,
}
);
export default mongoose.model("AppSettings", AppSettingsSchema);

View File

@@ -9,67 +9,67 @@ import Notification from "./Notification.js";
* about the status and response of a particular check event.
*/
const CheckSchema = mongoose.Schema(
{
/**
* Reference to the associated Monitor document.
*
* @type {mongoose.Schema.Types.ObjectId}
*/
monitorId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Monitor",
immutable: true,
index: true,
},
/**
* Status of the check (true for up, false for down).
*
* @type {Boolean}
*/
status: {
type: Boolean,
index: true,
},
/**
* Response time of the check in milliseconds.
*
* @type {Number}
*/
responseTime: {
type: Number,
},
/**
* HTTP status code received during the check.
*
* @type {Number}
*/
statusCode: {
type: Number,
index: true,
},
/**
* Message or description of the check result.
*
* @type {String}
*/
message: {
type: String,
},
/**
* Expiry date of the check, auto-calculated to expire after 30 days.
*
* @type {Date}
*/
{
/**
* Reference to the associated Monitor document.
*
* @type {mongoose.Schema.Types.ObjectId}
*/
monitorId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Monitor",
immutable: true,
index: true,
},
/**
* Status of the check (true for up, false for down).
*
* @type {Boolean}
*/
status: {
type: Boolean,
index: true,
},
/**
* Response time of the check in milliseconds.
*
* @type {Number}
*/
responseTime: {
type: Number,
},
/**
* HTTP status code received during the check.
*
* @type {Number}
*/
statusCode: {
type: Number,
index: true,
},
/**
* Message or description of the check result.
*
* @type {String}
*/
message: {
type: String,
},
/**
* Expiry date of the check, auto-calculated to expire after 30 days.
*
* @type {Date}
*/
expiry: {
type: Date,
default: Date.now,
expires: 60 * 60 * 24 * 30, // 30 days
},
},
{
timestamps: true, // Adds createdAt and updatedAt timestamps
}
expiry: {
type: Date,
default: Date.now,
expires: 60 * 60 * 24 * 30, // 30 days
},
},
{
timestamps: true, // Adds createdAt and updatedAt timestamps
}
);
CheckSchema.index({ createdAt: 1 });

View File

@@ -1,34 +1,34 @@
import mongoose from "mongoose";
const InviteTokenSchema = mongoose.Schema(
{
email: {
type: String,
required: true,
unique: true,
},
teamId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Team",
immutable: true,
required: true,
},
role: {
type: Array,
required: true,
},
token: {
type: String,
required: true,
},
expiry: {
type: Date,
default: Date.now,
expires: 3600,
},
},
{
timestamps: true,
}
{
email: {
type: String,
required: true,
unique: true,
},
teamId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Team",
immutable: true,
required: true,
},
role: {
type: Array,
required: true,
},
token: {
type: String,
required: true,
},
expiry: {
type: Date,
default: Date.now,
expires: 3600,
},
},
{
timestamps: true,
}
);
export default mongoose.model("InviteToken", InviteTokenSchema);

View File

@@ -27,42 +27,42 @@ import mongoose from "mongoose";
*/
const MaintenanceWindow = mongoose.Schema(
{
monitorId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Monitor",
immutable: true,
},
teamId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Team",
immutable: true,
},
active: {
type: Boolean,
default: true,
},
name: {
type: String,
},
repeat: {
type: Number,
},
start: {
type: Date,
},
end: {
type: Date,
},
expiry: {
type: Date,
index: { expires: "0s" },
},
},
{
monitorId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Monitor",
immutable: true,
},
teamId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Team",
immutable: true,
},
active: {
type: Boolean,
default: true,
},
name: {
type: String,
},
repeat: {
type: Number,
},
start: {
type: Date,
},
end: {
type: Date,
},
expiry: {
type: Date,
index: { expires: "0s" },
},
},
{
timestamps: true,
}
{
timestamps: true,
}
);
export default mongoose.model("MaintenanceWindow", MaintenanceWindow);

View File

@@ -2,62 +2,62 @@ import mongoose from "mongoose";
import Notification from "./Notification.js";
const MonitorSchema = mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
immutable: true,
required: true,
},
teamId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Team",
immutable: true,
required: true,
},
name: {
type: String,
required: true,
},
description: {
type: String,
},
status: {
type: Boolean,
default: undefined,
},
type: {
type: String,
required: true,
enum: ["http", "ping", "pagespeed"],
},
url: {
type: String,
required: true,
},
isActive: {
type: Boolean,
default: true,
},
interval: {
// in milliseconds
type: Number,
default: 60000,
},
uptimePercentage: {
type: Number,
default: undefined,
},
notifications: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Notification",
},
],
},
{
timestamps: true,
}
{
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
immutable: true,
required: true,
},
teamId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Team",
immutable: true,
required: true,
},
name: {
type: String,
required: true,
},
description: {
type: String,
},
status: {
type: Boolean,
default: undefined,
},
type: {
type: String,
required: true,
enum: ["http", "ping", "pagespeed"],
},
url: {
type: String,
required: true,
},
isActive: {
type: Boolean,
default: true,
},
interval: {
// in milliseconds
type: Number,
default: 60000,
},
uptimePercentage: {
type: Number,
default: undefined,
},
notifications: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Notification",
},
],
},
{
timestamps: true,
}
);
export default mongoose.model("Monitor", MonitorSchema);

View File

@@ -1,24 +1,24 @@
import mongoose from "mongoose";
const NotificationSchema = mongoose.Schema(
{
monitorId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Monitor",
immutable: true,
},
type: {
type: String,
enum: ["email", "sms"],
},
address: {
type: String,
},
phone: {
type: String,
},
},
{
timestamps: true,
}
{
monitorId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Monitor",
immutable: true,
},
type: {
type: String,
enum: ["email", "sms"],
},
address: {
type: String,
},
phone: {
type: String,
},
},
{
timestamps: true,
}
);
export default mongoose.model("Notification", NotificationSchema);

View File

@@ -1,36 +1,36 @@
import mongoose from "mongoose";
const AuditSchema = mongoose.Schema({
id: { type: String, required: true },
title: { type: String, required: true },
description: { type: String, required: true },
score: { type: Number, required: true },
scoreDisplayMode: { type: String, required: true },
displayValue: { type: String, required: true },
numericValue: { type: Number, required: true },
numericUnit: { type: String, required: true },
id: { type: String, required: true },
title: { type: String, required: true },
description: { type: String, required: true },
score: { type: Number, required: true },
scoreDisplayMode: { type: String, required: true },
displayValue: { type: String, required: true },
numericValue: { type: Number, required: true },
numericUnit: { type: String, required: true },
});
const AuditsSchema = mongoose.Schema({
cls: {
type: AuditSchema,
required: true,
},
si: {
type: AuditSchema,
required: true,
},
fcp: {
type: AuditSchema,
required: true,
},
lcp: {
type: AuditSchema,
required: true,
},
tbt: {
type: AuditSchema,
required: true,
},
cls: {
type: AuditSchema,
required: true,
},
si: {
type: AuditSchema,
required: true,
},
fcp: {
type: AuditSchema,
required: true,
},
lcp: {
type: AuditSchema,
required: true,
},
tbt: {
type: AuditSchema,
required: true,
},
});
/**
@@ -44,40 +44,40 @@ const AuditsSchema = mongoose.Schema({
*/
const PageSpeedCheck = mongoose.Schema(
{
monitorId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Monitor",
immutable: true,
},
status: {
type: Boolean,
required: true,
},
accessibility: {
type: Number,
required: true,
},
bestPractices: {
type: Number,
required: true,
},
seo: {
type: Number,
required: true,
},
performance: {
type: Number,
required: true,
},
audits: {
type: AuditsSchema,
required: true,
},
},
{
timestamps: true,
}
{
monitorId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Monitor",
immutable: true,
},
status: {
type: Boolean,
required: true,
},
accessibility: {
type: Number,
required: true,
},
bestPractices: {
type: Number,
required: true,
},
seo: {
type: Number,
required: true,
},
performance: {
type: Number,
required: true,
},
audits: {
type: AuditsSchema,
required: true,
},
},
{
timestamps: true,
}
);
/**
@@ -86,26 +86,26 @@ const PageSpeedCheck = mongoose.Schema(
*/
PageSpeedCheck.pre("save", async function (next) {
try {
const monitor = await mongoose.model("Monitor").findById(this.monitorId);
if (monitor && monitor.status !== this.status) {
if (monitor.status === true && this.status === false) {
// TODO issue alert
console.log("Monitor went down");
}
try {
const monitor = await mongoose.model("Monitor").findById(this.monitorId);
if (monitor && monitor.status !== this.status) {
if (monitor.status === true && this.status === false) {
// TODO issue alert
console.log("Monitor went down");
}
if (monitor.status === false && this.status === true) {
// TODO issue alert
console.log("Monitor went up");
}
monitor.status = this.status;
await monitor.save();
}
} catch (error) {
console.log(error);
} finally {
next();
}
if (monitor.status === false && this.status === true) {
// TODO issue alert
console.log("Monitor went up");
}
monitor.status = this.status;
await monitor.save();
}
} catch (error) {
console.log(error);
} finally {
next();
}
});
export default mongoose.model("PageSpeedCheck", PageSpeedCheck);

View File

@@ -1,25 +1,25 @@
import mongoose from "mongoose";
const RecoveryTokenSchema = mongoose.Schema(
{
email: {
type: String,
required: true,
unique: true,
},
token: {
type: String,
required: true,
},
expiry: {
type: Date,
default: Date.now,
expires: 600,
},
},
{
timestamps: true,
}
{
email: {
type: String,
required: true,
unique: true,
},
token: {
type: String,
required: true,
},
expiry: {
type: Date,
default: Date.now,
expires: 600,
},
},
{
timestamps: true,
}
);
export default mongoose.model("RecoveryToken", RecoveryTokenSchema);

View File

@@ -1,14 +1,14 @@
import mongoose from "mongoose";
const TeamSchema = mongoose.Schema(
{
email: {
type: String,
required: true,
unique: true,
},
},
{
timestamps: true,
}
{
email: {
type: String,
required: true,
unique: true,
},
},
{
timestamps: true,
}
);
export default mongoose.model("Team", TeamSchema);

View File

@@ -7,34 +7,34 @@ import AppSettings from "../models/AppSettings.js";
//****************************************
const connect = async () => {
try {
const connectionString =
process.env.DB_CONNECTION_STRING || "mongodb://localhost:27017/uptime_db";
await mongoose.connect(connectionString);
// If there are no AppSettings, create one
let appSettings = await AppSettings.find();
if (appSettings.length === 0) {
appSettings = new AppSettings({});
await appSettings.save();
}
try {
const connectionString =
process.env.DB_CONNECTION_STRING || "mongodb://localhost:27017/uptime_db";
await mongoose.connect(connectionString);
// If there are no AppSettings, create one
let appSettings = await AppSettings.find();
if (appSettings.length === 0) {
appSettings = new AppSettings({});
await appSettings.save();
}
console.log("Connected to MongoDB");
} catch (error) {
console.error("Failed to connect to MongoDB");
throw error;
}
console.log("Connected to MongoDB");
} catch (error) {
console.error("Failed to connect to MongoDB");
throw error;
}
};
const checkSuperadmin = async (req, res) => {
try {
const superAdmin = await UserModel.findOne({ role: "superadmin" });
if (superAdmin !== null) {
return true;
}
return false;
} catch (error) {
throw error;
}
try {
const superAdmin = await UserModel.findOne({ role: "superadmin" });
if (superAdmin !== null) {
return true;
}
return false;
} catch (error) {
throw error;
}
};
//****************************************
@@ -42,14 +42,14 @@ const checkSuperadmin = async (req, res) => {
//****************************************
import {
insertUser,
getUserByEmail,
updateUser,
deleteUser,
deleteTeam,
deleteAllOtherUsers,
getAllUsers,
logoutUser,
insertUser,
getUserByEmail,
updateUser,
deleteUser,
deleteTeam,
deleteAllOtherUsers,
getAllUsers,
logoutUser,
} from "./modules/userModule.js";
//****************************************
@@ -57,18 +57,18 @@ import {
//****************************************
import {
requestInviteToken,
getInviteToken,
getInviteTokenAndDelete,
requestInviteToken,
getInviteToken,
getInviteTokenAndDelete,
} from "./modules/inviteModule.js";
//****************************************
// Recovery Operations
//****************************************
import {
requestRecoveryToken,
validateRecoveryToken,
resetPassword,
requestRecoveryToken,
validateRecoveryToken,
resetPassword,
} from "./modules/recoveryModule.js";
//****************************************
@@ -76,17 +76,17 @@ import {
//****************************************
import {
getAllMonitors,
getMonitorStatsById,
getMonitorById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
deleteMonitor,
deleteAllMonitors,
deleteMonitorsByUserId,
editMonitor,
addDemoMonitors,
getAllMonitors,
getMonitorStatsById,
getMonitorById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
deleteMonitor,
deleteAllMonitors,
deleteMonitorsByUserId,
editMonitor,
addDemoMonitors,
} from "./modules/monitorModule.js";
//****************************************
@@ -94,9 +94,9 @@ import {
//****************************************
import {
createPageSpeedCheck,
getPageSpeedChecks,
deletePageSpeedChecksByMonitorId,
createPageSpeedCheck,
getPageSpeedChecks,
deletePageSpeedChecksByMonitorId,
} from "./modules/pageSpeedCheckModule.js";
//****************************************
@@ -104,36 +104,36 @@ import {
//****************************************
import {
createCheck,
getChecksCount,
getChecks,
getTeamChecks,
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
createCheck,
getChecksCount,
getChecks,
getTeamChecks,
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
} from "./modules/checkModule.js";
//****************************************
// Maintenance Window
//****************************************
import {
createMaintenanceWindow,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindowById,
deleteMaintenanceWindowByMonitorId,
deleteMaintenanceWindowByUserId,
editMaintenanceWindowById,
createMaintenanceWindow,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindowById,
deleteMaintenanceWindowByMonitorId,
deleteMaintenanceWindowByUserId,
editMaintenanceWindowById,
} from "./modules/maintenanceWindowModule.js";
//****************************************
// Notifications
//****************************************
import {
createNotification,
getNotificationsByMonitorId,
deleteNotificationsByMonitorId,
createNotification,
getNotificationsByMonitorId,
deleteNotificationsByMonitorId,
} from "./modules/notificationModule.js";
//****************************************
@@ -142,54 +142,54 @@ import {
import { getAppSettings, updateAppSettings } from "./modules/settingsModule.js";
export default {
connect,
insertUser,
getUserByEmail,
updateUser,
deleteUser,
deleteTeam,
deleteAllOtherUsers,
getAllUsers,
logoutUser,
requestInviteToken,
getInviteToken,
getInviteTokenAndDelete,
requestRecoveryToken,
validateRecoveryToken,
resetPassword,
checkSuperadmin,
getAllMonitors,
getMonitorStatsById,
getMonitorById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
deleteMonitor,
deleteAllMonitors,
editMonitor,
addDemoMonitors,
createCheck,
getChecksCount,
getChecks,
getTeamChecks,
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
deleteMonitorsByUserId,
createPageSpeedCheck,
getPageSpeedChecks,
deletePageSpeedChecksByMonitorId,
createMaintenanceWindow,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowById,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindowById,
deleteMaintenanceWindowByMonitorId,
deleteMaintenanceWindowByUserId,
editMaintenanceWindowById,
createNotification,
getNotificationsByMonitorId,
deleteNotificationsByMonitorId,
getAppSettings,
updateAppSettings,
connect,
insertUser,
getUserByEmail,
updateUser,
deleteUser,
deleteTeam,
deleteAllOtherUsers,
getAllUsers,
logoutUser,
requestInviteToken,
getInviteToken,
getInviteTokenAndDelete,
requestRecoveryToken,
validateRecoveryToken,
resetPassword,
checkSuperadmin,
getAllMonitors,
getMonitorStatsById,
getMonitorById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
deleteMonitor,
deleteAllMonitors,
editMonitor,
addDemoMonitors,
createCheck,
getChecksCount,
getChecks,
getTeamChecks,
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
deleteMonitorsByUserId,
createPageSpeedCheck,
getPageSpeedChecks,
deletePageSpeedChecksByMonitorId,
createMaintenanceWindow,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowById,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindowById,
deleteMaintenanceWindowByMonitorId,
deleteMaintenanceWindowByUserId,
editMaintenanceWindowById,
createNotification,
getNotificationsByMonitorId,
deleteNotificationsByMonitorId,
getAppSettings,
updateAppSettings,
};

View File

@@ -4,9 +4,9 @@ import User from "../../models/User.js";
import logger from "../../../utils/logger.js";
const SERVICE_NAME = "checkModule";
const dateRangeLookup = {
day: new Date(new Date().setDate(new Date().getDate() - 1)),
week: new Date(new Date().setDate(new Date().getDate() - 7)),
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
day: new Date(new Date().setDate(new Date().getDate() - 1)),
week: new Date(new Date().setDate(new Date().getDate() - 7)),
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
};
/**
@@ -23,67 +23,67 @@ const dateRangeLookup = {
*/
const createCheck = async (checkData) => {
try {
const { monitorId, status } = checkData;
const n = (await Check.countDocuments({ monitorId })) + 1;
const check = await new Check({ ...checkData }).save();
const monitor = await Monitor.findById(monitorId);
try {
const { monitorId, status } = checkData;
const n = (await Check.countDocuments({ monitorId })) + 1;
const check = await new Check({ ...checkData }).save();
const monitor = await Monitor.findById(monitorId);
if (!monitor) {
logger.error("Monitor not found", {
service: SERVICE_NAME,
monitorId,
});
return;
}
if (!monitor) {
logger.error("Monitor not found", {
service: SERVICE_NAME,
monitorId,
});
return;
}
// Update uptime percentage
if (monitor.uptimePercentage === undefined) {
monitor.uptimePercentage = status === true ? 1 : 0;
} else {
monitor.uptimePercentage =
(monitor.uptimePercentage * (n - 1) + (status === true ? 1 : 0)) / n;
}
// Update uptime percentage
if (monitor.uptimePercentage === undefined) {
monitor.uptimePercentage = status === true ? 1 : 0;
} else {
monitor.uptimePercentage =
(monitor.uptimePercentage * (n - 1) + (status === true ? 1 : 0)) / n;
}
await monitor.save();
await monitor.save();
return check;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "createCheck";
throw error;
}
return check;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "createCheck";
throw error;
}
};
const getChecksCount = async (req) => {
const monitorId = req.params.monitorId;
const dateRange = req.query.dateRange;
const filter = req.query.filter;
// Build query
const checksQuery = { monitorId: monitorId };
// Filter checks by "day", "week", or "month"
if (dateRange !== undefined) {
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
}
const monitorId = req.params.monitorId;
const dateRange = req.query.dateRange;
const filter = req.query.filter;
// Build query
const checksQuery = { monitorId: monitorId };
// Filter checks by "day", "week", or "month"
if (dateRange !== undefined) {
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
}
if (filter !== undefined) {
checksQuery.status = false;
switch (filter) {
case "all":
break;
case "down":
break;
case "resolve":
checksQuery.statusCode = 5000;
break;
default:
console.log("default");
break;
}
}
if (filter !== undefined) {
checksQuery.status = false;
switch (filter) {
case "all":
break;
case "down":
break;
case "resolve":
checksQuery.statusCode = 5000;
break;
default:
console.log("default");
break;
}
}
const count = await Check.countDocuments(checksQuery);
return count;
const count = await Check.countDocuments(checksQuery);
return count;
};
/**
@@ -95,104 +95,104 @@ const getChecksCount = async (req) => {
*/
const getChecks = async (req) => {
try {
const { monitorId } = req.params;
let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query;
// Default limit to 0 if not provided
limit = limit === "undefined" ? 0 : limit;
try {
const { monitorId } = req.params;
let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query;
// Default limit to 0 if not provided
limit = limit === "undefined" ? 0 : limit;
// Default sort order is newest -> oldest
sortOrder = sortOrder === "asc" ? 1 : -1;
// Default sort order is newest -> oldest
sortOrder = sortOrder === "asc" ? 1 : -1;
// Build query
const checksQuery = { monitorId: monitorId };
// Filter checks by "day", "week", or "month"
if (dateRange !== undefined) {
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
}
// Fitler checks by status
if (filter !== undefined) {
checksQuery.status = false;
switch (filter) {
case "all":
break;
case "down":
break;
case "resolve":
checksQuery.statusCode = 5000;
break;
default:
console.log("default");
break;
}
}
// Build query
const checksQuery = { monitorId: monitorId };
// Filter checks by "day", "week", or "month"
if (dateRange !== undefined) {
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
}
// Fitler checks by status
if (filter !== undefined) {
checksQuery.status = false;
switch (filter) {
case "all":
break;
case "down":
break;
case "resolve":
checksQuery.statusCode = 5000;
break;
default:
console.log("default");
break;
}
}
// Need to skip and limit here
let skip = 0;
if (page && rowsPerPage) {
skip = page * rowsPerPage;
}
const checks = await Check.find(checksQuery)
.skip(skip)
.limit(rowsPerPage)
.sort({ createdAt: sortOrder });
return checks;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getChecks";
throw error;
}
// Need to skip and limit here
let skip = 0;
if (page && rowsPerPage) {
skip = page * rowsPerPage;
}
const checks = await Check.find(checksQuery)
.skip(skip)
.limit(rowsPerPage)
.sort({ createdAt: sortOrder });
return checks;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getChecks";
throw error;
}
};
const getTeamChecks = async (req) => {
const { teamId } = req.params;
let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query;
const { teamId } = req.params;
let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query;
// Get monitorIDs
const userMonitors = await Monitor.find({ teamId: teamId }).select("_id");
// Get monitorIDs
const userMonitors = await Monitor.find({ teamId: teamId }).select("_id");
//Build check query
// Default limit to 0 if not provided
limit = limit === undefined ? 0 : limit;
// Default sort order is newest -> oldest
sortOrder = sortOrder === "asc" ? 1 : -1;
//Build check query
// Default limit to 0 if not provided
limit = limit === undefined ? 0 : limit;
// Default sort order is newest -> oldest
sortOrder = sortOrder === "asc" ? 1 : -1;
checksQuery = { monitorId: { $in: userMonitors } };
checksQuery = { monitorId: { $in: userMonitors } };
if (filter !== undefined) {
checksQuery.status = false;
switch (filter) {
case "all":
break;
case "down":
break;
case "resolve":
checksQuery.statusCode = 5000;
break;
default:
console.log("default");
break;
}
}
if (filter !== undefined) {
checksQuery.status = false;
switch (filter) {
case "all":
break;
case "down":
break;
case "resolve":
checksQuery.statusCode = 5000;
break;
default:
console.log("default");
break;
}
}
if (dateRange !== undefined) {
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
}
if (dateRange !== undefined) {
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
}
// Skip and limit for pagination
let skip = 0;
if (page && rowsPerPage) {
skip = page * rowsPerPage;
}
// Skip and limit for pagination
let skip = 0;
if (page && rowsPerPage) {
skip = page * rowsPerPage;
}
const checksCount = await Check.countDocuments(checksQuery);
const checksCount = await Check.countDocuments(checksQuery);
const checks = await Check.find(checksQuery)
.skip(skip)
.limit(rowsPerPage)
.sort({ createdAt: sortOrder })
.select(["monitorId", "status", "responseTime", "statusCode", "message"]);
return { checksCount, checks };
const checks = await Check.find(checksQuery)
.skip(skip)
.limit(rowsPerPage)
.sort({ createdAt: sortOrder })
.select(["monitorId", "status", "responseTime", "statusCode", "message"]);
return { checksCount, checks };
};
/**
@@ -204,14 +204,14 @@ const getTeamChecks = async (req) => {
*/
const deleteChecks = async (monitorId) => {
try {
const result = await Check.deleteMany({ monitorId });
return result.deletedCount;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteChecks";
throw error;
}
try {
const result = await Check.deleteMany({ monitorId });
return result.deletedCount;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteChecks";
throw error;
}
};
/**
@@ -223,63 +223,63 @@ const deleteChecks = async (monitorId) => {
*/
const deleteChecksByTeamId = async (teamId) => {
try {
const teamMonitors = await Monitor.find({ teamId: teamId });
let totalDeletedCount = 0;
try {
const teamMonitors = await Monitor.find({ teamId: teamId });
let totalDeletedCount = 0;
await Promise.all(
teamMonitors.map(async (monitor) => {
const result = await Check.deleteMany({ monitorId: monitor._id });
totalDeletedCount += result.deletedCount;
monitor.status = true;
await monitor.save();
})
);
await Promise.all(
teamMonitors.map(async (monitor) => {
const result = await Check.deleteMany({ monitorId: monitor._id });
totalDeletedCount += result.deletedCount;
monitor.status = true;
await monitor.save();
})
);
return totalDeletedCount;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteChecksByTeamId";
throw error;
}
return totalDeletedCount;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteChecksByTeamId";
throw error;
}
};
const updateChecksTTL = async (teamId, ttl) => {
try {
await Check.collection.dropIndex("expiry_1");
} catch (error) {
logger.error("Failed to drop index", {
service: SERVICE_NAME,
method: "updateChecksTTL",
});
}
try {
await Check.collection.dropIndex("expiry_1");
} catch (error) {
logger.error("Failed to drop index", {
service: SERVICE_NAME,
method: "updateChecksTTL",
});
}
try {
await Check.collection.createIndex(
{ expiry: 1 },
{ expireAfterSeconds: ttl } // TTL in seconds, adjust as necessary
);
} catch (error) {
error.service = SERVICE_NAME;
error.method = "updateChecksTTL";
throw error;
}
// Update user
try {
await User.updateMany({ teamId: teamId }, { checkTTL: ttl });
} catch (error) {
error.service = SERVICE_NAME;
error.method = "updateChecksTTL";
throw error;
}
try {
await Check.collection.createIndex(
{ expiry: 1 },
{ expireAfterSeconds: ttl } // TTL in seconds, adjust as necessary
);
} catch (error) {
error.service = SERVICE_NAME;
error.method = "updateChecksTTL";
throw error;
}
// Update user
try {
await User.updateMany({ teamId: teamId }, { checkTTL: ttl });
} catch (error) {
error.service = SERVICE_NAME;
error.method = "updateChecksTTL";
throw error;
}
};
export {
createCheck,
getChecksCount,
getChecks,
getTeamChecks,
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
createCheck,
getChecksCount,
getChecks,
getTeamChecks,
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
};

View File

@@ -18,17 +18,17 @@ const SERVICE_NAME = "inviteModule";
* @throws {Error} If there is an error.
*/
const requestInviteToken = async (userData) => {
try {
await InviteToken.deleteMany({ email: userData.email });
userData.token = crypto.randomBytes(32).toString("hex");
let inviteToken = new InviteToken(userData);
await inviteToken.save();
return inviteToken;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "requestInviteToken";
throw error;
}
try {
await InviteToken.deleteMany({ email: userData.email });
userData.token = crypto.randomBytes(32).toString("hex");
let inviteToken = new InviteToken(userData);
await inviteToken.save();
return inviteToken;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "requestInviteToken";
throw error;
}
};
/**
@@ -42,19 +42,19 @@ const requestInviteToken = async (userData) => {
* @throws {Error} If the invite token is not found or there is another error.
*/
const getInviteToken = async (token) => {
try {
const invite = await InviteToken.findOne({
token,
});
if (invite === null) {
throw new Error(errorMessages.AUTH_INVITE_NOT_FOUND);
}
return invite;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getInviteToken";
throw error;
}
try {
const invite = await InviteToken.findOne({
token,
});
if (invite === null) {
throw new Error(errorMessages.AUTH_INVITE_NOT_FOUND);
}
return invite;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getInviteToken";
throw error;
}
};
/**
@@ -68,19 +68,19 @@ const getInviteToken = async (token) => {
* @throws {Error} If the invite token is not found or there is another error.
*/
const getInviteTokenAndDelete = async (token) => {
try {
const invite = await InviteToken.findOneAndDelete({
token,
});
if (invite === null) {
throw new Error(errorMessages.AUTH_INVITE_NOT_FOUND);
}
return invite;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getInviteToken";
throw error;
}
try {
const invite = await InviteToken.findOneAndDelete({
token,
});
if (invite === null) {
throw new Error(errorMessages.AUTH_INVITE_NOT_FOUND);
}
return invite;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getInviteToken";
throw error;
}
};
export { requestInviteToken, getInviteToken, getInviteTokenAndDelete };

View File

@@ -27,35 +27,35 @@ const SERVICE_NAME = "maintenanceWindowModule";
* .catch(error => console.error(error));
*/
const createMaintenanceWindow = async (maintenanceWindowData) => {
try {
const maintenanceWindow = new MaintenanceWindow({
...maintenanceWindowData,
});
try {
const maintenanceWindow = new MaintenanceWindow({
...maintenanceWindowData,
});
// If the maintenance window is a one time window, set the expiry to the end date
if (maintenanceWindowData.oneTime) {
maintenanceWindow.expiry = maintenanceWindowData.end;
}
const result = await maintenanceWindow.save();
return result;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "createMaintenanceWindow";
throw error;
}
// If the maintenance window is a one time window, set the expiry to the end date
if (maintenanceWindowData.oneTime) {
maintenanceWindow.expiry = maintenanceWindowData.end;
}
const result = await maintenanceWindow.save();
return result;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "createMaintenanceWindow";
throw error;
}
};
const getMaintenanceWindowById = async (maintenanceWindowId) => {
try {
const maintenanceWindow = await MaintenanceWindow.findById({
_id: maintenanceWindowId,
});
return maintenanceWindow;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMaintenanceWindowById";
throw error;
}
try {
const maintenanceWindow = await MaintenanceWindow.findById({
_id: maintenanceWindowId,
});
return maintenanceWindow;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMaintenanceWindowById";
throw error;
}
};
/**
@@ -72,38 +72,38 @@ const getMaintenanceWindowById = async (maintenanceWindowId) => {
* .catch(error => console.error(error));
*/
const getMaintenanceWindowsByTeamId = async (teamId, query) => {
try {
let { active, page, rowsPerPage, field, order } = query || {};
const maintenanceQuery = { teamId };
try {
let { active, page, rowsPerPage, field, order } = query || {};
const maintenanceQuery = { teamId };
if (active !== undefined) maintenanceQuery.active = active;
if (active !== undefined) maintenanceQuery.active = active;
const maintenanceWindowCount =
await MaintenanceWindow.countDocuments(maintenanceQuery);
const maintenanceWindowCount =
await MaintenanceWindow.countDocuments(maintenanceQuery);
// Pagination
let skip = 0;
if (page && rowsPerPage) {
skip = page * rowsPerPage;
}
// Pagination
let skip = 0;
if (page && rowsPerPage) {
skip = page * rowsPerPage;
}
// Sorting
let sort = {};
if (field !== undefined && order !== undefined) {
sort[field] = order === "asc" ? 1 : -1;
}
// Sorting
let sort = {};
if (field !== undefined && order !== undefined) {
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) {
error.service = SERVICE_NAME;
error.method = "getMaintenanceWindowByUserId";
throw error;
}
return { maintenanceWindows, maintenanceWindowCount };
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMaintenanceWindowByUserId";
throw error;
}
};
/**
@@ -119,16 +119,16 @@ const getMaintenanceWindowsByTeamId = async (teamId, query) => {
* .catch(error => console.error(error));
*/
const getMaintenanceWindowsByMonitorId = async (monitorId) => {
try {
const maintenanceWindows = await MaintenanceWindow.find({
monitorId: monitorId,
});
return maintenanceWindows;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMaintenanceWindowsByMonitorId";
throw error;
}
try {
const maintenanceWindows = await MaintenanceWindow.find({
monitorId: monitorId,
});
return maintenanceWindows;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMaintenanceWindowsByMonitorId";
throw error;
}
};
/**
@@ -144,15 +144,15 @@ const getMaintenanceWindowsByMonitorId = async (monitorId) => {
* .catch(error => console.error(error));
*/
const deleteMaintenanceWindowById = async (maintenanceWindowId) => {
try {
const maintenanceWindow =
await MaintenanceWindow.findByIdAndDelete(maintenanceWindowId);
return maintenanceWindow;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteMaintenanceWindowById";
throw error;
}
try {
const maintenanceWindow =
await MaintenanceWindow.findByIdAndDelete(maintenanceWindowId);
return maintenanceWindow;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteMaintenanceWindowById";
throw error;
}
};
/**
@@ -168,14 +168,14 @@ const deleteMaintenanceWindowById = async (maintenanceWindowId) => {
* .catch(error => console.error(error));
*/
const deleteMaintenanceWindowByMonitorId = async (monitorId) => {
try {
const result = await MaintenanceWindow.deleteMany({ monitorId: monitorId });
return result;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteMaintenanceWindowByMonitorId";
throw error;
}
try {
const result = await MaintenanceWindow.deleteMany({ monitorId: monitorId });
return result;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteMaintenanceWindowByMonitorId";
throw error;
}
};
/**
@@ -191,42 +191,39 @@ const deleteMaintenanceWindowByMonitorId = async (monitorId) => {
* .catch(error => console.error(error));
*/
const deleteMaintenanceWindowByUserId = async (userId) => {
try {
const result = await MaintenanceWindow.deleteMany({ userId: userId });
return result;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteMaintenanceWindowByUserId";
throw error;
}
try {
const result = await MaintenanceWindow.deleteMany({ userId: userId });
return result;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteMaintenanceWindowByUserId";
throw error;
}
};
const editMaintenanceWindowById = async (
maintenanceWindowId,
maintenanceWindowData
) => {
console.log(maintenanceWindowData);
try {
const editedMaintenanceWindow = MaintenanceWindow.findByIdAndUpdate(
maintenanceWindowId,
maintenanceWindowData,
{ new: true }
);
return editedMaintenanceWindow;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "editMaintenanceWindowById";
throw error;
}
const editMaintenanceWindowById = async (maintenanceWindowId, maintenanceWindowData) => {
console.log(maintenanceWindowData);
try {
const editedMaintenanceWindow = MaintenanceWindow.findByIdAndUpdate(
maintenanceWindowId,
maintenanceWindowData,
{ new: true }
);
return editedMaintenanceWindow;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "editMaintenanceWindowById";
throw error;
}
};
export {
createMaintenanceWindow,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindowById,
deleteMaintenanceWindowByMonitorId,
deleteMaintenanceWindowByUserId,
editMaintenanceWindowById,
createMaintenanceWindow,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindowById,
deleteMaintenanceWindowByMonitorId,
deleteMaintenanceWindowByUserId,
editMaintenanceWindowById,
};

View File

@@ -11,10 +11,7 @@ import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const demoMonitorsPath = path.resolve(
__dirname,
"../../../utils/demoMonitors.json"
);
const demoMonitorsPath = path.resolve(__dirname, "../../../utils/demoMonitors.json");
const demoMonitors = JSON.parse(fs.readFileSync(demoMonitorsPath, "utf8"));
const SERVICE_NAME = "monitorModule";
@@ -28,14 +25,14 @@ const SERVICE_NAME = "monitorModule";
* @throws {Error}
*/
const getAllMonitors = async (req, res) => {
try {
const monitors = await Monitor.find();
return monitors;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getAllMonitors";
throw error;
}
try {
const monitors = await Monitor.find();
return monitors;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getAllMonitors";
throw error;
}
};
/**
@@ -44,27 +41,27 @@ const getAllMonitors = async (req, res) => {
* @returns {number} Uptime duration in ms.
*/
const calculateUptimeDuration = (checks) => {
if (!checks || checks.length === 0) {
return 0;
}
if (!checks || checks.length === 0) {
return 0;
}
const latestCheck = new Date(checks[0].createdAt);
let latestDownCheck = 0;
const latestCheck = new Date(checks[0].createdAt);
let latestDownCheck = 0;
for (let i = checks.length; i <= 0; i--) {
if (checks[i].status === false) {
latestDownCheck = new Date(checks[i].createdAt);
break;
}
}
for (let i = checks.length; i <= 0; i--) {
if (checks[i].status === false) {
latestDownCheck = new Date(checks[i].createdAt);
break;
}
}
// If no down check is found, uptime is from the last check to now
if (latestDownCheck === 0) {
return Date.now() - new Date(checks[checks.length - 1].createdAt);
}
// If no down check is found, uptime is from the last check to now
if (latestDownCheck === 0) {
return Date.now() - new Date(checks[checks.length - 1].createdAt);
}
// Otherwise the uptime is from the last check to the last down check
return latestCheck - latestDownCheck;
// Otherwise the uptime is from the last check to the last down check
return latestCheck - latestDownCheck;
};
/**
@@ -73,11 +70,11 @@ const calculateUptimeDuration = (checks) => {
* @returns {number} Timestamp of the most recent check.
*/
const getLastChecked = (checks) => {
if (!checks || checks.length === 0) {
return 0; // Handle case when no checks are available
}
// Data is sorted newest->oldest, so last check is the most recent
return new Date() - new Date(checks[0].createdAt);
if (!checks || checks.length === 0) {
return 0; // Handle case when no checks are available
}
// Data is sorted newest->oldest, so last check is the most recent
return new Date() - new Date(checks[0].createdAt);
};
/**
@@ -86,10 +83,10 @@ const getLastChecked = (checks) => {
* @returns {number} Timestamp of the most recent check.
*/
const getLatestResponseTime = (checks) => {
if (!checks || checks.length === 0) {
return 0;
}
return checks[0].responseTime;
if (!checks || checks.length === 0) {
return 0;
}
return checks[0].responseTime;
};
/**
@@ -98,13 +95,13 @@ const getLatestResponseTime = (checks) => {
* @returns {number} Timestamp of the most recent check.
*/
const getAverageResponseTime = (checks) => {
if (!checks || checks.length === 0) {
return 0;
}
const aggResponseTime = checks.reduce((sum, check) => {
return sum + check.responseTime;
}, 0);
return aggResponseTime / checks.length;
if (!checks || checks.length === 0) {
return 0;
}
const aggResponseTime = checks.reduce((sum, check) => {
return sum + check.responseTime;
}, 0);
return aggResponseTime / checks.length;
};
/**
@@ -114,13 +111,13 @@ const getAverageResponseTime = (checks) => {
*/
const getUptimePercentage = (checks) => {
if (!checks || checks.length === 0) {
return 0;
}
const upCount = checks.reduce((count, check) => {
return check.status === true ? count + 1 : count;
}, 0);
return (upCount / checks.length) * 100;
if (!checks || checks.length === 0) {
return 0;
}
const upCount = checks.reduce((count, check) => {
return check.status === true ? count + 1 : count;
}, 0);
return (upCount / checks.length) * 100;
};
/**
@@ -130,12 +127,12 @@ const getUptimePercentage = (checks) => {
*/
const getIncidents = (checks) => {
if (!checks || checks.length === 0) {
return 0; // Handle case when no checks are available
}
return checks.reduce((acc, check) => {
return check.status === false ? (acc += 1) : acc;
}, 0);
if (!checks || checks.length === 0) {
return 0; // Handle case when no checks are available
}
return checks.reduce((acc, check) => {
return check.status === false ? (acc += 1) : acc;
}, 0);
};
/**
@@ -147,140 +144,136 @@ const getIncidents = (checks) => {
* @throws {Error}
*/
const getMonitorStatsById = async (req) => {
const startDates = {
day: new Date(new Date().setDate(new Date().getDate() - 1)),
week: new Date(new Date().setDate(new Date().getDate() - 7)),
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
};
const endDate = new Date();
try {
// Get monitor
const { monitorId } = req.params;
let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query;
const monitor = await Monitor.findById(monitorId);
if (monitor === null || monitor === undefined) {
throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
}
// This effectively removes limit, returning all checks
if (limit === undefined) limit = 0;
// Default sort order is newest -> oldest
sortOrder = sortOrder === "asc" ? 1 : -1;
const startDates = {
day: new Date(new Date().setDate(new Date().getDate() - 1)),
week: new Date(new Date().setDate(new Date().getDate() - 7)),
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
};
const endDate = new Date();
try {
// Get monitor
const { monitorId } = req.params;
let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query;
const monitor = await Monitor.findById(monitorId);
if (monitor === null || monitor === undefined) {
throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
}
// This effectively removes limit, returning all checks
if (limit === undefined) limit = 0;
// Default sort order is newest -> oldest
sortOrder = sortOrder === "asc" ? 1 : -1;
let model =
monitor.type === "http" || monitor.type === "ping"
? Check
: PageSpeedCheck;
let model =
monitor.type === "http" || monitor.type === "ping" ? Check : PageSpeedCheck;
const monitorStats = {
...monitor.toObject(),
};
const monitorStats = {
...monitor.toObject(),
};
// Build checks query
const checksQuery = { monitorId: monitor._id };
// Build checks query
const checksQuery = { monitorId: monitor._id };
// Get all checks
const checksAll = await model.find(checksQuery).sort({
createdAt: sortOrder,
});
// Get all checks
const checksAll = await model.find(checksQuery).sort({
createdAt: sortOrder,
});
const checksQueryForDateRange = {
...checksQuery,
createdAt: {
$gte: startDates[dateRange],
$lte: endDate,
},
};
const checksQueryForDateRange = {
...checksQuery,
createdAt: {
$gte: startDates[dateRange],
$lte: endDate,
},
};
const checksForDateRange = await model
.find(checksQueryForDateRange)
.sort({ createdAt: sortOrder });
const checksForDateRange = await model
.find(checksQueryForDateRange)
.sort({ createdAt: sortOrder });
if (monitor.type === "http" || monitor.type === "ping") {
// HTTP/PING Specific stats
monitorStats.periodAvgResponseTime =
getAverageResponseTime(checksForDateRange);
monitorStats.periodUptime = getUptimePercentage(checksForDateRange);
if (monitor.type === "http" || monitor.type === "ping") {
// HTTP/PING Specific stats
monitorStats.periodAvgResponseTime = getAverageResponseTime(checksForDateRange);
monitorStats.periodUptime = getUptimePercentage(checksForDateRange);
// Aggregate data
let groupedChecks;
// Group checks by hour if range is day
if (dateRange === "day") {
groupedChecks = checksForDateRange.reduce((acc, check) => {
const time = new Date(check.createdAt);
time.setMinutes(0, 0, 0);
if (!acc[time]) {
acc[time] = { time, checks: [] };
}
acc[time].checks.push(check);
return acc;
}, {});
} else {
groupedChecks = checksForDateRange.reduce((acc, check) => {
const time = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part
if (!acc[time]) {
acc[time] = { time, checks: [] };
}
acc[time].checks.push(check);
return acc;
}, {});
}
// Aggregate data
let groupedChecks;
// Group checks by hour if range is day
if (dateRange === "day") {
groupedChecks = checksForDateRange.reduce((acc, check) => {
const time = new Date(check.createdAt);
time.setMinutes(0, 0, 0);
if (!acc[time]) {
acc[time] = { time, checks: [] };
}
acc[time].checks.push(check);
return acc;
}, {});
} else {
groupedChecks = checksForDateRange.reduce((acc, check) => {
const time = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part
if (!acc[time]) {
acc[time] = { time, checks: [] };
}
acc[time].checks.push(check);
return acc;
}, {});
}
// Map grouped checks to stats
const aggregateData = Object.values(groupedChecks).map((group) => {
const totalChecks = group.checks.length;
const uptimePercentage = getUptimePercentage(group.checks);
const totalIncidents = group.checks.filter(
(check) => check.status === false
).length;
const avgResponseTime =
group.checks.reduce((sum, check) => sum + check.responseTime, 0) /
totalChecks;
// Map grouped checks to stats
const aggregateData = Object.values(groupedChecks).map((group) => {
const totalChecks = group.checks.length;
const uptimePercentage = getUptimePercentage(group.checks);
const totalIncidents = group.checks.filter(
(check) => check.status === false
).length;
const avgResponseTime =
group.checks.reduce((sum, check) => sum + check.responseTime, 0) / totalChecks;
return {
time: group.time,
uptimePercentage,
totalChecks,
totalIncidents,
avgResponseTime,
};
});
monitorStats.aggregateData = aggregateData;
}
return {
time: group.time,
uptimePercentage,
totalChecks,
totalIncidents,
avgResponseTime,
};
});
monitorStats.aggregateData = aggregateData;
}
monitorStats.periodIncidents = getIncidents(checksForDateRange);
monitorStats.periodTotalChecks = checksForDateRange.length;
monitorStats.periodIncidents = getIncidents(checksForDateRange);
monitorStats.periodTotalChecks = checksForDateRange.length;
// If more than numToDisplay checks, pick every nth check
// If more than numToDisplay checks, pick every nth check
let nthChecks = checksForDateRange;
let nthChecks = checksForDateRange;
if (
numToDisplay !== undefined &&
checksForDateRange &&
checksForDateRange.length > numToDisplay
) {
const n = Math.ceil(checksForDateRange.length / numToDisplay);
nthChecks = checksForDateRange.filter((_, index) => index % n === 0);
}
if (
numToDisplay !== undefined &&
checksForDateRange &&
checksForDateRange.length > numToDisplay
) {
const n = Math.ceil(checksForDateRange.length / numToDisplay);
nthChecks = checksForDateRange.filter((_, index) => index % n === 0);
}
// Normalize checks if requested
if (normalize !== undefined) {
const normailzedChecks = NormalizeData(nthChecks, 1, 100);
monitorStats.checks = normailzedChecks;
} else {
monitorStats.checks = nthChecks;
}
// Normalize checks if requested
if (normalize !== undefined) {
const normailzedChecks = NormalizeData(nthChecks, 1, 100);
monitorStats.checks = normailzedChecks;
} else {
monitorStats.checks = nthChecks;
}
monitorStats.uptimeDuration = calculateUptimeDuration(checksAll);
monitorStats.lastChecked = getLastChecked(checksAll);
monitorStats.latestResponseTime = getLatestResponseTime(checksAll);
monitorStats.uptimeDuration = calculateUptimeDuration(checksAll);
monitorStats.lastChecked = getLastChecked(checksAll);
monitorStats.latestResponseTime = getLatestResponseTime(checksAll);
return monitorStats;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMonitorStatsById";
throw error;
}
return monitorStats;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMonitorStatsById";
throw error;
}
};
/**
@@ -292,23 +285,23 @@ const getMonitorStatsById = async (req) => {
* @throws {Error}
*/
const getMonitorById = async (monitorId) => {
try {
const monitor = await Monitor.findById(monitorId);
if (monitor === null || monitor === undefined) {
throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
}
// Get notifications
const notifications = await Notification.find({
monitorId: monitorId,
});
monitor.notifications = notifications;
const monitorWithNotifications = await monitor.save();
return monitorWithNotifications;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMonitorById";
throw error;
}
try {
const monitor = await Monitor.findById(monitorId);
if (monitor === null || monitor === undefined) {
throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
}
// Get notifications
const notifications = await Notification.find({
monitorId: monitorId,
});
monitor.notifications = notifications;
const monitorWithNotifications = await monitor.save();
return monitorWithNotifications;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMonitorById";
throw error;
}
};
/**
@@ -321,32 +314,32 @@ const getMonitorById = async (monitorId) => {
*/
const getMonitorsAndSummaryByTeamId = async (teamId, type) => {
try {
const monitors = await Monitor.find({ teamId, type });
const monitorCounts = monitors.reduce(
(acc, monitor) => {
if (monitor.status === true) {
acc.up += 1;
}
try {
const monitors = await Monitor.find({ teamId, type });
const monitorCounts = monitors.reduce(
(acc, monitor) => {
if (monitor.status === true) {
acc.up += 1;
}
if (monitor.status === false) {
acc.down += 1;
}
if (monitor.status === false) {
acc.down += 1;
}
if (monitor.isActive === false) {
acc.paused += 1;
}
return acc;
},
{ up: 0, down: 0, paused: 0 }
);
monitorCounts.total = monitors.length;
return { monitors, monitorCounts };
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMonitorsAndSummaryByTeamId";
throw error;
}
if (monitor.isActive === false) {
acc.paused += 1;
}
return acc;
},
{ up: 0, down: 0, paused: 0 }
);
monitorCounts.total = monitors.length;
return { monitors, monitorCounts };
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMonitorsAndSummaryByTeamId";
throw error;
}
};
/**
@@ -358,104 +351,102 @@ const getMonitorsAndSummaryByTeamId = async (teamId, type) => {
* @throws {Error}
*/
const getMonitorsByTeamId = async (req, res) => {
try {
let {
limit,
type,
status,
checkOrder,
normalize,
page,
rowsPerPage,
filter,
field,
order,
} = req.query || {};
const monitorQuery = { teamId: req.params.teamId };
if (type !== undefined) {
monitorQuery.type = type;
}
// Add filter if provided
// $options: "i" makes the search case-insensitive
if (filter !== undefined) {
monitorQuery.$or = [
{ name: { $regex: filter, $options: "i" } },
{ url: { $regex: filter, $options: "i" } },
];
}
const monitorsCount = await Monitor.countDocuments(monitorQuery);
try {
let {
limit,
type,
status,
checkOrder,
normalize,
page,
rowsPerPage,
filter,
field,
order,
} = req.query || {};
const monitorQuery = { teamId: req.params.teamId };
if (type !== undefined) {
monitorQuery.type = type;
}
// Add filter if provided
// $options: "i" makes the search case-insensitive
if (filter !== undefined) {
monitorQuery.$or = [
{ name: { $regex: filter, $options: "i" } },
{ url: { $regex: filter, $options: "i" } },
];
}
const monitorsCount = await Monitor.countDocuments(monitorQuery);
// Pagination
let skip = 0;
if (page && rowsPerPage) {
skip = page * rowsPerPage;
}
// Pagination
let skip = 0;
if (page && rowsPerPage) {
skip = page * rowsPerPage;
}
if (type !== undefined) {
const types = Array.isArray(type) ? type : [type];
monitorQuery.type = { $in: types };
}
if (type !== undefined) {
const types = Array.isArray(type) ? type : [type];
monitorQuery.type = { $in: types };
}
// Default sort order is newest -> oldest
if (checkOrder === "asc") {
checkOrder = 1;
} else if (checkOrder === "desc") {
checkOrder = -1;
} else checkOrder = -1;
// Default sort order is newest -> oldest
if (checkOrder === "asc") {
checkOrder = 1;
} else if (checkOrder === "desc") {
checkOrder = -1;
} else checkOrder = -1;
// Sort order for monitors
let sort = {};
if (field !== undefined && order !== undefined) {
sort[field] = order === "asc" ? 1 : -1;
}
// Sort order for monitors
let sort = {};
if (field !== undefined && order !== undefined) {
sort[field] = order === "asc" ? 1 : -1;
}
const monitors = await Monitor.find(monitorQuery)
.skip(skip)
.limit(rowsPerPage)
.sort(sort);
const monitors = await Monitor.find(monitorQuery)
.skip(skip)
.limit(rowsPerPage)
.sort(sort);
// Early return if limit is set to -1, indicating we don't want any checks
if (limit === "-1") {
return { monitors, monitorCount: monitorsCount };
}
// Early return if limit is set to -1, indicating we don't want any checks
if (limit === "-1") {
return { monitors, monitorCount: monitorsCount };
}
// This effectively removes limit, returning all checks
if (limit === undefined) limit = 0;
// This effectively removes limit, returning all checks
if (limit === undefined) limit = 0;
// Map each monitor to include its associated checks
const monitorsWithChecks = await Promise.all(
monitors.map(async (monitor) => {
const checksQuery = { monitorId: monitor._id };
if (status !== undefined) {
checksQuery.status = status;
}
// Map each monitor to include its associated checks
const monitorsWithChecks = await Promise.all(
monitors.map(async (monitor) => {
const checksQuery = { monitorId: monitor._id };
if (status !== undefined) {
checksQuery.status = status;
}
let model =
monitor.type === "http" || monitor.type === "ping"
? Check
: PageSpeedCheck;
let model =
monitor.type === "http" || monitor.type === "ping" ? Check : PageSpeedCheck;
// Checks are order newest -> oldest
let checks = await model
.find(checksQuery)
.sort({
createdAt: checkOrder,
})
.limit(limit);
// Checks are order newest -> oldest
let checks = await model
.find(checksQuery)
.sort({
createdAt: checkOrder,
})
.limit(limit);
//Normalize checks if requested
if (normalize !== undefined) {
checks = NormalizeData(checks, 10, 100);
}
return { ...monitor.toObject(), checks };
})
);
return { monitors: monitorsWithChecks, monitorCount: monitorsCount };
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMonitorsByTeamId";
throw error;
}
//Normalize checks if requested
if (normalize !== undefined) {
checks = NormalizeData(checks, 10, 100);
}
return { ...monitor.toObject(), checks };
})
);
return { monitors: monitorsWithChecks, monitorCount: monitorsCount };
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMonitorsByTeamId";
throw error;
}
};
/**
@@ -467,17 +458,17 @@ const getMonitorsByTeamId = async (req, res) => {
* @throws {Error}
*/
const createMonitor = async (req, res) => {
try {
const monitor = new Monitor({ ...req.body });
// Remove notifications fom monitor as they aren't needed here
monitor.notifications = undefined;
await monitor.save();
return monitor;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "createMonitor";
throw error;
}
try {
const monitor = new Monitor({ ...req.body });
// Remove notifications fom monitor as they aren't needed here
monitor.notifications = undefined;
await monitor.save();
return monitor;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "createMonitor";
throw error;
}
};
/**
@@ -489,18 +480,18 @@ const createMonitor = async (req, res) => {
* @throws {Error}
*/
const deleteMonitor = async (req, res) => {
const monitorId = req.params.monitorId;
try {
const monitor = await Monitor.findByIdAndDelete(monitorId);
if (!monitor) {
throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
}
return monitor;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteMonitor";
throw error;
}
const monitorId = req.params.monitorId;
try {
const monitor = await Monitor.findByIdAndDelete(monitorId);
if (!monitor) {
throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
}
return monitor;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteMonitor";
throw error;
}
};
/**
@@ -508,16 +499,16 @@ const deleteMonitor = async (req, res) => {
*/
const deleteAllMonitors = async (teamId) => {
try {
const monitors = await Monitor.find({ teamId });
const { deletedCount } = await Monitor.deleteMany({ teamId });
try {
const monitors = await Monitor.find({ teamId });
const { deletedCount } = await Monitor.deleteMany({ teamId });
return { monitors, deletedCount };
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteAllMonitors";
throw error;
}
return { monitors, deletedCount };
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteAllMonitors";
throw error;
}
};
/**
@@ -527,14 +518,14 @@ const deleteAllMonitors = async (teamId) => {
* @returns {Promise} A promise that resolves when the operation is complete.
*/
const deleteMonitorsByUserId = async (userId) => {
try {
const result = await Monitor.deleteMany({ userId: userId });
return result;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteMonitorsByUserId";
throw error;
}
try {
const result = await Monitor.deleteMany({ userId: userId });
return result;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteMonitorsByUserId";
throw error;
}
};
/**
@@ -546,54 +537,52 @@ const deleteMonitorsByUserId = async (userId) => {
* @throws {Error}
*/
const editMonitor = async (candidateId, candidateMonitor) => {
candidateMonitor.notifications = undefined;
candidateMonitor.notifications = undefined;
try {
const editedMonitor = await Monitor.findByIdAndUpdate(
candidateId,
candidateMonitor,
{ new: true }
);
return editedMonitor;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "editMonitor";
throw error;
}
try {
const editedMonitor = await Monitor.findByIdAndUpdate(candidateId, candidateMonitor, {
new: true,
});
return editedMonitor;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "editMonitor";
throw error;
}
};
const addDemoMonitors = async (userId, teamId) => {
try {
const demoMonitorsToInsert = demoMonitors.map((monitor) => {
return {
userId,
teamId,
name: monitor.name,
description: monitor.name,
type: "http",
url: monitor.url,
interval: 60000,
};
});
const insertedMonitors = await Monitor.insertMany(demoMonitorsToInsert);
return insertedMonitors;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "addDemoMonitors";
throw error;
}
try {
const demoMonitorsToInsert = demoMonitors.map((monitor) => {
return {
userId,
teamId,
name: monitor.name,
description: monitor.name,
type: "http",
url: monitor.url,
interval: 60000,
};
});
const insertedMonitors = await Monitor.insertMany(demoMonitorsToInsert);
return insertedMonitors;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "addDemoMonitors";
throw error;
}
};
export {
getAllMonitors,
getMonitorStatsById,
getMonitorById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
deleteMonitor,
deleteAllMonitors,
deleteMonitorsByUserId,
editMonitor,
addDemoMonitors,
getAllMonitors,
getMonitorStatsById,
getMonitorById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
deleteMonitor,
deleteAllMonitors,
deleteMonitorsByUserId,
editMonitor,
addDemoMonitors,
};

View File

@@ -11,14 +11,14 @@ const SERVICE_NAME = "notificationModule";
* @throws Will throw an error if the notification cannot be created.
*/
const createNotification = async (notificationData) => {
try {
const notification = await new Notification({ ...notificationData }).save();
return notification;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "createNotification";
throw error;
}
try {
const notification = await new Notification({ ...notificationData }).save();
return notification;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "createNotification";
throw error;
}
};
/**
@@ -28,29 +28,29 @@ const createNotification = async (notificationData) => {
* @throws Will throw an error if the notifications cannot be retrieved.
*/
const getNotificationsByMonitorId = async (monitorId) => {
try {
const notifications = await Notification.find({ monitorId });
return notifications;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getNotificationsByMonitorId";
throw error;
}
try {
const notifications = await Notification.find({ monitorId });
return notifications;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getNotificationsByMonitorId";
throw error;
}
};
const deleteNotificationsByMonitorId = async (monitorId) => {
try {
const result = await Notification.deleteMany({ monitorId });
return result.deletedCount;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteNotificationsByMonitorId";
throw error;
}
try {
const result = await Notification.deleteMany({ monitorId });
return result.deletedCount;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteNotificationsByMonitorId";
throw error;
}
};
export {
createNotification,
getNotificationsByMonitorId,
deleteNotificationsByMonitorId,
createNotification,
getNotificationsByMonitorId,
deleteNotificationsByMonitorId,
};

View File

@@ -13,16 +13,16 @@ const SERVICE_NAME = "pageSpeedCheckModule";
* @throws {Error}
*/
const createPageSpeedCheck = async (pageSpeedCheckData) => {
try {
const pageSpeedCheck = await new PageSpeedCheck({
...pageSpeedCheckData,
}).save();
return pageSpeedCheck;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "createPageSpeedCheck";
throw error;
}
try {
const pageSpeedCheck = await new PageSpeedCheck({
...pageSpeedCheckData,
}).save();
return pageSpeedCheck;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "createPageSpeedCheck";
throw error;
}
};
/**
@@ -34,14 +34,14 @@ const createPageSpeedCheck = async (pageSpeedCheckData) => {
*/
const getPageSpeedChecks = async (monitorId) => {
try {
const pageSpeedChecks = await PageSpeedCheck.find({ monitorId });
return pageSpeedChecks;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getPageSpeedChecks";
throw error;
}
try {
const pageSpeedChecks = await PageSpeedCheck.find({ monitorId });
return pageSpeedChecks;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getPageSpeedChecks";
throw error;
}
};
/**
@@ -53,18 +53,14 @@ const getPageSpeedChecks = async (monitorId) => {
*/
const deletePageSpeedChecksByMonitorId = async (monitorId) => {
try {
const result = await PageSpeedCheck.deleteMany({ monitorId });
return result.deletedCount;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deletePageSpeedChecksByMonitorId";
throw error;
}
try {
const result = await PageSpeedCheck.deleteMany({ monitorId });
return result.deletedCount;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deletePageSpeedChecksByMonitorId";
throw error;
}
};
export {
createPageSpeedCheck,
getPageSpeedChecks,
deletePageSpeedChecksByMonitorId,
};
export { createPageSpeedCheck, getPageSpeedChecks, deletePageSpeedChecksByMonitorId };

View File

@@ -14,72 +14,72 @@ const SERVICE_NAME = "recoveryModule";
* @throws {Error}
*/
const requestRecoveryToken = async (req, res) => {
try {
// Delete any existing tokens
await RecoveryToken.deleteMany({ email: req.body.email });
let recoveryToken = new RecoveryToken({
email: req.body.email,
token: crypto.randomBytes(32).toString("hex"),
});
await recoveryToken.save();
return recoveryToken;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "requestRecoveryToken";
throw error;
}
try {
// Delete any existing tokens
await RecoveryToken.deleteMany({ email: req.body.email });
let recoveryToken = new RecoveryToken({
email: req.body.email,
token: crypto.randomBytes(32).toString("hex"),
});
await recoveryToken.save();
return recoveryToken;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "requestRecoveryToken";
throw error;
}
};
const validateRecoveryToken = async (req, res) => {
try {
const candidateToken = req.body.recoveryToken;
const recoveryToken = await RecoveryToken.findOne({
token: candidateToken,
});
if (recoveryToken !== null) {
return recoveryToken;
} else {
throw new Error(errorMessages.DB_TOKEN_NOT_FOUND);
}
} catch (error) {
error.service = SERVICE_NAME;
error.method = "validateRecoveryToken";
throw error;
}
try {
const candidateToken = req.body.recoveryToken;
const recoveryToken = await RecoveryToken.findOne({
token: candidateToken,
});
if (recoveryToken !== null) {
return recoveryToken;
} else {
throw new Error(errorMessages.DB_TOKEN_NOT_FOUND);
}
} catch (error) {
error.service = SERVICE_NAME;
error.method = "validateRecoveryToken";
throw error;
}
};
const resetPassword = async (req, res) => {
try {
const newPassword = req.body.password;
try {
const newPassword = req.body.password;
// Validate token again
const recoveryToken = await validateRecoveryToken(req, res);
const user = await UserModel.findOne({ email: recoveryToken.email });
// Validate token again
const recoveryToken = await validateRecoveryToken(req, res);
const user = await UserModel.findOne({ email: recoveryToken.email });
const match = await user.comparePassword(newPassword);
if (match === true) {
throw new Error(errorMessages.DB_RESET_PASSWORD_BAD_MATCH);
}
const match = await user.comparePassword(newPassword);
if (match === true) {
throw new Error(errorMessages.DB_RESET_PASSWORD_BAD_MATCH);
}
if (user !== null) {
user.password = newPassword;
await user.save();
await RecoveryToken.deleteMany({ email: recoveryToken.email });
// Fetch the user again without the password
const userWithoutPassword = await UserModel.findOne({
email: recoveryToken.email,
})
.select("-password")
.select("-profileImage");
return userWithoutPassword;
} else {
throw new Error(errorMessages.DB_USER_NOT_FOUND);
}
} catch (error) {
error.service = SERVICE_NAME;
error.method = "resetPassword";
throw error;
}
if (user !== null) {
user.password = newPassword;
await user.save();
await RecoveryToken.deleteMany({ email: recoveryToken.email });
// Fetch the user again without the password
const userWithoutPassword = await UserModel.findOne({
email: recoveryToken.email,
})
.select("-password")
.select("-profileImage");
return userWithoutPassword;
} else {
throw new Error(errorMessages.DB_USER_NOT_FOUND);
}
} catch (error) {
error.service = SERVICE_NAME;
error.method = "resetPassword";
throw error;
}
};
export { requestRecoveryToken, validateRecoveryToken, resetPassword };

View File

@@ -2,29 +2,29 @@ import AppSettings from "../../models/AppSettings.js";
const SERVICE_NAME = "SettingsModule";
const getAppSettings = async () => {
try {
const settings = AppSettings.findOne();
return settings;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getSettings";
throw error;
}
try {
const settings = AppSettings.findOne();
return settings;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getSettings";
throw error;
}
};
const updateAppSettings = async (newSettings) => {
try {
const settings = await AppSettings.findOneAndUpdate(
{},
{ $set: newSettings },
{ new: true }
);
return settings;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "updateAppSettings";
throw error;
}
try {
const settings = await AppSettings.findOneAndUpdate(
{},
{ $set: newSettings },
{ new: true }
);
return settings;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "updateAppSettings";
throw error;
}
};
export { getAppSettings, updateAppSettings };

View File

@@ -16,42 +16,42 @@ const SERVICE_NAME = "userModule";
* @throws {Error}
*/
const insertUser = async (userData, imageFile) => {
try {
if (imageFile) {
// 1. Save the full size image
userData.profileImage = {
data: imageFile.buffer,
contentType: imageFile.mimetype,
};
try {
if (imageFile) {
// 1. Save the full size image
userData.profileImage = {
data: imageFile.buffer,
contentType: imageFile.mimetype,
};
// 2. Get the avatar sized image
const avatar = await GenerateAvatarImage(imageFile);
userData.avatarImage = avatar;
}
// 2. Get the avatar sized image
const avatar = await GenerateAvatarImage(imageFile);
userData.avatarImage = avatar;
}
// Handle creating team if superadmin
if (userData.role.includes("superadmin")) {
const team = new TeamModel({
email: userData.email,
});
userData.teamId = team._id;
userData.checkTTL = 60 * 60 * 24 * 30;
await team.save();
}
// Handle creating team if superadmin
if (userData.role.includes("superadmin")) {
const team = new TeamModel({
email: userData.email,
});
userData.teamId = team._id;
userData.checkTTL = 60 * 60 * 24 * 30;
await team.save();
}
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
} catch (error) {
if (error.code === DUPLICATE_KEY_CODE) {
error.message = errorMessages.DB_USER_EXISTS;
}
error.service = SERVICE_NAME;
error.method = "insertUser";
throw error;
}
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
} catch (error) {
if (error.code === DUPLICATE_KEY_CODE) {
error.message = errorMessages.DB_USER_EXISTS;
}
error.service = SERVICE_NAME;
error.method = "insertUser";
throw error;
}
};
/**
@@ -66,22 +66,20 @@ const insertUser = async (userData, imageFile) => {
* @throws {Error}
*/
const getUserByEmail = async (email) => {
try {
// Need the password to be able to compare, removed .select()
// We can strip the hash before returing the user
const user = await UserModel.findOne({ email: email }).select(
"-profileImage"
);
if (user) {
return user;
} else {
throw new Error(errorMessages.DB_USER_NOT_FOUND);
}
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getUserByEmail";
throw error;
}
try {
// Need the password to be able to compare, removed .select()
// We can strip the hash before returing the user
const user = await UserModel.findOne({ email: email }).select("-profileImage");
if (user) {
return user;
} else {
throw new Error(errorMessages.DB_USER_NOT_FOUND);
}
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getUserByEmail";
throw error;
}
};
/**
@@ -94,45 +92,45 @@ const getUserByEmail = async (email) => {
*/
const updateUser = async (req, res) => {
const candidateUserId = req.params.userId;
try {
const candidateUser = { ...req.body };
// ******************************************
// Handle profile image
// ******************************************
const candidateUserId = req.params.userId;
try {
const candidateUser = { ...req.body };
// ******************************************
// Handle profile image
// ******************************************
if (ParseBoolean(candidateUser.deleteProfileImage) === true) {
candidateUser.profileImage = null;
candidateUser.avatarImage = null;
} else if (req.file) {
// 1. Save the full size image
candidateUser.profileImage = {
data: req.file.buffer,
contentType: req.file.mimetype,
};
if (ParseBoolean(candidateUser.deleteProfileImage) === true) {
candidateUser.profileImage = null;
candidateUser.avatarImage = null;
} else if (req.file) {
// 1. Save the full size image
candidateUser.profileImage = {
data: req.file.buffer,
contentType: req.file.mimetype,
};
// 2. Get the avaatar sized image
const avatar = await GenerateAvatarImage(req.file);
candidateUser.avatarImage = avatar;
}
// 2. Get the avaatar sized image
const avatar = await GenerateAvatarImage(req.file);
candidateUser.avatarImage = avatar;
}
// ******************************************
// End handling profile image
// ******************************************
// ******************************************
// End handling profile image
// ******************************************
const updatedUser = await UserModel.findByIdAndUpdate(
candidateUserId,
candidateUser,
{ new: true } // Returns updated user instead of pre-update user
)
.select("-password")
.select("-profileImage");
return updatedUser;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "updateUser";
throw error;
}
const updatedUser = await UserModel.findByIdAndUpdate(
candidateUserId,
candidateUser,
{ new: true } // Returns updated user instead of pre-update user
)
.select("-password")
.select("-profileImage");
return updatedUser;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "updateUser";
throw error;
}
};
/**
@@ -144,17 +142,17 @@ const updateUser = async (req, res) => {
* @throws {Error}
*/
const deleteUser = async (userId) => {
try {
const deletedUser = await UserModel.findByIdAndDelete(userId);
if (!deletedUser) {
throw new Error(errorMessages.DB_USER_NOT_FOUND);
}
return deletedUser;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteUser";
throw error;
}
try {
const deletedUser = await UserModel.findByIdAndDelete(userId);
if (!deletedUser) {
throw new Error(errorMessages.DB_USER_NOT_FOUND);
}
return deletedUser;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteUser";
throw error;
}
};
/**
@@ -165,56 +163,54 @@ const deleteUser = async (userId) => {
* @throws {Error}
*/
const deleteTeam = async (teamId) => {
try {
await TeamModel.findByIdAndDelete(teamId);
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteTeam";
throw error;
}
try {
await TeamModel.findByIdAndDelete(teamId);
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteTeam";
throw error;
}
};
const deleteAllOtherUsers = async () => {
try {
await UserModel.deleteMany({ role: { $ne: "superadmin" } });
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteAllOtherUsers";
throw error;
}
try {
await UserModel.deleteMany({ role: { $ne: "superadmin" } });
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteAllOtherUsers";
throw error;
}
};
const getAllUsers = async (req, res) => {
try {
const users = await UserModel.find()
.select("-password")
.select("-profileImage");
return users;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getAllUsers";
throw error;
}
try {
const users = await UserModel.find().select("-password").select("-profileImage");
return users;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getAllUsers";
throw error;
}
};
const logoutUser = async (userId) => {
try {
await UserModel.updateOne({ _id: userId }, { $unset: { authToken: null } });
return true;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "logoutUser";
throw error;
}
try {
await UserModel.updateOne({ _id: userId }, { $unset: { authToken: null } });
return true;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "logoutUser";
throw error;
}
};
export {
insertUser,
getUserByEmail,
updateUser,
deleteUser,
deleteTeam,
deleteAllOtherUsers,
getAllUsers,
logoutUser,
insertUser,
getUserByEmail,
updateUser,
deleteUser,
deleteTeam,
deleteAllOtherUsers,
getAllUsers,
logoutUser,
};

View File

@@ -2,14 +2,14 @@ import logger from "../utils/logger.js";
import { errorMessages } from "../utils/messages.js";
const handleErrors = (error, req, res, next) => {
const status = error.status || 500;
const message = error.message || errorMessages.FRIENDLY_ERROR;
const service = error.service || errorMessages.UNKNOWN_SERVICE;
logger.error(error.message, {
service: service,
method: error.method,
});
res.status(status).json({ success: false, msg: message });
const status = error.status || 500;
const message = error.message || errorMessages.FRIENDLY_ERROR;
const service = error.service || errorMessages.UNKNOWN_SERVICE;
logger.error(error.message, {
service: service,
method: error.method,
});
res.status(status).json({ success: false, msg: message });
};
export { handleErrors };

View File

@@ -4,52 +4,52 @@ const SERVICE_NAME = "allowedRoles";
import { errorMessages } from "../utils/messages.js";
const isAllowed = (allowedRoles) => {
return (req, res, next) => {
const token = req.headers["authorization"];
return (req, res, next) => {
const token = req.headers["authorization"];
// If no token is pressent, return an error
if (!token) {
const error = new Error(errorMessages.NO_AUTH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
return;
}
// If no token is pressent, return an error
if (!token) {
const error = new Error(errorMessages.NO_AUTH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
return;
}
// If the token is improperly formatted, return an error
if (!token.startsWith(TOKEN_PREFIX)) {
const error = new Error(errorMessages.INVALID_AUTH_TOKEN);
error.status = 400;
error.service = SERVICE_NAME;
next(error);
return;
}
// Parse the token
try {
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
const { jwtSecret } = req.settingsService.getSettings();
var decoded = jwt.verify(parsedToken, jwtSecret);
const userRoles = decoded.role;
// If the token is improperly formatted, return an error
if (!token.startsWith(TOKEN_PREFIX)) {
const error = new Error(errorMessages.INVALID_AUTH_TOKEN);
error.status = 400;
error.service = SERVICE_NAME;
next(error);
return;
}
// Parse the token
try {
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
const { jwtSecret } = req.settingsService.getSettings();
var decoded = jwt.verify(parsedToken, jwtSecret);
const userRoles = decoded.role;
// Check if the user has the required role
if (userRoles.some((role) => allowedRoles.includes(role))) {
next();
return;
} else {
const error = new Error(errorMessages.INSUFFICIENT_PERMISSIONS);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
return;
}
} catch (error) {
error.status = 401;
error.method = "isAllowed";
error.service = SERVICE_NAME;
next(error);
return;
}
};
// Check if the user has the required role
if (userRoles.some((role) => allowedRoles.includes(role))) {
next();
return;
} else {
const error = new Error(errorMessages.INSUFFICIENT_PERMISSIONS);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
return;
}
} catch (error) {
error.status = 401;
error.method = "isAllowed";
error.service = SERVICE_NAME;
next(error);
return;
}
};
};
export { isAllowed };

View File

@@ -13,82 +13,80 @@ const TOKEN_PREFIX = "Bearer ";
* @returns {express.Response}
*/
const verifyJWT = (req, res, next) => {
const token = req.headers["authorization"];
// Make sure a token is provided
if (!token) {
const error = new Error(errorMessages.NO_AUTH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
return;
}
// Make sure it is properly formatted
if (!token.startsWith(TOKEN_PREFIX)) {
const error = new Error(errorMessages.INVALID_AUTH_TOKEN); // Instantiate a new Error object for improperly formatted token
error.status = 400;
error.service = SERVICE_NAME;
error.method = "verifyJWT";
next(error);
return;
}
const token = req.headers["authorization"];
// Make sure a token is provided
if (!token) {
const error = new Error(errorMessages.NO_AUTH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
return;
}
// Make sure it is properly formatted
if (!token.startsWith(TOKEN_PREFIX)) {
const error = new Error(errorMessages.INVALID_AUTH_TOKEN); // Instantiate a new Error object for improperly formatted token
error.status = 400;
error.service = SERVICE_NAME;
error.method = "verifyJWT";
next(error);
return;
}
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
// Verify the token's authenticity
const { jwtSecret } = req.settingsService.getSettings();
jwt.verify(parsedToken, jwtSecret, (err, decoded) => {
if (err) {
if (err.name === "TokenExpiredError") {
// token has expired
handleExpiredJwtToken(req, res, next);
}
else {
// Invalid token (signature or token altered or other issue)
const errorMessage = errorMessages.INVALID_AUTH_TOKEN;
return res.status(401).json({ success: false, msg: errorMessage });
}
}
else {
// Token is valid and not expired, carry on with request, Add the decoded payload to the request
req.user = decoded;
next();
}
});
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
// Verify the token's authenticity
const { jwtSecret } = req.settingsService.getSettings();
jwt.verify(parsedToken, jwtSecret, (err, decoded) => {
if (err) {
if (err.name === "TokenExpiredError") {
// token has expired
handleExpiredJwtToken(req, res, next);
} else {
// Invalid token (signature or token altered or other issue)
const errorMessage = errorMessages.INVALID_AUTH_TOKEN;
return res.status(401).json({ success: false, msg: errorMessage });
}
} else {
// Token is valid and not expired, carry on with request, Add the decoded payload to the request
req.user = decoded;
next();
}
});
};
function handleExpiredJwtToken(req, res, next) {
// check for refreshToken
const refreshToken = req.headers["x-refresh-token"];
// check for refreshToken
const refreshToken = req.headers["x-refresh-token"];
if (!refreshToken) {
// No refresh token provided
const error = new Error(errorMessages.NO_REFRESH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
error.method = "handleExpiredJwtToken";
return next(error);
}
if (!refreshToken) {
// No refresh token provided
const error = new Error(errorMessages.NO_REFRESH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
error.method = "handleExpiredJwtToken";
return next(error);
}
// Verify refresh token
const { refreshTokenSecret } = req.settingsService.getSettings();
jwt.verify(refreshToken, refreshTokenSecret, (refreshErr, refreshDecoded) => {
if (refreshErr) {
// Invalid or expired refresh token, trigger logout
const errorMessage =
refreshErr.name === "TokenExpiredError"
? errorMessages.EXPIRED_REFRESH_TOKEN
: errorMessages.INVALID_REFRESH_TOKEN;
const error = new Error(errorMessage);
error.status = 401;
error.service = SERVICE_NAME;
return next(error);
}
// Verify refresh token
const { refreshTokenSecret } = req.settingsService.getSettings();
jwt.verify(refreshToken, refreshTokenSecret, (refreshErr, refreshDecoded) => {
if (refreshErr) {
// Invalid or expired refresh token, trigger logout
const errorMessage =
refreshErr.name === "TokenExpiredError"
? errorMessages.EXPIRED_REFRESH_TOKEN
: errorMessages.INVALID_REFRESH_TOKEN;
const error = new Error(errorMessage);
error.status = 401;
error.service = SERVICE_NAME;
return next(error);
}
// Refresh token is valid and unexpired, request for new access token
res.status(403).json({
success: false,
msg: errorMessages.REQUEST_NEW_ACCESS_TOKEN,
});
});
// Refresh token is valid and unexpired, request for new access token
res.status(403).json({
success: false,
msg: errorMessages.REQUEST_NEW_ACCESS_TOKEN,
});
});
}
export { verifyJWT };
export { verifyJWT };

View File

@@ -3,47 +3,47 @@ import { errorMessages } from "../utils/messages.js";
const SERVICE_NAME = "verifyOwnership";
const verifyOwnership = (Model, paramName) => {
return async (req, res, next) => {
const userId = req.user._id;
const documentId = req.params[paramName];
try {
const doc = await Model.findById(documentId);
//If the document is not found, return a 404 error
if (!doc) {
logger.error(errorMessages.VERIFY_OWNER_NOT_FOUND, {
service: SERVICE_NAME,
});
const error = new Error(errorMessages.VERIFY_OWNER_NOT_FOUND);
error.status = 404;
throw error;
}
return async (req, res, next) => {
const userId = req.user._id;
const documentId = req.params[paramName];
try {
const doc = await Model.findById(documentId);
//If the document is not found, return a 404 error
if (!doc) {
logger.error(errorMessages.VERIFY_OWNER_NOT_FOUND, {
service: SERVICE_NAME,
});
const error = new Error(errorMessages.VERIFY_OWNER_NOT_FOUND);
error.status = 404;
throw error;
}
// Special case for User model, as it will not have a `userId` field as other docs will
if (Model.modelName === "User") {
if (userId.toString() !== doc._id.toString()) {
const error = new Error(errorMessages.VERIFY_OWNER_UNAUTHORIZED);
error.status = 403;
throw error;
}
next();
return;
}
// Special case for User model, as it will not have a `userId` field as other docs will
if (Model.modelName === "User") {
if (userId.toString() !== doc._id.toString()) {
const error = new Error(errorMessages.VERIFY_OWNER_UNAUTHORIZED);
error.status = 403;
throw error;
}
next();
return;
}
// If the userID does not match the document's userID, return a 403 error
if (userId.toString() !== doc.userId.toString()) {
const error = new Error(errorMessages.VERIFY_OWNER_UNAUTHORIZED);
error.status = 403;
throw error;
}
next();
return;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "verifyOwnership";
next(error);
return;
}
};
// If the userID does not match the document's userID, return a 403 error
if (userId.toString() !== doc.userId.toString()) {
const error = new Error(errorMessages.VERIFY_OWNER_UNAUTHORIZED);
error.status = 403;
throw error;
}
next();
return;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "verifyOwnership";
next(error);
return;
}
};
};
export { verifyOwnership };

View File

@@ -12,49 +12,47 @@ const { errorMessages } = require("../utils/messages");
* @returns {express.Response}
*/
const verifySuperAdmin = (req, res, next) => {
const token = req.headers["authorization"];
// Make sure a token is provided
if (!token) {
const error = new Error(errorMessages.NO_AUTH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
return;
}
// Make sure it is properly formatted
if (!token.startsWith(TOKEN_PREFIX)) {
const error = new Error(errorMessages.INVALID_AUTH_TOKEN); // Instantiate a new Error object for improperly formatted token
error.status = 400;
error.service = SERVICE_NAME;
error.method = "verifySuperAdmin";
next(error);
return;
}
const token = req.headers["authorization"];
// Make sure a token is provided
if (!token) {
const error = new Error(errorMessages.NO_AUTH_TOKEN);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
return;
}
// Make sure it is properly formatted
if (!token.startsWith(TOKEN_PREFIX)) {
const error = new Error(errorMessages.INVALID_AUTH_TOKEN); // Instantiate a new Error object for improperly formatted token
error.status = 400;
error.service = SERVICE_NAME;
error.method = "verifySuperAdmin";
next(error);
return;
}
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
// verify admin role is present
const { jwtSecret } = req.settingsService.getSettings();
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
// verify admin role is present
const { jwtSecret } = req.settingsService.getSettings();
jwt.verify(parsedToken, jwtSecret, (err, decoded) => {
if (err) {
logger.error(errorMessages.INVALID_AUTH_TOKEN, {
service: SERVICE_NAME,
});
return res
.status(401)
.json({ success: false, msg: errorMessages.INVALID_AUTH_TOKEN });
}
jwt.verify(parsedToken, jwtSecret, (err, decoded) => {
if (err) {
logger.error(errorMessages.INVALID_AUTH_TOKEN, {
service: SERVICE_NAME,
});
return res
.status(401)
.json({ success: false, msg: errorMessages.INVALID_AUTH_TOKEN });
}
if (decoded.role.includes("superadmin") === false) {
logger.error(errorMessages.INVALID_AUTH_TOKEN, {
service: SERVICE_NAME,
});
return res
.status(401)
.json({ success: false, msg: errorMessages.UNAUTHORIZED });
}
next();
});
if (decoded.role.includes("superadmin") === false) {
logger.error(errorMessages.INVALID_AUTH_TOKEN, {
service: SERVICE_NAME,
});
return res.status(401).json({ success: false, msg: errorMessages.UNAUTHORIZED });
}
next();
});
};
module.exports = { verifySuperAdmin };

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
import { Router } from "express";
import {
createCheck,
getChecks,
deleteChecks,
getTeamChecks,
deleteChecksByTeamId,
updateChecksTTL,
createCheck,
getChecks,
deleteChecks,
getTeamChecks,
deleteChecksByTeamId,
updateChecksTTL,
} from "../controllers/checkController.js";
import { verifyOwnership } from "../middleware/verifyOwnership.js";
import { isAllowed } from "../middleware/isAllowed.js";
@@ -15,19 +15,11 @@ const router = Router();
router.get("/:monitorId", getChecks);
router.post("/:monitorId", verifyOwnership(Monitor, "monitorId"), createCheck);
router.delete(
"/:monitorId",
verifyOwnership(Monitor, "monitorId"),
deleteChecks
);
router.delete("/:monitorId", verifyOwnership(Monitor, "monitorId"), deleteChecks);
router.get("/team/:teamId", getTeamChecks);
router.delete(
"/team/:teamId",
isAllowed(["admin", "superadmin"]),
deleteChecksByTeamId
);
router.delete("/team/:teamId", isAllowed(["admin", "superadmin"]), deleteChecksByTeamId);
router.put("/team/ttl", isAllowed(["admin", "superadmin"]), updateChecksTTL);

View File

@@ -2,18 +2,13 @@ import { Router } from "express";
import { verifyJWT } from "../middleware/verifyJWT.js";
import { isAllowed } from "../middleware/isAllowed.js";
import {
issueInvitation,
inviteVerifyController,
issueInvitation,
inviteVerifyController,
} from "../controllers/inviteController.js";
const router = Router();
router.post(
"/",
isAllowed(["admin", "superadmin"]),
verifyJWT,
issueInvitation
);
router.post("/", isAllowed(["admin", "superadmin"]), verifyJWT, issueInvitation);
router.post("/verify", issueInvitation);
export default router;

View File

@@ -1,11 +1,11 @@
import { Router } from "express";
import {
createMaintenanceWindows,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindow,
editMaintenanceWindow,
createMaintenanceWindows,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindow,
editMaintenanceWindow,
} from "../controllers/maintenanceWindowController.js";
import { verifyOwnership } from "../middleware/verifyOwnership.js";
import Monitor from "../db/models/Monitor.js";
@@ -15,9 +15,9 @@ const router = Router();
router.post("/", createMaintenanceWindows);
router.get(
"/monitor/:monitorId",
verifyOwnership(Monitor, "monitorId"),
getMaintenanceWindowsByMonitorId
"/monitor/:monitorId",
verifyOwnership(Monitor, "monitorId"),
getMaintenanceWindowsByMonitorId
);
router.get("/team/", getMaintenanceWindowsByTeamId);

View File

@@ -1,9 +1,9 @@
import { Router } from "express";
import {
getMetrics,
getJobs,
addJob,
obliterateQueue,
getMetrics,
getJobs,
addJob,
obliterateQueue,
} from "../controllers/queueController.js";
const router = Router();

View File

@@ -1,8 +1,5 @@
import { Router } from "express";
import {
getAppSettings,
updateAppSettings,
} from "../controllers/settingsController.js";
import { getAppSettings, updateAppSettings } from "../controllers/settingsController.js";
import { isAllowed } from "../middleware/isAllowed.js";
const router = Router();

View File

@@ -15,128 +15,117 @@ const SERVICE_NAME = "EmailService";
* Represents an email service that can load templates, build, and send emails.
*/
class EmailService {
/**
* Constructs an instance of the EmailService, initializing template loaders and the email transporter.
*/
constructor(settingsService) {
this.settingsService = settingsService;
/**
* Loads an email template from the filesystem.
*
* @param {string} templateName - The name of the template to load.
* @returns {Function} A compiled template function that can be used to generate HTML email content.
*/
this.loadTemplate = (templateName) => {
try {
const templatePath = path.join(
__dirname,
`../templates/${templateName}.mjml`
);
const templateContent = fs.readFileSync(templatePath, "utf8");
return compile(templateContent);
} catch (error) {
logger.error("Error loading Email templates", {
error,
service: this.SERVICE_NAME,
});
}
};
/**
* Constructs an instance of the EmailService, initializing template loaders and the email transporter.
*/
constructor(settingsService) {
this.settingsService = settingsService;
/**
* Loads an email template from the filesystem.
*
* @param {string} templateName - The name of the template to load.
* @returns {Function} A compiled template function that can be used to generate HTML email content.
*/
this.loadTemplate = (templateName) => {
try {
const templatePath = path.join(__dirname, `../templates/${templateName}.mjml`);
const templateContent = fs.readFileSync(templatePath, "utf8");
return compile(templateContent);
} catch (error) {
logger.error("Error loading Email templates", {
error,
service: this.SERVICE_NAME,
});
}
};
/**
* A lookup object to access preloaded email templates.
* @type {Object.<string, Function>}
* TODO Load less used templates in their respective functions
*/
this.templateLookup = {
welcomeEmailTemplate: this.loadTemplate("welcomeEmail"),
employeeActivationTemplate: this.loadTemplate("employeeActivation"),
noIncidentsThisWeekTemplate: this.loadTemplate("noIncidentsThisWeek"),
serverIsDownTemplate: this.loadTemplate("serverIsDown"),
serverIsUpTemplate: this.loadTemplate("serverIsUp"),
passwordResetTemplate: this.loadTemplate("passwordReset"),
};
/**
* A lookup object to access preloaded email templates.
* @type {Object.<string, Function>}
* TODO Load less used templates in their respective functions
*/
this.templateLookup = {
welcomeEmailTemplate: this.loadTemplate("welcomeEmail"),
employeeActivationTemplate: this.loadTemplate("employeeActivation"),
noIncidentsThisWeekTemplate: this.loadTemplate("noIncidentsThisWeek"),
serverIsDownTemplate: this.loadTemplate("serverIsDown"),
serverIsUpTemplate: this.loadTemplate("serverIsUp"),
passwordResetTemplate: this.loadTemplate("passwordReset"),
};
/**
* The email transporter used to send emails.
* @type {Object}
*/
/**
* The email transporter used to send emails.
* @type {Object}
*/
const {
systemEmailHost,
systemEmailPort,
systemEmailAddress,
systemEmailPassword,
} = this.settingsService.getSettings();
const { systemEmailHost, systemEmailPort, systemEmailAddress, systemEmailPassword } =
this.settingsService.getSettings();
const emailConfig = {
host: systemEmailHost,
port: systemEmailPort,
secure: true,
auth: {
user: systemEmailAddress,
pass: systemEmailPassword,
},
};
const emailConfig = {
host: systemEmailHost,
port: systemEmailPort,
secure: true,
auth: {
user: systemEmailAddress,
pass: systemEmailPassword,
},
};
this.transporter = nodemailer.createTransport(emailConfig);
}
this.transporter = nodemailer.createTransport(emailConfig);
}
/**
* Asynchronously builds and sends an email using a specified template and context.
*
* @param {string} template - The name of the template to use for the email body.
* @param {Object} context - The data context to render the template with.
* @param {string} to - The recipient's email address.
* @param {string} subject - The subject of the email.
* @returns {Promise<string>} A promise that resolves to the messageId of the sent email.
*/
buildAndSendEmail = async (template, context, to, subject) => {
const buildHtml = async (template, context) => {
try {
const mjml = this.templateLookup[template](context);
const html = await mjml2html(mjml);
return html.html;
} catch (error) {
logger.error("Error building Email HTML", {
error,
service: SERVICE_NAME,
});
}
};
/**
* Asynchronously builds and sends an email using a specified template and context.
*
* @param {string} template - The name of the template to use for the email body.
* @param {Object} context - The data context to render the template with.
* @param {string} to - The recipient's email address.
* @param {string} subject - The subject of the email.
* @returns {Promise<string>} A promise that resolves to the messageId of the sent email.
*/
buildAndSendEmail = async (template, context, to, subject) => {
const buildHtml = async (template, context) => {
try {
const mjml = this.templateLookup[template](context);
const html = await mjml2html(mjml);
return html.html;
} catch (error) {
logger.error("Error building Email HTML", {
error,
service: SERVICE_NAME,
});
}
};
const sendEmail = async (to, subject, html) => {
try {
const info = await this.transporter.sendMail({
to: to,
subject: subject,
html: html,
});
return info;
} catch (error) {
logger.error("Error sending Email", {
error,
service: SERVICE_NAME,
});
}
};
const sendEmail = async (to, subject, html) => {
try {
const info = await this.transporter.sendMail({
to: to,
subject: subject,
html: html,
});
return info;
} catch (error) {
logger.error("Error sending Email", {
error,
service: SERVICE_NAME,
});
}
};
try {
const info = await sendEmail(
to,
subject,
await buildHtml(template, context)
);
return info.messageId;
} catch (error) {
error.service = SERVICE_NAME;
if (error.method === undefined) {
error.method = "buildAndSendEmail";
}
logger.error("Error building and sending Email", {
error,
service: SERVICE_NAME,
});
}
};
try {
const info = await sendEmail(to, subject, await buildHtml(template, context));
return info.messageId;
} catch (error) {
error.service = SERVICE_NAME;
if (error.method === undefined) {
error.method = "buildAndSendEmail";
}
logger.error("Error building and sending Email", {
error,
service: SERVICE_NAME,
});
}
};
}
export default EmailService;

View File

@@ -13,145 +13,142 @@ const SERVICE_NAME = "JobQueue";
* It scales the number of workers based on the number of jobs/worker
*/
class JobQueue {
/**
* Constructs a new JobQueue
* @constructor
* @param {SettingsService} settingsService - The settings service
* @throws {Error}
*/
constructor(settingsService) {
const { redisHost, redisPort } = settingsService.getSettings();
const connection = {
host: redisHost || "127.0.0.1",
port: redisPort || 6379,
};
this.connection = connection;
this.queue = new Queue(QUEUE_NAME, {
connection,
});
this.workers = [];
this.db = null;
this.networkService = null;
this.settingsService = settingsService;
}
/**
* Constructs a new JobQueue
* @constructor
* @param {SettingsService} settingsService - The settings service
* @throws {Error}
*/
constructor(settingsService) {
const { redisHost, redisPort } = settingsService.getSettings();
const connection = {
host: redisHost || "127.0.0.1",
port: redisPort || 6379,
};
this.connection = connection;
this.queue = new Queue(QUEUE_NAME, {
connection,
});
this.workers = [];
this.db = null;
this.networkService = null;
this.settingsService = settingsService;
}
/**
* Static factory method to create a JobQueue
* @static
* @async
* @returns {Promise<JobQueue>} - Returns a new JobQueue
*
*/
static async createJobQueue(db, networkService, settingsService) {
const queue = new JobQueue(settingsService);
try {
queue.db = db;
queue.networkService = networkService;
const monitors = await db.getAllMonitors();
for (const monitor of monitors) {
if (monitor.isActive) {
await queue.addJob(monitor.id, monitor);
}
}
const workerStats = await queue.getWorkerStats();
await queue.scaleWorkers(workerStats);
return queue;
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "createJobQueue") : null;
throw error;
}
}
/**
* Static factory method to create a JobQueue
* @static
* @async
* @returns {Promise<JobQueue>} - Returns a new JobQueue
*
*/
static async createJobQueue(db, networkService, settingsService) {
const queue = new JobQueue(settingsService);
try {
queue.db = db;
queue.networkService = networkService;
const monitors = await db.getAllMonitors();
for (const monitor of monitors) {
if (monitor.isActive) {
await queue.addJob(monitor.id, monitor);
}
}
const workerStats = await queue.getWorkerStats();
await queue.scaleWorkers(workerStats);
return queue;
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "createJobQueue") : null;
throw error;
}
}
/**
* Creates a worker for the queue
* Operations are carried out in the async callback
* @returns {Worker} The newly created worker
*/
createWorker() {
const worker = new Worker(
QUEUE_NAME,
async (job) => {
try {
// Get all maintenance windows for this monitor
const monitorId = job.data._id;
const maintenanceWindows =
await this.db.getMaintenanceWindowsByMonitorId(monitorId);
// Check for active maintenance window:
const maintenanceWindowActive = maintenanceWindows.reduce(
(acc, window) => {
if (window.active) {
const start = new Date(window.start);
const end = new Date(window.end);
const now = new Date();
const repeatInterval = window.repeat || 0;
/**
* Creates a worker for the queue
* Operations are carried out in the async callback
* @returns {Worker} The newly created worker
*/
createWorker() {
const worker = new Worker(
QUEUE_NAME,
async (job) => {
try {
// Get all maintenance windows for this monitor
const monitorId = job.data._id;
const maintenanceWindows =
await this.db.getMaintenanceWindowsByMonitorId(monitorId);
// Check for active maintenance window:
const maintenanceWindowActive = maintenanceWindows.reduce((acc, window) => {
if (window.active) {
const start = new Date(window.start);
const end = new Date(window.end);
const now = new Date();
const repeatInterval = window.repeat || 0;
while ((start < now) & (repeatInterval !== 0)) {
start.setTime(start.getTime() + repeatInterval);
end.setTime(end.getTime() + repeatInterval);
}
while ((start < now) & (repeatInterval !== 0)) {
start.setTime(start.getTime() + repeatInterval);
end.setTime(end.getTime() + repeatInterval);
}
if (start < now && end > now) {
return true;
}
}
return acc;
},
false
);
if (start < now && end > now) {
return true;
}
}
return acc;
}, false);
if (!maintenanceWindowActive) {
const res = await this.networkService.getStatus(job);
} else {
logger.info(`Monitor ${monitorId} is in maintenance window`, {
service: SERVICE_NAME,
monitorId,
});
}
} catch (error) {
logger.error(`Error processing job ${job.id}: ${error.message}`, {
service: SERVICE_NAME,
jobId: job.id,
error: error,
});
}
},
{
connection: this.connection,
}
);
return worker;
}
if (!maintenanceWindowActive) {
const res = await this.networkService.getStatus(job);
} else {
logger.info(`Monitor ${monitorId} is in maintenance window`, {
service: SERVICE_NAME,
monitorId,
});
}
} catch (error) {
logger.error(`Error processing job ${job.id}: ${error.message}`, {
service: SERVICE_NAME,
jobId: job.id,
error: error,
});
}
},
{
connection: this.connection,
}
);
return worker;
}
/**
* @typedef {Object} WorkerStats
* @property {Array<Job>} jobs - Array of jobs in the Queue
* @property {number} - workerLoad - The number of jobs per worker
*
*/
/**
* @typedef {Object} WorkerStats
* @property {Array<Job>} jobs - Array of jobs in the Queue
* @property {number} - workerLoad - The number of jobs per worker
*
*/
/**
* Gets stats related to the workers
* This is used for scaling workers right now
* In the future we will likely want to scale based on server performance metrics
* CPU Usage & memory usage, if too high, scale down workers.
* When to scale up? If jobs are taking too long to complete?
* @async
* @returns {Promise<WorkerStats>} - Returns the worker stats
*/
async getWorkerStats() {
try {
const jobs = await this.queue.getRepeatableJobs();
const load = jobs.length / this.workers.length;
return { jobs, load };
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "getWorkerStats") : null;
throw error;
}
}
/**
* Gets stats related to the workers
* This is used for scaling workers right now
* In the future we will likely want to scale based on server performance metrics
* CPU Usage & memory usage, if too high, scale down workers.
* When to scale up? If jobs are taking too long to complete?
* @async
* @returns {Promise<WorkerStats>} - Returns the worker stats
*/
async getWorkerStats() {
try {
const jobs = await this.queue.getRepeatableJobs();
const load = jobs.length / this.workers.length;
return { jobs, load };
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "getWorkerStats") : null;
throw error;
}
}
/**
/**
* Scale Workers
* This function scales workers based on the load per worker
* If the load is higher than the JOBS_PER_WORKER threshold, we add more workers
@@ -163,201 +160,200 @@ class JobQueue {
* @param {WorkerStats} workerStats - The payload for the job.
* @returns {Promise<boolean>}
*/
async scaleWorkers(workerStats) {
if (this.workers.length === 0) {
// There are no workers, need to add one
for (let i = 0; i < 5; i++) {
const worker = this.createWorker();
this.workers.push(worker);
}
return true;
}
async scaleWorkers(workerStats) {
if (this.workers.length === 0) {
// There are no workers, need to add one
for (let i = 0; i < 5; i++) {
const worker = this.createWorker();
this.workers.push(worker);
}
return true;
}
if (workerStats.load > JOBS_PER_WORKER) {
// Find out how many more jobs we have than current workers can handle
const excessJobs =
workerStats.jobs.length - this.workers.length * JOBS_PER_WORKER;
if (workerStats.load > JOBS_PER_WORKER) {
// Find out how many more jobs we have than current workers can handle
const excessJobs = workerStats.jobs.length - this.workers.length * JOBS_PER_WORKER;
// Divide by jobs/worker to find out how many workers to add
const workersToAdd = Math.ceil(excessJobs / JOBS_PER_WORKER);
for (let i = 0; i < workersToAdd; i++) {
const worker = this.createWorker();
this.workers.push(worker);
}
return true;
}
// Divide by jobs/worker to find out how many workers to add
const workersToAdd = Math.ceil(excessJobs / JOBS_PER_WORKER);
for (let i = 0; i < workersToAdd; i++) {
const worker = this.createWorker();
this.workers.push(worker);
}
return true;
}
if (workerStats.load < JOBS_PER_WORKER) {
// Find out how much excess capacity we have
const workerCapacity = this.workers.length * JOBS_PER_WORKER;
const excessCapacity = workerCapacity - workerStats.jobs.length;
// Calculate how many workers to remove
const workersToRemove = Math.floor(excessCapacity / JOBS_PER_WORKER);
if (this.workers.length > 5) {
for (let i = 0; i < workersToRemove; i++) {
const worker = this.workers.pop();
try {
await worker.close();
} catch (error) {
// Catch the error instead of throwing it
logger.error(errorMessages.JOB_QUEUE_WORKER_CLOSE, {
service: SERVICE_NAME,
});
}
}
}
return true;
}
return false;
}
if (workerStats.load < JOBS_PER_WORKER) {
// Find out how much excess capacity we have
const workerCapacity = this.workers.length * JOBS_PER_WORKER;
const excessCapacity = workerCapacity - workerStats.jobs.length;
// Calculate how many workers to remove
const workersToRemove = Math.floor(excessCapacity / JOBS_PER_WORKER);
if (this.workers.length > 5) {
for (let i = 0; i < workersToRemove; i++) {
const worker = this.workers.pop();
try {
await worker.close();
} catch (error) {
// Catch the error instead of throwing it
logger.error(errorMessages.JOB_QUEUE_WORKER_CLOSE, {
service: SERVICE_NAME,
});
}
}
}
return true;
}
return false;
}
/**
* Gets all jobs in the queue.
*
* @async
* @returns {Promise<Array<Job>>}
* @throws {Error} - Throws error if getting jobs fails
*/
async getJobs() {
try {
const jobs = await this.queue.getRepeatableJobs();
return jobs;
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "getJobs") : null;
throw error;
}
}
/**
* Gets all jobs in the queue.
*
* @async
* @returns {Promise<Array<Job>>}
* @throws {Error} - Throws error if getting jobs fails
*/
async getJobs() {
try {
const jobs = await this.queue.getRepeatableJobs();
return jobs;
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "getJobs") : null;
throw error;
}
}
async getJobStats() {
try {
const jobs = await this.queue.getJobs();
const ret = await Promise.all(
jobs.map(async (job) => {
const state = await job.getState();
return { url: job.data.url, state };
})
);
return { jobs: ret, workers: this.workers.length };
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "getJobStats") : null;
throw error;
}
}
async getJobStats() {
try {
const jobs = await this.queue.getJobs();
const ret = await Promise.all(
jobs.map(async (job) => {
const state = await job.getState();
return { url: job.data.url, state };
})
);
return { jobs: ret, workers: this.workers.length };
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "getJobStats") : null;
throw error;
}
}
/**
* Adds a job to the queue and scales workers based on worker stats.
*
* @async
* @param {string} jobName - The name of the job to be added.
* @param {Monitor} payload - The payload for the job.
* @throws {Error} - Will throw an error if the job cannot be added or workers don't scale
*/
async addJob(jobName, payload) {
try {
console.log("Adding job", payload?.url ?? "No URL");
// Execute job immediately
await this.queue.add(jobName, payload);
await this.queue.add(jobName, payload, {
repeat: {
every: payload?.interval ?? 60000,
},
});
const workerStats = await this.getWorkerStats();
await this.scaleWorkers(workerStats);
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "addJob") : null;
throw error;
}
}
/**
* Adds a job to the queue and scales workers based on worker stats.
*
* @async
* @param {string} jobName - The name of the job to be added.
* @param {Monitor} payload - The payload for the job.
* @throws {Error} - Will throw an error if the job cannot be added or workers don't scale
*/
async addJob(jobName, payload) {
try {
console.log("Adding job", payload?.url ?? "No URL");
// Execute job immediately
await this.queue.add(jobName, payload);
await this.queue.add(jobName, payload, {
repeat: {
every: payload?.interval ?? 60000,
},
});
const workerStats = await this.getWorkerStats();
await this.scaleWorkers(workerStats);
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "addJob") : null;
throw error;
}
}
/**
* Deletes a job from the queue.
*
* @async
* @param {Monitor} monitor - The monitor to remove.
* @throws {Error}
*/
async deleteJob(monitor) {
try {
const deleted = await this.queue.removeRepeatable(monitor._id, {
every: monitor.interval,
});
if (deleted) {
logger.info(successMessages.JOB_QUEUE_DELETE_JOB, {
service: SERVICE_NAME,
jobId: monitor.id,
});
const workerStats = await this.getWorkerStats();
await this.scaleWorkers(workerStats);
} else {
logger.error(errorMessages.JOB_QUEUE_DELETE_JOB, {
service: SERVICE_NAME,
jobId: monitor.id,
});
}
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "deleteJob") : null;
throw error;
}
}
/**
* Deletes a job from the queue.
*
* @async
* @param {Monitor} monitor - The monitor to remove.
* @throws {Error}
*/
async deleteJob(monitor) {
try {
const deleted = await this.queue.removeRepeatable(monitor._id, {
every: monitor.interval,
});
if (deleted) {
logger.info(successMessages.JOB_QUEUE_DELETE_JOB, {
service: SERVICE_NAME,
jobId: monitor.id,
});
const workerStats = await this.getWorkerStats();
await this.scaleWorkers(workerStats);
} else {
logger.error(errorMessages.JOB_QUEUE_DELETE_JOB, {
service: SERVICE_NAME,
jobId: monitor.id,
});
}
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "deleteJob") : null;
throw error;
}
}
async getMetrics() {
try {
const metrics = {
waiting: await this.queue.getWaitingCount(),
active: await this.queue.getActiveCount(),
completed: await this.queue.getCompletedCount(),
failed: await this.queue.getFailedCount(),
delayed: await this.queue.getDelayedCount(),
repeatableJobs: (await this.queue.getRepeatableJobs()).length,
};
return metrics;
} catch (error) {
logger.error("Failed to retrieve job queue metrics", {
service: SERVICE_NAME,
errorMsg: error.message,
});
}
}
async getMetrics() {
try {
const metrics = {
waiting: await this.queue.getWaitingCount(),
active: await this.queue.getActiveCount(),
completed: await this.queue.getCompletedCount(),
failed: await this.queue.getFailedCount(),
delayed: await this.queue.getDelayedCount(),
repeatableJobs: (await this.queue.getRepeatableJobs()).length,
};
return metrics;
} catch (error) {
logger.error("Failed to retrieve job queue metrics", {
service: SERVICE_NAME,
errorMsg: error.message,
});
}
}
/**
* @async
* @returns {Promise<boolean>} - Returns true if obliteration is successful
*/
async obliterate() {
try {
let metrics = await this.getMetrics();
console.log(metrics);
await this.queue.pause();
const jobs = await this.getJobs();
/**
* @async
* @returns {Promise<boolean>} - Returns true if obliteration is successful
*/
async obliterate() {
try {
let metrics = await this.getMetrics();
console.log(metrics);
await this.queue.pause();
const jobs = await this.getJobs();
for (const job of jobs) {
await this.queue.removeRepeatableByKey(job.key);
await this.queue.remove(job.id);
}
await Promise.all(
this.workers.map(async (worker) => {
await worker.close();
})
);
for (const job of jobs) {
await this.queue.removeRepeatableByKey(job.key);
await this.queue.remove(job.id);
}
await Promise.all(
this.workers.map(async (worker) => {
await worker.close();
})
);
await this.queue.obliterate();
metrics = await this.getMetrics();
console.log(metrics);
logger.info(successMessages.JOB_QUEUE_OBLITERATE, {
service: SERVICE_NAME,
});
return true;
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "obliterate") : null;
throw error;
}
}
await this.queue.obliterate();
metrics = await this.getMetrics();
console.log(metrics);
logger.info(successMessages.JOB_QUEUE_OBLITERATE, {
service: SERVICE_NAME,
});
return true;
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "obliterate") : null;
throw error;
}
}
}
export default JobQueue;

View File

@@ -12,321 +12,314 @@ import { errorMessages, successMessages } from "../utils/messages.js";
*/
class NetworkService {
constructor(db, emailService) {
this.db = db;
this.emailService = emailService;
this.TYPE_PING = "ping";
this.TYPE_HTTP = "http";
this.TYPE_PAGESPEED = "pagespeed";
this.SERVICE_NAME = "NetworkService";
this.NETWORK_ERROR = 5000;
}
constructor(db, emailService) {
this.db = db;
this.emailService = emailService;
this.TYPE_PING = "ping";
this.TYPE_HTTP = "http";
this.TYPE_PAGESPEED = "pagespeed";
this.SERVICE_NAME = "NetworkService";
this.NETWORK_ERROR = 5000;
}
async handleNotification(monitor, isAlive) {
try {
let template =
isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate";
let status = isAlive === true ? "up" : "down";
async handleNotification(monitor, isAlive) {
try {
let template = isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate";
let status = isAlive === true ? "up" : "down";
const notifications = await this.db.getNotificationsByMonitorId(
monitor._id
);
for (const notification of notifications) {
if (notification.type === "email") {
await this.emailService.buildAndSendEmail(
template,
{ monitorName: monitor.name, monitorUrl: monitor.url },
notification.address,
`Monitor ${monitor.name} is ${status}`
);
}
}
} catch (error) {
logger.error(error.message, {
method: "handleNotification",
service: this.SERVICE_NAME,
monitorId: monitor._id,
});
}
}
const notifications = await this.db.getNotificationsByMonitorId(monitor._id);
for (const notification of notifications) {
if (notification.type === "email") {
await this.emailService.buildAndSendEmail(
template,
{ monitorName: monitor.name, monitorUrl: monitor.url },
notification.address,
`Monitor ${monitor.name} is ${status}`
);
}
}
} catch (error) {
logger.error(error.message, {
method: "handleNotification",
service: this.SERVICE_NAME,
monitorId: monitor._id,
});
}
}
async handleStatusUpdate(job, isAlive) {
let monitor;
// Look up the monitor, if it doesn't exist, it's probably been removed, return
try {
const { _id } = job.data;
monitor = await this.db.getMonitorById(_id);
} catch (error) {
return;
}
async handleStatusUpdate(job, isAlive) {
let monitor;
// Look up the monitor, if it doesn't exist, it's probably been removed, return
try {
const { _id } = job.data;
monitor = await this.db.getMonitorById(_id);
} catch (error) {
return;
}
// Otherwise, try to update monitor status
try {
if (monitor === null || monitor === undefined) {
logger.error(`Null Monitor: ${_id}`, {
method: "handleStatusUpdate",
service: this.SERVICE_NAME,
jobId: job.id,
});
return;
}
if (monitor.status === undefined || monitor.status !== isAlive) {
const oldStatus = monitor.status;
monitor.status = isAlive;
await monitor.save();
// Otherwise, try to update monitor status
try {
if (monitor === null || monitor === undefined) {
logger.error(`Null Monitor: ${_id}`, {
method: "handleStatusUpdate",
service: this.SERVICE_NAME,
jobId: job.id,
});
return;
}
if (monitor.status === undefined || monitor.status !== isAlive) {
const oldStatus = monitor.status;
monitor.status = isAlive;
await monitor.save();
if (oldStatus !== undefined && oldStatus !== isAlive) {
this.handleNotification(monitor, isAlive);
}
}
} catch (error) {
logger.error(error.message, {
method: "handleStatusUpdate",
service: this.SERVICE_NAME,
jobId: job.id,
});
}
}
if (oldStatus !== undefined && oldStatus !== isAlive) {
this.handleNotification(monitor, isAlive);
}
}
} catch (error) {
logger.error(error.message, {
method: "handleStatusUpdate",
service: this.SERVICE_NAME,
jobId: job.id,
});
}
}
/**
* Measures the response time of an asynchronous operation.
* @param {Function} operation - An asynchronous operation to measure.
* @returns {Promise<{responseTime: number, response: any}>} An object containing the response time in milliseconds and the response from the operation.
* @throws {Error} The error object from the operation, contains response time.
*/
async measureResponseTime(operation) {
const startTime = Date.now();
try {
const response = await operation();
const endTime = Date.now();
return { responseTime: endTime - startTime, response };
} catch (error) {
const endTime = Date.now();
error.responseTime = endTime - startTime;
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined
? (error.method = "measureResponseTime")
: null;
throw error;
}
}
/**
* Measures the response time of an asynchronous operation.
* @param {Function} operation - An asynchronous operation to measure.
* @returns {Promise<{responseTime: number, response: any}>} An object containing the response time in milliseconds and the response from the operation.
* @throws {Error} The error object from the operation, contains response time.
*/
async measureResponseTime(operation) {
const startTime = Date.now();
try {
const response = await operation();
const endTime = Date.now();
return { responseTime: endTime - startTime, response };
} catch (error) {
const endTime = Date.now();
error.responseTime = endTime - startTime;
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "measureResponseTime") : null;
throw error;
}
}
/**
* Handles the ping operation for a given job, measures its response time, and logs the result.
* @param {Object} job - The job object containing data for the ping operation.
* @returns {Promise<{boolean}} The result of logging and storing the check
*/
async handlePing(job) {
const operation = async () => {
const response = await ping.promise.probe(job.data.url);
return response;
};
/**
* Handles the ping operation for a given job, measures its response time, and logs the result.
* @param {Object} job - The job object containing data for the ping operation.
* @returns {Promise<{boolean}} The result of logging and storing the check
*/
async handlePing(job) {
const operation = async () => {
const response = await ping.promise.probe(job.data.url);
return response;
};
let isAlive;
let isAlive;
try {
const { responseTime, response } =
await this.measureResponseTime(operation);
isAlive = response.alive;
const checkData = {
monitorId: job.data._id,
status: isAlive,
responseTime,
message: isAlive
? successMessages.PING_SUCCESS
: errorMessages.PING_CANNOT_RESOLVE,
};
return await this.logAndStoreCheck(checkData, this.db.createCheck);
} catch (error) {
isAlive = false;
const checkData = {
monitorId: job.data._id,
status: isAlive,
message: errorMessages.PING_CANNOT_RESOLVE,
responseTime: error.responseTime,
};
return await this.logAndStoreCheck(checkData, this.db.createCheck);
} finally {
this.handleStatusUpdate(job, isAlive);
}
}
try {
const { responseTime, response } = await this.measureResponseTime(operation);
isAlive = response.alive;
const checkData = {
monitorId: job.data._id,
status: isAlive,
responseTime,
message: isAlive
? successMessages.PING_SUCCESS
: errorMessages.PING_CANNOT_RESOLVE,
};
return await this.logAndStoreCheck(checkData, this.db.createCheck);
} catch (error) {
isAlive = false;
const checkData = {
monitorId: job.data._id,
status: isAlive,
message: errorMessages.PING_CANNOT_RESOLVE,
responseTime: error.responseTime,
};
return await this.logAndStoreCheck(checkData, this.db.createCheck);
} finally {
this.handleStatusUpdate(job, isAlive);
}
}
/**
* Handles the http operation for a given job, measures its response time, and logs the result.
* @param {Object} job - The job object containing data for the ping operation.
* @returns {Promise<{boolean}} The result of logging and storing the check
*/
async handleHttp(job) {
// Define operation for timing
const operation = async () => {
const response = await axios.get(job.data.url);
return response;
};
/**
* Handles the http operation for a given job, measures its response time, and logs the result.
* @param {Object} job - The job object containing data for the ping operation.
* @returns {Promise<{boolean}} The result of logging and storing the check
*/
async handleHttp(job) {
// Define operation for timing
const operation = async () => {
const response = await axios.get(job.data.url);
return response;
};
let isAlive;
let isAlive;
// attempt connection
try {
const { responseTime, response } =
await this.measureResponseTime(operation);
// check if response is in the 200 range, if so, service is up
isAlive = response.status >= 200 && response.status < 300;
// attempt connection
try {
const { responseTime, response } = await this.measureResponseTime(operation);
// check if response is in the 200 range, if so, service is up
isAlive = response.status >= 200 && response.status < 300;
//Create a check with relevant data
const checkData = {
monitorId: job.data._id,
status: isAlive,
responseTime,
statusCode: response.status,
message: http.STATUS_CODES[response.status],
};
return await this.logAndStoreCheck(checkData, this.db.createCheck);
} catch (error) {
const statusCode = error.response?.status || this.NETWORK_ERROR;
let message = http.STATUS_CODES[statusCode] || "Network Error";
isAlive = false;
const checkData = {
monitorId: job.data._id,
status: isAlive,
statusCode,
responseTime: error.responseTime,
message,
};
//Create a check with relevant data
const checkData = {
monitorId: job.data._id,
status: isAlive,
responseTime,
statusCode: response.status,
message: http.STATUS_CODES[response.status],
};
return await this.logAndStoreCheck(checkData, this.db.createCheck);
} catch (error) {
const statusCode = error.response?.status || this.NETWORK_ERROR;
let message = http.STATUS_CODES[statusCode] || "Network Error";
isAlive = false;
const checkData = {
monitorId: job.data._id,
status: isAlive,
statusCode,
responseTime: error.responseTime,
message,
};
return await this.logAndStoreCheck(checkData, this.db.createCheck);
} finally {
this.handleStatusUpdate(job, isAlive);
}
}
return await this.logAndStoreCheck(checkData, this.db.createCheck);
} finally {
this.handleStatusUpdate(job, isAlive);
}
}
/**
* Handles PageSpeed job types by fetching and processing PageSpeed insights.
*
* This method sends a request to the Google PageSpeed Insights API to get performance metrics
* for the specified URL, then logs and stores the check results.
*
* @param {Object} job - The job object containing data related to the PageSpeed check.
* @param {string} job.data.url - The URL to be analyzed by the PageSpeed Insights API.
* @param {string} job.data._id - The unique identifier for the monitor associated with the check.
*
* @returns {Promise<void>} A promise that resolves when the check results have been logged and stored.
*
* @throws {Error} Throws an error if there is an issue with fetching or processing the PageSpeed insights.
*/
async handlePagespeed(job) {
let isAlive;
try {
const url = job.data.url;
const response = await axios.get(
`https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`
);
const pageSpeedResults = response.data;
const categories = pageSpeedResults.lighthouseResult?.categories;
const audits = pageSpeedResults.lighthouseResult?.audits;
const {
"cumulative-layout-shift": cls,
"speed-index": si,
"first-contentful-paint": fcp,
"largest-contentful-paint": lcp,
"total-blocking-time": tbt,
} = audits;
/**
* Handles PageSpeed job types by fetching and processing PageSpeed insights.
*
* This method sends a request to the Google PageSpeed Insights API to get performance metrics
* for the specified URL, then logs and stores the check results.
*
* @param {Object} job - The job object containing data related to the PageSpeed check.
* @param {string} job.data.url - The URL to be analyzed by the PageSpeed Insights API.
* @param {string} job.data._id - The unique identifier for the monitor associated with the check.
*
* @returns {Promise<void>} A promise that resolves when the check results have been logged and stored.
*
* @throws {Error} Throws an error if there is an issue with fetching or processing the PageSpeed insights.
*/
async handlePagespeed(job) {
let isAlive;
try {
const url = job.data.url;
const response = await axios.get(
`https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`
);
const pageSpeedResults = response.data;
const categories = pageSpeedResults.lighthouseResult?.categories;
const audits = pageSpeedResults.lighthouseResult?.audits;
const {
"cumulative-layout-shift": cls,
"speed-index": si,
"first-contentful-paint": fcp,
"largest-contentful-paint": lcp,
"total-blocking-time": tbt,
} = audits;
// Weights
// First Contentful Paint 10%
// Speed Index 10%
// Largest Contentful Paint 25%
// Total Blocking Time 30%
// Cumulative Layout Shift 25%
// Weights
// First Contentful Paint 10%
// Speed Index 10%
// Largest Contentful Paint 25%
// Total Blocking Time 30%
// Cumulative Layout Shift 25%
isAlive = true;
const checkData = {
monitorId: job.data._id,
status: isAlive,
statusCode: response.status,
message: http.STATUS_CODES[response.status],
accessibility: (categories.accessibility?.score || 0) * 100,
bestPractices: (categories["best-practices"]?.score || 0) * 100,
seo: (categories.seo?.score || 0) * 100,
performance: (categories.performance?.score || 0) * 100,
audits: {
cls,
si,
fcp,
lcp,
tbt,
},
};
isAlive = true;
const checkData = {
monitorId: job.data._id,
status: isAlive,
statusCode: response.status,
message: http.STATUS_CODES[response.status],
accessibility: (categories.accessibility?.score || 0) * 100,
bestPractices: (categories["best-practices"]?.score || 0) * 100,
seo: (categories.seo?.score || 0) * 100,
performance: (categories.performance?.score || 0) * 100,
audits: {
cls,
si,
fcp,
lcp,
tbt,
},
};
this.logAndStoreCheck(checkData, this.db.createPageSpeedCheck);
} catch (error) {
isAlive = false;
const statusCode = error.response?.status || this.NETWORK_ERROR;
const message = http.STATUS_CODES[statusCode] || "Network Error";
const checkData = {
monitorId: job.data._id,
status: isAlive,
statusCode,
message,
accessibility: 0,
bestPractices: 0,
seo: 0,
performance: 0,
};
this.logAndStoreCheck(checkData, this.db.createPageSpeedCheck);
} finally {
this.handleStatusUpdate(job, isAlive);
}
}
this.logAndStoreCheck(checkData, this.db.createPageSpeedCheck);
} catch (error) {
isAlive = false;
const statusCode = error.response?.status || this.NETWORK_ERROR;
const message = http.STATUS_CODES[statusCode] || "Network Error";
const checkData = {
monitorId: job.data._id,
status: isAlive,
statusCode,
message,
accessibility: 0,
bestPractices: 0,
seo: 0,
performance: 0,
};
this.logAndStoreCheck(checkData, this.db.createPageSpeedCheck);
} finally {
this.handleStatusUpdate(job, isAlive);
}
}
/**
* Retrieves the status of a given job based on its type.
* For unsupported job types, it logs an error and returns false.
*
* @param {Object} job - The job object containing data necessary for processing.
* @returns {Promise<boolean>} The status of the job if it is supported and processed successfully, otherwise false.
*/
async getStatus(job) {
switch (job.data.type) {
case this.TYPE_PING:
return await this.handlePing(job);
case this.TYPE_HTTP:
return await this.handleHttp(job);
case this.TYPE_PAGESPEED:
return await this.handlePagespeed(job);
default:
logger.error(`Unsupported type: ${job.data.type}`, {
service: this.SERVICE_NAME,
method: "getStatus",
jobId: job.id,
});
return false;
}
}
/**
* Retrieves the status of a given job based on its type.
* For unsupported job types, it logs an error and returns false.
*
* @param {Object} job - The job object containing data necessary for processing.
* @returns {Promise<boolean>} The status of the job if it is supported and processed successfully, otherwise false.
*/
async getStatus(job) {
switch (job.data.type) {
case this.TYPE_PING:
return await this.handlePing(job);
case this.TYPE_HTTP:
return await this.handleHttp(job);
case this.TYPE_PAGESPEED:
return await this.handlePagespeed(job);
default:
logger.error(`Unsupported type: ${job.data.type}`, {
service: this.SERVICE_NAME,
method: "getStatus",
jobId: job.id,
});
return false;
}
}
/**
* Logs and stores the result of a check for a specific job.
*
* @param {Object} data - Data to be written
* @param {function} writeToDB - DB write method
*
* @returns {Promise<boolean>} The status of the inserted check if successful, otherwise false.
*/
/**
* Logs and stores the result of a check for a specific job.
*
* @param {Object} data - Data to be written
* @param {function} writeToDB - DB write method
*
* @returns {Promise<boolean>} The status of the inserted check if successful, otherwise false.
*/
async logAndStoreCheck(data, writeToDB) {
try {
const insertedCheck = await writeToDB(data);
if (insertedCheck !== null && insertedCheck !== undefined) {
return insertedCheck.status;
}
} catch (error) {
logger.error(`Error wrtiting check for ${data.monitorId}`, {
service: this.SERVICE_NAME,
method: "logAndStoreCheck",
monitorId: data.monitorId,
error: error,
});
}
}
async logAndStoreCheck(data, writeToDB) {
try {
const insertedCheck = await writeToDB(data);
if (insertedCheck !== null && insertedCheck !== undefined) {
return insertedCheck.status;
}
} catch (error) {
logger.error(`Error wrtiting check for ${data.monitorId}`, {
service: this.SERVICE_NAME,
method: "logAndStoreCheck",
monitorId: data.monitorId,
error: error,
});
}
}
}
export default NetworkService;

View File

@@ -1,22 +1,22 @@
import AppSettings from "../db/models/AppSettings.js";
const SERVICE_NAME = "SettingsService";
const envConfig = {
logLevel: undefined,
apiBaseUrl: undefined,
clientHost: process.env.CLIENT_HOST,
jwtSecret: process.env.JWT_SECRET,
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET,
dbType: process.env.DB_TYPE,
dbConnectionString: process.env.DB_CONNECTION_STRING,
redisHost: process.env.REDIS_HOST,
redisPort: process.env.REDIS_PORT,
jwtTTL: process.env.TOKEN_TTL,
refreshTokenTTL: process.env.REFRESH_TOKEN_TTL,
pagespeedApiKey: process.env.PAGESPEED_API_KEY,
systemEmailHost: process.env.SYSTEM_EMAIL_HOST,
systemEmailPort: process.env.SYSTEM_EMAIL_PORT,
systemEmailAddress: process.env.SYSTEM_EMAIL_ADDRESS,
systemEmailPassword: process.env.SYSTEM_EMAIL_PASSWORD,
logLevel: undefined,
apiBaseUrl: undefined,
clientHost: process.env.CLIENT_HOST,
jwtSecret: process.env.JWT_SECRET,
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET,
dbType: process.env.DB_TYPE,
dbConnectionString: process.env.DB_CONNECTION_STRING,
redisHost: process.env.REDIS_HOST,
redisPort: process.env.REDIS_PORT,
jwtTTL: process.env.TOKEN_TTL,
refreshTokenTTL: process.env.REFRESH_TOKEN_TTL,
pagespeedApiKey: process.env.PAGESPEED_API_KEY,
systemEmailHost: process.env.SYSTEM_EMAIL_HOST,
systemEmailPort: process.env.SYSTEM_EMAIL_PORT,
systemEmailAddress: process.env.SYSTEM_EMAIL_ADDRESS,
systemEmailPassword: process.env.SYSTEM_EMAIL_PASSWORD,
};
/**
* SettingsService
@@ -26,60 +26,60 @@ const envConfig = {
* from the database if they are not set in the environment.
*/
class SettingsService {
/**
* Constructs a new SettingsService
* @constructor
* @throws {Error}
*/ constructor() {
this.settings = { ...envConfig };
}
/**
* Load settings from the database and merge with environment settings.
* If there are any settings that weren't set by environment variables, use user settings from the database.
* @returns {Promise<Object>} The merged settings.
* @throws Will throw an error if settings are not found in the database or if settings have not been loaded.
*/ async loadSettings() {
try {
const dbSettings = await AppSettings.findOne();
if (!this.settings) {
throw new Error("Settings not found");
}
/**
* Constructs a new SettingsService
* @constructor
* @throws {Error}
*/ constructor() {
this.settings = { ...envConfig };
}
/**
* Load settings from the database and merge with environment settings.
* If there are any settings that weren't set by environment variables, use user settings from the database.
* @returns {Promise<Object>} The merged settings.
* @throws Will throw an error if settings are not found in the database or if settings have not been loaded.
*/ async loadSettings() {
try {
const dbSettings = await AppSettings.findOne();
if (!this.settings) {
throw new Error("Settings not found");
}
// If there are any settings that weren't set by environment variables, use user settings from DB
for (const key in envConfig) {
if (envConfig[key] === undefined && dbSettings[key] !== undefined) {
this.settings[key] = dbSettings[key];
}
}
// If there are any settings that weren't set by environment variables, use user settings from DB
for (const key in envConfig) {
if (envConfig[key] === undefined && dbSettings[key] !== undefined) {
this.settings[key] = dbSettings[key];
}
}
if (!this.settings) {
throw new Error("Settings not found");
}
return this.settings;
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "loadSettings") : null;
throw error;
}
}
/**
* Reload settings by calling loadSettings.
* @returns {Promise<Object>} The reloaded settings.
*/
async reloadSettings() {
return this.loadSettings();
}
/**
* Get the current settings.
* @returns {Object} The current settings.
* @throws Will throw an error if settings have not been loaded.
*/
getSettings() {
if (!this.settings) {
throw new Error("Settings have not been loaded");
}
return this.settings;
}
if (!this.settings) {
throw new Error("Settings not found");
}
return this.settings;
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "loadSettings") : null;
throw error;
}
}
/**
* Reload settings by calling loadSettings.
* @returns {Promise<Object>} The reloaded settings.
*/
async reloadSettings() {
return this.loadSettings();
}
/**
* Get the current settings.
* @returns {Object} The current settings.
* @throws Will throw an error if settings have not been loaded.
*/
getSettings() {
if (!this.settings) {
throw new Error("Settings have not been loaded");
}
return this.settings;
}
}
export default SettingsService;

View File

@@ -1,35 +1,52 @@
<!-- name, link -->
<mjml>
<mj-head>
<mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto:300,500"></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
<mj-text font-weight="300" font-size="16px" color="#616161" line-height="24px"></mj-text>
<mj-section padding="0px"></mj-section>
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px 0">
<mj-column width="100%">
<mj-text align="left" font-size="10px">Message from BlueWave Uptime Service</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column width="100%">
<mj-text>
<p>Hello {{name}}!</p>
<p>One of the admins created an account for you on the BlueWave Uptime server.</p>
<p>You can go ahead and create your account using this link.</p>
<p><a href={{link}}>{{link}}</a></p>
<p>Thank you.</p>
</mj-text>
</mj-column>
<mj-column width="100%">
<mj-divider border-width="1px" border-color="#E0E0E0"></mj-divider>
<mj-text font-size="12px">
<p>This email was sent by BlueWave Uptime.</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
<mj-head>
<mj-font
name="Roboto"
href="https://fonts.googleapis.com/css?family=Roboto:300,500"
></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
<mj-text
font-weight="300"
font-size="16px"
color="#616161"
line-height="24px"
></mj-text>
<mj-section padding="0px"></mj-section>
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px 0">
<mj-column width="100%">
<mj-text
align="left"
font-size="10px"
>Message from BlueWave Uptime Service</mj-text
>
</mj-column>
</mj-section>
<mj-section>
<mj-column width="100%">
<mj-text>
<p>Hello {{name}}!</p>
<p>
One of the admins created an account for you on the BlueWave Uptime server.
</p>
<p>You can go ahead and create your account using this link.</p>
<p><a href="{{link}}">{{link}}</a></p>
<p>Thank you.</p>
</mj-text>
</mj-column>
<mj-column width="100%">
<mj-divider
border-width="1px"
border-color="#E0E0E0"
></mj-divider>
<mj-text font-size="12px">
<p>This email was sent by BlueWave Uptime.</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>

View File

@@ -1,38 +1,66 @@
<mjml>
<mj-head>
<mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto:300,500"></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
<mj-text font-weight="300" font-size="16px" color="#616161" line-height="24px"></mj-text>
<mj-section padding="0px"></mj-section>
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px 0">
<mj-column width="100%">
<mj-text align="left" font-size="10px">Message from BlueWave Uptime Service</mj-text>
</mj-column>
<mj-column width="45%" padding-top="20px">
<mj-text align="center" font-weight="500" padding="0px" font-size="18px" color="green">No incidents this week!</mj-text>
<mj-divider border-width="2px" border-color="#616161"></mj-divider>
</mj-column>
</mj-section>
<mj-section>
<mj-column width="100%">
<mj-text>
<p>Hello {{name}}!</p>
<p>There were no incidents this week. Good job!</p>
<p><b>Current monitors:</b></p>
<p><b>Google:</b> 100% availability</p>
<p><b>Canada.ca:</b>100% availability</p>
</mj-text>
</mj-column>
<mj-column width="100%">
<mj-divider border-width="1px" border-color="#E0E0E0"></mj-divider>
<mj-text font-size="12px">
<p>This email was sent by BlueWave Uptime.</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
<mj-head>
<mj-font
name="Roboto"
href="https://fonts.googleapis.com/css?family=Roboto:300,500"
></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
<mj-text
font-weight="300"
font-size="16px"
color="#616161"
line-height="24px"
></mj-text>
<mj-section padding="0px"></mj-section>
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px 0">
<mj-column width="100%">
<mj-text
align="left"
font-size="10px"
>Message from BlueWave Uptime Service</mj-text
>
</mj-column>
<mj-column
width="45%"
padding-top="20px"
>
<mj-text
align="center"
font-weight="500"
padding="0px"
font-size="18px"
color="green"
>No incidents this week!</mj-text
>
<mj-divider
border-width="2px"
border-color="#616161"
></mj-divider>
</mj-column>
</mj-section>
<mj-section>
<mj-column width="100%">
<mj-text>
<p>Hello {{name}}!</p>
<p>There were no incidents this week. Good job!</p>
<p><b>Current monitors:</b></p>
<p><b>Google:</b> 100% availability</p>
<p><b>Canada.ca:</b>100% availability</p>
</mj-text>
</mj-column>
<mj-column width="100%">
<mj-divider
border-width="1px"
border-color="#E0E0E0"
></mj-divider>
<mj-text font-size="12px">
<p>This email was sent by BlueWave Uptime.</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>

View File

@@ -1,43 +1,56 @@
<!-- name, email, url -->
<mjml>
<mj-head>
<mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto:300,500"></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
<mj-text font-weight="300" font-size="16px" color="#616161" line-height="24px"></mj-text>
<mj-section padding="0px"></mj-section>
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px 0">
<mj-column width="100%">
<mj-text align="left" font-size="10px">
Message from BlueWave Uptime Service
</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column width="100%">
<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.
</p>
<a href="{{url}}">Reset Password</a>
<p>If you didn't request this, please ignore this email.</p>
<mj-head>
<mj-font
name="Roboto"
href="https://fonts.googleapis.com/css?family=Roboto:300,500"
></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
<mj-text
font-weight="300"
font-size="16px"
color="#616161"
line-height="24px"
></mj-text>
<mj-section padding="0px"></mj-section>
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px 0">
<mj-column width="100%">
<mj-text
align="left"
font-size="10px"
>
Message from BlueWave Uptime Service
</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column width="100%">
<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.
</p>
<a href="{{url}}">Reset Password</a>
<p>If you didn't request this, please ignore this email.</p>
<p>Thank you.</p>
</mj-text>
</mj-column>
<mj-column width="100%">
<mj-divider border-width="1px" border-color="#E0E0E0"></mj-divider>
<mj-text font-size="12px">
<p>This email was sent by BlueWave Uptime.</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
<p>Thank you.</p>
</mj-text>
</mj-column>
<mj-column width="100%">
<mj-divider
border-width="1px"
border-color="#E0E0E0"
></mj-divider>
<mj-text font-size="12px">
<p>This email was sent by BlueWave Uptime.</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>

View File

@@ -1,57 +1,73 @@
<mjml>
<mj-head>
<mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto:300,500"></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
<mj-text font-weight="300" font-size="16px" color="#616161" line-height="24px"></mj-text>
<mj-section padding="0px"></mj-section>
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px 0">
<mj-column width="100%">
<mj-text align="left" font-size="10px">
Message from BlueWave Uptime Service
</mj-text>
</mj-column>
<mj-column width="45%" padding-top="20px">
<mj-text align="center" font-weight="500" padding="0px" font-size="18px" color="red">
Google.com is down
</mj-text>
<mj-divider border-width="2px" border-color="#616161"></mj-divider>
</mj-column>
</mj-section>
<mj-section>
<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>
<b>Monitor name:</b> {{monitor}}
</p>
<p>
<b>URL:</b> {{url}}
</p>
<p>
<b>Problem:</b> {{problem}}
</p>
<p>
<b>Start date:</b> {{startDate}}
</p>
</mj-text>
</mj-column>
<mj-column width="100%">
<mj-divider border-width="1px" border-color="#E0E0E0"></mj-divider>
<mj-button background-color="#1570EF">
View incident details
</mj-button>
<mj-text font-size="12px">
<p>This email was sent by BlueWave Uptime.</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
<mj-head>
<mj-font
name="Roboto"
href="https://fonts.googleapis.com/css?family=Roboto:300,500"
></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
<mj-text
font-weight="300"
font-size="16px"
color="#616161"
line-height="24px"
></mj-text>
<mj-section padding="0px"></mj-section>
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px 0">
<mj-column width="100%">
<mj-text
align="left"
font-size="10px"
>
Message from BlueWave Uptime Service
</mj-text>
</mj-column>
<mj-column
width="45%"
padding-top="20px"
>
<mj-text
align="center"
font-weight="500"
padding="0px"
font-size="18px"
color="red"
>
Google.com is down
</mj-text>
<mj-divider
border-width="2px"
border-color="#616161"
></mj-divider>
</mj-column>
</mj-section>
<mj-section>
<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><b>Monitor name:</b> {{monitor}}</p>
<p><b>URL:</b> {{url}}</p>
<p><b>Problem:</b> {{problem}}</p>
<p><b>Start date:</b> {{startDate}}</p>
</mj-text>
</mj-column>
<mj-column width="100%">
<mj-divider
border-width="1px"
border-color="#E0E0E0"
></mj-divider>
<mj-button background-color="#1570EF"> View incident details </mj-button>
<mj-text font-size="12px">
<p>This email was sent by BlueWave Uptime.</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>

View File

@@ -1,63 +1,72 @@
<mjml>
<mj-head>
<mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto:300,500"></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
<mj-text font-weight="300" font-size="16px" color="#616161" line-height="24px"></mj-text>
<mj-section padding="0px"></mj-section>
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px 0">
<mj-column width="100%">
<mj-text align="left" font-size="10px">
Message from BlueWave Uptime Service
</mj-text>
</mj-column>
<mj-column width="45%" padding-top="20px">
<mj-text align="center" font-weight="500" padding="0px" font-size="18px" color="green">
{{monitor}} is up
</mj-text>
<mj-divider border-width="2px" border-color="#616161"></mj-divider>
</mj-column>
</mj-section>
<mj-section>
<mj-column width="100%">
<mj-text>
<p>Hello {{name}}!</p>
<p>
Your latest incident is resolved and your monitored service is
up again.
</p>
<p>
<b>Monitor name:</b> {{monitor}}
</p>
<p>
<b>URL:</b> {{url}}
</p>
<p>
<b>Problem:</b> {{problem}}
</p>
<p>
<b>Start date:</b> {{startDate}}
</p>
<p>
<b>Resolved date:</b> {{resolvedDate}}
</p>
<p>
<b>Duration:</b>{{duration}}
</p>
</mj-text>
</mj-column>
<mj-column width="100%">
<mj-divider border-width="1px" border-color="#E0E0E0"></mj-divider>
<mj-button background-color="#1570EF">
View incident details
</mj-button>
<mj-text font-size="12px">
<p>This email was sent by BlueWave Uptime.</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
<mj-head>
<mj-font
name="Roboto"
href="https://fonts.googleapis.com/css?family=Roboto:300,500"
></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
<mj-text
font-weight="300"
font-size="16px"
color="#616161"
line-height="24px"
></mj-text>
<mj-section padding="0px"></mj-section>
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px 0">
<mj-column width="100%">
<mj-text
align="left"
font-size="10px"
>
Message from BlueWave Uptime Service
</mj-text>
</mj-column>
<mj-column
width="45%"
padding-top="20px"
>
<mj-text
align="center"
font-weight="500"
padding="0px"
font-size="18px"
color="green"
>
{{monitor}} is up
</mj-text>
<mj-divider
border-width="2px"
border-color="#616161"
></mj-divider>
</mj-column>
</mj-section>
<mj-section>
<mj-column width="100%">
<mj-text>
<p>Hello {{name}}!</p>
<p>Your latest incident is resolved and your monitored service is up again.</p>
<p><b>Monitor name:</b> {{monitor}}</p>
<p><b>URL:</b> {{url}}</p>
<p><b>Problem:</b> {{problem}}</p>
<p><b>Start date:</b> {{startDate}}</p>
<p><b>Resolved date:</b> {{resolvedDate}}</p>
<p><b>Duration:</b>{{duration}}</p>
</mj-text>
</mj-column>
<mj-column width="100%">
<mj-divider
border-width="1px"
border-color="#E0E0E0"
></mj-divider>
<mj-button background-color="#1570EF"> View incident details </mj-button>
<mj-text font-size="12px">
<p>This email was sent by BlueWave Uptime.</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>

View File

@@ -1,44 +1,57 @@
<!-- name -->
<mjml>
<mj-head>
<mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto:300,500"></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
<mj-text font-weight="300" font-size="16px" color="#616161" line-height="24px"></mj-text>
<mj-section padding="0px"></mj-section>
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px 0">
<mj-column width="100%">
<mj-text align="left" font-size="10px">
Message from BlueWave Uptime Service
</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column width="100%">
<mj-text>
<p>Hello {{name}}!</p>
<p>
Thank you for trying out BlueWave Uptime! We developed it with
great care to meet our own needs, and we're excited to share it
with you.
</p>
<p>
BlueWave Uptime 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>
</mj-column>
<mj-column width="100%">
<mj-divider border-width="1px" border-color="#E0E0E0"></mj-divider>
<mj-text font-size="12px">
<p>This email was sent by BlueWave Uptime.</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
<mj-head>
<mj-font
name="Roboto"
href="https://fonts.googleapis.com/css?family=Roboto:300,500"
></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
<mj-text
font-weight="300"
font-size="16px"
color="#616161"
line-height="24px"
></mj-text>
<mj-section padding="0px"></mj-section>
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px 0">
<mj-column width="100%">
<mj-text
align="left"
font-size="10px"
>
Message from BlueWave Uptime Service
</mj-text>
</mj-column>
</mj-section>
<mj-section>
<mj-column width="100%">
<mj-text>
<p>Hello {{name}}!</p>
<p>
Thank you for trying out BlueWave Uptime! We developed it with great care to
meet our own needs, and we're excited to share it with you.
</p>
<p>
BlueWave Uptime 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>
</mj-column>
<mj-column width="100%">
<mj-divider
border-width="1px"
border-color="#E0E0E0"
></mj-divider>
<mj-text font-size="12px">
<p>This email was sent by BlueWave Uptime.</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>

View File

@@ -1,375 +1,373 @@
import {
createCheck,
getChecks,
getTeamChecks,
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
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";
describe("Check Controller - createCheck", () => {
let req, res, next, handleError;
beforeEach(() => {
req = {
params: {},
body: {},
db: {
createCheck: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
handleError = sinon.stub();
});
let req, res, next, handleError;
beforeEach(() => {
req = {
params: {},
body: {},
db: {
createCheck: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
handleError = sinon.stub();
});
afterEach(() => {
sinon.restore(); // Restore the original methods after each test
});
afterEach(() => {
sinon.restore(); // Restore the original methods after each test
});
it("should reject with a validation if params are invalid", async () => {
await createCheck(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with a validation if params are invalid", async () => {
await createCheck(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with a validation error if body is invalid", async () => {
req.params = {
monitorId: "monitorId",
};
await createCheck(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with a validation error if body is invalid", async () => {
req.params = {
monitorId: "monitorId",
};
await createCheck(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should call next with error if data retrieval fails", async () => {
req.params = {
monitorId: "monitorId",
};
req.body = {
monitorId: "monitorId",
status: true,
responseTime: 100,
statusCode: 200,
message: "message",
};
req.db.createCheck.rejects(new Error("error"));
await createCheck(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
});
it("should call next with error if data retrieval fails", async () => {
req.params = {
monitorId: "monitorId",
};
req.body = {
monitorId: "monitorId",
status: true,
responseTime: 100,
statusCode: 200,
message: "message",
};
req.db.createCheck.rejects(new Error("error"));
await createCheck(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
});
it("should return a success message if check is created", async () => {
req.params = {
monitorId: "monitorId",
};
req.db.createCheck.resolves({ id: "123" });
req.body = {
monitorId: "monitorId",
status: true,
responseTime: 100,
statusCode: 200,
message: "message",
};
await createCheck(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
res.json.calledWith({
success: true,
msg: successMessages.CHECK_CREATE,
data: { id: "123" },
})
).to.be.true;
expect(next.notCalled).to.be.true;
});
it("should return a success message if check is created", async () => {
req.params = {
monitorId: "monitorId",
};
req.db.createCheck.resolves({ id: "123" });
req.body = {
monitorId: "monitorId",
status: true,
responseTime: 100,
statusCode: 200,
message: "message",
};
await createCheck(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
res.json.calledWith({
success: true,
msg: successMessages.CHECK_CREATE,
data: { id: "123" },
})
).to.be.true;
expect(next.notCalled).to.be.true;
});
});
describe("Check Controller - getChecks", () => {
let req, res, next;
beforeEach(() => {
req = {
params: {},
query: {},
db: {
getChecks: sinon.stub(),
getChecksCount: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
let req, res, next;
beforeEach(() => {
req = {
params: {},
query: {},
db: {
getChecks: sinon.stub(),
getChecksCount: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
afterEach(() => {
sinon.restore();
});
it("should reject with a validation error if params are invalid", async () => {
await getChecks(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with a validation error if params are invalid", async () => {
await getChecks(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should return a success message if checks are found", async () => {
req.params = {
monitorId: "monitorId",
};
req.db.getChecks.resolves([{ id: "123" }]);
req.db.getChecksCount.resolves(1);
await getChecks(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
res.json.calledWith({
success: true,
msg: successMessages.CHECK_GET,
data: { checksCount: 1, checks: [{ id: "123" }] },
})
).to.be.true;
expect(next.notCalled).to.be.true;
});
it("should return a success message if checks are found", async () => {
req.params = {
monitorId: "monitorId",
};
req.db.getChecks.resolves([{ id: "123" }]);
req.db.getChecksCount.resolves(1);
await getChecks(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
res.json.calledWith({
success: true,
msg: successMessages.CHECK_GET,
data: { checksCount: 1, checks: [{ id: "123" }] },
})
).to.be.true;
expect(next.notCalled).to.be.true;
});
it("should call next with error if data retrieval fails", async () => {
req.params = {
monitorId: "monitorId",
};
req.db.getChecks.rejects(new Error("error"));
await getChecks(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
});
it("should call next with error if data retrieval fails", async () => {
req.params = {
monitorId: "monitorId",
};
req.db.getChecks.rejects(new Error("error"));
await getChecks(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
});
});
describe("Check Controller - getTeamChecks", () => {
let req, res, next;
beforeEach(() => {
req = {
params: {},
query: {},
db: {
getTeamChecks: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
let req, res, next;
beforeEach(() => {
req = {
params: {},
query: {},
db: {
getTeamChecks: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
afterEach(() => {
sinon.restore();
});
it("should reject with a validation error if params are invalid", async () => {
await getTeamChecks(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with a validation error if params are invalid", async () => {
await getTeamChecks(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should return 200 and check data on successful validation and data retrieval", async () => {
req.params = { teamId: "1" };
const checkData = [{ id: 1, name: "Check 1" }];
req.db.getTeamChecks.resolves(checkData);
it("should return 200 and check data on successful validation and data retrieval", async () => {
req.params = { teamId: "1" };
const checkData = [{ id: 1, name: "Check 1" }];
req.db.getTeamChecks.resolves(checkData);
await getTeamChecks(req, res, next);
expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true;
expect(res.status.calledOnceWith(200)).to.be.true;
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.CHECK_GET,
data: checkData,
})
).to.be.true;
});
await getTeamChecks(req, res, next);
expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true;
expect(res.status.calledOnceWith(200)).to.be.true;
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.CHECK_GET,
data: checkData,
})
).to.be.true;
});
it("should call next with error if data retrieval fails", async () => {
req.params = { teamId: "1" };
req.db.getTeamChecks.rejects(new Error("Retrieval Error"));
await getTeamChecks(req, res, next);
expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true;
expect(next.firstCall.args[0]).to.be.an("error");
expect(res.status.notCalled).to.be.true;
expect(res.json.notCalled).to.be.true;
});
it("should call next with error if data retrieval fails", async () => {
req.params = { teamId: "1" };
req.db.getTeamChecks.rejects(new Error("Retrieval Error"));
await getTeamChecks(req, res, next);
expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true;
expect(next.firstCall.args[0]).to.be.an("error");
expect(res.status.notCalled).to.be.true;
expect(res.json.notCalled).to.be.true;
});
});
describe("Check Controller - deleteChecks", () => {
let req, res, next;
beforeEach(() => {
req = {
params: {},
db: {
deleteChecks: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
let req, res, next;
beforeEach(() => {
req = {
params: {},
db: {
deleteChecks: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if param validation fails", async () => {
await deleteChecks(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if param validation fails", async () => {
await deleteChecks(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should call next with error if data retrieval fails", async () => {
req.params = { monitorId: "1" };
req.db.deleteChecks.rejects(new Error("Deletion Error"));
await deleteChecks(req, res, next);
expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true;
expect(next.firstCall.args[0]).to.be.an("error");
expect(res.status.notCalled).to.be.true;
expect(res.json.notCalled).to.be.true;
});
it("should call next with error if data retrieval fails", async () => {
req.params = { monitorId: "1" };
req.db.deleteChecks.rejects(new Error("Deletion Error"));
await deleteChecks(req, res, next);
expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true;
expect(next.firstCall.args[0]).to.be.an("error");
expect(res.status.notCalled).to.be.true;
expect(res.json.notCalled).to.be.true;
});
it("should delete checks successfully", async () => {
req.params = { monitorId: "123" };
req.db.deleteChecks.resolves(1);
await deleteChecks(req, res, next);
expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true;
expect(res.status.calledOnceWith(200)).to.be.true;
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.CHECK_DELETE,
data: { deletedCount: 1 },
})
).to.be.true;
});
it("should delete checks successfully", async () => {
req.params = { monitorId: "123" };
req.db.deleteChecks.resolves(1);
await deleteChecks(req, res, next);
expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true;
expect(res.status.calledOnceWith(200)).to.be.true;
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.CHECK_DELETE,
data: { deletedCount: 1 },
})
).to.be.true;
});
});
describe("Check Controller - deleteChecksByTeamId", () => {
let req, res, next;
beforeEach(() => {
req = {
params: {},
db: {
deleteChecksByTeamId: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
let req, res, next;
beforeEach(() => {
req = {
params: {},
db: {
deleteChecksByTeamId: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if param validation fails", async () => {
await deleteChecksByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if param validation fails", async () => {
await deleteChecksByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should call next with error if data retrieval fails", async () => {
req.params = { teamId: "1" };
req.db.deleteChecksByTeamId.rejects(new Error("Deletion Error"));
await deleteChecksByTeamId(req, res, next);
expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be
.true;
expect(next.firstCall.args[0]).to.be.an("error");
expect(res.status.notCalled).to.be.true;
expect(res.json.notCalled).to.be.true;
});
it("should call next with error if data retrieval fails", async () => {
req.params = { teamId: "1" };
req.db.deleteChecksByTeamId.rejects(new Error("Deletion Error"));
await deleteChecksByTeamId(req, res, next);
expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be.true;
expect(next.firstCall.args[0]).to.be.an("error");
expect(res.status.notCalled).to.be.true;
expect(res.json.notCalled).to.be.true;
});
it("should delete checks successfully", async () => {
req.params = { teamId: "123" };
req.db.deleteChecksByTeamId.resolves(1);
await deleteChecksByTeamId(req, res, next);
expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be
.true;
expect(res.status.calledOnceWith(200)).to.be.true;
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.CHECK_DELETE,
data: { deletedCount: 1 },
})
).to.be.true;
});
it("should delete checks successfully", async () => {
req.params = { teamId: "123" };
req.db.deleteChecksByTeamId.resolves(1);
await deleteChecksByTeamId(req, res, next);
expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be.true;
expect(res.status.calledOnceWith(200)).to.be.true;
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.CHECK_DELETE,
data: { deletedCount: 1 },
})
).to.be.true;
});
});
describe("Check Controller - updateCheckTTL", () => {
let stub, req, res, next;
beforeEach(() => {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
let stub, req, res, next;
beforeEach(() => {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
req = {
body: {},
headers: { authorization: "Bearer token" },
settingsService: {
getSettings: sinon.stub().returns({ jwtSecret: "my_secret" }),
},
db: {
updateChecksTTL: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
req = {
body: {},
headers: { authorization: "Bearer token" },
settingsService: {
getSettings: sinon.stub().returns({ jwtSecret: "my_secret" }),
},
db: {
updateChecksTTL: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
stub.restore();
});
afterEach(() => {
sinon.restore();
stub.restore();
});
it("should reject if body validation fails", async () => {
await updateChecksTTL(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject if body validation fails", async () => {
await updateChecksTTL(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should throw a JwtError if verification fails", async () => {
stub.restore();
req.body = {
ttl: 1,
};
await updateChecksTTL(req, res, next);
expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
});
it("should throw a JwtError if verification fails", async () => {
stub.restore();
req.body = {
ttl: 1,
};
await updateChecksTTL(req, res, next);
expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
});
it("should call next with error if data retrieval fails", async () => {
req.body = {
ttl: 1,
};
req.db.updateChecksTTL.rejects(new Error("Update Error"));
await updateChecksTTL(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
});
it("should call next with error if data retrieval fails", async () => {
req.body = {
ttl: 1,
};
req.db.updateChecksTTL.rejects(new Error("Update Error"));
await updateChecksTTL(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
});
it("should update TTL successfully", async () => {
req.body = {
ttl: 1,
};
req.db.updateChecksTTL.resolves();
await updateChecksTTL(req, res, next);
expect(req.db.updateChecksTTL.calledOnceWith("123", 1 * 86400)).to.be.true;
expect(res.status.calledOnceWith(200)).to.be.true;
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.CHECK_UPDATE_TTL,
})
).to.be.true;
});
it("should update TTL successfully", async () => {
req.body = {
ttl: 1,
};
req.db.updateChecksTTL.resolves();
await updateChecksTTL(req, res, next);
expect(req.db.updateChecksTTL.calledOnceWith("123", 1 * 86400)).to.be.true;
expect(res.status.calledOnceWith(200)).to.be.true;
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.CHECK_UPDATE_TTL,
})
).to.be.true;
});
});

View File

@@ -1,203 +1,203 @@
import {
issueInvitation,
inviteVerifyController,
issueInvitation,
inviteVerifyController,
} from "../../controllers/inviteController.js";
import jwt from "jsonwebtoken";
import sinon from "sinon";
import joi from "joi";
describe("inviteController - issueInvitation", () => {
let req, res, next, stub;
beforeEach(() => {
req = {
headers: { authorization: "Bearer token" },
body: {
email: "test@test.com",
role: ["admin"],
teamId: "123",
},
db: { requestInviteToken: sinon.stub() },
settingsService: { getSettings: sinon.stub() },
emailService: { buildAndSendEmail: sinon.stub() },
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
let req, res, next, stub;
beforeEach(() => {
req = {
headers: { authorization: "Bearer token" },
body: {
email: "test@test.com",
role: ["admin"],
teamId: "123",
},
db: { requestInviteToken: sinon.stub() },
settingsService: { getSettings: sinon.stub() },
emailService: { buildAndSendEmail: sinon.stub() },
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if role validation fails", async () => {
stub = sinon.stub(jwt, "decode").callsFake(() => {
return { role: ["bad_role"], firstname: "first_name", teamId: "1" };
});
await issueInvitation(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0]).to.be.instanceOf(joi.ValidationError);
expect(next.firstCall.args[0].status).to.equal(422);
stub.restore();
});
it("should reject with an error if role validation fails", async () => {
stub = sinon.stub(jwt, "decode").callsFake(() => {
return { role: ["bad_role"], firstname: "first_name", teamId: "1" };
});
await issueInvitation(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0]).to.be.instanceOf(joi.ValidationError);
expect(next.firstCall.args[0].status).to.equal(422);
stub.restore();
});
it("should reject with an error if body validation fails", async () => {
stub = sinon.stub(jwt, "decode").callsFake(() => {
return { role: ["admin"], firstname: "first_name", teamId: "1" };
});
req.body = {};
await issueInvitation(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
stub.restore();
});
it("should reject with an error if body validation fails", async () => {
stub = sinon.stub(jwt, "decode").callsFake(() => {
return { role: ["admin"], firstname: "first_name", teamId: "1" };
});
req.body = {};
await issueInvitation(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
stub.restore();
});
it("should reject with an error if DB operations fail", async () => {
stub = sinon.stub(jwt, "decode").callsFake(() => {
return { role: ["admin"], firstname: "first_name", teamId: "1" };
});
req.db.requestInviteToken.throws(new Error("DB error"));
await issueInvitation(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
stub.restore();
});
it("should reject with an error if DB operations fail", async () => {
stub = sinon.stub(jwt, "decode").callsFake(() => {
return { role: ["admin"], firstname: "first_name", teamId: "1" };
});
req.db.requestInviteToken.throws(new Error("DB error"));
await issueInvitation(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
stub.restore();
});
it("should send an invite successfully", async () => {
const token = "token";
const decodedToken = {
role: "admin",
firstname: "John",
teamId: "team123",
};
const inviteToken = { token: "inviteToken" };
const clientHost = "http://localhost";
it("should send an invite successfully", async () => {
const token = "token";
const decodedToken = {
role: "admin",
firstname: "John",
teamId: "team123",
};
const inviteToken = { token: "inviteToken" };
const clientHost = "http://localhost";
stub = sinon.stub(jwt, "decode").callsFake(() => {
return decodedToken;
});
req.db.requestInviteToken.resolves(inviteToken);
req.settingsService.getSettings.returns({ clientHost });
req.emailService.buildAndSendEmail.resolves();
await issueInvitation(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
res.json.calledWith({
success: true,
msg: "Invite sent",
data: inviteToken,
})
).to.be.true;
stub.restore();
});
stub = sinon.stub(jwt, "decode").callsFake(() => {
return decodedToken;
});
req.db.requestInviteToken.resolves(inviteToken);
req.settingsService.getSettings.returns({ clientHost });
req.emailService.buildAndSendEmail.resolves();
await issueInvitation(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
res.json.calledWith({
success: true,
msg: "Invite sent",
data: inviteToken,
})
).to.be.true;
stub.restore();
});
it("should send an email successfully", async () => {
const token = "token";
const decodedToken = {
role: "admin",
firstname: "John",
teamId: "team123",
};
const inviteToken = { token: "inviteToken" };
const clientHost = "http://localhost";
it("should send an email successfully", async () => {
const token = "token";
const decodedToken = {
role: "admin",
firstname: "John",
teamId: "team123",
};
const inviteToken = { token: "inviteToken" };
const clientHost = "http://localhost";
stub = sinon.stub(jwt, "decode").callsFake(() => {
return decodedToken;
});
req.db.requestInviteToken.resolves(inviteToken);
req.settingsService.getSettings.returns({ clientHost });
req.emailService.buildAndSendEmail.resolves();
stub = sinon.stub(jwt, "decode").callsFake(() => {
return decodedToken;
});
req.db.requestInviteToken.resolves(inviteToken);
req.settingsService.getSettings.returns({ clientHost });
req.emailService.buildAndSendEmail.resolves();
await issueInvitation(req, res, next);
expect(req.emailService.buildAndSendEmail.calledOnce).to.be.true;
expect(
req.emailService.buildAndSendEmail.calledWith(
"employeeActivationTemplate",
{
name: "John",
link: "http://localhost/register/inviteToken",
},
"test@test.com",
"Welcome to Uptime Monitor"
)
).to.be.true;
stub.restore();
});
await issueInvitation(req, res, next);
expect(req.emailService.buildAndSendEmail.calledOnce).to.be.true;
expect(
req.emailService.buildAndSendEmail.calledWith(
"employeeActivationTemplate",
{
name: "John",
link: "http://localhost/register/inviteToken",
},
"test@test.com",
"Welcome to Uptime Monitor"
)
).to.be.true;
stub.restore();
});
it("should continue executing if sending an email fails", async () => {
const token = "token";
req.emailService.buildAndSendEmail.rejects(new Error("Email error"));
const decodedToken = {
role: "admin",
firstname: "John",
teamId: "team123",
};
const inviteToken = { token: "inviteToken" };
const clientHost = "http://localhost";
it("should continue executing if sending an email fails", async () => {
const token = "token";
req.emailService.buildAndSendEmail.rejects(new Error("Email error"));
const decodedToken = {
role: "admin",
firstname: "John",
teamId: "team123",
};
const inviteToken = { token: "inviteToken" };
const clientHost = "http://localhost";
stub = sinon.stub(jwt, "decode").callsFake(() => {
return decodedToken;
});
req.db.requestInviteToken.resolves(inviteToken);
req.settingsService.getSettings.returns({ clientHost });
await issueInvitation(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
res.json.calledWith({
success: true,
msg: "Invite sent",
data: inviteToken,
})
).to.be.true;
stub.restore();
});
stub = sinon.stub(jwt, "decode").callsFake(() => {
return decodedToken;
});
req.db.requestInviteToken.resolves(inviteToken);
req.settingsService.getSettings.returns({ clientHost });
await issueInvitation(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
res.json.calledWith({
success: true,
msg: "Invite sent",
data: inviteToken,
})
).to.be.true;
stub.restore();
});
});
describe("inviteController - inviteVerifyController", () => {
let req, res, next;
beforeEach(() => {
req = {
body: { token: "token" },
db: {
getInviteToken: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
let req, res, next;
beforeEach(() => {
req = {
body: { token: "token" },
db: {
getInviteToken: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if body validation fails", async () => {
req.body = {};
await inviteVerifyController(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if body validation fails", async () => {
req.body = {};
await inviteVerifyController(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if DB operations fail", async () => {
req.db.getInviteToken.throws(new Error("DB error"));
await inviteVerifyController(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should reject with an error if DB operations fail", async () => {
req.db.getInviteToken.throws(new Error("DB error"));
await inviteVerifyController(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return 200 and invite data when validation and invite retrieval are successful", async () => {
req.db.getInviteToken.resolves({ invite: "data" });
await inviteVerifyController(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
res.json.calledWith({
status: "success",
msg: "Invite verified",
data: { invite: "data" },
})
).to.be.true;
expect(next.called).to.be.false;
});
it("should return 200 and invite data when validation and invite retrieval are successful", async () => {
req.db.getInviteToken.resolves({ invite: "data" });
await inviteVerifyController(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
res.json.calledWith({
status: "success",
msg: "Invite verified",
data: { invite: "data" },
})
).to.be.true;
expect(next.called).to.be.false;
});
});

View File

@@ -1,10 +1,10 @@
import {
createMaintenanceWindows,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindow,
editMaintenanceWindow,
createMaintenanceWindows,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindow,
editMaintenanceWindow,
} from "../../controllers/maintenanceWindowController.js";
import jwt from "jsonwebtoken";
@@ -12,405 +12,399 @@ import { successMessages } from "../../utils/messages.js";
import sinon from "sinon";
describe("maintenanceWindowController - createMaintenanceWindows", () => {
let req, res, next, stub;
beforeEach(() => {
req = {
body: {
monitors: ["66ff52e7c5911c61698ac724"],
name: "window",
active: true,
start: "2024-10-11T05:27:13.747Z",
end: "2024-10-11T05:27:14.747Z",
repeat: "123",
},
headers: {
authorization: "Bearer token",
},
settingsService: {
getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
},
db: {
createMaintenanceWindow: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
let req, res, next, stub;
beforeEach(() => {
req = {
body: {
monitors: ["66ff52e7c5911c61698ac724"],
name: "window",
active: true,
start: "2024-10-11T05:27:13.747Z",
end: "2024-10-11T05:27:14.747Z",
repeat: "123",
},
headers: {
authorization: "Bearer token",
},
settingsService: {
getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
},
db: {
createMaintenanceWindow: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if body validation fails", async () => {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
req.body = {};
await createMaintenanceWindows(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
stub.restore();
});
it("should reject with an error if body validation fails", async () => {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
req.body = {};
await createMaintenanceWindows(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
stub.restore();
});
it("should reject with an error if jwt.verify fails", async () => {
stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError());
await createMaintenanceWindows(req, res, next);
expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
stub.restore();
});
it("should reject with an error if jwt.verify fails", async () => {
stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError());
await createMaintenanceWindows(req, res, next);
expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
stub.restore();
});
it("should reject with an error DB operations fail", async () => {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
req.db.createMaintenanceWindow.throws(new Error("DB error"));
await createMaintenanceWindows(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
stub.restore();
});
it("should return success message if all operations are successful", async () => {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
await createMaintenanceWindows(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(201);
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_CREATE,
})
).to.be.true;
stub.restore();
});
it("should return success message if all operations are successful with active set to undefined", async () => {
req.body.active = undefined;
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
await createMaintenanceWindows(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(201);
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_CREATE,
})
).to.be.true;
stub.restore();
});
it("should reject with an error DB operations fail", async () => {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
req.db.createMaintenanceWindow.throws(new Error("DB error"));
await createMaintenanceWindows(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
stub.restore();
});
it("should return success message if all operations are successful", async () => {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
await createMaintenanceWindows(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(201);
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_CREATE,
})
).to.be.true;
stub.restore();
});
it("should return success message if all operations are successful with active set to undefined", async () => {
req.body.active = undefined;
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
await createMaintenanceWindows(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(201);
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_CREATE,
})
).to.be.true;
stub.restore();
});
});
describe("maintenanceWindowController - getMaintenanceWindowById", () => {
let req, res, next;
beforeEach(() => {
req = {
body: {},
params: {
id: "123",
},
headers: {
authorization: "Bearer token",
},
settingsService: {
getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
},
db: {
getMaintenanceWindowById: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
let req, res, next;
beforeEach(() => {
req = {
body: {},
params: {
id: "123",
},
headers: {
authorization: "Bearer token",
},
settingsService: {
getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
},
db: {
getMaintenanceWindowById: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
it("should reject if param validation fails", async () => {
req.params = {};
await getMaintenanceWindowById(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject if param validation fails", async () => {
req.params = {};
await getMaintenanceWindowById(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject if DB operations fail", async () => {
req.db.getMaintenanceWindowById.throws(new Error("DB error"));
await getMaintenanceWindowById(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message with data if all operations are successful", async () => {
req.db.getMaintenanceWindowById.returns({ id: "123" });
await getMaintenanceWindowById(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID,
data: { id: "123" },
})
).to.be.true;
});
it("should reject if DB operations fail", async () => {
req.db.getMaintenanceWindowById.throws(new Error("DB error"));
await getMaintenanceWindowById(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message with data if all operations are successful", async () => {
req.db.getMaintenanceWindowById.returns({ id: "123" });
await getMaintenanceWindowById(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID,
data: { id: "123" },
})
).to.be.true;
});
});
describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", () => {
let req, res, next, stub;
beforeEach(() => {
req = {
body: {},
params: {},
query: {},
headers: {
authorization: "Bearer token",
},
settingsService: {
getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
},
db: {
getMaintenanceWindowsByTeamId: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
it("should reject if query validation fails", async () => {
req.query = {
invalid: 1,
};
await getMaintenanceWindowsByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject if jwt.verify fails", async () => {
stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError());
await getMaintenanceWindowsByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
stub.restore();
});
let req, res, next, stub;
beforeEach(() => {
req = {
body: {},
params: {},
query: {},
headers: {
authorization: "Bearer token",
},
settingsService: {
getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
},
db: {
getMaintenanceWindowsByTeamId: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
it("should reject if query validation fails", async () => {
req.query = {
invalid: 1,
};
await getMaintenanceWindowsByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject if jwt.verify fails", async () => {
stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError());
await getMaintenanceWindowsByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
stub.restore();
});
it("should reject with an error if DB operations fail", async () => {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
req.db.getMaintenanceWindowsByTeamId.throws(new Error("DB error"));
await getMaintenanceWindowsByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
stub.restore();
});
it("should reject with an error if DB operations fail", async () => {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
req.db.getMaintenanceWindowsByTeamId.throws(new Error("DB error"));
await getMaintenanceWindowsByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
stub.restore();
});
it("should return success message with data if all operations are successful", async () => {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
req.db.getMaintenanceWindowsByTeamId.returns([{ id: "123" }]);
await getMaintenanceWindowsByTeamId(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM,
data: [{ id: jwt.verify().teamId }],
})
).to.be.true;
stub.restore();
});
it("should return success message with data if all operations are successful", async () => {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
req.db.getMaintenanceWindowsByTeamId.returns([{ id: "123" }]);
await getMaintenanceWindowsByTeamId(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM,
data: [{ id: jwt.verify().teamId }],
})
).to.be.true;
stub.restore();
});
});
describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", () => {
let req, res, next;
beforeEach(() => {
req = {
body: {},
params: {
monitorId: "123",
},
query: {},
headers: {
authorization: "Bearer token",
},
settingsService: {
getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
},
db: {
getMaintenanceWindowsByMonitorId: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
let req, res, next;
beforeEach(() => {
req = {
body: {},
params: {
monitorId: "123",
},
query: {},
headers: {
authorization: "Bearer token",
},
settingsService: {
getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
},
db: {
getMaintenanceWindowsByMonitorId: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
afterEach(() => {
sinon.restore();
});
it("should reject if param validation fails", async () => {
req.params = {};
await getMaintenanceWindowsByMonitorId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject if param validation fails", async () => {
req.params = {};
await getMaintenanceWindowsByMonitorId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if DB operations fail", async () => {
req.db.getMaintenanceWindowsByMonitorId.throws(new Error("DB error"));
await getMaintenanceWindowsByMonitorId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should reject with an error if DB operations fail", async () => {
req.db.getMaintenanceWindowsByMonitorId.throws(new Error("DB error"));
await getMaintenanceWindowsByMonitorId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message with data if all operations are successful", async () => {
const data = [{ monitorId: "123" }];
req.db.getMaintenanceWindowsByMonitorId.returns(data);
await getMaintenanceWindowsByMonitorId(req, res, next);
expect(
req.db.getMaintenanceWindowsByMonitorId.calledOnceWith(
req.params.monitorId
)
);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_MONITOR,
data: data,
})
).to.be.true;
});
it("should return success message with data if all operations are successful", async () => {
const data = [{ monitorId: "123" }];
req.db.getMaintenanceWindowsByMonitorId.returns(data);
await getMaintenanceWindowsByMonitorId(req, res, next);
expect(req.db.getMaintenanceWindowsByMonitorId.calledOnceWith(req.params.monitorId));
expect(res.status.firstCall.args[0]).to.equal(200);
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_MONITOR,
data: data,
})
).to.be.true;
});
});
describe("maintenanceWindowController - deleteMaintenanceWindow", () => {
let req, res, next;
beforeEach(() => {
req = {
body: {},
params: {
id: "123",
},
query: {},
headers: {
authorization: "Bearer token",
},
settingsService: {
getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
},
db: {
deleteMaintenanceWindowById: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
let req, res, next;
beforeEach(() => {
req = {
body: {},
params: {
id: "123",
},
query: {},
headers: {
authorization: "Bearer token",
},
settingsService: {
getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
},
db: {
deleteMaintenanceWindowById: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should reject if param validation fails", async () => {
req.params = {};
await deleteMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject if param validation fails", async () => {
req.params = {};
await deleteMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if DB operations fail", async () => {
req.db.deleteMaintenanceWindowById.throws(new Error("DB error"));
await deleteMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should reject with an error if DB operations fail", async () => {
req.db.deleteMaintenanceWindowById.throws(new Error("DB error"));
await deleteMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message if all operations are successful", async () => {
await deleteMaintenanceWindow(req, res, next);
expect(req.db.deleteMaintenanceWindowById.calledOnceWith(req.params.id));
expect(res.status.firstCall.args[0]).to.equal(200);
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_DELETE,
})
).to.be.true;
});
it("should return success message if all operations are successful", async () => {
await deleteMaintenanceWindow(req, res, next);
expect(req.db.deleteMaintenanceWindowById.calledOnceWith(req.params.id));
expect(res.status.firstCall.args[0]).to.equal(200);
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_DELETE,
})
).to.be.true;
});
});
describe("maintenanceWindowController - editMaintenanceWindow", () => {
let req, res, next;
beforeEach(() => {
req = {
body: {
active: true,
name: "test",
},
params: {
id: "123",
},
query: {},
headers: {
authorization: "Bearer token",
},
settingsService: {
getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
},
db: {
editMaintenanceWindowById: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
let req, res, next;
beforeEach(() => {
req = {
body: {
active: true,
name: "test",
},
params: {
id: "123",
},
query: {},
headers: {
authorization: "Bearer token",
},
settingsService: {
getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
},
db: {
editMaintenanceWindowById: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
afterEach(() => {
sinon.restore();
});
it("should reject if param validation fails", async () => {
req.params = {};
await editMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject if param validation fails", async () => {
req.params = {};
await editMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject if body validation fails", async () => {
req.body = { invalid: 1 };
await editMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject if body validation fails", async () => {
req.body = { invalid: 1 };
await editMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if DB operations fail", async () => {
req.db.editMaintenanceWindowById.throws(new Error("DB error"));
await editMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should reject with an error if DB operations fail", async () => {
req.db.editMaintenanceWindowById.throws(new Error("DB error"));
await editMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message with data if all operations are successful", async () => {
const data = { id: "123" };
req.db.editMaintenanceWindowById.returns(data);
it("should return success message with data if all operations are successful", async () => {
const data = { id: "123" };
req.db.editMaintenanceWindowById.returns(data);
await editMaintenanceWindow(req, res, next);
expect(
req.db.editMaintenanceWindowById.calledOnceWith(req.params.id, req.body)
);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_EDIT,
data: data,
})
).to.be.true;
});
await editMaintenanceWindow(req, res, next);
expect(req.db.editMaintenanceWindowById.calledOnceWith(req.params.id, req.body));
expect(res.status.firstCall.args[0]).to.equal(200);
expect(
res.json.calledOnceWith({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_EDIT,
data: data,
})
).to.be.true;
});
});

View File

@@ -1,166 +1,166 @@
import { afterEach } from "node:test";
import {
getMetrics,
getJobs,
addJob,
obliterateQueue,
getMetrics,
getJobs,
addJob,
obliterateQueue,
} from "../../controllers/queueController.js";
import { successMessages } from "../../utils/messages.js";
import sinon from "sinon";
describe("Queue Controller - getMetrics", () => {
let req, res, next;
beforeEach(() => {
req = {
headers: {},
params: {},
body: {},
db: {},
jobQueue: {
getMetrics: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should throw an error if getMetrics throws an error", async () => {
req.jobQueue.getMetrics.throws(new Error("getMetrics error"));
await getMetrics(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("getMetrics error");
});
let req, res, next;
beforeEach(() => {
req = {
headers: {},
params: {},
body: {},
db: {},
jobQueue: {
getMetrics: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should throw an error if getMetrics throws an error", async () => {
req.jobQueue.getMetrics.throws(new Error("getMetrics error"));
await getMetrics(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("getMetrics error");
});
it("should return a success message and data if getMetrics is successful", async () => {
const data = { data: "metrics" };
req.jobQueue.getMetrics.returns(data);
await getMetrics(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(res.json.firstCall.args[0]).to.deep.equal({
success: true,
msg: successMessages.QUEUE_GET_METRICS,
data,
});
});
it("should return a success message and data if getMetrics is successful", async () => {
const data = { data: "metrics" };
req.jobQueue.getMetrics.returns(data);
await getMetrics(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(res.json.firstCall.args[0]).to.deep.equal({
success: true,
msg: successMessages.QUEUE_GET_METRICS,
data,
});
});
});
describe("Queue Controller - getJobs", () => {
let req, res, next;
beforeEach(() => {
req = {
headers: {},
params: {},
body: {},
db: {},
jobQueue: {
getJobStats: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if getJobs throws an error", async () => {
req.jobQueue.getJobStats.throws(new Error("getJobs error"));
await getJobs(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("getJobs error");
});
it("should return a success message and data if getJobs is successful", async () => {
const data = { data: "jobs" };
req.jobQueue.getJobStats.returns(data);
await getJobs(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(res.json.firstCall.args[0]).to.deep.equal({
success: true,
msg: successMessages.QUEUE_GET_METRICS,
data,
});
});
let req, res, next;
beforeEach(() => {
req = {
headers: {},
params: {},
body: {},
db: {},
jobQueue: {
getJobStats: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if getJobs throws an error", async () => {
req.jobQueue.getJobStats.throws(new Error("getJobs error"));
await getJobs(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("getJobs error");
});
it("should return a success message and data if getJobs is successful", async () => {
const data = { data: "jobs" };
req.jobQueue.getJobStats.returns(data);
await getJobs(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(res.json.firstCall.args[0]).to.deep.equal({
success: true,
msg: successMessages.QUEUE_GET_METRICS,
data,
});
});
});
describe("Queue Controller - addJob", () => {
let req, res, next;
beforeEach(() => {
req = {
headers: {},
params: {},
body: {},
db: {},
jobQueue: {
addJob: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if addJob throws an error", async () => {
req.jobQueue.addJob.throws(new Error("addJob error"));
await addJob(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("addJob error");
});
it("should return a success message if addJob is successful", async () => {
req.jobQueue.addJob.resolves();
await addJob(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(res.json.firstCall.args[0]).to.deep.equal({
success: true,
msg: successMessages.QUEUE_ADD_JOB,
});
});
let req, res, next;
beforeEach(() => {
req = {
headers: {},
params: {},
body: {},
db: {},
jobQueue: {
addJob: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if addJob throws an error", async () => {
req.jobQueue.addJob.throws(new Error("addJob error"));
await addJob(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("addJob error");
});
it("should return a success message if addJob is successful", async () => {
req.jobQueue.addJob.resolves();
await addJob(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(res.json.firstCall.args[0]).to.deep.equal({
success: true,
msg: successMessages.QUEUE_ADD_JOB,
});
});
});
describe("Queue Controller - obliterateQueue", () => {
let req, res, next;
beforeEach(() => {
req = {
headers: {},
params: {},
body: {},
db: {},
jobQueue: {
obliterate: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if obliterateQueue throws an error", async () => {
req.jobQueue.obliterate.throws(new Error("obliterateQueue error"));
await obliterateQueue(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("obliterateQueue error");
});
it("should return a success message if obliterateQueue is successful", async () => {
req.jobQueue.obliterate.resolves();
await obliterateQueue(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(res.json.firstCall.args[0]).to.deep.equal({
success: true,
msg: successMessages.QUEUE_OBLITERATE,
});
});
let req, res, next;
beforeEach(() => {
req = {
headers: {},
params: {},
body: {},
db: {},
jobQueue: {
obliterate: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if obliterateQueue throws an error", async () => {
req.jobQueue.obliterate.throws(new Error("obliterateQueue error"));
await obliterateQueue(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("obliterateQueue error");
});
it("should return a success message if obliterateQueue is successful", async () => {
req.jobQueue.obliterate.resolves();
await obliterateQueue(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(res.json.firstCall.args[0]).to.deep.equal({
success: true,
msg: successMessages.QUEUE_OBLITERATE,
});
});
});

View File

@@ -1,105 +1,103 @@
import { afterEach } from "node:test";
import {
getAppSettings,
updateAppSettings,
getAppSettings,
updateAppSettings,
} from "../../controllers/settingsController.js";
import { successMessages } from "../../utils/messages.js";
import sinon from "sinon";
describe("Settings Controller - getAppSettings", () => {
let req, res, next;
beforeEach(() => {
req = {
headers: {},
params: {},
body: {},
db: {},
settingsService: {
getSettings: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should throw an error if getSettings throws an error", async () => {
req.settingsService.getSettings.throws(new Error("getSettings error"));
await getAppSettings(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("getSettings error");
});
let req, res, next;
beforeEach(() => {
req = {
headers: {},
params: {},
body: {},
db: {},
settingsService: {
getSettings: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should throw an error if getSettings throws an error", async () => {
req.settingsService.getSettings.throws(new Error("getSettings error"));
await getAppSettings(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("getSettings error");
});
it("should return a success message and data if getSettings is successful", async () => {
const data = { data: "settings" };
req.settingsService.getSettings.returns(data);
await getAppSettings(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(res.json.firstCall.args[0]).to.deep.equal({
success: true,
msg: successMessages.GET_APP_SETTINGS,
data,
});
});
it("should return a success message and data if getSettings is successful", async () => {
const data = { data: "settings" };
req.settingsService.getSettings.returns(data);
await getAppSettings(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(res.json.firstCall.args[0]).to.deep.equal({
success: true,
msg: successMessages.GET_APP_SETTINGS,
data,
});
});
});
describe("Settings Controller - updateAppSettings", () => {
let req, res, next;
beforeEach(() => {
req = {
headers: {},
params: {},
body: {},
db: {
updateAppSettings: sinon.stub(),
},
settingsService: {
reloadSettings: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if body validation fails", async () => {
req.body = { invalid: 1 };
await updateAppSettings(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if updateAppSettings throws an error", async () => {
req.db.updateAppSettings.throws(new Error("updateAppSettings error"));
await updateAppSettings(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("updateAppSettings error");
});
it("should reject with an error if reloadSettings throws an error", async () => {
req.settingsService.reloadSettings.throws(
new Error("reloadSettings error")
);
await updateAppSettings(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("reloadSettings error");
});
it("should return a success message and data if updateAppSettings is successful", async () => {
const data = { data: "settings" };
req.settingsService.reloadSettings.returns(data);
await updateAppSettings(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(res.json.firstCall.args[0]).to.deep.equal({
success: true,
msg: successMessages.UPDATE_APP_SETTINGS,
data,
});
});
let req, res, next;
beforeEach(() => {
req = {
headers: {},
params: {},
body: {},
db: {
updateAppSettings: sinon.stub(),
},
settingsService: {
reloadSettings: sinon.stub(),
},
};
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if body validation fails", async () => {
req.body = { invalid: 1 };
await updateAppSettings(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if updateAppSettings throws an error", async () => {
req.db.updateAppSettings.throws(new Error("updateAppSettings error"));
await updateAppSettings(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("updateAppSettings error");
});
it("should reject with an error if reloadSettings throws an error", async () => {
req.settingsService.reloadSettings.throws(new Error("reloadSettings error"));
await updateAppSettings(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("reloadSettings error");
});
it("should return a success message and data if updateAppSettings is successful", async () => {
const data = { data: "settings" };
req.settingsService.reloadSettings.returns(data);
await updateAppSettings(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(res.json.firstCall.args[0]).to.deep.equal({
success: true,
msg: successMessages.UPDATE_APP_SETTINGS,
data,
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -4,22 +4,22 @@ import sharp from "sharp";
* @param {} file
*/
const GenerateAvatarImage = async (file) => {
try {
// Resize to target 64 * 64
let resizedImageBuffer = await sharp(file.buffer)
.resize({
width: 64,
height: 64,
fit: "cover",
})
.toBuffer();
try {
// Resize to target 64 * 64
let resizedImageBuffer = await sharp(file.buffer)
.resize({
width: 64,
height: 64,
fit: "cover",
})
.toBuffer();
//Get b64 string
const base64Image = resizedImageBuffer.toString("base64");
return base64Image;
} catch (error) {
throw error;
}
//Get b64 string
const base64Image = resizedImageBuffer.toString("base64");
return base64Image;
} catch (error) {
throw error;
}
};
export { GenerateAvatarImage };

View File

@@ -12,15 +12,12 @@ import winston from "winston";
* logger.error("User not found!",{"service":"Auth"})
*/
const logger = winston.createLogger({
level: "info",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: "app.log" }),
],
level: "info",
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: "app.log" }),
],
});
export default logger;

View File

@@ -1,124 +1,120 @@
const errorMessages = {
// General Errors:
FRIENDLY_ERROR: "Something went wrong...",
UNKNOWN_ERROR: "An unknown error occurred",
// General Errors:
FRIENDLY_ERROR: "Something went wrong...",
UNKNOWN_ERROR: "An unknown error occurred",
// Auth Controller
UNAUTHORIZED: "Unauthorized access",
AUTH_ADMIN_EXISTS: "Admin already exists",
AUTH_INVITE_NOT_FOUND: "Invite not found",
// Auth Controller
UNAUTHORIZED: "Unauthorized access",
AUTH_ADMIN_EXISTS: "Admin already exists",
AUTH_INVITE_NOT_FOUND: "Invite not found",
//Error handling middleware
UNKNOWN_SERVICE: "Unknown service",
NO_AUTH_TOKEN: "No auth token provided",
INVALID_AUTH_TOKEN: "Invalid auth token",
EXPIRED_AUTH_TOKEN: "Token expired",
NO_REFRESH_TOKEN: "No refresh token provided",
INVALID_REFRESH_TOKEN: "Invalid refresh token",
EXPIRED_REFRESH_TOKEN: "Refresh token expired",
REQUEST_NEW_ACCESS_TOKEN: "Request new access token",
//Error handling middleware
UNKNOWN_SERVICE: "Unknown service",
NO_AUTH_TOKEN: "No auth token provided",
INVALID_AUTH_TOKEN: "Invalid auth token",
EXPIRED_AUTH_TOKEN: "Token expired",
NO_REFRESH_TOKEN: "No refresh token provided",
INVALID_REFRESH_TOKEN: "Invalid refresh token",
EXPIRED_REFRESH_TOKEN: "Refresh token expired",
REQUEST_NEW_ACCESS_TOKEN: "Request new access token",
//Payload
INVALID_PAYLOAD: "Invalid payload",
//Payload
INVALID_PAYLOAD: "Invalid payload",
//Ownership Middleware
VERIFY_OWNER_NOT_FOUND: "Document not found",
VERIFY_OWNER_UNAUTHORIZED: "Unauthorized access",
//Ownership Middleware
VERIFY_OWNER_NOT_FOUND: "Document not found",
VERIFY_OWNER_UNAUTHORIZED: "Unauthorized access",
//Permissions Middleware
INSUFFICIENT_PERMISSIONS: "Insufficient permissions",
//Permissions Middleware
INSUFFICIENT_PERMISSIONS: "Insufficient permissions",
//DB Errors
DB_USER_EXISTS: "User already exists",
DB_USER_NOT_FOUND: "User not found",
DB_TOKEN_NOT_FOUND: "Token not found",
DB_RESET_PASSWORD_BAD_MATCH:
"New password must be different from old password",
DB_FIND_MONITOR_BY_ID: (monitorId) =>
`Monitor with id ${monitorId} not found`,
DB_DELETE_CHECKS: (monitorId) =>
`No checks found for monitor with id ${monitorId}`,
//DB Errors
DB_USER_EXISTS: "User already exists",
DB_USER_NOT_FOUND: "User not found",
DB_TOKEN_NOT_FOUND: "Token not found",
DB_RESET_PASSWORD_BAD_MATCH: "New password must be different from old password",
DB_FIND_MONITOR_BY_ID: (monitorId) => `Monitor with id ${monitorId} not found`,
DB_DELETE_CHECKS: (monitorId) => `No checks found for monitor with id ${monitorId}`,
//Auth errors
AUTH_INCORRECT_PASSWORD: "Incorrect password",
AUTH_UNAUTHORIZED: "Unauthorized access",
//Auth errors
AUTH_INCORRECT_PASSWORD: "Incorrect password",
AUTH_UNAUTHORIZED: "Unauthorized access",
// Monitor Errors
MONITOR_GET_BY_ID: "Monitor not found",
MONITOR_GET_BY_USER_ID: "No monitors found for user",
// Monitor Errors
MONITOR_GET_BY_ID: "Monitor not found",
MONITOR_GET_BY_USER_ID: "No monitors found for user",
// Job Queue Errors
JOB_QUEUE_WORKER_CLOSE: "Error closing worker",
JOB_QUEUE_DELETE_JOB: "Job not found in queue",
JOB_QUEUE_OBLITERATE: "Error obliterating queue",
// Job Queue Errors
JOB_QUEUE_WORKER_CLOSE: "Error closing worker",
JOB_QUEUE_DELETE_JOB: "Job not found in queue",
JOB_QUEUE_OBLITERATE: "Error obliterating queue",
// PING Operations
PING_CANNOT_RESOLVE: "No response",
// PING Operations
PING_CANNOT_RESOLVE: "No response",
};
const successMessages = {
//Alert Controller
ALERT_CREATE: "Alert created successfully",
ALERT_GET_BY_USER: "Got alerts successfully",
ALERT_GET_BY_MONITOR: "Got alerts by Monitor successfully",
ALERT_GET_BY_ID: "Got alert by Id successfully",
ALERT_EDIT: "Alert edited successfully",
ALERT_DELETE: "Alert deleted successfully",
//Alert Controller
ALERT_CREATE: "Alert created successfully",
ALERT_GET_BY_USER: "Got alerts successfully",
ALERT_GET_BY_MONITOR: "Got alerts by Monitor successfully",
ALERT_GET_BY_ID: "Got alert by Id successfully",
ALERT_EDIT: "Alert edited successfully",
ALERT_DELETE: "Alert deleted successfully",
// Auth Controller
AUTH_CREATE_USER: "User created successfully",
AUTH_LOGIN_USER: "User logged in successfully",
AUTH_LOGOUT_USER: "User logged out successfully",
AUTH_UPDATE_USER: "User updated successfully",
AUTH_CREATE_RECOVERY_TOKEN: "Recovery token created successfully",
AUTH_VERIFY_RECOVERY_TOKEN: "Recovery token verified successfully",
AUTH_RESET_PASSWORD: "Password reset successfully",
AUTH_ADMIN_CHECK: "Admin check completed successfully",
AUTH_DELETE_USER: "User deleted successfully",
// Auth Controller
AUTH_CREATE_USER: "User created successfully",
AUTH_LOGIN_USER: "User logged in successfully",
AUTH_LOGOUT_USER: "User logged out successfully",
AUTH_UPDATE_USER: "User updated successfully",
AUTH_CREATE_RECOVERY_TOKEN: "Recovery token created successfully",
AUTH_VERIFY_RECOVERY_TOKEN: "Recovery token verified successfully",
AUTH_RESET_PASSWORD: "Password reset successfully",
AUTH_ADMIN_CHECK: "Admin check completed successfully",
AUTH_DELETE_USER: "User deleted successfully",
// Check Controller
CHECK_CREATE: "Check created successfully",
CHECK_GET: "Got checks successfully",
CHECK_DELETE: "Checks deleted successfully",
CHECK_UPDATE_TTL: "Checks TTL updated successfully",
// Check Controller
CHECK_CREATE: "Check created successfully",
CHECK_GET: "Got checks successfully",
CHECK_DELETE: "Checks deleted successfully",
CHECK_UPDATE_TTL: "Checks TTL updated successfully",
//Monitor Controller
MONITOR_GET_ALL: "Got all monitors successfully",
MONITOR_STATS_BY_ID: "Got monitor stats by Id successfully",
MONITOR_GET_BY_ID: "Got monitor by Id successfully",
MONITOR_GET_BY_USER_ID: (userId) => `Got monitor for ${userId} successfully"`,
MONITOR_CREATE: "Monitor created successfully",
MONITOR_DELETE: "Monitor deleted successfully",
MONITOR_EDIT: "Monitor edited successfully",
MONITOR_CERTIFICATE: "Got monitor certificate successfully",
MONITOR_DEMO_ADDED: "Successfully added demo monitors",
//Monitor Controller
MONITOR_GET_ALL: "Got all monitors successfully",
MONITOR_STATS_BY_ID: "Got monitor stats by Id successfully",
MONITOR_GET_BY_ID: "Got monitor by Id successfully",
MONITOR_GET_BY_USER_ID: (userId) => `Got monitor for ${userId} successfully"`,
MONITOR_CREATE: "Monitor created successfully",
MONITOR_DELETE: "Monitor deleted successfully",
MONITOR_EDIT: "Monitor edited successfully",
MONITOR_CERTIFICATE: "Got monitor certificate successfully",
MONITOR_DEMO_ADDED: "Successfully added demo monitors",
// Queue Controller
QUEUE_GET_METRICS: "Got metrics successfully",
QUEUE_GET_METRICS: "Got job stats successfully",
QUEUE_ADD_JOB: "Job added successfully",
QUEUE_OBLITERATE: "Queue obliterated",
// Queue Controller
QUEUE_GET_METRICS: "Got metrics successfully",
QUEUE_GET_METRICS: "Got job stats successfully",
QUEUE_ADD_JOB: "Job added successfully",
QUEUE_OBLITERATE: "Queue obliterated",
//Job Queue
JOB_QUEUE_DELETE_JOB: "Job removed successfully",
JOB_QUEUE_OBLITERATE: "Queue OBLITERATED!!!",
JOB_QUEUE_PAUSE_JOB: "Job paused successfully",
JOB_QUEUE_RESUME_JOB: "Job resumed successfully",
//Job Queue
JOB_QUEUE_DELETE_JOB: "Job removed successfully",
JOB_QUEUE_OBLITERATE: "Queue OBLITERATED!!!",
JOB_QUEUE_PAUSE_JOB: "Job paused successfully",
JOB_QUEUE_RESUME_JOB: "Job resumed successfully",
//Maintenance Window Controller
MAINTENANCE_WINDOW_GET_BY_ID: "Got Maintenance Window by Id successfully",
MAINTENANCE_WINDOW_CREATE: "Maintenance Window created successfully",
MAINTENANCE_WINDOW_GET_BY_TEAM:
"Got Maintenance Windows by Team successfully",
MAINTENANCE_WINDOW_DELETE: "Maintenance Window deleted successfully",
MAINTENANCE_WINDOW_EDIT: "Maintenance Window edited successfully",
//Maintenance Window Controller
MAINTENANCE_WINDOW_GET_BY_ID: "Got Maintenance Window by Id successfully",
MAINTENANCE_WINDOW_CREATE: "Maintenance Window created successfully",
MAINTENANCE_WINDOW_GET_BY_TEAM: "Got Maintenance Windows by Team successfully",
MAINTENANCE_WINDOW_DELETE: "Maintenance Window deleted successfully",
MAINTENANCE_WINDOW_EDIT: "Maintenance Window edited successfully",
//Ping Operations
PING_SUCCESS: "Success",
//Ping Operations
PING_SUCCESS: "Success",
// App Settings
GET_APP_SETTINGS: "Got app settings successfully",
UPDATE_APP_SETTINGS: "Updated app settings successfully",
// App Settings
GET_APP_SETTINGS: "Got app settings successfully",
UPDATE_APP_SETTINGS: "Updated app settings successfully",
};
export { errorMessages, successMessages };

View File

@@ -5,13 +5,13 @@ 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(", ")}`
);
}
return value;
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(", ")}`
);
}
return value;
};
//****************************************
@@ -19,129 +19,129 @@ const roleValidatior = (role) => (value, helpers) => {
//****************************************
const loginValidation = joi.object({
email: joi
.string()
.email()
.required()
.custom((value, helpers) => {
const lowercasedValue = value.toLowerCase();
if (value !== lowercasedValue) {
return helpers.message("Email must be in lowercase");
}
return lowercasedValue;
}),
password: joi
.string()
.min(8)
.required()
.pattern(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/
),
email: joi
.string()
.email()
.required()
.custom((value, helpers) => {
const lowercasedValue = value.toLowerCase();
if (value !== lowercasedValue) {
return helpers.message("Email must be in lowercase");
}
return lowercasedValue;
}),
password: joi
.string()
.min(8)
.required()
.pattern(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/
),
});
const registrationBodyValidation = joi.object({
firstName: joi
.string()
.required()
.pattern(/^[A-Za-z]+$/),
lastName: joi
.string()
.required()
.pattern(/^[A-Za-z]+$/),
email: joi
.string()
.email()
.required()
.custom((value, helpers) => {
const lowercasedValue = value.toLowerCase();
if (value !== lowercasedValue) {
return helpers.message("Email must be in lowercase");
}
return lowercasedValue;
}),
password: joi
.string()
.min(8)
.required()
.pattern(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/
),
profileImage: joi.any(),
role: joi
.array()
.items(joi.string().valid("superadmin", "admin", "user", "demo"))
.min(1)
.required(),
teamId: joi.string().allow("").required(),
inviteToken: joi.string().allow("").required(),
firstName: joi
.string()
.required()
.pattern(/^[A-Za-z]+$/),
lastName: joi
.string()
.required()
.pattern(/^[A-Za-z]+$/),
email: joi
.string()
.email()
.required()
.custom((value, helpers) => {
const lowercasedValue = value.toLowerCase();
if (value !== lowercasedValue) {
return helpers.message("Email must be in lowercase");
}
return lowercasedValue;
}),
password: joi
.string()
.min(8)
.required()
.pattern(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/
),
profileImage: joi.any(),
role: joi
.array()
.items(joi.string().valid("superadmin", "admin", "user", "demo"))
.min(1)
.required(),
teamId: joi.string().allow("").required(),
inviteToken: joi.string().allow("").required(),
});
const editUserParamValidation = joi.object({
userId: joi.string().required(),
userId: joi.string().required(),
});
const editUserBodyValidation = joi.object({
firstName: joi.string().pattern(/^[A-Za-z]+$/),
lastName: joi.string().pattern(/^[A-Za-z]+$/),
profileImage: joi.any(),
newPassword: joi
.string()
.min(8)
.pattern(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/
),
password: joi
.string()
.min(8)
.pattern(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/
),
deleteProfileImage: joi.boolean(),
role: joi.array(),
firstName: joi.string().pattern(/^[A-Za-z]+$/),
lastName: joi.string().pattern(/^[A-Za-z]+$/),
profileImage: joi.any(),
newPassword: joi
.string()
.min(8)
.pattern(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/
),
password: joi
.string()
.min(8)
.pattern(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/
),
deleteProfileImage: joi.boolean(),
role: joi.array(),
});
const recoveryValidation = joi.object({
email: joi
.string()
.email({ tlds: { allow: false } })
.required(),
email: joi
.string()
.email({ tlds: { allow: false } })
.required(),
});
const recoveryTokenValidation = joi.object({
recoveryToken: joi.string().required(),
recoveryToken: joi.string().required(),
});
const newPasswordValidation = joi.object({
recoveryToken: joi.string().required(),
password: joi
.string()
.min(8)
.required()
.pattern(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/
),
confirm: joi.string(),
recoveryToken: joi.string().required(),
password: joi
.string()
.min(8)
.required()
.pattern(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/
),
confirm: joi.string(),
});
const deleteUserParamValidation = joi.object({
email: joi.string().email().required(),
email: joi.string().email().required(),
});
const inviteRoleValidation = joi.object({
roles: joi.custom(roleValidatior(["admin", "superadmin"])).required(),
roles: joi.custom(roleValidatior(["admin", "superadmin"])).required(),
});
const inviteBodyValidation = joi.object({
email: joi.string().trim().email().required().messages({
"string.empty": "Email is required",
"string.email": "Must be a valid email address",
}),
role: joi.array().required(),
teamId: joi.string().required(),
email: joi.string().trim().email().required().messages({
"string.empty": "Email is required",
"string.email": "Must be a valid email address",
}),
role: joi.array().required(),
teamId: joi.string().required(),
});
const inviteVerificationBodyValidation = joi.object({
token: joi.string().required(),
token: joi.string().required(),
});
//****************************************
@@ -149,91 +149,91 @@ const inviteVerificationBodyValidation = joi.object({
//****************************************
const getMonitorByIdParamValidation = joi.object({
monitorId: joi.string().required(),
monitorId: joi.string().required(),
});
const getMonitorByIdQueryValidation = joi.object({
status: joi.boolean(),
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("day", "week", "month"),
numToDisplay: joi.number(),
normalize: joi.boolean(),
status: joi.boolean(),
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("day", "week", "month"),
numToDisplay: joi.number(),
normalize: joi.boolean(),
});
const getMonitorsAndSummaryByTeamIdParamValidation = joi.object({
teamId: joi.string().required(),
teamId: joi.string().required(),
});
const getMonitorsAndSummaryByTeamIdQueryValidation = joi.object({
type: joi
.alternatives()
.try(
joi.string().valid("http", "ping", "pagespeed"),
joi.array().items(joi.string().valid("http", "ping", "pagespeed"))
),
type: joi
.alternatives()
.try(
joi.string().valid("http", "ping", "pagespeed"),
joi.array().items(joi.string().valid("http", "ping", "pagespeed"))
),
});
const getMonitorsByTeamIdValidation = joi.object({
teamId: joi.string().required(),
teamId: joi.string().required(),
});
const getMonitorsByTeamIdQueryValidation = joi.object({
status: joi.boolean(),
checkOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
normalize: joi.boolean(),
type: joi
.alternatives()
.try(
joi.string().valid("http", "ping", "pagespeed"),
joi.array().items(joi.string().valid("http", "ping", "pagespeed"))
),
page: joi.number(),
rowsPerPage: joi.number(),
filter: joi.string(),
field: joi.string(),
order: joi.string().valid("asc", "desc"),
status: joi.boolean(),
checkOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
normalize: joi.boolean(),
type: joi
.alternatives()
.try(
joi.string().valid("http", "ping", "pagespeed"),
joi.array().items(joi.string().valid("http", "ping", "pagespeed"))
),
page: joi.number(),
rowsPerPage: joi.number(),
filter: joi.string(),
field: joi.string(),
order: joi.string().valid("asc", "desc"),
});
const getMonitorStatsByIdParamValidation = joi.object({
monitorId: joi.string().required(),
monitorId: joi.string().required(),
});
const getMonitorStatsByIdQueryValidation = joi.object({
status: joi.string(),
limit: joi.number(),
sortOrder: joi.string().valid("asc", "desc"),
dateRange: joi.string().valid("day", "week", "month"),
numToDisplay: joi.number(),
normalize: joi.boolean(),
status: joi.string(),
limit: joi.number(),
sortOrder: joi.string().valid("asc", "desc"),
dateRange: joi.string().valid("day", "week", "month"),
numToDisplay: joi.number(),
normalize: joi.boolean(),
});
const getCertificateParamValidation = joi.object({
monitorId: joi.string().required(),
monitorId: joi.string().required(),
});
const createMonitorBodyValidation = joi.object({
_id: joi.string(),
userId: joi.string().required(),
teamId: joi.string().required(),
name: joi.string().required(),
description: joi.string().required(),
type: joi.string().required(),
url: joi.string().required(),
isActive: joi.boolean(),
interval: joi.number(),
notifications: joi.array().items(joi.object()),
_id: joi.string(),
userId: joi.string().required(),
teamId: joi.string().required(),
name: joi.string().required(),
description: joi.string().required(),
type: joi.string().required(),
url: joi.string().required(),
isActive: joi.boolean(),
interval: joi.number(),
notifications: joi.array().items(joi.object()),
});
const editMonitorBodyValidation = joi.object({
name: joi.string(),
description: joi.string(),
interval: joi.number(),
notifications: joi.array().items(joi.object()),
name: joi.string(),
description: joi.string(),
interval: joi.number(),
notifications: joi.array().items(joi.object()),
});
const pauseMonitorParamValidation = joi.object({
monitorId: joi.string().required(),
monitorId: joi.string().required(),
});
//****************************************
@@ -241,44 +241,44 @@ const pauseMonitorParamValidation = joi.object({
//****************************************
const createAlertParamValidation = joi.object({
monitorId: joi.string().required(),
monitorId: joi.string().required(),
});
const createAlertBodyValidation = joi.object({
checkId: joi.string().required(),
monitorId: joi.string().required(),
userId: joi.string().required(),
status: joi.boolean(),
message: joi.string(),
notifiedStatus: joi.boolean(),
acknowledgeStatus: joi.boolean(),
checkId: joi.string().required(),
monitorId: joi.string().required(),
userId: joi.string().required(),
status: joi.boolean(),
message: joi.string(),
notifiedStatus: joi.boolean(),
acknowledgeStatus: joi.boolean(),
});
const getAlertsByUserIdParamValidation = joi.object({
userId: joi.string().required(),
userId: joi.string().required(),
});
const getAlertsByMonitorIdParamValidation = joi.object({
monitorId: joi.string().required(),
monitorId: joi.string().required(),
});
const getAlertByIdParamValidation = joi.object({
alertId: joi.string().required(),
alertId: joi.string().required(),
});
const editAlertParamValidation = joi.object({
alertId: joi.string().required(),
alertId: joi.string().required(),
});
const editAlertBodyValidation = joi.object({
status: joi.boolean(),
message: joi.string(),
notifiedStatus: joi.boolean(),
acknowledgeStatus: joi.boolean(),
status: joi.boolean(),
message: joi.string(),
notifiedStatus: joi.boolean(),
acknowledgeStatus: joi.boolean(),
});
const deleteAlertParamValidation = joi.object({
alertId: joi.string().required(),
alertId: joi.string().required(),
});
//****************************************
@@ -286,53 +286,53 @@ const deleteAlertParamValidation = joi.object({
//****************************************
const createCheckParamValidation = joi.object({
monitorId: joi.string().required(),
monitorId: joi.string().required(),
});
const createCheckBodyValidation = joi.object({
monitorId: joi.string().required(),
status: joi.boolean().required(),
responseTime: joi.number().required(),
statusCode: joi.number().required(),
message: joi.string().required(),
monitorId: joi.string().required(),
status: joi.boolean().required(),
responseTime: joi.number().required(),
statusCode: joi.number().required(),
message: joi.string().required(),
});
const getChecksParamValidation = joi.object({
monitorId: joi.string().required(),
monitorId: joi.string().required(),
});
const getChecksQueryValidation = joi.object({
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("day", "week", "month"),
filter: joi.string().valid("all", "down", "resolve"),
page: joi.number(),
rowsPerPage: joi.number(),
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("day", "week", "month"),
filter: joi.string().valid("all", "down", "resolve"),
page: joi.number(),
rowsPerPage: joi.number(),
});
const getTeamChecksParamValidation = joi.object({
teamId: joi.string().required(),
teamId: joi.string().required(),
});
const getTeamChecksQueryValidation = joi.object({
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("day", "week", "month"),
filter: joi.string().valid("all", "down", "resolve"),
page: joi.number(),
rowsPerPage: joi.number(),
sortOrder: joi.string().valid("asc", "desc"),
limit: joi.number(),
dateRange: joi.string().valid("day", "week", "month"),
filter: joi.string().valid("all", "down", "resolve"),
page: joi.number(),
rowsPerPage: joi.number(),
});
const deleteChecksParamValidation = joi.object({
monitorId: joi.string().required(),
monitorId: joi.string().required(),
});
const deleteChecksByTeamIdParamValidation = joi.object({
teamId: joi.string().required(),
teamId: joi.string().required(),
});
const updateChecksTTLBodyValidation = joi.object({
ttl: joi.number().required(),
ttl: joi.number().required(),
});
//****************************************
@@ -340,21 +340,21 @@ const updateChecksTTLBodyValidation = joi.object({
//****************************************
const getPageSpeedCheckParamValidation = joi.object({
monitorId: joi.string().required(),
monitorId: joi.string().required(),
});
//Validation schema for the monitorId parameter
const createPageSpeedCheckParamValidation = joi.object({
monitorId: joi.string().required(),
monitorId: joi.string().required(),
});
//Validation schema for the monitorId body
const createPageSpeedCheckBodyValidation = joi.object({
url: joi.string().required(),
url: joi.string().required(),
});
const deletePageSpeedCheckParamValidation = joi.object({
monitorId: joi.string().required(),
monitorId: joi.string().required(),
});
//****************************************
@@ -362,120 +362,120 @@ const deletePageSpeedCheckParamValidation = joi.object({
//****************************************
const createMaintenanceWindowBodyValidation = joi.object({
monitors: joi.array().items(joi.string()).required(),
name: joi.string().required(),
active: joi.boolean(),
start: joi.date().required(),
end: joi.date().required(),
repeat: joi.number().required(),
expiry: joi.date(),
monitors: joi.array().items(joi.string()).required(),
name: joi.string().required(),
active: joi.boolean(),
start: joi.date().required(),
end: joi.date().required(),
repeat: joi.number().required(),
expiry: joi.date(),
});
const getMaintenanceWindowByIdParamValidation = joi.object({
id: joi.string().required(),
id: joi.string().required(),
});
const getMaintenanceWindowsByTeamIdQueryValidation = joi.object({
active: joi.boolean(),
page: joi.number(),
rowsPerPage: joi.number(),
field: joi.string(),
order: joi.string().valid("asc", "desc"),
active: joi.boolean(),
page: joi.number(),
rowsPerPage: joi.number(),
field: joi.string(),
order: joi.string().valid("asc", "desc"),
});
const getMaintenanceWindowsByMonitorIdParamValidation = joi.object({
monitorId: joi.string().required(),
monitorId: joi.string().required(),
});
const deleteMaintenanceWindowByIdParamValidation = joi.object({
id: joi.string().required(),
id: joi.string().required(),
});
const editMaintenanceWindowByIdParamValidation = joi.object({
id: joi.string().required(),
id: joi.string().required(),
});
const editMaintenanceByIdWindowBodyValidation = joi.object({
active: joi.boolean(),
name: joi.string(),
repeat: joi.number(),
start: joi.date(),
end: joi.date(),
expiry: joi.date(),
monitors: joi.array(),
active: joi.boolean(),
name: joi.string(),
repeat: joi.number(),
start: joi.date(),
end: joi.date(),
expiry: joi.date(),
monitors: joi.array(),
});
//****************************************
// SettingsValidation
//****************************************
const updateAppSettingsBodyValidation = joi.object({
apiBaseUrl: joi.string().allow(""),
logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""),
clientHost: joi.string().allow(""),
dbType: joi.string().allow(""),
dbConnectionString: joi.string().allow(""),
redisHost: joi.string().allow(""),
redisPort: joi.number().allow(null, ""),
jwtTTL: joi.string().allow(""),
pagespeedApiKey: joi.string().allow(""),
systemEmailHost: joi.string().allow(""),
systemEmailPort: joi.number().allow(""),
systemEmailAddress: joi.string().allow(""),
systemEmailPassword: joi.string().allow(""),
apiBaseUrl: joi.string().allow(""),
logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""),
clientHost: joi.string().allow(""),
dbType: joi.string().allow(""),
dbConnectionString: joi.string().allow(""),
redisHost: joi.string().allow(""),
redisPort: joi.number().allow(null, ""),
jwtTTL: joi.string().allow(""),
pagespeedApiKey: joi.string().allow(""),
systemEmailHost: joi.string().allow(""),
systemEmailPort: joi.number().allow(""),
systemEmailAddress: joi.string().allow(""),
systemEmailPassword: joi.string().allow(""),
});
export {
roleValidatior,
loginValidation,
registrationBodyValidation,
recoveryValidation,
recoveryTokenValidation,
newPasswordValidation,
inviteRoleValidation,
inviteBodyValidation,
inviteVerificationBodyValidation,
createMonitorBodyValidation,
getMonitorByIdParamValidation,
getMonitorByIdQueryValidation,
getMonitorsAndSummaryByTeamIdParamValidation,
getMonitorsAndSummaryByTeamIdQueryValidation,
getMonitorsByTeamIdValidation,
getMonitorsByTeamIdQueryValidation,
getMonitorStatsByIdParamValidation,
getMonitorStatsByIdQueryValidation,
getCertificateParamValidation,
editMonitorBodyValidation,
pauseMonitorParamValidation,
editUserParamValidation,
editUserBodyValidation,
createAlertParamValidation,
createAlertBodyValidation,
getAlertsByUserIdParamValidation,
getAlertsByMonitorIdParamValidation,
getAlertByIdParamValidation,
editAlertParamValidation,
editAlertBodyValidation,
deleteAlertParamValidation,
createCheckParamValidation,
createCheckBodyValidation,
getChecksParamValidation,
getChecksQueryValidation,
getTeamChecksParamValidation,
getTeamChecksQueryValidation,
deleteChecksParamValidation,
deleteChecksByTeamIdParamValidation,
updateChecksTTLBodyValidation,
deleteUserParamValidation,
getPageSpeedCheckParamValidation,
createPageSpeedCheckParamValidation,
deletePageSpeedCheckParamValidation,
createPageSpeedCheckBodyValidation,
createMaintenanceWindowBodyValidation,
getMaintenanceWindowByIdParamValidation,
getMaintenanceWindowsByTeamIdQueryValidation,
getMaintenanceWindowsByMonitorIdParamValidation,
deleteMaintenanceWindowByIdParamValidation,
editMaintenanceWindowByIdParamValidation,
editMaintenanceByIdWindowBodyValidation,
updateAppSettingsBodyValidation,
roleValidatior,
loginValidation,
registrationBodyValidation,
recoveryValidation,
recoveryTokenValidation,
newPasswordValidation,
inviteRoleValidation,
inviteBodyValidation,
inviteVerificationBodyValidation,
createMonitorBodyValidation,
getMonitorByIdParamValidation,
getMonitorByIdQueryValidation,
getMonitorsAndSummaryByTeamIdParamValidation,
getMonitorsAndSummaryByTeamIdQueryValidation,
getMonitorsByTeamIdValidation,
getMonitorsByTeamIdQueryValidation,
getMonitorStatsByIdParamValidation,
getMonitorStatsByIdQueryValidation,
getCertificateParamValidation,
editMonitorBodyValidation,
pauseMonitorParamValidation,
editUserParamValidation,
editUserBodyValidation,
createAlertParamValidation,
createAlertBodyValidation,
getAlertsByUserIdParamValidation,
getAlertsByMonitorIdParamValidation,
getAlertByIdParamValidation,
editAlertParamValidation,
editAlertBodyValidation,
deleteAlertParamValidation,
createCheckParamValidation,
createCheckBodyValidation,
getChecksParamValidation,
getChecksQueryValidation,
getTeamChecksParamValidation,
getTeamChecksQueryValidation,
deleteChecksParamValidation,
deleteChecksByTeamIdParamValidation,
updateChecksTTLBodyValidation,
deleteUserParamValidation,
getPageSpeedCheckParamValidation,
createPageSpeedCheckParamValidation,
deletePageSpeedCheckParamValidation,
createPageSpeedCheckBodyValidation,
createMaintenanceWindowBodyValidation,
getMaintenanceWindowByIdParamValidation,
getMaintenanceWindowsByTeamIdQueryValidation,
getMaintenanceWindowsByMonitorIdParamValidation,
deleteMaintenanceWindowByIdParamValidation,
editMaintenanceWindowByIdParamValidation,
editMaintenanceByIdWindowBodyValidation,
updateAppSettingsBodyValidation,
};