docs: Organize documentation into structured folders
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

Organized documentation into cleaner structure:

Root directory (user-facing):
- README.md - Main documentation
- CLAUDE.md - AI context (referenced by system)
- QUICKSTART.md - Quick start guide

docs/ (technical documentation):
- CASL_AUTHORIZATION.md - Authorization guide
- ERROR_HANDLING.md - Error handling patterns
- REQUIREMENTS.md - Project requirements

docs/deployment/ (production deployment):
- HTTPS_SETUP.md - SSL/TLS setup
- PRODUCTION_ENVIRONMENT_TEMPLATE.md - Env vars template
- PRODUCTION_VERIFICATION_CHECKLIST.md - Deployment checklist

Removed:
- DOCKER_TROUBLESHOOTING.md - Outdated (referenced Google OAuth, old domain)

Updated references:
- Fixed links to moved files in CASL_AUTHORIZATION.md
- Fixed links to moved files in ERROR_HANDLING.md
- Removed reference to deleted BUILD_STATUS.md in QUICKSTART.md

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 17:13:47 +01:00
parent e8987d5970
commit 440884666d
8 changed files with 3 additions and 182 deletions

650
docs/CASL_AUTHORIZATION.md Normal file
View File

@@ -0,0 +1,650 @@
# CASL Authorization System
This document describes the CASL-based authorization system implemented in the VIP Coordinator application.
## Overview
CASL (pronounced "castle") is an isomorphic authorization library that makes it easy to manage permissions in both frontend and backend code. It allows us to define abilities once and reuse them across the entire application.
**Key Benefits:**
- ✅ Type-safe permissions with TypeScript
- ✅ Consistent authorization logic between frontend and backend
- ✅ Declarative permission checks
- ✅ Easy to extend and maintain
- ✅ Supports complex conditional permissions
## Architecture
### Permissions Model
**Actions:** What can be done
- `manage` - Special action that allows everything
- `create` - Create new resources
- `read` - View resources
- `update` - Modify existing resources
- `delete` - Remove resources
- `approve` - Special: Approve user accounts
- `update-status` - Special: Update event status (for drivers)
**Subjects:** What resources can be acted upon
- `User` - User accounts
- `VIP` - VIP profiles
- `Driver` - Driver resources
- `ScheduleEvent` - Schedule events
- `Flight` - Flight information
- `Vehicle` - Vehicle management
- `all` - Special subject representing all resources
### Role-Based Permissions
| Action | Administrator | Coordinator | Driver |
|--------|--------------|-------------|--------|
| **Users** | | | |
| Create | ✅ | ❌ | ❌ |
| Read | ✅ | ❌ | ❌ |
| Update | ✅ | ❌ | ❌ |
| Delete | ✅ | ❌ | ❌ |
| Approve | ✅ | ❌ | ❌ |
| **VIPs** | | | |
| Create | ✅ | ✅ | ❌ |
| Read | ✅ | ✅ | ✅ |
| Update | ✅ | ✅ | ❌ |
| Delete | ✅ | ✅ | ❌ |
| **Drivers** | | | |
| Create | ✅ | ✅ | ❌ |
| Read | ✅ | ✅ | ✅ |
| Update | ✅ | ✅ | ❌ |
| Delete | ✅ | ✅ | ❌ |
| **Vehicles** | | | |
| Create | ✅ | ✅ | ❌ |
| Read | ✅ | ✅ | ✅ |
| Update | ✅ | ✅ | ❌ |
| Delete | ✅ | ✅ | ❌ |
| **Schedule Events** | | | |
| Create | ✅ | ✅ | ❌ |
| Read | ✅ | ✅ | ✅ |
| Update | ✅ | ✅ | ❌ |
| Delete | ✅ | ✅ | ❌ |
| UpdateStatus | ✅ | ✅ | ✅ (own events) |
| **Flights** | | | |
| Read/Manage | ✅ | ✅ | ❌ |
## Backend Implementation
### 1. Ability Factory
**Location:** `backend/src/auth/abilities/ability.factory.ts`
Defines all permissions based on user roles.
```typescript
import { AbilityFactory, Action } from '../auth/abilities/ability.factory';
@Injectable()
export class MyService {
constructor(private abilityFactory: AbilityFactory) {}
async doSomething(user: User) {
const ability = this.abilityFactory.defineAbilitiesFor(user);
if (ability.can(Action.Create, 'VIP')) {
// User can create VIPs
}
}
}
```
### 2. Abilities Guard
**Location:** `backend/src/auth/guards/abilities.guard.ts`
Guard that checks CASL abilities on routes.
```typescript
import { UseGuards } from '@nestjs/common';
import { AbilitiesGuard } from '../auth/guards/abilities.guard';
@Controller('vips')
@UseGuards(JwtAuthGuard, AbilitiesGuard)
export class VipsController {
// Routes protected by AbilitiesGuard
}
```
### 3. Permission Decorators
**Location:** `backend/src/auth/decorators/check-ability.decorator.ts`
Decorators to specify required permissions on routes.
#### Using Helper Decorators (Recommended)
```typescript
import { CanCreate, CanRead, CanUpdate, CanDelete } from '../auth/decorators/check-ability.decorator';
@Post()
@CanCreate('VIP')
create(@Body() dto: CreateVIPDto) {
return this.service.create(dto);
}
@Get()
@CanRead('VIP')
findAll() {
return this.service.findAll();
}
@Patch(':id')
@CanUpdate('VIP')
update(@Param('id') id: string, @Body() dto: UpdateVIPDto) {
return this.service.update(id, dto);
}
@Delete(':id')
@CanDelete('VIP')
remove(@Param('id') id: string) {
return this.service.remove(id);
}
```
#### Using CheckAbilities Decorator (For Custom Actions)
```typescript
import { CheckAbilities } from '../auth/decorators/check-ability.decorator';
import { Action } from '../auth/abilities/ability.factory';
@Patch(':id/approve')
@CheckAbilities({ action: Action.Approve, subject: 'User' })
approve(@Param('id') id: string, @Body() dto: ApproveUserDto) {
return this.service.approve(id, dto);
}
```
#### Multiple Permissions (All Must Be Satisfied)
```typescript
@Post('complex')
@CheckAbilities(
{ action: Action.Read, subject: 'VIP' },
{ action: Action.Create, subject: 'ScheduleEvent' }
)
complexOperation() {
// User must have BOTH permissions
}
```
### 4. Controller Examples
#### VIPsController
```typescript
import { Controller, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { AbilitiesGuard } from '../auth/guards/abilities.guard';
import { CanCreate, CanRead, CanUpdate, CanDelete } from '../auth/decorators/check-ability.decorator';
@Controller('vips')
@UseGuards(JwtAuthGuard, AbilitiesGuard)
export class VipsController {
@Post()
@CanCreate('VIP')
create(@Body() dto: CreateVipDto) { }
@Get()
@CanRead('VIP')
findAll() { }
@Patch(':id')
@CanUpdate('VIP')
update(@Param('id') id: string, @Body() dto: UpdateVipDto) { }
@Delete(':id')
@CanDelete('VIP')
remove(@Param('id') id: string) { }
}
```
#### UsersController (Admin Only)
```typescript
@Controller('users')
@UseGuards(JwtAuthGuard, AbilitiesGuard)
export class UsersController {
@Get()
@CanRead('User') // Only admins can read users
findAll() { }
@Patch(':id/approve')
@CheckAbilities({ action: Action.Approve, subject: 'User' })
approve(@Param('id') id: string) { }
}
```
## Frontend Implementation
### 1. Ability Definitions
**Location:** `frontend/src/lib/abilities.ts`
Mirrors backend ability definitions for consistent permissions.
```typescript
import { defineAbilitiesFor, Action } from '@/lib/abilities';
const user = { id: '1', role: 'COORDINATOR', isApproved: true };
const ability = defineAbilitiesFor(user);
if (ability.can(Action.Create, 'VIP')) {
// User can create VIPs
}
```
### 2. AbilityContext & Hooks
**Location:** `frontend/src/contexts/AbilityContext.tsx`
Provides CASL abilities throughout the React component tree.
#### useAbility Hook
```typescript
import { useAbility } from '@/contexts/AbilityContext';
import { Action } from '@/lib/abilities';
function MyComponent() {
const ability = useAbility();
const canCreateVIP = ability.can(Action.Create, 'VIP');
const canDeleteDriver = ability.can(Action.Delete, 'Driver');
return (
<div>
{canCreateVIP && <button>Add VIP</button>}
{canDeleteDriver && <button>Delete Driver</button>}
</div>
);
}
```
#### Can Component (Declarative)
```typescript
import { Can } from '@/contexts/AbilityContext';
function MyComponent() {
return (
<div>
<Can I="create" a="VIP">
<button>Add VIP</button>
</Can>
<Can I="update" a="ScheduleEvent">
<button>Edit Event</button>
</Can>
<Can I="delete" a="Driver">
<button>Delete Driver</button>
</Can>
</div>
);
}
```
### 3. Layout Component Example
**Location:** `frontend/src/components/Layout.tsx`
Navigation filtered by permissions:
```typescript
import { useAbility } from '@/contexts/AbilityContext';
import { Action } from '@/lib/abilities';
export function Layout() {
const ability = useAbility();
const allNavigation = [
{ name: 'Dashboard', href: '/dashboard', alwaysShow: true },
{ name: 'VIPs', href: '/vips', requireRead: 'VIP' as const },
{ name: 'Users', href: '/users', requireRead: 'User' as const },
];
const navigation = allNavigation.filter((item) => {
if (item.alwaysShow) return true;
if (item.requireRead) {
return ability.can(Action.Read, item.requireRead);
}
return true;
});
return (
<nav>
{navigation.map(item => (
<Link key={item.name} to={item.href}>{item.name}</Link>
))}
</nav>
);
}
```
### 4. Component Examples
#### Conditional Button Rendering
```typescript
function VIPList() {
const ability = useAbility();
return (
<div>
<h1>VIPs</h1>
{ability.can(Action.Create, 'VIP') && (
<button onClick={handleCreate}>Add VIP</button>
)}
{vips.map(vip => (
<div key={vip.id}>
{vip.name}
{ability.can(Action.Update, 'VIP') && (
<button onClick={() => handleEdit(vip.id)}>Edit</button>
)}
{ability.can(Action.Delete, 'VIP') && (
<button onClick={() => handleDelete(vip.id)}>Delete</button>
)}
</div>
))}
</div>
);
}
```
#### Using Can Component
```typescript
import { Can } from '@/contexts/AbilityContext';
function DriverDashboard() {
return (
<div>
<h1>Driver Dashboard</h1>
<Can I="read" a="ScheduleEvent">
<section>
<h2>My Schedule</h2>
<EventList />
</section>
</Can>
<Can I="update-status" a="ScheduleEvent">
<button>Update Event Status</button>
</Can>
<Can not I="read" a="Flight">
<p>You don't have access to flight information.</p>
</Can>
</div>
);
}
```
## Migration Guide
### Backend Migration
#### Before (Old RolesGuard Pattern)
```typescript
import { UseGuards } from '@nestjs/common';
import { RolesGuard } from '../auth/guards/roles.guard';
import { Roles } from '../auth/decorators/roles.decorator';
import { Role } from '@prisma/client';
@Controller('vips')
@UseGuards(JwtAuthGuard, RolesGuard)
export class VipsController {
@Post()
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR)
create(@Body() dto: CreateVIPDto) { }
@Get()
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR, Role.DRIVER)
findAll() { }
}
```
#### After (New CASL Pattern)
```typescript
import { UseGuards } from '@nestjs/common';
import { AbilitiesGuard } from '../auth/guards/abilities.guard';
import { CanCreate, CanRead } from '../auth/decorators/check-ability.decorator';
@Controller('vips')
@UseGuards(JwtAuthGuard, AbilitiesGuard)
export class VipsController {
@Post()
@CanCreate('VIP')
create(@Body() dto: CreateVIPDto) { }
@Get()
@CanRead('VIP')
findAll() { }
}
```
**Benefits:**
- ✅ More semantic (describes WHAT, not WHO)
- ✅ Type-safe with autocomplete
- ✅ Easier to understand intent
- ✅ Supports complex conditions
### Frontend Migration
#### Before (Direct Role Checks)
```typescript
import { useAuth } from '@/contexts/AuthContext';
function MyComponent() {
const { backendUser } = useAuth();
const isAdmin = backendUser?.role === 'ADMINISTRATOR';
const canManageVIPs = isAdmin || backendUser?.role === 'COORDINATOR';
return (
<div>
{canManageVIPs && <button>Add VIP</button>}
{isAdmin && <Link to="/users">Users</Link>}
</div>
);
}
```
#### After (CASL Abilities)
```typescript
import { useAbility } from '@/contexts/AbilityContext';
import { Action } from '@/lib/abilities';
function MyComponent() {
const ability = useAbility();
return (
<div>
{ability.can(Action.Create, 'VIP') && <button>Add VIP</button>}
{ability.can(Action.Read, 'User') && <Link to="/users">Users</Link>}
</div>
);
}
```
**Or using Can component:**
```typescript
import { Can } from '@/contexts/AbilityContext';
function MyComponent() {
return (
<div>
<Can I="create" a="VIP">
<button>Add VIP</button>
</Can>
<Can I="read" a="User">
<Link to="/users">Users</Link>
</Can>
</div>
);
}
```
## Adding New Permissions
### 1. Define New Action (If Needed)
**Backend:** `backend/src/auth/abilities/ability.factory.ts`
**Frontend:** `frontend/src/lib/abilities.ts`
```typescript
export enum Action {
// ... existing actions
Export = 'export', // New action
}
```
### 2. Update Ability Definitions
**Backend:** `backend/src/auth/abilities/ability.factory.ts`
```typescript
defineAbilitiesFor(user: User): AppAbility {
const { can, cannot, build } = new AbilityBuilder<AppAbility>(/* ... */);
if (user.role === Role.ADMINISTRATOR) {
can(Action.Manage, 'all');
can(Action.Export, 'all'); // Admins can export anything
} else if (user.role === Role.COORDINATOR) {
can(Action.Export, 'VIP'); // Coordinators can only export VIPs
}
return build();
}
```
**Frontend:** `frontend/src/lib/abilities.ts` (same pattern)
### 3. Use in Controllers
```typescript
import { CheckAbilities } from '../auth/decorators/check-ability.decorator';
import { Action } from '../auth/abilities/ability.factory';
@Get('export')
@CheckAbilities({ action: Action.Export, subject: 'VIP' })
export() {
return this.service.exportToCSV();
}
```
### 4. Use in Components
```typescript
import { Can } from '@/contexts/AbilityContext';
function VIPList() {
return (
<div>
<Can I="export" a="VIP">
<button onClick={handleExport}>Export to CSV</button>
</Can>
</div>
);
}
```
## Best Practices
### ✅ DO
```typescript
// Use semantic ability checks
if (ability.can(Action.Create, 'VIP')) { }
// Use Can component for declarative rendering
<Can I="update" a="Driver">
<EditButton />
</Can>
// Group related permissions in decorators
@CheckAbilities(
{ action: Action.Read, subject: 'VIP' },
{ action: Action.Read, subject: 'Driver' }
)
// Define abilities based on resources, not roles
can(Action.Update, 'VIP') // Good
can(Action.Manage, 'all') // For admins only
```
### ❌ DON'T
```typescript
// Don't check roles directly (use abilities instead)
if (user.role === 'ADMINISTRATOR') { } // Bad
// Don't mix role checks and ability checks
if (isAdmin || ability.can(Action.Create, 'VIP')) { } // Confusing
// Don't create overly specific actions
Action.CreateVIPForJamboree // Too specific
Action.Create // Better
// Don't forget to check both frontend and backend
// Backend enforces security, frontend improves UX
```
## Debugging
### Check User Abilities
**Backend:**
```typescript
const ability = this.abilityFactory.defineAbilitiesFor(user);
console.log('Can create VIP?', ability.can(Action.Create, 'VIP'));
console.log('Can manage all?', ability.can(Action.Manage, 'all'));
```
**Frontend:**
```typescript
const ability = useAbility();
console.log('Can create VIP?', ability.can(Action.Create, 'VIP'));
console.log('User abilities:', ability.rules);
```
### Common Issues
**"User does not have required permissions" error:**
1. Check user role in database
2. Verify ability definitions match frontend/backend
3. Ensure AbilitiesGuard is applied to controller
4. Check if decorator is correctly specified
**Navigation items not showing:**
1. Verify AbilityProvider wraps the app
2. Check ability.can() returns true for expected permissions
3. Ensure user is authenticated and role is set
**Tests failing:**
1. Mock AbilityFactory in tests
2. Provide test abilities in test setup
3. Use `@casl/ability` test utilities
---
**Last Updated:** 2026-01-25
**See also:**
- [CLAUDE.md](../CLAUDE.md) - General project documentation
- [ERROR_HANDLING.md](./ERROR_HANDLING.md) - Error handling guide
- [CASL Documentation](https://casl.js.org/) - Official CASL docs

337
docs/ERROR_HANDLING.md Normal file
View 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

282
docs/REQUIREMENTS.md Normal file
View File

@@ -0,0 +1,282 @@
# VIP Coordinator - Requirements Document
## Application Purpose
The VIP Coordinator is a web application for managing VIP transportation logistics and event coordination. Organizations use it to coordinate VIP arrivals, manage driver resources, track flight statuses, and schedule events.
---
## Core Features
### 1. VIP Management
The application must allow users to create and manage VIP profiles.
**Required Functionality**:
- Create VIP profiles with: name, organization, department
- Support two transportation modes:
- **Flight**: VIPs arriving by air
- **Self-driving**: VIPs arriving by car
- For flight arrivals: Support multiple flight segments per VIP (e.g., JFK → LAX → SFO)
- For self-driving arrivals: Track expected arrival time
- Organize VIPs by department (Office of Development, Admin)
- Store notes and special requirements:
- Airport pickup needed (yes/no)
- Venue transportation needed (yes/no)
- General notes
**Required Operations**:
- Create new VIP
- View list of all VIPs
- View VIP details
- Update VIP information
- Delete VIP
---
### 2. Driver Management
The application must allow users to manage driver resources.
**Required Functionality**:
- Create driver profiles with: name, phone number, department
- Link drivers to user accounts (optional - a driver can be a system user)
- View driver's complete schedule across all VIP assignments
- Organize drivers by department
**Required Operations**:
- Create new driver
- View list of all drivers
- View driver details
- View driver's complete schedule
- Update driver information
- Delete driver
---
### 3. Schedule Management
The application must allow users to create and manage events for VIPs.
**Required Functionality**:
- Create events for VIPs with the following types:
- Transport
- Meeting
- Event
- Meal
- Accommodation
- Event details must include:
- Title
- Location
- Start time
- End time
- Description (optional)
- Assigned driver
- Event status tracking:
- Scheduled
- In-progress
- Completed
- Cancelled
- Conflict prevention:
- Prevent double-booking drivers (same driver cannot be assigned to overlapping time slots)
- Validate that required fields are provided
- Check driver availability before assignment
**Required Operations**:
- Create event for a VIP
- View schedule for a VIP
- View schedule for a driver
- Update event details
- Update event status
- Delete event
- Check driver availability for a time slot
- Check for conflicts when assigning a driver
---
### 4. Flight Tracking
The application must track real-time flight status for VIPs arriving by air.
**Required Functionality**:
- Integrate with a flight data API (AviationStack or equivalent)
- Track multiple flights per VIP (multi-segment itineraries)
- Store flight information:
- Flight number
- Flight date
- Segment number (for multi-flight itineraries)
- Departure airport code
- Arrival airport code
- Scheduled departure time
- Scheduled arrival time
- Actual departure time (updated automatically)
- Actual arrival time (updated automatically)
- Flight status (scheduled, delayed, landed, etc.)
- Automatically update flight status via background jobs
- Validate flight numbers and dates
**Required Operations**:
- Look up flight information by flight number and date
- Batch lookup for multiple flights
- Automatically update flight statuses in the background
- Display flight status in VIP details
---
### 5. User Authentication & Authorization
The application must control access through authentication and role-based permissions.
**Required Functionality**:
- Authenticate users via OAuth (Auth0 or equivalent)
- Support three user roles:
- **Administrator**: Full system access including user management
- **Coordinator**: Can manage VIPs, drivers, and schedules (cannot manage users)
- **Driver**: Can view assigned schedules and update event statuses only
- First user to register becomes Administrator automatically
- New users require Administrator approval before accessing the system
- User approval workflow:
- New users start with "pending" status
- Administrator can approve or deny users
- Only approved users can access the application
**Required Operations**:
- User login/logout
- View current user information
- List all users (Administrator only)
- View user details (Administrator only)
- Approve/deny pending users (Administrator only)
- Update user roles (Administrator only)
- Delete users (Administrator only)
**Permission Matrix**:
| Feature | Administrator | Coordinator | Driver |
|--------|--------------|-------------|--------|
| User Management | ✅ Full | ❌ None | ❌ None |
| VIP Management | ✅ Full CRUD | ✅ Full CRUD | ❌ View only |
| Driver Management | ✅ Full CRUD | ✅ Full CRUD | ❌ View only |
| Schedule Management | ✅ Full CRUD | ✅ Full CRUD | ✅ View + Update status |
| Flight Tracking | ✅ Full | ✅ Full | ❌ None |
---
## Technical Constraints
### Must Use
- **PostgreSQL** - Database system
- **Docker** - For local development environment
- **TypeScript** - Programming language
- **React** - Frontend framework
### Should Use
- **Auth0** - Authentication provider (preferred, but open to alternatives if better)
- **Redis** - Caching and background job processing
### Cannot Use
- **Keycloak** - Not acceptable
---
## Data Model Requirements
The application must store the following entities and relationships:
### Entities
**User**
- Unique identifier
- Email address (unique)
- Name
- Role (Administrator, Coordinator, Driver)
- Approval status (Pending, Approved, Denied)
- Authentication provider identifier
- Profile picture URL (optional)
- Timestamps (created, updated, last login)
**VIP**
- Unique identifier
- Name
- Organization
- Department (Office of Development, Admin)
- Transport mode (Flight, Self-driving)
- Expected arrival time (for self-driving)
- Airport pickup needed (boolean)
- Venue transportation needed (boolean)
- Notes (text)
- Timestamps (created, updated)
**Flight**
- Unique identifier
- Linked to VIP
- Flight number
- Flight date
- Segment number (for multi-flight itineraries)
- Departure airport code
- Arrival airport code
- Scheduled departure time
- Scheduled arrival time
- Actual departure time
- Actual arrival time
- Flight status
- Timestamps (created, updated)
**Driver**
- Unique identifier
- Name
- Phone number
- Department
- Linked to User (optional)
- Timestamps (created, updated)
**Schedule Event**
- Unique identifier
- Linked to VIP
- Title
- Location
- Start time
- End time
- Description (optional)
- Assigned driver (optional)
- Event type (Transport, Meeting, Event, Meal, Accommodation)
- Status (Scheduled, In-progress, Completed, Cancelled)
- Timestamps (created, updated)
### Relationships
- One VIP can have many Flights
- One VIP can have many Schedule Events
- One Driver can be assigned to many Schedule Events
- One User can be linked to one Driver (optional)
---
## Success Criteria
### Functional Requirements
- ✅ Users can authenticate and remain authenticated
- ✅ All five core features work as specified
- ✅ Role-based permissions are enforced correctly
- ✅ No authentication errors or redirect loops
- ✅ Application runs locally via Docker
### Quality Requirements
- ✅ Code is clean and understandable
- ✅ Error messages are clear and helpful
- ✅ User interface is responsive (works on desktop and mobile)
- ✅ Application performs well (feels fast, no noticeable delays)
---
## Implementation Notes
- Choose the architecture, patterns, and libraries that make sense for this project
- Focus on simplicity and maintainability
- Avoid over-engineering
- Make it easy to add new features later
- Prioritize getting authentication working reliably first
---
**Document Purpose**: This document describes what the application must do, not how to build it.
**Last Updated**: January 24, 2026
**Status**: Requirements Document

View File

@@ -0,0 +1,100 @@
# Enabling HTTPS with Certbot and Nginx
The production Docker stack ships with an Nginx front-end (the `frontend` service). Follow these steps to terminate HTTPS traffic with Let's Encrypt certificates.
## 1. Prerequisites
- DNS `A` records for your domain (e.g. `vip.example.com`) pointing to `162.243.171.221`.
- Ports **80** and **443** open in the DigitalOcean firewall.
- Docker Compose production stack deployed.
- Certbot installed on the droplet (`apt-get install -y certbot` already run).
## 2. Obtain certificates
Run Certbot in standalone mode (temporarily stop the `frontend` container during issuance if it is already binding to port 80):
```bash
# Stop the frontend container temporarily
docker compose -f docker-compose.prod.yml stop frontend
# Request the certificate (replace the domain names)
sudo certbot certonly --standalone \
-d vip.example.com \
-d www.vip.example.com
# Restart the frontend container
docker compose -f docker-compose.prod.yml start frontend
```
Certificates will be stored under `/etc/letsencrypt/live/vip.example.com/`.
## 3. Mount certificates into the frontend container
Copy the key pair into the repository (or mount the original directory as a read-only volume). For example:
```bash
sudo mkdir -p /opt/vip-coordinator/certs
sudo cp /etc/letsencrypt/live/vip.example.com/fullchain.pem /opt/vip-coordinator/certs/
sudo cp /etc/letsencrypt/live/vip.example.com/privkey.pem /opt/vip-coordinator/certs/
sudo chown root:root /opt/vip-coordinator/certs/*.pem
sudo chmod 600 /opt/vip-coordinator/certs/privkey.pem
```
Update `docker-compose.prod.yml` to mount the certificate directory (uncomment the example below):
```yaml
frontend:
volumes:
- /opt/vip-coordinator/certs:/etc/nginx/certs:ro
```
## 4. Enable the TLS server block
Edit `frontend/nginx.conf`:
1. Uncomment the TLS server block and point to `/etc/nginx/certs/fullchain.pem` and `privkey.pem`.
2. Change the port 80 server block to redirect to HTTPS (e.g. `return 301 https://$host$request_uri;`).
Example TLS block:
```nginx
server {
listen 443 ssl http2;
server_name vip.example.com www.vip.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
include /etc/nginx/snippets/ssl-params.conf; # optional hardening
proxy_cache_bypass $http_upgrade;
...
}
```
Rebuild and redeploy the frontend container:
```bash
docker compose -f docker-compose.prod.yml build frontend
docker compose -f docker-compose.prod.yml up -d frontend
```
## 5. Automate renewals
Certbot installs a systemd timer that runs `certbot renew` twice a day. After renewal, copy the updated certificates into `/opt/vip-coordinator/certs/` and reload the frontend container:
```bash
sudo certbot renew --dry-run
sudo cp /etc/letsencrypt/live/vip.example.com/{fullchain.pem,privkey.pem} /opt/vip-coordinator/certs/
docker compose -f docker-compose.prod.yml exec frontend nginx -s reload
```
Consider scripting the copy + reload to run after each renewal (e.g. with a cron job).
## 6. Hardening checklist
- Add `ssl_ciphers`, `ssl_prefer_server_ciphers`, and HSTS headers to Nginx.
- Restrict `ADMIN_PASSWORD` to a strong value and rotate `JWT_SECRET`.
- Enable a firewall (DigitalOcean VPC or `ufw`) allowing only SSH, HTTP, HTTPS.
- Configure automatic backups for PostgreSQL (snapshot or `pg_dump`).

View File

@@ -0,0 +1,24 @@
# Production Environment Variables
Copy this template to your deployment secrets manager or `.env` file before bringing up the production stack.
```bash
# PostgreSQL
DB_PASSWORD=change-me
# Backend application
FRONTEND_URL=https://your-domain.com
# Auth0 configuration
AUTH0_DOMAIN=your-tenant.region.auth0.com
AUTH0_CLIENT_ID=your-auth0-client-id
AUTH0_CLIENT_SECRET=your-auth0-client-secret
AUTH0_AUDIENCE=https://your-api-identifier (create an API in Auth0 and use its identifier)
INITIAL_ADMIN_EMAILS=primary.admin@example.com,another.admin@example.com
# Optional third-party integrations
AVIATIONSTACK_API_KEY=
```
> ⚠️ Never commit real secrets to version control. Use this file as a reference only.

View File

@@ -0,0 +1,29 @@
# Production Verification Checklist
Use this run-book after deploying the production stack.
## 1. Application health
- [ ] `curl http://<server-ip>:3000/api/health` returns `OK`.
- [ ] `docker compose -f docker-compose.prod.yml ps` shows `backend`, `frontend`, `db`, `redis` as healthy.
- [ ] PostgreSQL contains expected tables (`vips`, `drivers`, `schedule_events`, `flights`, `users`).
## 2. HTTPS validation
- [ ] DNS resolves the public domain to the Droplet (`dig vip.example.com +short`).
- [ ] `certbot certificates` shows the Let's Encrypt certificate with a valid expiry date.
- [ ] `curl -I https://vip.example.com` returns `200 OK`.
- [ ] Qualys SSL Labs scan reaches at least grade **A**.
## 3. Front-end smoke tests
- [ ] Load `/` in a private browser window and confirm the login screen renders.
- [ ] Use dev login or OAuth (depending on environment) to access the dashboard.
- [ ] Verify VIP and driver lists render without JavaScript console errors.
## 4. Hardening review
- [ ] UFW/DigitalOcean firewall allows only SSH (22), HTTP (80), HTTPS (443).
- [ ] `ADMIN_PASSWORD` and `JWT_SECRET` rotated to secure values.
- [ ] Scheduled backups for PostgreSQL configured (e.g., `pg_dump` cron or DO backups).
- [ ] Droplet rebooted after kernel updates (`needrestart` output clean).
- [ ] `docker compose logs` reviewed for warnings or stack traces.
Document any deviations above and create follow-up tasks.