From cc40bd6006b51a7787793d1479ecddeeb35309e0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 24 Dec 2024 11:58:42 -0800 Subject: [PATCH] refactor controllers to be class based --- Server/controllers/authController.js | 941 ++++++++--------- Server/controllers/checkController.js | 245 +++-- Server/controllers/inviteController.js | 141 +-- .../maintenanceWindowController.js | 279 +++--- Server/controllers/monitorController.js | 947 +++++++++--------- Server/controllers/queueController.js | 103 +- Server/controllers/settingsController.js | 75 +- Server/controllers/statusPageController.js | 79 +- 8 files changed, 1414 insertions(+), 1396 deletions(-) diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index e4566429a..58458b0da 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -17,500 +17,501 @@ import { handleValidationError, handleError } from "./controllerUtils.js"; const SERVICE_NAME = "authController"; dotenv.config(); -/** - * Creates and returns JWT token with an arbitrary payload - * @function - * @param {Object} payload - * @param {tokenType} typeOfToken - Whether to generate refresh token with long TTL or access token with short TTL. - * @param {Object} appSettings - * @returns {String} - * @throws {Error} - */ -const issueToken = (payload, typeOfToken, appSettings) => { - try { - const tokenTTL = - typeOfToken === tokenType.REFRESH_TOKEN - ? (appSettings?.refreshTokenTTL ?? "7d") - : (appSettings?.jwtTTL ?? "2h"); - const tokenSecret = - typeOfToken === tokenType.REFRESH_TOKEN - ? appSettings?.refreshTokenSecret - : appSettings?.jwtSecret; - const payloadData = typeOfToken === tokenType.REFRESH_TOKEN ? {} : payload; - - return jwt.sign(payloadData, tokenSecret, { expiresIn: tokenTTL }); - } catch (error) { - throw handleError(error, SERVICE_NAME, "issueToken"); +class AuthController { + constructor(db, settingsService, emailService, jobQueue) { + this.db = db; + this.settingsService = settingsService; + this.emailService = emailService; + this.jobQueue = jobQueue; } -}; -/** - * Registers a new user. If the user is the first account, a JWT secret is created. If not, an invite token is required. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.inviteToken - The invite token for registration. - * @property {Object} req.file - The file object for the user's profile image. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the creation of the user, the created user data, and a JWT token. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ -const registerUser = async (req, res, next) => { - // joi validation - try { - await registrationBodyValidation.validateAsync(req.body); - } catch (error) { - const validationError = handleValidationError(error, SERVICE_NAME); - next(validationError); - return; - } - // Create a new user - try { - const { inviteToken } = req.body; - // If superAdmin exists, a token should be attached to all further register requests - const superAdminExists = await req.db.checkSuperadmin(req, res); - if (superAdminExists) { - await req.db.getInviteTokenAndDelete(inviteToken); - } else { - // This is the first account, create JWT secret to use if one is not supplied by env - const jwtSecret = crypto.randomBytes(64).toString("hex"); - await req.db.updateAppSettings({ jwtSecret }); + /** + * Creates and returns JWT token with an arbitrary payload + * @function + * @param {Object} payload + * @param {tokenType} typeOfToken - Whether to generate refresh token with long TTL or access token with short TTL. + * @param {Object} appSettings + * @returns {String} + * @throws {Error} + */ + issueToken = (payload, typeOfToken, appSettings) => { + try { + const tokenTTL = + typeOfToken === tokenType.REFRESH_TOKEN + ? (appSettings?.refreshTokenTTL ?? "7d") + : (appSettings?.jwtTTL ?? "2h"); + const tokenSecret = + typeOfToken === tokenType.REFRESH_TOKEN + ? appSettings?.refreshTokenSecret + : appSettings?.jwtSecret; + const payloadData = typeOfToken === tokenType.REFRESH_TOKEN ? {} : payload; + + return jwt.sign(payloadData, tokenSecret, { expiresIn: tokenTTL }); + } catch (error) { + throw handleError(error, SERVICE_NAME, "issueToken"); } + }; - const newUser = await req.db.insertUser({ ...req.body }, req.file); - logger.info({ - message: successMessages.AUTH_CREATE_USER, - service: SERVICE_NAME, - details: newUser._id, - }); + /** + * Registers a new user. If the user is the first account, a JWT secret is created. If not, an invite token is required. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.body - The body of the request. + * @property {string} req.body.inviteToken - The invite token for registration. + * @property {Object} req.file - The file object for the user's profile image. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message indicating the creation of the user, the created user data, and a JWT token. + * @throws {Error} If there is an error during the process, especially if there is a validation error (422). + */ + registerUser = async (req, res, next) => { + try { + await registrationBodyValidation.validateAsync(req.body); + } catch (error) { + const validationError = handleValidationError(error, SERVICE_NAME); + next(validationError); + return; + } + // Create a new user + try { + const { inviteToken } = req.body; + // If superAdmin exists, a token should be attached to all further register requests + const superAdminExists = await this.db.checkSuperadmin(req, res); + if (superAdminExists) { + await this.db.getInviteTokenAndDelete(inviteToken); + } else { + // This is the first account, create JWT secret to use if one is not supplied by env + const jwtSecret = crypto.randomBytes(64).toString("hex"); + await this.db.updateAppSettings({ jwtSecret }); + } - const userForToken = { ...newUser._doc }; - delete userForToken.profileImage; - delete userForToken.avatarImage; - - const appSettings = await req.settingsService.getSettings(); - - const token = issueToken(userForToken, tokenType.ACCESS_TOKEN, appSettings); - const refreshToken = issueToken({}, tokenType.REFRESH_TOKEN, appSettings); - - req.emailService - .buildAndSendEmail( - "welcomeEmailTemplate", - { name: newUser.firstName }, - newUser.email, - "Welcome to Uptime Monitor" - ) - .catch((error) => { - logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "registerUser", - stack: error.stack, - }); + const newUser = await this.db.insertUser({ ...req.body }, req.file); + logger.info({ + message: successMessages.AUTH_CREATE_USER, + service: SERVICE_NAME, + details: newUser._id, }); - return res.status(200).json({ - success: true, - msg: successMessages.AUTH_CREATE_USER, - data: { user: newUser, token: token, refreshToken: refreshToken }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "registerController")); - } -}; + const userForToken = { ...newUser._doc }; + delete userForToken.profileImage; + delete userForToken.avatarImage; -/** - * Logs in a user by validating the user's credentials and issuing a JWT token. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.email - The email of the user. - * @property {string} req.body.password - The password of the user. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the login of the user, the user data (without password and avatar image), and a JWT token. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422) or the password is incorrect. - */ -const loginUser = async (req, res, next) => { - try { - await loginValidation.validateAsync(req.body); - } catch (error) { - const validationError = handleValidationError(error, SERVICE_NAME); - next(validationError); - return; - } - try { - const { email, password } = req.body; + const appSettings = await this.settingsService.getSettings(); - // Check if user exists - const user = await req.db.getUserByEmail(email); + const token = this.issueToken(userForToken, tokenType.ACCESS_TOKEN, appSettings); + const refreshToken = this.issueToken({}, tokenType.REFRESH_TOKEN, appSettings); - // Compare password - const match = await user.comparePassword(password); - if (match !== true) { - next(new Error(errorMessages.AUTH_INCORRECT_PASSWORD)); + this.emailService + .buildAndSendEmail( + "welcomeEmailTemplate", + { name: newUser.firstName }, + newUser.email, + "Welcome to Uptime Monitor" + ) + .catch((error) => { + logger.error({ + message: error.message, + service: SERVICE_NAME, + method: "registerUser", + stack: error.stack, + }); + }); + + return res.status(200).json({ + success: true, + msg: successMessages.AUTH_CREATE_USER, + data: { user: newUser, token: token, refreshToken: refreshToken }, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "registerController")); + } + }; + + /** + * Logs in a user by validating the user's credentials and issuing a JWT token. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.body - The body of the request. + * @property {string} req.body.email - The email of the user. + * @property {string} req.body.password - The password of the user. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message indicating the login of the user, the user data (without password and avatar image), and a JWT token. + * @throws {Error} If there is an error during the process, especially if there is a validation error (422) or the password is incorrect. + */ + loginUser = async (req, res, next) => { + try { + await loginValidation.validateAsync(req.body); + } catch (error) { + const validationError = handleValidationError(error, SERVICE_NAME); + next(validationError); + return; + } + try { + const { email, password } = req.body; + + // Check if user exists + const user = await this.db.getUserByEmail(email); + + // Compare password + const match = await user.comparePassword(password); + if (match !== true) { + next(new Error(errorMessages.AUTH_INCORRECT_PASSWORD)); + return; + } + + // Remove password from user object. Should this be abstracted to DB layer? + const userWithoutPassword = { ...user._doc }; + delete userWithoutPassword.password; + delete userWithoutPassword.avatarImage; + + // Happy path, return token + const appSettings = await this.settingsService.getSettings(); + const token = this.issueToken( + userWithoutPassword, + tokenType.ACCESS_TOKEN, + appSettings + ); + const refreshToken = this.issueToken({}, tokenType.REFRESH_TOKEN, appSettings); + // reset avatar image + userWithoutPassword.avatarImage = user.avatarImage; + + return res.status(200).json({ + success: true, + msg: successMessages.AUTH_LOGIN_USER, + data: { user: userWithoutPassword, token: token, refreshToken: refreshToken }, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "loginUser")); + } + }; + + /** + * Generates new auth token if the refresh token is valid + * @async + * @param {Express.Request} req - The Express request object. + * @property {Object} req.headers - The parameter of the request. + * @param {Express.Response} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message indicating new auth token is generated. + * @throws {Error} If there is an error during the process such as any of the token is not received + */ + refreshAuthToken = async (req, res, next) => { + try { + // check for refreshToken + const refreshToken = req.headers["x-refresh-token"]; + + if (!refreshToken) { + // No refresh token provided + const error = new Error(errorMessages.NO_REFRESH_TOKEN); + error.status = 401; + error.service = SERVICE_NAME; + error.method = "refreshAuthToken"; + return next(error); + } + + // Verify refresh token + const appSettings = await this.settingsService.getSettings(); + const { refreshTokenSecret } = appSettings; + jwt.verify(refreshToken, refreshTokenSecret, async (refreshErr, refreshDecoded) => { + if (refreshErr) { + // Invalid or expired refresh token, trigger logout + const errorMessage = + refreshErr.name === "TokenExpiredError" + ? errorMessages.EXPIRED_REFRESH_TOKEN + : errorMessages.INVALID_REFRESH_TOKEN; + const error = new Error(errorMessage); + error.status = 401; + error.service = SERVICE_NAME; + return next(error); + } + }); + // Refresh token is valid and unexpired, generate new access token + const oldAuthToken = getTokenFromHeaders(req.headers); + const { jwtSecret } = await this.settingsService.getSettings(); + const payloadData = jwt.verify(oldAuthToken, jwtSecret, { ignoreExpiration: true }); + // delete old token related data + delete payloadData.iat; + delete payloadData.exp; + const newAuthToken = this.issueToken( + payloadData, + tokenType.ACCESS_TOKEN, + appSettings + ); + + return res.status(200).json({ + success: true, + msg: successMessages.AUTH_TOKEN_REFRESHED, + data: { user: payloadData, token: newAuthToken, refreshToken: refreshToken }, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "refreshAuthToken")); + } + }; + /** + * Edits a user's information. If the user wants to change their password, the current password is checked before updating to the new password. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.params - The parameters of the request. + * @property {string} req.params.userId - The ID of the user to be edited. + * @property {Object} req.body - The body of the request. + * @property {string} req.body.password - The current password of the user. + * @property {string} req.body.newPassword - The new password of the user. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message indicating the update of the user, and the updated user data. + * @throws {Error} If there is an error during the process, especially if there is a validation error (422), the user is unauthorized (401), or the password is incorrect (403). + */ + editUser = async (req, res, next) => { + try { + await editUserParamValidation.validateAsync(req.params); + await editUserBodyValidation.validateAsync(req.body); + } catch (error) { + const validationError = handleValidationError(error, SERVICE_NAME); + next(validationError); return; } - // Remove password from user object. Should this be abstracted to DB layer? - const userWithoutPassword = { ...user._doc }; - delete userWithoutPassword.password; - delete userWithoutPassword.avatarImage; - - // Happy path, return token - const appSettings = await req.settingsService.getSettings(); - const token = issueToken(userWithoutPassword, tokenType.ACCESS_TOKEN, appSettings); - const refreshToken = issueToken({}, tokenType.REFRESH_TOKEN, appSettings); - // reset avatar image - userWithoutPassword.avatarImage = user.avatarImage; - - return res.status(200).json({ - success: true, - msg: successMessages.AUTH_LOGIN_USER, - data: { user: userWithoutPassword, token: token, refreshToken: refreshToken }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "loginUser")); - } -}; - -/** - * Generates new auth token if the refresh token is valid - * @async - * @param {Express.Request} req - The Express request object. - * @property {Object} req.headers - The parameter of the request. - * @param {Express.Response} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating new auth token is generated. - * @throws {Error} If there is an error during the process such as any of the token is not received - */ -const refreshAuthToken = async (req, res, next) => { - try { - // check for refreshToken - const refreshToken = req.headers["x-refresh-token"]; - - if (!refreshToken) { - // No refresh token provided - const error = new Error(errorMessages.NO_REFRESH_TOKEN); + // TODO is this neccessary any longer? Verify ownership middleware should handle this + if (req.params.userId !== req.user._id.toString()) { + const error = new Error(errorMessages.AUTH_UNAUTHORIZED); error.status = 401; error.service = SERVICE_NAME; - error.method = "refreshAuthToken"; - return next(error); + next(error); + return; } - // Verify refresh token - const appSettings = await req.settingsService.getSettings(); - const { refreshTokenSecret } = appSettings; - jwt.verify(refreshToken, refreshTokenSecret, async (refreshErr, refreshDecoded) => { - if (refreshErr) { - // Invalid or expired refresh token, trigger logout - const errorMessage = - refreshErr.name === "TokenExpiredError" - ? errorMessages.EXPIRED_REFRESH_TOKEN - : errorMessages.INVALID_REFRESH_TOKEN; - const error = new Error(errorMessage); - error.status = 401; - error.service = SERVICE_NAME; - return next(error); + try { + // Change Password check + if (req.body.password && req.body.newPassword) { + // Get token from headers + const token = getTokenFromHeaders(req.headers); + // Get email from token + const { jwtSecret } = this.settingsService.getSettings(); + const { email } = jwt.verify(token, jwtSecret); + // Add user email to body for DB operation + req.body.email = email; + // Get user + const user = await this.db.getUserByEmail(email); + // Compare passwords + const match = await user.comparePassword(req.body.password); + // If not a match, throw a 403 + // 403 instead of 401 to avoid triggering axios interceptor + if (!match) { + const error = new Error(errorMessages.AUTH_INCORRECT_PASSWORD); + error.status = 403; + next(error); + return; + } + // If a match, update the password + req.body.password = req.body.newPassword; } - }); - // Refresh token is valid and unexpired, generate new access token - const oldAuthToken = getTokenFromHeaders(req.headers); - const { jwtSecret } = await req.settingsService.getSettings(); - const payloadData = jwt.verify(oldAuthToken, jwtSecret, { ignoreExpiration: true }); - // delete old token related data - delete payloadData.iat; - delete payloadData.exp; - const newAuthToken = issueToken(payloadData, tokenType.ACCESS_TOKEN, appSettings); - return res.status(200).json({ - success: true, - msg: successMessages.AUTH_TOKEN_REFRESHED, - data: { user: payloadData, token: newAuthToken, refreshToken: refreshToken }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "refreshAuthToken")); - } -}; + const updatedUser = await this.db.updateUser(req, res); + return res.status(200).json({ + success: true, + msg: successMessages.AUTH_UPDATE_USER, + data: updatedUser, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "userEditController")); + } + }; -/** - * Edits a user's information. If the user wants to change their password, the current password is checked before updating to the new password. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.userId - The ID of the user to be edited. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.password - The current password of the user. - * @property {string} req.body.newPassword - The new password of the user. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the update of the user, and the updated user data. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422), the user is unauthorized (401), or the password is incorrect (403). - */ -const editUser = async (req, res, next) => { - try { - await editUserParamValidation.validateAsync(req.params); - await editUserBodyValidation.validateAsync(req.body); - } catch (error) { - const validationError = handleValidationError(error, SERVICE_NAME); - next(validationError); - return; - } - - // TODO is this neccessary any longer? Verify ownership middleware should handle this - if (req.params.userId !== req.user._id.toString()) { - const error = new Error(errorMessages.AUTH_UNAUTHORIZED); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - - try { - // Change Password check - if (req.body.password && req.body.newPassword) { - // Get token from headers - const token = getTokenFromHeaders(req.headers); - // Get email from token - const { jwtSecret } = req.settingsService.getSettings(); - const { email } = jwt.verify(token, jwtSecret); - // Add user email to body for DB operation - req.body.email = email; - // Get user - const user = await req.db.getUserByEmail(email); - // Compare passwords - const match = await user.comparePassword(req.body.password); - // If not a match, throw a 403 - // 403 instead of 401 to avoid triggering axios interceptor - if (!match) { - const error = new Error(errorMessages.AUTH_INCORRECT_PASSWORD); - error.status = 403; - next(error); - return; - } - // If a match, update the password - req.body.password = req.body.newPassword; + /** + * Checks if a superadmin account exists in the database. + * @async + * @param {Object} req - The Express request object. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message indicating the existence of a superadmin, and a boolean indicating the existence of a superadmin. + * @throws {Error} If there is an error during the process. + */ + checkSuperadminExists = async (req, res, next) => { + try { + const superAdminExists = await this.db.checkSuperadmin(req, res); + return res.status(200).json({ + success: true, + msg: successMessages.AUTH_ADMIN_EXISTS, + data: superAdminExists, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "checkSuperadminController")); + } + }; + /** + * Requests a recovery token for a user. The user's email is validated and a recovery token is created and sent via email. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.body - The body of the request. + * @property {string} req.body.email - The email of the user requesting recovery. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message indicating the creation of the recovery token, and the message ID of the sent email. + * @throws {Error} If there is an error during the process, especially if there is a validation error (422). + */ + requestRecovery = async (req, res, next) => { + try { + await recoveryValidation.validateAsync(req.body); + } catch (error) { + const validationError = handleValidationError(error, SERVICE_NAME); + next(validationError); + return; } - const updatedUser = await req.db.updateUser(req, res); - return res.status(200).json({ - success: true, - msg: successMessages.AUTH_UPDATE_USER, - data: updatedUser, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "userEditController")); - } -}; - -/** - * Checks if a superadmin account exists in the database. - * @async - * @param {Object} req - The Express request object. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the existence of a superadmin, and a boolean indicating the existence of a superadmin. - * @throws {Error} If there is an error during the process. - */ -const checkSuperadminExists = async (req, res, next) => { - try { - const superAdminExists = await req.db.checkSuperadmin(req, res); - return res.status(200).json({ - success: true, - msg: successMessages.AUTH_ADMIN_EXISTS, - data: superAdminExists, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "checkSuperadminController")); - } -}; - -/** - * Requests a recovery token for a user. The user's email is validated and a recovery token is created and sent via email. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.email - The email of the user requesting recovery. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the creation of the recovery token, and the message ID of the sent email. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ -const requestRecovery = async (req, res, next) => { - try { - await recoveryValidation.validateAsync(req.body); - } catch (error) { - const validationError = handleValidationError(error, SERVICE_NAME); - next(validationError); - return; - } - - try { - const { email } = req.body; - const user = await req.db.getUserByEmail(email); - const recoveryToken = await req.db.requestRecoveryToken(req, res); - const name = user.firstName; - const { clientHost } = req.settingsService.getSettings(); - const url = `${clientHost}/set-new-password/${recoveryToken.token}`; - const msgId = await req.emailService.buildAndSendEmail( - "passwordResetTemplate", - { - name, + try { + const { email } = req.body; + const user = await this.db.getUserByEmail(email); + const recoveryToken = await this.db.requestRecoveryToken(req, res); + const name = user.firstName; + const { clientHost } = this.settingsService.getSettings(); + const url = `${clientHost}/set-new-password/${recoveryToken.token}`; + const msgId = await this.emailService.buildAndSendEmail( + "passwordResetTemplate", + { + name, + email, + url, + }, email, - url, - }, - email, - "Checkmate Password Reset" - ); + "Checkmate Password Reset" + ); - return res.status(200).json({ - success: true, - msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN, - data: msgId, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "recoveryRequestController")); - } -}; - -/** - * Validates a recovery token. The recovery token is validated and if valid, a success message is returned. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.token - The recovery token to be validated. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status and a message indicating the validation of the recovery token. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ -const validateRecovery = async (req, res, next) => { - try { - await recoveryTokenValidation.validateAsync(req.body); - } catch (error) { - const validationError = handleValidationError(error, SERVICE_NAME); - next(validationError); - return; - } - - try { - await req.db.validateRecoveryToken(req, res); - return res.status(200).json({ - success: true, - msg: successMessages.AUTH_VERIFY_RECOVERY_TOKEN, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "validateRecoveryTokenController")); - } -}; - -/** - * Resets a user's password. The new password is validated and if valid, the user's password is updated in the database and a new JWT token is issued. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.token - The recovery token. - * @property {string} req.body.password - The new password of the user. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the reset of the password, the updated user data (without password and avatar image), and a new JWT token. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ -const resetPassword = async (req, res, next) => { - try { - await newPasswordValidation.validateAsync(req.body); - } catch (error) { - const validationError = handleValidationError(error, SERVICE_NAME); - next(validationError); - return; - } - try { - const user = await req.db.resetPassword(req, res); - const appSettings = await req.settingsService.getSettings(); - const token = issueToken(user._doc, tokenType.ACCESS_TOKEN, appSettings); - res.status(200).json({ - success: true, - msg: successMessages.AUTH_RESET_PASSWORD, - data: { user, token }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "resetPasswordController")); - } -}; - -/** - * Deletes a user and all associated monitors, checks, and alerts. - * - * @param {Object} req - The request object. - * @param {Object} res - The response object. - * @param {Function} next - The next middleware function. - * @returns {Object} The response object with success status and message. - * @throws {Error} If user validation fails or user is not found in the database. - */ -const deleteUser = async (req, res, next) => { - try { - const token = getTokenFromHeaders(req.headers); - const decodedToken = jwt.decode(token); - const { email } = decodedToken; - - // Check if the user exists - const user = await req.db.getUserByEmail(email); - // 1. Find all the monitors associated with the team ID if superadmin - - const result = await req.db.getMonitorsByTeamId({ - params: { teamId: user.teamId }, - }); - - if (user.role.includes("superadmin")) { - //2. Remove all jobs, delete checks and alerts - result?.monitors.length > 0 && - (await Promise.all( - result.monitors.map(async (monitor) => { - await req.jobQueue.deleteJob(monitor); - await req.db.deleteChecks(monitor._id); - await req.db.deletePageSpeedChecksByMonitorId(monitor._id); - await req.db.deleteNotificationsByMonitorId(monitor._id); - }) - )); - - // 3. Delete team - await req.db.deleteTeam(user.teamId); - // 4. Delete all other team members - await req.db.deleteAllOtherUsers(); - // 5. Delete each monitor - await req.db.deleteMonitorsByUserId(user._id); + return res.status(200).json({ + success: true, + msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN, + data: msgId, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "recoveryRequestController")); + } + }; + /** + * Validates a recovery token. The recovery token is validated and if valid, a success message is returned. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.body - The body of the request. + * @property {string} req.body.token - The recovery token to be validated. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status and a message indicating the validation of the recovery token. + * @throws {Error} If there is an error during the process, especially if there is a validation error (422). + */ + validateRecovery = async (req, res, next) => { + try { + await recoveryTokenValidation.validateAsync(req.body); + } catch (error) { + const validationError = handleValidationError(error, SERVICE_NAME); + next(validationError); + return; } - // 6. Delete the user by id - await req.db.deleteUser(user._id); - return res.status(200).json({ - success: true, - msg: successMessages.AUTH_DELETE_USER, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteUserController")); - } -}; -const getAllUsers = async (req, res, next) => { - try { - const allUsers = await req.db.getAllUsers(req, res); - res.status(200).json({ success: true, msg: "Got all users", data: allUsers }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getAllUsersController")); - } -}; + try { + await this.db.validateRecoveryToken(req, res); + return res.status(200).json({ + success: true, + msg: successMessages.AUTH_VERIFY_RECOVERY_TOKEN, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "validateRecoveryTokenController")); + } + }; -export { - issueToken, - registerUser, - loginUser, - refreshAuthToken, - editUser, - checkSuperadminExists, - requestRecovery, - validateRecovery, - resetPassword, - deleteUser, - getAllUsers, -}; + /** + * Resets a user's password. The new password is validated and if valid, the user's password is updated in the database and a new JWT token is issued. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.body - The body of the request. + * @property {string} req.body.token - The recovery token. + * @property {string} req.body.password - The new password of the user. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message indicating the reset of the password, the updated user data (without password and avatar image), and a new JWT token. + * @throws {Error} If there is an error during the process, especially if there is a validation error (422). + */ + resetPassword = async (req, res, next) => { + try { + await newPasswordValidation.validateAsync(req.body); + } catch (error) { + const validationError = handleValidationError(error, SERVICE_NAME); + next(validationError); + return; + } + try { + const user = await this.db.resetPassword(req, res); + const appSettings = await this.settingsService.getSettings(); + const token = this.issueToken(user._doc, tokenType.ACCESS_TOKEN, appSettings); + res.status(200).json({ + success: true, + msg: successMessages.AUTH_RESET_PASSWORD, + data: { user, token }, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "resetPasswordController")); + } + }; + + /** + * Deletes a user and all associated monitors, checks, and alerts. + * + * @param {Object} req - The request object. + * @param {Object} res - The response object. + * @param {Function} next - The next middleware function. + * @returns {Object} The response object with success status and message. + * @throws {Error} If user validation fails or user is not found in the database. + */ + deleteUser = async (req, res, next) => { + try { + const token = getTokenFromHeaders(req.headers); + const decodedToken = jwt.decode(token); + const { email } = decodedToken; + + // Check if the user exists + const user = await this.db.getUserByEmail(email); + // 1. Find all the monitors associated with the team ID if superadmin + + const result = await this.db.getMonitorsByTeamId({ + params: { teamId: user.teamId }, + }); + + if (user.role.includes("superadmin")) { + //2. Remove all jobs, delete checks and alerts + result?.monitors.length > 0 && + (await Promise.all( + result.monitors.map(async (monitor) => { + await this.jobQueue.deleteJob(monitor); + await this.db.deleteChecks(monitor._id); + await this.db.deletePageSpeedChecksByMonitorId(monitor._id); + await this.db.deleteNotificationsByMonitorId(monitor._id); + }) + )); + + // 3. Delete team + await this.db.deleteTeam(user.teamId); + // 4. Delete all other team members + await this.db.deleteAllOtherUsers(); + // 5. Delete each monitor + await this.db.deleteMonitorsByUserId(user._id); + } + // 6. Delete the user by id + await this.db.deleteUser(user._id); + return res.status(200).json({ + success: true, + msg: successMessages.AUTH_DELETE_USER, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "deleteUserController")); + } + }; + + getAllUsers = async (req, res, next) => { + try { + const allUsers = await this.db.getAllUsers(req, res); + res.status(200).json({ success: true, msg: "Got all users", data: allUsers }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getAllUsersController")); + } + }; +} + +export default AuthController; diff --git a/Server/controllers/checkController.js b/Server/controllers/checkController.js index a47f3f8c4..fce245b69 100644 --- a/Server/controllers/checkController.js +++ b/Server/controllers/checkController.js @@ -16,139 +16,138 @@ import { handleValidationError, handleError } from "./controllerUtils.js"; const SERVICE_NAME = "checkController"; -const createCheck = async (req, res, next) => { - try { - await createCheckParamValidation.validateAsync(req.params); - await createCheckBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; +class CheckController { + constructor(db, settingsService) { + this.db = db; + this.settingsService = settingsService; } - try { - const checkData = { ...req.body }; - const check = await req.db.createCheck(checkData); - return res - .status(200) - .json({ success: true, msg: successMessages.CHECK_CREATE, data: check }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "createCheck")); - } -}; + createCheck = async (req, res, next) => { + try { + await createCheckParamValidation.validateAsync(req.params); + await createCheckBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } -const getChecks = async (req, res, next) => { - try { - await getChecksParamValidation.validateAsync(req.params); - await getChecksQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + const checkData = { ...req.body }; + const check = await this.db.createCheck(checkData); + return res + .status(200) + .json({ success: true, msg: successMessages.CHECK_CREATE, data: check }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "createCheck")); + } + }; - try { - const checks = await req.db.getChecks(req); - const checksCount = await req.db.getChecksCount(req); - return res.status(200).json({ - success: true, - msg: successMessages.CHECK_GET, - data: { checksCount, checks }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getChecks")); - } -}; + getChecks = async (req, res, next) => { + try { + await getChecksParamValidation.validateAsync(req.params); + await getChecksQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } -const getTeamChecks = async (req, res, next) => { - try { - await getTeamChecksParamValidation.validateAsync(req.params); - await getTeamChecksQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - const checkData = await req.db.getTeamChecks(req); - return res.status(200).json({ - success: true, - msg: successMessages.CHECK_GET, - data: checkData, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getTeamChecks")); - } -}; + try { + const checks = await this.db.getChecks(req); + const checksCount = await this.db.getChecksCount(req); + return res.status(200).json({ + success: true, + msg: successMessages.CHECK_GET, + data: { checksCount, checks }, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getChecks")); + } + }; -const deleteChecks = async (req, res, next) => { - try { - await deleteChecksParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + getTeamChecks = async (req, res, next) => { + try { + await getTeamChecksParamValidation.validateAsync(req.params); + await getTeamChecksQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + try { + const checkData = await this.db.getTeamChecks(req); + return res.status(200).json({ + success: true, + msg: successMessages.CHECK_GET, + data: checkData, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getTeamChecks")); + } + }; - try { - const deletedCount = await req.db.deleteChecks(req.params.monitorId); - return res.status(200).json({ - success: true, - msg: successMessages.CHECK_DELETE, - data: { deletedCount }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteChecks")); - } -}; + deleteChecks = async (req, res, next) => { + try { + await deleteChecksParamValidation.validateAsync(req.params); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } -const deleteChecksByTeamId = async (req, res, next) => { - try { - await deleteChecksByTeamIdParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + const deletedCount = await this.db.deleteChecks(req.params.monitorId); + return res.status(200).json({ + success: true, + msg: successMessages.CHECK_DELETE, + data: { deletedCount }, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "deleteChecks")); + } + }; - try { - const deletedCount = await req.db.deleteChecksByTeamId(req.params.teamId); - return res.status(200).json({ - success: true, - msg: successMessages.CHECK_DELETE, - data: { deletedCount }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteChecksByTeamId")); - } -}; + deleteChecksByTeamId = async (req, res, next) => { + try { + await deleteChecksByTeamIdParamValidation.validateAsync(req.params); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } -const updateChecksTTL = async (req, res, next) => { - const SECONDS_PER_DAY = 86400; + try { + const deletedCount = await this.db.deleteChecksByTeamId(req.params.teamId); + return res.status(200).json({ + success: true, + msg: successMessages.CHECK_DELETE, + data: { deletedCount }, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "deleteChecksByTeamId")); + } + }; - try { - await updateChecksTTLBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + updateChecksTTL = async (req, res, next) => { + const SECONDS_PER_DAY = 86400; - try { - // Get user's teamId - const token = getTokenFromHeaders(req.headers); - const { jwtSecret } = req.settingsService.getSettings(); - const { teamId } = jwt.verify(token, jwtSecret); - const ttl = parseInt(req.body.ttl, 10) * SECONDS_PER_DAY; - await req.db.updateChecksTTL(teamId, ttl); - return res.status(200).json({ - success: true, - msg: successMessages.CHECK_UPDATE_TTL, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "updateTTL")); - } -}; + try { + await updateChecksTTLBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } -export { - createCheck, - getChecks, - getTeamChecks, - deleteChecks, - deleteChecksByTeamId, - updateChecksTTL, -}; + try { + // Get user's teamId + const token = getTokenFromHeaders(req.headers); + const { jwtSecret } = this.settingsService.getSettings(); + const { teamId } = jwt.verify(token, jwtSecret); + const ttl = parseInt(req.body.ttl, 10) * SECONDS_PER_DAY; + await this.db.updateChecksTTL(teamId, ttl); + return res.status(200).json({ + success: true, + msg: successMessages.CHECK_UPDATE_TTL, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "updateTTL")); + } + }; +} +export default CheckController; diff --git a/Server/controllers/inviteController.js b/Server/controllers/inviteController.js index 96b19cc56..d4eedd502 100644 --- a/Server/controllers/inviteController.js +++ b/Server/controllers/inviteController.js @@ -4,83 +4,90 @@ import { inviteVerificationBodyValidation, } from "../validation/joi.js"; import logger from "../utils/logger.js"; -import dotenv from "dotenv"; import jwt from "jsonwebtoken"; import { handleError, handleValidationError } from "./controllerUtils.js"; import { getTokenFromHeaders } from "../utils/utils.js"; -dotenv.config(); - const SERVICE_NAME = "inviteController"; -/** - * Issues an invitation to a new user. Only admins can invite new users. An invitation token is created and sent via email. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.headers - The headers of the request. - * @property {string} req.headers.authorization - The authorization header containing the JWT token. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.email - The email of the user to be invited. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the sending of the invitation, and the invitation token. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ -const issueInvitation = async (req, res, next) => { - try { - // Only admins can invite - const token = getTokenFromHeaders(req.headers); - const { role, firstname, teamId } = jwt.decode(token); - req.body.teamId = teamId; +class InviteController { + constructor(db, settingsService, emailService) { + this.db = db; + this.settingsService = settingsService; + this.emailService = emailService; + } + + /** + * Issues an invitation to a new user. Only admins can invite new users. An invitation token is created and sent via email. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.headers - The headers of the request. + * @property {string} req.headers.authorization - The authorization header containing the JWT token. + * @property {Object} req.body - The body of the request. + * @property {string} req.body.email - The email of the user to be invited. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message indicating the sending of the invitation, and the invitation token. + * @throws {Error} If there is an error during the process, especially if there is a validation error (422). + */ + issueInvitation = async (req, res, next) => { try { - await inviteRoleValidation.validateAsync({ roles: role }); - await inviteBodyValidation.validateAsync(req.body); + // Only admins can invite + const token = getTokenFromHeaders(req.headers); + const { role, firstname, teamId } = jwt.decode(token); + req.body.teamId = teamId; + try { + await inviteRoleValidation.validateAsync({ roles: role }); + await inviteBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + + const inviteToken = await this.db.requestInviteToken({ ...req.body }); + const { clientHost } = this.settingsService.getSettings(); + this.emailService + .buildAndSendEmail( + "employeeActivationTemplate", + { + name: firstname, + link: `${clientHost}/register/${inviteToken.token}`, + }, + req.body.email, + "Welcome to Uptime Monitor" + ) + .catch((error) => { + logger.error({ + message: error.message, + service: SERVICE_NAME, + method: "issueInvitation", + stack: error.stack, + }); + }); + + return res + .status(200) + .json({ success: true, msg: "Invite sent", data: inviteToken }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "inviteController")); + } + }; + + inviteVerifyController = async (req, res, next) => { + try { + await inviteVerificationBodyValidation.validateAsync(req.body); } catch (error) { next(handleValidationError(error, SERVICE_NAME)); return; } - const inviteToken = await req.db.requestInviteToken({ ...req.body }); - const { clientHost } = req.settingsService.getSettings(); - req.emailService - .buildAndSendEmail( - "employeeActivationTemplate", - { - name: firstname, - link: `${clientHost}/register/${inviteToken.token}`, - }, - req.body.email, - "Welcome to Uptime Monitor" - ) - .catch((error) => { - logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "issueInvitation", - stack: error.stack, - }); - }); + try { + const invite = await this.db.getInviteToken(req.body.token); + res.status(200).json({ status: "success", msg: "Invite verified", data: invite }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "inviteVerifyController")); + } + }; +} - return res.status(200).json({ success: true, msg: "Invite sent", data: inviteToken }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "inviteController")); - } -}; - -const inviteVerifyController = async (req, res, next) => { - try { - await inviteVerificationBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const invite = await req.db.getInviteToken(req.body.token); - res.status(200).json({ status: "success", msg: "Invite verified", data: invite }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "inviteVerifyController")); - } -}; - -export { issueInvitation, inviteVerifyController }; +export default InviteController; diff --git a/Server/controllers/maintenanceWindowController.js b/Server/controllers/maintenanceWindowController.js index 1700a483e..72b8dc27b 100644 --- a/Server/controllers/maintenanceWindowController.js +++ b/Server/controllers/maintenanceWindowController.js @@ -11,157 +11,156 @@ import jwt from "jsonwebtoken"; import { getTokenFromHeaders } from "../utils/utils.js"; import { successMessages } from "../utils/messages.js"; import { handleValidationError, handleError } from "./controllerUtils.js"; - const SERVICE_NAME = "maintenanceWindowController"; -const createMaintenanceWindows = async (req, res, next) => { - try { - await createMaintenanceWindowBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; +class MaintenanceWindowController { + constructor(db, settingsService) { + this.db = db; + this.settingsService = settingsService; } - try { - const token = getTokenFromHeaders(req.headers); - const { jwtSecret } = req.settingsService.getSettings(); - const { teamId } = jwt.verify(token, jwtSecret); - const monitorIds = req.body.monitors; - const dbTransactions = monitorIds.map((monitorId) => { - return req.db.createMaintenanceWindow({ - teamId, - monitorId, - name: req.body.name, - active: req.body.active ? req.body.active : true, - repeat: req.body.repeat, - start: req.body.start, - end: req.body.end, + + createMaintenanceWindows = async (req, res, next) => { + try { + await createMaintenanceWindowBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + try { + const token = getTokenFromHeaders(req.headers); + const { jwtSecret } = this.settingsService.getSettings(); + const { teamId } = jwt.verify(token, jwtSecret); + const monitorIds = req.body.monitors; + const dbTransactions = monitorIds.map((monitorId) => { + return this.db.createMaintenanceWindow({ + teamId, + monitorId, + name: req.body.name, + active: req.body.active ? req.body.active : true, + repeat: req.body.repeat, + start: req.body.start, + end: req.body.end, + }); }); - }); - await Promise.all(dbTransactions); - return res.status(201).json({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_CREATE, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "createMaintenanceWindow")); - } -}; + await Promise.all(dbTransactions); + return res.status(201).json({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_CREATE, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "createMaintenanceWindow")); + } + }; -const getMaintenanceWindowById = async (req, res, next) => { - try { - await getMaintenanceWindowByIdParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - const maintenanceWindow = await req.db.getMaintenanceWindowById(req.params.id); - return res.status(200).json({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID, - data: maintenanceWindow, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMaintenanceWindowById")); - } -}; + getMaintenanceWindowById = async (req, res, next) => { + try { + await getMaintenanceWindowByIdParamValidation.validateAsync(req.params); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + try { + const maintenanceWindow = await this.db.getMaintenanceWindowById(req.params.id); + return res.status(200).json({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID, + data: maintenanceWindow, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMaintenanceWindowById")); + } + }; -const getMaintenanceWindowsByTeamId = async (req, res, next) => { - try { - await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + getMaintenanceWindowsByTeamId = async (req, res, next) => { + try { + await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - try { - const token = getTokenFromHeaders(req.headers); - const { jwtSecret } = req.settingsService.getSettings(); - const { teamId } = jwt.verify(token, jwtSecret); - const maintenanceWindows = await req.db.getMaintenanceWindowsByTeamId( - teamId, - req.query - ); + try { + const token = getTokenFromHeaders(req.headers); + const { jwtSecret } = this.settingsService.getSettings(); + const { teamId } = jwt.verify(token, jwtSecret); + const maintenanceWindows = await this.db.getMaintenanceWindowsByTeamId( + teamId, + req.query + ); - return res.status(200).json({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM, - data: maintenanceWindows, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMaintenanceWindowsByUserId")); - } -}; + return res.status(200).json({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM, + data: maintenanceWindows, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMaintenanceWindowsByUserId")); + } + }; -const getMaintenanceWindowsByMonitorId = async (req, res, next) => { - try { - await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + getMaintenanceWindowsByMonitorId = async (req, res, next) => { + try { + await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - try { - const maintenanceWindows = await req.db.getMaintenanceWindowsByMonitorId( - req.params.monitorId - ); + try { + const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId( + req.params.monitorId + ); - return res.status(200).json({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_USER, - data: maintenanceWindows, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMaintenanceWindowsByMonitorId")); - } -}; + return res.status(200).json({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_GET_BY_USER, + data: maintenanceWindows, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMaintenanceWindowsByMonitorId")); + } + }; -const deleteMaintenanceWindow = async (req, res, next) => { - try { - await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - await req.db.deleteMaintenanceWindowById(req.params.id); - return res.status(200).json({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_DELETE, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteMaintenanceWindow")); - } -}; + deleteMaintenanceWindow = async (req, res, next) => { + try { + await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + try { + await this.db.deleteMaintenanceWindowById(req.params.id); + return res.status(200).json({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_DELETE, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "deleteMaintenanceWindow")); + } + }; -const editMaintenanceWindow = async (req, res, next) => { - try { - await editMaintenanceWindowByIdParamValidation.validateAsync(req.params); - await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - const editedMaintenanceWindow = await req.db.editMaintenanceWindowById( - req.params.id, - req.body - ); - return res.status(200).json({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_EDIT, - data: editedMaintenanceWindow, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "editMaintenanceWindow")); - } -}; + editMaintenanceWindow = async (req, res, next) => { + try { + await editMaintenanceWindowByIdParamValidation.validateAsync(req.params); + await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + try { + const editedMaintenanceWindow = await this.db.editMaintenanceWindowById( + req.params.id, + req.body + ); + return res.status(200).json({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_EDIT, + data: editedMaintenanceWindow, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "editMaintenanceWindow")); + } + }; +} -export { - createMaintenanceWindows, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindow, - editMaintenanceWindow, -}; +export default MaintenanceWindowController; diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 625c1a0f7..613027f53 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -14,7 +14,7 @@ import { getCertificateParamValidation, } from "../validation/joi.js"; import sslChecker from "ssl-checker"; -import { errorMessages, successMessages } from "../utils/messages.js"; +import { successMessages } from "../utils/messages.js"; import jwt from "jsonwebtoken"; import { getTokenFromHeaders } from "../utils/utils.js"; import logger from "../utils/logger.js"; @@ -23,510 +23,503 @@ import axios from "axios"; const SERVICE_NAME = "monitorController"; -/** - * Returns all monitors - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @param {function} next - * @returns {Promise} - * @throws {Error} - */ -const getAllMonitors = async (req, res, next) => { - try { - const monitors = await req.db.getAllMonitors(); - return res.status(200).json({ - success: true, - msg: successMessages.MONITOR_GET_ALL, - data: monitors, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getAllMonitors")); - } -}; - -/** - * Returns all monitors with uptime stats for 1,7,30, and 90 days - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @param {function} next - * @returns {Promise} - * @throws {Error} - */ -const getAllMonitorsWithUptimeStats = async (req, res, next) => { - try { - const monitors = await req.db.getAllMonitorsWithUptimeStats(); - return res.status(200).json({ - success: true, - msg: successMessages.MONITOR_GET_ALL, - data: monitors, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getAllMonitorsWithUptimeStats")); - } -}; - -/** - * Returns monitor stats for monitor with matching ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @param {function} next - * @returns {Promise} - * @throws {Error} - */ -const getMonitorStatsById = async (req, res, next) => { - try { - await getMonitorStatsByIdParamValidation.validateAsync(req.params); - await getMonitorStatsByIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; +class MonitorController { + constructor(db, settingsService, jobQueue) { + this.db = db; + this.settingsService = settingsService; + this.jobQueue = jobQueue; } - try { - const monitorStats = await req.db.getMonitorStatsById(req); - return res.status(200).json({ - success: true, - msg: successMessages.MONITOR_STATS_BY_ID, - data: monitorStats, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorStatsById")); - } -}; + /** + * Returns all monitors + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @param {function} next + * @returns {Promise} + * @throws {Error} + */ + getAllMonitors = async (req, res, next) => { + try { + const monitors = await this.db.getAllMonitors(); + return res.status(200).json({ + success: true, + msg: successMessages.MONITOR_GET_ALL, + data: monitors, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getAllMonitors")); + } + }; -const getMonitorCertificate = async (req, res, next, fetchMonitorCertificate) => { - try { - await getCertificateParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - } + /** + * Returns all monitors with uptime stats for 1,7,30, and 90 days + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @param {function} next + * @returns {Promise} + * @throws {Error} + */ + getAllMonitorsWithUptimeStats = async (req, res, next) => { + try { + const monitors = await this.db.getAllMonitorsWithUptimeStats(); + return res.status(200).json({ + success: true, + msg: successMessages.MONITOR_GET_ALL, + data: monitors, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getAllMonitorsWithUptimeStats")); + } + }; - try { - const { monitorId } = req.params; - const monitor = await req.db.getMonitorById(monitorId); - const certificate = await fetchMonitorCertificate(sslChecker, monitor); + /** + * Returns monitor stats for monitor with matching ID + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @param {function} next + * @returns {Promise} + * @throws {Error} + */ + getMonitorStatsById = async (req, res, next) => { + try { + await getMonitorStatsByIdParamValidation.validateAsync(req.params); + await getMonitorStatsByIdQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - return res.status(200).json({ - success: true, - msg: successMessages.MONITOR_CERTIFICATE, - data: { - certificateDate: new Date(certificate.validTo), - }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorCertificate")); - } -}; + try { + const monitorStats = await this.db.getMonitorStatsById(req); + return res.status(200).json({ + success: true, + msg: successMessages.MONITOR_STATS_BY_ID, + data: monitorStats, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMonitorStatsById")); + } + }; -/** - * Retrieves a monitor by its ID. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be retrieved. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message, and the retrieved monitor data. - * @throws {Error} If there is an error during the process, especially if the monitor is not found (404) or if there is a validation error (422). - */ -const getMonitorById = async (req, res, next) => { - try { - await getMonitorByIdParamValidation.validateAsync(req.params); - await getMonitorByIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + getMonitorCertificate = async (req, res, next, fetchMonitorCertificate) => { + try { + await getCertificateParamValidation.validateAsync(req.params); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + } - try { - const monitor = await req.db.getMonitorById(req.params.monitorId); - return res.status(200).json({ - success: true, - msg: successMessages.MONITOR_GET_BY_ID, - data: monitor, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorById")); - } -}; + try { + const { monitorId } = req.params; + const monitor = await this.db.getMonitorById(monitorId); + const certificate = await fetchMonitorCertificate(sslChecker, monitor); -/** - * Retrieves all monitors and a summary for a team based on the team ID. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.teamId - The ID of the team. - * @property {Object} req.query - The query parameters of the request. - * @property {string} req.query.type - The type of the request. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message, and the data containing the monitors and summary for the team. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ -const getMonitorsAndSummaryByTeamId = async (req, res, next) => { - try { - await getMonitorsAndSummaryByTeamIdParamValidation.validateAsync(req.params); - await getMonitorsAndSummaryByTeamIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + return res.status(200).json({ + success: true, + msg: successMessages.MONITOR_CERTIFICATE, + data: { + certificateDate: new Date(certificate.validTo), + }, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMonitorCertificate")); + } + }; - try { - const { teamId } = req.params; - const { type } = req.query; - const monitorsSummary = await req.db.getMonitorsAndSummaryByTeamId(teamId, type); - return res.status(200).json({ - success: true, - msg: successMessages.MONITOR_GET_BY_USER_ID(teamId), - data: monitorsSummary, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorsAndSummaryByTeamId")); - } -}; + /** + * Retrieves a monitor by its ID. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.params - The parameters of the request. + * @property {string} req.params.monitorId - The ID of the monitor to be retrieved. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message, and the retrieved monitor data. + * @throws {Error} If there is an error during the process, especially if the monitor is not found (404) or if there is a validation error (422). + */ + getMonitorById = async (req, res, next) => { + try { + await getMonitorByIdParamValidation.validateAsync(req.params); + await getMonitorByIdQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } -/** - * Retrieves all monitors associated with a team by the team's ID. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.teamId - The ID of the team. - * @property {Object} req.query - The query parameters of the request. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message, and the data containing the monitors for the team. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ -const getMonitorsByTeamId = async (req, res, next) => { - try { - await getMonitorsByTeamIdValidation.validateAsync(req.params); - await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + const monitor = await this.db.getMonitorById(req.params.monitorId); + return res.status(200).json({ + success: true, + msg: successMessages.MONITOR_GET_BY_ID, + data: monitor, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMonitorById")); + } + }; - try { - const teamId = req.params.teamId; - const monitors = await req.db.getMonitorsByTeamId(req, res); - return res.status(200).json({ - success: true, - msg: successMessages.MONITOR_GET_BY_USER_ID(teamId), - data: monitors, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId")); - next(error); - } -}; + /** + * Retrieves all monitors and a summary for a team based on the team ID. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.params - The parameters of the request. + * @property {string} req.params.teamId - The ID of the team. + * @property {Object} req.query - The query parameters of the request. + * @property {string} req.query.type - The type of the request. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message, and the data containing the monitors and summary for the team. + * @throws {Error} If there is an error during the process, especially if there is a validation error (422). + */ + getMonitorsAndSummaryByTeamId = async (req, res, next) => { + try { + await getMonitorsAndSummaryByTeamIdParamValidation.validateAsync(req.params); + await getMonitorsAndSummaryByTeamIdQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } -/** - * Creates a new monitor and adds it to the job queue. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {Array} req.body.notifications - The notifications associated with the monitor. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the creation of the monitor, and the created monitor data. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ -const createMonitor = async (req, res, next) => { - try { - await createMonitorBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + const { teamId } = req.params; + const { type } = req.query; + const monitorsSummary = await this.db.getMonitorsAndSummaryByTeamId(teamId, type); + return res.status(200).json({ + success: true, + msg: successMessages.MONITOR_GET_BY_USER_ID(teamId), + data: monitorsSummary, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMonitorsAndSummaryByTeamId")); + } + }; - try { - const notifications = req.body.notifications; - const monitor = await req.db.createMonitor(req, res); + /** + * Retrieves all monitors associated with a team by the team's ID. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.params - The parameters of the request. + * @property {string} req.params.teamId - The ID of the team. + * @property {Object} req.query - The query parameters of the request. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message, and the data containing the monitors for the team. + * @throws {Error} If there is an error during the process, especially if there is a validation error (422). + */ + getMonitorsByTeamId = async (req, res, next) => { + try { + await getMonitorsByTeamIdValidation.validateAsync(req.params); + await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - if (notifications && notifications.length > 0) { - monitor.notifications = await Promise.all( - notifications.map(async (notification) => { - notification.monitorId = monitor._id; - return await req.db.createNotification(notification); + try { + const teamId = req.params.teamId; + const monitors = await this.db.getMonitorsByTeamId(req, res); + return res.status(200).json({ + success: true, + msg: successMessages.MONITOR_GET_BY_USER_ID(teamId), + data: monitors, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId")); + next(error); + } + }; + + /** + * Creates a new monitor and adds it to the job queue. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.body - The body of the request. + * @property {Array} req.body.notifications - The notifications associated with the monitor. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message indicating the creation of the monitor, and the created monitor data. + * @throws {Error} If there is an error during the process, especially if there is a validation error (422). + */ + createMonitor = async (req, res, next) => { + try { + await createMonitorBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + + try { + const notifications = req.body.notifications; + const monitor = await this.db.createMonitor(req, res); + + if (notifications && notifications.length > 0) { + monitor.notifications = await Promise.all( + notifications.map(async (notification) => { + notification.monitorId = monitor._id; + return await this.db.createNotification(notification); + }) + ); + } + + await monitor.save(); + // Add monitor to job queue + this.jobQueue.addJob(monitor._id, monitor); + return res.status(201).json({ + success: true, + msg: successMessages.MONITOR_CREATE, + data: monitor, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "createMonitor")); + } + }; + + /** + * Checks if the endpoint can be resolved + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.query - The query parameters of the request. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message, and the resolution result. + * @throws {Error} If there is an error during the process, especially if there is a validation error (422). + */ + checkEndpointResolution = async (req, res, next) => { + try { + await getMonitorURLByQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + + try { + const { monitorURL } = req.query; + const parsedUrl = new URL(monitorURL); + const response = await axios.get(parsedUrl, { + timeout: 5000, + validateStatus: () => true, + }); + return res.status(200).json({ + success: true, + code: response.status, + statusText: response.statusText, + msg: `URL resolved successfully`, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "checkEndpointResolution")); + } + }; + + /** + * Deletes a monitor by its ID and also deletes associated checks, alerts, and notifications. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.params - The parameters of the request. + * @property {string} req.params.monitorId - The ID of the monitor to be deleted. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status and a message indicating the deletion of the monitor. + * @throws {Error} If there is an error during the process, especially if there is a validation error (422) or an error in deleting associated records. + */ + deleteMonitor = async (req, res, next) => { + try { + await getMonitorByIdParamValidation.validateAsync(req.params); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + + try { + const monitor = await this.db.deleteMonitor(req, res, next); + // Delete associated checks,alerts,and notifications + try { + await this.jobQueue.deleteJob(monitor); + await this.db.deleteChecks(monitor._id); + await this.db.deletePageSpeedChecksByMonitorId(monitor._id); + await this.db.deleteNotificationsByMonitorId(monitor._id); + await this.db.deleteHardwareChecksByMonitorId(monitor._id); + } catch (error) { + logger.error({ + message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, + service: SERVICE_NAME, + method: "deleteMonitor", + stack: error.stack, + }); + } + return res.status(200).json({ success: true, msg: successMessages.MONITOR_DELETE }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "deleteMonitor")); + } + }; + + /** + * Deletes all monitors associated with a team. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.headers - The headers of the request. + * @property {string} req.headers.authorization - The authorization header containing the JWT token. + * @param {Object} res - The Express response object. + * @param {function} next + * @returns {Object} The response object with a success status and a message indicating the number of deleted monitors. + * @throws {Error} If there is an error during the deletion process. + */ + deleteAllMonitors = async (req, res, next) => { + try { + const token = getTokenFromHeaders(req.headers); + const { jwtSecret } = this.settingsService.getSettings(); + const { teamId } = jwt.verify(token, jwtSecret); + const { monitors, deletedCount } = await this.db.deleteAllMonitors(teamId); + await Promise.all( + monitors.map(async (monitor) => { + try { + await this.jobQueue.deleteJob(monitor); + await this.db.deleteChecks(monitor._id); + await this.db.deletePageSpeedChecksByMonitorId(monitor._id); + await this.db.deleteNotificationsByMonitorId(monitor._id); + } catch (error) { + logger.error({ + message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, + service: SERVICE_NAME, + method: "deleteAllMonitors", + stack: error.stack, + }); + } }) ); - } - - await monitor.save(); - // Add monitor to job queue - req.jobQueue.addJob(monitor._id, monitor); - return res.status(201).json({ - success: true, - msg: successMessages.MONITOR_CREATE, - data: monitor, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "createMonitor")); - } -}; - -/** - * Checks if the endpoint can be resolved - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.query - The query parameters of the request. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message, and the resolution result. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ -const checkEndpointResolution = async (req, res, next) => { - try { - await getMonitorURLByQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const { monitorURL } = req.query; - const parsedUrl = new URL(monitorURL); - const response = await axios.get(parsedUrl, { - timeout: 5000, - validateStatus: () => true, - }); - return res.status(200).json({ - success: true, - code: response.status, - statusText: response.statusText, - msg: `URL resolved successfully`, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "checkEndpointResolution")); - } -}; - -/** - * Deletes a monitor by its ID and also deletes associated checks, alerts, and notifications. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be deleted. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status and a message indicating the deletion of the monitor. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422) or an error in deleting associated records. - */ -const deleteMonitor = async (req, res, next) => { - try { - await getMonitorByIdParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const monitor = await req.db.deleteMonitor(req, res, next); - // Delete associated checks,alerts,and notifications - try { - await req.jobQueue.deleteJob(monitor); - await req.db.deleteChecks(monitor._id); - await req.db.deletePageSpeedChecksByMonitorId(monitor._id); - await req.db.deleteNotificationsByMonitorId(monitor._id); - await req.db.deleteHardwareChecksByMonitorId(monitor._id); + return res + .status(200) + .json({ success: true, msg: `Deleted ${deletedCount} monitors` }); } catch (error) { - logger.error({ - message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, - service: SERVICE_NAME, - method: "deleteMonitor", - stack: error.stack, - }); + next(handleError(error, SERVICE_NAME, "deleteAllMonitors")); } - return res.status(200).json({ success: true, msg: successMessages.MONITOR_DELETE }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteMonitor")); - } -}; + }; -/** - * Deletes all monitors associated with a team. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.headers - The headers of the request. - * @property {string} req.headers.authorization - The authorization header containing the JWT token. - * @param {Object} res - The Express response object. - * @param {function} next - * @returns {Object} The response object with a success status and a message indicating the number of deleted monitors. - * @throws {Error} If there is an error during the deletion process. - */ -const deleteAllMonitors = async (req, res, next) => { - try { - const token = getTokenFromHeaders(req.headers); - const { jwtSecret } = req.settingsService.getSettings(); - const { teamId } = jwt.verify(token, jwtSecret); - const { monitors, deletedCount } = await req.db.deleteAllMonitors(teamId); - await Promise.all( - monitors.map(async (monitor) => { - try { - await req.jobQueue.deleteJob(monitor); - await req.db.deleteChecks(monitor._id); - await req.db.deletePageSpeedChecksByMonitorId(monitor._id); - await req.db.deleteNotificationsByMonitorId(monitor._id); - } catch (error) { - logger.error({ - message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, - service: SERVICE_NAME, - method: "deleteAllMonitors", - stack: error.stack, - }); - } - }) - ); - return res - .status(200) - .json({ success: true, msg: `Deleted ${deletedCount} monitors` }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteAllMonitors")); - } -}; + /** + * Edits a monitor by its ID, updates its notifications, and updates its job in the job queue. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.params - The parameters of the request. + * @property {string} req.params.monitorId - The ID of the monitor to be edited. + * @property {Object} req.body - The body of the request. + * @property {Array} req.body.notifications - The notifications to be associated with the monitor. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message indicating the editing of the monitor, and the edited monitor data. + * @throws {Error} If there is an error during the process, especially if there is a validation error (422). + */ + editMonitor = async (req, res, next) => { + try { + await getMonitorByIdParamValidation.validateAsync(req.params); + await editMonitorBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } -/** - * Edits a monitor by its ID, updates its notifications, and updates its job in the job queue. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be edited. - * @property {Object} req.body - The body of the request. - * @property {Array} req.body.notifications - The notifications to be associated with the monitor. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the editing of the monitor, and the edited monitor data. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ -const editMonitor = async (req, res, next) => { - try { - await getMonitorByIdParamValidation.validateAsync(req.params); - await editMonitorBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + const { monitorId } = req.params; + const monitorBeforeEdit = await this.db.getMonitorById(monitorId); - try { - const { monitorId } = req.params; - const monitorBeforeEdit = await req.db.getMonitorById(monitorId); + // Get notifications from the request body + const notifications = req.body.notifications; - // Get notifications from the request body - const notifications = req.body.notifications; + const editedMonitor = await this.db.editMonitor(monitorId, req.body); - const editedMonitor = await req.db.editMonitor(monitorId, req.body); + await this.db.deleteNotificationsByMonitorId(editedMonitor._id); - await req.db.deleteNotificationsByMonitorId(editedMonitor._id); + await Promise.all( + notifications && + notifications.map(async (notification) => { + notification.monitorId = editedMonitor._id; + await this.db.createNotification(notification); + }) + ); - await Promise.all( - notifications && - notifications.map(async (notification) => { - notification.monitorId = editedMonitor._id; - await req.db.createNotification(notification); - }) - ); + // Delete the old job(editedMonitor has the same ID as the old monitor) + await this.jobQueue.deleteJob(monitorBeforeEdit); + // Add the new job back to the queue + await this.jobQueue.addJob(editedMonitor._id, editedMonitor); + return res.status(200).json({ + success: true, + msg: successMessages.MONITOR_EDIT, + data: editedMonitor, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "editMonitor")); + } + }; - // Delete the old job(editedMonitor has the same ID as the old monitor) - await req.jobQueue.deleteJob(monitorBeforeEdit); - // Add the new job back to the queue - await req.jobQueue.addJob(editedMonitor._id, editedMonitor); - return res.status(200).json({ - success: true, - msg: successMessages.MONITOR_EDIT, - data: editedMonitor, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "editMonitor")); - } -}; + /** + * Pauses or resumes a monitor based on its current state. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.params - The parameters of the request. + * @property {string} req.params.monitorId - The ID of the monitor to be paused or resumed. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message indicating the new state of the monitor, and the updated monitor data. + * @throws {Error} If there is an error during the process. + */ + pauseMonitor = async (req, res, next) => { + try { + await pauseMonitorParamValidation.validateAsync(req.params); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + } -/** - * Pauses or resumes a monitor based on its current state. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be paused or resumed. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the new state of the monitor, and the updated monitor data. - * @throws {Error} If there is an error during the process. - */ -const pauseMonitor = async (req, res, next) => { - try { - await pauseMonitorParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - } + try { + const monitor = await this.db.getMonitorById(req.params.monitorId); + monitor.isActive === true + ? await this.jobQueue.deleteJob(monitor) + : await this.jobQueue.addJob(monitor._id, monitor); - try { - const monitor = await req.db.getMonitorById(req.params.monitorId); - monitor.isActive === true - ? await req.jobQueue.deleteJob(monitor) - : await req.jobQueue.addJob(monitor._id, monitor); + monitor.isActive = !monitor.isActive; + monitor.status = undefined; + monitor.save(); + return res.status(200).json({ + success: true, + msg: monitor.isActive + ? successMessages.MONITOR_RESUME + : successMessages.MONITOR_PAUSE, + data: monitor, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "pauseMonitor")); + } + }; - monitor.isActive = !monitor.isActive; - monitor.status = undefined; - monitor.save(); - return res.status(200).json({ - success: true, - msg: monitor.isActive - ? successMessages.MONITOR_RESUME - : successMessages.MONITOR_PAUSE, - data: monitor, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "pauseMonitor")); - } -}; + /** + * Adds demo monitors for a team. + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.headers - The headers of the request. + * @property {string} req.headers.authorization - The authorization header containing the JWT token. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message indicating the addition of demo monitors, and the number of demo monitors added. + * @throws {Error} If there is an error during the process. + */ + addDemoMonitors = async (req, res, next) => { + try { + const token = getTokenFromHeaders(req.headers); + const { jwtSecret } = this.settingsService.getSettings(); + const { _id, teamId } = jwt.verify(token, jwtSecret); + const demoMonitors = await this.db.addDemoMonitors(_id, teamId); + await Promise.all( + demoMonitors.map((monitor) => this.jobQueue.addJob(monitor._id, monitor)) + ); -/** - * Adds demo monitors for a team. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.headers - The headers of the request. - * @property {string} req.headers.authorization - The authorization header containing the JWT token. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the addition of demo monitors, and the number of demo monitors added. - * @throws {Error} If there is an error during the process. - */ -const addDemoMonitors = async (req, res, next) => { - try { - const token = getTokenFromHeaders(req.headers); - const { jwtSecret } = req.settingsService.getSettings(); - const { _id, teamId } = jwt.verify(token, jwtSecret); - const demoMonitors = await req.db.addDemoMonitors(_id, teamId); - await Promise.all( - demoMonitors.map((monitor) => req.jobQueue.addJob(monitor._id, monitor)) - ); + return res.status(200).json({ + success: true, + msg: successMessages.MONITOR_DEMO_ADDED, + data: demoMonitors.length, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "addDemoMonitors")); + } + }; +} - return res.status(200).json({ - success: true, - msg: successMessages.MONITOR_DEMO_ADDED, - data: demoMonitors.length, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "addDemoMonitors")); - } -}; - -export { - getAllMonitors, - getAllMonitorsWithUptimeStats, - getMonitorStatsById, - getMonitorCertificate, - getMonitorById, - getMonitorsAndSummaryByTeamId, - getMonitorsByTeamId, - createMonitor, - checkEndpointResolution, - deleteMonitor, - deleteAllMonitors, - editMonitor, - pauseMonitor, - addDemoMonitors, -}; +export default MonitorController; diff --git a/Server/controllers/queueController.js b/Server/controllers/queueController.js index 5e525de58..501074d42 100644 --- a/Server/controllers/queueController.js +++ b/Server/controllers/queueController.js @@ -1,57 +1,64 @@ import { handleError } from "./controllerUtils.js"; -import { errorMessages, successMessages } from "../utils/messages.js"; +import { successMessages } from "../utils/messages.js"; const SERVICE_NAME = "JobQueueController"; -const getMetrics = async (req, res, next) => { - try { - const metrics = await req.jobQueue.getMetrics(); - res.status(200).json({ - success: true, - msg: successMessages.QUEUE_GET_METRICS, - data: metrics, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMetrics")); - return; +class JobQueueController { + constructor(jobQueue) { + this.jobQueue = jobQueue; } -}; -const getJobs = async (req, res, next) => { - try { - const jobs = await req.jobQueue.getJobStats(); - return res.status(200).json({ - success: true, - msg: successMessages.QUEUE_GET_METRICS, - data: jobs, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getJobs")); - return; - } -}; + getMetrics = async (req, res, next) => { + try { + const metrics = await this.jobQueue.getMetrics(); + res.status(200).json({ + success: true, + msg: successMessages.QUEUE_GET_METRICS, + data: metrics, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMetrics")); + return; + } + }; -const addJob = async (req, res, next) => { - try { - await req.jobQueue.addJob(Math.random().toString(36).substring(7)); - return res.status(200).json({ - success: true, - msg: successMessages.QUEUE_ADD_JOB, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "addJob")); - return; - } -}; + getJobs = async (req, res, next) => { + try { + const jobs = await this.jobQueue.getJobStats(); + return res.status(200).json({ + success: true, + msg: successMessages.QUEUE_GET_METRICS, + data: jobs, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getJobs")); + return; + } + }; -const obliterateQueue = async (req, res, next) => { - try { - await req.jobQueue.obliterate(); - return res.status(200).json({ success: true, msg: successMessages.QUEUE_OBLITERATE }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "obliterateQueue")); - return; - } -}; + addJob = async (req, res, next) => { + try { + await this.jobQueue.addJob(Math.random().toString(36).substring(7)); + return res.status(200).json({ + success: true, + msg: successMessages.QUEUE_ADD_JOB, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "addJob")); + return; + } + }; -export { getMetrics, getJobs, addJob, obliterateQueue }; + obliterateQueue = async (req, res, next) => { + try { + await this.jobQueue.obliterate(); + return res + .status(200) + .json({ success: true, msg: successMessages.QUEUE_OBLITERATE }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "obliterateQueue")); + return; + } + }; +} +export default JobQueueController; diff --git a/Server/controllers/settingsController.js b/Server/controllers/settingsController.js index 3e232ac7a..06a446dec 100644 --- a/Server/controllers/settingsController.js +++ b/Server/controllers/settingsController.js @@ -3,40 +3,47 @@ import { updateAppSettingsBodyValidation } from "../validation/joi.js"; import { handleValidationError, handleError } from "./controllerUtils.js"; const SERVICE_NAME = "SettingsController"; -const getAppSettings = async (req, res, next) => { - try { - const settings = { ...(await req.settingsService.getSettings()) }; - delete settings.jwtSecret; - return res.status(200).json({ - success: true, - msg: successMessages.GET_APP_SETTINGS, - data: settings, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getAppSettings")); - } -}; - -const updateAppSettings = async (req, res, next) => { - try { - await updateAppSettingsBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; +class SettingsController { + constructor(db, settingsService) { + this.db = db; + this.settingsService = settingsService; } - try { - await req.db.updateAppSettings(req.body); - const updatedSettings = { ...(await req.settingsService.reloadSettings()) }; - delete updatedSettings.jwtSecret; - return res.status(200).json({ - success: true, - msg: successMessages.UPDATE_APP_SETTINGS, - data: updatedSettings, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "updateAppSettings")); - } -}; + getAppSettings = async (req, res, next) => { + try { + const settings = { ...(await this.settingsService.getSettings()) }; + delete settings.jwtSecret; + return res.status(200).json({ + success: true, + msg: successMessages.GET_APP_SETTINGS, + data: settings, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getAppSettings")); + } + }; -export { getAppSettings, updateAppSettings }; + updateAppSettings = async (req, res, next) => { + try { + await updateAppSettingsBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + + try { + await this.db.updateAppSettings(req.body); + const updatedSettings = { ...(await this.settingsService.reloadSettings()) }; + delete updatedSettings.jwtSecret; + return res.status(200).json({ + success: true, + msg: successMessages.UPDATE_APP_SETTINGS, + data: updatedSettings, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "updateAppSettings")); + } + }; +} + +export default SettingsController; diff --git a/Server/controllers/statusPageController.js b/Server/controllers/statusPageController.js index facac7c4a..bbb7a18ee 100644 --- a/Server/controllers/statusPageController.js +++ b/Server/controllers/statusPageController.js @@ -7,45 +7,50 @@ import { successMessages } from "../utils/messages.js"; const SERVICE_NAME = "statusPageController"; -const createStatusPage = async (req, res, next) => { - try { - await createStatusPageBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; +class StatusPageController { + constructor(db) { + this.db = db; } - try { - const statusPage = await req.db.createStatusPage(req.body); - return res.status(200).json({ - success: true, - msg: successMessages.STATUS_PAGE_CREATE, - data: statusPage, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "createStatusPage")); - } -}; + createStatusPage = async (req, res, next) => { + try { + await createStatusPageBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } -const getStatusPageByUrl = async (req, res, next) => { - try { - await getStatusPageParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + const statusPage = await this.db.createStatusPage(req.body); + return res.status(200).json({ + success: true, + msg: successMessages.STATUS_PAGE_CREATE, + data: statusPage, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "createStatusPage")); + } + }; + getStatusPageByUrl = async (req, res, next) => { + try { + await getStatusPageParamValidation.validateAsync(req.params); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - try { - const { url } = req.params; - const statusPage = await req.db.getStatusPageByUrl(url); - return res.status(200).json({ - success: true, - msg: successMessages.STATUS_PAGE_BY_URL, - data: statusPage, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getStatusPage")); - } -}; + try { + const { url } = req.params; + const statusPage = await this.db.getStatusPageByUrl(url); + return res.status(200).json({ + success: true, + msg: successMessages.STATUS_PAGE_BY_URL, + data: statusPage, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getStatusPage")); + } + }; +} -export { createStatusPage, getStatusPageByUrl }; +export default StatusPageController;