diff --git a/Server/configs/db.js b/Server/configs/db.js index 153962e36..e42595b4c 100644 --- a/Server/configs/db.js +++ b/Server/configs/db.js @@ -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 }; diff --git a/Server/controllers/checkController.js b/Server/controllers/checkController.js index fddf66949..a47f3f8c4 100644 --- a/Server/controllers/checkController.js +++ b/Server/controllers/checkController.js @@ -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, }; diff --git a/Server/controllers/inviteController.js b/Server/controllers/inviteController.js index 162861e89..72e3d99a3 100644 --- a/Server/controllers/inviteController.js +++ b/Server/controllers/inviteController.js @@ -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 }; diff --git a/Server/controllers/maintenanceWindowController.js b/Server/controllers/maintenanceWindowController.js index 663429835..1700a483e 100644 --- a/Server/controllers/maintenanceWindowController.js +++ b/Server/controllers/maintenanceWindowController.js @@ -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, }; diff --git a/Server/controllers/queueController.js b/Server/controllers/queueController.js index fbf7022d2..5e525de58 100644 --- a/Server/controllers/queueController.js +++ b/Server/controllers/queueController.js @@ -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 }; diff --git a/Server/controllers/settingsController.js b/Server/controllers/settingsController.js index e96bedace..3e232ac7a 100644 --- a/Server/controllers/settingsController.js +++ b/Server/controllers/settingsController.js @@ -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 }; diff --git a/Server/db/FakeDb.js b/Server/db/FakeDb.js index a7c8bf4ee..4e9b32992 100644 --- a/Server/db/FakeDb.js +++ b/Server/db/FakeDb.js @@ -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, }; diff --git a/Server/db/models/AppSettings.js b/Server/db/models/AppSettings.js index 547af5f78..c4faa0881 100644 --- a/Server/db/models/AppSettings.js +++ b/Server/db/models/AppSettings.js @@ -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); diff --git a/Server/db/models/Check.js b/Server/db/models/Check.js index e560f8530..320ddefe9 100644 --- a/Server/db/models/Check.js +++ b/Server/db/models/Check.js @@ -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 }); diff --git a/Server/db/models/InviteToken.js b/Server/db/models/InviteToken.js index 5e12d8e5d..0c2402b07 100644 --- a/Server/db/models/InviteToken.js +++ b/Server/db/models/InviteToken.js @@ -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); diff --git a/Server/db/models/MaintenanceWindow.js b/Server/db/models/MaintenanceWindow.js index 95664bdcd..b68abb413 100644 --- a/Server/db/models/MaintenanceWindow.js +++ b/Server/db/models/MaintenanceWindow.js @@ -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); diff --git a/Server/db/models/Monitor.js b/Server/db/models/Monitor.js index 2184484aa..d1f0ef5a7 100644 --- a/Server/db/models/Monitor.js +++ b/Server/db/models/Monitor.js @@ -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); diff --git a/Server/db/models/Notification.js b/Server/db/models/Notification.js index 79217f6fe..9edb0187a 100644 --- a/Server/db/models/Notification.js +++ b/Server/db/models/Notification.js @@ -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); diff --git a/Server/db/models/PageSpeedCheck.js b/Server/db/models/PageSpeedCheck.js index c3afd5231..a7c013f93 100644 --- a/Server/db/models/PageSpeedCheck.js +++ b/Server/db/models/PageSpeedCheck.js @@ -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); diff --git a/Server/db/models/RecoveryToken.js b/Server/db/models/RecoveryToken.js index ed7eee3b1..2219a4bca 100644 --- a/Server/db/models/RecoveryToken.js +++ b/Server/db/models/RecoveryToken.js @@ -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); diff --git a/Server/db/models/Team.js b/Server/db/models/Team.js index c125df329..95337c201 100644 --- a/Server/db/models/Team.js +++ b/Server/db/models/Team.js @@ -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); diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js index ad9ea2a2a..61e60e836 100644 --- a/Server/db/mongo/MongoDB.js +++ b/Server/db/mongo/MongoDB.js @@ -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, }; diff --git a/Server/db/mongo/modules/checkModule.js b/Server/db/mongo/modules/checkModule.js index ae1d5df4d..09868dc43 100644 --- a/Server/db/mongo/modules/checkModule.js +++ b/Server/db/mongo/modules/checkModule.js @@ -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, }; diff --git a/Server/db/mongo/modules/inviteModule.js b/Server/db/mongo/modules/inviteModule.js index c2a8eecc4..63636be86 100644 --- a/Server/db/mongo/modules/inviteModule.js +++ b/Server/db/mongo/modules/inviteModule.js @@ -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 }; diff --git a/Server/db/mongo/modules/maintenanceWindowModule.js b/Server/db/mongo/modules/maintenanceWindowModule.js index 173af5467..4644d90bf 100644 --- a/Server/db/mongo/modules/maintenanceWindowModule.js +++ b/Server/db/mongo/modules/maintenanceWindowModule.js @@ -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, }; diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 847d07841..5d54db393 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -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, }; diff --git a/Server/db/mongo/modules/notificationModule.js b/Server/db/mongo/modules/notificationModule.js index ec690b5d2..aab2d03bd 100644 --- a/Server/db/mongo/modules/notificationModule.js +++ b/Server/db/mongo/modules/notificationModule.js @@ -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, }; diff --git a/Server/db/mongo/modules/pageSpeedCheckModule.js b/Server/db/mongo/modules/pageSpeedCheckModule.js index be3dda2d9..56dafa697 100644 --- a/Server/db/mongo/modules/pageSpeedCheckModule.js +++ b/Server/db/mongo/modules/pageSpeedCheckModule.js @@ -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 }; diff --git a/Server/db/mongo/modules/recoveryModule.js b/Server/db/mongo/modules/recoveryModule.js index e9faa5a3c..e061fe664 100644 --- a/Server/db/mongo/modules/recoveryModule.js +++ b/Server/db/mongo/modules/recoveryModule.js @@ -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 }; diff --git a/Server/db/mongo/modules/settingsModule.js b/Server/db/mongo/modules/settingsModule.js index 70dc16175..3b5f68e19 100644 --- a/Server/db/mongo/modules/settingsModule.js +++ b/Server/db/mongo/modules/settingsModule.js @@ -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 }; diff --git a/Server/db/mongo/modules/userModule.js b/Server/db/mongo/modules/userModule.js index 689aceb30..7afe04763 100644 --- a/Server/db/mongo/modules/userModule.js +++ b/Server/db/mongo/modules/userModule.js @@ -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, }; diff --git a/Server/middleware/handleErrors.js b/Server/middleware/handleErrors.js index c59967382..51b880878 100644 --- a/Server/middleware/handleErrors.js +++ b/Server/middleware/handleErrors.js @@ -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 }; diff --git a/Server/middleware/isAllowed.js b/Server/middleware/isAllowed.js index a48afb337..4aa1a0d81 100644 --- a/Server/middleware/isAllowed.js +++ b/Server/middleware/isAllowed.js @@ -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 }; diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js index 23b79e2c8..cb52ef297 100644 --- a/Server/middleware/verifyJWT.js +++ b/Server/middleware/verifyJWT.js @@ -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 }; \ No newline at end of file +export { verifyJWT }; diff --git a/Server/middleware/verifyOwnership.js b/Server/middleware/verifyOwnership.js index 2b656568b..37c19b42b 100644 --- a/Server/middleware/verifyOwnership.js +++ b/Server/middleware/verifyOwnership.js @@ -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 }; diff --git a/Server/middleware/verifySuperAdmin.js b/Server/middleware/verifySuperAdmin.js index cf8774c31..073526791 100644 --- a/Server/middleware/verifySuperAdmin.js +++ b/Server/middleware/verifySuperAdmin.js @@ -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 }; diff --git a/Server/openapi.json b/Server/openapi.json index 6d079e685..fd7eae94f 100644 --- a/Server/openapi.json +++ b/Server/openapi.json @@ -1,2278 +1,2265 @@ { - "openapi": "3.1.0", - "info": { - "title": "BlueWave Uptime", - "summary": "BlueWave Uptime OpenAPI Specifications", - "description": "BlueWave Uptime is an open source server monitoring application used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.", - "contact": { - "name": "API Support", - "url": "mailto:support@bluewavelabs.ca", - "email": "support@bluewavelabs.ca" - }, - "license": { - "name": "AGPLv3", - "url": "https://github.com/bluewave-labs/bluewave-uptime/tree/HEAD/LICENSE" - }, - "version": "1.0" - }, - "servers": [ - { - "url": "http://localhost:{PORT}/{API_PATH}", - "description": "Local Development Server", - "variables": { - "PORT": { - "description": "API Port", - "enum": ["5000"], - "default": "5000" - }, - "API_PATH": { - "description": "API Base Path", - "enum": ["api/v1"], - "default": "api/v1" - } - } - }, - { - "url": "http://localhost/{API_PATH}", - "description": "Distribution Local Development Server", - "variables": { - "API_PATH": { - "description": "API Base Path", - "enum": ["api/v1"], - "default": "api/v1" - } - } - }, - { - "url": "https://uptime-demo.bluewavelabs.ca/{API_PATH}", - "description": "Bluewave Demo Server", - "variables": { - "PORT": { - "description": "API Port", - "enum": ["5000"], - "default": "5000" - }, - "API_PATH": { - "description": "API Base Path", - "enum": ["api/v1"], - "default": "api/v1" - } - } - } - ], - "tags": [ - { - "name": "auth", - "description": "Authentication" - }, - { - "name": "invite", - "description": "Invite" - }, - { - "name": "monitors", - "description": "Monitors" - }, - { - "name": "checks", - "description": "Checks" - }, - { - "name": "maintenance-window", - "description": "Maintenance window" - }, - { - "name": "queue", - "description": "Queue" - } - ], - "paths": { - "/auth/register": { - "post": { - "tags": ["auth"], - "description": "Register a new user", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "firstName", - "lastName", - "email", - "password", - "role", - "teamId" - ], - "properties": { - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string", - "format": "email" - }, - "password": { - "type": "string", - "format": "password" - }, - "profileImage": { - "type": "file", - "format": "file" - }, - "role": { - "type": "array", - "enum": [["user"], ["admin"], ["superadmin"], ["Demo"]], - "default": ["superadmin"] - }, - "teamId": { - "type": "string", - "format": "uuid" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/login": { - "post": { - "tags": ["auth"], - "description": "Login with credentials", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["email", "password"], - "properties": { - "email": { - "type": "string", - "format": "email" - }, - "password": { - "type": "string", - "format": "password" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/user/{userId}": { - "put": { - "tags": ["auth"], - "description": "Change user information", - "parameters": [ - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserUpdateRequest" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["auth"], - "description": "Delete user", - "parameters": [ - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/auth/users/superadmin": { - "get": { - "tags": ["auth"], - "description": "Checks to see if an admin account exists", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/auth/users": { - "get": { - "tags": ["auth"], - "description": "Get all users", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/auth/recovery/request": { - "post": { - "tags": ["auth"], - "description": "Request a recovery token", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["email"], - "properties": { - "email": { - "type": "string", - "format": "email" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/recovery/validate": { - "post": { - "tags": ["auth"], - "description": "Validate recovery token", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["recoveryToken"], - "properties": { - "recoveryToken": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/recovery/reset": { - "post": { - "tags": ["auth"], - "description": "Password reset", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["recoveryToken", "password"], - "properties": { - "recoveryToken": { - "type": "string" - }, - "password": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/invite": { - "post": { - "tags": ["invite"], - "description": "Request an invitation", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["email", "role"], - "properties": { - "email": { - "type": "string" - }, - "role": { - "type": "array" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/invite/verify": { - "post": { - "tags": ["invite"], - "description": "Request an invitation", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["token"], - "properties": { - "token": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors": { - "get": { - "tags": ["monitors"], - "description": "Get all monitors", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": ["monitors"], - "description": "Create a new monitor", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateMonitorBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["monitors"], - "description": "Delete all monitors", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/{monitorId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitor by id", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "put": { - "tags": ["monitors"], - "description": "Update monitor by id", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateMonitorBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["monitors"], - "description": "Delete monitor by id", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/stats/{monitorId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitor stats", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/certificate/{monitorId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitor certificate", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/team/summary/{teamId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitors and summary by teamId", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "type", - "in": "query", - "required": false, - "schema": { - "type": "array", - "enum": ["http", "ping", "pagespeed"] - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/team/{teamId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitors by teamId", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "status", - "description": "Status of monitor, true for up, false for down", - "in": "query", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "name": "checkOrder", - "description": "Order of checks", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": ["asc", "desc"] - } - }, - { - "name": "limit", - "description": "Number of checks to return with monitor", - "in": "query", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "type", - "description": "Type of monitor", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": ["http", "ping", "pagespeed"] - } - }, - { - "name": "page", - "in": "query", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "rowsPerPage", - "in": "query", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "filter", - "description": "Value to filter by", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "field", - "description": "Field to filter on", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "order", - "description": "Sort order of results", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": ["http", "ping", "pagespeed"] - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/pause/{monitorId}": { - "post": { - "tags": ["monitors"], - "description": "Pause monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/demo": { - "post": { - "tags": ["monitors"], - "description": "Create a demo monitor", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateMonitorBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/checks/{monitorId}": { - "get": { - "tags": ["checks"], - "description": "Get all checks for a monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": ["checks"], - "description": "Create a new check", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateCheckBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["checks"], - "description": "Delete all checks for a monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/checks/team/{teamId}": { - "get": { - "tags": ["checks"], - "description": "Get all checks for a team", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["checks"], - "description": "Delete all checks for a team", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/checks/team/ttl": { - "put": { - "tags": ["checks"], - "description": "Update check TTL", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateCheckTTLBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/maintenance-window/monitor/{monitorId}": { - "get": { - "tags": ["maintenance-window"], - "description": "Get maintenance window for monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": ["maintenance-window"], - "description": "Create maintenance window for monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateMaintenanceWindowBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/maintenance-window/user/{userId}": { - "get": { - "tags": ["maintenance-window"], - "description": "Get maintenance window for user", - "parameters": [ - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/queue/jobs": { - "get": { - "tags": ["queue"], - "description": "Get all jobs in queue", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": ["queue"], - "description": "Create a new job. Useful for testing scaling workers", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/queue/metrics": { - "get": { - "tags": ["queue"], - "description": "Get queue metrics", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/queue/obliterate": { - "post": { - "tags": ["queue"], - "description": "Obliterate job queue", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - } - }, + "openapi": "3.1.0", + "info": { + "title": "BlueWave Uptime", + "summary": "BlueWave Uptime OpenAPI Specifications", + "description": "BlueWave Uptime is an open source server monitoring application used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.", + "contact": { + "name": "API Support", + "url": "mailto:support@bluewavelabs.ca", + "email": "support@bluewavelabs.ca" + }, + "license": { + "name": "AGPLv3", + "url": "https://github.com/bluewave-labs/bluewave-uptime/tree/HEAD/LICENSE" + }, + "version": "1.0" + }, + "servers": [ + { + "url": "http://localhost:{PORT}/{API_PATH}", + "description": "Local Development Server", + "variables": { + "PORT": { + "description": "API Port", + "enum": ["5000"], + "default": "5000" + }, + "API_PATH": { + "description": "API Base Path", + "enum": ["api/v1"], + "default": "api/v1" + } + } + }, + { + "url": "http://localhost/{API_PATH}", + "description": "Distribution Local Development Server", + "variables": { + "API_PATH": { + "description": "API Base Path", + "enum": ["api/v1"], + "default": "api/v1" + } + } + }, + { + "url": "https://uptime-demo.bluewavelabs.ca/{API_PATH}", + "description": "Bluewave Demo Server", + "variables": { + "PORT": { + "description": "API Port", + "enum": ["5000"], + "default": "5000" + }, + "API_PATH": { + "description": "API Base Path", + "enum": ["api/v1"], + "default": "api/v1" + } + } + } + ], + "tags": [ + { + "name": "auth", + "description": "Authentication" + }, + { + "name": "invite", + "description": "Invite" + }, + { + "name": "monitors", + "description": "Monitors" + }, + { + "name": "checks", + "description": "Checks" + }, + { + "name": "maintenance-window", + "description": "Maintenance window" + }, + { + "name": "queue", + "description": "Queue" + } + ], + "paths": { + "/auth/register": { + "post": { + "tags": ["auth"], + "description": "Register a new user", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "firstName", + "lastName", + "email", + "password", + "role", + "teamId" + ], + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "format": "password" + }, + "profileImage": { + "type": "file", + "format": "file" + }, + "role": { + "type": "array", + "enum": [["user"], ["admin"], ["superadmin"], ["Demo"]], + "default": ["superadmin"] + }, + "teamId": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/login": { + "post": { + "tags": ["auth"], + "description": "Login with credentials", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["email", "password"], + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "format": "password" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/user/{userId}": { + "put": { + "tags": ["auth"], + "description": "Change user information", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": ["auth"], + "description": "Delete user", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/auth/users/superadmin": { + "get": { + "tags": ["auth"], + "description": "Checks to see if an admin account exists", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/auth/users": { + "get": { + "tags": ["auth"], + "description": "Get all users", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/auth/recovery/request": { + "post": { + "tags": ["auth"], + "description": "Request a recovery token", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["email"], + "properties": { + "email": { + "type": "string", + "format": "email" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/recovery/validate": { + "post": { + "tags": ["auth"], + "description": "Validate recovery token", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["recoveryToken"], + "properties": { + "recoveryToken": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/recovery/reset": { + "post": { + "tags": ["auth"], + "description": "Password reset", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["recoveryToken", "password"], + "properties": { + "recoveryToken": { + "type": "string" + }, + "password": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/invite": { + "post": { + "tags": ["invite"], + "description": "Request an invitation", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["email", "role"], + "properties": { + "email": { + "type": "string" + }, + "role": { + "type": "array" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/invite/verify": { + "post": { + "tags": ["invite"], + "description": "Request an invitation", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["token"], + "properties": { + "token": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors": { + "get": { + "tags": ["monitors"], + "description": "Get all monitors", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": ["monitors"], + "description": "Create a new monitor", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateMonitorBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": ["monitors"], + "description": "Delete all monitors", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/{monitorId}": { + "get": { + "tags": ["monitors"], + "description": "Get monitor by id", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "put": { + "tags": ["monitors"], + "description": "Update monitor by id", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateMonitorBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": ["monitors"], + "description": "Delete monitor by id", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/stats/{monitorId}": { + "get": { + "tags": ["monitors"], + "description": "Get monitor stats", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/certificate/{monitorId}": { + "get": { + "tags": ["monitors"], + "description": "Get monitor certificate", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/team/summary/{teamId}": { + "get": { + "tags": ["monitors"], + "description": "Get monitors and summary by teamId", + "parameters": [ + { + "name": "teamId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "required": false, + "schema": { + "type": "array", + "enum": ["http", "ping", "pagespeed"] + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/team/{teamId}": { + "get": { + "tags": ["monitors"], + "description": "Get monitors by teamId", + "parameters": [ + { + "name": "teamId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "status", + "description": "Status of monitor, true for up, false for down", + "in": "query", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "checkOrder", + "description": "Order of checks", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": ["asc", "desc"] + } + }, + { + "name": "limit", + "description": "Number of checks to return with monitor", + "in": "query", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "type", + "description": "Type of monitor", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": ["http", "ping", "pagespeed"] + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "rowsPerPage", + "in": "query", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "filter", + "description": "Value to filter by", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "field", + "description": "Field to filter on", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "order", + "description": "Sort order of results", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": ["http", "ping", "pagespeed"] + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/pause/{monitorId}": { + "post": { + "tags": ["monitors"], + "description": "Pause monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/demo": { + "post": { + "tags": ["monitors"], + "description": "Create a demo monitor", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateMonitorBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/checks/{monitorId}": { + "get": { + "tags": ["checks"], + "description": "Get all checks for a monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": ["checks"], + "description": "Create a new check", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCheckBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": ["checks"], + "description": "Delete all checks for a monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/checks/team/{teamId}": { + "get": { + "tags": ["checks"], + "description": "Get all checks for a team", + "parameters": [ + { + "name": "teamId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": ["checks"], + "description": "Delete all checks for a team", + "parameters": [ + { + "name": "teamId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/checks/team/ttl": { + "put": { + "tags": ["checks"], + "description": "Update check TTL", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCheckTTLBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/maintenance-window/monitor/{monitorId}": { + "get": { + "tags": ["maintenance-window"], + "description": "Get maintenance window for monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": ["maintenance-window"], + "description": "Create maintenance window for monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateMaintenanceWindowBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/maintenance-window/user/{userId}": { + "get": { + "tags": ["maintenance-window"], + "description": "Get maintenance window for user", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/queue/jobs": { + "get": { + "tags": ["queue"], + "description": "Get all jobs in queue", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": ["queue"], + "description": "Create a new job. Useful for testing scaling workers", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/queue/metrics": { + "get": { + "tags": ["queue"], + "description": "Get queue metrics", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/queue/obliterate": { + "post": { + "tags": ["queue"], + "description": "Obliterate job queue", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + } + }, - "components": { - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } - }, - "schemas": { - "ErrorResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "default": false - }, - "msg": { - "type": "string" - } - } - }, - "SuccessResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "default": true - }, - "msg": { - "type": "string" - }, - "data": { - "type": "object" - } - } - }, - "UserUpdateRequest": { - "type": "object", - "required": [ - "firstName", - "lastName", - "email", - "password", - "role", - "teamId" - ], - "properties": { - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "password": { - "type": "string", - "format": "password" - }, - "newPassword": { - "type": "string", - "format": "password" - }, - "profileImage": { - "type": "file", - "format": "file" - }, - "role": { - "type": "array", - "enum": [["user"], ["admin"], ["superadmin"], ["Demo"]], - "default": ["superadmin"] - }, - "deleteProfileImage": { - "type": "boolean" - } - } - }, - "CreateMonitorBody": { - "type": "object", - "required": ["userId", "teamId", "name", "description", "type", "url"], - "properties": { - "_id": { - "type": "string" - }, - "userId": { - "type": "string" - }, - "teamId": { - "type": "string" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["http", "ping", "pagespeed"] - }, - "url": { - "type": "string" - }, - "isActive": { - "type": "boolean" - }, - "interval": { - "type": "integer" - }, - "notifications": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "UpdateMonitorBody": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "interval": { - "type": "integer" - }, - "notifications": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "CreateCheckBody": { - "type": "object", - "required": [ - "monitorId", - "status", - "responseTime", - "statusCode", - "message" - ], - "properties": { - "monitorId": { - "type": "string" - }, - "status": { - "type": "boolean" - }, - "responseTime": { - "type": "integer" - }, - "statusCode": { - "type": "integer" - }, - "message": { - "type": "string" - } - } - }, - "UpdateCheckTTLBody": { - "type": "object", - "required": ["ttl"], - "properties": { - "ttl": { - "type": "integer" - } - } - }, - "CreateMaintenanceWindowBody": { - "type": "object", - "required": ["userId", "active", "oneTime", "start", "end"], - "properties": { - "userId": { - "type": "string" - }, - "active": { - "type": "boolean" - }, - "oneTime": { - "type": "boolean" - }, - "start": { - "type": "string", - "format": "date-time" - }, - "end": { - "type": "string", - "format": "date-time" - }, - "expiry": { - "type": "string", - "format": "date-time" - } - } - } - } - } + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + }, + "schemas": { + "ErrorResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "default": false + }, + "msg": { + "type": "string" + } + } + }, + "SuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "default": true + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object" + } + } + }, + "UserUpdateRequest": { + "type": "object", + "required": ["firstName", "lastName", "email", "password", "role", "teamId"], + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "password": { + "type": "string", + "format": "password" + }, + "newPassword": { + "type": "string", + "format": "password" + }, + "profileImage": { + "type": "file", + "format": "file" + }, + "role": { + "type": "array", + "enum": [["user"], ["admin"], ["superadmin"], ["Demo"]], + "default": ["superadmin"] + }, + "deleteProfileImage": { + "type": "boolean" + } + } + }, + "CreateMonitorBody": { + "type": "object", + "required": ["userId", "teamId", "name", "description", "type", "url"], + "properties": { + "_id": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "teamId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["http", "ping", "pagespeed"] + }, + "url": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "interval": { + "type": "integer" + }, + "notifications": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "UpdateMonitorBody": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "interval": { + "type": "integer" + }, + "notifications": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "CreateCheckBody": { + "type": "object", + "required": ["monitorId", "status", "responseTime", "statusCode", "message"], + "properties": { + "monitorId": { + "type": "string" + }, + "status": { + "type": "boolean" + }, + "responseTime": { + "type": "integer" + }, + "statusCode": { + "type": "integer" + }, + "message": { + "type": "string" + } + } + }, + "UpdateCheckTTLBody": { + "type": "object", + "required": ["ttl"], + "properties": { + "ttl": { + "type": "integer" + } + } + }, + "CreateMaintenanceWindowBody": { + "type": "object", + "required": ["userId", "active", "oneTime", "start", "end"], + "properties": { + "userId": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "oneTime": { + "type": "boolean" + }, + "start": { + "type": "string", + "format": "date-time" + }, + "end": { + "type": "string", + "format": "date-time" + }, + "expiry": { + "type": "string", + "format": "date-time" + } + } + } + } + } } diff --git a/Server/routes/checkRoute.js b/Server/routes/checkRoute.js index 7a29ae5bd..a57b7410d 100644 --- a/Server/routes/checkRoute.js +++ b/Server/routes/checkRoute.js @@ -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); diff --git a/Server/routes/inviteRoute.js b/Server/routes/inviteRoute.js index b64074751..f1f8a20e1 100644 --- a/Server/routes/inviteRoute.js +++ b/Server/routes/inviteRoute.js @@ -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; diff --git a/Server/routes/maintenanceWindowRoute.js b/Server/routes/maintenanceWindowRoute.js index 8db5b670d..e21e4b9ec 100644 --- a/Server/routes/maintenanceWindowRoute.js +++ b/Server/routes/maintenanceWindowRoute.js @@ -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); diff --git a/Server/routes/queueRoute.js b/Server/routes/queueRoute.js index 7d0a262ff..addd9dab8 100644 --- a/Server/routes/queueRoute.js +++ b/Server/routes/queueRoute.js @@ -1,9 +1,9 @@ import { Router } from "express"; import { - getMetrics, - getJobs, - addJob, - obliterateQueue, + getMetrics, + getJobs, + addJob, + obliterateQueue, } from "../controllers/queueController.js"; const router = Router(); diff --git a/Server/routes/settingsRoute.js b/Server/routes/settingsRoute.js index 6970f926b..1b8dc19ad 100644 --- a/Server/routes/settingsRoute.js +++ b/Server/routes/settingsRoute.js @@ -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(); diff --git a/Server/service/emailService.js b/Server/service/emailService.js index 7bcaa31f0..527b7915b 100644 --- a/Server/service/emailService.js +++ b/Server/service/emailService.js @@ -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.} - * 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.} + * 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} 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} 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; diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index b8e56a7d3..1e8c7f8e4 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -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} - 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} - 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} jobs - Array of jobs in the Queue - * @property {number} - workerLoad - The number of jobs per worker - * - */ + /** + * @typedef {Object} WorkerStats + * @property {Array} 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} - 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} - 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} */ - 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>} - * @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>} + * @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} - 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} - 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; diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 158bff62b..d2964bd25 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -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} 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} 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} 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} 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} 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} 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; diff --git a/Server/service/settingsService.js b/Server/service/settingsService.js index bac92cc12..130de2934 100644 --- a/Server/service/settingsService.js +++ b/Server/service/settingsService.js @@ -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} 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} 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} 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} 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; diff --git a/Server/templates/employeeActivation.mjml b/Server/templates/employeeActivation.mjml index 9d3dc773a..ebc306418 100644 --- a/Server/templates/employeeActivation.mjml +++ b/Server/templates/employeeActivation.mjml @@ -1,35 +1,52 @@ - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - -

Hello {{name}}!

-

One of the admins created an account for you on the BlueWave Uptime server.

-

You can go ahead and create your account using this link.

-

{{link}}

-

Thank you.

-
-
- - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + +

Hello {{name}}!

+

+ One of the admins created an account for you on the BlueWave Uptime server. +

+

You can go ahead and create your account using this link.

+

{{link}}

+

Thank you.

+
+
+ + + +

This email was sent by BlueWave Uptime.

+
+
+
+
+ diff --git a/Server/templates/noIncidentsThisWeek.mjml b/Server/templates/noIncidentsThisWeek.mjml index 960a876fe..ca9ac0f23 100644 --- a/Server/templates/noIncidentsThisWeek.mjml +++ b/Server/templates/noIncidentsThisWeek.mjml @@ -1,38 +1,66 @@ - - - - - - - - - - - - Message from BlueWave Uptime Service - - - No incidents this week! - - - - - - -

Hello {{name}}!

-

There were no incidents this week. Good job!

-

Current monitors:

-

Google: 100% availability

-

Canada.ca:100% availability

-
-
- - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file + + + + + + + + + + + + Message from BlueWave Uptime Service + + + No incidents this week! + + + + + + +

Hello {{name}}!

+

There were no incidents this week. Good job!

+

Current monitors:

+

Google: 100% availability

+

Canada.ca:100% availability

+
+
+ + + +

This email was sent by BlueWave Uptime.

+
+
+
+
+ diff --git a/Server/templates/passwordReset.mjml b/Server/templates/passwordReset.mjml index 480fcb572..4bc0ce80a 100644 --- a/Server/templates/passwordReset.mjml +++ b/Server/templates/passwordReset.mjml @@ -1,43 +1,56 @@ - - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - - -

Hello {{name}}!

-

- 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. -

- Reset Password -

If you didn't request this, please ignore this email.

+ + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + + +

Hello {{name}}!

+

+ 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. +

+ Reset Password +

If you didn't request this, please ignore this email.

-

Thank you.

-
-
- - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file +

Thank you.

+ + + + + +

This email was sent by BlueWave Uptime.

+
+
+ + + diff --git a/Server/templates/serverIsDown.mjml b/Server/templates/serverIsDown.mjml index 3caa6549c..ffe6ffc0e 100644 --- a/Server/templates/serverIsDown.mjml +++ b/Server/templates/serverIsDown.mjml @@ -1,57 +1,73 @@ - - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - Google.com is down - - - - - - - -

Hello {{name}}!

-

- 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. -

-

- Monitor name: {{monitor}} -

-

- URL: {{url}} -

-

- Problem: {{problem}} -

-

- Start date: {{startDate}} -

-
-
- - - - View incident details - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file + + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + Google.com is down + + + + + + + +

Hello {{name}}!

+

+ 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. +

+

Monitor name: {{monitor}}

+

URL: {{url}}

+

Problem: {{problem}}

+

Start date: {{startDate}}

+
+
+ + + View incident details + +

This email was sent by BlueWave Uptime.

+
+
+
+
+ diff --git a/Server/templates/serverIsUp.mjml b/Server/templates/serverIsUp.mjml index 9e73a8559..154ad44d9 100644 --- a/Server/templates/serverIsUp.mjml +++ b/Server/templates/serverIsUp.mjml @@ -1,63 +1,72 @@ - - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - {{monitor}} is up - - - - - - - -

Hello {{name}}!

-

- Your latest incident is resolved and your monitored service is - up again. -

-

- Monitor name: {{monitor}} -

-

- URL: {{url}} -

-

- Problem: {{problem}} -

-

- Start date: {{startDate}} -

-

- Resolved date: {{resolvedDate}} -

-

- Duration:{{duration}} -

-
-
- - - - View incident details - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file + + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + {{monitor}} is up + + + + + + + +

Hello {{name}}!

+

Your latest incident is resolved and your monitored service is up again.

+

Monitor name: {{monitor}}

+

URL: {{url}}

+

Problem: {{problem}}

+

Start date: {{startDate}}

+

Resolved date: {{resolvedDate}}

+

Duration:{{duration}}

+
+
+ + + View incident details + +

This email was sent by BlueWave Uptime.

+
+
+
+
+ diff --git a/Server/templates/welcomeEmail.mjml b/Server/templates/welcomeEmail.mjml index a58c9f017..27b816aa3 100644 --- a/Server/templates/welcomeEmail.mjml +++ b/Server/templates/welcomeEmail.mjml @@ -1,44 +1,57 @@ - - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - - -

Hello {{name}}!

-

- 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. -

-

- BlueWave Uptime is an automated way of checking whether a - service such as a website or an application is available or not. -

-

We hope you find our service as valuable as we do.

-

Thank you.

-
-
- - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file + + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + + +

Hello {{name}}!

+

+ 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. +

+

+ BlueWave Uptime is an automated way of checking whether a service such as a + website or an application is available or not. +

+

We hope you find our service as valuable as we do.

+

Thank you.

+
+
+ + + +

This email was sent by BlueWave Uptime.

+
+
+
+
+ diff --git a/Server/tests/controllers/checkController.test.js b/Server/tests/controllers/checkController.test.js index b6870086e..88e7d721e 100644 --- a/Server/tests/controllers/checkController.test.js +++ b/Server/tests/controllers/checkController.test.js @@ -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; + }); }); diff --git a/Server/tests/controllers/inviteController.test.js b/Server/tests/controllers/inviteController.test.js index d08d4ec78..13e5ede51 100644 --- a/Server/tests/controllers/inviteController.test.js +++ b/Server/tests/controllers/inviteController.test.js @@ -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; + }); }); diff --git a/Server/tests/controllers/maintenanceWindowController.test.js b/Server/tests/controllers/maintenanceWindowController.test.js index 2c24c0f22..92bab9a77 100644 --- a/Server/tests/controllers/maintenanceWindowController.test.js +++ b/Server/tests/controllers/maintenanceWindowController.test.js @@ -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; + }); }); diff --git a/Server/tests/controllers/queueController.test.js b/Server/tests/controllers/queueController.test.js index b3f3ac63a..f9bd46ea1 100644 --- a/Server/tests/controllers/queueController.test.js +++ b/Server/tests/controllers/queueController.test.js @@ -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, + }); + }); }); diff --git a/Server/tests/controllers/settingsController.test.js b/Server/tests/controllers/settingsController.test.js index c491b22a5..b36addf38 100644 --- a/Server/tests/controllers/settingsController.test.js +++ b/Server/tests/controllers/settingsController.test.js @@ -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, + }); + }); }); diff --git a/Server/utils/demoMonitors.json b/Server/utils/demoMonitors.json index 6f57940f2..ffce7d226 100644 --- a/Server/utils/demoMonitors.json +++ b/Server/utils/demoMonitors.json @@ -1,1271 +1,1271 @@ [ - { - "name": "0to255", - "url": "https://0to255.com" - }, - { - "name": "10015.io", - "url": "https://10015.io" - }, - { - "name": "3DIcons", - "url": "https://3dicons.co" - }, - { - "name": "About.me", - "url": "https://about.me" - }, - { - "name": "Alias", - "url": "https://alias.co" - }, - { - "name": "All About Berlin", - "url": "https://allaboutberlin.com" - }, - { - "name": "All Acronyms", - "url": "https://allacronyms.com" - }, - { - "name": "All You Can Read ", - "url": "https://allyoucanread.com" - }, - { - "name": "AllTrails", - "url": "https://alltrails.com" - }, - { - "name": "Anotepad", - "url": "https://anotepad.com" - }, - { - "name": "AnswerSocrates", - "url": "https://answersocrates.com" - }, - { - "name": "AnswerThePublic ", - "url": "https://answerthepublic.com" - }, - { - "name": "Apollo ", - "url": "https://apollo.io" - }, - { - "name": "ArrayList", - "url": "https://arraylist.org" - }, - { - "name": "Ask Difference", - "url": "https://askdifference.com" - }, - { - "name": "Audd.io", - "url": "https://audd.io" - }, - { - "name": "Audiocheck", - "url": "https://audiocheck.net" - }, - { - "name": "Audionautix", - "url": "https://audionautix.com" - }, - { - "name": "Authentic Jobs", - "url": "https://authenticjobs.com" - }, - { - "name": "Behind the Name", - "url": "https://behindthename.com" - }, - { - "name": "Bilim Terimleri", - "url": "https://terimler.org" - }, - { - "name": "BitBof", - "url": "https://bitbof.com" - }, - { - "name": "Blank Page", - "url": "https://blank.page" - }, - { - "name": "Bonanza", - "url": "https://bonanza.com" - }, - { - "name": "BookCrossing", - "url": "https://bookcrossing.com" - }, - { - "name": "Browse AI", - "url": "https://browse.ai" - }, - { - "name": "Bubbl.us", - "url": "https://bubbl.us" - }, - { - "name": "Business Model Toolbox", - "url": "https://bmtoolbox.net" - }, - { - "name": "ByClickDownloader", - "url": "https://byclickdownloader.com" - }, - { - "name": "Calligraphr", - "url": "https://calligraphr.com" - }, - { - "name": "CertificateClaim", - "url": "https://certificateclaim.com" - }, - { - "name": "Chosic", - "url": "https://chosic.com" - }, - { - "name": "ClipDrop", - "url": "https://clipdrop.co" - }, - { - "name": "CloudConvert", - "url": "https://cloudconvert.com" - }, - { - "name": "CodingFont", - "url": "https://codingfont.com" - }, - { - "name": "Color Hunt", - "url": "https://colorhunt.co" - }, - { - "name": "ColorHexa", - "url": "https://colorhexa.com" - }, - { - "name": "Conversion-Tool", - "url": "https://conversion-tool.com" - }, - { - "name": "Cool Startup Jobs", - "url": "https://coolstartupjobs.com" - }, - { - "name": "Coroflot", - "url": "https://coroflot.com" - }, - { - "name": "Corrupt-a-File", - "url": "https://corrupt-a-file.net" - }, - { - "name": "Couchsurfing", - "url": "https://couchsurfing.com" - }, - { - "name": "Countries Been", - "url": "https://countriesbeen.com" - }, - { - "name": "Country Code", - "url": "https://countrycode.org" - }, - { - "name": "Creately", - "url": "https://creately.com" - }, - { - "name": "Creately ", - "url": "https://creately.com" - }, - { - "name": "Crossfade.io", - "url": "https://crossfade.io" - }, - { - "name": "Crunchbase", - "url": "https://crunchbase.com" - }, - { - "name": "CVmkr", - "url": "https://cvwizard.com" - }, - { - "name": "Daily Remote", - "url": "https://dailyremote.com" - }, - { - "name": "David Li", - "url": "https://david.li" - }, - { - "name": "DemandHunt", - "url": "https://demandhunt.com" - }, - { - "name": "Designify", - "url": "https://designify.com" - }, - { - "name": "Diff Checker", - "url": "https://diffchecker.com" - }, - { - "name": "DifferenceBetween.info", - "url": "https://differencebetween.info" - }, - { - "name": "Digital Glossary", - "url": "https://digital-glossary.com" - }, - { - "name": "Dimensions", - "url": "https://dimensions.com" - }, - { - "name": "Discoverify Music", - "url": "https://discoverifymusic.com" - }, - { - "name": "discu.eu", - "url": "https://discu.eu" - }, - { - "name": "Do It Yourself", - "url": "https://doityourself.com" - }, - { - "name": "draw.io", - "url": "https://drawio.com" - }, - { - "name": "Drumeo", - "url": "https://drumeo.com" - }, - { - "name": "Dummies", - "url": "https://dummies.com" - }, - { - "name": "Easel.ly", - "url": "https://easel.ly" - }, - { - "name": "Educalingo", - "url": "https://educalingo.com" - }, - { - "name": "Emoji Combos", - "url": "https://emojicombos.com" - }, - { - "name": "EquityBee", - "url": "https://equitybee.com" - }, - { - "name": "EquityZen", - "url": "https://equityzen.com" - }, - { - "name": "Escape Room Tips", - "url": "https://escaperoomtips.com" - }, - { - "name": "Every Noise", - "url": "https://everynoise.com" - }, - { - "name": "Every Time Zone", - "url": "https://everytimezone.com" - }, - { - "name": "Excalideck", - "url": "https://excalideck.com" - }, - { - "name": "Excalidraw", - "url": "https://excalidraw.com" - }, - { - "name": "Extract pics", - "url": "https://extract.pics" - }, - { - "name": "EZGIF", - "url": "https://ezgif.com" - }, - { - "name": "FactSlides", - "url": "https://factslides.com" - }, - { - "name": "FIGR ", - "url": "https://figr.app" - }, - { - "name": "Fine Dictionary", - "url": "https://finedictionary.com" - }, - { - "name": "Fiverr", - "url": "https://fiverr.com" - }, - { - "name": "Fix It Club", - "url": "https://fixitclub.com" - }, - { - "name": "Flightradar24", - "url": "https://flightradar24.com" - }, - { - "name": "FlowCV ", - "url": "https://flowcv.com" - }, - { - "name": "Font Squirrel", - "url": "https://fontsquirrel.com" - }, - { - "name": "FontAwesome", - "url": "https://fontawesome.com" - }, - { - "name": "Fontello ", - "url": "https://fontello.com" - }, - { - "name": "Form to Chatbot", - "url": "https://formtochatbot.com" - }, - { - "name": "Founder Resources", - "url": "https://founderresources.io" - }, - { - "name": "Franz", - "url": "https://meetfranz.com" - }, - { - "name": "Fraze It", - "url": "https://fraze.it" - }, - { - "name": "Freecycle", - "url": "https://freecycle.org" - }, - { - "name": "FreeType", - "url": "https://freetype.org" - }, - { - "name": "FutureM", - "url": "https://futureme.org" - }, - { - "name": "Generated.Photos", - "url": "https://generated.photos" - }, - { - "name": "Get Human", - "url": "https://gethuman.com" - }, - { - "name": "Go Bento", - "url": "https://gobento.com" - }, - { - "name": "Good CV", - "url": "https://goodcv.com" - }, - { - "name": "Grammar Monster", - "url": "https://grammar-monster.com" - }, - { - "name": "Grammar Book", - "url": "https://grammarbook.com" - }, - { - "name": "Gummy Search", - "url": "https://gummysearch.com" - }, - { - "name": "Gumroad", - "url": "https://gumroad.com" - }, - { - "name": "HealthIcons", - "url": "https://healthicons.org" - }, - { - "name": "HexColor", - "url": "https://hexcolor.co" - }, - { - "name": "Hidden Life Radio", - "url": "https://hiddenliferadio.com" - }, - { - "name": "Hired", - "url": "https://lhh.com" - }, - { - "name": "Honey", - "url": "https://joinhoney.com" - }, - { - "name": "HowStuffWorks", - "url": "https://howstuffworks.com" - }, - { - "name": "HugeIcons Pro", - "url": "https://hugeicons.com" - }, - { - "name": "Humble Bundle", - "url": "https://humblebundle.com" - }, - { - "name": "I Have No TV", - "url": "https://ihavenotv.com" - }, - { - "name": "I Miss the Office", - "url": "https://imisstheoffice.eu" - }, - { - "name": "IcoMoon", - "url": "https://icomoon.io" - }, - { - "name": "Iconfinder", - "url": "https://iconfinder.com" - }, - { - "name": "Icon Packs", - "url": "https://iconpacks.net" - }, - { - "name": "Iconshock", - "url": "https://iconshock.com" - }, - { - "name": "Iconz Design", - "url": "https://iconz.design" - }, - { - "name": "iFixit", - "url": "https://ifixit.com" - }, - { - "name": "IFTTT", - "url": "https://ifttt.com" - }, - { - "name": "Illlustrations", - "url": "https://illlustrations.co" - }, - { - "name": "Illustration Kit", - "url": "https://illustrationkit.com" - }, - { - "name": "IMSDB", - "url": "https://imsdb.com" - }, - { - "name": "Incompetech", - "url": "https://incompetech.com" - }, - { - "name": "Incredibox", - "url": "https://incredibox.com" - }, - { - "name": "InnerBod", - "url": "https://innerbody.com" - }, - { - "name": "Instructables", - "url": "https://instructables.com" - }, - { - "name": "Integromat", - "url": "https://make.com" - }, - { - "name": "Investopedia", - "url": "https://investopedia.com" - }, - { - "name": "Japanese Wiki Corpus", - "url": "https://japanesewiki.com" - }, - { - "name": "Jitter.Video", - "url": "https://jitter.video" - }, - { - "name": "Jobspresso", - "url": "https://jobspresso.co" - }, - { - "name": "JPEG-Optimizer", - "url": "https://jpeg-optimizer.com" - }, - { - "name": "JS Remotely", - "url": "https://jsremotely.com" - }, - { - "name": "JScreenFix", - "url": "https://jscreenfix.com" - }, - { - "name": "JSON Resume", - "url": "https://jsonresume.io" - }, - { - "name": "Just Join", - "url": "https://justjoin.it" - }, - { - "name": "Just the Recipe", - "url": "https://justtherecipe.com" - }, - { - "name": "JustRemote", - "url": "https://justremote.co" - }, - { - "name": "JustWatch", - "url": "https://justwatch.com" - }, - { - "name": "Kanopy", - "url": "https://kanopy.com" - }, - { - "name": "Kassellabs", - "url": "https://kassellabs.io" - }, - { - "name": "Key Differences", - "url": "https://keydifferences.com" - }, - { - "name": "Keybase", - "url": "https://keybase.io" - }, - { - "name": "KeyValues", - "url": "https://keyvalues.com" - }, - { - "name": "KHInsider", - "url": "https://khinsider.com" - }, - { - "name": "Killed by Google", - "url": "https://killedbygoogle.com" - }, - { - "name": "Kimovil", - "url": "https://kimovil.com" - }, - { - "name": "Lalal.ai", - "url": "https://www.lalal.ai" - }, - { - "name": "Learn Anything", - "url": "https://learn-anything.xyz" - }, - { - "name": "LendingTree", - "url": "https://lendingtree.com" - }, - { - "name": "Lightyear.fm", - "url": "https://lightyear.fm" - }, - { - "name": "LittleSis", - "url": "https://littlesis.org" - }, - { - "name": "Looria", - "url": "https://looria.com" - }, - { - "name": "Lucidchart", - "url": "https://lucidchart.com" - }, - { - "name": "Lunar", - "url": "https://lunar.fyi" - }, - { - "name": "Manuals Lib", - "url": "https://manualslib.com" - }, - { - "name": "Map Crunch", - "url": "https://mapcrunch.com" - }, - { - "name": "Masterworks", - "url": "https://masterworks.com" - }, - { - "name": "MediaFire", - "url": "https://mediafire.com" - }, - { - "name": "Mixlr", - "url": "https://mixlr.com" - }, - { - "name": "Moises AI", - "url": "https://moises.ai" - }, - { - "name": "Money", - "url": "https://money.com" - }, - { - "name": "Mountain Project", - "url": "https://mountainproject.com" - }, - { - "name": "Movie Map", - "url": "https://movie-map.com" - }, - { - "name": "Movie Sounds", - "url": "https://movie-sounds.org" - }, - { - "name": "MP3Cut", - "url": "https://mp3cut.net" - }, - { - "name": "Murmel", - "url": "https://murmel.social" - }, - { - "name": "Muscle Wiki", - "url": "https://musclewiki.com" - }, - { - "name": "Music-Map", - "url": "https://music-map.com" - }, - { - "name": "MusicTheory.net", - "url": "https://musictheory.net" - }, - { - "name": "MyFonts", - "url": "https://myfonts.com" - }, - { - "name": "MyFridgeFood", - "url": "https://myfridgefood.com" - }, - { - "name": "Nameberry", - "url": "https://nameberry.com" - }, - { - "name": "Namechk", - "url": "https://namechk.com" - }, - { - "name": "Ncase", - "url": "https://ncase.me" - }, - { - "name": "News in Levels", - "url": "https://newsinlevels.com" - }, - { - "name": "Noisli", - "url": "https://noisli.com" - }, - { - "name": "Notes.io", - "url": "https://notes.io" - }, - { - "name": "Novoresume", - "url": "https://novoresume.com" - }, - { - "name": "Ocoya", - "url": "https://ocoya.com" - }, - { - "name": "Old Computers Museum", - "url": "https://oldcomputers.net" - }, - { - "name": "Online Tone Generator", - "url": "https://onlinetonegenerator.com" - }, - { - "name": "Online-Convert", - "url": "https://online-convert.com" - }, - { - "name": "OnlineConversion", - "url": "https://onlineconversion.com" - }, - { - "name": "Online OCR", - "url": "https://onlineocr.net" - }, - { - "name": "OpenWeatherMap", - "url": "https://openweathermap.org" - }, - { - "name": "OrgPad", - "url": "https://orgpad.com" - }, - { - "name": "Passport Index", - "url": "https://passportindex.org" - }, - { - "name": "PDF Candy", - "url": "https://pdfcandy.com" - }, - { - "name": "PDF2DOC", - "url": "https://pdf2doc.com" - }, - { - "name": "PDFescape", - "url": "https://pdfescape.com" - }, - { - "name": "PfpMaker", - "url": "https://pfpmaker.com" - }, - { - "name": "PIDGI Wiki ", - "url": "https://pidgi.net" - }, - { - "name": "PimEyes", - "url": "https://pimeyes.com" - }, - { - "name": "Pipl ", - "url": "https://pipl.com" - }, - { - "name": "PixelBazaar", - "url": "https://pixelbazaar.com" - }, - { - "name": "PixelPaper", - "url": "https://pixelpaper.io" - }, - { - "name": "Ponly", - "url": "https://ponly.com" - }, - { - "name": "PowerToFly", - "url": "https://powertofly.com" - }, - { - "name": "Pretzel Rocks", - "url": "https://pretzel.rocks" - }, - { - "name": "PrintIt", - "url": "https://printit.work" - }, - { - "name": "Prismatext", - "url": "https://prismatext.com" - }, - { - "name": "Puffin Maps", - "url": "https://puffinmaps.com" - }, - { - "name": "Puzzle Loop ", - "url": "https://puzzle-loop.com" - }, - { - "name": "QuoteMaster", - "url": "https://quotemaster.org" - }, - { - "name": "Radio Garden", - "url": "https://radio.garden" - }, - { - "name": "Radiooooo", - "url": "https://radiooooo.com" - }, - { - "name": "Radiosondy", - "url": "https://radiosondy.info" - }, - { - "name": "Rainy Mood", - "url": "https://rainymood.com" - }, - { - "name": "Random Street View", - "url": "https://randomstreetview.com" - }, - { - "name": "Rap4Ever", - "url": "https://rap4all.com" - }, - { - "name": "RareFilm", - "url": "https://rarefilm.net" - }, - { - "name": "Rattibha", - "url": "https://rattibha.com" - }, - { - "name": "Reddit List ", - "url": "https://redditlist.com" - }, - { - "name": "RedditSearch.io", - "url": "https://redditsearch.io" - }, - { - "name": "Reelgood", - "url": "https://reelgood.com" - }, - { - "name": "Reface", - "url": "https://reface.ai" - }, - { - "name": "Rejected.us", - "url": "https://rejected.us" - }, - { - "name": "Relanote", - "url": "https://relanote.com" - }, - { - "name": "Remote Leaf", - "url": "https://remoteleaf.com" - }, - { - "name": "Remote OK", - "url": "https://remoteok.com" - }, - { - "name": "Remote Starter Kit ", - "url": "https://remotestarterkit.com" - }, - { - "name": "Remote.co", - "url": "https://remote.co" - }, - { - "name": "Remote Base ", - "url": "https://remotebase.com" - }, - { - "name": "Remote Bear", - "url": "https://remotebear.io" - }, - { - "name": "Remove.bg", - "url": "https://remove.bg" - }, - { - "name": "Respresso", - "url": "https://respresso.io" - }, - { - "name": "Reveddit", - "url": "https://reveddit.com" - }, - { - "name": "Rhymer", - "url": "https://rhymer.com" - }, - { - "name": "RhymeZone", - "url": "https://rhymezone.com" - }, - { - "name": "Ribbet", - "url": "https://ribbet.com" - }, - { - "name": "Roadmap.sh", - "url": "https://roadmap.sh" - }, - { - "name": "Roadtrippers", - "url": "https://roadtrippers.com" - }, - { - "name": "RxResu.me", - "url": "https://rxresu.me" - }, - { - "name": "SchemeColor", - "url": "https://schemecolor.com" - }, - { - "name": "Screenshot.Guru", - "url": "https://screenshot.guru" - }, - { - "name": "SeatGuru", - "url": "https://seatguru.com" - }, - { - "name": "Sessions", - "url": "https://sessions.us" - }, - { - "name": "Shottr", - "url": "https://shottr.cc" - }, - { - "name": "Signature Maker", - "url": "https://signature-maker.net" - }, - { - "name": "Skip The Drive", - "url": "https://skipthedrive.com" - }, - { - "name": "Slowby", - "url": "https://slowby.travel" - }, - { - "name": "Small World", - "url": "https://smallworld.kiwi" - }, - { - "name": "SmallPDF", - "url": "https://smallpdf.com" - }, - { - "name": "Social Image Maker", - "url": "https://socialimagemaker.io" - }, - { - "name": "Social Sizes", - "url": "https://socialsizes.io" - }, - { - "name": "SoundLove", - "url": "https://soundlove.se" - }, - { - "name": "Spline", - "url": "https://spline.design" - }, - { - "name": "Starkey Comics", - "url": "https://starkeycomics.com" - }, + { + "name": "0to255", + "url": "https://0to255.com" + }, + { + "name": "10015.io", + "url": "https://10015.io" + }, + { + "name": "3DIcons", + "url": "https://3dicons.co" + }, + { + "name": "About.me", + "url": "https://about.me" + }, + { + "name": "Alias", + "url": "https://alias.co" + }, + { + "name": "All About Berlin", + "url": "https://allaboutberlin.com" + }, + { + "name": "All Acronyms", + "url": "https://allacronyms.com" + }, + { + "name": "All You Can Read ", + "url": "https://allyoucanread.com" + }, + { + "name": "AllTrails", + "url": "https://alltrails.com" + }, + { + "name": "Anotepad", + "url": "https://anotepad.com" + }, + { + "name": "AnswerSocrates", + "url": "https://answersocrates.com" + }, + { + "name": "AnswerThePublic ", + "url": "https://answerthepublic.com" + }, + { + "name": "Apollo ", + "url": "https://apollo.io" + }, + { + "name": "ArrayList", + "url": "https://arraylist.org" + }, + { + "name": "Ask Difference", + "url": "https://askdifference.com" + }, + { + "name": "Audd.io", + "url": "https://audd.io" + }, + { + "name": "Audiocheck", + "url": "https://audiocheck.net" + }, + { + "name": "Audionautix", + "url": "https://audionautix.com" + }, + { + "name": "Authentic Jobs", + "url": "https://authenticjobs.com" + }, + { + "name": "Behind the Name", + "url": "https://behindthename.com" + }, + { + "name": "Bilim Terimleri", + "url": "https://terimler.org" + }, + { + "name": "BitBof", + "url": "https://bitbof.com" + }, + { + "name": "Blank Page", + "url": "https://blank.page" + }, + { + "name": "Bonanza", + "url": "https://bonanza.com" + }, + { + "name": "BookCrossing", + "url": "https://bookcrossing.com" + }, + { + "name": "Browse AI", + "url": "https://browse.ai" + }, + { + "name": "Bubbl.us", + "url": "https://bubbl.us" + }, + { + "name": "Business Model Toolbox", + "url": "https://bmtoolbox.net" + }, + { + "name": "ByClickDownloader", + "url": "https://byclickdownloader.com" + }, + { + "name": "Calligraphr", + "url": "https://calligraphr.com" + }, + { + "name": "CertificateClaim", + "url": "https://certificateclaim.com" + }, + { + "name": "Chosic", + "url": "https://chosic.com" + }, + { + "name": "ClipDrop", + "url": "https://clipdrop.co" + }, + { + "name": "CloudConvert", + "url": "https://cloudconvert.com" + }, + { + "name": "CodingFont", + "url": "https://codingfont.com" + }, + { + "name": "Color Hunt", + "url": "https://colorhunt.co" + }, + { + "name": "ColorHexa", + "url": "https://colorhexa.com" + }, + { + "name": "Conversion-Tool", + "url": "https://conversion-tool.com" + }, + { + "name": "Cool Startup Jobs", + "url": "https://coolstartupjobs.com" + }, + { + "name": "Coroflot", + "url": "https://coroflot.com" + }, + { + "name": "Corrupt-a-File", + "url": "https://corrupt-a-file.net" + }, + { + "name": "Couchsurfing", + "url": "https://couchsurfing.com" + }, + { + "name": "Countries Been", + "url": "https://countriesbeen.com" + }, + { + "name": "Country Code", + "url": "https://countrycode.org" + }, + { + "name": "Creately", + "url": "https://creately.com" + }, + { + "name": "Creately ", + "url": "https://creately.com" + }, + { + "name": "Crossfade.io", + "url": "https://crossfade.io" + }, + { + "name": "Crunchbase", + "url": "https://crunchbase.com" + }, + { + "name": "CVmkr", + "url": "https://cvwizard.com" + }, + { + "name": "Daily Remote", + "url": "https://dailyremote.com" + }, + { + "name": "David Li", + "url": "https://david.li" + }, + { + "name": "DemandHunt", + "url": "https://demandhunt.com" + }, + { + "name": "Designify", + "url": "https://designify.com" + }, + { + "name": "Diff Checker", + "url": "https://diffchecker.com" + }, + { + "name": "DifferenceBetween.info", + "url": "https://differencebetween.info" + }, + { + "name": "Digital Glossary", + "url": "https://digital-glossary.com" + }, + { + "name": "Dimensions", + "url": "https://dimensions.com" + }, + { + "name": "Discoverify Music", + "url": "https://discoverifymusic.com" + }, + { + "name": "discu.eu", + "url": "https://discu.eu" + }, + { + "name": "Do It Yourself", + "url": "https://doityourself.com" + }, + { + "name": "draw.io", + "url": "https://drawio.com" + }, + { + "name": "Drumeo", + "url": "https://drumeo.com" + }, + { + "name": "Dummies", + "url": "https://dummies.com" + }, + { + "name": "Easel.ly", + "url": "https://easel.ly" + }, + { + "name": "Educalingo", + "url": "https://educalingo.com" + }, + { + "name": "Emoji Combos", + "url": "https://emojicombos.com" + }, + { + "name": "EquityBee", + "url": "https://equitybee.com" + }, + { + "name": "EquityZen", + "url": "https://equityzen.com" + }, + { + "name": "Escape Room Tips", + "url": "https://escaperoomtips.com" + }, + { + "name": "Every Noise", + "url": "https://everynoise.com" + }, + { + "name": "Every Time Zone", + "url": "https://everytimezone.com" + }, + { + "name": "Excalideck", + "url": "https://excalideck.com" + }, + { + "name": "Excalidraw", + "url": "https://excalidraw.com" + }, + { + "name": "Extract pics", + "url": "https://extract.pics" + }, + { + "name": "EZGIF", + "url": "https://ezgif.com" + }, + { + "name": "FactSlides", + "url": "https://factslides.com" + }, + { + "name": "FIGR ", + "url": "https://figr.app" + }, + { + "name": "Fine Dictionary", + "url": "https://finedictionary.com" + }, + { + "name": "Fiverr", + "url": "https://fiverr.com" + }, + { + "name": "Fix It Club", + "url": "https://fixitclub.com" + }, + { + "name": "Flightradar24", + "url": "https://flightradar24.com" + }, + { + "name": "FlowCV ", + "url": "https://flowcv.com" + }, + { + "name": "Font Squirrel", + "url": "https://fontsquirrel.com" + }, + { + "name": "FontAwesome", + "url": "https://fontawesome.com" + }, + { + "name": "Fontello ", + "url": "https://fontello.com" + }, + { + "name": "Form to Chatbot", + "url": "https://formtochatbot.com" + }, + { + "name": "Founder Resources", + "url": "https://founderresources.io" + }, + { + "name": "Franz", + "url": "https://meetfranz.com" + }, + { + "name": "Fraze It", + "url": "https://fraze.it" + }, + { + "name": "Freecycle", + "url": "https://freecycle.org" + }, + { + "name": "FreeType", + "url": "https://freetype.org" + }, + { + "name": "FutureM", + "url": "https://futureme.org" + }, + { + "name": "Generated.Photos", + "url": "https://generated.photos" + }, + { + "name": "Get Human", + "url": "https://gethuman.com" + }, + { + "name": "Go Bento", + "url": "https://gobento.com" + }, + { + "name": "Good CV", + "url": "https://goodcv.com" + }, + { + "name": "Grammar Monster", + "url": "https://grammar-monster.com" + }, + { + "name": "Grammar Book", + "url": "https://grammarbook.com" + }, + { + "name": "Gummy Search", + "url": "https://gummysearch.com" + }, + { + "name": "Gumroad", + "url": "https://gumroad.com" + }, + { + "name": "HealthIcons", + "url": "https://healthicons.org" + }, + { + "name": "HexColor", + "url": "https://hexcolor.co" + }, + { + "name": "Hidden Life Radio", + "url": "https://hiddenliferadio.com" + }, + { + "name": "Hired", + "url": "https://lhh.com" + }, + { + "name": "Honey", + "url": "https://joinhoney.com" + }, + { + "name": "HowStuffWorks", + "url": "https://howstuffworks.com" + }, + { + "name": "HugeIcons Pro", + "url": "https://hugeicons.com" + }, + { + "name": "Humble Bundle", + "url": "https://humblebundle.com" + }, + { + "name": "I Have No TV", + "url": "https://ihavenotv.com" + }, + { + "name": "I Miss the Office", + "url": "https://imisstheoffice.eu" + }, + { + "name": "IcoMoon", + "url": "https://icomoon.io" + }, + { + "name": "Iconfinder", + "url": "https://iconfinder.com" + }, + { + "name": "Icon Packs", + "url": "https://iconpacks.net" + }, + { + "name": "Iconshock", + "url": "https://iconshock.com" + }, + { + "name": "Iconz Design", + "url": "https://iconz.design" + }, + { + "name": "iFixit", + "url": "https://ifixit.com" + }, + { + "name": "IFTTT", + "url": "https://ifttt.com" + }, + { + "name": "Illlustrations", + "url": "https://illlustrations.co" + }, + { + "name": "Illustration Kit", + "url": "https://illustrationkit.com" + }, + { + "name": "IMSDB", + "url": "https://imsdb.com" + }, + { + "name": "Incompetech", + "url": "https://incompetech.com" + }, + { + "name": "Incredibox", + "url": "https://incredibox.com" + }, + { + "name": "InnerBod", + "url": "https://innerbody.com" + }, + { + "name": "Instructables", + "url": "https://instructables.com" + }, + { + "name": "Integromat", + "url": "https://make.com" + }, + { + "name": "Investopedia", + "url": "https://investopedia.com" + }, + { + "name": "Japanese Wiki Corpus", + "url": "https://japanesewiki.com" + }, + { + "name": "Jitter.Video", + "url": "https://jitter.video" + }, + { + "name": "Jobspresso", + "url": "https://jobspresso.co" + }, + { + "name": "JPEG-Optimizer", + "url": "https://jpeg-optimizer.com" + }, + { + "name": "JS Remotely", + "url": "https://jsremotely.com" + }, + { + "name": "JScreenFix", + "url": "https://jscreenfix.com" + }, + { + "name": "JSON Resume", + "url": "https://jsonresume.io" + }, + { + "name": "Just Join", + "url": "https://justjoin.it" + }, + { + "name": "Just the Recipe", + "url": "https://justtherecipe.com" + }, + { + "name": "JustRemote", + "url": "https://justremote.co" + }, + { + "name": "JustWatch", + "url": "https://justwatch.com" + }, + { + "name": "Kanopy", + "url": "https://kanopy.com" + }, + { + "name": "Kassellabs", + "url": "https://kassellabs.io" + }, + { + "name": "Key Differences", + "url": "https://keydifferences.com" + }, + { + "name": "Keybase", + "url": "https://keybase.io" + }, + { + "name": "KeyValues", + "url": "https://keyvalues.com" + }, + { + "name": "KHInsider", + "url": "https://khinsider.com" + }, + { + "name": "Killed by Google", + "url": "https://killedbygoogle.com" + }, + { + "name": "Kimovil", + "url": "https://kimovil.com" + }, + { + "name": "Lalal.ai", + "url": "https://www.lalal.ai" + }, + { + "name": "Learn Anything", + "url": "https://learn-anything.xyz" + }, + { + "name": "LendingTree", + "url": "https://lendingtree.com" + }, + { + "name": "Lightyear.fm", + "url": "https://lightyear.fm" + }, + { + "name": "LittleSis", + "url": "https://littlesis.org" + }, + { + "name": "Looria", + "url": "https://looria.com" + }, + { + "name": "Lucidchart", + "url": "https://lucidchart.com" + }, + { + "name": "Lunar", + "url": "https://lunar.fyi" + }, + { + "name": "Manuals Lib", + "url": "https://manualslib.com" + }, + { + "name": "Map Crunch", + "url": "https://mapcrunch.com" + }, + { + "name": "Masterworks", + "url": "https://masterworks.com" + }, + { + "name": "MediaFire", + "url": "https://mediafire.com" + }, + { + "name": "Mixlr", + "url": "https://mixlr.com" + }, + { + "name": "Moises AI", + "url": "https://moises.ai" + }, + { + "name": "Money", + "url": "https://money.com" + }, + { + "name": "Mountain Project", + "url": "https://mountainproject.com" + }, + { + "name": "Movie Map", + "url": "https://movie-map.com" + }, + { + "name": "Movie Sounds", + "url": "https://movie-sounds.org" + }, + { + "name": "MP3Cut", + "url": "https://mp3cut.net" + }, + { + "name": "Murmel", + "url": "https://murmel.social" + }, + { + "name": "Muscle Wiki", + "url": "https://musclewiki.com" + }, + { + "name": "Music-Map", + "url": "https://music-map.com" + }, + { + "name": "MusicTheory.net", + "url": "https://musictheory.net" + }, + { + "name": "MyFonts", + "url": "https://myfonts.com" + }, + { + "name": "MyFridgeFood", + "url": "https://myfridgefood.com" + }, + { + "name": "Nameberry", + "url": "https://nameberry.com" + }, + { + "name": "Namechk", + "url": "https://namechk.com" + }, + { + "name": "Ncase", + "url": "https://ncase.me" + }, + { + "name": "News in Levels", + "url": "https://newsinlevels.com" + }, + { + "name": "Noisli", + "url": "https://noisli.com" + }, + { + "name": "Notes.io", + "url": "https://notes.io" + }, + { + "name": "Novoresume", + "url": "https://novoresume.com" + }, + { + "name": "Ocoya", + "url": "https://ocoya.com" + }, + { + "name": "Old Computers Museum", + "url": "https://oldcomputers.net" + }, + { + "name": "Online Tone Generator", + "url": "https://onlinetonegenerator.com" + }, + { + "name": "Online-Convert", + "url": "https://online-convert.com" + }, + { + "name": "OnlineConversion", + "url": "https://onlineconversion.com" + }, + { + "name": "Online OCR", + "url": "https://onlineocr.net" + }, + { + "name": "OpenWeatherMap", + "url": "https://openweathermap.org" + }, + { + "name": "OrgPad", + "url": "https://orgpad.com" + }, + { + "name": "Passport Index", + "url": "https://passportindex.org" + }, + { + "name": "PDF Candy", + "url": "https://pdfcandy.com" + }, + { + "name": "PDF2DOC", + "url": "https://pdf2doc.com" + }, + { + "name": "PDFescape", + "url": "https://pdfescape.com" + }, + { + "name": "PfpMaker", + "url": "https://pfpmaker.com" + }, + { + "name": "PIDGI Wiki ", + "url": "https://pidgi.net" + }, + { + "name": "PimEyes", + "url": "https://pimeyes.com" + }, + { + "name": "Pipl ", + "url": "https://pipl.com" + }, + { + "name": "PixelBazaar", + "url": "https://pixelbazaar.com" + }, + { + "name": "PixelPaper", + "url": "https://pixelpaper.io" + }, + { + "name": "Ponly", + "url": "https://ponly.com" + }, + { + "name": "PowerToFly", + "url": "https://powertofly.com" + }, + { + "name": "Pretzel Rocks", + "url": "https://pretzel.rocks" + }, + { + "name": "PrintIt", + "url": "https://printit.work" + }, + { + "name": "Prismatext", + "url": "https://prismatext.com" + }, + { + "name": "Puffin Maps", + "url": "https://puffinmaps.com" + }, + { + "name": "Puzzle Loop ", + "url": "https://puzzle-loop.com" + }, + { + "name": "QuoteMaster", + "url": "https://quotemaster.org" + }, + { + "name": "Radio Garden", + "url": "https://radio.garden" + }, + { + "name": "Radiooooo", + "url": "https://radiooooo.com" + }, + { + "name": "Radiosondy", + "url": "https://radiosondy.info" + }, + { + "name": "Rainy Mood", + "url": "https://rainymood.com" + }, + { + "name": "Random Street View", + "url": "https://randomstreetview.com" + }, + { + "name": "Rap4Ever", + "url": "https://rap4all.com" + }, + { + "name": "RareFilm", + "url": "https://rarefilm.net" + }, + { + "name": "Rattibha", + "url": "https://rattibha.com" + }, + { + "name": "Reddit List ", + "url": "https://redditlist.com" + }, + { + "name": "RedditSearch.io", + "url": "https://redditsearch.io" + }, + { + "name": "Reelgood", + "url": "https://reelgood.com" + }, + { + "name": "Reface", + "url": "https://reface.ai" + }, + { + "name": "Rejected.us", + "url": "https://rejected.us" + }, + { + "name": "Relanote", + "url": "https://relanote.com" + }, + { + "name": "Remote Leaf", + "url": "https://remoteleaf.com" + }, + { + "name": "Remote OK", + "url": "https://remoteok.com" + }, + { + "name": "Remote Starter Kit ", + "url": "https://remotestarterkit.com" + }, + { + "name": "Remote.co", + "url": "https://remote.co" + }, + { + "name": "Remote Base ", + "url": "https://remotebase.com" + }, + { + "name": "Remote Bear", + "url": "https://remotebear.io" + }, + { + "name": "Remove.bg", + "url": "https://remove.bg" + }, + { + "name": "Respresso", + "url": "https://respresso.io" + }, + { + "name": "Reveddit", + "url": "https://reveddit.com" + }, + { + "name": "Rhymer", + "url": "https://rhymer.com" + }, + { + "name": "RhymeZone", + "url": "https://rhymezone.com" + }, + { + "name": "Ribbet", + "url": "https://ribbet.com" + }, + { + "name": "Roadmap.sh", + "url": "https://roadmap.sh" + }, + { + "name": "Roadtrippers", + "url": "https://roadtrippers.com" + }, + { + "name": "RxResu.me", + "url": "https://rxresu.me" + }, + { + "name": "SchemeColor", + "url": "https://schemecolor.com" + }, + { + "name": "Screenshot.Guru", + "url": "https://screenshot.guru" + }, + { + "name": "SeatGuru", + "url": "https://seatguru.com" + }, + { + "name": "Sessions", + "url": "https://sessions.us" + }, + { + "name": "Shottr", + "url": "https://shottr.cc" + }, + { + "name": "Signature Maker", + "url": "https://signature-maker.net" + }, + { + "name": "Skip The Drive", + "url": "https://skipthedrive.com" + }, + { + "name": "Slowby", + "url": "https://slowby.travel" + }, + { + "name": "Small World", + "url": "https://smallworld.kiwi" + }, + { + "name": "SmallPDF", + "url": "https://smallpdf.com" + }, + { + "name": "Social Image Maker", + "url": "https://socialimagemaker.io" + }, + { + "name": "Social Sizes", + "url": "https://socialsizes.io" + }, + { + "name": "SoundLove", + "url": "https://soundlove.se" + }, + { + "name": "Spline", + "url": "https://spline.design" + }, + { + "name": "Starkey Comics", + "url": "https://starkeycomics.com" + }, - { - "name": "Statista", - "url": "https://statista.com" - }, - { - "name": "Stolen Camera Finder", - "url": "https://stolencamerafinder.com" - }, - { - "name": "Strobe.Cool", - "url": "https://strobe.cool" - }, - { - "name": "Sumo", - "url": "https://sumo.app" - }, - { - "name": "SuperMeme AI", - "url": "https://supermeme.ai" - }, - { - "name": "Synthesia", - "url": "https://synthesia.io" - }, - { - "name": "TablerIcons", - "url": "https://tablericons.com" - }, - { - "name": "Tango", - "url": "https://tango.us" - }, - { - "name": "TasteDive", - "url": "https://tastedive.com" - }, - { - "name": "TechSpecs", - "url": "https://techspecs.io" - }, - { - "name": "Teoria", - "url": "https://teoria.com" - }, - { - "name": "Text Faces", - "url": "https://textfac.es" - }, - { - "name": "The Balance Money", - "url": "https://thebalancemoney.com" - }, - { - "name": "The Punctuation Guide", - "url": "https://thepunctuationguide.com" - }, - { - "name": "This to That", - "url": "https://thistothat.com" - }, - { - "name": "This vs That", - "url": "https://thisvsthat.io" - }, - { - "name": "ThreadReaderApp ", - "url": "https://threadreaderapp.com" - }, - { - "name": "Thumbly", - "url": "https://tokee.ai" - }, - { - "name": "Tiii.me", - "url": "https://tiii.me" - }, - { - "name": "TikTok Video Downloader", - "url": "https://ttvdl.com" - }, - { - "name": "Time and Date", - "url": "https://timeanddate.com" - }, - { - "name": "Time.is", - "url": "https://time.is" - }, - { - "name": "Title Case", - "url": "https://titlecase.com" - }, - { - "name": "Toaster Central", - "url": "https://toastercentral.com" - }, - { - "name": "Tongue-Twister ", - "url": "https://tongue-twister.net" - }, - { - "name": "TradingView", - "url": "https://tradingview.com" - }, - { - "name": "Transparent Textures", - "url": "https://transparenttextures.com" - }, - { - "name": "Tubi TV", - "url": "https://tubitv.com" - }, - { - "name": "Tunefind", - "url": "https://tunefind.com" - }, - { - "name": "TuneMyMusic", - "url": "https://tunemymusic.com" - }, - { - "name": "Tweepsmap", - "url": "https://fedica.com" - }, - { - "name": "Two Peas and Their Pod", - "url": "https://twopeasandtheirpod.com" - }, - { - "name": "Typatone", - "url": "https://typatone.com" - }, - { - "name": "Under Glass", - "url": "https://underglass.io" - }, - { - "name": "UniCorner", - "url": "https://unicorner.news" - }, - { - "name": "Unita", - "url": "https://unita.co" - }, - { - "name": "UnitConverters", - "url": "https://unitconverters.net" - }, - { - "name": "Unreadit", - "url": "https://unreadit.com" - }, - { - "name": "Unscreen", - "url": "https://unscreen.com" - }, - { - "name": "UnTools ", - "url": "https://untools.co" - }, - { - "name": "Upwork", - "url": "https://upwork.com" - }, - { - "name": "UTF8 Icons", - "url": "https://utf8icons.com" - }, - { - "name": "Vector Magic", - "url": "https://vectormagic.com" - }, - { - "name": "Virtual Vacation", - "url": "https://virtualvacation.us" - }, - { - "name": "Virtual Vocations", - "url": "https://virtualvocations.com" - }, - { - "name": "Visiwig", - "url": "https://visiwig.com" - }, - { - "name": "Visual CV", - "url": "https://visualcv.com" - }, - { - "name": "Vocus.io", - "url": "https://vocus.io" - }, - { - "name": "Voscreen", - "url": "https://voscreen.com" - }, - { - "name": "Wanderprep", - "url": "https://wanderprep.com" - }, - { - "name": "Warmshowers", - "url": "https:/warmshowers.org" - }, - { - "name": "Watch Documentaries", - "url": "https://watchdocumentaries.com" - }, - { - "name": "We Work Remotely", - "url": "https://weworkremotely.com" - }, - { - "name": "Web2PDFConvert", - "url": "https://web2pdfconvert.com" - }, - { - "name": "Welcome to My Garden", - "url": "https://welcometomygarden.org" - }, - { - "name": "When2meet ", - "url": "https://when2meet.com" - }, - { - "name": "Where's George", - "url": "https://wheresgeorge.com" - }, - { - "name": "Where's Willy", - "url": "https://whereswilly.com" - }, - { - "name": "WikiHow", - "url": "https://wikihow.com" - }, - { - "name": "Windy", - "url": "https://www.windy.com" - }, - { - "name": "WonderHowTo", - "url": "https://wonderhowto.com" - }, - { - "name": "Working Nomads", - "url": "https://workingnomads.com" - }, - { - "name": "Wormhole", - "url": "https://wormhole.app" - }, - { - "name": "Y Combinator Jobs", - "url": "https://ycombinator.com" - }, - { - "name": "Yes Promo", - "url": "https://yespromo.me" - }, - { - "name": "YouGlish", - "url": "https://youglish.com" - }, - { - "name": "Zamzar", - "url": "https://zamzar.com" - }, - { - "name": "Zippyshare", - "url": "https://zippyshare.com" - }, - { - "name": "Zoom Earth", - "url": "https://zoom.earth" - }, - { - "name": "Zoom.it", - "url": "https://zoom.it" - } + { + "name": "Statista", + "url": "https://statista.com" + }, + { + "name": "Stolen Camera Finder", + "url": "https://stolencamerafinder.com" + }, + { + "name": "Strobe.Cool", + "url": "https://strobe.cool" + }, + { + "name": "Sumo", + "url": "https://sumo.app" + }, + { + "name": "SuperMeme AI", + "url": "https://supermeme.ai" + }, + { + "name": "Synthesia", + "url": "https://synthesia.io" + }, + { + "name": "TablerIcons", + "url": "https://tablericons.com" + }, + { + "name": "Tango", + "url": "https://tango.us" + }, + { + "name": "TasteDive", + "url": "https://tastedive.com" + }, + { + "name": "TechSpecs", + "url": "https://techspecs.io" + }, + { + "name": "Teoria", + "url": "https://teoria.com" + }, + { + "name": "Text Faces", + "url": "https://textfac.es" + }, + { + "name": "The Balance Money", + "url": "https://thebalancemoney.com" + }, + { + "name": "The Punctuation Guide", + "url": "https://thepunctuationguide.com" + }, + { + "name": "This to That", + "url": "https://thistothat.com" + }, + { + "name": "This vs That", + "url": "https://thisvsthat.io" + }, + { + "name": "ThreadReaderApp ", + "url": "https://threadreaderapp.com" + }, + { + "name": "Thumbly", + "url": "https://tokee.ai" + }, + { + "name": "Tiii.me", + "url": "https://tiii.me" + }, + { + "name": "TikTok Video Downloader", + "url": "https://ttvdl.com" + }, + { + "name": "Time and Date", + "url": "https://timeanddate.com" + }, + { + "name": "Time.is", + "url": "https://time.is" + }, + { + "name": "Title Case", + "url": "https://titlecase.com" + }, + { + "name": "Toaster Central", + "url": "https://toastercentral.com" + }, + { + "name": "Tongue-Twister ", + "url": "https://tongue-twister.net" + }, + { + "name": "TradingView", + "url": "https://tradingview.com" + }, + { + "name": "Transparent Textures", + "url": "https://transparenttextures.com" + }, + { + "name": "Tubi TV", + "url": "https://tubitv.com" + }, + { + "name": "Tunefind", + "url": "https://tunefind.com" + }, + { + "name": "TuneMyMusic", + "url": "https://tunemymusic.com" + }, + { + "name": "Tweepsmap", + "url": "https://fedica.com" + }, + { + "name": "Two Peas and Their Pod", + "url": "https://twopeasandtheirpod.com" + }, + { + "name": "Typatone", + "url": "https://typatone.com" + }, + { + "name": "Under Glass", + "url": "https://underglass.io" + }, + { + "name": "UniCorner", + "url": "https://unicorner.news" + }, + { + "name": "Unita", + "url": "https://unita.co" + }, + { + "name": "UnitConverters", + "url": "https://unitconverters.net" + }, + { + "name": "Unreadit", + "url": "https://unreadit.com" + }, + { + "name": "Unscreen", + "url": "https://unscreen.com" + }, + { + "name": "UnTools ", + "url": "https://untools.co" + }, + { + "name": "Upwork", + "url": "https://upwork.com" + }, + { + "name": "UTF8 Icons", + "url": "https://utf8icons.com" + }, + { + "name": "Vector Magic", + "url": "https://vectormagic.com" + }, + { + "name": "Virtual Vacation", + "url": "https://virtualvacation.us" + }, + { + "name": "Virtual Vocations", + "url": "https://virtualvocations.com" + }, + { + "name": "Visiwig", + "url": "https://visiwig.com" + }, + { + "name": "Visual CV", + "url": "https://visualcv.com" + }, + { + "name": "Vocus.io", + "url": "https://vocus.io" + }, + { + "name": "Voscreen", + "url": "https://voscreen.com" + }, + { + "name": "Wanderprep", + "url": "https://wanderprep.com" + }, + { + "name": "Warmshowers", + "url": "https:/warmshowers.org" + }, + { + "name": "Watch Documentaries", + "url": "https://watchdocumentaries.com" + }, + { + "name": "We Work Remotely", + "url": "https://weworkremotely.com" + }, + { + "name": "Web2PDFConvert", + "url": "https://web2pdfconvert.com" + }, + { + "name": "Welcome to My Garden", + "url": "https://welcometomygarden.org" + }, + { + "name": "When2meet ", + "url": "https://when2meet.com" + }, + { + "name": "Where's George", + "url": "https://wheresgeorge.com" + }, + { + "name": "Where's Willy", + "url": "https://whereswilly.com" + }, + { + "name": "WikiHow", + "url": "https://wikihow.com" + }, + { + "name": "Windy", + "url": "https://www.windy.com" + }, + { + "name": "WonderHowTo", + "url": "https://wonderhowto.com" + }, + { + "name": "Working Nomads", + "url": "https://workingnomads.com" + }, + { + "name": "Wormhole", + "url": "https://wormhole.app" + }, + { + "name": "Y Combinator Jobs", + "url": "https://ycombinator.com" + }, + { + "name": "Yes Promo", + "url": "https://yespromo.me" + }, + { + "name": "YouGlish", + "url": "https://youglish.com" + }, + { + "name": "Zamzar", + "url": "https://zamzar.com" + }, + { + "name": "Zippyshare", + "url": "https://zippyshare.com" + }, + { + "name": "Zoom Earth", + "url": "https://zoom.earth" + }, + { + "name": "Zoom.it", + "url": "https://zoom.it" + } ] diff --git a/Server/utils/imageProcessing.js b/Server/utils/imageProcessing.js index e40d46ff7..470a5c27e 100644 --- a/Server/utils/imageProcessing.js +++ b/Server/utils/imageProcessing.js @@ -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 }; diff --git a/Server/utils/logger.js b/Server/utils/logger.js index 4d37dc255..6c0bddcd8 100644 --- a/Server/utils/logger.js +++ b/Server/utils/logger.js @@ -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; diff --git a/Server/utils/messages.js b/Server/utils/messages.js index f7255af1f..a42498d65 100644 --- a/Server/utils/messages.js +++ b/Server/utils/messages.js @@ -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 }; diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 033650a3d..ad5c480ae 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -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, };