Files
vip-coordinator/CLAUDE.md
kyle 868f7efc23
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
Major Enhancement: NestJS Migration + CASL Authorization + Error Handling
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>
2026-01-31 08:50:25 +01:00

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: true by 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

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

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
  1. Update prisma/schema.prisma with new models
  2. Run npx prisma migrate dev
  3. Create DTOs in src/<name>/dto/
  4. Add guards: @Roles('ADMINISTRATOR', 'COORDINATOR')
  5. Implement service methods using Prisma

Frontend (React):

  1. Create page in src/pages/<Name>Page.tsx
  2. Add route in App.tsx
  3. Create API hooks using TanStack Query
  4. Add navigation link
  5. 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:

  1. 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

  1. Network Errors:
When I try to create a VIP, the request fails:
Request: POST /api/v1/vips
Status: 500
Response: { "message": "Prisma validation error" }
  1. 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

  1. Claude makes changes → Writes code
  2. You run the app → Test locally
  3. You share feedback → Console logs, screenshots, error messages
  4. Claude debugs → Fixes based on your feedback
  5. Repeat until working

Tips for Success

  • Run docker-compose logs -f backend to 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:

  1. Push code to GitHub
  2. Create DO App Platform app
  3. Connect GitHub repository
  4. Configure components:
    • Backend: Node.js (port 3000)
    • Frontend: Static Site (Vite build)
    • Database: Managed PostgreSQL
  5. Set environment variables in DO dashboard
  6. 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

  1. Create Production Auth0 application (separate from dev)
  2. Update callback URLs to production domain
  3. Configure custom domain (optional)
  4. 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

  1. Always validate input:
@Post()
async create(@Body() dto: CreateVipDto) {  // DTO with class-validator
  return this.service.create(dto);
}
  1. Use guards everywhere:
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('ADMINISTRATOR')
  1. Soft deletes (preserve data):
async delete(id: string) {
  return this.prisma.vip.update({
    where: { id },
    data: { deletedAt: new Date() },
  });
}
  1. Prisma prevents SQL injection automatically

Frontend Security

  1. Never store tokens in localStorage (Auth0 handles this)
  2. Always use HTTPS in production
  3. Sanitize user input before display
  4. 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_DOMAIN and AUTH0_CLIENT_ID match 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


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).