125 lines
2.9 KiB
JavaScript
125 lines
2.9 KiB
JavaScript
const logger = require("../config/logger");
|
|
const {
|
|
isDevelopment,
|
|
PG_ERROR_CODES,
|
|
MULTER_ERROR_CODES,
|
|
STATIC_ASSET_EXTENSIONS,
|
|
} = require("../config/constants");
|
|
|
|
class AppError extends Error {
|
|
constructor(message, statusCode, isOperational = true) {
|
|
super(message);
|
|
this.statusCode = statusCode;
|
|
this.isOperational = isOperational;
|
|
this.timestamp = new Date().toISOString();
|
|
Error.captureStackTrace(this, this.constructor);
|
|
}
|
|
}
|
|
|
|
const ERROR_MAPPINGS = {
|
|
[PG_ERROR_CODES.UNIQUE_VIOLATION]: {
|
|
message: "Duplicate entry: Resource already exists",
|
|
statusCode: 409,
|
|
},
|
|
[PG_ERROR_CODES.FOREIGN_KEY_VIOLATION]: {
|
|
message: "Referenced resource does not exist",
|
|
statusCode: 400,
|
|
},
|
|
[PG_ERROR_CODES.INVALID_TEXT]: {
|
|
message: "Invalid data format",
|
|
statusCode: 400,
|
|
},
|
|
[MULTER_ERROR_CODES.FILE_SIZE]: {
|
|
message: "File too large. Maximum size is 5MB",
|
|
statusCode: 400,
|
|
},
|
|
[MULTER_ERROR_CODES.FILE_COUNT]: {
|
|
message: "Too many files. Maximum is 10 files per upload",
|
|
statusCode: 400,
|
|
},
|
|
};
|
|
|
|
// Global error handler middleware
|
|
const errorHandler = (err, req, res, next) => {
|
|
let error = { ...err };
|
|
error.message = err.message;
|
|
error.statusCode = err.statusCode || 500;
|
|
|
|
// Log error
|
|
logger.error("Error occurred", {
|
|
message: error.message,
|
|
statusCode: error.statusCode,
|
|
path: req.path,
|
|
method: req.method,
|
|
ip: req.ip,
|
|
stack: err.stack,
|
|
});
|
|
|
|
// Map known error codes
|
|
const errorMapping = ERROR_MAPPINGS[err.code];
|
|
if (errorMapping) {
|
|
error.message = errorMapping.message;
|
|
error.statusCode = errorMapping.statusCode;
|
|
}
|
|
|
|
// SAFEGUARD: Don't send response if headers already sent
|
|
if (res.headersSent) {
|
|
logger.warn("Headers already sent in error handler", {
|
|
path: req.path,
|
|
error: error.message,
|
|
});
|
|
return next(err);
|
|
}
|
|
|
|
res.status(error.statusCode).json({
|
|
success: false,
|
|
message: error.message || "Server error",
|
|
...(isDevelopment() && {
|
|
error: err.message,
|
|
stack: err.stack,
|
|
}),
|
|
});
|
|
};
|
|
|
|
// 404 handler
|
|
const notFoundHandler = (req, res) => {
|
|
const isStaticAsset = STATIC_ASSET_EXTENSIONS.test(req.path);
|
|
|
|
if (!isStaticAsset) {
|
|
logger.warn("Route not found", {
|
|
path: req.path,
|
|
method: req.method,
|
|
ip: req.ip,
|
|
});
|
|
} else {
|
|
logger.debug("Static asset not found", {
|
|
path: req.path,
|
|
method: req.method,
|
|
});
|
|
}
|
|
|
|
// SAFEGUARD: Check if response already sent
|
|
if (res.headersSent) {
|
|
logger.warn("Headers already sent in 404 handler", { path: req.path });
|
|
return;
|
|
}
|
|
|
|
res.status(404).json({
|
|
success: false,
|
|
message: "Route not found",
|
|
path: req.path,
|
|
});
|
|
};
|
|
|
|
// Async handler wrapper to catch errors in async routes
|
|
const asyncHandler = (fn) => (req, res, next) => {
|
|
Promise.resolve(fn(req, res, next)).catch(next);
|
|
};
|
|
|
|
module.exports = {
|
|
AppError,
|
|
errorHandler,
|
|
notFoundHandler,
|
|
asyncHandler,
|
|
};
|