Files
Emailsorter/server/middleware/errorHandler.mjs
ANDJ ecae89a79d fix(dev): Vite-API-Proxy, Auth, Stripe-Mails und Backend-Erweiterungen
- Client: API-Basis-URL (joinApiUrl, /v1-Falle), Vite strictPort + Proxy 127.0.0.1, Nicht-JSON-Fehler

- Server: /api-404 ohne Wildcard-Bug, SPA-Fallback, Auth-Middleware, Cron, Mailer, Crypto

- Routen: OAuth-State, Email/Stripe/Analytics; client/.env.example

Made-with: Cursor
2026-04-03 00:23:01 +02:00

126 lines
3.1 KiB
JavaScript

/**
* Global Error Handler Middleware
* Catches all errors and returns consistent JSON responses
*/
import { AppwriteException } from 'node-appwrite'
export class AppError extends Error {
constructor(message, statusCode = 500, code = 'INTERNAL_ERROR') {
super(message)
this.statusCode = statusCode
this.code = code
this.isOperational = true
Error.captureStackTrace(this, this.constructor)
}
}
export class ValidationError extends AppError {
constructor(message, fields = {}) {
super(message, 400, 'VALIDATION_ERROR')
this.fields = fields
}
}
export class AuthenticationError extends AppError {
constructor(message = 'Nicht authentifiziert') {
super(message, 401, 'AUTHENTICATION_ERROR')
}
}
export class AuthorizationError extends AppError {
constructor(message = 'Keine Berechtigung') {
super(message, 403, 'AUTHORIZATION_ERROR')
}
}
export class NotFoundError extends AppError {
constructor(resource = 'Ressource') {
super(`${resource} nicht gefunden`, 404, 'NOT_FOUND')
}
}
export class RateLimitError extends AppError {
constructor(message = 'Zu viele Anfragen') {
super(message, 429, 'RATE_LIMIT_EXCEEDED')
}
}
/**
* Error handler middleware
*/
export function errorHandler(err, req, res, next) {
// Log error
console.error(`[ERROR] ${new Date().toISOString()}`, {
method: req.method,
path: req.path,
error: err.message,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
})
// Default error values (AppwriteException uses numeric err.code — do not reuse as JSON "code" string)
let statusCode =
typeof err.statusCode === 'number' ? err.statusCode : undefined
let code = typeof err.code === 'string' && err.code ? err.code : 'INTERNAL_ERROR'
let message = err.message || 'Ein Fehler ist aufgetreten'
if (
err instanceof AppwriteException &&
typeof err.code === 'number' &&
err.code >= 400 &&
err.code < 600
) {
statusCode = err.code
code = err.type || 'APPWRITE_ERROR'
message = err.message || message
err.isOperational = true
}
if (statusCode === undefined) {
statusCode = 500
}
// Handle specific error types
if (err.name === 'ValidationError') {
statusCode = 400
code = 'VALIDATION_ERROR'
}
if (err.name === 'JsonWebTokenError') {
statusCode = 401
code = 'INVALID_TOKEN'
message = 'Ungültiger Token'
}
if (err.name === 'TokenExpiredError') {
statusCode = 401
code = 'TOKEN_EXPIRED'
message = 'Token abgelaufen'
}
// Don't expose internal errors in production
if (!err.isOperational && process.env.NODE_ENV === 'production') {
message = 'Ein interner Fehler ist aufgetreten'
}
// Send response
res.status(statusCode).json({
success: false,
error: {
code,
message,
...(err.fields && { fields: err.fields }),
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
},
})
}
/**
* Async handler wrapper to catch errors in async routes
*/
export function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next)
}
}