Some checks failed
CI/CD Pipeline / Backend Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Tests (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
Complete rewrite from Express to NestJS with enterprise-grade features: ## Backend Improvements - Migrated from Express to NestJS 11.0.1 with TypeScript - Implemented Prisma ORM 7.3.0 for type-safe database access - Added CASL authorization system replacing role-based guards - Created global exception filters with structured logging - Implemented Auth0 JWT authentication with Passport.js - Added vehicle management with conflict detection - Enhanced event scheduling with driver/vehicle assignment - Comprehensive error handling and logging ## Frontend Improvements - Upgraded to React 19.2.0 with Vite 7.2.4 - Implemented CASL-based permission system - Added AbilityContext for declarative permissions - Created ErrorHandler utility for consistent error messages - Enhanced API client with request/response logging - Added War Room (Command Center) dashboard - Created VIP Schedule view with complete itineraries - Implemented Vehicle Management UI - Added mock data generators for testing (288 events across 20 VIPs) ## New Features - Vehicle fleet management (types, capacity, status tracking) - Complete 3-day Jamboree schedule generation - Individual VIP schedule pages with PDF export (planned) - Real-time War Room dashboard with auto-refresh - Permission-based navigation filtering - First user auto-approval as administrator ## Documentation - Created CASL_AUTHORIZATION.md (comprehensive guide) - Created ERROR_HANDLING.md (error handling patterns) - Updated CLAUDE.md with new architecture - Added migration guides and best practices ## Technical Debt Resolved - Removed custom authentication in favor of Auth0 - Replaced role checks with CASL abilities - Standardized error responses across API - Implemented proper TypeScript typing - Added comprehensive logging Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1178 lines
29 KiB
Markdown
1178 lines
29 KiB
Markdown
# 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
|
|
|
|
```prisma
|
|
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:
|
|
```typescript
|
|
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**
|
|
```bash
|
|
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`:**
|
|
```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`:**
|
|
```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**
|
|
```bash
|
|
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)**
|
|
```bash
|
|
# From project root
|
|
docker-compose up -d
|
|
|
|
# View logs
|
|
docker-compose logs -f backend
|
|
docker-compose logs -f frontend
|
|
```
|
|
|
|
**Option B: Local**
|
|
```bash
|
|
# 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**
|
|
```bash
|
|
# 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):**
|
|
```bash
|
|
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**
|
|
```bash
|
|
# 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**
|
|
```typescript
|
|
@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)**
|
|
```typescript
|
|
@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**
|
|
```typescript
|
|
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**
|
|
```typescript
|
|
// Get current user from request
|
|
export const CurrentUser = createParamDecorator(
|
|
(data: unknown, ctx: ExecutionContext): User => {
|
|
const request = ctx.switchToHttp().getRequest();
|
|
return request.user;
|
|
},
|
|
);
|
|
```
|
|
|
|
**5. Guards**
|
|
```typescript
|
|
@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**
|
|
```typescript
|
|
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**
|
|
```typescript
|
|
// 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**
|
|
```typescript
|
|
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**
|
|
```typescript
|
|
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**
|
|
```typescript
|
|
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:**
|
|
```typescript
|
|
// 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:**
|
|
```typescript
|
|
// 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):**
|
|
```typescript
|
|
@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:**
|
|
```typescript
|
|
// 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
|
|
```
|
|
```
|
|
|
|
2. **Network Errors:**
|
|
```
|
|
When I try to create a VIP, the request fails:
|
|
Request: POST /api/v1/vips
|
|
Status: 500
|
|
Response: { "message": "Prisma validation error" }
|
|
```
|
|
|
|
3. **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:**
|
|
```bash
|
|
# 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:**
|
|
```env
|
|
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:**
|
|
```env
|
|
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:**
|
|
```bash
|
|
# 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):**
|
|
```typescript
|
|
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):**
|
|
```typescript
|
|
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):**
|
|
```typescript
|
|
test('VIPList renders VIPs', async () => {
|
|
render(<VIPList />);
|
|
await waitFor(() => {
|
|
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
});
|
|
});
|
|
```
|
|
|
|
**E2E Tests (Playwright) - Recommended:**
|
|
```typescript
|
|
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
|
|
|
|
```bash
|
|
# 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:**
|
|
```typescript
|
|
@Post()
|
|
async create(@Body() dto: CreateVipDto) { // DTO with class-validator
|
|
return this.service.create(dto);
|
|
}
|
|
```
|
|
|
|
2. **Use guards everywhere:**
|
|
```typescript
|
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
@Roles('ADMINISTRATOR')
|
|
```
|
|
|
|
3. **Soft deletes (preserve data):**
|
|
```typescript
|
|
async delete(id: string) {
|
|
return this.prisma.vip.update({
|
|
where: { id },
|
|
data: { deletedAt: new Date() },
|
|
});
|
|
}
|
|
```
|
|
|
|
4. **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:**
|
|
```bash
|
|
# 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:**
|
|
```bash
|
|
cd backend
|
|
npx prisma generate
|
|
npm run build
|
|
```
|
|
|
|
### Issue: "Port already in use"
|
|
|
|
**Solution:**
|
|
```bash
|
|
# 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:
|
|
```sql
|
|
-- 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
|
|
|
|
```bash
|
|
# 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](backend/src/main.ts)
|
|
- **Frontend Entry:** [frontend/src/main.tsx](frontend/src/main.tsx)
|
|
- **Database Schema:** [backend/prisma/schema.prisma](backend/prisma/schema.prisma)
|
|
- **Auth Guard:** [backend/src/auth/guards/jwt-auth.guard.ts](backend/src/auth/guards/jwt-auth.guard.ts)
|
|
- **API Client:** [frontend/src/lib/api.ts](frontend/src/lib/api.ts)
|
|
- **Protected Routes:** [frontend/src/components/auth/ProtectedRoute.tsx](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).
|