chore: Major repository cleanup - remove 273+ obsolete files
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
This commit removes obsolete, duplicate, and legacy files that have accumulated
over the course of development. The repository is now focused on the current
Auth0-based, NestJS/React implementation.
Files Removed:
1. Old Backup Directories (150+ files)
- backend-old-20260125/ (entire directory)
- frontend-old-20260125/ (entire directory)
These should never have been committed to version control.
2. Obsolete Authentication Documentation (12 files)
- KEYCLOAK_INTEGRATION_COMPLETE.md
- KEYCLOAK_SETUP.md
- SUPABASE_MIGRATION.md
- GOOGLE_OAUTH_*.md (4 files)
- OAUTH_*.md (3 files)
- auth0-action.js
- auth0-signup-form.json
We are using Auth0 only - these docs are no longer relevant.
3. Legacy Deployment Files (15 files)
- DOCKER_HUB_*.md (3 files)
- STANDALONE_INSTALL.md
- UBUNTU_INSTALL.md
- SIMPLE_DEPLOY.md
- deploy.sh, simple-deploy.sh, standalone-setup.sh
- setup.sh, setup.ps1
- docker-compose.{hub,prod,test}.yml
- Dockerfile.e2e
- install.md
These deployment approaches were abandoned.
4. Legacy Populate Scripts (12 files)
- populate-events*.{js,sh} (4 files)
- populate-test-data.{js,sh}
- populate-vips.js
- quick-populate-events.sh
- update-departments.js
- reset-database.ps1
- test-*.js (2 files)
All replaced by Prisma seed (backend/prisma/seed.ts).
5. Implementation Status Docs (16 files)
- BUILD_STATUS.md
- NAVIGATION_UX_IMPROVEMENTS.md
- NOTIFICATION_BADGE_IMPLEMENTATION.md
- DATABASE_MIGRATION_SUMMARY.md
- DOCUMENTATION_CLEANUP_SUMMARY.md
- PERMISSION_ISSUES_FIXED.md
Historical implementation notes - no longer needed.
6. Duplicate/Outdated Documentation (10 files)
- PORT_3000_SETUP_GUIDE.md
- POSTGRESQL_USER_MANAGEMENT.md
- REVERSE_PROXY_OAUTH_SETUP.md
- WEB_SERVER_PROXY_SETUP.md
- SIMPLE_USER_MANAGEMENT.md
- USER_MANAGEMENT_RECOMMENDATIONS.md
- ROLE_BASED_ACCESS_CONTROL.md
- README-API.md
Information already covered in main README.md and CLAUDE.md.
7. Old API Documentation (2 files)
- api-docs.html
- api-documentation.yaml
Outdated - API has changed significantly.
8. Environment File Duplicates (2 files)
- .env.prod
- .env.production
Redundant with .env.example.
Updated .gitignore:
- Added patterns to prevent future backup directory commits
- Added *-old-*, backend-old*, frontend-old*
Impact:
- Removed 273 files
- Reduced repository size significantly
- Cleaner, more navigable codebase
- Easier onboarding for new developers
Current Documentation:
- README.md - Main documentation
- CLAUDE.md - AI context and development guide
- REQUIREMENTS.md - Requirements
- CASL_AUTHORIZATION.md - Current auth system
- ERROR_HANDLING.md - Error handling patterns
- QUICKSTART.md - Quick start guide
- DEPLOYMENT.md - Deployment guide
- TESTING*.md - Testing guides
- SETUP_GUIDE.md - Setup instructions
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -34,7 +34,8 @@
|
|||||||
"Bash(timeout /t 10 /nobreak)",
|
"Bash(timeout /t 10 /nobreak)",
|
||||||
"Bash(dir:*)",
|
"Bash(dir:*)",
|
||||||
"Bash(lsof:*)",
|
"Bash(lsof:*)",
|
||||||
"Bash(powershell -Command:*)"
|
"Bash(powershell -Command:*)",
|
||||||
|
"Bash(git rm:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
.env.prod
29
.env.prod
@@ -1,29 +0,0 @@
|
|||||||
# Production Environment Configuration - SECURE VALUES
|
|
||||||
|
|
||||||
# Database Configuration
|
|
||||||
DB_PASSWORD=VipCoord2025SecureDB
|
|
||||||
|
|
||||||
# Domain Configuration
|
|
||||||
DOMAIN=bsa.madeamess.online
|
|
||||||
VITE_API_URL=https://api.bsa.madeamess.online
|
|
||||||
|
|
||||||
# Authentication Configuration (Secure production keys)
|
|
||||||
# JWT_SECRET - No longer needed! Keys are auto-generated and rotated every 24 hours
|
|
||||||
SESSION_SECRET=VipCoord2025SessionSecret9g8f7e6d5c4b3a2z1y0x9w8v7u6t5s4r3q2p1o0n9m8l7k6j5i4h3g2f1e
|
|
||||||
|
|
||||||
# Google OAuth Configuration
|
|
||||||
GOOGLE_CLIENT_ID=308004695553-6k34bbq22frc4e76kejnkgq8mncepbbg.apps.googleusercontent.com
|
|
||||||
GOOGLE_CLIENT_SECRET=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
|
|
||||||
GOOGLE_REDIRECT_URI=https://api.bsa.madeamess.online/auth/google/callback
|
|
||||||
|
|
||||||
# Frontend URL
|
|
||||||
FRONTEND_URL=https://bsa.madeamess.online
|
|
||||||
|
|
||||||
# Flight API Configuration
|
|
||||||
AVIATIONSTACK_API_KEY=your-aviationstack-api-key
|
|
||||||
|
|
||||||
# Admin Configuration
|
|
||||||
ADMIN_PASSWORD=VipAdmin2025Secure
|
|
||||||
|
|
||||||
# Port Configuration
|
|
||||||
PORT=3000
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -76,6 +76,9 @@ Thumbs.db
|
|||||||
*backup*
|
*backup*
|
||||||
*.bak
|
*.bak
|
||||||
*.tmp
|
*.tmp
|
||||||
|
*-old-*
|
||||||
|
backend-old*
|
||||||
|
frontend-old*
|
||||||
|
|
||||||
# Database files
|
# Database files
|
||||||
*.sqlite
|
*.sqlite
|
||||||
|
|||||||
373
BUILD_STATUS.md
373
BUILD_STATUS.md
@@ -1,373 +0,0 @@
|
|||||||
# VIP Coordinator - Build Status Report
|
|
||||||
|
|
||||||
**Date:** January 25, 2026
|
|
||||||
**Status:** Backend Complete ✅ | Frontend Pending
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎉 What We've Built
|
|
||||||
|
|
||||||
### ✅ Complete Backend API (100%)
|
|
||||||
|
|
||||||
A production-ready NestJS backend with Auth0 authentication, Prisma ORM, and PostgreSQL.
|
|
||||||
|
|
||||||
#### Tech Stack
|
|
||||||
- **Framework:** NestJS 10.x (TypeScript)
|
|
||||||
- **Database:** PostgreSQL 15 via Docker (port 5433)
|
|
||||||
- **ORM:** Prisma 5.x
|
|
||||||
- **Authentication:** Auth0 + Passport JWT
|
|
||||||
- **Validation:** class-validator + class-transformer
|
|
||||||
- **HTTP Client:** Axios (@nestjs/axios)
|
|
||||||
|
|
||||||
#### Modules Implemented
|
|
||||||
|
|
||||||
1. **Auth Module** ✅
|
|
||||||
- JWT strategy with Auth0 integration
|
|
||||||
- JWKS key validation
|
|
||||||
- JWT auth guard (global)
|
|
||||||
- Roles guard for RBAC
|
|
||||||
- Custom decorators (@CurrentUser, @Roles, @Public)
|
|
||||||
- First user auto-approval as admin
|
|
||||||
- User approval workflow
|
|
||||||
|
|
||||||
2. **Users Module** ✅
|
|
||||||
- List all users
|
|
||||||
- Get user by ID
|
|
||||||
- Update user (name, role)
|
|
||||||
- Approve/deny pending users
|
|
||||||
- Soft delete users
|
|
||||||
- Admin-only access
|
|
||||||
|
|
||||||
3. **VIPs Module** ✅
|
|
||||||
- Create VIP profiles
|
|
||||||
- List all VIPs with flights and events
|
|
||||||
- Get VIP details
|
|
||||||
- Update VIP information
|
|
||||||
- Soft delete VIPs
|
|
||||||
- Two arrival modes: Flight, Self-driving
|
|
||||||
- Department organization
|
|
||||||
- Airport pickup / venue transport flags
|
|
||||||
|
|
||||||
4. **Drivers Module** ✅
|
|
||||||
- Create driver profiles
|
|
||||||
- List all drivers with schedules
|
|
||||||
- Get driver details
|
|
||||||
- Get complete driver schedule
|
|
||||||
- Update driver information
|
|
||||||
- Optional user account linking
|
|
||||||
- Soft delete drivers
|
|
||||||
|
|
||||||
5. **Events Module** ✅
|
|
||||||
- Create schedule events
|
|
||||||
- **Conflict detection** (prevents double-booking drivers)
|
|
||||||
- List all events
|
|
||||||
- Get event details
|
|
||||||
- Update events (with conflict recheck)
|
|
||||||
- Update event status (drivers can do this)
|
|
||||||
- Soft delete events
|
|
||||||
- 5 event types: Transport, Meeting, Event, Meal, Accommodation
|
|
||||||
- 4 event statuses: Scheduled, In-Progress, Completed, Cancelled
|
|
||||||
|
|
||||||
6. **Flights Module** ✅
|
|
||||||
- Create flight records
|
|
||||||
- List all flights
|
|
||||||
- Get flights by VIP
|
|
||||||
- Update flight information
|
|
||||||
- Delete flights
|
|
||||||
- **Real-time flight tracking** (AviationStack API integration)
|
|
||||||
- Multi-segment itinerary support
|
|
||||||
|
|
||||||
#### Database Schema
|
|
||||||
|
|
||||||
**5 Core Models:**
|
|
||||||
- User (auth0Id, email, role, isApproved, deletedAt)
|
|
||||||
- VIP (name, organization, department, arrivalMode, etc.)
|
|
||||||
- Driver (name, phone, department, userId, deletedAt)
|
|
||||||
- ScheduleEvent (vipId, driverId, times, type, status, deletedAt)
|
|
||||||
- Flight (vipId, flightNumber, airports, times, status)
|
|
||||||
|
|
||||||
**3 Enums:**
|
|
||||||
- Role: ADMINISTRATOR, COORDINATOR, DRIVER
|
|
||||||
- Department: OFFICE_OF_DEVELOPMENT, ADMIN
|
|
||||||
- ArrivalMode: FLIGHT, SELF_DRIVING
|
|
||||||
- EventType: TRANSPORT, MEETING, EVENT, MEAL, ACCOMMODATION
|
|
||||||
- EventStatus: SCHEDULED, IN_PROGRESS, COMPLETED, CANCELLED
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Soft deletes on all main entities
|
|
||||||
- Automatic timestamps (createdAt, updatedAt)
|
|
||||||
- Cascading relationships
|
|
||||||
- Indexed columns for performance
|
|
||||||
|
|
||||||
#### API Endpoints (40+ endpoints)
|
|
||||||
|
|
||||||
All endpoints prefixed with `/api/v1`
|
|
||||||
|
|
||||||
**Public:**
|
|
||||||
- GET /health - Health check
|
|
||||||
|
|
||||||
**Auth:**
|
|
||||||
- GET /auth/profile - Get current user
|
|
||||||
|
|
||||||
**Users** (Admin only):
|
|
||||||
- GET /users
|
|
||||||
- GET /users/pending
|
|
||||||
- GET /users/:id
|
|
||||||
- PATCH /users/:id
|
|
||||||
- PATCH /users/:id/approve
|
|
||||||
- DELETE /users/:id
|
|
||||||
|
|
||||||
**VIPs** (Admin, Coordinator; Drivers view-only):
|
|
||||||
- GET /vips
|
|
||||||
- POST /vips
|
|
||||||
- GET /vips/:id
|
|
||||||
- PATCH /vips/:id
|
|
||||||
- DELETE /vips/:id
|
|
||||||
|
|
||||||
**Drivers** (Admin, Coordinator; Drivers view-only):
|
|
||||||
- GET /drivers
|
|
||||||
- POST /drivers
|
|
||||||
- GET /drivers/:id
|
|
||||||
- GET /drivers/:id/schedule
|
|
||||||
- PATCH /drivers/:id
|
|
||||||
- DELETE /drivers/:id
|
|
||||||
|
|
||||||
**Events** (Admin, Coordinator create/update; Drivers can update status):
|
|
||||||
- GET /events
|
|
||||||
- POST /events (with conflict detection!)
|
|
||||||
- GET /events/:id
|
|
||||||
- PATCH /events/:id
|
|
||||||
- PATCH /events/:id/status
|
|
||||||
- DELETE /events/:id
|
|
||||||
|
|
||||||
**Flights** (Admin, Coordinator):
|
|
||||||
- GET /flights
|
|
||||||
- POST /flights
|
|
||||||
- GET /flights/status/:flightNumber (real-time tracking!)
|
|
||||||
- GET /flights/vip/:vipId
|
|
||||||
- GET /flights/:id
|
|
||||||
- PATCH /flights/:id
|
|
||||||
- DELETE /flights/:id
|
|
||||||
|
|
||||||
#### Security Features
|
|
||||||
|
|
||||||
- ✅ JWT authentication on all routes (except @Public)
|
|
||||||
- ✅ Role-based access control (RBAC)
|
|
||||||
- ✅ User approval workflow (prevents unauthorized access)
|
|
||||||
- ✅ First user auto-admin (solves bootstrap problem)
|
|
||||||
- ✅ Input validation on all DTOs
|
|
||||||
- ✅ SQL injection prevention (Prisma ORM)
|
|
||||||
- ✅ Soft deletes (preserve data)
|
|
||||||
- ✅ CORS configuration
|
|
||||||
|
|
||||||
#### Sample Data
|
|
||||||
|
|
||||||
Database seeded with:
|
|
||||||
- 2 sample users (admin, coordinator)
|
|
||||||
- 2 sample VIPs (flight arrival, self-driving)
|
|
||||||
- 2 sample drivers
|
|
||||||
- 3 sample events (airport pickup, dinner, conference transport)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📁 Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
backend/
|
|
||||||
├── prisma/
|
|
||||||
│ ├── schema.prisma # Database schema (source of truth)
|
|
||||||
│ ├── migrations/ # Auto-generated migrations
|
|
||||||
│ │ └── 20260125085806_init/
|
|
||||||
│ └── seed.ts # Sample data seeder
|
|
||||||
├── src/
|
|
||||||
│ ├── main.ts # App entry point
|
|
||||||
│ ├── app.module.ts # Root module (imports all features)
|
|
||||||
│ ├── app.controller.ts # Health check
|
|
||||||
│ ├── app.service.ts
|
|
||||||
│ ├── prisma/
|
|
||||||
│ │ ├── prisma.module.ts
|
|
||||||
│ │ └── prisma.service.ts # Database service (singleton)
|
|
||||||
│ ├── auth/
|
|
||||||
│ │ ├── auth.module.ts
|
|
||||||
│ │ ├── auth.service.ts
|
|
||||||
│ │ ├── auth.controller.ts
|
|
||||||
│ │ ├── strategies/
|
|
||||||
│ │ │ └── jwt.strategy.ts (Auth0 JWT validation)
|
|
||||||
│ │ ├── guards/
|
|
||||||
│ │ │ ├── jwt-auth.guard.ts (global guard)
|
|
||||||
│ │ │ └── roles.guard.ts (RBAC guard)
|
|
||||||
│ │ └── decorators/
|
|
||||||
│ │ ├── current-user.decorator.ts
|
|
||||||
│ │ ├── roles.decorator.ts
|
|
||||||
│ │ └── public.decorator.ts
|
|
||||||
│ ├── users/
|
|
||||||
│ │ ├── users.module.ts
|
|
||||||
│ │ ├── users.service.ts
|
|
||||||
│ │ ├── users.controller.ts
|
|
||||||
│ │ └── dto/ (UpdateUserDto, ApproveUserDto)
|
|
||||||
│ ├── vips/
|
|
||||||
│ │ ├── vips.module.ts
|
|
||||||
│ │ ├── vips.service.ts
|
|
||||||
│ │ ├── vips.controller.ts
|
|
||||||
│ │ └── dto/ (CreateVipDto, UpdateVipDto)
|
|
||||||
│ ├── drivers/
|
|
||||||
│ │ ├── drivers.module.ts
|
|
||||||
│ │ ├── drivers.service.ts
|
|
||||||
│ │ ├── drivers.controller.ts
|
|
||||||
│ │ └── dto/ (CreateDriverDto, UpdateDriverDto)
|
|
||||||
│ ├── events/
|
|
||||||
│ │ ├── events.module.ts
|
|
||||||
│ │ ├── events.service.ts (includes conflict detection)
|
|
||||||
│ │ ├── events.controller.ts
|
|
||||||
│ │ └── dto/ (CreateEventDto, UpdateEventDto, UpdateEventStatusDto)
|
|
||||||
│ └── flights/
|
|
||||||
│ ├── flights.module.ts
|
|
||||||
│ ├── flights.service.ts (AviationStack integration)
|
|
||||||
│ ├── flights.controller.ts
|
|
||||||
│ └── dto/ (CreateFlightDto, UpdateFlightDto)
|
|
||||||
├── package.json
|
|
||||||
├── tsconfig.json
|
|
||||||
├── nest-cli.json
|
|
||||||
├── .env
|
|
||||||
├── .env.example
|
|
||||||
└── README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Running the Backend
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
- Node.js 20+
|
|
||||||
- Docker Desktop
|
|
||||||
- Auth0 Account (free tier)
|
|
||||||
|
|
||||||
### Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Start PostgreSQL
|
|
||||||
cd vip-coordinator
|
|
||||||
docker-compose up -d postgres
|
|
||||||
|
|
||||||
# 2. Install dependencies
|
|
||||||
cd backend
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# 3. Configure Auth0
|
|
||||||
# Edit backend/.env with your Auth0 credentials
|
|
||||||
|
|
||||||
# 4. Run migrations
|
|
||||||
npx prisma generate
|
|
||||||
npx prisma migrate dev
|
|
||||||
|
|
||||||
# 5. Seed sample data (optional)
|
|
||||||
npm run prisma:seed
|
|
||||||
|
|
||||||
# 6. Start backend
|
|
||||||
npm run start:dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Backend will be available at: **http://localhost:3000/api/v1**
|
|
||||||
|
|
||||||
### Test It
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Health check (public)
|
|
||||||
curl http://localhost:3000/api/v1/health
|
|
||||||
|
|
||||||
# Get profile (requires Auth0 token)
|
|
||||||
curl http://localhost:3000/api/v1/auth/profile \
|
|
||||||
-H "Authorization: Bearer YOUR_AUTH0_TOKEN"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Build Statistics
|
|
||||||
|
|
||||||
- **Total Files Created:** 60+
|
|
||||||
- **Lines of Code:** ~3,500+
|
|
||||||
- **Modules:** 6 feature modules
|
|
||||||
- **API Endpoints:** 40+
|
|
||||||
- **Database Tables:** 5 models
|
|
||||||
- **Time to Build:** ~2 hours
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ What Works
|
|
||||||
|
|
||||||
1. ✅ **Auth0 Integration** - JWT authentication fully configured
|
|
||||||
2. ✅ **User Management** - CRUD + approval workflow
|
|
||||||
3. ✅ **VIP Management** - Complete CRUD with relationships
|
|
||||||
4. ✅ **Driver Management** - Complete CRUD with schedule views
|
|
||||||
5. ✅ **Event Scheduling** - CRUD + intelligent conflict detection
|
|
||||||
6. ✅ **Flight Tracking** - CRUD + real-time API integration
|
|
||||||
7. ✅ **Role-Based Access** - Administrator, Coordinator, Driver permissions
|
|
||||||
8. ✅ **Database** - PostgreSQL with Prisma, migrations, seeding
|
|
||||||
9. ✅ **Docker** - PostgreSQL running in container
|
|
||||||
10. ✅ **TypeScript** - Fully typed, compiles without errors
|
|
||||||
11. ✅ **Validation** - All inputs validated with DTOs
|
|
||||||
12. ✅ **Soft Deletes** - Data preservation across all entities
|
|
||||||
13. ✅ **Logging** - NestJS logger throughout
|
|
||||||
14. ✅ **Documentation** - README.md, CLAUDE.md
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔜 What's Next (Frontend)
|
|
||||||
|
|
||||||
To complete the application, we need to build:
|
|
||||||
|
|
||||||
1. **React Frontend** with Vite
|
|
||||||
2. **Shadcn UI** + Tailwind CSS
|
|
||||||
3. **Auth0 React SDK** for authentication
|
|
||||||
4. **TanStack Query** for data fetching
|
|
||||||
5. **React Router** for navigation
|
|
||||||
6. **Pages:**
|
|
||||||
- Login / Callback
|
|
||||||
- Dashboard
|
|
||||||
- VIP List / Details / Forms
|
|
||||||
- Driver List / Details / Forms
|
|
||||||
- Schedule Manager (calendar view)
|
|
||||||
- Flight Tracking
|
|
||||||
- User Management (admin)
|
|
||||||
7. **Components:**
|
|
||||||
- Protected routes
|
|
||||||
- Navigation
|
|
||||||
- Forms with validation
|
|
||||||
- Data tables
|
|
||||||
- Loading states
|
|
||||||
- Error handling
|
|
||||||
|
|
||||||
**Estimated Time:** 4-6 hours for complete frontend
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Current State
|
|
||||||
|
|
||||||
**Backend:** ✅ 100% Complete & Tested
|
|
||||||
**Frontend:** ⏳ 0% (not started)
|
|
||||||
**Total Progress:** ~50% of full application
|
|
||||||
|
|
||||||
The backend is production-ready and can be deployed to Digital Ocean App Platform or any Docker-compatible host. It's fully functional and awaiting the React frontend to become a complete application.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Need to continue building?** Start with the React frontend initialization:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd vip-coordinator
|
|
||||||
npm create vite@latest frontend -- --template react-ts
|
|
||||||
cd frontend
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
Then add:
|
|
||||||
- Shadcn UI setup
|
|
||||||
- Auth0 React SDK
|
|
||||||
- TanStack Query
|
|
||||||
- React Router
|
|
||||||
- All pages and components
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Last Updated:** January 25, 2026
|
|
||||||
**Status:** Backend production-ready, awaiting frontend development
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
# ✅ CORRECTED Google OAuth Setup Guide
|
|
||||||
|
|
||||||
## ⚠️ Issues Found with Previous Setup
|
|
||||||
|
|
||||||
The previous coder was using **deprecated Google+ API** which was shut down in 2019. This guide provides the correct modern approach using Google Identity API.
|
|
||||||
|
|
||||||
## 🔧 What Was Fixed
|
|
||||||
|
|
||||||
1. **Removed Google+ API references** - Now uses Google Identity API
|
|
||||||
2. **Fixed redirect URI configuration** - Points to backend instead of frontend
|
|
||||||
3. **Added missing `/auth/setup` endpoint** - Frontend was calling non-existent endpoint
|
|
||||||
4. **Corrected OAuth flow** - Proper backend callback handling
|
|
||||||
|
|
||||||
## 🚀 Correct Setup Instructions
|
|
||||||
|
|
||||||
### Step 1: Google Cloud Console Setup
|
|
||||||
|
|
||||||
1. **Go to Google Cloud Console**
|
|
||||||
- Visit: https://console.cloud.google.com/
|
|
||||||
|
|
||||||
2. **Create or Select Project**
|
|
||||||
- Create new project: "VIP Coordinator"
|
|
||||||
- Or select existing project
|
|
||||||
|
|
||||||
3. **Enable Google Identity API** ⚠️ **NOT Google+ API**
|
|
||||||
- Go to "APIs & Services" → "Library"
|
|
||||||
- Search for "Google Identity API" or "Google+ API"
|
|
||||||
- **Important**: Use "Google Identity API" - Google+ is deprecated!
|
|
||||||
- Click "Enable"
|
|
||||||
|
|
||||||
4. **Create OAuth 2.0 Credentials**
|
|
||||||
- Go to "APIs & Services" → "Credentials"
|
|
||||||
- Click "Create Credentials" → "OAuth 2.0 Client IDs"
|
|
||||||
- Application type: "Web application"
|
|
||||||
- Name: "VIP Coordinator Web App"
|
|
||||||
|
|
||||||
5. **Configure Authorized URLs** ⚠️ **CRITICAL: Use Backend URLs**
|
|
||||||
|
|
||||||
**Authorized JavaScript origins:**
|
|
||||||
```
|
|
||||||
http://localhost:3000
|
|
||||||
http://bsa.madeamess.online:3000
|
|
||||||
```
|
|
||||||
|
|
||||||
**Authorized redirect URIs:** ⚠️ **Backend callback, NOT frontend**
|
|
||||||
```
|
|
||||||
http://localhost:3000/auth/google/callback
|
|
||||||
http://bsa.madeamess.online:3000/auth/google/callback
|
|
||||||
```
|
|
||||||
|
|
||||||
6. **Save Credentials**
|
|
||||||
- Copy **Client ID** and **Client Secret**
|
|
||||||
|
|
||||||
### Step 2: Update Environment Variables
|
|
||||||
|
|
||||||
Edit `backend/.env`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Replace these values with your actual Google OAuth credentials
|
|
||||||
GOOGLE_CLIENT_ID=your-actual-client-id-here.apps.googleusercontent.com
|
|
||||||
GOOGLE_CLIENT_SECRET=GOCSPX-your-actual-client-secret-here
|
|
||||||
GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google/callback
|
|
||||||
|
|
||||||
# For production, also update:
|
|
||||||
# GOOGLE_REDIRECT_URI=http://bsa.madeamess.online:3000/auth/google/callback
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Test the Setup
|
|
||||||
|
|
||||||
1. **Restart the backend:**
|
|
||||||
```bash
|
|
||||||
cd vip-coordinator
|
|
||||||
docker-compose -f docker-compose.dev.yml restart backend
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Test the OAuth flow:**
|
|
||||||
- Visit: http://localhost:5173 (or your frontend URL)
|
|
||||||
- Click "Continue with Google"
|
|
||||||
- Should redirect to Google login
|
|
||||||
- After login, should redirect back and log you in
|
|
||||||
|
|
||||||
3. **Check backend logs:**
|
|
||||||
```bash
|
|
||||||
docker-compose -f docker-compose.dev.yml logs backend
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔍 How the Corrected Flow Works
|
|
||||||
|
|
||||||
1. **User clicks "Continue with Google"**
|
|
||||||
2. **Frontend calls** `/auth/google/url` to get OAuth URL
|
|
||||||
3. **Frontend redirects** to Google OAuth
|
|
||||||
4. **Google redirects back** to `http://localhost:3000/auth/google/callback`
|
|
||||||
5. **Backend handles callback**, exchanges code for user info
|
|
||||||
6. **Backend creates JWT token** and redirects to frontend with token
|
|
||||||
7. **Frontend receives token** and authenticates user
|
|
||||||
|
|
||||||
## 🛠️ Key Differences from Previous Implementation
|
|
||||||
|
|
||||||
| Previous (Broken) | Corrected |
|
|
||||||
|-------------------|-----------|
|
|
||||||
| Google+ API (deprecated) | Google Identity API |
|
|
||||||
| Frontend redirect URI | Backend redirect URI |
|
|
||||||
| Missing `/auth/setup` endpoint | Added setup status endpoint |
|
|
||||||
| Inconsistent OAuth flow | Standard OAuth 2.0 flow |
|
|
||||||
|
|
||||||
## 🔧 Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues:
|
|
||||||
|
|
||||||
1. **"OAuth not configured" error:**
|
|
||||||
- Check `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` in `.env`
|
|
||||||
- Restart backend after changing environment variables
|
|
||||||
|
|
||||||
2. **"Invalid redirect URI" error:**
|
|
||||||
- Verify redirect URIs in Google Console match exactly:
|
|
||||||
- `http://localhost:3000/auth/google/callback`
|
|
||||||
- `http://bsa.madeamess.online:3000/auth/google/callback`
|
|
||||||
- No trailing slashes!
|
|
||||||
|
|
||||||
3. **"API not enabled" error:**
|
|
||||||
- Make sure you enabled "Google Identity API" (not Google+)
|
|
||||||
- Wait a few minutes for API to activate
|
|
||||||
|
|
||||||
4. **Login button doesn't work:**
|
|
||||||
- Check browser console for errors
|
|
||||||
- Verify backend is running on port 3000
|
|
||||||
- Check `/auth/setup` endpoint returns proper status
|
|
||||||
|
|
||||||
### Debug Commands:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check if backend is running
|
|
||||||
curl http://localhost:3000/api/health
|
|
||||||
|
|
||||||
# Check OAuth setup status
|
|
||||||
curl http://localhost:3000/auth/setup
|
|
||||||
|
|
||||||
# Check backend logs
|
|
||||||
docker-compose -f docker-compose.dev.yml logs backend
|
|
||||||
|
|
||||||
# Check environment variables are loaded
|
|
||||||
docker exec vip-coordinator-backend-1 env | grep GOOGLE
|
|
||||||
```
|
|
||||||
|
|
||||||
## ✅ Verification Steps
|
|
||||||
|
|
||||||
1. **Setup status should show configured:**
|
|
||||||
```bash
|
|
||||||
curl http://localhost:3000/auth/setup
|
|
||||||
# Should return: {"setupCompleted": true, "firstAdminCreated": false, "oauthConfigured": true}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **OAuth URL should be generated:**
|
|
||||||
```bash
|
|
||||||
curl http://localhost:3000/auth/google/url
|
|
||||||
# Should return: {"url": "https://accounts.google.com/o/oauth2/v2/auth?..."}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Login flow should work:**
|
|
||||||
- Visit frontend
|
|
||||||
- Click "Continue with Google"
|
|
||||||
- Complete Google login
|
|
||||||
- Should be redirected back and logged in
|
|
||||||
|
|
||||||
## 🎉 Success!
|
|
||||||
|
|
||||||
Once working, you should see:
|
|
||||||
- ✅ Google login button works
|
|
||||||
- ✅ Redirects to Google OAuth
|
|
||||||
- ✅ Returns to app after login
|
|
||||||
- ✅ User is authenticated with JWT token
|
|
||||||
- ✅ First user becomes administrator
|
|
||||||
|
|
||||||
The authentication system now uses modern Google Identity API and follows proper OAuth 2.0 standards!
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
# VIP Coordinator Database Migration Summary
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
Successfully migrated the VIP Coordinator application from JSON file storage to a proper database architecture using PostgreSQL and Redis.
|
|
||||||
|
|
||||||
## Architecture Changes
|
|
||||||
|
|
||||||
### Before (JSON File Storage)
|
|
||||||
- All data stored in `backend/data/vip-coordinator.json`
|
|
||||||
- Single file for VIPs, drivers, schedules, and admin settings
|
|
||||||
- No concurrent access control
|
|
||||||
- No real-time capabilities
|
|
||||||
- Risk of data corruption
|
|
||||||
|
|
||||||
### After (PostgreSQL + Redis)
|
|
||||||
- **PostgreSQL**: Persistent business data with ACID compliance
|
|
||||||
- **Redis**: Real-time data and caching
|
|
||||||
- Proper data relationships and constraints
|
|
||||||
- Concurrent access support
|
|
||||||
- Real-time location tracking
|
|
||||||
- Flight data caching
|
|
||||||
|
|
||||||
## Database Schema
|
|
||||||
|
|
||||||
### PostgreSQL Tables
|
|
||||||
1. **vips** - VIP profiles and basic information
|
|
||||||
2. **flights** - Flight details linked to VIPs
|
|
||||||
3. **drivers** - Driver profiles
|
|
||||||
4. **schedule_events** - Event scheduling with driver assignments
|
|
||||||
5. **admin_settings** - System configuration (key-value pairs)
|
|
||||||
|
|
||||||
### Redis Data Structure
|
|
||||||
- `driver:{id}:location` - Real-time driver locations
|
|
||||||
- `event:{id}:status` - Live event status updates
|
|
||||||
- `flight:{key}` - Cached flight API responses
|
|
||||||
|
|
||||||
## Key Features Implemented
|
|
||||||
|
|
||||||
### 1. Database Configuration
|
|
||||||
- **PostgreSQL connection pool** (`backend/src/config/database.ts`)
|
|
||||||
- **Redis client setup** (`backend/src/config/redis.ts`)
|
|
||||||
- **Database schema** (`backend/src/config/schema.sql`)
|
|
||||||
|
|
||||||
### 2. Data Services
|
|
||||||
- **DatabaseService** (`backend/src/services/databaseService.ts`)
|
|
||||||
- Database initialization and migration
|
|
||||||
- Redis operations for real-time data
|
|
||||||
- Automatic JSON data migration
|
|
||||||
- **EnhancedDataService** (`backend/src/services/enhancedDataService.ts`)
|
|
||||||
- PostgreSQL CRUD operations
|
|
||||||
- Complex queries with joins
|
|
||||||
- Transaction support
|
|
||||||
|
|
||||||
### 3. Migration Features
|
|
||||||
- **Automatic migration** from existing JSON data
|
|
||||||
- **Backup creation** of original JSON file
|
|
||||||
- **Zero-downtime migration** process
|
|
||||||
- **Data validation** during migration
|
|
||||||
|
|
||||||
### 4. Real-time Capabilities
|
|
||||||
- **Driver location tracking** in Redis
|
|
||||||
- **Event status updates** with timestamps
|
|
||||||
- **Flight data caching** with TTL
|
|
||||||
- **Performance optimization** through caching
|
|
||||||
|
|
||||||
## Data Flow
|
|
||||||
|
|
||||||
### VIP Management
|
|
||||||
```
|
|
||||||
Frontend → API → EnhancedDataService → PostgreSQL
|
|
||||||
→ Redis (for real-time data)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Driver Location Updates
|
|
||||||
```
|
|
||||||
Frontend → API → DatabaseService → Redis (hSet driver location)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Flight Tracking
|
|
||||||
```
|
|
||||||
Flight API → FlightService → Redis (cache) → Database (if needed)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Benefits Achieved
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
- **Faster queries** with PostgreSQL indexes
|
|
||||||
- **Reduced API calls** through Redis caching
|
|
||||||
- **Concurrent access** without file locking issues
|
|
||||||
|
|
||||||
### Scalability
|
|
||||||
- **Multiple server instances** supported
|
|
||||||
- **Database connection pooling**
|
|
||||||
- **Redis clustering** ready
|
|
||||||
|
|
||||||
### Reliability
|
|
||||||
- **ACID transactions** for data integrity
|
|
||||||
- **Automatic backups** during migration
|
|
||||||
- **Error handling** and rollback support
|
|
||||||
|
|
||||||
### Real-time Features
|
|
||||||
- **Live driver locations** via Redis
|
|
||||||
- **Event status tracking** with timestamps
|
|
||||||
- **Flight data caching** for performance
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
```bash
|
|
||||||
DATABASE_URL=postgresql://postgres:changeme@db:5432/vip_coordinator
|
|
||||||
REDIS_URL=redis://redis:6379
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker Services
|
|
||||||
- **PostgreSQL 15** with persistent volume
|
|
||||||
- **Redis 7** for caching and real-time data
|
|
||||||
- **Backend** with database connections
|
|
||||||
|
|
||||||
## Migration Process
|
|
||||||
|
|
||||||
### Automatic Steps
|
|
||||||
1. **Schema creation** with tables and indexes
|
|
||||||
2. **Data validation** and transformation
|
|
||||||
3. **VIP migration** with flight relationships
|
|
||||||
4. **Driver migration** with location data to Redis
|
|
||||||
5. **Schedule migration** with proper relationships
|
|
||||||
6. **Admin settings** flattened to key-value pairs
|
|
||||||
7. **Backup creation** of original JSON file
|
|
||||||
|
|
||||||
### Manual Steps (if needed)
|
|
||||||
1. Install dependencies: `npm install`
|
|
||||||
2. Start services: `make dev`
|
|
||||||
3. Verify migration in logs
|
|
||||||
|
|
||||||
## API Changes
|
|
||||||
|
|
||||||
### Enhanced Endpoints
|
|
||||||
- All VIP endpoints now use PostgreSQL
|
|
||||||
- Driver location updates go to Redis
|
|
||||||
- Flight data cached in Redis
|
|
||||||
- Schedule operations with proper relationships
|
|
||||||
|
|
||||||
### Backward Compatibility
|
|
||||||
- All existing API endpoints maintained
|
|
||||||
- Same request/response formats
|
|
||||||
- Legacy field support during transition
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
### Database Connection
|
|
||||||
```bash
|
|
||||||
# Health check includes database status
|
|
||||||
curl http://localhost:3000/api/health
|
|
||||||
```
|
|
||||||
|
|
||||||
### Data Verification
|
|
||||||
```bash
|
|
||||||
# Check VIPs migrated correctly
|
|
||||||
curl http://localhost:3000/api/vips
|
|
||||||
|
|
||||||
# Check drivers with locations
|
|
||||||
curl http://localhost:3000/api/drivers
|
|
||||||
```
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
### Immediate
|
|
||||||
1. **Test the migration** with Docker
|
|
||||||
2. **Verify all endpoints** work correctly
|
|
||||||
3. **Check real-time features** function
|
|
||||||
|
|
||||||
### Future Enhancements
|
|
||||||
1. **WebSocket integration** for live updates
|
|
||||||
2. **Advanced Redis patterns** for pub/sub
|
|
||||||
3. **Database optimization** with query analysis
|
|
||||||
4. **Monitoring and metrics** setup
|
|
||||||
|
|
||||||
## Files Created/Modified
|
|
||||||
|
|
||||||
### New Files
|
|
||||||
- `backend/src/config/database.ts` - PostgreSQL configuration
|
|
||||||
- `backend/src/config/redis.ts` - Redis configuration
|
|
||||||
- `backend/src/config/schema.sql` - Database schema
|
|
||||||
- `backend/src/services/databaseService.ts` - Migration and Redis ops
|
|
||||||
- `backend/src/services/enhancedDataService.ts` - PostgreSQL operations
|
|
||||||
|
|
||||||
### Modified Files
|
|
||||||
- `backend/package.json` - Added pg, redis, uuid dependencies
|
|
||||||
- `backend/src/index.ts` - Updated to use new services
|
|
||||||
- `docker-compose.dev.yml` - Already configured for databases
|
|
||||||
|
|
||||||
## Redis Usage Patterns
|
|
||||||
|
|
||||||
### Driver Locations
|
|
||||||
```typescript
|
|
||||||
// Update location
|
|
||||||
await databaseService.updateDriverLocation(driverId, { lat: 39.7392, lng: -104.9903 });
|
|
||||||
|
|
||||||
// Get location
|
|
||||||
const location = await databaseService.getDriverLocation(driverId);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Event Status
|
|
||||||
```typescript
|
|
||||||
// Set status
|
|
||||||
await databaseService.setEventStatus(eventId, 'in-progress');
|
|
||||||
|
|
||||||
// Get status
|
|
||||||
const status = await databaseService.getEventStatus(eventId);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Flight Caching
|
|
||||||
```typescript
|
|
||||||
// Cache flight data
|
|
||||||
await databaseService.cacheFlightData(flightKey, flightData, 300);
|
|
||||||
|
|
||||||
// Get cached data
|
|
||||||
const cached = await databaseService.getCachedFlightData(flightKey);
|
|
||||||
```
|
|
||||||
|
|
||||||
This migration provides a solid foundation for scaling the VIP Coordinator application with proper data persistence, real-time capabilities, and performance optimization.
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
# 🚀 Docker Hub Deployment Plan for VIP Coordinator
|
|
||||||
|
|
||||||
## 📋 Overview
|
|
||||||
This document outlines the complete plan to prepare the VIP Coordinator project for Docker Hub deployment, ensuring it's secure, portable, and easy to deploy.
|
|
||||||
|
|
||||||
## 🔍 Security Issues Identified & Resolved
|
|
||||||
|
|
||||||
### ✅ Environment Configuration
|
|
||||||
- **FIXED**: Removed hardcoded sensitive data from environment files
|
|
||||||
- **FIXED**: Created single `.env.example` template for all deployments
|
|
||||||
- **FIXED**: Removed redundant environment files (`.env.production`, `backend/.env`)
|
|
||||||
- **FIXED**: Updated `.gitignore` to exclude sensitive files
|
|
||||||
- **FIXED**: Removed unused JWT_SECRET and SESSION_SECRET (auto-managed by jwtKeyManager)
|
|
||||||
|
|
||||||
### ✅ Authentication System
|
|
||||||
- **SECURE**: JWT keys are automatically generated and rotated every 24 hours
|
|
||||||
- **SECURE**: No hardcoded authentication secrets in codebase
|
|
||||||
- **SECURE**: Google OAuth credentials must be provided by user
|
|
||||||
|
|
||||||
## 🛠️ Remaining Tasks for Docker Hub Readiness
|
|
||||||
|
|
||||||
### 1. Fix Docker Configuration Issues
|
|
||||||
|
|
||||||
#### Backend Dockerfile Issues:
|
|
||||||
- Production stage runs `npm run dev` instead of production build
|
|
||||||
- Missing proper multi-stage optimization
|
|
||||||
- No health checks
|
|
||||||
|
|
||||||
#### Frontend Dockerfile Issues:
|
|
||||||
- Need to verify production build configuration
|
|
||||||
- Ensure proper Nginx setup for production
|
|
||||||
|
|
||||||
### 2. Create Docker Hub Deployment Documentation
|
|
||||||
|
|
||||||
#### Required Files:
|
|
||||||
- [ ] `DEPLOYMENT.md` - Complete deployment guide
|
|
||||||
- [ ] `docker-compose.yml` - Single production-ready compose file
|
|
||||||
- [ ] Update `README.md` with Docker Hub instructions
|
|
||||||
|
|
||||||
### 3. Security Hardening
|
|
||||||
|
|
||||||
#### Container Security:
|
|
||||||
- [ ] Add health checks to Dockerfiles
|
|
||||||
- [ ] Use non-root users in containers
|
|
||||||
- [ ] Minimize container attack surface
|
|
||||||
- [ ] Add security scanning
|
|
||||||
|
|
||||||
#### Environment Security:
|
|
||||||
- [ ] Validate all environment variables are properly templated
|
|
||||||
- [ ] Ensure no test data contains sensitive information
|
|
||||||
- [ ] Add environment validation on startup
|
|
||||||
|
|
||||||
### 4. Portability Improvements
|
|
||||||
|
|
||||||
#### Configuration:
|
|
||||||
- [ ] Make all hardcoded URLs configurable
|
|
||||||
- [ ] Ensure database initialization works in any environment
|
|
||||||
- [ ] Add proper error handling for missing configuration
|
|
||||||
|
|
||||||
#### Documentation:
|
|
||||||
- [ ] Create quick-start guide for Docker Hub users
|
|
||||||
- [ ] Add troubleshooting section
|
|
||||||
- [ ] Include example configurations
|
|
||||||
|
|
||||||
## 📁 Current File Structure (Clean)
|
|
||||||
|
|
||||||
```
|
|
||||||
vip-coordinator/
|
|
||||||
├── .env.example # ✅ Single environment template
|
|
||||||
├── .gitignore # ✅ Excludes sensitive files
|
|
||||||
├── docker-compose.prod.yml # Production compose file
|
|
||||||
├── backend/
|
|
||||||
│ ├── Dockerfile # ⚠️ Needs production fixes
|
|
||||||
│ └── src/ # ✅ Clean source code
|
|
||||||
├── frontend/
|
|
||||||
│ ├── Dockerfile # ⚠️ Needs verification
|
|
||||||
│ └── src/ # ✅ Clean source code
|
|
||||||
└── README.md # ⚠️ Needs Docker Hub instructions
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 Next Steps Priority
|
|
||||||
|
|
||||||
### High Priority (Required for Docker Hub)
|
|
||||||
1. **Fix Backend Dockerfile** - Production build configuration
|
|
||||||
2. **Fix Frontend Dockerfile** - Verify production setup
|
|
||||||
3. **Create DEPLOYMENT.md** - Complete user guide
|
|
||||||
4. **Update README.md** - Add Docker Hub quick start
|
|
||||||
|
|
||||||
### Medium Priority (Security & Polish)
|
|
||||||
5. **Add Health Checks** - Container monitoring
|
|
||||||
6. **Security Hardening** - Non-root users, scanning
|
|
||||||
7. **Environment Validation** - Startup checks
|
|
||||||
|
|
||||||
### Low Priority (Nice to Have)
|
|
||||||
8. **Advanced Documentation** - Troubleshooting, examples
|
|
||||||
9. **CI/CD Integration** - Automated builds
|
|
||||||
10. **Monitoring Setup** - Logging, metrics
|
|
||||||
|
|
||||||
## 🔧 Implementation Plan
|
|
||||||
|
|
||||||
### Phase 1: Core Fixes (Required)
|
|
||||||
- Fix Dockerfile production configurations
|
|
||||||
- Create deployment documentation
|
|
||||||
- Test complete deployment flow
|
|
||||||
|
|
||||||
### Phase 2: Security & Polish
|
|
||||||
- Add container security measures
|
|
||||||
- Implement health checks
|
|
||||||
- Add environment validation
|
|
||||||
|
|
||||||
### Phase 3: Documentation & Examples
|
|
||||||
- Create comprehensive guides
|
|
||||||
- Add example configurations
|
|
||||||
- Include troubleshooting help
|
|
||||||
|
|
||||||
## ✅ Completed Tasks
|
|
||||||
- [x] Created `.env.example` template
|
|
||||||
- [x] Removed sensitive data from environment files
|
|
||||||
- [x] Updated `.gitignore` for security
|
|
||||||
- [x] Cleaned up redundant environment files
|
|
||||||
- [x] Updated SETUP_GUIDE.md references
|
|
||||||
- [x] Verified JWT/Session secret removal
|
|
||||||
|
|
||||||
## 🚨 Critical Notes
|
|
||||||
- **AviationStack API Key**: Can be configured via admin interface, not required in environment
|
|
||||||
- **Google OAuth**: Must be configured by user for authentication to work
|
|
||||||
- **Database Password**: Must be changed from default for production
|
|
||||||
- **Admin Password**: Must be changed from default for security
|
|
||||||
|
|
||||||
This plan ensures the VIP Coordinator will be secure, portable, and ready for Docker Hub deployment.
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
# 🚀 VIP Coordinator - Docker Hub Ready Summary
|
|
||||||
|
|
||||||
## ✅ Completed Tasks
|
|
||||||
|
|
||||||
### 🔐 Security Hardening
|
|
||||||
- [x] **Removed all hardcoded sensitive data** from source code
|
|
||||||
- [x] **Created secure environment template** (`.env.example`)
|
|
||||||
- [x] **Removed redundant environment files** (`.env.production`, `backend/.env`)
|
|
||||||
- [x] **Updated .gitignore** to exclude sensitive files
|
|
||||||
- [x] **Cleaned hardcoded domains** from source code
|
|
||||||
- [x] **Secured admin password fallbacks** in source code
|
|
||||||
- [x] **Removed unused JWT/Session secrets** (auto-managed by jwtKeyManager)
|
|
||||||
|
|
||||||
### 🐳 Docker Configuration
|
|
||||||
- [x] **Fixed Backend Dockerfile** - Proper production build with TypeScript compilation
|
|
||||||
- [x] **Fixed Frontend Dockerfile** - Multi-stage build with Nginx serving
|
|
||||||
- [x] **Updated docker-compose.prod.yml** - Removed sensitive defaults, added health checks
|
|
||||||
- [x] **Added .dockerignore** - Optimized build context
|
|
||||||
- [x] **Added health checks** - Container monitoring for all services
|
|
||||||
- [x] **Implemented non-root users** - Enhanced container security
|
|
||||||
|
|
||||||
### 📚 Documentation
|
|
||||||
- [x] **Created DEPLOYMENT.md** - Comprehensive Docker Hub deployment guide
|
|
||||||
- [x] **Updated README.md** - Added Docker Hub quick start section
|
|
||||||
- [x] **Updated SETUP_GUIDE.md** - Fixed environment file references
|
|
||||||
- [x] **Created deployment plan** - Complete roadmap document
|
|
||||||
|
|
||||||
## 🏗️ Architecture Improvements
|
|
||||||
|
|
||||||
### Security Features
|
|
||||||
- **JWT Auto-Rotation**: Keys automatically rotate every 24 hours
|
|
||||||
- **Non-Root Containers**: All services run as non-privileged users
|
|
||||||
- **Health Monitoring**: Built-in health checks for all services
|
|
||||||
- **Secure Headers**: Nginx configured with security headers
|
|
||||||
- **Environment Isolation**: Clean separation of dev/prod configurations
|
|
||||||
|
|
||||||
### Production Optimizations
|
|
||||||
- **Multi-Stage Builds**: Optimized Docker images
|
|
||||||
- **Static Asset Serving**: Nginx serves React build with caching
|
|
||||||
- **Database Health Checks**: PostgreSQL monitoring
|
|
||||||
- **Redis Health Checks**: Cache service monitoring
|
|
||||||
- **Dependency Optimization**: Production-only dependencies in final images
|
|
||||||
|
|
||||||
## 📁 Clean File Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
vip-coordinator/
|
|
||||||
├── .env.example # ✅ Single environment template
|
|
||||||
├── .gitignore # ✅ Excludes sensitive files
|
|
||||||
├── .dockerignore # ✅ Optimizes Docker builds
|
|
||||||
├── docker-compose.prod.yml # ✅ Production-ready compose
|
|
||||||
├── DEPLOYMENT.md # ✅ Docker Hub deployment guide
|
|
||||||
├── backend/
|
|
||||||
│ ├── Dockerfile # ✅ Production-optimized
|
|
||||||
│ └── src/ # ✅ Clean source code
|
|
||||||
├── frontend/
|
|
||||||
│ ├── Dockerfile # ✅ Nginx + React build
|
|
||||||
│ ├── nginx.conf # ✅ Production web server
|
|
||||||
│ └── src/ # ✅ Clean source code
|
|
||||||
└── README.md # ✅ Updated with Docker Hub info
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Environment Configuration
|
|
||||||
|
|
||||||
### Required Variables (All must be set by user)
|
|
||||||
- `DB_PASSWORD` - Secure database password
|
|
||||||
- `DOMAIN` - User's domain
|
|
||||||
- `VITE_API_URL` - API endpoint URL
|
|
||||||
- `GOOGLE_CLIENT_ID` - Google OAuth client ID
|
|
||||||
- `GOOGLE_CLIENT_SECRET` - Google OAuth client secret
|
|
||||||
- `GOOGLE_REDIRECT_URI` - OAuth redirect URI
|
|
||||||
- `FRONTEND_URL` - Frontend URL
|
|
||||||
- `ADMIN_PASSWORD` - Admin panel password
|
|
||||||
|
|
||||||
### Removed Variables (No longer needed)
|
|
||||||
- ❌ `JWT_SECRET` - Auto-generated and rotated
|
|
||||||
- ❌ `SESSION_SECRET` - Not used in current implementation
|
|
||||||
- ❌ `AVIATIONSTACK_API_KEY` - Configurable via admin interface
|
|
||||||
|
|
||||||
## 🚀 Deployment Process
|
|
||||||
|
|
||||||
### For Docker Hub Users
|
|
||||||
1. **Download**: `git clone <repo-url>`
|
|
||||||
2. **Configure**: `cp .env.example .env.prod` and edit
|
|
||||||
3. **Deploy**: `docker-compose -f docker-compose.prod.yml up -d`
|
|
||||||
4. **Setup OAuth**: Configure Google Cloud Console
|
|
||||||
5. **Access**: Visit frontend URL and login
|
|
||||||
|
|
||||||
### Services Available
|
|
||||||
- **Frontend**: Port 80 (Nginx serving React build)
|
|
||||||
- **Backend**: Port 3000 (Node.js API)
|
|
||||||
- **Database**: PostgreSQL with auto-schema setup
|
|
||||||
- **Redis**: Caching and real-time features
|
|
||||||
|
|
||||||
## 🔍 Security Verification
|
|
||||||
|
|
||||||
### ✅ No Sensitive Data in Source
|
|
||||||
- No hardcoded passwords
|
|
||||||
- No API keys in code
|
|
||||||
- No real domain names
|
|
||||||
- No OAuth credentials
|
|
||||||
- No database passwords
|
|
||||||
|
|
||||||
### ✅ Secure Defaults
|
|
||||||
- Strong password requirements
|
|
||||||
- Environment variable validation
|
|
||||||
- Non-root container users
|
|
||||||
- Health check monitoring
|
|
||||||
- Secure HTTP headers
|
|
||||||
|
|
||||||
## 📋 Pre-Deployment Checklist
|
|
||||||
|
|
||||||
### Required by User
|
|
||||||
- [ ] Set secure `DB_PASSWORD`
|
|
||||||
- [ ] Configure own domain names
|
|
||||||
- [ ] Create Google OAuth credentials
|
|
||||||
- [ ] Set secure `ADMIN_PASSWORD`
|
|
||||||
- [ ] Configure SSL/TLS certificates (production)
|
|
||||||
|
|
||||||
### Automatic
|
|
||||||
- [x] JWT key generation and rotation
|
|
||||||
- [x] Database schema initialization
|
|
||||||
- [x] Container health monitoring
|
|
||||||
- [x] Security headers configuration
|
|
||||||
- [x] Static asset optimization
|
|
||||||
|
|
||||||
## 🎯 Ready for Docker Hub
|
|
||||||
|
|
||||||
The VIP Coordinator project is now **fully prepared for Docker Hub deployment** with:
|
|
||||||
|
|
||||||
- ✅ **Security**: No sensitive data exposed
|
|
||||||
- ✅ **Portability**: Works in any environment with proper configuration
|
|
||||||
- ✅ **Documentation**: Complete deployment guides
|
|
||||||
- ✅ **Optimization**: Production-ready Docker configurations
|
|
||||||
- ✅ **Monitoring**: Health checks and logging
|
|
||||||
- ✅ **Usability**: Simple setup process for end users
|
|
||||||
|
|
||||||
## 🚨 Important Notes
|
|
||||||
|
|
||||||
1. **User Responsibility**: Users must provide their own OAuth credentials and secure passwords
|
|
||||||
2. **Domain Configuration**: All domain references must be updated by the user
|
|
||||||
3. **SSL/HTTPS**: Required for production deployments
|
|
||||||
4. **Database Security**: Default passwords must be changed
|
|
||||||
5. **Regular Updates**: Keep Docker images and dependencies updated
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Status**: ✅ **READY FOR DOCKER HUB DEPLOYMENT**
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
# VIP Coordinator - Docker Hub Deployment Summary
|
|
||||||
|
|
||||||
## 🎉 Successfully Deployed to Docker Hub!
|
|
||||||
|
|
||||||
The VIP Coordinator application has been successfully built and deployed to Docker Hub at:
|
|
||||||
|
|
||||||
- **Backend Image**: `t72chevy/vip-coordinator:backend-latest`
|
|
||||||
- **Frontend Image**: `t72chevy/vip-coordinator:frontend-latest`
|
|
||||||
|
|
||||||
## 📦 What's Included
|
|
||||||
|
|
||||||
### Docker Images
|
|
||||||
- **Backend**: Node.js/Express API with TypeScript, JWT auto-rotation, Google OAuth
|
|
||||||
- **Frontend**: React application with Vite build, served by Nginx
|
|
||||||
- **Size**: Backend ~404MB, Frontend ~75MB (optimized for production)
|
|
||||||
|
|
||||||
### Deployment Files
|
|
||||||
- `README.md` - Comprehensive documentation
|
|
||||||
- `docker-compose.yml` - Production-ready orchestration
|
|
||||||
- `.env.example` - Environment configuration template
|
|
||||||
- `deploy.sh` - Automated deployment script
|
|
||||||
|
|
||||||
## 🚀 Quick Start for Users
|
|
||||||
|
|
||||||
Users can now deploy the VIP Coordinator with just a few commands:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Download deployment files
|
|
||||||
curl -O https://raw.githubusercontent.com/your-repo/vip-coordinator/main/docker-compose.yml
|
|
||||||
curl -O https://raw.githubusercontent.com/your-repo/vip-coordinator/main/.env.example
|
|
||||||
curl -O https://raw.githubusercontent.com/your-repo/vip-coordinator/main/deploy.sh
|
|
||||||
|
|
||||||
# Make deploy script executable
|
|
||||||
chmod +x deploy.sh
|
|
||||||
|
|
||||||
# Copy and configure environment
|
|
||||||
cp .env.example .env
|
|
||||||
# Edit .env with your configuration
|
|
||||||
|
|
||||||
# Deploy the application
|
|
||||||
./deploy.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Key Features Deployed
|
|
||||||
|
|
||||||
### Security Features
|
|
||||||
- ✅ JWT auto-rotation system
|
|
||||||
- ✅ Google OAuth integration
|
|
||||||
- ✅ Non-root container users
|
|
||||||
- ✅ Input validation and sanitization
|
|
||||||
- ✅ Secure environment variable handling
|
|
||||||
|
|
||||||
### Production Features
|
|
||||||
- ✅ Multi-stage Docker builds
|
|
||||||
- ✅ Health checks for all services
|
|
||||||
- ✅ Automatic restart policies
|
|
||||||
- ✅ Optimized image sizes
|
|
||||||
- ✅ Comprehensive logging
|
|
||||||
|
|
||||||
### Application Features
|
|
||||||
- ✅ Real-time VIP scheduling
|
|
||||||
- ✅ Driver management system
|
|
||||||
- ✅ Role-based access control
|
|
||||||
- ✅ Responsive web interface
|
|
||||||
- ✅ Data export capabilities
|
|
||||||
|
|
||||||
## 🏗️ Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────┐ ┌─────────────────┐
|
|
||||||
│ Frontend │ │ Backend │
|
|
||||||
│ (Nginx) │◄──►│ (Node.js) │
|
|
||||||
│ Port: 80 │ │ Port: 3001 │
|
|
||||||
└─────────────────┘ └─────────────────┘
|
|
||||||
│ │
|
|
||||||
└───────────┬───────────┘
|
|
||||||
│
|
|
||||||
┌─────────────────┐ ┌─────────────────┐
|
|
||||||
│ PostgreSQL │ │ Redis │
|
|
||||||
│ Port: 5432 │ │ Port: 6379 │
|
|
||||||
└─────────────────┘ └─────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📊 Image Details
|
|
||||||
|
|
||||||
### Backend Image (`t72chevy/vip-coordinator:backend-latest`)
|
|
||||||
- **Base**: Node.js 22 Alpine
|
|
||||||
- **Size**: ~404MB
|
|
||||||
- **Features**: TypeScript compilation, production dependencies only
|
|
||||||
- **Security**: Non-root user (nodejs:1001)
|
|
||||||
- **Health Check**: `/health` endpoint
|
|
||||||
|
|
||||||
### Frontend Image (`t72chevy/vip-coordinator:frontend-latest`)
|
|
||||||
- **Base**: Nginx Alpine
|
|
||||||
- **Size**: ~75MB
|
|
||||||
- **Features**: Optimized React build, custom nginx config
|
|
||||||
- **Security**: Non-root user (appuser:1001)
|
|
||||||
- **Health Check**: HTTP response check
|
|
||||||
|
|
||||||
## 🔍 Verification
|
|
||||||
|
|
||||||
Both images have been tested and verified:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
✅ Backend build: Successful
|
|
||||||
✅ Frontend build: Successful
|
|
||||||
✅ Docker Hub push: Successful
|
|
||||||
✅ Image pull test: Successful
|
|
||||||
✅ Health checks: Working
|
|
||||||
✅ Production deployment: Tested
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🌐 Access Points
|
|
||||||
|
|
||||||
Once deployed, users can access:
|
|
||||||
|
|
||||||
- **Frontend Application**: `http://localhost` (or your domain)
|
|
||||||
- **Backend API**: `http://localhost:3000`
|
|
||||||
- **Health Check**: `http://localhost:3000/health`
|
|
||||||
- **API Documentation**: Available via backend endpoints
|
|
||||||
|
|
||||||
## 📋 Environment Requirements
|
|
||||||
|
|
||||||
### Required Configuration
|
|
||||||
- Google OAuth credentials (Client ID & Secret)
|
|
||||||
- Secure PostgreSQL password
|
|
||||||
- Domain configuration for production
|
|
||||||
|
|
||||||
### Optional Configuration
|
|
||||||
- Custom JWT secret (auto-generates if not provided)
|
|
||||||
- Redis configuration (defaults provided)
|
|
||||||
- Custom ports and URLs
|
|
||||||
|
|
||||||
## 🆘 Support & Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
1. **Google OAuth Setup**: Ensure proper callback URLs
|
|
||||||
2. **Database Connection**: Check password special characters
|
|
||||||
3. **Port Conflicts**: Ensure ports 80 and 3000 are available
|
|
||||||
4. **Health Checks**: Allow time for services to start
|
|
||||||
|
|
||||||
### Getting Help
|
|
||||||
- Check the comprehensive README.md
|
|
||||||
- Review Docker Compose logs
|
|
||||||
- Verify environment configuration
|
|
||||||
- Ensure all required variables are set
|
|
||||||
|
|
||||||
## 🔄 Updates
|
|
||||||
|
|
||||||
To update to newer versions:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose pull
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📈 Production Considerations
|
|
||||||
|
|
||||||
For production deployment:
|
|
||||||
- Use HTTPS with SSL certificates
|
|
||||||
- Implement proper backup strategies
|
|
||||||
- Set up monitoring and alerting
|
|
||||||
- Use strong, unique passwords
|
|
||||||
- Consider load balancing for high availability
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**🎯 Mission Accomplished!**
|
|
||||||
|
|
||||||
The VIP Coordinator is now available on Docker Hub and ready for deployment by users worldwide. The application provides enterprise-grade VIP transportation coordination with modern security practices and scalable architecture.
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
# VIP Coordinator Documentation Cleanup - COMPLETED ✅
|
|
||||||
|
|
||||||
## 🎉 Complete Documentation Cleanup Successfully Finished
|
|
||||||
|
|
||||||
The VIP Coordinator project has been **completely cleaned up and modernized**. We've streamlined from **30+ files** down to **10 essential files**, removing all outdated documentation and redundant scripts.
|
|
||||||
|
|
||||||
## 📊 Final Results
|
|
||||||
|
|
||||||
### Before Cleanup (30+ files)
|
|
||||||
- **9 OAuth setup guides** - Multiple confusing, outdated approaches
|
|
||||||
- **8 Test data scripts** - External scripts for data population
|
|
||||||
- **3 One-time utility scripts** - API testing and migration scripts
|
|
||||||
- **8 Redundant documentation** - User management, troubleshooting, RBAC docs
|
|
||||||
- **2 Database migration docs** - Completed migration summaries
|
|
||||||
- **Scattered information** across many files
|
|
||||||
|
|
||||||
### After Cleanup (10 files)
|
|
||||||
- **1 Setup guide** - Single, comprehensive SETUP_GUIDE.md
|
|
||||||
- **1 Project overview** - Modern README.md with current features
|
|
||||||
- **1 API guide** - Detailed README-API.md
|
|
||||||
- **2 API documentation files** - Interactive Swagger UI and OpenAPI spec
|
|
||||||
- **3 Docker configuration files** - Development and production environments
|
|
||||||
- **1 Development tool** - Makefile for commands
|
|
||||||
- **2 Code directories** - backend/ and frontend/
|
|
||||||
|
|
||||||
## ✅ Total Files Removed: 28 files
|
|
||||||
|
|
||||||
### OAuth Documentation (9 files) ❌ REMOVED
|
|
||||||
- CORRECTED_GOOGLE_OAUTH_SETUP.md
|
|
||||||
- GOOGLE_OAUTH_DOMAIN_SETUP.md
|
|
||||||
- GOOGLE_OAUTH_QUICK_SETUP.md
|
|
||||||
- GOOGLE_OAUTH_SETUP.md
|
|
||||||
- OAUTH_CALLBACK_FIX_SUMMARY.md
|
|
||||||
- OAUTH_FRONTEND_ONLY_SETUP.md
|
|
||||||
- REVERSE_PROXY_OAUTH_SETUP.md
|
|
||||||
- SIMPLE_OAUTH_SETUP.md
|
|
||||||
- WEB_SERVER_PROXY_SETUP.md
|
|
||||||
|
|
||||||
### Test Data Scripts (8 files) ❌ REMOVED
|
|
||||||
*Reason: Built into admin dashboard UI*
|
|
||||||
- populate-events-dynamic.js
|
|
||||||
- populate-events-dynamic.sh
|
|
||||||
- populate-events.js
|
|
||||||
- populate-events.sh
|
|
||||||
- populate-test-data.js
|
|
||||||
- populate-test-data.sh
|
|
||||||
- populate-vips.js
|
|
||||||
- quick-populate-events.sh
|
|
||||||
|
|
||||||
### One-Time Utility Scripts (3 files) ❌ REMOVED
|
|
||||||
*Reason: No longer needed*
|
|
||||||
- test-aviationstack-endpoints.js (hardcoded API key, one-time testing)
|
|
||||||
- test-flight-api.js (redundant with admin dashboard API testing)
|
|
||||||
- update-departments.js (one-time migration script, already run)
|
|
||||||
|
|
||||||
### Redundant Documentation (8 files) ❌ REMOVED
|
|
||||||
- DATABASE_MIGRATION_SUMMARY.md
|
|
||||||
- POSTGRESQL_USER_MANAGEMENT.md
|
|
||||||
- SIMPLE_USER_MANAGEMENT.md
|
|
||||||
- USER_MANAGEMENT_RECOMMENDATIONS.md
|
|
||||||
- DOCKER_TROUBLESHOOTING.md
|
|
||||||
- PERMISSION_ISSUES_FIXED.md
|
|
||||||
- PORT_3000_SETUP_GUIDE.md
|
|
||||||
- ROLE_BASED_ACCESS_CONTROL.md
|
|
||||||
|
|
||||||
## 📚 Essential Files Preserved (10 files)
|
|
||||||
|
|
||||||
### Core Documentation ✅
|
|
||||||
1. **README.md** - Modern project overview with current features
|
|
||||||
2. **SETUP_GUIDE.md** - Comprehensive setup guide with Google OAuth
|
|
||||||
3. **README-API.md** - Detailed API documentation and examples
|
|
||||||
|
|
||||||
### API Documentation ✅
|
|
||||||
4. **api-docs.html** - Interactive Swagger UI documentation
|
|
||||||
5. **api-documentation.yaml** - OpenAPI specification
|
|
||||||
|
|
||||||
### Development Configuration ✅
|
|
||||||
6. **Makefile** - Development commands and workflows
|
|
||||||
7. **docker-compose.dev.yml** - Development environment
|
|
||||||
8. **docker-compose.prod.yml** - Production environment
|
|
||||||
|
|
||||||
### Project Structure ✅
|
|
||||||
9. **backend/** - Complete Node.js API server
|
|
||||||
10. **frontend/** - Complete React application
|
|
||||||
|
|
||||||
## 🚀 Key Improvements Achieved
|
|
||||||
|
|
||||||
### 1. **Simplified Setup Process**
|
|
||||||
- **Before**: 9+ OAuth guides with conflicting instructions
|
|
||||||
- **After**: Single SETUP_GUIDE.md with clear, step-by-step Google OAuth setup
|
|
||||||
|
|
||||||
### 2. **Modernized Test Data Management**
|
|
||||||
- **Before**: 8 external scripts requiring manual execution
|
|
||||||
- **After**: Built-in admin dashboard with one-click test data creation/removal
|
|
||||||
|
|
||||||
### 3. **Streamlined Documentation Maintenance**
|
|
||||||
- **Before**: 28+ files to keep updated
|
|
||||||
- **After**: 3 core documentation files (90% reduction in maintenance)
|
|
||||||
|
|
||||||
### 4. **Accurate System Representation**
|
|
||||||
- **Before**: Outdated documentation scattered across many files
|
|
||||||
- **After**: Current documentation reflecting JWT + Google OAuth architecture
|
|
||||||
|
|
||||||
### 5. **Clean Project Structure**
|
|
||||||
- **Before**: Root directory cluttered with 30+ files
|
|
||||||
- **After**: Clean, organized structure with only essential files
|
|
||||||
|
|
||||||
## 🎯 Current System (Properly Documented)
|
|
||||||
|
|
||||||
### Authentication System ✅
|
|
||||||
- **JWT-based authentication** with Google OAuth
|
|
||||||
- **Role-based access control**: Administrator, Coordinator, Driver
|
|
||||||
- **User approval system** for new registrations
|
|
||||||
- **Simple setup** documented in SETUP_GUIDE.md
|
|
||||||
|
|
||||||
### Test Data Management ✅
|
|
||||||
- **Built-in admin dashboard** for test data creation
|
|
||||||
- **One-click VIP generation** (20 diverse test VIPs with full schedules)
|
|
||||||
- **Easy cleanup** - remove all test data with one click
|
|
||||||
- **No external scripts needed**
|
|
||||||
|
|
||||||
### API Documentation ✅
|
|
||||||
- **Interactive Swagger UI** at `/api-docs.html`
|
|
||||||
- **"Try it out" functionality** for testing endpoints
|
|
||||||
- **Comprehensive API guide** in README-API.md
|
|
||||||
|
|
||||||
### Development Workflow ✅
|
|
||||||
- **Single command setup**: `make dev`
|
|
||||||
- **Docker-based development** with automatic database initialization
|
|
||||||
- **Clear troubleshooting** in SETUP_GUIDE.md
|
|
||||||
|
|
||||||
## 📋 Developer Experience
|
|
||||||
|
|
||||||
### New Developer Onboarding
|
|
||||||
1. **Clone repository**
|
|
||||||
2. **Follow SETUP_GUIDE.md** (single source of truth)
|
|
||||||
3. **Run `make dev`** (starts everything)
|
|
||||||
4. **Configure Google OAuth** (clear instructions)
|
|
||||||
5. **Use admin dashboard** for test data (no scripts)
|
|
||||||
6. **Access API docs** at localhost:3000/api-docs.html
|
|
||||||
|
|
||||||
### Documentation Maintenance
|
|
||||||
- **3 files to maintain** (vs. 28+ before)
|
|
||||||
- **No redundant information**
|
|
||||||
- **Clear ownership** of each documentation area
|
|
||||||
|
|
||||||
## 🎉 Success Metrics
|
|
||||||
|
|
||||||
- ✅ **28 files removed** (74% reduction)
|
|
||||||
- ✅ **All essential functionality preserved**
|
|
||||||
- ✅ **Test data management modernized**
|
|
||||||
- ✅ **Single, clear setup path established**
|
|
||||||
- ✅ **Documentation reflects current architecture**
|
|
||||||
- ✅ **Dramatically improved developer experience**
|
|
||||||
- ✅ **Massive reduction in maintenance burden**
|
|
||||||
|
|
||||||
## 🔮 Future Maintenance
|
|
||||||
|
|
||||||
### What to Keep Updated
|
|
||||||
1. **README.md** - Project overview and features
|
|
||||||
2. **SETUP_GUIDE.md** - Setup instructions and troubleshooting
|
|
||||||
3. **README-API.md** - API documentation and examples
|
|
||||||
|
|
||||||
### What's Self-Maintaining
|
|
||||||
- **api-docs.html** - Generated from OpenAPI spec
|
|
||||||
- **Test data** - Built into admin dashboard
|
|
||||||
- **OAuth setup** - Simplified to basic Google OAuth
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**The VIP Coordinator project now has clean, current, and maintainable documentation that accurately reflects the modern system architecture!** 🚀
|
|
||||||
|
|
||||||
**Total Impact**: From 30+ files to 10 essential files (74% reduction) while significantly improving functionality and developer experience.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
FROM mcr.microsoft.com/playwright:v1.41.0-jammy
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy E2E test files
|
|
||||||
COPY ./e2e/package*.json ./e2e/
|
|
||||||
RUN cd e2e && npm ci
|
|
||||||
|
|
||||||
COPY ./e2e ./e2e
|
|
||||||
|
|
||||||
# Install Playwright browsers
|
|
||||||
RUN cd e2e && npx playwright install
|
|
||||||
|
|
||||||
# Set up non-root user
|
|
||||||
RUN useradd -m -u 1001 testuser && \
|
|
||||||
chown -R testuser:testuser /app
|
|
||||||
|
|
||||||
USER testuser
|
|
||||||
|
|
||||||
WORKDIR /app/e2e
|
|
||||||
|
|
||||||
# Default command runs tests
|
|
||||||
CMD ["npx", "playwright", "test"]
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
# Google OAuth2 Domain Setup for bsa.madeamess.online
|
|
||||||
|
|
||||||
## 🔧 Current Configuration
|
|
||||||
|
|
||||||
Your VIP Coordinator is now configured for your domain:
|
|
||||||
- **Backend URL**: `https://bsa.madeamess.online:3000`
|
|
||||||
- **Frontend URL**: `https://bsa.madeamess.online:5173`
|
|
||||||
- **OAuth Redirect URI**: `https://bsa.madeamess.online:3000/auth/google/callback`
|
|
||||||
|
|
||||||
## 📋 Google Cloud Console Setup
|
|
||||||
|
|
||||||
You need to update your Google Cloud Console OAuth2 configuration:
|
|
||||||
|
|
||||||
### 1. Go to Google Cloud Console
|
|
||||||
- Visit: https://console.cloud.google.com/
|
|
||||||
- Select your project (or create one)
|
|
||||||
|
|
||||||
### 2. Enable APIs
|
|
||||||
- Go to "APIs & Services" → "Library"
|
|
||||||
- Enable "Google+ API" (or "People API")
|
|
||||||
|
|
||||||
### 3. Configure OAuth2 Credentials
|
|
||||||
- Go to "APIs & Services" → "Credentials"
|
|
||||||
- Find your OAuth 2.0 Client ID: `308004695553-6k34bbq22frc4e76kejnkgq8mncepbbg.apps.googleusercontent.com`
|
|
||||||
- Click "Edit" (pencil icon)
|
|
||||||
|
|
||||||
### 4. Update Authorized Redirect URIs
|
|
||||||
Add these exact URIs (case-sensitive):
|
|
||||||
```
|
|
||||||
https://bsa.madeamess.online:3000/auth/google/callback
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Update Authorized JavaScript Origins (if needed)
|
|
||||||
Add these origins:
|
|
||||||
```
|
|
||||||
https://bsa.madeamess.online:3000
|
|
||||||
https://bsa.madeamess.online:5173
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 Testing the OAuth Flow
|
|
||||||
|
|
||||||
Once you've updated Google Cloud Console:
|
|
||||||
|
|
||||||
1. **Visit the OAuth endpoint:**
|
|
||||||
```
|
|
||||||
https://bsa.madeamess.online:3000/auth/google
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Expected flow:**
|
|
||||||
- Redirects to Google login
|
|
||||||
- After login, Google redirects to: `https://bsa.madeamess.online:3000/auth/google/callback`
|
|
||||||
- Backend processes the callback and redirects to: `https://bsa.madeamess.online:5173/auth/callback?token=JWT_TOKEN`
|
|
||||||
|
|
||||||
3. **Check if backend is running:**
|
|
||||||
```bash
|
|
||||||
curl https://bsa.madeamess.online:3000/api/health
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔍 Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues:
|
|
||||||
|
|
||||||
1. **"redirect_uri_mismatch" error:**
|
|
||||||
- Make sure the redirect URI in Google Console exactly matches: `https://bsa.madeamess.online:3000/auth/google/callback`
|
|
||||||
- No trailing slashes
|
|
||||||
- Exact case match
|
|
||||||
- Include the port number `:3000`
|
|
||||||
|
|
||||||
2. **SSL/HTTPS issues:**
|
|
||||||
- Make sure your domain has valid SSL certificates
|
|
||||||
- Google requires HTTPS for production OAuth
|
|
||||||
|
|
||||||
3. **Port access:**
|
|
||||||
- Ensure ports 3000 and 5173 are accessible from the internet
|
|
||||||
- Check firewall settings
|
|
||||||
|
|
||||||
### Debug Commands:
|
|
||||||
```bash
|
|
||||||
# Check if containers are running
|
|
||||||
docker-compose -f docker-compose.dev.yml ps
|
|
||||||
|
|
||||||
# Check backend logs
|
|
||||||
docker-compose -f docker-compose.dev.yml logs backend
|
|
||||||
|
|
||||||
# Test backend health
|
|
||||||
curl https://bsa.madeamess.online:3000/api/health
|
|
||||||
|
|
||||||
# Test auth status
|
|
||||||
curl https://bsa.madeamess.online:3000/auth/status
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📝 Current Environment Variables
|
|
||||||
|
|
||||||
Your `.env` file is configured with:
|
|
||||||
```bash
|
|
||||||
GOOGLE_CLIENT_ID=308004695553-6k34bbq22frc4e76kejnkgq8mncepbbg.apps.googleusercontent.com
|
|
||||||
GOOGLE_CLIENT_SECRET=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
|
|
||||||
GOOGLE_REDIRECT_URI=https://bsa.madeamess.online:3000/auth/google/callback
|
|
||||||
FRONTEND_URL=https://bsa.madeamess.online:5173
|
|
||||||
```
|
|
||||||
|
|
||||||
## ✅ Next Steps
|
|
||||||
|
|
||||||
1. Update Google Cloud Console with the redirect URI above
|
|
||||||
2. Test the OAuth flow by visiting `https://bsa.madeamess.online:3000/auth/google`
|
|
||||||
3. Verify the frontend can handle the callback at `https://bsa.madeamess.online:5173/auth/callback`
|
|
||||||
|
|
||||||
The OAuth2 system should now work correctly with your domain! 🎉
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
# Quick Google OAuth Setup Guide
|
|
||||||
|
|
||||||
## Step 1: Get Your Google OAuth Credentials
|
|
||||||
|
|
||||||
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
|
|
||||||
2. Create a new project or select an existing one
|
|
||||||
3. Enable the Google+ API (or Google Identity API)
|
|
||||||
4. Go to "Credentials" → "Create Credentials" → "OAuth 2.0 Client IDs"
|
|
||||||
5. Set Application type to "Web application"
|
|
||||||
6. Add these Authorized redirect URIs:
|
|
||||||
- `http://localhost:5173/auth/google/callback`
|
|
||||||
- `http://bsa.madeamess.online:5173/auth/google/callback`
|
|
||||||
|
|
||||||
## Step 2: Update Your .env File
|
|
||||||
|
|
||||||
Replace these lines in `/home/kyle/Desktop/vip-coordinator/backend/.env`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# REPLACE THESE TWO LINES:
|
|
||||||
GOOGLE_CLIENT_ID=your-google-client-id-from-console
|
|
||||||
GOOGLE_CLIENT_SECRET=your-google-client-secret-from-console
|
|
||||||
|
|
||||||
# WITH YOUR ACTUAL VALUES:
|
|
||||||
GOOGLE_CLIENT_ID=123456789-abcdefghijklmnop.apps.googleusercontent.com
|
|
||||||
GOOGLE_CLIENT_SECRET=GOCSPX-your_actual_secret_here
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 3: Restart the Backend
|
|
||||||
|
|
||||||
After updating the .env file, restart the backend container:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/kyle/Desktop/vip-coordinator
|
|
||||||
docker-compose -f docker-compose.dev.yml restart backend
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 4: Test the Login
|
|
||||||
|
|
||||||
Visit: http://bsa.madeamess.online:5173 and click "Sign in with Google"
|
|
||||||
(The frontend proxies /auth requests to the backend automatically)
|
|
||||||
|
|
||||||
## Bypass Option (Temporary)
|
|
||||||
|
|
||||||
If you want to skip Google OAuth for now, visit:
|
|
||||||
http://bsa.madeamess.online:5173/admin-bypass
|
|
||||||
|
|
||||||
This will take you directly to the admin dashboard without authentication.
|
|
||||||
(The frontend will proxy this request to the backend)
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
# Google OAuth Setup Guide
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
Your VIP Coordinator now includes Google OAuth authentication! This guide will help you set up Google OAuth credentials so users can log in with their Google accounts.
|
|
||||||
|
|
||||||
## Step 1: Google Cloud Console Setup
|
|
||||||
|
|
||||||
### 1. Go to Google Cloud Console
|
|
||||||
Visit: https://console.cloud.google.com/
|
|
||||||
|
|
||||||
### 2. Create or Select a Project
|
|
||||||
- If you don't have a project, click "Create Project"
|
|
||||||
- Give it a name like "VIP Coordinator"
|
|
||||||
- Select your organization if applicable
|
|
||||||
|
|
||||||
### 3. Enable Google+ API
|
|
||||||
- Go to "APIs & Services" → "Library"
|
|
||||||
- Search for "Google+ API"
|
|
||||||
- Click on it and press "Enable"
|
|
||||||
|
|
||||||
### 4. Create OAuth 2.0 Credentials
|
|
||||||
- Go to "APIs & Services" → "Credentials"
|
|
||||||
- Click "Create Credentials" → "OAuth 2.0 Client IDs"
|
|
||||||
- Choose "Web application" as the application type
|
|
||||||
- Give it a name like "VIP Coordinator Web App"
|
|
||||||
|
|
||||||
### 5. Configure Authorized URLs
|
|
||||||
**Authorized JavaScript origins:**
|
|
||||||
```
|
|
||||||
http://bsa.madeamess.online:5173
|
|
||||||
http://localhost:5173
|
|
||||||
```
|
|
||||||
|
|
||||||
**Authorized redirect URIs:**
|
|
||||||
```
|
|
||||||
http://bsa.madeamess.online:3000/auth/google/callback
|
|
||||||
http://localhost:3000/auth/google/callback
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. Save Your Credentials
|
|
||||||
- Copy the **Client ID** and **Client Secret**
|
|
||||||
- You'll need these for the next step
|
|
||||||
|
|
||||||
## Step 2: Configure VIP Coordinator
|
|
||||||
|
|
||||||
### 1. Access Admin Dashboard
|
|
||||||
- Go to: http://bsa.madeamess.online:5173/admin
|
|
||||||
- Enter the admin password: `admin123`
|
|
||||||
|
|
||||||
### 2. Add Google OAuth Credentials
|
|
||||||
- Scroll to the "Google OAuth Credentials" section
|
|
||||||
- Paste your **Client ID** in the first field
|
|
||||||
- Paste your **Client Secret** in the second field
|
|
||||||
- Click "Save All Settings"
|
|
||||||
|
|
||||||
## Step 3: Test the Setup
|
|
||||||
|
|
||||||
### 1. Access the Application
|
|
||||||
- Go to: http://bsa.madeamess.online:5173
|
|
||||||
- You should see a Google login button
|
|
||||||
|
|
||||||
### 2. First Login (Admin Setup)
|
|
||||||
- The first person to log in will automatically become the administrator
|
|
||||||
- Subsequent users will be assigned the "coordinator" role by default
|
|
||||||
- Drivers will need to register separately
|
|
||||||
|
|
||||||
### 3. User Roles
|
|
||||||
- **Administrator**: Full system access, user management, settings
|
|
||||||
- **Coordinator**: VIP and schedule management, driver assignments
|
|
||||||
- **Driver**: Personal schedule view, location updates
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues:
|
|
||||||
|
|
||||||
1. **"Blocked request" error**
|
|
||||||
- Make sure your domain is added to authorized JavaScript origins
|
|
||||||
- Check that the redirect URI matches exactly
|
|
||||||
|
|
||||||
2. **"OAuth credentials not configured" warning**
|
|
||||||
- Verify you've entered both Client ID and Client Secret
|
|
||||||
- Make sure you clicked "Save All Settings"
|
|
||||||
|
|
||||||
3. **Login button not working**
|
|
||||||
- Check browser console for errors
|
|
||||||
- Verify the backend is running on port 3000
|
|
||||||
|
|
||||||
### Getting Help:
|
|
||||||
- Check the browser console for error messages
|
|
||||||
- Verify all URLs match exactly (including http/https)
|
|
||||||
- Make sure the Google+ API is enabled in your project
|
|
||||||
|
|
||||||
## Security Notes
|
|
||||||
|
|
||||||
- Keep your Client Secret secure and never share it publicly
|
|
||||||
- The credentials are stored securely in your database
|
|
||||||
- Sessions last 24 hours as requested
|
|
||||||
- Only the frontend (port 5173) is exposed externally for security
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
Once Google OAuth is working:
|
|
||||||
1. Test the login flow with different Google accounts
|
|
||||||
2. Assign appropriate roles to users through the admin dashboard
|
|
||||||
3. Create VIPs and schedules to test the full system
|
|
||||||
4. Set up additional API keys (AviationStack, etc.) as needed
|
|
||||||
|
|
||||||
Your VIP Coordinator is now ready for secure, role-based access!
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
# Keycloak Integration Complete! 🎉
|
|
||||||
|
|
||||||
## ✅ What Was Changed
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
1. **Created `backend/src/config/keycloak.ts`** - Keycloak JWT validation configuration
|
|
||||||
2. **Updated `backend/src/routes/auth.ts`** - Replaced Auth0 routes with Keycloak
|
|
||||||
3. **Updated `backend/src/services/userService.ts`** - Uses Keycloak user info API
|
|
||||||
4. **Updated `backend/src/middleware/auth.ts`** - Uses Keycloak config
|
|
||||||
5. **Updated `backend/src/index.ts`** - Uses Keycloak JWT middleware
|
|
||||||
6. **Updated `backend/.env`** - Replaced Auth0 vars with Keycloak vars
|
|
||||||
7. **Updated `docker-compose.dev.yml`** - Added Keycloak service, updated env vars
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
1. **Created `frontend/src/contexts/KeycloakContext.tsx`** - Keycloak React provider
|
|
||||||
2. **Updated `frontend/src/main.tsx`** - Uses KeycloakProvider instead of Auth0Provider
|
|
||||||
3. **Updated `frontend/src/App.tsx`** - Uses useKeycloak hook
|
|
||||||
4. **Updated `frontend/src/components/Login.tsx`** - Uses Keycloak login
|
|
||||||
5. **Updated `frontend/src/pages/PendingApproval.tsx`** - Uses Keycloak token
|
|
||||||
6. **Updated `frontend/src/hooks/useAuthToken.ts`** - Uses Keycloak token
|
|
||||||
7. **Updated `frontend/package.json`** - Replaced @auth0/auth0-react with keycloak-js
|
|
||||||
8. **Updated `frontend/.env`** - Replaced Auth0 vars with Keycloak vars
|
|
||||||
9. **Updated `docker-compose.dev.yml`** - Updated frontend env vars
|
|
||||||
|
|
||||||
## 🔧 Environment Variables
|
|
||||||
|
|
||||||
### Backend (.env)
|
|
||||||
```env
|
|
||||||
KEYCLOAK_SERVER_URL=http://localhost:8080
|
|
||||||
KEYCLOAK_REALM=vip-coordinator
|
|
||||||
KEYCLOAK_CLIENT_ID=vip-coordinator-frontend
|
|
||||||
```
|
|
||||||
|
|
||||||
### Frontend (.env)
|
|
||||||
```env
|
|
||||||
VITE_KEYCLOAK_URL=http://localhost:8080
|
|
||||||
VITE_KEYCLOAK_REALM=vip-coordinator
|
|
||||||
VITE_KEYCLOAK_CLIENT_ID=vip-coordinator-frontend
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 Next Steps
|
|
||||||
|
|
||||||
1. **Rebuild containers:**
|
|
||||||
```bash
|
|
||||||
docker compose -f docker-compose.dev.yml build
|
|
||||||
docker compose -f docker-compose.dev.yml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Install frontend dependencies:**
|
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Test the login flow:**
|
|
||||||
- Go to http://localhost:5173
|
|
||||||
- Click "Sign In with Keycloak"
|
|
||||||
- Login with Keycloak credentials
|
|
||||||
- First user becomes administrator
|
|
||||||
|
|
||||||
## 📝 Notes
|
|
||||||
|
|
||||||
- Database column `auth0_sub` still exists (stores Keycloak `sub` now)
|
|
||||||
- `identity_provider` column set to 'keycloak' for new users
|
|
||||||
- All Auth0 dependencies removed from package.json
|
|
||||||
- Keycloak runs on port 8080
|
|
||||||
- Admin console: http://localhost:8080 (admin/admin)
|
|
||||||
|
|
||||||
## 🐛 Troubleshooting
|
|
||||||
|
|
||||||
If you see errors:
|
|
||||||
1. Make sure Keycloak is running: `docker ps | grep keycloak`
|
|
||||||
2. Check Keycloak logs: `docker logs vip-coordinator-keycloak-1`
|
|
||||||
3. Verify realm and client exist in Keycloak admin console
|
|
||||||
4. Check browser console for frontend errors
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
# Keycloak Authentication Setup
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### 1. Start Services
|
|
||||||
```bash
|
|
||||||
docker compose -f docker-compose.dev.yml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Access Keycloak Admin Console
|
|
||||||
- URL: http://localhost:8080
|
|
||||||
- Username: `admin`
|
|
||||||
- Password: `admin`
|
|
||||||
|
|
||||||
### 3. Create Realm
|
|
||||||
1. Click "Create Realm"
|
|
||||||
2. Name: `vip-coordinator`
|
|
||||||
3. Click "Create"
|
|
||||||
|
|
||||||
### 4. Create Client (for your app)
|
|
||||||
1. Go to **Clients** → **Create client**
|
|
||||||
2. Client ID: `vip-coordinator-frontend`
|
|
||||||
3. Client type: `OpenID Connect`
|
|
||||||
4. Click **Next**
|
|
||||||
5. **Capability config:**
|
|
||||||
- ✅ Client authentication: OFF (public client)
|
|
||||||
- ✅ Authorization: OFF
|
|
||||||
- ✅ Standard flow: ON
|
|
||||||
- ✅ Direct access grants: ON
|
|
||||||
6. Click **Next**
|
|
||||||
7. **Login settings:**
|
|
||||||
- Valid redirect URIs: `http://localhost:5173/*`
|
|
||||||
- Web origins: `http://localhost:5173`
|
|
||||||
- Valid post logout redirect URIs: `http://localhost:5173/*`
|
|
||||||
8. Click **Save**
|
|
||||||
|
|
||||||
### 5. Enable Google Social Login (Optional)
|
|
||||||
1. Go to **Identity providers** → **Add provider** → **Google**
|
|
||||||
2. Client ID: (your Google OAuth client ID)
|
|
||||||
3. Client Secret: (your Google OAuth secret)
|
|
||||||
4. Click **Save**
|
|
||||||
|
|
||||||
### 6. Get Configuration
|
|
||||||
After setup, Keycloak provides:
|
|
||||||
- **Realm URL**: `http://localhost:8080/realms/vip-coordinator`
|
|
||||||
- **Client ID**: `vip-coordinator-frontend`
|
|
||||||
- **Discovery URL**: `http://localhost:8080/realms/vip-coordinator/.well-known/openid-configuration`
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
1. Update backend to use Keycloak JWT validation
|
|
||||||
2. Update frontend to use Keycloak React SDK
|
|
||||||
3. Test login flow
|
|
||||||
|
|
||||||
## Benefits
|
|
||||||
- ✅ Self-hosted in Docker
|
|
||||||
- ✅ No external dependencies
|
|
||||||
- ✅ Full control over users and roles
|
|
||||||
- ✅ Social login support
|
|
||||||
- ✅ JWT tokens
|
|
||||||
- ✅ User management UI
|
|
||||||
- ✅ Role-based access control
|
|
||||||
@@ -1,277 +0,0 @@
|
|||||||
# Navigation UX Improvements
|
|
||||||
|
|
||||||
## Changes Implemented ✅
|
|
||||||
|
|
||||||
### 1. **Nested Admin Navigation**
|
|
||||||
**Problem:** Top navigation was cluttered with both "Users" and "Admin Tools" tabs visible to administrators, taking up unnecessary space.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
- Created a single "Admin" dropdown menu that contains both "Users" and "Admin Tools"
|
|
||||||
- Used Shield icon to represent admin functions (more intuitive than separate icons)
|
|
||||||
- Desktop: Hover/click dropdown with smooth animation
|
|
||||||
- Mobile: Expandable section with chevron indicator
|
|
||||||
- Reduces navigation items by 1 for admins, making the UI cleaner
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- ✅ Cleaner top navigation (reduced clutter by 50% for admin items)
|
|
||||||
- ✅ Hierarchical organization (admin functions grouped together)
|
|
||||||
- ✅ Better visual hierarchy with icon and dropdown indicator
|
|
||||||
- ✅ Follows popular UI patterns (Gmail, GitHub, AWS Console)
|
|
||||||
|
|
||||||
### 2. **Reorganized Navigation by Workflow Priority**
|
|
||||||
**Before:**
|
|
||||||
1. Dashboard
|
|
||||||
2. War Room
|
|
||||||
3. VIPs
|
|
||||||
4. Drivers
|
|
||||||
5. Vehicles
|
|
||||||
6. Schedule
|
|
||||||
7. Flights
|
|
||||||
8. Users (admin)
|
|
||||||
9. Admin Tools (admin)
|
|
||||||
|
|
||||||
**After:**
|
|
||||||
1. Dashboard (always visible - home)
|
|
||||||
2. War Room (high-priority operations)
|
|
||||||
3. VIPs (core resource)
|
|
||||||
4. Drivers (core resource)
|
|
||||||
5. Vehicles (supporting resource)
|
|
||||||
6. Schedule (planning)
|
|
||||||
7. Flights (logistics)
|
|
||||||
8. **Admin** (admin-only dropdown containing Users & Admin Tools)
|
|
||||||
|
|
||||||
**Rationale:**
|
|
||||||
- Most frequently accessed items appear first
|
|
||||||
- Resources organized by importance to operations
|
|
||||||
- Admin functions grouped at the end (less frequently used)
|
|
||||||
|
|
||||||
### 3. **Improved Mobile Navigation**
|
|
||||||
**Desktop (≥ 1024px):**
|
|
||||||
- Horizontal navigation bar with dropdown menu
|
|
||||||
- Click outside to close dropdown
|
|
||||||
- Chevron rotates when opened (visual feedback)
|
|
||||||
|
|
||||||
**Mobile/Tablet (< 1024px):**
|
|
||||||
- Hamburger menu opens drawer from left
|
|
||||||
- Admin section is expandable/collapsible
|
|
||||||
- Nested items are indented for visual hierarchy
|
|
||||||
- All touch targets meet 44px minimum
|
|
||||||
|
|
||||||
## Popular UX Trends Incorporated
|
|
||||||
|
|
||||||
### ✅ **Progressive Disclosure**
|
|
||||||
- Hide complexity by nesting related items
|
|
||||||
- Show only what users need based on their role
|
|
||||||
- Expandable sections reveal more details on demand
|
|
||||||
|
|
||||||
### ✅ **Grouping Related Actions**
|
|
||||||
- Admin functions are logically grouped together
|
|
||||||
- Resources grouped by type (people vs. things vs. logistics)
|
|
||||||
|
|
||||||
### ✅ **Visual Hierarchy with Icons**
|
|
||||||
- Shield icon for admin functions (security/authority)
|
|
||||||
- Chevron indicators for expandable sections
|
|
||||||
- Clear active state highlighting
|
|
||||||
|
|
||||||
### ✅ **Touch-Friendly Design**
|
|
||||||
- All buttons meet 44px minimum touch target
|
|
||||||
- Expandable sections have clear hit areas
|
|
||||||
- Proper spacing between interactive elements
|
|
||||||
|
|
||||||
### ✅ **Consistent Patterns**
|
|
||||||
- Desktop dropdown follows standard web patterns
|
|
||||||
- Mobile drawer follows iOS/Android conventions
|
|
||||||
- Active state highlighting consistent across all nav items
|
|
||||||
|
|
||||||
## Additional UX Recommendations
|
|
||||||
|
|
||||||
### 🎯 **High Priority Improvements**
|
|
||||||
|
|
||||||
#### 1. **Add Breadcrumb Navigation**
|
|
||||||
**Why:** Users can get lost in nested admin pages
|
|
||||||
**How:** Add breadcrumbs below the header showing: Dashboard > Admin > Users
|
|
||||||
**Example:**
|
|
||||||
```
|
|
||||||
Home / Admin / Users
|
|
||||||
```
|
|
||||||
**Benefit:** Faster navigation back to parent pages
|
|
||||||
|
|
||||||
#### 2. **Add Keyboard Shortcuts**
|
|
||||||
**Why:** Power users work faster with keyboard navigation
|
|
||||||
**How:**
|
|
||||||
- `Shift + D` → Dashboard
|
|
||||||
- `Shift + W` → War Room
|
|
||||||
- `Shift + V` → VIPs
|
|
||||||
- `Shift + A` → Admin dropdown toggle
|
|
||||||
- `Esc` → Close dropdown/drawer
|
|
||||||
**Benefit:** 40% faster navigation for frequent users
|
|
||||||
|
|
||||||
#### 3. **Add Search Bar in Navigation**
|
|
||||||
**Why:** Quick access to specific VIPs/drivers/events
|
|
||||||
**How:** Add global search icon (magnifying glass) in top nav
|
|
||||||
**Example:** Slack, GitHub, Linear all have this
|
|
||||||
**Benefit:** Find anything in 1-2 seconds instead of navigating through pages
|
|
||||||
|
|
||||||
#### 4. **Add Notification Badge**
|
|
||||||
**Why:** Admins need to know about pending approvals
|
|
||||||
**How:** Show red badge with count on Admin dropdown when there are pending approvals
|
|
||||||
**Example:** `Admin [3]` where 3 = pending approvals
|
|
||||||
**Benefit:** Proactive alerts, faster response time
|
|
||||||
|
|
||||||
#### 5. **Add Recently Viewed**
|
|
||||||
**Why:** Users often switch between same 2-3 pages
|
|
||||||
**How:** Add "Recent" section at bottom of mobile drawer with last 3 visited pages
|
|
||||||
**Example:** Notion, Linear, Jira all have this
|
|
||||||
**Benefit:** 50% reduction in navigation clicks for common workflows
|
|
||||||
|
|
||||||
### 🔹 **Medium Priority Improvements**
|
|
||||||
|
|
||||||
#### 6. **Favorites/Pinning**
|
|
||||||
**Why:** Let users customize their navigation
|
|
||||||
**How:** Add star icon next to nav items, pinned items appear first
|
|
||||||
**Example:** Chrome bookmarks bar
|
|
||||||
**Benefit:** Personalized experience for different user types
|
|
||||||
|
|
||||||
#### 7. **Context-Aware Navigation**
|
|
||||||
**Why:** Show relevant actions based on current page
|
|
||||||
**How:** When viewing a VIP, show quick actions in header (Schedule, Edit, Delete)
|
|
||||||
**Example:** GitHub's file view has Edit/Delete buttons in header
|
|
||||||
**Benefit:** Reduces clicks for common actions
|
|
||||||
|
|
||||||
#### 8. **Add Quick Actions Menu**
|
|
||||||
**Why:** Common tasks should be 1 click away
|
|
||||||
**How:** Add "+" button in header with dropdown:
|
|
||||||
- Add VIP
|
|
||||||
- Add Driver
|
|
||||||
- Create Event
|
|
||||||
- Schedule Trip
|
|
||||||
**Example:** Asana, Notion, Linear all have "New" buttons
|
|
||||||
**Benefit:** Faster task creation workflow
|
|
||||||
|
|
||||||
#### 9. **Collapsible Navigation (Desktop)**
|
|
||||||
**Why:** Maximize screen space for content
|
|
||||||
**How:** Add toggle to collapse nav to icons only
|
|
||||||
**Example:** Gmail's left sidebar can collapse
|
|
||||||
**Benefit:** More room for tables/calendars on smaller screens
|
|
||||||
|
|
||||||
#### 10. **Smart Positioning**
|
|
||||||
**Why:** Dropdown shouldn't go off-screen on small viewports
|
|
||||||
**How:** Auto-detect dropdown position and flip if needed
|
|
||||||
**Example:** If near right edge, open dropdown to the left
|
|
||||||
**Benefit:** Better UX on all screen sizes
|
|
||||||
|
|
||||||
### 💡 **Low Priority / Future Enhancements**
|
|
||||||
|
|
||||||
#### 11. **Tooltips on Hover**
|
|
||||||
**Why:** Explain what each navigation item does
|
|
||||||
**How:** Show brief description on hover
|
|
||||||
**Example:** "War Room - Real-time operations dashboard"
|
|
||||||
**Benefit:** Better onboarding for new users
|
|
||||||
|
|
||||||
#### 12. **Navigation Analytics**
|
|
||||||
**Why:** Understand how users navigate the app
|
|
||||||
**How:** Track which nav items are clicked most often
|
|
||||||
**Example:** Google Analytics events
|
|
||||||
**Benefit:** Data-driven decisions for future nav improvements
|
|
||||||
|
|
||||||
#### 13. **Navigation Themes**
|
|
||||||
**Why:** Let users customize appearance
|
|
||||||
**How:** Light/dark mode toggle in user menu
|
|
||||||
**Example:** Most modern apps have dark mode
|
|
||||||
**Benefit:** Better user experience in different lighting conditions
|
|
||||||
|
|
||||||
#### 14. **Mobile App-Style Navigation**
|
|
||||||
**Why:** Native app feel on tablets
|
|
||||||
**How:** Add bottom tab bar on tablets with most common items
|
|
||||||
**Example:** Instagram, Twitter have bottom nav bars
|
|
||||||
**Benefit:** Easier one-handed use on tablets
|
|
||||||
|
|
||||||
#### 15. **Command Palette**
|
|
||||||
**Why:** Super fast navigation for power users
|
|
||||||
**How:** Add Cmd+K / Ctrl+K shortcut to open command palette
|
|
||||||
**Example:** Linear, Raycast, VS Code all have this
|
|
||||||
**Benefit:** Navigate anywhere instantly
|
|
||||||
|
|
||||||
## UX Principles Applied
|
|
||||||
|
|
||||||
### 1. **Hick's Law**
|
|
||||||
- Reduced number of top-level choices by grouping admin items
|
|
||||||
- Faster decision-making when navigating
|
|
||||||
|
|
||||||
### 2. **Fitts's Law**
|
|
||||||
- Touch targets meet 44px minimum
|
|
||||||
- Larger click areas for more important actions (Dashboard, War Room)
|
|
||||||
|
|
||||||
### 3. **Jakob's Law**
|
|
||||||
- Users spend most of their time on other sites
|
|
||||||
- Our patterns match Gmail, GitHub, AWS (familiar)
|
|
||||||
|
|
||||||
### 4. **Progressive Disclosure**
|
|
||||||
- Hide advanced features (admin tools) behind dropdown
|
|
||||||
- Show complexity only when needed
|
|
||||||
|
|
||||||
### 5. **Recognition over Recall**
|
|
||||||
- Icons help users recognize functions quickly
|
|
||||||
- Visual hierarchy reduces cognitive load
|
|
||||||
|
|
||||||
## Metrics to Track
|
|
||||||
|
|
||||||
After implementing these improvements, measure:
|
|
||||||
1. **Time to Complete Tasks** - Should decrease by 20-30%
|
|
||||||
2. **Click Depth** - Average clicks to reach destination
|
|
||||||
3. **Navigation Bounce Rate** - % of users who click wrong nav item
|
|
||||||
4. **Mobile Usage** - Should increase with better mobile nav
|
|
||||||
5. **User Satisfaction** - Survey scores should improve
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
- [x] Desktop navigation works (≥ 1024px)
|
|
||||||
- [x] Tablet portrait navigation works (768px)
|
|
||||||
- [x] Tablet landscape navigation works (1024px)
|
|
||||||
- [x] Mobile navigation works (< 768px)
|
|
||||||
- [x] Dropdown closes when clicking outside
|
|
||||||
- [x] Admin items only visible to administrators
|
|
||||||
- [x] Active state highlighting works correctly
|
|
||||||
- [x] Touch targets meet 44px minimum
|
|
||||||
- [x] Keyboard navigation works (Tab, Enter, Esc)
|
|
||||||
- [x] Screen reader compatible (aria-labels added)
|
|
||||||
|
|
||||||
## Browser Compatibility
|
|
||||||
|
|
||||||
Tested and working on:
|
|
||||||
- ✅ Chrome/Edge (Chromium) - Latest
|
|
||||||
- ✅ Safari - Latest
|
|
||||||
- ✅ Firefox - Latest
|
|
||||||
- ✅ Mobile Safari (iOS)
|
|
||||||
- ✅ Chrome Mobile (Android)
|
|
||||||
|
|
||||||
## Accessibility (WCAG 2.1 AA)
|
|
||||||
|
|
||||||
- ✅ Keyboard navigation supported
|
|
||||||
- ✅ ARIA labels added for screen readers
|
|
||||||
- ✅ Focus indicators visible
|
|
||||||
- ✅ Color contrast meets minimum ratio
|
|
||||||
- ✅ Touch targets meet minimum size
|
|
||||||
- ✅ Semantic HTML used
|
|
||||||
|
|
||||||
## Performance Impact
|
|
||||||
|
|
||||||
- **Bundle Size:** +2KB (dropdown component + animations)
|
|
||||||
- **Load Time:** No measurable impact
|
|
||||||
- **Runtime Performance:** Negligible (CSS transitions only)
|
|
||||||
- **Mobile Performance:** Improved (fewer DOM nodes in mobile menu)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. **Monitor user feedback** - Are users finding the new navigation easier?
|
|
||||||
2. **Implement high-priority recommendations** - Breadcrumbs, keyboard shortcuts, search
|
|
||||||
3. **A/B test variations** - Try different icon sets, positioning
|
|
||||||
4. **Iterate based on data** - Use analytics to inform next improvements
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Last Updated:** 2026-01-31
|
|
||||||
**Author:** Claude Sonnet 4.5
|
|
||||||
**Status:** ✅ Implemented and Tested
|
|
||||||
@@ -1,324 +0,0 @@
|
|||||||
# Notification Badge Implementation
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
Implemented notification badges on the Admin dropdown menu to show pending user approvals, making admin tasks more visible while decluttering the Dashboard.
|
|
||||||
|
|
||||||
## Changes Implemented ✅
|
|
||||||
|
|
||||||
### 1. **Added Pending Approvals Badge to Admin Dropdown**
|
|
||||||
|
|
||||||
#### Desktop Navigation ([Layout.tsx](frontend/src/components/Layout.tsx))
|
|
||||||
```tsx
|
|
||||||
<Shield className="h-4 w-4 mr-2" />
|
|
||||||
Admin
|
|
||||||
{pendingApprovalsCount > 0 && (
|
|
||||||
<span className="ml-1.5 inline-flex items-center justify-center px-2 py-0.5 text-xs font-bold leading-none text-white bg-red-600 rounded-full">
|
|
||||||
{pendingApprovalsCount}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Visual Result:**
|
|
||||||
- Badge appears next to "Admin" text when there are pending approvals
|
|
||||||
- Red background (`bg-red-600`) draws attention
|
|
||||||
- Small, rounded pill shape
|
|
||||||
- Auto-hides when count is 0
|
|
||||||
|
|
||||||
#### Mobile Navigation ([Layout.tsx](frontend/src/components/Layout.tsx))
|
|
||||||
```tsx
|
|
||||||
<Shield className="h-5 w-5 mr-3 flex-shrink-0" />
|
|
||||||
Admin
|
|
||||||
{pendingApprovalsCount > 0 && (
|
|
||||||
<span className="ml-2 inline-flex items-center justify-center px-2 py-0.5 text-xs font-bold leading-none text-white bg-red-600 rounded-full">
|
|
||||||
{pendingApprovalsCount}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Same badge styling on mobile drawer
|
|
||||||
- Positioned next to Admin text
|
|
||||||
- Updates in real-time when users are approved
|
|
||||||
|
|
||||||
### 2. **Removed Pending Approvals from Dashboard**
|
|
||||||
|
|
||||||
#### Before:
|
|
||||||
```
|
|
||||||
Dashboard Stats Grid:
|
|
||||||
┌──────────────┬──────────────┬──────────────┬──────────────┬────────────────────┐
|
|
||||||
│ Total VIPs │Active Drivers│ Events Today │ Flights Today│Pending Approvals(*)|
|
|
||||||
└──────────────┴──────────────┴──────────────┴──────────────┴────────────────────┘
|
|
||||||
(*) Only visible to admins
|
|
||||||
```
|
|
||||||
|
|
||||||
#### After:
|
|
||||||
```
|
|
||||||
Dashboard Stats Grid:
|
|
||||||
┌──────────────┬──────────────┬──────────────┬──────────────┐
|
|
||||||
│ Total VIPs │Active Drivers│ Events Today │ Flights Today│
|
|
||||||
└──────────────┴──────────────┴──────────────┴──────────────┘
|
|
||||||
Cleaner, more focused on operations
|
|
||||||
```
|
|
||||||
|
|
||||||
**Changes Made to [Dashboard.tsx](frontend/src/pages/Dashboard.tsx):**
|
|
||||||
|
|
||||||
1. **Removed imports:**
|
|
||||||
- `UserCheck` icon (no longer needed)
|
|
||||||
- `useAuth` hook (no longer checking isAdmin)
|
|
||||||
|
|
||||||
2. **Removed interfaces:**
|
|
||||||
- `User` interface (moved to Layout.tsx)
|
|
||||||
|
|
||||||
3. **Removed queries:**
|
|
||||||
- `users` query (now only in Layout.tsx for admins)
|
|
||||||
- `isAdmin` check
|
|
||||||
- `backendUser` reference
|
|
||||||
|
|
||||||
4. **Removed calculations:**
|
|
||||||
- `pendingApprovals` count
|
|
||||||
|
|
||||||
5. **Removed stats:**
|
|
||||||
- Pending Approvals stat card
|
|
||||||
|
|
||||||
6. **Updated grid layout:**
|
|
||||||
- **Before:** `grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5`
|
|
||||||
- **After:** `grid-cols-1 sm:grid-cols-2 lg:grid-cols-4`
|
|
||||||
- Cleaner breakpoints for exactly 4 stats
|
|
||||||
|
|
||||||
### 3. **Real-Time Badge Updates**
|
|
||||||
|
|
||||||
The badge automatically updates when:
|
|
||||||
- A user is approved (count decreases)
|
|
||||||
- A new user registers (count increases)
|
|
||||||
- Admin navigates between pages (React Query refetches)
|
|
||||||
|
|
||||||
**Implementation Details:**
|
|
||||||
```tsx
|
|
||||||
// Layout.tsx - Fetch pending approvals for admins
|
|
||||||
const { data: users } = useQuery<User[]>({
|
|
||||||
queryKey: ['users'],
|
|
||||||
queryFn: async () => {
|
|
||||||
const { data } = await api.get('/users');
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
enabled: canAccessAdmin, // Only fetch if user can access admin
|
|
||||||
});
|
|
||||||
|
|
||||||
const pendingApprovalsCount = users?.filter((u) => !u.isApproved).length || 0;
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- Uses React Query caching - no extra API calls
|
|
||||||
- Automatically refetches on window focus
|
|
||||||
- Shares cache with Users page (efficient)
|
|
||||||
|
|
||||||
## Design Rationale
|
|
||||||
|
|
||||||
### Why Badges on Navigation?
|
|
||||||
1. **Always Visible** - Admins see pending approvals count everywhere
|
|
||||||
2. **Non-Intrusive** - Doesn't clutter the main dashboard
|
|
||||||
3. **Industry Standard** - Gmail, Slack, GitHub all use nav badges
|
|
||||||
4. **Action-Oriented** - Badge is on the menu that leads to the Users page
|
|
||||||
|
|
||||||
### Why Remove from Dashboard?
|
|
||||||
1. **Not Everyone Cares** - Only admins care about pending approvals
|
|
||||||
2. **Operational Focus** - Dashboard should focus on VIP operations, not admin tasks
|
|
||||||
3. **Cleaner Layout** - 4 stats look better than 5 (especially on tablets)
|
|
||||||
4. **Better Placement** - Badge on Admin menu is more contextual
|
|
||||||
|
|
||||||
## Visual Examples
|
|
||||||
|
|
||||||
### Desktop Navigation
|
|
||||||
```
|
|
||||||
Before:
|
|
||||||
Dashboard | War Room | VIPs | Drivers | Vehicles | Schedule | Flights | Users | Admin Tools
|
|
||||||
|
|
||||||
After:
|
|
||||||
Dashboard | War Room | VIPs | Drivers | Vehicles | Schedule | Flights | Admin [3] ▼
|
|
||||||
├─ Users
|
|
||||||
└─ Admin Tools
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mobile Navigation
|
|
||||||
```
|
|
||||||
Before:
|
|
||||||
☰ Menu
|
|
||||||
├─ Dashboard
|
|
||||||
├─ War Room
|
|
||||||
├─ VIPs
|
|
||||||
├─ Drivers
|
|
||||||
├─ Vehicles
|
|
||||||
├─ Schedule
|
|
||||||
├─ Flights
|
|
||||||
├─ Users
|
|
||||||
└─ Admin Tools
|
|
||||||
|
|
||||||
After:
|
|
||||||
☰ Menu
|
|
||||||
├─ Dashboard
|
|
||||||
├─ War Room
|
|
||||||
├─ VIPs
|
|
||||||
├─ Drivers
|
|
||||||
├─ Vehicles
|
|
||||||
├─ Schedule
|
|
||||||
├─ Flights
|
|
||||||
└─ Admin [3] ▼
|
|
||||||
├─ Users
|
|
||||||
└─ Admin Tools
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dashboard Grid Responsiveness
|
|
||||||
|
|
||||||
### New Layout Breakpoints:
|
|
||||||
- **Mobile (< 640px):** 1 column
|
|
||||||
- **Small (640px - 1024px):** 2 columns (2x2 grid)
|
|
||||||
- **Large (≥ 1024px):** 4 columns (1x4 row)
|
|
||||||
|
|
||||||
**Visual:**
|
|
||||||
```
|
|
||||||
Mobile: Tablet: Desktop:
|
|
||||||
┌─────────┐ ┌─────┬─────┐ ┌───┬───┬───┬───┐
|
|
||||||
│ VIPs │ │VIPs │Drive│ │VIP│Drv│Evt│Flt│
|
|
||||||
├─────────┤ ├─────┼─────┤ └───┴───┴───┴───┘
|
|
||||||
│ Drivers │ │Evnt │Flgt │
|
|
||||||
├─────────┤ └─────┴─────┘
|
|
||||||
│ Events │
|
|
||||||
├─────────┤
|
|
||||||
│ Flights │
|
|
||||||
└─────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Color Scheme
|
|
||||||
|
|
||||||
**Badge Color:** `bg-red-600`
|
|
||||||
- **Reason:** Red signifies action needed (industry standard)
|
|
||||||
- **Contrast:** White text on red meets WCAG AAA standards
|
|
||||||
- **Visibility:** Stands out but not garish
|
|
||||||
|
|
||||||
**Alternative Colors Considered:**
|
|
||||||
- ❌ Yellow (`bg-yellow-500`) - Too soft, less urgent
|
|
||||||
- ❌ Orange (`bg-orange-500`) - Could work but less common
|
|
||||||
- ✅ Red (`bg-red-600`) - Standard for notifications
|
|
||||||
|
|
||||||
## Accessibility
|
|
||||||
|
|
||||||
**ARIA Support:**
|
|
||||||
- Badge has `aria-label` via parent button context
|
|
||||||
- Screen readers announce: "Admin, 3 notifications"
|
|
||||||
- Color isn't the only indicator (badge contains number)
|
|
||||||
|
|
||||||
**Keyboard Navigation:**
|
|
||||||
- Badge doesn't interfere with keyboard nav
|
|
||||||
- Still tab to Admin dropdown and press Enter
|
|
||||||
- Badge is purely visual enhancement
|
|
||||||
|
|
||||||
**Touch Targets:**
|
|
||||||
- Badge doesn't change touch target size
|
|
||||||
- Admin button still meets 44px minimum
|
|
||||||
|
|
||||||
## Performance Impact
|
|
||||||
|
|
||||||
**Bundle Size:** +1KB (badge styling + query logic)
|
|
||||||
**Runtime Performance:**
|
|
||||||
- ✅ Uses existing React Query cache
|
|
||||||
- ✅ No extra API calls (shares with Users page)
|
|
||||||
- ✅ Minimal re-renders (only when count changes)
|
|
||||||
|
|
||||||
**Memory Impact:** Negligible (one extra computed value)
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
- [x] Badge shows correct count on desktop
|
|
||||||
- [x] Badge shows correct count on mobile
|
|
||||||
- [x] Badge hides when count is 0
|
|
||||||
- [x] Badge updates when user is approved
|
|
||||||
- [x] Dashboard grid looks good with 4 stats
|
|
||||||
- [x] No console errors
|
|
||||||
- [x] HMR updates working correctly
|
|
||||||
- [x] React Query cache working efficiently
|
|
||||||
|
|
||||||
## Browser Compatibility
|
|
||||||
|
|
||||||
Tested and working on:
|
|
||||||
- ✅ Chrome/Edge (Chromium) - Latest
|
|
||||||
- ✅ Safari - Latest
|
|
||||||
- ✅ Firefox - Latest
|
|
||||||
- ✅ Mobile Safari (iOS)
|
|
||||||
- ✅ Chrome Mobile (Android)
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
### 🎯 Potential Improvements
|
|
||||||
|
|
||||||
1. **Click Badge to Go to Users Page**
|
|
||||||
```tsx
|
|
||||||
<Link to="/users">
|
|
||||||
<span className="badge">3</span>
|
|
||||||
</Link>
|
|
||||||
```
|
|
||||||
- One-click access to pending users
|
|
||||||
- Even more convenient
|
|
||||||
|
|
||||||
2. **Different Badge Colors by Urgency**
|
|
||||||
```tsx
|
|
||||||
{pendingApprovalsCount > 10 && 'bg-red-600'} // Many pending
|
|
||||||
{pendingApprovalsCount > 5 && 'bg-orange-500'} // Some pending
|
|
||||||
{pendingApprovalsCount > 0 && 'bg-yellow-500'} // Few pending
|
|
||||||
```
|
|
||||||
- Visual priority system
|
|
||||||
- More information at a glance
|
|
||||||
|
|
||||||
3. **Hover Tooltip with Details**
|
|
||||||
```tsx
|
|
||||||
<Tooltip content="3 users awaiting approval">
|
|
||||||
<span className="badge">3</span>
|
|
||||||
</Tooltip>
|
|
||||||
```
|
|
||||||
- Show who's waiting
|
|
||||||
- Better context
|
|
||||||
|
|
||||||
4. **Real-time Updates via WebSocket**
|
|
||||||
- Instant badge updates
|
|
||||||
- No need to refresh
|
|
||||||
- Better UX for multi-admin scenarios
|
|
||||||
|
|
||||||
5. **Sound/Visual Alert for New Pending**
|
|
||||||
- Notification sound when new user registers
|
|
||||||
- Brief animation on badge
|
|
||||||
- More proactive alerts
|
|
||||||
|
|
||||||
## Comparison to Industry Standards
|
|
||||||
|
|
||||||
### Gmail
|
|
||||||
- Red badge on unread count
|
|
||||||
- Shows on sidebar categories
|
|
||||||
- ✅ We follow this pattern
|
|
||||||
|
|
||||||
### Slack
|
|
||||||
- Red badge for mentions
|
|
||||||
- Shows on channel names
|
|
||||||
- ✅ Similar approach
|
|
||||||
|
|
||||||
### GitHub
|
|
||||||
- Blue badge for notifications
|
|
||||||
- Shows on bell icon
|
|
||||||
- 🔵 We could add hover dropdown with details (future)
|
|
||||||
|
|
||||||
### AWS Console
|
|
||||||
- No notification badges (different UX pattern)
|
|
||||||
- ❌ Not applicable
|
|
||||||
|
|
||||||
## Metrics to Track
|
|
||||||
|
|
||||||
After this implementation, monitor:
|
|
||||||
1. **Time to Approval** - Should decrease (more visible)
|
|
||||||
2. **Admin Engagement** - Track clicks on Admin dropdown
|
|
||||||
3. **User Satisfaction** - Survey admins about visibility
|
|
||||||
4. **Performance** - No measurable impact expected
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Implementation Date:** 2026-01-31
|
|
||||||
**Author:** Claude Sonnet 4.5
|
|
||||||
**Status:** ✅ Completed and Tested
|
|
||||||
**Breaking Changes:** None (additive only)
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
# ✅ OAuth Callback Issue RESOLVED!
|
|
||||||
|
|
||||||
## 🎯 Problem Identified & Fixed
|
|
||||||
|
|
||||||
**Root Cause:** The Vite proxy configuration was intercepting ALL `/auth/*` routes and forwarding them to the backend, including the OAuth callback route `/auth/google/callback` that needed to be handled by the React frontend.
|
|
||||||
|
|
||||||
## 🔧 Solution Applied
|
|
||||||
|
|
||||||
**Fixed Vite Configuration** (`frontend/vite.config.ts`):
|
|
||||||
|
|
||||||
**BEFORE (Problematic):**
|
|
||||||
```typescript
|
|
||||||
proxy: {
|
|
||||||
'/api': {
|
|
||||||
target: 'http://backend:3000',
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
'/auth': { // ❌ This was intercepting ALL /auth routes
|
|
||||||
target: 'http://backend:3000',
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**AFTER (Fixed):**
|
|
||||||
```typescript
|
|
||||||
proxy: {
|
|
||||||
'/api': {
|
|
||||||
target: 'http://backend:3000',
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
// ✅ Only proxy specific auth endpoints, not the callback route
|
|
||||||
'/auth/setup': {
|
|
||||||
target: 'http://backend:3000',
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
'/auth/google/url': {
|
|
||||||
target: 'http://backend:3000',
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
'/auth/google/exchange': {
|
|
||||||
target: 'http://backend:3000',
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
'/auth/me': {
|
|
||||||
target: 'http://backend:3000',
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
'/auth/logout': {
|
|
||||||
target: 'http://backend:3000',
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
'/auth/status': {
|
|
||||||
target: 'http://backend:3000',
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔄 How OAuth Flow Works Now
|
|
||||||
|
|
||||||
1. **User clicks "Continue with Google"**
|
|
||||||
- Frontend calls `/auth/google/url` → Proxied to backend
|
|
||||||
- Backend returns Google OAuth URL with correct redirect URI
|
|
||||||
|
|
||||||
2. **Google Authentication**
|
|
||||||
- User authenticates with Google
|
|
||||||
- Google redirects to: `https://bsa.madeamess.online:5173/auth/google/callback?code=...`
|
|
||||||
|
|
||||||
3. **Frontend Handles Callback** ✅
|
|
||||||
- `/auth/google/callback` is NOT proxied to backend
|
|
||||||
- React Router serves the frontend app
|
|
||||||
- Login component detects callback route and authorization code
|
|
||||||
- Calls `/auth/google/exchange` → Proxied to backend
|
|
||||||
- Backend exchanges code for JWT token
|
|
||||||
- Frontend receives token and user info, logs user in
|
|
||||||
|
|
||||||
## 🎉 Current Status
|
|
||||||
|
|
||||||
**✅ All containers running successfully**
|
|
||||||
**✅ Vite proxy configuration fixed**
|
|
||||||
**✅ OAuth callback route now handled by frontend**
|
|
||||||
**✅ Backend OAuth endpoints working correctly**
|
|
||||||
|
|
||||||
## 🧪 Test the Fix
|
|
||||||
|
|
||||||
1. Visit your domain: `https://bsa.madeamess.online:5173`
|
|
||||||
2. Click "Continue with Google"
|
|
||||||
3. Complete Google authentication
|
|
||||||
4. You should be redirected back and logged in successfully!
|
|
||||||
|
|
||||||
The OAuth callback handoff issue has been completely resolved! 🎊
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
# OAuth2 Setup for Frontend-Only Port (5173)
|
|
||||||
|
|
||||||
## 🎯 Configuration Overview
|
|
||||||
|
|
||||||
Since you're only forwarding port 5173, the OAuth flow has been configured to work entirely through the frontend:
|
|
||||||
|
|
||||||
**Current Setup:**
|
|
||||||
- **Frontend**: `https://bsa.madeamess.online:5173` (publicly accessible)
|
|
||||||
- **Backend**: `http://localhost:3000` (internal only)
|
|
||||||
- **OAuth Redirect**: `https://bsa.madeamess.online:5173/auth/google/callback`
|
|
||||||
|
|
||||||
## 🔧 Google Cloud Console Configuration
|
|
||||||
|
|
||||||
**Update your OAuth2 client with this redirect URI:**
|
|
||||||
```
|
|
||||||
https://bsa.madeamess.online:5173/auth/google/callback
|
|
||||||
```
|
|
||||||
|
|
||||||
**Authorized JavaScript Origins:**
|
|
||||||
```
|
|
||||||
https://bsa.madeamess.online:5173
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔄 How the OAuth Flow Works
|
|
||||||
|
|
||||||
### 1. **Frontend Initiates OAuth**
|
|
||||||
```javascript
|
|
||||||
// Frontend calls backend to get OAuth URL
|
|
||||||
const response = await fetch('/api/auth/google/url');
|
|
||||||
const { url } = await response.json();
|
|
||||||
window.location.href = url; // Redirect to Google
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. **Google Redirects to Frontend**
|
|
||||||
```
|
|
||||||
https://bsa.madeamess.online:5173/auth/google/callback?code=AUTHORIZATION_CODE
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. **Frontend Exchanges Code for Token**
|
|
||||||
```javascript
|
|
||||||
// Frontend sends code to backend
|
|
||||||
const response = await fetch('/api/auth/google/exchange', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ code: authorizationCode })
|
|
||||||
});
|
|
||||||
|
|
||||||
const { token, user } = await response.json();
|
|
||||||
// Store token in localStorage or secure cookie
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🛠️ Backend API Endpoints
|
|
||||||
|
|
||||||
### **GET /api/auth/google/url**
|
|
||||||
Returns the Google OAuth URL for frontend to redirect to.
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=..."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### **POST /api/auth/google/exchange**
|
|
||||||
Exchanges authorization code for JWT token.
|
|
||||||
|
|
||||||
**Request:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": "authorization_code_from_google"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"token": "jwt_token_here",
|
|
||||||
"user": {
|
|
||||||
"id": "user_id",
|
|
||||||
"email": "user@example.com",
|
|
||||||
"name": "User Name",
|
|
||||||
"picture": "profile_picture_url",
|
|
||||||
"role": "coordinator"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### **GET /api/auth/status**
|
|
||||||
Check authentication status.
|
|
||||||
|
|
||||||
**Headers:**
|
|
||||||
```
|
|
||||||
Authorization: Bearer jwt_token_here
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"authenticated": true,
|
|
||||||
"user": { ... }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📝 Frontend Implementation Example
|
|
||||||
|
|
||||||
### **Login Component**
|
|
||||||
```javascript
|
|
||||||
const handleGoogleLogin = async () => {
|
|
||||||
try {
|
|
||||||
// Get OAuth URL from backend
|
|
||||||
const response = await fetch('/api/auth/google/url');
|
|
||||||
const { url } = await response.json();
|
|
||||||
|
|
||||||
// Redirect to Google
|
|
||||||
window.location.href = url;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Login failed:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### **OAuth Callback Handler**
|
|
||||||
```javascript
|
|
||||||
// In your callback route component
|
|
||||||
useEffect(() => {
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const code = urlParams.get('code');
|
|
||||||
const error = urlParams.get('error');
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.error('OAuth error:', error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (code) {
|
|
||||||
exchangeCodeForToken(code);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const exchangeCodeForToken = async (code) => {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/auth/google/exchange', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ code })
|
|
||||||
});
|
|
||||||
|
|
||||||
const { token, user } = await response.json();
|
|
||||||
|
|
||||||
// Store token securely
|
|
||||||
localStorage.setItem('authToken', token);
|
|
||||||
|
|
||||||
// Redirect to dashboard
|
|
||||||
navigate('/dashboard');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Token exchange failed:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### **API Request Helper**
|
|
||||||
```javascript
|
|
||||||
const apiRequest = async (url, options = {}) => {
|
|
||||||
const token = localStorage.getItem('authToken');
|
|
||||||
|
|
||||||
return fetch(url, {
|
|
||||||
...options,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
...options.headers
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 Testing the Setup
|
|
||||||
|
|
||||||
### 1. **Test OAuth URL Generation**
|
|
||||||
```bash
|
|
||||||
curl http://localhost:3000/api/auth/google/url
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. **Test Full Flow**
|
|
||||||
1. Visit: `https://bsa.madeamess.online:5173`
|
|
||||||
2. Click login button
|
|
||||||
3. Should redirect to Google
|
|
||||||
4. After Google login, should redirect back to: `https://bsa.madeamess.online:5173/auth/google/callback?code=...`
|
|
||||||
5. Frontend should exchange code for token
|
|
||||||
6. User should be logged in
|
|
||||||
|
|
||||||
### 3. **Test API Access**
|
|
||||||
```bash
|
|
||||||
# Get a token first, then:
|
|
||||||
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" http://localhost:3000/api/auth/status
|
|
||||||
```
|
|
||||||
|
|
||||||
## ✅ Current Status
|
|
||||||
|
|
||||||
**✅ Containers Running:**
|
|
||||||
- Backend: http://localhost:3000
|
|
||||||
- Frontend: http://localhost:5173
|
|
||||||
- Database: PostgreSQL on port 5432
|
|
||||||
- Redis: Running on port 6379
|
|
||||||
|
|
||||||
**✅ OAuth Configuration:**
|
|
||||||
- Redirect URI: `https://bsa.madeamess.online:5173/auth/google/callback`
|
|
||||||
- Frontend URL: `https://bsa.madeamess.online:5173`
|
|
||||||
- Backend endpoints ready for frontend integration
|
|
||||||
|
|
||||||
**🔄 Next Steps:**
|
|
||||||
1. Update Google Cloud Console with the redirect URI
|
|
||||||
2. Implement frontend OAuth handling
|
|
||||||
3. Test the complete flow
|
|
||||||
|
|
||||||
The OAuth system is now properly configured to work through your frontend-only port setup! 🎉
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
# User Permission Issues - Debugging Summary
|
|
||||||
|
|
||||||
## Issues Found and Fixed
|
|
||||||
|
|
||||||
### 1. **Token Storage Inconsistency** ❌ → ✅
|
|
||||||
**Problem**: Different components were using different localStorage keys for the authentication token:
|
|
||||||
- `App.tsx` used `localStorage.getItem('authToken')`
|
|
||||||
- `UserManagement.tsx` used `localStorage.getItem('token')` in one place
|
|
||||||
|
|
||||||
**Fix**: Standardized all components to use `'authToken'` as the localStorage key.
|
|
||||||
|
|
||||||
**Files Fixed**:
|
|
||||||
- `frontend/src/components/UserManagement.tsx` - Line 69: Changed `localStorage.getItem('token')` to `localStorage.getItem('authToken')`
|
|
||||||
|
|
||||||
### 2. **Missing Authentication Headers in VIP Operations** ❌ → ✅
|
|
||||||
**Problem**: The VIP management operations (add, edit, delete, fetch) were not including authentication headers, causing 401/403 errors.
|
|
||||||
|
|
||||||
**Fix**: Added proper authentication headers to all VIP API calls.
|
|
||||||
|
|
||||||
**Files Fixed**:
|
|
||||||
- `frontend/src/pages/VipList.tsx`:
|
|
||||||
- Added `apiCall` import from config
|
|
||||||
- Updated `fetchVips()` to include `Authorization: Bearer ${token}` header
|
|
||||||
- Updated `handleAddVip()` to include authentication headers
|
|
||||||
- Updated `handleEditVip()` to include authentication headers
|
|
||||||
- Updated `handleDeleteVip()` to include authentication headers
|
|
||||||
- Fixed TypeScript error with EditVipForm props
|
|
||||||
|
|
||||||
### 3. **API URL Configuration** ✅
|
|
||||||
**Status**: Already correctly configured
|
|
||||||
- Frontend uses `https://api.bsa.madeamess.online` via `apiCall` helper
|
|
||||||
- Backend has proper CORS configuration for the frontend domain
|
|
||||||
|
|
||||||
### 4. **Backend Authentication Middleware** ✅
|
|
||||||
**Status**: Already properly implemented
|
|
||||||
- VIP routes are protected with `requireAuth` middleware
|
|
||||||
- Role-based access control with `requireRole(['coordinator', 'administrator'])`
|
|
||||||
- User management routes require `administrator` role
|
|
||||||
|
|
||||||
## Backend Permission Structure (Already Working)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// VIP Operations - Require coordinator or administrator role
|
|
||||||
app.post('/api/vips', requireAuth, requireRole(['coordinator', 'administrator']))
|
|
||||||
app.put('/api/vips/:id', requireAuth, requireRole(['coordinator', 'administrator']))
|
|
||||||
app.delete('/api/vips/:id', requireAuth, requireRole(['coordinator', 'administrator']))
|
|
||||||
app.get('/api/vips', requireAuth) // All authenticated users can view
|
|
||||||
|
|
||||||
// User Management - Require administrator role only
|
|
||||||
app.get('/auth/users', requireAuth, requireRole(['administrator']))
|
|
||||||
app.patch('/auth/users/:email/role', requireAuth, requireRole(['administrator']))
|
|
||||||
app.delete('/auth/users/:email', requireAuth, requireRole(['administrator']))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Role Hierarchy
|
|
||||||
|
|
||||||
1. **Administrator**:
|
|
||||||
- Full access to all features
|
|
||||||
- Can manage users and change roles
|
|
||||||
- Can add/edit/delete VIPs
|
|
||||||
- Can manage drivers and schedules
|
|
||||||
|
|
||||||
2. **Coordinator**:
|
|
||||||
- Can add/edit/delete VIPs
|
|
||||||
- Can manage drivers and schedules
|
|
||||||
- Cannot manage users or change roles
|
|
||||||
|
|
||||||
3. **Driver**:
|
|
||||||
- Can view assigned schedules
|
|
||||||
- Can update status
|
|
||||||
- Cannot manage VIPs or users
|
|
||||||
|
|
||||||
## Testing the Fixes
|
|
||||||
|
|
||||||
After these fixes, the admin should now be able to:
|
|
||||||
|
|
||||||
1. ✅ **Add VIPs**: The "Add New VIP" button will work with proper authentication
|
|
||||||
2. ✅ **Change User Roles**: The role dropdown in User Management will work correctly
|
|
||||||
3. ✅ **View All Data**: All API calls now include proper authentication headers
|
|
||||||
|
|
||||||
## What Was Happening Before
|
|
||||||
|
|
||||||
1. **VIP Operations Failing**: When clicking "Add New VIP" or trying to edit/delete VIPs, the requests were being sent without authentication headers, causing the backend to return 401 Unauthorized errors.
|
|
||||||
|
|
||||||
2. **User Role Changes Failing**: The user management component was using the wrong token storage key, so role update requests were failing with authentication errors.
|
|
||||||
|
|
||||||
3. **Silent Failures**: The frontend wasn't showing proper error messages, so it appeared that buttons weren't working when actually the API calls were being rejected.
|
|
||||||
|
|
||||||
## Additional Recommendations
|
|
||||||
|
|
||||||
1. **Error Handling**: Consider adding user-friendly error messages when API calls fail
|
|
||||||
2. **Loading States**: Add loading indicators for user actions (role changes, VIP operations)
|
|
||||||
3. **Token Refresh**: Implement token refresh logic for long-running sessions
|
|
||||||
4. **Audit Logging**: Consider logging user actions for security auditing
|
|
||||||
|
|
||||||
## Files Modified
|
|
||||||
|
|
||||||
1. `frontend/src/components/UserManagement.tsx` - Fixed token storage key inconsistency
|
|
||||||
2. `frontend/src/pages/VipList.tsx` - Added authentication headers to all VIP operations
|
|
||||||
3. `frontend/src/pages/DriverList.tsx` - Added authentication headers to all driver operations
|
|
||||||
4. `frontend/src/pages/Dashboard.tsx` - Added authentication headers to dashboard data fetching
|
|
||||||
5. `vip-coordinator/PERMISSION_ISSUES_FIXED.md` - This documentation
|
|
||||||
|
|
||||||
## Site-Wide Authentication Fix
|
|
||||||
|
|
||||||
You were absolutely right - this was a site-wide problem! I've now fixed authentication headers across all major components:
|
|
||||||
|
|
||||||
### ✅ Fixed Components:
|
|
||||||
- **VipList**: All CRUD operations (create, read, update, delete) now include auth headers
|
|
||||||
- **DriverList**: All CRUD operations now include auth headers
|
|
||||||
- **Dashboard**: Data fetching for VIPs, drivers, and schedules now includes auth headers
|
|
||||||
- **UserManagement**: Token storage key fixed and all operations include auth headers
|
|
||||||
|
|
||||||
### 🔍 Components Still Needing Review:
|
|
||||||
- `ScheduleManager.tsx` - Schedule operations
|
|
||||||
- `DriverSelector.tsx` - Driver availability checks
|
|
||||||
- `VipDetails.tsx` - VIP detail fetching
|
|
||||||
- `DriverDashboard.tsx` - Driver schedule operations
|
|
||||||
- `FlightStatus.tsx` - Flight data fetching
|
|
||||||
- `VipForm.tsx` & `EditVipForm.tsx` - Flight validation
|
|
||||||
|
|
||||||
The permission system is now working correctly with proper authentication and authorization for the main management operations!
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
# 🚀 Port 3000 Direct Access Setup Guide
|
|
||||||
|
|
||||||
## Your Optimal Setup (Based on Google's AI Analysis)
|
|
||||||
|
|
||||||
Google's AI correctly identified that the OAuth redirect to `localhost:3000` is the issue. Here's the **simplest solution**:
|
|
||||||
|
|
||||||
## Option A: Expose Port 3000 Directly (Recommended)
|
|
||||||
|
|
||||||
### 1. Router/Firewall Configuration
|
|
||||||
Configure your router to forward **both ports**:
|
|
||||||
|
|
||||||
```
|
|
||||||
Internet → Router → Your Server
|
|
||||||
Port 443/80 → Frontend (port 5173) ✅ Already working
|
|
||||||
Port 3000 → Backend (port 3000) ⚠️ ADD THIS
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Google Cloud Console Update
|
|
||||||
|
|
||||||
**Authorized JavaScript origins:**
|
|
||||||
```
|
|
||||||
https://bsa.madeamess.online
|
|
||||||
https://bsa.madeamess.online:3000
|
|
||||||
```
|
|
||||||
|
|
||||||
**Authorized redirect URIs:**
|
|
||||||
```
|
|
||||||
https://bsa.madeamess.online:3000/auth/google/callback
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Environment Variables (Already Updated)
|
|
||||||
✅ I've already updated your `.env` file:
|
|
||||||
```bash
|
|
||||||
GOOGLE_REDIRECT_URI=https://bsa.madeamess.online:3000/auth/google/callback
|
|
||||||
FRONTEND_URL=https://bsa.madeamess.online
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. SSL Certificate for Port 3000
|
|
||||||
You'll need SSL on port 3000. Options:
|
|
||||||
|
|
||||||
**Option A: Reverse proxy for port 3000 too**
|
|
||||||
```nginx
|
|
||||||
# Frontend (existing)
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
server_name bsa.madeamess.online;
|
|
||||||
location / {
|
|
||||||
proxy_pass http://localhost:5173;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Backend (add this)
|
|
||||||
server {
|
|
||||||
listen 3000 ssl;
|
|
||||||
server_name bsa.madeamess.online;
|
|
||||||
ssl_certificate /path/to/your/cert.pem;
|
|
||||||
ssl_certificate_key /path/to/your/key.pem;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://localhost:3000;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option B: Direct Docker port mapping with SSL termination**
|
|
||||||
```yaml
|
|
||||||
# In docker-compose.dev.yml
|
|
||||||
services:
|
|
||||||
backend:
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
environment:
|
|
||||||
- SSL_CERT_PATH=/certs/cert.pem
|
|
||||||
- SSL_KEY_PATH=/certs/key.pem
|
|
||||||
```
|
|
||||||
|
|
||||||
## Option B: Alternative - Use Standard HTTPS Port
|
|
||||||
|
|
||||||
If you don't want to expose port 3000, use a subdomain:
|
|
||||||
|
|
||||||
### 1. Create Subdomain
|
|
||||||
Point `api.bsa.madeamess.online` to your server
|
|
||||||
|
|
||||||
### 2. Update Environment Variables
|
|
||||||
```bash
|
|
||||||
GOOGLE_REDIRECT_URI=https://api.bsa.madeamess.online/auth/google/callback
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Configure Reverse Proxy
|
|
||||||
```nginx
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
server_name api.bsa.madeamess.online;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://localhost:3000;
|
|
||||||
# ... headers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing Your Setup
|
|
||||||
|
|
||||||
### 1. Restart Containers
|
|
||||||
```bash
|
|
||||||
cd /home/kyle/Desktop/vip-coordinator
|
|
||||||
docker-compose -f docker-compose.dev.yml restart
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Test Backend Accessibility
|
|
||||||
```bash
|
|
||||||
# Should work from internet
|
|
||||||
curl https://bsa.madeamess.online:3000/auth/setup
|
|
||||||
# Should return: {"setupCompleted":true,"firstAdminCreated":false,"oauthConfigured":true}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Test OAuth URL Generation
|
|
||||||
```bash
|
|
||||||
curl https://bsa.madeamess.online:3000/auth/google/url
|
|
||||||
# Should return Google OAuth URL with correct redirect_uri
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Test Complete OAuth Flow
|
|
||||||
1. Visit `https://bsa.madeamess.online` (frontend)
|
|
||||||
2. Click "Continue with Google"
|
|
||||||
3. Google redirects to `https://bsa.madeamess.online:3000/auth/google/callback`
|
|
||||||
4. Backend processes OAuth and redirects back to frontend with token
|
|
||||||
5. User is authenticated ✅
|
|
||||||
|
|
||||||
## Why This Works Better
|
|
||||||
|
|
||||||
✅ **Direct backend access** - Google can reach your OAuth callback
|
|
||||||
✅ **Simpler configuration** - No complex reverse proxy routing
|
|
||||||
✅ **Easier debugging** - Clear separation of frontend/backend
|
|
||||||
✅ **Standard OAuth flow** - Follows OAuth 2.0 best practices
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
🔒 **SSL Required**: Port 3000 must use HTTPS for OAuth
|
|
||||||
🔒 **Firewall Rules**: Only expose necessary ports
|
|
||||||
🔒 **CORS Configuration**: Already configured for your domain
|
|
||||||
|
|
||||||
## Quick Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Restart containers with new config
|
|
||||||
docker-compose -f docker-compose.dev.yml restart
|
|
||||||
|
|
||||||
# 2. Test backend
|
|
||||||
curl https://bsa.madeamess.online:3000/auth/setup
|
|
||||||
|
|
||||||
# 3. Check OAuth URL
|
|
||||||
curl https://bsa.madeamess.online:3000/auth/google/url
|
|
||||||
|
|
||||||
# 4. Test frontend
|
|
||||||
curl https://bsa.madeamess.online
|
|
||||||
```
|
|
||||||
|
|
||||||
## Expected Flow After Setup
|
|
||||||
|
|
||||||
1. **User visits**: `https://bsa.madeamess.online` (frontend)
|
|
||||||
2. **Clicks login**: Frontend calls `https://bsa.madeamess.online:3000/auth/google/url`
|
|
||||||
3. **Redirects to Google**: User authenticates with Google
|
|
||||||
4. **Google redirects back**: `https://bsa.madeamess.online:3000/auth/google/callback`
|
|
||||||
5. **Backend processes**: Creates JWT token
|
|
||||||
6. **Redirects to frontend**: `https://bsa.madeamess.online/auth/callback?token=...`
|
|
||||||
7. **Frontend receives token**: User is logged in ✅
|
|
||||||
|
|
||||||
This setup will resolve the OAuth callback issue you're experiencing!
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
# 🐘 PostgreSQL User Management System
|
|
||||||
|
|
||||||
## ✅ What We Built
|
|
||||||
|
|
||||||
A **production-ready user management system** using your existing PostgreSQL database infrastructure with proper database design, indexing, and transactional operations.
|
|
||||||
|
|
||||||
## 🎯 Database Architecture
|
|
||||||
|
|
||||||
### **Users Table Schema**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE users (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
email VARCHAR(255) UNIQUE NOT NULL,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
picture TEXT,
|
|
||||||
role VARCHAR(50) NOT NULL DEFAULT 'coordinator',
|
|
||||||
provider VARCHAR(50) NOT NULL DEFAULT 'google',
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
last_sign_in_at TIMESTAMP WITH TIME ZONE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Optimized indexes for performance
|
|
||||||
CREATE INDEX idx_users_email ON users(email);
|
|
||||||
CREATE INDEX idx_users_role ON users(role);
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Key Features**
|
|
||||||
- ✅ **Primary key constraints** - Unique user identification
|
|
||||||
- ✅ **Email uniqueness** - Prevents duplicate accounts
|
|
||||||
- ✅ **Proper indexing** - Fast lookups by email and role
|
|
||||||
- ✅ **Timezone-aware timestamps** - Accurate time tracking
|
|
||||||
- ✅ **Default values** - Sensible defaults for new users
|
|
||||||
|
|
||||||
## 🚀 System Components
|
|
||||||
|
|
||||||
### **1. DatabaseService (`databaseService.ts`)**
|
|
||||||
- **Connection pooling** with PostgreSQL
|
|
||||||
- **Automatic schema initialization** on startup
|
|
||||||
- **Transactional operations** for data consistency
|
|
||||||
- **Error handling** and connection management
|
|
||||||
- **Future-ready** with VIP and schedule tables
|
|
||||||
|
|
||||||
### **2. Enhanced Auth Routes (`simpleAuth.ts`)**
|
|
||||||
- **Async/await** for all database operations
|
|
||||||
- **Proper error handling** with database fallbacks
|
|
||||||
- **User creation** with automatic role assignment
|
|
||||||
- **Login tracking** with timestamp updates
|
|
||||||
- **Role-based access control** for admin operations
|
|
||||||
|
|
||||||
### **3. User Management API**
|
|
||||||
```typescript
|
|
||||||
// List all users (admin only)
|
|
||||||
GET /auth/users
|
|
||||||
|
|
||||||
// Update user role (admin only)
|
|
||||||
PATCH /auth/users/:email/role
|
|
||||||
Body: { "role": "administrator" | "coordinator" | "driver" }
|
|
||||||
|
|
||||||
// Delete user (admin only)
|
|
||||||
DELETE /auth/users/:email
|
|
||||||
|
|
||||||
// Get specific user (admin only)
|
|
||||||
GET /auth/users/:email
|
|
||||||
```
|
|
||||||
|
|
||||||
### **4. Frontend Interface (`UserManagement.tsx`)**
|
|
||||||
- **Real-time data** from PostgreSQL
|
|
||||||
- **Professional UI** with loading states
|
|
||||||
- **Error handling** with user feedback
|
|
||||||
- **Role management** with instant updates
|
|
||||||
- **Responsive design** for all screen sizes
|
|
||||||
|
|
||||||
## 🔧 Technical Advantages
|
|
||||||
|
|
||||||
### **Database Benefits:**
|
|
||||||
- ✅ **ACID compliance** - Guaranteed data consistency
|
|
||||||
- ✅ **Concurrent access** - Multiple users safely
|
|
||||||
- ✅ **Backup & recovery** - Enterprise-grade data protection
|
|
||||||
- ✅ **Scalability** - Handles thousands of users
|
|
||||||
- ✅ **Query optimization** - Indexed for performance
|
|
||||||
|
|
||||||
### **Security Features:**
|
|
||||||
- ✅ **SQL injection protection** - Parameterized queries
|
|
||||||
- ✅ **Connection pooling** - Efficient resource usage
|
|
||||||
- ✅ **Role validation** - Server-side permission checks
|
|
||||||
- ✅ **Transaction safety** - Atomic operations
|
|
||||||
|
|
||||||
### **Production Ready:**
|
|
||||||
- ✅ **Error handling** - Graceful failure recovery
|
|
||||||
- ✅ **Logging** - Comprehensive operation tracking
|
|
||||||
- ✅ **Connection management** - Automatic reconnection
|
|
||||||
- ✅ **Schema migration** - Safe database updates
|
|
||||||
|
|
||||||
## 📋 Setup & Usage
|
|
||||||
|
|
||||||
### **1. Database Initialization**
|
|
||||||
The system automatically creates tables on startup:
|
|
||||||
```bash
|
|
||||||
# Your existing Docker setup handles this
|
|
||||||
docker-compose -f docker-compose.dev.yml up
|
|
||||||
```
|
|
||||||
|
|
||||||
### **2. First User Setup**
|
|
||||||
- **First user** becomes administrator automatically
|
|
||||||
- **Subsequent users** become coordinators by default
|
|
||||||
- **Role changes** can be made through admin interface
|
|
||||||
|
|
||||||
### **3. User Management Workflow**
|
|
||||||
1. **Login with Google OAuth** - Users authenticate via Google
|
|
||||||
2. **Automatic user creation** - New users added to database
|
|
||||||
3. **Role assignment** - Admin can change user roles
|
|
||||||
4. **Permission enforcement** - Role-based access control
|
|
||||||
5. **User lifecycle** - Full CRUD operations for admins
|
|
||||||
|
|
||||||
## 🎯 Database Operations
|
|
||||||
|
|
||||||
### **User Creation Flow:**
|
|
||||||
```sql
|
|
||||||
-- Check if user exists
|
|
||||||
SELECT * FROM users WHERE email = $1;
|
|
||||||
|
|
||||||
-- Create new user if not exists
|
|
||||||
INSERT INTO users (id, email, name, picture, role, provider, last_sign_in_at)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP)
|
|
||||||
RETURNING *;
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Role Update Flow:**
|
|
||||||
```sql
|
|
||||||
-- Update user role with timestamp
|
|
||||||
UPDATE users
|
|
||||||
SET role = $1, updated_at = CURRENT_TIMESTAMP
|
|
||||||
WHERE email = $2
|
|
||||||
RETURNING *;
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Login Tracking:**
|
|
||||||
```sql
|
|
||||||
-- Update last sign-in timestamp
|
|
||||||
UPDATE users
|
|
||||||
SET last_sign_in_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
|
|
||||||
WHERE email = $1
|
|
||||||
RETURNING *;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔍 Monitoring & Maintenance
|
|
||||||
|
|
||||||
### **Database Health:**
|
|
||||||
- **Connection status** logged on startup
|
|
||||||
- **Query performance** tracked in logs
|
|
||||||
- **Error handling** with detailed logging
|
|
||||||
- **Connection pooling** metrics available
|
|
||||||
|
|
||||||
### **User Analytics:**
|
|
||||||
- **User count** tracking for admin setup
|
|
||||||
- **Login patterns** via last_sign_in_at
|
|
||||||
- **Role distribution** via role indexing
|
|
||||||
- **Account creation** trends via created_at
|
|
||||||
|
|
||||||
## 🚀 Future Enhancements
|
|
||||||
|
|
||||||
### **Ready for Extension:**
|
|
||||||
- **User profiles** - Additional metadata fields
|
|
||||||
- **User groups** - Team-based permissions
|
|
||||||
- **Audit logging** - Track all user actions
|
|
||||||
- **Session management** - Advanced security
|
|
||||||
- **Multi-factor auth** - Enhanced security
|
|
||||||
|
|
||||||
### **Database Scaling:**
|
|
||||||
- **Read replicas** - For high-traffic scenarios
|
|
||||||
- **Partitioning** - For large user bases
|
|
||||||
- **Caching** - Redis integration ready
|
|
||||||
- **Backup strategies** - Automated backups
|
|
||||||
|
|
||||||
## 🎉 Production Benefits
|
|
||||||
|
|
||||||
### **Enterprise Grade:**
|
|
||||||
- ✅ **Reliable** - PostgreSQL battle-tested reliability
|
|
||||||
- ✅ **Scalable** - Handles growth from 10 to 10,000+ users
|
|
||||||
- ✅ **Secure** - Industry-standard security practices
|
|
||||||
- ✅ **Maintainable** - Clean, documented codebase
|
|
||||||
|
|
||||||
### **Developer Friendly:**
|
|
||||||
- ✅ **Type-safe** - Full TypeScript integration
|
|
||||||
- ✅ **Well-documented** - Clear API and database schema
|
|
||||||
- ✅ **Error-handled** - Graceful failure modes
|
|
||||||
- ✅ **Testable** - Isolated database operations
|
|
||||||
|
|
||||||
Your user management system is now **production-ready** with enterprise-grade PostgreSQL backing! 🚀
|
|
||||||
|
|
||||||
## 🔧 Quick Start
|
|
||||||
|
|
||||||
1. **Ensure PostgreSQL is running** (your Docker setup handles this)
|
|
||||||
2. **Restart your backend** to initialize tables
|
|
||||||
3. **Login as first user** to become administrator
|
|
||||||
4. **Manage users** through the beautiful admin interface
|
|
||||||
|
|
||||||
All user data is now safely stored in PostgreSQL with proper indexing, relationships, and ACID compliance!
|
|
||||||
218
README-API.md
218
README-API.md
@@ -1,218 +0,0 @@
|
|||||||
# VIP Coordinator API Documentation
|
|
||||||
|
|
||||||
## 📚 Overview
|
|
||||||
|
|
||||||
This document provides comprehensive API documentation for the VIP Coordinator system using **OpenAPI 3.0** (Swagger) specification. The API enables management of VIP transportation coordination, including flight tracking, driver management, and event scheduling.
|
|
||||||
|
|
||||||
## 🚀 Quick Start
|
|
||||||
|
|
||||||
### View API Documentation
|
|
||||||
|
|
||||||
1. **Interactive Documentation (Recommended):**
|
|
||||||
```bash
|
|
||||||
# Open the interactive Swagger UI documentation
|
|
||||||
open vip-coordinator/api-docs.html
|
|
||||||
```
|
|
||||||
Or visit: `file:///path/to/vip-coordinator/api-docs.html`
|
|
||||||
|
|
||||||
2. **Raw OpenAPI Specification:**
|
|
||||||
```bash
|
|
||||||
# View the YAML specification file
|
|
||||||
cat vip-coordinator/api-documentation.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test the API
|
|
||||||
|
|
||||||
The interactive documentation includes a "Try it out" feature that allows you to test endpoints directly:
|
|
||||||
|
|
||||||
1. Open `api-docs.html` in your browser
|
|
||||||
2. Click on any endpoint to expand it
|
|
||||||
3. Click "Try it out" button
|
|
||||||
4. Fill in parameters and request body
|
|
||||||
5. Click "Execute" to make the API call
|
|
||||||
|
|
||||||
## 📋 API Categories
|
|
||||||
|
|
||||||
### 🏥 Health
|
|
||||||
- `GET /api/health` - System health check
|
|
||||||
|
|
||||||
### 👥 VIPs
|
|
||||||
- `GET /api/vips` - Get all VIPs
|
|
||||||
- `POST /api/vips` - Create new VIP
|
|
||||||
- `PUT /api/vips/{id}` - Update VIP
|
|
||||||
- `DELETE /api/vips/{id}` - Delete VIP
|
|
||||||
|
|
||||||
### 🚗 Drivers
|
|
||||||
- `GET /api/drivers` - Get all drivers
|
|
||||||
- `POST /api/drivers` - Create new driver
|
|
||||||
- `PUT /api/drivers/{id}` - Update driver
|
|
||||||
- `DELETE /api/drivers/{id}` - Delete driver
|
|
||||||
- `GET /api/drivers/{driverId}/schedule` - Get driver's schedule
|
|
||||||
- `POST /api/drivers/availability` - Check driver availability
|
|
||||||
- `POST /api/drivers/{driverId}/conflicts` - Check driver conflicts
|
|
||||||
|
|
||||||
### ✈️ Flights
|
|
||||||
- `GET /api/flights/{flightNumber}` - Get flight information
|
|
||||||
- `POST /api/flights/{flightNumber}/track` - Start flight tracking
|
|
||||||
- `DELETE /api/flights/{flightNumber}/track` - Stop flight tracking
|
|
||||||
- `POST /api/flights/batch` - Get multiple flights info
|
|
||||||
- `GET /api/flights/tracking/status` - Get tracking status
|
|
||||||
|
|
||||||
### 📅 Schedule
|
|
||||||
- `GET /api/vips/{vipId}/schedule` - Get VIP's schedule
|
|
||||||
- `POST /api/vips/{vipId}/schedule` - Add event to schedule
|
|
||||||
- `PUT /api/vips/{vipId}/schedule/{eventId}` - Update event
|
|
||||||
- `DELETE /api/vips/{vipId}/schedule/{eventId}` - Delete event
|
|
||||||
- `PATCH /api/vips/{vipId}/schedule/{eventId}/status` - Update event status
|
|
||||||
|
|
||||||
### ⚙️ Admin
|
|
||||||
- `POST /api/admin/authenticate` - Admin authentication
|
|
||||||
- `GET /api/admin/settings` - Get admin settings
|
|
||||||
- `POST /api/admin/settings` - Update admin settings
|
|
||||||
|
|
||||||
## 💡 Example API Calls
|
|
||||||
|
|
||||||
### Create a VIP with Flight
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:3000/api/vips \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"name": "John Doe",
|
|
||||||
"organization": "Tech Corp",
|
|
||||||
"transportMode": "flight",
|
|
||||||
"flights": [
|
|
||||||
{
|
|
||||||
"flightNumber": "UA1234",
|
|
||||||
"flightDate": "2025-06-26",
|
|
||||||
"segment": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"needsAirportPickup": true,
|
|
||||||
"needsVenueTransport": true,
|
|
||||||
"notes": "CEO - requires executive transport"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Add Event to VIP Schedule
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:3000/api/vips/{vipId}/schedule \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"title": "Meeting with CEO",
|
|
||||||
"location": "Hyatt Regency Denver",
|
|
||||||
"startTime": "2025-06-26T11:00:00",
|
|
||||||
"endTime": "2025-06-26T12:30:00",
|
|
||||||
"type": "meeting",
|
|
||||||
"assignedDriverId": "1748780965562",
|
|
||||||
"description": "Important strategic meeting"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Check Driver Availability
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:3000/api/drivers/availability \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"startTime": "2025-06-26T11:00:00",
|
|
||||||
"endTime": "2025-06-26T12:30:00",
|
|
||||||
"location": "Denver Convention Center"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Flight Information
|
|
||||||
```bash
|
|
||||||
curl "http://localhost:3000/api/flights/UA1234?date=2025-06-26"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Tools for API Documentation
|
|
||||||
|
|
||||||
### 1. **Swagger UI (Recommended)**
|
|
||||||
- **What it is:** Interactive web-based API documentation
|
|
||||||
- **Features:**
|
|
||||||
- Try endpoints directly in browser
|
|
||||||
- Auto-generated from OpenAPI spec
|
|
||||||
- Beautiful, responsive interface
|
|
||||||
- Request/response examples
|
|
||||||
- **Access:** Open `api-docs.html` in your browser
|
|
||||||
|
|
||||||
### 2. **OpenAPI Specification**
|
|
||||||
- **What it is:** Industry-standard API specification format
|
|
||||||
- **Features:**
|
|
||||||
- Machine-readable API definition
|
|
||||||
- Can generate client SDKs
|
|
||||||
- Supports validation and testing
|
|
||||||
- Compatible with many tools
|
|
||||||
- **File:** `api-documentation.yaml`
|
|
||||||
|
|
||||||
### 3. **Alternative Tools**
|
|
||||||
|
|
||||||
You can use the OpenAPI specification with other tools:
|
|
||||||
|
|
||||||
#### Postman
|
|
||||||
1. Import `api-documentation.yaml` into Postman
|
|
||||||
2. Automatically creates a collection with all endpoints
|
|
||||||
3. Includes examples and validation
|
|
||||||
|
|
||||||
#### Insomnia
|
|
||||||
1. Import the OpenAPI spec
|
|
||||||
2. Generate requests automatically
|
|
||||||
3. Built-in environment management
|
|
||||||
|
|
||||||
#### VS Code Extensions
|
|
||||||
- **OpenAPI (Swagger) Editor** - Edit and preview API specs
|
|
||||||
- **REST Client** - Test APIs directly in VS Code
|
|
||||||
|
|
||||||
## 📖 Documentation Best Practices
|
|
||||||
|
|
||||||
### Why OpenAPI/Swagger?
|
|
||||||
|
|
||||||
1. **Industry Standard:** Most widely adopted API documentation format
|
|
||||||
2. **Interactive:** Users can test APIs directly in the documentation
|
|
||||||
3. **Code Generation:** Can generate client libraries in multiple languages
|
|
||||||
4. **Validation:** Ensures API requests/responses match specification
|
|
||||||
5. **Tooling:** Extensive ecosystem of tools and integrations
|
|
||||||
|
|
||||||
### Documentation Features
|
|
||||||
|
|
||||||
- **Comprehensive:** All endpoints, parameters, and responses documented
|
|
||||||
- **Examples:** Real-world examples for all operations
|
|
||||||
- **Schemas:** Detailed data models with validation rules
|
|
||||||
- **Error Handling:** Clear error response documentation
|
|
||||||
- **Authentication:** Security requirements clearly specified
|
|
||||||
|
|
||||||
## 🔗 Integration Examples
|
|
||||||
|
|
||||||
### Frontend Integration
|
|
||||||
```javascript
|
|
||||||
// Example: Fetch VIPs in React
|
|
||||||
const fetchVips = async () => {
|
|
||||||
const response = await fetch('/api/vips');
|
|
||||||
const vips = await response.json();
|
|
||||||
return vips;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Backend Integration
|
|
||||||
```bash
|
|
||||||
# Example: Using curl to test endpoints
|
|
||||||
curl -X GET http://localhost:3000/api/health
|
|
||||||
curl -X GET http://localhost:3000/api/vips
|
|
||||||
curl -X GET http://localhost:3000/api/drivers
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 Next Steps
|
|
||||||
|
|
||||||
1. **Explore the Interactive Docs:** Open `api-docs.html` and try the endpoints
|
|
||||||
2. **Test with Real Data:** Use the populated test data to explore functionality
|
|
||||||
3. **Build Integrations:** Use the API specification to build client applications
|
|
||||||
4. **Extend the API:** Add new endpoints following the established patterns
|
|
||||||
|
|
||||||
## 📞 Support
|
|
||||||
|
|
||||||
For questions about the API:
|
|
||||||
- Review the interactive documentation
|
|
||||||
- Check the OpenAPI specification for detailed schemas
|
|
||||||
- Test endpoints using the "Try it out" feature
|
|
||||||
- Refer to the example requests and responses
|
|
||||||
|
|
||||||
The API documentation is designed to be self-service and comprehensive, providing everything needed to integrate with the VIP Coordinator system.
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
# 🌐 Reverse Proxy OAuth Setup Guide
|
|
||||||
|
|
||||||
## Your Current Setup
|
|
||||||
- **Internet** → **Router (ports 80/443)** → **Reverse Proxy** → **Frontend (port 5173)**
|
|
||||||
- **Backend (port 3000)** is only accessible locally
|
|
||||||
- **OAuth callback fails** because Google can't reach the backend
|
|
||||||
|
|
||||||
## The Problem
|
|
||||||
Google OAuth needs to redirect to your **backend** (`/auth/google/callback`), but your reverse proxy only forwards to the frontend. The backend port 3000 isn't exposed to the internet.
|
|
||||||
|
|
||||||
## Solution: Configure Reverse Proxy for Both Frontend and Backend
|
|
||||||
|
|
||||||
### Option 1: Single Domain with Path-Based Routing (Recommended)
|
|
||||||
|
|
||||||
Configure your reverse proxy to route both frontend and backend on the same domain:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
# Example Nginx configuration
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
server_name bsa.madeamess.online;
|
|
||||||
|
|
||||||
# Frontend routes (everything except /auth and /api)
|
|
||||||
location / {
|
|
||||||
proxy_pass http://localhost:5173;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Backend API routes
|
|
||||||
location /api/ {
|
|
||||||
proxy_pass http://localhost:3000;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Backend auth routes (CRITICAL for OAuth)
|
|
||||||
location /auth/ {
|
|
||||||
proxy_pass http://localhost:3000;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: Subdomain Routing
|
|
||||||
|
|
||||||
If you prefer separate subdomains:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
# Frontend
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
server_name bsa.madeamess.online;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://localhost:5173;
|
|
||||||
# ... headers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Backend API
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
server_name api.bsa.madeamess.online;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://localhost:3000;
|
|
||||||
# ... headers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Update Environment Variables
|
|
||||||
|
|
||||||
### For Option 1 (Path-based - Recommended):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# backend/.env
|
|
||||||
GOOGLE_CLIENT_ID=308004695553-6k34bbq22frc4e76kejnkgq8mncepbbg.apps.googleusercontent.com
|
|
||||||
GOOGLE_CLIENT_SECRET=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
|
|
||||||
GOOGLE_REDIRECT_URI=https://bsa.madeamess.online/auth/google/callback
|
|
||||||
FRONTEND_URL=https://bsa.madeamess.online
|
|
||||||
```
|
|
||||||
|
|
||||||
### For Option 2 (Subdomain):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# backend/.env
|
|
||||||
GOOGLE_CLIENT_ID=308004695553-6k34bbq22frc4e76kejnkgq8mncepbbg.apps.googleusercontent.com
|
|
||||||
GOOGLE_CLIENT_SECRET=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
|
|
||||||
GOOGLE_REDIRECT_URI=https://api.bsa.madeamess.online/auth/google/callback
|
|
||||||
FRONTEND_URL=https://bsa.madeamess.online
|
|
||||||
```
|
|
||||||
|
|
||||||
## Update Google Cloud Console
|
|
||||||
|
|
||||||
### For Option 1 (Path-based):
|
|
||||||
|
|
||||||
**Authorized JavaScript origins:**
|
|
||||||
```
|
|
||||||
https://bsa.madeamess.online
|
|
||||||
```
|
|
||||||
|
|
||||||
**Authorized redirect URIs:**
|
|
||||||
```
|
|
||||||
https://bsa.madeamess.online/auth/google/callback
|
|
||||||
```
|
|
||||||
|
|
||||||
### For Option 2 (Subdomain):
|
|
||||||
|
|
||||||
**Authorized JavaScript origins:**
|
|
||||||
```
|
|
||||||
https://bsa.madeamess.online
|
|
||||||
https://api.bsa.madeamess.online
|
|
||||||
```
|
|
||||||
|
|
||||||
**Authorized redirect URIs:**
|
|
||||||
```
|
|
||||||
https://api.bsa.madeamess.online/auth/google/callback
|
|
||||||
```
|
|
||||||
|
|
||||||
## Frontend Configuration Update
|
|
||||||
|
|
||||||
If using Option 2 (subdomain), update your frontend to call the API subdomain:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// In your frontend code, change API calls from:
|
|
||||||
fetch('/auth/google/url')
|
|
||||||
|
|
||||||
// To:
|
|
||||||
fetch('https://api.bsa.madeamess.online/auth/google/url')
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing Your Setup
|
|
||||||
|
|
||||||
### 1. Test Backend Accessibility
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Should work from internet
|
|
||||||
curl https://bsa.madeamess.online/auth/setup
|
|
||||||
# or for subdomain:
|
|
||||||
curl https://api.bsa.madeamess.online/auth/setup
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Test OAuth URL Generation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl https://bsa.madeamess.online/auth/google/url
|
|
||||||
# Should return a Google OAuth URL
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Test Complete Flow
|
|
||||||
|
|
||||||
1. Visit `https://bsa.madeamess.online`
|
|
||||||
2. Click "Continue with Google"
|
|
||||||
3. Complete Google login
|
|
||||||
4. Should redirect back and authenticate
|
|
||||||
|
|
||||||
## Common Issues and Solutions
|
|
||||||
|
|
||||||
### Issue: "Invalid redirect URI"
|
|
||||||
- **Cause**: Google Console redirect URI doesn't match exactly
|
|
||||||
- **Fix**: Ensure exact match including `https://` and no trailing slash
|
|
||||||
|
|
||||||
### Issue: "OAuth not configured"
|
|
||||||
- **Cause**: Backend environment variables not updated
|
|
||||||
- **Fix**: Update `.env` file and restart containers
|
|
||||||
|
|
||||||
### Issue: Frontend can't reach backend
|
|
||||||
- **Cause**: Reverse proxy not configured for `/auth` and `/api` routes
|
|
||||||
- **Fix**: Add backend routing to your reverse proxy config
|
|
||||||
|
|
||||||
### Issue: CORS errors
|
|
||||||
- **Cause**: Frontend and backend on different origins
|
|
||||||
- **Fix**: Update CORS configuration in backend:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// In backend/src/index.ts
|
|
||||||
app.use(cors({
|
|
||||||
origin: [
|
|
||||||
'https://bsa.madeamess.online',
|
|
||||||
'http://localhost:5173' // for local development
|
|
||||||
],
|
|
||||||
credentials: true
|
|
||||||
}));
|
|
||||||
```
|
|
||||||
|
|
||||||
## Recommended: Path-Based Routing
|
|
||||||
|
|
||||||
I recommend **Option 1 (path-based routing)** because:
|
|
||||||
- ✅ Single domain simplifies CORS
|
|
||||||
- ✅ Easier SSL certificate management
|
|
||||||
- ✅ Simpler frontend configuration
|
|
||||||
- ✅ Better for SEO and user experience
|
|
||||||
|
|
||||||
## Quick Setup Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Update environment variables
|
|
||||||
cd /home/kyle/Desktop/vip-coordinator
|
|
||||||
# Edit backend/.env with your domain
|
|
||||||
|
|
||||||
# 2. Restart containers
|
|
||||||
docker-compose -f docker-compose.dev.yml restart
|
|
||||||
|
|
||||||
# 3. Test the setup
|
|
||||||
curl https://bsa.madeamess.online/auth/setup
|
|
||||||
```
|
|
||||||
|
|
||||||
Your OAuth should work once you configure your reverse proxy to forward `/auth` and `/api` routes to the backend (port 3000)!
|
|
||||||
@@ -1,300 +0,0 @@
|
|||||||
# Role-Based Access Control (RBAC) System
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The VIP Coordinator application implements a comprehensive role-based access control system with three distinct user roles, each with specific permissions and access levels.
|
|
||||||
|
|
||||||
## User Roles
|
|
||||||
|
|
||||||
### 1. System Administrator (`administrator`)
|
|
||||||
**Highest privilege level - Full system access**
|
|
||||||
|
|
||||||
#### Permissions:
|
|
||||||
- ✅ **User Management**: Create, read, update, delete users
|
|
||||||
- ✅ **Role Management**: Assign and modify user roles
|
|
||||||
- ✅ **VIP Management**: Full CRUD operations on VIP records
|
|
||||||
- ✅ **Driver Management**: Full CRUD operations on driver records
|
|
||||||
- ✅ **Schedule Management**: Full CRUD operations on schedules
|
|
||||||
- ✅ **System Settings**: Access to admin panel and API configurations
|
|
||||||
- ✅ **Flight Tracking**: Access to all flight tracking features
|
|
||||||
- ✅ **Reports & Analytics**: Access to all system reports
|
|
||||||
|
|
||||||
#### API Endpoints Access:
|
|
||||||
```
|
|
||||||
POST /auth/users ✅ Admin only
|
|
||||||
GET /auth/users ✅ Admin only
|
|
||||||
PATCH /auth/users/:email/role ✅ Admin only
|
|
||||||
DELETE /auth/users/:email ✅ Admin only
|
|
||||||
|
|
||||||
POST /api/vips ✅ Admin + Coordinator
|
|
||||||
GET /api/vips ✅ All authenticated users
|
|
||||||
PUT /api/vips/:id ✅ Admin + Coordinator
|
|
||||||
DELETE /api/vips/:id ✅ Admin + Coordinator
|
|
||||||
|
|
||||||
POST /api/drivers ✅ Admin + Coordinator
|
|
||||||
GET /api/drivers ✅ All authenticated users
|
|
||||||
PUT /api/drivers/:id ✅ Admin + Coordinator
|
|
||||||
DELETE /api/drivers/:id ✅ Admin + Coordinator
|
|
||||||
|
|
||||||
POST /api/vips/:vipId/schedule ✅ Admin + Coordinator
|
|
||||||
GET /api/vips/:vipId/schedule ✅ All authenticated users
|
|
||||||
PUT /api/vips/:vipId/schedule/:id ✅ Admin + Coordinator
|
|
||||||
PATCH /api/vips/:vipId/schedule/:id/status ✅ All authenticated users
|
|
||||||
DELETE /api/vips/:vipId/schedule/:id ✅ Admin + Coordinator
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Coordinator (`coordinator`)
|
|
||||||
**Standard operational access - Can manage VIPs, drivers, and schedules**
|
|
||||||
|
|
||||||
#### Permissions:
|
|
||||||
- ❌ **User Management**: Cannot manage users or roles
|
|
||||||
- ✅ **VIP Management**: Full CRUD operations on VIP records
|
|
||||||
- ✅ **Driver Management**: Full CRUD operations on driver records
|
|
||||||
- ✅ **Schedule Management**: Full CRUD operations on schedules
|
|
||||||
- ❌ **System Settings**: No access to admin panel
|
|
||||||
- ✅ **Flight Tracking**: Access to flight tracking features
|
|
||||||
- ✅ **Driver Availability**: Can check driver conflicts and availability
|
|
||||||
- ✅ **Status Updates**: Can update event statuses
|
|
||||||
|
|
||||||
#### Typical Use Cases:
|
|
||||||
- Managing VIP arrivals and departures
|
|
||||||
- Assigning drivers to VIPs
|
|
||||||
- Creating and updating schedules
|
|
||||||
- Monitoring flight statuses
|
|
||||||
- Coordinating transportation logistics
|
|
||||||
|
|
||||||
### 3. Driver (`driver`)
|
|
||||||
**Limited access - Can view assigned schedules and update status**
|
|
||||||
|
|
||||||
#### Permissions:
|
|
||||||
- ❌ **User Management**: Cannot manage users
|
|
||||||
- ❌ **VIP Management**: Cannot create/edit/delete VIPs
|
|
||||||
- ❌ **Driver Management**: Cannot manage other drivers
|
|
||||||
- ❌ **Schedule Creation**: Cannot create or delete schedules
|
|
||||||
- ✅ **View Schedules**: Can view VIP schedules and assigned events
|
|
||||||
- ✅ **Status Updates**: Can update status of assigned events
|
|
||||||
- ✅ **Personal Schedule**: Can view their own complete schedule
|
|
||||||
- ❌ **System Settings**: No access to admin features
|
|
||||||
|
|
||||||
#### API Endpoints Access:
|
|
||||||
```
|
|
||||||
GET /api/vips ✅ View only
|
|
||||||
GET /api/drivers ✅ View only
|
|
||||||
GET /api/vips/:vipId/schedule ✅ View only
|
|
||||||
PATCH /api/vips/:vipId/schedule/:id/status ✅ Can update status
|
|
||||||
GET /api/drivers/:driverId/schedule ✅ Own schedule only
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Typical Use Cases:
|
|
||||||
- Viewing assigned VIP transportation schedules
|
|
||||||
- Updating event status (en route, completed, delayed)
|
|
||||||
- Checking personal daily/weekly schedule
|
|
||||||
- Viewing VIP contact information and notes
|
|
||||||
|
|
||||||
## Authentication Flow
|
|
||||||
|
|
||||||
### 1. Google OAuth Integration
|
|
||||||
- Users authenticate via Google OAuth 2.0
|
|
||||||
- First user automatically becomes `administrator`
|
|
||||||
- Subsequent users default to `coordinator` role
|
|
||||||
- Administrators can change user roles after authentication
|
|
||||||
|
|
||||||
### 2. JWT Token System
|
|
||||||
- Secure JWT tokens issued after successful authentication
|
|
||||||
- Tokens include user role information
|
|
||||||
- Middleware validates tokens and role permissions on each request
|
|
||||||
|
|
||||||
### 3. Role Assignment
|
|
||||||
```typescript
|
|
||||||
// First user becomes admin
|
|
||||||
const userCount = await databaseService.getUserCount();
|
|
||||||
const role = userCount === 0 ? 'administrator' : 'coordinator';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security Implementation
|
|
||||||
|
|
||||||
### Middleware Protection
|
|
||||||
```typescript
|
|
||||||
// Authentication required
|
|
||||||
app.get('/api/vips', requireAuth, async (req, res) => { ... });
|
|
||||||
|
|
||||||
// Role-based access
|
|
||||||
app.post('/api/vips', requireAuth, requireRole(['coordinator', 'administrator']),
|
|
||||||
async (req, res) => { ... });
|
|
||||||
|
|
||||||
// Admin only
|
|
||||||
app.get('/auth/users', requireAuth, requireRole(['administrator']),
|
|
||||||
async (req, res) => { ... });
|
|
||||||
```
|
|
||||||
|
|
||||||
### Frontend Role Checking
|
|
||||||
```typescript
|
|
||||||
// User Management component
|
|
||||||
if (currentUser?.role !== 'administrator') {
|
|
||||||
return (
|
|
||||||
<div className="p-6 bg-red-50 border border-red-200 rounded-lg">
|
|
||||||
<h2 className="text-xl font-semibold text-red-800 mb-2">Access Denied</h2>
|
|
||||||
<p className="text-red-600">You need administrator privileges to access user management.</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Database Schema
|
|
||||||
|
|
||||||
### Users Table
|
|
||||||
```sql
|
|
||||||
CREATE TABLE users (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
email VARCHAR(255) UNIQUE NOT NULL,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
picture TEXT,
|
|
||||||
role VARCHAR(50) NOT NULL DEFAULT 'coordinator',
|
|
||||||
provider VARCHAR(50) NOT NULL DEFAULT 'google',
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
last_sign_in_at TIMESTAMP WITH TIME ZONE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Indexes for performance
|
|
||||||
CREATE INDEX idx_users_email ON users(email);
|
|
||||||
CREATE INDEX idx_users_role ON users(role);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Role Transition Guidelines
|
|
||||||
|
|
||||||
### Promoting Users
|
|
||||||
1. **Coordinator → Administrator**
|
|
||||||
- Grants full system access
|
|
||||||
- Can manage other users
|
|
||||||
- Access to system settings
|
|
||||||
- Should be limited to trusted personnel
|
|
||||||
|
|
||||||
2. **Driver → Coordinator**
|
|
||||||
- Grants VIP and schedule management
|
|
||||||
- Can assign other drivers
|
|
||||||
- Suitable for supervisory roles
|
|
||||||
|
|
||||||
### Demoting Users
|
|
||||||
1. **Administrator → Coordinator**
|
|
||||||
- Removes user management access
|
|
||||||
- Retains operational capabilities
|
|
||||||
- Cannot access system settings
|
|
||||||
|
|
||||||
2. **Coordinator → Driver**
|
|
||||||
- Removes management capabilities
|
|
||||||
- Retains view and status update access
|
|
||||||
- Suitable for field personnel
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### 1. Principle of Least Privilege
|
|
||||||
- Users should have minimum permissions necessary for their role
|
|
||||||
- Regular review of user roles and permissions
|
|
||||||
- Temporary elevation should be avoided
|
|
||||||
|
|
||||||
### 2. Role Assignment Strategy
|
|
||||||
- **Administrators**: IT staff, senior management (limit to 2-3 users)
|
|
||||||
- **Coordinators**: Operations staff, event coordinators (primary users)
|
|
||||||
- **Drivers**: Field personnel, transportation staff
|
|
||||||
|
|
||||||
### 3. Security Considerations
|
|
||||||
- Regular audit of user access logs
|
|
||||||
- Monitor for privilege escalation attempts
|
|
||||||
- Implement session timeouts for sensitive operations
|
|
||||||
- Use HTTPS for all authentication flows
|
|
||||||
|
|
||||||
### 4. Emergency Access
|
|
||||||
- Maintain at least one administrator account
|
|
||||||
- Document emergency access procedures
|
|
||||||
- Consider backup authentication methods
|
|
||||||
|
|
||||||
## API Security Features
|
|
||||||
|
|
||||||
### 1. Token Validation
|
|
||||||
```typescript
|
|
||||||
export function requireAuth(req: Request, res: Response, next: NextFunction) {
|
|
||||||
const authHeader = req.headers.authorization;
|
|
||||||
|
|
||||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
||||||
return res.status(401).json({ error: 'No token provided' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = authHeader.substring(7);
|
|
||||||
const user = verifyToken(token);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return res.status(401).json({ error: 'Invalid token' });
|
|
||||||
}
|
|
||||||
|
|
||||||
(req as any).user = user;
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Role Validation
|
|
||||||
```typescript
|
|
||||||
export function requireRole(roles: string[]) {
|
|
||||||
return (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
const user = (req as any).user;
|
|
||||||
|
|
||||||
if (!user || !roles.includes(user.role)) {
|
|
||||||
return res.status(403).json({ error: 'Insufficient permissions' });
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Monitoring and Auditing
|
|
||||||
|
|
||||||
### 1. User Activity Logging
|
|
||||||
- Track user login/logout events
|
|
||||||
- Log role changes and who made them
|
|
||||||
- Monitor sensitive operations (user deletion, role changes)
|
|
||||||
|
|
||||||
### 2. Access Attempt Monitoring
|
|
||||||
- Failed authentication attempts
|
|
||||||
- Unauthorized access attempts
|
|
||||||
- Privilege escalation attempts
|
|
||||||
|
|
||||||
### 3. Regular Security Reviews
|
|
||||||
- Quarterly review of user roles
|
|
||||||
- Annual security audit
|
|
||||||
- Regular password/token rotation
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
### 1. Granular Permissions
|
|
||||||
- Department-based access control
|
|
||||||
- Resource-specific permissions
|
|
||||||
- Time-based access restrictions
|
|
||||||
|
|
||||||
### 2. Advanced Security Features
|
|
||||||
- Multi-factor authentication
|
|
||||||
- IP-based access restrictions
|
|
||||||
- Session management improvements
|
|
||||||
|
|
||||||
### 3. Audit Trail
|
|
||||||
- Comprehensive activity logging
|
|
||||||
- Change history tracking
|
|
||||||
- Compliance reporting
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Reference
|
|
||||||
|
|
||||||
| Feature | Administrator | Coordinator | Driver |
|
|
||||||
|---------|--------------|-------------|--------|
|
|
||||||
| User Management | ✅ | ❌ | ❌ |
|
|
||||||
| VIP CRUD | ✅ | ✅ | ❌ |
|
|
||||||
| Driver CRUD | ✅ | ✅ | ❌ |
|
|
||||||
| Schedule CRUD | ✅ | ✅ | ❌ |
|
|
||||||
| Status Updates | ✅ | ✅ | ✅ |
|
|
||||||
| View Data | ✅ | ✅ | ✅ |
|
|
||||||
| System Settings | ✅ | ❌ | ❌ |
|
|
||||||
| Flight Tracking | ✅ | ✅ | ❌ |
|
|
||||||
|
|
||||||
**Last Updated**: June 2, 2025
|
|
||||||
**Version**: 1.0
|
|
||||||
179
SIMPLE_DEPLOY.md
179
SIMPLE_DEPLOY.md
@@ -1,179 +0,0 @@
|
|||||||
# VIP Coordinator - Simple Digital Ocean Deployment
|
|
||||||
|
|
||||||
This is a streamlined deployment script designed specifically for clean Digital Ocean Docker droplets.
|
|
||||||
|
|
||||||
## 🚀 Quick Start
|
|
||||||
|
|
||||||
1. **Upload the script** to your Digital Ocean droplet:
|
|
||||||
```bash
|
|
||||||
wget https://raw.githubusercontent.com/your-repo/vip-coordinator/main/simple-deploy.sh
|
|
||||||
chmod +x simple-deploy.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Run the deployment**:
|
|
||||||
```bash
|
|
||||||
./simple-deploy.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Follow the prompts** to configure:
|
|
||||||
- Your domain name (e.g., `mysite.com`)
|
|
||||||
- API subdomain (e.g., `api.mysite.com`)
|
|
||||||
- Email for SSL certificates
|
|
||||||
- Google OAuth credentials
|
|
||||||
- SSL certificate setup (optional)
|
|
||||||
|
|
||||||
## 📋 What It Does
|
|
||||||
|
|
||||||
### ✅ **Automatic Setup**
|
|
||||||
- Creates Docker Compose configuration using v2 syntax
|
|
||||||
- Generates secure random passwords
|
|
||||||
- Sets up environment variables
|
|
||||||
- Creates management scripts
|
|
||||||
|
|
||||||
### ✅ **SSL Certificate Automation** (Optional)
|
|
||||||
- Uses official certbot Docker container
|
|
||||||
- Webroot validation method
|
|
||||||
- Generates nginx SSL configuration
|
|
||||||
- Sets up automatic renewal script
|
|
||||||
|
|
||||||
### ✅ **Generated Files**
|
|
||||||
- `.env` - Environment configuration
|
|
||||||
- `docker-compose.yml` - Docker services
|
|
||||||
- `start.sh` - Start the application
|
|
||||||
- `stop.sh` - Stop the application
|
|
||||||
- `status.sh` - Check application status
|
|
||||||
- `nginx-ssl.conf` - SSL nginx configuration (if SSL enabled)
|
|
||||||
- `renew-ssl.sh` - Certificate renewal script (if SSL enabled)
|
|
||||||
|
|
||||||
## 🔧 Requirements
|
|
||||||
|
|
||||||
### **Digital Ocean Droplet**
|
|
||||||
- Ubuntu 20.04+ or similar
|
|
||||||
- Docker and Docker Compose v2 installed
|
|
||||||
- Ports 80, 443, and 3000 open
|
|
||||||
|
|
||||||
### **Domain Setup**
|
|
||||||
- Domain pointing to your droplet IP
|
|
||||||
- API subdomain pointing to your droplet IP
|
|
||||||
- DNS propagated (check with `nslookup yourdomain.com`)
|
|
||||||
|
|
||||||
### **Google OAuth**
|
|
||||||
- Google Cloud Console project
|
|
||||||
- OAuth 2.0 Client ID and Secret
|
|
||||||
- Redirect URI configured
|
|
||||||
|
|
||||||
## 🌐 Access URLs
|
|
||||||
|
|
||||||
After deployment:
|
|
||||||
- **Frontend**: `https://yourdomain.com` (or `http://` if no SSL)
|
|
||||||
- **Backend API**: `https://api.yourdomain.com` (or `http://` if no SSL)
|
|
||||||
|
|
||||||
## 🔒 SSL Certificate Setup
|
|
||||||
|
|
||||||
If you choose SSL during setup:
|
|
||||||
|
|
||||||
1. **Automatic Generation**: Uses Let's Encrypt with certbot Docker
|
|
||||||
2. **Nginx Configuration**: Generated automatically
|
|
||||||
3. **Manual Steps**:
|
|
||||||
```bash
|
|
||||||
# Install nginx
|
|
||||||
apt update && apt install nginx
|
|
||||||
|
|
||||||
# Copy SSL configuration
|
|
||||||
cp nginx-ssl.conf /etc/nginx/sites-available/vip-coordinator
|
|
||||||
ln -s /etc/nginx/sites-available/vip-coordinator /etc/nginx/sites-enabled/
|
|
||||||
rm /etc/nginx/sites-enabled/default
|
|
||||||
|
|
||||||
# Test and restart
|
|
||||||
nginx -t
|
|
||||||
systemctl restart nginx
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Auto-Renewal**: Set up cron job
|
|
||||||
```bash
|
|
||||||
echo "0 3 1 * * $(pwd)/renew-ssl.sh" | crontab -
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🛠️ Management Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start the application
|
|
||||||
./start.sh
|
|
||||||
|
|
||||||
# Stop the application
|
|
||||||
./stop.sh
|
|
||||||
|
|
||||||
# Check status
|
|
||||||
./status.sh
|
|
||||||
|
|
||||||
# View logs
|
|
||||||
docker compose logs -f
|
|
||||||
|
|
||||||
# Update to latest version
|
|
||||||
docker compose pull
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔑 Important Credentials
|
|
||||||
|
|
||||||
The script generates and displays:
|
|
||||||
- **Admin Password**: For emergency access
|
|
||||||
- **Database Password**: For PostgreSQL
|
|
||||||
- **Keep these secure!**
|
|
||||||
|
|
||||||
## 🎯 First Time Login
|
|
||||||
|
|
||||||
1. Open your frontend URL
|
|
||||||
2. Click "Continue with Google"
|
|
||||||
3. The first user becomes the administrator
|
|
||||||
4. Use the admin password if needed
|
|
||||||
|
|
||||||
## 🐛 Troubleshooting
|
|
||||||
|
|
||||||
### **Port Conflicts**
|
|
||||||
- Uses standard ports (80, 443, 3000)
|
|
||||||
- Ensure no other services are running on these ports
|
|
||||||
|
|
||||||
### **SSL Issues**
|
|
||||||
- Verify domain DNS is pointing to your server
|
|
||||||
- Check firewall allows ports 80 and 443
|
|
||||||
- Ensure no other web server is running
|
|
||||||
|
|
||||||
### **Docker Issues**
|
|
||||||
```bash
|
|
||||||
# Check Docker version (should be v2)
|
|
||||||
docker compose version
|
|
||||||
|
|
||||||
# Check container status
|
|
||||||
docker compose ps
|
|
||||||
|
|
||||||
# View logs
|
|
||||||
docker compose logs backend
|
|
||||||
docker compose logs frontend
|
|
||||||
```
|
|
||||||
|
|
||||||
### **OAuth Issues**
|
|
||||||
- Verify redirect URI in Google Console matches exactly
|
|
||||||
- Check Client ID and Secret are correct
|
|
||||||
- Ensure domain is accessible from internet
|
|
||||||
|
|
||||||
## 📞 Support
|
|
||||||
|
|
||||||
If you encounter issues:
|
|
||||||
|
|
||||||
1. Check `./status.sh` for service health
|
|
||||||
2. Review logs with `docker compose logs`
|
|
||||||
3. Verify domain DNS resolution
|
|
||||||
4. Ensure all ports are accessible
|
|
||||||
|
|
||||||
## 🎉 Success!
|
|
||||||
|
|
||||||
Your VIP Coordinator should now be running with:
|
|
||||||
- ✅ Google OAuth authentication
|
|
||||||
- ✅ Mobile-friendly interface
|
|
||||||
- ✅ Real-time scheduling
|
|
||||||
- ✅ User management
|
|
||||||
- ✅ SSL encryption (if enabled)
|
|
||||||
- ✅ Automatic updates from Docker Hub
|
|
||||||
|
|
||||||
Perfect for Digital Ocean droplet deployments!
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
# Simple OAuth2 Setup Guide
|
|
||||||
|
|
||||||
## ✅ What's Working Now
|
|
||||||
|
|
||||||
The VIP Coordinator now has a **much simpler** OAuth2 implementation that actually works! Here's what I've done:
|
|
||||||
|
|
||||||
### 🔧 Simplified Implementation
|
|
||||||
- **Removed complex Passport.js** - No more confusing middleware chains
|
|
||||||
- **Simple JWT tokens** - Clean, stateless authentication
|
|
||||||
- **Direct Google API calls** - Using fetch instead of heavy libraries
|
|
||||||
- **Clean error handling** - Easy to debug and understand
|
|
||||||
|
|
||||||
### 📁 New Files Created
|
|
||||||
- `backend/src/config/simpleAuth.ts` - Core auth functions
|
|
||||||
- `backend/src/routes/simpleAuth.ts` - Auth endpoints
|
|
||||||
|
|
||||||
## 🚀 How to Set Up Google OAuth2
|
|
||||||
|
|
||||||
### Step 1: Get Google OAuth2 Credentials
|
|
||||||
|
|
||||||
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
|
|
||||||
2. Create a new project or select existing one
|
|
||||||
3. Enable the Google+ API
|
|
||||||
4. Go to "Credentials" → "Create Credentials" → "OAuth 2.0 Client IDs"
|
|
||||||
5. Set application type to "Web application"
|
|
||||||
6. Add these redirect URIs:
|
|
||||||
- `http://localhost:3000/auth/google/callback`
|
|
||||||
- `http://localhost:5173/auth/callback`
|
|
||||||
|
|
||||||
### Step 2: Update Environment Variables
|
|
||||||
|
|
||||||
Edit `backend/.env` and add:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Google OAuth2 Settings
|
|
||||||
GOOGLE_CLIENT_ID=your_google_client_id_here
|
|
||||||
GOOGLE_CLIENT_SECRET=your_google_client_secret_here
|
|
||||||
GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google/callback
|
|
||||||
|
|
||||||
# JWT Secret (change this!)
|
|
||||||
JWT_SECRET=your-super-secret-jwt-key-change-this
|
|
||||||
|
|
||||||
# Frontend URL
|
|
||||||
FRONTEND_URL=http://localhost:5173
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Test the Setup
|
|
||||||
|
|
||||||
1. **Start the application:**
|
|
||||||
```bash
|
|
||||||
docker-compose -f docker-compose.dev.yml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Test auth endpoints:**
|
|
||||||
```bash
|
|
||||||
# Check if backend is running
|
|
||||||
curl http://localhost:3000/api/health
|
|
||||||
|
|
||||||
# Check auth status (should return {"authenticated":false})
|
|
||||||
curl http://localhost:3000/auth/status
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Test Google OAuth flow:**
|
|
||||||
- Visit: `http://localhost:3000/auth/google`
|
|
||||||
- Should redirect to Google login
|
|
||||||
- After login, redirects back with JWT token
|
|
||||||
|
|
||||||
## 🔄 How It Works
|
|
||||||
|
|
||||||
### Simple Flow:
|
|
||||||
1. User clicks "Login with Google"
|
|
||||||
2. Redirects to `http://localhost:3000/auth/google`
|
|
||||||
3. Backend redirects to Google OAuth
|
|
||||||
4. Google redirects back to `/auth/google/callback`
|
|
||||||
5. Backend exchanges code for user info
|
|
||||||
6. Backend creates JWT token
|
|
||||||
7. Frontend receives token and stores it
|
|
||||||
|
|
||||||
### API Endpoints:
|
|
||||||
- `GET /auth/google` - Start OAuth flow
|
|
||||||
- `GET /auth/google/callback` - Handle OAuth callback
|
|
||||||
- `GET /auth/status` - Check if user is authenticated
|
|
||||||
- `GET /auth/me` - Get current user info (requires auth)
|
|
||||||
- `POST /auth/logout` - Logout (client-side token removal)
|
|
||||||
|
|
||||||
## 🛠️ Frontend Integration
|
|
||||||
|
|
||||||
The frontend needs to:
|
|
||||||
|
|
||||||
1. **Handle the OAuth callback:**
|
|
||||||
```javascript
|
|
||||||
// In your React app, handle the callback route
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const token = urlParams.get('token');
|
|
||||||
if (token) {
|
|
||||||
localStorage.setItem('authToken', token);
|
|
||||||
// Redirect to dashboard
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Include token in API requests:**
|
|
||||||
```javascript
|
|
||||||
const token = localStorage.getItem('authToken');
|
|
||||||
fetch('/api/vips', {
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Add login button:**
|
|
||||||
```javascript
|
|
||||||
<button onClick={() => window.location.href = '/auth/google'}>
|
|
||||||
Login with Google
|
|
||||||
</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 Benefits of This Approach
|
|
||||||
|
|
||||||
- **Simple to understand** - No complex middleware
|
|
||||||
- **Easy to debug** - Clear error messages
|
|
||||||
- **Lightweight** - Fewer dependencies
|
|
||||||
- **Secure** - Uses standard JWT tokens
|
|
||||||
- **Flexible** - Easy to extend or modify
|
|
||||||
|
|
||||||
## 🔍 Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues:
|
|
||||||
|
|
||||||
1. **"OAuth not configured" error:**
|
|
||||||
- Make sure `GOOGLE_CLIENT_ID` is set in `.env`
|
|
||||||
- Restart the backend after changing `.env`
|
|
||||||
|
|
||||||
2. **"Invalid redirect URI" error:**
|
|
||||||
- Check Google Console redirect URIs match exactly
|
|
||||||
- Make sure no trailing slashes
|
|
||||||
|
|
||||||
3. **Token verification fails:**
|
|
||||||
- Check `JWT_SECRET` is set and consistent
|
|
||||||
- Make sure token is being sent with `Bearer ` prefix
|
|
||||||
|
|
||||||
### Debug Commands:
|
|
||||||
```bash
|
|
||||||
# Check backend logs
|
|
||||||
docker-compose -f docker-compose.dev.yml logs backend
|
|
||||||
|
|
||||||
# Check if environment variables are loaded
|
|
||||||
docker exec vip-coordinator-backend-1 env | grep GOOGLE
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎉 Next Steps
|
|
||||||
|
|
||||||
1. Set up your Google OAuth2 credentials
|
|
||||||
2. Update the `.env` file
|
|
||||||
3. Test the login flow
|
|
||||||
4. Integrate with the frontend
|
|
||||||
5. Customize user roles and permissions
|
|
||||||
|
|
||||||
The authentication system is now much simpler and actually works! 🚀
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
# 🔐 Simple User Management System
|
|
||||||
|
|
||||||
## ✅ What We Built
|
|
||||||
|
|
||||||
A **lightweight, persistent user management system** that extends your existing OAuth2 authentication using your existing JSON data storage.
|
|
||||||
|
|
||||||
## 🎯 Key Features
|
|
||||||
|
|
||||||
### ✅ **Persistent Storage**
|
|
||||||
- Uses your existing JSON data file storage
|
|
||||||
- No third-party services required
|
|
||||||
- Completely self-contained
|
|
||||||
- Users preserved across server restarts
|
|
||||||
|
|
||||||
### 🔧 **New API Endpoints**
|
|
||||||
- `GET /auth/users` - List all users (admin only)
|
|
||||||
- `PATCH /auth/users/:email/role` - Update user role (admin only)
|
|
||||||
- `DELETE /auth/users/:email` - Delete user (admin only)
|
|
||||||
- `GET /auth/users/:email` - Get specific user (admin only)
|
|
||||||
|
|
||||||
### 🎨 **Admin Interface**
|
|
||||||
- Beautiful React component for user management
|
|
||||||
- Role-based access control (admin only)
|
|
||||||
- Change user roles with dropdown
|
|
||||||
- Delete users with confirmation
|
|
||||||
- Responsive design
|
|
||||||
|
|
||||||
## 🚀 How It Works
|
|
||||||
|
|
||||||
### 1. **User Registration**
|
|
||||||
- First user becomes administrator automatically
|
|
||||||
- Subsequent users become coordinators by default
|
|
||||||
- All via your existing Google OAuth flow
|
|
||||||
|
|
||||||
### 2. **Role Management**
|
|
||||||
- **Administrator:** Full access including user management
|
|
||||||
- **Coordinator:** Can manage VIPs, drivers, schedules
|
|
||||||
- **Driver:** Can view assigned schedules
|
|
||||||
|
|
||||||
### 3. **User Management Interface**
|
|
||||||
- Only administrators can access user management
|
|
||||||
- View all users with profile pictures
|
|
||||||
- Change roles instantly
|
|
||||||
- Delete users (except yourself)
|
|
||||||
- Clear role descriptions
|
|
||||||
|
|
||||||
## 📋 Usage
|
|
||||||
|
|
||||||
### For Administrators:
|
|
||||||
1. Login with Google (first user becomes admin)
|
|
||||||
2. Access user management interface
|
|
||||||
3. View all registered users
|
|
||||||
4. Change user roles as needed
|
|
||||||
5. Remove users if necessary
|
|
||||||
|
|
||||||
### API Examples:
|
|
||||||
```bash
|
|
||||||
# List all users (admin only)
|
|
||||||
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
|
||||||
http://localhost:3000/auth/users
|
|
||||||
|
|
||||||
# Update user role
|
|
||||||
curl -X PATCH \
|
|
||||||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"role": "administrator"}' \
|
|
||||||
http://localhost:3000/auth/users/user@example.com/role
|
|
||||||
|
|
||||||
# Delete user
|
|
||||||
curl -X DELETE \
|
|
||||||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
|
||||||
http://localhost:3000/auth/users/user@example.com
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔒 Security Features
|
|
||||||
|
|
||||||
- **Role-based access control** - Only admins can manage users
|
|
||||||
- **Self-deletion prevention** - Admins can't delete themselves
|
|
||||||
- **JWT token validation** - All endpoints require authentication
|
|
||||||
- **Input validation** - Role validation on updates
|
|
||||||
|
|
||||||
## ✅ Important Notes
|
|
||||||
|
|
||||||
### **Persistent File Storage**
|
|
||||||
- Users are stored in your existing JSON data file
|
|
||||||
- **Users are preserved across server restarts**
|
|
||||||
- Perfect for development and production
|
|
||||||
- Integrates seamlessly with your existing data storage
|
|
||||||
|
|
||||||
### **Simple & Lightweight**
|
|
||||||
- No external dependencies
|
|
||||||
- No complex setup required
|
|
||||||
- Works with your existing OAuth system
|
|
||||||
- Easy to understand and modify
|
|
||||||
|
|
||||||
## 🎯 Perfect For
|
|
||||||
|
|
||||||
- **Development and production environments**
|
|
||||||
- **Small to medium teams** (< 100 users)
|
|
||||||
- **Self-hosted applications**
|
|
||||||
- **When you want full control** over your user data
|
|
||||||
- **Simple, reliable user management**
|
|
||||||
|
|
||||||
## 🔄 Future Enhancements
|
|
||||||
|
|
||||||
You can easily extend this to:
|
|
||||||
- Migrate to your existing PostgreSQL database if needed
|
|
||||||
- Add user metadata and profiles
|
|
||||||
- Implement audit logging
|
|
||||||
- Add email notifications
|
|
||||||
- Create user groups/teams
|
|
||||||
- Add Redis caching for better performance
|
|
||||||
|
|
||||||
## 🎉 Ready to Use!
|
|
||||||
|
|
||||||
Your user management system is now complete and ready to use:
|
|
||||||
|
|
||||||
1. **Restart your backend** to pick up the new endpoints
|
|
||||||
2. **Login as the first user** to become administrator
|
|
||||||
3. **Access user management** through your admin interface
|
|
||||||
4. **Manage users** with the beautiful interface we built
|
|
||||||
|
|
||||||
**✅ Persistent storage:** All user data is automatically saved to your existing JSON data file and preserved across server restarts!
|
|
||||||
|
|
||||||
No external dependencies, no complex setup - just simple, effective, persistent user management! 🚀
|
|
||||||
@@ -1,258 +0,0 @@
|
|||||||
# 🚀 VIP Coordinator - Standalone Installation
|
|
||||||
|
|
||||||
Deploy VIP Coordinator directly from Docker Hub - **No GitHub required!**
|
|
||||||
|
|
||||||
## 📦 What You Get
|
|
||||||
|
|
||||||
- ✅ **Pre-built Docker images** from Docker Hub
|
|
||||||
- ✅ **Interactive setup script** that configures everything
|
|
||||||
- ✅ **Complete deployment** in under 5 minutes
|
|
||||||
- ✅ **No source code needed** - just Docker containers
|
|
||||||
|
|
||||||
## 🔧 Prerequisites
|
|
||||||
|
|
||||||
**Ubuntu/Linux:**
|
|
||||||
```bash
|
|
||||||
# Install Docker and Docker Compose
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install docker.io docker-compose
|
|
||||||
sudo usermod -aG docker $USER
|
|
||||||
# Log out and back in, or run: newgrp docker
|
|
||||||
```
|
|
||||||
|
|
||||||
**Other Systems:**
|
|
||||||
- Install Docker Desktop from https://docker.com/get-started
|
|
||||||
|
|
||||||
## 🚀 Installation Methods
|
|
||||||
|
|
||||||
### Method 1: Direct Download (Recommended)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create directory
|
|
||||||
mkdir vip-coordinator
|
|
||||||
cd vip-coordinator
|
|
||||||
|
|
||||||
# Download the standalone setup script
|
|
||||||
curl -O https://your-domain.com/standalone-setup.sh
|
|
||||||
|
|
||||||
# Make executable and run
|
|
||||||
chmod +x standalone-setup.sh
|
|
||||||
./standalone-setup.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Method 2: Copy-Paste Installation
|
|
||||||
|
|
||||||
If you can't download the script, you can create it manually:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create directory
|
|
||||||
mkdir vip-coordinator
|
|
||||||
cd vip-coordinator
|
|
||||||
|
|
||||||
# Create the setup script (copy the content from standalone-setup.sh)
|
|
||||||
nano standalone-setup.sh
|
|
||||||
|
|
||||||
# Make executable and run
|
|
||||||
chmod +x standalone-setup.sh
|
|
||||||
./standalone-setup.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Method 3: Manual Docker Hub Deployment
|
|
||||||
|
|
||||||
If you prefer to set up manually:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create directory
|
|
||||||
mkdir vip-coordinator
|
|
||||||
cd vip-coordinator
|
|
||||||
|
|
||||||
# Create docker-compose.yml
|
|
||||||
cat > docker-compose.yml << 'EOF'
|
|
||||||
version: '3.8'
|
|
||||||
services:
|
|
||||||
db:
|
|
||||||
image: postgres:15
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: vip_coordinator
|
|
||||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
|
||||||
volumes:
|
|
||||||
- postgres-data:/var/lib/postgresql/data
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: redis:7
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
backend:
|
|
||||||
image: t72chevy/vip-coordinator:backend-latest
|
|
||||||
environment:
|
|
||||||
DATABASE_URL: postgresql://postgres:${DB_PASSWORD}@db:5432/vip_coordinator
|
|
||||||
REDIS_URL: redis://redis:6379
|
|
||||||
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
|
|
||||||
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
|
|
||||||
GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI}
|
|
||||||
FRONTEND_URL: ${FRONTEND_URL}
|
|
||||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
|
|
||||||
PORT: 3000
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
depends_on:
|
|
||||||
db:
|
|
||||||
condition: service_healthy
|
|
||||||
redis:
|
|
||||||
condition: service_healthy
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
frontend:
|
|
||||||
image: t72chevy/vip-coordinator:frontend-latest
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
depends_on:
|
|
||||||
- backend
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres-data:
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Create .env file with your configuration
|
|
||||||
nano .env
|
|
||||||
|
|
||||||
# Start the application
|
|
||||||
docker-compose pull
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 What the Setup Script Does
|
|
||||||
|
|
||||||
1. **Checks Prerequisites**: Verifies Docker and Docker Compose are installed
|
|
||||||
2. **Interactive Configuration**: Asks for your deployment preferences
|
|
||||||
3. **Generates Files**: Creates all necessary configuration files
|
|
||||||
4. **Pulls Images**: Downloads pre-built images from Docker Hub
|
|
||||||
5. **Creates Management Scripts**: Provides easy start/stop/update commands
|
|
||||||
|
|
||||||
## 📋 Configuration Options
|
|
||||||
|
|
||||||
The script will ask you for:
|
|
||||||
|
|
||||||
- **Deployment Type**: Local development or production
|
|
||||||
- **Domain Settings**: Your domain names (for production)
|
|
||||||
- **Security**: Generates secure passwords automatically
|
|
||||||
- **Google OAuth**: Your Google Cloud Console credentials
|
|
||||||
- **Optional**: AviationStack API key for flight data
|
|
||||||
|
|
||||||
## 🔐 Google OAuth Setup
|
|
||||||
|
|
||||||
You'll need to set up Google OAuth (the script guides you through this):
|
|
||||||
|
|
||||||
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
|
|
||||||
2. Create a new project
|
|
||||||
3. Enable Google+ API
|
|
||||||
4. Create OAuth 2.0 Client ID
|
|
||||||
5. Add redirect URI (provided by the script)
|
|
||||||
6. Copy Client ID and Secret
|
|
||||||
|
|
||||||
## 📦 Docker Hub Images Used
|
|
||||||
|
|
||||||
This deployment uses these pre-built images:
|
|
||||||
|
|
||||||
- **`t72chevy/vip-coordinator:backend-latest`** (404MB)
|
|
||||||
- Complete Node.js backend with OAuth fixes
|
|
||||||
- PostgreSQL and Redis integration
|
|
||||||
- Health checks and monitoring
|
|
||||||
|
|
||||||
- **`t72chevy/vip-coordinator:frontend-latest`** (74.8MB)
|
|
||||||
- React frontend with mobile OAuth fixes
|
|
||||||
- Nginx web server
|
|
||||||
- Production-optimized build
|
|
||||||
|
|
||||||
- **`postgres:15`** - Database
|
|
||||||
- **`redis:7`** - Cache and sessions
|
|
||||||
|
|
||||||
## 🚀 After Installation
|
|
||||||
|
|
||||||
Once setup completes, you'll have these commands:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./start.sh # Start VIP Coordinator
|
|
||||||
./stop.sh # Stop VIP Coordinator
|
|
||||||
./update.sh # Update to latest Docker Hub images
|
|
||||||
./status.sh # Check system status
|
|
||||||
./logs.sh # View application logs
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🌐 Access Your Application
|
|
||||||
|
|
||||||
- **Local**: http://localhost
|
|
||||||
- **Production**: https://your-domain.com
|
|
||||||
|
|
||||||
## 🔄 Updates
|
|
||||||
|
|
||||||
To update to the latest version:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./update.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This pulls the latest images from Docker Hub and restarts the services.
|
|
||||||
|
|
||||||
## 📱 Mobile Support
|
|
||||||
|
|
||||||
This deployment includes fixes for mobile OAuth authentication:
|
|
||||||
- ✅ Mobile users can now log in successfully
|
|
||||||
- ✅ Proper API endpoint configuration
|
|
||||||
- ✅ Enhanced error handling
|
|
||||||
|
|
||||||
## 🛠️ Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
**Docker permission denied:**
|
|
||||||
```bash
|
|
||||||
sudo usermod -aG docker $USER
|
|
||||||
newgrp docker
|
|
||||||
```
|
|
||||||
|
|
||||||
**Port conflicts:**
|
|
||||||
```bash
|
|
||||||
# Check what's using ports 80 and 3000
|
|
||||||
sudo netstat -tulpn | grep :80
|
|
||||||
sudo netstat -tulpn | grep :3000
|
|
||||||
```
|
|
||||||
|
|
||||||
**Service not starting:**
|
|
||||||
```bash
|
|
||||||
./status.sh # Check status
|
|
||||||
./logs.sh # View logs
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📞 Distribution
|
|
||||||
|
|
||||||
To share VIP Coordinator with others:
|
|
||||||
|
|
||||||
1. **Share the setup script**: Give them `standalone-setup.sh`
|
|
||||||
2. **Share this guide**: Include `STANDALONE_INSTALL.md`
|
|
||||||
3. **No GitHub needed**: Everything pulls from Docker Hub
|
|
||||||
|
|
||||||
## 🎉 Benefits of Standalone Deployment
|
|
||||||
|
|
||||||
- ✅ **No source code required**
|
|
||||||
- ✅ **No GitHub repository needed**
|
|
||||||
- ✅ **Pre-built, tested images**
|
|
||||||
- ✅ **Automatic updates from Docker Hub**
|
|
||||||
- ✅ **Cross-platform compatibility**
|
|
||||||
- ✅ **Production-ready configuration**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**🚀 Get VIP Coordinator running in under 5 minutes with just Docker and one script!**
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# Supabase Auth Migration Plan
|
|
||||||
|
|
||||||
## Why Supabase?
|
|
||||||
- ✅ **Self-hosted Docker** - Full control, no external dependencies
|
|
||||||
- ✅ **Built-in Auth** - JWT tokens, social login (Google), user management
|
|
||||||
- ✅ **Simple API** - Similar to Auth0 but simpler
|
|
||||||
- ✅ **PostgreSQL** - Uses your existing database
|
|
||||||
- ✅ **React SDK** - Easy frontend integration
|
|
||||||
- ✅ **No Tailwind issues** - Doesn't affect frontend build
|
|
||||||
|
|
||||||
## Migration Steps
|
|
||||||
|
|
||||||
### 1. Add Supabase to Docker Compose
|
|
||||||
- Add Supabase services (Auth API, PostgREST, GoTrue)
|
|
||||||
- Configure to use existing PostgreSQL database
|
|
||||||
- Set up environment variables
|
|
||||||
|
|
||||||
### 2. Update Backend
|
|
||||||
- Replace Auth0 SDK with Supabase client
|
|
||||||
- Update JWT validation to use Supabase keys
|
|
||||||
- Simplify auth routes
|
|
||||||
|
|
||||||
### 3. Update Frontend
|
|
||||||
- Replace `@auth0/auth0-react` with `@supabase/supabase-js`
|
|
||||||
- Update Login component
|
|
||||||
- Update App.tsx auth logic
|
|
||||||
|
|
||||||
### 4. Database Migration
|
|
||||||
- Keep existing user table structure
|
|
||||||
- Add Supabase auth tables (if needed)
|
|
||||||
- Map `auth0_sub` → `supabase_user_id`
|
|
||||||
|
|
||||||
## Estimated Time: 30-45 minutes
|
|
||||||
@@ -1,281 +0,0 @@
|
|||||||
# 🐧 VIP Coordinator - Ubuntu Installation Guide
|
|
||||||
|
|
||||||
Deploy VIP Coordinator on Ubuntu in just a few commands!
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
First, ensure Docker and Docker Compose are installed on your Ubuntu system:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Update package index
|
|
||||||
sudo apt update
|
|
||||||
|
|
||||||
# Install Docker
|
|
||||||
sudo apt install -y docker.io
|
|
||||||
|
|
||||||
# Install Docker Compose
|
|
||||||
sudo apt install -y docker-compose
|
|
||||||
|
|
||||||
# Add your user to docker group (to run docker without sudo)
|
|
||||||
sudo usermod -aG docker $USER
|
|
||||||
|
|
||||||
# Log out and back in, or run:
|
|
||||||
newgrp docker
|
|
||||||
|
|
||||||
# Verify installation
|
|
||||||
docker --version
|
|
||||||
docker-compose --version
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick Install (One Command)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Download and run the interactive setup script
|
|
||||||
curl -sSL https://raw.githubusercontent.com/your-repo/vip-coordinator/main/setup.sh | bash
|
|
||||||
```
|
|
||||||
|
|
||||||
## Manual Installation
|
|
||||||
|
|
||||||
If you prefer to download and inspect the script first:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create a directory for VIP Coordinator
|
|
||||||
mkdir vip-coordinator
|
|
||||||
cd vip-coordinator
|
|
||||||
|
|
||||||
# Download the setup script
|
|
||||||
wget https://raw.githubusercontent.com/your-repo/vip-coordinator/main/setup.sh
|
|
||||||
|
|
||||||
# Make it executable
|
|
||||||
chmod +x setup.sh
|
|
||||||
|
|
||||||
# Run the interactive setup
|
|
||||||
./setup.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## What the Setup Script Does
|
|
||||||
|
|
||||||
The script will interactively ask you for:
|
|
||||||
|
|
||||||
1. **Deployment Type**: Local development or production with custom domain
|
|
||||||
2. **Domain Configuration**: Your domain names (for production)
|
|
||||||
3. **Security**: Generates secure passwords or lets you set custom ones
|
|
||||||
4. **Google OAuth**: Your Google Cloud Console credentials
|
|
||||||
5. **Optional**: AviationStack API key for flight data
|
|
||||||
|
|
||||||
Then it automatically generates:
|
|
||||||
|
|
||||||
- ✅ `.env` - Your configuration file
|
|
||||||
- ✅ `docker-compose.yml` - Docker services configuration
|
|
||||||
- ✅ `start.sh` - Script to start VIP Coordinator
|
|
||||||
- ✅ `stop.sh` - Script to stop VIP Coordinator
|
|
||||||
- ✅ `update.sh` - Script to update to latest version
|
|
||||||
- ✅ `README.md` - Your deployment documentation
|
|
||||||
- ✅ `nginx.conf` - Production nginx config (if needed)
|
|
||||||
|
|
||||||
## After Setup
|
|
||||||
|
|
||||||
Once the setup script completes:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start VIP Coordinator
|
|
||||||
./start.sh
|
|
||||||
|
|
||||||
# Check status
|
|
||||||
docker-compose ps
|
|
||||||
|
|
||||||
# View logs
|
|
||||||
docker-compose logs
|
|
||||||
|
|
||||||
# Stop when needed
|
|
||||||
./stop.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Access Your Application
|
|
||||||
|
|
||||||
- **Local Development**: http://localhost
|
|
||||||
- **Production**: https://your-domain.com
|
|
||||||
|
|
||||||
## Google OAuth Setup
|
|
||||||
|
|
||||||
The script will guide you through setting up Google OAuth:
|
|
||||||
|
|
||||||
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
|
|
||||||
2. Create a new project or select existing
|
|
||||||
3. Enable Google+ API
|
|
||||||
4. Create OAuth 2.0 Client ID credentials
|
|
||||||
5. Add the redirect URI provided by the script
|
|
||||||
6. Copy Client ID and Secret when prompted
|
|
||||||
|
|
||||||
## Ubuntu-Specific Notes
|
|
||||||
|
|
||||||
### Firewall Configuration
|
|
||||||
|
|
||||||
If you're using UFW (Ubuntu's firewall):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# For local development
|
|
||||||
sudo ufw allow 80
|
|
||||||
sudo ufw allow 3000
|
|
||||||
|
|
||||||
# For production (if using nginx proxy)
|
|
||||||
sudo ufw allow 80
|
|
||||||
sudo ufw allow 443
|
|
||||||
sudo ufw allow 22 # SSH access
|
|
||||||
```
|
|
||||||
|
|
||||||
### Production Deployment on Ubuntu
|
|
||||||
|
|
||||||
For production deployment, the script generates an nginx configuration. To use it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install nginx
|
|
||||||
sudo apt install nginx
|
|
||||||
|
|
||||||
# Copy the generated config
|
|
||||||
sudo cp nginx.conf /etc/nginx/sites-available/vip-coordinator
|
|
||||||
|
|
||||||
# Enable the site
|
|
||||||
sudo ln -s /etc/nginx/sites-available/vip-coordinator /etc/nginx/sites-enabled/
|
|
||||||
|
|
||||||
# Remove default site
|
|
||||||
sudo rm /etc/nginx/sites-enabled/default
|
|
||||||
|
|
||||||
# Test nginx configuration
|
|
||||||
sudo nginx -t
|
|
||||||
|
|
||||||
# Restart nginx
|
|
||||||
sudo systemctl restart nginx
|
|
||||||
```
|
|
||||||
|
|
||||||
### SSL Certificates with Let's Encrypt
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install certbot
|
|
||||||
sudo apt install certbot python3-certbot-nginx
|
|
||||||
|
|
||||||
# Get certificates (replace with your domains)
|
|
||||||
sudo certbot --nginx -d yourdomain.com -d api.yourdomain.com
|
|
||||||
|
|
||||||
# Certbot will automatically update your nginx config for HTTPS
|
|
||||||
```
|
|
||||||
|
|
||||||
### System Service (Optional)
|
|
||||||
|
|
||||||
To run VIP Coordinator as a system service:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create service file
|
|
||||||
sudo tee /etc/systemd/system/vip-coordinator.service > /dev/null <<EOF
|
|
||||||
[Unit]
|
|
||||||
Description=VIP Coordinator
|
|
||||||
Requires=docker.service
|
|
||||||
After=docker.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
RemainAfterExit=yes
|
|
||||||
WorkingDirectory=/path/to/your/vip-coordinator
|
|
||||||
ExecStart=/usr/bin/docker-compose up -d
|
|
||||||
ExecStop=/usr/bin/docker-compose down
|
|
||||||
TimeoutStartSec=0
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Enable and start the service
|
|
||||||
sudo systemctl enable vip-coordinator
|
|
||||||
sudo systemctl start vip-coordinator
|
|
||||||
|
|
||||||
# Check status
|
|
||||||
sudo systemctl status vip-coordinator
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Ubuntu Issues
|
|
||||||
|
|
||||||
**Docker permission denied:**
|
|
||||||
```bash
|
|
||||||
sudo usermod -aG docker $USER
|
|
||||||
newgrp docker
|
|
||||||
```
|
|
||||||
|
|
||||||
**Port already in use:**
|
|
||||||
```bash
|
|
||||||
# Check what's using the port
|
|
||||||
sudo netstat -tulpn | grep :80
|
|
||||||
sudo netstat -tulpn | grep :3000
|
|
||||||
|
|
||||||
# Stop conflicting services
|
|
||||||
sudo systemctl stop apache2 # if Apache is running
|
|
||||||
sudo systemctl stop nginx # if nginx is running
|
|
||||||
```
|
|
||||||
|
|
||||||
**Can't connect to Docker daemon:**
|
|
||||||
```bash
|
|
||||||
# Start Docker service
|
|
||||||
sudo systemctl start docker
|
|
||||||
sudo systemctl enable docker
|
|
||||||
```
|
|
||||||
|
|
||||||
### Viewing Logs
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# All services
|
|
||||||
docker-compose logs
|
|
||||||
|
|
||||||
# Specific service
|
|
||||||
docker-compose logs backend
|
|
||||||
docker-compose logs frontend
|
|
||||||
|
|
||||||
# Follow logs in real-time
|
|
||||||
docker-compose logs -f
|
|
||||||
```
|
|
||||||
|
|
||||||
### Updating
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Update to latest version
|
|
||||||
./update.sh
|
|
||||||
|
|
||||||
# Or manually
|
|
||||||
docker-compose pull
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Optimization
|
|
||||||
|
|
||||||
For production Ubuntu servers:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Increase file limits
|
|
||||||
echo "fs.file-max = 65536" | sudo tee -a /etc/sysctl.conf
|
|
||||||
|
|
||||||
# Optimize Docker
|
|
||||||
echo '{"log-driver": "json-file", "log-opts": {"max-size": "10m", "max-file": "3"}}' | sudo tee /etc/docker/daemon.json
|
|
||||||
|
|
||||||
# Restart Docker
|
|
||||||
sudo systemctl restart docker
|
|
||||||
```
|
|
||||||
|
|
||||||
## Backup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Backup database
|
|
||||||
docker-compose exec db pg_dump -U postgres vip_coordinator > backup.sql
|
|
||||||
|
|
||||||
# Backup volumes
|
|
||||||
docker run --rm -v vip-coordinator_postgres-data:/data -v $(pwd):/backup ubuntu tar czf /backup/postgres-backup.tar.gz /data
|
|
||||||
```
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
- 📖 Full documentation: [DEPLOYMENT.md](DEPLOYMENT.md)
|
|
||||||
- 🐛 Issues: GitHub Issues
|
|
||||||
- 💬 Community: GitHub Discussions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**🎉 Your VIP Coordinator will be running on Ubuntu in under 5 minutes!**
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
# 🔐 User Management System Recommendations
|
|
||||||
|
|
||||||
## Current State Analysis
|
|
||||||
✅ **You have:** Basic OAuth2 with Google, JWT tokens, role-based access (administrator/coordinator)
|
|
||||||
❌ **You need:** Comprehensive user management, permissions, user lifecycle, admin interface
|
|
||||||
|
|
||||||
## 🏆 Top Recommendations
|
|
||||||
|
|
||||||
### 1. **Supabase Auth** (Recommended - Easy Integration)
|
|
||||||
**Why it's perfect for you:**
|
|
||||||
- Drop-in replacement for your current auth system
|
|
||||||
- Built-in user management dashboard
|
|
||||||
- Row Level Security (RLS) for fine-grained permissions
|
|
||||||
- Supports Google OAuth (you can keep your current flow)
|
|
||||||
- Real-time subscriptions
|
|
||||||
- Built-in user roles and metadata
|
|
||||||
|
|
||||||
**Integration effort:** Low (2-3 days)
|
|
||||||
```bash
|
|
||||||
npm install @supabase/supabase-js
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features you get:**
|
|
||||||
- User registration/login/logout
|
|
||||||
- Email verification
|
|
||||||
- Password reset
|
|
||||||
- User metadata and custom claims
|
|
||||||
- Admin dashboard for user management
|
|
||||||
- Real-time user presence
|
|
||||||
- Multi-factor authentication
|
|
||||||
|
|
||||||
### 2. **Auth0** (Enterprise-grade)
|
|
||||||
**Why it's great:**
|
|
||||||
- Industry standard for enterprise applications
|
|
||||||
- Extensive user management dashboard
|
|
||||||
- Advanced security features
|
|
||||||
- Supports all OAuth providers
|
|
||||||
- Fine-grained permissions and roles
|
|
||||||
- Audit logs and analytics
|
|
||||||
|
|
||||||
**Integration effort:** Medium (3-5 days)
|
|
||||||
```bash
|
|
||||||
npm install auth0 express-oauth-server
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features you get:**
|
|
||||||
- Complete user lifecycle management
|
|
||||||
- Advanced role-based access control (RBAC)
|
|
||||||
- Multi-factor authentication
|
|
||||||
- Social logins (Google, Facebook, etc.)
|
|
||||||
- Enterprise SSO
|
|
||||||
- Comprehensive admin dashboard
|
|
||||||
|
|
||||||
### 3. **Firebase Auth + Firestore** (Google Ecosystem)
|
|
||||||
**Why it fits:**
|
|
||||||
- You're already using Google OAuth
|
|
||||||
- Seamless integration with Google services
|
|
||||||
- Real-time database
|
|
||||||
- Built-in user management
|
|
||||||
- Offline support
|
|
||||||
|
|
||||||
**Integration effort:** Medium (4-6 days)
|
|
||||||
```bash
|
|
||||||
npm install firebase-admin
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. **Clerk** (Modern Developer Experience)
|
|
||||||
**Why developers love it:**
|
|
||||||
- Beautiful pre-built UI components
|
|
||||||
- Excellent TypeScript support
|
|
||||||
- Built-in user management dashboard
|
|
||||||
- Easy role and permission management
|
|
||||||
- Great documentation
|
|
||||||
|
|
||||||
**Integration effort:** Low-Medium (2-4 days)
|
|
||||||
```bash
|
|
||||||
npm install @clerk/clerk-sdk-node
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 My Recommendation: **Supabase Auth**
|
|
||||||
|
|
||||||
### Why Supabase is perfect for your project:
|
|
||||||
|
|
||||||
1. **Minimal code changes** - Can integrate with your existing JWT system
|
|
||||||
2. **Built-in admin dashboard** - No need to build user management UI
|
|
||||||
3. **PostgreSQL-based** - Familiar database, easy to extend
|
|
||||||
4. **Real-time features** - Perfect for your VIP coordination needs
|
|
||||||
5. **Row Level Security** - Fine-grained permissions per user/role
|
|
||||||
6. **Free tier** - Great for development and small deployments
|
|
||||||
|
|
||||||
### Quick Integration Plan:
|
|
||||||
|
|
||||||
#### Step 1: Setup Supabase Project
|
|
||||||
```bash
|
|
||||||
# Install Supabase
|
|
||||||
npm install @supabase/supabase-js
|
|
||||||
|
|
||||||
# Create project at https://supabase.com
|
|
||||||
# Get your project URL and anon key
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 2: Replace your user storage
|
|
||||||
```typescript
|
|
||||||
// Instead of: const users: Map<string, User> = new Map();
|
|
||||||
// Use Supabase's built-in auth.users table
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 3: Add user management endpoints
|
|
||||||
```typescript
|
|
||||||
// Get all users (admin only)
|
|
||||||
router.get('/users', requireAuth, requireRole(['administrator']), async (req, res) => {
|
|
||||||
const { data: users } = await supabase.auth.admin.listUsers();
|
|
||||||
res.json(users);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update user role
|
|
||||||
router.patch('/users/:id/role', requireAuth, requireRole(['administrator']), async (req, res) => {
|
|
||||||
const { role } = req.body;
|
|
||||||
const { data } = await supabase.auth.admin.updateUserById(req.params.id, {
|
|
||||||
user_metadata: { role }
|
|
||||||
});
|
|
||||||
res.json(data);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 4: Add frontend user management
|
|
||||||
- Use Supabase's built-in dashboard OR
|
|
||||||
- Build simple admin interface with user list/edit/delete
|
|
||||||
|
|
||||||
## 🚀 Implementation Options
|
|
||||||
|
|
||||||
### Option A: Quick Integration (Keep your current system + add Supabase)
|
|
||||||
- Keep your current OAuth flow
|
|
||||||
- Add Supabase for user storage and management
|
|
||||||
- Use Supabase dashboard for admin tasks
|
|
||||||
- **Time:** 2-3 days
|
|
||||||
|
|
||||||
### Option B: Full Migration (Replace with Supabase Auth)
|
|
||||||
- Migrate to Supabase Auth completely
|
|
||||||
- Use their OAuth providers
|
|
||||||
- Get all advanced features
|
|
||||||
- **Time:** 4-5 days
|
|
||||||
|
|
||||||
### Option C: Custom Admin Interface
|
|
||||||
- Keep your current system
|
|
||||||
- Build custom React admin interface
|
|
||||||
- Add user CRUD operations
|
|
||||||
- **Time:** 1-2 weeks
|
|
||||||
|
|
||||||
## 📋 Next Steps
|
|
||||||
|
|
||||||
1. **Choose your approach** (I recommend Option A - Quick Integration)
|
|
||||||
2. **Set up Supabase project** (5 minutes)
|
|
||||||
3. **Integrate user storage** (1 day)
|
|
||||||
4. **Add admin endpoints** (1 day)
|
|
||||||
5. **Test and refine** (1 day)
|
|
||||||
|
|
||||||
## 🔧 Alternative: Lightweight Custom Solution
|
|
||||||
|
|
||||||
If you prefer to keep it simple and custom:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Add these endpoints to your existing auth system:
|
|
||||||
|
|
||||||
// List all users (admin only)
|
|
||||||
router.get('/users', requireAuth, requireRole(['administrator']), (req, res) => {
|
|
||||||
const userList = Array.from(users.values()).map(user => ({
|
|
||||||
id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
name: user.name,
|
|
||||||
role: user.role,
|
|
||||||
lastLogin: user.lastLogin
|
|
||||||
}));
|
|
||||||
res.json(userList);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update user role
|
|
||||||
router.patch('/users/:email/role', requireAuth, requireRole(['administrator']), (req, res) => {
|
|
||||||
const { role } = req.body;
|
|
||||||
const user = users.get(req.params.email);
|
|
||||||
if (user) {
|
|
||||||
user.role = role;
|
|
||||||
users.set(req.params.email, user);
|
|
||||||
res.json({ success: true });
|
|
||||||
} else {
|
|
||||||
res.status(404).json({ error: 'User not found' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Delete user
|
|
||||||
router.delete('/users/:email', requireAuth, requireRole(['administrator']), (req, res) => {
|
|
||||||
users.delete(req.params.email);
|
|
||||||
res.json({ success: true });
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Would you like me to help you implement any of these options?
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
# 🌐 Web Server Proxy Configuration for OAuth
|
|
||||||
|
|
||||||
## 🎯 Problem Identified
|
|
||||||
|
|
||||||
Your domain `bsa.madeamess.online` is not properly configured to proxy requests to your Docker containers. When Google redirects to `https://bsa.madeamess.online:5173/auth/google/callback`, it gets "ERR_CONNECTION_REFUSED" because there's no web server listening on port 5173 for your domain.
|
|
||||||
|
|
||||||
## 🔧 Solution Options
|
|
||||||
|
|
||||||
### Option 1: Configure Nginx Proxy (Recommended)
|
|
||||||
|
|
||||||
If you're using nginx, add this configuration:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
# /etc/nginx/sites-available/bsa.madeamess.online
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
server_name bsa.madeamess.online;
|
|
||||||
|
|
||||||
# SSL configuration (your existing SSL setup)
|
|
||||||
ssl_certificate /path/to/your/certificate.crt;
|
|
||||||
ssl_certificate_key /path/to/your/private.key;
|
|
||||||
|
|
||||||
# Proxy to your Docker frontend container
|
|
||||||
location / {
|
|
||||||
proxy_pass http://localhost:5173;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection 'upgrade';
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_cache_bypass $http_upgrade;
|
|
||||||
|
|
||||||
# Important: Handle all routes for SPA
|
|
||||||
try_files $uri $uri/ @fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Fallback for SPA routing
|
|
||||||
location @fallback {
|
|
||||||
proxy_pass http://localhost:5173;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Redirect HTTP to HTTPS
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name bsa.madeamess.online;
|
|
||||||
return 301 https://$server_name$request_uri;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: Configure Apache Proxy
|
|
||||||
|
|
||||||
If you're using Apache, add this to your virtual host:
|
|
||||||
|
|
||||||
```apache
|
|
||||||
<VirtualHost *:443>
|
|
||||||
ServerName bsa.madeamess.online
|
|
||||||
|
|
||||||
# SSL configuration (your existing SSL setup)
|
|
||||||
SSLEngine on
|
|
||||||
SSLCertificateFile /path/to/your/certificate.crt
|
|
||||||
SSLCertificateKeyFile /path/to/your/private.key
|
|
||||||
|
|
||||||
# Enable proxy modules
|
|
||||||
ProxyPreserveHost On
|
|
||||||
ProxyRequests Off
|
|
||||||
|
|
||||||
# Proxy to your Docker frontend container
|
|
||||||
ProxyPass / http://localhost:5173/
|
|
||||||
ProxyPassReverse / http://localhost:5173/
|
|
||||||
|
|
||||||
# Handle WebSocket connections for Vite HMR
|
|
||||||
ProxyPass /ws ws://localhost:5173/ws
|
|
||||||
ProxyPassReverse /ws ws://localhost:5173/ws
|
|
||||||
</VirtualHost>
|
|
||||||
|
|
||||||
<VirtualHost *:80>
|
|
||||||
ServerName bsa.madeamess.online
|
|
||||||
Redirect permanent / https://bsa.madeamess.online/
|
|
||||||
</VirtualHost>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 3: Update Google OAuth Redirect URI (Quick Fix)
|
|
||||||
|
|
||||||
**Temporary workaround:** Update your Google Cloud Console OAuth settings to use `http://localhost:5173/auth/google/callback` instead of your domain, then access your app directly via `http://localhost:5173`.
|
|
||||||
|
|
||||||
## 🔄 Alternative: Use Standard Ports
|
|
||||||
|
|
||||||
### Option 4: Configure to use standard ports (80/443)
|
|
||||||
|
|
||||||
Modify your docker-compose to use standard ports:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# In docker-compose.dev.yml
|
|
||||||
services:
|
|
||||||
frontend:
|
|
||||||
ports:
|
|
||||||
- "80:5173" # HTTP
|
|
||||||
# or
|
|
||||||
- "443:5173" # HTTPS (requires SSL setup in container)
|
|
||||||
```
|
|
||||||
|
|
||||||
Then update Google OAuth redirect URI to:
|
|
||||||
- `https://bsa.madeamess.online/auth/google/callback` (no port)
|
|
||||||
|
|
||||||
## 🧪 Testing Steps
|
|
||||||
|
|
||||||
1. **Apply web server configuration**
|
|
||||||
2. **Restart your web server:**
|
|
||||||
```bash
|
|
||||||
# For nginx
|
|
||||||
sudo systemctl reload nginx
|
|
||||||
|
|
||||||
# For Apache
|
|
||||||
sudo systemctl reload apache2
|
|
||||||
```
|
|
||||||
3. **Test the proxy:**
|
|
||||||
```bash
|
|
||||||
curl -I https://bsa.madeamess.online
|
|
||||||
```
|
|
||||||
4. **Test OAuth flow:**
|
|
||||||
- Visit `https://bsa.madeamess.online`
|
|
||||||
- Click "Continue with Google"
|
|
||||||
- Complete authentication
|
|
||||||
- Should redirect back successfully
|
|
||||||
|
|
||||||
## 🎯 Root Cause Summary
|
|
||||||
|
|
||||||
The OAuth callback was failing because:
|
|
||||||
1. ✅ **Frontend routing** - Fixed (React Router now handles callback)
|
|
||||||
2. ✅ **CORS configuration** - Fixed (Backend accepts your domain)
|
|
||||||
3. ❌ **Web server proxy** - **NEEDS FIXING** (Domain not proxying to Docker)
|
|
||||||
|
|
||||||
Once you configure your web server to proxy `bsa.madeamess.online` to `localhost:5173`, the OAuth flow will work perfectly!
|
|
||||||
148
api-docs.html
148
api-docs.html
@@ -1,148 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>VIP Coordinator API Documentation</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui.css" />
|
|
||||||
<style>
|
|
||||||
html {
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: -moz-scrollbars-vertical;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
*, *:before, *:after {
|
|
||||||
box-sizing: inherit;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
margin:0;
|
|
||||||
background: #fafafa;
|
|
||||||
}
|
|
||||||
.swagger-ui .topbar {
|
|
||||||
background-color: #3498db;
|
|
||||||
}
|
|
||||||
.swagger-ui .topbar .download-url-wrapper .select-label {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.swagger-ui .topbar .download-url-wrapper input[type=text] {
|
|
||||||
border: 2px solid #2980b9;
|
|
||||||
}
|
|
||||||
.swagger-ui .info .title {
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
.custom-header {
|
|
||||||
background: linear-gradient(135deg, #3498db, #2980b9);
|
|
||||||
color: white;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.custom-header h1 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 2.5em;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
.custom-header p {
|
|
||||||
margin: 10px 0 0 0;
|
|
||||||
font-size: 1.2em;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
.quick-links {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
margin: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.quick-links h3 {
|
|
||||||
color: #2c3e50;
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
.quick-links ul {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
.quick-links li {
|
|
||||||
background: #ecf0f1;
|
|
||||||
padding: 10px 15px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border-left: 4px solid #3498db;
|
|
||||||
}
|
|
||||||
.quick-links li strong {
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
.quick-links li code {
|
|
||||||
background: #34495e;
|
|
||||||
color: white;
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="custom-header">
|
|
||||||
<h1>🚗 VIP Coordinator API</h1>
|
|
||||||
<p>Comprehensive API for managing VIP transportation coordination</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="quick-links">
|
|
||||||
<h3>🚀 Quick Start Examples</h3>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Health Check:</strong> <code>GET /api/health</code></li>
|
|
||||||
<li><strong>Get All VIPs:</strong> <code>GET /api/vips</code></li>
|
|
||||||
<li><strong>Get All Drivers:</strong> <code>GET /api/drivers</code></li>
|
|
||||||
<li><strong>Flight Info:</strong> <code>GET /api/flights/UA1234?date=2025-06-26</code></li>
|
|
||||||
<li><strong>VIP Schedule:</strong> <code>GET /api/vips/{vipId}/schedule</code></li>
|
|
||||||
<li><strong>Driver Availability:</strong> <code>POST /api/drivers/availability</code></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="swagger-ui"></div>
|
|
||||||
|
|
||||||
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-bundle.js"></script>
|
|
||||||
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-standalone-preset.js"></script>
|
|
||||||
<script>
|
|
||||||
window.onload = function() {
|
|
||||||
// Begin Swagger UI call region
|
|
||||||
const ui = SwaggerUIBundle({
|
|
||||||
url: './api-documentation.yaml',
|
|
||||||
dom_id: '#swagger-ui',
|
|
||||||
deepLinking: true,
|
|
||||||
presets: [
|
|
||||||
SwaggerUIBundle.presets.apis,
|
|
||||||
SwaggerUIStandalonePreset
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
SwaggerUIBundle.plugins.DownloadUrl
|
|
||||||
],
|
|
||||||
layout: "StandaloneLayout",
|
|
||||||
tryItOutEnabled: true,
|
|
||||||
requestInterceptor: function(request) {
|
|
||||||
// Add base URL if not present
|
|
||||||
if (request.url.startsWith('/api/')) {
|
|
||||||
request.url = 'http://localhost:3000' + request.url;
|
|
||||||
}
|
|
||||||
return request;
|
|
||||||
},
|
|
||||||
onComplete: function() {
|
|
||||||
console.log('VIP Coordinator API Documentation loaded successfully!');
|
|
||||||
},
|
|
||||||
docExpansion: 'list',
|
|
||||||
defaultModelsExpandDepth: 2,
|
|
||||||
defaultModelExpandDepth: 2,
|
|
||||||
showExtensions: true,
|
|
||||||
showCommonExtensions: true,
|
|
||||||
supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
|
|
||||||
validatorUrl: null
|
|
||||||
});
|
|
||||||
// End Swagger UI call region
|
|
||||||
|
|
||||||
window.ui = ui;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,35 +0,0 @@
|
|||||||
/**
|
|
||||||
* Auth0 Action: Add User Info to Token
|
|
||||||
*
|
|
||||||
* This action adds user profile information to the access token
|
|
||||||
* so the backend can create/validate users properly.
|
|
||||||
*
|
|
||||||
* Deploy this in Auth0 Dashboard:
|
|
||||||
* 1. Go to Actions → Flows → Login
|
|
||||||
* 2. Click "+" → Build from scratch
|
|
||||||
* 3. Name: "Add User Info to Token"
|
|
||||||
* 4. Copy this code
|
|
||||||
* 5. Click Deploy
|
|
||||||
* 6. Drag into flow between Start and Complete
|
|
||||||
* 7. Click Apply
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.onExecutePostLogin = async (event, api) => {
|
|
||||||
const namespace = 'https://vip-coordinator-api';
|
|
||||||
|
|
||||||
if (event.authorization) {
|
|
||||||
// Add user profile to access token
|
|
||||||
api.accessToken.setCustomClaim(`${namespace}/email`, event.user.email);
|
|
||||||
api.accessToken.setCustomClaim(`${namespace}/name`, event.user.name);
|
|
||||||
api.accessToken.setCustomClaim(`${namespace}/picture`, event.user.picture);
|
|
||||||
api.accessToken.setCustomClaim(`${namespace}/email_verified`, event.user.email_verified);
|
|
||||||
|
|
||||||
// Optionally require email verification before allowing access
|
|
||||||
// Uncomment the lines below if you want to enforce email verification
|
|
||||||
/*
|
|
||||||
if (!event.user.email_verified) {
|
|
||||||
api.access.deny('Please verify your email before accessing the application.');
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "VIP Coordinator User Registration",
|
|
||||||
"languages": {
|
|
||||||
"default": "en",
|
|
||||||
"primary": "en"
|
|
||||||
},
|
|
||||||
"pages": [
|
|
||||||
{
|
|
||||||
"name": "user-registration",
|
|
||||||
"components": [
|
|
||||||
{
|
|
||||||
"type": "header",
|
|
||||||
"config": {
|
|
||||||
"text": "Welcome to VIP Coordinator",
|
|
||||||
"level": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "description",
|
|
||||||
"config": {
|
|
||||||
"text": "Please provide your information to request access. The first user will be automatically approved as Administrator. Subsequent users require admin approval."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text-input",
|
|
||||||
"config": {
|
|
||||||
"label": "Full Name",
|
|
||||||
"name": "name",
|
|
||||||
"placeholder": "Enter your full name",
|
|
||||||
"required": true,
|
|
||||||
"validation": {
|
|
||||||
"min": 2,
|
|
||||||
"max": 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "email-input",
|
|
||||||
"config": {
|
|
||||||
"label": "Email Address",
|
|
||||||
"name": "email",
|
|
||||||
"placeholder": "your.email@example.com",
|
|
||||||
"required": true,
|
|
||||||
"validation": {
|
|
||||||
"email": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "password-input",
|
|
||||||
"config": {
|
|
||||||
"label": "Password",
|
|
||||||
"name": "password",
|
|
||||||
"placeholder": "Create a secure password",
|
|
||||||
"required": true,
|
|
||||||
"validation": {
|
|
||||||
"min": 8,
|
|
||||||
"passwordStrength": "fair"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text-input",
|
|
||||||
"config": {
|
|
||||||
"label": "Department (Optional)",
|
|
||||||
"name": "user_metadata.department",
|
|
||||||
"placeholder": "e.g., Transportation, Events, Security",
|
|
||||||
"required": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text-input",
|
|
||||||
"config": {
|
|
||||||
"label": "Organization (Optional)",
|
|
||||||
"name": "user_metadata.organization",
|
|
||||||
"placeholder": "Your organization name",
|
|
||||||
"required": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "checkbox",
|
|
||||||
"config": {
|
|
||||||
"label": "I agree to the terms and conditions",
|
|
||||||
"name": "terms_accepted",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "submit-button",
|
|
||||||
"config": {
|
|
||||||
"label": "Request Access"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ending": {
|
|
||||||
"after_signup": {
|
|
||||||
"type": "redirect",
|
|
||||||
"url": "http://localhost:5173/callback"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"style": {
|
|
||||||
"primaryColor": "#3B82F6",
|
|
||||||
"backgroundColor": "#F9FAFB",
|
|
||||||
"buttonRadius": "8px"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# ============================================
|
|
||||||
# Auth0 Configuration (NEW)
|
|
||||||
# ============================================
|
|
||||||
AUTH0_DOMAIN=your-tenant.us.auth0.com
|
|
||||||
AUTH0_CLIENT_ID=your_client_id_here
|
|
||||||
AUTH0_CLIENT_SECRET=your_client_secret_here
|
|
||||||
AUTH0_AUDIENCE=https://vip-coordinator-api
|
|
||||||
AUTH0_API_ID=6910ee5a03672ebf6f04fc1c
|
|
||||||
AUTH0_CALLBACK_URL=http://localhost:3000/auth/auth0/callback
|
|
||||||
AUTH0_LOGOUT_URL=http://localhost:5173/login
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# Application Configuration
|
|
||||||
# ============================================
|
|
||||||
PORT=3000
|
|
||||||
NODE_ENV=development
|
|
||||||
FRONTEND_URL=http://localhost:5173
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# Database Configuration
|
|
||||||
# ============================================
|
|
||||||
DATABASE_URL=postgresql://postgres:changeme@db:5432/vip_coordinator
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# Redis Configuration
|
|
||||||
# ============================================
|
|
||||||
REDIS_URL=redis://redis:6379
|
|
||||||
|
|
||||||
# ============================================
|
|
||||||
# Legacy Google OAuth (REMOVE THESE)
|
|
||||||
# These are no longer needed with Auth0
|
|
||||||
# ============================================
|
|
||||||
# GOOGLE_CLIENT_ID=
|
|
||||||
# GOOGLE_CLIENT_SECRET=
|
|
||||||
# GOOGLE_REDIRECT_URI=
|
|
||||||
# JWT_SECRET=
|
|
||||||
# ADMIN_PASSWORD=
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
# Multi-stage build for development and production
|
|
||||||
FROM node:22-alpine AS base
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy package files
|
|
||||||
COPY package*.json ./
|
|
||||||
|
|
||||||
# Development stage
|
|
||||||
FROM base AS development
|
|
||||||
RUN npm install
|
|
||||||
COPY . .
|
|
||||||
EXPOSE 3000
|
|
||||||
CMD ["npm", "run", "dev"]
|
|
||||||
|
|
||||||
# Production stage
|
|
||||||
FROM base AS production
|
|
||||||
|
|
||||||
# Install dependencies (including dev dependencies for build)
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# Copy source code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Build the application
|
|
||||||
RUN npx tsc --version && npx tsc
|
|
||||||
|
|
||||||
# Remove dev dependencies to reduce image size
|
|
||||||
RUN npm prune --omit=dev
|
|
||||||
|
|
||||||
# Create non-root user for security
|
|
||||||
RUN addgroup -g 1001 -S nodejs && \
|
|
||||||
adduser -S nodejs -u 1001
|
|
||||||
|
|
||||||
# Change ownership of the app directory
|
|
||||||
RUN chown -R nodejs:nodejs /app
|
|
||||||
USER nodejs
|
|
||||||
|
|
||||||
# Health check
|
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
||||||
CMD node -e "require('http').get('http://localhost:3000/api/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })" || exit 1
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
# Start the production server
|
|
||||||
CMD ["npm", "start"]
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import { Pool } from 'pg';
|
|
||||||
declare const pool: Pool;
|
|
||||||
export default pool;
|
|
||||||
//# sourceMappingURL=database.d.ts.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/config/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAK1B,QAAA,MAAM,IAAI,MAKR,CAAC;AAWH,eAAe,IAAI,CAAC"}
|
|
||||||
23
backend-old-20260125/dist/config/database.js
vendored
23
backend-old-20260125/dist/config/database.js
vendored
@@ -1,23 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
const pg_1 = require("pg");
|
|
||||||
const dotenv_1 = __importDefault(require("dotenv"));
|
|
||||||
dotenv_1.default.config();
|
|
||||||
const pool = new pg_1.Pool({
|
|
||||||
connectionString: process.env.DATABASE_URL || 'postgresql://postgres:changeme@localhost:5432/vip_coordinator',
|
|
||||||
max: 20,
|
|
||||||
idleTimeoutMillis: 30000,
|
|
||||||
connectionTimeoutMillis: 2000,
|
|
||||||
});
|
|
||||||
// Test the connection
|
|
||||||
pool.on('connect', () => {
|
|
||||||
console.log('✅ Connected to PostgreSQL database');
|
|
||||||
});
|
|
||||||
pool.on('error', (err) => {
|
|
||||||
console.error('❌ PostgreSQL connection error:', err);
|
|
||||||
});
|
|
||||||
exports.default = pool;
|
|
||||||
//# sourceMappingURL=database.js.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/config/database.ts"],"names":[],"mappings":";;;;;AAAA,2BAA0B;AAC1B,oDAA4B;AAE5B,gBAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,MAAM,IAAI,GAAG,IAAI,SAAI,CAAC;IACpB,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,+DAA+D;IAC7G,GAAG,EAAE,EAAE;IACP,iBAAiB,EAAE,KAAK;IACxB,uBAAuB,EAAE,IAAI;CAC9B,CAAC,CAAC;AAEH,sBAAsB;AACtB,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IACtB,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;IACvB,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,GAAG,CAAC,CAAC;AACvD,CAAC,CAAC,CAAC;AAEH,kBAAe,IAAI,CAAC"}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
declare class MockDatabase {
|
|
||||||
private users;
|
|
||||||
private vips;
|
|
||||||
private drivers;
|
|
||||||
private scheduleEvents;
|
|
||||||
private adminSettings;
|
|
||||||
constructor();
|
|
||||||
query(text: string, params?: any[]): Promise<any>;
|
|
||||||
connect(): Promise<{
|
|
||||||
query: (text: string, params?: any[]) => Promise<any>;
|
|
||||||
release: () => void;
|
|
||||||
}>;
|
|
||||||
end(): Promise<void>;
|
|
||||||
on(event: string, callback: Function): void;
|
|
||||||
}
|
|
||||||
export default MockDatabase;
|
|
||||||
//# sourceMappingURL=mockDatabase.d.ts.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"mockDatabase.d.ts","sourceRoot":"","sources":["../../src/config/mockDatabase.ts"],"names":[],"mappings":"AAyBA,cAAM,YAAY;IAChB,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,IAAI,CAAmC;IAC/C,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,aAAa,CAAkC;;IA8BjD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;IAiGjD,OAAO;sBAjGK,MAAM,WAAW,GAAG,EAAE,KAAG,OAAO,CAAC,GAAG,CAAC;;;IAyGjD,GAAG;IAIT,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ;CAKrC;AAED,eAAe,YAAY,CAAC"}
|
|
||||||
137
backend-old-20260125/dist/config/mockDatabase.js
vendored
137
backend-old-20260125/dist/config/mockDatabase.js
vendored
@@ -1,137 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
class MockDatabase {
|
|
||||||
constructor() {
|
|
||||||
this.users = new Map();
|
|
||||||
this.vips = new Map();
|
|
||||||
this.drivers = new Map();
|
|
||||||
this.scheduleEvents = new Map();
|
|
||||||
this.adminSettings = new Map();
|
|
||||||
// Add a test admin user
|
|
||||||
const adminId = '1';
|
|
||||||
this.users.set(adminId, {
|
|
||||||
id: adminId,
|
|
||||||
email: 'admin@example.com',
|
|
||||||
name: 'Test Admin',
|
|
||||||
role: 'admin',
|
|
||||||
created_at: new Date(),
|
|
||||||
updated_at: new Date()
|
|
||||||
});
|
|
||||||
// Add some test VIPs
|
|
||||||
this.vips.set('1', {
|
|
||||||
id: '1',
|
|
||||||
name: 'John Doe',
|
|
||||||
organization: 'Test Org',
|
|
||||||
department: 'Office of Development',
|
|
||||||
transport_mode: 'flight',
|
|
||||||
expected_arrival: '2025-07-25 14:00',
|
|
||||||
needs_airport_pickup: true,
|
|
||||||
needs_venue_transport: true,
|
|
||||||
notes: 'Test VIP',
|
|
||||||
created_at: new Date(),
|
|
||||||
updated_at: new Date()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async query(text, params) {
|
|
||||||
console.log('Mock DB Query:', text.substring(0, 50) + '...');
|
|
||||||
// Handle user queries
|
|
||||||
if (text.includes('COUNT(*) FROM users')) {
|
|
||||||
return { rows: [{ count: this.users.size.toString() }] };
|
|
||||||
}
|
|
||||||
if (text.includes('SELECT * FROM users WHERE email')) {
|
|
||||||
const email = params?.[0];
|
|
||||||
const user = Array.from(this.users.values()).find(u => u.email === email);
|
|
||||||
return { rows: user ? [user] : [] };
|
|
||||||
}
|
|
||||||
if (text.includes('SELECT * FROM users WHERE id')) {
|
|
||||||
const id = params?.[0];
|
|
||||||
const user = this.users.get(id);
|
|
||||||
return { rows: user ? [user] : [] };
|
|
||||||
}
|
|
||||||
if (text.includes('SELECT * FROM users WHERE google_id')) {
|
|
||||||
const google_id = params?.[0];
|
|
||||||
const user = Array.from(this.users.values()).find(u => u.google_id === google_id);
|
|
||||||
return { rows: user ? [user] : [] };
|
|
||||||
}
|
|
||||||
if (text.includes('INSERT INTO users')) {
|
|
||||||
const id = Date.now().toString();
|
|
||||||
const user = {
|
|
||||||
id,
|
|
||||||
email: params?.[0],
|
|
||||||
name: params?.[1],
|
|
||||||
role: params?.[2] || 'coordinator',
|
|
||||||
google_id: params?.[4],
|
|
||||||
created_at: new Date(),
|
|
||||||
updated_at: new Date()
|
|
||||||
};
|
|
||||||
this.users.set(id, user);
|
|
||||||
return { rows: [user] };
|
|
||||||
}
|
|
||||||
// Handle VIP queries
|
|
||||||
if (text.includes('SELECT v.*') && text.includes('FROM vips')) {
|
|
||||||
const vips = Array.from(this.vips.values());
|
|
||||||
return {
|
|
||||||
rows: vips.map(v => ({
|
|
||||||
...v,
|
|
||||||
flights: []
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Handle admin settings queries
|
|
||||||
if (text.includes('SELECT * FROM admin_settings')) {
|
|
||||||
const settings = Array.from(this.adminSettings.entries()).map(([key, value]) => ({
|
|
||||||
key,
|
|
||||||
value
|
|
||||||
}));
|
|
||||||
return { rows: settings };
|
|
||||||
}
|
|
||||||
// Handle drivers queries
|
|
||||||
if (text.includes('SELECT * FROM drivers')) {
|
|
||||||
const drivers = Array.from(this.drivers.values());
|
|
||||||
return { rows: drivers };
|
|
||||||
}
|
|
||||||
// Handle schedule events queries
|
|
||||||
if (text.includes('SELECT * FROM schedule_events')) {
|
|
||||||
const events = Array.from(this.scheduleEvents.values());
|
|
||||||
return { rows: events };
|
|
||||||
}
|
|
||||||
if (text.includes('INSERT INTO vips')) {
|
|
||||||
const id = Date.now().toString();
|
|
||||||
const vip = {
|
|
||||||
id,
|
|
||||||
name: params?.[0],
|
|
||||||
organization: params?.[1],
|
|
||||||
department: params?.[2] || 'Office of Development',
|
|
||||||
transport_mode: params?.[3] || 'flight',
|
|
||||||
expected_arrival: params?.[4],
|
|
||||||
needs_airport_pickup: params?.[5] !== false,
|
|
||||||
needs_venue_transport: params?.[6] !== false,
|
|
||||||
notes: params?.[7] || '',
|
|
||||||
created_at: new Date(),
|
|
||||||
updated_at: new Date()
|
|
||||||
};
|
|
||||||
this.vips.set(id, vip);
|
|
||||||
return { rows: [vip] };
|
|
||||||
}
|
|
||||||
// Default empty result
|
|
||||||
console.log('Unhandled query:', text);
|
|
||||||
return { rows: [] };
|
|
||||||
}
|
|
||||||
async connect() {
|
|
||||||
return {
|
|
||||||
query: this.query.bind(this),
|
|
||||||
release: () => { }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Make compatible with pg Pool interface
|
|
||||||
async end() {
|
|
||||||
console.log('Mock database connection closed');
|
|
||||||
}
|
|
||||||
on(event, callback) {
|
|
||||||
if (event === 'connect') {
|
|
||||||
setTimeout(() => callback(), 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.default = MockDatabase;
|
|
||||||
//# sourceMappingURL=mockDatabase.js.map
|
|
||||||
File diff suppressed because one or more lines are too long
292
backend-old-20260125/dist/config/redis.d.ts
vendored
292
backend-old-20260125/dist/config/redis.d.ts
vendored
@@ -1,292 +0,0 @@
|
|||||||
declare const redisClient: import("@redis/client").RedisClientType<{
|
|
||||||
graph: {
|
|
||||||
CONFIG_GET: typeof import("@redis/graph/dist/commands/CONFIG_GET");
|
|
||||||
configGet: typeof import("@redis/graph/dist/commands/CONFIG_GET");
|
|
||||||
CONFIG_SET: typeof import("@redis/graph/dist/commands/CONFIG_SET");
|
|
||||||
configSet: typeof import("@redis/graph/dist/commands/CONFIG_SET");
|
|
||||||
DELETE: typeof import("@redis/graph/dist/commands/DELETE");
|
|
||||||
delete: typeof import("@redis/graph/dist/commands/DELETE");
|
|
||||||
EXPLAIN: typeof import("@redis/graph/dist/commands/EXPLAIN");
|
|
||||||
explain: typeof import("@redis/graph/dist/commands/EXPLAIN");
|
|
||||||
LIST: typeof import("@redis/graph/dist/commands/LIST");
|
|
||||||
list: typeof import("@redis/graph/dist/commands/LIST");
|
|
||||||
PROFILE: typeof import("@redis/graph/dist/commands/PROFILE");
|
|
||||||
profile: typeof import("@redis/graph/dist/commands/PROFILE");
|
|
||||||
QUERY: typeof import("@redis/graph/dist/commands/QUERY");
|
|
||||||
query: typeof import("@redis/graph/dist/commands/QUERY");
|
|
||||||
RO_QUERY: typeof import("@redis/graph/dist/commands/RO_QUERY");
|
|
||||||
roQuery: typeof import("@redis/graph/dist/commands/RO_QUERY");
|
|
||||||
SLOWLOG: typeof import("@redis/graph/dist/commands/SLOWLOG");
|
|
||||||
slowLog: typeof import("@redis/graph/dist/commands/SLOWLOG");
|
|
||||||
};
|
|
||||||
json: {
|
|
||||||
ARRAPPEND: typeof import("@redis/json/dist/commands/ARRAPPEND");
|
|
||||||
arrAppend: typeof import("@redis/json/dist/commands/ARRAPPEND");
|
|
||||||
ARRINDEX: typeof import("@redis/json/dist/commands/ARRINDEX");
|
|
||||||
arrIndex: typeof import("@redis/json/dist/commands/ARRINDEX");
|
|
||||||
ARRINSERT: typeof import("@redis/json/dist/commands/ARRINSERT");
|
|
||||||
arrInsert: typeof import("@redis/json/dist/commands/ARRINSERT");
|
|
||||||
ARRLEN: typeof import("@redis/json/dist/commands/ARRLEN");
|
|
||||||
arrLen: typeof import("@redis/json/dist/commands/ARRLEN");
|
|
||||||
ARRPOP: typeof import("@redis/json/dist/commands/ARRPOP");
|
|
||||||
arrPop: typeof import("@redis/json/dist/commands/ARRPOP");
|
|
||||||
ARRTRIM: typeof import("@redis/json/dist/commands/ARRTRIM");
|
|
||||||
arrTrim: typeof import("@redis/json/dist/commands/ARRTRIM");
|
|
||||||
DEBUG_MEMORY: typeof import("@redis/json/dist/commands/DEBUG_MEMORY");
|
|
||||||
debugMemory: typeof import("@redis/json/dist/commands/DEBUG_MEMORY");
|
|
||||||
DEL: typeof import("@redis/json/dist/commands/DEL");
|
|
||||||
del: typeof import("@redis/json/dist/commands/DEL");
|
|
||||||
FORGET: typeof import("@redis/json/dist/commands/FORGET");
|
|
||||||
forget: typeof import("@redis/json/dist/commands/FORGET");
|
|
||||||
GET: typeof import("@redis/json/dist/commands/GET");
|
|
||||||
get: typeof import("@redis/json/dist/commands/GET");
|
|
||||||
MERGE: typeof import("@redis/json/dist/commands/MERGE");
|
|
||||||
merge: typeof import("@redis/json/dist/commands/MERGE");
|
|
||||||
MGET: typeof import("@redis/json/dist/commands/MGET");
|
|
||||||
mGet: typeof import("@redis/json/dist/commands/MGET");
|
|
||||||
MSET: typeof import("@redis/json/dist/commands/MSET");
|
|
||||||
mSet: typeof import("@redis/json/dist/commands/MSET");
|
|
||||||
NUMINCRBY: typeof import("@redis/json/dist/commands/NUMINCRBY");
|
|
||||||
numIncrBy: typeof import("@redis/json/dist/commands/NUMINCRBY");
|
|
||||||
NUMMULTBY: typeof import("@redis/json/dist/commands/NUMMULTBY");
|
|
||||||
numMultBy: typeof import("@redis/json/dist/commands/NUMMULTBY");
|
|
||||||
OBJKEYS: typeof import("@redis/json/dist/commands/OBJKEYS");
|
|
||||||
objKeys: typeof import("@redis/json/dist/commands/OBJKEYS");
|
|
||||||
OBJLEN: typeof import("@redis/json/dist/commands/OBJLEN");
|
|
||||||
objLen: typeof import("@redis/json/dist/commands/OBJLEN");
|
|
||||||
RESP: typeof import("@redis/json/dist/commands/RESP");
|
|
||||||
resp: typeof import("@redis/json/dist/commands/RESP");
|
|
||||||
SET: typeof import("@redis/json/dist/commands/SET");
|
|
||||||
set: typeof import("@redis/json/dist/commands/SET");
|
|
||||||
STRAPPEND: typeof import("@redis/json/dist/commands/STRAPPEND");
|
|
||||||
strAppend: typeof import("@redis/json/dist/commands/STRAPPEND");
|
|
||||||
STRLEN: typeof import("@redis/json/dist/commands/STRLEN");
|
|
||||||
strLen: typeof import("@redis/json/dist/commands/STRLEN");
|
|
||||||
TYPE: typeof import("@redis/json/dist/commands/TYPE");
|
|
||||||
type: typeof import("@redis/json/dist/commands/TYPE");
|
|
||||||
};
|
|
||||||
ft: {
|
|
||||||
_LIST: typeof import("@redis/search/dist/commands/_LIST");
|
|
||||||
_list: typeof import("@redis/search/dist/commands/_LIST");
|
|
||||||
ALTER: typeof import("@redis/search/dist/commands/ALTER");
|
|
||||||
alter: typeof import("@redis/search/dist/commands/ALTER");
|
|
||||||
AGGREGATE_WITHCURSOR: typeof import("@redis/search/dist/commands/AGGREGATE_WITHCURSOR");
|
|
||||||
aggregateWithCursor: typeof import("@redis/search/dist/commands/AGGREGATE_WITHCURSOR");
|
|
||||||
AGGREGATE: typeof import("@redis/search/dist/commands/AGGREGATE");
|
|
||||||
aggregate: typeof import("@redis/search/dist/commands/AGGREGATE");
|
|
||||||
ALIASADD: typeof import("@redis/search/dist/commands/ALIASADD");
|
|
||||||
aliasAdd: typeof import("@redis/search/dist/commands/ALIASADD");
|
|
||||||
ALIASDEL: typeof import("@redis/search/dist/commands/ALIASDEL");
|
|
||||||
aliasDel: typeof import("@redis/search/dist/commands/ALIASDEL");
|
|
||||||
ALIASUPDATE: typeof import("@redis/search/dist/commands/ALIASUPDATE");
|
|
||||||
aliasUpdate: typeof import("@redis/search/dist/commands/ALIASUPDATE");
|
|
||||||
CONFIG_GET: typeof import("@redis/search/dist/commands/CONFIG_GET");
|
|
||||||
configGet: typeof import("@redis/search/dist/commands/CONFIG_GET");
|
|
||||||
CONFIG_SET: typeof import("@redis/search/dist/commands/CONFIG_SET");
|
|
||||||
configSet: typeof import("@redis/search/dist/commands/CONFIG_SET");
|
|
||||||
CREATE: typeof import("@redis/search/dist/commands/CREATE");
|
|
||||||
create: typeof import("@redis/search/dist/commands/CREATE");
|
|
||||||
CURSOR_DEL: typeof import("@redis/search/dist/commands/CURSOR_DEL");
|
|
||||||
cursorDel: typeof import("@redis/search/dist/commands/CURSOR_DEL");
|
|
||||||
CURSOR_READ: typeof import("@redis/search/dist/commands/CURSOR_READ");
|
|
||||||
cursorRead: typeof import("@redis/search/dist/commands/CURSOR_READ");
|
|
||||||
DICTADD: typeof import("@redis/search/dist/commands/DICTADD");
|
|
||||||
dictAdd: typeof import("@redis/search/dist/commands/DICTADD");
|
|
||||||
DICTDEL: typeof import("@redis/search/dist/commands/DICTDEL");
|
|
||||||
dictDel: typeof import("@redis/search/dist/commands/DICTDEL");
|
|
||||||
DICTDUMP: typeof import("@redis/search/dist/commands/DICTDUMP");
|
|
||||||
dictDump: typeof import("@redis/search/dist/commands/DICTDUMP");
|
|
||||||
DROPINDEX: typeof import("@redis/search/dist/commands/DROPINDEX");
|
|
||||||
dropIndex: typeof import("@redis/search/dist/commands/DROPINDEX");
|
|
||||||
EXPLAIN: typeof import("@redis/search/dist/commands/EXPLAIN");
|
|
||||||
explain: typeof import("@redis/search/dist/commands/EXPLAIN");
|
|
||||||
EXPLAINCLI: typeof import("@redis/search/dist/commands/EXPLAINCLI");
|
|
||||||
explainCli: typeof import("@redis/search/dist/commands/EXPLAINCLI");
|
|
||||||
INFO: typeof import("@redis/search/dist/commands/INFO");
|
|
||||||
info: typeof import("@redis/search/dist/commands/INFO");
|
|
||||||
PROFILESEARCH: typeof import("@redis/search/dist/commands/PROFILE_SEARCH");
|
|
||||||
profileSearch: typeof import("@redis/search/dist/commands/PROFILE_SEARCH");
|
|
||||||
PROFILEAGGREGATE: typeof import("@redis/search/dist/commands/PROFILE_AGGREGATE");
|
|
||||||
profileAggregate: typeof import("@redis/search/dist/commands/PROFILE_AGGREGATE");
|
|
||||||
SEARCH: typeof import("@redis/search/dist/commands/SEARCH");
|
|
||||||
search: typeof import("@redis/search/dist/commands/SEARCH");
|
|
||||||
SEARCH_NOCONTENT: typeof import("@redis/search/dist/commands/SEARCH_NOCONTENT");
|
|
||||||
searchNoContent: typeof import("@redis/search/dist/commands/SEARCH_NOCONTENT");
|
|
||||||
SPELLCHECK: typeof import("@redis/search/dist/commands/SPELLCHECK");
|
|
||||||
spellCheck: typeof import("@redis/search/dist/commands/SPELLCHECK");
|
|
||||||
SUGADD: typeof import("@redis/search/dist/commands/SUGADD");
|
|
||||||
sugAdd: typeof import("@redis/search/dist/commands/SUGADD");
|
|
||||||
SUGDEL: typeof import("@redis/search/dist/commands/SUGDEL");
|
|
||||||
sugDel: typeof import("@redis/search/dist/commands/SUGDEL");
|
|
||||||
SUGGET_WITHPAYLOADS: typeof import("@redis/search/dist/commands/SUGGET_WITHPAYLOADS");
|
|
||||||
sugGetWithPayloads: typeof import("@redis/search/dist/commands/SUGGET_WITHPAYLOADS");
|
|
||||||
SUGGET_WITHSCORES_WITHPAYLOADS: typeof import("@redis/search/dist/commands/SUGGET_WITHSCORES_WITHPAYLOADS");
|
|
||||||
sugGetWithScoresWithPayloads: typeof import("@redis/search/dist/commands/SUGGET_WITHSCORES_WITHPAYLOADS");
|
|
||||||
SUGGET_WITHSCORES: typeof import("@redis/search/dist/commands/SUGGET_WITHSCORES");
|
|
||||||
sugGetWithScores: typeof import("@redis/search/dist/commands/SUGGET_WITHSCORES");
|
|
||||||
SUGGET: typeof import("@redis/search/dist/commands/SUGGET");
|
|
||||||
sugGet: typeof import("@redis/search/dist/commands/SUGGET");
|
|
||||||
SUGLEN: typeof import("@redis/search/dist/commands/SUGLEN");
|
|
||||||
sugLen: typeof import("@redis/search/dist/commands/SUGLEN");
|
|
||||||
SYNDUMP: typeof import("@redis/search/dist/commands/SYNDUMP");
|
|
||||||
synDump: typeof import("@redis/search/dist/commands/SYNDUMP");
|
|
||||||
SYNUPDATE: typeof import("@redis/search/dist/commands/SYNUPDATE");
|
|
||||||
synUpdate: typeof import("@redis/search/dist/commands/SYNUPDATE");
|
|
||||||
TAGVALS: typeof import("@redis/search/dist/commands/TAGVALS");
|
|
||||||
tagVals: typeof import("@redis/search/dist/commands/TAGVALS");
|
|
||||||
};
|
|
||||||
ts: {
|
|
||||||
ADD: typeof import("@redis/time-series/dist/commands/ADD");
|
|
||||||
add: typeof import("@redis/time-series/dist/commands/ADD");
|
|
||||||
ALTER: typeof import("@redis/time-series/dist/commands/ALTER");
|
|
||||||
alter: typeof import("@redis/time-series/dist/commands/ALTER");
|
|
||||||
CREATE: typeof import("@redis/time-series/dist/commands/CREATE");
|
|
||||||
create: typeof import("@redis/time-series/dist/commands/CREATE");
|
|
||||||
CREATERULE: typeof import("@redis/time-series/dist/commands/CREATERULE");
|
|
||||||
createRule: typeof import("@redis/time-series/dist/commands/CREATERULE");
|
|
||||||
DECRBY: typeof import("@redis/time-series/dist/commands/DECRBY");
|
|
||||||
decrBy: typeof import("@redis/time-series/dist/commands/DECRBY");
|
|
||||||
DEL: typeof import("@redis/time-series/dist/commands/DEL");
|
|
||||||
del: typeof import("@redis/time-series/dist/commands/DEL");
|
|
||||||
DELETERULE: typeof import("@redis/time-series/dist/commands/DELETERULE");
|
|
||||||
deleteRule: typeof import("@redis/time-series/dist/commands/DELETERULE");
|
|
||||||
GET: typeof import("@redis/time-series/dist/commands/GET");
|
|
||||||
get: typeof import("@redis/time-series/dist/commands/GET");
|
|
||||||
INCRBY: typeof import("@redis/time-series/dist/commands/INCRBY");
|
|
||||||
incrBy: typeof import("@redis/time-series/dist/commands/INCRBY");
|
|
||||||
INFO_DEBUG: typeof import("@redis/time-series/dist/commands/INFO_DEBUG");
|
|
||||||
infoDebug: typeof import("@redis/time-series/dist/commands/INFO_DEBUG");
|
|
||||||
INFO: typeof import("@redis/time-series/dist/commands/INFO");
|
|
||||||
info: typeof import("@redis/time-series/dist/commands/INFO");
|
|
||||||
MADD: typeof import("@redis/time-series/dist/commands/MADD");
|
|
||||||
mAdd: typeof import("@redis/time-series/dist/commands/MADD");
|
|
||||||
MGET: typeof import("@redis/time-series/dist/commands/MGET");
|
|
||||||
mGet: typeof import("@redis/time-series/dist/commands/MGET");
|
|
||||||
MGET_WITHLABELS: typeof import("@redis/time-series/dist/commands/MGET_WITHLABELS");
|
|
||||||
mGetWithLabels: typeof import("@redis/time-series/dist/commands/MGET_WITHLABELS");
|
|
||||||
QUERYINDEX: typeof import("@redis/time-series/dist/commands/QUERYINDEX");
|
|
||||||
queryIndex: typeof import("@redis/time-series/dist/commands/QUERYINDEX");
|
|
||||||
RANGE: typeof import("@redis/time-series/dist/commands/RANGE");
|
|
||||||
range: typeof import("@redis/time-series/dist/commands/RANGE");
|
|
||||||
REVRANGE: typeof import("@redis/time-series/dist/commands/REVRANGE");
|
|
||||||
revRange: typeof import("@redis/time-series/dist/commands/REVRANGE");
|
|
||||||
MRANGE: typeof import("@redis/time-series/dist/commands/MRANGE");
|
|
||||||
mRange: typeof import("@redis/time-series/dist/commands/MRANGE");
|
|
||||||
MRANGE_WITHLABELS: typeof import("@redis/time-series/dist/commands/MRANGE_WITHLABELS");
|
|
||||||
mRangeWithLabels: typeof import("@redis/time-series/dist/commands/MRANGE_WITHLABELS");
|
|
||||||
MREVRANGE: typeof import("@redis/time-series/dist/commands/MREVRANGE");
|
|
||||||
mRevRange: typeof import("@redis/time-series/dist/commands/MREVRANGE");
|
|
||||||
MREVRANGE_WITHLABELS: typeof import("@redis/time-series/dist/commands/MREVRANGE_WITHLABELS");
|
|
||||||
mRevRangeWithLabels: typeof import("@redis/time-series/dist/commands/MREVRANGE_WITHLABELS");
|
|
||||||
};
|
|
||||||
bf: {
|
|
||||||
ADD: typeof import("@redis/bloom/dist/commands/bloom/ADD");
|
|
||||||
add: typeof import("@redis/bloom/dist/commands/bloom/ADD");
|
|
||||||
CARD: typeof import("@redis/bloom/dist/commands/bloom/CARD");
|
|
||||||
card: typeof import("@redis/bloom/dist/commands/bloom/CARD");
|
|
||||||
EXISTS: typeof import("@redis/bloom/dist/commands/bloom/EXISTS");
|
|
||||||
exists: typeof import("@redis/bloom/dist/commands/bloom/EXISTS");
|
|
||||||
INFO: typeof import("@redis/bloom/dist/commands/bloom/INFO");
|
|
||||||
info: typeof import("@redis/bloom/dist/commands/bloom/INFO");
|
|
||||||
INSERT: typeof import("@redis/bloom/dist/commands/bloom/INSERT");
|
|
||||||
insert: typeof import("@redis/bloom/dist/commands/bloom/INSERT");
|
|
||||||
LOADCHUNK: typeof import("@redis/bloom/dist/commands/bloom/LOADCHUNK");
|
|
||||||
loadChunk: typeof import("@redis/bloom/dist/commands/bloom/LOADCHUNK");
|
|
||||||
MADD: typeof import("@redis/bloom/dist/commands/bloom/MADD");
|
|
||||||
mAdd: typeof import("@redis/bloom/dist/commands/bloom/MADD");
|
|
||||||
MEXISTS: typeof import("@redis/bloom/dist/commands/bloom/MEXISTS");
|
|
||||||
mExists: typeof import("@redis/bloom/dist/commands/bloom/MEXISTS");
|
|
||||||
RESERVE: typeof import("@redis/bloom/dist/commands/bloom/RESERVE");
|
|
||||||
reserve: typeof import("@redis/bloom/dist/commands/bloom/RESERVE");
|
|
||||||
SCANDUMP: typeof import("@redis/bloom/dist/commands/bloom/SCANDUMP");
|
|
||||||
scanDump: typeof import("@redis/bloom/dist/commands/bloom/SCANDUMP");
|
|
||||||
};
|
|
||||||
cms: {
|
|
||||||
INCRBY: typeof import("@redis/bloom/dist/commands/count-min-sketch/INCRBY");
|
|
||||||
incrBy: typeof import("@redis/bloom/dist/commands/count-min-sketch/INCRBY");
|
|
||||||
INFO: typeof import("@redis/bloom/dist/commands/count-min-sketch/INFO");
|
|
||||||
info: typeof import("@redis/bloom/dist/commands/count-min-sketch/INFO");
|
|
||||||
INITBYDIM: typeof import("@redis/bloom/dist/commands/count-min-sketch/INITBYDIM");
|
|
||||||
initByDim: typeof import("@redis/bloom/dist/commands/count-min-sketch/INITBYDIM");
|
|
||||||
INITBYPROB: typeof import("@redis/bloom/dist/commands/count-min-sketch/INITBYPROB");
|
|
||||||
initByProb: typeof import("@redis/bloom/dist/commands/count-min-sketch/INITBYPROB");
|
|
||||||
MERGE: typeof import("@redis/bloom/dist/commands/count-min-sketch/MERGE");
|
|
||||||
merge: typeof import("@redis/bloom/dist/commands/count-min-sketch/MERGE");
|
|
||||||
QUERY: typeof import("@redis/bloom/dist/commands/count-min-sketch/QUERY");
|
|
||||||
query: typeof import("@redis/bloom/dist/commands/count-min-sketch/QUERY");
|
|
||||||
};
|
|
||||||
cf: {
|
|
||||||
ADD: typeof import("@redis/bloom/dist/commands/cuckoo/ADD");
|
|
||||||
add: typeof import("@redis/bloom/dist/commands/cuckoo/ADD");
|
|
||||||
ADDNX: typeof import("@redis/bloom/dist/commands/cuckoo/ADDNX");
|
|
||||||
addNX: typeof import("@redis/bloom/dist/commands/cuckoo/ADDNX");
|
|
||||||
COUNT: typeof import("@redis/bloom/dist/commands/cuckoo/COUNT");
|
|
||||||
count: typeof import("@redis/bloom/dist/commands/cuckoo/COUNT");
|
|
||||||
DEL: typeof import("@redis/bloom/dist/commands/cuckoo/DEL");
|
|
||||||
del: typeof import("@redis/bloom/dist/commands/cuckoo/DEL");
|
|
||||||
EXISTS: typeof import("@redis/bloom/dist/commands/cuckoo/EXISTS");
|
|
||||||
exists: typeof import("@redis/bloom/dist/commands/cuckoo/EXISTS");
|
|
||||||
INFO: typeof import("@redis/bloom/dist/commands/cuckoo/INFO");
|
|
||||||
info: typeof import("@redis/bloom/dist/commands/cuckoo/INFO");
|
|
||||||
INSERT: typeof import("@redis/bloom/dist/commands/cuckoo/INSERT");
|
|
||||||
insert: typeof import("@redis/bloom/dist/commands/cuckoo/INSERT");
|
|
||||||
INSERTNX: typeof import("@redis/bloom/dist/commands/cuckoo/INSERTNX");
|
|
||||||
insertNX: typeof import("@redis/bloom/dist/commands/cuckoo/INSERTNX");
|
|
||||||
LOADCHUNK: typeof import("@redis/bloom/dist/commands/cuckoo/LOADCHUNK");
|
|
||||||
loadChunk: typeof import("@redis/bloom/dist/commands/cuckoo/LOADCHUNK");
|
|
||||||
RESERVE: typeof import("@redis/bloom/dist/commands/cuckoo/RESERVE");
|
|
||||||
reserve: typeof import("@redis/bloom/dist/commands/cuckoo/RESERVE");
|
|
||||||
SCANDUMP: typeof import("@redis/bloom/dist/commands/cuckoo/SCANDUMP");
|
|
||||||
scanDump: typeof import("@redis/bloom/dist/commands/cuckoo/SCANDUMP");
|
|
||||||
};
|
|
||||||
tDigest: {
|
|
||||||
ADD: typeof import("@redis/bloom/dist/commands/t-digest/ADD");
|
|
||||||
add: typeof import("@redis/bloom/dist/commands/t-digest/ADD");
|
|
||||||
BYRANK: typeof import("@redis/bloom/dist/commands/t-digest/BYRANK");
|
|
||||||
byRank: typeof import("@redis/bloom/dist/commands/t-digest/BYRANK");
|
|
||||||
BYREVRANK: typeof import("@redis/bloom/dist/commands/t-digest/BYREVRANK");
|
|
||||||
byRevRank: typeof import("@redis/bloom/dist/commands/t-digest/BYREVRANK");
|
|
||||||
CDF: typeof import("@redis/bloom/dist/commands/t-digest/CDF");
|
|
||||||
cdf: typeof import("@redis/bloom/dist/commands/t-digest/CDF");
|
|
||||||
CREATE: typeof import("@redis/bloom/dist/commands/t-digest/CREATE");
|
|
||||||
create: typeof import("@redis/bloom/dist/commands/t-digest/CREATE");
|
|
||||||
INFO: typeof import("@redis/bloom/dist/commands/t-digest/INFO");
|
|
||||||
info: typeof import("@redis/bloom/dist/commands/t-digest/INFO");
|
|
||||||
MAX: typeof import("@redis/bloom/dist/commands/t-digest/MAX");
|
|
||||||
max: typeof import("@redis/bloom/dist/commands/t-digest/MAX");
|
|
||||||
MERGE: typeof import("@redis/bloom/dist/commands/t-digest/MERGE");
|
|
||||||
merge: typeof import("@redis/bloom/dist/commands/t-digest/MERGE");
|
|
||||||
MIN: typeof import("@redis/bloom/dist/commands/t-digest/MIN");
|
|
||||||
min: typeof import("@redis/bloom/dist/commands/t-digest/MIN");
|
|
||||||
QUANTILE: typeof import("@redis/bloom/dist/commands/t-digest/QUANTILE");
|
|
||||||
quantile: typeof import("@redis/bloom/dist/commands/t-digest/QUANTILE");
|
|
||||||
RANK: typeof import("@redis/bloom/dist/commands/t-digest/RANK");
|
|
||||||
rank: typeof import("@redis/bloom/dist/commands/t-digest/RANK");
|
|
||||||
RESET: typeof import("@redis/bloom/dist/commands/t-digest/RESET");
|
|
||||||
reset: typeof import("@redis/bloom/dist/commands/t-digest/RESET");
|
|
||||||
REVRANK: typeof import("@redis/bloom/dist/commands/t-digest/REVRANK");
|
|
||||||
revRank: typeof import("@redis/bloom/dist/commands/t-digest/REVRANK");
|
|
||||||
TRIMMED_MEAN: typeof import("@redis/bloom/dist/commands/t-digest/TRIMMED_MEAN");
|
|
||||||
trimmedMean: typeof import("@redis/bloom/dist/commands/t-digest/TRIMMED_MEAN");
|
|
||||||
};
|
|
||||||
topK: {
|
|
||||||
ADD: typeof import("@redis/bloom/dist/commands/top-k/ADD");
|
|
||||||
add: typeof import("@redis/bloom/dist/commands/top-k/ADD");
|
|
||||||
COUNT: typeof import("@redis/bloom/dist/commands/top-k/COUNT");
|
|
||||||
count: typeof import("@redis/bloom/dist/commands/top-k/COUNT");
|
|
||||||
INCRBY: typeof import("@redis/bloom/dist/commands/top-k/INCRBY");
|
|
||||||
incrBy: typeof import("@redis/bloom/dist/commands/top-k/INCRBY");
|
|
||||||
INFO: typeof import("@redis/bloom/dist/commands/top-k/INFO");
|
|
||||||
info: typeof import("@redis/bloom/dist/commands/top-k/INFO");
|
|
||||||
LIST_WITHCOUNT: typeof import("@redis/bloom/dist/commands/top-k/LIST_WITHCOUNT");
|
|
||||||
listWithCount: typeof import("@redis/bloom/dist/commands/top-k/LIST_WITHCOUNT");
|
|
||||||
LIST: typeof import("@redis/bloom/dist/commands/top-k/LIST");
|
|
||||||
list: typeof import("@redis/bloom/dist/commands/top-k/LIST");
|
|
||||||
QUERY: typeof import("@redis/bloom/dist/commands/top-k/QUERY");
|
|
||||||
query: typeof import("@redis/bloom/dist/commands/top-k/QUERY");
|
|
||||||
RESERVE: typeof import("@redis/bloom/dist/commands/top-k/RESERVE");
|
|
||||||
reserve: typeof import("@redis/bloom/dist/commands/top-k/RESERVE");
|
|
||||||
};
|
|
||||||
} & import("redis").RedisModules, import("redis").RedisFunctions, import("redis").RedisScripts>;
|
|
||||||
export default redisClient;
|
|
||||||
//# sourceMappingURL=redis.d.ts.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/config/redis.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+FAEf,CAAC;AAeH,eAAe,WAAW,CAAC"}
|
|
||||||
23
backend-old-20260125/dist/config/redis.js
vendored
23
backend-old-20260125/dist/config/redis.js
vendored
@@ -1,23 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
const redis_1 = require("redis");
|
|
||||||
const dotenv_1 = __importDefault(require("dotenv"));
|
|
||||||
dotenv_1.default.config();
|
|
||||||
const redisClient = (0, redis_1.createClient)({
|
|
||||||
url: process.env.REDIS_URL || 'redis://localhost:6379'
|
|
||||||
});
|
|
||||||
redisClient.on('connect', () => {
|
|
||||||
console.log('✅ Connected to Redis');
|
|
||||||
});
|
|
||||||
redisClient.on('error', (err) => {
|
|
||||||
console.error('❌ Redis connection error:', err);
|
|
||||||
});
|
|
||||||
// Connect to Redis
|
|
||||||
redisClient.connect().catch((err) => {
|
|
||||||
console.error('❌ Failed to connect to Redis:', err);
|
|
||||||
});
|
|
||||||
exports.default = redisClient;
|
|
||||||
//# sourceMappingURL=redis.js.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/config/redis.ts"],"names":[],"mappings":";;;;;AAAA,iCAAqC;AACrC,oDAA4B;AAE5B,gBAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,MAAM,WAAW,GAAG,IAAA,oBAAY,EAAC;IAC/B,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,wBAAwB;CACvD,CAAC,CAAC;AAEH,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IAC7B,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;AACtC,CAAC,CAAC,CAAC;AAEH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;IACrC,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,GAAG,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEH,mBAAmB;AACnB,WAAW,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;IACzC,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,GAAG,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC;AAEH,kBAAe,WAAW,CAAC"}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { User } from '../services/jwtKeyManager';
|
|
||||||
export { User } from '../services/jwtKeyManager';
|
|
||||||
export declare function generateToken(user: User): string;
|
|
||||||
export declare function verifyToken(token: string): User | null;
|
|
||||||
export declare function verifyGoogleToken(googleToken: string): Promise<any>;
|
|
||||||
export declare function getGoogleAuthUrl(): string;
|
|
||||||
export declare function exchangeCodeForTokens(code: string): Promise<any>;
|
|
||||||
export declare function getGoogleUserInfo(accessToken: string): Promise<any>;
|
|
||||||
//# sourceMappingURL=simpleAuth.d.ts.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"simpleAuth.d.ts","sourceRoot":"","sources":["../../src/config/simpleAuth.ts"],"names":[],"mappings":"AAAA,OAAsB,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AAKhE,OAAO,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AAEjD,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAEhD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEtD;AAGD,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAWzE;AAGD,wBAAgB,gBAAgB,IAAI,MAAM,CAiCzC;AAGD,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAgGtE;AAGD,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAsEzE"}
|
|
||||||
217
backend-old-20260125/dist/config/simpleAuth.js
vendored
217
backend-old-20260125/dist/config/simpleAuth.js
vendored
@@ -1,217 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.User = void 0;
|
|
||||||
exports.generateToken = generateToken;
|
|
||||||
exports.verifyToken = verifyToken;
|
|
||||||
exports.verifyGoogleToken = verifyGoogleToken;
|
|
||||||
exports.getGoogleAuthUrl = getGoogleAuthUrl;
|
|
||||||
exports.exchangeCodeForTokens = exchangeCodeForTokens;
|
|
||||||
exports.getGoogleUserInfo = getGoogleUserInfo;
|
|
||||||
const jwtKeyManager_1 = __importDefault(require("../services/jwtKeyManager"));
|
|
||||||
// JWT Key Manager now handles all token operations with automatic rotation
|
|
||||||
// No more static JWT_SECRET needed!
|
|
||||||
var jwtKeyManager_2 = require("../services/jwtKeyManager");
|
|
||||||
Object.defineProperty(exports, "User", { enumerable: true, get: function () { return jwtKeyManager_2.User; } });
|
|
||||||
function generateToken(user) {
|
|
||||||
return jwtKeyManager_1.default.generateToken(user);
|
|
||||||
}
|
|
||||||
function verifyToken(token) {
|
|
||||||
return jwtKeyManager_1.default.verifyToken(token);
|
|
||||||
}
|
|
||||||
// Simple Google OAuth2 client using fetch
|
|
||||||
async function verifyGoogleToken(googleToken) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`https://www.googleapis.com/oauth2/v1/userinfo?access_token=${googleToken}`);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Invalid Google token');
|
|
||||||
}
|
|
||||||
return await response.json();
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error verifying Google token:', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Get Google OAuth2 URL
|
|
||||||
function getGoogleAuthUrl() {
|
|
||||||
const clientId = process.env.GOOGLE_CLIENT_ID;
|
|
||||||
const redirectUri = process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/auth/google/callback';
|
|
||||||
console.log('🔗 Generating Google OAuth URL:', {
|
|
||||||
client_id_present: !!clientId,
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
environment: process.env.NODE_ENV || 'development'
|
|
||||||
});
|
|
||||||
if (!clientId) {
|
|
||||||
console.error('❌ GOOGLE_CLIENT_ID not configured');
|
|
||||||
throw new Error('GOOGLE_CLIENT_ID not configured');
|
|
||||||
}
|
|
||||||
if (!redirectUri.startsWith('http')) {
|
|
||||||
console.error('❌ Invalid redirect URI:', redirectUri);
|
|
||||||
throw new Error('GOOGLE_REDIRECT_URI must be a valid HTTP/HTTPS URL');
|
|
||||||
}
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
client_id: clientId,
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
response_type: 'code',
|
|
||||||
scope: 'openid email profile',
|
|
||||||
access_type: 'offline',
|
|
||||||
prompt: 'consent'
|
|
||||||
});
|
|
||||||
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
||||||
console.log('✅ Google OAuth URL generated successfully');
|
|
||||||
return authUrl;
|
|
||||||
}
|
|
||||||
// Exchange authorization code for tokens
|
|
||||||
async function exchangeCodeForTokens(code) {
|
|
||||||
const clientId = process.env.GOOGLE_CLIENT_ID;
|
|
||||||
const clientSecret = process.env.GOOGLE_CLIENT_SECRET;
|
|
||||||
const redirectUri = process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/auth/google/callback';
|
|
||||||
console.log('🔄 Exchanging OAuth code for tokens:', {
|
|
||||||
client_id_present: !!clientId,
|
|
||||||
client_secret_present: !!clientSecret,
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
code_length: code?.length || 0
|
|
||||||
});
|
|
||||||
if (!clientId || !clientSecret) {
|
|
||||||
console.error('❌ Google OAuth credentials not configured:', {
|
|
||||||
client_id: !!clientId,
|
|
||||||
client_secret: !!clientSecret
|
|
||||||
});
|
|
||||||
throw new Error('Google OAuth credentials not configured');
|
|
||||||
}
|
|
||||||
if (!code || code.length < 10) {
|
|
||||||
console.error('❌ Invalid authorization code:', { code_length: code?.length || 0 });
|
|
||||||
throw new Error('Invalid authorization code provided');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const tokenUrl = 'https://oauth2.googleapis.com/token';
|
|
||||||
const requestBody = new URLSearchParams({
|
|
||||||
client_id: clientId,
|
|
||||||
client_secret: clientSecret,
|
|
||||||
code,
|
|
||||||
grant_type: 'authorization_code',
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
});
|
|
||||||
console.log('📡 Making token exchange request to Google:', {
|
|
||||||
url: tokenUrl,
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
grant_type: 'authorization_code'
|
|
||||||
});
|
|
||||||
const response = await fetch(tokenUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'Accept': 'application/json'
|
|
||||||
},
|
|
||||||
body: requestBody,
|
|
||||||
});
|
|
||||||
const responseText = await response.text();
|
|
||||||
console.log('📨 Token exchange response:', {
|
|
||||||
status: response.status,
|
|
||||||
ok: response.ok,
|
|
||||||
content_type: response.headers.get('content-type'),
|
|
||||||
response_length: responseText.length
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error('❌ Token exchange failed:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
response: responseText
|
|
||||||
});
|
|
||||||
throw new Error(`Failed to exchange code for tokens: ${response.status} ${response.statusText}`);
|
|
||||||
}
|
|
||||||
let tokenData;
|
|
||||||
try {
|
|
||||||
tokenData = JSON.parse(responseText);
|
|
||||||
}
|
|
||||||
catch (parseError) {
|
|
||||||
console.error('❌ Failed to parse token response:', { response: responseText });
|
|
||||||
throw new Error('Invalid JSON response from Google token endpoint');
|
|
||||||
}
|
|
||||||
if (!tokenData.access_token) {
|
|
||||||
console.error('❌ No access token in response:', tokenData);
|
|
||||||
throw new Error('No access token received from Google');
|
|
||||||
}
|
|
||||||
console.log('✅ Token exchange successful:', {
|
|
||||||
has_access_token: !!tokenData.access_token,
|
|
||||||
has_refresh_token: !!tokenData.refresh_token,
|
|
||||||
token_type: tokenData.token_type,
|
|
||||||
expires_in: tokenData.expires_in
|
|
||||||
});
|
|
||||||
return tokenData;
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('❌ Error exchanging code for tokens:', {
|
|
||||||
error: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
stack: error instanceof Error ? error.stack : undefined
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Get user info from Google
|
|
||||||
async function getGoogleUserInfo(accessToken) {
|
|
||||||
console.log('👤 Getting user info from Google:', {
|
|
||||||
token_length: accessToken?.length || 0,
|
|
||||||
token_prefix: accessToken ? accessToken.substring(0, 10) + '...' : 'none'
|
|
||||||
});
|
|
||||||
if (!accessToken || accessToken.length < 10) {
|
|
||||||
console.error('❌ Invalid access token for user info request');
|
|
||||||
throw new Error('Invalid access token provided');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const userInfoUrl = `https://www.googleapis.com/oauth2/v2/userinfo?access_token=${accessToken}`;
|
|
||||||
console.log('📡 Making user info request to Google');
|
|
||||||
const response = await fetch(userInfoUrl, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Authorization': `Bearer ${accessToken}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const responseText = await response.text();
|
|
||||||
console.log('📨 User info response:', {
|
|
||||||
status: response.status,
|
|
||||||
ok: response.ok,
|
|
||||||
content_type: response.headers.get('content-type'),
|
|
||||||
response_length: responseText.length
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error('❌ Failed to get user info:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
response: responseText
|
|
||||||
});
|
|
||||||
throw new Error(`Failed to get user info: ${response.status} ${response.statusText}`);
|
|
||||||
}
|
|
||||||
let userData;
|
|
||||||
try {
|
|
||||||
userData = JSON.parse(responseText);
|
|
||||||
}
|
|
||||||
catch (parseError) {
|
|
||||||
console.error('❌ Failed to parse user info response:', { response: responseText });
|
|
||||||
throw new Error('Invalid JSON response from Google user info endpoint');
|
|
||||||
}
|
|
||||||
if (!userData.email) {
|
|
||||||
console.error('❌ No email in user info response:', userData);
|
|
||||||
throw new Error('No email address received from Google');
|
|
||||||
}
|
|
||||||
console.log('✅ User info retrieved successfully:', {
|
|
||||||
email: userData.email,
|
|
||||||
name: userData.name,
|
|
||||||
verified_email: userData.verified_email,
|
|
||||||
has_picture: !!userData.picture
|
|
||||||
});
|
|
||||||
return userData;
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('❌ Error getting Google user info:', {
|
|
||||||
error: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
stack: error instanceof Error ? error.stack : undefined
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//# sourceMappingURL=simpleAuth.js.map
|
|
||||||
File diff suppressed because one or more lines are too long
2
backend-old-20260125/dist/index.d.ts
vendored
2
backend-old-20260125/dist/index.d.ts
vendored
@@ -1,2 +0,0 @@
|
|||||||
export {};
|
|
||||||
//# sourceMappingURL=index.d.ts.map
|
|
||||||
1
backend-old-20260125/dist/index.d.ts.map
vendored
1
backend-old-20260125/dist/index.d.ts.map
vendored
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
|
||||||
271
backend-old-20260125/dist/index.js
vendored
271
backend-old-20260125/dist/index.js
vendored
@@ -1,271 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
const express_1 = __importDefault(require("express"));
|
|
||||||
const cors_1 = __importDefault(require("cors"));
|
|
||||||
const dotenv_1 = __importDefault(require("dotenv"));
|
|
||||||
const authService_1 = __importDefault(require("./services/authService"));
|
|
||||||
const unifiedDataService_1 = __importDefault(require("./services/unifiedDataService"));
|
|
||||||
const simpleValidation_1 = require("./middleware/simpleValidation");
|
|
||||||
const errorHandler_1 = require("./middleware/errorHandler");
|
|
||||||
dotenv_1.default.config();
|
|
||||||
// Log environment variables status on startup
|
|
||||||
console.log('Environment variables loaded:');
|
|
||||||
console.log('- GOOGLE_CLIENT_ID:', process.env.GOOGLE_CLIENT_ID ? 'Set' : 'Not set');
|
|
||||||
console.log('- GOOGLE_CLIENT_SECRET:', process.env.GOOGLE_CLIENT_SECRET ? 'Set' : 'Not set');
|
|
||||||
console.log('- GOOGLE_REDIRECT_URI:', process.env.GOOGLE_REDIRECT_URI || 'Not set');
|
|
||||||
const app = (0, express_1.default)();
|
|
||||||
const port = process.env.PORT || 3000;
|
|
||||||
// Middleware
|
|
||||||
app.use((0, cors_1.default)({
|
|
||||||
origin: [
|
|
||||||
process.env.FRONTEND_URL || 'http://localhost:5173',
|
|
||||||
'https://bsa.madeamess.online'
|
|
||||||
],
|
|
||||||
credentials: true
|
|
||||||
}));
|
|
||||||
app.use(express_1.default.json());
|
|
||||||
app.use(express_1.default.static('public'));
|
|
||||||
// Health check
|
|
||||||
app.get('/api/health', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
status: 'OK',
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
version: '2.0.0' // Simplified version
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// Auth routes
|
|
||||||
app.get('/auth/setup', async (req, res) => {
|
|
||||||
try {
|
|
||||||
// Check if any users exist in the system
|
|
||||||
const userCount = await unifiedDataService_1.default.getUserCount();
|
|
||||||
res.json({
|
|
||||||
needsSetup: userCount === 0,
|
|
||||||
hasUsers: userCount > 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error in /auth/setup:', error);
|
|
||||||
res.status(500).json({ error: 'Failed to check setup status' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.get('/auth/google', (req, res) => {
|
|
||||||
res.redirect(authService_1.default.getGoogleAuthUrl());
|
|
||||||
});
|
|
||||||
app.get('/auth/google/url', (req, res) => {
|
|
||||||
try {
|
|
||||||
// Return the Google OAuth URL as JSON for the frontend
|
|
||||||
const url = authService_1.default.getGoogleAuthUrl();
|
|
||||||
res.json({ url });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error generating Google Auth URL:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
error: 'Google OAuth configuration error',
|
|
||||||
message: error.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/auth/google/callback', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { code } = req.body;
|
|
||||||
const { user, token } = await authService_1.default.handleGoogleAuth(code);
|
|
||||||
res.json({ user, token });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(400).json({ error: 'Authentication failed' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/auth/google/exchange', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { code } = req.body;
|
|
||||||
const { user, token } = await authService_1.default.handleGoogleAuth(code);
|
|
||||||
res.json({ user, token });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(400).json({ error: 'Authentication failed' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/auth/google/verify', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { credential } = req.body;
|
|
||||||
const { user, token } = await authService_1.default.verifyGoogleToken(credential);
|
|
||||||
res.json({ user, token });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Google token verification error:', error);
|
|
||||||
res.status(400).json({ error: 'Authentication failed' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.get('/auth/me', authService_1.default.requireAuth, (req, res) => {
|
|
||||||
res.json(req.user);
|
|
||||||
});
|
|
||||||
app.post('/auth/logout', (req, res) => {
|
|
||||||
res.json({ message: 'Logged out successfully' });
|
|
||||||
});
|
|
||||||
// VIP routes
|
|
||||||
app.get('/api/vips', async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const vips = await unifiedDataService_1.default.getVips();
|
|
||||||
res.json(vips);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.get('/api/vips/:id', async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const vip = await unifiedDataService_1.default.getVipById(req.params.id);
|
|
||||||
if (!vip)
|
|
||||||
return res.status(404).json({ error: 'VIP not found' });
|
|
||||||
res.json(vip);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/api/vips', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), (0, simpleValidation_1.validate)(simpleValidation_1.schemas.createVip), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const vip = await unifiedDataService_1.default.createVip(req.body);
|
|
||||||
res.status(201).json(vip);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.put('/api/vips/:id', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), (0, simpleValidation_1.validate)(simpleValidation_1.schemas.updateVip), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const vip = await unifiedDataService_1.default.updateVip(req.params.id, req.body);
|
|
||||||
if (!vip)
|
|
||||||
return res.status(404).json({ error: 'VIP not found' });
|
|
||||||
res.json(vip);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.delete('/api/vips/:id', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const vip = await unifiedDataService_1.default.deleteVip(req.params.id);
|
|
||||||
if (!vip)
|
|
||||||
return res.status(404).json({ error: 'VIP not found' });
|
|
||||||
res.json({ message: 'VIP deleted successfully' });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Driver routes
|
|
||||||
app.get('/api/drivers', async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const drivers = await unifiedDataService_1.default.getDrivers();
|
|
||||||
res.json(drivers);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/api/drivers', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), (0, simpleValidation_1.validate)(simpleValidation_1.schemas.createDriver), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const driver = await unifiedDataService_1.default.createDriver(req.body);
|
|
||||||
res.status(201).json(driver);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.put('/api/drivers/:id', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), (0, simpleValidation_1.validate)(simpleValidation_1.schemas.updateDriver), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const driver = await unifiedDataService_1.default.updateDriver(req.params.id, req.body);
|
|
||||||
if (!driver)
|
|
||||||
return res.status(404).json({ error: 'Driver not found' });
|
|
||||||
res.json(driver);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.delete('/api/drivers/:id', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const driver = await unifiedDataService_1.default.deleteDriver(req.params.id);
|
|
||||||
if (!driver)
|
|
||||||
return res.status(404).json({ error: 'Driver not found' });
|
|
||||||
res.json({ message: 'Driver deleted successfully' });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Schedule routes
|
|
||||||
app.get('/api/vips/:vipId/schedule', authService_1.default.requireAuth, async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const schedule = await unifiedDataService_1.default.getScheduleByVipId(req.params.vipId);
|
|
||||||
res.json(schedule);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/api/vips/:vipId/schedule', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), (0, simpleValidation_1.validate)(simpleValidation_1.schemas.createScheduleEvent), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const event = await unifiedDataService_1.default.createScheduleEvent(req.params.vipId, req.body);
|
|
||||||
res.status(201).json(event);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.put('/api/vips/:vipId/schedule/:eventId', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), (0, simpleValidation_1.validate)(simpleValidation_1.schemas.updateScheduleEvent), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const event = await unifiedDataService_1.default.updateScheduleEvent(req.params.eventId, req.body);
|
|
||||||
if (!event)
|
|
||||||
return res.status(404).json({ error: 'Event not found' });
|
|
||||||
res.json(event);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.delete('/api/vips/:vipId/schedule/:eventId', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const event = await unifiedDataService_1.default.deleteScheduleEvent(req.params.eventId);
|
|
||||||
if (!event)
|
|
||||||
return res.status(404).json({ error: 'Event not found' });
|
|
||||||
res.json({ message: 'Event deleted successfully' });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Admin routes (simplified)
|
|
||||||
app.get('/api/admin/settings', authService_1.default.requireAuth, authService_1.default.requireRole(['administrator']), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const settings = await unifiedDataService_1.default.getAdminSettings();
|
|
||||||
res.json(settings);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/api/admin/settings', authService_1.default.requireAuth, authService_1.default.requireRole(['administrator']), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const { key, value } = req.body;
|
|
||||||
await unifiedDataService_1.default.updateAdminSetting(key, value);
|
|
||||||
res.json({ message: 'Setting updated successfully' });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Error handling
|
|
||||||
app.use(errorHandler_1.notFoundHandler);
|
|
||||||
app.use(errorHandler_1.errorHandler);
|
|
||||||
// Start server
|
|
||||||
app.listen(port, () => {
|
|
||||||
console.log(`🚀 Server running on port ${port}`);
|
|
||||||
console.log(`🏥 Health check: http://localhost:${port}/api/health`);
|
|
||||||
console.log(`📚 API docs: http://localhost:${port}/api-docs.html`);
|
|
||||||
});
|
|
||||||
//# sourceMappingURL=index.js.map
|
|
||||||
1
backend-old-20260125/dist/index.js.map
vendored
1
backend-old-20260125/dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
export {};
|
|
||||||
//# sourceMappingURL=index.original.d.ts.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"index.original.d.ts","sourceRoot":"","sources":["../src/index.original.ts"],"names":[],"mappings":""}
|
|
||||||
765
backend-old-20260125/dist/index.original.js
vendored
765
backend-old-20260125/dist/index.original.js
vendored
@@ -1,765 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
||||||
if (k2 === undefined) k2 = k;
|
|
||||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
||||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
||||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
||||||
}
|
|
||||||
Object.defineProperty(o, k2, desc);
|
|
||||||
}) : (function(o, m, k, k2) {
|
|
||||||
if (k2 === undefined) k2 = k;
|
|
||||||
o[k2] = m[k];
|
|
||||||
}));
|
|
||||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
||||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
||||||
}) : function(o, v) {
|
|
||||||
o["default"] = v;
|
|
||||||
});
|
|
||||||
var __importStar = (this && this.__importStar) || (function () {
|
|
||||||
var ownKeys = function(o) {
|
|
||||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
||||||
var ar = [];
|
|
||||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
||||||
return ar;
|
|
||||||
};
|
|
||||||
return ownKeys(o);
|
|
||||||
};
|
|
||||||
return function (mod) {
|
|
||||||
if (mod && mod.__esModule) return mod;
|
|
||||||
var result = {};
|
|
||||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
||||||
__setModuleDefault(result, mod);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
const express_1 = __importDefault(require("express"));
|
|
||||||
const dotenv_1 = __importDefault(require("dotenv"));
|
|
||||||
const cors_1 = __importDefault(require("cors"));
|
|
||||||
const simpleAuth_1 = __importStar(require("./routes/simpleAuth"));
|
|
||||||
const flightService_1 = __importDefault(require("./services/flightService"));
|
|
||||||
const driverConflictService_1 = __importDefault(require("./services/driverConflictService"));
|
|
||||||
const scheduleValidationService_1 = __importDefault(require("./services/scheduleValidationService"));
|
|
||||||
const flightTrackingScheduler_1 = __importDefault(require("./services/flightTrackingScheduler"));
|
|
||||||
const enhancedDataService_1 = __importDefault(require("./services/enhancedDataService"));
|
|
||||||
const databaseService_1 = __importDefault(require("./services/databaseService"));
|
|
||||||
const jwtKeyManager_1 = __importDefault(require("./services/jwtKeyManager")); // Initialize JWT Key Manager
|
|
||||||
const errorHandler_1 = require("./middleware/errorHandler");
|
|
||||||
const logger_1 = require("./middleware/logger");
|
|
||||||
const validation_1 = require("./middleware/validation");
|
|
||||||
const schemas_1 = require("./types/schemas");
|
|
||||||
dotenv_1.default.config();
|
|
||||||
const app = (0, express_1.default)();
|
|
||||||
const port = process.env.PORT ? parseInt(process.env.PORT) : 3000;
|
|
||||||
// Middleware
|
|
||||||
app.use((0, cors_1.default)({
|
|
||||||
origin: [
|
|
||||||
process.env.FRONTEND_URL || 'http://localhost:5173',
|
|
||||||
'http://localhost:5173',
|
|
||||||
'http://localhost:3000',
|
|
||||||
'http://localhost', // Frontend Docker container (local testing)
|
|
||||||
'https://bsa.madeamess.online' // Production frontend domain (where users access the site)
|
|
||||||
],
|
|
||||||
credentials: true
|
|
||||||
}));
|
|
||||||
app.use(express_1.default.json());
|
|
||||||
app.use(express_1.default.urlencoded({ extended: true }));
|
|
||||||
// Add request logging
|
|
||||||
app.use(logger_1.requestLogger);
|
|
||||||
// Simple JWT-based authentication - no passport needed
|
|
||||||
// Authentication routes
|
|
||||||
app.use('/auth', simpleAuth_1.default);
|
|
||||||
// Temporary admin bypass route (remove after setup)
|
|
||||||
app.get('/admin-bypass', (req, res) => {
|
|
||||||
res.redirect(`${process.env.FRONTEND_URL || 'http://localhost:5173'}/admin?bypass=true`);
|
|
||||||
});
|
|
||||||
// Serve static files from public directory
|
|
||||||
app.use(express_1.default.static('public'));
|
|
||||||
// Enhanced health check endpoint with authentication system status
|
|
||||||
app.get('/api/health', (0, errorHandler_1.asyncHandler)(async (req, res) => {
|
|
||||||
const timestamp = new Date().toISOString();
|
|
||||||
// Check JWT Key Manager status
|
|
||||||
const jwtStatus = jwtKeyManager_1.default.getStatus();
|
|
||||||
// Check environment variables
|
|
||||||
const envCheck = {
|
|
||||||
google_client_id: !!process.env.GOOGLE_CLIENT_ID,
|
|
||||||
google_client_secret: !!process.env.GOOGLE_CLIENT_SECRET,
|
|
||||||
google_redirect_uri: !!process.env.GOOGLE_REDIRECT_URI,
|
|
||||||
frontend_url: !!process.env.FRONTEND_URL,
|
|
||||||
database_url: !!process.env.DATABASE_URL,
|
|
||||||
admin_password: !!process.env.ADMIN_PASSWORD
|
|
||||||
};
|
|
||||||
// Check database connectivity
|
|
||||||
let databaseStatus = 'unknown';
|
|
||||||
let userCount = 0;
|
|
||||||
try {
|
|
||||||
userCount = await databaseService_1.default.getUserCount();
|
|
||||||
databaseStatus = 'connected';
|
|
||||||
}
|
|
||||||
catch (dbError) {
|
|
||||||
databaseStatus = 'disconnected';
|
|
||||||
console.error('Health check - Database error:', dbError);
|
|
||||||
}
|
|
||||||
// Overall system health
|
|
||||||
const isHealthy = databaseStatus === 'connected' &&
|
|
||||||
jwtStatus.hasCurrentKey &&
|
|
||||||
envCheck.google_client_id &&
|
|
||||||
envCheck.google_client_secret;
|
|
||||||
const healthData = {
|
|
||||||
status: isHealthy ? 'OK' : 'DEGRADED',
|
|
||||||
timestamp,
|
|
||||||
version: '1.0.0',
|
|
||||||
environment: process.env.NODE_ENV || 'development',
|
|
||||||
services: {
|
|
||||||
database: {
|
|
||||||
status: databaseStatus,
|
|
||||||
user_count: databaseStatus === 'connected' ? userCount : null
|
|
||||||
},
|
|
||||||
authentication: {
|
|
||||||
jwt_key_manager: jwtStatus,
|
|
||||||
oauth_configured: envCheck.google_client_id && envCheck.google_client_secret,
|
|
||||||
environment_variables: envCheck
|
|
||||||
}
|
|
||||||
},
|
|
||||||
uptime: process.uptime(),
|
|
||||||
memory: process.memoryUsage()
|
|
||||||
};
|
|
||||||
// Log health check for monitoring
|
|
||||||
console.log(`🏥 Health Check [${timestamp}]:`, {
|
|
||||||
status: healthData.status,
|
|
||||||
database: databaseStatus,
|
|
||||||
jwt_keys: jwtStatus.hasCurrentKey,
|
|
||||||
oauth: envCheck.google_client_id && envCheck.google_client_secret
|
|
||||||
});
|
|
||||||
res.status(isHealthy ? 200 : 503).json(healthData);
|
|
||||||
}));
|
|
||||||
// Data is now persisted using dataService - no more in-memory storage!
|
|
||||||
// Admin password - MUST be set via environment variable in production
|
|
||||||
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'CHANGE_ME_ADMIN_PASSWORD';
|
|
||||||
// Initialize flight tracking scheduler
|
|
||||||
const flightTracker = new flightTrackingScheduler_1.default(flightService_1.default);
|
|
||||||
// VIP routes (protected)
|
|
||||||
app.post('/api/vips', simpleAuth_1.requireAuth, (0, simpleAuth_1.requireRole)(['coordinator', 'administrator']), (0, validation_1.validate)(schemas_1.createVipSchema), (0, errorHandler_1.asyncHandler)(async (req, res) => {
|
|
||||||
// Create a new VIP - data is already validated
|
|
||||||
const { name, organization, department, // New: Office of Development or Admin
|
|
||||||
transportMode, flightNumber, // Legacy single flight
|
|
||||||
flights, // New: array of flights
|
|
||||||
expectedArrival, needsAirportPickup, needsVenueTransport, notes } = req.body;
|
|
||||||
const newVip = {
|
|
||||||
id: Date.now().toString(), // Simple ID generation
|
|
||||||
name,
|
|
||||||
organization,
|
|
||||||
department: department || 'Office of Development', // Default to Office of Development
|
|
||||||
transportMode: transportMode || 'flight',
|
|
||||||
// Support both legacy single flight and new multiple flights
|
|
||||||
flightNumber: transportMode === 'flight' && !flights ? flightNumber : undefined,
|
|
||||||
flights: transportMode === 'flight' && flights ? flights : undefined,
|
|
||||||
expectedArrival: transportMode === 'self-driving' ? expectedArrival : undefined,
|
|
||||||
arrivalTime: transportMode === 'flight' ? undefined : expectedArrival, // Legacy field for flight arrivals
|
|
||||||
needsAirportPickup: transportMode === 'flight' ? (needsAirportPickup !== false) : false,
|
|
||||||
needsVenueTransport: needsVenueTransport !== false, // Default to true
|
|
||||||
assignedDriverIds: [],
|
|
||||||
notes: notes || '',
|
|
||||||
schedule: []
|
|
||||||
};
|
|
||||||
const savedVip = await enhancedDataService_1.default.addVip(newVip);
|
|
||||||
// Add flights to tracking scheduler if applicable
|
|
||||||
if (savedVip.transportMode === 'flight' && savedVip.flights && savedVip.flights.length > 0) {
|
|
||||||
flightTracker.addVipFlights(savedVip.id, savedVip.name, savedVip.flights);
|
|
||||||
}
|
|
||||||
res.status(201).json(savedVip);
|
|
||||||
}));
|
|
||||||
app.get('/api/vips', simpleAuth_1.requireAuth, async (req, res) => {
|
|
||||||
try {
|
|
||||||
// Fetch all VIPs
|
|
||||||
const vips = await enhancedDataService_1.default.getVips();
|
|
||||||
res.json(vips);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to fetch VIPs' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.put('/api/vips/:id', simpleAuth_1.requireAuth, (0, simpleAuth_1.requireRole)(['coordinator', 'administrator']), (0, validation_1.validate)(schemas_1.updateVipSchema), (0, errorHandler_1.asyncHandler)(async (req, res) => {
|
|
||||||
// Update a VIP - data is already validated
|
|
||||||
const { id } = req.params;
|
|
||||||
const { name, organization, department, // New: Office of Development or Admin
|
|
||||||
transportMode, flightNumber, // Legacy single flight
|
|
||||||
flights, // New: array of flights
|
|
||||||
expectedArrival, needsAirportPickup, needsVenueTransport, notes } = req.body;
|
|
||||||
const updatedVip = {
|
|
||||||
name,
|
|
||||||
organization,
|
|
||||||
department: department || 'Office of Development',
|
|
||||||
transportMode: transportMode || 'flight',
|
|
||||||
// Support both legacy single flight and new multiple flights
|
|
||||||
flights: transportMode === 'flight' && flights ? flights : undefined,
|
|
||||||
expectedArrival: transportMode === 'self-driving' ? expectedArrival : undefined,
|
|
||||||
needsAirportPickup: transportMode === 'flight' ? (needsAirportPickup !== false) : false,
|
|
||||||
needsVenueTransport: needsVenueTransport !== false,
|
|
||||||
notes: notes || ''
|
|
||||||
};
|
|
||||||
const savedVip = await enhancedDataService_1.default.updateVip(id, updatedVip);
|
|
||||||
if (!savedVip) {
|
|
||||||
return res.status(404).json({ error: 'VIP not found' });
|
|
||||||
}
|
|
||||||
// Update flight tracking if needed
|
|
||||||
if (savedVip.transportMode === 'flight') {
|
|
||||||
// Remove old flights
|
|
||||||
flightTracker.removeVipFlights(id);
|
|
||||||
// Add new flights if any
|
|
||||||
if (savedVip.flights && savedVip.flights.length > 0) {
|
|
||||||
flightTracker.addVipFlights(savedVip.id, savedVip.name, savedVip.flights);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.json(savedVip);
|
|
||||||
}));
|
|
||||||
app.delete('/api/vips/:id', simpleAuth_1.requireAuth, (0, simpleAuth_1.requireRole)(['coordinator', 'administrator']), async (req, res) => {
|
|
||||||
// Delete a VIP
|
|
||||||
const { id } = req.params;
|
|
||||||
try {
|
|
||||||
const deletedVip = await enhancedDataService_1.default.deleteVip(id);
|
|
||||||
if (!deletedVip) {
|
|
||||||
return res.status(404).json({ error: 'VIP not found' });
|
|
||||||
}
|
|
||||||
// Remove from flight tracking
|
|
||||||
flightTracker.removeVipFlights(id);
|
|
||||||
res.json({ message: 'VIP deleted successfully', vip: deletedVip });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to delete VIP' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Driver routes (protected)
|
|
||||||
app.post('/api/drivers', simpleAuth_1.requireAuth, (0, simpleAuth_1.requireRole)(['coordinator', 'administrator']), (0, validation_1.validate)(schemas_1.createDriverSchema), (0, errorHandler_1.asyncHandler)(async (req, res) => {
|
|
||||||
// Create a new driver - data is already validated
|
|
||||||
const { name, phone, email, vehicleInfo, status } = req.body;
|
|
||||||
const newDriver = {
|
|
||||||
id: Date.now().toString(),
|
|
||||||
name,
|
|
||||||
phone,
|
|
||||||
email,
|
|
||||||
vehicleInfo,
|
|
||||||
status: status || 'available',
|
|
||||||
department: 'Office of Development', // Default to Office of Development
|
|
||||||
currentLocation: { lat: 0, lng: 0 },
|
|
||||||
assignedVipIds: []
|
|
||||||
};
|
|
||||||
const savedDriver = await enhancedDataService_1.default.addDriver(newDriver);
|
|
||||||
res.status(201).json(savedDriver);
|
|
||||||
}));
|
|
||||||
app.get('/api/drivers', simpleAuth_1.requireAuth, async (req, res) => {
|
|
||||||
try {
|
|
||||||
// Fetch all drivers
|
|
||||||
const drivers = await enhancedDataService_1.default.getDrivers();
|
|
||||||
res.json(drivers);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to fetch drivers' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.put('/api/drivers/:id', simpleAuth_1.requireAuth, (0, simpleAuth_1.requireRole)(['coordinator', 'administrator']), async (req, res) => {
|
|
||||||
// Update a driver
|
|
||||||
const { id } = req.params;
|
|
||||||
const { name, phone, currentLocation, department } = req.body;
|
|
||||||
try {
|
|
||||||
const updatedDriver = {
|
|
||||||
name,
|
|
||||||
phone,
|
|
||||||
department: department || 'Office of Development',
|
|
||||||
currentLocation: currentLocation || { lat: 0, lng: 0 }
|
|
||||||
};
|
|
||||||
const savedDriver = await enhancedDataService_1.default.updateDriver(id, updatedDriver);
|
|
||||||
if (!savedDriver) {
|
|
||||||
return res.status(404).json({ error: 'Driver not found' });
|
|
||||||
}
|
|
||||||
res.json(savedDriver);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to update driver' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.delete('/api/drivers/:id', simpleAuth_1.requireAuth, (0, simpleAuth_1.requireRole)(['coordinator', 'administrator']), async (req, res) => {
|
|
||||||
// Delete a driver
|
|
||||||
const { id } = req.params;
|
|
||||||
try {
|
|
||||||
const deletedDriver = await enhancedDataService_1.default.deleteDriver(id);
|
|
||||||
if (!deletedDriver) {
|
|
||||||
return res.status(404).json({ error: 'Driver not found' });
|
|
||||||
}
|
|
||||||
res.json({ message: 'Driver deleted successfully', driver: deletedDriver });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to delete driver' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Enhanced flight tracking routes with date specificity
|
|
||||||
app.get('/api/flights/:flightNumber', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { flightNumber } = req.params;
|
|
||||||
const { date, departureAirport, arrivalAirport } = req.query;
|
|
||||||
// Default to today if no date provided
|
|
||||||
const flightDate = date || new Date().toISOString().split('T')[0];
|
|
||||||
const flightData = await flightService_1.default.getFlightInfo({
|
|
||||||
flightNumber,
|
|
||||||
date: flightDate,
|
|
||||||
departureAirport: departureAirport,
|
|
||||||
arrivalAirport: arrivalAirport
|
|
||||||
});
|
|
||||||
if (flightData) {
|
|
||||||
// Always return flight data for validation, even if date doesn't match
|
|
||||||
res.json(flightData);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Only return 404 if the flight number itself is invalid
|
|
||||||
res.status(404).json({ error: 'Invalid flight number - this flight does not exist' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to fetch flight data' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Start periodic updates for a flight
|
|
||||||
app.post('/api/flights/:flightNumber/track', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { flightNumber } = req.params;
|
|
||||||
const { date, intervalMinutes = 5 } = req.body;
|
|
||||||
if (!date) {
|
|
||||||
return res.status(400).json({ error: 'Flight date is required' });
|
|
||||||
}
|
|
||||||
flightService_1.default.startPeriodicUpdates({
|
|
||||||
flightNumber,
|
|
||||||
date
|
|
||||||
}, intervalMinutes);
|
|
||||||
res.json({ message: `Started tracking ${flightNumber} on ${date}` });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to start flight tracking' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Stop periodic updates for a flight
|
|
||||||
app.delete('/api/flights/:flightNumber/track', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { flightNumber } = req.params;
|
|
||||||
const { date } = req.query;
|
|
||||||
if (!date) {
|
|
||||||
return res.status(400).json({ error: 'Flight date is required' });
|
|
||||||
}
|
|
||||||
const key = `${flightNumber}_${date}`;
|
|
||||||
flightService_1.default.stopPeriodicUpdates(key);
|
|
||||||
res.json({ message: `Stopped tracking ${flightNumber} on ${date}` });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to stop flight tracking' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/api/flights/batch', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { flights } = req.body;
|
|
||||||
if (!Array.isArray(flights)) {
|
|
||||||
return res.status(400).json({ error: 'flights must be an array of {flightNumber, date} objects' });
|
|
||||||
}
|
|
||||||
// Validate flight objects
|
|
||||||
for (const flight of flights) {
|
|
||||||
if (!flight.flightNumber || !flight.date) {
|
|
||||||
return res.status(400).json({ error: 'Each flight must have flightNumber and date' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const flightData = await flightService_1.default.getMultipleFlights(flights);
|
|
||||||
res.json(flightData);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to fetch flight data' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Get flight tracking status
|
|
||||||
app.get('/api/flights/tracking/status', (req, res) => {
|
|
||||||
const status = flightTracker.getTrackingStatus();
|
|
||||||
res.json(status);
|
|
||||||
});
|
|
||||||
// Schedule management routes (protected)
|
|
||||||
app.get('/api/vips/:vipId/schedule', simpleAuth_1.requireAuth, async (req, res) => {
|
|
||||||
const { vipId } = req.params;
|
|
||||||
try {
|
|
||||||
const vipSchedule = await enhancedDataService_1.default.getSchedule(vipId);
|
|
||||||
res.json(vipSchedule);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to fetch schedule' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/api/vips/:vipId/schedule', simpleAuth_1.requireAuth, (0, simpleAuth_1.requireRole)(['coordinator', 'administrator']), async (req, res) => {
|
|
||||||
const { vipId } = req.params;
|
|
||||||
const { title, location, startTime, endTime, description, type, assignedDriverId } = req.body;
|
|
||||||
// Validate the event
|
|
||||||
const validationErrors = scheduleValidationService_1.default.validateEvent({
|
|
||||||
title: title || '',
|
|
||||||
location: location || '',
|
|
||||||
startTime: startTime || '',
|
|
||||||
endTime: endTime || '',
|
|
||||||
type: type || ''
|
|
||||||
}, false);
|
|
||||||
const { critical, warnings } = scheduleValidationService_1.default.categorizeErrors(validationErrors);
|
|
||||||
// Return validation errors if any critical errors exist
|
|
||||||
if (critical.length > 0) {
|
|
||||||
return res.status(400).json({
|
|
||||||
error: 'Validation failed',
|
|
||||||
validationErrors: critical,
|
|
||||||
warnings: warnings,
|
|
||||||
message: scheduleValidationService_1.default.getErrorSummary(critical)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const newEvent = {
|
|
||||||
id: Date.now().toString(),
|
|
||||||
title,
|
|
||||||
location,
|
|
||||||
startTime,
|
|
||||||
endTime,
|
|
||||||
description: description || '',
|
|
||||||
assignedDriverId: assignedDriverId || '',
|
|
||||||
status: 'scheduled',
|
|
||||||
type
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
const savedEvent = await enhancedDataService_1.default.addScheduleEvent(vipId, newEvent);
|
|
||||||
// Include warnings in the response if any
|
|
||||||
const response = { ...savedEvent };
|
|
||||||
if (warnings.length > 0) {
|
|
||||||
response.warnings = warnings;
|
|
||||||
}
|
|
||||||
res.status(201).json(response);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to create schedule event' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.put('/api/vips/:vipId/schedule/:eventId', simpleAuth_1.requireAuth, (0, simpleAuth_1.requireRole)(['coordinator', 'administrator']), async (req, res) => {
|
|
||||||
const { vipId, eventId } = req.params;
|
|
||||||
const { title, location, startTime, endTime, description, type, assignedDriverId, status } = req.body;
|
|
||||||
// Validate the updated event (with edit flag for grace period)
|
|
||||||
const validationErrors = scheduleValidationService_1.default.validateEvent({
|
|
||||||
title: title || '',
|
|
||||||
location: location || '',
|
|
||||||
startTime: startTime || '',
|
|
||||||
endTime: endTime || '',
|
|
||||||
type: type || ''
|
|
||||||
}, true);
|
|
||||||
const { critical, warnings } = scheduleValidationService_1.default.categorizeErrors(validationErrors);
|
|
||||||
// Return validation errors if any critical errors exist
|
|
||||||
if (critical.length > 0) {
|
|
||||||
return res.status(400).json({
|
|
||||||
error: 'Validation failed',
|
|
||||||
validationErrors: critical,
|
|
||||||
warnings: warnings,
|
|
||||||
message: scheduleValidationService_1.default.getErrorSummary(critical)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const updatedEvent = {
|
|
||||||
id: eventId,
|
|
||||||
title,
|
|
||||||
location,
|
|
||||||
startTime,
|
|
||||||
endTime,
|
|
||||||
description: description || '',
|
|
||||||
assignedDriverId: assignedDriverId || '',
|
|
||||||
type,
|
|
||||||
status: status || 'scheduled'
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
const savedEvent = await enhancedDataService_1.default.updateScheduleEvent(vipId, eventId, updatedEvent);
|
|
||||||
if (!savedEvent) {
|
|
||||||
return res.status(404).json({ error: 'Event not found' });
|
|
||||||
}
|
|
||||||
// Include warnings in the response if any
|
|
||||||
const response = { ...savedEvent };
|
|
||||||
if (warnings.length > 0) {
|
|
||||||
response.warnings = warnings;
|
|
||||||
}
|
|
||||||
res.json(response);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to update schedule event' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.patch('/api/vips/:vipId/schedule/:eventId/status', simpleAuth_1.requireAuth, async (req, res) => {
|
|
||||||
const { vipId, eventId } = req.params;
|
|
||||||
const { status } = req.body;
|
|
||||||
try {
|
|
||||||
const currentSchedule = await enhancedDataService_1.default.getSchedule(vipId);
|
|
||||||
const currentEvent = currentSchedule.find((event) => event.id === eventId);
|
|
||||||
if (!currentEvent) {
|
|
||||||
return res.status(404).json({ error: 'Event not found' });
|
|
||||||
}
|
|
||||||
const updatedEvent = { ...currentEvent, status };
|
|
||||||
const savedEvent = await enhancedDataService_1.default.updateScheduleEvent(vipId, eventId, updatedEvent);
|
|
||||||
if (!savedEvent) {
|
|
||||||
return res.status(404).json({ error: 'Event not found' });
|
|
||||||
}
|
|
||||||
res.json(savedEvent);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to update event status' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.delete('/api/vips/:vipId/schedule/:eventId', simpleAuth_1.requireAuth, (0, simpleAuth_1.requireRole)(['coordinator', 'administrator']), async (req, res) => {
|
|
||||||
const { vipId, eventId } = req.params;
|
|
||||||
try {
|
|
||||||
const deletedEvent = await enhancedDataService_1.default.deleteScheduleEvent(vipId, eventId);
|
|
||||||
if (!deletedEvent) {
|
|
||||||
return res.status(404).json({ error: 'Event not found' });
|
|
||||||
}
|
|
||||||
res.json({ message: 'Event deleted successfully', event: deletedEvent });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to delete schedule event' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Driver availability and conflict checking (protected)
|
|
||||||
app.post('/api/drivers/availability', simpleAuth_1.requireAuth, async (req, res) => {
|
|
||||||
const { startTime, endTime, location } = req.body;
|
|
||||||
if (!startTime || !endTime) {
|
|
||||||
return res.status(400).json({ error: 'startTime and endTime are required' });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const allSchedules = await enhancedDataService_1.default.getAllSchedules();
|
|
||||||
const drivers = await enhancedDataService_1.default.getDrivers();
|
|
||||||
const availability = driverConflictService_1.default.getDriverAvailability({ startTime, endTime, location: location || '' }, allSchedules, drivers);
|
|
||||||
res.json(availability);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to check driver availability' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Check conflicts for specific driver assignment (protected)
|
|
||||||
app.post('/api/drivers/:driverId/conflicts', simpleAuth_1.requireAuth, async (req, res) => {
|
|
||||||
const { driverId } = req.params;
|
|
||||||
const { startTime, endTime, location } = req.body;
|
|
||||||
if (!startTime || !endTime) {
|
|
||||||
return res.status(400).json({ error: 'startTime and endTime are required' });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const allSchedules = await enhancedDataService_1.default.getAllSchedules();
|
|
||||||
const drivers = await enhancedDataService_1.default.getDrivers();
|
|
||||||
const conflicts = driverConflictService_1.default.checkDriverConflicts(driverId, { startTime, endTime, location: location || '' }, allSchedules, drivers);
|
|
||||||
res.json({ conflicts });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to check driver conflicts' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Get driver's complete schedule (protected)
|
|
||||||
app.get('/api/drivers/:driverId/schedule', simpleAuth_1.requireAuth, async (req, res) => {
|
|
||||||
const { driverId } = req.params;
|
|
||||||
try {
|
|
||||||
const drivers = await enhancedDataService_1.default.getDrivers();
|
|
||||||
const driver = drivers.find((d) => d.id === driverId);
|
|
||||||
if (!driver) {
|
|
||||||
return res.status(404).json({ error: 'Driver not found' });
|
|
||||||
}
|
|
||||||
// Get all events assigned to this driver across all VIPs
|
|
||||||
const driverSchedule = [];
|
|
||||||
const allSchedules = await enhancedDataService_1.default.getAllSchedules();
|
|
||||||
const vips = await enhancedDataService_1.default.getVips();
|
|
||||||
Object.entries(allSchedules).forEach(([vipId, events]) => {
|
|
||||||
events.forEach((event) => {
|
|
||||||
if (event.assignedDriverId === driverId) {
|
|
||||||
// Get VIP name
|
|
||||||
const vip = vips.find((v) => v.id === vipId);
|
|
||||||
driverSchedule.push({
|
|
||||||
...event,
|
|
||||||
vipId,
|
|
||||||
vipName: vip ? vip.name : 'Unknown VIP'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// Sort by start time
|
|
||||||
driverSchedule.sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime());
|
|
||||||
res.json({
|
|
||||||
driver: {
|
|
||||||
id: driver.id,
|
|
||||||
name: driver.name,
|
|
||||||
phone: driver.phone,
|
|
||||||
department: driver.department
|
|
||||||
},
|
|
||||||
schedule: driverSchedule
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to fetch driver schedule' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Admin routes
|
|
||||||
app.post('/api/admin/authenticate', (req, res) => {
|
|
||||||
const { password } = req.body;
|
|
||||||
if (password === ADMIN_PASSWORD) {
|
|
||||||
res.json({ success: true });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
res.status(401).json({ error: 'Invalid password' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.get('/api/admin/settings', async (req, res) => {
|
|
||||||
const adminAuth = req.headers['admin-auth'];
|
|
||||||
if (adminAuth !== 'true') {
|
|
||||||
return res.status(401).json({ error: 'Unauthorized' });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const adminSettings = await enhancedDataService_1.default.getAdminSettings();
|
|
||||||
// Return settings but mask API keys for display only
|
|
||||||
// IMPORTANT: Don't return the actual keys, just indicate they exist
|
|
||||||
const maskedSettings = {
|
|
||||||
apiKeys: {
|
|
||||||
aviationStackKey: adminSettings.apiKeys.aviationStackKey ? '***' + adminSettings.apiKeys.aviationStackKey.slice(-4) : '',
|
|
||||||
googleMapsKey: adminSettings.apiKeys.googleMapsKey ? '***' + adminSettings.apiKeys.googleMapsKey.slice(-4) : '',
|
|
||||||
twilioKey: adminSettings.apiKeys.twilioKey ? '***' + adminSettings.apiKeys.twilioKey.slice(-4) : '',
|
|
||||||
googleClientId: adminSettings.apiKeys.googleClientId ? '***' + adminSettings.apiKeys.googleClientId.slice(-4) : '',
|
|
||||||
googleClientSecret: adminSettings.apiKeys.googleClientSecret ? '***' + adminSettings.apiKeys.googleClientSecret.slice(-4) : ''
|
|
||||||
},
|
|
||||||
systemSettings: adminSettings.systemSettings
|
|
||||||
};
|
|
||||||
res.json(maskedSettings);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to fetch admin settings' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/api/admin/settings', async (req, res) => {
|
|
||||||
const adminAuth = req.headers['admin-auth'];
|
|
||||||
if (adminAuth !== 'true') {
|
|
||||||
return res.status(401).json({ error: 'Unauthorized' });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const { apiKeys, systemSettings } = req.body;
|
|
||||||
const currentSettings = await enhancedDataService_1.default.getAdminSettings();
|
|
||||||
// Update API keys (only if provided and not masked)
|
|
||||||
if (apiKeys) {
|
|
||||||
if (apiKeys.aviationStackKey && !apiKeys.aviationStackKey.startsWith('***')) {
|
|
||||||
currentSettings.apiKeys.aviationStackKey = apiKeys.aviationStackKey;
|
|
||||||
// Update the environment variable for the flight service
|
|
||||||
process.env.AVIATIONSTACK_API_KEY = apiKeys.aviationStackKey;
|
|
||||||
}
|
|
||||||
if (apiKeys.googleMapsKey && !apiKeys.googleMapsKey.startsWith('***')) {
|
|
||||||
currentSettings.apiKeys.googleMapsKey = apiKeys.googleMapsKey;
|
|
||||||
}
|
|
||||||
if (apiKeys.twilioKey && !apiKeys.twilioKey.startsWith('***')) {
|
|
||||||
currentSettings.apiKeys.twilioKey = apiKeys.twilioKey;
|
|
||||||
}
|
|
||||||
if (apiKeys.googleClientId && !apiKeys.googleClientId.startsWith('***')) {
|
|
||||||
currentSettings.apiKeys.googleClientId = apiKeys.googleClientId;
|
|
||||||
// Update the environment variable for Google OAuth
|
|
||||||
process.env.GOOGLE_CLIENT_ID = apiKeys.googleClientId;
|
|
||||||
}
|
|
||||||
if (apiKeys.googleClientSecret && !apiKeys.googleClientSecret.startsWith('***')) {
|
|
||||||
currentSettings.apiKeys.googleClientSecret = apiKeys.googleClientSecret;
|
|
||||||
// Update the environment variable for Google OAuth
|
|
||||||
process.env.GOOGLE_CLIENT_SECRET = apiKeys.googleClientSecret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Update system settings
|
|
||||||
if (systemSettings) {
|
|
||||||
currentSettings.systemSettings = { ...currentSettings.systemSettings, ...systemSettings };
|
|
||||||
}
|
|
||||||
// Save the updated settings
|
|
||||||
await enhancedDataService_1.default.updateAdminSettings(currentSettings);
|
|
||||||
res.json({ success: true });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to update admin settings' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/api/admin/test-api/:apiType', async (req, res) => {
|
|
||||||
const adminAuth = req.headers['admin-auth'];
|
|
||||||
if (adminAuth !== 'true') {
|
|
||||||
return res.status(401).json({ error: 'Unauthorized' });
|
|
||||||
}
|
|
||||||
const { apiType } = req.params;
|
|
||||||
const { apiKey } = req.body;
|
|
||||||
try {
|
|
||||||
switch (apiType) {
|
|
||||||
case 'aviationStackKey':
|
|
||||||
// Test AviationStack API
|
|
||||||
const testUrl = `http://api.aviationstack.com/v1/flights?access_key=${apiKey}&limit=1`;
|
|
||||||
const response = await fetch(testUrl);
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.error) {
|
|
||||||
res.status(400).json({ error: data.error.message || 'Invalid API key' });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
res.json({ success: true, message: 'API key is valid!' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
res.status(400).json({ error: 'Failed to validate API key' });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'googleMapsKey':
|
|
||||||
res.json({ success: true, message: 'Google Maps API testing not yet implemented' });
|
|
||||||
break;
|
|
||||||
case 'twilioKey':
|
|
||||||
res.json({ success: true, message: 'Twilio API testing not yet implemented' });
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
res.status(400).json({ error: 'Unknown API type' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to test API connection' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// JWT Key Management endpoints (admin only)
|
|
||||||
app.get('/api/admin/jwt-status', simpleAuth_1.requireAuth, (0, simpleAuth_1.requireRole)(['administrator']), (req, res) => {
|
|
||||||
const jwtKeyManager = require('./services/jwtKeyManager').default;
|
|
||||||
const status = jwtKeyManager.getStatus();
|
|
||||||
res.json({
|
|
||||||
keyRotationEnabled: true,
|
|
||||||
rotationInterval: '24 hours',
|
|
||||||
gracePeriod: '24 hours',
|
|
||||||
...status,
|
|
||||||
message: 'JWT keys are automatically rotated every 24 hours for enhanced security'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
app.post('/api/admin/jwt-rotate', simpleAuth_1.requireAuth, (0, simpleAuth_1.requireRole)(['administrator']), (req, res) => {
|
|
||||||
const jwtKeyManager = require('./services/jwtKeyManager').default;
|
|
||||||
try {
|
|
||||||
jwtKeyManager.forceRotation();
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
message: 'JWT key rotation triggered successfully. New tokens will use the new key.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(500).json({ error: 'Failed to rotate JWT keys' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Initialize database and start server
|
|
||||||
// Add 404 handler for undefined routes
|
|
||||||
app.use(errorHandler_1.notFoundHandler);
|
|
||||||
// Add error logging middleware
|
|
||||||
app.use(logger_1.errorLogger);
|
|
||||||
// Add global error handler (must be last!)
|
|
||||||
app.use(errorHandler_1.errorHandler);
|
|
||||||
async function startServer() {
|
|
||||||
try {
|
|
||||||
// Initialize database schema and migrate data
|
|
||||||
await databaseService_1.default.initializeDatabase();
|
|
||||||
console.log('✅ Database initialization completed');
|
|
||||||
// Start the server
|
|
||||||
app.listen(port, () => {
|
|
||||||
console.log(`🚀 Server is running on port ${port}`);
|
|
||||||
console.log(`🔐 Admin password: ${ADMIN_PASSWORD}`);
|
|
||||||
console.log(`📊 Admin dashboard: http://localhost:${port === 3000 ? 5173 : port}/admin`);
|
|
||||||
console.log(`🏥 Health check: http://localhost:${port}/api/health`);
|
|
||||||
console.log(`📚 API docs: http://localhost:${port}/api-docs.html`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('❌ Failed to start server:', error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
startServer();
|
|
||||||
//# sourceMappingURL=index.original.js.map
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
export {};
|
|
||||||
//# sourceMappingURL=indexSimplified.d.ts.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"indexSimplified.d.ts","sourceRoot":"","sources":["../src/indexSimplified.ts"],"names":[],"mappings":""}
|
|
||||||
217
backend-old-20260125/dist/indexSimplified.js
vendored
217
backend-old-20260125/dist/indexSimplified.js
vendored
@@ -1,217 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
const express_1 = __importDefault(require("express"));
|
|
||||||
const cors_1 = __importDefault(require("cors"));
|
|
||||||
const dotenv_1 = __importDefault(require("dotenv"));
|
|
||||||
const authService_1 = __importDefault(require("./services/authService"));
|
|
||||||
const unifiedDataService_1 = __importDefault(require("./services/unifiedDataService"));
|
|
||||||
const simpleValidation_1 = require("./middleware/simpleValidation");
|
|
||||||
const errorHandler_1 = require("./middleware/errorHandler");
|
|
||||||
dotenv_1.default.config();
|
|
||||||
const app = (0, express_1.default)();
|
|
||||||
const port = process.env.PORT || 3000;
|
|
||||||
// Middleware
|
|
||||||
app.use((0, cors_1.default)({
|
|
||||||
origin: [
|
|
||||||
process.env.FRONTEND_URL || 'http://localhost:5173',
|
|
||||||
'https://bsa.madeamess.online'
|
|
||||||
],
|
|
||||||
credentials: true
|
|
||||||
}));
|
|
||||||
app.use(express_1.default.json());
|
|
||||||
app.use(express_1.default.static('public'));
|
|
||||||
// Health check
|
|
||||||
app.get('/api/health', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
status: 'OK',
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
version: '2.0.0' // Simplified version
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// Auth routes
|
|
||||||
app.get('/auth/google', (req, res) => {
|
|
||||||
res.redirect(authService_1.default.getGoogleAuthUrl());
|
|
||||||
});
|
|
||||||
app.post('/auth/google/callback', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { code } = req.body;
|
|
||||||
const { user, token } = await authService_1.default.handleGoogleAuth(code);
|
|
||||||
res.json({ user, token });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
res.status(400).json({ error: 'Authentication failed' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.get('/auth/me', authService_1.default.requireAuth, (req, res) => {
|
|
||||||
res.json(req.user);
|
|
||||||
});
|
|
||||||
app.post('/auth/logout', (req, res) => {
|
|
||||||
res.json({ message: 'Logged out successfully' });
|
|
||||||
});
|
|
||||||
// VIP routes
|
|
||||||
app.get('/api/vips', authService_1.default.requireAuth, async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const vips = await unifiedDataService_1.default.getVips();
|
|
||||||
res.json(vips);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.get('/api/vips/:id', authService_1.default.requireAuth, async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const vip = await unifiedDataService_1.default.getVipById(req.params.id);
|
|
||||||
if (!vip)
|
|
||||||
return res.status(404).json({ error: 'VIP not found' });
|
|
||||||
res.json(vip);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/api/vips', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), (0, simpleValidation_1.validate)(simpleValidation_1.schemas.createVip), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const vip = await unifiedDataService_1.default.createVip(req.body);
|
|
||||||
res.status(201).json(vip);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.put('/api/vips/:id', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), (0, simpleValidation_1.validate)(simpleValidation_1.schemas.updateVip), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const vip = await unifiedDataService_1.default.updateVip(req.params.id, req.body);
|
|
||||||
if (!vip)
|
|
||||||
return res.status(404).json({ error: 'VIP not found' });
|
|
||||||
res.json(vip);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.delete('/api/vips/:id', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const vip = await unifiedDataService_1.default.deleteVip(req.params.id);
|
|
||||||
if (!vip)
|
|
||||||
return res.status(404).json({ error: 'VIP not found' });
|
|
||||||
res.json({ message: 'VIP deleted successfully' });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Driver routes
|
|
||||||
app.get('/api/drivers', authService_1.default.requireAuth, async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const drivers = await unifiedDataService_1.default.getDrivers();
|
|
||||||
res.json(drivers);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/api/drivers', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), (0, simpleValidation_1.validate)(simpleValidation_1.schemas.createDriver), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const driver = await unifiedDataService_1.default.createDriver(req.body);
|
|
||||||
res.status(201).json(driver);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.put('/api/drivers/:id', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), (0, simpleValidation_1.validate)(simpleValidation_1.schemas.updateDriver), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const driver = await unifiedDataService_1.default.updateDriver(req.params.id, req.body);
|
|
||||||
if (!driver)
|
|
||||||
return res.status(404).json({ error: 'Driver not found' });
|
|
||||||
res.json(driver);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.delete('/api/drivers/:id', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const driver = await unifiedDataService_1.default.deleteDriver(req.params.id);
|
|
||||||
if (!driver)
|
|
||||||
return res.status(404).json({ error: 'Driver not found' });
|
|
||||||
res.json({ message: 'Driver deleted successfully' });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Schedule routes
|
|
||||||
app.get('/api/vips/:vipId/schedule', authService_1.default.requireAuth, async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const schedule = await unifiedDataService_1.default.getScheduleByVipId(req.params.vipId);
|
|
||||||
res.json(schedule);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/api/vips/:vipId/schedule', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), (0, simpleValidation_1.validate)(simpleValidation_1.schemas.createScheduleEvent), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const event = await unifiedDataService_1.default.createScheduleEvent(req.params.vipId, req.body);
|
|
||||||
res.status(201).json(event);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.put('/api/vips/:vipId/schedule/:eventId', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), (0, simpleValidation_1.validate)(simpleValidation_1.schemas.updateScheduleEvent), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const event = await unifiedDataService_1.default.updateScheduleEvent(req.params.eventId, req.body);
|
|
||||||
if (!event)
|
|
||||||
return res.status(404).json({ error: 'Event not found' });
|
|
||||||
res.json(event);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.delete('/api/vips/:vipId/schedule/:eventId', authService_1.default.requireAuth, authService_1.default.requireRole(['coordinator', 'administrator']), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const event = await unifiedDataService_1.default.deleteScheduleEvent(req.params.eventId);
|
|
||||||
if (!event)
|
|
||||||
return res.status(404).json({ error: 'Event not found' });
|
|
||||||
res.json({ message: 'Event deleted successfully' });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Admin routes (simplified)
|
|
||||||
app.get('/api/admin/settings', authService_1.default.requireAuth, authService_1.default.requireRole(['administrator']), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const settings = await unifiedDataService_1.default.getAdminSettings();
|
|
||||||
res.json(settings);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app.post('/api/admin/settings', authService_1.default.requireAuth, authService_1.default.requireRole(['administrator']), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const { key, value } = req.body;
|
|
||||||
await unifiedDataService_1.default.updateAdminSetting(key, value);
|
|
||||||
res.json({ message: 'Setting updated successfully' });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Error handling
|
|
||||||
app.use(errorHandler_1.notFoundHandler);
|
|
||||||
app.use(errorHandler_1.errorHandler);
|
|
||||||
// Start server
|
|
||||||
app.listen(port, () => {
|
|
||||||
console.log(`🚀 Server running on port ${port}`);
|
|
||||||
console.log(`🏥 Health check: http://localhost:${port}/api/health`);
|
|
||||||
console.log(`📚 API docs: http://localhost:${port}/api-docs.html`);
|
|
||||||
});
|
|
||||||
//# sourceMappingURL=indexSimplified.js.map
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,6 +0,0 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
|
||||||
import { AppError } from '../types/errors';
|
|
||||||
export declare const errorHandler: (err: Error | AppError, req: Request, res: Response, next: NextFunction) => void;
|
|
||||||
export declare const asyncHandler: (fn: Function) => (req: Request, res: Response, next: NextFunction) => void;
|
|
||||||
export declare const notFoundHandler: (req: Request, res: Response) => void;
|
|
||||||
//# sourceMappingURL=errorHandler.d.ts.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/middleware/errorHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAiB,MAAM,iBAAiB,CAAC;AAE1D,eAAO,MAAM,YAAY,GACvB,KAAK,KAAK,GAAG,QAAQ,EACrB,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,MAAM,YAAY,KACjB,IA+CF,CAAC;AAGF,eAAO,MAAM,YAAY,GAAI,IAAI,QAAQ,MAC/B,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,SAGxD,CAAC;AAGF,eAAO,MAAM,eAAe,GAAI,KAAK,OAAO,EAAE,KAAK,QAAQ,KAAG,IAY7D,CAAC"}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.notFoundHandler = exports.asyncHandler = exports.errorHandler = void 0;
|
|
||||||
const errors_1 = require("../types/errors");
|
|
||||||
const errorHandler = (err, req, res, next) => {
|
|
||||||
// Default error values
|
|
||||||
let statusCode = 500;
|
|
||||||
let message = 'Internal server error';
|
|
||||||
let isOperational = false;
|
|
||||||
// If it's an AppError, use its properties
|
|
||||||
if (err instanceof errors_1.AppError) {
|
|
||||||
statusCode = err.statusCode;
|
|
||||||
message = err.message;
|
|
||||||
isOperational = err.isOperational;
|
|
||||||
}
|
|
||||||
else if (err.name === 'ValidationError') {
|
|
||||||
// Handle validation errors (e.g., from libraries)
|
|
||||||
statusCode = 400;
|
|
||||||
message = err.message;
|
|
||||||
isOperational = true;
|
|
||||||
}
|
|
||||||
else if (err.name === 'JsonWebTokenError') {
|
|
||||||
statusCode = 401;
|
|
||||||
message = 'Invalid token';
|
|
||||||
isOperational = true;
|
|
||||||
}
|
|
||||||
else if (err.name === 'TokenExpiredError') {
|
|
||||||
statusCode = 401;
|
|
||||||
message = 'Token expired';
|
|
||||||
isOperational = true;
|
|
||||||
}
|
|
||||||
// Log error details (in production, use proper logging service)
|
|
||||||
if (!isOperational) {
|
|
||||||
console.error('ERROR 💥:', err);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.error(`Operational error: ${message}`);
|
|
||||||
}
|
|
||||||
// Create error response
|
|
||||||
const errorResponse = {
|
|
||||||
success: false,
|
|
||||||
error: {
|
|
||||||
message,
|
|
||||||
...(process.env.NODE_ENV === 'development' && {
|
|
||||||
details: err.stack
|
|
||||||
})
|
|
||||||
},
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
path: req.path
|
|
||||||
};
|
|
||||||
res.status(statusCode).json(errorResponse);
|
|
||||||
};
|
|
||||||
exports.errorHandler = errorHandler;
|
|
||||||
// Async error wrapper to catch errors in async route handlers
|
|
||||||
const asyncHandler = (fn) => {
|
|
||||||
return (req, res, next) => {
|
|
||||||
Promise.resolve(fn(req, res, next)).catch(next);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
exports.asyncHandler = asyncHandler;
|
|
||||||
// 404 Not Found handler
|
|
||||||
const notFoundHandler = (req, res) => {
|
|
||||||
const errorResponse = {
|
|
||||||
success: false,
|
|
||||||
error: {
|
|
||||||
message: `Route ${req.originalUrl} not found`,
|
|
||||||
code: 'ROUTE_NOT_FOUND'
|
|
||||||
},
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
path: req.path
|
|
||||||
};
|
|
||||||
res.status(404).json(errorResponse);
|
|
||||||
};
|
|
||||||
exports.notFoundHandler = notFoundHandler;
|
|
||||||
//# sourceMappingURL=errorHandler.js.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"errorHandler.js","sourceRoot":"","sources":["../../src/middleware/errorHandler.ts"],"names":[],"mappings":";;;AACA,4CAA0D;AAEnD,MAAM,YAAY,GAAG,CAC1B,GAAqB,EACrB,GAAY,EACZ,GAAa,EACb,IAAkB,EACZ,EAAE;IACR,uBAAuB;IACvB,IAAI,UAAU,GAAG,GAAG,CAAC;IACrB,IAAI,OAAO,GAAG,uBAAuB,CAAC;IACtC,IAAI,aAAa,GAAG,KAAK,CAAC;IAE1B,0CAA0C;IAC1C,IAAI,GAAG,YAAY,iBAAQ,EAAE,CAAC;QAC5B,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC;QAC5B,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;QACtB,aAAa,GAAG,GAAG,CAAC,aAAa,CAAC;IACpC,CAAC;SAAM,IAAI,GAAG,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;QAC1C,kDAAkD;QAClD,UAAU,GAAG,GAAG,CAAC;QACjB,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;QACtB,aAAa,GAAG,IAAI,CAAC;IACvB,CAAC;SAAM,IAAI,GAAG,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;QAC5C,UAAU,GAAG,GAAG,CAAC;QACjB,OAAO,GAAG,eAAe,CAAC;QAC1B,aAAa,GAAG,IAAI,CAAC;IACvB,CAAC;SAAM,IAAI,GAAG,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;QAC5C,UAAU,GAAG,GAAG,CAAC;QACjB,OAAO,GAAG,eAAe,CAAC;QAC1B,aAAa,GAAG,IAAI,CAAC;IACvB,CAAC;IAED,gEAAgE;IAChE,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IAClC,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,sBAAsB,OAAO,EAAE,CAAC,CAAC;IACjD,CAAC;IAED,wBAAwB;IACxB,MAAM,aAAa,GAAkB;QACnC,OAAO,EAAE,KAAK;QACd,KAAK,EAAE;YACL,OAAO;YACP,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa,IAAI;gBAC5C,OAAO,EAAE,GAAG,CAAC,KAAK;aACnB,CAAC;SACH;QACD,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,IAAI,EAAE,GAAG,CAAC,IAAI;KACf,CAAC;IAEF,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AAC7C,CAAC,CAAC;AApDW,QAAA,YAAY,gBAoDvB;AAEF,8DAA8D;AACvD,MAAM,YAAY,GAAG,CAAC,EAAY,EAAE,EAAE;IAC3C,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACzD,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC,CAAC;AACJ,CAAC,CAAC;AAJW,QAAA,YAAY,gBAIvB;AAEF,wBAAwB;AACjB,MAAM,eAAe,GAAG,CAAC,GAAY,EAAE,GAAa,EAAQ,EAAE;IACnE,MAAM,aAAa,GAAkB;QACnC,OAAO,EAAE,KAAK;QACd,KAAK,EAAE;YACL,OAAO,EAAE,SAAS,GAAG,CAAC,WAAW,YAAY;YAC7C,IAAI,EAAE,iBAAiB;SACxB;QACD,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,IAAI,EAAE,GAAG,CAAC,IAAI;KACf,CAAC;IAEF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AACtC,CAAC,CAAC;AAZW,QAAA,eAAe,mBAY1B"}
|
|
||||||
15
backend-old-20260125/dist/middleware/logger.d.ts
vendored
15
backend-old-20260125/dist/middleware/logger.d.ts
vendored
@@ -1,15 +0,0 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
|
||||||
declare module 'express' {
|
|
||||||
interface Request {
|
|
||||||
requestId?: string;
|
|
||||||
user?: {
|
|
||||||
id: string;
|
|
||||||
email: string;
|
|
||||||
name: string;
|
|
||||||
role: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export declare const requestLogger: (req: Request, res: Response, next: NextFunction) => void;
|
|
||||||
export declare const errorLogger: (err: Error, req: Request, res: Response, next: NextFunction) => void;
|
|
||||||
//# sourceMappingURL=logger.d.ts.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/middleware/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAa1D,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,OAAO;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE;YACL,EAAE,EAAE,MAAM,CAAC;YACX,KAAK,EAAE,MAAM,CAAC;YACd,IAAI,EAAE,MAAM,CAAC;YACb,IAAI,EAAE,MAAM,CAAC;SACd,CAAC;KACH;CACF;AAQD,eAAO,MAAM,aAAa,GAAI,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAkC/E,CAAC;AAGF,eAAO,MAAM,WAAW,GAAI,KAAK,KAAK,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,KAAG,IAmBzF,CAAC"}
|
|
||||||
58
backend-old-20260125/dist/middleware/logger.js
vendored
58
backend-old-20260125/dist/middleware/logger.js
vendored
@@ -1,58 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.errorLogger = exports.requestLogger = void 0;
|
|
||||||
// Generate a simple request ID
|
|
||||||
const generateRequestId = () => {
|
|
||||||
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
||||||
};
|
|
||||||
// Request logger middleware
|
|
||||||
const requestLogger = (req, res, next) => {
|
|
||||||
const requestId = generateRequestId();
|
|
||||||
// Attach request ID to request object
|
|
||||||
req.requestId = requestId;
|
|
||||||
const startTime = Date.now();
|
|
||||||
// Log request
|
|
||||||
const logContext = {
|
|
||||||
requestId,
|
|
||||||
method: req.method,
|
|
||||||
url: req.originalUrl,
|
|
||||||
ip: req.ip || 'unknown',
|
|
||||||
userAgent: req.get('user-agent'),
|
|
||||||
userId: req.user?.id
|
|
||||||
};
|
|
||||||
console.log(`[${new Date().toISOString()}] REQUEST:`, JSON.stringify(logContext));
|
|
||||||
// Log response
|
|
||||||
const originalSend = res.send;
|
|
||||||
res.send = function (data) {
|
|
||||||
const duration = Date.now() - startTime;
|
|
||||||
console.log(`[${new Date().toISOString()}] RESPONSE:`, JSON.stringify({
|
|
||||||
requestId,
|
|
||||||
statusCode: res.statusCode,
|
|
||||||
duration: `${duration}ms`
|
|
||||||
}));
|
|
||||||
return originalSend.call(this, data);
|
|
||||||
};
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
exports.requestLogger = requestLogger;
|
|
||||||
// Error logger (to be used before error handler)
|
|
||||||
const errorLogger = (err, req, res, next) => {
|
|
||||||
const requestId = req.requestId || 'unknown';
|
|
||||||
console.error(`[${new Date().toISOString()}] ERROR:`, JSON.stringify({
|
|
||||||
requestId,
|
|
||||||
error: {
|
|
||||||
name: err.name,
|
|
||||||
message: err.message,
|
|
||||||
stack: err.stack
|
|
||||||
},
|
|
||||||
request: {
|
|
||||||
method: req.method,
|
|
||||||
url: req.originalUrl,
|
|
||||||
headers: req.headers,
|
|
||||||
body: req.body
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
next(err);
|
|
||||||
};
|
|
||||||
exports.errorLogger = errorLogger;
|
|
||||||
//# sourceMappingURL=logger.js.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/middleware/logger.ts"],"names":[],"mappings":";;;AAyBA,+BAA+B;AAC/B,MAAM,iBAAiB,GAAG,GAAW,EAAE;IACrC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AACpE,CAAC,CAAC;AAEF,4BAA4B;AACrB,MAAM,aAAa,GAAG,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;IACrF,MAAM,SAAS,GAAG,iBAAiB,EAAE,CAAC;IAEtC,sCAAsC;IACtC,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC;IAE1B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,cAAc;IACd,MAAM,UAAU,GAAe;QAC7B,SAAS;QACT,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,GAAG,EAAE,GAAG,CAAC,WAAW;QACpB,EAAE,EAAE,GAAG,CAAC,EAAE,IAAI,SAAS;QACvB,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC;QAChC,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE;KACrB,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;IAElF,eAAe;IACf,MAAM,YAAY,GAAG,GAAG,CAAC,IAAI,CAAC;IAC9B,GAAG,CAAC,IAAI,GAAG,UAAS,IAAa;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC;YACpE,SAAS;YACT,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,QAAQ,EAAE,GAAG,QAAQ,IAAI;SAC1B,CAAC,CAAC,CAAC;QAEJ,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC,CAAC;IAEF,IAAI,EAAE,CAAC;AACT,CAAC,CAAC;AAlCW,QAAA,aAAa,iBAkCxB;AAEF,iDAAiD;AAC1C,MAAM,WAAW,GAAG,CAAC,GAAU,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;IAC/F,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,IAAI,SAAS,CAAC;IAE7C,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC;QACnE,SAAS;QACT,KAAK,EAAE;YACL,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,KAAK,EAAE,GAAG,CAAC,KAAK;SACjB;QACD,OAAO,EAAE;YACP,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,GAAG,EAAE,GAAG,CAAC,WAAW;YACpB,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,IAAI,EAAE,GAAG,CAAC,IAAI;SACf;KACF,CAAC,CAAC,CAAC;IAEJ,IAAI,CAAC,GAAG,CAAC,CAAC;AACZ,CAAC,CAAC;AAnBW,QAAA,WAAW,eAmBtB"}
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
import { z } from 'zod';
|
|
||||||
import { Request, Response, NextFunction } from 'express';
|
|
||||||
export declare const schemas: {
|
|
||||||
createVip: z.ZodObject<{
|
|
||||||
name: z.ZodString;
|
|
||||||
organization: z.ZodOptional<z.ZodString>;
|
|
||||||
department: z.ZodDefault<z.ZodEnum<["Office of Development", "Admin"]>>;
|
|
||||||
transportMode: z.ZodDefault<z.ZodEnum<["flight", "self-driving"]>>;
|
|
||||||
flights: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
||||||
flightNumber: z.ZodString;
|
|
||||||
airline: z.ZodOptional<z.ZodString>;
|
|
||||||
scheduledArrival: z.ZodString;
|
|
||||||
scheduledDeparture: z.ZodOptional<z.ZodString>;
|
|
||||||
}, "strip", z.ZodTypeAny, {
|
|
||||||
flightNumber: string;
|
|
||||||
scheduledArrival: string;
|
|
||||||
airline?: string | undefined;
|
|
||||||
scheduledDeparture?: string | undefined;
|
|
||||||
}, {
|
|
||||||
flightNumber: string;
|
|
||||||
scheduledArrival: string;
|
|
||||||
airline?: string | undefined;
|
|
||||||
scheduledDeparture?: string | undefined;
|
|
||||||
}>, "many">>;
|
|
||||||
expectedArrival: z.ZodOptional<z.ZodString>;
|
|
||||||
needsAirportPickup: z.ZodDefault<z.ZodBoolean>;
|
|
||||||
needsVenueTransport: z.ZodDefault<z.ZodBoolean>;
|
|
||||||
notes: z.ZodOptional<z.ZodString>;
|
|
||||||
}, "strip", z.ZodTypeAny, {
|
|
||||||
name: string;
|
|
||||||
department: "Office of Development" | "Admin";
|
|
||||||
transportMode: "flight" | "self-driving";
|
|
||||||
needsAirportPickup: boolean;
|
|
||||||
needsVenueTransport: boolean;
|
|
||||||
organization?: string | undefined;
|
|
||||||
flights?: {
|
|
||||||
flightNumber: string;
|
|
||||||
scheduledArrival: string;
|
|
||||||
airline?: string | undefined;
|
|
||||||
scheduledDeparture?: string | undefined;
|
|
||||||
}[] | undefined;
|
|
||||||
expectedArrival?: string | undefined;
|
|
||||||
notes?: string | undefined;
|
|
||||||
}, {
|
|
||||||
name: string;
|
|
||||||
organization?: string | undefined;
|
|
||||||
department?: "Office of Development" | "Admin" | undefined;
|
|
||||||
transportMode?: "flight" | "self-driving" | undefined;
|
|
||||||
flights?: {
|
|
||||||
flightNumber: string;
|
|
||||||
scheduledArrival: string;
|
|
||||||
airline?: string | undefined;
|
|
||||||
scheduledDeparture?: string | undefined;
|
|
||||||
}[] | undefined;
|
|
||||||
expectedArrival?: string | undefined;
|
|
||||||
needsAirportPickup?: boolean | undefined;
|
|
||||||
needsVenueTransport?: boolean | undefined;
|
|
||||||
notes?: string | undefined;
|
|
||||||
}>;
|
|
||||||
updateVip: z.ZodObject<{
|
|
||||||
name: z.ZodOptional<z.ZodString>;
|
|
||||||
organization: z.ZodOptional<z.ZodString>;
|
|
||||||
department: z.ZodOptional<z.ZodEnum<["Office of Development", "Admin"]>>;
|
|
||||||
transportMode: z.ZodOptional<z.ZodEnum<["flight", "self-driving"]>>;
|
|
||||||
flights: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
||||||
flightNumber: z.ZodString;
|
|
||||||
airline: z.ZodOptional<z.ZodString>;
|
|
||||||
scheduledArrival: z.ZodString;
|
|
||||||
scheduledDeparture: z.ZodOptional<z.ZodString>;
|
|
||||||
}, "strip", z.ZodTypeAny, {
|
|
||||||
flightNumber: string;
|
|
||||||
scheduledArrival: string;
|
|
||||||
airline?: string | undefined;
|
|
||||||
scheduledDeparture?: string | undefined;
|
|
||||||
}, {
|
|
||||||
flightNumber: string;
|
|
||||||
scheduledArrival: string;
|
|
||||||
airline?: string | undefined;
|
|
||||||
scheduledDeparture?: string | undefined;
|
|
||||||
}>, "many">>;
|
|
||||||
expectedArrival: z.ZodOptional<z.ZodString>;
|
|
||||||
needsAirportPickup: z.ZodOptional<z.ZodBoolean>;
|
|
||||||
needsVenueTransport: z.ZodOptional<z.ZodBoolean>;
|
|
||||||
notes: z.ZodOptional<z.ZodString>;
|
|
||||||
}, "strip", z.ZodTypeAny, {
|
|
||||||
name?: string | undefined;
|
|
||||||
organization?: string | undefined;
|
|
||||||
department?: "Office of Development" | "Admin" | undefined;
|
|
||||||
transportMode?: "flight" | "self-driving" | undefined;
|
|
||||||
flights?: {
|
|
||||||
flightNumber: string;
|
|
||||||
scheduledArrival: string;
|
|
||||||
airline?: string | undefined;
|
|
||||||
scheduledDeparture?: string | undefined;
|
|
||||||
}[] | undefined;
|
|
||||||
expectedArrival?: string | undefined;
|
|
||||||
needsAirportPickup?: boolean | undefined;
|
|
||||||
needsVenueTransport?: boolean | undefined;
|
|
||||||
notes?: string | undefined;
|
|
||||||
}, {
|
|
||||||
name?: string | undefined;
|
|
||||||
organization?: string | undefined;
|
|
||||||
department?: "Office of Development" | "Admin" | undefined;
|
|
||||||
transportMode?: "flight" | "self-driving" | undefined;
|
|
||||||
flights?: {
|
|
||||||
flightNumber: string;
|
|
||||||
scheduledArrival: string;
|
|
||||||
airline?: string | undefined;
|
|
||||||
scheduledDeparture?: string | undefined;
|
|
||||||
}[] | undefined;
|
|
||||||
expectedArrival?: string | undefined;
|
|
||||||
needsAirportPickup?: boolean | undefined;
|
|
||||||
needsVenueTransport?: boolean | undefined;
|
|
||||||
notes?: string | undefined;
|
|
||||||
}>;
|
|
||||||
createDriver: z.ZodObject<{
|
|
||||||
name: z.ZodString;
|
|
||||||
email: z.ZodOptional<z.ZodString>;
|
|
||||||
phone: z.ZodString;
|
|
||||||
vehicleInfo: z.ZodOptional<z.ZodString>;
|
|
||||||
status: z.ZodDefault<z.ZodEnum<["available", "assigned", "unavailable"]>>;
|
|
||||||
}, "strip", z.ZodTypeAny, {
|
|
||||||
name: string;
|
|
||||||
phone: string;
|
|
||||||
status: "available" | "assigned" | "unavailable";
|
|
||||||
email?: string | undefined;
|
|
||||||
vehicleInfo?: string | undefined;
|
|
||||||
}, {
|
|
||||||
name: string;
|
|
||||||
phone: string;
|
|
||||||
email?: string | undefined;
|
|
||||||
vehicleInfo?: string | undefined;
|
|
||||||
status?: "available" | "assigned" | "unavailable" | undefined;
|
|
||||||
}>;
|
|
||||||
updateDriver: z.ZodObject<{
|
|
||||||
name: z.ZodOptional<z.ZodString>;
|
|
||||||
email: z.ZodOptional<z.ZodString>;
|
|
||||||
phone: z.ZodOptional<z.ZodString>;
|
|
||||||
vehicleInfo: z.ZodOptional<z.ZodString>;
|
|
||||||
status: z.ZodOptional<z.ZodEnum<["available", "assigned", "unavailable"]>>;
|
|
||||||
}, "strip", z.ZodTypeAny, {
|
|
||||||
name?: string | undefined;
|
|
||||||
email?: string | undefined;
|
|
||||||
phone?: string | undefined;
|
|
||||||
vehicleInfo?: string | undefined;
|
|
||||||
status?: "available" | "assigned" | "unavailable" | undefined;
|
|
||||||
}, {
|
|
||||||
name?: string | undefined;
|
|
||||||
email?: string | undefined;
|
|
||||||
phone?: string | undefined;
|
|
||||||
vehicleInfo?: string | undefined;
|
|
||||||
status?: "available" | "assigned" | "unavailable" | undefined;
|
|
||||||
}>;
|
|
||||||
createScheduleEvent: z.ZodObject<{
|
|
||||||
driverId: z.ZodOptional<z.ZodString>;
|
|
||||||
eventTime: z.ZodString;
|
|
||||||
eventType: z.ZodEnum<["pickup", "dropoff", "custom"]>;
|
|
||||||
location: z.ZodString;
|
|
||||||
notes: z.ZodOptional<z.ZodString>;
|
|
||||||
}, "strip", z.ZodTypeAny, {
|
|
||||||
eventTime: string;
|
|
||||||
eventType: "custom" | "pickup" | "dropoff";
|
|
||||||
location: string;
|
|
||||||
notes?: string | undefined;
|
|
||||||
driverId?: string | undefined;
|
|
||||||
}, {
|
|
||||||
eventTime: string;
|
|
||||||
eventType: "custom" | "pickup" | "dropoff";
|
|
||||||
location: string;
|
|
||||||
notes?: string | undefined;
|
|
||||||
driverId?: string | undefined;
|
|
||||||
}>;
|
|
||||||
updateScheduleEvent: z.ZodObject<{
|
|
||||||
driverId: z.ZodOptional<z.ZodString>;
|
|
||||||
eventTime: z.ZodOptional<z.ZodString>;
|
|
||||||
eventType: z.ZodOptional<z.ZodEnum<["pickup", "dropoff", "custom"]>>;
|
|
||||||
location: z.ZodOptional<z.ZodString>;
|
|
||||||
notes: z.ZodOptional<z.ZodString>;
|
|
||||||
status: z.ZodOptional<z.ZodEnum<["scheduled", "in_progress", "completed", "cancelled"]>>;
|
|
||||||
}, "strip", z.ZodTypeAny, {
|
|
||||||
notes?: string | undefined;
|
|
||||||
status?: "scheduled" | "in_progress" | "completed" | "cancelled" | undefined;
|
|
||||||
driverId?: string | undefined;
|
|
||||||
eventTime?: string | undefined;
|
|
||||||
eventType?: "custom" | "pickup" | "dropoff" | undefined;
|
|
||||||
location?: string | undefined;
|
|
||||||
}, {
|
|
||||||
notes?: string | undefined;
|
|
||||||
status?: "scheduled" | "in_progress" | "completed" | "cancelled" | undefined;
|
|
||||||
driverId?: string | undefined;
|
|
||||||
eventTime?: string | undefined;
|
|
||||||
eventType?: "custom" | "pickup" | "dropoff" | undefined;
|
|
||||||
location?: string | undefined;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
export declare const validate: (schema: z.ZodSchema) => (req: Request, res: Response, next: NextFunction) => Promise<Response<any, Record<string, any>> | undefined>;
|
|
||||||
//# sourceMappingURL=simpleValidation.d.ts.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"simpleValidation.d.ts","sourceRoot":"","sources":["../../src/middleware/simpleValidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG1D,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsEnB,CAAC;AAGF,eAAO,MAAM,QAAQ,GAAI,QAAQ,CAAC,CAAC,SAAS,MAC5B,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,4DAc9D,CAAC"}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.validate = exports.schemas = void 0;
|
|
||||||
const zod_1 = require("zod");
|
|
||||||
// Simplified validation schemas - removed unnecessary complexity
|
|
||||||
exports.schemas = {
|
|
||||||
// VIP schemas
|
|
||||||
createVip: zod_1.z.object({
|
|
||||||
name: zod_1.z.string().min(1).max(100),
|
|
||||||
organization: zod_1.z.string().max(100).optional(),
|
|
||||||
department: zod_1.z.enum(['Office of Development', 'Admin']).default('Office of Development'),
|
|
||||||
transportMode: zod_1.z.enum(['flight', 'self-driving']).default('flight'),
|
|
||||||
flights: zod_1.z.array(zod_1.z.object({
|
|
||||||
flightNumber: zod_1.z.string(),
|
|
||||||
airline: zod_1.z.string().optional(),
|
|
||||||
scheduledArrival: zod_1.z.string(),
|
|
||||||
scheduledDeparture: zod_1.z.string().optional()
|
|
||||||
})).optional(),
|
|
||||||
expectedArrival: zod_1.z.string().optional(),
|
|
||||||
needsAirportPickup: zod_1.z.boolean().default(true),
|
|
||||||
needsVenueTransport: zod_1.z.boolean().default(true),
|
|
||||||
notes: zod_1.z.string().max(500).optional()
|
|
||||||
}),
|
|
||||||
updateVip: zod_1.z.object({
|
|
||||||
name: zod_1.z.string().min(1).max(100).optional(),
|
|
||||||
organization: zod_1.z.string().max(100).optional(),
|
|
||||||
department: zod_1.z.enum(['Office of Development', 'Admin']).optional(),
|
|
||||||
transportMode: zod_1.z.enum(['flight', 'self-driving']).optional(),
|
|
||||||
flights: zod_1.z.array(zod_1.z.object({
|
|
||||||
flightNumber: zod_1.z.string(),
|
|
||||||
airline: zod_1.z.string().optional(),
|
|
||||||
scheduledArrival: zod_1.z.string(),
|
|
||||||
scheduledDeparture: zod_1.z.string().optional()
|
|
||||||
})).optional(),
|
|
||||||
expectedArrival: zod_1.z.string().optional(),
|
|
||||||
needsAirportPickup: zod_1.z.boolean().optional(),
|
|
||||||
needsVenueTransport: zod_1.z.boolean().optional(),
|
|
||||||
notes: zod_1.z.string().max(500).optional()
|
|
||||||
}),
|
|
||||||
// Driver schemas
|
|
||||||
createDriver: zod_1.z.object({
|
|
||||||
name: zod_1.z.string().min(1).max(100),
|
|
||||||
email: zod_1.z.string().email().optional(),
|
|
||||||
phone: zod_1.z.string(),
|
|
||||||
vehicleInfo: zod_1.z.string().max(200).optional(),
|
|
||||||
status: zod_1.z.enum(['available', 'assigned', 'unavailable']).default('available')
|
|
||||||
}),
|
|
||||||
updateDriver: zod_1.z.object({
|
|
||||||
name: zod_1.z.string().min(1).max(100).optional(),
|
|
||||||
email: zod_1.z.string().email().optional(),
|
|
||||||
phone: zod_1.z.string().optional(),
|
|
||||||
vehicleInfo: zod_1.z.string().max(200).optional(),
|
|
||||||
status: zod_1.z.enum(['available', 'assigned', 'unavailable']).optional()
|
|
||||||
}),
|
|
||||||
// Schedule schemas
|
|
||||||
createScheduleEvent: zod_1.z.object({
|
|
||||||
driverId: zod_1.z.string().optional(),
|
|
||||||
eventTime: zod_1.z.string(),
|
|
||||||
eventType: zod_1.z.enum(['pickup', 'dropoff', 'custom']),
|
|
||||||
location: zod_1.z.string().min(1).max(200),
|
|
||||||
notes: zod_1.z.string().max(500).optional()
|
|
||||||
}),
|
|
||||||
updateScheduleEvent: zod_1.z.object({
|
|
||||||
driverId: zod_1.z.string().optional(),
|
|
||||||
eventTime: zod_1.z.string().optional(),
|
|
||||||
eventType: zod_1.z.enum(['pickup', 'dropoff', 'custom']).optional(),
|
|
||||||
location: zod_1.z.string().min(1).max(200).optional(),
|
|
||||||
notes: zod_1.z.string().max(500).optional(),
|
|
||||||
status: zod_1.z.enum(['scheduled', 'in_progress', 'completed', 'cancelled']).optional()
|
|
||||||
})
|
|
||||||
};
|
|
||||||
// Single validation middleware
|
|
||||||
const validate = (schema) => {
|
|
||||||
return async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
req.body = await schema.parseAsync(req.body);
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
if (error instanceof zod_1.z.ZodError) {
|
|
||||||
const message = error.errors
|
|
||||||
.map(err => `${err.path.join('.')}: ${err.message}`)
|
|
||||||
.join(', ');
|
|
||||||
return res.status(400).json({ error: message });
|
|
||||||
}
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
exports.validate = validate;
|
|
||||||
//# sourceMappingURL=simpleValidation.js.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"simpleValidation.js","sourceRoot":"","sources":["../../src/middleware/simpleValidation.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAGxB,iEAAiE;AACpD,QAAA,OAAO,GAAG;IACrB,cAAc;IACd,SAAS,EAAE,OAAC,CAAC,MAAM,CAAC;QAClB,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;QAChC,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QAC5C,UAAU,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC;QACvF,aAAa,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;QACnE,OAAO,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,CAAC;YACxB,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE;YACxB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC9B,gBAAgB,EAAE,OAAC,CAAC,MAAM,EAAE;YAC5B,kBAAkB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SAC1C,CAAC,CAAC,CAAC,QAAQ,EAAE;QACd,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QACtC,kBAAkB,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;QAC7C,mBAAmB,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;QAC9C,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;KACtC,CAAC;IAEF,SAAS,EAAE,OAAC,CAAC,MAAM,CAAC;QAClB,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QAC3C,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QAC5C,UAAU,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE;QACjE,aAAa,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,QAAQ,EAAE;QAC5D,OAAO,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,CAAC;YACxB,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE;YACxB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC9B,gBAAgB,EAAE,OAAC,CAAC,MAAM,EAAE;YAC5B,kBAAkB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SAC1C,CAAC,CAAC,CAAC,QAAQ,EAAE;QACd,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QACtC,kBAAkB,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;QAC1C,mBAAmB,EAAE,OAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;QAC3C,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;KACtC,CAAC;IAEF,iBAAiB;IACjB,YAAY,EAAE,OAAC,CAAC,MAAM,CAAC;QACrB,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;QAChC,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;QACpC,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE;QACjB,WAAW,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QAC3C,MAAM,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;KAC9E,CAAC;IAEF,YAAY,EAAE,OAAC,CAAC,MAAM,CAAC;QACrB,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QAC3C,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;QACpC,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC5B,WAAW,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QAC3C,MAAM,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,QAAQ,EAAE;KACpE,CAAC;IAEF,mBAAmB;IACnB,mBAAmB,EAAE,OAAC,CAAC,MAAM,CAAC;QAC5B,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC/B,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE;QACrB,SAAS,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;QAClD,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;QACpC,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;KACtC,CAAC;IAEF,mBAAmB,EAAE,OAAC,CAAC,MAAM,CAAC;QAC5B,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC/B,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAChC,SAAS,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE;QAC7D,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QAC/C,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QACrC,MAAM,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,QAAQ,EAAE;KAClF,CAAC;CACH,CAAC;AAEF,+BAA+B;AACxB,MAAM,QAAQ,GAAG,CAAC,MAAmB,EAAE,EAAE;IAC9C,OAAO,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC/D,IAAI,CAAC;YACH,GAAG,CAAC,IAAI,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC7C,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,OAAC,CAAC,QAAQ,EAAE,CAAC;gBAChC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM;qBACzB,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC;qBACnD,IAAI,CAAC,IAAI,CAAC,CAAC;gBACd,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;YAClD,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC,CAAC;AAfW,QAAA,QAAQ,YAenB"}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
|
||||||
import { z } from 'zod';
|
|
||||||
export declare const validate: (schema: z.ZodSchema) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
||||||
export declare const validateQuery: (schema: z.ZodSchema) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
||||||
export declare const validateParams: (schema: z.ZodSchema) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
||||||
//# sourceMappingURL=validation.d.ts.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/middleware/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,CAAC,EAAY,MAAM,KAAK,CAAC;AAGlC,eAAO,MAAM,QAAQ,GAAI,QAAQ,CAAC,CAAC,SAAS,MAC5B,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,kBAqB9D,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,QAAQ,CAAC,CAAC,SAAS,MACjC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,kBAqB9D,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,QAAQ,CAAC,CAAC,SAAS,MAClC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,kBAqB9D,CAAC"}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.validateParams = exports.validateQuery = exports.validate = void 0;
|
|
||||||
const zod_1 = require("zod");
|
|
||||||
const errors_1 = require("../types/errors");
|
|
||||||
const validate = (schema) => {
|
|
||||||
return async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
// Validate request body
|
|
||||||
req.body = await schema.parseAsync(req.body);
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
if (error instanceof zod_1.ZodError) {
|
|
||||||
// Format Zod errors into a user-friendly message
|
|
||||||
const errors = error.errors.map(err => ({
|
|
||||||
field: err.path.join('.'),
|
|
||||||
message: err.message
|
|
||||||
}));
|
|
||||||
const message = errors.map(e => `${e.field}: ${e.message}`).join(', ');
|
|
||||||
next(new errors_1.ValidationError(message));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
exports.validate = validate;
|
|
||||||
const validateQuery = (schema) => {
|
|
||||||
return async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
// Validate query parameters
|
|
||||||
req.query = await schema.parseAsync(req.query);
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
if (error instanceof zod_1.ZodError) {
|
|
||||||
// Format Zod errors into a user-friendly message
|
|
||||||
const errors = error.errors.map(err => ({
|
|
||||||
field: err.path.join('.'),
|
|
||||||
message: err.message
|
|
||||||
}));
|
|
||||||
const message = errors.map(e => `${e.field}: ${e.message}`).join(', ');
|
|
||||||
next(new errors_1.ValidationError(`Invalid query parameters: ${message}`));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
exports.validateQuery = validateQuery;
|
|
||||||
const validateParams = (schema) => {
|
|
||||||
return async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
// Validate route parameters
|
|
||||||
req.params = await schema.parseAsync(req.params);
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
if (error instanceof zod_1.ZodError) {
|
|
||||||
// Format Zod errors into a user-friendly message
|
|
||||||
const errors = error.errors.map(err => ({
|
|
||||||
field: err.path.join('.'),
|
|
||||||
message: err.message
|
|
||||||
}));
|
|
||||||
const message = errors.map(e => `${e.field}: ${e.message}`).join(', ');
|
|
||||||
next(new errors_1.ValidationError(`Invalid route parameters: ${message}`));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
exports.validateParams = validateParams;
|
|
||||||
//# sourceMappingURL=validation.js.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/middleware/validation.ts"],"names":[],"mappings":";;;AACA,6BAAkC;AAClC,4CAAkD;AAE3C,MAAM,QAAQ,GAAG,CAAC,MAAmB,EAAE,EAAE;IAC9C,OAAO,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC/D,IAAI,CAAC;YACH,wBAAwB;YACxB,GAAG,CAAC,IAAI,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC7C,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,cAAQ,EAAE,CAAC;gBAC9B,iDAAiD;gBACjD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBACtC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;oBACzB,OAAO,EAAE,GAAG,CAAC,OAAO;iBACrB,CAAC,CAAC,CAAC;gBAEJ,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAEvE,IAAI,CAAC,IAAI,wBAAe,CAAC,OAAO,CAAC,CAAC,CAAC;YACrC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,KAAK,CAAC,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC,CAAC;AAtBW,QAAA,QAAQ,YAsBnB;AAEK,MAAM,aAAa,GAAG,CAAC,MAAmB,EAAE,EAAE;IACnD,OAAO,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC/D,IAAI,CAAC;YACH,4BAA4B;YAC5B,GAAG,CAAC,KAAK,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAC/C,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,cAAQ,EAAE,CAAC;gBAC9B,iDAAiD;gBACjD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBACtC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;oBACzB,OAAO,EAAE,GAAG,CAAC,OAAO;iBACrB,CAAC,CAAC,CAAC;gBAEJ,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAEvE,IAAI,CAAC,IAAI,wBAAe,CAAC,6BAA6B,OAAO,EAAE,CAAC,CAAC,CAAC;YACpE,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,KAAK,CAAC,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC,CAAC;AAtBW,QAAA,aAAa,iBAsBxB;AAEK,MAAM,cAAc,GAAG,CAAC,MAAmB,EAAE,EAAE;IACpD,OAAO,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC/D,IAAI,CAAC;YACH,4BAA4B;YAC5B,GAAG,CAAC,MAAM,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACjD,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,cAAQ,EAAE,CAAC;gBAC9B,iDAAiD;gBACjD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBACtC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;oBACzB,OAAO,EAAE,GAAG,CAAC,OAAO;iBACrB,CAAC,CAAC,CAAC;gBAEJ,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAEvE,IAAI,CAAC,IAAI,wBAAe,CAAC,6BAA6B,OAAO,EAAE,CAAC,CAAC,CAAC;YACpE,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,KAAK,CAAC,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC,CAAC;AAtBW,QAAA,cAAc,kBAsBzB"}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import express, { Request, Response, NextFunction } from 'express';
|
|
||||||
declare const router: import("express-serve-static-core").Router;
|
|
||||||
export declare function requireAuth(req: Request, res: Response, next: NextFunction): express.Response<any, Record<string, any>> | undefined;
|
|
||||||
export declare function requireRole(roles: string[]): (req: Request, res: Response, next: NextFunction) => express.Response<any, Record<string, any>> | undefined;
|
|
||||||
export default router;
|
|
||||||
//# sourceMappingURL=simpleAuth.d.ts.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"simpleAuth.d.ts","sourceRoot":"","sources":["../../src/routes/simpleAuth.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAWnE,QAAA,MAAM,MAAM,4CAAmB,CAAC;AA8ChC,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,0DAwD1E;AAGD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IACjC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,4DASxD;AAseD,eAAe,MAAM,CAAC"}
|
|
||||||
534
backend-old-20260125/dist/routes/simpleAuth.js
vendored
534
backend-old-20260125/dist/routes/simpleAuth.js
vendored
@@ -1,534 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.requireAuth = requireAuth;
|
|
||||||
exports.requireRole = requireRole;
|
|
||||||
const express_1 = __importDefault(require("express"));
|
|
||||||
const simpleAuth_1 = require("../config/simpleAuth");
|
|
||||||
const databaseService_1 = __importDefault(require("../services/databaseService"));
|
|
||||||
const router = express_1.default.Router();
|
|
||||||
// Enhanced logging for production debugging
|
|
||||||
function logAuthEvent(event, details = {}) {
|
|
||||||
const timestamp = new Date().toISOString();
|
|
||||||
console.log(`🔐 [AUTH ${timestamp}] ${event}:`, JSON.stringify(details, null, 2));
|
|
||||||
}
|
|
||||||
// Validate environment variables on startup
|
|
||||||
function validateAuthEnvironment() {
|
|
||||||
const required = ['GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_SECRET', 'GOOGLE_REDIRECT_URI', 'FRONTEND_URL'];
|
|
||||||
const missing = required.filter(key => !process.env[key]);
|
|
||||||
if (missing.length > 0) {
|
|
||||||
logAuthEvent('ENVIRONMENT_ERROR', { missing_variables: missing });
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Validate URLs
|
|
||||||
const frontendUrl = process.env.FRONTEND_URL;
|
|
||||||
const redirectUri = process.env.GOOGLE_REDIRECT_URI;
|
|
||||||
if (!frontendUrl?.startsWith('http')) {
|
|
||||||
logAuthEvent('ENVIRONMENT_ERROR', { error: 'FRONTEND_URL must start with http/https' });
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!redirectUri?.startsWith('http')) {
|
|
||||||
logAuthEvent('ENVIRONMENT_ERROR', { error: 'GOOGLE_REDIRECT_URI must start with http/https' });
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
logAuthEvent('ENVIRONMENT_VALIDATED', {
|
|
||||||
frontend_url: frontendUrl,
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
client_id_configured: !!process.env.GOOGLE_CLIENT_ID,
|
|
||||||
client_secret_configured: !!process.env.GOOGLE_CLIENT_SECRET
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Validate environment on module load
|
|
||||||
const isEnvironmentValid = validateAuthEnvironment();
|
|
||||||
// Middleware to check authentication
|
|
||||||
function requireAuth(req, res, next) {
|
|
||||||
try {
|
|
||||||
const authHeader = req.headers.authorization;
|
|
||||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
||||||
logAuthEvent('AUTH_FAILED', {
|
|
||||||
reason: 'no_token',
|
|
||||||
ip: req.ip,
|
|
||||||
path: req.path,
|
|
||||||
headers_present: !!req.headers.authorization
|
|
||||||
});
|
|
||||||
return res.status(401).json({ error: 'No token provided' });
|
|
||||||
}
|
|
||||||
const token = authHeader.substring(7);
|
|
||||||
if (!token || token.length < 10) {
|
|
||||||
logAuthEvent('AUTH_FAILED', {
|
|
||||||
reason: 'invalid_token_format',
|
|
||||||
ip: req.ip,
|
|
||||||
path: req.path,
|
|
||||||
token_length: token?.length || 0
|
|
||||||
});
|
|
||||||
return res.status(401).json({ error: 'Invalid token format' });
|
|
||||||
}
|
|
||||||
const user = (0, simpleAuth_1.verifyToken)(token);
|
|
||||||
if (!user) {
|
|
||||||
logAuthEvent('AUTH_FAILED', {
|
|
||||||
reason: 'token_verification_failed',
|
|
||||||
ip: req.ip,
|
|
||||||
path: req.path,
|
|
||||||
token_prefix: token.substring(0, 10) + '...'
|
|
||||||
});
|
|
||||||
return res.status(401).json({ error: 'Invalid or expired token' });
|
|
||||||
}
|
|
||||||
logAuthEvent('AUTH_SUCCESS', {
|
|
||||||
user_id: user.id,
|
|
||||||
user_email: user.email,
|
|
||||||
user_role: user.role,
|
|
||||||
ip: req.ip,
|
|
||||||
path: req.path
|
|
||||||
});
|
|
||||||
req.user = user;
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
logAuthEvent('AUTH_ERROR', {
|
|
||||||
error: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
ip: req.ip,
|
|
||||||
path: req.path
|
|
||||||
});
|
|
||||||
return res.status(500).json({ error: 'Authentication system error' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Middleware to check role
|
|
||||||
function requireRole(roles) {
|
|
||||||
return (req, res, next) => {
|
|
||||||
const user = req.user;
|
|
||||||
if (!user || !roles.includes(user.role)) {
|
|
||||||
return res.status(403).json({ error: 'Insufficient permissions' });
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Get current user
|
|
||||||
router.get('/me', requireAuth, (req, res) => {
|
|
||||||
res.json(req.user);
|
|
||||||
});
|
|
||||||
// Setup status endpoint (required by frontend)
|
|
||||||
router.get('/setup', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const clientId = process.env.GOOGLE_CLIENT_ID;
|
|
||||||
const clientSecret = process.env.GOOGLE_CLIENT_SECRET;
|
|
||||||
const redirectUri = process.env.GOOGLE_REDIRECT_URI;
|
|
||||||
const frontendUrl = process.env.FRONTEND_URL;
|
|
||||||
logAuthEvent('SETUP_CHECK', {
|
|
||||||
client_id_present: !!clientId,
|
|
||||||
client_secret_present: !!clientSecret,
|
|
||||||
redirect_uri_present: !!redirectUri,
|
|
||||||
frontend_url_present: !!frontendUrl,
|
|
||||||
environment_valid: isEnvironmentValid
|
|
||||||
});
|
|
||||||
// Check database connectivity
|
|
||||||
let userCount = 0;
|
|
||||||
let databaseConnected = false;
|
|
||||||
try {
|
|
||||||
userCount = await databaseService_1.default.getUserCount();
|
|
||||||
databaseConnected = true;
|
|
||||||
logAuthEvent('DATABASE_CHECK', { status: 'connected', user_count: userCount });
|
|
||||||
}
|
|
||||||
catch (dbError) {
|
|
||||||
logAuthEvent('DATABASE_ERROR', {
|
|
||||||
error: dbError instanceof Error ? dbError.message : 'Unknown database error'
|
|
||||||
});
|
|
||||||
return res.status(500).json({
|
|
||||||
error: 'Database connection failed',
|
|
||||||
details: 'Cannot connect to PostgreSQL database'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const setupCompleted = !!(clientId &&
|
|
||||||
clientSecret &&
|
|
||||||
redirectUri &&
|
|
||||||
frontendUrl &&
|
|
||||||
clientId !== 'your-google-client-id-from-console' &&
|
|
||||||
clientId !== 'your-google-client-id' &&
|
|
||||||
isEnvironmentValid);
|
|
||||||
const response = {
|
|
||||||
setupCompleted,
|
|
||||||
firstAdminCreated: userCount > 0,
|
|
||||||
oauthConfigured: !!(clientId && clientSecret),
|
|
||||||
databaseConnected,
|
|
||||||
environmentValid: isEnvironmentValid,
|
|
||||||
configuration: {
|
|
||||||
google_oauth: !!(clientId && clientSecret),
|
|
||||||
redirect_uri_configured: !!redirectUri,
|
|
||||||
frontend_url_configured: !!frontendUrl,
|
|
||||||
production_ready: setupCompleted && databaseConnected
|
|
||||||
}
|
|
||||||
};
|
|
||||||
logAuthEvent('SETUP_STATUS', response);
|
|
||||||
res.json(response);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
logAuthEvent('SETUP_ERROR', {
|
|
||||||
error: error instanceof Error ? error.message : 'Unknown setup error'
|
|
||||||
});
|
|
||||||
res.status(500).json({
|
|
||||||
error: 'Setup check failed',
|
|
||||||
details: error instanceof Error ? error.message : 'Unknown error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Start Google OAuth flow
|
|
||||||
router.get('/google', (req, res) => {
|
|
||||||
try {
|
|
||||||
const authUrl = (0, simpleAuth_1.getGoogleAuthUrl)();
|
|
||||||
res.redirect(authUrl);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error starting Google OAuth:', error);
|
|
||||||
const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:5173';
|
|
||||||
res.redirect(`${frontendUrl}?error=oauth_not_configured`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Handle Google OAuth callback (this is where Google redirects back to)
|
|
||||||
router.get('/google/callback', async (req, res) => {
|
|
||||||
const { code, error, state } = req.query;
|
|
||||||
const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:5173';
|
|
||||||
logAuthEvent('OAUTH_CALLBACK', {
|
|
||||||
has_code: !!code,
|
|
||||||
has_error: !!error,
|
|
||||||
error_type: error,
|
|
||||||
state,
|
|
||||||
frontend_url: frontendUrl,
|
|
||||||
ip: req.ip,
|
|
||||||
user_agent: req.get('User-Agent')
|
|
||||||
});
|
|
||||||
// Validate environment before proceeding
|
|
||||||
if (!isEnvironmentValid) {
|
|
||||||
logAuthEvent('OAUTH_CALLBACK_ERROR', { reason: 'invalid_environment' });
|
|
||||||
return res.redirect(`${frontendUrl}?error=configuration_error&message=OAuth not properly configured`);
|
|
||||||
}
|
|
||||||
if (error) {
|
|
||||||
logAuthEvent('OAUTH_ERROR', { error, ip: req.ip });
|
|
||||||
return res.redirect(`${frontendUrl}?error=${error}&message=OAuth authorization failed`);
|
|
||||||
}
|
|
||||||
if (!code) {
|
|
||||||
logAuthEvent('OAUTH_ERROR', { reason: 'no_authorization_code', ip: req.ip });
|
|
||||||
return res.redirect(`${frontendUrl}?error=no_code&message=No authorization code received`);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
logAuthEvent('OAUTH_TOKEN_EXCHANGE_START', { code_length: code.length });
|
|
||||||
// Exchange code for tokens
|
|
||||||
const tokens = await (0, simpleAuth_1.exchangeCodeForTokens)(code);
|
|
||||||
if (!tokens || !tokens.access_token) {
|
|
||||||
logAuthEvent('OAUTH_TOKEN_EXCHANGE_FAILED', { tokens_received: !!tokens });
|
|
||||||
return res.redirect(`${frontendUrl}?error=token_exchange_failed&message=Failed to exchange authorization code`);
|
|
||||||
}
|
|
||||||
logAuthEvent('OAUTH_TOKEN_EXCHANGE_SUCCESS', { has_access_token: !!tokens.access_token });
|
|
||||||
// Get user info
|
|
||||||
const googleUser = await (0, simpleAuth_1.getGoogleUserInfo)(tokens.access_token);
|
|
||||||
if (!googleUser || !googleUser.email) {
|
|
||||||
logAuthEvent('OAUTH_USER_INFO_FAILED', { user_data: !!googleUser });
|
|
||||||
return res.redirect(`${frontendUrl}?error=user_info_failed&message=Failed to get user information from Google`);
|
|
||||||
}
|
|
||||||
logAuthEvent('OAUTH_USER_INFO_SUCCESS', {
|
|
||||||
email: googleUser.email,
|
|
||||||
name: googleUser.name,
|
|
||||||
verified_email: googleUser.verified_email
|
|
||||||
});
|
|
||||||
// Check if user exists or create new user
|
|
||||||
let user = await databaseService_1.default.getUserByEmail(googleUser.email);
|
|
||||||
if (!user) {
|
|
||||||
// Determine role - first user becomes admin, others need approval
|
|
||||||
const approvedUserCount = await databaseService_1.default.getApprovedUserCount();
|
|
||||||
const role = approvedUserCount === 0 ? 'administrator' : 'coordinator';
|
|
||||||
logAuthEvent('USER_CREATION', {
|
|
||||||
email: googleUser.email,
|
|
||||||
role,
|
|
||||||
is_first_user: approvedUserCount === 0
|
|
||||||
});
|
|
||||||
user = await databaseService_1.default.createUser({
|
|
||||||
id: googleUser.id,
|
|
||||||
google_id: googleUser.id,
|
|
||||||
email: googleUser.email,
|
|
||||||
name: googleUser.name,
|
|
||||||
profile_picture_url: googleUser.picture,
|
|
||||||
role
|
|
||||||
});
|
|
||||||
// Auto-approve first admin, others need approval
|
|
||||||
if (approvedUserCount === 0) {
|
|
||||||
await databaseService_1.default.updateUserApprovalStatus(googleUser.email, 'approved');
|
|
||||||
user.approval_status = 'approved';
|
|
||||||
logAuthEvent('FIRST_ADMIN_CREATED', { email: googleUser.email });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logAuthEvent('USER_PENDING_APPROVAL', { email: googleUser.email });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Update last sign in
|
|
||||||
await databaseService_1.default.updateUserLastSignIn(googleUser.email);
|
|
||||||
logAuthEvent('USER_LOGIN', {
|
|
||||||
email: user.email,
|
|
||||||
name: user.name,
|
|
||||||
role: user.role,
|
|
||||||
approval_status: user.approval_status
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Check if user is approved
|
|
||||||
if (user.approval_status !== 'approved') {
|
|
||||||
logAuthEvent('USER_NOT_APPROVED', { email: user.email, status: user.approval_status });
|
|
||||||
return res.redirect(`${frontendUrl}?error=pending_approval&message=Your account is pending administrator approval`);
|
|
||||||
}
|
|
||||||
// Generate JWT token
|
|
||||||
const token = (0, simpleAuth_1.generateToken)(user);
|
|
||||||
logAuthEvent('JWT_TOKEN_GENERATED', {
|
|
||||||
user_id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
role: user.role,
|
|
||||||
token_length: token.length
|
|
||||||
});
|
|
||||||
// Redirect to frontend with token
|
|
||||||
const callbackUrl = `${frontendUrl}/auth/callback?token=${token}`;
|
|
||||||
logAuthEvent('OAUTH_SUCCESS_REDIRECT', { callback_url: callbackUrl });
|
|
||||||
res.redirect(callbackUrl);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
logAuthEvent('OAUTH_CALLBACK_ERROR', {
|
|
||||||
error: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
ip: req.ip
|
|
||||||
});
|
|
||||||
res.redirect(`${frontendUrl}?error=oauth_failed&message=Authentication failed due to server error`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Exchange OAuth code for JWT token (alternative endpoint for frontend)
|
|
||||||
router.post('/google/exchange', async (req, res) => {
|
|
||||||
const { code } = req.body;
|
|
||||||
if (!code) {
|
|
||||||
return res.status(400).json({ error: 'Authorization code is required' });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// Exchange code for tokens
|
|
||||||
const tokens = await (0, simpleAuth_1.exchangeCodeForTokens)(code);
|
|
||||||
// Get user info
|
|
||||||
const googleUser = await (0, simpleAuth_1.getGoogleUserInfo)(tokens.access_token);
|
|
||||||
// Check if user exists or create new user
|
|
||||||
let user = await databaseService_1.default.getUserByEmail(googleUser.email);
|
|
||||||
if (!user) {
|
|
||||||
// Determine role - first user becomes admin
|
|
||||||
const userCount = await databaseService_1.default.getUserCount();
|
|
||||||
const role = userCount === 0 ? 'administrator' : 'coordinator';
|
|
||||||
user = await databaseService_1.default.createUser({
|
|
||||||
id: googleUser.id,
|
|
||||||
google_id: googleUser.id,
|
|
||||||
email: googleUser.email,
|
|
||||||
name: googleUser.name,
|
|
||||||
profile_picture_url: googleUser.picture,
|
|
||||||
role
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Update last sign in
|
|
||||||
await databaseService_1.default.updateUserLastSignIn(googleUser.email);
|
|
||||||
console.log(`✅ User logged in: ${user.name} (${user.email})`);
|
|
||||||
}
|
|
||||||
// Generate JWT token
|
|
||||||
const token = (0, simpleAuth_1.generateToken)(user);
|
|
||||||
// Return token to frontend
|
|
||||||
res.json({
|
|
||||||
token,
|
|
||||||
user: {
|
|
||||||
id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
name: user.name,
|
|
||||||
picture: user.profile_picture_url,
|
|
||||||
role: user.role
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error in OAuth exchange:', error);
|
|
||||||
res.status(500).json({ error: 'Failed to exchange authorization code' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Get OAuth URL for frontend to redirect to
|
|
||||||
router.get('/google/url', (req, res) => {
|
|
||||||
try {
|
|
||||||
const authUrl = (0, simpleAuth_1.getGoogleAuthUrl)();
|
|
||||||
res.json({ url: authUrl });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error getting Google OAuth URL:', error);
|
|
||||||
res.status(500).json({ error: 'OAuth not configured' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Logout
|
|
||||||
router.post('/logout', (req, res) => {
|
|
||||||
// With JWT, logout is handled client-side by removing the token
|
|
||||||
res.json({ message: 'Logged out successfully' });
|
|
||||||
});
|
|
||||||
// Get auth status
|
|
||||||
router.get('/status', (req, res) => {
|
|
||||||
const authHeader = req.headers.authorization;
|
|
||||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
||||||
return res.json({ authenticated: false });
|
|
||||||
}
|
|
||||||
const token = authHeader.substring(7);
|
|
||||||
const user = (0, simpleAuth_1.verifyToken)(token);
|
|
||||||
if (!user) {
|
|
||||||
return res.json({ authenticated: false });
|
|
||||||
}
|
|
||||||
res.json({
|
|
||||||
authenticated: true,
|
|
||||||
user: {
|
|
||||||
id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
name: user.name,
|
|
||||||
picture: user.profile_picture_url,
|
|
||||||
role: user.role
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// USER MANAGEMENT ENDPOINTS
|
|
||||||
// List all users (admin only)
|
|
||||||
router.get('/users', requireAuth, requireRole(['administrator']), async (req, res) => {
|
|
||||||
try {
|
|
||||||
const users = await databaseService_1.default.getAllUsers();
|
|
||||||
const userList = users.map(user => ({
|
|
||||||
id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
name: user.name,
|
|
||||||
picture: user.profile_picture_url,
|
|
||||||
role: user.role,
|
|
||||||
created_at: user.created_at,
|
|
||||||
last_login: user.last_login,
|
|
||||||
provider: 'google'
|
|
||||||
}));
|
|
||||||
res.json(userList);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error fetching users:', error);
|
|
||||||
res.status(500).json({ error: 'Failed to fetch users' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Update user role (admin only)
|
|
||||||
router.patch('/users/:email/role', requireAuth, requireRole(['administrator']), async (req, res) => {
|
|
||||||
const { email } = req.params;
|
|
||||||
const { role } = req.body;
|
|
||||||
if (!['administrator', 'coordinator', 'driver'].includes(role)) {
|
|
||||||
return res.status(400).json({ error: 'Invalid role' });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const user = await databaseService_1.default.updateUserRole(email, role);
|
|
||||||
if (!user) {
|
|
||||||
return res.status(404).json({ error: 'User not found' });
|
|
||||||
}
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
user: {
|
|
||||||
id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
name: user.name,
|
|
||||||
role: user.role
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error updating user role:', error);
|
|
||||||
res.status(500).json({ error: 'Failed to update user role' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Delete user (admin only)
|
|
||||||
router.delete('/users/:email', requireAuth, requireRole(['administrator']), async (req, res) => {
|
|
||||||
const { email } = req.params;
|
|
||||||
const currentUser = req.user;
|
|
||||||
// Prevent admin from deleting themselves
|
|
||||||
if (email === currentUser.email) {
|
|
||||||
return res.status(400).json({ error: 'Cannot delete your own account' });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const deletedUser = await databaseService_1.default.deleteUser(email);
|
|
||||||
if (!deletedUser) {
|
|
||||||
return res.status(404).json({ error: 'User not found' });
|
|
||||||
}
|
|
||||||
res.json({ success: true, message: 'User deleted successfully' });
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error deleting user:', error);
|
|
||||||
res.status(500).json({ error: 'Failed to delete user' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Get user by email (admin only)
|
|
||||||
router.get('/users/:email', requireAuth, requireRole(['administrator']), async (req, res) => {
|
|
||||||
const { email } = req.params;
|
|
||||||
try {
|
|
||||||
const user = await databaseService_1.default.getUserByEmail(email);
|
|
||||||
if (!user) {
|
|
||||||
return res.status(404).json({ error: 'User not found' });
|
|
||||||
}
|
|
||||||
res.json({
|
|
||||||
id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
name: user.name,
|
|
||||||
picture: user.profile_picture_url,
|
|
||||||
role: user.role,
|
|
||||||
created_at: user.created_at,
|
|
||||||
last_login: user.last_login,
|
|
||||||
provider: 'google',
|
|
||||||
approval_status: user.approval_status
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error fetching user:', error);
|
|
||||||
res.status(500).json({ error: 'Failed to fetch user' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// USER APPROVAL ENDPOINTS
|
|
||||||
// Get pending users (admin only)
|
|
||||||
router.get('/users/pending/list', requireAuth, requireRole(['administrator']), async (req, res) => {
|
|
||||||
try {
|
|
||||||
const pendingUsers = await databaseService_1.default.getPendingUsers();
|
|
||||||
const userList = pendingUsers.map(user => ({
|
|
||||||
id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
name: user.name,
|
|
||||||
picture: user.profile_picture_url,
|
|
||||||
role: user.role,
|
|
||||||
created_at: user.created_at,
|
|
||||||
provider: 'google',
|
|
||||||
approval_status: user.approval_status
|
|
||||||
}));
|
|
||||||
res.json(userList);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error fetching pending users:', error);
|
|
||||||
res.status(500).json({ error: 'Failed to fetch pending users' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Approve or deny user (admin only)
|
|
||||||
router.patch('/users/:email/approval', requireAuth, requireRole(['administrator']), async (req, res) => {
|
|
||||||
const { email } = req.params;
|
|
||||||
const { status } = req.body;
|
|
||||||
if (!['approved', 'denied'].includes(status)) {
|
|
||||||
return res.status(400).json({ error: 'Invalid approval status. Must be "approved" or "denied"' });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const user = await databaseService_1.default.updateUserApprovalStatus(email, status);
|
|
||||||
if (!user) {
|
|
||||||
return res.status(404).json({ error: 'User not found' });
|
|
||||||
}
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
message: `User ${status} successfully`,
|
|
||||||
user: {
|
|
||||||
id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
name: user.name,
|
|
||||||
role: user.role,
|
|
||||||
approval_status: user.approval_status
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Error updating user approval:', error);
|
|
||||||
res.status(500).json({ error: 'Failed to update user approval' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
exports.default = router;
|
|
||||||
//# sourceMappingURL=simpleAuth.js.map
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,29 +0,0 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
|
||||||
declare class AuthService {
|
|
||||||
private jwtSecret;
|
|
||||||
private jwtExpiry;
|
|
||||||
private googleClient;
|
|
||||||
constructor();
|
|
||||||
generateToken(user: any): string;
|
|
||||||
verifyGoogleToken(credential: string): Promise<{
|
|
||||||
user: any;
|
|
||||||
token: string;
|
|
||||||
}>;
|
|
||||||
verifyToken(token: string): any;
|
|
||||||
requireAuth: (req: Request & {
|
|
||||||
user?: any;
|
|
||||||
}, res: Response, next: NextFunction) => Promise<Response<any, Record<string, any>> | undefined>;
|
|
||||||
requireRole: (roles: string[]) => (req: Request & {
|
|
||||||
user?: any;
|
|
||||||
}, res: Response, next: NextFunction) => Response<any, Record<string, any>> | undefined;
|
|
||||||
getGoogleAuthUrl(): string;
|
|
||||||
exchangeGoogleCode(code: string): Promise<any>;
|
|
||||||
getGoogleUserInfo(accessToken: string): Promise<any>;
|
|
||||||
handleGoogleAuth(code: string): Promise<{
|
|
||||||
user: any;
|
|
||||||
token: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
declare const _default: AuthService;
|
|
||||||
export default _default;
|
|
||||||
//# sourceMappingURL=authService.d.ts.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"authService.d.ts","sourceRoot":"","sources":["../../src/services/authService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAK1D,cAAM,WAAW;IACf,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,YAAY,CAAe;;IAoBnC,aAAa,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM;IAM1B,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAqClF,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG;IAS/B,WAAW,GAAU,KAAK,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAA;KAAE,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,6DAoBnF;IAGF,WAAW,GAAI,OAAO,MAAM,EAAE,MACpB,KAAK,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAA;KAAE,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,oDAWxE;IAGF,gBAAgB,IAAI,MAAM;IAiBpB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAoB9C,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAapD,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAyB5E;;AAED,wBAAiC"}
|
|
||||||
168
backend-old-20260125/dist/services/authService.js
vendored
168
backend-old-20260125/dist/services/authService.js
vendored
@@ -1,168 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
const jwt = require('jsonwebtoken');
|
|
||||||
const google_auth_library_1 = require("google-auth-library");
|
|
||||||
const unifiedDataService_1 = __importDefault(require("./unifiedDataService"));
|
|
||||||
// Simplified authentication service - removes excessive logging and complexity
|
|
||||||
class AuthService {
|
|
||||||
constructor() {
|
|
||||||
this.jwtExpiry = '24h';
|
|
||||||
// Middleware to check authentication
|
|
||||||
this.requireAuth = async (req, res, next) => {
|
|
||||||
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
||||||
if (!token) {
|
|
||||||
return res.status(401).json({ error: 'Authentication required' });
|
|
||||||
}
|
|
||||||
const decoded = this.verifyToken(token);
|
|
||||||
if (!decoded) {
|
|
||||||
return res.status(401).json({ error: 'Invalid or expired token' });
|
|
||||||
}
|
|
||||||
// Get fresh user data
|
|
||||||
const user = await unifiedDataService_1.default.getUserById(decoded.id);
|
|
||||||
if (!user) {
|
|
||||||
return res.status(401).json({ error: 'User not found' });
|
|
||||||
}
|
|
||||||
req.user = user;
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
// Middleware to check role
|
|
||||||
this.requireRole = (roles) => {
|
|
||||||
return (req, res, next) => {
|
|
||||||
if (!req.user) {
|
|
||||||
return res.status(401).json({ error: 'Authentication required' });
|
|
||||||
}
|
|
||||||
if (!roles.includes(req.user.role)) {
|
|
||||||
return res.status(403).json({ error: 'Insufficient permissions' });
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
// Auto-generate a secure JWT secret if not provided
|
|
||||||
if (process.env.JWT_SECRET) {
|
|
||||||
this.jwtSecret = process.env.JWT_SECRET;
|
|
||||||
console.log('Using JWT_SECRET from environment');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Generate a cryptographically secure random secret
|
|
||||||
const crypto = require('crypto');
|
|
||||||
this.jwtSecret = crypto.randomBytes(64).toString('hex');
|
|
||||||
console.log('Generated new JWT_SECRET (this will change on restart)');
|
|
||||||
console.log('To persist sessions across restarts, set JWT_SECRET in .env');
|
|
||||||
}
|
|
||||||
// Initialize Google OAuth client
|
|
||||||
this.googleClient = new google_auth_library_1.OAuth2Client(process.env.GOOGLE_CLIENT_ID);
|
|
||||||
}
|
|
||||||
// Generate JWT token
|
|
||||||
generateToken(user) {
|
|
||||||
const payload = { id: user.id, email: user.email, role: user.role };
|
|
||||||
return jwt.sign(payload, this.jwtSecret, { expiresIn: this.jwtExpiry });
|
|
||||||
}
|
|
||||||
// Verify Google ID token from frontend
|
|
||||||
async verifyGoogleToken(credential) {
|
|
||||||
try {
|
|
||||||
// Verify the token with Google
|
|
||||||
const ticket = await this.googleClient.verifyIdToken({
|
|
||||||
idToken: credential,
|
|
||||||
audience: process.env.GOOGLE_CLIENT_ID,
|
|
||||||
});
|
|
||||||
const payload = ticket.getPayload();
|
|
||||||
if (!payload || !payload.email) {
|
|
||||||
throw new Error('Invalid token payload');
|
|
||||||
}
|
|
||||||
// Find or create user
|
|
||||||
let user = await unifiedDataService_1.default.getUserByEmail(payload.email);
|
|
||||||
if (!user) {
|
|
||||||
// Auto-create user with coordinator role
|
|
||||||
user = await unifiedDataService_1.default.createUser({
|
|
||||||
email: payload.email,
|
|
||||||
name: payload.name || payload.email,
|
|
||||||
role: 'coordinator',
|
|
||||||
googleId: payload.sub
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Generate our JWT
|
|
||||||
const token = this.generateToken(user);
|
|
||||||
return { user, token };
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Token verification error:', error);
|
|
||||||
throw new Error('Failed to verify Google token');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Verify JWT token
|
|
||||||
verifyToken(token) {
|
|
||||||
try {
|
|
||||||
return jwt.verify(token, this.jwtSecret);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Google OAuth helpers
|
|
||||||
getGoogleAuthUrl() {
|
|
||||||
if (!process.env.GOOGLE_CLIENT_ID || !process.env.GOOGLE_REDIRECT_URI) {
|
|
||||||
throw new Error('Google OAuth not configured. Please set GOOGLE_CLIENT_ID and GOOGLE_REDIRECT_URI in .env file');
|
|
||||||
}
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
client_id: process.env.GOOGLE_CLIENT_ID,
|
|
||||||
redirect_uri: process.env.GOOGLE_REDIRECT_URI,
|
|
||||||
response_type: 'code',
|
|
||||||
scope: 'email profile',
|
|
||||||
access_type: 'offline',
|
|
||||||
prompt: 'consent'
|
|
||||||
});
|
|
||||||
return `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
|
|
||||||
}
|
|
||||||
async exchangeGoogleCode(code) {
|
|
||||||
const response = await fetch('https://oauth2.googleapis.com/token', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
code,
|
|
||||||
client_id: process.env.GOOGLE_CLIENT_ID,
|
|
||||||
client_secret: process.env.GOOGLE_CLIENT_SECRET,
|
|
||||||
redirect_uri: process.env.GOOGLE_REDIRECT_URI,
|
|
||||||
grant_type: 'authorization_code'
|
|
||||||
})
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to exchange authorization code');
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
async getGoogleUserInfo(accessToken) {
|
|
||||||
const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
|
|
||||||
headers: { Authorization: `Bearer ${accessToken}` }
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to get user info');
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
// Simplified login/signup
|
|
||||||
async handleGoogleAuth(code) {
|
|
||||||
// Exchange code for tokens
|
|
||||||
const tokens = await this.exchangeGoogleCode(code);
|
|
||||||
// Get user info
|
|
||||||
const googleUser = await this.getGoogleUserInfo(tokens.access_token);
|
|
||||||
// Find or create user
|
|
||||||
let user = await unifiedDataService_1.default.getUserByEmail(googleUser.email);
|
|
||||||
if (!user) {
|
|
||||||
// Auto-create user with coordinator role
|
|
||||||
user = await unifiedDataService_1.default.createUser({
|
|
||||||
email: googleUser.email,
|
|
||||||
name: googleUser.name,
|
|
||||||
role: 'coordinator',
|
|
||||||
googleId: googleUser.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Generate JWT
|
|
||||||
const token = this.generateToken(user);
|
|
||||||
return { user, token };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.default = new AuthService();
|
|
||||||
//# sourceMappingURL=authService.js.map
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,39 +0,0 @@
|
|||||||
declare class DataService {
|
|
||||||
private dataDir;
|
|
||||||
private dataFile;
|
|
||||||
private data;
|
|
||||||
constructor();
|
|
||||||
private loadData;
|
|
||||||
private saveData;
|
|
||||||
getVips(): any[];
|
|
||||||
addVip(vip: any): any;
|
|
||||||
updateVip(id: string, updatedVip: any): any | null;
|
|
||||||
deleteVip(id: string): any | null;
|
|
||||||
getDrivers(): any[];
|
|
||||||
addDriver(driver: any): any;
|
|
||||||
updateDriver(id: string, updatedDriver: any): any | null;
|
|
||||||
deleteDriver(id: string): any | null;
|
|
||||||
getSchedule(vipId: string): any[];
|
|
||||||
addScheduleEvent(vipId: string, event: any): any;
|
|
||||||
updateScheduleEvent(vipId: string, eventId: string, updatedEvent: any): any | null;
|
|
||||||
deleteScheduleEvent(vipId: string, eventId: string): any | null;
|
|
||||||
getAllSchedules(): {
|
|
||||||
[vipId: string]: any[];
|
|
||||||
};
|
|
||||||
getAdminSettings(): any;
|
|
||||||
updateAdminSettings(settings: any): void;
|
|
||||||
createBackup(): string;
|
|
||||||
getUsers(): any[];
|
|
||||||
getUserByEmail(email: string): any | null;
|
|
||||||
getUserById(id: string): any | null;
|
|
||||||
addUser(user: any): any;
|
|
||||||
updateUser(email: string, updatedUser: any): any | null;
|
|
||||||
updateUserRole(email: string, role: string): any | null;
|
|
||||||
updateUserLastSignIn(email: string): any | null;
|
|
||||||
deleteUser(email: string): any | null;
|
|
||||||
getUserCount(): number;
|
|
||||||
getDataStats(): any;
|
|
||||||
}
|
|
||||||
declare const _default: DataService;
|
|
||||||
export default _default;
|
|
||||||
//# sourceMappingURL=dataService.d.ts.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"dataService.d.ts","sourceRoot":"","sources":["../../src/services/dataService.ts"],"names":[],"mappings":"AAWA,cAAM,WAAW;IACf,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,IAAI,CAAY;;IAcxB,OAAO,CAAC,QAAQ;IA6ChB,OAAO,CAAC,QAAQ;IAWhB,OAAO,IAAI,GAAG,EAAE;IAIhB,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG;IAMrB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,GAAG,GAAG,GAAG,IAAI;IAUlD,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAajC,UAAU,IAAI,GAAG,EAAE;IAInB,SAAS,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG;IAM3B,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,GAAG,GAAG,GAAG,IAAI;IAUxD,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAWpC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;IAIjC,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,GAAG;IAShD,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,GAAG,GAAG,GAAG,IAAI;IAclF,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAc/D,eAAe,IAAI;QAAE,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE,CAAA;KAAE;IAK7C,gBAAgB,IAAI,GAAG;IAIvB,mBAAmB,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI;IAMxC,YAAY,IAAI,MAAM;IAetB,QAAQ,IAAI,GAAG,EAAE;IAIjB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAIzC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAInC,OAAO,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG;IAcvB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,GAAG,GAAG,GAAG,IAAI;IAWvD,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAWvD,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAU/C,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAWrC,YAAY,IAAI,MAAM;IAItB,YAAY,IAAI,GAAG;CAWpB;;AAED,wBAAiC"}
|
|
||||||
264
backend-old-20260125/dist/services/dataService.js
vendored
264
backend-old-20260125/dist/services/dataService.js
vendored
@@ -1,264 +0,0 @@
|
|||||||
"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
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,56 +0,0 @@
|
|||||||
import { PoolClient } from 'pg';
|
|
||||||
declare class EnhancedDatabaseService {
|
|
||||||
private backupService;
|
|
||||||
constructor();
|
|
||||||
query(text: string, params?: any[]): Promise<any>;
|
|
||||||
getClient(): Promise<PoolClient>;
|
|
||||||
close(): Promise<void>;
|
|
||||||
initializeTables(): Promise<void>;
|
|
||||||
createUser(user: any): Promise<any>;
|
|
||||||
getUserByEmail(email: string): Promise<any>;
|
|
||||||
getUserById(id: string): Promise<any>;
|
|
||||||
updateUserRole(email: string, role: string): Promise<any>;
|
|
||||||
updateUserLastSignIn(email: string): Promise<any>;
|
|
||||||
getUserCount(): Promise<number>;
|
|
||||||
updateUserApprovalStatus(email: string, status: 'pending' | 'approved' | 'denied'): Promise<any>;
|
|
||||||
getApprovedUserCount(): Promise<number>;
|
|
||||||
getAllUsers(): Promise<any[]>;
|
|
||||||
deleteUser(email: string): Promise<boolean>;
|
|
||||||
getPendingUsers(): Promise<any[]>;
|
|
||||||
completeUserOnboarding(email: string, onboardingData: any): Promise<any>;
|
|
||||||
approveUser(userEmail: string, approvedBy: string, newRole?: string): Promise<any>;
|
|
||||||
rejectUser(userEmail: string, rejectedBy: string, reason?: string): Promise<any>;
|
|
||||||
deactivateUser(userEmail: string, deactivatedBy: string): Promise<any>;
|
|
||||||
reactivateUser(userEmail: string, reactivatedBy: string): Promise<any>;
|
|
||||||
createAuditLog(action: string, userEmail: string, performedBy: string, details: any): Promise<void>;
|
|
||||||
getUserAuditLog(userEmail: string): Promise<any[]>;
|
|
||||||
getUsersWithFilters(filters: {
|
|
||||||
status?: string;
|
|
||||||
role?: string;
|
|
||||||
search?: string;
|
|
||||||
}): Promise<any[]>;
|
|
||||||
getActiveUserCount(): Promise<number>;
|
|
||||||
isFirstUser(): Promise<boolean>;
|
|
||||||
createVip(vip: any): Promise<any>;
|
|
||||||
getVipById(id: string): Promise<any>;
|
|
||||||
getAllVips(): Promise<any[]>;
|
|
||||||
updateVip(id: string, vip: any): Promise<any>;
|
|
||||||
deleteVip(id: string): Promise<boolean>;
|
|
||||||
getVipsByDepartment(department: string): Promise<any[]>;
|
|
||||||
createDriver(driver: any): Promise<any>;
|
|
||||||
getDriverById(id: string): Promise<any>;
|
|
||||||
getAllDrivers(): Promise<any[]>;
|
|
||||||
updateDriver(id: string, driver: any): Promise<any>;
|
|
||||||
deleteDriver(id: string): Promise<boolean>;
|
|
||||||
getDriversByDepartment(department: string): Promise<any[]>;
|
|
||||||
updateDriverLocation(id: string, location: any): Promise<any>;
|
|
||||||
createScheduleEvent(vipId: string, event: any): Promise<any>;
|
|
||||||
getScheduleByVipId(vipId: string): Promise<any[]>;
|
|
||||||
updateScheduleEvent(vipId: string, eventId: string, event: any): Promise<any>;
|
|
||||||
deleteScheduleEvent(vipId: string, eventId: string): Promise<boolean>;
|
|
||||||
getAllScheduleEvents(): Promise<any[]>;
|
|
||||||
getScheduleEventsByDateRange(startDate: Date, endDate: Date): Promise<any[]>;
|
|
||||||
}
|
|
||||||
declare const databaseService: EnhancedDatabaseService;
|
|
||||||
export default databaseService;
|
|
||||||
//# sourceMappingURL=databaseService.d.ts.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"databaseService.d.ts","sourceRoot":"","sources":["../../src/services/databaseService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAE,MAAM,IAAI,CAAC;AAOtC,cAAM,uBAAuB;IAC3B,OAAO,CAAC,aAAa,CAA+B;;IAO9C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;IAIjD,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC;IAIhC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAKjC,UAAU,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAInC,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAI3C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAIrC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAIzD,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAIjD,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAI/B,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;IAIhG,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;IAIvC,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAI7B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI3C,eAAe,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAKjC,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAqBxE,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAuBlF,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAsBhF,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAqBtE,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAqBtE,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IASnG,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAWlD,mBAAmB,CAAC,OAAO,EAAE;QACjC,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IA8BZ,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;IAMrC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAQ/B,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAIjC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAIpC,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAI5B,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAI7C,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIvC,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAKvD,YAAY,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAIvC,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAIvC,aAAa,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAI/B,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAInD,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI1C,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAI1D,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAK7D,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAI5D,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAIjD,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAI7E,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrE,oBAAoB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAItC,4BAA4B,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;CAGnF;AAGD,QAAA,MAAM,eAAe,yBAAgC,CAAC;AACtD,eAAe,eAAe,CAAC"}
|
|
||||||
@@ -1,265 +0,0 @@
|
|||||||
"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
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user