scoreaimanage / lib /error-handler.ts
PenceZao's picture
first commit
2a889d3
import { NextRequest, NextResponse } from 'next/server';
// Error types
export enum ErrorType {
VALIDATION_ERROR = 'VALIDATION_ERROR',
AUTHENTICATION_ERROR = 'AUTHENTICATION_ERROR',
AUTHORIZATION_ERROR = 'AUTHORIZATION_ERROR',
NOT_FOUND_ERROR = 'NOT_FOUND_ERROR',
DUPLICATE_ERROR = 'DUPLICATE_ERROR',
DATABASE_ERROR = 'DATABASE_ERROR',
RATE_LIMIT_ERROR = 'RATE_LIMIT_ERROR',
INTERNAL_ERROR = 'INTERNAL_ERROR'
}
// Custom error class
export class AppError extends Error {
public readonly type: ErrorType;
public readonly statusCode: number;
public readonly isOperational: boolean;
public readonly context?: any;
constructor(
message: string,
type: ErrorType,
statusCode: number = 500,
isOperational: boolean = true,
context?: any
) {
super(message);
this.type = type;
this.statusCode = statusCode;
this.isOperational = isOperational;
this.context = context;
Error.captureStackTrace(this, this.constructor);
}
}
// Logger utility
export class Logger {
private static log(level: string, message: string, meta?: any) {
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
level,
message,
meta,
environment: process.env.NODE_ENV || 'development'
};
// In production, send to logging service
if (process.env.NODE_ENV === 'production') {
console.log(JSON.stringify(logEntry));
} else {
console.log(`[${timestamp}] ${level.toUpperCase()}: ${message}`);
if (meta) console.log('Meta:', meta);
}
}
static info(message: string, meta?: any) {
this.log('info', message, meta);
}
static warn(message: string, meta?: any) {
this.log('warn', message, meta);
}
static error(message: string, error?: Error | any, meta?: any) {
this.log('error', message, {
error: error?.message || error,
stack: error?.stack,
...meta
});
}
static debug(message: string, meta?: any) {
if (process.env.NODE_ENV === 'development') {
this.log('debug', message, meta);
}
}
static security(message: string, meta?: any) {
this.log('security', message, {
...meta,
timestamp: new Date().toISOString(),
ip: meta?.ip || 'unknown',
userAgent: meta?.userAgent || 'unknown'
});
}
}
// Audit logging for data changes
export class AuditLogger {
static log(
action: string,
entity: string,
entityId: string,
userId: string,
changes?: any,
meta?: any
) {
const auditEntry = {
timestamp: new Date().toISOString(),
action,
entity,
entityId,
userId,
changes,
meta,
environment: process.env.NODE_ENV || 'development'
};
Logger.info(`AUDIT: ${action} on ${entity} ${entityId}`, auditEntry);
// In production, store in secure audit database
if (process.env.NODE_ENV === 'production') {
// TODO: Implement secure audit log storage
}
}
static logSecurity(
eventType: string,
identifier: string,
details?: any
) {
const securityEntry = {
timestamp: new Date().toISOString(),
eventType,
identifier,
details,
environment: process.env.NODE_ENV || 'development'
};
Logger.security(`SECURITY: ${eventType} from ${identifier}`, securityEntry);
// In production, store in secure security log database
if (process.env.NODE_ENV === 'production') {
// TODO: Implement secure security log storage
}
}
}
// Error handler middleware
export function handleApiError(error: any, request?: NextRequest): NextResponse {
Logger.error('API Error occurred', error, {
url: request?.url,
method: request?.method,
userAgent: request?.headers.get('user-agent'),
ip: request?.headers.get('x-forwarded-for') || request?.headers.get('x-real-ip')
});
// Handle validation errors
if (error.type === ErrorType.VALIDATION_ERROR) {
return NextResponse.json(
{
success: false,
error: 'Validation Error',
message: error.message,
details: error.context?.errors || []
},
{ status: 400 }
);
}
// Handle authentication errors
if (error.type === ErrorType.AUTHENTICATION_ERROR) {
Logger.security('Authentication failed', {
url: request?.url,
userAgent: request?.headers.get('user-agent')
});
return NextResponse.json(
{
success: false,
error: 'Authentication Error',
message: 'Please provide valid authentication credentials'
},
{ status: 401 }
);
}
// Handle authorization errors
if (error.type === ErrorType.AUTHORIZATION_ERROR) {
Logger.security('Authorization failed', {
url: request?.url,
userAgent: request?.headers.get('user-agent')
});
return NextResponse.json(
{
success: false,
error: 'Authorization Error',
message: 'You do not have permission to perform this action'
},
{ status: 403 }
);
}
// Handle not found errors
if (error.type === ErrorType.NOT_FOUND_ERROR) {
return NextResponse.json(
{
success: false,
error: 'Not Found',
message: error.message
},
{ status: 404 }
);
}
// Handle duplicate errors
if (error.type === ErrorType.DUPLICATE_ERROR) {
return NextResponse.json(
{
success: false,
error: 'Duplicate Entry',
message: error.message
},
{ status: 409 }
);
}
// Handle rate limit errors
if (error.type === ErrorType.RATE_LIMIT_ERROR) {
return NextResponse.json(
{
success: false,
error: 'Rate Limit Exceeded',
message: 'Too many requests. Please try again later.'
},
{ status: 429 }
);
}
// Handle database errors
if (error.type === ErrorType.DATABASE_ERROR) {
Logger.error('Database error occurred', error);
return NextResponse.json(
{
success: false,
error: 'Database Error',
message: 'An error occurred while processing your request'
},
{ status: 500 }
);
}
// Handle all other errors
const isDevelopment = process.env.NODE_ENV === 'development';
return NextResponse.json(
{
success: false,
error: 'Internal Server Error',
message: isDevelopment ? error.message : 'An unexpected error occurred',
...(isDevelopment && { stack: error.stack, context: error.context })
},
{ status: error.statusCode || 500 }
);
}
// Validation error helper
export function createValidationError(errors: string[], context?: any): AppError {
return new AppError(
'Validation failed',
ErrorType.VALIDATION_ERROR,
400,
true,
{ errors, ...context }
);
}
// Authentication error helper
export function createAuthenticationError(message: string = 'Authentication required'): AppError {
return new AppError(message, ErrorType.AUTHENTICATION_ERROR, 401);
}
// Authorization error helper
export function createAuthorizationError(message: string = 'Insufficient permissions'): AppError {
return new AppError(message, ErrorType.AUTHORIZATION_ERROR, 403);
}
// Not found error helper
export function createNotFoundError(entity: string, id?: string): AppError {
const message = id ? `${entity} with id ${id} not found` : `${entity} not found`;
return new AppError(message, ErrorType.NOT_FOUND_ERROR, 404);
}
// Duplicate error helper
export function createDuplicateError(entity: string, field: string, value: string): AppError {
return new AppError(
`${entity} with ${field} '${value}' already exists`,
ErrorType.DUPLICATE_ERROR,
409
);
}
// Database error helper
export function createDatabaseError(message: string, originalError?: any): AppError {
return new AppError(
message,
ErrorType.DATABASE_ERROR,
500,
true,
{ originalError: originalError?.message }
);
}
// Rate limit error helper
export function createRateLimitError(limit: number, windowMs: number): AppError {
return new AppError(
`Rate limit exceeded. Maximum ${limit} requests per ${windowMs}ms allowed.`,
ErrorType.RATE_LIMIT_ERROR,
429
);
}