Major Enhancement: NestJS Migration + CASL Authorization + Error Handling
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>
This commit is contained in:
2026-01-31 08:50:25 +01:00
parent 8ace1ab2c1
commit 868f7efc23
351 changed files with 44997 additions and 6276 deletions

View File

@@ -0,0 +1,219 @@
"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