move to src

This commit is contained in:
Alex Holliday committed 2025-07-25 15:33:48 -07:00
1 parent acc910cbc0
commit 1c47c8ce2c
127 files changed
+71 -19

No files matched your search

+77
View File
@@ -0,0 +1,77 @@
import { createAnnouncementValidation } from "../validation/joi.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "announcementController";
/**
* Controller for managing announcements in the system.
* This class handles the creation of new announcements.
*
* @class AnnouncementController
*/
class AnnouncementController extends BaseController {
static SERVICE_NAME = SERVICE_NAME;
constructor(commonDependencies) {
super(commonDependencies);
this.createAnnouncement = this.createAnnouncement.bind(this);
this.getAnnouncement = this.getAnnouncement.bind(this);
}
get serviceName() {
return AnnouncementController.SERVICE_NAME;
}
/**
* Handles the creation of a new announcement.
*
* @async
* @param {Object} req - The request object, containing the announcement data in the body.
* @param {Object} res - The response object used to send the result back to the client.
* @param {Function} next - The next middleware function in the stack for error handling.
*
* @returns {Promise<void>} A promise that resolves once the response is sent.
*/
createAnnouncement = asyncHandler(
async (req, res, next) => {
await createAnnouncementValidation.validateAsync(req.body);
const { title, message } = req.body;
const announcementData = {
title: title.trim(),
message: message.trim(),
userId: req.user._id,
};
const newAnnouncement = await this.db.createAnnouncement(announcementData);
return res.success({
msg: this.stringService.createAnnouncement,
data: newAnnouncement,
});
},
SERVICE_NAME,
"createAnnouncement"
);
/**
* Handles retrieving announcements with pagination.
*
* @async
* @param {Object} res - The response object used to send the result back to the client.
* - `data`: The list of announcements to be sent back to the client.
* - `msg`: A message about the success of the request.
* @param {Function} next - The next middleware function in the stack for error handling.
*/
getAnnouncement = asyncHandler(
async (req, res, next) => {
const allAnnouncements = await this.db.getAnnouncements();
return res.success({
msg: this.stringService.getAnnouncement,
data: allAnnouncements,
});
},
SERVICE_NAME,
"getAnnouncement"
);
}
export default AnnouncementController;
+461
View File
@@ -0,0 +1,461 @@
import BaseController from "./baseController.js";
import {
registrationBodyValidation,
loginValidation,
editUserBodyValidation,
recoveryValidation,
recoveryTokenBodyValidation,
newPasswordValidation,
getUserByIdParamValidation,
editUserByIdParamValidation,
editUserByIdBodyValidation,
editSuperadminUserByIdBodyValidation,
} from "../validation/joi.js";
const SERVICE_NAME = "authController";
/**
* Authentication Controller
*
* Handles all authentication-related HTTP requests including user registration,
* login, password recovery, and user management operations.
*
* @class AuthController
* @description Manages user authentication and authorization operations
*/
class AuthController extends BaseController {
static SERVICE_NAME = SERVICE_NAME;
/**
* Creates an instance of AuthController.
*
* @param {Object} commonDependencies - Common dependencies injected into the controller
* @param {Object} dependencies - The dependencies required by the controller
* @param {Object} dependencies.settingsService - Service for application settings
* @param {Object} dependencies.emailService - Service for email operations
* @param {Object} dependencies.jobQueue - Service for job queue operations
* @param {Object} dependencies.userService - User business logic service
*/
constructor(commonDependencies, { settingsService, emailService, jobQueue, userService }) {
super(commonDependencies);
this.settingsService = settingsService;
this.emailService = emailService;
this.jobQueue = jobQueue;
this.userService = userService;
}
get serviceName() {
return AuthController.SERVICE_NAME;
}
/**
* Registers a new user in the system.
*
* @async
* @function registerUser
* @param {Object} req - Express request object
* @param {Object} req.body - Request body containing user registration data
* @param {string} req.body.firstName - User's first name
* @param {string} req.body.lastName - User's last name
* @param {string} req.body.email - User's email address (will be converted to lowercase)
* @param {string} req.body.password - User's password
* @param {string} [req.body.inviteToken] - Invite token for registration (required if superadmin exists)
* @param {string} [req.body.teamId] - Team ID (auto-assigned if superadmin)
* @param {Array<string>} [req.body.role] - User roles (auto-assigned if superadmin)
* @param {Object} [req.file] - Profile image file uploaded via multer
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with user data and JWT token
* @throws {Error} 422 - Validation error if request body is invalid
* @throws {Error} 409 - Conflict if user already exists
* @example
* // Register first user (becomes superadmin)
* POST /auth/register
* {
* "firstName": "John",
* "lastName": "Doe",
* "email": "john@example.com",
* "password": "SecurePass123!"
* }
*
* // Register subsequent user (requires invite token)
* POST /auth/register
* {
* "firstName": "Jane",
* "lastName": "Smith",
* "email": "jane@example.com",
* "password": "SecurePass123!",
* "inviteToken": "abc123..."
* }
*/
registerUser = this.asyncHandler(
async (req, res) => {
if (req.body?.email) {
req.body.email = req.body.email?.toLowerCase();
}
await registrationBodyValidation.validateAsync(req.body);
const { user, token } = await this.userService.registerUser(req.body, req.file);
res.success({
msg: this.stringService.authCreateUser,
data: { user, token },
});
},
SERVICE_NAME,
"registerUser"
);
/**
* Authenticates a user and returns a JWT token.
*
* @async
* @function loginUser
* @param {Object} req - Express request object
* @param {Object} req.body - Request body containing login credentials
* @param {string} req.body.email - User's email address (will be converted to lowercase)
* @param {string} req.body.password - User's password
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with user data and JWT token
* @throws {Error} 422 - Validation error if request body is invalid
* @throws {Error} 401 - Unauthorized if credentials are incorrect
* @example
* POST /auth/login
* {
* "email": "john@example.com",
* "password": "SecurePass123!"
* }
*/
loginUser = this.asyncHandler(
async (req, res) => {
if (req.body?.email) {
req.body.email = req.body.email?.toLowerCase();
}
await loginValidation.validateAsync(req.body);
const { user, token } = await this.userService.loginUser(req.body.email, req.body.password);
return res.success({
msg: this.stringService.authLoginUser,
data: {
user,
token,
},
});
},
SERVICE_NAME,
"loginUser"
);
/**
* Updates the current user's profile information.
*
* @async
* @function editUser
* @param {Object} req - Express request object
* @param {Object} req.body - Request body containing user update data
* @param {string} [req.body.firstName] - Updated first name
* @param {string} [req.body.lastName] - Updated last name
* @param {string} [req.body.password] - Current password (required for password change)
* @param {string} [req.body.newPassword] - New password (required for password change)
* @param {boolean} [req.body.deleteProfileImage] - Flag to delete profile image
* @param {Object} [req.file] - New profile image file
* @param {Object} req.user - Current authenticated user (from JWT)
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with updated user data
* @throws {Error} 422 - Validation error if request body is invalid
* @throws {Error} 403 - Forbidden if current password is incorrect
* @example
* PUT /auth/user
* {
* "firstName": "John Updated",
* "lastName": "Doe Updated"
* }
*
* // Change password
* PUT /auth/user
* {
* "password": "OldPass123!",
* "newPassword": "NewPass123!"
* }
*/
editUser = this.asyncHandler(
async (req, res) => {
await editUserBodyValidation.validateAsync(req.body);
const updatedUser = await this.userService.editUser(req.body, req.file, req.user);
res.success({
msg: this.stringService.authUpdateUser,
data: updatedUser,
});
},
SERVICE_NAME,
"editUser"
);
/**
* Checks if a superadmin account exists in the system.
*
* @async
* @function checkSuperadminExists
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with boolean indicating superadmin existence
* @example
* GET /auth/users/superadmin
* // Response: { "data": true } or { "data": false }
*/
checkSuperadminExists = this.asyncHandler(
async (req, res) => {
const superAdminExists = await this.userService.checkSuperadminExists();
return res.success({
msg: this.stringService.authAdminExists,
data: superAdminExists,
});
},
SERVICE_NAME,
"checkSuperadminExists"
);
/**
* Initiates password recovery process by sending a recovery email.
*
* @async
* @function requestRecovery
* @param {Object} req - Express request object
* @param {Object} req.body - Request body containing email
* @param {string} req.body.email - Email address for password recovery
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with message ID
* @throws {Error} 422 - Validation error if email is invalid
* @throws {Error} 404 - Not found if user doesn't exist
* @example
* POST /auth/recovery/request
* {
* "email": "john@example.com"
* }
*/
requestRecovery = this.asyncHandler(
async (req, res) => {
await recoveryValidation.validateAsync(req.body);
const email = req?.body?.email;
const msgId = await this.userService.requestRecovery(email);
return res.success({
msg: this.stringService.authCreateRecoveryToken,
data: msgId,
});
},
SERVICE_NAME,
"requestRecovery"
);
/**
* Validates a password recovery token.
*
* @async
* @function validateRecovery
* @param {Object} req - Express request object
* @param {Object} req.body - Request body containing recovery token
* @param {string} req.body.recoveryToken - Recovery token to validate
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response if token is valid
* @throws {Error} 422 - Validation error if token format is invalid
* @throws {Error} 400 - Bad request if token is invalid or expired
* @example
* POST /auth/recovery/validate
* {
* "recoveryToken": "abc123..."
* }
*/
validateRecovery = this.asyncHandler(
async (req, res) => {
await recoveryTokenBodyValidation.validateAsync(req.body);
await this.userService.validateRecovery(req.body.recoveryToken);
return res.success({
msg: this.stringService.authVerifyRecoveryToken,
});
},
SERVICE_NAME,
"validateRecovery"
);
/**
* Resets user password using a valid recovery token.
*
* @async
* @function resetPassword
* @param {Object} req - Express request object
* @param {Object} req.body - Request body containing new password and recovery token
* @param {string} req.body.password - New password
* @param {string} req.body.recoveryToken - Valid recovery token
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with user data and JWT token
* @throws {Error} 422 - Validation error if password format is invalid
* @throws {Error} 400 - Bad request if token is invalid or expired
* @example
* POST /auth/recovery/reset
* {
* "password": "NewSecurePass123!",
* "recoveryToken": "abc123..."
* }
*/
resetPassword = this.asyncHandler(
async (req, res) => {
await newPasswordValidation.validateAsync(req.body);
const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken);
return res.success({
msg: this.stringService.authResetPassword,
data: { user, token },
});
},
SERVICE_NAME,
"resetPassword"
);
/**
* Deletes the current user's account and associated data.
*
* @async
* @function deleteUser
* @param {Object} req - Express request object
* @param {Object} req.user - Current authenticated user (from JWT)
* @param {string} req.user._id - User ID
* @param {string} req.user.email - User email
* @param {string} req.user.teamId - User's team ID
* @param {Array<string>} req.user.role - User roles
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response confirming user deletion
* @throws {Error} 400 - Bad request if user is demo user
* @throws {Error} 404 - Not found if user doesn't exist
* @example
* DELETE /auth/user
* // Requires JWT authentication
*/
deleteUser = this.asyncHandler(
async (req, res) => {
await this.userService.deleteUser(req.user);
return res.success({
msg: this.stringService.authDeleteUser,
});
},
SERVICE_NAME,
"deleteUser"
);
/**
* Retrieves all users in the system (admin/superadmin only).
*
* @async
* @function getAllUsers
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with array of users
* @throws {Error} 403 - Forbidden if user doesn't have admin/superadmin role
* @example
* GET /auth/users
* // Requires JWT authentication with admin/superadmin role
*/
getAllUsers = this.asyncHandler(
async (req, res) => {
const allUsers = await this.userService.getAllUsers();
return res.success({
msg: this.stringService.authGetAllUsers,
data: allUsers,
});
},
SERVICE_NAME,
"getAllUsers"
);
/**
* Retrieves a specific user by ID (superadmin only).
*
* @async
* @function getUserById
* @param {Object} req - Express request object
* @param {Object} req.params - URL parameters
* @param {string} req.params.userId - ID of the user to retrieve
* @param {Object} req.user - Current authenticated user (from JWT)
* @param {Array<string>} req.user.role - Current user's roles
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with user data
* @throws {Error} 422 - Validation error if userId is invalid
* @throws {Error} 403 - Forbidden if user doesn't have superadmin role
* @throws {Error} 404 - Not found if user doesn't exist
* @example
* GET /auth/users/507f1f77bcf86cd799439011
* // Requires JWT authentication with superadmin role
*/
getUserById = this.asyncHandler(
async (req, res) => {
await getUserByIdParamValidation.validateAsync(req.params);
const userId = req?.params?.userId;
const roles = req?.user?.role;
if (!userId) {
throw new Error("No user ID in request");
}
if (!roles || roles.length === 0) {
throw new Error("No roles in request");
}
const user = await this.userService.getUserById(roles, userId);
return res.success({ msg: "ok", data: user });
},
SERVICE_NAME,
"getUserById"
);
/**
* Updates a specific user by ID (superadmin only).
*
* @async
* @function editUserById
* @param {Object} req - Express request object
* @param {Object} req.params - URL parameters
* @param {string} req.params.userId - ID of the user to update
* @param {Object} req.body - Request body containing user update data
* @param {string} [req.body.firstName] - Updated first name
* @param {string} [req.body.lastName] - Updated last name
* @param {Array<string>} [req.body.role] - Updated user roles
* @param {Object} req.user - Current authenticated user (from JWT)
* @param {string} req.user._id - Current user's ID
* @param {Array<string>} req.user.role - Current user's roles
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response confirming user update
* @throws {Error} 422 - Validation error if parameters or body are invalid
* @throws {Error} 403 - Forbidden if user doesn't have superadmin role
* @throws {Error} 404 - Not found if user doesn't exist
* @example
* PUT /auth/users/507f1f77bcf86cd799439011
* {
* "firstName": "Updated Name",
* "role": ["admin"]
* }
* // Requires JWT authentication with superadmin role
*/
editUserById = this.asyncHandler(
async (req, res) => {
const roles = req?.user?.role;
if (!roles.includes("superadmin")) {
throw createError("Unauthorized", 403);
}
const userId = req.params.userId;
const user = { ...req.body };
await editUserByIdParamValidation.validateAsync(req.params);
// If this is superadmin self edit, allow "superadmin" role
if (userId === req.user._id) {
await editSuperadminUserByIdBodyValidation.validateAsync(req.body);
} else {
await editUserByIdBodyValidation.validateAsync(req.body);
}
await this.userService.editUserById(userId, user);
return res.success({ msg: "ok" });
},
SERVICE_NAME,
"editUserById"
);
}
export default AuthController;
+83
View File
@@ -0,0 +1,83 @@
import { AppError } from "../service/infrastructure/errorService.js";
export const createCommonDependencies = (db, errorService, logger, stringService) => {
return {
db,
errorService,
logger,
stringService,
};
};
class BaseController {
constructor({ db, logger, errorService, ...additionalDependencies }) {
this.db = db;
this.logger = logger;
this.errorService = errorService;
Object.assign(this, additionalDependencies);
this.asyncHandler = (fn, serviceName, methodName) => {
return async (req, res, next) => {
try {
await fn(req, res, next);
} catch (error) {
// Handle validation errors
if (error.isJoi) {
const validationError = this.errorService.createValidationError(error.message, error.details, serviceName, methodName);
return next(validationError);
}
if (error.name === "ValidationError") {
const validationError = this.errorService.createValidationError("Database validation failed", error.errors, serviceName, methodName);
return next(validationError);
}
if (error.name === "CastError") {
const notFoundError = this.errorService.createNotFoundError(
"Invalid resource identifier",
{ field: error.path, value: error.value },
serviceName,
methodName
);
return next(notFoundError);
}
if (error.code === "11000") {
const conflictError = this.errorService.createConflictError("Resource already exists", {
originalError: error.message,
code: error.code,
});
conflictError.service = serviceName;
conflictError.method = methodName;
return next(conflictError);
}
if (error instanceof AppError) {
error.service = error.service || serviceName;
error.method = error.method || methodName;
return next(error);
}
if (error.status) {
const appError = this.errorService.createError(error.message, error.status, serviceName, methodName, {
originalError: error.message,
stack: error.stack,
});
return next(appError);
}
// For unknown errors, create a server error
const appError = this.errorService.createServerError(error.message || "An unexpected error occurred", {
originalError: error.message,
stack: error.stack,
});
appError.service = serviceName;
appError.method = methodName;
appError.stack = error.stack; // Preserve original stack
return next(appError);
}
};
};
}
}
export default BaseController;
+360
View File
@@ -0,0 +1,360 @@
import BaseController from "./baseController.js";
import {
getChecksParamValidation,
getChecksQueryValidation,
getTeamChecksQueryValidation,
deleteChecksParamValidation,
deleteChecksByTeamIdParamValidation,
updateChecksTTLBodyValidation,
ackCheckBodyValidation,
ackAllChecksParamValidation,
ackAllChecksBodyValidation,
} from "../validation/joi.js";
const SERVICE_NAME = "checkController";
/**
* Check Controller
*
* Handles all check-related HTTP requests including retrieving checks,
* acknowledging checks, deleting checks, and managing check TTL settings.
*
* @class CheckController
* @description Manages check operations and monitoring data
*/
class CheckController extends BaseController {
static SERVICE_NAME = SERVICE_NAME;
/**
* Creates an instance of CheckController.
*
* @param {Object} commonDependencies - Common dependencies injected into the controller
* @param {Object} dependencies - The dependencies required by the controller
* @param {Object} dependencies.settingsService - Service for application settings
* @param {Object} dependencies.checkService - Check business logic service
*/
constructor(commonDependencies, { settingsService, checkService }) {
super(commonDependencies);
this.settingsService = settingsService;
this.checkService = checkService;
}
get serviceName() {
return CheckController.SERVICE_NAME;
}
/**
* Retrieves checks for a specific monitor with filtering and pagination.
*
* @async
* @function getChecksByMonitor
* @param {Object} req - Express request object
* @param {Object} req.params - URL parameters
* @param {string} req.params.monitorId - ID of the monitor to get checks for
* @param {Object} req.query - Query parameters for filtering and pagination
* @param {string} [req.query.type] - Type of checks to filter by
* @param {string} [req.query.sortOrder] - Sort order (asc/desc)
* @param {string} [req.query.dateRange] - Date range filter
* @param {string} [req.query.filter] - General filter string
* @param {boolean} [req.query.ack] - Filter by acknowledgment status
* @param {number} [req.query.page] - Page number for pagination
* @param {number} [req.query.rowsPerPage] - Number of rows per page
* @param {string} [req.query.status] - Filter by check status
* @param {Object} req.user - Current authenticated user (from JWT)
* @param {string} req.user.teamId - User's team ID
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with checks data
* @throws {Error} 422 - Validation error if parameters are invalid
* @throws {Error} 404 - Not found if monitor doesn't exist
* @throws {Error} 403 - Forbidden if user doesn't have access to monitor
* @example
* GET /checks/monitor/507f1f77bcf86cd799439011?page=1&rowsPerPage=10&status=down
* // Requires JWT authentication
*/
getChecksByMonitor = this.asyncHandler(
async (req, res) => {
await getChecksParamValidation.validateAsync(req.params);
await getChecksQueryValidation.validateAsync(req.query);
const result = await this.checkService.getChecksByMonitor({
monitorId: req?.params?.monitorId,
query: req?.query,
teamId: req?.user?.teamId,
});
return res.success({
msg: this.stringService.checkGet,
data: result,
});
},
SERVICE_NAME,
"getChecksByMonitor"
);
/**
* Retrieves all checks for the current user's team with filtering and pagination.
*
* @async
* @function getChecksByTeam
* @param {Object} req - Express request object
* @param {Object} req.query - Query parameters for filtering and pagination
* @param {string} [req.query.sortOrder] - Sort order (asc/desc)
* @param {string} [req.query.dateRange] - Date range filter
* @param {string} [req.query.filter] - General filter string
* @param {boolean} [req.query.ack] - Filter by acknowledgment status
* @param {number} [req.query.page] - Page number for pagination
* @param {number} [req.query.rowsPerPage] - Number of rows per page
* @param {Object} req.user - Current authenticated user (from JWT)
* @param {string} req.user.teamId - User's team ID
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with checks data
* @throws {Error} 422 - Validation error if query parameters are invalid
* @example
* GET /checks/team?page=1&rowsPerPage=20&status=down&ack=false
* // Requires JWT authentication
*/
getChecksByTeam = this.asyncHandler(
async (req, res) => {
await getTeamChecksQueryValidation.validateAsync(req.query);
const checkData = await this.checkService.getChecksByTeam({
teamId: req?.user?.teamId,
query: req?.query,
});
return res.success({
msg: this.stringService.checkGet,
data: checkData,
});
},
SERVICE_NAME,
"getChecksByTeam"
);
/**
* Retrieves a summary of checks for the current user's team.
*
* @async
* @function getChecksSummaryByTeamId
* @param {Object} req - Express request object
* @param {Object} req.user - Current authenticated user (from JWT)
* @param {string} req.user.teamId - User's team ID
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with checks summary
* @example
* GET /checks/summary
* // Requires JWT authentication
* // Response includes counts by status, time ranges, etc.
*/
getChecksSummaryByTeamId = this.asyncHandler(
async (req, res) => {
const summary = await this.checkService.getChecksSummaryByTeamId({ teamId: req?.user?.teamId });
return res.success({
msg: this.stringService.checkGetSummary,
data: summary,
});
},
SERVICE_NAME,
"getChecksSummaryByTeamId"
);
/**
* Acknowledges a specific check by ID.
*
* @async
* @function ackCheck
* @param {Object} req - Express request object
* @param {Object} req.params - URL parameters
* @param {string} req.params.checkId - ID of the check to acknowledge
* @param {Object} req.body - Request body containing acknowledgment data
* @param {boolean} req.body.ack - Acknowledgment status (true/false)
* @param {Object} req.user - Current authenticated user (from JWT)
* @param {string} req.user.teamId - User's team ID
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with updated check data
* @throws {Error} 422 - Validation error if request body is invalid
* @throws {Error} 404 - Not found if check doesn't exist
* @throws {Error} 403 - Forbidden if user doesn't have access to check
* @example
* PUT /checks/507f1f77bcf86cd799439011/ack
* {
* "ack": true
* }
* // Requires JWT authentication
*/
ackCheck = this.asyncHandler(
async (req, res) => {
await ackCheckBodyValidation.validateAsync(req.body);
const updatedCheck = await this.checkService.ackCheck({
checkId: req?.params?.checkId,
teamId: req?.user?.teamId,
ack: req?.body?.ack,
});
return res.success({
msg: this.stringService.checkUpdateStatus,
data: updatedCheck,
});
},
SERVICE_NAME,
"ackCheck"
);
/**
* Acknowledges all checks for a specific monitor or path.
*
* @async
* @function ackAllChecks
* @param {Object} req - Express request object
* @param {Object} req.params - URL parameters
* @param {string} req.params.monitorId - ID of the monitor
* @param {string} req.params.path - Path for acknowledgment (e.g., "monitor")
* @param {Object} req.body - Request body containing acknowledgment data
* @param {boolean} req.body.ack - Acknowledgment status (true/false)
* @param {Object} req.user - Current authenticated user (from JWT)
* @param {string} req.user.teamId - User's team ID
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with updated checks data
* @throws {Error} 422 - Validation error if parameters or body are invalid
* @throws {Error} 404 - Not found if monitor doesn't exist
* @throws {Error} 403 - Forbidden if user doesn't have access to monitor
* @example
* PUT /checks/monitor/507f1f77bcf86cd799439011/ack
* {
* "ack": true
* }
* // Requires JWT authentication
*/
ackAllChecks = this.asyncHandler(
async (req, res) => {
await ackAllChecksParamValidation.validateAsync(req.params);
await ackAllChecksBodyValidation.validateAsync(req.body);
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("No team ID in request");
}
const updatedChecks = await this.checkService.ackAllChecks({
monitorId: req?.params?.monitorId,
path: req?.params?.path,
teamId: req?.user?.teamId,
ack: req?.body?.ack,
});
return res.success({
msg: this.stringService.checkUpdateStatus,
data: updatedChecks,
});
},
SERVICE_NAME,
"ackAllChecks"
);
/**
* Deletes all checks for a specific monitor.
*
* @async
* @function deleteChecks
* @param {Object} req - Express request object
* @param {Object} req.params - URL parameters
* @param {string} req.params.monitorId - ID of the monitor whose checks to delete
* @param {Object} req.user - Current authenticated user (from JWT)
* @param {string} req.user.teamId - User's team ID
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with deletion count
* @throws {Error} 422 - Validation error if monitorId is invalid
* @throws {Error} 404 - Not found if monitor doesn't exist
* @throws {Error} 403 - Forbidden if user doesn't have access to monitor
* @example
* DELETE /checks/monitor/507f1f77bcf86cd799439011
* // Requires JWT authentication
* // Response: { "data": { "deletedCount": 150 } }
*/
deleteChecks = this.asyncHandler(
async (req, res) => {
await deleteChecksParamValidation.validateAsync(req.params);
const deletedCount = await this.checkService.deleteChecks({
monitorId: req.params.monitorId,
teamId: req?.user?.teamId,
});
return res.success({
msg: this.stringService.checkDelete,
data: { deletedCount },
});
},
SERVICE_NAME,
"deleteChecks"
);
/**
* Deletes all checks for the current user's team.
*
* @async
* @function deleteChecksByTeamId
* @param {Object} req - Express request object
* @param {Object} req.user - Current authenticated user (from JWT)
* @param {string} req.user.teamId - User's team ID
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with deletion count
* @throws {Error} 422 - Validation error if parameters are invalid
* @example
* DELETE /checks/team
* // Requires JWT authentication
* // Response: { "data": { "deletedCount": 1250 } }
*/
deleteChecksByTeamId = this.asyncHandler(
async (req, res) => {
await deleteChecksByTeamIdParamValidation.validateAsync(req.params);
const deletedCount = await this.checkService.deleteChecksByTeamId({ teamId: req?.user?.teamId });
return res.success({
msg: this.stringService.checkDelete,
data: { deletedCount },
});
},
SERVICE_NAME,
"deleteChecksByTeamId"
);
/**
* Updates the TTL (Time To Live) setting for checks in the current user's team.
*
* @async
* @function updateChecksTTL
* @param {Object} req - Express request object
* @param {Object} req.body - Request body containing TTL data
* @param {number} req.body.ttl - TTL value in days
* @param {Object} req.user - Current authenticated user (from JWT)
* @param {string} req.user.teamId - User's team ID
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response confirming TTL update
* @throws {Error} 422 - Validation error if TTL value is invalid
* @example
* PUT /checks/ttl
* {
* "ttl": 30
* }
* // Requires JWT authentication
* // Sets check TTL to 30 days
*/
updateChecksTTL = this.asyncHandler(
async (req, res) => {
await updateChecksTTLBodyValidation.validateAsync(req.body);
await this.checkService.updateChecksTTL({
teamId: req?.user?.teamId,
ttl: req?.body?.ttl,
});
return res.success({
msg: this.stringService.checkUpdateTTL,
});
},
SERVICE_NAME,
"updateChecksTtl"
);
}
export default CheckController;
+12
View File
@@ -0,0 +1,12 @@
const fetchMonitorCertificate = async (sslChecker, monitor) => {
const monitorUrl = new URL(monitor.url);
const hostname = monitorUrl.hostname;
const cert = await sslChecker(hostname);
// Throw an error if no cert or if cert.validTo is not present
if (cert?.validTo === null || cert?.validTo === undefined) {
throw new Error("Certificate not found");
}
return cert;
};
export { fetchMonitorCertificate };
+61
View File
@@ -0,0 +1,61 @@
const SERVICE_NAME = "diagnosticController";
import BaseController from "./baseController.js";
/**
* Diagnostic Controller
*
* Handles system diagnostic and monitoring requests including system statistics,
* performance metrics, and health checks.
*
* @class DiagnosticController
* @description Manages system diagnostics and performance monitoring
*/
class DiagnosticController extends BaseController {
static SERVICE_NAME = SERVICE_NAME;
/**
* Creates an instance of DiagnosticController.
* @param {Object} commonDependencies - Common dependencies injected into the controller
* @param {Object} dependencies - The dependencies required by the controller
* @param {Object} dependencies.diagnosticService - Service for system diagnostics and monitoring
*/
constructor(commonDependencies, { diagnosticService }) {
super(commonDependencies);
this.diagnosticService = diagnosticService;
}
get serviceName() {
return DiagnosticController.SERVICE_NAME;
}
/**
* Retrieves comprehensive system statistics and performance metrics.
*
* @async
* @function getSystemStats
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<Object>} Success response with system diagnostics data
* @description Returns system performance metrics, memory usage, CPU statistics,
* and other diagnostic information useful for monitoring system health.
* @example
* GET /diagnostics/stats
* // Response includes:
* // - Memory usage (heap, external, arrayBuffers)
* // - CPU usage statistics
* // - System uptime
* // - Performance metrics
* // - Database connection status
* // - Active processes/connections
*/
getSystemStats = this.asyncHandler(
async (req, res) => {
const diagnostics = await this.diagnosticService.getSystemStats();
return res.success({
msg: "OK",
data: diagnostics,
});
},
SERVICE_NAME,
"getSystemStats"
);
}
export default DiagnosticController;
+101
View File
@@ -0,0 +1,101 @@
import { inviteBodyValidation, inviteVerificationBodyValidation } from "../validation/joi.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "inviteController";
/**
* Controller for handling user invitation operations
* Manages invite token generation, email sending, and token verification
*/
class InviteController extends BaseController {
static SERVICE_NAME = SERVICE_NAME;
/**
* Creates a new InviteController instance
* @param {Object} commonDependencies - Common dependencies injected into the controller
* @param {Object} dependencies.inviteService - Service for invite-related operations
*/
constructor(commonDependencies, { inviteService }) {
super(commonDependencies);
this.inviteService = inviteService;
}
get serviceName() {
return InviteController.SERVICE_NAME;
}
/**
* Generates an invite token for a user invitation
* @param {Object} req - Express request object
* @param {Object} req.body - Request body containing invite details
* @param {Object} req.user - Authenticated user object
* @param {string} req.user.teamId - Team ID of the authenticated user
* @param {Object} res - Express response object
* @returns {Promise<Object>} Response with invite token data
*/
getInviteToken = this.asyncHandler(
async (req, res) => {
const invite = req.body;
const teamId = req?.user?.teamId;
invite.teamId = teamId;
await inviteBodyValidation.validateAsync(invite);
const inviteToken = await this.inviteService.getInviteToken({ invite, teamId });
return res.success({
msg: this.stringService.inviteIssued,
data: inviteToken,
});
},
SERVICE_NAME,
"getInviteToken"
);
/**
* Sends an invitation email to a user
* @param {Object} req - Express request object
* @param {Object} req.body - Request body containing invite details
* @param {Object} req.user - Authenticated user object
* @param {string} req.user.teamId - Team ID of the authenticated user
* @param {string} req.user.firstName - First name of the authenticated user
* @param {Object} res - Express response object
* @returns {Promise<Object>} Response with invite token data
*/
sendInviteEmail = this.asyncHandler(
async (req, res) => {
const inviteRequest = req.body;
inviteRequest.teamId = req?.user?.teamId;
await inviteBodyValidation.validateAsync(inviteRequest);
const inviteToken = await this.inviteService.sendInviteEmail({
inviteRequest,
firstName: req?.user?.firstName,
});
return res.success({
msg: this.stringService.inviteIssued,
data: inviteToken,
});
},
SERVICE_NAME,
"sendInviteEmail"
);
/**
* Verifies an invite token and returns invite details
* @param {Object} req - Express request object
* @param {Object} req.body - Request body containing the invite token
* @param {string} req.body.token - The invite token to verify
* @param {Object} res - Express response object
* @returns {Promise<Object>} Response with verified invite data
*/
verifyInviteToken = this.asyncHandler(
async (req, res) => {
await inviteVerificationBodyValidation.validateAsync(req.body);
const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token });
return res.success({
msg: this.stringService.inviteVerified,
data: invite,
});
},
SERVICE_NAME,
"verifyInviteToken"
);
}
export default InviteController;
+26
View File
@@ -0,0 +1,26 @@
import BaseController from "./baseController.js";
const SERVICE_NAME = "LogController";
class LogController extends BaseController {
static SERVICE_NAME = SERVICE_NAME;
constructor(commonDependencies) {
super(commonDependencies);
}
get serviceName() {
return LogController.SERVICE_NAME;
}
getLogs = this.asyncHandler(
async (req, res) => {
const logs = await this.logger.getLogs();
res.success({
msg: "Logs fetched successfully",
data: logs,
});
},
SERVICE_NAME,
"getLogs"
);
}
export default LogController;
+146
View File
@@ -0,0 +1,146 @@
import {
createMaintenanceWindowBodyValidation,
editMaintenanceWindowByIdParamValidation,
editMaintenanceByIdWindowBodyValidation,
getMaintenanceWindowByIdParamValidation,
getMaintenanceWindowsByMonitorIdParamValidation,
getMaintenanceWindowsByTeamIdQueryValidation,
deleteMaintenanceWindowByIdParamValidation,
} from "../validation/joi.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "maintenanceWindowController";
class MaintenanceWindowController extends BaseController {
static SERVICE_NAME = SERVICE_NAME;
constructor(commonDependencies, { settingsService, maintenanceWindowService }) {
super(commonDependencies);
this.settingsService = settingsService;
this.maintenanceWindowService = maintenanceWindowService;
}
get serviceName() {
return MaintenanceWindowController.SERVICE_NAME;
}
createMaintenanceWindows = this.asyncHandler(
async (req, res) => {
await createMaintenanceWindowBodyValidation.validateAsync(req.body);
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
await this.maintenanceWindowService.createMaintenanceWindow({ teamId, body: req.body });
return res.success({
msg: this.stringService.maintenanceWindowCreate,
});
},
SERVICE_NAME,
"createMaintenanceWindows"
);
getMaintenanceWindowById = this.asyncHandler(
async (req, res) => {
await getMaintenanceWindowByIdParamValidation.validateAsync(req.params);
const teamId = req.user.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: req.params.id, teamId });
return res.success({
msg: this.stringService.maintenanceWindowGetById,
data: maintenanceWindow,
});
},
SERVICE_NAME,
"getMaintenanceWindowById"
);
getMaintenanceWindowsByTeamId = this.asyncHandler(
async (req, res) => {
await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query);
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({ teamId, query: req.query });
return res.success({
msg: this.stringService.maintenanceWindowGetByTeam,
data: maintenanceWindows,
});
},
SERVICE_NAME,
"getMaintenanceWindowsByTeamId"
);
getMaintenanceWindowsByMonitorId = this.asyncHandler(
async (req, res) => {
await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params);
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({ monitorId: req.params.monitorId, teamId });
return res.success({
msg: this.stringService.maintenanceWindowGetByUser,
data: maintenanceWindows,
});
},
SERVICE_NAME,
"getMaintenanceWindowsByMonitorId"
);
deleteMaintenanceWindow = this.asyncHandler(
async (req, res) => {
await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params);
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
await this.maintenanceWindowService.deleteMaintenanceWindow({ id: req.params.id, teamId });
return res.success({
msg: this.stringService.maintenanceWindowDelete,
});
},
SERVICE_NAME,
"deleteMaintenanceWindow"
);
editMaintenanceWindow = this.asyncHandler(
async (req, res) => {
await editMaintenanceWindowByIdParamValidation.validateAsync(req.params);
await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body);
const teamId = req.user.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({ id: req.params.id, body: req.body, teamId });
return res.success({
msg: this.stringService.maintenanceWindowEdit,
data: editedMaintenanceWindow,
});
},
SERVICE_NAME,
"editMaintenanceWindow"
);
}
export default MaintenanceWindowController;
+446
View File
@@ -0,0 +1,446 @@
import {
getMonitorByIdParamValidation,
getMonitorByIdQueryValidation,
getMonitorsByTeamIdParamValidation,
getMonitorsByTeamIdQueryValidation,
createMonitorBodyValidation,
editMonitorBodyValidation,
pauseMonitorParamValidation,
getMonitorStatsByIdParamValidation,
getMonitorStatsByIdQueryValidation,
getCertificateParamValidation,
getHardwareDetailsByIdParamValidation,
getHardwareDetailsByIdQueryValidation,
} from "../validation/joi.js";
import sslChecker from "ssl-checker";
import { fetchMonitorCertificate } from "./controllerUtils.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "monitorController";
class MonitorController extends BaseController {
static SERVICE_NAME = SERVICE_NAME;
constructor(commonDependencies, { settingsService, jobQueue, emailService, monitorService }) {
super(commonDependencies);
this.settingsService = settingsService;
this.jobQueue = jobQueue;
this.emailService = emailService;
this.monitorService = monitorService;
}
get serviceName() {
return MonitorController.SERVICE_NAME;
}
async verifyTeamAccess(teamId, monitorId) {
const monitor = await this.db.getMonitorById(monitorId);
if (!monitor.teamId.equals(teamId)) {
throw this.errorService.createAuthorizationError();
}
}
getAllMonitors = this.asyncHandler(
async (req, res) => {
const monitors = await this.monitorService.getAllMonitors();
return res.success({
msg: this.stringService.monitorGetAll,
data: monitors,
});
},
SERVICE_NAME,
"getAllMonitors"
);
getUptimeDetailsById = this.asyncHandler(
async (req, res) => {
const monitorId = req?.params?.monitorId;
const dateRange = req?.query?.dateRange;
const normalize = req?.query?.normalize;
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const data = await this.monitorService.getUptimeDetailsById({
teamId,
monitorId,
dateRange,
normalize,
});
return res.success({
msg: this.stringService.monitorGetByIdSuccess,
data: data,
});
},
SERVICE_NAME,
"getUptimeDetailsById"
);
getMonitorStatsById = this.asyncHandler(
async (req, res) => {
await getMonitorStatsByIdParamValidation.validateAsync(req.params);
await getMonitorStatsByIdQueryValidation.validateAsync(req.query);
let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query;
const monitorId = req?.params?.monitorId;
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const monitorStats = await this.monitorService.getMonitorStatsById({
teamId,
monitorId,
limit,
sortOrder,
dateRange,
numToDisplay,
normalize,
});
return res.success({
msg: this.stringService.monitorStatsById,
data: monitorStats,
});
},
SERVICE_NAME,
"getMonitorStatsById"
);
/**
* Get hardware details for a specific monitor by ID
* @async
* @param {Express.Request} req - Express request object containing monitorId in params
* @param {Express.Response} res - Express response object
* @param {Express.NextFunction} next - Express next middleware function
* @returns {Promise<Express.Response>}
* @throws {Error} - Throws error if monitor not found or other database errors
*/
getHardwareDetailsById = this.asyncHandler(
async (req, res) => {
await getHardwareDetailsByIdParamValidation.validateAsync(req.params);
await getHardwareDetailsByIdQueryValidation.validateAsync(req.query);
const monitorId = req?.params?.monitorId;
const dateRange = req?.query?.dateRange;
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const monitor = await this.monitorService.getHardwareDetailsById({
teamId,
monitorId,
dateRange,
});
return res.success({
msg: this.stringService.monitorGetByIdSuccess,
data: monitor,
});
},
SERVICE_NAME,
"getHardwareDetailsById"
);
getMonitorCertificate = this.asyncHandler(
async (req, res) => {
await getCertificateParamValidation.validateAsync(req.params);
const { monitorId } = req.params;
const monitor = await this.db.getMonitorById(monitorId);
const certificate = await fetchMonitorCertificate(sslChecker, monitor);
return res.success({
msg: this.stringService.monitorCertificate,
data: {
certificateDate: new Date(certificate.validTo),
},
});
},
SERVICE_NAME,
"getMonitorCertificate"
);
getMonitorById = this.asyncHandler(
async (req, res) => {
await getMonitorByIdParamValidation.validateAsync(req.params);
await getMonitorByIdQueryValidation.validateAsync(req.query);
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: req?.params?.monitorId });
return res.success({
msg: this.stringService.monitorGetByIdSuccess,
data: monitor,
});
},
SERVICE_NAME,
"getMonitorById"
);
createMonitor = this.asyncHandler(
async (req, res) => {
await createMonitorBodyValidation.validateAsync(req.body);
const userId = req?.user?._id;
const teamId = req?.user?.teamId;
const monitor = await this.monitorService.createMonitor({ teamId, userId, body: req.body });
return res.success({
msg: this.stringService.monitorCreate,
data: monitor,
});
},
SERVICE_NAME,
"createMonitor"
);
createBulkMonitors = this.asyncHandler(
async (req, res) => {
if (!req.file) {
throw this.errorService.createBadRequestError("No file uploaded");
}
if (!req.file.mimetype.includes("csv")) {
throw this.errorService.createBadRequestError("File is not a CSV");
}
if (req.file.size === 0) {
throw this.errorService.createBadRequestError("File is empty");
}
const userId = req?.user?._id;
const teamId = req?.user?.teamId;
if (!userId || !teamId) {
throw this.errorService.createBadRequestError("Missing userId or teamId");
}
const fileData = req?.file?.buffer?.toString("utf-8");
if (!fileData) {
throw this.errorService.createBadRequestError("Cannot get file from buffer");
}
const monitors = await this.monitorService.createBulkMonitors({ fileData, userId, teamId });
return res.success({
msg: this.stringService.bulkMonitorsCreate,
data: monitors,
});
},
SERVICE_NAME,
"createBulkMonitors"
);
deleteMonitor = this.asyncHandler(
async (req, res) => {
await getMonitorByIdParamValidation.validateAsync(req.params);
const monitorId = req.params.monitorId;
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
return res.success({ msg: this.stringService.monitorDelete, data: deletedMonitor });
},
SERVICE_NAME,
"deleteMonitor"
);
deleteAllMonitors = this.asyncHandler(
async (req, res) => {
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
return res.success({ msg: `Deleted ${deletedCount} monitors` });
},
SERVICE_NAME,
"deleteAllMonitors"
);
editMonitor = this.asyncHandler(
async (req, res) => {
await getMonitorByIdParamValidation.validateAsync(req.params);
await editMonitorBodyValidation.validateAsync(req.body);
const monitorId = req?.params?.monitorId;
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body });
return res.success({
msg: this.stringService.monitorEdit,
data: editedMonitor,
});
},
SERVICE_NAME,
"editMonitor"
);
pauseMonitor = this.asyncHandler(
async (req, res) => {
await pauseMonitorParamValidation.validateAsync(req.params);
const monitorId = req.params.monitorId;
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
return res.success({
msg: monitor.isActive ? this.stringService.monitorResume : this.stringService.monitorPause,
data: monitor,
});
},
SERVICE_NAME,
"pauseMonitor"
);
addDemoMonitors = this.asyncHandler(
async (req, res) => {
const { _id, teamId } = req.user;
const demoMonitors = await this.monitorService.addDemoMonitors({ userId: _id, teamId });
return res.success({
msg: this.stringService.monitorDemoAdded,
data: demoMonitors?.length ?? 0,
});
},
SERVICE_NAME,
"addDemoMonitors"
);
sendTestEmail = this.asyncHandler(
async (req, res) => {
const { to } = req.body;
if (!to || typeof to !== "string") {
throw this.errorService.createBadRequestError(this.stringService.errorForValidEmailAddress);
}
const messageId = await this.monitorService.sendTestEmail({ to });
return res.success({
msg: this.stringService.sendTestEmail,
data: { messageId },
});
},
SERVICE_NAME,
"sendTestEmail"
);
getMonitorsByTeamId = this.asyncHandler(
async (req, res) => {
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
const teamId = req?.user?.teamId;
const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, limit, type, page, rowsPerPage, filter, field, order });
return res.success({
msg: this.stringService.monitorGetByTeamId,
data: monitors,
});
},
SERVICE_NAME,
"getMonitorsByTeamId"
);
getMonitorsAndSummaryByTeamId = this.asyncHandler(
async (req, res) => {
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
const explain = req?.query?.explain;
const type = req?.query?.type;
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const result = await this.monitorService.getMonitorsAndSummaryByTeamId({ teamId, type, explain });
return res.success({
msg: "OK", // TODO
data: result,
});
},
SERVICE_NAME,
"getMonitorsAndSummaryByTeamId"
);
getMonitorsWithChecksByTeamId = this.asyncHandler(
async (req, res) => {
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
const explain = req?.query?.explain;
let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
teamId,
limit,
type,
page,
rowsPerPage,
filter,
field,
order,
explain,
});
return res.success({
msg: "OK",
data: monitors,
});
},
SERVICE_NAME,
"getMonitorsWithChecksByTeamId"
);
exportMonitorsToCSV = this.asyncHandler(
async (req, res) => {
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const csv = await this.monitorService.exportMonitorsToCSV({ teamId });
return res.file({
data: csv,
headers: {
"Content-Type": "text/csv",
"Content-Disposition": "attachment; filename=monitors.csv",
},
});
},
SERVICE_NAME,
"exportMonitorsToCSV"
);
}
export default MonitorController;
+180
View File
@@ -0,0 +1,180 @@
import { createNotificationBodyValidation } from "../validation/joi.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "NotificationController";
class NotificationController extends BaseController {
static SERVICE_NAME = SERVICE_NAME;
constructor(commonDependencies, { notificationService, statusService }) {
super(commonDependencies);
this.notificationService = notificationService;
this.statusService = statusService;
}
get serviceName() {
return NotificationController.SERVICE_NAME;
}
testNotification = this.asyncHandler(
async (req, res) => {
const notification = req.body;
const success = await this.notificationService.sendTestNotification(notification);
if (!success) {
throw this.errorService.createServerError("Sending notification failed");
}
return res.success({
msg: "Notification sent successfully",
});
},
SERVICE_NAME,
"testNotification"
);
createNotification = this.asyncHandler(
async (req, res) => {
await createNotificationBodyValidation.validateAsync(req.body, {
abortEarly: false,
});
const body = req.body;
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const userId = req?.user?._id;
if (!userId) {
throw this.errorService.createBadRequestError("User ID is required");
}
body.userId = userId;
body.teamId = teamId;
const notification = await this.db.createNotification(body);
return res.success({
msg: "Notification created successfully",
data: notification,
});
},
SERVICE_NAME,
"createNotification"
);
getNotificationsByTeamId = this.asyncHandler(
async (req, res) => {
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const notifications = await this.db.getNotificationsByTeamId(teamId);
return res.success({
msg: "Notifications fetched successfully",
data: notifications,
});
},
SERVICE_NAME,
"getNotificationsByTeamId"
);
deleteNotification = this.asyncHandler(
async (req, res) => {
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const notification = await this.db.getNotificationById(req.params.id);
if (!notification.teamId.equals(teamId)) {
throw this.errorService.createAuthorizationError();
}
await this.db.deleteNotificationById(req.params.id);
return res.success({
msg: "Notification deleted successfully",
});
},
SERVICE_NAME,
"deleteNotification"
);
getNotificationById = this.asyncHandler(
async (req, res) => {
const notification = await this.db.getNotificationById(req.params.id);
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
if (!notification.teamId.equals(teamId)) {
throw this.errorService.createAuthorizationError();
}
return res.success({
msg: "Notification fetched successfully",
data: notification,
});
},
SERVICE_NAME,
"getNotificationById"
);
editNotification = this.asyncHandler(
async (req, res) => {
await createNotificationBodyValidation.validateAsync(req.body, {
abortEarly: false,
});
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const notification = await this.db.getNotificationById(req.params.id);
if (!notification.teamId.equals(teamId)) {
throw this.errorService.createAuthorizationError();
}
const editedNotification = await this.db.editNotification(req.params.id, req.body);
return res.success({
msg: "Notification updated successfully",
data: editedNotification,
});
},
SERVICE_NAME,
"editNotification"
);
testAllNotifications = this.asyncHandler(
async (req, res) => {
const monitorId = req.body.monitorId;
const teamId = req?.user?.teamId;
if (!teamId) {
throw this.errorService.createBadRequestError("Team ID is required");
}
const monitor = await this.db.getMonitorById(monitorId);
if (!monitor.teamId.equals(teamId)) {
throw this.errorService.createAuthorizationError();
}
const notifications = monitor.notifications;
if (notifications.length === 0) throw this.errorService.createBadRequestError("No notifications");
const result = await this.notificationService.testAllNotifications(notifications);
if (!result) throw this.errorService.createServerError("Failed to send all notifications");
return res.success({
msg: "All notifications sent successfully",
});
},
SERVICE_NAME,
"testAllNotifications"
);
}
export default NotificationController;
+87
View File
@@ -0,0 +1,87 @@
import BaseController from "./baseController.js";
const SERVICE_NAME = "JobQueueController";
class JobQueueController extends BaseController {
static SERVICE_NAME = SERVICE_NAME;
constructor(commonDependencies, { jobQueue }) {
super(commonDependencies);
this.jobQueue = jobQueue;
}
get serviceName() {
return JobQueueController.SERVICE_NAME;
}
getMetrics = this.asyncHandler(
async (req, res) => {
const metrics = await this.jobQueue.getMetrics();
res.success({
msg: this.stringService.queueGetMetrics,
data: metrics,
});
},
SERVICE_NAME,
"getMetrics"
);
getJobs = this.asyncHandler(
async (req, res) => {
const jobs = await this.jobQueue.getJobs();
return res.success({
msg: this.stringService.queueGetJobs,
data: jobs,
});
},
SERVICE_NAME,
"getJobs"
);
getAllMetrics = this.asyncHandler(
async (req, res) => {
const jobs = await this.jobQueue.getJobs();
const metrics = await this.jobQueue.getMetrics();
return res.success({
msg: this.stringService.queueGetAllMetrics,
data: { jobs, metrics },
});
},
SERVICE_NAME,
"getAllMetrics"
);
addJob = this.asyncHandler(
async (req, res) => {
await this.jobQueue.addJob(Math.random().toString(36).substring(7));
return res.success({
msg: this.stringService.queueAddJob,
});
},
SERVICE_NAME,
"addJob"
);
flushQueue = this.asyncHandler(
async (req, res) => {
const result = await this.jobQueue.flushQueues();
return res.success({
msg: this.stringService.jobQueueFlush,
data: result,
});
},
SERVICE_NAME,
"flushQueue"
);
checkQueueHealth = this.asyncHandler(
async (req, res) => {
const stuckQueues = await this.jobQueue.checkQueueHealth();
return res.success({
msg: this.stringService.queueHealthCheck,
data: stuckQueues,
});
},
SERVICE_NAME,
"checkQueueHealth"
);
}
export default JobQueueController;
+122
View File
@@ -0,0 +1,122 @@
import { updateAppSettingsBodyValidation } from "../validation/joi.js";
import { sendTestEmailBodyValidation } from "../validation/joi.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "SettingsController";
class SettingsController extends BaseController {
static SERVICE_NAME = SERVICE_NAME;
constructor(commonDependencies, { settingsService, emailService }) {
super(commonDependencies);
this.settingsService = settingsService;
this.emailService = emailService;
}
get serviceName() {
return SettingsController.SERVICE_NAME;
}
buildAppSettings = (dbSettings) => {
const sanitizedSettings = { ...dbSettings };
delete sanitizedSettings.version;
const returnSettings = {
pagespeedKeySet: false,
emailPasswordSet: false,
};
if (typeof sanitizedSettings.pagespeedApiKey !== "undefined") {
returnSettings.pagespeedKeySet = true;
delete sanitizedSettings.pagespeedApiKey;
}
if (typeof sanitizedSettings.systemEmailPassword !== "undefined") {
returnSettings.emailPasswordSet = true;
delete sanitizedSettings.systemEmailPassword;
}
returnSettings.settings = sanitizedSettings;
return returnSettings;
};
getAppSettings = this.asyncHandler(
async (req, res) => {
const dbSettings = await this.settingsService.getDBSettings();
const returnSettings = this.buildAppSettings(dbSettings);
return res.success({
msg: this.stringService.getAppSettings,
data: returnSettings,
});
},
SERVICE_NAME,
"getAppSettings"
);
updateAppSettings = this.asyncHandler(
async (req, res) => {
await updateAppSettingsBodyValidation.validateAsync(req.body);
const updatedSettings = await this.db.updateAppSettings(req.body);
const returnSettings = this.buildAppSettings(updatedSettings);
return res.success({
msg: this.stringService.updateAppSettings,
data: returnSettings,
});
},
SERVICE_NAME,
"updateAppSettings"
);
sendTestEmail = this.asyncHandler(
async (req, res) => {
await sendTestEmailBodyValidation.validateAsync(req.body);
const {
to,
systemEmailHost,
systemEmailPort,
systemEmailAddress,
systemEmailPassword,
systemEmailUser,
systemEmailConnectionHost,
systemEmailSecure,
systemEmailPool,
systemEmailIgnoreTLS,
systemEmailRequireTLS,
systemEmailRejectUnauthorized,
systemEmailTLSServername,
} = req.body;
const subject = this.stringService.testEmailSubject;
const context = { testName: "Monitoring System" };
const html = await this.emailService.buildEmail("testEmailTemplate", context);
const messageId = await this.emailService.sendEmail(to, subject, html, {
systemEmailHost,
systemEmailPort,
systemEmailUser,
systemEmailAddress,
systemEmailPassword,
systemEmailConnectionHost,
systemEmailSecure,
systemEmailPool,
systemEmailIgnoreTLS,
systemEmailRequireTLS,
systemEmailRejectUnauthorized,
systemEmailTLSServername,
});
if (!messageId) {
throw this.errorService.createServerError("Failed to send test email.");
}
return res.success({
msg: this.stringService.sendTestEmail,
data: { messageId },
});
},
SERVICE_NAME,
"sendTestEmail"
);
}
export default SettingsController;
+108
View File
@@ -0,0 +1,108 @@
import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "../validation/joi.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "statusPageController";
class StatusPageController extends BaseController {
static SERVICE_NAME = SERVICE_NAME;
constructor(commonDependencies) {
super(commonDependencies);
}
get serviceName() {
return StatusPageController.SERVICE_NAME;
}
createStatusPage = this.asyncHandler(
async (req, res) => {
await createStatusPageBodyValidation.validateAsync(req.body);
await imageValidation.validateAsync(req.file);
const { _id, teamId } = req.user;
const statusPage = await this.db.createStatusPage({
statusPageData: req.body,
image: req.file,
userId: _id,
teamId,
});
return res.success({
msg: this.stringService.statusPageCreate,
data: statusPage,
});
},
SERVICE_NAME,
"createStatusPage"
);
updateStatusPage = this.asyncHandler(
async (req, res) => {
await createStatusPageBodyValidation.validateAsync(req.body);
await imageValidation.validateAsync(req.file);
const statusPage = await this.db.updateStatusPage(req.body, req.file);
if (statusPage === null) {
throw this.errorService.createNotFoundError(this.stringService.statusPageNotFound);
}
return res.success({
msg: this.stringService.statusPageUpdate,
data: statusPage,
});
},
SERVICE_NAME,
"updateStatusPage"
);
getStatusPage = this.asyncHandler(
async (req, res) => {
const statusPage = await this.db.getStatusPage();
return res.success({
msg: this.stringService.statusPageByUrl,
data: statusPage,
});
},
SERVICE_NAME,
"getStatusPage"
);
getStatusPageByUrl = this.asyncHandler(
async (req, res) => {
await getStatusPageParamValidation.validateAsync(req.params);
await getStatusPageQueryValidation.validateAsync(req.query);
const statusPage = await this.db.getStatusPageByUrl(req.params.url, req.query.type);
return res.success({
msg: this.stringService.statusPageByUrl,
data: statusPage,
});
},
SERVICE_NAME,
"getStatusPageByUrl"
);
getStatusPagesByTeamId = this.asyncHandler(
async (req, res) => {
const teamId = req.user.teamId;
const statusPages = await this.db.getStatusPagesByTeamId(teamId);
return res.success({
msg: this.stringService.statusPageByTeamId,
data: statusPages,
});
},
SERVICE_NAME,
"getStatusPagesByTeamId"
);
deleteStatusPage = this.asyncHandler(
async (req, res) => {
await this.db.deleteStatusPage(req.params.url);
return res.success({
msg: this.stringService.statusPageDelete,
});
},
SERVICE_NAME,
"deleteStatusPage"
);
}
export default StatusPageController;