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>
29 KiB
VIP Coordinator - Claude AI Context
Last Updated: 2026-01-25 Status: Active Development - Starting Fresh Build
Project Overview
VIP Coordinator is a full-stack web application for managing VIP transportation logistics and event coordination. It handles VIP profiles, driver assignments, event scheduling with conflict detection, flight tracking, and user management with role-based access control.
⚠️ CURRENT STATUS: FRESH BUILD IN PROGRESS
- Starting from scratch with modern 2026 best practices
- No production data - database can be reset at any time
- Feel free to make breaking changes without asking
- Focus: Getting features working correctly > preserving legacy code
Technology Stack (2026 Best Practices)
Backend
- Framework: NestJS 10.x (TypeScript)
- Database ORM: Prisma 5.x
- Database: PostgreSQL 15+
- Caching: Redis 7 (optional, for driver location tracking)
- Authentication: Auth0 + Passport.js (JWT strategy)
- Validation: class-validator + class-transformer
- Runtime: Node.js 20+
Frontend
- Framework: React 18.2.0 (stable)
- Build Tool: Vite 5.x
- UI Components: Shadcn UI (modern, lightweight)
- Styling: Tailwind CSS 3.4+
- Data Fetching: TanStack Query v5 (formerly React Query)
- Routing: React Router 6.x
- Forms: React Hook Form + Zod
- Auth: Auth0 React SDK (@auth0/auth0-react)
Infrastructure
- Development: Docker + Docker Compose
- Production Target: Digital Ocean (App Platform or Droplet)
- Services: PostgreSQL, Redis (optional), Backend API, Frontend SPA
Why These Choices?
NestJS over Express:
- Built-in structure prevents spaghetti code
- Dependency injection, decorators, guards
- Excellent TypeScript support
- Scales well for complex applications
Prisma over raw SQL:
- Type-safe database queries
- Automatic migrations
- Prevents SQL injection by default
- Auto-completion in IDE
Shadcn UI over Material-UI:
- Modern design (2024-2026 standard)
- Lightweight (copy components into project)
- Full control over styling
- Better performance
TanStack Query:
- Essential for server state management
- Automatic caching and refetching
- Optimistic updates
- Industry standard for React
Auth0:
- Reliable hosted authentication
- No self-hosting required
- Social login support
- Excellent documentation
- Free tier for development
Project Structure
vip-coordinator/
├── backend/ # NestJS Backend (port 3000)
│ ├── prisma/
│ │ ├── schema.prisma # Database schema (source of truth)
│ │ ├── migrations/ # Auto-generated migrations
│ │ └── seed.ts # Sample data for development
│ ├── src/
│ │ ├── main.ts # App entry point
│ │ ├── app.module.ts # Root module
│ │ ├── auth/ # Auth0 + JWT authentication
│ │ │ ├── auth.module.ts
│ │ │ ├── auth.service.ts
│ │ │ ├── jwt.strategy.ts
│ │ │ ├── guards/ # @Roles(), @Public() guards
│ │ │ └── decorators/ # @CurrentUser() decorator
│ │ ├── users/ # User management + approval
│ │ │ ├── users.module.ts
│ │ │ ├── users.service.ts
│ │ │ ├── users.controller.ts
│ │ │ └── dto/
│ │ ├── vips/ # VIP profile management
│ │ ├── drivers/ # Driver resource management
│ │ ├── events/ # Schedule + conflict detection
│ │ ├── flights/ # Flight tracking API
│ │ ├── prisma/ # Prisma service
│ │ │ ├── prisma.module.ts
│ │ │ └── prisma.service.ts
│ │ └── common/ # Shared utilities
│ ├── package.json
│ └── .env
│
├── frontend/ # React Frontend (port 5173)
│ ├── src/
│ │ ├── main.tsx # App entry point
│ │ ├── App.tsx # Root component + routing
│ │ ├── components/
│ │ │ ├── ui/ # Shadcn UI components
│ │ │ ├── auth/ # Login, ProtectedRoute
│ │ │ ├── vips/ # VIP forms, lists
│ │ │ ├── drivers/ # Driver components
│ │ │ └── events/ # Schedule components
│ │ ├── pages/
│ │ │ ├── Dashboard.tsx
│ │ │ ├── VIPList.tsx
│ │ │ ├── VIPDetails.tsx
│ │ │ ├── DriverList.tsx
│ │ │ ├── SchedulePage.tsx
│ │ │ ├── FlightsPage.tsx
│ │ │ └── AdminUsersPage.tsx
│ │ ├── lib/
│ │ │ ├── api.ts # Axios client
│ │ │ └── queryClient.ts # TanStack Query config
│ │ ├── hooks/
│ │ │ ├── useAuth.ts # Auth0 wrapper
│ │ │ └── usePermissions.ts
│ │ └── types/ # TypeScript interfaces
│ ├── package.json
│ └── .env
│
├── docker-compose.yml # Development environment
├── docker-compose.prod.yml # Production build
└── CLAUDE.md # This file (source of truth)
Database Schema (Prisma)
Core Models
model User {
id String @id @default(uuid())
auth0Id String @unique // Auth0 sub claim
email String @unique
name String?
picture String?
role Role @default(COORDINATOR)
isApproved Boolean @default(false)
driver Driver? // Optional linked driver
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // Soft delete
}
model VIP {
id String @id @default(uuid())
name String
organization String?
department Department
arrivalMode ArrivalMode
expectedArrival DateTime? // For self-driving
airportPickup Boolean @default(false)
venueTransport Boolean @default(false)
notes String?
flights Flight[]
events ScheduleEvent[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
}
model Driver {
id String @id @default(uuid())
name String
phone String
department Department?
userId String? @unique
user User? @relation(fields: [userId], references: [id])
events ScheduleEvent[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
}
model ScheduleEvent {
id String @id @default(uuid())
vipId String
vip VIP @relation(fields: [vipId], references: [id])
title String
location String?
startTime DateTime
endTime DateTime
description String?
type EventType @default(TRANSPORT)
status EventStatus @default(SCHEDULED)
driverId String?
driver Driver? @relation(fields: [driverId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
}
model Flight {
id String @id @default(uuid())
vipId String
vip VIP @relation(fields: [vipId], references: [id])
flightNumber String
flightDate DateTime
segment Int @default(1)
departureAirport String // IATA code (e.g., "JFK")
arrivalAirport String // IATA code (e.g., "LAX")
scheduledDeparture DateTime?
scheduledArrival DateTime?
actualDeparture DateTime?
actualArrival DateTime?
status String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Role {
ADMINISTRATOR
COORDINATOR
DRIVER
}
enum Department {
OFFICE_OF_DEVELOPMENT
ADMIN
}
enum ArrivalMode {
FLIGHT
SELF_DRIVING
}
enum EventType {
TRANSPORT
MEETING
EVENT
MEAL
ACCOMMODATION
}
enum EventStatus {
SCHEDULED
IN_PROGRESS
COMPLETED
CANCELLED
}
Relationships
- User ↔ Driver: One-to-one (optional)
- VIP ↔ Flight: One-to-many
- VIP ↔ ScheduleEvent: One-to-many
- Driver ↔ ScheduleEvent: One-to-many
Soft Delete Pattern
All main entities have deletedAt field. Always filter:
where: { deletedAt: null, ...otherConditions }
User Roles & Permissions
| Feature | Administrator | Coordinator | Driver |
|---|---|---|---|
| User Management | ✅ Full CRUD | ❌ None | ❌ None |
| VIP Management | ✅ Full CRUD | ✅ Full CRUD | 👁️ View Only |
| Driver Management | ✅ Full CRUD | ✅ Full CRUD | 👁️ View Only |
| Event Scheduling | ✅ Full CRUD | ✅ Full CRUD | ⚠️ View + Update Status |
| Flight Tracking | ✅ Full Access | ✅ Full Access | ❌ None |
First User Bootstrap
- First user to register becomes Administrator automatically
isApproved: trueby default for first user- Subsequent users require admin approval
- No manual database editing needed
Development Workflow
Prerequisites
- Node.js 20+ and npm 10+
- Docker Desktop
- Auth0 Account (free tier)
- Git
Initial Setup
1. Clone and Install
git clone <repository>
cd vip-coordinator
# Backend
cd backend
npm install
# Frontend
cd ../frontend
npm install
2. Configure Auth0
- Create Auth0 account at https://auth0.com
- Create new Application (Single Page Application)
- Create new API
- Note:
Domain,Client ID,Audience - Configure callback URLs:
- Allowed Callback URLs:
http://localhost:5173/callback - Allowed Logout URLs:
http://localhost:5173 - Allowed Web Origins:
http://localhost:5173
- Allowed Callback URLs:
3. Environment Variables
Backend .env:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/vip_coordinator"
REDIS_URL="redis://localhost:6379"
AUTH0_DOMAIN="your-tenant.us.auth0.com"
AUTH0_AUDIENCE="https://your-api-identifier"
AVIATIONSTACK_API_KEY="your-api-key" # Optional for flight tracking
NODE_ENV=development
PORT=3000
Frontend .env:
VITE_API_URL=http://localhost:3000/api/v1
VITE_AUTH0_DOMAIN=your-tenant.us.auth0.com
VITE_AUTH0_CLIENT_ID=your-client-id
VITE_AUTH0_AUDIENCE=https://your-api-identifier
4. Database Setup
cd backend
# Generate Prisma Client
npx prisma generate
# Run migrations
npx prisma migrate dev --name init
# Seed sample data (optional)
npx prisma db seed
5. Run Development Servers
Option A: Docker (Recommended)
# From project root
docker-compose up -d
# View logs
docker-compose logs -f backend
docker-compose logs -f frontend
Option B: Local
# Terminal 1: PostgreSQL + Redis
docker-compose up -d postgres redis
# Terminal 2: Backend
cd backend
npm run start:dev
# Terminal 3: Frontend
cd frontend
npm run dev
6. Access Application
- Frontend: http://localhost:5173
- Backend API: http://localhost:3000/api/v1
- Prisma Studio:
npx prisma studio(database GUI)
Common Development Tasks
Database Migrations
# Create new migration after schema changes
npx prisma migrate dev --name describe_your_changes
# Reset database (SAFE IN DEV - no important data)
npx prisma migrate reset
# View database in GUI
npx prisma studio
Adding a New Feature Module
Backend (NestJS):
cd backend
nest g resource <name> --no-spec # Generates module, service, controller
- Update
prisma/schema.prismawith new models - Run
npx prisma migrate dev - Create DTOs in
src/<name>/dto/ - Add guards:
@Roles('ADMINISTRATOR', 'COORDINATOR') - Implement service methods using Prisma
Frontend (React):
- Create page in
src/pages/<Name>Page.tsx - Add route in
App.tsx - Create API hooks using TanStack Query
- Add navigation link
- Implement role-based rendering
Code Generation
# Backend
nest g module <name>
nest g service <name>
nest g controller <name>
# Prisma
npx prisma generate # After schema changes
npx prisma migrate dev # Create migration
Key Patterns & Best Practices
Backend (NestJS) Patterns
1. Controllers
@Controller('vips')
@UseGuards(JwtAuthGuard, RolesGuard)
export class VipsController {
constructor(private vipsService: VipsService) {}
@Get()
@Roles('ADMINISTRATOR', 'COORDINATOR', 'DRIVER')
async findAll(@CurrentUser() user: User) {
return this.vipsService.findAll();
}
@Post()
@Roles('ADMINISTRATOR', 'COORDINATOR')
async create(@Body() createVipDto: CreateVipDto, @CurrentUser() user: User) {
return this.vipsService.create(createVipDto);
}
}
2. Services (Prisma)
@Injectable()
export class VipsService {
constructor(private prisma: PrismaService) {}
async findAll() {
return this.prisma.vip.findMany({
where: { deletedAt: null },
include: { flights: true, events: true },
orderBy: { createdAt: 'desc' },
});
}
async create(data: CreateVipDto) {
return this.prisma.vip.create({
data: {
...data,
// Prisma handles timestamps automatically
},
});
}
async softDelete(id: string) {
return this.prisma.vip.update({
where: { id },
data: { deletedAt: new Date() },
});
}
}
3. DTOs with Validation
import { IsString, IsEnum, IsOptional, IsBoolean } from 'class-validator';
export class CreateVipDto {
@IsString()
name: string;
@IsEnum(Department)
department: Department;
@IsEnum(ArrivalMode)
arrivalMode: ArrivalMode;
@IsOptional()
@IsBoolean()
airportPickup?: boolean;
}
4. Custom Decorators
// Get current user from request
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext): User => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
5. Guards
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<Role[]>('roles', context.getHandler());
if (!requiredRoles) return true;
const { user } = context.switchToHttp().getRequest();
return requiredRoles.includes(user.role);
}
}
Frontend (React) Patterns
1. Protected Routes
function App() {
return (
<Auth0Provider domain={...} clientId={...} redirectUri={...}>
<QueryClientProvider client={queryClient}>
<Routes>
<Route path="/login" element={<Login />} />
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/vips" element={<VIPList />} />
</Route>
</Routes>
</QueryClientProvider>
</Auth0Provider>
);
}
2. Data Fetching with TanStack Query
// Hook
function useVIPs() {
return useQuery({
queryKey: ['vips'],
queryFn: async () => {
const { data } = await api.get('/vips');
return data;
},
});
}
// Component
function VIPList() {
const { data: vips, isLoading, error } = useVIPs();
if (isLoading) return <Spinner />;
if (error) return <ErrorAlert error={error} />;
return <VIPTable vips={vips} />;
}
3. Mutations
function useCreateVIP() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (vip: CreateVIPDto) => {
const { data } = await api.post('/vips', vip);
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['vips'] });
toast.success('VIP created successfully');
},
onError: (error) => {
toast.error(error.message);
},
});
}
4. Forms with React Hook Form + Zod
const vipSchema = z.object({
name: z.string().min(1, 'Name required'),
department: z.enum(['OFFICE_OF_DEVELOPMENT', 'ADMIN']),
arrivalMode: z.enum(['FLIGHT', 'SELF_DRIVING']),
});
type VIPFormData = z.infer<typeof vipSchema>;
function VIPForm() {
const { register, handleSubmit, formState: { errors } } = useForm<VIPFormData>({
resolver: zodResolver(vipSchema),
});
const createVIP = useCreateVIP();
const onSubmit = (data: VIPFormData) => {
createVIP.mutate(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input {...register('name')} error={errors.name?.message} />
<Button type="submit" loading={createVIP.isPending}>Create VIP</Button>
</form>
);
}
5. Role-Based Rendering
function usePermissions() {
const { user } = useAuth0();
return {
canManageUsers: user?.role === 'ADMINISTRATOR',
canEditVIPs: ['ADMINISTRATOR', 'COORDINATOR'].includes(user?.role),
isDriver: user?.role === 'DRIVER',
};
}
// Usage
function VIPList() {
const { canEditVIPs } = usePermissions();
return (
<div>
{canEditVIPs && <Button>Add VIP</Button>}
<VIPTable />
</div>
);
}
Error Handling
Backend:
// Built-in HTTP exceptions
throw new NotFoundException(`VIP with ID ${id} not found`);
throw new BadRequestException('Invalid flight number format');
throw new UnauthorizedException('User not approved');
throw new ForbiddenException('Insufficient permissions');
// Custom exception filter (optional)
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
message: exception.message,
timestamp: new Date().toISOString(),
});
}
}
Frontend:
// Global error boundary
<ErrorBoundary fallback={<ErrorPage />}>
<App />
</ErrorBoundary>
// Query error handling
const { data, error } = useQuery({
queryKey: ['vips'],
queryFn: fetchVIPs,
onError: (error) => {
console.error('[VIP] Failed to fetch:', error);
toast.error('Failed to load VIPs. Please try again.');
},
});
Logging
Backend (NestJS Logger):
@Injectable()
export class VipsService {
private logger = new Logger(VipsService.name);
async create(data: CreateVipDto) {
this.logger.log(`Creating VIP: ${data.name}`);
try {
const vip = await this.prisma.vip.create({ data });
this.logger.log(`VIP created: ${vip.id}`);
return vip;
} catch (error) {
this.logger.error(`Failed to create VIP: ${error.message}`, error.stack);
throw error;
}
}
}
Frontend:
// Prefix all logs with feature area
console.log('[AUTH] User logged in:', user);
console.error('[API] Request failed:', error);
console.warn('[SCHEDULE] Conflict detected:', conflict);
console.debug('[FLIGHT] Status updated:', flight);
Working with Claude AI
What Claude CANNOT Do
⚠️ Browser Testing Limitations:
- Cannot see your browser window or UI
- Cannot view browser console directly
- Cannot see network requests in DevTools
- Cannot click buttons or interact with the app
How to Help Claude Debug
✅ Good Feedback Examples:
- Console Errors:
I clicked Login and got this error:
[AUTH] Failed to authenticate POST http://localhost:3000/api/v1/auth/login 401 Unauthorized: Invalid credentials
- Network Errors:
When I try to create a VIP, the request fails:
Request: POST /api/v1/vips
Status: 500
Response: { "message": "Prisma validation error" }
- UI Issues:
The VIP list page shows a blank screen.
Console shows:
TypeError: Cannot read property 'map' of undefined
at VIPList.tsx:45
❌ Unhelpful Feedback:
- "It doesn't work"
- "There's an error"
- "The button is broken"
Effective Workflow
- Claude makes changes → Writes code
- You run the app → Test locally
- You share feedback → Console logs, screenshots, error messages
- Claude debugs → Fixes based on your feedback
- Repeat until working
Tips for Success
- Run
docker-compose logs -f backendto watch backend logs - Keep browser DevTools open (F12)
- Share full error messages (not just summaries)
- Take screenshots when UI looks wrong
- Copy/paste network request/response bodies
Deployment (Digital Ocean)
Option 1: App Platform (Easiest)
Pros:
- Managed PostgreSQL database
- Auto-scaling
- Zero-downtime deployments
- SSL certificates automatic
Setup:
- Push code to GitHub
- Create DO App Platform app
- Connect GitHub repository
- Configure components:
- Backend: Node.js (port 3000)
- Frontend: Static Site (Vite build)
- Database: Managed PostgreSQL
- Set environment variables in DO dashboard
- Deploy
Cost: ~$12/month (basic tier)
Option 2: Droplet + Docker (More Control)
Pros:
- Full control
- Cheaper for small apps
- Run everything on one server
Setup:
# On Digital Ocean Droplet (Ubuntu 22.04)
apt update && apt upgrade -y
apt install docker.io docker-compose -y
# Clone repository
git clone <your-repo>
cd vip-coordinator
# Set environment variables
cp .env.example .env
nano .env # Edit with production values
# Run with Docker Compose
docker-compose -f docker-compose.prod.yml up -d
# Set up nginx reverse proxy
apt install nginx certbot -y
# Configure nginx for SSL
Cost: ~$6/month (basic droplet)
Environment Variables (Production)
Backend:
DATABASE_URL="postgresql://user:pass@db-host:5432/vip_coordinator"
AUTH0_DOMAIN="your-tenant.auth0.com"
AUTH0_AUDIENCE="https://api.yourapp.com"
NODE_ENV=production
PORT=3000
Frontend:
VITE_API_URL=https://api.yourapp.com/api/v1
VITE_AUTH0_DOMAIN=your-tenant.auth0.com
VITE_AUTH0_CLIENT_ID=your-production-client-id
VITE_AUTH0_AUDIENCE=https://api.yourapp.com
Auth0 Production Setup
- Create Production Auth0 application (separate from dev)
- Update callback URLs to production domain
- Configure custom domain (optional)
- Enable social logins if needed
Database Backups
Managed Database (DO):
- Daily automatic backups included
- Point-in-time recovery
Self-hosted:
# Backup script
pg_dump -U postgres vip_coordinator > backup_$(date +%Y%m%d).sql
# Cron job (daily at 2 AM)
0 2 * * * /usr/bin/pg_dump -U postgres vip_coordinator > /backups/backup_$(date +\%Y\%m\%d).sql
Testing Strategy
Backend Testing
Unit Tests (Services):
describe('VipsService', () => {
it('should create a VIP', async () => {
const vip = await service.create({ name: 'John Doe', ... });
expect(vip.name).toBe('John Doe');
});
});
Integration Tests (Controllers):
describe('VipsController', () => {
it('GET /vips should return all VIPs', async () => {
const response = await request(app.getHttpServer())
.get('/api/v1/vips')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(response.body).toBeInstanceOf(Array);
});
});
Frontend Testing
Component Tests (Vitest + Testing Library):
test('VIPList renders VIPs', async () => {
render(<VIPList />);
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
});
E2E Tests (Playwright) - Recommended:
test('admin can create VIP', async ({ page }) => {
await page.goto('http://localhost:5173/login');
await page.click('text=Login');
// ... auth flow
await page.goto('/vips');
await page.click('text=Add VIP');
await page.fill('[name=name]', 'Jane Smith');
await page.click('text=Submit');
await expect(page.getByText('VIP created')).toBeVisible();
});
Running Tests
# Backend
cd backend
npm test
npm run test:watch
npm run test:cov
# Frontend
cd frontend
npm test
npm run test:ui
# E2E
npm run test:e2e
Security Best Practices
Backend Security
- Always validate input:
@Post()
async create(@Body() dto: CreateVipDto) { // DTO with class-validator
return this.service.create(dto);
}
- Use guards everywhere:
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('ADMINISTRATOR')
- Soft deletes (preserve data):
async delete(id: string) {
return this.prisma.vip.update({
where: { id },
data: { deletedAt: new Date() },
});
}
- Prisma prevents SQL injection automatically
Frontend Security
- Never store tokens in localStorage (Auth0 handles this)
- Always use HTTPS in production
- Sanitize user input before display
- Check permissions on backend, not just frontend
Auth0 Security
- Enable MFA for admin accounts
- Use Auth0 Actions to add custom claims
- Rotate client secrets regularly
- Monitor Auth0 logs for suspicious activity
Common Issues & Solutions
Issue: "Cannot connect to database"
Solution:
# Check PostgreSQL is running
docker ps | grep postgres
# Check connection string in .env
echo $DATABASE_URL
# Test connection
docker-compose exec backend npx prisma db pull
Issue: "Auth0 redirect loop"
Solution:
- Verify
AUTH0_DOMAINandAUTH0_CLIENT_IDmatch Auth0 dashboard - Check callback URLs in Auth0 settings
- Clear browser cache and cookies
Issue: "Prisma Client not generated"
Solution:
cd backend
npx prisma generate
npm run build
Issue: "Port already in use"
Solution:
# Windows
netstat -ano | findstr ":3000"
taskkill /PID <process_id> /F
# Mac/Linux
lsof -ti:3000 | xargs kill -9
Issue: "First user can't login (not approved)"
Solution: First user is auto-approved. If this fails:
-- Manually approve first user
UPDATE "User" SET "isApproved" = true WHERE email = 'admin@example.com';
Or check backend/src/auth/auth.service.ts first-user logic.
Development Mode Permissions
Claude AI has full permission to:
- ✅ Nuke and rebuild the database without asking
- ✅ Make breaking changes to the codebase
- ✅ Delete/rewrite entire files if it improves the code
- ✅ Install new packages to solve problems properly
- ✅ Start fresh if something is fundamentally broken
- ✅ Change architecture if current approach is flawed
Focus: Getting it working correctly > preserving broken code
No production data exists. Feel free to reset everything.
Quick Reference
Useful Commands
# Development
npm run start:dev # Backend dev server
npm run dev # Frontend dev server
docker-compose up -d # Start all services
# Database
npx prisma migrate dev # Create migration
npx prisma studio # Database GUI
npx prisma migrate reset # Reset database
# Production
docker-compose -f docker-compose.prod.yml up -d
npm run build # Build frontend
npm run start:prod # Production server
# Testing
npm test # Run tests
npm run test:watch # Watch mode
npm run test:e2e # E2E tests
API Endpoints
All endpoints prefixed with /api/v1:
POST /auth/login # Login with Auth0
GET /auth/profile # Get current user
GET /users # List users (admin only)
PATCH /users/:id/approve # Approve user (admin only)
GET /vips # List VIPs
POST /vips # Create VIP
GET /vips/:id # Get VIP details
PATCH /vips/:id # Update VIP
DELETE /vips/:id # Soft delete VIP
GET /drivers # List drivers
POST /drivers # Create driver
GET /drivers/:id # Get driver + schedule
PATCH /drivers/:id # Update driver
DELETE /drivers/:id # Soft delete driver
GET /events # List events
POST /events # Create event (with conflict check)
GET /events/:id # Get event
PATCH /events/:id # Update event
PATCH /events/:id/status # Update status (driver can do this)
DELETE /events/:id # Cancel event
GET /flights/:number # Get flight status
POST /flights/batch # Batch flight lookup
Key Files
- Backend Entry: backend/src/main.ts
- Frontend Entry: frontend/src/main.tsx
- Database Schema: backend/prisma/schema.prisma
- Auth Guard: backend/src/auth/guards/jwt-auth.guard.ts
- API Client: frontend/src/lib/api.ts
- Protected Routes: frontend/src/components/auth/ProtectedRoute.tsx
This document is the SOURCE OF TRUTH for the VIP Coordinator project.
When code conflicts with this document, update the code to match this spec (not vice versa).