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;
diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js
index fb1a0fc78..800ec994c 100644
--- a/Server/controllers/monitorController.js
+++ b/Server/controllers/monitorController.js
@@ -161,8 +161,10 @@ const deleteMonitor = async (req, res, next) => {
try {
const monitor = await req.db.deleteMonitor(req, res, next);
- req.jobQueue.deleteJob(monitor);
-
+ // Delete associated checks and alerts
+ 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
@@ -178,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
@@ -200,13 +214,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);
@@ -219,5 +231,6 @@ module.exports = {
getMonitorsByUserId,
createMonitor,
deleteMonitor,
+ deleteAllMonitors,
editMonitor,
};
diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js
index 4f1d0c5a7..40db3c5e2 100644
--- a/Server/db/MongoDB.js
+++ b/Server/db/MongoDB.js
@@ -258,6 +258,19 @@ const deleteMonitor = async (req, res) => {
}
};
+/**
+ * DELETE ALL MONITORS (TEMP)
+ */
+
+const deleteAllMonitors = async (req, res) => {
+ try {
+ const deletedCount = await Monitor.deleteMany({});
+ return deletedCount.deletedCount;
+ } catch (error) {
+ throw error;
+ }
+};
+
/**
* Edit a monitor by ID
* @async
@@ -336,11 +349,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;
}
@@ -457,11 +466,16 @@ 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;
+ }
+};
+
+const deleteAlertByMonitorId = async (monitorId) => {
+ try {
+ const result = await Alert.deleteMany({ monitorId });
+ return result;
} catch (error) {
throw error;
}
@@ -480,6 +494,7 @@ module.exports = {
getMonitorsByUserId,
createMonitor,
deleteMonitor,
+ deleteAllMonitors,
editMonitor,
createCheck,
getChecks,
@@ -490,4 +505,5 @@ module.exports = {
getAlertById,
editAlert,
deleteAlert,
+ deleteAlertByMonitorId,
};
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/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
);
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/jobQueue.js b/Server/service/jobQueue.js
index 43983bc48..ae735ae86 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 {
+ const res = 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..f3f40999d
--- /dev/null
+++ b/Server/service/networkService.js
@@ -0,0 +1,162 @@
+const axios = require("axios");
+const ping = require("ping");
+const logger = require("../utils/logger");
+const Check = require("../models/Check");
+
+class NetworkService {
+ constructor(db) {
+ this.db = db;
+ this.TYPE_PING = "ping";
+ this.TYPE_HTTP = "http";
+ this.SERVICE_NAME = "NetworkService";
+ this.NETWORK_ERROR = 5000;
+ }
+
+ /**
+ * Measures the response time of an asynchronous operation.
+ * @param {Function} operation - An asynchronous operation to measure.
+ * @returns {Promise<{responseTime: number, response: any}>} An object containing the response time in milliseconds and the response from the operation.
+ * @throws {Error} The error object from the operation, contains response time.
+ */
+ async measureResponseTime(operation) {
+ const startTime = Date.now();
+ try {
+ const response = await operation();
+ const endTime = Date.now();
+ return { responseTime: endTime - startTime, response };
+ } catch (error) {
+ const endTime = Date.now();
+ error.responseTime = endTime - startTime;
+ throw error;
+ }
+ }
+
+ /**
+ * Handles the ping operation for a given job, measures its response time, and logs the result.
+ * @param {Object} job - The job object containing data for the ping operation.
+ * @returns {Promise<{boolean}} The result of logging and storing the check
+ */
+ async handlePing(job) {
+ const operation = async () => {
+ const response = await ping.promise.probe(job.data.url);
+ return response;
+ };
+
+ try {
+ const { responseTime, response } = await this.measureResponseTime(
+ operation
+ );
+ const isAlive = response.alive;
+
+ const check = new Check({
+ monitorId: job.data._id,
+ status: isAlive,
+ responseTime,
+ });
+ return await this.logAndStoreCheck(check);
+ } catch (error) {
+ const check = new Check({
+ monitorId: job.data._id,
+ status: false,
+ responseTime: error.responseTime,
+ });
+ return await this.logAndStoreCheck(check);
+ }
+ }
+
+ /**
+ * Handles the http operation for a given job, measures its response time, and logs the result.
+ * @param {Object} job - The job object containing data for the ping operation.
+ * @returns {Promise<{boolean}} The result of logging and storing the check
+ */
+ async handleHttp(job) {
+ // Define operation for timing
+ const operation = async () => {
+ const response = await axios.get(job.data.url);
+ return response;
+ };
+
+ // 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;
+
+ //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) {
+ 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.
+ *
+ * @param {Object} job - The job object containing data necessary for processing.
+ * @returns {Promise} The status of the job if it is supported and processed successfully, otherwise false.
+ */
+ async getStatus(job) {
+ switch (job.data.type) {
+ case this.TYPE_PING:
+ return await this.handlePing(job);
+ case this.TYPE_HTTP:
+ return await this.handleHttp(job);
+ default:
+ logger.error(`Unsupported type: ${job.data.type}`, {
+ service: this.SERVICE_NAME,
+ 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(check) {
+ 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;
+ }
+ }
+}
+
+module.exports = NetworkService;