Files
vip-coordinator/backend/prisma/schema.prisma
kyle 0f0f1cbf38 feat: add smart flight tracking with AviationStack API + visual progress
- Add 20+ flight fields (terminal, gate, delays, estimated times, etc.)
- Smart polling cron with budget-aware priority queue (100 req/month)
- Tracking phases: FAR_OUT → PRE_DEPARTURE → ACTIVE → LANDED
- Visual FlightProgressBar with animated airplane between airports
- FlightCard with status dots, delay badges, expandable details
- FlightList rewrite: card-based, grouped by status, search/filter
- Dashboard: enriched flight status widget with compact progress bars
- CommandCenter: flight alerts + enriched arrivals with gate/terminal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 19:42:52 +01:00

462 lines
13 KiB
Plaintext

// VIP Coordinator - Prisma Schema
// This is your database schema (source of truth)
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ============================================
// User Management
// ============================================
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 account
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // Soft delete
@@map("users")
}
enum Role {
ADMINISTRATOR
COORDINATOR
DRIVER
}
// ============================================
// VIP Management
// ============================================
model VIP {
id String @id @default(uuid())
name String
organization String?
department Department
arrivalMode ArrivalMode
expectedArrival DateTime? // For self-driving arrivals
airportPickup Boolean @default(false)
venueTransport Boolean @default(false)
partySize Int @default(1) // Total people: VIP + entourage
notes String? @db.Text
// Roster-only flag: true = just tracking for accountability, not active coordination
isRosterOnly Boolean @default(false)
// Emergency contact info (for accountability reports)
phone String?
email String?
emergencyContactName String?
emergencyContactPhone String?
flights Flight[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // Soft delete
@@map("vips")
}
enum Department {
OFFICE_OF_DEVELOPMENT
ADMIN
OTHER
}
enum ArrivalMode {
FLIGHT
SELF_DRIVING
}
// ============================================
// Flight Tracking
// ============================================
model Flight {
id String @id @default(uuid())
vipId String
vip VIP @relation(fields: [vipId], references: [id], onDelete: Cascade)
flightNumber String
flightDate DateTime
segment Int @default(1) // For multi-segment itineraries
departureAirport String // IATA code (e.g., "JFK")
arrivalAirport String // IATA code (e.g., "LAX")
scheduledDeparture DateTime?
scheduledArrival DateTime?
actualDeparture DateTime?
actualArrival DateTime?
status String? // scheduled, active, landed, cancelled, incident, diverted
// Airline info (from AviationStack API)
airlineName String?
airlineIata String? // "AA", "UA", "DL"
// Terminal/gate/baggage (critical for driver dispatch)
departureTerminal String?
departureGate String?
arrivalTerminal String?
arrivalGate String?
arrivalBaggage String?
// Estimated times (updated by API, distinct from scheduled)
estimatedDeparture DateTime?
estimatedArrival DateTime?
// Delay in minutes (from API)
departureDelay Int?
arrivalDelay Int?
// Aircraft info
aircraftType String? // IATA type code e.g. "A321", "B738"
// Live position data (may not be available on free tier)
liveLatitude Float?
liveLongitude Float?
liveAltitude Float?
liveSpeed Float? // horizontal speed
liveDirection Float? // heading in degrees
liveIsGround Boolean?
liveUpdatedAt DateTime?
// Polling metadata
lastPolledAt DateTime?
pollCount Int @default(0)
trackingPhase String @default("FAR_OUT") // FAR_OUT, PRE_DEPARTURE, DEPARTURE_WINDOW, ACTIVE, ARRIVAL_WINDOW, LANDED, TERMINAL
autoTrackEnabled Boolean @default(true)
lastApiResponse Json? // Full AviationStack response for debugging
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("flights")
@@index([vipId])
@@index([flightNumber, flightDate])
@@index([trackingPhase])
@@index([scheduledDeparture])
}
// ============================================
// Flight API Budget Tracking
// ============================================
model FlightApiBudget {
id String @id @default(uuid())
monthYear String @unique // "2026-02" format
requestsUsed Int @default(0)
requestLimit Int @default(100)
lastRequestAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("flight_api_budget")
}
// ============================================
// Driver Management
// ============================================
model Driver {
id String @id @default(uuid())
name String
phone String? // Optional - driver should add via profile
department Department?
userId String? @unique
user User? @relation(fields: [userId], references: [id])
// Shift/Availability
shiftStartTime DateTime? // When driver's shift starts
shiftEndTime DateTime? // When driver's shift ends
isAvailable Boolean @default(true)
events ScheduleEvent[]
assignedVehicle Vehicle? @relation("AssignedDriver")
messages SignalMessage[] // Signal chat messages
gpsDevice GpsDevice? // GPS tracking device
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // Soft delete
@@map("drivers")
}
// ============================================
// Vehicle Management
// ============================================
model Vehicle {
id String @id @default(uuid())
name String // "Blue Van", "Suburban #3"
type VehicleType @default(VAN)
licensePlate String?
seatCapacity Int // Total seats (e.g., 8)
status VehicleStatus @default(AVAILABLE)
// Current assignment
currentDriverId String? @unique
currentDriver Driver? @relation("AssignedDriver", fields: [currentDriverId], references: [id])
// Relationships
events ScheduleEvent[]
notes String? @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // Soft delete
@@map("vehicles")
}
enum VehicleType {
VAN // 7-15 seats
SUV // 5-8 seats
SEDAN // 4-5 seats
BUS // 15+ seats
GOLF_CART // 2-6 seats
TRUCK // For equipment/supplies
}
enum VehicleStatus {
AVAILABLE // Ready to use
IN_USE // Currently on a trip
MAINTENANCE // Out of service
RESERVED // Scheduled for upcoming trip
}
// ============================================
// Schedule & Event Management
// ============================================
model ScheduleEvent {
id String @id @default(uuid())
vipIds String[] // Array of VIP IDs for multi-passenger trips
title String
// Location details
pickupLocation String?
dropoffLocation String?
location String? // For non-transport events
// Timing
startTime DateTime
endTime DateTime
actualStartTime DateTime?
actualEndTime DateTime?
description String? @db.Text
type EventType @default(TRANSPORT)
status EventStatus @default(SCHEDULED)
// Assignments
driverId String?
driver Driver? @relation(fields: [driverId], references: [id], onDelete: SetNull)
vehicleId String?
vehicle Vehicle? @relation(fields: [vehicleId], references: [id], onDelete: SetNull)
// Master/child event hierarchy (shared activity → transport legs)
masterEventId String?
masterEvent ScheduleEvent? @relation("EventHierarchy", fields: [masterEventId], references: [id], onDelete: SetNull)
childEvents ScheduleEvent[] @relation("EventHierarchy")
// Metadata
notes String? @db.Text
// Reminder tracking
reminder20MinSent Boolean @default(false)
reminder5MinSent Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // Soft delete
@@map("schedule_events")
@@index([driverId])
@@index([vehicleId])
@@index([startTime, endTime])
@@index([status])
}
enum EventType {
TRANSPORT
MEETING
EVENT
MEAL
ACCOMMODATION
}
enum EventStatus {
SCHEDULED
IN_PROGRESS
COMPLETED
CANCELLED
}
// ============================================
// Signal Messaging
// ============================================
model SignalMessage {
id String @id @default(uuid())
driverId String
driver Driver @relation(fields: [driverId], references: [id], onDelete: Cascade)
direction MessageDirection
content String @db.Text
timestamp DateTime @default(now())
isRead Boolean @default(false)
signalTimestamp String? // Signal's message timestamp for deduplication
@@map("signal_messages")
@@index([driverId])
@@index([driverId, isRead])
@@index([timestamp])
}
enum MessageDirection {
INBOUND // Message from driver
OUTBOUND // Message to driver
}
// ============================================
// PDF Settings (Singleton)
// ============================================
model PdfSettings {
id String @id @default(uuid())
// Branding
organizationName String @default("VIP Coordinator")
logoUrl String? @db.Text // Base64 data URL or external URL
accentColor String @default("#2c3e50") // Hex color
tagline String?
// Contact Info
contactEmail String @default("contact@example.com")
contactPhone String @default("555-0100")
secondaryContactName String?
secondaryContactPhone String?
contactLabel String @default("Questions or Changes?")
// Document Options
showDraftWatermark Boolean @default(false)
showConfidentialWatermark Boolean @default(false)
showTimestamp Boolean @default(true)
showAppUrl Boolean @default(false)
pageSize PageSize @default(LETTER)
// Timezone for correspondence and display (IANA timezone format)
timezone String @default("America/New_York")
// Content Toggles
showFlightInfo Boolean @default(true)
showDriverNames Boolean @default(true)
showVehicleNames Boolean @default(true)
showVipNotes Boolean @default(true)
showEventDescriptions Boolean @default(true)
// Custom Text
headerMessage String? @db.Text
footerMessage String? @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("pdf_settings")
}
enum PageSize {
LETTER
A4
}
// ============================================
// GPS Tracking
// ============================================
model GpsDevice {
id String @id @default(uuid())
driverId String @unique
driver Driver @relation(fields: [driverId], references: [id], onDelete: Cascade)
// Traccar device information
traccarDeviceId Int @unique // Traccar's internal device ID
deviceIdentifier String @unique // Unique ID for Traccar Client app
// Privacy & Consent
enrolledAt DateTime @default(now())
consentGiven Boolean @default(false)
consentGivenAt DateTime?
lastActive DateTime? // Last location report timestamp
// Settings
isActive Boolean @default(true)
// Location history
locationHistory GpsLocationHistory[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("gps_devices")
}
model GpsLocationHistory {
id String @id @default(uuid())
deviceId String
device GpsDevice @relation(fields: [deviceId], references: [id], onDelete: Cascade)
latitude Float
longitude Float
altitude Float?
speed Float? // km/h
course Float? // Bearing in degrees
accuracy Float? // Meters
battery Float? // Battery percentage (0-100)
timestamp DateTime
createdAt DateTime @default(now())
@@map("gps_location_history")
@@index([deviceId, timestamp])
@@index([timestamp]) // For cleanup job
}
model GpsSettings {
id String @id @default(uuid())
// Update frequency (seconds)
updateIntervalSeconds Int @default(60)
// Shift-based tracking (4AM - 1AM next day)
shiftStartHour Int @default(4) // 4 AM
shiftStartMinute Int @default(0)
shiftEndHour Int @default(1) // 1 AM next day
shiftEndMinute Int @default(0)
// Data retention (days)
retentionDays Int @default(30)
// Traccar credentials
traccarAdminUser String @default("admin")
traccarAdminPassword String? // Encrypted or hashed
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("gps_settings")
}