UNPKG

self-serve-integration-service

Version:

Self-Serve Integration Service for managing multiple funder integrations including REST APIs, SOAP APIs, and UI automation

165 lines (146 loc) 4.14 kB
import { Request, Response, NextFunction } from 'express'; import { logger } from '../utils/logger'; export interface AppError extends Error { statusCode?: number; isOperational?: boolean; } export class CustomError extends Error implements AppError { public statusCode: number; public isOperational: boolean; constructor(message: string, statusCode: number = 500) { super(message); this.statusCode = statusCode; this.isOperational = true; Error.captureStackTrace(this, this.constructor); } } export class ValidationError extends CustomError { constructor(message: string) { super(message, 400); } } export class AuthenticationError extends CustomError { constructor(message: string = 'Authentication failed') { super(message, 401); } } export class AuthorizationError extends CustomError { constructor(message: string = 'Access denied') { super(message, 403); } } export class NotFoundError extends CustomError { constructor(message: string = 'Resource not found') { super(message, 404); } } export class ConflictError extends CustomError { constructor(message: string = 'Resource conflict') { super(message, 409); } } export class RateLimitError extends CustomError { constructor(message: string = 'Too many requests') { super(message, 429); } } export class ExternalServiceError extends CustomError { constructor(message: string = 'External service error') { super(message, 502); } } /** * Global error handling middleware */ export const errorHandler = ( error: AppError, req: Request, res: Response, _next: NextFunction ): void => { let { statusCode = 500, message } = error; // Log the error logger.error('Error occurred:', { error: { message: error.message, stack: error.stack, statusCode: error.statusCode, name: error.name, }, request: { method: req.method, url: req.url, ip: req.ip, userAgent: req.get('User-Agent'), }, }); // Handle specific error types if (error.name === 'ValidationError') { statusCode = 400; message = 'Validation failed'; } else if (error.name === 'CastError') { statusCode = 400; message = 'Invalid ID format'; } else if (error.name === 'JsonWebTokenError') { statusCode = 401; message = 'Invalid token'; } else if (error.name === 'TokenExpiredError') { statusCode = 401; message = 'Token expired'; } else if (error.name === 'PrismaClientKnownRequestError') { // Handle Prisma-specific errors const prismaError = error as unknown as { code: string; meta?: { target?: string[] } }; if (prismaError.code === 'P2002') { statusCode = 409; message = 'Duplicate entry'; } else if (prismaError.code === 'P2025') { statusCode = 404; message = 'Record not found'; } else { statusCode = 400; message = 'Database operation failed'; } } else if (error.name === 'PrismaClientValidationError') { statusCode = 400; message = 'Invalid data provided'; } // Don't leak error details in production if (process.env.NODE_ENV === 'production' && statusCode === 500) { message = 'Internal server error'; } // Send error response res.status(statusCode).json({ success: false, message, ...(process.env.NODE_ENV === 'development' && { error: { name: error.name, stack: error.stack, details: error.message, }, }), timestamp: new Date().toISOString(), path: req.url, }); }; /** * Async error wrapper to catch async errors in route handlers */ export const asyncHandler = ( fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown> | void ) => { return (req: Request, res: Response, next: NextFunction) => { Promise.resolve(fn(req, res, next)).catch(next); }; }; /** * 404 handler for unmatched routes */ export const notFoundHandler = (req: Request, res: Response): void => { res.status(404).json({ success: false, message: 'Route not found', path: req.originalUrl, timestamp: new Date().toISOString(), }); };