From e47d3685ce261c5ca45c871daec48a8a991caac2 Mon Sep 17 00:00:00 2001 From: Alexander Holliday Date: Fri, 26 Jul 2024 18:25:20 -0700 Subject: [PATCH] Moved all methods to modules (#460) * Moved all methods to modules * removed old mongoDB file --- Server/db/MongoDB.js | 801 ------------------ Server/db/mongo/MongoDB.js | 147 ++++ Server/db/mongo/modules/alertModule.js | 140 +++ Server/db/mongo/modules/checkModule.js | 58 ++ Server/db/mongo/modules/inviteModule.js | 37 + Server/db/mongo/modules/monitorModule.js | 238 ++++++ .../db/mongo/modules/pageSpeedCheckModule.js | 64 ++ Server/db/mongo/modules/recoveryModule.js | 80 ++ Server/db/mongo/modules/userModule.js | 160 ++++ Server/index.js | 2 +- 10 files changed, 925 insertions(+), 802 deletions(-) delete mode 100644 Server/db/MongoDB.js create mode 100644 Server/db/mongo/MongoDB.js create mode 100644 Server/db/mongo/modules/alertModule.js create mode 100644 Server/db/mongo/modules/checkModule.js create mode 100644 Server/db/mongo/modules/inviteModule.js create mode 100644 Server/db/mongo/modules/monitorModule.js create mode 100644 Server/db/mongo/modules/pageSpeedCheckModule.js create mode 100644 Server/db/mongo/modules/recoveryModule.js create mode 100644 Server/db/mongo/modules/userModule.js diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js deleted file mode 100644 index c01b0ed71..000000000 --- a/Server/db/MongoDB.js +++ /dev/null @@ -1,801 +0,0 @@ -const Monitor = require("../models/Monitor"); -const mongoose = require("mongoose"); -const UserModel = require("../models/user"); -const Check = require("../models/Check"); -const Alert = require("../models/Alert"); -const RecoveryToken = require("../models/RecoveryToken"); -const PageSpeedCheck = require("../models/PageSpeedCheck"); -const InviteToken = require("../models/InviteToken"); -const crypto = require("crypto"); -const DUPLICATE_KEY_CODE = 11000; // MongoDB error code for duplicate key -const { errorMessages, successMessages } = require("../utils/messages"); -const { GenerateAvatarImage } = require("../utils/imageProcessing"); -const { ParseBoolean } = require("../utils/utils"); -const connect = async () => { - try { - await mongoose.connect(process.env.DB_CONNECTION_STRING); - console.log("Connected to MongoDB"); - } catch (error) { - console.error("Failed to connect to MongoDB"); - throw error; - } -}; - -//**************************************** -// Users -//**************************************** - -/** - * Insert a User - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const insertUser = async (req, res) => { - try { - const userData = { ...req.body }; - if (req.file) { - // 1. Save the full size image - userData.profileImage = { - data: req.file.buffer, - contentType: req.file.mimetype, - }; - - // 2. Get the avatar sized image - const avatar = await GenerateAvatarImage(req.file); - userData.avatarImage = avatar; - } - console.log(userData); - const newUser = new UserModel(userData); - await newUser.save(); - return await UserModel.findOne({ _id: newUser._id }) - .select("-password") - .select("-profileImage"); // .select() doesn't work with create, need to save then find - } catch (error) { - if (error.code === DUPLICATE_KEY_CODE) { - throw new Error(errorMessages.DB_USER_EXISTS); - } - throw error; - } -}; - -/** - * Get User by Email - * Gets a user by Email. Not sure if we'll ever need this except for login. - * If not needed except for login, we can move password comparison here - * Throws error if user not found - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getUserByEmail = async (req, res) => { - try { - // Need the password to be able to compare, removed .select() - // We can strip the hash before returing the user - const user = await UserModel.findOne({ email: req.body.email }).select( - "-profileImage" - ); - if (user) { - return user; - } else { - throw new Error(errorMessages.DB_USER_NOT_FOUND); - } - } catch (error) { - throw error; - } -}; - -/** - * 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; - - try { - const candidateUser = { ...req.body }; - // ****************************************** - // Handle profile image - // ****************************************** - - if (ParseBoolean(candidateUser.deleteProfileImage) === true) { - candidateUser.profileImage = null; - candidateUser.avatarImage = null; - } else if (req.file) { - // 1. Save the full size image - candidateUser.profileImage = { - data: req.file.buffer, - contentType: req.file.mimetype, - }; - - // 2. Get the avaatar sized image - const avatar = await GenerateAvatarImage(req.file); - candidateUser.avatarImage = avatar; - } - - // ****************************************** - // End handling profile image - // ****************************************** - - const updatedUser = await UserModel.findByIdAndUpdate( - candidateUserId, - candidateUser, - { new: true } // Returns updated user instead of pre-update user - ) - .select("-password") - .select("-profileImage"); - return updatedUser; - } catch (error) { - throw error; - } -}; - -/** - * Delete a user by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const deleteUser = async (req, res) => { - const userId = req.params.userId; - try { - const deletedUser = await UserModel.findByIdAndDelete(userId); - if (!deletedUser) { - throw new Error(errorMessages.DB_USER_NOT_FOUND); - } - return deletedUser; - } catch (error) { - throw error; - } -}; - -const getAllUsers = async (req, res) => { - try { - const users = await UserModel.find() - .select("-password") - .select("-profileImage"); - return users; - } catch (error) { - throw error; - } -}; - -const requestInviteToken = async (req, res) => { - try { - await InviteToken.deleteMany({ email: req.body.email }); - let inviteToken = new InviteToken({ - email: req.body.email, - role: req.body.role, - token: crypto.randomBytes(32).toString("hex"), - }); - await inviteToken.save(); - return inviteToken; - } catch (error) { - throw error; - } -}; - -const getInviteToken = async (req, res) => { - try { - const invite = await InviteToken.findOneAndDelete({ - token: req.body.token, - }); - if (invite === null) { - throw new Error(errorMessages.AUTH_INVITE_NOT_FOUND); - } - return invite; - } catch (error) { - throw error; - } -}; - -/** - * Request a recovery token - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const requestRecoveryToken = async (req, res) => { - try { - // Delete any existing tokens - await RecoveryToken.deleteMany({ email: req.body.email }); - let recoveryToken = new RecoveryToken({ - email: req.body.email, - token: crypto.randomBytes(32).toString("hex"), - }); - await recoveryToken.save(); - return recoveryToken; - } catch (error) { - throw error; - } -}; - -const validateRecoveryToken = async (req, res) => { - try { - const candidateToken = req.body.recoveryToken; - const recoveryToken = await RecoveryToken.findOne({ - token: candidateToken, - }); - if (recoveryToken !== null) { - return recoveryToken; - } else { - throw new Error(errorMessages.DB_TOKEN_NOT_FOUND); - } - } catch (error) { - throw error; - } -}; - -const resetPassword = async (req, res) => { - try { - const newPassword = req.body.password; - - // Validate token again - const recoveryToken = await validateRecoveryToken(req, res); - const user = await UserModel.findOne({ email: recoveryToken.email }); - - const match = await user.comparePassword(newPassword); - if (match === true) { - throw new Error(errorMessages.DB_RESET_PASSWORD_BAD_MATCH); - } - - if (user !== null) { - user.password = newPassword; - await user.save(); - await RecoveryToken.deleteMany({ email: recoveryToken.email }); - // Fetch the user again without the password - const userWithoutPassword = await UserModel.findOne({ - email: recoveryToken.email, - }) - .select("-password") - .select("-profileImage"); - return userWithoutPassword; - } else { - throw new Error(errorMessages.DB_USER_NOT_FOUND); - } - } catch (error) { - throw error; - } -}; - -const checkAdmin = async (req, res) => { - try { - const admin = await UserModel.findOne({ role: "admin" }); - if (admin !== null) { - return true; - } - return false; - } catch (error) { - throw error; - } -}; - -//**************************************** -// Monitors -//**************************************** - -/** - * Get all monitors - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise>} - * @throws {Error} - */ -const getAllMonitors = async (req, res) => { - try { - const monitors = await Monitor.find({ isActive: true }); - return monitors; - } catch (error) { - throw error; - } -}; - -/** - * Get a monitor by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getMonitorById = async (req, res) => { - try { - const monitor = await Monitor.findById(req.params.monitorId); - const checks = await Check.find({ monitorId: monitor._id }).sort({ - createdAt: 1, - }); - const monitorWithChecks = { ...monitor.toObject(), checks }; - return monitorWithChecks; - } catch (error) { - throw error; - } -}; - -/** - * Get monitors by UserID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise>} - * @throws {Error} - */ -const getMonitorsByUserId = async (req, res) => { - try { - const limit = req.query ? req.query.limit : undefined; - const monitors = await Monitor.find({ userId: req.params.userId }); - // Map each monitor to include its associated checks - const monitorsWithChecks = await Promise.all( - monitors.map(async (monitor) => { - if (limit) { - // Checks are order newest -> oldest - const checks = await Check.find({ monitorId: monitor._id }) - .sort({ - createdAt: -1, - }) - .limit(limit); - return { ...monitor.toObject(), checks }; - } else { - // Checks are order newest -> oldest - // TODO is this ever used? - const checks = await Check.find({ monitorId: monitor._id }).sort({ - createdAt: 1, - }); - return { ...monitor.toObject(), checks }; - } - }) - ); - - return monitorsWithChecks; - } catch (error) { - throw error; - } -}; - -/** - * Get monitors by UserID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getMonitorByIdForIncidents = async (req, res, next) => { - try { - const monitor = await Monitor.findById(req.params.monitorId); - const checks = await Check.find({ - monitorId: monitor._id, - status: false, - }).sort({ - createdAt: 1, - }); - const monitorWithChecks = { ...monitor.toObject(), checks }; - return monitorWithChecks; - } catch (error) { - throw error; - } -}; - -/** - * Get monitors by UserID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise>} - * @throws {Error} - */ -const getMonitorsByUserIdForIncidents = async (req, res) => { - try { - const monitors = await Monitor.find({ userId: req.params.userId }); - // Map each monitor to include its associated checks - const monitorsWithChecks = await Promise.all( - monitors.map(async (monitor) => { - const checks = await Check.find({ - monitorId: monitor._id, - status: false, - }).sort({ - createdAt: 1, - }); - return { ...monitor.toObject(), checks }; - }) - ); - return monitorsWithChecks; - } catch (error) { - throw error; - } -}; - -/** - * Create a monitor - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const createMonitor = async (req, res) => { - try { - const monitor = new Monitor({ ...req.body }); - monitor.userId = req.user._id; - await monitor.save(); - return monitor; - } catch (error) { - throw error; - } -}; - -/** - * Delete a monitor by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const deleteMonitor = async (req, res) => { - const monitorId = req.params.monitorId; - try { - const monitor = await Monitor.findByIdAndDelete(monitorId); - if (!monitor) { - throw new Error(errorMessages.DB_FIND_MONTIOR_BY_ID(monitorId)); - } - return monitor; - } catch (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; - } -}; - -/** - * Delete all monitors associated with a user ID - * @async - * @param {string} userId - The ID of the user whose monitors are to be deleted. - * @returns {Promise} A promise that resolves when the operation is complete. - */ -const deleteMonitorsByUserId = async (userId) => { - try { - const result = await Monitor.deleteMany({ userId: userId }); - return result; - } catch (error) { - throw error; - } -}; - -/** - * Edit a monitor by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const editMonitor = async (req, res) => { - const candidateId = req.params.monitorId; - const candidateMonitor = req.body; - try { - const editedMonitor = await Monitor.findByIdAndUpdate( - candidateId, - candidateMonitor, - { new: true } - ); - return editedMonitor; - } catch (error) { - throw error; - } -}; - -//**************************************** -// Page Speed Checks -//**************************************** - -/** - * Create a PageSpeed check for a monitor - * @async - * @param {Object} pageSpeedCheckData - * @param {string} pageSpeedCheckData.monitorId - * @param {number} pageSpeedCheckData.accessibility - * @param {number} pageSpeedCheckData.bestPractices - * @param {number} pageSpeedCheckData.seo - * @param {number} pageSpeedCheckData.performance - * @returns {Promise} - * @throws {Error} - */ - -const createPageSpeedCheck = async (pageSpeedCheckData) => { - try { - const pageSpeedCheck = await new PageSpeedCheck({ ...pageSpeedCheckData }).save(); - return pageSpeedCheck; - } catch (error) { - throw error; - } -}; - -/** - * Get all PageSpeed checks for a monitor - * @async - * @param {string} monitorId - * @returns {Promise>} - * @throws {Error} - */ - -const getPageSpeedChecks = async (monitorId) => { - try { - const pageSpeedChecks = await PageSpeedCheck.find({ monitorId }); - return pageSpeedChecks; - } catch (error) { - throw error; - } -}; - -/** - * Delete all PageSpeed checks for a monitor - * @async - * @param {string} monitorId - * @returns {number} - * @throws {Error} - */ - -const deletePageSpeedChecks = async (monitorId) => { - try { - const result = await PageSpeedCheck.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - throw error; - } -}; - -//**************************************** -// Checks -//**************************************** - -/** - * Create a check for a monitor - * @async - * @param {Object} checkData - * @param {string} checkData.monitorId - * @param {boolean} checkData.status - * @param {number} checkData.responseTime - * @param {number} checkData.statusCode - * @param {string} checkData.message - * @returns {Promise} - * @throws {Error} - */ - -const createCheck = async (checkData) => { - try { - const check = await new Check({ ...checkData }).save(); - return check; - } catch (error) { - throw error; - } -}; - -/** - * Get all checks for a monitor - * @async - * @param {string} monitorId - * @returns {Promise>} - * @throws {Error} - */ - -const getChecks = async (monitorId) => { - try { - const checks = await Check.find({ monitorId }); - return checks; - } catch (error) { - throw error; - } -}; - -/** - * Delete all checks for a monitor - * @async - * @param {string} monitorId - * @returns {number} - * @throws {Error} - */ - -const deleteChecks = async (monitorId) => { - try { - const result = await Check.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - throw error; - } -}; - -//**************************************** -// Alerts -//**************************************** - -/** - * Creates an Alert associated with a Monitor and User - * @async - * @param {Object} alertData - * @param {string} alertData.checkId - * @param {string} alert.monitorId - * @param {string} alert.userId - * @param {boolean} alert.status - * @param {string} alert.message - * @param {boolean} alert.notifiedStatus - * @param {boolean} alert.acknowledgeStatus - * @returns {>} - * @throws {Error} - */ -const createAlert = async (alertData) => { - try { - const alert = await new Alert({ ...alertData }).save(); - return alert; - } catch (error) { - throw error; - } -}; - -/** - * Gets all alerts a User has set - * @async - * @param {string} userId - * @returns {>} - * @throws {Error} - */ -const getAlertsByUserId = async (userId) => { - try { - const alerts = await Alert.find({ userId }); - return alerts; - } catch (error) { - throw error; - } -}; - -/** - * Gets all alerts a for an associated Monitor - * @async - * @param {string} monitorId - * @returns {>} - * @throws {Error} - */ -const getAlertsByMonitorId = async (monitorId) => { - try { - const alerts = await Alert.find({ monitorId }); - return alerts; - } catch (error) { - throw error; - } -}; - -/** - * Returns an alert with specified ID - * @async - * @param {string} alertId - * @returns {Promise} - * @throws {Error} - */ -const getAlertById = async (alertId) => { - try { - const alert = await Alert.findById(alertId); - return alert; - } catch (error) { - throw error; - } -}; - -/** - * Returns an edited alert with the specified ID - * @async - * @param {string} alertId - * @param {Object} alertData - * @param {string} alertData.checkId - * @param {string} alertData.monitorId - * @param {string} alertData.userId - * @param {boolean} alertData.status - * @param {string} alertData.message - * @param {boolean} alertData.notifiedStatus - * @param {boolean} alertData.acknowledgeStatus - * @param {Express.Response} res - * @returns {Promise>} - * @throws {Error} - */ -const editAlert = async (alertId, alertData) => { - try { - const editedAlert = await Alert.findByIdAndUpdate(alertId, alertData, { - new: true, - }); - return editedAlert; - } catch (error) { - throw error; - } -}; - -/** - * Deletes an alert with the specified ID - * @async - * @param {string} alertId - * @throws {Error} - */ -const deleteAlert = async (alertId) => { - try { - const result = await Alert.findByIdAndDelete(alertId); - return result; - } catch (error) { - throw error; - } -}; - -/** - * Deletes alerts by monitor ID. - * - * @param {string} monitorId - The ID of the monitor. - * @returns {Promise} A promise that resolves to the result of the delete operation. - * @throws {Error} If an error occurs while deleting the alerts. - */ -const deleteAlertByMonitorId = async (monitorId) => { - try { - const result = await Alert.deleteMany({ monitorId }); - return result; - } catch (error) { - throw error; - } -}; - -module.exports = { - connect, - insertUser, - getUserByEmail, - updateUser, - deleteUser, - getAllUsers, - requestInviteToken, - getInviteToken, - requestRecoveryToken, - validateRecoveryToken, - resetPassword, - checkAdmin, - getAllMonitors, - getMonitorById, - getMonitorsByUserId, - getMonitorByIdForIncidents, - getMonitorsByUserIdForIncidents, - createMonitor, - deleteMonitor, - deleteAllMonitors, - editMonitor, - createCheck, - getChecks, - deleteChecks, - createAlert, - getAlertsByUserId, - getAlertsByMonitorId, - getAlertById, - editAlert, - deleteAlert, - deleteAlertByMonitorId, - deleteMonitorsByUserId, - createPageSpeedCheck, - getPageSpeedChecks, - deletePageSpeedChecks, -}; diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js new file mode 100644 index 000000000..fbd281714 --- /dev/null +++ b/Server/db/mongo/MongoDB.js @@ -0,0 +1,147 @@ +const mongoose = require("mongoose"); +const UserModel = require("../../models/user"); + +//**************************************** +// DB Connection +//**************************************** + +const connect = async () => { + try { + await mongoose.connect(process.env.DB_CONNECTION_STRING); + console.log("Connected to MongoDB"); + } catch (error) { + console.error("Failed to connect to MongoDB"); + throw error; + } +}; + +const checkAdmin = async (req, res) => { + try { + const admin = await UserModel.findOne({ role: "admin" }); + if (admin !== null) { + return true; + } + return false; + } catch (error) { + throw error; + } +}; + +//**************************************** +// User Operations +//**************************************** + +const { + insertUser, + getUserByEmail, + updateUser, + deleteUser, + getAllUsers, +} = require("./modules/userModule"); + +//**************************************** +// Invite Token Operations +//**************************************** + +const { + requestInviteToken, + getInviteToken, +} = require("./modules/inviteModule"); + +//**************************************** +// Recovery Operations +//**************************************** +const { + requestRecoveryToken, + validateRecoveryToken, + resetPassword, +} = require("./modules/recoveryModule"); + +//**************************************** +// Monitors +//**************************************** + +const { + getAllMonitors, + getMonitorById, + getMonitorsByUserId, + getMonitorByIdForIncidents, + getMonitorsByUserIdForIncidents, + createMonitor, + deleteMonitor, + deleteAllMonitors, + deleteMonitorsByUserId, + editMonitor, +} = require("./modules/monitorModule"); + +//**************************************** +// Page Speed Checks +//**************************************** + +const { + createPageSpeedCheck, + getPageSpeedChecks, + deletePageSpeedChecks, +} = require("./modules/pageSpeedCheckModule"); + +//**************************************** +// Checks +//**************************************** + +const { + createCheck, + getChecks, + deleteChecks, +} = require("./modules/checkModule"); + +//**************************************** +// Alerts +//**************************************** + +const { + createAlert, + getAlertsByUserId, + getAlertsByMonitorId, + getAlertById, + editAlert, + deleteAlert, + deleteAlertByMonitorId, +} = require("./modules/alertModule"); + +module.exports = { + connect, + insertUser, + getUserByEmail, + updateUser, + deleteUser, + getAllUsers, + requestInviteToken, + getInviteToken, + requestRecoveryToken, + validateRecoveryToken, + resetPassword, + checkAdmin, + getAllMonitors, + getMonitorById, + getMonitorsByUserId, + getMonitorByIdForIncidents, + getMonitorsByUserIdForIncidents, + createMonitor, + deleteMonitor, + deleteAllMonitors, + editMonitor, + createCheck, + getChecks, + deleteChecks, + createAlert, + getAlertsByUserId, + getAlertsByMonitorId, + getAlertById, + editAlert, + deleteAlert, + deleteAlertByMonitorId, + deleteMonitorsByUserId, + createPageSpeedCheck, + getPageSpeedChecks, + deletePageSpeedChecks, +}; diff --git a/Server/db/mongo/modules/alertModule.js b/Server/db/mongo/modules/alertModule.js new file mode 100644 index 000000000..b9e65647b --- /dev/null +++ b/Server/db/mongo/modules/alertModule.js @@ -0,0 +1,140 @@ +const Alert = require("../../../models/Alert"); + +/** + * Creates an Alert associated with a Monitor and User + * @async + * @param {Object} alertData + * @param {string} alertData.checkId + * @param {string} alert.monitorId + * @param {string} alert.userId + * @param {boolean} alert.status + * @param {string} alert.message + * @param {boolean} alert.notifiedStatus + * @param {boolean} alert.acknowledgeStatus + * @returns {>} + * @throws {Error} + */ +const createAlert = async (alertData) => { + try { + const alert = await new Alert({ ...alertData }).save(); + return alert; + } catch (error) { + throw error; + } +}; + +/** + * Gets all alerts a User has set + * @async + * @param {string} userId + * @returns {>} + * @throws {Error} + */ +const getAlertsByUserId = async (userId) => { + try { + const alerts = await Alert.find({ userId }); + return alerts; + } catch (error) { + throw error; + } +}; + +/** + * Gets all alerts a for an associated Monitor + * @async + * @param {string} monitorId + * @returns {>} + * @throws {Error} + */ +const getAlertsByMonitorId = async (monitorId) => { + try { + const alerts = await Alert.find({ monitorId }); + return alerts; + } catch (error) { + throw error; + } +}; + +/** + * Returns an alert with specified ID + * @async + * @param {string} alertId + * @returns {Promise} + * @throws {Error} + */ +const getAlertById = async (alertId) => { + try { + const alert = await Alert.findById(alertId); + return alert; + } catch (error) { + throw error; + } +}; + +/** + * Returns an edited alert with the specified ID + * @async + * @param {string} alertId + * @param {Object} alertData + * @param {string} alertData.checkId + * @param {string} alertData.monitorId + * @param {string} alertData.userId + * @param {boolean} alertData.status + * @param {string} alertData.message + * @param {boolean} alertData.notifiedStatus + * @param {boolean} alertData.acknowledgeStatus + * @param {Express.Response} res + * @returns {Promise>} + * @throws {Error} + */ +const editAlert = async (alertId, alertData) => { + try { + const editedAlert = await Alert.findByIdAndUpdate(alertId, alertData, { + new: true, + }); + return editedAlert; + } catch (error) { + throw error; + } +}; + +/** + * Deletes an alert with the specified ID + * @async + * @param {string} alertId + * @throws {Error} + */ +const deleteAlert = async (alertId) => { + try { + const result = await Alert.findByIdAndDelete(alertId); + return result; + } catch (error) { + throw error; + } +}; + +/** + * Deletes alerts by monitor ID. + * + * @param {string} monitorId - The ID of the monitor. + * @returns {Promise} A promise that resolves to the result of the delete operation. + * @throws {Error} If an error occurs while deleting the alerts. + */ +const deleteAlertByMonitorId = async (monitorId) => { + try { + const result = await Alert.deleteMany({ monitorId }); + return result; + } catch (error) { + throw error; + } +}; + +module.exports = { + createAlert, + getAlertsByUserId, + getAlertsByMonitorId, + getAlertById, + editAlert, + deleteAlert, + deleteAlertByMonitorId, +}; diff --git a/Server/db/mongo/modules/checkModule.js b/Server/db/mongo/modules/checkModule.js new file mode 100644 index 000000000..35c313fd9 --- /dev/null +++ b/Server/db/mongo/modules/checkModule.js @@ -0,0 +1,58 @@ +const Check = require("../../../models/Check"); + +/** + * Create a check for a monitor + * @async + * @param {Object} checkData + * @param {string} checkData.monitorId + * @param {boolean} checkData.status + * @param {number} checkData.responseTime + * @param {number} checkData.statusCode + * @param {string} checkData.message + * @returns {Promise} + * @throws {Error} + */ + +const createCheck = async (checkData) => { + try { + const check = await new Check({ ...checkData }).save(); + return check; + } catch (error) { + throw error; + } +}; + +/** + * Get all checks for a monitor + * @async + * @param {string} monitorId + * @returns {Promise>} + * @throws {Error} + */ + +const getChecks = async (monitorId) => { + try { + const checks = await Check.find({ monitorId }); + return checks; + } catch (error) { + throw error; + } +}; + +/** + * Delete all checks for a monitor + * @async + * @param {string} monitorId + * @returns {number} + * @throws {Error} + */ + +const deleteChecks = async (monitorId) => { + try { + const result = await Check.deleteMany({ monitorId }); + return result.deletedCount; + } catch (error) { + throw error; + } +}; +module.exports = { createCheck, getChecks, deleteChecks }; diff --git a/Server/db/mongo/modules/inviteModule.js b/Server/db/mongo/modules/inviteModule.js new file mode 100644 index 000000000..b4404c615 --- /dev/null +++ b/Server/db/mongo/modules/inviteModule.js @@ -0,0 +1,37 @@ +const InviteToken = require("../../../models/InviteToken"); +const crypto = require("crypto"); +const { errorMessages } = require("../../../utils/messages"); + +const requestInviteToken = async (req, res) => { + try { + await InviteToken.deleteMany({ email: req.body.email }); + let inviteToken = new InviteToken({ + email: req.body.email, + role: req.body.role, + token: crypto.randomBytes(32).toString("hex"), + }); + await inviteToken.save(); + return inviteToken; + } catch (error) { + throw error; + } +}; + +const getInviteToken = async (req, res) => { + try { + const invite = await InviteToken.findOneAndDelete({ + token: req.body.token, + }); + if (invite === null) { + throw new Error(errorMessages.AUTH_INVITE_NOT_FOUND); + } + return invite; + } catch (error) { + throw error; + } +}; + +module.exports = { + requestInviteToken, + getInviteToken, +}; diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js new file mode 100644 index 000000000..694bcf745 --- /dev/null +++ b/Server/db/mongo/modules/monitorModule.js @@ -0,0 +1,238 @@ +const Monitor = require("../../../models/Monitor"); +const Check = require("../../../models/Check"); + +const { errorMessages } = require("../../../utils/messages"); + +/** + * Get all monitors + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise>} + * @throws {Error} + */ +const getAllMonitors = async (req, res) => { + try { + const monitors = await Monitor.find({ isActive: true }); + return monitors; + } catch (error) { + throw error; + } +}; + +/** + * Get a monitor by ID + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ +const getMonitorById = async (req, res) => { + try { + const monitor = await Monitor.findById(req.params.monitorId); + const checks = await Check.find({ monitorId: monitor._id }).sort({ + createdAt: 1, + }); + const monitorWithChecks = { ...monitor.toObject(), checks }; + return monitorWithChecks; + } catch (error) { + throw error; + } +}; + +/** + * Get monitors by UserID + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise>} + * @throws {Error} + */ +const getMonitorsByUserId = async (req, res) => { + try { + const limit = req.query ? req.query.limit : undefined; + const monitors = await Monitor.find({ userId: req.params.userId }); + console.log("monitors", monitors); + // Map each monitor to include its associated checks + const monitorsWithChecks = await Promise.all( + monitors.map(async (monitor) => { + if (limit) { + // Checks are order newest -> oldest + const checks = await Check.find({ monitorId: monitor._id }) + .sort({ + createdAt: -1, + }) + .limit(limit); + return { ...monitor.toObject(), checks }; + } else { + const checks = await Check.find({ monitorId: monitor._id }).sort({ + createdAt: -1, + }); + return { ...monitor.toObject(), checks }; + } + }) + ); + + return monitorsWithChecks; + } catch (error) { + throw error; + } +}; + +/** + * Get monitors by UserID + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ +const getMonitorByIdForIncidents = async (req, res, next) => { + try { + const monitor = await Monitor.findById(req.params.monitorId); + const checks = await Check.find({ + monitorId: monitor._id, + status: false, + }).sort({ + createdAt: 1, + }); + const monitorWithChecks = { ...monitor.toObject(), checks }; + return monitorWithChecks; + } catch (error) { + throw error; + } +}; + +/** + * Get monitors by UserID + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise>} + * @throws {Error} + */ +const getMonitorsByUserIdForIncidents = async (req, res) => { + try { + const monitors = await Monitor.find({ userId: req.params.userId }); + // Map each monitor to include its associated checks + const monitorsWithChecks = await Promise.all( + monitors.map(async (monitor) => { + const checks = await Check.find({ + monitorId: monitor._id, + status: false, + }).sort({ + createdAt: 1, + }); + return { ...monitor.toObject(), checks }; + }) + ); + return monitorsWithChecks; + } catch (error) { + throw error; + } +}; + +/** + * Create a monitor + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ +const createMonitor = async (req, res) => { + try { + const monitor = new Monitor({ ...req.body }); + monitor.userId = req.user._id; + await monitor.save(); + return monitor; + } catch (error) { + throw error; + } +}; + +/** + * Delete a monitor by ID + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ +const deleteMonitor = async (req, res) => { + const monitorId = req.params.monitorId; + try { + const monitor = await Monitor.findByIdAndDelete(monitorId); + if (!monitor) { + throw new Error(errorMessages.DB_FIND_MONTIOR_BY_ID(monitorId)); + } + return monitor; + } catch (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; + } +}; + +/** + * Delete all monitors associated with a user ID + * @async + * @param {string} userId - The ID of the user whose monitors are to be deleted. + * @returns {Promise} A promise that resolves when the operation is complete. + */ +const deleteMonitorsByUserId = async (userId) => { + try { + const result = await Monitor.deleteMany({ userId: userId }); + return result; + } catch (error) { + throw error; + } +}; + +/** + * Edit a monitor by ID + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ +const editMonitor = async (req, res) => { + const candidateId = req.params.monitorId; + const candidateMonitor = req.body; + try { + const editedMonitor = await Monitor.findByIdAndUpdate( + candidateId, + candidateMonitor, + { new: true } + ); + return editedMonitor; + } catch (error) { + throw error; + } +}; + +module.exports = { + getAllMonitors, + getMonitorById, + getMonitorsByUserId, + getMonitorByIdForIncidents, + getMonitorsByUserIdForIncidents, + createMonitor, + deleteMonitor, + deleteAllMonitors, + deleteMonitorsByUserId, + editMonitor, +}; diff --git a/Server/db/mongo/modules/pageSpeedCheckModule.js b/Server/db/mongo/modules/pageSpeedCheckModule.js new file mode 100644 index 000000000..77f076311 --- /dev/null +++ b/Server/db/mongo/modules/pageSpeedCheckModule.js @@ -0,0 +1,64 @@ +const PageSpeedCheck = require("../../../models/PageSpeedCheck"); + +/** + * Create a PageSpeed check for a monitor + * @async + * @param {Object} pageSpeedCheckData + * @param {string} pageSpeedCheckData.monitorId + * @param {number} pageSpeedCheckData.accessibility + * @param {number} pageSpeedCheckData.bestPractices + * @param {number} pageSpeedCheckData.seo + * @param {number} pageSpeedCheckData.performance + * @returns {Promise} + * @throws {Error} + */ +const createPageSpeedCheck = async (pageSpeedCheckData) => { + try { + const pageSpeedCheck = await new pageSpeedCheck({ + ...pageSpeedCheckData, + }).save(); + return pageSpeedCheck; + } catch (error) { + throw error; + } +}; + +/** + * Get all PageSpeed checks for a monitor + * @async + * @param {string} monitorId + * @returns {Promise>} + * @throws {Error} + */ + +const getPageSpeedChecks = async (monitorId) => { + try { + const pageSpeedChecks = await PageSpeedCheck.find({ monitorId }); + return pageSpeedChecks; + } catch (error) { + throw error; + } +}; + +/** + * Delete all PageSpeed checks for a monitor + * @async + * @param {string} monitorId + * @returns {number} + * @throws {Error} + */ + +const deletePageSpeedChecks = async (monitorId) => { + try { + const result = await PageSpeedCheck.deleteMany({ monitorId }); + return result.deletedCount; + } catch (error) { + throw error; + } +}; + +module.exports = { + createPageSpeedCheck, + getPageSpeedChecks, + deletePageSpeedChecks, +}; diff --git a/Server/db/mongo/modules/recoveryModule.js b/Server/db/mongo/modules/recoveryModule.js new file mode 100644 index 000000000..c1cc23b94 --- /dev/null +++ b/Server/db/mongo/modules/recoveryModule.js @@ -0,0 +1,80 @@ +const RecoveryToken = require("../../../models/RecoveryToken"); +const crypto = require("crypto"); +const { errorMessages } = require("../../../utils/messages"); + +/** + * Request a recovery token + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ +const requestRecoveryToken = async (req, res) => { + try { + // Delete any existing tokens + await RecoveryToken.deleteMany({ email: req.body.email }); + let recoveryToken = new RecoveryToken({ + email: req.body.email, + token: crypto.randomBytes(32).toString("hex"), + }); + await recoveryToken.save(); + return recoveryToken; + } catch (error) { + throw error; + } +}; + +const validateRecoveryToken = async (req, res) => { + try { + const candidateToken = req.body.recoveryToken; + const recoveryToken = await RecoveryToken.findOne({ + token: candidateToken, + }); + if (recoveryToken !== null) { + return recoveryToken; + } else { + throw new Error(errorMessages.DB_TOKEN_NOT_FOUND); + } + } catch (error) { + throw error; + } +}; + +const resetPassword = async (req, res) => { + try { + const newPassword = req.body.password; + + // Validate token again + const recoveryToken = await validateRecoveryToken(req, res); + const user = await UserModel.findOne({ email: recoveryToken.email }); + + const match = await user.comparePassword(newPassword); + if (match === true) { + throw new Error(errorMessages.DB_RESET_PASSWORD_BAD_MATCH); + } + + if (user !== null) { + user.password = newPassword; + await user.save(); + await RecoveryToken.deleteMany({ email: recoveryToken.email }); + // Fetch the user again without the password + const userWithoutPassword = await UserModel.findOne({ + email: recoveryToken.email, + }) + .select("-password") + .select("-profileImage"); + return userWithoutPassword; + } else { + throw new Error(errorMessages.DB_USER_NOT_FOUND); + } + } catch (error) { + throw error; + } +}; + +module.exports = { + requestRecoveryToken, + validateRecoveryToken, + resetPassword, +}; diff --git a/Server/db/mongo/modules/userModule.js b/Server/db/mongo/modules/userModule.js new file mode 100644 index 000000000..de7d54ebf --- /dev/null +++ b/Server/db/mongo/modules/userModule.js @@ -0,0 +1,160 @@ +const UserModel = require("../../../models/user"); +const { errorMessages } = require("../../../utils/messages"); +const { GenerateAvatarImage } = require("../../../utils/imageProcessing"); + +const DUPLICATE_KEY_CODE = 11000; // MongoDB error code for duplicate key +const { ParseBoolean } = require("../../../utils/utils"); + +/** + * Insert a User + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ +const insertUser = async (req, res) => { + try { + const userData = { ...req.body }; + if (req.file) { + // 1. Save the full size image + userData.profileImage = { + data: req.file.buffer, + contentType: req.file.mimetype, + }; + + // 2. Get the avatar sized image + const avatar = await GenerateAvatarImage(req.file); + userData.avatarImage = avatar; + } + console.log(userData); + const newUser = new UserModel(userData); + await newUser.save(); + return await UserModel.findOne({ _id: newUser._id }) + .select("-password") + .select("-profileImage"); // .select() doesn't work with create, need to save then find + } catch (error) { + if (error.code === DUPLICATE_KEY_CODE) { + throw new Error(errorMessages.DB_USER_EXISTS); + } + throw error; + } +}; + +/** + * Get User by Email + * Gets a user by Email. Not sure if we'll ever need this except for login. + * If not needed except for login, we can move password comparison here + * Throws error if user not found + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ +const getUserByEmail = async (req, res) => { + try { + // Need the password to be able to compare, removed .select() + // We can strip the hash before returing the user + const user = await UserModel.findOne({ email: req.body.email }).select( + "-profileImage" + ); + if (user) { + return user; + } else { + throw new Error(errorMessages.DB_USER_NOT_FOUND); + } + } catch (error) { + throw error; + } +}; + +/** + * 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; + + try { + const candidateUser = { ...req.body }; + // ****************************************** + // Handle profile image + // ****************************************** + + if (ParseBoolean(candidateUser.deleteProfileImage) === true) { + candidateUser.profileImage = null; + candidateUser.avatarImage = null; + } else if (req.file) { + // 1. Save the full size image + candidateUser.profileImage = { + data: req.file.buffer, + contentType: req.file.mimetype, + }; + + // 2. Get the avaatar sized image + const avatar = await GenerateAvatarImage(req.file); + candidateUser.avatarImage = avatar; + } + + // ****************************************** + // End handling profile image + // ****************************************** + + const updatedUser = await UserModel.findByIdAndUpdate( + candidateUserId, + candidateUser, + { new: true } // Returns updated user instead of pre-update user + ) + .select("-password") + .select("-profileImage"); + return updatedUser; + } catch (error) { + throw error; + } +}; + +/** + * Delete a user by ID + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ +const deleteUser = async (req, res) => { + const userId = req.params.userId; + try { + const deletedUser = await UserModel.findByIdAndDelete(userId); + if (!deletedUser) { + throw new Error(errorMessages.DB_USER_NOT_FOUND); + } + return deletedUser; + } catch (error) { + throw error; + } +}; + +const getAllUsers = async (req, res) => { + try { + const users = await UserModel.find() + .select("-password") + .select("-profileImage"); + return users; + } catch (error) { + throw error; + } +}; + +module.exports = { + insertUser, + getUserByEmail, + updateUser, + deleteUser, + getAllUsers, +}; diff --git a/Server/index.js b/Server/index.js index a08b46a5b..8fd8b82f9 100644 --- a/Server/index.js +++ b/Server/index.js @@ -33,7 +33,7 @@ const startApp = async () => { // // ************************** const DB_TYPE = { - MongoDB: () => require("./db/MongoDB"), + MongoDB: () => require("./db/mongo/MongoDB"), FakedDB: () => require("./db/FakeDb"), };