diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 873db5cfe..fa3f25484 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -2,11 +2,11 @@ language: "en-CA" tone_instructions: "His palms are sweaty, knees weak, arms are heavy There’s vomit on his sweater already, mom’s spaghetti" early_access: false reviews: - profile: "assertive" + profile: "chill" request_changes_workflow: false high_level_summary: false poem: false - review_status: true + review_status: false auto_review: enabled: true drafts: false diff --git a/Client/src/Components/Button/index.css b/Client/src/Components/Button/index.css index e69de29bb..63791d46b 100644 --- a/Client/src/Components/Button/index.css +++ b/Client/src/Components/Button/index.css @@ -0,0 +1,33 @@ +.MuiButtonBase-root.button-rotate90 svg { + transition: transform 500ms ease; +} +.MuiButtonBase-root.button-rotate90:hover svg { + transform: rotate(90deg); + transform-origin: center; +} + +.MuiButtonBase-root.button-rotate180 svg { + transition: transform 700ms ease; +} +.MuiButtonBase-root.button-rotate180:hover svg { + transform: rotate(180deg); + transform-origin: center; +} + +.MuiButtonBase-root.button-slideLeft { + padding-left: 45px; +} +.MuiButtonBase-root.button-slideLeft svg { + position: absolute; + left: 15px; + transition: left 250ms cubic-bezier(0.65, 0, 0.076, 1); +} +.MuiButtonBase-root.button-slideLeft:hover svg { + left: 11px; +} + +.MuiButtonBase-root.button-slideLeft:hover, +.MuiButtonBase-root.button-rotate180:hover, +.MuiButtonBase-root.button-rotate90:hover { + background-color: rgba(71, 84, 103, 0.08); +} diff --git a/Client/src/Components/Button/index.jsx b/Client/src/Components/Button/index.jsx index c53ece09e..9078e2497 100644 --- a/Client/src/Components/Button/index.jsx +++ b/Client/src/Components/Button/index.jsx @@ -1,5 +1,6 @@ import { Button as MuiButton } from "@mui/material"; import PropTypes from "prop-types"; +import "./index.css"; const levelConfig = { primary: { @@ -48,11 +49,22 @@ const levelConfig = { * */ -const Button = ({ id, type, level, label, disabled, img, onClick, sx }) => { +const Button = ({ + id, + animate, + type, + level, + label, + disabled, + img, + onClick, + sx, +}) => { const { variant, color } = levelConfig[level]; return ( { onClick={onClick} disableRipple sx={{ + lineHeight: 1, + boxShadow: "none", textTransform: "none", "&:focus": { outline: "none", }, + "&:hover": { + boxShadow: "none", + }, ...sx, }} > {img && img} - {label} + {label} ); }; diff --git a/Client/src/Components/ButtonSpinner/index.jsx b/Client/src/Components/ButtonSpinner/index.jsx index 2d940c539..62cfae477 100644 --- a/Client/src/Components/ButtonSpinner/index.jsx +++ b/Client/src/Components/ButtonSpinner/index.jsx @@ -72,10 +72,15 @@ const ButtonSpinner = ({ loading={isLoading} disableRipple sx={{ + boxShadow: "none", textTransform: "none", "&:focus": { outline: "none", }, + "&:hover": { + boxShadow: "none", + transition: "none" + }, ...sx, }} > diff --git a/Client/src/Pages/Monitors/Configure/index.css b/Client/src/Pages/Monitors/Configure/index.css index 3d3ed04f2..005c97188 100644 --- a/Client/src/Pages/Monitors/Configure/index.css +++ b/Client/src/Pages/Monitors/Configure/index.css @@ -38,6 +38,7 @@ padding-bottom: 60px; border: 1px solid var(--env-var-color-16); border-radius: var(--env-var-radius-1); + background-color: var(--env-var-color-8); } .configure-monitor .config-box .MuiBox-root, .configure-monitor .config-box .MuiStack-root { @@ -74,4 +75,4 @@ body:has(.configure-monitor) .select-dropdown .MuiMenuItem-root, .configure-monitor .select-wrapper .select-component > .MuiSelect-select { text-transform: lowercase; -} \ No newline at end of file +} diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx index 0aed07500..7ff28b576 100644 --- a/Client/src/Pages/Monitors/Configure/index.jsx +++ b/Client/src/Pages/Monitors/Configure/index.jsx @@ -122,6 +122,7 @@ const Configure = () => { } onClick={() => navigate(-1)} sx={{ @@ -129,7 +130,8 @@ const Configure = () => { mb: theme.gap.medium, px: theme.gap.ml, "& svg.MuiSvgIcon-root": { - pr: theme.gap.small, + mr: theme.gap.small, + fill: theme.palette.otherColors.slateGray, }, }} /> @@ -166,13 +168,14 @@ const Configure = () => { } sx={{ backgroundColor: "#f4f4f4", pl: theme.gap.small, pr: theme.gap.medium, "& svg": { - pr: theme.gap.xs, + mr: theme.gap.xs, }, }} /> diff --git a/Client/src/Pages/Monitors/CreateMonitor/index.css b/Client/src/Pages/Monitors/CreateMonitor/index.css index fbf4c4f14..d379bb602 100644 --- a/Client/src/Pages/Monitors/CreateMonitor/index.css +++ b/Client/src/Pages/Monitors/CreateMonitor/index.css @@ -48,6 +48,7 @@ border-radius: var(--env-var-radius-1); flex-direction: row; gap: var(--env-var-spacing-4); + background-color: var(--env-var-color-8); } .create-monitor .config-box .MuiBox-root, .create-monitor .config-box .MuiStack-root { diff --git a/Client/src/Pages/Monitors/CreateMonitor/index.jsx b/Client/src/Pages/Monitors/CreateMonitor/index.jsx index de370971a..c63599d7f 100644 --- a/Client/src/Pages/Monitors/CreateMonitor/index.jsx +++ b/Client/src/Pages/Monitors/CreateMonitor/index.jsx @@ -145,6 +145,7 @@ const CreateMonitor = () => { } onClick={() => navigate("/monitors")} sx={{ @@ -152,7 +153,8 @@ const CreateMonitor = () => { mb: theme.gap.medium, px: theme.gap.ml, "& svg.MuiSvgIcon-root": { - pr: theme.gap.small, + mr: theme.gap.small, + fill: theme.palette.otherColors.slateGray, }, }} /> diff --git a/Client/src/Pages/Monitors/Details/index.css b/Client/src/Pages/Monitors/Details/index.css index 1abd12ef9..f58ff7088 100644 --- a/Client/src/Pages/Monitors/Details/index.css +++ b/Client/src/Pages/Monitors/Details/index.css @@ -37,13 +37,18 @@ .monitor-details .stat-box { flex: 1; min-width: 100px; - border: solid 1px var(--env-var-color-4); + border: solid 1px; border-radius: var(--env-var-radius-1); padding: var(--env-var-spacing-1-plus) var(--env-var-spacing-2); + background-color: var(--env-var-color-8); } .monitor-details .stat-box:not(:first-of-type) { margin-left: var(--env-var-spacing-2); } .monitor-details .stat-box h6.MuiTypography-root { margin-bottom: var(--env-var-spacing-1); -} \ No newline at end of file +} +.monitor-details button.MuiButtonBase-root, +.monitor-details .stat-box { + border-color: var(--env-var-color-16); +} diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 1ee258cd4..d5d8cb0c1 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -147,6 +147,7 @@ const DetailsPage = () => { } onClick={() => navigate("/monitors")} sx={{ @@ -154,7 +155,8 @@ const DetailsPage = () => { mb: theme.gap.medium, px: theme.gap.ml, "& svg.MuiSvgIcon-root": { - pr: theme.gap.small, + mr: theme.gap.small, + fill: theme.palette.otherColors.slateGray, }, }} /> @@ -184,15 +186,16 @@ const DetailsPage = () => { } onClick={() => navigate(`/monitors/configure/${monitorId}`)} sx={{ ml: "auto", alignSelf: "flex-end", backgroundColor: "#f4f4f4", - px: theme.gap.ml, + px: theme.gap.medium, "& svg": { - pr: theme.gap.xs, + mr: "6px", }, }} /> @@ -222,19 +225,28 @@ const DetailsPage = () => { level="secondary" label="Day" onClick={() => setFilter("day")} - sx={{ backgroundColor: filter === "day" && "#f4f4f4" }} + sx={{ + backgroundColor: + filter === "day" && theme.palette.otherColors.fillGray, + }} /> setFilter("week")} - sx={{ backgroundColor: filter === "week" && "#f4f4f4" }} + sx={{ + backgroundColor: + filter === "week" && theme.palette.otherColors.fillGray, + }} /> setFilter("month")} - sx={{ backgroundColor: filter === "month" && "#f4f4f4" }} + sx={{ + backgroundColor: + filter === "month" && theme.palette.otherColors.fillGray, + }} /> diff --git a/README.md b/README.md index 26ac9f275..6631d99be 100644 --- a/README.md +++ b/README.md @@ -6,24 +6,57 @@   + BlueWave Uptime An open source server monitoring application + +  + 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. +## Features + +* Completely open source, deployable on your servers +* Website monitoring +* Port monitoring +* Ping monitoring +* Incidents at a glance +* Status pages +* Page speed monitoring (in the works) + +**Roadmap:** +* 3rd party integrations +* Domain monitoring +* SSL monitoring + +## Tech stack + +* [ReactJs](https://react.dev/) +* [MUI (React framework)](https://mui.com/) +* [Node.js](https://nodejs.org/en) +* [MongoDB](https://mongodb.com) + ## Contributing -You are welcome to provide contributions to the project. It uses React on the FE, and Nodejs and MongoDB on the BE, hence if you are comfortable with working with those technologies, you are encouraged to send your PRs. Please read [Contributor's guideline](https://github.com/bluewave-labs/bluewave-uptime/blob/master/CONTRIBUTING.md). Once you start contributing, we can also add you to our weekly team calls (every Thursday). +We love contributors. Here's how you can contribute: -Note that We have a Figma file that includes: +* Check [Contributor's guideline](https://github.com/bluewave-labs/bluewave-uptime/blob/master/CONTRIBUTING.md). +* Have a look at our Figma designs [here](https://www.figma.com/design/RPSfaw66HjzSwzntKcgDUV/Uptime-Genie?node-id=0-1&t=WqOFv9jqNTFGItpL-1). We encourage you to copy to your own Figma page, then work on it as it is read-only. +* Open an issue if you believe you've encountered a bug +* Make a pull request to add new features/make quality-of-life improvements/fix bugs. -- All the dashboard elements and components -- The design guideline for the app + + + + +Made with [contrib.rocks](https://contrib.rocks). + + -You can see the designs [here](https://www.figma.com/design/RPSfaw66HjzSwzntKcgDUV/Uptime-Genie?node-id=0-1&t=WqOFv9jqNTFGItpL-1). Since it is read-only, we encourage you to copy to your own Figma page, then work on it. ## Getting Started @@ -2067,10 +2100,3 @@ const myRoute = async(req, res, next) => { Errors should not be handled at the controller level and should be left to the middleware to handle. -## Contributors - - - - - -Made with [contrib.rocks](https://contrib.rocks). diff --git a/Server/controllers/alertController.js b/Server/controllers/alertController.js index cd2fa01ac..93d165469 100644 --- a/Server/controllers/alertController.js +++ b/Server/controllers/alertController.js @@ -27,7 +27,8 @@ const createAlert = async (req, res, next) => { await createAlertBodyValidation.validateAsync(req.body); } catch (error) { error.status = 422; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; error.service = SERVICE_NAME; next(error); return; @@ -58,7 +59,8 @@ const getAlertsByUserId = async (req, res, next) => { await getAlertsByUserIdParamValidation.validateAsync(req.params); } catch (error) { error.status = 422; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; error.service = SERVICE_NAME; next(error); return; @@ -82,7 +84,8 @@ const getAlertsByMonitorId = async (req, res, next) => { await getAlertsByMonitorIdParamValidation.validateAsync(req.params); } catch (error) { error.status = 422; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; error.service = SERVICE_NAME; next(error); return; @@ -106,7 +109,8 @@ const getAlertById = async (req, res, next) => { await getAlertByIdParamValidation.validateAsync(req.params); } catch (error) { error.status = 422; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; error.service = SERVICE_NAME; next(error); return; @@ -132,7 +136,8 @@ const editAlert = async (req, res, next) => { } catch (error) { console.log(error); error.status = 422; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; error.service = SERVICE_NAME; next(error); return; @@ -156,7 +161,8 @@ const deleteAlert = async (req, res, next) => { await deleteAlertParamValidation.validateAsync(req.params); } catch (error) { error.status = 422; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; error.service = SERVICE_NAME; next(error); return; diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 88a7c7f71..6d3e757a5 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -54,7 +54,8 @@ const registerController = async (req, res, next) => { } catch (error) { error.status = 422; error.service = SERVICE_NAME; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; next(error); return; } @@ -115,14 +116,20 @@ const registerController = async (req, res, next) => { */ const loginController = async (req, res, next) => { try { - // Validate input await loginValidation.validateAsync(req.body); - + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + try { // Check if user exists const user = await req.db.getUserByEmail(req, res); // Compare password - const match = await user.comparePassword(req.body.password); if (match !== true) { throw new Error(errorMessages.AUTH_INCORRECT_PASSWORD); @@ -157,7 +164,8 @@ const userEditController = async (req, res, next) => { } catch (error) { error.status = 422; error.service = SERVICE_NAME; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; next(error); return; } @@ -216,8 +224,18 @@ const inviteController = async (req, res, next) => { // Only admins can invite const token = getTokenFromHeaders(req.headers); const { role, firstname } = jwt.decode(token); - await inviteRoleValidation.validateAsync({ roles: role }); - await inviteBodyValidation.validateAsync(req.body); + try { + await inviteRoleValidation.validateAsync({ roles: role }); + await inviteBodyValidation.validateAsync(req.body); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + const inviteToken = await req.db.requestInviteToken(req, res); req.emailService.buildAndSendEmail( "employeeActivationTemplate", @@ -242,6 +260,16 @@ const inviteController = async (req, res, next) => { const inviteVerifyController = async (req, res, next) => { try { await inviteVerifciationBodyValidation.validateAsync(req.body); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + + try { const invite = await req.db.getInviteToken(req, res); res @@ -289,6 +317,16 @@ const checkAdminController = async (req, res) => { const recoveryRequestController = async (req, res, next) => { try { await recoveryValidation.validateAsync(req.body); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + + try { const user = await req.db.getUserByEmail(req, res); if (user) { const recoveryToken = await req.db.requestRecoveryToken(req, res); @@ -328,6 +366,16 @@ const recoveryRequestController = async (req, res, next) => { const validateRecoveryTokenController = async (req, res, next) => { try { await recoveryTokenValidation.validateAsync(req.body); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + + try { await req.db.validateRecoveryToken(req, res); // TODO Redirect user to reset password after validating token return res.status(200).json({ @@ -354,6 +402,15 @@ const validateRecoveryTokenController = async (req, res, next) => { const resetPasswordController = async (req, res, next) => { try { await newPasswordValidation.validateAsync(req.body); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + try { const user = await req.db.resetPassword(req, res); const token = issueToken(user._doc); res.status(200).json({ @@ -392,9 +449,18 @@ const deleteUserController = async (req, res, next) => { }; // Validate user - await deleteUserParamValidation.validateAsync( - decodedTokenCastedAsRequest.body - ); + try { + await deleteUserParamValidation.validateAsync( + decodedTokenCastedAsRequest.body + ); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } // Check if the user exists const user = await req.db.getUserByEmail(decodedTokenCastedAsRequest); @@ -408,25 +474,19 @@ const deleteUserController = async (req, res, next) => { ); if (monitors) { - // 2. Delete jobs associated with each monitor - for (const monitor of monitors) { - await req.jobQueue.deleteJob(monitor); - } + //2. Remove all jobs, delete checks and alerts + await Promise.all( + monitors.map(async (monitor) => { + await req.jobQueue.deleteJob(monitor); + await req.db.deleteChecks(monitor._id); + await req.db.deleteAlertByMonitorId(monitor._id); + }) + ); - // 3. Delete all checks associated with each monitor - for (const monitor of monitors) { - await req.db.deleteChecks(monitor._id); - } - - // 4. Delete all alerts associated with each monitor - for (const monitor of monitors) { - await req.db.deleteAlertByMonitorId(monitor._id); - } - - // 5. Delete each monitor + // 3. Delete each monitor await req.db.deleteMonitorsByUserId(user._id); - // 6. Delete the user by id + // 4. Delete the user by id await req.db.deleteUser(decodedTokenCastedAsRequest); return res.status(200).json({ diff --git a/Server/controllers/checkController.js b/Server/controllers/checkController.js index 391ae2700..f3c85352f 100644 --- a/Server/controllers/checkController.js +++ b/Server/controllers/checkController.js @@ -14,7 +14,8 @@ const createCheck = async (req, res, next) => { } catch (error) { error.status = 422; error.service = SERVICE_NAME; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; next(error); return; } @@ -37,7 +38,8 @@ const getChecks = async (req, res, next) => { } catch (error) { error.status = 422; error.service = SERVICE_NAME; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; next(error); return; } @@ -59,20 +61,19 @@ const deleteChecks = async (req, res, next) => { } catch (error) { error.status = 422; error.service = SERVICE_NAME; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; next(error); return; } try { const deletedCount = await req.db.deleteChecks(req.params.monitorId); - return res - .status(200) - .json({ - success: true, - msg: successMessages.CHECK_DELETE, - data: { deletedCount }, - }); + return res.status(200).json({ + success: true, + msg: successMessages.CHECK_DELETE, + data: { deletedCount }, + }); } catch (error) { error.service = SERVICE_NAME; next(error); diff --git a/Server/controllers/maintenanceWindowController.js b/Server/controllers/maintenanceWindowController.js index d94746fe3..5fe8ab7db 100644 --- a/Server/controllers/maintenanceWindowController.js +++ b/Server/controllers/maintenanceWindowController.js @@ -15,7 +15,8 @@ const createMaintenanceWindow = async (req, res, next) => { } catch (error) { error.status = 422; error.service = SERVICE_NAME; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; next(error); return; } @@ -48,7 +49,15 @@ const getMaintenanceWindowsByUserId = async (req, res, next) => { await getMaintenanceWindowsByUserIdParamValidation.validateAsync( req.params ); - + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + try { const maintenanceWindows = await req.db.getMaintenanceWindowsByUserId( req.params.userId ); @@ -69,7 +78,16 @@ const getMaintenanceWindowsByMonitorId = async (req, res, next) => { await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync( req.params ); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + try { const maintenanceWindows = await req.db.getMaintenanceWindowsByMonitorId( req.params.monitorId ); diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 5e3899005..0b5523c8a 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -9,7 +9,6 @@ const { const SERVICE_NAME = "monitorController"; const { errorMessages, successMessages } = require("../utils/messages"); -const { error } = require("winston"); /** * Returns all monitors @@ -47,7 +46,8 @@ const getMonitorById = async (req, res, next) => { await getMonitorByIdQueryValidation.validateAsync(req.query); } catch (error) { error.status = 422; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; next(error); return; } @@ -83,6 +83,16 @@ const getMonitorsByUserId = async (req, res, next) => { try { await getMonitorsByUserIdValidation.validateAsync(req.params); await getMonitorsByUserIdQueryValidation.validateAsync(req.query); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + + try { const userId = req.params.userId; const monitors = await req.db.getMonitorsByUserId(req, res); @@ -112,7 +122,9 @@ const createMonitor = async (req, res, next) => { } catch (error) { error.status = 422; error.service = SERVICE_NAME; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); return; } @@ -143,21 +155,24 @@ const createMonitor = async (req, res, next) => { const deleteMonitor = async (req, res, next) => { try { - await getMonitorByIdValidation.validateAsync(req.params); + await getMonitorByIdParamValidation.validateAsync(req.params); } catch (error) { error.status = 422; error.service = SERVICE_NAME; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; next(error); return; } try { const monitor = await req.db.deleteMonitor(req, res, next); - // Delete associated checks and alerts + // Delete associated checks,alerts,and notifications await req.jobQueue.deleteJob(monitor); await req.db.deleteChecks(monitor._id); await req.db.deleteAlertByMonitorId(monitor._id); + await req.db.deleteNotificationsByMonitorId(monitor._id); + /** * TODO * We should remove all checks and alerts associated with this monitor @@ -195,12 +210,13 @@ const deleteAllMonitors = async (req, res) => { */ const editMonitor = async (req, res, next) => { try { - await getMonitorByIdValidation.validateAsync(req.params); + await getMonitorByIdParamValidation.validateAsync(req.params); await editMonitorBodyValidation.validateAsync(req.body); } catch (error) { error.status = 422; error.service = SERVICE_NAME; - error.message = error.details[0].message; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; next(error); return; } diff --git a/Server/controllers/pageSpeedCheckController.js b/Server/controllers/pageSpeedCheckController.js index 7cc8696fc..eb290ad6e 100644 --- a/Server/controllers/pageSpeedCheckController.js +++ b/Server/controllers/pageSpeedCheckController.js @@ -1,4 +1,3 @@ -const PageSpeedCheck = require("../models/PageSpeedCheck"); const PageSpeedService = require("../service/pageSpeedService"); const { successMessages } = require("../utils/messages"); const pageSpeedService = new PageSpeedService(); @@ -22,12 +21,19 @@ const getPageSpeedChecks = async (req, res, next) => { try { // Validate monitorId parameter await getPageSpeedCheckParamValidation.validateAsync(req.params); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + try { return res.status(200).json({ msg: "Hit getPageSpeedChecks" }); } catch (error) { - if (error.isJoi) { - return res.status(400).json({ error: error.details[0].message }); - } + error.service = SERVICE_NAME; next(error); } }; @@ -42,12 +48,18 @@ const getPageSpeedChecks = async (req, res, next) => { */ const createPageSpeedCheck = async (req, res, next) => { try { - // Validate monitorId parameter await createPageSpeedCheckParamValidation.validateAsync(req.params); - - // Validate request body await createPageSpeedCheckBodyValidation.validateAsync(req.body); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + try { const { monitorId } = req.params; const { url } = req.body; @@ -74,9 +86,6 @@ const createPageSpeedCheck = async (req, res, next) => { data: newPageSpeedCheck, }); } catch (error) { - if (error.isJoi) { - return res.status(400).json({ error: error.details[0].message }); - } error.service = SERVICE_NAME; next(error); } @@ -94,12 +103,17 @@ const deletePageSpeedCheck = async (req, res, next) => { try { // Validate monitorId parameter await deletePageSpeedCheckParamValidation.validateAsync(req.params); - + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + try { return res.status(200).json({ msg: "Hit deletePageSpeedCheck" }); } catch (error) { - if (error.isJoi) { - return res.status(400).json({ error: error.details[0].message }); - } error.service = SERVICE_NAME; next(error); } diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js index de2b3466e..2813a3c00 100644 --- a/Server/db/mongo/MongoDB.js +++ b/Server/db/mongo/MongoDB.js @@ -65,8 +65,6 @@ const { getAllMonitors, getMonitorById, getMonitorsByUserId, - getMonitorByIdForIncidents, - getMonitorsByUserIdForIncidents, createMonitor, deleteMonitor, deleteAllMonitors, @@ -120,6 +118,12 @@ const { deleteMaintenanceWindowByUserId, } = require("./modules/maintenaceWindowModule"); +const { + createNotification, + getNotificationsByMonitorId, + deleteNotificationsByMonitorId, +} = require("./modules/notificationModule"); + module.exports = { connect, insertUser, @@ -136,8 +140,6 @@ module.exports = { getAllMonitors, getMonitorById, getMonitorsByUserId, - getMonitorByIdForIncidents, - getMonitorsByUserIdForIncidents, createMonitor, deleteMonitor, deleteAllMonitors, @@ -162,4 +164,7 @@ module.exports = { deleteMaintenaceWindowById, deleteMaintenanceWindowByMonitorId, deleteMaintenanceWindowByUserId, + createNotification, + getNotificationsByMonitorId, + deleteNotificationsByMonitorId, }; diff --git a/Server/db/mongo/modules/notificationModule.js b/Server/db/mongo/modules/notificationModule.js new file mode 100644 index 000000000..f5c8708f4 --- /dev/null +++ b/Server/db/mongo/modules/notificationModule.js @@ -0,0 +1,50 @@ +const Notification = require("../../../models/Notification"); + +/** + * Creates a new notification. + * @param {Object} notificationData - The data for the new notification. + * @param {mongoose.Types.ObjectId} notificationData.monitorId - The ID of the monitor. + * @param {string} notificationData.type - The type of the notification (e.g., "email", "sms"). + * @param {string} [notificationData.address] - The address for the notification (if applicable). + * @param {string} [notificationData.phone] - The phone number for the notification (if applicable). + * @returns {Promise} The created notification. + * @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) { + throw error; + } +}; + +/** + * Retrieves notifications by monitor ID. + * @param {mongoose.Types.ObjectId} monitorId - The ID of the monitor. + * @returns {Promise>} An array of notifications. + * @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) { + throw error; + } +}; + +const deleteNotificationsByMonitorId = async (monitorId) => { + try { + const result = await Notification.deleteMany({ monitorId }); + return result.deletedCount; + } catch (error) { + throw error; + } +}; + +module.exports = { + createNotification, + getNotificationsByMonitorId, + deleteNotificationsByMonitorId, +};
An open source server monitoring application