const logger = require("../config/logger"); const { isDevelopment, PG_ERROR_CODES, MULTER_ERROR_CODES, STATIC_ASSET_EXTENSIONS, } = require("../config/constants"); class AppError extends Error { constructor(message, statusCode, isOperational = true) { super(message); this.statusCode = statusCode; this.isOperational = isOperational; this.timestamp = new Date().toISOString(); Error.captureStackTrace(this, this.constructor); } } const ERROR_MAPPINGS = { [PG_ERROR_CODES.UNIQUE_VIOLATION]: { message: "Duplicate entry: Resource already exists", statusCode: 409, }, [PG_ERROR_CODES.FOREIGN_KEY_VIOLATION]: { message: "Referenced resource does not exist", statusCode: 400, }, [PG_ERROR_CODES.INVALID_TEXT]: { message: "Invalid data format", statusCode: 400, }, [MULTER_ERROR_CODES.FILE_SIZE]: { message: "File too large. Maximum size is 5MB", statusCode: 400, }, [MULTER_ERROR_CODES.FILE_COUNT]: { message: "Too many files. Maximum is 10 files per upload", statusCode: 400, }, }; // Global error handler middleware const errorHandler = (err, req, res, next) => { let error = { ...err }; error.message = err.message; error.statusCode = err.statusCode || 500; // Log error logger.error("Error occurred", { message: error.message, statusCode: error.statusCode, path: req.path, method: req.method, ip: req.ip, stack: err.stack, }); // Map known error codes const errorMapping = ERROR_MAPPINGS[err.code]; if (errorMapping) { error.message = errorMapping.message; error.statusCode = errorMapping.statusCode; } // SAFEGUARD: Don't send response if headers already sent if (res.headersSent) { logger.warn("Headers already sent in error handler", { path: req.path, error: error.message, }); return next(err); } res.status(error.statusCode).json({ success: false, message: error.message || "Server error", ...(isDevelopment() && { error: err.message, stack: err.stack, }), }); }; // 404 handler const notFoundHandler = (req, res) => { const isStaticAsset = STATIC_ASSET_EXTENSIONS.test(req.path); if (!isStaticAsset) { logger.warn("Route not found", { path: req.path, method: req.method, ip: req.ip, }); } else { logger.debug("Static asset not found", { path: req.path, method: req.method, }); } // SAFEGUARD: Check if response already sent if (res.headersSent) { logger.warn("Headers already sent in 404 handler", { path: req.path }); return; } res.status(404).json({ success: false, message: "Route not found", path: req.path, }); }; // Async handler wrapper to catch errors in async routes const asyncHandler = (fn) => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; module.exports = { AppError, errorHandler, notFoundHandler, asyncHandler, };