const logger = require('../config/logger'); const { AppError } = require('../utils/AppError'); /** * Handle Sequelize validation errors */ const handleSequelizeValidationError = (error) => { const errors = error.errors.map(err => ({ field: err.path, message: err.message, value: err.value })); return { statusCode: 400, message: 'Validation error', errors }; }; /** * Handle Sequelize unique constraint errors */ const handleSequelizeUniqueConstraintError = (error) => { const field = error.errors[0]?.path; const value = error.errors[0]?.value; return { statusCode: 409, message: `${field} '${value}' already exists` }; }; /** * Handle Sequelize foreign key constraint errors */ const handleSequelizeForeignKeyConstraintError = (error) => { return { statusCode: 400, message: 'Invalid reference to related resource' }; }; /** * Handle Sequelize database connection errors */ const handleSequelizeConnectionError = (error) => { return { statusCode: 503, message: 'Database connection error. Please try again later.' }; }; /** * Handle JWT errors */ const handleJWTError = () => { return { statusCode: 401, message: 'Invalid token. Please log in again.' }; }; /** * Handle JWT expired errors */ const handleJWTExpiredError = () => { return { statusCode: 401, message: 'Your token has expired. Please log in again.' }; }; /** * Handle Sequelize errors */ const handleSequelizeError = (error) => { // Validation error if (error.name === 'SequelizeValidationError') { return handleSequelizeValidationError(error); } // Unique constraint violation if (error.name === 'SequelizeUniqueConstraintError') { return handleSequelizeUniqueConstraintError(error); } // Foreign key constraint violation if (error.name === 'SequelizeForeignKeyConstraintError') { return handleSequelizeForeignKeyConstraintError(error); } // Database connection error if (error.name === 'SequelizeConnectionError' || error.name === 'SequelizeConnectionRefusedError' || error.name === 'SequelizeHostNotFoundError' || error.name === 'SequelizeAccessDeniedError') { return handleSequelizeConnectionError(error); } // Generic database error return { statusCode: 500, message: 'Database error occurred' }; }; /** * Send error response in development */ const sendErrorDev = (err, res) => { res.status(err.statusCode).json({ status: err.status, message: err.message, error: err, stack: err.stack, ...(err.errors && { errors: err.errors }) }); }; /** * Send error response in production */ const sendErrorProd = (err, res) => { // Operational, trusted error: send message to client if (err.isOperational) { res.status(err.statusCode).json({ status: err.status, message: err.message, ...(err.errors && { errors: err.errors }) }); } // Programming or unknown error: don't leak error details else { // Log error for debugging logger.error('ERROR 💥', err); // Send generic message res.status(500).json({ status: 'error', message: 'Something went wrong. Please try again later.' }); } }; /** * Centralized error handling middleware */ const errorHandler = (err, req, res, next) => { err.statusCode = err.statusCode || 500; err.status = err.status || 'error'; // Log the error logger.logError(err, req); // Handle specific error types let error = { ...err }; error.message = err.message; error.stack = err.stack; // Sequelize errors if (err.name && err.name.startsWith('Sequelize')) { const handled = handleSequelizeError(err); error.statusCode = handled.statusCode; error.message = handled.message; error.isOperational = true; if (handled.errors) error.errors = handled.errors; } // JWT errors if (err.name === 'JsonWebTokenError') { const handled = handleJWTError(); error.statusCode = handled.statusCode; error.message = handled.message; error.isOperational = true; } if (err.name === 'TokenExpiredError') { const handled = handleJWTExpiredError(); error.statusCode = handled.statusCode; error.message = handled.message; error.isOperational = true; } // Multer errors (file upload) if (err.name === 'MulterError') { error.statusCode = 400; error.message = `File upload error: ${err.message}`; error.isOperational = true; } // Send error response if (process.env.NODE_ENV === 'development') { sendErrorDev(error, res); } else { sendErrorProd(error, res); } }; /** * Handle async errors (wrap async route handlers) */ const catchAsync = (fn) => { return (req, res, next) => { fn(req, res, next).catch(next); }; }; /** * Handle 404 Not Found errors */ const notFoundHandler = (req, res, next) => { const error = new AppError( `Cannot find ${req.originalUrl} on this server`, 404 ); next(error); }; /** * Log unhandled rejections */ process.on('unhandledRejection', (reason, promise) => { logger.error('Unhandled Rejection at:', { promise, reason: reason.stack || reason }); // Optional: Exit process in production // process.exit(1); }); /** * Log uncaught exceptions */ process.on('uncaughtException', (error) => { logger.error('Uncaught Exception:', { message: error.message, stack: error.stack }); // Exit process on uncaught exception process.exit(1); }); module.exports = { errorHandler, catchAsync, notFoundHandler };