// 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") }