Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
- Node.js/Express backend with TypeScript - SQLite database for users, sessions, and content metadata - Authentication with TOTP and WebAuthn MFA support - Admin user auto-created on first startup - User content gallery with view/delete functionality - RunPod API proxy (keeps API keys server-side) - Docker setup with CI/CD for Gitea registry 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
65 lines
1.7 KiB
TypeScript
65 lines
1.7 KiB
TypeScript
import type { Request, Response, NextFunction, ErrorRequestHandler } from 'express';
|
|
import { logger } from '../utils/logger.js';
|
|
import { ZodError } from 'zod';
|
|
|
|
export interface AppError extends Error {
|
|
statusCode?: number;
|
|
isOperational?: boolean;
|
|
}
|
|
|
|
export function createError(message: string, statusCode: number = 500): AppError {
|
|
const error: AppError = new Error(message);
|
|
error.statusCode = statusCode;
|
|
error.isOperational = true;
|
|
return error;
|
|
}
|
|
|
|
export const errorHandler: ErrorRequestHandler = (
|
|
err: AppError | Error,
|
|
req: Request,
|
|
res: Response,
|
|
_next: NextFunction
|
|
): void => {
|
|
// Handle Zod validation errors
|
|
if (err instanceof ZodError) {
|
|
const message = err.errors.map(e => e.message).join(', ');
|
|
res.status(400).json({ error: message });
|
|
return;
|
|
}
|
|
|
|
// Get status code and message
|
|
const statusCode = 'statusCode' in err ? err.statusCode || 500 : 500;
|
|
const isOperational = 'isOperational' in err ? err.isOperational : false;
|
|
|
|
// Log error
|
|
if (statusCode >= 500 || !isOperational) {
|
|
logger.error({
|
|
err,
|
|
method: req.method,
|
|
url: req.url,
|
|
statusCode,
|
|
}, 'Unhandled error');
|
|
} else {
|
|
logger.warn({
|
|
message: err.message,
|
|
method: req.method,
|
|
url: req.url,
|
|
statusCode,
|
|
}, 'Operational error');
|
|
}
|
|
|
|
// Send response
|
|
res.status(statusCode).json({
|
|
error: isOperational ? err.message : 'Internal server error',
|
|
});
|
|
};
|
|
|
|
// Async route handler wrapper
|
|
export function asyncHandler<T>(
|
|
fn: (req: Request, res: Response, next: NextFunction) => Promise<T>
|
|
): (req: Request, res: Response, next: NextFunction) => void {
|
|
return (req, res, next) => {
|
|
Promise.resolve(fn(req, res, next)).catch(next);
|
|
};
|
|
}
|