add an error service, base controller

This commit is contained in:
Alex Holliday committed 2025-07-24 15:06:46 -07:00
1 parent 0cfe9d60fb
commit ddf3e8251d
20 files changed
+412 -301

No files matched your search

+16 -14
View File
@@ -1,3 +1,4 @@
import BaseController from "./baseController.js";
import {
registrationBodyValidation,
loginValidation,
@@ -10,7 +11,6 @@ import {
editUserByIdBodyValidation,
editSuperadminUserByIdBodyValidation,
} from "../validation/joi.js";
import { asyncHandler, createError } from "../utils/errorUtils.js";
const SERVICE_NAME = "authController";
@@ -23,7 +23,7 @@ const SERVICE_NAME = "authController";
* @class AuthController
* @description Manages user authentication and authorization operations
*/
class AuthController {
class AuthController extends BaseController {
/**
* Creates an instance of AuthController.
*
@@ -36,7 +36,8 @@ class AuthController {
* @param {Object} dependencies.logger - Logger service
* @param {Object} dependencies.userService - User business logic service
*/
constructor({ db, settingsService, emailService, jobQueue, stringService, logger, userService }) {
constructor({ db, settingsService, emailService, jobQueue, stringService, logger, userService, errorService }) {
super();
this.db = db;
this.settingsService = settingsService;
this.emailService = emailService;
@@ -44,6 +45,7 @@ class AuthController {
this.stringService = stringService;
this.logger = logger;
this.userService = userService;
this.errorService = errorService;
}
/**
@@ -85,7 +87,7 @@ class AuthController {
* "inviteToken": "abc123..."
* }
*/
registerUser = asyncHandler(
registerUser = this.asyncHandler(
async (req, res) => {
if (req.body?.email) {
req.body.email = req.body.email?.toLowerCase();
@@ -121,7 +123,7 @@ class AuthController {
* "password": "SecurePass123!"
* }
*/
loginUser = asyncHandler(
loginUser = this.asyncHandler(
async (req, res) => {
if (req.body?.email) {
req.body.email = req.body.email?.toLowerCase();
@@ -173,7 +175,7 @@ class AuthController {
* "newPassword": "NewPass123!"
* }
*/
editUser = asyncHandler(
editUser = this.asyncHandler(
async (req, res) => {
await editUserBodyValidation.validateAsync(req.body);
@@ -200,7 +202,7 @@ class AuthController {
* GET /auth/users/superadmin
* // Response: { "data": true } or { "data": false }
*/
checkSuperadminExists = asyncHandler(
checkSuperadminExists = this.asyncHandler(
async (req, res) => {
const superAdminExists = await this.userService.checkSuperadminExists();
return res.success({
@@ -230,7 +232,7 @@ class AuthController {
* "email": "john@example.com"
* }
*/
requestRecovery = asyncHandler(
requestRecovery = this.asyncHandler(
async (req, res) => {
await recoveryValidation.validateAsync(req.body);
const email = req?.body?.email;
@@ -262,7 +264,7 @@ class AuthController {
* "recoveryToken": "abc123..."
* }
*/
validateRecovery = asyncHandler(
validateRecovery = this.asyncHandler(
async (req, res) => {
await recoveryTokenBodyValidation.validateAsync(req.body);
await this.userService.validateRecovery(req.body.recoveryToken);
@@ -294,7 +296,7 @@ class AuthController {
* "recoveryToken": "abc123..."
* }
*/
resetPassword = asyncHandler(
resetPassword = this.asyncHandler(
async (req, res) => {
await newPasswordValidation.validateAsync(req.body);
const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken);
@@ -326,7 +328,7 @@ class AuthController {
* DELETE /auth/user
* // Requires JWT authentication
*/
deleteUser = asyncHandler(
deleteUser = this.asyncHandler(
async (req, res) => {
await this.userService.deleteUser(req.user);
return res.success({
@@ -350,7 +352,7 @@ class AuthController {
* GET /auth/users
* // Requires JWT authentication with admin/superadmin role
*/
getAllUsers = asyncHandler(
getAllUsers = this.asyncHandler(
async (req, res) => {
const allUsers = await this.userService.getAllUsers();
return res.success({
@@ -381,7 +383,7 @@ class AuthController {
* GET /auth/users/507f1f77bcf86cd799439011
* // Requires JWT authentication with superadmin role
*/
getUserById = asyncHandler(
getUserById = this.asyncHandler(
async (req, res) => {
await getUserByIdParamValidation.validateAsync(req.params);
const userId = req?.params?.userId;
@@ -431,7 +433,7 @@ class AuthController {
* }
* // Requires JWT authentication with superadmin role
*/
editUserById = asyncHandler(
editUserById = this.asyncHandler(
async (req, res) => {
const roles = req?.user?.role;
if (!roles.includes("superadmin")) {
+8
View File
@@ -0,0 +1,8 @@
import { asyncHandler } from "../service/infrastructure/errorService.js";
class BaseController {
constructor() {
this.asyncHandler = asyncHandler;
}
}
export default BaseController;
+14 -11
View File
@@ -1,3 +1,4 @@
import BaseController from "./baseController.js";
import {
getChecksParamValidation,
getChecksQueryValidation,
@@ -9,7 +10,6 @@ import {
ackAllChecksParamValidation,
ackAllChecksBodyValidation,
} from "../validation/joi.js";
import { asyncHandler } from "../utils/errorUtils.js";
const SERVICE_NAME = "checkController";
@@ -22,7 +22,7 @@ const SERVICE_NAME = "checkController";
* @class CheckController
* @description Manages check operations and monitoring data
*/
class CheckController {
class CheckController extends BaseController {
/**
* Creates an instance of CheckController.
*
@@ -31,12 +31,15 @@ class CheckController {
* @param {Object} dependencies.settingsService - Service for application settings
* @param {Object} dependencies.stringService - Service for string/localization
* @param {Object} dependencies.checkService - Check business logic service
* @param {Object} dependencies.errorService - Error service for error handling
*/
constructor({ db, settingsService, stringService, checkService }) {
constructor({ db, settingsService, stringService, checkService, errorService }) {
super();
this.db = db;
this.settingsService = settingsService;
this.stringService = stringService;
this.checkService = checkService;
this.errorService = errorService;
}
/**
@@ -67,7 +70,7 @@ class CheckController {
* GET /checks/monitor/507f1f77bcf86cd799439011?page=1&rowsPerPage=10&status=down
* // Requires JWT authentication
*/
getChecksByMonitor = asyncHandler(
getChecksByMonitor = this.asyncHandler(
async (req, res) => {
await getChecksParamValidation.validateAsync(req.params);
await getChecksQueryValidation.validateAsync(req.query);
@@ -109,7 +112,7 @@ class CheckController {
* GET /checks/team?page=1&rowsPerPage=20&status=down&ack=false
* // Requires JWT authentication
*/
getChecksByTeam = asyncHandler(
getChecksByTeam = this.asyncHandler(
async (req, res) => {
await getTeamChecksQueryValidation.validateAsync(req.query);
const checkData = await this.checkService.getChecksByTeam({
@@ -140,7 +143,7 @@ class CheckController {
* // Requires JWT authentication
* // Response includes counts by status, time ranges, etc.
*/
getChecksSummaryByTeamId = asyncHandler(
getChecksSummaryByTeamId = this.asyncHandler(
async (req, res) => {
const summary = await this.checkService.getChecksSummaryByTeamId({ teamId: req?.user?.teamId });
return res.success({
@@ -176,7 +179,7 @@ class CheckController {
* }
* // Requires JWT authentication
*/
ackCheck = asyncHandler(
ackCheck = this.asyncHandler(
async (req, res) => {
await ackCheckBodyValidation.validateAsync(req.body);
@@ -220,7 +223,7 @@ class CheckController {
* }
* // Requires JWT authentication
*/
ackAllChecks = asyncHandler(
ackAllChecks = this.asyncHandler(
async (req, res) => {
await ackAllChecksParamValidation.validateAsync(req.params);
await ackAllChecksBodyValidation.validateAsync(req.body);
@@ -266,7 +269,7 @@ class CheckController {
* // Requires JWT authentication
* // Response: { "data": { "deletedCount": 150 } }
*/
deleteChecks = asyncHandler(
deleteChecks = this.asyncHandler(
async (req, res) => {
await deleteChecksParamValidation.validateAsync(req.params);
@@ -300,7 +303,7 @@ class CheckController {
* // Requires JWT authentication
* // Response: { "data": { "deletedCount": 1250 } }
*/
deleteChecksByTeamId = asyncHandler(
deleteChecksByTeamId = this.asyncHandler(
async (req, res) => {
await deleteChecksByTeamIdParamValidation.validateAsync(req.params);
@@ -336,7 +339,7 @@ class CheckController {
* // Requires JWT authentication
* // Sets check TTL to 30 days
*/
updateChecksTTL = asyncHandler(
updateChecksTTL = this.asyncHandler(
async (req, res) => {
await updateChecksTTLBodyValidation.validateAsync(req.body);
+1 -3
View File
@@ -1,12 +1,10 @@
import { createServerError } from "../utils/errorUtils.js";
const fetchMonitorCertificate = async (sslChecker, monitor) => {
const monitorUrl = new URL(monitor.url);
const hostname = monitorUrl.hostname;
const cert = await sslChecker(hostname);
// Throw an error if no cert or if cert.validTo is not present
if (cert?.validTo === null || cert?.validTo === undefined) {
throw createServerError("Certificate not found");
throw new Error("Certificate not found");
}
return cert;
};
+7 -5
View File
@@ -1,6 +1,5 @@
import { asyncHandler } from "../utils/errorUtils.js";
const SERVICE_NAME = "diagnosticController";
import BaseController from "./baseController.js";
/**
* Diagnostic Controller
*
@@ -10,15 +9,18 @@ const SERVICE_NAME = "diagnosticController";
* @class DiagnosticController
* @description Manages system diagnostics and performance monitoring
*/
class DiagnosticController {
class DiagnosticController extends BaseController {
/**
* Creates an instance of DiagnosticController.
*
* @param {Object} dependencies - The dependencies required by the controller
* @param {Object} dependencies.diagnosticService - Service for system diagnostics and monitoring
* @param {Object} dependencies.errorService - Service for error handling
*/
constructor({ diagnosticService }) {
constructor({ diagnosticService, errorService }) {
super();
this.diagnosticService = diagnosticService;
this.errorService = errorService;
}
/**
@@ -41,7 +43,7 @@ class DiagnosticController {
* // - Database connection status
* // - Active processes/connections
*/
getSystemStats = asyncHandler(
getSystemStats = this.asyncHandler(
async (req, res) => {
const diagnostics = await this.diagnosticService.getSystemStats();
return res.success({
+10 -8
View File
@@ -1,22 +1,24 @@
import { inviteBodyValidation, inviteVerificationBodyValidation } from "../validation/joi.js";
import { asyncHandler } from "../utils/errorUtils.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "inviteController";
/**
* Controller for handling user invitation operations
* Manages invite token generation, email sending, and token verification
*/
class InviteController {
class InviteController extends BaseController {
/**
* Creates a new InviteController instance
* @param {Object} dependencies - Dependencies injected into the controller
* @param {Object} dependencies.stringService - Service for internationalized strings
* @param {Object} dependencies.inviteService - Service for invite-related operations
* @param {Object} dependencies.inviteServ ice - Service for invite-related operations
* @param {Object} dependencies.errorService - Service for error handling
*/
constructor({ stringService, inviteService }) {
constructor({ stringService, inviteService, errorService }) {
super();
this.stringService = stringService;
this.inviteService = inviteService;
this.errorService = errorService;
}
/**
@@ -28,7 +30,7 @@ class InviteController {
* @param {Object} res - Express response object
* @returns {Promise<Object>} Response with invite token data
*/
getInviteToken = asyncHandler(
getInviteToken = this.asyncHandler(
async (req, res) => {
const invite = req.body;
const teamId = req?.user?.teamId;
@@ -54,7 +56,7 @@ class InviteController {
* @param {Object} res - Express response object
* @returns {Promise<Object>} Response with invite token data
*/
sendInviteEmail = asyncHandler(
sendInviteEmail = this.asyncHandler(
async (req, res) => {
const inviteRequest = req.body;
inviteRequest.teamId = req?.user?.teamId;
@@ -81,7 +83,7 @@ class InviteController {
* @param {Object} res - Express response object
* @returns {Promise<Object>} Response with verified invite data
*/
verifyInviteToken = asyncHandler(
verifyInviteToken = this.asyncHandler(
async (req, res) => {
await inviteVerificationBodyValidation.validateAsync(req.body);
const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token });
+7 -6
View File
@@ -1,14 +1,15 @@
import { asyncHandler } from "../utils/errorUtils.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "LogController";
class LogController {
constructor(logger) {
class LogController extends BaseController {
constructor({ logger, errorService }) {
super();
this.logger = logger;
this.errorService = errorService;
}
getLogs = asyncHandler(
async (req, res, next) => {
getLogs = this.asyncHandler(
async (req, res) => {
const logs = await this.logger.getLogs();
res.success({
msg: "Logs fetched successfully",
@@ -7,25 +7,28 @@ import {
getMaintenanceWindowsByTeamIdQueryValidation,
deleteMaintenanceWindowByIdParamValidation,
} from "../validation/joi.js";
import { asyncHandler } from "../utils/errorUtils.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "maintenanceWindowController";
class MaintenanceWindowController {
constructor({ db, settingsService, stringService, maintenanceWindowService }) {
class MaintenanceWindowController extends BaseController {
constructor({ db, settingsService, stringService, maintenanceWindowService, errorService }) {
super();
this.db = db;
this.settingsService = settingsService;
this.stringService = stringService;
this.maintenanceWindowService = maintenanceWindowService;
this.errorService = errorService;
this.asyncHandler = errorService.asyncHandler;
}
createMaintenanceWindows = asyncHandler(
createMaintenanceWindows = this.asyncHandler(
async (req, res) => {
await createMaintenanceWindowBodyValidation.validateAsync(req.body);
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
await this.maintenanceWindowService.createMaintenanceWindow({ teamId, body: req.body });
@@ -38,13 +41,13 @@ class MaintenanceWindowController {
"createMaintenanceWindows"
);
getMaintenanceWindowById = asyncHandler(
getMaintenanceWindowById = this.asyncHandler(
async (req, res) => {
await getMaintenanceWindowByIdParamValidation.validateAsync(req.params);
const teamId = req.user.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: req.params.id, teamId });
@@ -58,14 +61,14 @@ class MaintenanceWindowController {
"getMaintenanceWindowById"
);
getMaintenanceWindowsByTeamId = asyncHandler(
getMaintenanceWindowsByTeamId = this.asyncHandler(
async (req, res) => {
await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query);
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({ teamId, query: req.query });
@@ -79,13 +82,13 @@ class MaintenanceWindowController {
"getMaintenanceWindowsByTeamId"
);
getMaintenanceWindowsByMonitorId = asyncHandler(
getMaintenanceWindowsByMonitorId = this.asyncHandler(
async (req, res) => {
await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params);
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({ monitorId: req.params.monitorId, teamId });
@@ -99,13 +102,13 @@ class MaintenanceWindowController {
"getMaintenanceWindowsByMonitorId"
);
deleteMaintenanceWindow = asyncHandler(
deleteMaintenanceWindow = this.asyncHandler(
async (req, res) => {
await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params);
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
await this.maintenanceWindowService.deleteMaintenanceWindow({ id: req.params.id, teamId });
@@ -118,14 +121,14 @@ class MaintenanceWindowController {
"deleteMaintenanceWindow"
);
editMaintenanceWindow = asyncHandler(
editMaintenanceWindow = this.asyncHandler(
async (req, res) => {
await editMaintenanceWindowByIdParamValidation.validateAsync(req.params);
await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body);
const teamId = req.user.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({ id: req.params.id, body: req.body, teamId });
+44 -43
View File
@@ -13,30 +13,31 @@ import {
getHardwareDetailsByIdQueryValidation,
} from "../validation/joi.js";
import sslChecker from "ssl-checker";
import { asyncHandler } from "../utils/errorUtils.js";
import { fetchMonitorCertificate } from "./controllerUtils.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "monitorController";
class MonitorController {
constructor({ db, settingsService, jobQueue, stringService, emailService, monitorService }) {
class MonitorController extends BaseController {
constructor({ db, settingsService, jobQueue, stringService, emailService, monitorService, errorService }) {
super();
this.db = db;
this.settingsService = settingsService;
this.jobQueue = jobQueue;
this.stringService = stringService;
this.emailService = emailService;
this.monitorService = monitorService;
this.errorService = errorService;
this.asyncHandler = errorService.asyncHandler;
}
async verifyTeamAccess(teamId, monitorId) {
const monitor = await this.db.getMonitorById(monitorId);
if (!monitor.teamId.equals(teamId)) {
const error = new Error("Unauthorized");
error.status = 403;
throw error;
throw this.errorService.createAuthorizationError();
}
}
getAllMonitors = asyncHandler(
getAllMonitors = this.asyncHandler(
async (req, res) => {
const monitors = await this.monitorService.getAllMonitors();
return res.success({
@@ -48,7 +49,7 @@ class MonitorController {
"getAllMonitors"
);
getUptimeDetailsById = asyncHandler(
getUptimeDetailsById = this.asyncHandler(
async (req, res) => {
const monitorId = req?.params?.monitorId;
const dateRange = req?.query?.dateRange;
@@ -57,7 +58,7 @@ class MonitorController {
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const data = await this.monitorService.getUptimeDetailsById({
@@ -75,7 +76,7 @@ class MonitorController {
"getUptimeDetailsById"
);
getMonitorStatsById = asyncHandler(
getMonitorStatsById = this.asyncHandler(
async (req, res) => {
await getMonitorStatsByIdParamValidation.validateAsync(req.params);
await getMonitorStatsByIdQueryValidation.validateAsync(req.query);
@@ -85,7 +86,7 @@ class MonitorController {
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const monitorStats = await this.monitorService.getMonitorStatsById({
@@ -116,7 +117,7 @@ class MonitorController {
* @returns {Promise<Express.Response>}
* @throws {Error} - Throws error if monitor not found or other database errors
*/
getHardwareDetailsById = asyncHandler(
getHardwareDetailsById = this.asyncHandler(
async (req, res) => {
await getHardwareDetailsByIdParamValidation.validateAsync(req.params);
await getHardwareDetailsByIdQueryValidation.validateAsync(req.query);
@@ -125,7 +126,7 @@ class MonitorController {
const dateRange = req?.query?.dateRange;
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const monitor = await this.monitorService.getHardwareDetailsById({
@@ -143,8 +144,8 @@ class MonitorController {
"getHardwareDetailsById"
);
getMonitorCertificate = asyncHandler(
async (req, res, next) => {
getMonitorCertificate = this.asyncHandler(
async (req, res) => {
await getCertificateParamValidation.validateAsync(req.params);
const { monitorId } = req.params;
@@ -162,14 +163,14 @@ class MonitorController {
"getMonitorCertificate"
);
getMonitorById = asyncHandler(
getMonitorById = this.asyncHandler(
async (req, res) => {
await getMonitorByIdParamValidation.validateAsync(req.params);
await getMonitorByIdQueryValidation.validateAsync(req.query);
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: req?.params?.monitorId });
@@ -183,7 +184,7 @@ class MonitorController {
"getMonitorById"
);
createMonitor = asyncHandler(
createMonitor = this.asyncHandler(
async (req, res) => {
await createMonitorBodyValidation.validateAsync(req.body);
@@ -201,30 +202,30 @@ class MonitorController {
"createMonitor"
);
createBulkMonitors = asyncHandler(
createBulkMonitors = this.asyncHandler(
async (req, res) => {
if (!req.file) {
throw new Error("No file uploaded");
throw this.errorService.createBadRequestError("No file uploaded");
}
if (!req.file.mimetype.includes("csv")) {
throw new Error("File is not a CSV");
throw this.errorService.createBadRequestError("File is not a CSV");
}
if (req.file.size === 0) {
throw new Error("File is empty");
throw this.errorService.createBadRequestError("File is empty");
}
const userId = req?.user?._id;
const teamId = req?.user?.teamId;
if (!userId || !teamId) {
throw new Error("Missing userId or teamId");
throw this.errorService.createBadRequestError("Missing userId or teamId");
}
const fileData = req?.file?.buffer?.toString("utf-8");
if (!fileData) {
throw new Error("Cannot get file from buffer");
throw this.errorService.createBadRequestError("Cannot get file from buffer");
}
const monitors = await this.monitorService.createBulkMonitors({ fileData, userId, teamId });
@@ -238,13 +239,13 @@ class MonitorController {
"createBulkMonitors"
);
deleteMonitor = asyncHandler(
deleteMonitor = this.asyncHandler(
async (req, res) => {
await getMonitorByIdParamValidation.validateAsync(req.params);
const monitorId = req.params.monitorId;
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
@@ -255,11 +256,11 @@ class MonitorController {
"deleteMonitor"
);
deleteAllMonitors = asyncHandler(
deleteAllMonitors = this.asyncHandler(
async (req, res) => {
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
@@ -270,7 +271,7 @@ class MonitorController {
"deleteAllMonitors"
);
editMonitor = asyncHandler(
editMonitor = this.asyncHandler(
async (req, res) => {
await getMonitorByIdParamValidation.validateAsync(req.params);
await editMonitorBodyValidation.validateAsync(req.body);
@@ -278,7 +279,7 @@ class MonitorController {
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: req.body });
@@ -292,14 +293,14 @@ class MonitorController {
"editMonitor"
);
pauseMonitor = asyncHandler(
pauseMonitor = this.asyncHandler(
async (req, res) => {
await pauseMonitorParamValidation.validateAsync(req.params);
const monitorId = req.params.monitorId;
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
@@ -313,7 +314,7 @@ class MonitorController {
"pauseMonitor"
);
addDemoMonitors = asyncHandler(
addDemoMonitors = this.asyncHandler(
async (req, res) => {
const { _id, teamId } = req.user;
const demoMonitors = await this.monitorService.addDemoMonitors({ userId: _id, teamId });
@@ -327,11 +328,11 @@ class MonitorController {
"addDemoMonitors"
);
sendTestEmail = asyncHandler(
sendTestEmail = this.asyncHandler(
async (req, res) => {
const { to } = req.body;
if (!to || typeof to !== "string") {
throw new Error(this.stringService.errorForValidEmailAddress);
throw this.errorService.createBadRequestError(this.stringService.errorForValidEmailAddress);
}
const messageId = await this.monitorService.sendTestEmail({ to });
@@ -344,8 +345,8 @@ class MonitorController {
"sendTestEmail"
);
getMonitorsByTeamId = asyncHandler(
async (req, res, next) => {
getMonitorsByTeamId = this.asyncHandler(
async (req, res) => {
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
@@ -363,7 +364,7 @@ class MonitorController {
"getMonitorsByTeamId"
);
getMonitorsAndSummaryByTeamId = asyncHandler(
getMonitorsAndSummaryByTeamId = this.asyncHandler(
async (req, res) => {
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
@@ -372,7 +373,7 @@ class MonitorController {
const type = req?.query?.type;
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const result = await this.monitorService.getMonitorsAndSummaryByTeamId({ teamId, type, explain });
@@ -386,7 +387,7 @@ class MonitorController {
"getMonitorsAndSummaryByTeamId"
);
getMonitorsWithChecksByTeamId = asyncHandler(
getMonitorsWithChecksByTeamId = this.asyncHandler(
async (req, res) => {
await getMonitorsByTeamIdParamValidation.validateAsync(req.params);
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
@@ -395,7 +396,7 @@ class MonitorController {
let { limit, type, page, rowsPerPage, filter, field, order } = req.query;
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
@@ -419,11 +420,11 @@ class MonitorController {
"getMonitorsWithChecksByTeamId"
);
exportMonitorsToCSV = asyncHandler(
exportMonitorsToCSV = this.asyncHandler(
async (req, res) => {
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("Team ID is required");
throw this.errorService.createBadRequestError("Team ID is required");
}
const csv = await this.monitorService.exportMonitorsToCSV({ teamId });
+34 -42
View File
@@ -1,27 +1,27 @@
import { createNotificationBodyValidation } from "../validation/joi.js";
import { asyncHandler } from "../utils/errorUtils.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "NotificationController";
class NotificationController {
constructor({ notificationService, stringService, statusService, db }) {
class NotificationController extends BaseController {
constructor({ notificationService, stringService, statusService, db, errorService }) {
super();
this.notificationService = notificationService;
this.stringService = stringService;
this.statusService = statusService;
this.db = db;
this.errorService = errorService;
this.asyncHandler = errorService.asyncHandler;
}
testNotification = asyncHandler(
async (req, res, next) => {
testNotification = this.asyncHandler(
async (req, res) => {
const notification = req.body;
const success = await this.notificationService.sendTestNotification(notification);
if (!success) {
return res.error({
msg: "Sending notification failed",
status: 400,
});
throw this.errorService.createServerError("Sending notification failed");
}
return res.success({
@@ -32,8 +32,8 @@ class NotificationController {
"testNotification"
);
createNotification = asyncHandler(
async (req, res, next) => {
createNotification = this.asyncHandler(
async (req, res) => {
await createNotificationBodyValidation.validateAsync(req.body, {
abortEarly: false,
});
@@ -42,12 +42,12 @@ class NotificationController {
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("No team ID in request");
throw this.errorService.createBadRequestError("Team ID is required");
}
const userId = req?.user?._id;
if (!userId) {
throw new Error("No user ID in request");
throw this.errorService.createBadRequestError("User ID is required");
}
body.userId = userId;
body.teamId = teamId;
@@ -62,11 +62,11 @@ class NotificationController {
"createNotification"
);
getNotificationsByTeamId = asyncHandler(
async (req, res, next) => {
getNotificationsByTeamId = this.asyncHandler(
async (req, res) => {
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("No team ID in request");
throw this.errorService.createBadRequestError("Team ID is required");
}
const notifications = await this.db.getNotificationsByTeamId(teamId);
@@ -80,18 +80,16 @@ class NotificationController {
"getNotificationsByTeamId"
);
deleteNotification = asyncHandler(
async (req, res, next) => {
deleteNotification = this.asyncHandler(
async (req, res) => {
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("No team ID in request");
throw this.errorService.createBadRequestError("Team ID is required");
}
const notification = await this.db.getNotificationById(req.params.id);
if (!notification.teamId.equals(teamId)) {
const error = new Error("Unauthorized");
error.status = 403;
throw error;
throw this.errorService.createAuthorizationError();
}
await this.db.deleteNotificationById(req.params.id);
@@ -103,19 +101,17 @@ class NotificationController {
"deleteNotification"
);
getNotificationById = asyncHandler(
async (req, res, next) => {
getNotificationById = this.asyncHandler(
async (req, res) => {
const notification = await this.db.getNotificationById(req.params.id);
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("No team ID in request");
throw this.errorService.createBadRequestError("Team ID is required");
}
if (!notification.teamId.equals(teamId)) {
const error = new Error("Unauthorized");
error.status = 403;
throw error;
throw this.errorService.createAuthorizationError();
}
return res.success({
msg: "Notification fetched successfully",
@@ -126,23 +122,21 @@ class NotificationController {
"getNotificationById"
);
editNotification = asyncHandler(
async (req, res, next) => {
editNotification = this.asyncHandler(
async (req, res) => {
await createNotificationBodyValidation.validateAsync(req.body, {
abortEarly: false,
});
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("No team ID in request");
throw this.errorService.createBadRequestError("Team ID is required");
}
const notification = await this.db.getNotificationById(req.params.id);
if (!notification.teamId.equals(teamId)) {
const error = new Error("Unauthorized");
error.status = 403;
throw error;
throw this.errorService.createAuthorizationError();
}
const editedNotification = await this.db.editNotification(req.params.id, req.body);
@@ -155,26 +149,24 @@ class NotificationController {
"editNotification"
);
testAllNotifications = asyncHandler(
async (req, res, next) => {
testAllNotifications = this.asyncHandler(
async (req, res) => {
const monitorId = req.body.monitorId;
const teamId = req?.user?.teamId;
if (!teamId) {
throw new Error("No team ID in request");
throw this.errorService.createBadRequestError("Team ID is required");
}
const monitor = await this.db.getMonitorById(monitorId);
if (!monitor.teamId.equals(teamId)) {
const error = new Error("Unauthorized");
error.status = 403;
throw error;
throw this.errorService.createAuthorizationError();
}
const notifications = monitor.notifications;
if (notifications.length === 0) throw new Error("No notifications");
if (notifications.length === 0) throw this.errorService.createBadRequestError("No notifications");
const result = await this.notificationService.testAllNotifications(notifications);
if (!result) throw new Error("Failed to send all notifications");
if (!result) throw this.errorService.createServerError("Failed to send all notifications");
return res.success({
msg: "All notifications sent successfully",
});
+17 -16
View File
@@ -1,15 +1,16 @@
import { asyncHandler } from "../utils/errorUtils.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "JobQueueController";
class JobQueueController {
constructor(jobQueue, stringService) {
class JobQueueController extends BaseController {
constructor({ jobQueue, stringService, errorService }) {
super();
this.jobQueue = jobQueue;
this.stringService = stringService;
this.errorService = errorService;
}
getMetrics = asyncHandler(
async (req, res, next) => {
getMetrics = this.asyncHandler(
async (req, res) => {
const metrics = await this.jobQueue.getMetrics();
res.success({
msg: this.stringService.queueGetMetrics,
@@ -20,8 +21,8 @@ class JobQueueController {
"getMetrics"
);
getJobs = asyncHandler(
async (req, res, next) => {
getJobs = this.asyncHandler(
async (req, res) => {
const jobs = await this.jobQueue.getJobs();
return res.success({
msg: this.stringService.queueGetJobs,
@@ -32,8 +33,8 @@ class JobQueueController {
"getJobs"
);
getAllMetrics = asyncHandler(
async (req, res, next) => {
getAllMetrics = this.asyncHandler(
async (req, res) => {
const jobs = await this.jobQueue.getJobs();
const metrics = await this.jobQueue.getMetrics();
return res.success({
@@ -45,8 +46,8 @@ class JobQueueController {
"getAllMetrics"
);
addJob = asyncHandler(
async (req, res, next) => {
addJob = this.asyncHandler(
async (req, res) => {
await this.jobQueue.addJob(Math.random().toString(36).substring(7));
return res.success({
msg: this.stringService.queueAddJob,
@@ -56,8 +57,8 @@ class JobQueueController {
"addJob"
);
flushQueue = asyncHandler(
async (req, res, next) => {
flushQueue = this.asyncHandler(
async (req, res) => {
const result = await this.jobQueue.flushQueues();
return res.success({
msg: this.stringService.jobQueueFlush,
@@ -68,8 +69,8 @@ class JobQueueController {
"flushQueue"
);
checkQueueHealth = asyncHandler(
async (req, res, next) => {
checkQueueHealth = this.asyncHandler(
async (req, res) => {
const stuckQueues = await this.jobQueue.checkQueueHealth();
return res.success({
msg: this.stringService.queueHealthCheck,
+13 -16
View File
@@ -1,15 +1,17 @@
import { updateAppSettingsBodyValidation } from "../validation/joi.js";
import { sendTestEmailBodyValidation } from "../validation/joi.js";
import { asyncHandler, createServerError } from "../utils/errorUtils.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "SettingsController";
class SettingsController {
constructor({ db, settingsService, stringService, emailService }) {
class SettingsController extends BaseController {
constructor({ db, settingsService, stringService, emailService, errorService }) {
super();
this.db = db;
this.settingsService = settingsService;
this.stringService = stringService;
this.emailService = emailService;
this.errorService = errorService;
}
buildAppSettings = (dbSettings) => {
@@ -33,8 +35,8 @@ class SettingsController {
return returnSettings;
};
getAppSettings = asyncHandler(
async (req, res, next) => {
getAppSettings = this.asyncHandler(
async (req, res) => {
const dbSettings = await this.settingsService.getDBSettings();
const returnSettings = this.buildAppSettings(dbSettings);
@@ -47,8 +49,8 @@ class SettingsController {
"getAppSettings"
);
updateAppSettings = asyncHandler(
async (req, res, next) => {
updateAppSettings = this.asyncHandler(
async (req, res) => {
await updateAppSettingsBodyValidation.validateAsync(req.body);
const updatedSettings = await this.db.updateAppSettings(req.body);
@@ -62,14 +64,9 @@ class SettingsController {
"updateAppSettings"
);
sendTestEmail = asyncHandler(
async (req, res, next) => {
try {
await sendTestEmailBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
sendTestEmail = this.asyncHandler(
async (req, res) => {
await sendTestEmailBodyValidation.validateAsync(req.body);
const {
to,
@@ -107,7 +104,7 @@ class SettingsController {
});
if (!messageId) {
throw createServerError("Failed to send test email.");
throw this.errorService.createServerError("Failed to send test email.");
}
return res.success({
+18 -18
View File
@@ -1,16 +1,18 @@
import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "../validation/joi.js";
import { asyncHandler } from "../utils/errorUtils.js";
import BaseController from "./baseController.js";
const SERVICE_NAME = "statusPageController";
class StatusPageController {
constructor(db, stringService) {
class StatusPageController extends BaseController {
constructor({ db, stringService, errorService }) {
super();
this.db = db;
this.stringService = stringService;
this.errorService = errorService;
}
createStatusPage = asyncHandler(
async (req, res, next) => {
createStatusPage = this.asyncHandler(
async (req, res) => {
await createStatusPageBodyValidation.validateAsync(req.body);
await imageValidation.validateAsync(req.file);
@@ -30,16 +32,14 @@ class StatusPageController {
"createStatusPage"
);
updateStatusPage = asyncHandler(
async (req, res, next) => {
updateStatusPage = this.asyncHandler(
async (req, res) => {
await createStatusPageBodyValidation.validateAsync(req.body);
await imageValidation.validateAsync(req.file);
const statusPage = await this.db.updateStatusPage(req.body, req.file);
if (statusPage === null) {
const error = new Error(this.stringService.statusPageNotFound);
error.status = 404;
throw error;
throw this.errorService.createNotFoundError(this.stringService.statusPageNotFound);
}
return res.success({
msg: this.stringService.statusPageUpdate,
@@ -50,8 +50,8 @@ class StatusPageController {
"updateStatusPage"
);
getStatusPage = asyncHandler(
async (req, res, next) => {
getStatusPage = this.asyncHandler(
async (req, res) => {
const statusPage = await this.db.getStatusPage();
return res.success({
msg: this.stringService.statusPageByUrl,
@@ -62,8 +62,8 @@ class StatusPageController {
"getStatusPage"
);
getStatusPageByUrl = asyncHandler(
async (req, res, next) => {
getStatusPageByUrl = this.asyncHandler(
async (req, res) => {
await getStatusPageParamValidation.validateAsync(req.params);
await getStatusPageQueryValidation.validateAsync(req.query);
@@ -77,8 +77,8 @@ class StatusPageController {
"getStatusPageByUrl"
);
getStatusPagesByTeamId = asyncHandler(
async (req, res, next) => {
getStatusPagesByTeamId = this.asyncHandler(
async (req, res) => {
const teamId = req.user.teamId;
const statusPages = await this.db.getStatusPagesByTeamId(teamId);
@@ -91,8 +91,8 @@ class StatusPageController {
"getStatusPagesByTeamId"
);
deleteStatusPage = asyncHandler(
async (req, res, next) => {
deleteStatusPage = this.asyncHandler(
async (req, res) => {
await this.db.deleteStatusPage(req.params.url);
return res.success({
msg: this.stringService.statusPageDelete,
+29 -6
View File
@@ -3,7 +3,6 @@ import fs from "fs";
import swaggerUi from "swagger-ui-express";
import jwt from "jsonwebtoken";
import papaparse from "papaparse";
import express from "express";
import helmet from "helmet";
import cors from "cors";
@@ -58,6 +57,8 @@ import PulseQueueHelper from "./service/infrastructure/PulseQueue/PulseQueueHelp
import SuperSimpleQueue from "./service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
import SuperSimpleQueueHelper from "./service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
import ErrorService from "./service/infrastructure/errorService.js";
// Business services
import UserService from "./service/business/userService.js";
import CheckService from "./service/business/checkService.js";
@@ -186,6 +187,7 @@ const startApp = async () => {
});
const redisService = new RedisService({ Redis: IORedis, logger });
const errorService = new ErrorService();
// const jobQueueHelper = new JobQueueHelper({
// redisService,
@@ -240,6 +242,7 @@ const startApp = async () => {
logger,
stringService,
jwt,
errorService,
});
const checkService = new CheckService({
db,
@@ -252,6 +255,7 @@ const startApp = async () => {
settingsService,
emailService,
stringService,
errorService,
});
const maintenanceWindowService = new MaintenanceWindowService({
db,
@@ -266,11 +270,13 @@ const startApp = async () => {
emailService,
papaparse,
logger,
errorService,
});
// Register services
// ServiceRegistry.register(JobQueue.SERVICE_NAME, jobQueue);
// ServiceRegistry.register(JobQueue.SERVICE_NAME, pulseQueue);
ServiceRegistry.register(JobQueue.SERVICE_NAME, superSimpleQueue);
ServiceRegistry.register(ErrorService.SERVICE_NAME, errorService);
ServiceRegistry.register(MongoDB.SERVICE_NAME, db);
ServiceRegistry.register(SettingsService.SERVICE_NAME, settingsService);
ServiceRegistry.register(EmailService.SERVICE_NAME, emailService);
@@ -285,6 +291,7 @@ const startApp = async () => {
ServiceRegistry.register(InviteService.SERVICE_NAME, inviteService);
ServiceRegistry.register(MaintenanceWindowService.SERVICE_NAME, maintenanceWindowService);
ServiceRegistry.register(MonitorService.SERVICE_NAME, monitorService);
ServiceRegistry.register(DiagnosticService.SERVICE_NAME, diagnosticService);
await translationService.initialize();
@@ -306,6 +313,7 @@ const startApp = async () => {
stringService: ServiceRegistry.get(StringService.SERVICE_NAME),
logger: logger,
userService: ServiceRegistry.get(UserService.SERVICE_NAME),
errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME),
});
const monitorController = new MonitorController({
@@ -315,6 +323,7 @@ const startApp = async () => {
stringService: ServiceRegistry.get(StringService.SERVICE_NAME),
emailService: ServiceRegistry.get(EmailService.SERVICE_NAME),
monitorService: ServiceRegistry.get(MonitorService.SERVICE_NAME),
errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME),
});
const settingsController = new SettingsController({
@@ -322,6 +331,7 @@ const startApp = async () => {
settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME),
stringService: ServiceRegistry.get(StringService.SERVICE_NAME),
emailService: ServiceRegistry.get(EmailService.SERVICE_NAME),
errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME),
});
const checkController = new CheckController({
@@ -329,11 +339,13 @@ const startApp = async () => {
settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME),
stringService: ServiceRegistry.get(StringService.SERVICE_NAME),
checkService: ServiceRegistry.get(CheckService.SERVICE_NAME),
errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME),
});
const inviteController = new InviteController({
stringService: ServiceRegistry.get(StringService.SERVICE_NAME),
inviteService,
inviteService: ServiceRegistry.get(InviteService.SERVICE_NAME),
errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME),
});
const maintenanceWindowController = new MaintenanceWindowController({
@@ -341,23 +353,34 @@ const startApp = async () => {
settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME),
stringService: ServiceRegistry.get(StringService.SERVICE_NAME),
maintenanceWindowService: ServiceRegistry.get(MaintenanceWindowService.SERVICE_NAME),
errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME),
});
const queueController = new QueueController(ServiceRegistry.get(JobQueue.SERVICE_NAME), ServiceRegistry.get(StringService.SERVICE_NAME));
const queueController = new QueueController({
jobQueue: ServiceRegistry.get(JobQueue.SERVICE_NAME),
stringService: ServiceRegistry.get(StringService.SERVICE_NAME),
errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME),
});
const logController = new LogController(logger);
const logController = new LogController({ logger, errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME) });
const statusPageController = new StatusPageController(ServiceRegistry.get(MongoDB.SERVICE_NAME), ServiceRegistry.get(StringService.SERVICE_NAME));
const statusPageController = new StatusPageController({
db: ServiceRegistry.get(MongoDB.SERVICE_NAME),
stringService: ServiceRegistry.get(StringService.SERVICE_NAME),
errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME),
});
const notificationController = new NotificationController({
notificationService: ServiceRegistry.get(NotificationService.SERVICE_NAME),
stringService: ServiceRegistry.get(StringService.SERVICE_NAME),
statusService: ServiceRegistry.get(StatusService.SERVICE_NAME),
db: ServiceRegistry.get(MongoDB.SERVICE_NAME),
errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME),
});
const diagnosticController = new DiagnosticController({
diagnosticService,
diagnosticService: ServiceRegistry.get(DiagnosticService.SERVICE_NAME),
errorService: ServiceRegistry.get(ErrorService.SERVICE_NAME),
});
//Create routes
-1
View File
@@ -3,7 +3,6 @@ import ServiceRegistry from "../service/system/serviceRegistry.js";
import StringService from "../service/system/stringService.js";
const handleErrors = (error, req, res, next) => {
console.log("ERROR", error);
const status = error.status || 500;
const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
const message = error.message || stringService.authIncorrectPassword;
+3 -3
View File
@@ -1,14 +1,14 @@
const SERVICE_NAME = "inviteService";
import { createServerError } from "../../utils/errorUtils.js";
class InviteService {
static SERVICE_NAME = SERVICE_NAME;
constructor({ db, settingsService, emailService, stringService }) {
constructor({ db, settingsService, emailService, stringService, errorService }) {
this.db = db;
this.settingsService = settingsService;
this.emailService = emailService;
this.stringService = stringService;
this.errorService = errorService;
}
getInviteToken = async ({ invite, teamId }) => {
@@ -27,7 +27,7 @@ class InviteService {
});
const result = await this.emailService.sendEmail(inviteRequest.email, "Welcome to Uptime Monitor", html);
if (!result) {
throw createServerError("Failed to send invite e-mail... Please verify your settings.");
throw this.errorService.createServerError("Failed to send invite e-mail... Please verify your settings.");
}
};
+8 -10
View File
@@ -1,12 +1,10 @@
import { createMonitorsBodyValidation } from "../../validation/joi.js";
import { createServerError } from "../../utils/errorUtils.js";
import logger from "../../utils/logger.js";
const SERVICE_NAME = "MonitorService";
class MonitorService {
SERVICE_NAME = SERVICE_NAME;
constructor({ db, settingsService, jobQueue, stringService, emailService, papaparse }) {
constructor({ db, settingsService, jobQueue, stringService, emailService, papaparse, logger, errorService }) {
this.db = db;
this.settingsService = settingsService;
this.jobQueue = jobQueue;
@@ -14,14 +12,14 @@ class MonitorService {
this.emailService = emailService;
this.papaparse = papaparse;
this.logger = logger;
this.errorService = errorService;
this.asyncHandler = errorService.asyncHandler;
}
verifyTeamAccess = async ({ teamId, monitorId }) => {
const monitor = await this.db.getMonitorById(monitorId);
if (!monitor?.teamId?.equals(teamId)) {
const error = new Error("Unauthorized");
error.status = 403;
throw error;
throw this.errorService.createAuthorizationError();
}
};
@@ -103,11 +101,11 @@ class MonitorService {
complete: async ({ data, errors }) => {
try {
if (errors.length > 0) {
throw createServerError("Error parsing CSV");
throw this.errorService.createServerError("Error parsing CSV");
}
if (!data || data.length === 0) {
throw createServerError("CSV file contains no data rows");
throw this.errorService.createServerError("CSV file contains no data rows");
}
const enrichedData = data.map((monitor) => ({
@@ -156,7 +154,7 @@ class MonitorService {
await this.db.deletePageSpeedChecksByMonitorId(monitor._id);
await this.db.deleteNotificationsByMonitorId(monitor._id);
} catch (error) {
logger.warn({
this.logger.warn({
message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
service: SERVICE_NAME,
method: "deleteAllMonitors",
@@ -195,7 +193,7 @@ class MonitorService {
const messageId = await this.emailService.sendEmail(to, subject, html);
if (!messageId) {
throw createServerError("Failed to send test email.");
throw this.errorService.createServerError("Failed to send test email.");
}
return messageId;
+4 -4
View File
@@ -1,15 +1,15 @@
const SERVICE_NAME = "userService";
import { createAuthError, createError } from "../../utils/errorUtils.js";
class UserService {
static SERVICE_NAME = SERVICE_NAME;
constructor({ db, emailService, settingsService, logger, stringService, jwt }) {
constructor({ db, emailService, settingsService, logger, stringService, jwt, errorService }) {
this.db = db;
this.emailService = emailService;
this.settingsService = settingsService;
this.logger = logger;
this.stringService = stringService;
this.jwt = jwt;
this.errorService = errorService;
}
issueToken = (payload, appSettings) => {
@@ -80,7 +80,7 @@ class UserService {
// Compare password
const match = await user.comparePassword(password);
if (match !== true) {
throw createAuthError(this.stringService.authIncorrectPassword);
throw this.errorService.createAuthenticationError(this.stringService.authIncorrectPassword);
}
// Remove password from user object. Should this be abstracted to DB layer?
@@ -109,7 +109,7 @@ class UserService {
// If not a match, throw a 403
// 403 instead of 401 to avoid triggering axios interceptor
if (!match) {
throw createError(this.stringService.authIncorrectPassword, 403);
throw this.errorService.createAuthorizationError(this.stringService.authIncorrectPassword);
}
// If a match, update the password
updates.password = updates.newPassword;
@@ -0,0 +1,161 @@
class AppError extends Error {
constructor(message, status = 500, service = null, method = null, details = null) {
super(message);
this.status = status;
this.service = service;
this.method = method;
this.details = details;
Error.captureStackTrace(this, this.constructor);
}
}
class ValidationError extends AppError {
constructor(message, details = null, service = null, method = null) {
super(message, 422, service, method, details);
}
}
class AuthenticationError extends AppError {
constructor(message, details = null, service = null, method = null) {
super(message, 401, service, method, details);
}
}
class AuthorizationError extends AppError {
constructor(message, details = null, service = null, method = null) {
super(message, 403, service, method, details);
}
}
class NotFoundError extends AppError {
constructor(message, details = null, service = null, method = null) {
super(message, 404, service, method, details);
}
}
class ConflictError extends AppError {
constructor(message, details = null, service = null, method = null) {
super(message, 409, service, method, details);
}
}
class DatabaseError extends AppError {
constructor(message, details = null, service = null, method = null) {
super(message, 500, service, method, details);
}
}
class BadRequestError extends AppError {
constructor(message, details = null, service = null, method = null) {
super(message, 400, service, method, details);
}
}
const SERVICE_NAME = "ErrorService";
class ErrorService {
static SERVICE_NAME = SERVICE_NAME;
constructor() {}
createError = (message, status = 500, service = null, method = null, details = null) => {
return new AppError(message, status, service, method, details);
};
createValidationError = (message, details = null, service = null, method = null) => {
return new ValidationError(message, details, service, method);
};
createAuthenticationError = (message = "Unauthorized", details = null, service = null, method = null) => {
return new AuthenticationError(message, details, service, method);
};
createAuthorizationError = (message, details = null, service = null, method = null) => {
return new AuthorizationError(message, details, service, method);
};
createNotFoundError = (message, details = null, service = null, method = null) => {
return new NotFoundError(message, details, service, method);
};
createConflictError = (message, details = null, service = null, method = null) => {
return new ConflictError(message, details, service, method);
};
createDatabaseError = (message, details = null, service = null, method = null) => {
return new DatabaseError(message, details, service, method);
};
createServerError = (message, details = null, service = null, method = null) => {
return this.createError(message, 500, service, method, details);
};
createBadRequestError = (message = "BadRequest", details = null, service = null, method = null) => {
return new BadRequestError(message, details, service, method);
};
}
export default ErrorService;
export const asyncHandler = (fn, serviceName, methodName) => {
return async (req, res, next) => {
try {
await fn(req, res, next);
} catch (error) {
// Handle validation errors
if (error.isJoi) {
const validationError = this.createValidationError(error.message, error.details, serviceName, methodName);
return next(validationError);
}
if (error.name === "ValidationError") {
const validationError = this.createValidationError("Database validation failed", error.errors, serviceName, methodName);
return next(validationError);
}
if (error.name === "CastError") {
const notFoundError = this.createNotFoundError(
"Invalid resource identifier",
{ field: error.path, value: error.value },
serviceName,
methodName
);
return next(notFoundError);
}
if (error.code === "11000") {
const conflictError = this.createConflictError("Resource already exists", {
originalError: error.message,
code: error.code,
});
conflictError.service = serviceName;
conflictError.method = methodName;
return next(conflictError);
}
if (error instanceof AppError) {
error.service = error.service || serviceName;
error.method = error.method || methodName;
return next(error);
}
if (error.status) {
const appError = this.createError(error.message, error.status, serviceName, methodName, {
originalError: error.message,
stack: error.stack,
});
return next(appError);
}
// For unknown errors, create a server error
const appError = this.createServerError(error.message || "An unexpected error occurred", {
originalError: error.message,
stack: error.stack,
});
appError.service = serviceName;
appError.method = methodName;
appError.stack = error.stack; // Preserve original stack
return next(appError);
}
};
};
-80
View File
@@ -1,81 +1 @@
class AppError extends Error {
constructor(message, status = 500, service = null, method = null, details = null) {
super(message);
this.status = status;
this.service = service;
this.method = method;
this.details = details;
Error.captureStackTrace(this, this.constructor);
}
}
export const createError = (message, status = 500, service = null, method = null, details = null) => {
return new AppError(message, status, service, method, details);
};
export const createValidationError = (message, details = null, service = null, method = null) => {
return createError(message, 422, service, method, details);
};
export const createAuthError = (message, details = null, service = null, method = null) => {
return createError(message, 401, service, method, details);
};
export const createForbiddenError = (message, details = null, service = null, method = null) => {
return createError(message, 403, service, method, details);
};
export const createNotFoundError = (message, details = null, service = null, method = null) => {
return createError(message, 404, service, method, details);
};
export const createConflictError = (message, details = null, service = null, method = null) => {
return createError(message, 409, service, method, details);
};
export const createServerError = (message, details = null, service = null, method = null) => {
return createError(message, 500, service, method, details);
};
export const asyncHandler = (fn, serviceName, methodName) => {
return async (req, res, next) => {
try {
await fn(req, res, next);
} catch (error) {
// Handle validation errors
if (error.isJoi || error.name === "ValidationError") {
const validationError = createValidationError(error.message, error.details, serviceName, methodName);
return next(validationError);
}
if (error instanceof AppError) {
error.service = error.service || serviceName;
error.method = error.method || methodName;
return next(error);
}
if (error.code === "23505") {
const appError = createConflictError("Resource already exists", {
originalError: error.message,
code: error.code,
});
appError.service = serviceName;
appError.method = methodName;
return next(appError);
}
if (error.status) {
const appError = createError(error.message, error.status, serviceName, methodName, { originalError: error.message, stack: error.stack });
return next(appError);
}
// For unknown errors, create a server error
const appError = createServerError(error.message || "An unexpected error occurred", { originalError: error.message, stack: error.stack });
appError.service = serviceName;
appError.method = methodName;
appError.stack = error.stack; // Preserve original stack
return next(appError);
}
};
};