145 lines
2.9 KiB
JavaScript
145 lines
2.9 KiB
JavaScript
/**
|
|
* 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,
|
|
};
|