Files
vip-coordinator/backend-old-20260125/dist/services/enhancedDataService.js
kyle 868f7efc23
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
Major Enhancement: NestJS Migration + CASL Authorization + Error Handling
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>
2026-01-31 08:50:25 +01:00

571 lines
21 KiB
JavaScript

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const database_1 = __importDefault(require("../config/database"));
const databaseService_1 = __importDefault(require("./databaseService"));
class EnhancedDataService {
// VIP operations
async getVips() {
try {
const query = `
SELECT v.*,
COALESCE(
json_agg(
json_build_object(
'flightNumber', f.flight_number,
'flightDate', f.flight_date,
'segment', f.segment
) ORDER BY f.segment
) FILTER (WHERE f.id IS NOT NULL),
'[]'::json
) as flights
FROM vips v
LEFT JOIN flights f ON v.id = f.vip_id
GROUP BY v.id
ORDER BY v.name
`;
const result = await database_1.default.query(query);
return result.rows.map(row => ({
id: row.id,
name: row.name,
organization: row.organization,
department: row.department,
transportMode: row.transport_mode,
expectedArrival: row.expected_arrival,
needsAirportPickup: row.needs_airport_pickup,
needsVenueTransport: row.needs_venue_transport,
notes: row.notes,
flights: row.flights
}));
}
catch (error) {
console.error('❌ Error fetching VIPs:', error);
throw error;
}
}
async addVip(vip) {
const client = await database_1.default.connect();
try {
await client.query('BEGIN');
// Insert VIP
const vipQuery = `
INSERT INTO vips (id, name, organization, department, transport_mode, expected_arrival, needs_airport_pickup, needs_venue_transport, notes)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING *
`;
const vipResult = await client.query(vipQuery, [
vip.id,
vip.name,
vip.organization,
vip.department || 'Office of Development',
vip.transportMode,
vip.expectedArrival || null,
vip.needsAirportPickup || false,
vip.needsVenueTransport,
vip.notes || ''
]);
// Insert flights if any
if (vip.flights && vip.flights.length > 0) {
for (const flight of vip.flights) {
const flightQuery = `
INSERT INTO flights (vip_id, flight_number, flight_date, segment)
VALUES ($1, $2, $3, $4)
`;
await client.query(flightQuery, [
vip.id,
flight.flightNumber,
flight.flightDate,
flight.segment
]);
}
}
await client.query('COMMIT');
const savedVip = {
...vip,
department: vipResult.rows[0].department,
transportMode: vipResult.rows[0].transport_mode,
expectedArrival: vipResult.rows[0].expected_arrival,
needsAirportPickup: vipResult.rows[0].needs_airport_pickup,
needsVenueTransport: vipResult.rows[0].needs_venue_transport
};
return savedVip;
}
catch (error) {
await client.query('ROLLBACK');
console.error('❌ Error adding VIP:', error);
throw error;
}
finally {
client.release();
}
}
async updateVip(id, vip) {
const client = await database_1.default.connect();
try {
await client.query('BEGIN');
// Update VIP
const vipQuery = `
UPDATE vips
SET name = $2, organization = $3, department = $4, transport_mode = $5,
expected_arrival = $6, needs_airport_pickup = $7, needs_venue_transport = $8, notes = $9
WHERE id = $1
RETURNING *
`;
const vipResult = await client.query(vipQuery, [
id,
vip.name,
vip.organization,
vip.department || 'Office of Development',
vip.transportMode,
vip.expectedArrival || null,
vip.needsAirportPickup || false,
vip.needsVenueTransport,
vip.notes || ''
]);
if (vipResult.rows.length === 0) {
await client.query('ROLLBACK');
return null;
}
// Delete existing flights and insert new ones
await client.query('DELETE FROM flights WHERE vip_id = $1', [id]);
if (vip.flights && vip.flights.length > 0) {
for (const flight of vip.flights) {
const flightQuery = `
INSERT INTO flights (vip_id, flight_number, flight_date, segment)
VALUES ($1, $2, $3, $4)
`;
await client.query(flightQuery, [
id,
flight.flightNumber,
flight.flightDate,
flight.segment
]);
}
}
await client.query('COMMIT');
const updatedVip = {
id: vipResult.rows[0].id,
name: vipResult.rows[0].name,
organization: vipResult.rows[0].organization,
department: vipResult.rows[0].department,
transportMode: vipResult.rows[0].transport_mode,
expectedArrival: vipResult.rows[0].expected_arrival,
needsAirportPickup: vipResult.rows[0].needs_airport_pickup,
needsVenueTransport: vipResult.rows[0].needs_venue_transport,
notes: vipResult.rows[0].notes,
flights: vip.flights || []
};
return updatedVip;
}
catch (error) {
await client.query('ROLLBACK');
console.error('❌ Error updating VIP:', error);
throw error;
}
finally {
client.release();
}
}
async deleteVip(id) {
try {
const query = `
DELETE FROM vips WHERE id = $1 RETURNING *
`;
const result = await database_1.default.query(query, [id]);
if (result.rows.length === 0) {
return null;
}
const deletedVip = {
id: result.rows[0].id,
name: result.rows[0].name,
organization: result.rows[0].organization,
department: result.rows[0].department,
transportMode: result.rows[0].transport_mode,
expectedArrival: result.rows[0].expected_arrival,
needsAirportPickup: result.rows[0].needs_airport_pickup,
needsVenueTransport: result.rows[0].needs_venue_transport,
notes: result.rows[0].notes
};
return deletedVip;
}
catch (error) {
console.error('❌ Error deleting VIP:', error);
throw error;
}
}
// Driver operations
async getDrivers() {
try {
const query = `
SELECT d.*,
COALESCE(
json_agg(DISTINCT se.vip_id) FILTER (WHERE se.vip_id IS NOT NULL),
'[]'::json
) as assigned_vip_ids
FROM drivers d
LEFT JOIN schedule_events se ON d.id = se.assigned_driver_id
GROUP BY d.id
ORDER BY d.name
`;
const result = await database_1.default.query(query);
// Get current locations from Redis
const driversWithLocations = await Promise.all(result.rows.map(async (row) => {
const location = await databaseService_1.default.getDriverLocation(row.id);
return {
id: row.id,
name: row.name,
phone: row.phone,
department: row.department,
currentLocation: location ? { lat: location.lat, lng: location.lng } : { lat: 0, lng: 0 },
assignedVipIds: row.assigned_vip_ids || []
};
}));
return driversWithLocations;
}
catch (error) {
console.error('❌ Error fetching drivers:', error);
throw error;
}
}
async addDriver(driver) {
try {
const query = `
INSERT INTO drivers (id, name, phone, department)
VALUES ($1, $2, $3, $4)
RETURNING *
`;
const result = await database_1.default.query(query, [
driver.id,
driver.name,
driver.phone,
driver.department || 'Office of Development'
]);
// Store location in Redis if provided
if (driver.currentLocation) {
await databaseService_1.default.updateDriverLocation(driver.id, driver.currentLocation);
}
const savedDriver = {
id: result.rows[0].id,
name: result.rows[0].name,
phone: result.rows[0].phone,
department: result.rows[0].department,
currentLocation: driver.currentLocation || { lat: 0, lng: 0 }
};
return savedDriver;
}
catch (error) {
console.error('❌ Error adding driver:', error);
throw error;
}
}
async updateDriver(id, driver) {
try {
const query = `
UPDATE drivers
SET name = $2, phone = $3, department = $4
WHERE id = $1
RETURNING *
`;
const result = await database_1.default.query(query, [
id,
driver.name,
driver.phone,
driver.department || 'Office of Development'
]);
if (result.rows.length === 0) {
return null;
}
// Update location in Redis if provided
if (driver.currentLocation) {
await databaseService_1.default.updateDriverLocation(id, driver.currentLocation);
}
const updatedDriver = {
id: result.rows[0].id,
name: result.rows[0].name,
phone: result.rows[0].phone,
department: result.rows[0].department,
currentLocation: driver.currentLocation || { lat: 0, lng: 0 }
};
return updatedDriver;
}
catch (error) {
console.error('❌ Error updating driver:', error);
throw error;
}
}
async deleteDriver(id) {
try {
const query = `
DELETE FROM drivers WHERE id = $1 RETURNING *
`;
const result = await database_1.default.query(query, [id]);
if (result.rows.length === 0) {
return null;
}
const deletedDriver = {
id: result.rows[0].id,
name: result.rows[0].name,
phone: result.rows[0].phone,
department: result.rows[0].department
};
return deletedDriver;
}
catch (error) {
console.error('❌ Error deleting driver:', error);
throw error;
}
}
// Schedule operations
async getSchedule(vipId) {
try {
const query = `
SELECT * FROM schedule_events
WHERE vip_id = $1
ORDER BY start_time
`;
const result = await database_1.default.query(query, [vipId]);
return result.rows.map(row => ({
id: row.id,
title: row.title,
location: row.location,
startTime: row.start_time,
endTime: row.end_time,
description: row.description,
assignedDriverId: row.assigned_driver_id,
status: row.status,
type: row.event_type
}));
}
catch (error) {
console.error('❌ Error fetching schedule:', error);
throw error;
}
}
async addScheduleEvent(vipId, event) {
try {
const query = `
INSERT INTO schedule_events (id, vip_id, title, location, start_time, end_time, description, assigned_driver_id, status, event_type)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING *
`;
const result = await database_1.default.query(query, [
event.id,
vipId,
event.title,
event.location,
event.startTime,
event.endTime,
event.description || '',
event.assignedDriverId || null,
event.status,
event.type
]);
const savedEvent = {
id: result.rows[0].id,
title: result.rows[0].title,
location: result.rows[0].location,
startTime: result.rows[0].start_time,
endTime: result.rows[0].end_time,
description: result.rows[0].description,
assignedDriverId: result.rows[0].assigned_driver_id,
status: result.rows[0].status,
type: result.rows[0].event_type
};
return savedEvent;
}
catch (error) {
console.error('❌ Error adding schedule event:', error);
throw error;
}
}
async updateScheduleEvent(vipId, eventId, event) {
try {
const query = `
UPDATE schedule_events
SET title = $3, location = $4, start_time = $5, end_time = $6, description = $7, assigned_driver_id = $8, status = $9, event_type = $10
WHERE id = $1 AND vip_id = $2
RETURNING *
`;
const result = await database_1.default.query(query, [
eventId,
vipId,
event.title,
event.location,
event.startTime,
event.endTime,
event.description || '',
event.assignedDriverId || null,
event.status,
event.type
]);
if (result.rows.length === 0) {
return null;
}
const updatedEvent = {
id: result.rows[0].id,
title: result.rows[0].title,
location: result.rows[0].location,
startTime: result.rows[0].start_time,
endTime: result.rows[0].end_time,
description: result.rows[0].description,
assignedDriverId: result.rows[0].assigned_driver_id,
status: result.rows[0].status,
type: result.rows[0].event_type
};
return updatedEvent;
}
catch (error) {
console.error('❌ Error updating schedule event:', error);
throw error;
}
}
async deleteScheduleEvent(vipId, eventId) {
try {
const query = `
DELETE FROM schedule_events
WHERE id = $1 AND vip_id = $2
RETURNING *
`;
const result = await database_1.default.query(query, [eventId, vipId]);
if (result.rows.length === 0) {
return null;
}
const deletedEvent = {
id: result.rows[0].id,
title: result.rows[0].title,
location: result.rows[0].location,
startTime: result.rows[0].start_time,
endTime: result.rows[0].end_time,
description: result.rows[0].description,
assignedDriverId: result.rows[0].assigned_driver_id,
status: result.rows[0].status,
type: result.rows[0].event_type
};
return deletedEvent;
}
catch (error) {
console.error('❌ Error deleting schedule event:', error);
throw error;
}
}
async getAllSchedules() {
try {
const query = `
SELECT * FROM schedule_events
ORDER BY vip_id, start_time
`;
const result = await database_1.default.query(query);
const schedules = {};
for (const row of result.rows) {
const vipId = row.vip_id;
if (!schedules[vipId]) {
schedules[vipId] = [];
}
schedules[vipId].push({
id: row.id,
title: row.title,
location: row.location,
startTime: row.start_time,
endTime: row.end_time,
description: row.description,
assignedDriverId: row.assigned_driver_id,
status: row.status,
type: row.event_type
});
}
return schedules;
}
catch (error) {
console.error('❌ Error fetching all schedules:', error);
throw error;
}
}
// Admin settings operations
async getAdminSettings() {
try {
const query = `
SELECT setting_key, setting_value FROM admin_settings
`;
const result = await database_1.default.query(query);
// Default settings structure
const defaultSettings = {
apiKeys: {
aviationStackKey: '',
googleMapsKey: '',
twilioKey: '',
googleClientId: '',
googleClientSecret: ''
},
systemSettings: {
defaultPickupLocation: '',
defaultDropoffLocation: '',
timeZone: 'America/New_York',
notificationsEnabled: false
}
};
// If no settings exist, return defaults
if (result.rows.length === 0) {
return defaultSettings;
}
// Reconstruct nested object from flattened keys
const settings = { ...defaultSettings };
for (const row of result.rows) {
const keys = row.setting_key.split('.');
let current = settings;
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) {
current[keys[i]] = {};
}
current = current[keys[i]];
}
// Parse boolean values
let value = row.setting_value;
if (value === 'true')
value = true;
else if (value === 'false')
value = false;
current[keys[keys.length - 1]] = value;
}
return settings;
}
catch (error) {
console.error('❌ Error fetching admin settings:', error);
throw error;
}
}
async updateAdminSettings(settings) {
try {
// Flatten settings and update
const flattenSettings = (obj, prefix = '') => {
const result = [];
for (const [key, value] of Object.entries(obj)) {
const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'object' && value !== null) {
result.push(...flattenSettings(value, fullKey));
}
else {
result.push({ key: fullKey, value: String(value) });
}
}
return result;
};
const flatSettings = flattenSettings(settings);
for (const setting of flatSettings) {
const query = `
INSERT INTO admin_settings (setting_key, setting_value)
VALUES ($1, $2)
ON CONFLICT (setting_key) DO UPDATE SET setting_value = $2
`;
await database_1.default.query(query, [setting.key, setting.value]);
}
}
catch (error) {
console.error('❌ Error updating admin settings:', error);
throw error;
}
}
}
exports.default = new EnhancedDataService();
//# sourceMappingURL=enhancedDataService.js.map