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
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:
264
backend-old-20260125/dist/services/dataService.js
vendored
Normal file
264
backend-old-20260125/dist/services/dataService.js
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
class DataService {
|
||||
constructor() {
|
||||
this.dataDir = path_1.default.join(process.cwd(), 'data');
|
||||
this.dataFile = path_1.default.join(this.dataDir, 'vip-coordinator.json');
|
||||
// Ensure data directory exists
|
||||
if (!fs_1.default.existsSync(this.dataDir)) {
|
||||
fs_1.default.mkdirSync(this.dataDir, { recursive: true });
|
||||
}
|
||||
this.data = this.loadData();
|
||||
}
|
||||
loadData() {
|
||||
try {
|
||||
if (fs_1.default.existsSync(this.dataFile)) {
|
||||
const fileContent = fs_1.default.readFileSync(this.dataFile, 'utf8');
|
||||
const loadedData = JSON.parse(fileContent);
|
||||
console.log(`✅ Loaded data from ${this.dataFile}`);
|
||||
console.log(` - VIPs: ${loadedData.vips?.length || 0}`);
|
||||
console.log(` - Drivers: ${loadedData.drivers?.length || 0}`);
|
||||
console.log(` - Users: ${loadedData.users?.length || 0}`);
|
||||
console.log(` - Schedules: ${Object.keys(loadedData.schedules || {}).length} VIPs with schedules`);
|
||||
// Ensure users array exists for backward compatibility
|
||||
if (!loadedData.users) {
|
||||
loadedData.users = [];
|
||||
}
|
||||
return loadedData;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error loading data file:', error);
|
||||
}
|
||||
// Return default empty data structure
|
||||
console.log('📝 Starting with empty data store');
|
||||
return {
|
||||
vips: [],
|
||||
drivers: [],
|
||||
schedules: {},
|
||||
users: [],
|
||||
adminSettings: {
|
||||
apiKeys: {
|
||||
aviationStackKey: process.env.AVIATIONSTACK_API_KEY || '',
|
||||
googleMapsKey: '',
|
||||
twilioKey: ''
|
||||
},
|
||||
systemSettings: {
|
||||
defaultPickupLocation: '',
|
||||
defaultDropoffLocation: '',
|
||||
timeZone: 'America/New_York',
|
||||
notificationsEnabled: false
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
saveData() {
|
||||
try {
|
||||
const dataToSave = JSON.stringify(this.data, null, 2);
|
||||
fs_1.default.writeFileSync(this.dataFile, dataToSave, 'utf8');
|
||||
console.log(`💾 Data saved to ${this.dataFile}`);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error saving data file:', error);
|
||||
}
|
||||
}
|
||||
// VIP operations
|
||||
getVips() {
|
||||
return this.data.vips;
|
||||
}
|
||||
addVip(vip) {
|
||||
this.data.vips.push(vip);
|
||||
this.saveData();
|
||||
return vip;
|
||||
}
|
||||
updateVip(id, updatedVip) {
|
||||
const index = this.data.vips.findIndex(vip => vip.id === id);
|
||||
if (index !== -1) {
|
||||
this.data.vips[index] = updatedVip;
|
||||
this.saveData();
|
||||
return this.data.vips[index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
deleteVip(id) {
|
||||
const index = this.data.vips.findIndex(vip => vip.id === id);
|
||||
if (index !== -1) {
|
||||
const deletedVip = this.data.vips.splice(index, 1)[0];
|
||||
// Also delete the VIP's schedule
|
||||
delete this.data.schedules[id];
|
||||
this.saveData();
|
||||
return deletedVip;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// Driver operations
|
||||
getDrivers() {
|
||||
return this.data.drivers;
|
||||
}
|
||||
addDriver(driver) {
|
||||
this.data.drivers.push(driver);
|
||||
this.saveData();
|
||||
return driver;
|
||||
}
|
||||
updateDriver(id, updatedDriver) {
|
||||
const index = this.data.drivers.findIndex(driver => driver.id === id);
|
||||
if (index !== -1) {
|
||||
this.data.drivers[index] = updatedDriver;
|
||||
this.saveData();
|
||||
return this.data.drivers[index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
deleteDriver(id) {
|
||||
const index = this.data.drivers.findIndex(driver => driver.id === id);
|
||||
if (index !== -1) {
|
||||
const deletedDriver = this.data.drivers.splice(index, 1)[0];
|
||||
this.saveData();
|
||||
return deletedDriver;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// Schedule operations
|
||||
getSchedule(vipId) {
|
||||
return this.data.schedules[vipId] || [];
|
||||
}
|
||||
addScheduleEvent(vipId, event) {
|
||||
if (!this.data.schedules[vipId]) {
|
||||
this.data.schedules[vipId] = [];
|
||||
}
|
||||
this.data.schedules[vipId].push(event);
|
||||
this.saveData();
|
||||
return event;
|
||||
}
|
||||
updateScheduleEvent(vipId, eventId, updatedEvent) {
|
||||
if (!this.data.schedules[vipId]) {
|
||||
return null;
|
||||
}
|
||||
const index = this.data.schedules[vipId].findIndex(event => event.id === eventId);
|
||||
if (index !== -1) {
|
||||
this.data.schedules[vipId][index] = updatedEvent;
|
||||
this.saveData();
|
||||
return this.data.schedules[vipId][index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
deleteScheduleEvent(vipId, eventId) {
|
||||
if (!this.data.schedules[vipId]) {
|
||||
return null;
|
||||
}
|
||||
const index = this.data.schedules[vipId].findIndex(event => event.id === eventId);
|
||||
if (index !== -1) {
|
||||
const deletedEvent = this.data.schedules[vipId].splice(index, 1)[0];
|
||||
this.saveData();
|
||||
return deletedEvent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
getAllSchedules() {
|
||||
return this.data.schedules;
|
||||
}
|
||||
// Admin settings operations
|
||||
getAdminSettings() {
|
||||
return this.data.adminSettings;
|
||||
}
|
||||
updateAdminSettings(settings) {
|
||||
this.data.adminSettings = { ...this.data.adminSettings, ...settings };
|
||||
this.saveData();
|
||||
}
|
||||
// Backup and restore operations
|
||||
createBackup() {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const backupFile = path_1.default.join(this.dataDir, `backup-${timestamp}.json`);
|
||||
try {
|
||||
fs_1.default.copyFileSync(this.dataFile, backupFile);
|
||||
console.log(`📦 Backup created: ${backupFile}`);
|
||||
return backupFile;
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error creating backup:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// User operations
|
||||
getUsers() {
|
||||
return this.data.users;
|
||||
}
|
||||
getUserByEmail(email) {
|
||||
return this.data.users.find(user => user.email === email) || null;
|
||||
}
|
||||
getUserById(id) {
|
||||
return this.data.users.find(user => user.id === id) || null;
|
||||
}
|
||||
addUser(user) {
|
||||
// Add timestamps
|
||||
const userWithTimestamps = {
|
||||
...user,
|
||||
created_at: new Date().toISOString(),
|
||||
last_sign_in_at: new Date().toISOString()
|
||||
};
|
||||
this.data.users.push(userWithTimestamps);
|
||||
this.saveData();
|
||||
console.log(`👤 Added user: ${user.name} (${user.email}) as ${user.role}`);
|
||||
return userWithTimestamps;
|
||||
}
|
||||
updateUser(email, updatedUser) {
|
||||
const index = this.data.users.findIndex(user => user.email === email);
|
||||
if (index !== -1) {
|
||||
this.data.users[index] = { ...this.data.users[index], ...updatedUser };
|
||||
this.saveData();
|
||||
console.log(`👤 Updated user: ${this.data.users[index].name} (${email})`);
|
||||
return this.data.users[index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
updateUserRole(email, role) {
|
||||
const index = this.data.users.findIndex(user => user.email === email);
|
||||
if (index !== -1) {
|
||||
this.data.users[index].role = role;
|
||||
this.saveData();
|
||||
console.log(`👤 Updated user role: ${this.data.users[index].name} (${email}) -> ${role}`);
|
||||
return this.data.users[index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
updateUserLastSignIn(email) {
|
||||
const index = this.data.users.findIndex(user => user.email === email);
|
||||
if (index !== -1) {
|
||||
this.data.users[index].last_sign_in_at = new Date().toISOString();
|
||||
this.saveData();
|
||||
return this.data.users[index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
deleteUser(email) {
|
||||
const index = this.data.users.findIndex(user => user.email === email);
|
||||
if (index !== -1) {
|
||||
const deletedUser = this.data.users.splice(index, 1)[0];
|
||||
this.saveData();
|
||||
console.log(`👤 Deleted user: ${deletedUser.name} (${email})`);
|
||||
return deletedUser;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
getUserCount() {
|
||||
return this.data.users.length;
|
||||
}
|
||||
getDataStats() {
|
||||
return {
|
||||
vips: this.data.vips.length,
|
||||
drivers: this.data.drivers.length,
|
||||
users: this.data.users.length,
|
||||
scheduledEvents: Object.values(this.data.schedules).reduce((total, events) => total + events.length, 0),
|
||||
vipsWithSchedules: Object.keys(this.data.schedules).length,
|
||||
dataFile: this.dataFile,
|
||||
lastModified: fs_1.default.existsSync(this.dataFile) ? fs_1.default.statSync(this.dataFile).mtime : null
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.default = new DataService();
|
||||
//# sourceMappingURL=dataService.js.map
|
||||
Reference in New Issue
Block a user