From ef8867658e0d87c48fee0edcecfd7bcf82a1f170 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 24 May 2024 13:44:42 -0700 Subject: [PATCH 01/13] Removed package-lock.json from root --- Server/controllers/monitorController.js | 35 +++++++++++++-------- Server/db/MongoDB.js | 5 +++ Server/index.js | 2 +- Server/middleware/verifyOwnership.js | 41 +++++++++++++++++++++++++ Server/models/user.js | 1 - Server/routes/monitorRoute.js | 14 +++++++-- package-lock.json | 6 ---- 7 files changed, 81 insertions(+), 23 deletions(-) create mode 100644 Server/middleware/verifyOwnership.js delete mode 100644 package-lock.json diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index f004f98f3..b8e2e1ec7 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -5,6 +5,7 @@ const { } = require("../validation/joi"); const logger = require("../utils/logger"); +const SERVICE_NAME = "monitorController"; /** * Returns all monitors @@ -19,7 +20,7 @@ const getAllMonitors = async (req, res) => { const monitors = await req.db.getAllMonitors(); return res.json({ success: true, msg: "Monitors found", data: monitors }); } catch (error) { - logger.error(error.message, { service: "monitor" }); + logger.error(error.message, { service: SERVICE_NAME }); return res.status(500).json({ success: false, msg: error.message }); } }; @@ -42,9 +43,14 @@ const getMonitorById = async (req, res) => { try { const monitor = await req.db.getMonitorById(req, res); + if (!monitor) { + logger.error("Monitor not found", { service: SERVICE_NAME }); + return res.status(404).json({ success: false, msg: "Monitor not found" }); + } + return res.json({ success: true, msg: "Monitor found", data: monitor }); } catch (error) { - logger.error(error.message, { service: "monitor" }); + logger.error(error.message, { service: SERVICE_NAME }); return res.status(500).json({ success: false, msg: error.message }); } }; @@ -68,17 +74,20 @@ const getMonitorsByUserId = async (req, res) => { try { const userId = req.params.userId; const monitors = await req.db.getMonitorsByUserId(req, res); - logger.info(`Monitors for user ${userId} found`, { - service: "monitor", - userId: userId, - }); + + if (monitors && monitors.length === 0) { + return res + .status(404) + .json({ success: false, msg: "No monitors not found" }); + } + return res.json({ success: true, msg: `Monitors for user ${userId} found`, data: monitors, }); } catch (error) { - logger.error(error.message, { service: "monitor" }); + logger.error(error.message, { service: SERVICE_NAME }); return res.status(500).json({ success: false, msg: error.message }); } }; @@ -106,7 +115,7 @@ const createMonitor = async (req, res) => { .status(201) .json({ success: true, msg: "Monitor created", data: monitor }); } catch (error) { - logger.error(error.message, { service: "monitor" }); + logger.error(error.message, { service: SERVICE_NAME }); return res.status(500).json({ success: false, msg: error.message }); } }; @@ -137,7 +146,7 @@ const deleteMonitor = async (req, res) => { */ return res.status(200).json({ success: true, msg: "Monitor deleted" }); } catch (error) { - logger.error(error.message, { service: "monitor" }); + logger.error(error.message, { service: SERVICE_NAME }); return res.status(500).json({ success: false, msg: error.message }); } }; @@ -150,9 +159,9 @@ const deleteMonitor = async (req, res) => { * @returns {Promise} * @throws {Error} */ -const editMonitor = async (req, res) => { - let paramError = getMonitorByIdValidation.validate(req.params); - if (paramError.error) { +const editMonitor = async (req, res, next) => { + let { paramError } = getMonitorByIdValidation.validate(req.params); + if (paramError) { return res .status(422) .json({ success: false, msg: paramError.error.details[0].message }); @@ -171,7 +180,7 @@ const editMonitor = async (req, res) => { .status(200) .json({ success: true, msg: "Monitor edited", data: editedMonitor }); } catch (error) { - logger.error(error.message, { service: "monitor" }); + logger.error(error.message, { service: SERVICE_NAME }); return res.status(500).json({ success: false, msg: error.message }); } }; diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index 8fcc1dced..44b734954 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -2,6 +2,10 @@ const Monitor = require("../models/Monitor"); const mongoose = require("mongoose"); const UserModel = require("../models/user"); +const verifyId = (userId, monitorId) => { + return userId.toString() === monitorId.toString(); +}; + const connect = async () => { try { await mongoose.connect(process.env.DB_CONNECTION_STRING); @@ -113,6 +117,7 @@ const getMonitorsByUserId = async (req, res) => { const createMonitor = async (req, res) => { try { const monitor = new Monitor({ ...req.body }); + monitor.userId = req.user._id; await monitor.save(); return monitor; } catch (error) { diff --git a/Server/index.js b/Server/index.js index 5068c98d8..11e2b5b5b 100644 --- a/Server/index.js +++ b/Server/index.js @@ -6,7 +6,7 @@ const monitorRouter = require("./routes/monitorRoute"); const { connectDbAndRunServer } = require("./configs/db"); require("dotenv").config(); const logger = require("./utils/logger"); -var { verifyJWT } = require("./middleware/verifyJWT"); +const { verifyJWT } = require("./middleware/verifyJWT"); // const { sendEmail } = require('./utils/sendEmail') diff --git a/Server/middleware/verifyOwnership.js b/Server/middleware/verifyOwnership.js new file mode 100644 index 000000000..1b496fbce --- /dev/null +++ b/Server/middleware/verifyOwnership.js @@ -0,0 +1,41 @@ +const logger = require("../utils/logger"); +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("Document not found", { + service: SERVICE_NAME, + }); + return res + .status(404) + .json({ success: false, msg: "Document not found" }); + } + + // If the userID does not match the document's userID, return a 403 error + if (userId.toString() !== doc.userId.toString()) { + logger.error("Unauthorized access", { + service: SERVICE_NAME, + }); + + return res.status(403).json({ + success: false, + msg: "You are not authorized to perform this action", + }); + } + next(); + } catch (error) { + logger.error(error.message, { + service: SERVICE_NAME, + }); + return res.status(500).json({ success: false, msg: error.message }); + } + }; +}; + +module.exports = { verifyOwnership }; diff --git a/Server/models/user.js b/Server/models/user.js index 02d0d53b3..d90b484bb 100644 --- a/Server/models/user.js +++ b/Server/models/user.js @@ -50,7 +50,6 @@ UserSchema.pre("save", async function (next) { }); UserSchema.methods.comparePassword = function (submittedPassword) { - console.log(submittedPassword, this.password); return bcrypt.compare(submittedPassword, this.password); }; diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js index 5f2ce4bb5..ffdce1d4d 100644 --- a/Server/routes/monitorRoute.js +++ b/Server/routes/monitorRoute.js @@ -1,11 +1,21 @@ const router = require("express").Router(); const monitorController = require("../controllers/monitorController"); +const { verifyOwnership } = require("../middleware/verifyOwnership"); +const Monitor = require("../models/Monitor"); router.get("/", monitorController.getAllMonitors); router.get("/:monitorId", monitorController.getMonitorById); router.get("/user/:userId", monitorController.getMonitorsByUserId); router.post("/", monitorController.createMonitor); -router.post("/delete/:monitorId", monitorController.deleteMonitor); -router.post("/edit/:monitorId", monitorController.editMonitor); +router.post( + "/delete/:monitorId", + verifyOwnership(Monitor, "monitorId"), + monitorController.deleteMonitor +); +router.post( + "/edit/:monitorId", + verifyOwnership(Monitor, "monitorId"), + monitorController.editMonitor +); module.exports = router; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index f71a78c1f..000000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "bluewave-uptime", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} From 75e007142fd112d431eeff618792a1cd19a0b60e Mon Sep 17 00:00:00 2001 From: Alvaro Torres <79110517+alvarotorrestx@users.noreply.github.com> Date: Sat, 25 May 2024 13:56:19 -0500 Subject: [PATCH 02/13] Add Server endpoints to ToC in README --- README.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 44a30ea94..610f21f8e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,14 @@ BlueWave uptime monitoring application 1. [Installation (Server)](#server) 1. [Configuration(Server)](#config-server) 1. [Endpoints](#endpoints) + - POST [/api/v1/auth/register](#post-register) + - POST [/api/v1/auth/login](#post-login) + - GET [/api/v1/monitors](#get-monitors) + - GET [/api/v1/monitor/{id}](#get-monitor-id) + - GET [/api/v1/monitors/user/{userId}](#get-monitors-user-userid) + - POST [/api/v1/monitors](#post-monitors) + - POST [/api/v1/monitors/delete/{monitorId}](#post-monitors-del-id) + - POST [/api/v1/monitors/edit/{monitorId}](#post-monitors-edit-id) --- @@ -111,7 +119,7 @@ Example: ---
-POST /api/v1/auth/register +POST /api/v1/auth/register ##### Method/Headers @@ -164,7 +172,7 @@ curl --request POST \ ---
-POST /api/v1/auth/login +POST /api/v1/auth/login ##### Method/Headers @@ -213,7 +221,7 @@ curl --request POST \ ---
-GET /api/v1/monitors +GET /api/v1/monitors ##### Method/Headers @@ -275,7 +283,7 @@ curl --request GET \ ---
-GET /api/v1/monitor/{id} +GET /api/v1/monitor/{id} ###### Method/Headers @@ -323,7 +331,7 @@ curl --request GET \ ---
-GET /api/v1/monitors/user/{userId} +GET /api/v1/monitors/user/{userId} ###### Method/Headers @@ -385,7 +393,7 @@ curl --request GET \ ---
-POST/api/v1/monitors +POST/api/v1/monitors ###### Method/Headers @@ -440,7 +448,7 @@ curl --request POST \ ---
-POST/api/v1/monitors/delete/{monitorId} +POST/api/v1/monitors/delete/{monitorId} ###### Method/Headers @@ -477,7 +485,7 @@ curl --request POST \ ---
-POST/api/v1/monitors/edit/{monitorId} +POST/api/v1/monitors/edit/{monitorId} ###### Method/Headers From 54f7f2d4048a7ceae9737b8ab43f27b6dd6a24ee Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 25 May 2024 20:14:06 -0700 Subject: [PATCH 03/13] Added missing details tag, contributors was hidden under the last endpoint --- README.md | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 610f21f8e..c7dc928f1 100644 --- a/README.md +++ b/README.md @@ -7,17 +7,18 @@ BlueWave uptime monitoring application - Clone this repository to your local machine 1. [Installation (Client)](#client) -1. [Installation (Server)](#server) -1. [Configuration(Server)](#config-server) -1. [Endpoints](#endpoints) - - POST [/api/v1/auth/register](#post-register) - - POST [/api/v1/auth/login](#post-login) - - GET [/api/v1/monitors](#get-monitors) - - GET [/api/v1/monitor/{id}](#get-monitor-id) - - GET [/api/v1/monitors/user/{userId}](#get-monitors-user-userid) - - POST [/api/v1/monitors](#post-monitors) - - POST [/api/v1/monitors/delete/{monitorId}](#post-monitors-del-id) - - POST [/api/v1/monitors/edit/{monitorId}](#post-monitors-edit-id) +2. [Installation (Server)](#server) +3. [Configuration(Server)](#config-server) +4. [Endpoints](#endpoints) + - POST [/api/v1/auth/register](#post-register) + - POST [/api/v1/auth/login](#post-login) + - GET [/api/v1/monitors](#get-monitors) + - GET [/api/v1/monitor/{id}](#get-monitor-id) + - GET [/api/v1/monitors/user/{userId}](#get-monitors-user-userid) + - POST [/api/v1/monitors](#post-monitors) + - POST [/api/v1/monitors/delete/{monitorId}](#post-monitors-del-id) + - POST [/api/v1/monitors/edit/{monitorId}](#post-monitors-edit-id) +5. [Contributors](#contributors) --- @@ -539,6 +540,8 @@ curl --request POST \ } ``` +
+ ## Contributors From 3cb63919f014c57571765a739ec5f3e647d930f1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 25 May 2024 20:14:24 -0700 Subject: [PATCH 04/13] Added divider --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c7dc928f1..5440f0e7d 100644 --- a/README.md +++ b/README.md @@ -542,6 +542,8 @@ curl --request POST \
+--- + ## Contributors From 54220a6125f469e78cc21f8d31d9604aad3eeeb3 Mon Sep 17 00:00:00 2001 From: M M Date: Sat, 25 May 2024 21:35:37 -0700 Subject: [PATCH 05/13] Implemented the dashboard progress bars. --- Client/package-lock.json | 230 ++++++++++++++++++ Client/package.json | 1 + .../DashboardProgressBars/index.css | 0 .../DashboardProgressBars/index.jsx | 19 ++ Client/src/Layouts/HomeLayout/index.jsx | 4 +- 5 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 Client/src/Components/DashboardProgressBars/index.css create mode 100644 Client/src/Components/DashboardProgressBars/index.jsx diff --git a/Client/package-lock.json b/Client/package-lock.json index fe3d684f5..e4a8fd7a8 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -13,6 +13,7 @@ "@fontsource/roboto": "^5.0.13", "@mui/icons-material": "^5.15.17", "@mui/material": "^5.15.16", + "@mui/x-charts": "^7.5.1", "@mui/x-data-grid": "7.3.2", "@mui/x-date-pickers": "7.3.2", "dayjs": "1.11.11", @@ -989,6 +990,44 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, + "node_modules/@mui/x-charts": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.5.1.tgz", + "integrity": "sha512-8vMQyJL13cGXMbp+gm2QdABzFuOpP4hxEa/o6nL3ZpVNp7hja6h9F5bc0FNGR8h/eRN+4+swDnrEebnR0w4cbA==", + "dependencies": { + "@babel/runtime": "^7.24.5", + "@mui/base": "^5.0.0-beta.40", + "@mui/system": "^5.15.14", + "@mui/utils": "^5.15.14", + "@react-spring/rafz": "^9.7.3", + "@react-spring/web": "^9.7.3", + "clsx": "^2.1.1", + "d3-color": "^3.1.0", + "d3-delaunay": "^6.0.4", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.2.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, "node_modules/@mui/x-data-grid": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.3.2.tgz", @@ -1123,6 +1162,71 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@react-spring/animated": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz", + "integrity": "sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==", + "dependencies": { + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/core": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz", + "integrity": "sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==", + "dependencies": { + "@react-spring/animated": "~9.7.3", + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/rafz": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.3.tgz", + "integrity": "sha512-9vzW1zJPcC4nS3aCV+GgcsK/WLaB520Iyvm55ARHfM5AuyBqycjvh1wbmWmgCyJuX4VPoWigzemq1CaaeRSHhQ==" + }, + "node_modules/@react-spring/shared": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz", + "integrity": "sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==", + "dependencies": { + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/types": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz", + "integrity": "sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw==" + }, + "node_modules/@react-spring/web": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.3.tgz", + "integrity": "sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==", + "dependencies": { + "@react-spring/animated": "~9.7.3", + "@react-spring/core": "~9.7.3", + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@remix-run/router": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", @@ -1674,6 +1778,111 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", @@ -1787,6 +1996,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2739,6 +2956,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -3805,6 +4030,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, "node_modules/rollup": { "version": "4.17.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", diff --git a/Client/package.json b/Client/package.json index cca88fb31..213119f38 100644 --- a/Client/package.json +++ b/Client/package.json @@ -15,6 +15,7 @@ "@fontsource/roboto": "^5.0.13", "@mui/icons-material": "^5.15.17", "@mui/material": "^5.15.16", + "@mui/x-charts": "^7.5.1", "@mui/x-data-grid": "7.3.2", "@mui/x-date-pickers": "7.3.2", "dayjs": "1.11.11", diff --git a/Client/src/Components/DashboardProgressBars/index.css b/Client/src/Components/DashboardProgressBars/index.css new file mode 100644 index 000000000..e69de29bb diff --git a/Client/src/Components/DashboardProgressBars/index.jsx b/Client/src/Components/DashboardProgressBars/index.jsx new file mode 100644 index 000000000..8215a0d17 --- /dev/null +++ b/Client/src/Components/DashboardProgressBars/index.jsx @@ -0,0 +1,19 @@ +import { BarChart } from '@mui/x-charts/BarChart'; + +const ChartsOverviewDemo = () => { + return ( + + ); +}; + +export default ChartsOverviewDemo; diff --git a/Client/src/Layouts/HomeLayout/index.jsx b/Client/src/Layouts/HomeLayout/index.jsx index 454f1452a..bb37d4768 100644 --- a/Client/src/Layouts/HomeLayout/index.jsx +++ b/Client/src/Layouts/HomeLayout/index.jsx @@ -1,8 +1,9 @@ -import React from 'react' import NavBar from '../../Components/NavBar' import { Outlet } from 'react-router' import Home from '../../Pages/Home' import './index.css' +import ChartsOverviewDemo from '../../Components/DashboardProgressBars' + const HomeLayout = () => { return ( @@ -10,6 +11,7 @@ const HomeLayout = () => { + ) } From 950222f94ec791bf144b5e38c5de53e046477b7d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 May 2024 10:47:14 -0700 Subject: [PATCH 06/13] Implemented error handling middleware --- Server/controllers/authController.js | 35 ++++---- Server/controllers/monitorController.js | 110 +++++++++++++----------- Server/index.js | 7 ++ Server/middleware/handleErrors.js | 9 ++ Server/middleware/verifyJWT.js | 14 +-- Server/middleware/verifyOwnership.js | 23 ++--- 6 files changed, 106 insertions(+), 92 deletions(-) create mode 100644 Server/middleware/handleErrors.js diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 1285726e1..a3a43ba0d 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -21,35 +21,32 @@ const issueToken = (payload) => { * @param {express.Response} res * @returns {{success: Boolean, msg: String}} */ -const registerController = async (req, res) => { +const registerController = async (req, res, next) => { // joi validation try { await registerValidation.validateAsync(req.body); } catch (error) { - return res - .status(400) - .json({ success: false, msg: error.details[0].message }); + error.status = 400; + error.service = SERVICE_NAME; + error.message = error.details[0].message; + next(error); + return; } // Check if the user exists try { const isUser = await req.db.getUserByEmail(req, res); if (isUser) { - logger.error("User already exists", { - service: SERVICE_NAME, - userId: isUser._id, - }); - return res - .status(400) - .json({ success: false, msg: "User already exists" }); + throw new Error("User already exists"); } } catch (error) { - logger.error(error.message, { service: SERVICE_NAME }); - return res.status(500).json({ success: false, msg: error.message }); + error.service = SERVICE_NAME; + next(error); + return; } + // Create a new user try { - // Create a new user const newUser = await req.db.insertUser(req, res); // TODO: Send an email to user // Will add this later @@ -63,8 +60,8 @@ const registerController = async (req, res) => { .status(200) .json({ success: true, msg: "User created", data: token }); } catch (error) { - logger.error(error.message, { service: SERVICE_NAME }); - return res.status(500).json({ success: false, msg: error.message }); + error.service = SERVICE_NAME; + next(error); } }; @@ -76,7 +73,7 @@ const registerController = async (req, res) => { * @returns {Promise} * @throws {Error} */ -const loginController = async (req, res) => { +const loginController = async (req, res, next) => { try { // Validate input await loginValidation.validateAsync(req.body); @@ -104,9 +101,9 @@ const loginController = async (req, res) => { .status(200) .json({ success: true, msg: "Found user", data: token }); } catch (error) { + error.status = 500; // Anything else should be an error - logger.error(error.message, { service: SERVICE_NAME }); - return res.status(500).json({ success: false, msg: error.message }); + next(error); } }; diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index b8e2e1ec7..250dc55b8 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -4,7 +4,6 @@ const { monitorValidation, } = require("../validation/joi"); -const logger = require("../utils/logger"); const SERVICE_NAME = "monitorController"; /** @@ -15,13 +14,13 @@ const SERVICE_NAME = "monitorController"; * @returns {Promise} * @throws {Error} */ -const getAllMonitors = async (req, res) => { +const getAllMonitors = async (req, res, next) => { try { const monitors = await req.db.getAllMonitors(); return res.json({ success: true, msg: "Monitors found", data: monitors }); } catch (error) { - logger.error(error.message, { service: SERVICE_NAME }); - return res.status(500).json({ success: false, msg: error.message }); + error.service = SERVICE_NAME; + next(error); } }; @@ -33,25 +32,28 @@ const getAllMonitors = async (req, res) => { * @returns {Promise} * @throws {Error} */ -const getMonitorById = async (req, res) => { - const { error } = getMonitorByIdValidation.validate(req.params); - if (error) { - return res - .status(422) - .json({ success: false, msg: error.details[0].message }); +const getMonitorById = async (req, res, next) => { + try { + await getMonitorByIdValidation.validateAsync(req.params); + } catch (error) { + error.status = 422; + error.message = error.details[0].message; + next(error); + return; } try { const monitor = await req.db.getMonitorById(req, res); if (!monitor) { - logger.error("Monitor not found", { service: SERVICE_NAME }); - return res.status(404).json({ success: false, msg: "Monitor not found" }); + const error = new Error("Monitor not found"); + error.status = 404; + throw error; } return res.json({ success: true, msg: "Monitor found", data: monitor }); } catch (error) { - logger.error(error.message, { service: SERVICE_NAME }); - return res.status(500).json({ success: false, msg: error.message }); + error.service = SERVICE_NAME; + next(error); } }; @@ -63,7 +65,7 @@ const getMonitorById = async (req, res) => { * @returns {Promise} * @throws {Error} */ -const getMonitorsByUserId = async (req, res) => { +const getMonitorsByUserId = async (req, res, next) => { const { error } = getMonitorsByUserIdValidation.validate(req.params); if (error) { return res @@ -76,9 +78,9 @@ const getMonitorsByUserId = async (req, res) => { const monitors = await req.db.getMonitorsByUserId(req, res); if (monitors && monitors.length === 0) { - return res - .status(404) - .json({ success: false, msg: "No monitors not found" }); + const err = new Error("No monitors found"); + err.status = 404; + throw err; } return res.json({ @@ -87,8 +89,8 @@ const getMonitorsByUserId = async (req, res) => { data: monitors, }); } catch (error) { - logger.error(error.message, { service: SERVICE_NAME }); - return res.status(500).json({ success: false, msg: error.message }); + error.service = SERVICE_NAME; + next(error); } }; @@ -101,12 +103,15 @@ const getMonitorsByUserId = async (req, res) => { * @throws {Error} */ -const createMonitor = async (req, res) => { - const { error } = monitorValidation.validate(req.body); - if (error) { - return res - .status(422) - .json({ success: false, msg: error.details[0].message }); +const createMonitor = async (req, res, next) => { + try { + await monitorValidation.validateAsync(req.body); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = error.details[0].message; + next(error); + return; } try { @@ -115,8 +120,8 @@ const createMonitor = async (req, res) => { .status(201) .json({ success: true, msg: "Monitor created", data: monitor }); } catch (error) { - logger.error(error.message, { service: SERVICE_NAME }); - return res.status(500).json({ success: false, msg: error.message }); + error.service = SERVICE_NAME; + next(error); } }; @@ -129,15 +134,19 @@ const createMonitor = async (req, res) => { * @throws {Error} */ -const deleteMonitor = async (req, res) => { - const { error } = getMonitorByIdValidation.validate(req.params); - if (error) { - return res - .status(422) - .json({ success: false, msg: error.details[0].message }); - } +const deleteMonitor = async (req, res, next) => { try { - const monitor = await req.db.deleteMonitor(req, res); + await getMonitorByIdValidation.validateAsync(req.params); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = error.details[0].message; + next(error); + return; + } + + try { + const monitor = await req.db.deleteMonitor(req, res, next); /** * TODO * We should remove all checks and alerts associated with this monitor @@ -146,8 +155,8 @@ const deleteMonitor = async (req, res) => { */ return res.status(200).json({ success: true, msg: "Monitor deleted" }); } catch (error) { - logger.error(error.message, { service: SERVICE_NAME }); - return res.status(500).json({ success: false, msg: error.message }); + error.service = SERVICE_NAME; + next(error); } }; @@ -160,18 +169,15 @@ const deleteMonitor = async (req, res) => { * @throws {Error} */ const editMonitor = async (req, res, next) => { - let { paramError } = getMonitorByIdValidation.validate(req.params); - if (paramError) { - return res - .status(422) - .json({ success: false, msg: paramError.error.details[0].message }); - } - - let { error } = monitorValidation.validate(req.body); - if (error) { - return res - .status(422) - .json({ success: false, msg: error.details[0].message }); + try { + await getMonitorByIdValidation.validateAsync(req.params); + await monitorValidation.validateAsync(req.body); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = error.details[0].message; + next(error); + return; } try { @@ -180,8 +186,8 @@ const editMonitor = async (req, res, next) => { .status(200) .json({ success: true, msg: "Monitor edited", data: editedMonitor }); } catch (error) { - logger.error(error.message, { service: SERVICE_NAME }); - return res.status(500).json({ success: false, msg: error.message }); + error.service = SERVICE_NAME; + next(error); } }; diff --git a/Server/index.js b/Server/index.js index 11e2b5b5b..db3be459c 100644 --- a/Server/index.js +++ b/Server/index.js @@ -7,6 +7,7 @@ const { connectDbAndRunServer } = require("./configs/db"); require("dotenv").config(); const logger = require("./utils/logger"); const { verifyJWT } = require("./middleware/verifyJWT"); +const { handleErrors } = require("./middleware/handleErrors"); // const { sendEmail } = require('./utils/sendEmail') @@ -62,6 +63,12 @@ app.use((req, res, next) => { app.use("/api/v1/auth", authRouter); app.use("/api/v1/monitors", verifyJWT, monitorRouter); +/** + * Error handler middleware + * MUST be called after all routes + */ +app.use(handleErrors); + // Testing email service // app.use('/sendEmail', async (req, res) => { // const response = sendEmail(['veysel.boybay@bluewavelabs.ca'], 'Testing email service', '

Testing Bluewavelabs

'); diff --git a/Server/middleware/handleErrors.js b/Server/middleware/handleErrors.js new file mode 100644 index 000000000..2febc6b31 --- /dev/null +++ b/Server/middleware/handleErrors.js @@ -0,0 +1,9 @@ +const logger = require("../utils/logger"); + +const handleErrors = (error, req, res, next) => { + error.status = error.status ? error.status : 500; + logger.error(error.message, { service: error.service }); + res.status(error.status).json({ success: false, msg: error.message }); +}; + +module.exports = { handleErrors }; diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js index 53464c606..2e8895623 100644 --- a/Server/middleware/verifyJWT.js +++ b/Server/middleware/verifyJWT.js @@ -15,8 +15,11 @@ const verifyJWT = (req, res, next) => { const token = req.headers["authorization"]; // Make sure a token is provided if (!token) { - logger.error("No token provided", { service: SERVICE_NAME }); - return res.status(401).json({ success: false, msg: "No token provided" }); + const error = new Error("No token provided"); + error.status = 401; + error.service = SERVICE_NAME; + next(error); + return; } // Make sure it is properly formatted if (token.startsWith(TOKEN_PREFIX)) { @@ -32,10 +35,9 @@ const verifyJWT = (req, res, next) => { next(); }); } else { - logger.error("Invalid token format", { service: SERVICE_NAME }); - return res - .status(400) - .json({ success: false, msg: "Invalid token format" }); + error.status = 400; + error.service = SERVICE_NAME; + next(error); } }; diff --git a/Server/middleware/verifyOwnership.js b/Server/middleware/verifyOwnership.js index 1b496fbce..00f7b0787 100644 --- a/Server/middleware/verifyOwnership.js +++ b/Server/middleware/verifyOwnership.js @@ -12,28 +12,21 @@ const verifyOwnership = (Model, paramName) => { logger.error("Document not found", { service: SERVICE_NAME, }); - return res - .status(404) - .json({ success: false, msg: "Document not found" }); + const error = new Error("Document not found"); + error.status = 404; + throw error; } // If the userID does not match the document's userID, return a 403 error if (userId.toString() !== doc.userId.toString()) { - logger.error("Unauthorized access", { - service: SERVICE_NAME, - }); - - return res.status(403).json({ - success: false, - msg: "You are not authorized to perform this action", - }); + const error = new Error("Unauthorized access"); + error.status = 403; + throw error; } next(); } catch (error) { - logger.error(error.message, { - service: SERVICE_NAME, - }); - return res.status(500).json({ success: false, msg: error.message }); + error.service = SERVICE_NAME; + next(error); } }; }; From 5fa73a1f17f34af26b93c2b232e2c2cf386a04b5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 May 2024 11:03:12 -0700 Subject: [PATCH 07/13] Added documentation for error handling --- README.md | 37 ++++++++++++++++++++++++++++++- Server/middleware/handleErrors.js | 9 +++++--- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5440f0e7d..a8e1b1f32 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ BlueWave uptime monitoring application - POST [/api/v1/monitors](#post-monitors) - POST [/api/v1/monitors/delete/{monitorId}](#post-monitors-del-id) - POST [/api/v1/monitors/edit/{monitorId}](#post-monitors-edit-id) -5. [Contributors](#contributors) +5. [Error Handling](#error-handling) +6. [Contributors](#contributors) --- @@ -544,6 +545,40 @@ curl --request POST \ --- +### Error handling {#error-handling} + +Errors are returned in a standard format: + +`{"success": false, "msg": "No token provided"}` + +Errors are handled by error handling middleware and should be thrown with the following parameters + +| Name | Type | Default | Notes | +| ------- | --------- | ---------------------- | ------------------------------------ | +| status | `integer` | 500 | Standard HTTP codes | +| message | `string` | "Something went wrong" | An error message | +| service | `string` | "Unknown Service" | Name of service that threw the error | + +Example: + +``` +const myRoute = async(req, res, next) => { + try{ + const result = myRiskyOperationHere(); + } + catch(error){ + error.status = 404 + error.message = "Resource not found" + error.service = service name + next(error) + return; + } +} + +``` + +Errors should not be handled at the controller level and should be left to the middleware to handle. + ## Contributors
diff --git a/Server/middleware/handleErrors.js b/Server/middleware/handleErrors.js index 2febc6b31..78764c1e1 100644 --- a/Server/middleware/handleErrors.js +++ b/Server/middleware/handleErrors.js @@ -1,9 +1,12 @@ const logger = require("../utils/logger"); const handleErrors = (error, req, res, next) => { - error.status = error.status ? error.status : 500; - logger.error(error.message, { service: error.service }); - res.status(error.status).json({ success: false, msg: error.message }); + const status = error.status || 500; + const message = error.message || "Something went wrong"; + const service = error.errorService || "Unknown service"; + + logger.error(error.message, { service: service }); + res.status(status).json({ success: false, msg: message }); }; module.exports = { handleErrors }; From d84a0f9c87276c5d5376a3610663a1f793a583b7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 May 2024 11:57:25 -0700 Subject: [PATCH 08/13] Started update user route --- Server/controllers/authController.js | 42 +++++++++++++++- Server/db/MongoDB.js | 26 ++++++++++ Server/index.js | 12 ++--- Server/middleware/verifyOwnership.js | 1 + Server/models/user.js | 71 +++++++++++++--------------- Server/routes/authRoute.js | 6 +++ Server/validation/joi.js | 13 ++++- 7 files changed, 125 insertions(+), 46 deletions(-) diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index a3a43ba0d..3d3dde796 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -1,5 +1,10 @@ const express = require("express"); -const { registerValidation, loginValidation } = require("../validation/joi"); +const { + registerValidation, + loginValidation, + editUserParamValidation, + editUserBodyValidation, +} = require("../validation/joi"); const logger = require("../utils/logger"); require("dotenv").config(); var jwt = require("jsonwebtoken"); @@ -107,4 +112,37 @@ const loginController = async (req, res, next) => { } }; -module.exports = { registerController, loginController }; +const userEditController = async (req, res, next) => { + try { + await editUserParamValidation.validateAsync(req.params); + await editUserBodyValidation.validateAsync(req.body); + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.message = error.details[0].message; + next(error); + return; + } + + if (req.params.userId !== req.user._id.toString()) { + const error = new Error("Unauthorized access"); + error.status = 401; + error.service = SERVICE_NAME; + next(error); + return; + } + + //Find and update user + try { + const updatedUser = await req.db.updateUser(req, res); + res + .status(200) + .json({ success: true, msg: "User updated", data: updatedUser }); + } catch (error) { + error.service = SERVICE_NAME; + next(error); + return; + } +}; + +module.exports = { registerController, loginController, userEditController }; diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index 44b734954..a2e45f5d6 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -55,6 +55,31 @@ const getUserByEmail = async (req, res) => { } }; +/** + * Update a user by ID + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ + +const updateUser = async (req, res) => { + const candidateUserId = req.params.userId; + const candidateUser = req.body; + + try { + const updatedUser = await user.findByIdAndUpdate( + candidateUserId, + candidateUser, + { new: true } // Returns updated user instead of pre-update user + ); + return updatedUser; + } catch (error) { + throw error; + } +}; + /** * Get all monitors * @async @@ -173,6 +198,7 @@ module.exports = { connect, insertUser, getUserByEmail, + updateUser, getAllMonitors, getMonitorById, getMonitorsByUserId, diff --git a/Server/index.js b/Server/index.js index db3be459c..63d919b8d 100644 --- a/Server/index.js +++ b/Server/index.js @@ -63,12 +63,6 @@ app.use((req, res, next) => { app.use("/api/v1/auth", authRouter); app.use("/api/v1/monitors", verifyJWT, monitorRouter); -/** - * Error handler middleware - * MUST be called after all routes - */ -app.use(handleErrors); - // Testing email service // app.use('/sendEmail', async (req, res) => { // const response = sendEmail(['veysel.boybay@bluewavelabs.ca'], 'Testing email service', '

Testing Bluewavelabs

'); @@ -86,4 +80,10 @@ app.use("/api/v1/healthy", (req, res) => { } }); +/** + * Error handler middleware + * Should be called last + */ +app.use(handleErrors); + connectDbAndRunServer(app, db); diff --git a/Server/middleware/verifyOwnership.js b/Server/middleware/verifyOwnership.js index 00f7b0787..409303fa4 100644 --- a/Server/middleware/verifyOwnership.js +++ b/Server/middleware/verifyOwnership.js @@ -19,6 +19,7 @@ const verifyOwnership = (Model, paramName) => { // If the userID does not match the document's userID, return a 403 error if (userId.toString() !== doc.userId.toString()) { + console.log("boom"); const error = new Error("Unauthorized access"); error.status = 403; throw error; diff --git a/Server/models/user.js b/Server/models/user.js index d90b484bb..7f3945354 100644 --- a/Server/models/user.js +++ b/Server/models/user.js @@ -1,44 +1,41 @@ const mongoose = require("mongoose"); const bcrypt = require("bcrypt"); -const UserSchema = mongoose.Schema({ - firstname: { - type: String, - required: true, +const UserSchema = mongoose.Schema( + { + firstname: { + type: String, + required: true, + }, + lastname: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + password: { + type: String, + required: true, + }, + profilePicUrl: { + type: String, + }, + isActive: { + type: Boolean, + default: true, + }, + isVerified: { + type: Boolean, + default: false, + }, }, - lastname: { - type: String, - required: true, - }, - email: { - type: String, - required: true, - unique: true, - }, - password: { - type: String, - required: true, - }, - profilePicUrl: { - type: String, - }, - isActive: { - type: Boolean, - default: true, - }, - isVerified: { - type: Boolean, - default: false, - }, - updated_at: { - type: Date, - default: Date.now, - }, - created_at: { - type: Date, - default: Date.now, - }, -}); + { + timestamps: true, + } +); UserSchema.pre("save", async function (next) { if (!this.isModified("password")) { diff --git a/Server/routes/authRoute.js b/Server/routes/authRoute.js index 48a173b92..94f0ce5cc 100644 --- a/Server/routes/authRoute.js +++ b/Server/routes/authRoute.js @@ -1,10 +1,16 @@ const router = require("express").Router(); +const user = require("../models/user"); +const { verifyJWT } = require("../middleware/verifyJWT"); +const { verifyOwnership } = require("../middleware/verifyOwnership"); + const { registerController, loginController, + userEditController, } = require("../controllers/authController"); router.post("/register", registerController); router.post("/login", loginController); +router.post("/user/:userId", verifyJWT, userEditController); module.exports = router; diff --git a/Server/validation/joi.js b/Server/validation/joi.js index ecc19253e..399c243dc 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -1,5 +1,4 @@ const joi = require("joi"); -const user = require("../models/user"); const loginValidation = joi.object({ email: joi.string().email().required(), @@ -30,10 +29,22 @@ const monitorValidation = joi.object({ interval: joi.number(), }); +const editUserParamValidation = joi.object({ + userId: joi.string().required(), +}); + +const editUserBodyValidation = joi.object({ + firstname: joi.string(), + lastname: joi.string(), + profilePicUrl: joi.string(), +}); + module.exports = { loginValidation, registerValidation, getMonitorByIdValidation, getMonitorsByUserIdValidation, monitorValidation, + editUserParamValidation, + editUserBodyValidation, }; From 78abdd7f704cf405d825e7b845da88e0a9304dbd Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 May 2024 12:03:32 -0700 Subject: [PATCH 09/13] Fixed bug in error handler, finished implementing update user endpoint --- Server/controllers/authController.js | 1 - Server/db/MongoDB.js | 2 +- Server/middleware/handleErrors.js | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 3d3dde796..cc476ab54 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -132,7 +132,6 @@ const userEditController = async (req, res, next) => { return; } - //Find and update user try { const updatedUser = await req.db.updateUser(req, res); res diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index a2e45f5d6..b3b269600 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -69,7 +69,7 @@ const updateUser = async (req, res) => { const candidateUser = req.body; try { - const updatedUser = await user.findByIdAndUpdate( + const updatedUser = await UserModel.findByIdAndUpdate( candidateUserId, candidateUser, { new: true } // Returns updated user instead of pre-update user diff --git a/Server/middleware/handleErrors.js b/Server/middleware/handleErrors.js index 78764c1e1..a20db8571 100644 --- a/Server/middleware/handleErrors.js +++ b/Server/middleware/handleErrors.js @@ -3,7 +3,7 @@ const logger = require("../utils/logger"); const handleErrors = (error, req, res, next) => { const status = error.status || 500; const message = error.message || "Something went wrong"; - const service = error.errorService || "Unknown service"; + const service = error.service || "Unknown service"; logger.error(error.message, { service: service }); res.status(status).json({ success: false, msg: message }); From 36936266acf35392dc737a42bcb71ba44e1b9993 Mon Sep 17 00:00:00 2001 From: veyselboybay Date: Mon, 27 May 2024 15:07:34 -0400 Subject: [PATCH 10/13] Added Check and Alert models and updated ReadMe --- README.md | 29 +++++++++++++++++++++++++++++ Server/models/Alert.js | 28 ++++++++++++++++++++++++++++ Server/models/Check.js | 28 ++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 Server/models/Alert.js create mode 100644 Server/models/Check.js diff --git a/README.md b/README.md index a8e1b1f32..039750dc1 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,35 @@ Example:
+
+Check + +| Name | Type | Notes | +| ----------- | --------- | ------------------------------------------------| +| monitorId | `string` | Unique ID for the monitor | +| status | `boolean` | Indicates the service is Up or Down | +| responseTime| `integer` | Indicates the response time of the service (ms) | +| statusCode | `integer` | Status Code returned from the service | +| message | `string` | Message returned from the service | +| updatedAt | `Date` | Last time the check was updated | +| CreatedAt | `Date` | When the check was created | + +
+ +
+Alert + +| Name | Type | Notes | +| ----------- | --------- | --------------------------------------------------| +| checkId | `string` | Unique ID for the check | +| status | `boolean` | Indicates the service is Up or Down | +| message | `string` | Message for the user about the down service | +| notifiedStatus | `boolean` | Indicates whether the user is notified | +| acknowledgeStatus | `boolean` | Indicates whether the user acknowledged the alert | +| updatedAt | `Date` | Last time the alert was updated | +| CreatedAt | `Date` | When the alert was created | + +
---
diff --git a/Server/models/Alert.js b/Server/models/Alert.js new file mode 100644 index 000000000..72705d947 --- /dev/null +++ b/Server/models/Alert.js @@ -0,0 +1,28 @@ +const mongoose = require('mongoose') + +const AlertSchema = mongoose.Schema( + { + checkId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Check', + immutable: true, + }, + status: { + type: Boolean, + }, + message: { + type: String, + }, + notifiedStatus: { + type: Boolean, + }, + acknowledgeStatus: { + type: Boolean, + } + }, + { + timestamps: true, + } +); + +module.exports = mongoose.model('Alert', AlertSchema); diff --git a/Server/models/Check.js b/Server/models/Check.js new file mode 100644 index 000000000..eedc2a28e --- /dev/null +++ b/Server/models/Check.js @@ -0,0 +1,28 @@ +const mongoose = require('mongoose') + +const CheckSchema = mongoose.Schema( + { + monitorId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Monitor', + immutable: true, + }, + status: { + type: Boolean, + }, + responseTime: { + type: Number, // milliseconds + }, + statusCode: { + type: Number, // 200, ... , 500 + }, + message: { + type: String, + } + }, + { + timestamps: true, + } +); + +module.exports = mongoose.model('Check', CheckSchema); From 7672cd55300ef6676d92fdd266c279e491a66470 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 May 2024 12:27:57 -0700 Subject: [PATCH 11/13] Removed password from returned user, updated readme --- README.md | 62 ++++++++++++++++++++++++++++++++++++++ Server/db/MongoDB.js | 2 +- Server/routes/authRoute.js | 2 -- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a8e1b1f32..63f5625aa 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ BlueWave uptime monitoring application 4. [Endpoints](#endpoints) - POST [/api/v1/auth/register](#post-register) - POST [/api/v1/auth/login](#post-login) + - POST [/api/v1/auth/user/{userId}](#post-auth-user-edit-id) - GET [/api/v1/monitors](#get-monitors) - GET [/api/v1/monitor/{id}](#get-monitor-id) - GET [/api/v1/monitors/user/{userId}](#get-monitors-user-userid) @@ -222,6 +223,67 @@ curl --request POST \ --- +
+POST/api/v1/auth/user/{userId} + +###### Method/Headers + +> | Method/Headers | Value | +> | -------------- | ----- | +> | Method | POST | + +##### Body + +> | Name | Type | Notes | +> | ------------- | -------- | ----------- | +> | firstname | `string` | | +> | lastname | `string` | | +> | profilePicUrl | `string` | | +> | password | `string` | Min 8 chars | + +###### Response Payload + +> | Type | Notes | +> | ------ | ------------------------ | +> | `User` | Returns the updated user | + +##### Sample CURL request + +``` +curl --request POST \ + --url http://localhost:5000/api/v1/auth/user/6654d156634754f789e1f10e \ + --header 'Authorization: ' \ + --header 'Content-Type: application/json' \ + --data '{ + "firstname": "First Name", + "lastname: "Last Name" +}' +``` + +###### Sample Response + +```json +{ + "success": true, + "msg": "User updated", + "data": { + "_id": "6654d156634754f789e1f10e", + "firstname": "First Name", + "lastname": "Last Name", + "email": "me@gmail.com", + "isActive": true, + "isVerified": false, + "createdAt": "2024-05-27T18:30:46.358Z", + "updatedAt": "2024-05-27T19:21:51.747Z", + "__v": 0 + } +} +``` + +
+ +--- +
GET /api/v1/monitors diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index b3b269600..71b6fe44a 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -73,7 +73,7 @@ const updateUser = async (req, res) => { candidateUserId, candidateUser, { new: true } // Returns updated user instead of pre-update user - ); + ).select("-password"); return updatedUser; } catch (error) { throw error; diff --git a/Server/routes/authRoute.js b/Server/routes/authRoute.js index 94f0ce5cc..637e4bf5e 100644 --- a/Server/routes/authRoute.js +++ b/Server/routes/authRoute.js @@ -1,7 +1,5 @@ const router = require("express").Router(); -const user = require("../models/user"); const { verifyJWT } = require("../middleware/verifyJWT"); -const { verifyOwnership } = require("../middleware/verifyOwnership"); const { registerController, From a417662f68680b5fd60ede8156e6b470a0544bc6 Mon Sep 17 00:00:00 2001 From: M M Date: Mon, 27 May 2024 17:26:12 -0700 Subject: [PATCH 12/13] Implemented the dashboard settings icon with its dropdown menu. --- .../DashboardSettingsIcon/index.css | 0 .../DashboardSettingsIcon/index.jsx | 78 +++++++++++++++++++ Client/src/Layouts/HomeLayout/index.jsx | 2 + 3 files changed, 80 insertions(+) create mode 100644 Client/src/Components/DashboardSettingsIcon/index.css create mode 100644 Client/src/Components/DashboardSettingsIcon/index.jsx diff --git a/Client/src/Components/DashboardSettingsIcon/index.css b/Client/src/Components/DashboardSettingsIcon/index.css new file mode 100644 index 000000000..e69de29bb diff --git a/Client/src/Components/DashboardSettingsIcon/index.jsx b/Client/src/Components/DashboardSettingsIcon/index.jsx new file mode 100644 index 000000000..e1f1d59da --- /dev/null +++ b/Client/src/Components/DashboardSettingsIcon/index.jsx @@ -0,0 +1,78 @@ +import { useState } from 'react'; +import Box from '@mui/material/Box'; +import IconButton from '@mui/material/IconButton'; +import Menu from '@mui/material/Menu'; +import Tooltip from '@mui/material/Tooltip'; +import MenuItem from '@mui/material/MenuItem'; +import Typography from '@mui/material/Typography'; +import SvgIcon from '@mui/material/SvgIcon'; +import SettingsIcon from '@mui/icons-material/Settings'; +import { useTheme } from '@mui/material/styles'; + +const settings = ['Open Site', 'Detailed View', 'Incidents', 'Configure', 'Remove']; + +/** + * DashboardSettings component + * + * A component that provides a settings menu accessible from an icon. + * + * @component + * @example + * return ( + * + * ) + */ +function DashboardSettings() { + const theme = useTheme(); + const [anchorElUser, setAnchorElUser] = useState(null); + + /** + * Handles opening the user menu. + * + * @param {React.MouseEvent} event - The event triggered by clicking the user menu button. + */ + const handleOpenUserMenu = (event) => { + setAnchorElUser(event.currentTarget); + }; + + /** + * Handles closing the user menu. + */ + const handleCloseUserMenu = () => { + setAnchorElUser(null); + }; + + return ( + + + + + + + + {settings.map((setting) => ( + + {setting} + + ))} + + + ); +} + +export default DashboardSettings; diff --git a/Client/src/Layouts/HomeLayout/index.jsx b/Client/src/Layouts/HomeLayout/index.jsx index bb37d4768..e75863b3b 100644 --- a/Client/src/Layouts/HomeLayout/index.jsx +++ b/Client/src/Layouts/HomeLayout/index.jsx @@ -3,6 +3,7 @@ import { Outlet } from 'react-router' import Home from '../../Pages/Home' import './index.css' import ChartsOverviewDemo from '../../Components/DashboardProgressBars' +import DashboardSettings from '../../Components/DashboardSettings' const HomeLayout = () => { @@ -12,6 +13,7 @@ const HomeLayout = () => { + ) } From d24b5d8ada832342116bdb95f1477354a0697675 Mon Sep 17 00:00:00 2001 From: M M Date: Mon, 27 May 2024 23:11:23 -0700 Subject: [PATCH 13/13] Addressed comments and fixed the issue with the import statement. --- Client/src/Layouts/HomeLayout/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Layouts/HomeLayout/index.jsx b/Client/src/Layouts/HomeLayout/index.jsx index e75863b3b..ed1d06fe6 100644 --- a/Client/src/Layouts/HomeLayout/index.jsx +++ b/Client/src/Layouts/HomeLayout/index.jsx @@ -3,7 +3,7 @@ import { Outlet } from 'react-router' import Home from '../../Pages/Home' import './index.css' import ChartsOverviewDemo from '../../Components/DashboardProgressBars' -import DashboardSettings from '../../Components/DashboardSettings' +import DashboardSettings from '../../Components/DashboardSettingsIcon' const HomeLayout = () => {