267e80e2ec
- Refactor server initialization to separate concerns and improve error handling. - Implement centralized environment validation using Zod. - Introduce database, Redis, and queue lifecycle management. - Add health check endpoints for liveness and readiness. - Enhance error handling middleware for better response structure. - Implement rate limiting for API endpoints. - Add request ID middleware for traceability. - Create Sequelize CLI configuration and baseline migration for schema management. - Establish CI workflow with Gitea for testing and syntax checks. - Document foundational changes and migration strategy in PHASE_0_FOUNDATION_STABILIZATION.md. - Add Docker Compose configuration for local development and testing. - Implement unit and integration tests for critical functionality.
47 lines
2.1 KiB
JavaScript
47 lines
2.1 KiB
JavaScript
const { ValidationError, UniqueConstraintError } = require("sequelize");
|
|
const { ZodError } = require("zod");
|
|
|
|
class AppError extends Error {
|
|
constructor(status, code, message, details) {
|
|
super(message);
|
|
this.status = status;
|
|
this.code = code;
|
|
this.details = details;
|
|
}
|
|
}
|
|
|
|
const notFound = (req, _res, next) => next(new AppError(404, "NOT_FOUND", "Route not found"));
|
|
|
|
const errorHandler = (error, req, res, _next) => {
|
|
let status = error.status || error.statusCode || 500;
|
|
let code = error.code || "INTERNAL_ERROR";
|
|
let message = error.message || "An unexpected error occurred";
|
|
let details = error.details;
|
|
|
|
if (error instanceof ZodError) {
|
|
status = 400; code = "VALIDATION_ERROR"; message = "Invalid request data";
|
|
details = error.issues.map(({ path, message: detailMessage }) => ({ field: path.join("."), message: detailMessage }));
|
|
} else if (error instanceof UniqueConstraintError) {
|
|
status = 409; code = "CONFLICT"; message = "A record with these values already exists";
|
|
details = error.errors?.map(({ path, message: detailMessage }) => ({ field: path, message: detailMessage }));
|
|
} else if (error instanceof ValidationError) {
|
|
status = 400; code = "VALIDATION_ERROR"; message = "Invalid request data";
|
|
details = error.errors?.map(({ path, message: detailMessage }) => ({ field: path, message: detailMessage }));
|
|
} else if (error.type === "entity.too.large") {
|
|
status = 413; code = "PAYLOAD_TOO_LARGE"; message = "Request body is too large";
|
|
} else if (error.name === "UnauthorizedError" || error.name === "JsonWebTokenError") {
|
|
status = 401; code = "UNAUTHORIZED"; message = "Authentication failed";
|
|
}
|
|
|
|
if (status >= 500) {
|
|
console.error(`[${req.id || "no-request-id"}] Request failed`, { name: error.name, message: error.message });
|
|
if (process.env.NODE_ENV === "production") message = "An unexpected error occurred";
|
|
}
|
|
|
|
const payload = { success: false, error: { code, message }, requestId: req.id };
|
|
if (details && status < 500) payload.error.details = details;
|
|
res.status(status).json(payload);
|
|
};
|
|
|
|
module.exports = { AppError, notFound, errorHandler };
|