- Deleted obsolete service architecture templates in both English and Vietnamese to streamline content. - Updated the Vietnamese architecture documentation with improved Mermaid diagrams for better visual clarity. - Enhanced color coding in diagrams to improve readability and consistency across documentation. - Added a new section detailing visual indicators for better understanding of architecture components.
206 lines
6.7 KiB
TypeScript
206 lines
6.7 KiB
TypeScript
import { logger } from '@goodgo/logger';
|
|
import express from 'express';
|
|
|
|
import { ErrorCode, getStatusFromErrorCode, isOperationalError } from '../errors/error-codes';
|
|
import { HttpError } from '../errors/http-error';
|
|
|
|
/**
|
|
* EN: Global error handler middleware with enhanced error handling
|
|
* VI: Middleware xử lý lỗi toàn cục với enhanced error handling
|
|
*/
|
|
export const errorHandler = (
|
|
err: any,
|
|
req: express.Request,
|
|
res: express.Response,
|
|
_next: express.NextFunction
|
|
): void => {
|
|
let statusCode = 500;
|
|
let errorCode = ErrorCode.INTERNAL_ERROR;
|
|
let message = 'Internal server error / Lỗi máy chủ nội bộ';
|
|
let details: any = undefined;
|
|
let isOperational = false;
|
|
|
|
// EN: Handle HttpError instances (our custom errors)
|
|
// VI: Xử lý HttpError instances (custom errors của chúng ta)
|
|
if (err instanceof HttpError) {
|
|
statusCode = err.statusCode;
|
|
errorCode = err.errorCode as ErrorCode;
|
|
message = err.message;
|
|
details = err.details;
|
|
isOperational = err.isOperational;
|
|
}
|
|
// EN: Handle Prisma errors
|
|
// VI: Xử lý Prisma errors
|
|
else if (err.code && typeof err.code === 'string') {
|
|
if (err.code === 'P2002') {
|
|
// Unique constraint violation
|
|
statusCode = 409;
|
|
errorCode = ErrorCode.CONSTRAINT_VIOLATION;
|
|
message = 'Resource already exists / Tài nguyên đã tồn tại';
|
|
isOperational = true;
|
|
} else if (err.code.startsWith('P1')) {
|
|
// Database connection/query errors
|
|
statusCode = 500;
|
|
errorCode = ErrorCode.DATABASE_ERROR;
|
|
message = 'Database operation failed / Thao tác database thất bại';
|
|
isOperational = false;
|
|
} else if (err.code.startsWith('P2')) {
|
|
// Data validation errors
|
|
statusCode = 422;
|
|
errorCode = ErrorCode.VALIDATION_ERROR;
|
|
message = 'Data validation failed / Validation dữ liệu thất bại';
|
|
isOperational = true;
|
|
}
|
|
}
|
|
// EN: Handle JWT errors
|
|
// VI: Xử lý JWT errors
|
|
else if (err.name === 'JsonWebTokenError') {
|
|
statusCode = 401;
|
|
errorCode = ErrorCode.INVALID_TOKEN;
|
|
message = 'Invalid authentication token / Token xác thực không hợp lệ';
|
|
isOperational = true;
|
|
} else if (err.name === 'TokenExpiredError') {
|
|
statusCode = 401;
|
|
errorCode = ErrorCode.TOKEN_EXPIRED;
|
|
message = 'Authentication token expired / Token xác thực đã hết hạn';
|
|
isOperational = true;
|
|
}
|
|
// EN: Handle Zod validation errors
|
|
// VI: Xử lý Zod validation errors
|
|
else if (err.name === 'ZodError') {
|
|
statusCode = 422;
|
|
errorCode = ErrorCode.VALIDATION_ERROR;
|
|
message = 'Validation failed / Validation thất bại';
|
|
details = err.errors.map((e: any) => ({
|
|
field: e.path.join('.'),
|
|
message: e.message,
|
|
code: e.code,
|
|
}));
|
|
isOperational = true;
|
|
}
|
|
// EN: Handle Express/Multer file upload errors
|
|
// VI: Xử lý Express/Multer file upload errors
|
|
else if (err.name === 'MulterError') {
|
|
statusCode = 400;
|
|
errorCode = ErrorCode.INVALID_FORMAT;
|
|
message = 'File upload error / Lỗi upload file';
|
|
isOperational = true;
|
|
}
|
|
// EN: Handle rate limiting errors
|
|
// VI: Xử lý rate limiting errors
|
|
else if (err.message && err.message.includes('Too many requests')) {
|
|
statusCode = 429;
|
|
errorCode = ErrorCode.RATE_LIMIT_EXCEEDED;
|
|
message = 'Rate limit exceeded / Vượt quá giới hạn tốc độ';
|
|
isOperational = true;
|
|
}
|
|
// EN: Handle generic errors
|
|
// VI: Xử lý generic errors
|
|
else {
|
|
// EN: Try to map error message patterns
|
|
// VI: Thử map error message patterns
|
|
const errorMessage = err.message?.toLowerCase() || '';
|
|
|
|
if (errorMessage.includes('not found')) {
|
|
statusCode = 404;
|
|
errorCode = ErrorCode.NOT_FOUND;
|
|
message = err.message;
|
|
isOperational = true;
|
|
} else if (errorMessage.includes('unauthorized') || errorMessage.includes('not authenticated')) {
|
|
statusCode = 401;
|
|
errorCode = ErrorCode.UNAUTHORIZED;
|
|
message = err.message;
|
|
isOperational = true;
|
|
} else if (errorMessage.includes('forbidden') || errorMessage.includes('not allowed')) {
|
|
statusCode = 403;
|
|
errorCode = ErrorCode.FORBIDDEN;
|
|
message = err.message;
|
|
isOperational = true;
|
|
} else if (errorMessage.includes('validation') || errorMessage.includes('invalid')) {
|
|
statusCode = 422;
|
|
errorCode = ErrorCode.VALIDATION_ERROR;
|
|
message = err.message;
|
|
isOperational = true;
|
|
}
|
|
}
|
|
|
|
// EN: Prepare error details for logging
|
|
// VI: Chuẩn bị chi tiết lỗi để logging
|
|
const errorDetails = {
|
|
message: err.message,
|
|
name: err.name,
|
|
code: err.code,
|
|
statusCode,
|
|
errorCode,
|
|
isOperational,
|
|
stack: err.stack,
|
|
url: req.url,
|
|
method: req.method,
|
|
userAgent: req.get('User-Agent'),
|
|
ip: req.ip,
|
|
userId: (req as any).user?.userId,
|
|
details,
|
|
};
|
|
|
|
// EN: Log error with appropriate level
|
|
// VI: Log lỗi với level phù hợp
|
|
if (!isOperational || statusCode >= 500) {
|
|
logger.error('Unhandled error occurred / Lỗi không mong muốn xảy ra', errorDetails);
|
|
} else {
|
|
logger.warn('Operational error occurred / Lỗi operational xảy ra', errorDetails);
|
|
}
|
|
|
|
// EN: Prepare response based on environment
|
|
// VI: Chuẩn bị response dựa trên environment
|
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
|
|
const response = {
|
|
success: false,
|
|
error: {
|
|
code: errorCode,
|
|
message: isProduction && statusCode >= 500 ? 'Internal server error / Lỗi máy chủ nội bộ' : message,
|
|
...(details && !isProduction && { details }),
|
|
},
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
|
|
res.status(statusCode).json(response);
|
|
};
|
|
|
|
/**
|
|
* EN: 404 Not Found handler with enhanced error details
|
|
* VI: Handler 404 Not Found với enhanced error details
|
|
*/
|
|
export const notFoundHandler = (
|
|
req: express.Request,
|
|
_res: express.Response,
|
|
next: express.NextFunction
|
|
): void => {
|
|
const error = new HttpError(
|
|
`Route ${req.originalUrl} not found / Route ${req.originalUrl} không tìm thấy`,
|
|
404,
|
|
ErrorCode.NOT_FOUND
|
|
);
|
|
next(error);
|
|
};
|
|
|
|
/**
|
|
* EN: Async error wrapper to catch promise rejections
|
|
* VI: Async error wrapper để catch promise rejections
|
|
*/
|
|
export const asyncHandler = (fn: Function) => {
|
|
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
Promise.resolve(fn(req, res, next)).catch(next);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* EN: Create HttpError from error code
|
|
* VI: Tạo HttpError từ error code
|
|
*/
|
|
export const createHttpError = (errorCode: ErrorCode, message?: string, details?: any): HttpError => {
|
|
const statusCode = getStatusFromErrorCode(errorCode);
|
|
const isOperational = isOperationalError(errorCode);
|
|
return new HttpError(message || `${errorCode}`, statusCode, errorCode, isOperational, details);
|
|
};
|