const logger = require('../utils/logger'); // Custom error classes class AppError extends Error { constructor(message, statusCode) { super(message); this.statusCode = statusCode; this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'; this.isOperational = true; Error.captureStackTrace(this, this.constructor); } } class ValidationError extends AppError { constructor(message) { super(message, 400); } } class AuthenticationError extends AppError { constructor(message = 'Authentication failed') { super(message, 401); } } class AuthorizationError extends AppError { constructor(message = 'Access denied') { super(message, 403); } } class NotFoundError extends AppError { constructor(message = 'Resource not found') { super(message, 404); } } class ConflictError extends AppError { constructor(message = 'Resource conflict') { super(message, 409); } } class DatabaseError extends AppError { constructor(message = 'Database operation failed') { super(message, 500); } } // Error handling middleware const errorHandler = (err, req, res, next) => { let error = { ...err }; error.message = err.message; // Log error logger.error({ error: err, request: { method: req.method, url: req.url, ip: req.ip, userAgent: req.get('User-Agent'), userId: req.userId } }); // Mongoose validation error if (err.name === 'ValidationError') { const message = Object.values(err.errors).map(val => val.message).join(', '); error = new ValidationError(message); } // Mongoose duplicate key error if (err.code === 11000) { const field = Object.keys(err.keyValue)[0]; const value = err.keyValue[field]; error = new ConflictError(`${field} '${value}' already exists`); } // Mongoose cast error if (err.name === 'CastError') { error = new ValidationError(`Invalid ${err.path}: ${err.value}`); } // JWT errors if (err.name === 'JsonWebTokenError') { error = new AuthenticationError('Invalid token'); } if (err.name === 'TokenExpiredError') { error = new AuthenticationError('Token expired'); } // Default error if (!error.isOperational) { error = new AppError('Something went wrong', 500); } res.status(error.statusCode || 500).json({ status: error.status || 'error', message: error.message, ...(process.env.NODE_ENV === 'development' && { stack: error.stack, error: err }) }); }; // Async error wrapper const catchAsync = (fn) => { return (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; }; // 404 handler const notFound = (req, res, next) => { const error = new NotFoundError(`Route ${req.originalUrl} not found`); next(error); }; module.exports = { AppError, ValidationError, AuthenticationError, AuthorizationError, NotFoundError, ConflictError, DatabaseError, errorHandler, catchAsync, notFound };