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
text/typescript
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(),
});
};