Skip to content

Commit 301f6d4

Browse files
committed
Fix
1 parent 9f443c3 commit 301f6d4

11 files changed

Lines changed: 357 additions & 94 deletions

File tree

server/db/flights.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import flightsPool from './connections/flightsConnection.js';
99
import crypto from 'crypto';
1010

1111
function sanitizeFlightForClient(flight) {
12-
const { user_id, ip_address, ...sanitizedFlight } = flight;
12+
const { user_id, ip_address, acars_token, ...sanitizedFlight } = flight;
1313
return {
1414
...sanitizedFlight,
1515
cruisingFL: flight.cruisingfl,
@@ -29,6 +29,20 @@ export async function getFlightsBySession(sessionId) {
2929
return flights;
3030
}
3131

32+
export async function validateAcarsAccess(sessionId, flightId, acarsToken) {
33+
const tableName = `flights_${sessionId}`;
34+
const result = await flightsPool.query(
35+
`SELECT acars_token FROM ${tableName} WHERE id = $1`,
36+
[flightId]
37+
);
38+
39+
if (result.rows.length === 0) {
40+
return false;
41+
}
42+
43+
return result.rows[0].acars_token === acarsToken;
44+
}
45+
3246
export async function getFlightsBySessionWithTime(sessionId, hoursBack = 2) {
3347
try {
3448
const tableName = `flights_${sessionId}`;
@@ -173,7 +187,11 @@ export async function addFlight(sessionId, flightData) {
173187
const result = await flightsPool.query(query, values);
174188

175189
const flight = result.rows[0];
176-
return sanitizeFlightForClient(flight);
190+
return {
191+
...flight,
192+
cruisingFL: flight.cruisingfl,
193+
clearedFL: flight.clearedfl,
194+
};
177195
}
178196

179197
export async function updateFlight(sessionId, flightId, updates) {

server/routes/flights.js

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
import express from 'express';
22
import requireAuth from '../middleware/isAuthenticated.js';
3-
import { getFlightsBySession, addFlight, updateFlight, deleteFlight } from '../db/flights.js';
3+
import { getFlightsBySession, addFlight, updateFlight, deleteFlight, validateAcarsAccess } from '../db/flights.js';
44
import { broadcastFlightEvent } from '../websockets/flightsWebsocket.js';
55
import { recordNewFlight } from '../db/statistics.js';
66
import { getClientIp } from '../tools/getIpAddress.js';
7+
import flightsPool from '../db/connections/flightsConnection.js';
78

89
const router = express.Router();
910

11+
const activeAcarsTerminals = new Map();
12+
1013
// GET: /api/flights/:sessionId - get all flights for a session
1114
router.get('/:sessionId', requireAuth, async (req, res) => {
1215
try {
1316
const flights = await getFlightsBySession(req.params.sessionId);
1417
res.json(flights);
1518
} catch (error) {
16-
console.error('Error fetching flights:', error);
1719
res.status(500).json({ error: 'Failed to fetch flights' });
1820
}
1921
});
@@ -31,11 +33,10 @@ router.post('/:sessionId', async (req, res) => {
3133

3234
await recordNewFlight();
3335

34-
broadcastFlightEvent(req.params.sessionId, 'flightAdded', flight);
35-
36+
const { acars_token, user_id, ip_address, ...sanitizedFlight } = flight;
37+
broadcastFlightEvent(req.params.sessionId, 'flightAdded', sanitizedFlight);
3638
res.status(201).json(flight);
3739
} catch (error) {
38-
console.error('Error adding flight:', error);
3940
res.status(500).json({ error: 'Failed to add flight' });
4041
}
4142
});
@@ -75,4 +76,89 @@ router.delete('/:sessionId/:flightId', requireAuth, async (req, res) => {
7576
}
7677
});
7778

79+
// GET: /api/flights/:sessionId/:flightId/validate-acars - validate ACARS access token
80+
router.get('/:sessionId/:flightId/validate-acars', async (req, res) => {
81+
try {
82+
const { sessionId, flightId } = req.params;
83+
const acarsToken = req.query.accessId;
84+
85+
if (!acarsToken) {
86+
return res.status(400).json({ valid: false, error: 'Missing access token' });
87+
}
88+
89+
const isValid = await validateAcarsAccess(sessionId, flightId, acarsToken);
90+
res.json({ valid: isValid });
91+
} catch (error) {
92+
res.status(500).json({ valid: false, error: 'Validation failed' });
93+
}
94+
});
95+
96+
// POST: /api/flights/acars/active - mark ACARS terminal as active
97+
router.post('/acars/active', async (req, res) => {
98+
try {
99+
const { sessionId, flightId, acarsToken } = req.body;
100+
101+
if (!sessionId || !flightId || !acarsToken) {
102+
return res.status(400).json({ error: 'Missing required fields' });
103+
}
104+
105+
const isValid = await validateAcarsAccess(sessionId, flightId, acarsToken);
106+
if (!isValid) {
107+
return res.status(403).json({ error: 'Invalid ACARS token' });
108+
}
109+
110+
const key = `${sessionId}:${flightId}`;
111+
activeAcarsTerminals.set(key, {
112+
sessionId,
113+
flightId,
114+
connectedAt: new Date().toISOString()
115+
});
116+
117+
res.json({ success: true });
118+
} catch (error) {
119+
res.status(500).json({ error: 'Failed to mark ACARS as active' });
120+
}
121+
});
122+
123+
// DELETE: /api/flights/acars/active/:sessionId/:flightId - mark ACARS terminal as inactive
124+
router.delete('/acars/active/:sessionId/:flightId', async (req, res) => {
125+
try {
126+
const { sessionId, flightId } = req.params;
127+
const key = `${sessionId}:${flightId}`;
128+
129+
activeAcarsTerminals.delete(key);
130+
131+
res.json({ success: true });
132+
} catch (error) {
133+
res.status(500).json({ error: 'Failed to mark ACARS as inactive' });
134+
}
135+
});
136+
137+
// GET: /api/flights/acars/active - get all active ACARS terminals
138+
router.get('/acars/active', async (req, res) => {
139+
try {
140+
const activeFlights = [];
141+
142+
for (const [key, { sessionId, flightId }] of activeAcarsTerminals.entries()) {
143+
try {
144+
const tableName = `flights_${sessionId}`;
145+
const result = await flightsPool.query(
146+
`SELECT id, callsign, departure, arrival, aircraft, session_id FROM ${tableName} WHERE id = $1`,
147+
[flightId]
148+
);
149+
150+
if (result.rows.length > 0) {
151+
activeFlights.push(result.rows[0]);
152+
}
153+
} catch (error) {
154+
activeAcarsTerminals.delete(key);
155+
}
156+
}
157+
158+
res.json(activeFlights);
159+
} catch (error) {
160+
res.status(500).json({ error: 'Failed to fetch active ACARS terminals' });
161+
}
162+
});
163+
78164
export default router;

server/websockets/flightsWebsocket.js

Lines changed: 29 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { validateSessionAccess } from '../middleware/sessionAccess.js';
44
import { updateSession, getAllSessions, getSessionById } from '../db/sessions.js';
55
import { getArrivalsIO } from './arrivalsWebsocket.js';
66
import { handleFlightStatusChange } from '../services/logbookStatusHandler.js';
7+
import flightsPool from '../db/connections/flightsConnection.js';
78

89
let io;
910
const updateTimers = new Map();
@@ -21,16 +22,11 @@ export function setupFlightsWebsocket(httpServer) {
2122
const sessionId = socket.handshake.query.sessionId;
2223
const accessId = socket.handshake.query.accessId;
2324

24-
// Basic session check
2525
if (!sessionId) {
2626
socket.disconnect(true);
2727
return;
2828
}
2929

30-
// Determine role:
31-
// - If an accessId is provided and validates -> controller (full privileges)
32-
// - If no accessId provided -> pilot (limited privileges: can listen & addFlight)
33-
// This avoids giving pilots controller access while still allowing them to receive events.
3430
let role = 'pilot';
3531
if (accessId) {
3632
const valid = await validateSessionAccess(sessionId, accessId);
@@ -45,20 +41,13 @@ export function setupFlightsWebsocket(httpServer) {
4541
socket.join(sessionId);
4642

4743
socket.on('updateFlight', async ({ flightId, updates }) => {
48-
// restrict updates from pilots (controllers only)
4944
if (socket.data.role !== 'controller') {
5045
socket.emit('flightError', { action: 'update', flightId, error: 'Not authorized' });
5146
return;
5247
}
5348
try {
54-
// Log all updates to see what's being received
55-
if (updates.status || updates.callsign) {
56-
console.log(`[FlightWS] Received update for ${updates.callsign || flightId}:`, JSON.stringify(updates));
57-
}
58-
59-
// Handle local hide/unhide - don't process these on server
6049
if (updates.hasOwnProperty('hidden')) {
61-
return; // Ignore hidden field updates
50+
return;
6251
}
6352

6453
if (updates.callsign && updates.callsign.length > 16) {
@@ -83,10 +72,7 @@ export function setupFlightsWebsocket(httpServer) {
8372

8473
await broadcastToArrivalSessions(updatedFlight);
8574

86-
// Handle logbook status changes
8775
if (updates.status && updatedFlight.callsign) {
88-
console.log(`[FlightWS] Detected status change: ${updatedFlight.callsign} -> ${updates.status}`);
89-
// Get session's airport to determine origin vs destination
9076
const session = await getSessionById(sessionId);
9177
const controllerAirport = session?.airport_icao || null;
9278
await handleFlightStatusChange(updatedFlight.callsign, updates.status, controllerAirport);
@@ -104,12 +90,10 @@ export function setupFlightsWebsocket(httpServer) {
10490
await updateFlight(sessionId, flightId, updates);
10591
updateTimers.delete(timerKey);
10692
} catch (error) {
107-
console.error('Error saving flight update to DB:', error);
10893
}
10994
}, 1000));
11095

11196
} catch (error) {
112-
console.error('Error updating flight via websocket:', error);
11397
socket.emit('flightError', { action: 'update', flightId, error: 'Failed to update flight' });
11498
}
11599
});
@@ -124,17 +108,18 @@ export function setupFlightsWebsocket(httpServer) {
124108

125109
const flight = await addFlight(sessionId, enhancedFlightData);
126110

127-
io.to(sessionId).emit('flightAdded', flight);
111+
socket.emit('flightAdded', flight);
128112

129-
await broadcastToArrivalSessions(flight);
113+
const { acars_token, user_id, ip_address, ...sanitizedFlight } = flight;
114+
socket.to(sessionId).emit('flightAdded', sanitizedFlight);
115+
116+
await broadcastToArrivalSessions(sanitizedFlight);
130117
} catch (error) {
131-
console.error('Error adding flight via websocket:', error);
132118
socket.emit('flightError', { action: 'add', error: 'Failed to add flight' });
133119
}
134120
});
135121

136122
socket.on('deleteFlight', async (flightId) => {
137-
// controllers only
138123
if (socket.data.role !== 'controller') {
139124
socket.emit('flightError', { action: 'delete', flightId, error: 'Not authorized' });
140125
return;
@@ -143,13 +128,11 @@ export function setupFlightsWebsocket(httpServer) {
143128
await deleteFlight(sessionId, flightId);
144129
io.to(sessionId).emit('flightDeleted', { flightId });
145130
} catch (error) {
146-
console.error('Error deleting flight via websocket:', error);
147131
socket.emit('flightError', { action: 'delete', flightId, error: 'Failed to delete flight' });
148132
}
149133
});
150134

151135
socket.on('updateSession', async (updates) => {
152-
// controllers only
153136
if (socket.data.role !== 'controller') {
154137
socket.emit('sessionError', { error: 'Not authorized' });
155138
return;
@@ -164,14 +147,11 @@ export function setupFlightsWebsocket(httpServer) {
164147
socket.emit('sessionError', { error: 'Session not found or update failed' });
165148
}
166149
} catch (error) {
167-
console.error('Error updating session via websocket:', error);
168150
socket.emit('sessionError', { error: 'Failed to update session' });
169151
}
170152
});
171153

172-
// NEW: controller issues a PDC for a flight -> persist & broadcast
173154
socket.on('issuePDC', async ({ flightId, pdcText, targetPilotUserId }) => {
174-
// controllers only
175155
if (socket.data.role !== 'controller') {
176156
socket.emit('flightError', { action: 'issuePDC', flightId, error: 'Not authorized' });
177157
return;
@@ -181,8 +161,6 @@ export function setupFlightsWebsocket(httpServer) {
181161
socket.emit('flightError', { action: 'issuePDC', error: 'Missing flightId' });
182162
return;
183163
}
184-
185-
// Use pdc_remarks (frontend checks this). Avoid writing unknown columns.
186164
const updates = {
187165
pdc_remarks: pdcText
188166
};
@@ -197,15 +175,12 @@ export function setupFlightsWebsocket(httpServer) {
197175
socket.emit('flightError', { action: 'issuePDC', flightId, error: 'Flight not found' });
198176
}
199177
} catch (error) {
200-
console.error('Error issuing PDC via websocket:', error);
201178
socket.emit('flightError', { action: 'issuePDC', flightId, error: 'Failed to issue PDC' });
202179
}
203180
});
204181

205-
// client requests that controllers issue a PDC for a flight
206182
socket.on('requestPDC', ({ flightId, callsign, note }) => {
207183
try {
208-
// broadcast to everyone in session (controllers should respond by flashing their flightstrip)
209184
io.to(sessionId).emit('pdcRequest', {
210185
flightId,
211186
callsign: callsign ?? null,
@@ -214,24 +189,41 @@ export function setupFlightsWebsocket(httpServer) {
214189
ts: new Date().toISOString()
215190
});
216191
} catch (err) {
217-
console.error('Error handling requestPDC:', err);
218192
socket.emit('flightError', { action: 'requestPDC', flightId, error: 'Failed to request PDC' });
219193
}
220194
});
221195

222-
socket.on('contactMe', ({ flightId, message }) => {
196+
socket.on('contactMe', async ({ flightId, message }) => {
223197
if (socket.data.role !== 'controller') {
224198
socket.emit('flightError', { action: 'contactMe', flightId, error: 'Not authorized' });
225199
return;
226200
}
227201
try {
228-
io.to(sessionId).emit('contactMe', {
202+
const allSessions = await getAllSessions();
203+
let targetSessionId = sessionId;
204+
205+
for (const session of allSessions) {
206+
try {
207+
const tableName = `flights_${session.session_id}`;
208+
const result = await flightsPool.query(
209+
`SELECT session_id FROM ${tableName} WHERE id = $1`,
210+
[flightId]
211+
);
212+
if (result.rows.length > 0) {
213+
targetSessionId = session.session_id;
214+
break;
215+
}
216+
} catch (err) {
217+
continue;
218+
}
219+
}
220+
221+
io.to(targetSessionId).emit('contactMe', {
229222
flightId,
230223
message: message || 'CONTACT CONTROLLER ON FREQUENCY',
231224
ts: new Date().toISOString()
232225
});
233226
} catch (err) {
234-
console.error('Error handling contactMe:', err);
235227
socket.emit('flightError', { action: 'contactMe', flightId, error: 'Failed to send contact message' });
236228
}
237229
});
@@ -257,7 +249,6 @@ async function broadcastToArrivalSessions(flight) {
257249
}
258250
}
259251
} catch (error) {
260-
console.error('Error broadcasting to arrival sessions:', error);
261252
}
262253
}
263254

@@ -269,4 +260,4 @@ export function broadcastFlightEvent(sessionId, event, data) {
269260
if (io) {
270261
io.to(sessionId).emit(event, data);
271262
}
272-
}
263+
}

src/components/ACARSPanel.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// ...existing code...
2-
import React from 'react';
31
import Button from './common/Button';
42

53
interface ACARSPanelProps {

0 commit comments

Comments
 (0)