Major Enhancement: NestJS Migration + CASL Authorization + Error Handling
Some checks failed
CI/CD Pipeline / Backend Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Tests (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
Some checks failed
CI/CD Pipeline / Backend Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Tests (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
Complete rewrite from Express to NestJS with enterprise-grade features: ## Backend Improvements - Migrated from Express to NestJS 11.0.1 with TypeScript - Implemented Prisma ORM 7.3.0 for type-safe database access - Added CASL authorization system replacing role-based guards - Created global exception filters with structured logging - Implemented Auth0 JWT authentication with Passport.js - Added vehicle management with conflict detection - Enhanced event scheduling with driver/vehicle assignment - Comprehensive error handling and logging ## Frontend Improvements - Upgraded to React 19.2.0 with Vite 7.2.4 - Implemented CASL-based permission system - Added AbilityContext for declarative permissions - Created ErrorHandler utility for consistent error messages - Enhanced API client with request/response logging - Added War Room (Command Center) dashboard - Created VIP Schedule view with complete itineraries - Implemented Vehicle Management UI - Added mock data generators for testing (288 events across 20 VIPs) ## New Features - Vehicle fleet management (types, capacity, status tracking) - Complete 3-day Jamboree schedule generation - Individual VIP schedule pages with PDF export (planned) - Real-time War Room dashboard with auto-refresh - Permission-based navigation filtering - First user auto-approval as administrator ## Documentation - Created CASL_AUTHORIZATION.md (comprehensive guide) - Created ERROR_HANDLING.md (error handling patterns) - Updated CLAUDE.md with new architecture - Added migration guides and best practices ## Technical Debt Resolved - Removed custom authentication in favor of Auth0 - Replaced role checks with CASL abilities - Standardized error responses across API - Implemented proper TypeScript typing - Added comprehensive logging Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
337
ERROR_HANDLING.md
Normal file
337
ERROR_HANDLING.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# Error Handling Guide
|
||||
|
||||
This document describes the error handling patterns implemented in the VIP Coordinator application.
|
||||
|
||||
## Backend Error Handling
|
||||
|
||||
### Global Exception Filters
|
||||
|
||||
The backend uses two global exception filters for consistent error responses:
|
||||
|
||||
#### 1. HttpExceptionFilter
|
||||
Handles all HTTP exceptions (4xx, 5xx errors) and formats them consistently.
|
||||
|
||||
**Features:**
|
||||
- Standardized error response format
|
||||
- Automatic logging with appropriate levels (error/warn/log)
|
||||
- Request context logging (params, query, body)
|
||||
- Sensitive data redaction (passwords, tokens, API keys)
|
||||
|
||||
**Error Response Format:**
|
||||
```json
|
||||
{
|
||||
"statusCode": 400,
|
||||
"timestamp": "2026-01-25T10:30:00.000Z",
|
||||
"path": "/api/v1/events",
|
||||
"method": "POST",
|
||||
"message": "Validation failed",
|
||||
"error": "Bad Request",
|
||||
"details": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. AllExceptionsFilter
|
||||
Catch-all filter for unhandled errors (database errors, runtime exceptions, etc.).
|
||||
|
||||
**Features:**
|
||||
- Catches all non-HTTP exceptions
|
||||
- Prevents server crashes from unhandled errors
|
||||
- Logs full stack traces
|
||||
- Returns 500 Internal Server Error to client
|
||||
- In development: includes stack trace in response
|
||||
|
||||
### Using Exceptions in Controllers/Services
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
|
||||
// Simple error
|
||||
throw new NotFoundException('VIP not found');
|
||||
|
||||
// Error with details
|
||||
throw new BadRequestException({
|
||||
message: 'Driver has conflicting events',
|
||||
conflicts: [
|
||||
{ id: '123', title: 'Airport Pickup', startTime: '...' }
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### Logging Best Practices
|
||||
|
||||
```typescript
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
export class MyService {
|
||||
private readonly logger = new Logger(MyService.name);
|
||||
|
||||
async doSomething() {
|
||||
this.logger.log('Starting operation...');
|
||||
this.logger.warn('Conflict detected');
|
||||
this.logger.error('Operation failed', error.stack);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Frontend Error Handling
|
||||
|
||||
### ErrorHandler Utility
|
||||
|
||||
The `ErrorHandler` class provides centralized error handling for the frontend.
|
||||
|
||||
**Location:** `frontend/src/lib/errorHandler.ts`
|
||||
|
||||
### Quick Usage
|
||||
|
||||
```typescript
|
||||
import { handleError } from '@/lib/errorHandler';
|
||||
|
||||
try {
|
||||
await api.createVIP(data);
|
||||
toast.success('VIP created successfully');
|
||||
} catch (error) {
|
||||
handleError('VIP Creation', error, 'Failed to create VIP');
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced Usage
|
||||
|
||||
#### Extract Error Message Only
|
||||
```typescript
|
||||
import { ErrorHandler } from '@/lib/errorHandler';
|
||||
|
||||
try {
|
||||
await api.get('/vips');
|
||||
} catch (error) {
|
||||
const message = ErrorHandler.getMessage(error, 'Failed to load VIPs');
|
||||
setErrorMessage(message);
|
||||
}
|
||||
```
|
||||
|
||||
#### Show Toast Only
|
||||
```typescript
|
||||
ErrorHandler.showError(error, 'Operation failed');
|
||||
```
|
||||
|
||||
#### Log Only (No Toast)
|
||||
```typescript
|
||||
ErrorHandler.log('VIP Fetch', error, { vipId: '123' });
|
||||
```
|
||||
|
||||
#### Check Error Type
|
||||
```typescript
|
||||
try {
|
||||
await api.updateVIP(id, data);
|
||||
} catch (error) {
|
||||
if (ErrorHandler.isAuthError(error)) {
|
||||
// Redirect to login
|
||||
navigate('/login');
|
||||
} else if (ErrorHandler.isConflict(error)) {
|
||||
// Show conflict resolution UI
|
||||
const conflicts = ErrorHandler.getConflicts(error);
|
||||
setConflicts(conflicts);
|
||||
} else {
|
||||
ErrorHandler.showError(error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Available Methods
|
||||
|
||||
| Method | Description | Example |
|
||||
|--------|-------------|---------|
|
||||
| `getMessage(error, fallback?)` | Extract user-friendly message | `const msg = ErrorHandler.getMessage(error)` |
|
||||
| `showError(error, fallback?)` | Show error toast | `ErrorHandler.showError(error)` |
|
||||
| `log(context, error, info?)` | Log to console with context | `ErrorHandler.log('API', error, { id })` |
|
||||
| `handle(context, error, fallback?, info?)` | Log + toast | `ErrorHandler.handle('Save', error)` |
|
||||
| `isAuthError(error)` | Check if 401/403 | `if (ErrorHandler.isAuthError(error))` |
|
||||
| `isConflict(error)` | Check if 409 | `if (ErrorHandler.isConflict(error))` |
|
||||
| `isValidationError(error)` | Check if 400 | `if (ErrorHandler.isValidationError(error))` |
|
||||
| `getConflicts(error)` | Extract conflicts array | `const conflicts = ErrorHandler.getConflicts(error)` |
|
||||
|
||||
### API Client Logging
|
||||
|
||||
The axios client automatically logs all requests and responses in development mode.
|
||||
|
||||
**Console output format:**
|
||||
```
|
||||
[API] → POST /api/v1/vips { data: {...} }
|
||||
[API] ← 201 POST /api/v1/vips { data: {...} }
|
||||
[API] ✖ 400 POST /api/v1/events { status: 400, data: {...} }
|
||||
```
|
||||
|
||||
**Error types logged:**
|
||||
- 401: Authentication required
|
||||
- 403: Permission denied
|
||||
- 404: Resource not found
|
||||
- 409: Conflict (with conflict details)
|
||||
- 500+: Server error
|
||||
|
||||
### Migration Guide
|
||||
|
||||
#### Before (Old Pattern)
|
||||
```typescript
|
||||
try {
|
||||
await api.createVIP(data);
|
||||
toast.success('VIP created');
|
||||
} catch (error: any) {
|
||||
console.error('Failed to create VIP:', error);
|
||||
toast.error(error.response?.data?.message || 'Failed to create VIP');
|
||||
}
|
||||
```
|
||||
|
||||
#### After (New Pattern)
|
||||
```typescript
|
||||
import { handleError } from '@/lib/errorHandler';
|
||||
|
||||
try {
|
||||
await api.createVIP(data);
|
||||
toast.success('VIP created');
|
||||
} catch (error) {
|
||||
handleError('VIP Creation', error, 'Failed to create VIP');
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Consistent error messages across app
|
||||
- ✅ Automatic console logging with context
|
||||
- ✅ Handles all error types (Axios, Error, unknown)
|
||||
- ✅ Type-safe error checking
|
||||
- ✅ Less code duplication
|
||||
|
||||
## Common Error Scenarios
|
||||
|
||||
### 1. Network Error (Backend Down)
|
||||
**Backend logs:** None (backend not reachable)
|
||||
**Frontend logs:** `[API] ✖ Network error - no response received`
|
||||
**User sees:** Toast: "Network error. Please check your connection."
|
||||
|
||||
### 2. Authentication Error (401)
|
||||
**Backend logs:** `[WARN] [GET] /api/v1/vips - 401 - Unauthorized`
|
||||
**Frontend logs:** `[API] ✖ 401 GET /api/v1/vips` + warning
|
||||
**User sees:** Toast: "Authentication required. Please log in again."
|
||||
|
||||
### 3. Permission Error (403)
|
||||
**Backend logs:** `[WARN] [POST] /api/v1/users - 403 - Forbidden resource`
|
||||
**Frontend logs:** `[API] ✖ 403 POST /api/v1/users` + warning
|
||||
**User sees:** Toast: "You do not have permission to perform this action."
|
||||
|
||||
### 4. Validation Error (400)
|
||||
**Backend logs:** `[WARN] [POST] /api/v1/events - 400 - Validation failed`
|
||||
**Frontend logs:** `[API] ✖ 400 POST /api/v1/events` + response data
|
||||
**User sees:** Toast: "Validation failed: email must be a valid email"
|
||||
|
||||
### 5. Conflict Error (409)
|
||||
**Backend logs:** `[WARN] [POST] /api/v1/events - 409 - Driver has conflicting events`
|
||||
**Frontend logs:** `[API] ✖ 409 POST /api/v1/events` + conflicts array
|
||||
**User sees:** Toast: "A conflict occurred. Please check for overlapping schedules."
|
||||
|
||||
### 6. Server Error (500)
|
||||
**Backend logs:** `[ERROR] [GET] /api/v1/vips - 500 - Internal server error` + stack trace
|
||||
**Frontend logs:** `[API] ✖ 500 GET /api/v1/vips` + error message
|
||||
**User sees:** Toast: "Server error. Please try again later."
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ DO
|
||||
|
||||
```typescript
|
||||
// Use handleError for simple cases
|
||||
try {
|
||||
await api.post('/vips', data);
|
||||
toast.success('Success');
|
||||
} catch (error) {
|
||||
handleError('VIP Creation', error);
|
||||
}
|
||||
|
||||
// Use ErrorHandler methods for complex cases
|
||||
try {
|
||||
await api.updateEvent(id, data);
|
||||
toast.success('Event updated');
|
||||
} catch (error) {
|
||||
if (ErrorHandler.isConflict(error)) {
|
||||
// Show conflict resolution dialog
|
||||
showConflictDialog(ErrorHandler.getConflicts(error));
|
||||
} else {
|
||||
ErrorHandler.showError(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Log errors with context
|
||||
ErrorHandler.log('Event Update', error, { eventId: id, changes: data });
|
||||
|
||||
// Use structured logging in backend
|
||||
this.logger.log(`Created VIP: ${vip.name} (ID: ${vip.id})`);
|
||||
this.logger.error(`Failed to create VIP: ${error.message}`, error.stack);
|
||||
```
|
||||
|
||||
### ❌ DON'T
|
||||
|
||||
```typescript
|
||||
// Don't use generic error messages
|
||||
catch (error) {
|
||||
toast.error('An error occurred'); // Too vague!
|
||||
}
|
||||
|
||||
// Don't ignore errors
|
||||
catch (error) {
|
||||
console.log(error); // Use console.error and proper context
|
||||
}
|
||||
|
||||
// Don't manually extract error messages
|
||||
catch (error: any) {
|
||||
const msg = error.response?.data?.message || 'Failed'; // Use ErrorHandler.getMessage()
|
||||
toast.error(msg);
|
||||
}
|
||||
|
||||
// Don't log sensitive data
|
||||
this.logger.log(`User login: ${email} with password ${password}`); // Never log passwords!
|
||||
```
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
### Enable Verbose Logging
|
||||
|
||||
**Backend:** Set `LOG_LEVEL=debug` in `.env`
|
||||
|
||||
**Frontend:** Development mode already has verbose logging enabled
|
||||
|
||||
### Check Error Context
|
||||
|
||||
**Backend logs include:**
|
||||
- Timestamp
|
||||
- HTTP method
|
||||
- URL path
|
||||
- Status code
|
||||
- Request params/query/body
|
||||
- User email (if authenticated)
|
||||
|
||||
**Frontend logs include:**
|
||||
- Timestamp
|
||||
- Context (feature area)
|
||||
- HTTP method
|
||||
- URL
|
||||
- Request/response data
|
||||
- Error type
|
||||
|
||||
### Common Issues
|
||||
|
||||
**"Network error" but backend is running:**
|
||||
- Check CORS configuration
|
||||
- Verify API_URL in `.env`
|
||||
- Check browser DevTools Network tab
|
||||
|
||||
**"Authentication required" after login:**
|
||||
- Check if token is stored in localStorage
|
||||
- Verify Auth0 configuration
|
||||
- Check if user is approved in database
|
||||
|
||||
**Validation errors don't show field names:**
|
||||
- Backend DTO needs proper validation decorators
|
||||
- Use class-validator's @IsString(), @IsEmail(), etc.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-01-25
|
||||
**See also:** [CLAUDE.md](./CLAUDE.md) for general project documentation
|
||||
Reference in New Issue
Block a user