/** * Error Handler Middleware * Xử lý tất cả errors trong ứng dụng */ class AppError extends Error { constructor(message, statusCode = 500, isOperational = true) { super(message); this.statusCode = statusCode; this.isOperational = isOperational; this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'; Error.captureStackTrace(this, this.constructor); } } /** * Handle Sequelize Validation Errors */ const handleSequelizeValidationError = (err) => { const errors = err.errors.map(e => ({ field: e.path, message: e.message, })); return new AppError( 'Validation Error', 400, true, errors ); }; /** * Handle Sequelize Unique Constraint Error */ const handleSequelizeUniqueError = (err) => { const field = err.errors[0]?.path || 'field'; const message = `${field} already exists`; return new AppError(message, 409); }; /** * Handle Sequelize Foreign Key Error */ const handleSequelizeForeignKeyError = (err) => { return new AppError('Referenced record not found', 404); }; /** * Development Error Response */ const sendErrorDev = (err, res) => { res.status(err.statusCode).json({ success: false, status: err.status, error: err, message: err.message, stack: err.stack, }); }; /** * Production Error Response */ const sendErrorProd = (err, res) => { // Operational, trusted error: send message to client if (err.isOperational) { res.status(err.statusCode).json({ success: false, status: err.status, message: err.message, errors: err.errors, }); } // Programming or unknown error: don't leak error details else { console.error('❌ ERROR:', err); res.status(500).json({ success: false, status: 'error', message: 'Something went wrong!', }); } }; /** * Global Error Handler Middleware */ const errorHandler = (err, req, res, next) => { err.statusCode = err.statusCode || 500; err.status = err.status || 'error'; if (process.env.NODE_ENV === 'development') { sendErrorDev(err, res); } else { let error = { ...err }; error.message = err.message; // Handle specific Sequelize errors if (err.name === 'SequelizeValidationError') { error = handleSequelizeValidationError(err); } if (err.name === 'SequelizeUniqueConstraintError') { error = handleSequelizeUniqueError(err); } if (err.name === 'SequelizeForeignKeyConstraintError') { error = handleSequelizeForeignKeyError(err); } sendErrorProd(error, res); } }; /** * 404 Handler */ const notFoundHandler = (req, res, next) => { const err = new AppError( `Cannot find ${req.originalUrl} on this server`, 404 ); next(err); }; /** * Async Error Wrapper */ const catchAsync = (fn) => { return (req, res, next) => { fn(req, res, next).catch(next); }; }; module.exports = { AppError, errorHandler, notFoundHandler, catchAsync, };