From bd13e9d9c801965a7d5c057cb6f34dfc2c117994 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 15 Jul 2026 10:02:36 -0700 Subject: [PATCH] add catchAsync to handle errors, remove all try/catch blocks from contorllers --- server/src/api/controllers/authController.ts | 415 ++++----- server/src/api/controllers/checkController.ts | 173 ++-- .../api/controllers/diagnosticController.ts | 25 +- .../src/api/controllers/geoCheckController.ts | 47 +- .../src/api/controllers/incidentController.ts | 147 ++-- .../src/api/controllers/inviteController.ts | 101 +-- server/src/api/controllers/logController.ts | 25 +- .../maintenanceWindowController.ts | 201 ++--- .../src/api/controllers/monitorController.ts | 791 ++++++++---------- .../api/controllers/notificationController.ts | 229 +++-- server/src/api/controllers/queueController.ts | 97 +-- .../src/api/controllers/settingsController.ts | 163 ++-- .../api/controllers/statusPageController.ts | 219 +++-- server/src/api/controllers/tagController.ts | 131 ++- server/src/utils/catchAsync.ts | 14 + 15 files changed, 1243 insertions(+), 1535 deletions(-) create mode 100644 server/src/utils/catchAsync.ts diff --git a/server/src/api/controllers/authController.ts b/server/src/api/controllers/authController.ts index 45f806a8c..1eb992529 100755 --- a/server/src/api/controllers/authController.ts +++ b/server/src/api/controllers/authController.ts @@ -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; - createUser(req: Request, res: Response, next: NextFunction): Promise; - loginUser(req: Request, res: Response, next: NextFunction): Promise; - editUser(req: Request, res: Response, next: NextFunction): Promise; - checkSuperadminExists(req: Request, res: Response, next: NextFunction): Promise; - requestRecovery(req: Request, res: Response, next: NextFunction): Promise; - validateRecovery(req: Request, res: Response, next: NextFunction): Promise; - resetPassword(req: Request, res: Response, next: NextFunction): Promise; - deleteUser(req: Request, res: Response, next: NextFunction): Promise; - deleteUserById(req: Request, res: Response, next: NextFunction): Promise; - getAllUsers(req: Request, res: Response, next: NextFunction): Promise; - getUserById(req: Request, res: Response, next: NextFunction): Promise; - editUserById(req: Request, res: Response, next: NextFunction): Promise; - editUserPasswordById(req: Request, res: Response, next: NextFunction): Promise; + 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; diff --git a/server/src/api/controllers/checkController.ts b/server/src/api/controllers/checkController.ts index 23fbe2227..ccc91921d 100755 --- a/server/src/api/controllers/checkController.ts +++ b/server/src/api/controllers/checkController.ts @@ -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; - getChecksByTeam: (req: Request, res: Response, next: NextFunction) => Promise; - getChecksSummaryByTeamId: (req: Request, res: Response, next: NextFunction) => Promise; - deleteChecks: (req: Request, res: Response, next: NextFunction) => Promise; - deleteChecksByTeamId: (req: Request, res: Response, next: NextFunction) => Promise; + 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; diff --git a/server/src/api/controllers/diagnosticController.ts b/server/src/api/controllers/diagnosticController.ts index efc383e1a..0864f7237 100755 --- a/server/src/api/controllers/diagnosticController.ts +++ b/server/src/api/controllers/diagnosticController.ts @@ -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; + 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; diff --git a/server/src/api/controllers/geoCheckController.ts b/server/src/api/controllers/geoCheckController.ts index a50176188..81082b6ed 100644 --- a/server/src/api/controllers/geoCheckController.ts +++ b/server/src/api/controllers/geoCheckController.ts @@ -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; + 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; diff --git a/server/src/api/controllers/incidentController.ts b/server/src/api/controllers/incidentController.ts index 6358d2a89..f1998f16f 100644 --- a/server/src/api/controllers/incidentController.ts +++ b/server/src/api/controllers/incidentController.ts @@ -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; - getIncidentSummary: (req: Request, res: Response, next: NextFunction) => Promise; - getIncidentById: (req: Request, res: Response, next: NextFunction) => Promise; - resolveIncidentManually: (req: Request, res: Response, next: NextFunction) => Promise; + 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; diff --git a/server/src/api/controllers/inviteController.ts b/server/src/api/controllers/inviteController.ts index c77385f7b..0efc1e0a7 100755 --- a/server/src/api/controllers/inviteController.ts +++ b/server/src/api/controllers/inviteController.ts @@ -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; - sendInviteEmail: (req: Request, res: Response, next: NextFunction) => Promise; - verifyInviteToken: (req: Request, res: Response, next: NextFunction) => Promise; + 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; diff --git a/server/src/api/controllers/logController.ts b/server/src/api/controllers/logController.ts index 7d221c6c5..f0449bbf2 100644 --- a/server/src/api/controllers/logController.ts +++ b/server/src/api/controllers/logController.ts @@ -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; + 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; diff --git a/server/src/api/controllers/maintenanceWindowController.ts b/server/src/api/controllers/maintenanceWindowController.ts index ddbf4deca..49fa605ec 100644 --- a/server/src/api/controllers/maintenanceWindowController.ts +++ b/server/src/api/controllers/maintenanceWindowController.ts @@ -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; - getMaintenanceWindowById: (req: Request, res: Response, next: NextFunction) => Promise; - getMaintenanceWindowsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise; - getMaintenanceWindowsByMonitorId: (req: Request, res: Response, next: NextFunction) => Promise; - deleteMaintenanceWindow: (req: Request, res: Response, next: NextFunction) => Promise; - editMaintenanceWindow: (req: Request, res: Response, next: NextFunction) => Promise; + 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; diff --git a/server/src/api/controllers/monitorController.ts b/server/src/api/controllers/monitorController.ts index a0cb073d2..e0f820a62 100644 --- a/server/src/api/controllers/monitorController.ts +++ b/server/src/api/controllers/monitorController.ts @@ -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; - getUptimeDetailsById: (req: Request, res: Response, next: NextFunction) => Promise; - getHardwareDetailsById: (req: Request, res: Response, next: NextFunction) => Promise; - getPageSpeedDetailsById: (req: Request, res: Response, next: NextFunction) => Promise; - getGeoChecksByMonitorId: (req: Request, res: Response, next: NextFunction) => Promise; - getMonitorById: (req: Request, res: Response, next: NextFunction) => Promise; - createMonitor: (req: Request, res: Response, next: NextFunction) => Promise; - importMonitorsFromJSON: (req: Request, res: Response, next: NextFunction) => Promise; - deleteMonitor: (req: Request, res: Response, next: NextFunction) => Promise; - deleteAllMonitors: (req: Request, res: Response, next: NextFunction) => Promise; - editMonitor: (req: Request, res: Response, next: NextFunction) => Promise; - pauseMonitor: (req: Request, res: Response, next: NextFunction) => Promise; - bulkPauseMonitors: (req: Request, res: Response, next: NextFunction) => Promise; - addDemoMonitors: (req: Request, res: Response, next: NextFunction) => Promise; - getMonitorsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise; - getMonitorsWithChecksByTeamId: (req: Request, res: Response, next: NextFunction) => Promise; - exportMonitorsToJSON: (req: Request, res: Response, next: NextFunction) => Promise; - getAllGames: (req: Request, res: Response, next: NextFunction) => Promise; - getGroupsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise; - updateNotifications: (req: Request, res: Response, next: NextFunction) => Promise; + 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; diff --git a/server/src/api/controllers/notificationController.ts b/server/src/api/controllers/notificationController.ts index 3aecfcad8..53dd7645c 100644 --- a/server/src/api/controllers/notificationController.ts +++ b/server/src/api/controllers/notificationController.ts @@ -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; - createNotification: (req: Request, res: Response, next: NextFunction) => Promise; - getNotificationsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise; - deleteNotification: (req: Request, res: Response, next: NextFunction) => Promise; - getNotificationById: (req: Request, res: Response, next: NextFunction) => Promise; - editNotification: (req: Request, res: Response, next: NextFunction) => Promise; - testAllNotifications: (req: Request, res: Response, next: NextFunction) => Promise; + 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; diff --git a/server/src/api/controllers/queueController.ts b/server/src/api/controllers/queueController.ts index 0a21761ab..72c1463f9 100755 --- a/server/src/api/controllers/queueController.ts +++ b/server/src/api/controllers/queueController.ts @@ -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; - getJobs(req: Request, res: Response, next: NextFunction): Promise; - getAllMetrics(req: Request, res: Response, next: NextFunction): Promise; - flushQueue(req: Request, res: Response, next: NextFunction): Promise; + 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; diff --git a/server/src/api/controllers/settingsController.ts b/server/src/api/controllers/settingsController.ts index 4acfdf552..06085c3f8 100644 --- a/server/src/api/controllers/settingsController.ts +++ b/server/src/api/controllers/settingsController.ts @@ -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; - updateAppSettings(req: Request, res: Response, next: NextFunction): Promise; - sendTestEmail(req: Request, res: Response, next: NextFunction): Promise; + 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; diff --git a/server/src/api/controllers/statusPageController.ts b/server/src/api/controllers/statusPageController.ts index d820e5eed..f75c5e7fb 100644 --- a/server/src/api/controllers/statusPageController.ts +++ b/server/src/api/controllers/statusPageController.ts @@ -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; - updateStatusPage(req: Request, res: Response, next: NextFunction): Promise; - getStatusPageByUrl(req: Request, res: Response, next: NextFunction): Promise; - resolveStatusPageByDomain(req: Request, res: Response, next: NextFunction): Promise; - getStatusPagesByTeamId(req: Request, res: Response, next: NextFunction): Promise; - deleteStatusPage(req: Request, res: Response, next: NextFunction): Promise; + 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; diff --git a/server/src/api/controllers/tagController.ts b/server/src/api/controllers/tagController.ts index 95dd81296..abe042efa 100644 --- a/server/src/api/controllers/tagController.ts +++ b/server/src/api/controllers/tagController.ts @@ -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; - getTagById: (req: Request, res: Response, next: NextFunction) => Promise; - getTagsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise; - editTag: (req: Request, res: Response, next: NextFunction) => Promise; - deleteTag: (req: Request, res: Response, next: NextFunction) => Promise; + 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; diff --git a/server/src/utils/catchAsync.ts b/server/src/utils/catchAsync.ts new file mode 100644 index 000000000..3f207608b --- /dev/null +++ b/server/src/utils/catchAsync.ts @@ -0,0 +1,14 @@ +import type { NextFunction, Request, RequestHandler, Response } from "express"; +type AsyncRequestHandler = (req: Request, res: Response) => Promise; + +/** + * 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; +};