Files
vip-coordinator/backend/src/services/flightService.ts

263 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Real Flight tracking service with Google scraping
// No mock data - only real flight information
interface FlightData {
flightNumber: string;
flightDate: string;
status: string;
airline?: string;
aircraft?: string;
departure: {
airport: string;
airportName?: string;
scheduled: string;
estimated?: string;
actual?: string;
terminal?: string;
gate?: string;
};
arrival: {
airport: string;
airportName?: string;
scheduled: string;
estimated?: string;
actual?: string;
terminal?: string;
gate?: string;
};
delay?: number;
lastUpdated: string;
source: 'google' | 'aviationstack' | 'not_found';
}
interface FlightSearchParams {
flightNumber: string;
date: string; // YYYY-MM-DD format
departureAirport?: string;
arrivalAirport?: string;
}
class FlightService {
private flightCache: Map<string, { data: FlightData; expires: number }> = new Map();
private updateIntervals: Map<string, NodeJS.Timeout> = new Map();
constructor() {
// No API keys needed for Google scraping
}
// Real flight lookup - no mock data
async getFlightInfo(params: FlightSearchParams): Promise<FlightData | null> {
const cacheKey = `${params.flightNumber}_${params.date}`;
// Check cache first (shorter cache for real data)
const cached = this.flightCache.get(cacheKey);
if (cached && cached.expires > Date.now()) {
return cached.data;
}
try {
// Try Google scraping first
let flightData = await this.scrapeGoogleFlights(params);
// If Google fails, try AviationStack (if API key available)
if (!flightData) {
flightData = await this.getFromAviationStack(params);
}
// Cache the result for 2 minutes (shorter for real data)
if (flightData) {
this.flightCache.set(cacheKey, {
data: flightData,
expires: Date.now() + (2 * 60 * 1000)
});
}
return flightData;
} catch (error) {
console.error('Error fetching flight data:', error);
return null; // Return null instead of mock data
}
}
// Google Flights scraping implementation
private async scrapeGoogleFlights(params: FlightSearchParams): Promise<FlightData | null> {
try {
// Google Flights URL format
const googleUrl = `https://www.google.com/travel/flights/search?tfs=CBwQAhoeEgoyMDI1LTA3LTAxagcIARIDTEFYcgcIARIDSkZLQAFIAXABggELCP___________wFAAUgBmAEB&hl=en`;
// For now, return null to indicate no real scraping implementation
// In production, you would implement actual web scraping here
console.log(`Would scrape Google for flight ${params.flightNumber} on ${params.date}`);
return null;
} catch (error) {
console.error('Google scraping error:', error);
return null;
}
}
// AviationStack API integration (only if API key available)
private async getFromAviationStack(params: FlightSearchParams): Promise<FlightData | null> {
const apiKey = process.env.AVIATIONSTACK_API_KEY;
console.log('Checking AviationStack API key:', apiKey ? `Key present (${apiKey.length} chars)` : 'No key');
if (!apiKey || apiKey === 'demo_key' || apiKey === '') {
console.log('No valid AviationStack API key available');
return null; // No API key available
}
try {
// Format flight number: Remove spaces and convert to uppercase
const formattedFlightNumber = params.flightNumber.replace(/\s+/g, '').toUpperCase();
console.log(`Formatted flight number: ${params.flightNumber} -> ${formattedFlightNumber}`);
// Note: Free tier doesn't support date filtering, so we get recent flights
// For future dates, this won't work well - consider upgrading subscription
const url = `http://api.aviationstack.com/v1/flights?access_key=${apiKey}&flight_iata=${formattedFlightNumber}&limit=10`;
console.log('AviationStack API URL:', url.replace(apiKey, '***'));
console.log('Note: Free tier returns recent flights only, not future scheduled flights');
const response = await fetch(url);
const data: any = await response.json();
console.log('AviationStack response status:', response.status);
if (!response.ok) {
console.error('AviationStack API error - HTTP status:', response.status);
return null;
}
// Check for API errors in response
if (data?.error) {
console.error('AviationStack API error:', data.error);
return null;
}
if (Array.isArray(data?.data) && data.data.length > 0) {
// This is a valid flight number that exists!
console.log(`✅ Valid flight number: ${formattedFlightNumber} exists in the system`);
// Try to find a flight matching the requested date
let flight = data.data.find((f: any) => f.flight_date === params.date);
// If no exact date match, use most recent for validation
if (!flight) {
flight = data.data[0];
console.log(` Flight ${formattedFlightNumber} is valid`);
console.log(`Recent flight: ${flight.departure.airport}${flight.arrival.airport}`);
console.log(`Operated by: ${flight.airline?.name || 'Unknown'}`);
console.log(`Note: Showing recent data from ${flight.flight_date} for validation`);
} else {
console.log(`✅ Flight found for exact date: ${params.date}`);
}
console.log('Flight route:', `${flight.departure.iata}${flight.arrival.iata}`);
console.log('Status:', flight.flight_status);
return {
flightNumber: flight.flight.iata,
flightDate: flight.flight_date,
status: this.normalizeStatus(flight.flight_status),
airline: flight.airline?.name,
aircraft: flight.aircraft?.registration,
departure: {
airport: flight.departure.iata,
airportName: flight.departure.airport,
scheduled: flight.departure.scheduled,
estimated: flight.departure.estimated,
actual: flight.departure.actual,
terminal: flight.departure.terminal,
gate: flight.departure.gate
},
arrival: {
airport: flight.arrival.iata,
airportName: flight.arrival.airport,
scheduled: flight.arrival.scheduled,
estimated: flight.arrival.estimated,
actual: flight.arrival.actual,
terminal: flight.arrival.terminal,
gate: flight.arrival.gate
},
delay: flight.departure.delay || 0,
lastUpdated: new Date().toISOString(),
source: 'aviationstack'
};
}
console.log(`❌ Invalid flight number: ${formattedFlightNumber} not found`);
console.log('This flight number does not exist or has not operated recently');
return null;
} catch (error) {
console.error('AviationStack API error:', error);
return null;
}
}
// Start periodic updates for a flight
startPeriodicUpdates(params: FlightSearchParams, intervalMinutes: number = 5): void {
const key = `${params.flightNumber}_${params.date}`;
// Clear existing interval if any
this.stopPeriodicUpdates(key);
// Set up new interval
const interval = setInterval(async () => {
try {
await this.getFlightInfo(params); // This will update the cache
console.log(`Updated flight data for ${params.flightNumber} on ${params.date}`);
} catch (error) {
console.error(`Error updating flight ${params.flightNumber}:`, error);
}
}, intervalMinutes * 60 * 1000);
this.updateIntervals.set(key, interval);
}
// Stop periodic updates for a flight
stopPeriodicUpdates(key: string): void {
const interval = this.updateIntervals.get(key);
if (interval) {
clearInterval(interval);
this.updateIntervals.delete(key);
}
}
// Get multiple flights with date specificity
async getMultipleFlights(flightParams: FlightSearchParams[]): Promise<{ [key: string]: FlightData | null }> {
const results: { [key: string]: FlightData | null } = {};
for (const params of flightParams) {
const key = `${params.flightNumber}_${params.date}`;
results[key] = await this.getFlightInfo(params);
}
return results;
}
// Normalize flight status across different APIs
private normalizeStatus(status: string): string {
const statusMap: { [key: string]: string } = {
'scheduled': 'scheduled',
'active': 'active',
'landed': 'landed',
'cancelled': 'cancelled',
'incident': 'delayed',
'diverted': 'diverted'
};
return statusMap[status.toLowerCase()] || status;
}
// Clean up resources
cleanup(): void {
for (const [key, interval] of this.updateIntervals) {
clearInterval(interval);
}
this.updateIntervals.clear();
this.flightCache.clear();
}
}
export default new FlightService();
export { FlightData, FlightSearchParams };