From 14f68d58f712c99ecd549d6d3437ef2b32e505b1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 17 Jun 2024 10:47:36 -0700 Subject: [PATCH 1/7] Implemented network service --- Server/service/jobQueue.js | 31 +++++-------- Server/service/networkService.js | 79 ++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 Server/service/networkService.js diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index 43983bc48..511f01e9a 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -7,9 +7,8 @@ const connection = { const JOBS_PER_WORKER = 5; const logger = require("../utils/logger"); const { errorMessages, successMessages } = require("../utils/messages"); +const NetworkService = require("./networkService"); const SERVICE_NAME = "JobQueue"; -const axios = require("axios"); -const ping = require("ping"); class JobQueue { /** @@ -23,6 +22,7 @@ class JobQueue { }); this.workers = []; this.db = null; + this.networkService = null; } /** @@ -36,6 +36,7 @@ class JobQueue { const queue = new JobQueue(); try { queue.db = db; + queue.networkService = new NetworkService(db); const monitors = await db.getAllMonitors(); for (const monitor of monitors) { await queue.addJob(monitor.id, monitor); @@ -57,24 +58,14 @@ class JobQueue { const worker = new Worker( QUEUE_NAME, async (job) => { - // TODO Extract to service - if (job.data.type === "ping") { - const response = await ping.promise.probe(job.data.url); - // TODO insert checks into DB - if (response.alive === true) { - console.log(`${job.data.url} is alive`); - } else { - console.log(`${job.data.url} is dead`); - } - } else if (job.data.type === "http") { - const response = await axios.get(job.data.url); - // TODO create a check object and save it to the db - if (response.status >= 200 && response.status < 300) { - // TODO insert checks into DB - console.log(`${job.data.url} is alive`); - } else { - console.log(`${job.data.url} is dead`); - } + try { + await this.networkService.getStatus(job); + } catch (error) { + logger.error(`Error processing job ${job.id}: ${error.message}`, { + service: SERVICE_NAME, + jobId: job.id, + error: error, + }); } }, { diff --git a/Server/service/networkService.js b/Server/service/networkService.js new file mode 100644 index 000000000..ed2006917 --- /dev/null +++ b/Server/service/networkService.js @@ -0,0 +1,79 @@ +const axios = require("axios"); +const ping = require("ping"); +const logger = require("../utils/logger"); + +class NetworkService { + constructor(db) { + this.db = db; + this.TYPE_PING = "ping"; + this.TYPE_HTTP = "http"; + this.SERVICE_NAME = "NetworkService"; + } + + async handlePing(job) { + try { + const response = await ping.promise.probe(job.data.url); + const isAlive = response.alive; + await this.logAndStoreCheck(job, isAlive); + return isAlive; + } catch (error) { + await this.logAndStoreCheck(job, false, error); + return false; + } + } + + async handleHttp(job) { + try { + const response = await axios.get(job.data.url); + const isAlive = response.status >= 200 && response.status < 300; + await this.logAndStoreCheck(job, isAlive); + return isAlive; + } catch (error) { + await this.logAndStoreCheck(job, false, error); + return false; + } + } + + async getStatus(job) { + try { + switch (job.data.type) { + case this.TYPE_PING: + return await this.handlePing(job); + case this.TYPE_HTTP: + return await this.handleHttp(job); + default: + console.error(`Unsupported type: ${job.data.type}`, { + service: this.SERVICE_NAME, + timestamp: new Date().toISOString(), + }); + return false; + } + } catch (error) { + throw error; + } + } + + async logAndStoreCheck(job, isAlive, error = null) { + const status = isAlive ? "alive" : "dead"; + if (error) { + logger.error(`Error processing job ${job.id}: ${error.message}`, { + service: this.SERVICE_NAME, + jobId: job.id, + error: error, + }); + return; + } + + if (isAlive) { + console.log(`Job ${job.data.url} is alive`); + return; + } + + if (!isAlive) { + console.log(`Job ${job.data.url} is dead`); + return; + } + } +} + +module.exports = NetworkService; From a0ca205d6a37c7436a0a96b4b8110f3106e52b65 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 17 Jun 2024 11:05:22 -0700 Subject: [PATCH 2/7] Added response time --- Server/service/networkService.js | 50 ++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/Server/service/networkService.js b/Server/service/networkService.js index ed2006917..12ff8a99b 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -11,49 +11,55 @@ class NetworkService { } async handlePing(job) { + const startTime = Date.now(); try { const response = await ping.promise.probe(job.data.url); const isAlive = response.alive; - await this.logAndStoreCheck(job, isAlive); + const endTime = Date.now(); + const responseTime = endTime - startTime; + await this.logAndStoreCheck(job, isAlive, responseTime); return isAlive; } catch (error) { - await this.logAndStoreCheck(job, false, error); + const endTime = Date.now(); + const responseTime = endTime - startTime; + await this.logAndStoreCheck(job, false, responseTime, error); return false; } } async handleHttp(job) { try { + const startTime = Date.now(); const response = await axios.get(job.data.url); const isAlive = response.status >= 200 && response.status < 300; - await this.logAndStoreCheck(job, isAlive); + const endTime = Date.now(); + const responseTime = endTime - startTime; + await this.logAndStoreCheck(job, isAlive, responseTime); return isAlive; } catch (error) { - await this.logAndStoreCheck(job, false, error); + const endTime = Date.now(); + const responseTime = endTime - startTime; + await this.logAndStoreCheck(job, false, responseTime, error); return false; } } async getStatus(job) { - try { - switch (job.data.type) { - case this.TYPE_PING: - return await this.handlePing(job); - case this.TYPE_HTTP: - return await this.handleHttp(job); - default: - console.error(`Unsupported type: ${job.data.type}`, { - service: this.SERVICE_NAME, - timestamp: new Date().toISOString(), - }); - return false; - } - } catch (error) { - throw error; + switch (job.data.type) { + case this.TYPE_PING: + return await this.handlePing(job); + case this.TYPE_HTTP: + return await this.handleHttp(job); + default: + console.error(`Unsupported type: ${job.data.type}`, { + service: this.SERVICE_NAME, + timestamp: new Date().toISOString(), + }); + return false; } } - async logAndStoreCheck(job, isAlive, error = null) { + async logAndStoreCheck(job, isAlive, responseTime, error = null) { const status = isAlive ? "alive" : "dead"; if (error) { logger.error(`Error processing job ${job.id}: ${error.message}`, { @@ -65,7 +71,9 @@ class NetworkService { } if (isAlive) { - console.log(`Job ${job.data.url} is alive`); + console.log( + `Job ${job.data.url} is alive, response time: ${responseTime}ms` + ); return; } From 0b52a5bdc7e7a9469d422f50c07021f14bcec0c7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 17 Jun 2024 11:33:55 -0700 Subject: [PATCH 3/7] Fixed error handling, refactored response time measurement --- Server/service/networkService.js | 54 ++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 12ff8a99b..1e1da69c2 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -10,36 +10,50 @@ class NetworkService { this.SERVICE_NAME = "NetworkService"; } - async handlePing(job) { + async measureResponseTime(operation) { const startTime = Date.now(); try { - const response = await ping.promise.probe(job.data.url); - const isAlive = response.alive; + const response = await operation(); const endTime = Date.now(); - const responseTime = endTime - startTime; + return { responseTime: endTime - startTime, response }; + } catch (error) { + throw error; + } + } + + async handlePing(job) { + const operation = async () => { + const response = await ping.promise.probe(job.data.url); + return response; + }; + + try { + const { responseTime, response } = await this.measureResponseTime( + operation + ); + const isAlive = response.alive; await this.logAndStoreCheck(job, isAlive, responseTime); return isAlive; } catch (error) { - const endTime = Date.now(); - const responseTime = endTime - startTime; - await this.logAndStoreCheck(job, false, responseTime, error); + await this.logAndStoreCheck(job, false, 0, error); return false; } } async handleHttp(job) { - try { - const startTime = Date.now(); + const operation = async () => { const response = await axios.get(job.data.url); + return response; + }; + try { + const { responseTime, response } = await this.measureResponseTime( + operation + ); const isAlive = response.status >= 200 && response.status < 300; - const endTime = Date.now(); - const responseTime = endTime - startTime; await this.logAndStoreCheck(job, isAlive, responseTime); return isAlive; } catch (error) { - const endTime = Date.now(); - const responseTime = endTime - startTime; - await this.logAndStoreCheck(job, false, responseTime, error); + await this.logAndStoreCheck(job, false, 0, error); return false; } } @@ -62,11 +76,7 @@ class NetworkService { async logAndStoreCheck(job, isAlive, responseTime, error = null) { const status = isAlive ? "alive" : "dead"; if (error) { - logger.error(`Error processing job ${job.id}: ${error.message}`, { - service: this.SERVICE_NAME, - jobId: job.id, - error: error, - }); + console.log(`Job ${job.data.url} is dead`); return; } @@ -77,10 +87,8 @@ class NetworkService { return; } - if (!isAlive) { - console.log(`Job ${job.data.url} is dead`); - return; - } + console.log(`Job ${job.data.url} is dead`); + return; } } From 6801d066a13ed92016e1c308f0da4332aa5abff1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 17 Jun 2024 12:08:41 -0700 Subject: [PATCH 4/7] Added delete cascade for Montiors. Alerts and Checks now also deleted --- Server/controllers/monitorController.js | 14 ++++++------ Server/db/MongoDB.js | 14 ++++++++++++ Server/middleware/verifyOwnership.js | 2 +- Server/routes/alertRoute.js | 29 ++++++++++++++++++++----- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index fb1a0fc78..c883d32bb 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -161,6 +161,8 @@ const deleteMonitor = async (req, res, next) => { try { const monitor = await req.db.deleteMonitor(req, res, next); + // Delete associated checks and alerts + console.log(monitor); req.jobQueue.deleteJob(monitor); /** @@ -200,13 +202,11 @@ const editMonitor = async (req, res, next) => { try { const editedMonitor = await req.db.editMonitor(req, res); - return res - .status(200) - .json({ - success: true, - msg: successMessages.MONITOR_EDIT, - data: editedMonitor, - }); + return res.status(200).json({ + success: true, + msg: successMessages.MONITOR_EDIT, + data: editedMonitor, + }); } catch (error) { error.service = SERVICE_NAME; next(error); diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index 4f1d0c5a7..fb2d3e6d7 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -249,9 +249,12 @@ const deleteMonitor = async (req, res) => { const monitorId = req.params.monitorId; try { const monitor = await Monitor.findByIdAndDelete(monitorId); + deleteChecks(monitorId); + deleteAlertByMonitorId(monitorId); if (!monitor) { throw new Error(errorMessages.DB_FIND_MONTIOR_BY_ID(monitorId)); } + return monitor; } catch (error) { throw error; @@ -319,6 +322,7 @@ const createCheck = async (checkData) => { const getChecks = async (monitorId) => { try { const checks = await Check.find({ monitorId }); + console.log(checks); return checks; } catch (error) { throw error; @@ -467,6 +471,15 @@ const deleteAlert = async (alertId) => { } }; +const deleteAlertByMonitorId = async (monitorId) => { + try { + const result = await Alert.deleteMany({ monitorId }); + return result; + } catch (error) { + throw error; + } +}; + module.exports = { connect, insertUser, @@ -490,4 +503,5 @@ module.exports = { getAlertById, editAlert, deleteAlert, + deleteAlertByMonitorId, }; diff --git a/Server/middleware/verifyOwnership.js b/Server/middleware/verifyOwnership.js index cc7f544f7..950d0a6e4 100644 --- a/Server/middleware/verifyOwnership.js +++ b/Server/middleware/verifyOwnership.js @@ -20,7 +20,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"); + console.log(userId.toString(), doc.userId.toString()); const error = new Error(errorMessages.VERIFY_OWNER_UNAUTHORIZED); error.status = 403; throw error; diff --git a/Server/routes/alertRoute.js b/Server/routes/alertRoute.js index be252e8c4..ba30fd117 100644 --- a/Server/routes/alertRoute.js +++ b/Server/routes/alertRoute.js @@ -2,25 +2,42 @@ const router = require("express").Router(); const alertController = require("../controllers/alertController"); const { verifyOwnership } = require("../middleware/verifyOwnership"); const Alert = require("../models/Alert"); +const Monitor = require("../models/Monitor"); // Create alert -router.post("/:monitorId", alertController.createAlert); +router.post( + "/:monitorId", + verifyOwnership(Monitor, "monitorId"), + alertController.createAlert +); // Get all alerts for a user -router.get("/user/:userId", alertController.getAlertsByUserId); +router.get( + "/user/:userId", + verifyOwnership(Monitor, "monitorId"), + alertController.getAlertsByUserId +); // Get all alerts for a monitor -router.get("/monitor/:monitorId", alertController.getAlertsByMonitorId); +router.get( + "/monitor/:monitorId", + verifyOwnership(Monitor, "monitorId"), + alertController.getAlertsByMonitorId +); // Get a single alert -router.get("/:alertId", alertController.getAlertById); +router.get( + "/:alertId", + verifyOwnership(Monitor, "monitorId"), + alertController.getAlertById +); // Edit router.post( "/edit/:alertId", - verifyOwnership(Alert, "alertId"), + verifyOwnership(Monitor, "monitorId"), alertController.editAlert ); //Delete router.post( "/delete/:alertId", - verifyOwnership(Alert, "alertId"), + verifyOwnership(Monitor, "monitorId"), alertController.deleteAlert ); From 771cce98b5ee6a8de63307a31fb45f38fec6a470 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 17 Jun 2024 13:52:20 -0700 Subject: [PATCH 5/7] Checks now inserted into db during monitor status check --- Server/controllers/monitorController.js | 6 +- Server/db/MongoDB.js | 16 +---- Server/service/jobQueue.js | 2 +- Server/service/networkService.js | 86 ++++++++++++++++++------- 4 files changed, 69 insertions(+), 41 deletions(-) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index c883d32bb..a8ef8aecc 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -162,9 +162,9 @@ const deleteMonitor = async (req, res, next) => { try { const monitor = await req.db.deleteMonitor(req, res, next); // Delete associated checks and alerts - console.log(monitor); - req.jobQueue.deleteJob(monitor); - + await req.jobQueue.deleteJob(monitor); + await req.db.deleteChecks(monitor._id); + await req.db.deleteAlertByMonitorId(monitor._id); /** * TODO * We should remove all checks and alerts associated with this monitor diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index fb2d3e6d7..ad37ee084 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -249,14 +249,12 @@ const deleteMonitor = async (req, res) => { const monitorId = req.params.monitorId; try { const monitor = await Monitor.findByIdAndDelete(monitorId); - deleteChecks(monitorId); - deleteAlertByMonitorId(monitorId); if (!monitor) { throw new Error(errorMessages.DB_FIND_MONTIOR_BY_ID(monitorId)); } - return monitor; } catch (error) { + console.log("CATHCING DELETE MONITOR ERROR"); throw error; } }; @@ -340,11 +338,7 @@ const getChecks = async (monitorId) => { const deleteChecks = async (monitorId) => { try { const result = await Check.deleteMany({ monitorId }); - if (result.deletedCount > 0) { - return result.deletedCount; - } else { - throw new Error(errorMessages.DB_DELETE_CHECKS(monitorId)); - } + return result.deletedCount; } catch (error) { throw error; } @@ -461,11 +455,7 @@ const editAlert = async (alertId, alertData) => { const deleteAlert = async (alertId) => { try { const result = await Alert.findByIdAndDelete(alertId); - if (result) { - return result; - } else { - throw new Error(errorMessages.DB_DELETE_ALERT(alertId)); - } + return result; } catch (error) { throw error; } diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index 511f01e9a..ae735ae86 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -59,7 +59,7 @@ class JobQueue { QUEUE_NAME, async (job) => { try { - await this.networkService.getStatus(job); + const res = await this.networkService.getStatus(job); } catch (error) { logger.error(`Error processing job ${job.id}: ${error.message}`, { service: SERVICE_NAME, diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 1e1da69c2..1fa2daac4 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -1,6 +1,7 @@ const axios = require("axios"); const ping = require("ping"); const logger = require("../utils/logger"); +const Check = require("../models/Check"); class NetworkService { constructor(db) { @@ -10,6 +11,12 @@ class NetworkService { this.SERVICE_NAME = "NetworkService"; } + /** + * 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 { @@ -17,10 +24,17 @@ class NetworkService { const endTime = Date.now(); return { responseTime: endTime - startTime, response }; } catch (error) { + const endTime = Date.now(); + error.responseTime = endTime - startTime; 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); @@ -32,14 +46,17 @@ class NetworkService { operation ); const isAlive = response.alive; - await this.logAndStoreCheck(job, isAlive, responseTime); - return isAlive; + return await this.logAndStoreCheck(job, isAlive, responseTime); } catch (error) { - await this.logAndStoreCheck(job, false, 0, error); - return false; + return await this.logAndStoreCheck(job, false, error.responseTime, error); } } + /** + * 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) { const operation = async () => { const response = await axios.get(job.data.url); @@ -50,14 +67,18 @@ class NetworkService { operation ); const isAlive = response.status >= 200 && response.status < 300; - await this.logAndStoreCheck(job, isAlive, responseTime); - return isAlive; + return await this.logAndStoreCheck(job, isAlive, responseTime); } catch (error) { - await this.logAndStoreCheck(job, false, 0, error); - return false; + return await this.logAndStoreCheck(job, false, error.responseTime, error); } } - + /** + * 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: @@ -65,30 +86,47 @@ class NetworkService { case this.TYPE_HTTP: return await this.handleHttp(job); default: - console.error(`Unsupported type: ${job.data.type}`, { + logger.error(`Unsupported type: ${job.data.type}`, { service: this.SERVICE_NAME, - timestamp: new Date().toISOString(), + jobId: job.id, }); return false; } } + /** + * Logs and stores the result of a check for a specific job. + * This function creates a new Check object with the job's details and the result of the check, + * then attempts to save this object to the database. If the save operation is successful, + * it returns the status of the inserted check. If an error occurs during the save operation, + * it logs the error and returns false. + * + * @param {Object} job - The job object containing data necessary for the check. + * @param {boolean} isAlive - The result of the check, indicating if the target is alive. + * @param {number} responseTime - The response time measured during the check. + * @param {Error} [error=null] - Optional error object if an error occurred during the check. + * @returns {Promise} The status of the inserted check if successful, otherwise false. + */ async logAndStoreCheck(job, isAlive, responseTime, error = null) { - const status = isAlive ? "alive" : "dead"; - if (error) { - console.log(`Job ${job.data.url} is dead`); - return; - } + const check = new Check({ + monitorId: job.data._id, + status: isAlive, + responseTime, + statusCode: 0, + message: error ? error.message : "", + }); - if (isAlive) { - console.log( - `Job ${job.data.url} is alive, response time: ${responseTime}ms` - ); - return; + try { + const insertedCheck = await check.save(); + return insertedCheck.status; + } catch (error) { + logger.error(`Error wrtiting check for ${job.id}`, { + service: this.SERVICE_NAME, + jobId: job.id, + error: error, + }); + return false; } - - console.log(`Job ${job.data.url} is dead`); - return; } } From 7fa3f95cd0e3a61790f9c4dbbcf33fdbc7d8ae68 Mon Sep 17 00:00:00 2001 From: M M Date: Mon, 17 Jun 2024 16:59:07 -0700 Subject: [PATCH 6/7] Updated toast with prop, prop validation, and JSDocs. --- Client/src/Components/Toast/index.jsx | 30 ++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/Client/src/Components/Toast/index.jsx b/Client/src/Components/Toast/index.jsx index ceb7a6f60..87e6c2ea9 100644 --- a/Client/src/Components/Toast/index.jsx +++ b/Client/src/Components/Toast/index.jsx @@ -1,19 +1,32 @@ +import PropTypes from "prop-types"; import ComplexAlert from "../../Components/Icons/ComplexAlert/ComplexAlert"; import AnnouncementsDualButtonWithIcon from "../../Components/Announcements/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon"; import "react-toastify/dist/ReactToastify.css"; import { toast, ToastContainer } from "react-toastify"; import "./index.css"; -const ToastComponent = () => { +/** + * ToastComponent displays a notification that is triggered by error messages. + * + * @component + * @param {Object} props - The component props + * @param {string} props.subject - The subject or title of the toast message + * @param {string} props.body - The body content of the toast message + * @param {string} props.esc - The text for the dismiss action button + * @param {string} props.primary - The text for the primary action button + * @returns {JSX.Element} JSX element representing the ToastComponent + */ + +const ToastComponent = ({ subject, body, esc, primary }) => { const displayMsg = () => { toast( ({ closeToast, toastProps }) => ( } - subject="There was a problem with that action" - body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid pariatur, ipsum dolor." - esc="Dismiss" - primary="Learn more" + subject={subject} + body={body} + esc={esc} + primary={primary} closeToast={closeToast} /> ), @@ -29,4 +42,11 @@ const ToastComponent = () => { ); }; +ToastComponent.propTypes = { + subject: PropTypes.string.isRequired, + body: PropTypes.string.isRequired, + esc: PropTypes.string.isRequired, + primary: PropTypes.string.isRequired, +}; + export default ToastComponent; From 59562b10c949d2c3981ddc0c62dbc9e54bb93ae8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 18 Jun 2024 11:42:25 -0700 Subject: [PATCH 7/7] Improved network service error handling --- Server/controllers/monitorController.js | 13 ++++++ Server/db/MongoDB.js | 16 ++++++- Server/middleware/verifyJWT.js | 38 +++++++++-------- Server/routes/monitorRoute.js | 2 + Server/service/networkService.js | 55 +++++++++++++++++++------ 5 files changed, 91 insertions(+), 33 deletions(-) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index a8ef8aecc..800ec994c 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -180,6 +180,18 @@ const deleteMonitor = async (req, res, next) => { } }; +const deleteAllMonitors = async (req, res) => { + try { + const deleteCount = await req.db.deleteAllMonitors(); + return res + .status(200) + .json({ success: true, msg: `Deleted ${deleteCount} monitors` }); + } catch (error) { + error.service = SERVICE_NAME; + next(error); + } +}; + /** * Edit a monitor by ID * @async @@ -219,5 +231,6 @@ module.exports = { getMonitorsByUserId, createMonitor, deleteMonitor, + deleteAllMonitors, editMonitor, }; diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index ad37ee084..40db3c5e2 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -254,7 +254,19 @@ const deleteMonitor = async (req, res) => { } return monitor; } catch (error) { - console.log("CATHCING DELETE MONITOR ERROR"); + throw error; + } +}; + +/** + * DELETE ALL MONITORS (TEMP) + */ + +const deleteAllMonitors = async (req, res) => { + try { + const deletedCount = await Monitor.deleteMany({}); + return deletedCount.deletedCount; + } catch (error) { throw error; } }; @@ -320,7 +332,6 @@ const createCheck = async (checkData) => { const getChecks = async (monitorId) => { try { const checks = await Check.find({ monitorId }); - console.log(checks); return checks; } catch (error) { throw error; @@ -483,6 +494,7 @@ module.exports = { getMonitorsByUserId, createMonitor, deleteMonitor, + deleteAllMonitors, editMonitor, createCheck, getChecks, diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js index 9c9a3759a..1dbbe8d99 100644 --- a/Server/middleware/verifyJWT.js +++ b/Server/middleware/verifyJWT.js @@ -15,34 +15,36 @@ const verifyJWT = (req, res, next) => { const token = req.headers["authorization"]; // Make sure a token is provided if (!token) { - const error = new Error(error.errorMessages.NO_AUTH_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 parsedToken = token.slice(TOKEN_PREFIX.length, token.length); - // Verify the token's authenticity - jwt.verify(parsedToken, process.env.JWT_SECRET, (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 }); - } - //Add the user to the request object for use in the route - req.user = decoded; - next(); - }); - } else { + 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; next(error); + return; } + + const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); + // Verify the token's authenticity + jwt.verify(parsedToken, process.env.JWT_SECRET, (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 }); + } + //Add the user to the request object for use in the route + req.user = decoded; + next(); + }); }; module.exports = { verifyJWT }; diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js index ffdce1d4d..ae7a5852e 100644 --- a/Server/routes/monitorRoute.js +++ b/Server/routes/monitorRoute.js @@ -18,4 +18,6 @@ router.post( verifyOwnership(Monitor, "monitorId"), monitorController.editMonitor ); + +router.delete("/delete/all", monitorController.deleteAllMonitors); module.exports = router; diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 1fa2daac4..f3f40999d 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -9,6 +9,7 @@ class NetworkService { this.TYPE_PING = "ping"; this.TYPE_HTTP = "http"; this.SERVICE_NAME = "NetworkService"; + this.NETWORK_ERROR = 5000; } /** @@ -46,9 +47,20 @@ class NetworkService { operation ); const isAlive = response.alive; - return await this.logAndStoreCheck(job, isAlive, responseTime); + + const check = new Check({ + monitorId: job.data._id, + status: isAlive, + responseTime, + }); + return await this.logAndStoreCheck(check); } catch (error) { - return await this.logAndStoreCheck(job, false, error.responseTime, error); + const check = new Check({ + monitorId: job.data._id, + status: false, + responseTime: error.responseTime, + }); + return await this.logAndStoreCheck(check); } } @@ -58,20 +70,45 @@ class NetworkService { * @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; }; + + // attempt connection try { const { responseTime, response } = await this.measureResponseTime( operation ); + + // check if response is in the 200 range, if so, service is up const isAlive = response.status >= 200 && response.status < 300; - return await this.logAndStoreCheck(job, isAlive, responseTime); + + //Create a check with relevant data + const check = new Check({ + monitorId: job.data._id, + status: isAlive, + responseTime, + statusCode: response.status, + }); + return await this.logAndStoreCheck(check); } catch (error) { - return await this.logAndStoreCheck(job, false, error.responseTime, error); + const check = new Check({ + monitorId: job.data._id, + status: false, + responseTime: error.responseTime, + }); + // The server returned a response + if (error.response) { + check.statusCode = error.response.status; + } else { + check.statusCode = this.NETWORK_ERROR; + } + return await this.logAndStoreCheck(check); } } + /** * Retrieves the status of a given job based on its type. * For unsupported job types, it logs an error and returns false. @@ -107,15 +144,7 @@ class NetworkService { * @param {Error} [error=null] - Optional error object if an error occurred during the check. * @returns {Promise} The status of the inserted check if successful, otherwise false. */ - async logAndStoreCheck(job, isAlive, responseTime, error = null) { - const check = new Check({ - monitorId: job.data._id, - status: isAlive, - responseTime, - statusCode: 0, - message: error ? error.message : "", - }); - + async logAndStoreCheck(check) { try { const insertedCheck = await check.save(); return insertedCheck.status;