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>
219 lines
9.2 KiB
JavaScript
219 lines
9.2 KiB
JavaScript
"use strict";
|
|
// Flight Tracking Scheduler Service
|
|
// Efficiently batches flight API calls and manages tracking schedules
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
class FlightTrackingScheduler {
|
|
constructor(flightService) {
|
|
this.trackingSchedule = {};
|
|
this.checkIntervals = new Map();
|
|
this.flightService = flightService;
|
|
}
|
|
// Add flights for a VIP to the tracking schedule
|
|
addVipFlights(vipId, vipName, flights) {
|
|
flights.forEach(flight => {
|
|
const key = flight.flightDate;
|
|
if (!this.trackingSchedule[key]) {
|
|
this.trackingSchedule[key] = [];
|
|
}
|
|
// Check if this flight is already being tracked
|
|
const existingIndex = this.trackingSchedule[key].findIndex(f => f.flightNumber === flight.flightNumber && f.vipId === vipId);
|
|
const scheduledFlight = {
|
|
vipId,
|
|
vipName,
|
|
flightNumber: flight.flightNumber,
|
|
flightDate: flight.flightDate,
|
|
segment: flight.segment,
|
|
scheduledDeparture: flight.validationData?.departure?.scheduled
|
|
};
|
|
if (existingIndex >= 0) {
|
|
// Update existing entry
|
|
this.trackingSchedule[key][existingIndex] = scheduledFlight;
|
|
}
|
|
else {
|
|
// Add new entry
|
|
this.trackingSchedule[key].push(scheduledFlight);
|
|
}
|
|
});
|
|
// Start or update tracking for affected dates
|
|
this.updateTrackingSchedules();
|
|
}
|
|
// Remove VIP flights from tracking
|
|
removeVipFlights(vipId) {
|
|
Object.keys(this.trackingSchedule).forEach(date => {
|
|
this.trackingSchedule[date] = this.trackingSchedule[date].filter(f => f.vipId !== vipId);
|
|
// Remove empty dates
|
|
if (this.trackingSchedule[date].length === 0) {
|
|
delete this.trackingSchedule[date];
|
|
}
|
|
});
|
|
this.updateTrackingSchedules();
|
|
}
|
|
// Update tracking schedules based on current flights
|
|
updateTrackingSchedules() {
|
|
// Clear existing intervals
|
|
this.checkIntervals.forEach(interval => clearInterval(interval));
|
|
this.checkIntervals.clear();
|
|
// Set up tracking for each date
|
|
Object.keys(this.trackingSchedule).forEach(date => {
|
|
this.setupDateTracking(date);
|
|
});
|
|
}
|
|
// Set up tracking for a specific date
|
|
setupDateTracking(date) {
|
|
const flights = this.trackingSchedule[date];
|
|
if (!flights || flights.length === 0)
|
|
return;
|
|
// Check if we should start tracking (4 hours before first flight)
|
|
const now = new Date();
|
|
const dateObj = new Date(date + 'T00:00:00');
|
|
// Find earliest departure time
|
|
let earliestDeparture = null;
|
|
flights.forEach(flight => {
|
|
if (flight.scheduledDeparture) {
|
|
const depTime = new Date(flight.scheduledDeparture);
|
|
if (!earliestDeparture || depTime < earliestDeparture) {
|
|
earliestDeparture = depTime;
|
|
}
|
|
}
|
|
});
|
|
// If no departure times, assume noon
|
|
if (!earliestDeparture) {
|
|
earliestDeparture = new Date(date + 'T12:00:00');
|
|
}
|
|
// Start tracking 4 hours before earliest departure
|
|
const trackingStartTime = new Date(earliestDeparture.getTime() - 4 * 60 * 60 * 1000);
|
|
// If tracking should have started, begin immediately
|
|
if (now >= trackingStartTime) {
|
|
this.performBatchCheck(date);
|
|
// Set up recurring checks every 60 minutes (or 30 if any delays)
|
|
const interval = setInterval(() => {
|
|
this.performBatchCheck(date);
|
|
}, 60 * 60 * 1000); // 60 minutes
|
|
this.checkIntervals.set(date, interval);
|
|
}
|
|
else {
|
|
// Schedule first check for tracking start time
|
|
const timeUntilStart = trackingStartTime.getTime() - now.getTime();
|
|
setTimeout(() => {
|
|
this.performBatchCheck(date);
|
|
// Then set up recurring checks
|
|
const interval = setInterval(() => {
|
|
this.performBatchCheck(date);
|
|
}, 60 * 60 * 1000);
|
|
this.checkIntervals.set(date, interval);
|
|
}, timeUntilStart);
|
|
}
|
|
}
|
|
// Perform batch check for all flights on a date
|
|
async performBatchCheck(date) {
|
|
const flights = this.trackingSchedule[date];
|
|
if (!flights || flights.length === 0)
|
|
return;
|
|
console.log(`\n=== Batch Flight Check for ${date} ===`);
|
|
console.log(`Checking ${flights.length} flights...`);
|
|
// Filter out flights that have already landed
|
|
const activeFlights = flights.filter(f => !f.hasLanded);
|
|
if (activeFlights.length === 0) {
|
|
console.log('All flights have landed. Stopping tracking for this date.');
|
|
this.stopDateTracking(date);
|
|
return;
|
|
}
|
|
// Get unique flight numbers to check
|
|
const uniqueFlights = Array.from(new Set(activeFlights.map(f => f.flightNumber)));
|
|
console.log(`Unique flight numbers to check: ${uniqueFlights.join(', ')}`);
|
|
try {
|
|
// Make batch API call
|
|
const flightParams = uniqueFlights.map(flightNumber => ({
|
|
flightNumber,
|
|
date
|
|
}));
|
|
const results = await this.flightService.getMultipleFlights(flightParams);
|
|
// Update flight statuses
|
|
let hasDelays = false;
|
|
let allLanded = true;
|
|
activeFlights.forEach(flight => {
|
|
const key = `${flight.flightNumber}_${date}`;
|
|
const data = results[key];
|
|
if (data) {
|
|
flight.lastChecked = new Date();
|
|
flight.status = data.status;
|
|
if (data.status === 'landed') {
|
|
flight.hasLanded = true;
|
|
console.log(`✅ ${flight.flightNumber} has landed`);
|
|
}
|
|
else {
|
|
allLanded = false;
|
|
if (data.delay && data.delay > 0) {
|
|
hasDelays = true;
|
|
console.log(`⚠️ ${flight.flightNumber} is delayed by ${data.delay} minutes`);
|
|
}
|
|
}
|
|
// Log status for each VIP
|
|
console.log(` VIP: ${flight.vipName} - Flight ${flight.segment}: ${flight.flightNumber} - Status: ${data.status}`);
|
|
}
|
|
});
|
|
// Update check frequency if delays detected
|
|
if (hasDelays && this.checkIntervals.has(date)) {
|
|
console.log('Delays detected - increasing check frequency to 30 minutes');
|
|
clearInterval(this.checkIntervals.get(date));
|
|
const interval = setInterval(() => {
|
|
this.performBatchCheck(date);
|
|
}, 30 * 60 * 1000); // 30 minutes
|
|
this.checkIntervals.set(date, interval);
|
|
}
|
|
// Stop tracking if all flights have landed
|
|
if (allLanded) {
|
|
console.log('All flights have landed. Stopping tracking for this date.');
|
|
this.stopDateTracking(date);
|
|
}
|
|
// Calculate next check time
|
|
const nextCheckTime = new Date(Date.now() + (hasDelays ? 30 : 60) * 60 * 1000);
|
|
console.log(`Next check scheduled for: ${nextCheckTime.toLocaleTimeString()}`);
|
|
}
|
|
catch (error) {
|
|
console.error('Error performing batch flight check:', error);
|
|
}
|
|
}
|
|
// Stop tracking for a specific date
|
|
stopDateTracking(date) {
|
|
const interval = this.checkIntervals.get(date);
|
|
if (interval) {
|
|
clearInterval(interval);
|
|
this.checkIntervals.delete(date);
|
|
}
|
|
// Mark all flights as completed
|
|
if (this.trackingSchedule[date]) {
|
|
this.trackingSchedule[date].forEach(f => f.hasLanded = true);
|
|
}
|
|
}
|
|
// Get current tracking status
|
|
getTrackingStatus() {
|
|
const status = {};
|
|
Object.entries(this.trackingSchedule).forEach(([date, flights]) => {
|
|
const activeFlights = flights.filter(f => !f.hasLanded);
|
|
const landedFlights = flights.filter(f => f.hasLanded);
|
|
status[date] = {
|
|
totalFlights: flights.length,
|
|
activeFlights: activeFlights.length,
|
|
landedFlights: landedFlights.length,
|
|
flights: flights.map(f => ({
|
|
vipName: f.vipName,
|
|
flightNumber: f.flightNumber,
|
|
segment: f.segment,
|
|
status: f.status || 'Not checked yet',
|
|
lastChecked: f.lastChecked,
|
|
hasLanded: f.hasLanded
|
|
}))
|
|
};
|
|
});
|
|
return status;
|
|
}
|
|
// Clean up all tracking
|
|
cleanup() {
|
|
this.checkIntervals.forEach(interval => clearInterval(interval));
|
|
this.checkIntervals.clear();
|
|
this.trackingSchedule = {};
|
|
}
|
|
}
|
|
exports.default = FlightTrackingScheduler;
|
|
//# sourceMappingURL=flightTrackingScheduler.js.map
|