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>
265 lines
9.0 KiB
JavaScript
265 lines
9.0 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
// Import the existing backup service
|
|
const databaseService_1 = __importDefault(require("./backup-services/databaseService"));
|
|
// Extend the backup service with new user management methods
|
|
class EnhancedDatabaseService {
|
|
constructor() {
|
|
this.backupService = databaseService_1.default;
|
|
}
|
|
// Delegate all existing methods to backup service
|
|
async query(text, params) {
|
|
return this.backupService.query(text, params);
|
|
}
|
|
async getClient() {
|
|
return this.backupService.getClient();
|
|
}
|
|
async close() {
|
|
return this.backupService.close();
|
|
}
|
|
async initializeTables() {
|
|
return this.backupService.initializeTables();
|
|
}
|
|
// User methods from backup service
|
|
async createUser(user) {
|
|
return this.backupService.createUser(user);
|
|
}
|
|
async getUserByEmail(email) {
|
|
return this.backupService.getUserByEmail(email);
|
|
}
|
|
async getUserById(id) {
|
|
return this.backupService.getUserById(id);
|
|
}
|
|
async updateUserRole(email, role) {
|
|
return this.backupService.updateUserRole(email, role);
|
|
}
|
|
async updateUserLastSignIn(email) {
|
|
return this.backupService.updateUserLastSignIn(email);
|
|
}
|
|
async getUserCount() {
|
|
return this.backupService.getUserCount();
|
|
}
|
|
async updateUserApprovalStatus(email, status) {
|
|
return this.backupService.updateUserApprovalStatus(email, status);
|
|
}
|
|
async getApprovedUserCount() {
|
|
return this.backupService.getApprovedUserCount();
|
|
}
|
|
async getAllUsers() {
|
|
return this.backupService.getAllUsers();
|
|
}
|
|
async deleteUser(email) {
|
|
return this.backupService.deleteUser(email);
|
|
}
|
|
async getPendingUsers() {
|
|
return this.backupService.getPendingUsers();
|
|
}
|
|
// NEW: Enhanced user management methods
|
|
async completeUserOnboarding(email, onboardingData) {
|
|
const query = `
|
|
UPDATE users
|
|
SET phone = $1,
|
|
organization = $2,
|
|
onboarding_data = $3,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE email = $4
|
|
RETURNING *
|
|
`;
|
|
const result = await this.query(query, [
|
|
onboardingData.phone,
|
|
onboardingData.organization,
|
|
JSON.stringify(onboardingData),
|
|
email
|
|
]);
|
|
return result.rows[0] || null;
|
|
}
|
|
async approveUser(userEmail, approvedBy, newRole) {
|
|
const query = `
|
|
UPDATE users
|
|
SET status = 'active',
|
|
approval_status = 'approved',
|
|
approved_by = $1,
|
|
approved_at = CURRENT_TIMESTAMP,
|
|
role = COALESCE($2, role),
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE email = $3
|
|
RETURNING *
|
|
`;
|
|
const result = await this.query(query, [approvedBy, newRole, userEmail]);
|
|
// Log audit
|
|
if (result.rows[0]) {
|
|
await this.createAuditLog('user_approved', userEmail, approvedBy, { newRole });
|
|
}
|
|
return result.rows[0] || null;
|
|
}
|
|
async rejectUser(userEmail, rejectedBy, reason) {
|
|
const query = `
|
|
UPDATE users
|
|
SET status = 'deactivated',
|
|
approval_status = 'denied',
|
|
rejected_by = $1,
|
|
rejected_at = CURRENT_TIMESTAMP,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE email = $2
|
|
RETURNING *
|
|
`;
|
|
const result = await this.query(query, [rejectedBy, userEmail]);
|
|
// Log audit
|
|
if (result.rows[0]) {
|
|
await this.createAuditLog('user_rejected', userEmail, rejectedBy, { reason });
|
|
}
|
|
return result.rows[0] || null;
|
|
}
|
|
async deactivateUser(userEmail, deactivatedBy) {
|
|
const query = `
|
|
UPDATE users
|
|
SET status = 'deactivated',
|
|
deactivated_by = $1,
|
|
deactivated_at = CURRENT_TIMESTAMP,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE email = $2
|
|
RETURNING *
|
|
`;
|
|
const result = await this.query(query, [deactivatedBy, userEmail]);
|
|
// Log audit
|
|
if (result.rows[0]) {
|
|
await this.createAuditLog('user_deactivated', userEmail, deactivatedBy, {});
|
|
}
|
|
return result.rows[0] || null;
|
|
}
|
|
async reactivateUser(userEmail, reactivatedBy) {
|
|
const query = `
|
|
UPDATE users
|
|
SET status = 'active',
|
|
deactivated_by = NULL,
|
|
deactivated_at = NULL,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE email = $1
|
|
RETURNING *
|
|
`;
|
|
const result = await this.query(query, [userEmail]);
|
|
// Log audit
|
|
if (result.rows[0]) {
|
|
await this.createAuditLog('user_reactivated', userEmail, reactivatedBy, {});
|
|
}
|
|
return result.rows[0] || null;
|
|
}
|
|
async createAuditLog(action, userEmail, performedBy, details) {
|
|
const query = `
|
|
INSERT INTO user_audit_log (action, user_email, performed_by, action_details)
|
|
VALUES ($1, $2, $3, $4)
|
|
`;
|
|
await this.query(query, [action, userEmail, performedBy, JSON.stringify(details)]);
|
|
}
|
|
async getUserAuditLog(userEmail) {
|
|
const query = `
|
|
SELECT * FROM user_audit_log
|
|
WHERE user_email = $1
|
|
ORDER BY created_at DESC
|
|
`;
|
|
const result = await this.query(query, [userEmail]);
|
|
return result.rows;
|
|
}
|
|
async getUsersWithFilters(filters) {
|
|
let query = 'SELECT * FROM users WHERE 1=1';
|
|
const params = [];
|
|
let paramIndex = 1;
|
|
if (filters.status) {
|
|
query += ` AND status = $${paramIndex}`;
|
|
params.push(filters.status);
|
|
paramIndex++;
|
|
}
|
|
if (filters.role) {
|
|
query += ` AND role = $${paramIndex}`;
|
|
params.push(filters.role);
|
|
paramIndex++;
|
|
}
|
|
if (filters.search) {
|
|
query += ` AND (LOWER(name) LIKE LOWER($${paramIndex}) OR LOWER(email) LIKE LOWER($${paramIndex}) OR LOWER(organization) LIKE LOWER($${paramIndex}))`;
|
|
params.push(`%${filters.search}%`);
|
|
paramIndex++;
|
|
}
|
|
query += ' ORDER BY created_at DESC';
|
|
const result = await this.query(query, params);
|
|
return result.rows;
|
|
}
|
|
// Fix for first user admin issue
|
|
async getActiveUserCount() {
|
|
const query = "SELECT COUNT(*) as count FROM users WHERE status = 'active'";
|
|
const result = await this.query(query);
|
|
return parseInt(result.rows[0].count);
|
|
}
|
|
async isFirstUser() {
|
|
// Check if there are any active or approved users
|
|
const query = "SELECT COUNT(*) as count FROM users WHERE status = 'active' OR approval_status = 'approved'";
|
|
const result = await this.query(query);
|
|
return parseInt(result.rows[0].count) === 0;
|
|
}
|
|
// VIP methods from backup service
|
|
async createVip(vip) {
|
|
return this.backupService.createVip(vip);
|
|
}
|
|
async getVipById(id) {
|
|
return this.backupService.getVipById(id);
|
|
}
|
|
async getAllVips() {
|
|
return this.backupService.getAllVips();
|
|
}
|
|
async updateVip(id, vip) {
|
|
return this.backupService.updateVip(id, vip);
|
|
}
|
|
async deleteVip(id) {
|
|
return this.backupService.deleteVip(id);
|
|
}
|
|
async getVipsByDepartment(department) {
|
|
return this.backupService.getVipsByDepartment(department);
|
|
}
|
|
// Driver methods from backup service
|
|
async createDriver(driver) {
|
|
return this.backupService.createDriver(driver);
|
|
}
|
|
async getDriverById(id) {
|
|
return this.backupService.getDriverById(id);
|
|
}
|
|
async getAllDrivers() {
|
|
return this.backupService.getAllDrivers();
|
|
}
|
|
async updateDriver(id, driver) {
|
|
return this.backupService.updateDriver(id, driver);
|
|
}
|
|
async deleteDriver(id) {
|
|
return this.backupService.deleteDriver(id);
|
|
}
|
|
async getDriversByDepartment(department) {
|
|
return this.backupService.getDriversByDepartment(department);
|
|
}
|
|
async updateDriverLocation(id, location) {
|
|
return this.backupService.updateDriverLocation(id, location);
|
|
}
|
|
// Schedule methods from backup service
|
|
async createScheduleEvent(vipId, event) {
|
|
return this.backupService.createScheduleEvent(vipId, event);
|
|
}
|
|
async getScheduleByVipId(vipId) {
|
|
return this.backupService.getScheduleByVipId(vipId);
|
|
}
|
|
async updateScheduleEvent(vipId, eventId, event) {
|
|
return this.backupService.updateScheduleEvent(vipId, eventId, event);
|
|
}
|
|
async deleteScheduleEvent(vipId, eventId) {
|
|
return this.backupService.deleteScheduleEvent(vipId, eventId);
|
|
}
|
|
async getAllScheduleEvents() {
|
|
return this.backupService.getAllScheduleEvents();
|
|
}
|
|
async getScheduleEventsByDateRange(startDate, endDate) {
|
|
return this.backupService.getScheduleEventsByDateRange(startDate, endDate);
|
|
}
|
|
}
|
|
// Export singleton instance
|
|
const databaseService = new EnhancedDatabaseService();
|
|
exports.default = databaseService;
|
|
//# sourceMappingURL=databaseService.js.map
|