Merge pull request #1470 from bluewave-labs/feat/be/migrate-to-class

feat: be/migrate to class
This commit is contained in:
Alexander Holliday authored and GitHub committed 2024-12-27 09:54:20 -08:00
commit 955d3c6298
28 files changed
+1961 -1839

No files matched your search

+471 -470
View File
@@ -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;
+122 -123
View File
@@ -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;
+74 -67
View File
@@ -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;
+139 -140
View File
@@ -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;
+470 -477
View File
@@ -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<Express.Response>}
* @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<Express.Response>}
* @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<Express.Response>}
* @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<Express.Response>}
* @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<Express.Response>}
* @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<Express.Response>}
* @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;
+55 -48
View File
@@ -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;
+41 -34
View File
@@ -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;
+42 -37
View File
@@ -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;
+76 -181
View File
@@ -2,234 +2,129 @@ import mongoose from "mongoose";
import UserModel from "../models/User.js";
import AppSettings from "../models/AppSettings.js";
import logger from "../../utils/logger.js";
const SERVICE_NAME = "MongoDB";
//****************************************
// DB Connection
//****************************************
const connect = async () => {
try {
const connectionString =
process.env.DB_CONNECTION_STRING || "mongodb://localhost:27017/uptime_db";
await mongoose.connect(connectionString);
// If there are no AppSettings, create one
let appSettings = await AppSettings.find();
if (appSettings.length === 0) {
appSettings = new AppSettings({});
await appSettings.save();
}
logger.info({ message: "Connected to MongoDB" });
} catch (error) {
logger.error({
message: error.message,
service: SERVICE_NAME,
method: "connect",
stack: error.stack,
});
throw error;
}
};
const disconnect = async () => {
try {
logger.info({ message: "Disconnecting from MongoDB" });
await mongoose.disconnect();
logger.info({ message: "Disconnected from MongoDB" });
return;
} catch (error) {
logger.error({
message: error.message,
service: SERVICE_NAME,
method: "disconnect",
stack: error.stack,
});
}
};
const checkSuperadmin = async (req, res) => {
try {
const superAdmin = await UserModel.findOne({ role: "superadmin" });
if (superAdmin !== null) {
return true;
}
return false;
} catch (error) {
throw error;
}
};
//****************************************
// User Operations
//****************************************
import {
insertUser,
getUserByEmail,
updateUser,
deleteUser,
deleteTeam,
deleteAllOtherUsers,
getAllUsers,
logoutUser,
} from "./modules/userModule.js";
import * as userModule from "./modules/userModule.js";
//****************************************
// Invite Token Operations
//****************************************
import {
requestInviteToken,
getInviteToken,
getInviteTokenAndDelete,
} from "./modules/inviteModule.js";
import * as inviteModule from "./modules/inviteModule.js";
//****************************************
// Recovery Operations
//****************************************
import {
requestRecoveryToken,
validateRecoveryToken,
resetPassword,
} from "./modules/recoveryModule.js";
import * as recoveryModule from "./modules/recoveryModule.js";
//****************************************
// Monitors
//****************************************
import {
getAllMonitors,
getAllMonitorsWithUptimeStats,
getMonitorStatsById,
getMonitorById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
deleteMonitor,
deleteAllMonitors,
deleteMonitorsByUserId,
editMonitor,
addDemoMonitors,
} from "./modules/monitorModule.js";
import * as monitorModule from "./modules/monitorModule.js";
//****************************************
// Page Speed Checks
//****************************************
import {
createPageSpeedCheck,
deletePageSpeedChecksByMonitorId,
} from "./modules/pageSpeedCheckModule.js";
import * as pageSpeedCheckModule from "./modules/pageSpeedCheckModule.js";
//****************************************
// Hardware Checks
//****************************************
import {
createHardwareCheck,
deleteHardwareChecksByMonitorId,
} from "./modules/hardwareCheckModule.js";
import * as hardwareCheckModule from "./modules/hardwareCheckModule.js";
//****************************************
// Checks
//****************************************
import {
createCheck,
getChecksCount,
getChecks,
getTeamChecks,
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
} from "./modules/checkModule.js";
import * as checkModule from "./modules/checkModule.js";
//****************************************
// Maintenance Window
//****************************************
import {
createMaintenanceWindow,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindowById,
deleteMaintenanceWindowByMonitorId,
deleteMaintenanceWindowByUserId,
editMaintenanceWindowById,
} from "./modules/maintenanceWindowModule.js";
import * as maintenanceWindowModule from "./modules/maintenanceWindowModule.js";
//****************************************
// Notifications
//****************************************
import {
createNotification,
getNotificationsByMonitorId,
deleteNotificationsByMonitorId,
} from "./modules/notificationModule.js";
import * as notificationModule from "./modules/notificationModule.js";
//****************************************
// AppSettings
//****************************************
import { getAppSettings, updateAppSettings } from "./modules/settingsModule.js";
import * as settingsModule from "./modules/settingsModule.js";
//****************************************
// Status Page
//****************************************
import { createStatusPage, getStatusPageByUrl } from "./modules/statusPageModule.js";
import * as statusPageModule from "./modules/statusPageModule.js";
export default {
connect,
disconnect,
insertUser,
getUserByEmail,
updateUser,
deleteUser,
deleteTeam,
deleteAllOtherUsers,
getAllUsers,
logoutUser,
requestInviteToken,
getInviteToken,
getInviteTokenAndDelete,
requestRecoveryToken,
validateRecoveryToken,
resetPassword,
checkSuperadmin,
getAllMonitors,
getAllMonitorsWithUptimeStats,
getMonitorStatsById,
getMonitorById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
deleteMonitor,
deleteAllMonitors,
editMonitor,
addDemoMonitors,
createCheck,
getChecksCount,
getChecks,
getTeamChecks,
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
deleteMonitorsByUserId,
createPageSpeedCheck,
deletePageSpeedChecksByMonitorId,
createHardwareCheck,
deleteHardwareChecksByMonitorId,
createMaintenanceWindow,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowById,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindowById,
deleteMaintenanceWindowByMonitorId,
deleteMaintenanceWindowByUserId,
editMaintenanceWindowById,
createNotification,
getNotificationsByMonitorId,
deleteNotificationsByMonitorId,
getAppSettings,
updateAppSettings,
createStatusPage,
getStatusPageByUrl,
};
class MongoDB {
static SERVICE_NAME = "MongoDB";
constructor() {
Object.assign(this, userModule);
Object.assign(this, inviteModule);
Object.assign(this, recoveryModule);
Object.assign(this, monitorModule);
Object.assign(this, pageSpeedCheckModule);
Object.assign(this, hardwareCheckModule);
Object.assign(this, checkModule);
Object.assign(this, maintenanceWindowModule);
Object.assign(this, notificationModule);
Object.assign(this, settingsModule);
Object.assign(this, statusPageModule);
}
connect = async () => {
try {
const connectionString =
process.env.DB_CONNECTION_STRING || "mongodb://localhost:27017/uptime_db";
await mongoose.connect(connectionString);
// If there are no AppSettings, create one
let appSettings = await AppSettings.find();
if (appSettings.length === 0) {
appSettings = new AppSettings({});
await appSettings.save();
}
logger.info({ message: "Connected to MongoDB" });
} catch (error) {
logger.error({
message: error.message,
service: this.SERVICE_NAME,
method: "connect",
stack: error.stack,
});
throw error;
}
};
disconnect = async () => {
try {
logger.info({ message: "Disconnecting from MongoDB" });
await mongoose.disconnect();
logger.info({ message: "Disconnected from MongoDB" });
return;
} catch (error) {
logger.error({
message: error.message,
service: this.SERVICE_NAME,
method: "disconnect",
stack: error.stack,
});
}
};
checkSuperadmin = async (req, res) => {
const superAdmin = await UserModel.findOne({ role: "superadmin" });
if (superAdmin !== null) {
return true;
}
return false;
};
}
export default MongoDB;
+149 -113
View File
@@ -8,16 +8,31 @@ import cors from "cors";
import logger from "./utils/logger.js";
import { verifyJWT } from "./middleware/verifyJWT.js";
import { handleErrors } from "./middleware/handleErrors.js";
import authRouter from "./routes/authRoute.js";
import inviteRouter from "./routes/inviteRoute.js";
import monitorRouter from "./routes/monitorRoute.js";
import checkRouter from "./routes/checkRoute.js";
import maintenanceWindowRouter from "./routes/maintenanceWindowRoute.js";
import settingsRouter from "./routes/settingsRoute.js";
import statusPageRouter from "./routes/statusPageRoute.js";
import { fileURLToPath } from "url";
import queueRouter from "./routes/queueRoute.js";
import AuthRoutes from "./routes/authRoute.js";
import AuthController from "./controllers/authController.js";
import InviteRoutes from "./routes/inviteRoute.js";
import InviteController from "./controllers/inviteController.js";
import MonitorRoutes from "./routes/monitorRoute.js";
import MonitorController from "./controllers/monitorController.js";
import CheckRoutes from "./routes/checkRoute.js";
import CheckController from "./controllers/checkController.js";
import MaintenanceWindowRoutes from "./routes/maintenanceWindowRoute.js";
import MaintenanceWindowController from "./controllers/maintenanceWindowController.js";
import SettingsRoutes from "./routes/settingsRoute.js";
import SettingsController from "./controllers/settingsController.js";
import StatusPageRoutes from "./routes/statusPageRoute.js";
import StatusPageController from "./controllers/statusPageController.js";
import QueueRoutes from "./routes/queueRoute.js";
import QueueController from "./controllers/queueController.js";
//JobQueue service and dependencies
import JobQueue from "./service/jobQueue.js";
@@ -41,102 +56,68 @@ import mjml2html from "mjml";
import SettingsService from "./service/settingsService.js";
import AppSettings from "./db/models/AppSettings.js";
// Status Service and dependencies
import StatusService from "./service/statusService.js";
// Notification Service and dependencies
import NotificationService from "./service/notificationService.js";
import db from "./db/mongo/MongoDB.js";
// Service Registry
import ServiceRegistry from "./service/serviceRegistry.js";
import MongoDB from "./db/mongo/MongoDB.js";
const SERVICE_NAME = "Server";
const SHUTDOWN_TIMEOUT = 1000;
let isShuttingDown = false;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const openApiSpec = JSON.parse(
fs.readFileSync(path.join(__dirname, "openapi.json"), "utf8")
);
let server;
const PORT = 5000;
const shutdown = async () => {
if (isShuttingDown) {
return;
}
isShuttingDown = true;
logger.info({ message: "Attempting graceful shutdown" });
setTimeout(() => {
logger.error({
message: "Could not shut down in time, forcing shutdown",
service: SERVICE_NAME,
method: "shutdown",
});
process.exit(1);
}, SHUTDOWN_TIMEOUT);
try {
server.close();
await ServiceRegistry.get(JobQueue.SERVICE_NAME).obliterate();
await ServiceRegistry.get(MongoDB.SERVICE_NAME).disconnect();
logger.info({ message: "Graceful shutdown complete" });
process.exit(0);
} catch (error) {
logger.error({
message: error.message,
service: SERVICE_NAME,
method: "shutdown",
stack: error.stack,
});
}
};
// Need to wrap server setup in a function to handle async nature of JobQueue
const startApp = async () => {
const app = express();
// middlewares
app.use(
cors()
//We will add configuration later
);
app.use(express.json());
app.use(helmet());
// Add db, jobQueue, emailService, and settingsService to request object for easy access
app.use((req, res, next) => {
req.db = db;
req.jobQueue = jobQueue;
req.emailService = emailService;
req.settingsService = settingsService;
next();
});
// Swagger UI
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec));
//routes
app.use("/api/v1/auth", authRouter);
app.use("/api/v1/settings", verifyJWT, settingsRouter);
app.use("/api/v1/invite", inviteRouter);
app.use("/api/v1/monitors", verifyJWT, monitorRouter);
app.use("/api/v1/checks", verifyJWT, checkRouter);
app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRouter);
app.use("/api/v1/queue", verifyJWT, queueRouter);
app.use("/api/v1/status-page", statusPageRouter);
app.use("/api/v1/dummy-data", async (req, res) => {
try {
const response = await axios.get(
"https://gist.githubusercontent.com/ajhollid/9afa39410c7bbf52cc905f285a2225bf/raw/429a231a3559ebc95f6f488ed2c766bd7d6f46e5/dummyData.json",
{
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-cache",
},
}
);
return res.status(200).json(response.data);
} catch (error) {
return res.status(500).json({ message: error.message });
}
});
//health check
app.use("/api/v1/healthy", (req, res) => {
try {
logger.info({ message: "Checking Health of the server." });
return res.status(200).json({ message: "Healthy" });
} catch (error) {
logger.error({
message: error.message,
service: SERVICE_NAME,
method: "healthCheck",
stack: error.stack,
});
return res.status(500).json({ message: error.message });
}
});
/**
* Error handler middleware
* Should be called last
*/
app.use(handleErrors);
// Create DB
const db = new MongoDB();
await db.connect();
// Create services
await db.connect();
const server = app.listen(PORT, () => {
logger.info({ message: `server started on port:${PORT}` });
});
const settingsService = new SettingsService(AppSettings);
await settingsService.loadSettings();
const emailService = new EmailService(
@@ -162,39 +143,94 @@ const startApp = async () => {
Worker
);
const shutdown = async () => {
if (isShuttingDown) {
return;
}
isShuttingDown = true;
logger.info({ message: "Attempting graceful shutdown" });
setTimeout(() => {
logger.error({
message: "Could not shut down in time, forcing shutdown",
service: SERVICE_NAME,
method: "shutdown",
});
process.exit(1);
}, SHUTDOWN_TIMEOUT);
try {
server.close();
await jobQueue.obliterate();
await db.disconnect();
logger.info({ message: "Graceful shutdown complete" });
process.exit(0);
} catch (error) {
logger.error({
message: error.message,
service: SERVICE_NAME,
method: "shutdown",
stack: error.stack,
});
}
};
// Register services
ServiceRegistry.register(JobQueue.SERVICE_NAME, jobQueue);
ServiceRegistry.register(MongoDB.SERVICE_NAME, db);
ServiceRegistry.register(SettingsService.SERVICE_NAME, settingsService);
ServiceRegistry.register(EmailService.SERVICE_NAME, emailService);
ServiceRegistry.register(NetworkService.SERVICE_NAME, networkService);
ServiceRegistry.register(StatusService.SERVICE_NAME, statusService);
ServiceRegistry.register(NotificationService.SERVICE_NAME, notificationService);
server = app.listen(PORT, () => {
logger.info({ message: `server started on port:${PORT}` });
});
process.on("SIGUSR2", shutdown);
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
//Create controllers
const authController = new AuthController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME),
ServiceRegistry.get(EmailService.SERVICE_NAME),
ServiceRegistry.get(JobQueue.SERVICE_NAME)
);
const monitorController = new MonitorController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME),
ServiceRegistry.get(JobQueue.SERVICE_NAME)
);
const settingsController = new SettingsController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME)
);
const checkController = new CheckController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME)
);
const inviteController = new InviteController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME),
ServiceRegistry.get(EmailService.SERVICE_NAME)
);
const maintenanceWindowController = new MaintenanceWindowController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME)
);
const queueController = new QueueController(ServiceRegistry.get(JobQueue.SERVICE_NAME));
const statusPageController = new StatusPageController(
ServiceRegistry.get(MongoDB.SERVICE_NAME)
);
//Create routes
const authRoutes = new AuthRoutes(authController);
const monitorRoutes = new MonitorRoutes(monitorController);
const settingsRoutes = new SettingsRoutes(settingsController);
const checkRoutes = new CheckRoutes(checkController);
const inviteRoutes = new InviteRoutes(inviteController);
const maintenanceWindowRoutes = new MaintenanceWindowRoutes(
maintenanceWindowController
);
const queueRoutes = new QueueRoutes(queueController);
const statusPageRoutes = new StatusPageRoutes(statusPageController);
// Middleware
app.use(
cors()
//We will add configuration later
);
app.use(express.json());
app.use(helmet());
// Swagger UI
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec));
//routes
app.use("/api/v1/auth", authRoutes.getRouter());
app.use("/api/v1/settings", verifyJWT, settingsRoutes.getRouter());
app.use("/api/v1/invite", inviteRoutes.getRouter());
app.use("/api/v1/monitors", verifyJWT, monitorRoutes.getRouter());
app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter());
app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter());
app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter());
app.use("/api/v1/status-page", statusPageRoutes.getRouter());
app.use(handleErrors);
};
startApp().catch((error) => {
+6 -1
View File
@@ -1,6 +1,9 @@
import jwt from "jsonwebtoken";
const TOKEN_PREFIX = "Bearer ";
const SERVICE_NAME = "allowedRoles";
import ServiceRegistry from "../service/serviceRegistry.js";
import SettingsService from "../service/settingsService.js";
import { errorMessages } from "../utils/messages.js";
const isAllowed = (allowedRoles) => {
@@ -27,7 +30,9 @@ const isAllowed = (allowedRoles) => {
// Parse the token
try {
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
const { jwtSecret } = req.settingsService.getSettings();
const { jwtSecret } = ServiceRegistry.get(
SettingsService.SERVICE_NAME
).getSettings();
var decoded = jwt.verify(parsedToken, jwtSecret);
const userRoles = decoded.role;
+6 -3
View File
@@ -1,6 +1,7 @@
import jwt from "jsonwebtoken";
import { errorMessages } from "../utils/messages.js";
import ServiceRegistry from "../service/serviceRegistry.js";
import SettingsService from "../service/settingsService.js";
const SERVICE_NAME = "verifyJWT";
const TOKEN_PREFIX = "Bearer ";
@@ -34,7 +35,7 @@ const verifyJWT = (req, res, next) => {
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
// Verify the token's authenticity
const { jwtSecret } = req.settingsService.getSettings();
const { jwtSecret } = ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings();
jwt.verify(parsedToken, jwtSecret, (err, decoded) => {
if (err) {
if (err.name === "TokenExpiredError") {
@@ -67,7 +68,9 @@ function handleExpiredJwtToken(req, res, next) {
}
// Verify refresh token
const { refreshTokenSecret } = req.settingsService.getSettings();
const { refreshTokenSecret } = ServiceRegistry.get(
SettingsService.SERVICE_NAME
).getSettings();
jwt.verify(refreshToken, refreshTokenSecret, (refreshErr, refreshDecoded) => {
if (refreshErr) {
// Invalid or expired refresh token, trigger logout
+3 -1
View File
@@ -3,6 +3,8 @@ const logger = require("../utils/logger");
const SERVICE_NAME = "verifyAdmin";
const TOKEN_PREFIX = "Bearer ";
const { errorMessages } = require("../utils/messages");
import ServiceRegistry from "../service/serviceRegistry.js";
import SettingsService from "../service/settingsService.js";
/**
* Verifies the JWT token
* @function
@@ -33,7 +35,7 @@ const verifySuperAdmin = (req, res, next) => {
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
// verify admin role is present
const { jwtSecret } = req.settingsService.getSettings();
const { jwtSecret } = ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings();
jwt.verify(parsedToken, jwtSecret, (err, decoded) => {
if (err) {
+48 -26
View File
@@ -5,34 +5,56 @@ import { isAllowed } from "../middleware/isAllowed.js";
import multer from "multer";
import User from "../db/models/User.js";
const router = Router();
const upload = multer();
import {
registerUser,
loginUser,
refreshAuthToken,
editUser,
requestRecovery,
validateRecovery,
resetPassword,
checkSuperadminExists,
getAllUsers,
deleteUser,
} from "../controllers/authController.js";
class AuthRoutes {
constructor(authController) {
this.router = Router();
this.authController = authController;
this.initRoutes();
}
//Auth routes
router.post("/register", upload.single("profileImage"), registerUser);
router.post("/login", loginUser);
router.post("/refresh", refreshAuthToken);
router.put("/user/:userId", upload.single("profileImage"), verifyJWT, editUser);
router.get("/users/superadmin", checkSuperadminExists);
router.get("/users", verifyJWT, isAllowed(["admin", "superadmin"]), getAllUsers);
router.delete("/user/:userId", verifyJWT, verifyOwnership(User, "userId"), deleteUser);
initRoutes() {
this.router.post(
"/register",
upload.single("profileImage"),
this.authController.registerUser
);
this.router.post("/login", this.authController.loginUser);
this.router.post("/refresh", this.authController.refreshAuthToken);
//Recovery routes
router.post("/recovery/request", requestRecovery);
router.post("/recovery/validate", validateRecovery);
router.post("/recovery/reset/", resetPassword);
this.router.put(
"/user/:userId",
upload.single("profileImage"),
verifyJWT,
this.authController.editUser
);
export default router;
this.router.get("/users/superadmin", this.authController.checkSuperadminExists);
this.router.get(
"/users",
verifyJWT,
isAllowed(["admin", "superadmin"]),
this.authController.getAllUsers
);
this.router.delete(
"/user/:userId",
verifyJWT,
verifyOwnership(User, "userId"),
this.authController.deleteUser
);
// Recovery routes
this.router.post("/recovery/request", this.authController.requestRecovery);
this.router.post("/recovery/validate", this.authController.validateRecovery);
this.router.post("/recovery/reset/", this.authController.resetPassword);
}
getRouter() {
return this.router;
}
}
export default AuthRoutes;
+36 -16
View File
@@ -1,26 +1,46 @@
import { Router } from "express";
import {
createCheck,
getChecks,
deleteChecks,
getTeamChecks,
deleteChecksByTeamId,
updateChecksTTL,
} from "../controllers/checkController.js";
import { verifyOwnership } from "../middleware/verifyOwnership.js";
import { isAllowed } from "../middleware/isAllowed.js";
import Monitor from "../db/models/Monitor.js";
const router = Router();
class CheckRoutes {
constructor(checkController) {
this.router = Router();
this.checkController = checkController;
this.initRoutes();
}
router.get("/:monitorId", getChecks);
router.post("/:monitorId", verifyOwnership(Monitor, "monitorId"), createCheck);
router.delete("/:monitorId", verifyOwnership(Monitor, "monitorId"), deleteChecks);
initRoutes() {
this.router.get("/:monitorId", this.checkController.getChecks);
this.router.post(
"/:monitorId",
verifyOwnership(Monitor, "monitorId"),
this.checkController.createCheck
);
this.router.delete(
"/:monitorId",
verifyOwnership(Monitor, "monitorId"),
this.checkController.deleteChecks
);
router.get("/team/:teamId", getTeamChecks);
this.router.get("/team/:teamId", this.checkController.getTeamChecks);
router.delete("/team/:teamId", isAllowed(["admin", "superadmin"]), deleteChecksByTeamId);
this.router.delete(
"/team/:teamId",
isAllowed(["admin", "superadmin"]),
this.checkController.deleteChecksByTeamId
);
router.put("/team/ttl", isAllowed(["admin", "superadmin"]), updateChecksTTL);
this.router.put(
"/team/ttl",
isAllowed(["admin", "superadmin"]),
this.checkController.updateChecksTTL
);
}
export default router;
getRouter() {
return this.router;
}
}
export default CheckRoutes;
+21 -8
View File
@@ -1,14 +1,27 @@
import { Router } from "express";
import { verifyJWT } from "../middleware/verifyJWT.js";
import { isAllowed } from "../middleware/isAllowed.js";
import {
issueInvitation,
inviteVerifyController,
} from "../controllers/inviteController.js";
const router = Router();
class InviteRoutes {
constructor(inviteController) {
this.router = Router();
this.inviteController = inviteController;
this.initRoutes();
}
router.post("/", isAllowed(["admin", "superadmin"]), verifyJWT, issueInvitation);
router.post("/verify", inviteVerifyController);
initRoutes() {
this.router.post(
"/",
isAllowed(["admin", "superadmin"]),
verifyJWT,
this.inviteController.issueInvitation
);
this.router.post("/verify", this.inviteController.inviteVerifyController);
}
export default router;
getRouter() {
return this.router;
}
}
export default InviteRoutes;
+26 -20
View File
@@ -1,31 +1,37 @@
import { Router } from "express";
import {
createMaintenanceWindows,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindow,
editMaintenanceWindow,
} from "../controllers/maintenanceWindowController.js";
import { verifyOwnership } from "../middleware/verifyOwnership.js";
import Monitor from "../db/models/Monitor.js";
const router = Router();
class MaintenanceWindowRoutes {
constructor(maintenanceWindowController) {
this.router = Router();
this.maintenanceWindowController = maintenanceWindowController;
this.initRoutes();
}
initRoutes() {
this.router.post("/", this.maintenanceWindowController.createMaintenanceWindows);
router.post("/", createMaintenanceWindows);
this.router.get(
"/monitor/:monitorId",
verifyOwnership(Monitor, "monitorId"),
this.maintenanceWindowController.getMaintenanceWindowsByMonitorId
);
router.get(
"/monitor/:monitorId",
verifyOwnership(Monitor, "monitorId"),
getMaintenanceWindowsByMonitorId
);
this.router.get(
"/team/",
this.maintenanceWindowController.getMaintenanceWindowsByTeamId
);
router.get("/team/", getMaintenanceWindowsByTeamId);
this.router.get("/:id", this.maintenanceWindowController.getMaintenanceWindowById);
router.get("/:id", getMaintenanceWindowById);
this.router.put("/:id", this.maintenanceWindowController.editMaintenanceWindow);
router.put("/:id", editMaintenanceWindow);
this.router.delete("/:id", this.maintenanceWindowController.deleteMaintenanceWindow);
}
router.delete("/:id", deleteMaintenanceWindow);
getRouter() {
return this.router;
}
}
export default router;
export default MaintenanceWindowRoutes;
+66 -38
View File
@@ -1,51 +1,79 @@
import { Router } from "express";
import {
getAllMonitors,
getAllMonitorsWithUptimeStats,
getMonitorStatsById,
getMonitorCertificate,
getMonitorById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
checkEndpointResolution,
deleteMonitor,
deleteAllMonitors,
editMonitor,
pauseMonitor,
addDemoMonitors,
} from "../controllers/monitorController.js";
import { isAllowed } from "../middleware/isAllowed.js";
import { fetchMonitorCertificate } from "../controllers/controllerUtils.js";
const router = Router();
class MonitorRoutes {
constructor(monitorController) {
this.router = Router();
this.monitorController = monitorController;
this.initRoutes();
}
router.get("/", getAllMonitors);
router.get("/uptime", getAllMonitorsWithUptimeStats);
router.get("/stats/:monitorId", getMonitorStatsById);
router.get("/certificate/:monitorId", (req, res, next) => {
getMonitorCertificate(req, res, next, fetchMonitorCertificate);
});
router.get("/:monitorId", getMonitorById);
router.get("/team/summary/:teamId", getMonitorsAndSummaryByTeamId);
router.get("/team/:teamId", getMonitorsByTeamId);
initRoutes() {
this.router.get("/", this.monitorController.getAllMonitors);
this.router.get("/uptime", this.monitorController.getAllMonitorsWithUptimeStats);
this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById);
this.router.get("/certificate/:monitorId", (req, res, next) => {
this.monitorController.getMonitorCertificate(
req,
res,
next,
fetchMonitorCertificate
);
});
this.router.get("/:monitorId", this.monitorController.getMonitorById);
this.router.get(
"/team/summary/:teamId",
this.monitorController.getMonitorsAndSummaryByTeamId
);
this.router.get("/team/:teamId", this.monitorController.getMonitorsByTeamId);
router.get(
"/resolution/url",
isAllowed(["admin", "superadmin"]),
checkEndpointResolution
);
this.router.get(
"/resolution/url",
isAllowed(["admin", "superadmin"]),
this.monitorController.checkEndpointResolution
);
router.delete("/:monitorId", isAllowed(["admin", "superadmin"]), deleteMonitor);
this.router.delete(
"/:monitorId",
isAllowed(["admin", "superadmin"]),
this.monitorController.deleteMonitor
);
router.post("/", isAllowed(["admin", "superadmin"]), createMonitor);
this.router.post(
"/",
isAllowed(["admin", "superadmin"]),
this.monitorController.createMonitor
);
router.put("/:monitorId", isAllowed(["admin", "superadmin"]), editMonitor);
this.router.put(
"/:monitorId",
isAllowed(["admin", "superadmin"]),
this.monitorController.editMonitor
);
router.delete("/", isAllowed(["superadmin"]), deleteAllMonitors);
this.router.delete(
"/",
isAllowed(["superadmin"]),
this.monitorController.deleteAllMonitors
);
router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), pauseMonitor);
this.router.post(
"/pause/:monitorId",
isAllowed(["admin", "superadmin"]),
this.monitorController.pauseMonitor
);
router.post("/demo", isAllowed(["admin", "superadmin"]), addDemoMonitors);
this.router.post(
"/demo",
isAllowed(["admin", "superadmin"]),
this.monitorController.addDemoMonitors
);
}
export default router;
getRouter() {
return this.router;
}
}
export default MonitorRoutes;
+17 -17
View File
@@ -1,21 +1,21 @@
import { Router } from "express";
import {
getMetrics,
getJobs,
addJob,
obliterateQueue,
} from "../controllers/queueController.js";
const router = Router();
router.get("/metrics", getMetrics);
class QueueRoutes {
constructor(queueController) {
this.router = Router();
this.queueController = queueController;
this.initRoutes();
}
initRoutes() {
this.router.get("/metrics", this.queueController.getMetrics);
this.router.get("/jobs", this.queueController.getJobs);
this.router.post("/jobs", this.queueController.addJob);
this.router.post("/obliterate", this.queueController.obliterateQueue);
}
// Get Jobs
router.get("/jobs", getJobs);
getRouter() {
return this.router;
}
}
// Add Job
router.post("/jobs", addJob);
// Obliterate Queue
router.post("/obliterate", obliterateQueue);
export default router;
export default QueueRoutes;
+20 -5
View File
@@ -1,10 +1,25 @@
import { Router } from "express";
import { getAppSettings, updateAppSettings } from "../controllers/settingsController.js";
import { isAllowed } from "../middleware/isAllowed.js";
const router = Router();
class SettingsRoutes {
constructor(settingsController) {
this.router = Router();
this.settingsController = settingsController;
this.initRoutes();
}
router.get("/", getAppSettings);
router.put("/", isAllowed(["superadmin"]), updateAppSettings);
initRoutes() {
this.router.get("/", this.settingsController.getAppSettings);
this.router.put(
"/",
isAllowed(["superadmin"]),
this.settingsController.updateAppSettings
);
}
export default router;
getRouter() {
return this.router;
}
}
export default SettingsRoutes;
+21 -10
View File
@@ -1,11 +1,22 @@
import express from "express";
import { verifyJWT } from "../middleware/verifyJWT.js";
import {
createStatusPage,
getStatusPageByUrl,
} from "../controllers/statusPageController.js";
const router = express.Router();
import { Router } from "express";
router.get("/:url", getStatusPageByUrl);
router.post("/:url", verifyJWT, createStatusPage);
export default router;
import { verifyJWT } from "../middleware/verifyJWT.js";
class StatusPageRoutes {
constructor(statusPageController) {
this.router = Router();
this.statusPageController = statusPageController;
this.initRoutes();
}
initRoutes() {
this.router.get("/:url", this.statusPageController.getStatusPageByUrl);
this.router.post("/:url", verifyJWT, this.statusPageController.createStatusPage);
}
getRouter() {
return this.router;
}
}
export default StatusPageRoutes;
+1
View File
@@ -10,6 +10,7 @@ const SERVICE_NAME = "EmailService";
* Represents an email service that can load templates, build, and send emails.
*/
class EmailService {
static SERVICE_NAME = SERVICE_NAME;
/**
* Constructs an instance of the EmailService, initializing template loaders and the email transporter.
* @param {Object} settingsService - The settings service to get email configuration.
+3 -1
View File
@@ -1,7 +1,8 @@
const QUEUE_NAME = "monitors";
const JOBS_PER_WORKER = 5;
import { errorMessages, successMessages } from "../utils/messages.js";
const SERVICE_NAME = "JobQueue";
import { errorMessages, successMessages } from "../utils/messages.js";
/**
* JobQueue
*
@@ -10,6 +11,7 @@ const SERVICE_NAME = "JobQueue";
* It scales the number of workers based on the number of jobs/worker
*/
class JobQueue {
static SERVICE_NAME = SERVICE_NAME;
/**
* @class JobQueue
* @classdesc Manages job queue and workers.
+4 -1
View File
@@ -1,4 +1,6 @@
import { errorMessages, successMessages } from "../utils/messages.js";
const SERVICE_NAME = "NetworkService";
/**
* Constructs a new NetworkService instance.
*
@@ -8,13 +10,14 @@ import { errorMessages, successMessages } from "../utils/messages.js";
* @param {Object} http - The HTTP utility for network operations.
*/
class NetworkService {
static SERVICE_NAME = SERVICE_NAME;
constructor(axios, ping, logger, http, Docker) {
this.TYPE_PING = "ping";
this.TYPE_HTTP = "http";
this.TYPE_PAGESPEED = "pagespeed";
this.TYPE_HARDWARE = "hardware";
this.TYPE_DOCKER = "docker";
this.SERVICE_NAME = "NetworkService";
this.SERVICE_NAME = SERVICE_NAME;
this.NETWORK_ERROR = 5000;
this.PING_ERROR = 5001;
this.axios = axios;
+4 -1
View File
@@ -1,4 +1,7 @@
const SERVICE_NAME = "NotificationService";
class NotificationService {
static SERVICE_NAME = SERVICE_NAME;
/**
* Creates an instance of NotificationService.
*
@@ -7,7 +10,7 @@ class NotificationService {
* @param {Object} logger - The logger instance for logging activities.
*/
constructor(emailService, db, logger) {
this.SERVICE_NAME = "NotificationService";
this.SERVICE_NAME = SERVICE_NAME;
this.emailService = emailService;
this.db = db;
this.logger = logger;
+35
View File
@@ -0,0 +1,35 @@
const SERVICE_NAME = "ServiceRegistry";
import logger from "../utils/logger.js";
class ServiceRegistry {
static SERVICE_NAME = SERVICE_NAME;
constructor() {
this.services = {};
}
register(name, service) {
logger.info({
message: `Registering service ${name}`,
service: SERVICE_NAME,
method: "register",
});
this.services[name] = service;
}
get(name) {
if (!this.services[name]) {
logger.error({
message: `Service ${name} is not registered`,
service: SERVICE_NAME,
method: "get",
});
throw new Error(`Service ${name} is not registered`);
}
return this.services[name];
}
listServices() {
return Object.keys(this.services);
}
}
export default new ServiceRegistry();
+1
View File
@@ -25,6 +25,7 @@ const envConfig = {
* from the database if they are not set in the environment.
*/
class SettingsService {
static SERVICE_NAME = SERVICE_NAME;
/**
* Constructs a new SettingsService
* @constructor
+4 -1
View File
@@ -1,4 +1,7 @@
const SERVICE_NAME = "StatusService";
class StatusService {
static SERVICE_NAME = SERVICE_NAME;
/**
* Creates an instance of StatusService.
*
@@ -8,7 +11,7 @@ class StatusService {
constructor(db, logger) {
this.db = db;
this.logger = logger;
this.SERVICE_NAME = "StatusService";
this.SERVICE_NAME = SERVICE_NAME;
}
getStatusString = (status) => {