Merge pull request #3783 from bluewave-labs/chore/controller-refactor

chore: add catchAsync to handle errors, remove all try/catch blocks from con…
This commit is contained in:
Alexander Holliday authored and GitHub committed 2026-07-15 10:23:48 -07:00
commit caee9510eb
15 files changed
+1243 -1535

No files matched your search

+180 -235
View File
@@ -1,4 +1,5 @@
import { Request, Response, NextFunction } from "express";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
import { AppError } from "@/utils/AppError.js";
import { requireTeamId, requireUserEmail, requireUserId, requireUserRoles } from "@/api/controllers/controllerUtils.js";
@@ -24,20 +25,20 @@ import { IUserService } from "@/domain/users/user.service.js";
const SERVICE_NAME = "authController";
export interface IAuthController {
registerUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
createUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
loginUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
editUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
checkSuperadminExists(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
requestRecovery(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
validateRecovery(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
resetPassword(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
deleteUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
deleteUserById(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
getAllUsers(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
getUserById(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
editUserById(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
editUserPasswordById(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
registerUser: RequestHandler;
createUser: RequestHandler;
loginUser: RequestHandler;
editUser: RequestHandler;
checkSuperadminExists: RequestHandler;
requestRecovery: RequestHandler;
validateRecovery: RequestHandler;
resetPassword: RequestHandler;
deleteUser: RequestHandler;
deleteUserById: RequestHandler;
getAllUsers: RequestHandler;
getUserById: RequestHandler;
editUserById: RequestHandler;
editUserPasswordById: RequestHandler;
}
class AuthController implements IAuthController {
@@ -53,239 +54,183 @@ class AuthController implements IAuthController {
return AuthController.SERVICE_NAME;
}
registerUser = async (req: Request, res: Response, next: NextFunction) => {
try {
const newUser = req.body.user;
const newUserToken = req.body.token;
if (newUser?.email) {
const newUserEmail = requireUserEmail(newUser.email);
newUser.email = newUserEmail.toLowerCase();
}
const validatedBody = registrationBodyValidation.parse(newUser);
const { user, token } = await this.userService.registerUser(validatedBody, newUserToken, req?.file ?? null);
res.status(200).json({
success: true,
msg: "User registered successfully",
data: { user, token },
});
} catch (error) {
next(error);
registerUser = catchAsync(async (req: Request, res: Response) => {
const newUser = req.body.user;
const newUserToken = req.body.token;
if (newUser?.email) {
const newUserEmail = requireUserEmail(newUser.email);
newUser.email = newUserEmail.toLowerCase();
}
};
const validatedBody = registrationBodyValidation.parse(newUser);
createUser = async (req: Request, res: Response, next: NextFunction) => {
try {
const userData = req.body;
if (userData?.email) {
userData.email = userData.email.toLowerCase();
}
const validatedBody = createUserBodyValidation.parse(userData);
const { user, token } = await this.userService.registerUser(validatedBody, newUserToken, req?.file ?? null);
res.status(200).json({
success: true,
msg: "User registered successfully",
data: { user, token },
});
});
const teamId = requireTeamId(req.user?.teamId);
const actorRoles = requireUserRoles(req.user?.role);
const newUser = await this.userService.createUser(validatedBody, teamId, actorRoles, req?.file ?? null);
res.status(201).json({
success: true,
msg: "User created successfully",
data: newUser,
});
} catch (error) {
next(error);
createUser = catchAsync(async (req: Request, res: Response) => {
const userData = req.body;
if (userData?.email) {
userData.email = userData.email.toLowerCase();
}
};
const validatedBody = createUserBodyValidation.parse(userData);
loginUser = async (req: Request, res: Response, next: NextFunction) => {
try {
if (req.body?.email) {
req.body.email = req.body.email?.toLowerCase();
}
loginValidation.parse(req.body);
const { user, token } = await this.userService.loginUser(req.body.email, req.body.password);
const teamId = requireTeamId(req.user?.teamId);
const actorRoles = requireUserRoles(req.user?.role);
const newUser = await this.userService.createUser(validatedBody, teamId, actorRoles, req?.file ?? null);
res.status(201).json({
success: true,
msg: "User created successfully",
data: newUser,
});
});
return res.status(200).json({
success: true,
msg: "User logged in successfully",
data: {
user,
token,
},
});
} catch (error) {
next(error);
loginUser = catchAsync(async (req: Request, res: Response) => {
if (req.body?.email) {
req.body.email = req.body.email?.toLowerCase();
}
};
loginValidation.parse(req.body);
const { user, token } = await this.userService.loginUser(req.body.email, req.body.password);
editUser = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedBody = editUserBodyValidation.parse(req.body);
const userId = requireUserId(req.user?.id);
const userEmail = requireUserEmail(req.user?.email);
const updatedUser = await this.userService.editUser(validatedBody, req?.file ?? null, userId, userEmail);
return res.status(200).json({
success: true,
msg: "User logged in successfully",
data: {
user,
token,
},
});
});
res.status(200).json({
success: true,
msg: "User updated successfully",
data: updatedUser,
});
} catch (error) {
next(error);
editUser = catchAsync(async (req: Request, res: Response) => {
const validatedBody = editUserBodyValidation.parse(req.body);
const userId = requireUserId(req.user?.id);
const userEmail = requireUserEmail(req.user?.email);
const updatedUser = await this.userService.editUser(validatedBody, req?.file ?? null, userId, userEmail);
res.status(200).json({
success: true,
msg: "User updated successfully",
data: updatedUser,
});
});
checkSuperadminExists = catchAsync(async (req: Request, res: Response) => {
const superAdminExists = await this.userService.checkSuperadminExists();
return res.status(200).json({
success: true,
msg: "Superadmin existence checked successfully",
data: superAdminExists,
});
});
requestRecovery = catchAsync(async (req: Request, res: Response) => {
recoveryValidation.parse(req.body);
const email = req?.body?.email;
const msgId = await this.userService.requestRecovery(email);
return res.status(200).json({
success: true,
msg: "Password recovery email sent successfully",
data: msgId,
});
});
validateRecovery = catchAsync(async (req: Request, res: Response) => {
recoveryTokenBodyValidation.parse(req.body);
await this.userService.validateRecovery(req.body.recoveryToken);
return res.status(200).json({
success: true,
msg: "Recovery token is valid",
});
});
resetPassword = catchAsync(async (req: Request, res: Response) => {
newPasswordValidation.parse(req.body);
const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken);
return res.status(200).json({
success: true,
msg: "Password has been reset successfully",
data: { user, token },
});
});
deleteUser = catchAsync(async (req: Request, res: Response) => {
const userId = requireUserId(req.user?.id);
const teamId = requireTeamId(req.user?.teamId);
const roles = requireUserRoles(req.user?.role);
await this.userService.deleteUser({
userId,
teamId,
roles,
});
return res.status(200).json({
success: true,
msg: "User deleted successfully",
});
});
deleteUserById = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getUserByIdParamValidation.parse(req.params);
const targetUserId = validatedParams.userId;
const actorId = requireUserId(req.user?.id);
const actorTeamId = requireTeamId(req.user?.teamId);
const actorRoles = requireUserRoles(req.user?.role);
await this.userService.deleteUserById({ actorId, actorTeamId, actorRoles, targetUserId });
return res.status(200).json({
success: true,
msg: "User removed successfully",
});
});
getAllUsers = catchAsync(async (req: Request, res: Response) => {
const allUsers = await this.userService.getAllUsers();
return res.status(200).json({
success: true,
msg: "Users retrieved successfully",
data: allUsers,
});
});
getUserById = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getUserByIdParamValidation.parse(req.params);
const actorRoles = requireUserRoles(req.user?.role);
const user = await this.userService.getUserById(actorRoles, validatedParams.userId);
return res.status(200).json({ success: true, msg: "ok", data: user });
});
editUserById = catchAsync(async (req: Request, res: Response) => {
const actorRoles = requireUserRoles(req.user?.role);
const actorId = requireUserId(req.user?.id);
if (!actorRoles.includes("superadmin")) {
throw new AppError({ message: "Unauthorized", status: 403 });
}
};
checkSuperadminExists = async (req: Request, res: Response, next: NextFunction) => {
try {
const superAdminExists = await this.userService.checkSuperadminExists();
return res.status(200).json({
success: true,
msg: "Superadmin existence checked successfully",
data: superAdminExists,
});
} catch (error) {
next(error);
const validatedParams = editUserByIdParamValidation.parse(req.params);
// If this is superadmin self edit, allow "superadmin" role
const validatedBody =
validatedParams.userId === actorId ? editSuperadminUserByIdBodyValidation.parse(req.body) : editUserByIdBodyValidation.parse(req.body);
await this.userService.editUserById(validatedParams.userId, validatedBody);
return res.status(200).json({ success: true, msg: "ok" });
});
editUserPasswordById = catchAsync(async (req: Request, res: Response) => {
const actorRoles = requireUserRoles(req.user?.role);
if (!actorRoles.includes("superadmin")) {
throw new AppError({ message: "Unauthorized", status: 403 });
}
};
requestRecovery = async (req: Request, res: Response, next: NextFunction) => {
try {
recoveryValidation.parse(req.body);
const email = req?.body?.email;
const msgId = await this.userService.requestRecovery(email);
return res.status(200).json({
success: true,
msg: "Password recovery email sent successfully",
data: msgId,
});
} catch (error) {
next(error);
}
};
validateRecovery = async (req: Request, res: Response, next: NextFunction) => {
try {
recoveryTokenBodyValidation.parse(req.body);
await this.userService.validateRecovery(req.body.recoveryToken);
return res.status(200).json({
success: true,
msg: "Recovery token is valid",
});
} catch (error) {
next(error);
}
};
resetPassword = async (req: Request, res: Response, next: NextFunction) => {
try {
newPasswordValidation.parse(req.body);
const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken);
return res.status(200).json({
success: true,
msg: "Password has been reset successfully",
data: { user, token },
});
} catch (error) {
next(error);
}
};
deleteUser = async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = requireUserId(req.user?.id);
const teamId = requireTeamId(req.user?.teamId);
const roles = requireUserRoles(req.user?.role);
await this.userService.deleteUser({
userId,
teamId,
roles,
});
return res.status(200).json({
success: true,
msg: "User deleted successfully",
});
} catch (error) {
next(error);
}
};
deleteUserById = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = getUserByIdParamValidation.parse(req.params);
const targetUserId = validatedParams.userId;
const actorId = requireUserId(req.user?.id);
const actorTeamId = requireTeamId(req.user?.teamId);
const actorRoles = requireUserRoles(req.user?.role);
await this.userService.deleteUserById({ actorId, actorTeamId, actorRoles, targetUserId });
return res.status(200).json({
success: true,
msg: "User removed successfully",
});
} catch (error) {
next(error);
}
};
getAllUsers = async (req: Request, res: Response, next: NextFunction) => {
try {
const allUsers = await this.userService.getAllUsers();
return res.status(200).json({
success: true,
msg: "Users retrieved successfully",
data: allUsers,
});
} catch (error) {
next(error);
}
};
getUserById = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = getUserByIdParamValidation.parse(req.params);
const actorRoles = requireUserRoles(req.user?.role);
const user = await this.userService.getUserById(actorRoles, validatedParams.userId);
return res.status(200).json({ success: true, msg: "ok", data: user });
} catch (error) {
next(error);
}
};
editUserById = async (req: Request, res: Response, next: NextFunction) => {
try {
const actorRoles = requireUserRoles(req.user?.role);
const actorId = requireUserId(req.user?.id);
if (!actorRoles.includes("superadmin")) {
throw new AppError({ message: "Unauthorized", status: 403 });
}
const validatedParams = editUserByIdParamValidation.parse(req.params);
// If this is superadmin self edit, allow "superadmin" role
const validatedBody =
validatedParams.userId === actorId ? editSuperadminUserByIdBodyValidation.parse(req.body) : editUserByIdBodyValidation.parse(req.body);
await this.userService.editUserById(validatedParams.userId, validatedBody);
return res.status(200).json({ success: true, msg: "ok" });
} catch (error) {
next(error);
}
};
editUserPasswordById = async (req: Request, res: Response, next: NextFunction) => {
try {
const actorRoles = requireUserRoles(req.user?.role);
if (!actorRoles.includes("superadmin")) {
throw new AppError({ message: "Unauthorized", status: 403 });
}
const validatedParams = editUserByIdParamValidation.parse(req.params);
const validatedBody = editUserPasswordByIdBodyValidation.parse(req.body);
await this.userService.setPasswordByUserId(validatedParams.userId, validatedBody.password);
return res.status(200).json({ success: true, msg: "Password reset successfully" });
} catch (error) {
next(error);
}
};
const validatedParams = editUserByIdParamValidation.parse(req.params);
const validatedBody = editUserPasswordByIdBodyValidation.parse(req.body);
await this.userService.setPasswordByUserId(validatedParams.userId, validatedBody.password);
return res.status(200).json({ success: true, msg: "Password reset successfully" });
});
}
export default AuthController;
+77 -96
View File
@@ -1,4 +1,5 @@
import { Request, Response, NextFunction } from "express";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
import {
getChecksParamValidation,
@@ -14,11 +15,11 @@ const SERVICE_NAME = "checkController";
export interface ICheckController {
serviceName: string;
getChecksByMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getChecksByTeam: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getChecksSummaryByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
deleteChecks: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
deleteChecksByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getChecksByMonitor: RequestHandler;
getChecksByTeam: RequestHandler;
getChecksSummaryByTeamId: RequestHandler;
deleteChecks: RequestHandler;
deleteChecksByTeamId: RequestHandler;
}
class CheckController implements ICheckController {
@@ -33,109 +34,89 @@ class CheckController implements ICheckController {
return CheckController.SERVICE_NAME;
}
getChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = getChecksParamValidation.parse(req.params);
const validatedQuery = getChecksQueryValidation.parse(req.query);
getChecksByMonitor = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getChecksParamValidation.parse(req.params);
const validatedQuery = getChecksQueryValidation.parse(req.query);
const teamId = requireTeamId(req.user?.teamId);
const teamId = requireTeamId(req.user?.teamId);
const result = await this.checkService.getChecksByMonitor({
monitorId: validatedParams.monitorId,
teamId,
sortOrder: validatedQuery.sortOrder,
dateRange: validatedQuery.dateRange,
filter: validatedQuery.filter,
page: validatedQuery.page,
rowsPerPage: validatedQuery.rowsPerPage,
status: validatedQuery.status,
});
const result = await this.checkService.getChecksByMonitor({
monitorId: validatedParams.monitorId,
teamId,
sortOrder: validatedQuery.sortOrder,
dateRange: validatedQuery.dateRange,
filter: validatedQuery.filter,
page: validatedQuery.page,
rowsPerPage: validatedQuery.rowsPerPage,
status: validatedQuery.status,
});
return res.status(200).json({
success: true,
msg: "Checks retrieved successfully",
data: result,
});
} catch (error) {
next(error);
}
};
return res.status(200).json({
success: true,
msg: "Checks retrieved successfully",
data: result,
});
});
getChecksByTeam = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedQuery = getTeamChecksQueryValidation.parse(req.query);
const teamId = requireTeamId(req.user?.teamId);
getChecksByTeam = catchAsync(async (req: Request, res: Response) => {
const validatedQuery = getTeamChecksQueryValidation.parse(req.query);
const teamId = requireTeamId(req.user?.teamId);
const checkData = await this.checkService.getChecksByTeam({
teamId,
sortOrder: validatedQuery.sortOrder,
dateRange: validatedQuery.dateRange,
page: validatedQuery.page,
rowsPerPage: validatedQuery.rowsPerPage,
filter: validatedQuery.filter,
});
return res.status(200).json({
success: true,
msg: "Team checks retrieved successfully",
data: checkData,
});
} catch (error) {
next(error);
}
};
const checkData = await this.checkService.getChecksByTeam({
teamId,
sortOrder: validatedQuery.sortOrder,
dateRange: validatedQuery.dateRange,
page: validatedQuery.page,
rowsPerPage: validatedQuery.rowsPerPage,
filter: validatedQuery.filter,
});
return res.status(200).json({
success: true,
msg: "Team checks retrieved successfully",
data: checkData,
});
});
getChecksSummaryByTeamId = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedQuery = getChecksSummaryByTeamIdQueryValidation.parse(req.query);
const teamId = requireTeamId(req.user?.teamId);
const dateRange = validatedQuery.dateRange ?? "hour";
getChecksSummaryByTeamId = catchAsync(async (req: Request, res: Response) => {
const validatedQuery = getChecksSummaryByTeamIdQueryValidation.parse(req.query);
const teamId = requireTeamId(req.user?.teamId);
const dateRange = validatedQuery.dateRange ?? "hour";
const summary = await this.checkService.getChecksSummaryByTeamId({ teamId, dateRange });
return res.status(200).json({
success: true,
msg: "Checks summary retrieved successfully",
data: summary,
});
} catch (error) {
next(error);
}
};
const summary = await this.checkService.getChecksSummaryByTeamId({ teamId, dateRange });
return res.status(200).json({
success: true,
msg: "Checks summary retrieved successfully",
data: summary,
});
});
deleteChecks = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = deleteChecksParamValidation.parse(req.params);
const teamId = requireTeamId(req.user?.teamId);
deleteChecks = catchAsync(async (req: Request, res: Response) => {
const validatedParams = deleteChecksParamValidation.parse(req.params);
const teamId = requireTeamId(req.user?.teamId);
const deletedCount = await this.checkService.deleteChecks({
monitorId: validatedParams.monitorId,
teamId,
});
const deletedCount = await this.checkService.deleteChecks({
monitorId: validatedParams.monitorId,
teamId,
});
return res.status(200).json({
success: true,
msg: "Checks deleted successfully",
data: { deletedCount },
});
} catch (error) {
next(error);
}
};
return res.status(200).json({
success: true,
msg: "Checks deleted successfully",
data: { deletedCount },
});
});
deleteChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
deleteChecksByTeamId = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const deletedCount = await this.checkService.deleteChecksByTeamId({ teamId });
const deletedCount = await this.checkService.deleteChecksByTeamId({ teamId });
return res.status(200).json({
success: true,
msg: "Checks deleted successfully",
data: { deletedCount },
});
} catch (error) {
next(error);
}
};
return res.status(200).json({
success: true,
msg: "Checks deleted successfully",
data: { deletedCount },
});
});
}
export default CheckController;
@@ -1,10 +1,11 @@
import { IDiagnosticService } from "@/domain/diagnostics/diagnostic.service.js";
import { Request, Response, NextFunction } from "express";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
const SERVICE_NAME = "diagnosticController";
export interface IDiagnosticController {
getSystemStats: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getSystemStats: RequestHandler;
}
class DiagnosticController implements IDiagnosticController {
@@ -20,18 +21,14 @@ class DiagnosticController implements IDiagnosticController {
return DiagnosticController.SERVICE_NAME;
}
getSystemStats = async (req: Request, res: Response, next: NextFunction) => {
try {
const diagnostics = await this.diagnosticService.getSystemStats();
return res.status(200).json({
success: true,
msg: "OK",
data: diagnostics,
});
} catch (error) {
next(error);
}
};
getSystemStats = catchAsync(async (req: Request, res: Response) => {
const diagnostics = await this.diagnosticService.getSystemStats();
return res.status(200).json({
success: true,
msg: "OK",
data: diagnostics,
});
});
}
export default DiagnosticController;
@@ -1,4 +1,5 @@
import { Request, Response, NextFunction } from "express";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
import { getChecksParamValidation, getChecksQueryValidation } from "@/api/validation/checkValidation.js";
import type { IGeoChecksService } from "@/domain/geo-checks/geo-check.service.js";
import { requireTeamId } from "./controllerUtils.js";
@@ -6,7 +7,7 @@ import { requireTeamId } from "./controllerUtils.js";
const SERVICE_NAME = "geoCheckController";
export interface IGeoCheckController {
getGeoChecksByMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getGeoChecksByMonitor: RequestHandler;
}
class GeoCheckController implements IGeoCheckController {
static SERVICE_NAME = SERVICE_NAME;
@@ -20,32 +21,28 @@ class GeoCheckController implements IGeoCheckController {
return GeoCheckController.SERVICE_NAME;
}
getGeoChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = getChecksParamValidation.parse(req.params);
const validatedQuery = getChecksQueryValidation.parse(req.query);
getGeoChecksByMonitor = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getChecksParamValidation.parse(req.params);
const validatedQuery = getChecksQueryValidation.parse(req.query);
const teamId = requireTeamId(req.user?.teamId);
const teamId = requireTeamId(req.user?.teamId);
const result = await this.geoChecksService.getGeoChecksByMonitor({
monitorId: validatedParams.monitorId,
teamId: teamId,
sortOrder: validatedQuery.sortOrder,
dateRange: validatedQuery.dateRange,
page: validatedQuery.page,
rowsPerPage: validatedQuery.rowsPerPage,
continent: validatedQuery.continent,
});
const result = await this.geoChecksService.getGeoChecksByMonitor({
monitorId: validatedParams.monitorId,
teamId: teamId,
sortOrder: validatedQuery.sortOrder,
dateRange: validatedQuery.dateRange,
page: validatedQuery.page,
rowsPerPage: validatedQuery.rowsPerPage,
continent: validatedQuery.continent,
});
return res.status(200).json({
success: true,
msg: "Geo checks retrieved successfully",
data: result,
});
} catch (error) {
next(error);
}
};
return res.status(200).json({
success: true,
msg: "Geo checks retrieved successfully",
data: result,
});
});
}
export default GeoCheckController;
@@ -1,5 +1,6 @@
import { AppError } from "@/utils/AppError.js";
import { Request, Response, NextFunction } from "express";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
import { requireTeamId, requireUserId, requireUserEmail, extractString } from "./controllerUtils.js";
import { IIncidentService } from "@/domain/incidents/incident.service.js";
import { getIncidentsByTeamQueryValidation, getIncidentSummaryQueryValidation } from "@/api/validation/incidentValidation.js";
@@ -7,10 +8,10 @@ import { getIncidentsByTeamQueryValidation, getIncidentSummaryQueryValidation }
const SERVICE_NAME = "IncidentController";
export interface IIncidentController {
getIncidentsByTeam: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getIncidentSummary: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getIncidentById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
resolveIncidentManually: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getIncidentsByTeam: RequestHandler;
getIncidentSummary: RequestHandler;
getIncidentById: RequestHandler;
resolveIncidentManually: RequestHandler;
}
class IncidentController implements IIncidentController {
private incidentService: IIncidentService;
@@ -18,90 +19,74 @@ class IncidentController implements IIncidentController {
this.incidentService = incidentService;
}
getIncidentsByTeam = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedQuery = getIncidentsByTeamQueryValidation.parse(req.query);
getIncidentsByTeam = catchAsync(async (req: Request, res: Response) => {
const validatedQuery = getIncidentsByTeamQueryValidation.parse(req.query);
const teamId = requireTeamId(req.user?.teamId);
const result = await this.incidentService.getIncidentsByTeam(
teamId,
validatedQuery.sortOrder,
validatedQuery.dateRange,
validatedQuery.page,
validatedQuery.rowsPerPage,
validatedQuery.status,
validatedQuery.monitorId,
validatedQuery.resolutionType
);
const teamId = requireTeamId(req.user?.teamId);
const result = await this.incidentService.getIncidentsByTeam(
teamId,
validatedQuery.sortOrder,
validatedQuery.dateRange,
validatedQuery.page,
validatedQuery.rowsPerPage,
validatedQuery.status,
validatedQuery.monitorId,
validatedQuery.resolutionType
);
return res.status(200).json({
success: true,
msg: "Incidents retrieved successfully",
data: result,
});
} catch (error) {
next(error);
return res.status(200).json({
success: true,
msg: "Incidents retrieved successfully",
data: result,
});
});
getIncidentSummary = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const validatedQuery = getIncidentSummaryQueryValidation.parse(req.query);
const summary = await this.incidentService.getIncidentSummary(teamId, validatedQuery.limit);
return res.status(200).json({
success: true,
msg: "Incident summary retrieved successfully",
data: summary,
});
});
getIncidentById = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const incidentId = req.params.incidentId as string;
if (!incidentId) {
throw new AppError({ message: "Incident ID is required", service: SERVICE_NAME, status: 400 });
}
};
getIncidentSummary = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const validatedQuery = getIncidentSummaryQueryValidation.parse(req.query);
const incident = await this.incidentService.getIncidentById(incidentId, teamId);
const summary = await this.incidentService.getIncidentSummary(teamId, validatedQuery.limit);
return res.status(200).json({
success: true,
msg: "Incident summary retrieved successfully",
data: summary,
});
} catch (error) {
next(error);
return res.status(200).json({
success: true,
msg: "Incident retrieved successfully",
data: incident,
});
});
resolveIncidentManually = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const userId = requireUserId(req.user?.id);
const userEmail = requireUserEmail(req.user?.email);
const incidentId = extractString(req.params?.incidentId);
if (!incidentId) {
throw new AppError({ message: "Incident ID is required", service: SERVICE_NAME, status: 400 });
}
};
getIncidentById = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const incidentId = req.params.incidentId as string;
if (!incidentId) {
throw new AppError({ message: "Incident ID is required", service: SERVICE_NAME, status: 400 });
}
const comment = extractString(req.body?.comment);
const resolvedIncident = await this.incidentService.resolveIncident(incidentId, userId, teamId, comment, userEmail);
const incident = await this.incidentService.getIncidentById(incidentId, teamId);
return res.status(200).json({
success: true,
msg: "Incident retrieved successfully",
data: incident,
});
} catch (error) {
next(error);
}
};
resolveIncidentManually = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const userId = requireUserId(req.user?.id);
const userEmail = requireUserEmail(req.user?.email);
const incidentId = extractString(req.params?.incidentId);
if (!incidentId) {
throw new AppError({ message: "Incident ID is required", service: SERVICE_NAME, status: 400 });
}
const comment = extractString(req.body?.comment);
const resolvedIncident = await this.incidentService.resolveIncident(incidentId, userId, teamId, comment, userEmail);
return res.status(200).json({
success: true,
msg: "Incident resolved successfully",
data: resolvedIncident,
});
} catch (error) {
next(error);
}
};
return res.status(200).json({
success: true,
msg: "Incident resolved successfully",
data: resolvedIncident,
});
});
}
export default IncidentController;
+45 -56
View File
@@ -1,13 +1,14 @@
import { Request, Response, NextFunction } from "express";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/api/validation/authValidation.js";
import { requireFirstName, requireTeamId, requireUserRoles } from "@/api/controllers/controllerUtils.js";
import { IInviteService } from "@/domain/invites/invite.service.js";
const SERVICE_NAME = "inviteController";
export interface IInviteController {
getInviteToken: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
sendInviteEmail: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
verifyInviteToken: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getInviteToken: RequestHandler;
sendInviteEmail: RequestHandler;
verifyInviteToken: RequestHandler;
}
class InviteController implements IInviteController {
@@ -21,62 +22,50 @@ class InviteController implements IInviteController {
return InviteController.SERVICE_NAME;
}
getInviteToken = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const userRoles = requireUserRoles(req.user?.role);
const invite = req.body;
invite.teamId = teamId;
inviteBodyValidation.parse(invite);
const inviteToken = await this.inviteService.getInviteToken({ invite, teamId, userRoles });
return res.status(200).json({
success: true,
msg: "Invite token generated successfully",
data: inviteToken,
});
} catch (error) {
next(error);
}
};
getInviteToken = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const userRoles = requireUserRoles(req.user?.role);
const invite = req.body;
invite.teamId = teamId;
inviteBodyValidation.parse(invite);
const inviteToken = await this.inviteService.getInviteToken({ invite, teamId, userRoles });
return res.status(200).json({
success: true,
msg: "Invite token generated successfully",
data: inviteToken,
});
});
sendInviteEmail = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const userRoles = requireUserRoles(req.user?.role);
const firstName = requireFirstName(req.user?.firstName);
sendInviteEmail = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const userRoles = requireUserRoles(req.user?.role);
const firstName = requireFirstName(req.user?.firstName);
const inviteRequest = req.body;
inviteRequest.teamId = teamId;
inviteBodyValidation.parse(inviteRequest);
const inviteRequest = req.body;
inviteRequest.teamId = teamId;
inviteBodyValidation.parse(inviteRequest);
const inviteToken = await this.inviteService.sendInviteEmail({
invite: inviteRequest,
firstName,
userRoles,
});
return res.status(200).json({
success: true,
msg: "Invite issued successfully",
data: inviteToken,
});
} catch (error) {
next(error);
}
};
const inviteToken = await this.inviteService.sendInviteEmail({
invite: inviteRequest,
firstName,
userRoles,
});
return res.status(200).json({
success: true,
msg: "Invite issued successfully",
data: inviteToken,
});
});
verifyInviteToken = async (req: Request, res: Response, next: NextFunction) => {
try {
inviteVerificationBodyValidation.parse(req.body);
const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token });
return res.status(200).json({
success: true,
msg: "Invite verified successfully",
data: invite,
});
} catch (error) {
next(error);
}
};
verifyInviteToken = catchAsync(async (req: Request, res: Response) => {
inviteVerificationBodyValidation.parse(req.body);
const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token });
return res.status(200).json({
success: true,
msg: "Invite verified successfully",
data: invite,
});
});
}
export default InviteController;
+11 -14
View File
@@ -1,10 +1,11 @@
import { ILogger } from "@/utils/logger.js";
import { Request, Response, NextFunction } from "express";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
const SERVICE_NAME = "LogController";
export interface ILogController {
getLogs: (req: Request, res: Response, next: NextFunction) => Promise<void>;
getLogs: RequestHandler;
}
class LogController {
@@ -17,17 +18,13 @@ class LogController {
return LogController.SERVICE_NAME;
}
getLogs = async (req: Request, res: Response, next: NextFunction) => {
try {
const logs = this.logger.getLogs();
res.status(200).json({
success: true,
msg: "Logs fetched successfully",
data: logs,
});
} catch (error) {
next(error);
}
};
getLogs = catchAsync(async (req: Request, res: Response) => {
const logs = this.logger.getLogs();
res.status(200).json({
success: true,
msg: "Logs fetched successfully",
data: logs,
});
});
}
export default LogController;
@@ -1,4 +1,5 @@
import { Request, Response, NextFunction } from "express";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
import {
createMaintenanceWindowBodyValidation,
editMaintenanceWindowByIdParamValidation,
@@ -12,12 +13,12 @@ import { requireTeamId } from "@/api/controllers/controllerUtils.js";
import { IMaintenanceWindowService } from "@/domain/maintenance-windows/maintenance-window.service.js";
export interface IMaintenanceWindowController {
createMaintenanceWindows: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getMaintenanceWindowById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getMaintenanceWindowsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getMaintenanceWindowsByMonitorId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
deleteMaintenanceWindow: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
editMaintenanceWindow: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
createMaintenanceWindows: RequestHandler;
getMaintenanceWindowById: RequestHandler;
getMaintenanceWindowsByTeamId: RequestHandler;
getMaintenanceWindowsByMonitorId: RequestHandler;
deleteMaintenanceWindow: RequestHandler;
editMaintenanceWindow: RequestHandler;
}
class MaintenanceWindowController implements IMaintenanceWindowController {
private maintenanceWindowService: IMaintenanceWindowService;
@@ -25,131 +26,107 @@ class MaintenanceWindowController implements IMaintenanceWindowController {
this.maintenanceWindowService = maintenanceWindowService;
}
createMaintenanceWindows = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedBody = createMaintenanceWindowBodyValidation.parse(req.body);
const teamId = requireTeamId(req?.user?.teamId);
createMaintenanceWindows = catchAsync(async (req: Request, res: Response) => {
const validatedBody = createMaintenanceWindowBodyValidation.parse(req.body);
const teamId = requireTeamId(req?.user?.teamId);
const monitorIDs = validatedBody.monitors;
const name = validatedBody.name;
const active = validatedBody.active ?? true;
const duration = validatedBody.duration;
const durationUnit = validatedBody.durationUnit;
const repeat = validatedBody.repeat;
const start = validatedBody.start;
const end = validatedBody.end;
const monitorIDs = validatedBody.monitors;
const name = validatedBody.name;
const active = validatedBody.active ?? true;
const duration = validatedBody.duration;
const durationUnit = validatedBody.durationUnit;
const repeat = validatedBody.repeat;
const start = validatedBody.start;
const end = validatedBody.end;
await this.maintenanceWindowService.createMaintenanceWindow({ teamId, monitorIDs, name, active, duration, durationUnit, repeat, start, end });
await this.maintenanceWindowService.createMaintenanceWindow({ teamId, monitorIDs, name, active, duration, durationUnit, repeat, start, end });
return res.status(200).json({
success: true,
msg: "Maintenance window created successfully",
});
} catch (error) {
next(error);
}
};
getMaintenanceWindowById = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = getMaintenanceWindowByIdParamValidation.parse(req.params);
return res.status(200).json({
success: true,
msg: "Maintenance window created successfully",
});
});
getMaintenanceWindowById = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getMaintenanceWindowByIdParamValidation.parse(req.params);
const teamId = requireTeamId(req.user?.teamId);
const teamId = requireTeamId(req.user?.teamId);
const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: validatedParams.id, teamId });
const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: validatedParams.id, teamId });
return res.status(200).json({
success: true,
msg: "Maintenance window fetched successfully",
data: maintenanceWindow,
});
} catch (error) {
next(error);
}
};
return res.status(200).json({
success: true,
msg: "Maintenance window fetched successfully",
data: maintenanceWindow,
});
});
getMaintenanceWindowsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedQuery = getMaintenanceWindowsByTeamIdQueryValidation.parse(req.query);
getMaintenanceWindowsByTeamId = catchAsync(async (req: Request, res: Response) => {
const validatedQuery = getMaintenanceWindowsByTeamIdQueryValidation.parse(req.query);
const teamId = requireTeamId(req?.user?.teamId);
const teamId = requireTeamId(req?.user?.teamId);
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({
teamId,
active: validatedQuery.active,
page: validatedQuery.page,
rowsPerPage: validatedQuery.rowsPerPage,
field: validatedQuery.field,
order: validatedQuery.order,
});
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({
teamId,
active: validatedQuery.active,
page: validatedQuery.page,
rowsPerPage: validatedQuery.rowsPerPage,
field: validatedQuery.field,
order: validatedQuery.order,
});
return res.status(200).json({
success: true,
msg: "Maintenance windows fetched successfully",
data: maintenanceWindows,
});
} catch (error) {
next(error);
}
};
return res.status(200).json({
success: true,
msg: "Maintenance windows fetched successfully",
data: maintenanceWindows,
});
});
getMaintenanceWindowsByMonitorId = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = getMaintenanceWindowsByMonitorIdParamValidation.parse(req.params);
getMaintenanceWindowsByMonitorId = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getMaintenanceWindowsByMonitorIdParamValidation.parse(req.params);
const teamId = requireTeamId(req?.user?.teamId);
const teamId = requireTeamId(req?.user?.teamId);
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({
monitorId: validatedParams.monitorId,
teamId,
});
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({
monitorId: validatedParams.monitorId,
teamId,
});
return res.status(200).json({
success: true,
msg: "Maintenance windows fetched successfully",
data: maintenanceWindows,
});
} catch (error) {
next(error);
}
};
deleteMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = deleteMaintenanceWindowByIdParamValidation.parse(req.params);
return res.status(200).json({
success: true,
msg: "Maintenance windows fetched successfully",
data: maintenanceWindows,
});
});
deleteMaintenanceWindow = catchAsync(async (req: Request, res: Response) => {
const validatedParams = deleteMaintenanceWindowByIdParamValidation.parse(req.params);
const teamId = requireTeamId(req?.user?.teamId);
const teamId = requireTeamId(req?.user?.teamId);
await this.maintenanceWindowService.deleteMaintenanceWindow({ id: validatedParams.id, teamId });
await this.maintenanceWindowService.deleteMaintenanceWindow({ id: validatedParams.id, teamId });
return res.status(200).json({
success: true,
msg: "Maintenance window deleted successfully",
});
} catch (error) {
next(error);
}
};
return res.status(200).json({
success: true,
msg: "Maintenance window deleted successfully",
});
});
editMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = editMaintenanceWindowByIdParamValidation.parse(req.params);
const validatedBody = editMaintenanceByIdWindowBodyValidation.parse(req.body);
editMaintenanceWindow = catchAsync(async (req: Request, res: Response) => {
const validatedParams = editMaintenanceWindowByIdParamValidation.parse(req.params);
const validatedBody = editMaintenanceByIdWindowBodyValidation.parse(req.body);
const teamId = requireTeamId(req.user?.teamId);
const teamId = requireTeamId(req.user?.teamId);
const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({
id: validatedParams.id,
body: validatedBody,
teamId,
});
return res.status(200).json({
success: true,
msg: "Maintenance window edited successfully",
data: editedMaintenanceWindow,
});
} catch (error) {
next(error);
}
};
const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({
id: validatedParams.id,
body: validatedBody,
teamId,
});
return res.status(200).json({
success: true,
msg: "Maintenance window edited successfully",
data: editedMaintenanceWindow,
});
});
}
export default MaintenanceWindowController;
+356 -435
View File
@@ -1,4 +1,5 @@
import { Request, Response, NextFunction } from "express";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
import { updateNotificationsValidation } from "@/api/validation/notificationValidation.js";
import {
getMonitorByIdParamValidation,
@@ -25,26 +26,26 @@ import { INotificationsService } from "@/domain/notifications/notification.servi
const SERVICE_NAME = "monitorController";
export interface IMonitorController {
getMonitorCertificate: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getUptimeDetailsById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getHardwareDetailsById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getPageSpeedDetailsById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getGeoChecksByMonitorId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getMonitorById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
createMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
importMonitorsFromJSON: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
deleteMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
deleteAllMonitors: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
editMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
pauseMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
bulkPauseMonitors: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
addDemoMonitors: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getMonitorsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getMonitorsWithChecksByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
exportMonitorsToJSON: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getAllGames: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getGroupsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
updateNotifications: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getMonitorCertificate: RequestHandler;
getUptimeDetailsById: RequestHandler;
getHardwareDetailsById: RequestHandler;
getPageSpeedDetailsById: RequestHandler;
getGeoChecksByMonitorId: RequestHandler;
getMonitorById: RequestHandler;
createMonitor: RequestHandler;
importMonitorsFromJSON: RequestHandler;
deleteMonitor: RequestHandler;
deleteAllMonitors: RequestHandler;
editMonitor: RequestHandler;
pauseMonitor: RequestHandler;
bulkPauseMonitors: RequestHandler;
addDemoMonitors: RequestHandler;
getMonitorsByTeamId: RequestHandler;
getMonitorsWithChecksByTeamId: RequestHandler;
exportMonitorsToJSON: RequestHandler;
getAllGames: RequestHandler;
getGroupsByTeamId: RequestHandler;
updateNotifications: RequestHandler;
}
class MonitorController implements IMonitorController {
static SERVICE_NAME = SERVICE_NAME;
@@ -61,430 +62,350 @@ class MonitorController implements IMonitorController {
return MonitorController.SERVICE_NAME;
}
getMonitorCertificate = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = getCertificateParamValidation.parse(req.params);
const teamId = requireTeamId(req.user?.teamId);
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: validatedParams.monitorId });
const certificate = await fetchMonitorCertificate(sslChecker, monitor);
getMonitorCertificate = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getCertificateParamValidation.parse(req.params);
const teamId = requireTeamId(req.user?.teamId);
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: validatedParams.monitorId });
const certificate = await fetchMonitorCertificate(sslChecker, monitor);
return res.status(200).json({
success: true,
msg: "SSL certificate retrieved successfully",
data: {
certificateDate: new Date(certificate.validTo),
},
});
} catch (error) {
next(error);
return res.status(200).json({
success: true,
msg: "SSL certificate retrieved successfully",
data: {
certificateDate: new Date(certificate.validTo),
},
});
});
getUptimeDetailsById = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getUptimeDetailsByIdParamValidation.parse(req.params);
const validatedQuery = getUptimeDetailsByIdQueryValidation.parse(req.query);
const monitorId = validatedParams.monitorId;
const dateRange = validatedQuery.dateRange;
const teamId = requireTeamId(req.user?.teamId);
const data = await this.monitorService.getUptimeDetailsById({
teamId,
monitorId,
dateRange,
});
return res.status(200).json({
success: true,
msg: "Uptime details retrieved successfully",
data: data,
});
});
getHardwareDetailsById = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getHardwareDetailsByIdParamValidation.parse(req.params);
const validatedQuery = getHardwareDetailsByIdQueryValidation.parse(req.query);
const monitorId = validatedParams.monitorId;
const dateRange = validatedQuery.dateRange || "recent";
const teamId = requireTeamId(req.user?.teamId);
const data = await this.monitorService.getHardwareDetailsById({
teamId,
monitorId,
dateRange,
});
return res.status(200).json({
success: true,
msg: "Hardware details retrieved successfully",
data: data,
});
});
getPageSpeedDetailsById = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getHardwareDetailsByIdParamValidation.parse(req.params);
const validatedQuery = getHardwareDetailsByIdQueryValidation.parse(req.query);
const monitorId = validatedParams.monitorId;
const dateRange = validatedQuery.dateRange || "recent";
const teamId = requireTeamId(req.user?.teamId);
const data = await this.monitorService.getPageSpeedDetailsById({
teamId,
monitorId,
dateRange,
});
return res.status(200).json({
success: true,
msg: "Page speed details retrieved successfully",
data,
});
});
getGeoChecksByMonitorId = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
const validatedQuery = getMonitorByIdQueryValidation.parse(req.query);
const monitorId = validatedParams.monitorId;
const dateRange = validatedQuery.dateRange || "recent";
const continentParam = validatedQuery.continent;
const continents = continentParam ? (Array.isArray(continentParam) ? continentParam : [continentParam]) : undefined;
const teamId = requireTeamId(req.user?.teamId);
const data = await this.monitorService.getGeoChecksByMonitorId({
teamId,
monitorId,
dateRange,
continents,
});
return res.status(200).json({
success: true,
msg: "Geo checks retrieved successfully",
data,
});
});
getMonitorById = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
const teamId = requireTeamId(req.user?.teamId);
const monitorId = validatedParams.monitorId;
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
return res.status(200).json({
success: true,
msg: "Monitor retrieved successfully",
data: monitor,
});
});
createMonitor = catchAsync(async (req: Request, res: Response) => {
const validatedBody = createMonitorBodyValidation.parse(req.body);
const userId = requireUserId(req.user?.id);
const teamId = requireTeamId(req.user?.teamId);
const monitor = await this.monitorService.createMonitor(teamId, userId, validatedBody);
return res.status(200).json({
success: true,
msg: "Monitor created successfully",
data: monitor,
});
});
importMonitorsFromJSON = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const userId = requireUserId(req.user?.id);
const validatedBody = importMonitorsBodyValidation.parse(req.body);
const monitors = validatedBody.monitors;
const result = await this.monitorService.importMonitorsFromJSON({ teamId, userId, monitors });
return res.status(200).json({
success: true,
msg: `Successfully imported ${result.imported} monitor(s)`,
data: result,
});
});
deleteMonitor = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
const teamId = requireTeamId(req.user?.teamId);
const monitorId = validatedParams.monitorId;
const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
return res.status(200).json({
success: true,
msg: "Monitor deleted successfully",
data: deletedMonitor,
});
});
deleteAllMonitors = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
return res.status(200).json({
success: true,
msg: `Deleted ${deletedCount} monitors`,
});
});
editMonitor = catchAsync(async (req: Request, res: Response) => {
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
const validatedBody = editMonitorBodyValidation.parse(req.body);
const monitorId = validatedParams.monitorId;
const teamId = requireTeamId(req.user?.teamId);
const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: validatedBody });
return res.status(200).json({
success: true,
msg: "Monitor edited successfully",
data: editedMonitor,
});
});
pauseMonitor = catchAsync(async (req: Request, res: Response) => {
const validatedParams = pauseMonitorParamValidation.parse(req.params);
const monitorId = validatedParams.monitorId;
const teamId = requireTeamId(req.user?.teamId);
const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
return res.status(200).json({
success: true,
msg: monitor.isActive ? "Monitor resumed successfully" : "Monitor paused successfully",
data: monitor,
});
});
bulkPauseMonitors = catchAsync(async (req: Request, res: Response) => {
const validatedBody = bulkPauseMonitorBodyValidation.parse(req.body);
const { monitorIds, pause } = validatedBody;
const teamId = requireTeamId(req.user?.teamId);
const { monitors, failedCount } = await this.monitorService.bulkPauseMonitors({ teamId, monitorIds, pause });
const action = pause ? "paused" : "resumed";
const monitorStr = monitors.length === 1 ? "monitor" : "monitors";
let msg = `${monitors.length} ${monitorStr} ${action} successfully`;
if (failedCount > 0) {
msg = `${monitors.length} ${monitorStr} ${action} in database, but ${failedCount} failed to sync with the job queue. Please check logs.`;
}
};
getUptimeDetailsById = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = getUptimeDetailsByIdParamValidation.parse(req.params);
const validatedQuery = getUptimeDetailsByIdQueryValidation.parse(req.query);
return res.status(200).json({
success: true,
msg,
data: { monitors, failedCount },
});
});
const monitorId = validatedParams.monitorId;
const dateRange = validatedQuery.dateRange;
addDemoMonitors = catchAsync(async (req: Request, res: Response) => {
const id = requireUserId(req.user?.id);
const teamId = requireTeamId(req.user?.teamId);
const demoMonitors = await this.monitorService.addDemoMonitors({ userId: id, teamId });
const teamId = requireTeamId(req.user?.teamId);
return res.status(200).json({
success: true,
msg: "Demo monitors added successfully",
data: demoMonitors?.length ?? 0,
});
});
const data = await this.monitorService.getUptimeDetailsById({
teamId,
monitorId,
dateRange,
getMonitorsByTeamId = catchAsync(async (req: Request, res: Response) => {
const validatedQuery = getMonitorsByTeamIdQueryValidation.parse(req.query);
const teamId = requireTeamId(req.user?.teamId);
const type = validatedQuery.type;
const tags = validatedQuery.tags;
const filter = validatedQuery.filter;
const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, type, tags, filter });
return res.status(200).json({
success: true,
msg: "Monitors retrieved successfully",
data: monitors,
});
});
getMonitorsWithChecksByTeamId = catchAsync(async (req: Request, res: Response) => {
const validatedQuery = getMonitorsWithChecksQueryValidation.parse(req.query);
const limit = validatedQuery.limit;
const page = validatedQuery.page;
const rowsPerPage = validatedQuery.rowsPerPage;
const filter = validatedQuery.filter;
const field = validatedQuery.field;
const order = validatedQuery.order;
const type = validatedQuery.type;
const tags = validatedQuery.tags;
const teamId = requireTeamId(req.user?.teamId);
const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
teamId,
limit,
type,
tags,
page,
rowsPerPage,
filter,
field,
order,
});
return res.status(200).json({
msg: "Monitors retrieved successfully",
data: monitors,
});
});
exportMonitorsToJSON = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const json = await this.monitorService.exportMonitorsToJSON({ teamId });
return res.status(200).json({
success: true,
msg: "Monitors exported successfully",
data: json,
});
});
getAllGames = catchAsync(async (req: Request, res: Response) => {
const games = this.monitorService.getAllGames();
return res.status(200).json({
success: true,
msg: "Supported games retrieved successfully",
data: games,
});
});
getGroupsByTeamId = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const groups = await this.monitorService.getGroupsByTeamId({ teamId });
return res.status(200).json({
msg: "Groups retrieved successfully",
data: groups,
});
});
updateNotifications = catchAsync(async (req: Request, res: Response) => {
updateNotificationsValidation.parse(req.body);
const teamId = requireTeamId(req.user?.teamId);
const { monitorIds, notificationIds, action } = req.body;
// Verify all requested notification IDs actually belong to this team
const teamNotifications = await this.notificationsService.findNotificationsByTeamId(teamId);
const validNotificationIds = teamNotifications.map((n) => n.id);
const invalidIds = notificationIds.filter((id: string) => !validNotificationIds.includes(id));
if (invalidIds.length > 0) {
throw new AppError({
message: `The following notification IDs are invalid or do not belong to your team: ${invalidIds.join(", ")}`,
status: 403,
});
return res.status(200).json({
success: true,
msg: "Uptime details retrieved successfully",
data: data,
});
} catch (error) {
next(error);
}
};
getHardwareDetailsById = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = getHardwareDetailsByIdParamValidation.parse(req.params);
const validatedQuery = getHardwareDetailsByIdQueryValidation.parse(req.query);
const modifiedCount = await this.monitorService.updateNotifications({
teamId,
monitorIds,
notificationIds,
action,
});
const monitorId = validatedParams.monitorId;
const dateRange = validatedQuery.dateRange || "recent";
const teamId = requireTeamId(req.user?.teamId);
const data = await this.monitorService.getHardwareDetailsById({
teamId,
monitorId,
dateRange,
});
return res.status(200).json({
success: true,
msg: "Hardware details retrieved successfully",
data: data,
});
} catch (error) {
next(error);
}
};
getPageSpeedDetailsById = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = getHardwareDetailsByIdParamValidation.parse(req.params);
const validatedQuery = getHardwareDetailsByIdQueryValidation.parse(req.query);
const monitorId = validatedParams.monitorId;
const dateRange = validatedQuery.dateRange || "recent";
const teamId = requireTeamId(req.user?.teamId);
const data = await this.monitorService.getPageSpeedDetailsById({
teamId,
monitorId,
dateRange,
});
return res.status(200).json({
success: true,
msg: "Page speed details retrieved successfully",
data,
});
} catch (error) {
next(error);
}
};
getGeoChecksByMonitorId = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
const validatedQuery = getMonitorByIdQueryValidation.parse(req.query);
const monitorId = validatedParams.monitorId;
const dateRange = validatedQuery.dateRange || "recent";
const continentParam = validatedQuery.continent;
const continents = continentParam ? (Array.isArray(continentParam) ? continentParam : [continentParam]) : undefined;
const teamId = requireTeamId(req.user?.teamId);
const data = await this.monitorService.getGeoChecksByMonitorId({
teamId,
monitorId,
dateRange,
continents,
});
return res.status(200).json({
success: true,
msg: "Geo checks retrieved successfully",
data,
});
} catch (error) {
next(error);
}
};
getMonitorById = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
const teamId = requireTeamId(req.user?.teamId);
const monitorId = validatedParams.monitorId;
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
return res.status(200).json({
success: true,
msg: "Monitor retrieved successfully",
data: monitor,
});
} catch (error) {
next(error);
}
};
createMonitor = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedBody = createMonitorBodyValidation.parse(req.body);
const userId = requireUserId(req.user?.id);
const teamId = requireTeamId(req.user?.teamId);
const monitor = await this.monitorService.createMonitor(teamId, userId, validatedBody);
return res.status(200).json({
success: true,
msg: "Monitor created successfully",
data: monitor,
});
} catch (error) {
next(error);
}
};
importMonitorsFromJSON = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const userId = requireUserId(req.user?.id);
const validatedBody = importMonitorsBodyValidation.parse(req.body);
const monitors = validatedBody.monitors;
const result = await this.monitorService.importMonitorsFromJSON({ teamId, userId, monitors });
return res.status(200).json({
success: true,
msg: `Successfully imported ${result.imported} monitor(s)`,
data: result,
});
} catch (error) {
next(error);
}
};
deleteMonitor = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
const teamId = requireTeamId(req.user?.teamId);
const monitorId = validatedParams.monitorId;
const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
return res.status(200).json({
success: true,
msg: "Monitor deleted successfully",
data: deletedMonitor,
});
} catch (error) {
next(error);
}
};
deleteAllMonitors = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
return res.status(200).json({
success: true,
msg: `Deleted ${deletedCount} monitors`,
});
} catch (error) {
next(error);
}
};
editMonitor = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
const validatedBody = editMonitorBodyValidation.parse(req.body);
const monitorId = validatedParams.monitorId;
const teamId = requireTeamId(req.user?.teamId);
const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: validatedBody });
return res.status(200).json({
success: true,
msg: "Monitor edited successfully",
data: editedMonitor,
});
} catch (error) {
next(error);
}
};
pauseMonitor = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedParams = pauseMonitorParamValidation.parse(req.params);
const monitorId = validatedParams.monitorId;
const teamId = requireTeamId(req.user?.teamId);
const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
return res.status(200).json({
success: true,
msg: monitor.isActive ? "Monitor resumed successfully" : "Monitor paused successfully",
data: monitor,
});
} catch (error) {
next(error);
}
};
bulkPauseMonitors = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedBody = bulkPauseMonitorBodyValidation.parse(req.body);
const { monitorIds, pause } = validatedBody;
const teamId = requireTeamId(req.user?.teamId);
const { monitors, failedCount } = await this.monitorService.bulkPauseMonitors({ teamId, monitorIds, pause });
const action = pause ? "paused" : "resumed";
const monitorStr = monitors.length === 1 ? "monitor" : "monitors";
let msg = `${monitors.length} ${monitorStr} ${action} successfully`;
if (failedCount > 0) {
msg = `${monitors.length} ${monitorStr} ${action} in database, but ${failedCount} failed to sync with the job queue. Please check logs.`;
}
return res.status(200).json({
success: true,
msg,
data: { monitors, failedCount },
});
} catch (error) {
next(error);
}
};
addDemoMonitors = async (req: Request, res: Response, next: NextFunction) => {
try {
const id = requireUserId(req.user?.id);
const teamId = requireTeamId(req.user?.teamId);
const demoMonitors = await this.monitorService.addDemoMonitors({ userId: id, teamId });
return res.status(200).json({
success: true,
msg: "Demo monitors added successfully",
data: demoMonitors?.length ?? 0,
});
} catch (error) {
next(error);
}
};
getMonitorsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedQuery = getMonitorsByTeamIdQueryValidation.parse(req.query);
const teamId = requireTeamId(req.user?.teamId);
const type = validatedQuery.type;
const tags = validatedQuery.tags;
const filter = validatedQuery.filter;
const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, type, tags, filter });
return res.status(200).json({
success: true,
msg: "Monitors retrieved successfully",
data: monitors,
});
} catch (error) {
next(error);
}
};
getMonitorsWithChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedQuery = getMonitorsWithChecksQueryValidation.parse(req.query);
const limit = validatedQuery.limit;
const page = validatedQuery.page;
const rowsPerPage = validatedQuery.rowsPerPage;
const filter = validatedQuery.filter;
const field = validatedQuery.field;
const order = validatedQuery.order;
const type = validatedQuery.type;
const tags = validatedQuery.tags;
const teamId = requireTeamId(req.user?.teamId);
const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
teamId,
limit,
type,
tags,
page,
rowsPerPage,
filter,
field,
order,
});
return res.status(200).json({
msg: "Monitors retrieved successfully",
data: monitors,
});
} catch (error) {
next(error);
}
};
exportMonitorsToJSON = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const json = await this.monitorService.exportMonitorsToJSON({ teamId });
return res.status(200).json({
success: true,
msg: "Monitors exported successfully",
data: json,
});
} catch (error) {
next(error);
}
};
getAllGames = async (req: Request, res: Response, next: NextFunction) => {
try {
const games = this.monitorService.getAllGames();
return res.status(200).json({
success: true,
msg: "Supported games retrieved successfully",
data: games,
});
} catch (error) {
next(error);
}
};
getGroupsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const groups = await this.monitorService.getGroupsByTeamId({ teamId });
return res.status(200).json({
msg: "Groups retrieved successfully",
data: groups,
});
} catch (error) {
next(error);
}
};
updateNotifications = async (req: Request, res: Response, next: NextFunction) => {
try {
updateNotificationsValidation.parse(req.body);
const teamId = requireTeamId(req.user?.teamId);
const { monitorIds, notificationIds, action } = req.body;
// Verify all requested notification IDs actually belong to this team
const teamNotifications = await this.notificationsService.findNotificationsByTeamId(teamId);
const validNotificationIds = teamNotifications.map((n) => n.id);
const invalidIds = notificationIds.filter((id: string) => !validNotificationIds.includes(id));
if (invalidIds.length > 0) {
throw new AppError({
message: `The following notification IDs are invalid or do not belong to your team: ${invalidIds.join(", ")}`,
status: 403,
});
}
const modifiedCount = await this.monitorService.updateNotifications({
teamId,
monitorIds,
notificationIds,
action,
});
return res.status(200).json({
success: true,
msg: `Notifications updated successfully on ${modifiedCount} monitor(s)`,
data: { modifiedCount },
});
} catch (error) {
next(error);
}
};
return res.status(200).json({
success: true,
msg: `Notifications updated successfully on ${modifiedCount} monitor(s)`,
data: { modifiedCount },
});
});
}
export default MonitorController;
@@ -1,4 +1,5 @@
import { Request, Response, NextFunction } from "express";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
import {
createNotificationBodyValidation,
@@ -16,13 +17,13 @@ import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interf
const SERVICE_NAME = "NotificationController";
export interface INotificationController {
testNotification: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
createNotification: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getNotificationsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
deleteNotification: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getNotificationById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
editNotification: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
testAllNotifications: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
testNotification: RequestHandler;
createNotification: RequestHandler;
getNotificationsByTeamId: RequestHandler;
deleteNotification: RequestHandler;
getNotificationById: RequestHandler;
editNotification: RequestHandler;
testAllNotifications: RequestHandler;
}
class NotificationController implements INotificationController {
private notificationsService: INotificationsService;
@@ -32,132 +33,104 @@ class NotificationController implements INotificationController {
this.monitorsRepository = monitorsRepository;
}
testNotification = async (req: Request, res: Response, next: NextFunction) => {
try {
const notification = testNotificationBodyValidation.parse(req.body);
const success = await this.notificationsService.sendTestNotification(notification);
testNotification = catchAsync(async (req: Request, res: Response) => {
const notification = testNotificationBodyValidation.parse(req.body);
const success = await this.notificationsService.sendTestNotification(notification);
return res.status(200).json({
success,
msg: success ? "Notification sent successfully" : "Notification could not be sent — check the destination details.",
details: { service: SERVICE_NAME },
});
} catch (error) {
next(error);
return res.status(200).json({
success,
msg: success ? "Notification sent successfully" : "Notification could not be sent — check the destination details.",
details: { service: SERVICE_NAME },
});
});
createNotification = catchAsync(async (req: Request, res: Response) => {
const validatedBody = createNotificationBodyValidation.parse(req.body);
const teamId = requireTeamId(req.user?.teamId);
const userId = requireUserId(req.user?.id);
const notification = await this.notificationsService.createNotification(validatedBody, userId, teamId);
return res.status(200).json({
success: true,
msg: "Notification created successfully",
data: notification,
});
});
getNotificationsByTeamId = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const notifications = await this.notificationsService.findNotificationsByTeamId(teamId);
return res.status(200).json({
success: true,
msg: "Notifications fetched successfully",
data: notifications,
});
});
deleteNotification = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const validatedParams = deleteNotificationParamValidation.parse(req.params);
await this.notificationsService.deleteById(validatedParams.id, teamId);
return res.status(200).json({
success: true,
msg: "Notification deleted successfully",
});
});
getNotificationById = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const validatedParams = getNotificationByIdParamValidation.parse(req.params);
const notification = await this.notificationsService.findById(validatedParams.id, teamId);
return res.status(200).json({
success: true,
msg: "Notification fetched successfully",
data: notification,
});
});
editNotification = catchAsync(async (req: Request, res: Response) => {
const validatedBody = createNotificationBodyValidation.parse(req.body);
const validatedParams = editNotificationParamValidation.parse(req.params);
const teamId = requireTeamId(req.user?.teamId);
const notificationId = validatedParams.id;
const editedNotification = await this.notificationsService.updateById(notificationId, teamId, validatedBody);
return res.status(200).json({
success: true,
msg: "Notification updated successfully",
data: editedNotification,
});
});
testAllNotifications = catchAsync(async (req: Request, res: Response) => {
const validatedBody = testAllNotificationsBodyValidation.parse(req.body);
const teamId = requireTeamId(req.user?.teamId);
const monitor = await this.monitorsRepository.findById(validatedBody.monitorId, teamId);
const notifications = monitor.notifications || [];
if (notifications.length === 0) {
throw new AppError({ message: "No notifications", status: 400 });
}
};
createNotification = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedBody = createNotificationBodyValidation.parse(req.body);
const result = await this.notificationsService.testAllNotifications(notifications);
const teamId = requireTeamId(req.user?.teamId);
const userId = requireUserId(req.user?.id);
const notification = await this.notificationsService.createNotification(validatedBody, userId, teamId);
return res.status(200).json({
success: true,
msg: "Notification created successfully",
data: notification,
});
} catch (error) {
next(error);
if (!result) {
throw new AppError({ message: "Failed to send all notifications", status: 500 });
}
};
getNotificationsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const notifications = await this.notificationsService.findNotificationsByTeamId(teamId);
return res.status(200).json({
success: true,
msg: "Notifications fetched successfully",
data: notifications,
});
} catch (error) {
next(error);
}
};
deleteNotification = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const validatedParams = deleteNotificationParamValidation.parse(req.params);
await this.notificationsService.deleteById(validatedParams.id, teamId);
return res.status(200).json({
success: true,
msg: "Notification deleted successfully",
});
} catch (error) {
next(error);
}
};
getNotificationById = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const validatedParams = getNotificationByIdParamValidation.parse(req.params);
const notification = await this.notificationsService.findById(validatedParams.id, teamId);
return res.status(200).json({
success: true,
msg: "Notification fetched successfully",
data: notification,
});
} catch (error) {
next(error);
}
};
editNotification = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedBody = createNotificationBodyValidation.parse(req.body);
const validatedParams = editNotificationParamValidation.parse(req.params);
const teamId = requireTeamId(req.user?.teamId);
const notificationId = validatedParams.id;
const editedNotification = await this.notificationsService.updateById(notificationId, teamId, validatedBody);
return res.status(200).json({
success: true,
msg: "Notification updated successfully",
data: editedNotification,
});
} catch (error) {
next(error);
}
};
testAllNotifications = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedBody = testAllNotificationsBodyValidation.parse(req.body);
const teamId = requireTeamId(req.user?.teamId);
const monitor = await this.monitorsRepository.findById(validatedBody.monitorId, teamId);
const notifications = monitor.notifications || [];
if (notifications.length === 0) {
throw new AppError({ message: "No notifications", status: 400 });
}
const result = await this.notificationsService.testAllNotifications(notifications);
if (!result) {
throw new AppError({ message: "Failed to send all notifications", status: 500 });
}
return res.status(200).json({
success: true,
msg: "All notifications sent successfully",
});
} catch (error) {
next(error);
}
};
return res.status(200).json({
success: true,
msg: "All notifications sent successfully",
});
});
}
export default NotificationController;
+41 -56
View File
@@ -1,14 +1,15 @@
import { IJobScheduler } from "@/worker/worker.interface.js";
import { getQueueJobsQueryValidation } from "@/api/validation/queueValidation.js";
import { Request, Response, NextFunction } from "express";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
const SERVICE_NAME = "JobQueueController";
export interface IJobQueueController {
getMetrics(req: Request, res: Response, next: NextFunction): Promise<void>;
getJobs(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
getAllMetrics(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
flushQueue(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
getMetrics: RequestHandler;
getJobs: RequestHandler;
getAllMetrics: RequestHandler;
flushQueue: RequestHandler;
}
class JobQueueController implements IJobQueueController {
@@ -19,59 +20,43 @@ class JobQueueController implements IJobQueueController {
return JobQueueController.SERVICE_NAME;
}
getMetrics = async (req: Request, res: Response, next: NextFunction) => {
try {
const metrics = await this.scheduler.getMetrics();
res.status(200).json({
success: true,
msg: "Queue metrics fetched successfully",
data: metrics,
});
} catch (error) {
next(error);
}
};
getMetrics = catchAsync(async (req: Request, res: Response) => {
const metrics = await this.scheduler.getMetrics();
res.status(200).json({
success: true,
msg: "Queue metrics fetched successfully",
data: metrics,
});
});
getJobs = async (req: Request, res: Response, next: NextFunction) => {
try {
const pagination = getQueueJobsQueryValidation.parse(req.query);
const { jobs, count } = await this.scheduler.getJobs(pagination);
return res.status(200).json({
success: true,
msg: "Queue jobs fetched successfully",
data: { jobs, count },
});
} catch (error) {
next(error);
}
};
getJobs = catchAsync(async (req: Request, res: Response) => {
const pagination = getQueueJobsQueryValidation.parse(req.query);
const { jobs, count } = await this.scheduler.getJobs(pagination);
return res.status(200).json({
success: true,
msg: "Queue jobs fetched successfully",
data: { jobs, count },
});
});
getAllMetrics = async (req: Request, res: Response, next: NextFunction) => {
try {
const pagination = getQueueJobsQueryValidation.parse(req.query);
const { jobs, count } = await this.scheduler.getJobs(pagination);
const metrics = await this.scheduler.getMetrics();
return res.status(200).json({
success: true,
msg: "Queue metrics fetched successfully",
data: { jobs, count, metrics },
});
} catch (error) {
next(error);
}
};
getAllMetrics = catchAsync(async (req: Request, res: Response) => {
const pagination = getQueueJobsQueryValidation.parse(req.query);
const { jobs, count } = await this.scheduler.getJobs(pagination);
const metrics = await this.scheduler.getMetrics();
return res.status(200).json({
success: true,
msg: "Queue metrics fetched successfully",
data: { jobs, count, metrics },
});
});
flushQueue = async (req: Request, res: Response, next: NextFunction) => {
try {
const result = await this.scheduler.flushQueues();
return res.status(200).json({
success: true,
msg: "Queue flushed successfully",
data: result,
});
} catch (error) {
next(error);
}
};
flushQueue = catchAsync(async (req: Request, res: Response) => {
const result = await this.scheduler.flushQueues();
return res.status(200).json({
success: true,
msg: "Queue flushed successfully",
data: result,
});
});
}
export default JobQueueController;
@@ -1,4 +1,5 @@
import { Request, Response, NextFunction } from "express";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
import { updateAppSettingsBodyValidation } from "@/api/validation/settingsValidation.js";
import { sendTestEmailBodyValidation } from "@/api/validation/notificationValidation.js";
import { AppError } from "@/utils/AppError.js";
@@ -10,9 +11,9 @@ const SERVICE_NAME = "SettingsController";
export interface ISettingsController {
serviceName: string;
getAppSettings(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
updateAppSettings(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
sendTestEmail(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
getAppSettings: RequestHandler;
updateAppSettings: RequestHandler;
sendTestEmail: RequestHandler;
}
class SettingsController implements ISettingsController {
@@ -50,94 +51,82 @@ class SettingsController implements ISettingsController {
return returnSettings;
};
getAppSettings = async (req: Request, res: Response, next: NextFunction) => {
try {
const dbSettings = await this.settingsService.getDBSettings();
getAppSettings = catchAsync(async (req: Request, res: Response) => {
const dbSettings = await this.settingsService.getDBSettings();
const returnSettings = this.buildAppSettings(dbSettings);
return res.status(200).json({
success: true,
msg: "App settings fetched successfully",
data: returnSettings,
});
} catch (error) {
next(error);
const returnSettings = this.buildAppSettings(dbSettings);
return res.status(200).json({
success: true,
msg: "App settings fetched successfully",
data: returnSettings,
});
});
updateAppSettings = catchAsync(async (req: Request, res: Response) => {
const validatedBody = updateAppSettingsBodyValidation.parse(req.body);
const updatedSettings = await this.settingsService.updateDbSettings(validatedBody);
const returnSettings = this.buildAppSettings(updatedSettings);
return res.status(200).json({
success: true,
msg: "App settings updated successfully",
data: returnSettings,
});
});
sendTestEmail = catchAsync(async (req: Request, res: Response) => {
sendTestEmailBodyValidation.parse(req.body);
const {
to,
systemEmailHost,
systemEmailPort,
systemEmailAddress,
systemEmailDisplayName,
systemEmailPassword,
systemEmailUser,
systemEmailConnectionHost,
systemEmailSecure,
systemEmailPool,
systemEmailIgnoreTLS,
systemEmailRequireTLS,
systemEmailRejectUnauthorized,
systemEmailTLSServername,
} = req.body;
const subject = "This is a test email from Checkmate";
const context = { testName: "Monitoring System" };
const html = await this.emailService.buildEmail("testEmailTemplate", context);
if (!html) {
throw new AppError({ message: "Failed to build email template.", status: 500 });
}
};
const messageId = await this.emailService.sendEmail(to, subject, html, {
systemEmailHost,
systemEmailPort,
systemEmailUser,
systemEmailAddress,
systemEmailDisplayName,
systemEmailPassword,
systemEmailConnectionHost,
systemEmailSecure,
systemEmailPool,
systemEmailIgnoreTLS,
systemEmailRequireTLS,
systemEmailRejectUnauthorized,
systemEmailTLSServername,
});
updateAppSettings = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedBody = updateAppSettingsBodyValidation.parse(req.body);
const updatedSettings = await this.settingsService.updateDbSettings(validatedBody);
const returnSettings = this.buildAppSettings(updatedSettings);
return res.status(200).json({
success: true,
msg: "App settings updated successfully",
data: returnSettings,
});
} catch (error) {
next(error);
if (!messageId) {
throw new AppError({ message: "Failed to send test email.", status: 500 });
}
};
sendTestEmail = async (req: Request, res: Response, next: NextFunction) => {
try {
sendTestEmailBodyValidation.parse(req.body);
const {
to,
systemEmailHost,
systemEmailPort,
systemEmailAddress,
systemEmailDisplayName,
systemEmailPassword,
systemEmailUser,
systemEmailConnectionHost,
systemEmailSecure,
systemEmailPool,
systemEmailIgnoreTLS,
systemEmailRequireTLS,
systemEmailRejectUnauthorized,
systemEmailTLSServername,
} = req.body;
const subject = "This is a test email from Checkmate";
const context = { testName: "Monitoring System" };
const html = await this.emailService.buildEmail("testEmailTemplate", context);
if (!html) {
throw new AppError({ message: "Failed to build email template.", status: 500 });
}
const messageId = await this.emailService.sendEmail(to, subject, html, {
systemEmailHost,
systemEmailPort,
systemEmailUser,
systemEmailAddress,
systemEmailDisplayName,
systemEmailPassword,
systemEmailConnectionHost,
systemEmailSecure,
systemEmailPool,
systemEmailIgnoreTLS,
systemEmailRequireTLS,
systemEmailRejectUnauthorized,
systemEmailTLSServername,
});
if (!messageId) {
throw new AppError({ message: "Failed to send test email.", status: 500 });
}
return res.status(200).json({
success: true,
msg: "Test email sent successfully",
data: { messageId },
});
} catch (error) {
next(error);
}
};
return res.status(200).json({
success: true,
msg: "Test email sent successfully",
data: { messageId },
});
});
}
export default SettingsController;
@@ -1,4 +1,5 @@
import { Request, Response, NextFunction } from "express";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
import {
createStatusPageBodyValidation,
@@ -16,12 +17,12 @@ const SERVICE_NAME = "statusPageController";
export interface IStatusPageController {
readonly serviceName: string;
createStatusPage(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
updateStatusPage(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
getStatusPageByUrl(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
resolveStatusPageByDomain(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
getStatusPagesByTeamId(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
deleteStatusPage(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
createStatusPage: RequestHandler;
updateStatusPage: RequestHandler;
getStatusPageByUrl: RequestHandler;
resolveStatusPageByDomain: RequestHandler;
getStatusPagesByTeamId: RequestHandler;
deleteStatusPage: RequestHandler;
}
class StatusPageController implements IStatusPageController {
@@ -35,132 +36,108 @@ class StatusPageController implements IStatusPageController {
return StatusPageController.SERVICE_NAME;
}
createStatusPage = async (req: Request, res: Response, next: NextFunction) => {
try {
createStatusPageBodyValidation.parse(req.body);
if (req.file) {
imageValidation.parse(req.file);
}
const teamId = requireTeamId(req?.user?.teamId);
const userId = requireUserId(req?.user?.id);
const statusPage = await this.statusPageService.createStatusPage(userId, teamId, req.file, req.body);
return res.status(200).json({
success: true,
msg: "Status page created successfully",
data: statusPage,
});
} catch (error) {
next(error);
createStatusPage = catchAsync(async (req: Request, res: Response) => {
createStatusPageBodyValidation.parse(req.body);
if (req.file) {
imageValidation.parse(req.file);
}
};
updateStatusPage = async (req: Request, res: Response, next: NextFunction) => {
try {
createStatusPageBodyValidation.parse(req.body);
if (req.file) {
imageValidation.parse(req.file);
}
const teamId = requireTeamId(req?.user?.teamId);
const userId = requireUserId(req?.user?.id);
const statusPage = await this.statusPageService.createStatusPage(userId, teamId, req.file, req.body);
const teamId = requireTeamId(req?.user?.teamId);
const statusPageId = req.params.id as string;
if (!statusPageId) {
throw new AppError({ message: "Status page ID is required", status: 400 });
}
const statusPage = await this.statusPageService.updateStatusPage(statusPageId, teamId, req.file, req.body);
if (statusPage === null) {
throw new AppError({ message: "Status page not found", status: 404 });
}
res.status(200).json({
success: true,
msg: "Status page updated successfully",
data: statusPage,
});
} catch (error) {
next(error);
return res.status(200).json({
success: true,
msg: "Status page created successfully",
data: statusPage,
});
});
updateStatusPage = catchAsync(async (req: Request, res: Response) => {
createStatusPageBodyValidation.parse(req.body);
if (req.file) {
imageValidation.parse(req.file);
}
};
getStatusPageByUrl = async (req: Request, res: Response, next: NextFunction) => {
try {
getStatusPageParamValidation.parse(req.params);
getStatusPageQueryValidation.parse(req.query);
if (!req.params.url) {
throw new AppError({ message: "Status page URL is required", status: 400 });
}
const statusPage = await this.statusPageService.getStatusPageByUrl(req.params.url as string);
const data = await this.statusPageService.getPublicStatusPagePayload(statusPage, req.user?.teamId);
return res.status(200).json({
success: true,
msg: "Status page retrieved successfully",
data,
});
} catch (error) {
next(error);
const teamId = requireTeamId(req?.user?.teamId);
const statusPageId = req.params.id as string;
if (!statusPageId) {
throw new AppError({ message: "Status page ID is required", status: 400 });
}
};
resolveStatusPageByDomain = async (req: Request, res: Response, next: NextFunction) => {
try {
resolveStatusPageQueryValidation.parse(req.query);
const domain = resolveStatusPageDomainFromRequest(req.hostname, req.query.domain as string | undefined);
if (!domain) {
throw new AppError({ message: "Domain is required", status: 400 });
}
const statusPage = await this.statusPageService.getStatusPageByCustomDomain(domain);
if (!statusPage.isPublished) {
throw new AppError({ message: "Status page not found", status: 404 });
}
const data = await this.statusPageService.getPublicStatusPagePayload(statusPage, req.user?.teamId);
return res.status(200).json({
success: true,
msg: "Status page retrieved successfully",
data,
});
} catch (error) {
next(error);
const statusPage = await this.statusPageService.updateStatusPage(statusPageId, teamId, req.file, req.body);
if (statusPage === null) {
throw new AppError({ message: "Status page not found", status: 404 });
}
};
res.status(200).json({
success: true,
msg: "Status page updated successfully",
data: statusPage,
});
});
getStatusPagesByTeamId = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const statusPages = await this.statusPageService.getStatusPagesByTeamId(teamId);
getStatusPageByUrl = catchAsync(async (req: Request, res: Response) => {
getStatusPageParamValidation.parse(req.params);
getStatusPageQueryValidation.parse(req.query);
return res.status(200).json({
success: true,
msg: "Status pages retrieved successfully",
data: statusPages,
});
} catch (error) {
next(error);
if (!req.params.url) {
throw new AppError({ message: "Status page URL is required", status: 400 });
}
};
deleteStatusPage = async (req: Request, res: Response, next: NextFunction) => {
try {
const statusPageId = req.params.id as string;
if (!statusPageId) {
throw new AppError({ message: "Status page ID is required", status: 400 });
}
const teamId = requireTeamId(req.user?.teamId);
await this.statusPageService.deleteStatusPage(statusPageId, teamId);
return res.status(200).json({
success: true,
msg: "Status page deleted successfully",
});
} catch (error) {
next(error);
const statusPage = await this.statusPageService.getStatusPageByUrl(req.params.url as string);
const data = await this.statusPageService.getPublicStatusPagePayload(statusPage, req.user?.teamId);
return res.status(200).json({
success: true,
msg: "Status page retrieved successfully",
data,
});
});
resolveStatusPageByDomain = catchAsync(async (req: Request, res: Response) => {
resolveStatusPageQueryValidation.parse(req.query);
const domain = resolveStatusPageDomainFromRequest(req.hostname, req.query.domain as string | undefined);
if (!domain) {
throw new AppError({ message: "Domain is required", status: 400 });
}
};
const statusPage = await this.statusPageService.getStatusPageByCustomDomain(domain);
if (!statusPage.isPublished) {
throw new AppError({ message: "Status page not found", status: 404 });
}
const data = await this.statusPageService.getPublicStatusPagePayload(statusPage, req.user?.teamId);
return res.status(200).json({
success: true,
msg: "Status page retrieved successfully",
data,
});
});
getStatusPagesByTeamId = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const statusPages = await this.statusPageService.getStatusPagesByTeamId(teamId);
return res.status(200).json({
success: true,
msg: "Status pages retrieved successfully",
data: statusPages,
});
});
deleteStatusPage = catchAsync(async (req: Request, res: Response) => {
const statusPageId = req.params.id as string;
if (!statusPageId) {
throw new AppError({ message: "Status page ID is required", status: 400 });
}
const teamId = requireTeamId(req.user?.teamId);
await this.statusPageService.deleteStatusPage(statusPageId, teamId);
return res.status(200).json({
success: true,
msg: "Status page deleted successfully",
});
});
}
export default StatusPageController;
+56 -75
View File
@@ -1,4 +1,5 @@
import { Request, Response, NextFunction } from "express";
import { Request, Response, RequestHandler } from "express";
import { catchAsync } from "@/utils/catchAsync.js";
import { ITagsService } from "@/domain/tags/tag.service.js";
import { requireTeamId } from "./controllerUtils.js";
import {
@@ -10,89 +11,69 @@ import {
} from "@/api/validation/index.js";
export interface ITagsController {
createTag: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getTagById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
getTagsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
editTag: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
deleteTag: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
createTag: RequestHandler;
getTagById: RequestHandler;
getTagsByTeamId: RequestHandler;
editTag: RequestHandler;
deleteTag: RequestHandler;
}
class TagsController implements ITagsController {
constructor(private tagsService: ITagsService) {}
createTag = async (req: Request, res: Response, next: NextFunction) => {
try {
const validatedBody = createTagBodyValidation.parse(req.body);
const teamId = requireTeamId(req.user?.teamId);
createTag = catchAsync(async (req: Request, res: Response) => {
const validatedBody = createTagBodyValidation.parse(req.body);
const teamId = requireTeamId(req.user?.teamId);
const tag = await this.tagsService.createTag(validatedBody, teamId);
return res.status(200).json({
success: true,
msg: "Tag created successfully",
data: tag,
});
} catch (error) {
next(error);
}
};
getTagById = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const { id: tagId } = getTagByIdParamValidation.parse(req.params);
const tag = await this.tagsService.getTag(tagId, teamId);
return res.status(200).json({
success: true,
msg: "Tag retrieved successfully",
data: tag,
});
} catch (error) {
next(error);
}
};
const tag = await this.tagsService.createTag(validatedBody, teamId);
return res.status(200).json({
success: true,
msg: "Tag created successfully",
data: tag,
});
});
getTagById = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const { id: tagId } = getTagByIdParamValidation.parse(req.params);
const tag = await this.tagsService.getTag(tagId, teamId);
return res.status(200).json({
success: true,
msg: "Tag retrieved successfully",
data: tag,
});
});
getTagsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const tags = await this.tagsService.getTagsByTeamId(teamId);
return res.status(200).json({
success: true,
msg: "Tags retrieved successfully",
data: tags,
});
} catch (error) {
next(error);
}
};
getTagsByTeamId = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const tags = await this.tagsService.getTagsByTeamId(teamId);
return res.status(200).json({
success: true,
msg: "Tags retrieved successfully",
data: tags,
});
});
editTag = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const { id: tagId } = editTagParamValidation.parse(req.params);
const validatedBody = editTagBodyValidation.parse(req.body);
const updatedTag = await this.tagsService.updateTag(tagId, teamId, validatedBody);
return res.status(200).json({
success: true,
msg: "Tag updated successfully",
data: updatedTag,
});
} catch (error) {
next(error);
}
};
editTag = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const { id: tagId } = editTagParamValidation.parse(req.params);
const validatedBody = editTagBodyValidation.parse(req.body);
const updatedTag = await this.tagsService.updateTag(tagId, teamId, validatedBody);
return res.status(200).json({
success: true,
msg: "Tag updated successfully",
data: updatedTag,
});
});
deleteTag = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = requireTeamId(req.user?.teamId);
const { id: tagId } = deleteTagParamValidation.parse(req.params);
await this.tagsService.deleteTag(tagId, teamId);
return res.status(200).json({
success: true,
msg: "Tag deleted successfully",
});
} catch (error) {
next(error);
}
};
deleteTag = catchAsync(async (req: Request, res: Response) => {
const teamId = requireTeamId(req.user?.teamId);
const { id: tagId } = deleteTagParamValidation.parse(req.params);
await this.tagsService.deleteTag(tagId, teamId);
return res.status(200).json({
success: true,
msg: "Tag deleted successfully",
});
});
}
export default TagsController;
+14
View File
@@ -0,0 +1,14 @@
import type { NextFunction, Request, RequestHandler, Response } from "express";
type AsyncRequestHandler = (req: Request, res: Response) => Promise<unknown>;
/**
* Wraps an async route handler so that any error it throws or any promise it
* rejects is passed to Express's error middleware (`handleErrors`). Every async controller method must be wrapped.
*/
export const catchAsync = (fn: AsyncRequestHandler): RequestHandler => {
const wrappedHandler = (req: Request, res: Response, next: NextFunction) => {
fn(req, res).catch(next);
};
return wrappedHandler;
};