Compare commits
10 Commits
v0.2.0-doc
...
9e9d4245bb
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e9d4245bb | |||
| 147078d72f | |||
| 4d31e16381 | |||
| 440884666d | |||
| e8987d5970 | |||
| d3e08cd04c | |||
| ba5aa4731a | |||
| d2754db377 | |||
| 868f7efc23 | |||
| 8ace1ab2c1 |
27
.env.example
27
.env.example
@@ -1,27 +0,0 @@
|
||||
# Database Configuration
|
||||
POSTGRES_DB=vip_coordinator
|
||||
POSTGRES_USER=vip_user
|
||||
POSTGRES_PASSWORD=your_secure_password_here
|
||||
DATABASE_URL=postgresql://vip_user:your_secure_password_here@db:5432/vip_coordinator
|
||||
|
||||
# Redis Configuration
|
||||
REDIS_URL=redis://redis:6379
|
||||
|
||||
# Google OAuth Configuration
|
||||
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
|
||||
FRONTEND_URL=http://localhost:5173
|
||||
|
||||
# JWT Configuration
|
||||
JWT_SECRET=your_jwt_secret_here_minimum_32_characters_long
|
||||
|
||||
# Environment
|
||||
NODE_ENV=development
|
||||
|
||||
# API Configuration
|
||||
API_PORT=3000
|
||||
|
||||
# Frontend Configuration (for production)
|
||||
VITE_API_URL=http://localhost:3000/api
|
||||
VITE_GOOGLE_CLIENT_ID=your_google_client_id_here
|
||||
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
|
||||
239
.github/workflows/ci.yml
vendored
239
.github/workflows/ci.yml
vendored
@@ -1,239 +0,0 @@
|
||||
name: CI/CD Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
|
||||
env:
|
||||
REGISTRY: docker.io
|
||||
IMAGE_NAME: t72chevy/vip-coordinator
|
||||
|
||||
jobs:
|
||||
# Backend tests
|
||||
backend-tests:
|
||||
name: Backend Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
env:
|
||||
POSTGRES_USER: test_user
|
||||
POSTGRES_PASSWORD: test_password
|
||||
POSTGRES_DB: vip_coordinator_test
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
redis:
|
||||
image: redis:7
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: backend/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./backend
|
||||
run: npm ci
|
||||
|
||||
- name: Run linter
|
||||
working-directory: ./backend
|
||||
run: npm run lint || true
|
||||
|
||||
- name: Run type check
|
||||
working-directory: ./backend
|
||||
run: npx tsc --noEmit
|
||||
|
||||
- name: Run tests
|
||||
working-directory: ./backend
|
||||
env:
|
||||
DATABASE_URL: postgresql://test_user:test_password@localhost:5432/vip_coordinator_test
|
||||
REDIS_URL: redis://localhost:6379
|
||||
GOOGLE_CLIENT_ID: test_client_id
|
||||
GOOGLE_CLIENT_SECRET: test_client_secret
|
||||
GOOGLE_REDIRECT_URI: http://localhost:3000/auth/google/callback
|
||||
FRONTEND_URL: http://localhost:5173
|
||||
JWT_SECRET: test_jwt_secret_minimum_32_characters_long
|
||||
NODE_ENV: test
|
||||
run: npm test
|
||||
|
||||
- name: Upload coverage
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: backend-coverage
|
||||
path: backend/coverage/
|
||||
|
||||
# Frontend tests
|
||||
frontend-tests:
|
||||
name: Frontend Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./frontend
|
||||
run: npm ci
|
||||
|
||||
- name: Run linter
|
||||
working-directory: ./frontend
|
||||
run: npm run lint
|
||||
|
||||
- name: Run type check
|
||||
working-directory: ./frontend
|
||||
run: npx tsc --noEmit
|
||||
|
||||
- name: Run tests
|
||||
working-directory: ./frontend
|
||||
run: npm test -- --run
|
||||
|
||||
- name: Build frontend
|
||||
working-directory: ./frontend
|
||||
run: npm run build
|
||||
|
||||
- name: Upload coverage
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: frontend-coverage
|
||||
path: frontend/coverage/
|
||||
|
||||
# Build Docker images
|
||||
build-images:
|
||||
name: Build Docker Images
|
||||
runs-on: ubuntu-latest
|
||||
needs: [backend-tests, frontend-tests]
|
||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=sha,prefix={{branch}}-
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Backend
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./backend
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:backend-${{ github.sha }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:backend-latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Build and push Frontend
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./frontend
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:frontend-${{ github.sha }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:frontend-latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
# Security scan
|
||||
security-scan:
|
||||
name: Security Scan
|
||||
runs-on: ubuntu-latest
|
||||
needs: [backend-tests, frontend-tests]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
scan-type: 'fs'
|
||||
scan-ref: '.'
|
||||
format: 'sarif'
|
||||
output: 'trivy-results.sarif'
|
||||
|
||||
- name: Upload Trivy scan results
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
|
||||
# Deploy to staging (example)
|
||||
deploy-staging:
|
||||
name: Deploy to Staging
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-images]
|
||||
if: github.ref == 'refs/heads/develop'
|
||||
environment:
|
||||
name: staging
|
||||
url: https://staging.bsa.madeamess.online
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Deploy to staging
|
||||
run: |
|
||||
echo "Deploying to staging environment..."
|
||||
# Add your deployment script here
|
||||
# Example: ssh to server and docker-compose pull && up
|
||||
|
||||
# Deploy to production
|
||||
deploy-production:
|
||||
name: Deploy to Production
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-images, security-scan]
|
||||
if: github.ref == 'refs/heads/main'
|
||||
environment:
|
||||
name: production
|
||||
url: https://bsa.madeamess.online
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Deploy to production
|
||||
run: |
|
||||
echo "Deploying to production environment..."
|
||||
# Add your deployment script here
|
||||
# Example: ssh to server and docker-compose pull && up
|
||||
69
.github/workflows/dependency-update.yml
vendored
69
.github/workflows/dependency-update.yml
vendored
@@ -1,69 +0,0 @@
|
||||
name: Dependency Updates
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run weekly on Mondays at 3 AM UTC
|
||||
- cron: '0 3 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-dependencies:
|
||||
name: Update Dependencies
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Update Backend Dependencies
|
||||
working-directory: ./backend
|
||||
run: |
|
||||
npm update
|
||||
npm audit fix || true
|
||||
|
||||
- name: Update Frontend Dependencies
|
||||
working-directory: ./frontend
|
||||
run: |
|
||||
npm update
|
||||
npm audit fix || true
|
||||
|
||||
- name: Check for changes
|
||||
id: check_changes
|
||||
run: |
|
||||
if [[ -n $(git status -s) ]]; then
|
||||
echo "changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.check_changes.outputs.changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: 'chore: update dependencies'
|
||||
title: 'Automated Dependency Updates'
|
||||
body: |
|
||||
## Automated Dependency Updates
|
||||
|
||||
This PR contains automated dependency updates for both frontend and backend packages.
|
||||
|
||||
### What's included:
|
||||
- Updated npm dependencies to latest compatible versions
|
||||
- Applied security fixes from `npm audit`
|
||||
|
||||
### Checklist:
|
||||
- [ ] Review dependency changes
|
||||
- [ ] Run tests locally
|
||||
- [ ] Check for breaking changes in updated packages
|
||||
- [ ] Update any affected code if needed
|
||||
|
||||
*This PR was automatically generated by the dependency update workflow.*
|
||||
branch: deps/automated-update-${{ github.run_number }}
|
||||
delete-branch: true
|
||||
119
.github/workflows/e2e-tests.yml
vendored
119
.github/workflows/e2e-tests.yml
vendored
@@ -1,119 +0,0 @@
|
||||
name: E2E Tests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run E2E tests daily at 2 AM UTC
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: 'Environment to test'
|
||||
required: true
|
||||
default: 'staging'
|
||||
type: choice
|
||||
options:
|
||||
- staging
|
||||
- production
|
||||
|
||||
jobs:
|
||||
e2e-tests:
|
||||
name: E2E Tests - ${{ github.event.inputs.environment || 'staging' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install Playwright
|
||||
run: |
|
||||
npm init -y
|
||||
npm install -D @playwright/test
|
||||
npx playwright install --with-deps
|
||||
|
||||
- name: Create E2E test structure
|
||||
run: |
|
||||
mkdir -p e2e/tests
|
||||
cat > e2e/playwright.config.ts << 'EOF'
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './tests',
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: 'html',
|
||||
use: {
|
||||
baseURL: process.env.BASE_URL || 'https://staging.bsa.madeamess.online',
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
],
|
||||
});
|
||||
EOF
|
||||
|
||||
- name: Create sample E2E test
|
||||
run: |
|
||||
cat > e2e/tests/auth.spec.ts << 'EOF'
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Authentication Flow', () => {
|
||||
test('should display login page', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page).toHaveTitle(/VIP Coordinator/);
|
||||
await expect(page.locator('text=Sign in with Google')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should redirect to dashboard after login', async ({ page }) => {
|
||||
// This would require mocking Google OAuth or using test credentials
|
||||
// For now, just check that the login button exists
|
||||
await page.goto('/');
|
||||
const loginButton = page.locator('button:has-text("Sign in with Google")');
|
||||
await expect(loginButton).toBeVisible();
|
||||
});
|
||||
});
|
||||
EOF
|
||||
|
||||
- name: Run E2E tests
|
||||
env:
|
||||
BASE_URL: ${{ github.event.inputs.environment == 'production' && 'https://bsa.madeamess.online' || 'https://staging.bsa.madeamess.online' }}
|
||||
run: |
|
||||
cd e2e
|
||||
npx playwright test
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: e2e/playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
notify-results:
|
||||
name: Notify Results
|
||||
runs-on: ubuntu-latest
|
||||
needs: [e2e-tests]
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Send notification
|
||||
run: |
|
||||
echo "E2E tests completed with status: ${{ needs.e2e-tests.result }}"
|
||||
# Add notification logic here (Slack, email, etc.)
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -56,10 +56,22 @@ jspm_packages/
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
.claude/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# AI context files
|
||||
CLAUDE.md
|
||||
|
||||
# CI/CD (GitHub-specific, not needed for Gitea)
|
||||
.github/
|
||||
|
||||
# E2E tests (keep locally for development, don't commit)
|
||||
frontend/e2e/
|
||||
**/playwright-report/
|
||||
**/test-results/
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
@@ -76,6 +88,9 @@ Thumbs.db
|
||||
*backup*
|
||||
*.bak
|
||||
*.tmp
|
||||
*-old-*
|
||||
backend-old*
|
||||
frontend-old*
|
||||
|
||||
# Database files
|
||||
*.sqlite
|
||||
|
||||
154
CLAUDE.md
154
CLAUDE.md
@@ -1,154 +0,0 @@
|
||||
# VIP Coordinator - Technical Documentation
|
||||
|
||||
## Project Overview
|
||||
VIP Transportation Coordination System - A web application for managing VIP transportation with driver assignments, real-time tracking, and user management.
|
||||
|
||||
## Tech Stack
|
||||
- **Frontend**: React with TypeScript, Tailwind CSS
|
||||
- **Backend**: Node.js with Express, TypeScript
|
||||
- **Database**: PostgreSQL
|
||||
- **Authentication**: Google OAuth 2.0 via Google Identity Services
|
||||
- **Containerization**: Docker & Docker Compose
|
||||
- **State Management**: React Context API
|
||||
- **JWT**: Custom JWT Key Manager with automatic rotation
|
||||
|
||||
## Authentication System
|
||||
|
||||
### Current Implementation (Working)
|
||||
We use Google Identity Services (GIS) SDK on the frontend to avoid CORS issues:
|
||||
|
||||
1. **Frontend-First OAuth Flow**:
|
||||
- Frontend loads Google Identity Services SDK
|
||||
- User clicks "Sign in with Google" button
|
||||
- Google shows authentication popup
|
||||
- Google returns a credential (JWT) directly to frontend
|
||||
- Frontend sends credential to backend `/auth/google/verify`
|
||||
- Backend verifies credential, creates/updates user, returns JWT
|
||||
|
||||
2. **Key Files**:
|
||||
- `frontend/src/components/GoogleLogin.tsx` - Google Sign-In button with GIS SDK
|
||||
- `backend/src/routes/simpleAuth.ts` - Auth endpoints including `/google/verify`
|
||||
- `backend/src/services/jwtKeyManager.ts` - JWT token generation with rotation
|
||||
|
||||
3. **User Flow**:
|
||||
- First user → Administrator role with status='active'
|
||||
- Subsequent users → Coordinator role with status='pending'
|
||||
- Pending users see styled waiting page until admin approval
|
||||
|
||||
### Important Endpoints
|
||||
- `POST /auth/google/verify` - Verify Google credential and create/login user
|
||||
- `GET /auth/me` - Get current user from JWT token
|
||||
- `GET /auth/users/me` - Get detailed user info including status
|
||||
- `GET /auth/setup` - Check if system has users
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Users Table
|
||||
```sql
|
||||
users (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
google_id VARCHAR(255) UNIQUE,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(50) CHECK IN ('driver', 'coordinator', 'administrator'),
|
||||
profile_picture_url TEXT,
|
||||
status VARCHAR(20) DEFAULT 'pending' CHECK IN ('pending', 'active', 'deactivated'),
|
||||
approval_status VARCHAR(20) DEFAULT 'pending' CHECK IN ('pending', 'approved', 'denied'),
|
||||
phone VARCHAR(50),
|
||||
organization VARCHAR(255),
|
||||
onboarding_data JSONB,
|
||||
approved_by VARCHAR(255),
|
||||
approved_at TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
```
|
||||
|
||||
## Common Issues & Solutions
|
||||
|
||||
### 1. CORS/Cross-Origin Issues
|
||||
**Problem**: OAuth redirects and popups cause CORS errors
|
||||
**Solution**: Use Google Identity Services SDK directly in frontend, send credential to backend
|
||||
|
||||
### 2. Missing Database Columns
|
||||
**Problem**: Backend expects columns that don't exist
|
||||
**Solution**: Run migrations to add missing columns:
|
||||
```sql
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'pending';
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS approval_status VARCHAR(20) DEFAULT 'pending';
|
||||
```
|
||||
|
||||
### 3. JWT Token Missing Fields
|
||||
**Problem**: Frontend expects fields in JWT that aren't included
|
||||
**Solution**: Update `jwtKeyManager.ts` to include all required fields (status, approval_status, etc.)
|
||||
|
||||
### 4. First User Not Admin
|
||||
**Problem**: First user created as coordinator instead of administrator
|
||||
**Solution**: Check `isFirstUser()` method properly counts users in database
|
||||
|
||||
### 5. Auth Routes 404
|
||||
**Problem**: Frontend calling wrong API endpoints
|
||||
**Solution**: Auth routes are at `/auth/*` not `/api/auth/*`
|
||||
|
||||
## User Management
|
||||
|
||||
### User Roles
|
||||
- **Administrator**: Full access, can approve users, first user gets this role
|
||||
- **Coordinator**: Can manage VIPs and drivers, needs admin approval
|
||||
- **Driver**: Can view assigned trips, needs admin approval
|
||||
- **Viewer**: Read-only access (if implemented)
|
||||
|
||||
### User Status Flow
|
||||
1. User signs in with Google → Created with status='pending'
|
||||
2. Admin approves → Status changes to 'active'
|
||||
3. Admin can deactivate → Status changes to 'deactivated'
|
||||
|
||||
### Approval System
|
||||
- First user is auto-approved as administrator
|
||||
- All other users need admin approval
|
||||
- Pending users see a styled waiting page
|
||||
- Page auto-refreshes every 30 seconds to check approval
|
||||
|
||||
## Docker Setup
|
||||
|
||||
### Environment Variables
|
||||
Create `.env` file with:
|
||||
```
|
||||
GOOGLE_CLIENT_ID=your-client-id
|
||||
GOOGLE_CLIENT_SECRET=your-client-secret
|
||||
GOOGLE_REDIRECT_URI=https://yourdomain.com/auth/google/callback
|
||||
FRONTEND_URL=https://yourdomain.com
|
||||
DB_PASSWORD=your-secure-password
|
||||
```
|
||||
|
||||
### Running the System
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Services:
|
||||
- Frontend: http://localhost:5173
|
||||
- Backend: http://localhost:3000
|
||||
- PostgreSQL: localhost:5432
|
||||
- Redis: localhost:6379
|
||||
|
||||
## Key Learnings
|
||||
|
||||
1. **Google OAuth Strategy**: Frontend-first approach with GIS SDK avoids CORS issues entirely
|
||||
2. **JWT Management**: Custom JWT manager with key rotation provides better security
|
||||
3. **Database Migrations**: Always check table schema matches backend expectations
|
||||
4. **User Experience**: Clear, styled feedback for pending users improves perception
|
||||
5. **Error Handling**: Proper error messages and status codes help debugging
|
||||
6. **Docker Warnings**: POSTGRES_PASSWORD warnings are cosmetic and don't affect functionality
|
||||
|
||||
## Future Improvements
|
||||
|
||||
1. Email notifications when users are approved
|
||||
2. Role-based UI components (hide/show based on user role)
|
||||
3. Audit logging for all admin actions
|
||||
4. Batch user approval interface
|
||||
5. Password-based login as fallback
|
||||
6. User profile editing
|
||||
7. Organization-based access control
|
||||
@@ -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.
|
||||
232
DEPLOYMENT.md
232
DEPLOYMENT.md
@@ -1,232 +0,0 @@
|
||||
# 🚀 VIP Coordinator - Docker Hub Deployment Guide
|
||||
|
||||
## 📋 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
- Docker and Docker Compose installed
|
||||
- Google Cloud Console account (for OAuth setup)
|
||||
|
||||
### 1. Download and Configure
|
||||
|
||||
```bash
|
||||
# Pull the project
|
||||
git clone <your-dockerhub-repo-url>
|
||||
cd vip-coordinator
|
||||
|
||||
# Copy environment template
|
||||
cp .env.example .env.prod
|
||||
|
||||
# Edit with your configuration
|
||||
nano .env.prod
|
||||
```
|
||||
|
||||
### 2. Required Configuration
|
||||
|
||||
Edit `.env.prod` with your values:
|
||||
|
||||
```bash
|
||||
# Database Configuration
|
||||
DB_PASSWORD=your-secure-database-password
|
||||
|
||||
# Domain Configuration (update with your domains)
|
||||
DOMAIN=your-domain.com
|
||||
VITE_API_URL=https://api.your-domain.com/api
|
||||
|
||||
# Google OAuth Configuration (from Google Cloud Console)
|
||||
GOOGLE_CLIENT_ID=your-google-client-id
|
||||
GOOGLE_CLIENT_SECRET=your-google-client-secret
|
||||
GOOGLE_REDIRECT_URI=https://api.your-domain.com/auth/google/callback
|
||||
|
||||
# Frontend URL
|
||||
FRONTEND_URL=https://your-domain.com
|
||||
|
||||
# Admin Configuration
|
||||
ADMIN_PASSWORD=your-secure-admin-password
|
||||
```
|
||||
|
||||
### 3. Google OAuth Setup
|
||||
|
||||
1. **Create Google Cloud Project**:
|
||||
- Go to [Google Cloud Console](https://console.cloud.google.com/)
|
||||
- Create a new project
|
||||
|
||||
2. **Enable Google+ API**:
|
||||
- Navigate to "APIs & Services" > "Library"
|
||||
- Search for "Google+ API" and enable it
|
||||
|
||||
3. **Create OAuth Credentials**:
|
||||
- Go to "APIs & Services" > "Credentials"
|
||||
- Click "Create Credentials" > "OAuth 2.0 Client IDs"
|
||||
- Application type: "Web application"
|
||||
- Authorized redirect URIs: `https://api.your-domain.com/auth/google/callback`
|
||||
|
||||
### 4. Deploy
|
||||
|
||||
```bash
|
||||
# Start the application
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
|
||||
# Check status
|
||||
docker-compose -f docker-compose.prod.yml ps
|
||||
|
||||
# View logs
|
||||
docker-compose -f docker-compose.prod.yml logs -f
|
||||
```
|
||||
|
||||
### 5. Access Your Application
|
||||
|
||||
- **Frontend**: http://your-domain.com (or http://localhost if running locally)
|
||||
- **Backend API**: http://api.your-domain.com (or http://localhost:3000)
|
||||
- **API Documentation**: http://api.your-domain.com/api-docs.html
|
||||
|
||||
### 6. First Login
|
||||
|
||||
- Visit your frontend URL
|
||||
- Click "Continue with Google"
|
||||
- The first user becomes the system administrator
|
||||
- Subsequent users need admin approval
|
||||
|
||||
## 🔧 Configuration Details
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Required | Description | Example |
|
||||
|----------|----------|-------------|---------|
|
||||
| `DB_PASSWORD` | ✅ | PostgreSQL database password | `SecurePass123!` |
|
||||
| `DOMAIN` | ✅ | Your main domain | `example.com` |
|
||||
| `VITE_API_URL` | ✅ | API endpoint URL | `https://api.example.com/api` |
|
||||
| `GOOGLE_CLIENT_ID` | ✅ | Google OAuth client ID | `123456789-abc.apps.googleusercontent.com` |
|
||||
| `GOOGLE_CLIENT_SECRET` | ✅ | Google OAuth client secret | `GOCSPX-abcdef123456` |
|
||||
| `GOOGLE_REDIRECT_URI` | ✅ | OAuth redirect URI | `https://api.example.com/auth/google/callback` |
|
||||
| `FRONTEND_URL` | ✅ | Frontend URL | `https://example.com` |
|
||||
| `ADMIN_PASSWORD` | ✅ | Admin panel password | `AdminPass123!` |
|
||||
|
||||
### Optional Configuration
|
||||
|
||||
- **AviationStack API Key**: Configure via admin interface for flight tracking
|
||||
- **Custom Ports**: Modify docker-compose.prod.yml if needed
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
### Services
|
||||
- **Frontend**: React app served by Nginx (Port 80)
|
||||
- **Backend**: Node.js API server (Port 3000)
|
||||
- **Database**: PostgreSQL with automatic schema setup
|
||||
- **Redis**: Caching and real-time updates
|
||||
|
||||
### Security Features
|
||||
- JWT tokens with automatic key rotation (24-hour cycle)
|
||||
- Non-root containers for enhanced security
|
||||
- Health checks for all services
|
||||
- Secure headers and CORS configuration
|
||||
|
||||
## 🔐 Security Best Practices
|
||||
|
||||
### Required Changes
|
||||
1. **Change default passwords**: Update `DB_PASSWORD` and `ADMIN_PASSWORD`
|
||||
2. **Use HTTPS**: Configure SSL/TLS certificates for production
|
||||
3. **Secure domains**: Use your own domains, not the examples
|
||||
4. **Google OAuth**: Create your own OAuth credentials
|
||||
|
||||
### Recommended
|
||||
- Use strong, unique passwords (20+ characters)
|
||||
- Enable firewall rules for your server
|
||||
- Regular security updates for the host system
|
||||
- Monitor logs for suspicious activity
|
||||
|
||||
## 🚨 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**OAuth Not Working**:
|
||||
```bash
|
||||
# Check Google OAuth configuration
|
||||
docker-compose -f docker-compose.prod.yml logs backend | grep -i oauth
|
||||
|
||||
# Verify redirect URI matches exactly in Google Console
|
||||
```
|
||||
|
||||
**Database Connection Error**:
|
||||
```bash
|
||||
# Check database status
|
||||
docker-compose -f docker-compose.prod.yml ps db
|
||||
|
||||
# View database logs
|
||||
docker-compose -f docker-compose.prod.yml logs db
|
||||
```
|
||||
|
||||
**Frontend Can't Connect to Backend**:
|
||||
```bash
|
||||
# Verify backend is running
|
||||
curl http://localhost:3000/api/health
|
||||
|
||||
# Check CORS configuration
|
||||
docker-compose -f docker-compose.prod.yml logs backend | grep -i cors
|
||||
```
|
||||
|
||||
### Health Checks
|
||||
|
||||
```bash
|
||||
# Check all service health
|
||||
docker-compose -f docker-compose.prod.yml ps
|
||||
|
||||
# Test API health endpoint
|
||||
curl http://localhost:3000/api/health
|
||||
|
||||
# Test frontend
|
||||
curl http://localhost/
|
||||
```
|
||||
|
||||
### Logs
|
||||
|
||||
```bash
|
||||
# View all logs
|
||||
docker-compose -f docker-compose.prod.yml logs
|
||||
|
||||
# Follow specific service logs
|
||||
docker-compose -f docker-compose.prod.yml logs -f backend
|
||||
docker-compose -f docker-compose.prod.yml logs -f frontend
|
||||
docker-compose -f docker-compose.prod.yml logs -f db
|
||||
```
|
||||
|
||||
## 🔄 Updates and Maintenance
|
||||
|
||||
### Updating the Application
|
||||
|
||||
```bash
|
||||
# Pull latest changes
|
||||
git pull origin main
|
||||
|
||||
# Rebuild and restart
|
||||
docker-compose -f docker-compose.prod.yml down
|
||||
docker-compose -f docker-compose.prod.yml up -d --build
|
||||
```
|
||||
|
||||
### Backup Database
|
||||
|
||||
```bash
|
||||
# Create database backup
|
||||
docker-compose -f docker-compose.prod.yml exec db pg_dump -U postgres vip_coordinator > backup.sql
|
||||
|
||||
# Restore from backup
|
||||
docker-compose -f docker-compose.prod.yml exec -T db psql -U postgres vip_coordinator < backup.sql
|
||||
```
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
- **API Documentation**: Available at `/api-docs.html` when running
|
||||
- **User Roles**: Administrator, Coordinator, Driver
|
||||
- **Flight Tracking**: Configure AviationStack API key in admin panel
|
||||
- **Support**: Check GitHub issues for common problems
|
||||
|
||||
## 🆘 Getting Help
|
||||
|
||||
1. Check this deployment guide
|
||||
2. Review the troubleshooting section
|
||||
3. Check Docker container logs
|
||||
4. Verify environment configuration
|
||||
5. Test with health check endpoints
|
||||
|
||||
---
|
||||
|
||||
**VIP Coordinator** - Streamlined VIP logistics management with modern containerized deployment.
|
||||
@@ -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,179 +0,0 @@
|
||||
# Docker Container Stopping Issues - Troubleshooting Guide
|
||||
|
||||
## 🚨 Issue Observed
|
||||
|
||||
During development, we encountered issues where Docker containers would hang during the stopping process, requiring forceful termination. This is concerning for production stability.
|
||||
|
||||
## 🔍 Current System Status
|
||||
|
||||
**✅ All containers are currently running properly:**
|
||||
- Backend: http://localhost:3000 (responding correctly)
|
||||
- Frontend: http://localhost:5173
|
||||
- Database: PostgreSQL on port 5432
|
||||
- Redis: Running on port 6379
|
||||
|
||||
**Docker Configuration:**
|
||||
- Storage Driver: overlay2
|
||||
- Logging Driver: json-file
|
||||
- Cgroup Driver: systemd
|
||||
- Cgroup Version: 2
|
||||
|
||||
## 🛠️ Potential Causes & Solutions
|
||||
|
||||
### 1. **Graceful Shutdown Issues**
|
||||
**Problem:** Applications not handling SIGTERM signals properly
|
||||
**Solution:** Ensure applications handle shutdown gracefully
|
||||
|
||||
**For Node.js apps (backend/frontend):**
|
||||
```javascript
|
||||
// Add to your main application file
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('SIGTERM received, shutting down gracefully');
|
||||
server.close(() => {
|
||||
console.log('Process terminated');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('SIGINT received, shutting down gracefully');
|
||||
server.close(() => {
|
||||
console.log('Process terminated');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 2. **Docker Compose Configuration**
|
||||
**Current issue:** Using obsolete `version` attribute
|
||||
**Solution:** Update docker-compose.dev.yml
|
||||
|
||||
```yaml
|
||||
# Remove this line:
|
||||
# version: '3.8'
|
||||
|
||||
# And ensure proper stop configuration:
|
||||
services:
|
||||
backend:
|
||||
stop_grace_period: 30s
|
||||
stop_signal: SIGTERM
|
||||
|
||||
frontend:
|
||||
stop_grace_period: 30s
|
||||
stop_signal: SIGTERM
|
||||
```
|
||||
|
||||
### 3. **Resource Constraints**
|
||||
**Problem:** Insufficient memory/CPU causing hanging
|
||||
**Solution:** Add resource limits
|
||||
|
||||
```yaml
|
||||
services:
|
||||
backend:
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
reservations:
|
||||
memory: 256M
|
||||
```
|
||||
|
||||
### 4. **Database Connection Handling**
|
||||
**Problem:** Open database connections preventing shutdown
|
||||
**Solution:** Ensure proper connection cleanup
|
||||
|
||||
```javascript
|
||||
// In your backend application
|
||||
process.on('SIGTERM', async () => {
|
||||
console.log('Closing database connections...');
|
||||
await database.close();
|
||||
await redis.quit();
|
||||
process.exit(0);
|
||||
});
|
||||
```
|
||||
|
||||
## 🔧 Immediate Fixes to Implement
|
||||
|
||||
### 1. Update Docker Compose File
|
||||
```bash
|
||||
cd /home/kyle/Desktop/vip-coordinator
|
||||
# Remove the version line and add stop configurations
|
||||
```
|
||||
|
||||
### 2. Add Graceful Shutdown to Backend
|
||||
```bash
|
||||
# Update backend/src/index.ts with proper signal handling
|
||||
```
|
||||
|
||||
### 3. Monitor Container Behavior
|
||||
```bash
|
||||
# Use these commands to monitor:
|
||||
docker-compose -f docker-compose.dev.yml logs --follow
|
||||
docker stats
|
||||
```
|
||||
|
||||
## 🚨 Emergency Commands
|
||||
|
||||
If containers hang during stopping:
|
||||
|
||||
```bash
|
||||
# Force stop all containers
|
||||
docker-compose -f docker-compose.dev.yml kill
|
||||
|
||||
# Remove stopped containers
|
||||
docker-compose -f docker-compose.dev.yml rm -f
|
||||
|
||||
# Clean up system
|
||||
docker system prune -f
|
||||
|
||||
# Restart fresh
|
||||
docker-compose -f docker-compose.dev.yml up -d
|
||||
```
|
||||
|
||||
## 📊 Monitoring Commands
|
||||
|
||||
```bash
|
||||
# Check container status
|
||||
docker-compose -f docker-compose.dev.yml ps
|
||||
|
||||
# Monitor logs in real-time
|
||||
docker-compose -f docker-compose.dev.yml logs -f backend
|
||||
|
||||
# Check resource usage
|
||||
docker stats
|
||||
|
||||
# Check for hanging processes
|
||||
docker-compose -f docker-compose.dev.yml top
|
||||
```
|
||||
|
||||
## 🎯 Prevention Strategies
|
||||
|
||||
1. **Regular Health Checks**
|
||||
- Implement health check endpoints
|
||||
- Monitor container resource usage
|
||||
- Set up automated restarts for failed containers
|
||||
|
||||
2. **Proper Signal Handling**
|
||||
- Ensure all applications handle SIGTERM/SIGINT
|
||||
- Implement graceful shutdown procedures
|
||||
- Close database connections properly
|
||||
|
||||
3. **Resource Management**
|
||||
- Set appropriate memory/CPU limits
|
||||
- Monitor disk space usage
|
||||
- Regular cleanup of unused images/containers
|
||||
|
||||
## 🔄 Current OAuth2 Status
|
||||
|
||||
**✅ OAuth2 is now working correctly:**
|
||||
- Simplified implementation without Passport.js
|
||||
- Proper domain configuration for bsa.madeamess.online
|
||||
- Environment variables correctly set
|
||||
- Backend responding to auth endpoints
|
||||
|
||||
**Next steps for OAuth2:**
|
||||
1. Update Google Cloud Console with redirect URI: `https://bsa.madeamess.online:3000/auth/google/callback`
|
||||
2. Test the full OAuth flow
|
||||
3. Integrate with frontend
|
||||
|
||||
The container stopping issues are separate from the OAuth2 functionality and should be addressed through the solutions above.
|
||||
@@ -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!
|
||||
74
Makefile
74
Makefile
@@ -1,74 +0,0 @@
|
||||
.PHONY: dev build deploy test test-backend test-frontend test-e2e test-coverage clean help
|
||||
|
||||
# Development
|
||||
dev:
|
||||
docker-compose -f docker-compose.dev.yml up --build
|
||||
|
||||
# Production build
|
||||
build:
|
||||
docker-compose -f docker-compose.prod.yml build
|
||||
|
||||
# Deploy to production
|
||||
deploy:
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
|
||||
# Run all tests
|
||||
test:
|
||||
@bash scripts/test-runner.sh all
|
||||
|
||||
# Run backend tests only
|
||||
test-backend:
|
||||
@bash scripts/test-runner.sh backend
|
||||
|
||||
# Run frontend tests only
|
||||
test-frontend:
|
||||
@bash scripts/test-runner.sh frontend
|
||||
|
||||
# Run E2E tests only
|
||||
test-e2e:
|
||||
@bash scripts/test-runner.sh e2e
|
||||
|
||||
# Generate test coverage reports
|
||||
test-coverage:
|
||||
@bash scripts/test-runner.sh coverage
|
||||
|
||||
# Database commands
|
||||
db-setup:
|
||||
docker-compose -f docker-compose.dev.yml run --rm backend npm run db:setup
|
||||
|
||||
db-migrate:
|
||||
docker-compose -f docker-compose.dev.yml run --rm backend npm run db:migrate
|
||||
|
||||
db-seed:
|
||||
docker-compose -f docker-compose.dev.yml run --rm backend npm run db:seed
|
||||
|
||||
# Clean up Docker resources
|
||||
clean:
|
||||
docker-compose -f docker-compose.dev.yml down -v
|
||||
docker-compose -f docker-compose.test.yml down -v
|
||||
docker-compose -f docker-compose.prod.yml down -v
|
||||
|
||||
# Show available commands
|
||||
help:
|
||||
@echo "VIP Coordinator - Available Commands:"
|
||||
@echo ""
|
||||
@echo "Development:"
|
||||
@echo " make dev - Start development environment"
|
||||
@echo " make build - Build production containers"
|
||||
@echo " make deploy - Deploy to production"
|
||||
@echo ""
|
||||
@echo "Testing:"
|
||||
@echo " make test - Run all tests"
|
||||
@echo " make test-backend - Run backend tests only"
|
||||
@echo " make test-frontend - Run frontend tests only"
|
||||
@echo " make test-e2e - Run E2E tests only"
|
||||
@echo " make test-coverage - Generate test coverage reports"
|
||||
@echo ""
|
||||
@echo "Database:"
|
||||
@echo " make db-setup - Initialize database with schema and seed data"
|
||||
@echo " make db-migrate - Run database migrations"
|
||||
@echo " make db-seed - Seed database with test data"
|
||||
@echo ""
|
||||
@echo "Maintenance:"
|
||||
@echo " make clean - Clean up all Docker resources"
|
||||
@echo " make help - Show this help message"
|
||||
@@ -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!
|
||||
239
QUICKSTART.md
Normal file
239
QUICKSTART.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# VIP Coordinator - Quick Start Guide
|
||||
|
||||
## 🚀 Get Started in 5 Minutes
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 20+
|
||||
- Docker Desktop
|
||||
- Auth0 Account (free tier at https://auth0.com)
|
||||
|
||||
### Step 1: Start Database
|
||||
|
||||
```bash
|
||||
cd vip-coordinator
|
||||
docker-compose up -d postgres
|
||||
```
|
||||
|
||||
### Step 2: Configure Auth0
|
||||
|
||||
1. Go to https://auth0.com and create a free account
|
||||
2. Create a new **Application** (Single Page Application)
|
||||
3. Create a new **API**
|
||||
4. Note your credentials:
|
||||
- Domain: `your-tenant.us.auth0.com`
|
||||
- Client ID: `abc123...`
|
||||
- Audience: `https://your-api-identifier`
|
||||
|
||||
5. Configure callback URLs in Auth0 dashboard:
|
||||
- **Allowed Callback URLs:** `http://localhost:5173/callback`
|
||||
- **Allowed Logout URLs:** `http://localhost:5173`
|
||||
- **Allowed Web Origins:** `http://localhost:5173`
|
||||
|
||||
### Step 3: Configure Backend
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
|
||||
# Edit .env file
|
||||
# Replace these with your Auth0 credentials:
|
||||
AUTH0_DOMAIN="your-tenant.us.auth0.com"
|
||||
AUTH0_AUDIENCE="https://your-api-identifier"
|
||||
AUTH0_ISSUER="https://your-tenant.us.auth0.com/"
|
||||
|
||||
# Install and setup
|
||||
npm install
|
||||
npx prisma generate
|
||||
npx prisma migrate dev
|
||||
npm run prisma:seed
|
||||
```
|
||||
|
||||
### Step 4: Configure Frontend
|
||||
|
||||
```bash
|
||||
cd ../frontend
|
||||
|
||||
# Edit .env file
|
||||
# Replace these with your Auth0 credentials:
|
||||
VITE_AUTH0_DOMAIN="your-tenant.us.auth0.com"
|
||||
VITE_AUTH0_CLIENT_ID="your-client-id"
|
||||
VITE_AUTH0_AUDIENCE="https://your-api-identifier"
|
||||
|
||||
# Already installed during build
|
||||
# npm install (only if not already done)
|
||||
```
|
||||
|
||||
### Step 5: Start Everything
|
||||
|
||||
```bash
|
||||
# Terminal 1: Backend
|
||||
cd backend
|
||||
npm run start:dev
|
||||
|
||||
# Terminal 2: Frontend
|
||||
cd frontend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Step 6: Access the App
|
||||
|
||||
Open your browser to: **http://localhost:5173**
|
||||
|
||||
1. Click "Sign In with Auth0"
|
||||
2. Create an account or sign in
|
||||
3. **First user becomes Administrator automatically!**
|
||||
4. Explore the dashboard
|
||||
|
||||
---
|
||||
|
||||
## 🎯 What You Get
|
||||
|
||||
### Backend API (http://localhost:3000/api/v1)
|
||||
|
||||
- ✅ **Auth0 Authentication** - Secure JWT-based auth
|
||||
- ✅ **User Management** - Approval workflow for new users
|
||||
- ✅ **VIP Management** - Complete CRUD with relationships
|
||||
- ✅ **Driver Management** - Driver profiles and schedules
|
||||
- ✅ **Event Scheduling** - Smart conflict detection
|
||||
- ✅ **Flight Tracking** - Real-time flight status (AviationStack API)
|
||||
- ✅ **40+ API Endpoints** - Fully documented REST API
|
||||
- ✅ **Role-Based Access** - Administrator, Coordinator, Driver
|
||||
- ✅ **Sample Data** - Pre-loaded test data
|
||||
|
||||
### Frontend (http://localhost:5173)
|
||||
|
||||
- ✅ **Modern React UI** - React 18 + TypeScript
|
||||
- ✅ **Tailwind CSS** - Beautiful, responsive design
|
||||
- ✅ **Auth0 Integration** - Seamless authentication
|
||||
- ✅ **TanStack Query** - Smart data fetching and caching
|
||||
- ✅ **Dashboard** - Overview with stats and recent activity
|
||||
- ✅ **VIP Management** - List, view, create, edit VIPs
|
||||
- ✅ **Driver Management** - Manage driver profiles
|
||||
- ✅ **Schedule View** - See all events and assignments
|
||||
- ✅ **Protected Routes** - Automatic authentication checks
|
||||
|
||||
---
|
||||
|
||||
## 📊 Sample Data
|
||||
|
||||
The database is seeded with:
|
||||
|
||||
- **2 Users:** admin@example.com, coordinator@example.com
|
||||
- **2 VIPs:** Dr. Robert Johnson (flight), Ms. Sarah Williams (self-driving)
|
||||
- **2 Drivers:** John Smith, Jane Doe
|
||||
- **3 Events:** Airport pickup, welcome dinner, conference transport
|
||||
|
||||
---
|
||||
|
||||
## 🔑 User Roles
|
||||
|
||||
### Administrator
|
||||
- Full system access
|
||||
- Can approve/deny new users
|
||||
- Can manage all VIPs, drivers, events
|
||||
|
||||
### Coordinator
|
||||
- Can manage VIPs, drivers, events
|
||||
- Cannot manage users
|
||||
- Full scheduling access
|
||||
|
||||
### Driver
|
||||
- View assigned schedules
|
||||
- Update event status
|
||||
- Cannot create or delete
|
||||
|
||||
**First user to register = Administrator** (no manual setup needed!)
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing the API
|
||||
|
||||
### Health Check (Public)
|
||||
```bash
|
||||
curl http://localhost:3000/api/v1/health
|
||||
```
|
||||
|
||||
### Get Profile (Requires Auth0 Token)
|
||||
```bash
|
||||
# Get token from browser DevTools -> Application -> Local Storage -> auth0_token
|
||||
curl http://localhost:3000/api/v1/auth/profile \
|
||||
-H "Authorization: Bearer YOUR_TOKEN_HERE"
|
||||
```
|
||||
|
||||
### List VIPs
|
||||
```bash
|
||||
curl http://localhost:3000/api/v1/vips \
|
||||
-H "Authorization: Bearer YOUR_TOKEN_HERE"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### "Cannot connect to database"
|
||||
```bash
|
||||
# Check PostgreSQL is running
|
||||
docker ps | grep postgres
|
||||
|
||||
# Should see: vip-postgres running on port 5433
|
||||
```
|
||||
|
||||
### "Auth0 redirect loop"
|
||||
- Check your `.env` files have correct Auth0 credentials
|
||||
- Verify callback URLs in Auth0 dashboard match `http://localhost:5173/callback`
|
||||
- Clear browser cache and cookies
|
||||
|
||||
### "Cannot find module"
|
||||
```bash
|
||||
# Backend
|
||||
cd backend
|
||||
npx prisma generate
|
||||
npm run build
|
||||
|
||||
# Frontend
|
||||
cd frontend
|
||||
npm install
|
||||
```
|
||||
|
||||
### "Port already in use"
|
||||
- Backend uses port 3000
|
||||
- Frontend uses port 5173
|
||||
- PostgreSQL uses port 5433
|
||||
|
||||
Close any processes using these ports.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Next Steps
|
||||
|
||||
1. **Explore the Dashboard** - See stats and recent activity
|
||||
2. **Add a VIP** - Try creating a new VIP profile
|
||||
3. **Assign a Driver** - Schedule an event with driver assignment
|
||||
4. **Test Conflict Detection** - Try double-booking a driver
|
||||
5. **Approve Users** - Have someone else sign up, then approve them as admin
|
||||
6. **View API Docs** - Check [backend/README.md](backend/README.md)
|
||||
|
||||
---
|
||||
|
||||
## 🚢 Deploy to Production
|
||||
|
||||
See [CLAUDE.md](CLAUDE.md) for Digital Ocean deployment instructions.
|
||||
|
||||
Ready to deploy:
|
||||
- ✅ Docker Compose configuration
|
||||
- ✅ Production environment variables
|
||||
- ✅ Optimized builds
|
||||
- ✅ Auth0 production setup guide
|
||||
|
||||
---
|
||||
|
||||
**Need Help?**
|
||||
|
||||
- Check [CLAUDE.md](CLAUDE.md) for comprehensive documentation
|
||||
- Check [README.md](README.md) for detailed feature overview
|
||||
- Check [backend/README.md](backend/README.md) for API docs
|
||||
- Check [frontend/README.md](frontend/README.md) for frontend docs
|
||||
|
||||
**Built with:** NestJS, React, TypeScript, Prisma, PostgreSQL, Auth0, Tailwind CSS
|
||||
|
||||
**Last Updated:** January 25, 2026
|
||||
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.
|
||||
879
README.md
879
README.md
@@ -1,311 +1,640 @@
|
||||
# VIP Coordinator
|
||||
|
||||
A comprehensive VIP transportation coordination system built with React, Node.js, PostgreSQL, and Redis. This application provides real-time scheduling, driver management, and VIP coordination capabilities with enterprise-grade security and scalability.
|
||||
> **Enterprise VIP & Transportation Management System for BSA Jamborees**
|
||||
|
||||
## 🚀 Quick Start with Docker
|
||||
A comprehensive full-stack application for coordinating VIP transportation, scheduling, and logistics at large-scale scouting events. Built with NestJS, React, PostgreSQL, and designed specifically for BSA (Boy Scouts of America) Jamboree operations.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
VIP Coordinator streamlines the complex logistics of managing hundreds of VIPs during multi-day events. It handles:
|
||||
|
||||
- **Multi-VIP Activity Scheduling** - Coordinate transport and activities for multiple VIPs simultaneously
|
||||
- **Real-time Driver Assignment** - Assign and reassign drivers on-the-fly with conflict detection
|
||||
- **Intelligent Search & Filtering** - Find activities instantly by VIP name, location, or type
|
||||
- **Resource Optimization** - Track vehicle capacity utilization (e.g., "3/6 seats used")
|
||||
- **Complete Itineraries** - Generate detailed 3-day schedules per VIP with all activities
|
||||
- **Role-Based Access Control** - Administrator, Coordinator, and Driver roles with granular permissions
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker and Docker Compose installed
|
||||
- Google OAuth credentials (for authentication)
|
||||
- Domain name or localhost for development
|
||||
- **Node.js** 18+ and npm
|
||||
- **PostgreSQL** 16+
|
||||
- **Redis** 7+
|
||||
- **Auth0 Account** (for authentication)
|
||||
- **Docker** (optional, for containerized deployment)
|
||||
|
||||
### 1. Pull the Images
|
||||
### Development Setup
|
||||
|
||||
```bash
|
||||
docker pull t72chevy/vip-coordinator:backend-latest
|
||||
docker pull t72chevy/vip-coordinator:frontend-latest
|
||||
# Clone the repository
|
||||
git clone http://192.168.68.53:3000/kyle/vip-coordinator.git
|
||||
cd vip-coordinator
|
||||
|
||||
# === Backend Setup ===
|
||||
cd backend
|
||||
npm install
|
||||
|
||||
# Configure environment variables
|
||||
cp .env.example .env
|
||||
# Edit .env with your Auth0 credentials and database URL
|
||||
|
||||
# Run database migrations
|
||||
npx prisma migrate deploy
|
||||
|
||||
# Seed database with test data
|
||||
npx prisma db seed
|
||||
|
||||
# Start backend server (port 3000)
|
||||
npm run start:dev
|
||||
|
||||
# === Frontend Setup ===
|
||||
cd ../frontend
|
||||
npm install
|
||||
|
||||
# Configure environment variables
|
||||
cp .env.example .env
|
||||
# Edit .env with your backend API URL
|
||||
|
||||
# Start frontend dev server (port 5173)
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 2. Create Environment File
|
||||
### Access the Application
|
||||
|
||||
Create a `.env` file in your project directory:
|
||||
- **Frontend**: http://localhost:5173
|
||||
- **Backend API**: http://localhost:3000
|
||||
- **API Documentation**: http://localhost:3000/api (Swagger UI)
|
||||
|
||||
```env
|
||||
# Database Configuration
|
||||
POSTGRES_DB=vip_coordinator
|
||||
POSTGRES_USER=vip_user
|
||||
POSTGRES_PASSWORD=your_secure_password_here
|
||||
|
||||
# Backend Configuration
|
||||
DATABASE_URL=postgresql://vip_user:your_secure_password_here@postgres:5432/vip_coordinator
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
|
||||
# Frontend Configuration
|
||||
VITE_API_URL=http://localhost:3000
|
||||
VITE_FRONTEND_URL=http://localhost
|
||||
|
||||
# Google OAuth Configuration
|
||||
GOOGLE_CLIENT_ID=your_google_client_id_here
|
||||
GOOGLE_CLIENT_SECRET=your_google_client_secret_here
|
||||
|
||||
# Redis Configuration
|
||||
REDIS_URL=redis://redis:6379
|
||||
|
||||
# Security
|
||||
JWT_SECRET=auto-generated-on-startup
|
||||
```
|
||||
|
||||
### 3. Create Docker Compose File
|
||||
|
||||
Create a `docker-compose.yml` file:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
backend:
|
||||
image: t72chevy/vip-coordinator:backend-latest
|
||||
environment:
|
||||
- DATABASE_URL=${DATABASE_URL}
|
||||
- NODE_ENV=${NODE_ENV}
|
||||
- PORT=${PORT}
|
||||
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
|
||||
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
|
||||
- REDIS_URL=${REDIS_URL}
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
frontend:
|
||||
image: t72chevy/vip-coordinator:frontend-latest
|
||||
environment:
|
||||
- VITE_API_URL=${VITE_API_URL}
|
||||
- VITE_FRONTEND_URL=${VITE_FRONTEND_URL}
|
||||
ports:
|
||||
- "80:80"
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
### 4. Deploy the Application
|
||||
|
||||
```bash
|
||||
# Start all services
|
||||
docker-compose up -d
|
||||
|
||||
# Check service status
|
||||
docker-compose ps
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
### 5. Access the Application
|
||||
|
||||
- **Frontend**: http://localhost
|
||||
- **Backend API**: http://localhost:3001
|
||||
- **Health Check**: http://localhost:3001/health
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### Google OAuth Setup
|
||||
|
||||
1. Go to the [Google Cloud Console](https://console.cloud.google.com/)
|
||||
2. Create a new project or select an existing one
|
||||
3. Enable the Google+ API
|
||||
4. Create OAuth 2.0 credentials
|
||||
5. Add your domain to authorized origins
|
||||
6. Add your callback URL: `http://your-domain/auth/google/callback`
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description | Required | Default |
|
||||
|----------|-------------|----------|---------|
|
||||
| `POSTGRES_DB` | PostgreSQL database name | Yes | `vip_coordinator` |
|
||||
| `POSTGRES_USER` | PostgreSQL username | Yes | `vip_user` |
|
||||
| `POSTGRES_PASSWORD` | PostgreSQL password | Yes | - |
|
||||
| `DATABASE_URL` | Full database connection string | Yes | - |
|
||||
| `GOOGLE_CLIENT_ID` | Google OAuth client ID | Yes | - |
|
||||
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret | Yes | - |
|
||||
| `REDIS_URL` | Redis connection string | Yes | `redis://redis:6379` |
|
||||
| `NODE_ENV` | Node.js environment | No | `production` |
|
||||
| `PORT` | Backend server port | No | `3001` |
|
||||
| `VITE_API_URL` | Frontend API URL | Yes | - |
|
||||
| `VITE_FRONTEND_URL` | Frontend base URL | Yes | - |
|
||||
---
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
### Services
|
||||
### Technology Stack
|
||||
|
||||
- **Frontend**: React application with Vite build system, served by Nginx
|
||||
- **Backend**: Node.js/Express API server with TypeScript
|
||||
- **Database**: PostgreSQL for persistent data storage
|
||||
- **Cache**: Redis for session management and real-time features
|
||||
**Backend**
|
||||
- **Framework**: NestJS 11 (TypeScript)
|
||||
- **Database**: PostgreSQL 16 with Prisma ORM 7.3
|
||||
- **Cache**: Redis 7
|
||||
- **Authentication**: Auth0 + Passport.js (JWT strategy)
|
||||
- **API**: REST with Swagger documentation
|
||||
|
||||
### Security Features
|
||||
**Frontend**
|
||||
- **Framework**: React 19 with TypeScript
|
||||
- **Build Tool**: Vite 7.2
|
||||
- **UI Library**: Material-UI (MUI) 7.3
|
||||
- **State Management**: React Query 5.9 (server) + Zustand 5.0 (client)
|
||||
- **Routing**: React Router 7.13
|
||||
- **Forms**: React Hook Form (planned)
|
||||
|
||||
- **JWT Auto-Rotation**: Automatic JWT secret rotation for enhanced security
|
||||
- **Google OAuth**: Secure authentication via Google
|
||||
- **Non-Root Containers**: All containers run as non-root users
|
||||
- **Health Checks**: Comprehensive health monitoring
|
||||
- **Input Validation**: Robust input validation and sanitization
|
||||
**Infrastructure**
|
||||
- **Containerization**: Docker + Docker Compose
|
||||
- **Database Migrations**: Prisma Migrate
|
||||
- **Testing**: Playwright (E2E), Vitest (unit - planned)
|
||||
|
||||
### Key Features
|
||||
### Key Design Patterns
|
||||
|
||||
- **Real-time Scheduling**: Live updates for VIP schedules and assignments
|
||||
- **Driver Management**: Comprehensive driver tracking and assignment
|
||||
- **User Roles**: Admin and driver role-based access control
|
||||
- **Responsive Design**: Mobile-friendly interface
|
||||
- **Data Export**: Export capabilities for schedules and reports
|
||||
- **Audit Logging**: Comprehensive activity logging
|
||||
- **Unified Activity Model**: Single ScheduleEvent entity for all activity types (transport, meals, meetings, events)
|
||||
- **Multi-VIP Support**: Activities can have multiple VIPs (`vipIds[]`) for ridesharing and group events
|
||||
- **Soft Deletes**: All entities use `deletedAt` field for audit trail preservation
|
||||
- **RBAC with CASL**: Role-based access control with isomorphic permission checking
|
||||
- **API Prefix**: All endpoints use `/api/v1` namespace
|
||||
|
||||
## 🔍 Monitoring & Troubleshooting
|
||||
---
|
||||
|
||||
### Health Checks
|
||||
## 📋 Features
|
||||
|
||||
```bash
|
||||
# Check all services
|
||||
docker-compose ps
|
||||
### Core Functionality
|
||||
|
||||
# Backend health
|
||||
curl http://localhost:3001/health
|
||||
**VIP Management**
|
||||
- Create and manage VIP profiles with arrival details
|
||||
- Track arrival mode (FLIGHT, SELF_DRIVING, OTHER)
|
||||
- Flight information integration
|
||||
- Department assignment (OFFICE_OF_DEVELOPMENT for donors, ADMIN for BSA staff)
|
||||
- Complete activity timeline per VIP
|
||||
|
||||
# View logs
|
||||
docker-compose logs backend
|
||||
docker-compose logs frontend
|
||||
docker-compose logs postgres
|
||||
docker-compose logs redis
|
||||
**Activity Scheduling**
|
||||
- Unified model for all activity types:
|
||||
- 🚗 **TRANSPORT** - Airport pickups, venue shuttles, rideshares
|
||||
- 🍽️ **MEAL** - Breakfasts, lunches, dinners, receptions
|
||||
- 📅 **EVENT** - Ceremonies, tours, campfires, presentations
|
||||
- 🤝 **MEETING** - Donor meetings, briefings, private sessions
|
||||
- 🏨 **ACCOMMODATION** - Check-ins, check-outs, room assignments
|
||||
- Multi-VIP assignment (e.g., "3 VIPs sharing SUV to Campfire")
|
||||
- Driver and vehicle assignment with capacity tracking
|
||||
- Conflict detection and warnings
|
||||
- Status tracking (SCHEDULED, IN_PROGRESS, COMPLETED, CANCELLED)
|
||||
|
||||
**Search & Filtering**
|
||||
- **Real-time Search**: Instant filtering across title, location, description, VIP names, drivers, vehicles
|
||||
- **Type Filters**: Quick filter tabs (All, Transport, Meals, Events, Meetings, Accommodation)
|
||||
- **Sortable Columns**: Click to sort by Title, Type, VIPs, Start Time, or Status
|
||||
- **Combined Filtering**: Search + filters work together seamlessly
|
||||
|
||||
**Driver & Vehicle Management**
|
||||
- Driver profiles with contact info and department
|
||||
- Vehicle tracking with type and seat capacity
|
||||
- Real-time availability checking
|
||||
- Inline driver assignment from activity list
|
||||
|
||||
**Admin Tools**
|
||||
- One-click test data generation
|
||||
- Realistic BSA Jamboree scenarios (20 VIPs, 8 drivers, 8 vehicles, 300+ activities)
|
||||
- Balanced data: 50% OFFICE_OF_DEVELOPMENT, 50% ADMIN
|
||||
- Complete 3-day itineraries with 15 activities per VIP
|
||||
|
||||
### User Roles & Permissions
|
||||
|
||||
| Feature | Administrator | Coordinator | Driver |
|
||||
|---------|--------------|-------------|--------|
|
||||
| User Management | Full CRUD | View Only | None |
|
||||
| VIP Management | Full CRUD | Full CRUD | View Only |
|
||||
| Driver Management | Full CRUD | Full CRUD | View Only |
|
||||
| Activity Scheduling | Full CRUD | Full CRUD | View + Update Status |
|
||||
| Vehicle Management | Full CRUD | Full CRUD | View Only |
|
||||
| Flight Tracking | Full Access | Full Access | None |
|
||||
| Admin Tools | Full Access | Limited | None |
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Database Schema
|
||||
|
||||
### Core Models
|
||||
|
||||
**User**
|
||||
```typescript
|
||||
- auth0Sub: string (unique)
|
||||
- email: string
|
||||
- name: string
|
||||
- role: ADMINISTRATOR | COORDINATOR | DRIVER
|
||||
- isApproved: boolean (manual approval required)
|
||||
- deletedAt: DateTime? (soft delete)
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Database Connection Issues**
|
||||
- Ensure PostgreSQL is healthy: `docker-compose logs postgres`
|
||||
- Verify DATABASE_URL format
|
||||
- Check password special characters (avoid `!` and other special chars)
|
||||
|
||||
2. **Google OAuth Issues**
|
||||
- Verify client ID and secret
|
||||
- Check authorized origins in Google Console
|
||||
- Ensure callback URL matches your domain
|
||||
|
||||
3. **Frontend Not Loading**
|
||||
- Check VITE_API_URL points to correct backend
|
||||
- Verify backend is healthy
|
||||
- Check browser console for errors
|
||||
|
||||
## 🚀 Production Deployment
|
||||
|
||||
### For Production Use
|
||||
|
||||
1. **Use HTTPS**: Configure SSL/TLS certificates
|
||||
2. **Secure Passwords**: Use strong, unique passwords
|
||||
3. **Environment Secrets**: Use Docker secrets or external secret management
|
||||
4. **Backup Strategy**: Implement regular database backups
|
||||
5. **Monitoring**: Set up application and infrastructure monitoring
|
||||
6. **Load Balancing**: Consider load balancers for high availability
|
||||
|
||||
### Example Production Environment
|
||||
|
||||
```env
|
||||
# Production environment example
|
||||
POSTGRES_PASSWORD=super_secure_random_password_here
|
||||
VITE_API_URL=https://api.yourdomain.com
|
||||
VITE_FRONTEND_URL=https://yourdomain.com
|
||||
NODE_ENV=production
|
||||
**VIP**
|
||||
```typescript
|
||||
- name: string
|
||||
- organization: string?
|
||||
- department: OFFICE_OF_DEVELOPMENT | ADMIN
|
||||
- arrivalMode: FLIGHT | SELF_DRIVING | OTHER
|
||||
- expectedArrival: DateTime?
|
||||
- airportPickup: boolean
|
||||
- venueTransport: boolean
|
||||
- notes: string?
|
||||
- flights: Flight[] (relation)
|
||||
```
|
||||
|
||||
## 📝 API Documentation
|
||||
**ScheduleEvent** (Unified Activity Model)
|
||||
```typescript
|
||||
- title: string
|
||||
- type: TRANSPORT | MEAL | EVENT | MEETING | ACCOMMODATION
|
||||
- status: SCHEDULED | IN_PROGRESS | COMPLETED | CANCELLED
|
||||
- startTime: DateTime
|
||||
- endTime: DateTime
|
||||
- location: string?
|
||||
- pickupLocation: string? (transport only)
|
||||
- dropoffLocation: string? (transport only)
|
||||
- description: string?
|
||||
- notes: string?
|
||||
- vipIds: string[] (multi-VIP support)
|
||||
- driverId: string?
|
||||
- vehicleId: string?
|
||||
```
|
||||
|
||||
### Authentication Endpoints
|
||||
**Driver**
|
||||
```typescript
|
||||
- name: string
|
||||
- phone: string
|
||||
- department: OFFICE_OF_DEVELOPMENT | ADMIN
|
||||
- userId: string? (optional link to User)
|
||||
```
|
||||
|
||||
- `GET /auth/google` - Initiate Google OAuth
|
||||
- `GET /auth/google/callback` - OAuth callback
|
||||
- `POST /auth/logout` - Logout user
|
||||
- `GET /auth/me` - Get current user
|
||||
|
||||
### Core Endpoints
|
||||
|
||||
- `GET /api/vips` - List VIPs
|
||||
- `POST /api/vips` - Create VIP
|
||||
- `GET /api/drivers` - List drivers
|
||||
- `POST /api/drivers` - Create driver
|
||||
- `GET /api/schedules` - List schedules
|
||||
- `POST /api/schedules` - Create schedule
|
||||
|
||||
### Health & Status
|
||||
|
||||
- `GET /health` - Application health check
|
||||
- `GET /api/status` - Detailed system status
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Add tests if applicable
|
||||
5. Submit a pull request
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
For issues and questions:
|
||||
|
||||
1. Check the troubleshooting section above
|
||||
2. Review Docker Compose logs
|
||||
3. Create an issue on GitHub with:
|
||||
- Docker Compose version
|
||||
- Environment details
|
||||
- Error logs
|
||||
- Steps to reproduce
|
||||
|
||||
## 🔄 Updates
|
||||
|
||||
To update to the latest version:
|
||||
|
||||
```bash
|
||||
# Pull latest images
|
||||
docker-compose pull
|
||||
|
||||
# Restart services
|
||||
docker-compose up -d
|
||||
**Vehicle**
|
||||
```typescript
|
||||
- name: string
|
||||
- type: VAN | SUV | SEDAN | BUS | GOLF_CART | OTHER
|
||||
- licensePlate: string
|
||||
- seatCapacity: number
|
||||
- status: AVAILABLE | IN_USE | MAINTENANCE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Built with ❤️ for efficient VIP transportation coordination**
|
||||
## 🔐 Authentication & Security
|
||||
|
||||
### Auth0 Integration
|
||||
|
||||
1. **Setup Auth0 Application**
|
||||
- Create Auth0 tenant at https://auth0.com
|
||||
- Create a "Regular Web Application"
|
||||
- Configure Allowed Callback URLs: `http://localhost:5173/callback`
|
||||
- Configure Allowed Logout URLs: `http://localhost:5173`
|
||||
- Configure Allowed Web Origins: `http://localhost:5173`
|
||||
|
||||
2. **Configure Backend** (`backend/.env`)
|
||||
```env
|
||||
AUTH0_DOMAIN=your-tenant.auth0.com
|
||||
AUTH0_AUDIENCE=https://your-tenant.auth0.com/api/v2/
|
||||
```
|
||||
|
||||
3. **Configure Frontend** (`frontend/.env`)
|
||||
```env
|
||||
VITE_AUTH0_DOMAIN=your-tenant.auth0.com
|
||||
VITE_AUTH0_CLIENT_ID=your_client_id
|
||||
VITE_AUTH0_AUDIENCE=https://your-tenant.auth0.com/api/v2/
|
||||
```
|
||||
|
||||
### User Approval Workflow
|
||||
|
||||
**New users must be manually approved:**
|
||||
1. User registers via Auth0
|
||||
2. User account created with `isApproved: false`
|
||||
3. Administrator manually approves user
|
||||
4. User gains access to system
|
||||
|
||||
⚠️ **First User Chicken-and-Egg Problem**: Use database seed script or manually set `isApproved: true` for first admin.
|
||||
|
||||
### Security Features
|
||||
|
||||
- **JWT Authentication**: Stateless token-based auth
|
||||
- **RBAC**: Role-based access control at route and UI levels
|
||||
- **API Guards**: NestJS guards enforce permissions on all endpoints
|
||||
- **Soft Deletes**: Audit trail preservation
|
||||
- **Input Validation**: DTO validation with class-validator
|
||||
- **SQL Injection Protection**: Prisma ORM parameterized queries
|
||||
|
||||
---
|
||||
|
||||
## 📡 API Endpoints
|
||||
|
||||
### Authentication
|
||||
```
|
||||
POST /api/v1/auth/login - Auth0 login
|
||||
POST /api/v1/auth/logout - Logout
|
||||
GET /api/v1/auth/me - Get current user
|
||||
```
|
||||
|
||||
### VIPs
|
||||
```
|
||||
GET /api/v1/vips - List all VIPs
|
||||
POST /api/v1/vips - Create VIP (Admin/Coordinator)
|
||||
GET /api/v1/vips/:id - Get VIP details
|
||||
PATCH /api/v1/vips/:id - Update VIP (Admin/Coordinator)
|
||||
DELETE /api/v1/vips/:id - Delete VIP (Admin/Coordinator)
|
||||
GET /api/v1/vips/:id/schedule - Get VIP's complete itinerary
|
||||
```
|
||||
|
||||
### Activities (ScheduleEvents)
|
||||
```
|
||||
GET /api/v1/events - List all activities
|
||||
POST /api/v1/events - Create activity (Admin/Coordinator)
|
||||
GET /api/v1/events/:id - Get activity details
|
||||
PATCH /api/v1/events/:id - Update activity (Admin/Coordinator/Driver)
|
||||
DELETE /api/v1/events/:id - Delete activity (Admin/Coordinator)
|
||||
PATCH /api/v1/events/:id/status - Update activity status (Driver allowed)
|
||||
POST /api/v1/events/:id/vips - Add VIPs to activity
|
||||
```
|
||||
|
||||
### Drivers
|
||||
```
|
||||
GET /api/v1/drivers - List all drivers
|
||||
POST /api/v1/drivers - Create driver (Admin/Coordinator)
|
||||
GET /api/v1/drivers/:id - Get driver details
|
||||
PATCH /api/v1/drivers/:id - Update driver (Admin/Coordinator)
|
||||
DELETE /api/v1/drivers/:id - Delete driver (Admin/Coordinator)
|
||||
```
|
||||
|
||||
### Vehicles
|
||||
```
|
||||
GET /api/v1/vehicles - List all vehicles
|
||||
POST /api/v1/vehicles - Create vehicle (Admin/Coordinator)
|
||||
GET /api/v1/vehicles/:id - Get vehicle details
|
||||
PATCH /api/v1/vehicles/:id - Update vehicle (Admin/Coordinator)
|
||||
DELETE /api/v1/vehicles/:id - Delete vehicle (Admin/Coordinator)
|
||||
```
|
||||
|
||||
### Users
|
||||
```
|
||||
GET /api/v1/users - List all users (Admin only)
|
||||
PATCH /api/v1/users/:id/approve - Approve user (Admin only)
|
||||
PATCH /api/v1/users/:id/role - Update user role (Admin only)
|
||||
DELETE /api/v1/users/:id - Delete user (Admin only)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### E2E Testing with Playwright
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
|
||||
# Install Playwright browsers (first time only)
|
||||
npx playwright install
|
||||
|
||||
# Run all E2E tests
|
||||
npx playwright test
|
||||
|
||||
# Run specific test file
|
||||
npx playwright test e2e/multi-vip-events.spec.ts
|
||||
|
||||
# Run tests in UI mode (interactive)
|
||||
npx playwright test --ui
|
||||
|
||||
# View test report
|
||||
npx playwright show-report
|
||||
```
|
||||
|
||||
**Test Coverage**
|
||||
- ✅ Multi-VIP event creation and management
|
||||
- ✅ Search and filtering functionality
|
||||
- ✅ Driver assignment workflows
|
||||
- ✅ Authentication flows
|
||||
- ✅ Navigation and routing
|
||||
- ✅ API integration
|
||||
- ✅ Accessibility compliance
|
||||
- ✅ iPad/tablet UI responsiveness
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Common Issues & Solutions
|
||||
|
||||
### Database Issues
|
||||
|
||||
**Problem**: "Can't reach database server at localhost:5432"
|
||||
```bash
|
||||
# Start PostgreSQL (if using Docker)
|
||||
docker-compose up -d postgres
|
||||
|
||||
# Or check if PostgreSQL is running locally
|
||||
sudo service postgresql status
|
||||
```
|
||||
|
||||
**Problem**: "Migration failed" or "Schema drift detected"
|
||||
```bash
|
||||
cd backend
|
||||
npx prisma migrate reset # ⚠️ Deletes all data
|
||||
npx prisma migrate deploy
|
||||
npx prisma db seed
|
||||
```
|
||||
|
||||
### Authentication Issues
|
||||
|
||||
**Problem**: "401 Unauthorized" on all API calls
|
||||
- Verify Auth0 domain and audience are correct in both backend and frontend `.env`
|
||||
- Check browser console for Auth0 errors
|
||||
- Verify JWT token is being sent in request headers
|
||||
- Ensure user is approved (`isApproved: true`)
|
||||
|
||||
**Problem**: Redirect loop on login
|
||||
- Check Auth0 callback URLs match frontend URL exactly
|
||||
- Clear browser cookies and local storage
|
||||
- Verify Auth0 client ID is correct
|
||||
|
||||
### Frontend Issues
|
||||
|
||||
**Problem**: "Cannot read property 'map' of undefined"
|
||||
- Data not loaded yet - add loading states
|
||||
- Check React Query cache invalidation
|
||||
- Verify API endpoint returns expected data structure
|
||||
|
||||
**Problem**: Search/sorting not working
|
||||
- Clear browser cache
|
||||
- Check console for JavaScript errors
|
||||
- Verify `filteredEvents` useMemo dependencies
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment
|
||||
|
||||
### Production Checklist
|
||||
|
||||
- [ ] Set `NODE_ENV=production`
|
||||
- [ ] Use strong database passwords
|
||||
- [ ] Configure Auth0 for production domain
|
||||
- [ ] Enable HTTPS/SSL certificates
|
||||
- [ ] Set up automated database backups
|
||||
- [ ] Configure Redis persistence
|
||||
- [ ] Set up monitoring (e.g., Sentry, DataDog)
|
||||
- [ ] Configure CORS for production domain
|
||||
- [ ] Review and adjust rate limiting
|
||||
- [ ] Set up log aggregation
|
||||
- [ ] Configure CDN for frontend assets (optional)
|
||||
|
||||
### Docker Deployment
|
||||
|
||||
```bash
|
||||
# Build images
|
||||
docker-compose build
|
||||
|
||||
# Start all services
|
||||
docker-compose up -d
|
||||
|
||||
# Run migrations
|
||||
docker-compose exec backend npx prisma migrate deploy
|
||||
|
||||
# Seed database (optional)
|
||||
docker-compose exec backend npx prisma db seed
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f backend
|
||||
docker-compose logs -f frontend
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
**Backend** (`backend/.env`)
|
||||
```env
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/vip_coordinator
|
||||
REDIS_URL=redis://localhost:6379
|
||||
AUTH0_DOMAIN=your-tenant.auth0.com
|
||||
AUTH0_AUDIENCE=https://your-tenant.auth0.com/api/v2/
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
```
|
||||
|
||||
**Frontend** (`frontend/.env`)
|
||||
```env
|
||||
VITE_API_URL=https://api.yourdomain.com
|
||||
VITE_AUTH0_DOMAIN=your-tenant.auth0.com
|
||||
VITE_AUTH0_CLIENT_ID=your_client_id
|
||||
VITE_AUTH0_AUDIENCE=https://your-tenant.auth0.com/api/v2/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Development Guide
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
vip-coordinator/
|
||||
├── backend/ # NestJS Backend
|
||||
│ ├── prisma/
|
||||
│ │ ├── schema.prisma # Database schema
|
||||
│ │ ├── migrations/ # Database migrations
|
||||
│ │ └── seed.ts # Test data seeding
|
||||
│ ├── src/
|
||||
│ │ ├── auth/ # Auth0 + JWT authentication
|
||||
│ │ ├── users/ # User management
|
||||
│ │ ├── vips/ # VIP management
|
||||
│ │ ├── drivers/ # Driver management
|
||||
│ │ ├── vehicles/ # Vehicle management
|
||||
│ │ ├── events/ # Activity scheduling (ScheduleEvent)
|
||||
│ │ ├── flights/ # Flight tracking
|
||||
│ │ └── common/ # Shared utilities, guards, decorators
|
||||
│ └── package.json
|
||||
│
|
||||
├── frontend/ # React Frontend
|
||||
│ ├── e2e/ # Playwright E2E tests
|
||||
│ ├── src/
|
||||
│ │ ├── pages/ # Page components
|
||||
│ │ ├── components/ # Reusable UI components
|
||||
│ │ ├── contexts/ # React contexts (Auth)
|
||||
│ │ ├── hooks/ # Custom React hooks
|
||||
│ │ ├── lib/ # Utilities, API client
|
||||
│ │ └── types/ # TypeScript types
|
||||
│ ├── playwright.config.ts # Playwright configuration
|
||||
│ └── package.json
|
||||
│
|
||||
├── docker-compose.yml # Docker orchestration
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
### Database Migrations
|
||||
|
||||
```bash
|
||||
# Create a new migration
|
||||
cd backend
|
||||
npx prisma migrate dev --name description_of_change
|
||||
|
||||
# Apply migrations to production
|
||||
npx prisma migrate deploy
|
||||
|
||||
# Reset database (⚠️ deletes all data)
|
||||
npx prisma migrate reset
|
||||
|
||||
# View migration status
|
||||
npx prisma migrate status
|
||||
```
|
||||
|
||||
### Adding a New Feature
|
||||
|
||||
1. **Backend**
|
||||
```bash
|
||||
cd backend
|
||||
nest g module feature-name
|
||||
nest g service feature-name
|
||||
nest g controller feature-name
|
||||
```
|
||||
|
||||
2. **Update Prisma Schema** (if needed)
|
||||
```prisma
|
||||
model NewEntity {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime? // Soft delete
|
||||
}
|
||||
```
|
||||
|
||||
3. **Create Migration**
|
||||
```bash
|
||||
npx prisma migrate dev --name add_new_entity
|
||||
```
|
||||
|
||||
4. **Frontend**
|
||||
- Create page in `frontend/src/pages/`
|
||||
- Add route in `frontend/src/App.tsx`
|
||||
- Create API service methods in `frontend/src/lib/api.ts`
|
||||
- Add navigation link in `frontend/src/components/Layout.tsx`
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Make your changes
|
||||
4. Write/update tests if applicable
|
||||
5. Commit with descriptive messages
|
||||
6. Push to your branch
|
||||
7. Create a Pull Request
|
||||
|
||||
### Commit Message Format
|
||||
|
||||
```
|
||||
<type>: <subject>
|
||||
|
||||
<body>
|
||||
|
||||
Co-Authored-By: Your Name <your.email@example.com>
|
||||
```
|
||||
|
||||
**Types**: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
|
||||
|
||||
---
|
||||
|
||||
## 📖 Additional Documentation
|
||||
|
||||
- **CLAUDE.md** - Comprehensive project context for AI assistants
|
||||
- **PLAYWRIGHT_GUIDE.md** - E2E testing guide
|
||||
- **NAVIGATION_UX_IMPROVEMENTS.md** - UI/UX enhancement notes
|
||||
- **KEYCLOAK_SETUP.md** - Alternative auth setup (archived)
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Changelog
|
||||
|
||||
### Latest (v2.0.0) - 2026-01-31
|
||||
|
||||
**Major Changes**
|
||||
- ✨ Unified activity system (merged Event/EventTemplate → ScheduleEvent)
|
||||
- ✨ Multi-VIP support (`vipIds[]` array for ridesharing)
|
||||
- ✨ Advanced search with real-time filtering
|
||||
- ✨ Sortable data tables (Title, Type, VIPs, Start Time, Status)
|
||||
- ✨ Balanced BSA-relevant test data
|
||||
- 🔧 Renamed "Schedule" → "Activities" throughout app
|
||||
- 🗄️ Database schema overhaul (3 new migrations)
|
||||
- 🧪 Playwright E2E test suite added
|
||||
- 📚 Complete documentation rewrite
|
||||
|
||||
**Breaking Changes**
|
||||
- API: `vipId` → `vipIds[]` in all event endpoints
|
||||
- Database: Event/EventTemplate tables dropped
|
||||
- Migration required: `npx prisma migrate deploy`
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is proprietary software developed for BSA Jamboree operations.
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
**For Issues:**
|
||||
1. Check this README's troubleshooting section
|
||||
2. Review logs: `docker-compose logs -f`
|
||||
3. Check CLAUDE.md for detailed context
|
||||
4. Create an issue in Gitea with:
|
||||
- Steps to reproduce
|
||||
- Error messages/logs
|
||||
- Environment details
|
||||
- Screenshots if applicable
|
||||
|
||||
**For Questions:**
|
||||
- Review documentation files in repository root
|
||||
- Check API documentation at `/api` endpoint
|
||||
- Review Playwright test examples in `frontend/e2e/`
|
||||
|
||||
---
|
||||
|
||||
**Built for BSA Jamboree Operations** 🏕️
|
||||
|
||||
@@ -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
|
||||
314
SETUP_GUIDE.md
314
SETUP_GUIDE.md
@@ -1,314 +0,0 @@
|
||||
# VIP Coordinator Setup Guide
|
||||
|
||||
A comprehensive guide to set up and run the VIP Coordinator system.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
- Docker and Docker Compose
|
||||
- Google Cloud Console account (for OAuth)
|
||||
|
||||
### 1. Clone and Start
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd vip-coordinator
|
||||
make dev
|
||||
```
|
||||
|
||||
The application will be available at:
|
||||
- **Frontend**: http://localhost:5173
|
||||
- **Backend API**: http://localhost:3000
|
||||
- **API Documentation**: http://localhost:3000/api-docs.html
|
||||
|
||||
### 2. Google OAuth Setup (Required)
|
||||
|
||||
1. **Create Google Cloud Project**:
|
||||
- Go to [Google Cloud Console](https://console.cloud.google.com/)
|
||||
- Create a new project or select existing one
|
||||
|
||||
2. **Enable Google+ API**:
|
||||
- Navigate to "APIs & Services" > "Library"
|
||||
- Search for "Google+ API" and enable it
|
||||
|
||||
3. **Create OAuth Credentials**:
|
||||
- Go to "APIs & Services" > "Credentials"
|
||||
- Click "Create Credentials" > "OAuth 2.0 Client IDs"
|
||||
- Application type: "Web application"
|
||||
- Authorized redirect URIs: `http://localhost:3000/auth/google/callback`
|
||||
|
||||
4. **Configure Environment**:
|
||||
```bash
|
||||
# Copy the example environment file
|
||||
cp backend/.env.example backend/.env
|
||||
|
||||
# Edit backend/.env and add your Google OAuth credentials:
|
||||
GOOGLE_CLIENT_ID=your-client-id-here
|
||||
GOOGLE_CLIENT_SECRET=your-client-secret-here
|
||||
```
|
||||
|
||||
5. **Restart the Application**:
|
||||
```bash
|
||||
make dev
|
||||
```
|
||||
|
||||
### 3. First Login
|
||||
- Visit http://localhost:5173
|
||||
- Click "Continue with Google"
|
||||
- The first user to log in becomes the system administrator
|
||||
- Subsequent users need administrator approval
|
||||
|
||||
## 🏗️ Architecture Overview
|
||||
|
||||
### Authentication System
|
||||
- **JWT-based authentication** with Google OAuth
|
||||
- **Role-based access control**: Administrator, Coordinator, Driver
|
||||
- **User approval system** for new registrations
|
||||
- **Simple setup** - no complex OAuth configurations needed
|
||||
|
||||
### Database
|
||||
- **PostgreSQL** for persistent data storage
|
||||
- **Automatic schema initialization** on first run
|
||||
- **User management** with approval workflows
|
||||
- **VIP and driver data** with scheduling
|
||||
|
||||
### API Structure
|
||||
- **RESTful API** with comprehensive endpoints
|
||||
- **OpenAPI/Swagger documentation** at `/api-docs.html`
|
||||
- **Role-based endpoint protection**
|
||||
- **Real-time flight tracking** integration
|
||||
|
||||
## 📋 Features
|
||||
|
||||
### Current Features
|
||||
- ✅ **User Management**: Google OAuth with role-based access
|
||||
- ✅ **VIP Management**: Create, edit, track VIPs with flight information
|
||||
- ✅ **Driver Coordination**: Manage drivers and assignments
|
||||
- ✅ **Flight Tracking**: Real-time flight status updates
|
||||
- ✅ **Schedule Management**: Event scheduling with conflict detection
|
||||
- ✅ **Department Support**: Office of Development and Admin departments
|
||||
- ✅ **API Documentation**: Interactive Swagger UI
|
||||
|
||||
### User Roles
|
||||
- **Administrator**: Full system access, user management
|
||||
- **Coordinator**: VIP and driver management, scheduling
|
||||
- **Driver**: View assigned schedules (planned)
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### Environment Variables
|
||||
```bash
|
||||
# Database
|
||||
DATABASE_URL=postgresql://vip_user:vip_password@db:5432/vip_coordinator
|
||||
|
||||
# Authentication
|
||||
GOOGLE_CLIENT_ID=your-google-client-id
|
||||
GOOGLE_CLIENT_SECRET=your-google-client-secret
|
||||
JWT_SECRET=your-jwt-secret-key
|
||||
|
||||
# External APIs (Optional)
|
||||
AVIATIONSTACK_API_KEY=your-aviationstack-key
|
||||
|
||||
# Application
|
||||
FRONTEND_URL=http://localhost:5173
|
||||
PORT=3000
|
||||
```
|
||||
|
||||
### Docker Services
|
||||
- **Frontend**: React + Vite development server
|
||||
- **Backend**: Node.js + Express API server
|
||||
- **Database**: PostgreSQL with automatic initialization
|
||||
- **Redis**: Caching and real-time updates
|
||||
|
||||
## 🛠️ Development
|
||||
|
||||
### Available Commands
|
||||
```bash
|
||||
# Start development environment
|
||||
make dev
|
||||
|
||||
# View logs
|
||||
make logs
|
||||
|
||||
# Stop all services
|
||||
make down
|
||||
|
||||
# Rebuild containers
|
||||
make build
|
||||
|
||||
# Backend only
|
||||
cd backend && npm run dev
|
||||
|
||||
# Frontend only
|
||||
cd frontend && npm run dev
|
||||
```
|
||||
|
||||
### API Testing
|
||||
- **Interactive Documentation**: http://localhost:3000/api-docs.html
|
||||
- **Health Check**: http://localhost:3000/api/health
|
||||
- **Authentication Test**: Use the "Try it out" feature in Swagger UI
|
||||
|
||||
## 🔐 Security
|
||||
|
||||
### Authentication Flow
|
||||
1. User clicks "Continue with Google"
|
||||
2. Redirected to Google OAuth
|
||||
3. Google redirects back with authorization code
|
||||
4. Backend exchanges code for user info
|
||||
5. JWT token generated and returned
|
||||
6. Frontend stores token for API requests
|
||||
|
||||
### API Protection
|
||||
- All API endpoints require valid JWT token
|
||||
- Role-based access control on sensitive operations
|
||||
- User approval system for new registrations
|
||||
|
||||
## 📚 API Documentation
|
||||
|
||||
### Key Endpoints
|
||||
- **Authentication**: `/auth/*` - OAuth and user management
|
||||
- **VIPs**: `/api/vips/*` - VIP management and scheduling
|
||||
- **Drivers**: `/api/drivers/*` - Driver management and availability
|
||||
- **Flights**: `/api/flights/*` - Flight tracking and information
|
||||
- **Admin**: `/api/admin/*` - System administration
|
||||
|
||||
### Interactive Documentation
|
||||
Visit http://localhost:3000/api-docs.html for:
|
||||
- Complete API reference
|
||||
- Request/response examples
|
||||
- "Try it out" functionality
|
||||
- Schema definitions
|
||||
|
||||
## 🚨 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**OAuth Not Working**:
|
||||
- Verify Google Client ID and Secret in `.env`
|
||||
- Check redirect URI in Google Console matches exactly
|
||||
- Ensure Google+ API is enabled
|
||||
|
||||
**Database Connection Error**:
|
||||
- Verify Docker containers are running: `docker ps`
|
||||
- Check database logs: `docker-compose logs db`
|
||||
- Restart services: `make down && make dev`
|
||||
|
||||
**Frontend Can't Connect to Backend**:
|
||||
- Verify backend is running on port 3000
|
||||
- Check CORS configuration in backend
|
||||
- Ensure FRONTEND_URL is set correctly
|
||||
|
||||
### Getting Help
|
||||
1. Check the interactive API documentation
|
||||
2. Review Docker container logs
|
||||
3. Verify environment configuration
|
||||
4. Test with the health check endpoint
|
||||
|
||||
## 🔄 Production Deployment
|
||||
|
||||
### Prerequisites for Production
|
||||
|
||||
1. **Domain Setup**: Ensure your domains are configured:
|
||||
- Frontend: `https://bsa.madeamess.online`
|
||||
- API: `https://api.bsa.madeamess.online`
|
||||
|
||||
2. **SSL Certificates**: Configure SSL/TLS certificates for your domains
|
||||
|
||||
3. **Environment Configuration**: Copy and configure production environment:
|
||||
```bash
|
||||
cp .env.example .env.prod
|
||||
# Edit .env.prod with your secure values
|
||||
```
|
||||
|
||||
### Production Deployment Steps
|
||||
|
||||
1. **Configure Environment Variables**:
|
||||
```bash
|
||||
# Edit .env.prod with secure values:
|
||||
# - Change DB_PASSWORD to a strong password
|
||||
# - Generate new JWT_SECRET and SESSION_SECRET
|
||||
# - Update ADMIN_PASSWORD
|
||||
# - Set your AVIATIONSTACK_API_KEY
|
||||
```
|
||||
|
||||
2. **Deploy with Production Configuration**:
|
||||
```bash
|
||||
# Load production environment
|
||||
export $(cat .env.prod | xargs)
|
||||
|
||||
# Build and start production containers
|
||||
docker-compose -f docker-compose.prod.yml up -d --build
|
||||
```
|
||||
|
||||
3. **Verify Deployment**:
|
||||
```bash
|
||||
# Check container status
|
||||
docker-compose -f docker-compose.prod.yml ps
|
||||
|
||||
# View logs
|
||||
docker-compose -f docker-compose.prod.yml logs
|
||||
```
|
||||
|
||||
### Production vs Development Differences
|
||||
|
||||
| Feature | Development | Production |
|
||||
|---------|-------------|------------|
|
||||
| Build Target | `development` | `production` |
|
||||
| Source Code | Volume mounted (hot reload) | Built into image |
|
||||
| Database Password | Hardcoded `changeme` | Environment variable |
|
||||
| Frontend Server | Vite dev server (port 5173) | Nginx (port 80) |
|
||||
| API URL | `http://localhost:3000/api` | `https://api.bsa.madeamess.online/api` |
|
||||
| SSL/HTTPS | Not configured | Required |
|
||||
| Restart Policy | Manual | `unless-stopped` |
|
||||
|
||||
### Production Environment Variables
|
||||
|
||||
```bash
|
||||
# Database Configuration
|
||||
DB_PASSWORD=your-secure-database-password-here
|
||||
|
||||
# Domain Configuration
|
||||
DOMAIN=bsa.madeamess.online
|
||||
VITE_API_URL=https://api.bsa.madeamess.online/api
|
||||
|
||||
# Authentication Configuration (Generate new secure keys)
|
||||
JWT_SECRET=your-super-secure-jwt-secret-key-change-in-production-12345
|
||||
SESSION_SECRET=your-super-secure-session-secret-change-in-production-67890
|
||||
|
||||
# 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=your-secure-admin-password
|
||||
|
||||
# Port Configuration
|
||||
PORT=3000
|
||||
```
|
||||
|
||||
### Production-Specific Troubleshooting
|
||||
|
||||
**SSL Certificate errors**: Ensure certificates are properly configured
|
||||
**Domain resolution**: Verify DNS settings for your domains
|
||||
**Environment variables**: Check that all required variables are set in `.env.prod`
|
||||
**Firewall**: Ensure ports 80, 443, 3000 are accessible
|
||||
|
||||
### Production Logs
|
||||
|
||||
```bash
|
||||
# View production container logs
|
||||
docker-compose -f docker-compose.prod.yml logs backend
|
||||
docker-compose -f docker-compose.prod.yml logs frontend
|
||||
docker-compose -f docker-compose.prod.yml logs db
|
||||
|
||||
# Follow logs in real-time
|
||||
docker-compose -f docker-compose.prod.yml logs -f
|
||||
```
|
||||
|
||||
This setup guide reflects the current simple, effective architecture of the VIP Coordinator system with production-ready deployment capabilities.
|
||||
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!**
|
||||
344
TESTING.md
344
TESTING.md
@@ -1,344 +0,0 @@
|
||||
# VIP Coordinator - Testing Guide
|
||||
|
||||
This guide covers the complete testing infrastructure for the VIP Coordinator application.
|
||||
|
||||
## Overview
|
||||
|
||||
The testing setup includes:
|
||||
- **Backend Tests**: Jest with Supertest for API testing
|
||||
- **Frontend Tests**: Vitest with React Testing Library
|
||||
- **E2E Tests**: Playwright for end-to-end testing
|
||||
- **Test Database**: Separate PostgreSQL instance for tests
|
||||
- **CI/CD Pipeline**: GitHub Actions for automated testing
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Running All Tests
|
||||
```bash
|
||||
# Using Make
|
||||
make test
|
||||
|
||||
# Using Docker Compose
|
||||
docker-compose -f docker-compose.test.yml up
|
||||
```
|
||||
|
||||
### Running Specific Test Suites
|
||||
```bash
|
||||
# Backend tests only
|
||||
make test-backend
|
||||
|
||||
# Frontend tests only
|
||||
make test-frontend
|
||||
|
||||
# E2E tests only
|
||||
make test-e2e
|
||||
|
||||
# Generate coverage reports
|
||||
make test-coverage
|
||||
```
|
||||
|
||||
## Backend Testing
|
||||
|
||||
### Setup
|
||||
The backend uses Jest with TypeScript support and Supertest for API testing.
|
||||
|
||||
**Configuration**: `backend/jest.config.js`
|
||||
**Test Setup**: `backend/src/tests/setup.ts`
|
||||
|
||||
### Writing Tests
|
||||
|
||||
#### Unit Tests
|
||||
```typescript
|
||||
// backend/src/services/__tests__/authService.test.ts
|
||||
import { testPool } from '../../tests/setup';
|
||||
import { testUsers, insertTestUser } from '../../tests/fixtures';
|
||||
|
||||
describe('AuthService', () => {
|
||||
it('should create a new user', async () => {
|
||||
// Your test here
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### Integration Tests
|
||||
```typescript
|
||||
// backend/src/routes/__tests__/vips.test.ts
|
||||
import request from 'supertest';
|
||||
import app from '../../app';
|
||||
|
||||
describe('VIP API', () => {
|
||||
it('GET /api/vips should return all VIPs', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/vips')
|
||||
.set('Authorization', 'Bearer token');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Test Utilities
|
||||
- **Fixtures**: `backend/src/tests/fixtures.ts` - Pre-defined test data
|
||||
- **Test Database**: Automatically set up and torn down for each test
|
||||
- **Mock Services**: JWT, Google OAuth, etc.
|
||||
|
||||
### Running Backend Tests
|
||||
```bash
|
||||
cd backend
|
||||
npm test # Run all tests
|
||||
npm run test:watch # Watch mode
|
||||
npm run test:coverage # With coverage
|
||||
```
|
||||
|
||||
## Frontend Testing
|
||||
|
||||
### Setup
|
||||
The frontend uses Vitest with React Testing Library.
|
||||
|
||||
**Configuration**: `frontend/vitest.config.ts`
|
||||
**Test Setup**: `frontend/src/tests/setup.ts`
|
||||
|
||||
### Writing Tests
|
||||
|
||||
#### Component Tests
|
||||
```typescript
|
||||
// frontend/src/components/__tests__/VipForm.test.tsx
|
||||
import { render, screen } from '../../tests/test-utils';
|
||||
import VipForm from '../VipForm';
|
||||
|
||||
describe('VipForm', () => {
|
||||
it('renders all form fields', () => {
|
||||
render(<VipForm onSubmit={jest.fn()} onCancel={jest.fn()} />);
|
||||
expect(screen.getByLabelText(/full name/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### Page Tests
|
||||
```typescript
|
||||
// frontend/src/pages/__tests__/Dashboard.test.tsx
|
||||
import { render, waitFor } from '../../tests/test-utils';
|
||||
import Dashboard from '../Dashboard';
|
||||
|
||||
describe('Dashboard', () => {
|
||||
it('loads and displays VIPs', async () => {
|
||||
render(<Dashboard />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Test Utilities
|
||||
- **Custom Render**: Includes providers (Router, Toast, etc.)
|
||||
- **Mock Data**: Pre-defined users, VIPs, drivers
|
||||
- **API Mocks**: Mock fetch responses
|
||||
|
||||
### Running Frontend Tests
|
||||
```bash
|
||||
cd frontend
|
||||
npm test # Run all tests
|
||||
npm run test:ui # With UI
|
||||
npm run test:coverage # With coverage
|
||||
```
|
||||
|
||||
## E2E Testing
|
||||
|
||||
### Setup
|
||||
E2E tests use Playwright for cross-browser testing.
|
||||
|
||||
**Configuration**: `e2e/playwright.config.ts`
|
||||
|
||||
### Writing E2E Tests
|
||||
```typescript
|
||||
// e2e/tests/vip-management.spec.ts
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('create new VIP', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
// Login flow
|
||||
await page.click('text=Add VIP');
|
||||
await page.fill('[name="name"]', 'Test VIP');
|
||||
await page.click('text=Submit');
|
||||
await expect(page.locator('text=Test VIP')).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
### Running E2E Tests
|
||||
```bash
|
||||
# Local development
|
||||
npx playwright test
|
||||
|
||||
# In Docker
|
||||
make test-e2e
|
||||
```
|
||||
|
||||
## Database Testing
|
||||
|
||||
### Test Database Setup
|
||||
- Separate database instance for tests
|
||||
- Automatic schema creation and migrations
|
||||
- Test data seeding
|
||||
- Cleanup after each test
|
||||
|
||||
### Database Commands
|
||||
```bash
|
||||
# Set up test database with schema and seed data
|
||||
make db-setup
|
||||
|
||||
# Run migrations only
|
||||
make db-migrate
|
||||
|
||||
# Seed test data
|
||||
make db-seed
|
||||
```
|
||||
|
||||
### Creating Migrations
|
||||
```bash
|
||||
cd backend
|
||||
npm run db:migrate:create "add_new_column"
|
||||
```
|
||||
|
||||
## Docker Test Environment
|
||||
|
||||
### Configuration
|
||||
**File**: `docker-compose.test.yml`
|
||||
|
||||
Services:
|
||||
- `test-db`: PostgreSQL for tests (port 5433)
|
||||
- `test-redis`: Redis for tests (port 6380)
|
||||
- `backend-test`: Backend test runner
|
||||
- `frontend-test`: Frontend test runner
|
||||
- `e2e-test`: E2E test runner
|
||||
|
||||
### Environment Variables
|
||||
Create `.env.test` based on `.env.example`:
|
||||
```env
|
||||
DATABASE_URL=postgresql://test_user:test_password@test-db:5432/vip_coordinator_test
|
||||
REDIS_URL=redis://test-redis:6379
|
||||
GOOGLE_CLIENT_ID=test_client_id
|
||||
# ... other test values
|
||||
```
|
||||
|
||||
## CI/CD Pipeline
|
||||
|
||||
### GitHub Actions Workflows
|
||||
|
||||
#### Main CI Pipeline
|
||||
**File**: `.github/workflows/ci.yml`
|
||||
|
||||
Runs on every push and PR:
|
||||
1. Backend tests with coverage
|
||||
2. Frontend tests with coverage
|
||||
3. Security scanning
|
||||
4. Docker image building
|
||||
5. Deployment (staging/production)
|
||||
|
||||
#### E2E Test Schedule
|
||||
**File**: `.github/workflows/e2e-tests.yml`
|
||||
|
||||
Runs daily or on-demand:
|
||||
- Cross-browser testing
|
||||
- Multiple environments
|
||||
- Result notifications
|
||||
|
||||
#### Dependency Updates
|
||||
**File**: `.github/workflows/dependency-update.yml`
|
||||
|
||||
Weekly automated updates:
|
||||
- npm package updates
|
||||
- Security fixes
|
||||
- Automated PR creation
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Test Organization
|
||||
- Group related tests in describe blocks
|
||||
- Use descriptive test names
|
||||
- One assertion per test when possible
|
||||
- Use beforeEach/afterEach for setup/cleanup
|
||||
|
||||
### Test Data
|
||||
- Use fixtures for consistent test data
|
||||
- Clean up after tests
|
||||
- Don't rely on test execution order
|
||||
- Use unique identifiers to avoid conflicts
|
||||
|
||||
### Mocking
|
||||
- Mock external services (Google OAuth, APIs)
|
||||
- Use test doubles for database operations
|
||||
- Mock time-dependent operations
|
||||
|
||||
### Performance
|
||||
- Run tests in parallel when possible
|
||||
- Use test database in memory (tmpfs)
|
||||
- Cache Docker layers in CI
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Port Conflicts
|
||||
```bash
|
||||
# Check if ports are in use
|
||||
lsof -i :5433 # Test database
|
||||
lsof -i :6380 # Test Redis
|
||||
```
|
||||
|
||||
#### Database Connection Issues
|
||||
```bash
|
||||
# Ensure test database is running
|
||||
docker-compose -f docker-compose.test.yml up test-db -d
|
||||
|
||||
# Check logs
|
||||
docker-compose -f docker-compose.test.yml logs test-db
|
||||
```
|
||||
|
||||
#### Test Timeouts
|
||||
- Increase timeout in test configuration
|
||||
- Check for proper async/await usage
|
||||
- Ensure services are ready before tests
|
||||
|
||||
### Debug Mode
|
||||
```bash
|
||||
# Run tests with debug output
|
||||
DEBUG=* npm test
|
||||
|
||||
# Run specific test file
|
||||
npm test -- authService.test.ts
|
||||
|
||||
# Run tests matching pattern
|
||||
npm test -- --grep "should create user"
|
||||
```
|
||||
|
||||
## Coverage Reports
|
||||
|
||||
Coverage reports are generated in:
|
||||
- Backend: `backend/coverage/`
|
||||
- Frontend: `frontend/coverage/`
|
||||
|
||||
View HTML reports:
|
||||
```bash
|
||||
# Backend
|
||||
open backend/coverage/lcov-report/index.html
|
||||
|
||||
# Frontend
|
||||
open frontend/coverage/index.html
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
When adding new features:
|
||||
1. Write tests first (TDD approach)
|
||||
2. Ensure all tests pass
|
||||
3. Maintain >80% code coverage
|
||||
4. Update test documentation
|
||||
|
||||
## Resources
|
||||
|
||||
- [Jest Documentation](https://jestjs.io/docs/getting-started)
|
||||
- [Vitest Documentation](https://vitest.dev/guide/)
|
||||
- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/)
|
||||
- [Playwright Documentation](https://playwright.dev/docs/intro)
|
||||
- [Supertest Documentation](https://github.com/visionmedia/supertest)
|
||||
@@ -1,137 +0,0 @@
|
||||
# Testing Quick Start Guide
|
||||
|
||||
## 🚀 Get Testing in 5 Minutes
|
||||
|
||||
### 1. Prerequisites
|
||||
- Docker installed and running
|
||||
- Node.js 20+ (for local development)
|
||||
- Make command available
|
||||
|
||||
### 2. Initial Setup
|
||||
```bash
|
||||
# Clone and navigate to project
|
||||
cd vip-coordinator
|
||||
|
||||
# Copy environment variables
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env and add your values (or use defaults for testing)
|
||||
```
|
||||
|
||||
### 3. Run Your First Tests
|
||||
|
||||
#### Option A: Using Docker (Recommended)
|
||||
```bash
|
||||
# Run all tests
|
||||
make test
|
||||
|
||||
# Run specific test suites
|
||||
make test-backend # Backend only
|
||||
make test-frontend # Frontend only
|
||||
```
|
||||
|
||||
#### Option B: Local Development
|
||||
```bash
|
||||
# Backend tests
|
||||
cd backend
|
||||
npm install
|
||||
npm test
|
||||
|
||||
# Frontend tests
|
||||
cd ../frontend
|
||||
npm install
|
||||
npm test
|
||||
```
|
||||
|
||||
### 4. Writing Your First Test
|
||||
|
||||
#### Backend Test Example
|
||||
Create `backend/src/routes/__tests__/health.test.ts`:
|
||||
```typescript
|
||||
import request from 'supertest';
|
||||
import express from 'express';
|
||||
|
||||
const app = express();
|
||||
app.get('/health', (req, res) => res.json({ status: 'ok' }));
|
||||
|
||||
describe('Health Check', () => {
|
||||
it('should return status ok', async () => {
|
||||
const response = await request(app).get('/health');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.status).toBe('ok');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### Frontend Test Example
|
||||
Create `frontend/src/components/__tests__/Button.test.tsx`:
|
||||
```typescript
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Button } from '../Button';
|
||||
|
||||
describe('Button', () => {
|
||||
it('renders with text', () => {
|
||||
render(<Button>Click me</Button>);
|
||||
expect(screen.getByText('Click me')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 5. Common Commands
|
||||
|
||||
```bash
|
||||
# Database setup
|
||||
make db-setup # Initialize test database
|
||||
|
||||
# Run tests with coverage
|
||||
make test-coverage # Generate coverage reports
|
||||
|
||||
# Clean up
|
||||
make clean # Remove all test containers
|
||||
|
||||
# Get help
|
||||
make help # Show all available commands
|
||||
```
|
||||
|
||||
### 6. VS Code Integration
|
||||
|
||||
Add to `.vscode/settings.json`:
|
||||
```json
|
||||
{
|
||||
"jest.autoRun": {
|
||||
"watch": true,
|
||||
"onStartup": ["all-tests"]
|
||||
},
|
||||
"vitest.enable": true,
|
||||
"vitest.commandLine": "npm test"
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Debugging Tests
|
||||
|
||||
```bash
|
||||
# Run specific test file
|
||||
npm test -- authService.test.ts
|
||||
|
||||
# Run in watch mode
|
||||
npm run test:watch
|
||||
|
||||
# Debug mode
|
||||
node --inspect-brk node_modules/.bin/jest --runInBand
|
||||
```
|
||||
|
||||
### 8. Tips
|
||||
|
||||
- ✅ Run tests before committing
|
||||
- ✅ Write tests for new features
|
||||
- ✅ Keep tests simple and focused
|
||||
- ✅ Use the provided fixtures and utilities
|
||||
- ✅ Check coverage reports regularly
|
||||
|
||||
### Need Help?
|
||||
|
||||
- See `TESTING.md` for detailed documentation
|
||||
- Check example tests in `__tests__` directories
|
||||
- Review `TESTING_SETUP_SUMMARY.md` for architecture overview
|
||||
|
||||
Happy Testing! 🎉
|
||||
@@ -1,223 +0,0 @@
|
||||
# VIP Coordinator - Testing Infrastructure Setup Summary
|
||||
|
||||
## Overview
|
||||
This document summarizes the comprehensive testing infrastructure that has been set up for the VIP Transportation Coordination System. The system previously had NO automated tests, and now has a complete testing framework ready for implementation.
|
||||
|
||||
## What Was Accomplished
|
||||
|
||||
### 1. ✅ Backend Testing Infrastructure (Jest + Supertest)
|
||||
- **Configuration**: Created `backend/jest.config.js` with TypeScript support
|
||||
- **Test Setup**: Created `backend/src/tests/setup.ts` with:
|
||||
- Test database initialization
|
||||
- Redis test instance
|
||||
- Automatic cleanup between tests
|
||||
- Global setup/teardown
|
||||
- **Test Fixtures**: Created `backend/src/tests/fixtures.ts` with:
|
||||
- Mock users (admin, coordinator, driver, pending)
|
||||
- Mock VIPs (flight and self-driving)
|
||||
- Mock drivers and schedule events
|
||||
- Helper functions for database operations
|
||||
- **Sample Tests**: Created example tests for:
|
||||
- Authentication service (`authService.test.ts`)
|
||||
- VIP API endpoints (`vips.test.ts`)
|
||||
- **NPM Scripts**: Added test commands to package.json
|
||||
|
||||
### 2. ✅ Frontend Testing Infrastructure (Vitest + React Testing Library)
|
||||
- **Configuration**: Created `frontend/vitest.config.ts` with:
|
||||
- JSdom environment
|
||||
- React plugin
|
||||
- Coverage configuration
|
||||
- **Test Setup**: Created `frontend/src/tests/setup.ts` with:
|
||||
- React Testing Library configuration
|
||||
- Global mocks (fetch, Google Identity Services)
|
||||
- Window API mocks
|
||||
- **Test Utilities**: Created `frontend/src/tests/test-utils.tsx` with:
|
||||
- Custom render function with providers
|
||||
- Mock data for all entities
|
||||
- API response mocks
|
||||
- **Sample Tests**: Created example tests for:
|
||||
- GoogleLogin component
|
||||
- VipForm component
|
||||
- **NPM Scripts**: Added test commands to package.json
|
||||
|
||||
### 3. ✅ Security Improvements
|
||||
- **Environment Variables**:
|
||||
- Created `.env.example` template
|
||||
- Updated `docker-compose.dev.yml` to use env vars
|
||||
- Removed hardcoded Google OAuth credentials
|
||||
- **Secure Config**: Created `backend/src/config/env.ts` with:
|
||||
- Zod schema validation
|
||||
- Type-safe environment variables
|
||||
- Clear error messages for missing vars
|
||||
- **Git Security**: Verified `.gitignore` includes all sensitive files
|
||||
|
||||
### 4. ✅ Database Migration System
|
||||
- **Migration Service**: Created `backend/src/services/migrationService.ts` with:
|
||||
- Automatic migration runner
|
||||
- Checksum verification
|
||||
- Migration history tracking
|
||||
- Migration file generator
|
||||
- **Seed Service**: Created `backend/src/services/seedService.ts` with:
|
||||
- Test data for all entities
|
||||
- Reset functionality
|
||||
- Idempotent operations
|
||||
- **CLI Tool**: Created `backend/src/scripts/db-cli.ts` with commands:
|
||||
- `db:migrate` - Run pending migrations
|
||||
- `db:migrate:create` - Create new migration
|
||||
- `db:seed` - Seed test data
|
||||
- `db:setup` - Complete database setup
|
||||
- **NPM Scripts**: Added all database commands
|
||||
|
||||
### 5. ✅ Docker Test Environment
|
||||
- **Test Compose File**: Created `docker-compose.test.yml` with:
|
||||
- Separate test database (port 5433)
|
||||
- Separate test Redis (port 6380)
|
||||
- Test runners for backend/frontend
|
||||
- Health checks for all services
|
||||
- Memory-based database for speed
|
||||
- **E2E Dockerfile**: Created `Dockerfile.e2e` for Playwright
|
||||
- **Test Runner Script**: Created `scripts/test-runner.sh` with:
|
||||
- Color-coded output
|
||||
- Service orchestration
|
||||
- Cleanup handling
|
||||
- Multiple test modes
|
||||
|
||||
### 6. ✅ CI/CD Pipeline (GitHub Actions)
|
||||
- **Main CI Pipeline**: Created `.github/workflows/ci.yml` with:
|
||||
- Backend test job with PostgreSQL/Redis services
|
||||
- Frontend test job with build verification
|
||||
- Docker image building and pushing
|
||||
- Security scanning with Trivy
|
||||
- Deployment jobs for staging/production
|
||||
- **E2E Test Schedule**: Created `.github/workflows/e2e-tests.yml` with:
|
||||
- Daily scheduled runs
|
||||
- Manual trigger option
|
||||
- Multi-browser testing
|
||||
- Result artifacts
|
||||
- **Dependency Updates**: Created `.github/workflows/dependency-update.yml` with:
|
||||
- Weekly automated updates
|
||||
- Security fixes
|
||||
- Automated PR creation
|
||||
|
||||
### 7. ✅ Enhanced Makefile
|
||||
Updated `Makefile` with new commands:
|
||||
- `make test` - Run all tests
|
||||
- `make test-backend` - Backend tests only
|
||||
- `make test-frontend` - Frontend tests only
|
||||
- `make test-e2e` - E2E tests only
|
||||
- `make test-coverage` - Generate coverage reports
|
||||
- `make db-setup` - Initialize database
|
||||
- `make db-migrate` - Run migrations
|
||||
- `make db-seed` - Seed data
|
||||
- `make clean` - Clean all Docker resources
|
||||
- `make help` - Show all commands
|
||||
|
||||
### 8. ✅ Documentation
|
||||
- **TESTING.md**: Comprehensive testing guide covering:
|
||||
- How to write tests
|
||||
- How to run tests
|
||||
- Best practices
|
||||
- Troubleshooting
|
||||
- Coverage reports
|
||||
- **This Summary**: Complete overview of changes
|
||||
|
||||
## Current State vs. Previous State
|
||||
|
||||
### Before:
|
||||
- ❌ No automated tests
|
||||
- ❌ No test infrastructure
|
||||
- ❌ Hardcoded credentials in Docker files
|
||||
- ❌ No database migration system
|
||||
- ❌ No CI/CD pipeline
|
||||
- ❌ No test documentation
|
||||
|
||||
### After:
|
||||
- ✅ Complete test infrastructure for backend and frontend
|
||||
- ✅ Sample tests demonstrating patterns
|
||||
- ✅ Secure environment variable handling
|
||||
- ✅ Database migration and seeding system
|
||||
- ✅ Docker test environment
|
||||
- ✅ GitHub Actions CI/CD pipeline
|
||||
- ✅ Comprehensive documentation
|
||||
- ✅ Easy-to-use Make commands
|
||||
|
||||
## Next Steps
|
||||
|
||||
The remaining tasks from the todo list that need implementation:
|
||||
|
||||
1. **Create Backend Unit Tests** (High Priority)
|
||||
- Auth service tests
|
||||
- Scheduling service tests
|
||||
- Flight tracking service tests
|
||||
- Database service tests
|
||||
|
||||
2. **Create Backend Integration Tests** (High Priority)
|
||||
- Complete VIP API tests
|
||||
- Driver API tests
|
||||
- Schedule API tests
|
||||
- Admin API tests
|
||||
|
||||
3. **Create Frontend Component Tests** (Medium Priority)
|
||||
- Navigation components
|
||||
- Form components
|
||||
- Dashboard components
|
||||
- Error boundary tests
|
||||
|
||||
4. **Create Frontend Integration Tests** (Medium Priority)
|
||||
- Page-level tests
|
||||
- User workflow tests
|
||||
- API integration tests
|
||||
|
||||
5. **Set up E2E Testing Framework** (Medium Priority)
|
||||
- Install Playwright properly
|
||||
- Create page objects
|
||||
- Set up test data management
|
||||
|
||||
6. **Create E2E Tests** (Medium Priority)
|
||||
- Login flow
|
||||
- VIP management flow
|
||||
- Driver assignment flow
|
||||
- Schedule management flow
|
||||
|
||||
## How to Get Started
|
||||
|
||||
1. **Install Dependencies**:
|
||||
```bash
|
||||
cd backend && npm install
|
||||
cd ../frontend && npm install
|
||||
```
|
||||
|
||||
2. **Set Up Environment**:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with your values
|
||||
```
|
||||
|
||||
3. **Run Tests**:
|
||||
```bash
|
||||
make test # Run all tests
|
||||
```
|
||||
|
||||
4. **Start Writing Tests**:
|
||||
- Use the example tests as templates
|
||||
- Follow the patterns established
|
||||
- Refer to TESTING.md for guidelines
|
||||
|
||||
## Benefits of This Setup
|
||||
|
||||
1. **Quality Assurance**: Catch bugs before production
|
||||
2. **Refactoring Safety**: Change code with confidence
|
||||
3. **Documentation**: Tests serve as living documentation
|
||||
4. **CI/CD**: Automated deployment pipeline
|
||||
5. **Security**: No more hardcoded credentials
|
||||
6. **Developer Experience**: Easy commands and clear structure
|
||||
|
||||
## Technical Debt Addressed
|
||||
|
||||
1. **No Tests**: Now have complete test infrastructure
|
||||
2. **Security Issues**: Credentials now properly managed
|
||||
3. **No Migrations**: Database changes now versioned
|
||||
4. **Manual Deployment**: Now automated via CI/CD
|
||||
5. **No Standards**: Clear testing patterns established
|
||||
|
||||
This testing infrastructure provides a solid foundation for maintaining and scaling the VIP Coordinator application with confidence.
|
||||
@@ -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
57
backend/.env
57
backend/.env
@@ -1,26 +1,33 @@
|
||||
# Database Configuration
|
||||
DATABASE_URL=postgresql://postgres:changeme@db:5432/vip_coordinator
|
||||
|
||||
# Redis Configuration
|
||||
REDIS_URL=redis://redis:6379
|
||||
|
||||
# Authentication Configuration
|
||||
JWT_SECRET=your-super-secure-jwt-secret-key-change-in-production-12345
|
||||
SESSION_SECRET=your-super-secure-session-secret-change-in-production-67890
|
||||
|
||||
# Google OAuth Configuration (optional for local development)
|
||||
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=admin123
|
||||
|
||||
# Port Configuration
|
||||
# ============================================
|
||||
# Application Configuration
|
||||
# ============================================
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
FRONTEND_URL=http://localhost:5173
|
||||
|
||||
# ============================================
|
||||
# Database Configuration
|
||||
# ============================================
|
||||
DATABASE_URL="postgresql://postgres:changeme@localhost:5433/vip_coordinator"
|
||||
|
||||
# ============================================
|
||||
# Redis Configuration (Optional)
|
||||
# ============================================
|
||||
REDIS_URL="redis://localhost:6379"
|
||||
|
||||
# ============================================
|
||||
# Auth0 Configuration
|
||||
# ============================================
|
||||
# Get these from your Auth0 dashboard:
|
||||
# 1. Create Application (Single Page Application)
|
||||
# 2. Create API
|
||||
# 3. Configure callback URLs: http://localhost:5173/callback
|
||||
AUTH0_DOMAIN="dev-s855cy3bvjjbkljt.us.auth0.com"
|
||||
AUTH0_AUDIENCE="https://vip-coordinator-api"
|
||||
AUTH0_ISSUER="https://dev-s855cy3bvjjbkljt.us.auth0.com/"
|
||||
|
||||
# ============================================
|
||||
# Flight Tracking API (Optional)
|
||||
# ============================================
|
||||
# Get API key from: https://aviationstack.com/
|
||||
AVIATIONSTACK_API_KEY="your-aviationstack-api-key"
|
||||
|
||||
@@ -1,22 +1,34 @@
|
||||
# Database Configuration
|
||||
DATABASE_URL=postgresql://postgres:password@db:5432/vip_coordinator
|
||||
|
||||
# Redis Configuration
|
||||
REDIS_URL=redis://redis:6379
|
||||
|
||||
# Authentication Configuration
|
||||
JWT_SECRET=your-super-secure-jwt-secret-key-change-in-production
|
||||
SESSION_SECRET=your-super-secure-session-secret-change-in-production
|
||||
|
||||
# Google OAuth Configuration
|
||||
GOOGLE_CLIENT_ID=your-google-client-id-from-console
|
||||
GOOGLE_CLIENT_SECRET=your-google-client-secret-from-console
|
||||
|
||||
# Frontend URL
|
||||
# ============================================
|
||||
# Application Configuration
|
||||
# ============================================
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
FRONTEND_URL=http://localhost:5173
|
||||
|
||||
# Flight API Configuration
|
||||
AVIATIONSTACK_API_KEY=your-aviationstack-api-key
|
||||
# ============================================
|
||||
# Database Configuration
|
||||
# ============================================
|
||||
# Port 5433 is used to avoid conflicts with local PostgreSQL
|
||||
DATABASE_URL="postgresql://postgres:changeme@localhost:5433/vip_coordinator"
|
||||
|
||||
# Admin Configuration
|
||||
ADMIN_PASSWORD=admin123
|
||||
# ============================================
|
||||
# Redis Configuration (Optional)
|
||||
# ============================================
|
||||
# Port 6380 is used to avoid conflicts with local Redis
|
||||
REDIS_URL="redis://localhost:6380"
|
||||
|
||||
# ============================================
|
||||
# Auth0 Configuration
|
||||
# ============================================
|
||||
# Get these from your Auth0 dashboard:
|
||||
# 1. Create Application (Single Page Application)
|
||||
# 2. Create API
|
||||
# 3. Configure callback URLs: http://localhost:5173/callback
|
||||
AUTH0_DOMAIN="your-tenant.us.auth0.com"
|
||||
AUTH0_AUDIENCE="https://your-api-identifier"
|
||||
AUTH0_ISSUER="https://your-tenant.us.auth0.com/"
|
||||
|
||||
# ============================================
|
||||
# Flight Tracking API (Optional)
|
||||
# ============================================
|
||||
AVIATIONSTACK_API_KEY="your-aviationstack-api-key"
|
||||
|
||||
43
backend/.gitignore
vendored
Normal file
43
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
pnpm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
/.nyc_output
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.production
|
||||
|
||||
# Prisma
|
||||
prisma/migrations/.migrate_lock
|
||||
@@ -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"]
|
||||
134
backend/README.md
Normal file
134
backend/README.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# VIP Coordinator Backend
|
||||
|
||||
NestJS 10.x backend with Prisma ORM, Auth0 authentication, and PostgreSQL.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Set up environment variables
|
||||
cp .env.example .env
|
||||
# Edit .env with your Auth0 credentials
|
||||
|
||||
# Start PostgreSQL (via Docker)
|
||||
cd ..
|
||||
docker-compose up -d postgres
|
||||
|
||||
# Generate Prisma Client
|
||||
npx prisma generate
|
||||
|
||||
# Run database migrations
|
||||
npx prisma migrate dev
|
||||
|
||||
# Seed sample data (optional)
|
||||
npm run prisma:seed
|
||||
|
||||
# Start development server
|
||||
npm run start:dev
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
All endpoints are prefixed with `/api/v1`
|
||||
|
||||
### Public Endpoints
|
||||
- `GET /health` - Health check
|
||||
|
||||
### Authentication
|
||||
- `GET /auth/profile` - Get current user profile
|
||||
|
||||
### Users (Admin only)
|
||||
- `GET /users` - List all users
|
||||
- `GET /users/pending` - List pending approval users
|
||||
- `GET /users/:id` - Get user by ID
|
||||
- `PATCH /users/:id` - Update user
|
||||
- `PATCH /users/:id/approve` - Approve/deny user
|
||||
- `DELETE /users/:id` - Delete user (soft)
|
||||
|
||||
### VIPs (Admin, Coordinator)
|
||||
- `GET /vips` - List all VIPs
|
||||
- `POST /vips` - Create VIP
|
||||
- `GET /vips/:id` - Get VIP by ID
|
||||
- `PATCH /vips/:id` - Update VIP
|
||||
- `DELETE /vips/:id` - Delete VIP (soft)
|
||||
|
||||
### Drivers (Admin, Coordinator)
|
||||
- `GET /drivers` - List all drivers
|
||||
- `POST /drivers` - Create driver
|
||||
- `GET /drivers/:id` - Get driver by ID
|
||||
- `GET /drivers/:id/schedule` - Get driver schedule
|
||||
- `PATCH /drivers/:id` - Update driver
|
||||
- `DELETE /drivers/:id` - Delete driver (soft)
|
||||
|
||||
### Events (Admin, Coordinator; Drivers can view and update status)
|
||||
- `GET /events` - List all events
|
||||
- `POST /events` - Create event (with conflict detection)
|
||||
- `GET /events/:id` - Get event by ID
|
||||
- `PATCH /events/:id` - Update event
|
||||
- `PATCH /events/:id/status` - Update event status
|
||||
- `DELETE /events/:id` - Delete event (soft)
|
||||
|
||||
### Flights (Admin, Coordinator)
|
||||
- `GET /flights` - List all flights
|
||||
- `POST /flights` - Create flight
|
||||
- `GET /flights/status/:flightNumber` - Get real-time flight status
|
||||
- `GET /flights/vip/:vipId` - Get flights for VIP
|
||||
- `GET /flights/:id` - Get flight by ID
|
||||
- `PATCH /flights/:id` - Update flight
|
||||
- `DELETE /flights/:id` - Delete flight
|
||||
|
||||
## Development Commands
|
||||
|
||||
```bash
|
||||
npm run start:dev # Start dev server with hot reload
|
||||
npm run build # Build for production
|
||||
npm run start:prod # Start production server
|
||||
npm run lint # Run ESLint
|
||||
npm run test # Run tests
|
||||
npm run test:watch # Run tests in watch mode
|
||||
npm run test:cov # Run tests with coverage
|
||||
```
|
||||
|
||||
## Database Commands
|
||||
|
||||
```bash
|
||||
npx prisma studio # Open Prisma Studio (database GUI)
|
||||
npx prisma migrate dev # Create and apply migration
|
||||
npx prisma migrate deploy # Apply migrations (production)
|
||||
npx prisma migrate reset # Reset database (DEV ONLY)
|
||||
npx prisma generate # Regenerate Prisma Client
|
||||
npm run prisma:seed # Seed database with sample data
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
See `.env.example` for all required variables:
|
||||
|
||||
- `DATABASE_URL` - PostgreSQL connection string
|
||||
- `AUTH0_DOMAIN` - Your Auth0 tenant domain
|
||||
- `AUTH0_AUDIENCE` - Your Auth0 API identifier
|
||||
- `AUTH0_ISSUER` - Your Auth0 issuer URL
|
||||
- `AVIATIONSTACK_API_KEY` - Flight tracking API key (optional)
|
||||
|
||||
## Features
|
||||
|
||||
- ✅ Auth0 JWT authentication
|
||||
- ✅ Role-based access control (Administrator, Coordinator, Driver)
|
||||
- ✅ User approval workflow
|
||||
- ✅ VIP management
|
||||
- ✅ Driver management
|
||||
- ✅ Event scheduling with conflict detection
|
||||
- ✅ Flight tracking integration
|
||||
- ✅ Soft deletes for all entities
|
||||
- ✅ Comprehensive validation
|
||||
- ✅ Type-safe database queries with Prisma
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Framework:** NestJS 10.x
|
||||
- **Database:** PostgreSQL 15+ with Prisma 5.x ORM
|
||||
- **Authentication:** Auth0 + Passport JWT
|
||||
- **Validation:** class-validator + class-transformer
|
||||
- **HTTP Client:** @nestjs/axios (for flight tracking)
|
||||
@@ -1,23 +0,0 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
roots: ['<rootDir>/src'],
|
||||
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest',
|
||||
},
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.ts',
|
||||
'!src/**/*.d.ts',
|
||||
'!src/**/*.test.ts',
|
||||
'!src/**/*.spec.ts',
|
||||
'!src/types/**',
|
||||
],
|
||||
coverageDirectory: 'coverage',
|
||||
coverageReporters: ['text', 'lcov', 'html'],
|
||||
setupFilesAfterEnv: ['<rootDir>/src/tests/setup.ts'],
|
||||
testTimeout: 30000,
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
},
|
||||
};
|
||||
8
backend/nest-cli.json
Normal file
8
backend/nest-cli.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
||||
9432
backend/package-lock.json
generated
9432
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,55 +1,93 @@
|
||||
{
|
||||
"name": "vip-coordinator-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Backend API for VIP Coordinator Dashboard",
|
||||
"main": "dist/index.js",
|
||||
"description": "VIP Coordinator Backend API - NestJS + Prisma + Auth0",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "node dist/index.js",
|
||||
"dev": "npx tsx src/index.ts",
|
||||
"build": "tsc",
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
"db:migrate": "tsx src/scripts/db-cli.ts migrate",
|
||||
"db:migrate:create": "tsx src/scripts/db-cli.ts migrate:create",
|
||||
"db:seed": "tsx src/scripts/db-cli.ts seed",
|
||||
"db:seed:reset": "tsx src/scripts/db-cli.ts seed:reset",
|
||||
"db:setup": "tsx src/scripts/db-cli.ts setup"
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate": "prisma migrate dev",
|
||||
"prisma:studio": "prisma studio",
|
||||
"prisma:seed": "ts-node prisma/seed.ts"
|
||||
},
|
||||
"keywords": [
|
||||
"vip",
|
||||
"coordinator",
|
||||
"dashboard",
|
||||
"api"
|
||||
],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"google-auth-library": "^10.1.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"pg": "^8.11.3",
|
||||
"redis": "^4.6.8",
|
||||
"uuid": "^9.0.0",
|
||||
"zod": "^3.22.4"
|
||||
"@casl/ability": "^6.8.0",
|
||||
"@casl/prisma": "^1.6.1",
|
||||
"@nestjs/axios": "^4.0.1",
|
||||
"@nestjs/common": "^10.3.0",
|
||||
"@nestjs/config": "^3.1.1",
|
||||
"@nestjs/core": "^10.3.0",
|
||||
"@nestjs/jwt": "^10.2.0",
|
||||
"@nestjs/mapped-types": "^2.1.0",
|
||||
"@nestjs/passport": "^10.0.3",
|
||||
"@nestjs/platform-express": "^10.3.0",
|
||||
"@prisma/client": "^5.8.1",
|
||||
"axios": "^1.6.5",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"jwks-rsa": "^3.1.0",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"reflect-metadata": "^0.1.14",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/jsonwebtoken": "^9.0.2",
|
||||
"@types/node": "^20.5.0",
|
||||
"@types/pg": "^8.10.2",
|
||||
"@types/supertest": "^2.0.16",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"@nestjs/cli": "^10.2.1",
|
||||
"@nestjs/schematics": "^10.0.3",
|
||||
"@nestjs/testing": "^10.3.0",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^20.10.6",
|
||||
"@types/passport-jwt": "^4.0.0",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
||||
"@typescript-eslint/parser": "^6.17.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.2",
|
||||
"jest": "^29.7.0",
|
||||
"supertest": "^6.3.4",
|
||||
"ts-jest": "^29.1.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "^5.6.0"
|
||||
"prettier": "^3.1.1",
|
||||
"prisma": "^5.8.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "ts-node prisma/seed.ts"
|
||||
}
|
||||
}
|
||||
|
||||
137
backend/prisma/migrations/20260125085806_init/migration.sql
Normal file
137
backend/prisma/migrations/20260125085806_init/migration.sql
Normal file
@@ -0,0 +1,137 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Role" AS ENUM ('ADMINISTRATOR', 'COORDINATOR', 'DRIVER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Department" AS ENUM ('OFFICE_OF_DEVELOPMENT', 'ADMIN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ArrivalMode" AS ENUM ('FLIGHT', 'SELF_DRIVING');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "EventType" AS ENUM ('TRANSPORT', 'MEETING', 'EVENT', 'MEAL', 'ACCOMMODATION');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "EventStatus" AS ENUM ('SCHEDULED', 'IN_PROGRESS', 'COMPLETED', 'CANCELLED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "users" (
|
||||
"id" TEXT NOT NULL,
|
||||
"auth0Id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"name" TEXT,
|
||||
"picture" TEXT,
|
||||
"role" "Role" NOT NULL DEFAULT 'COORDINATOR',
|
||||
"isApproved" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "vips" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"organization" TEXT,
|
||||
"department" "Department" NOT NULL,
|
||||
"arrivalMode" "ArrivalMode" NOT NULL,
|
||||
"expectedArrival" TIMESTAMP(3),
|
||||
"airportPickup" BOOLEAN NOT NULL DEFAULT false,
|
||||
"venueTransport" BOOLEAN NOT NULL DEFAULT false,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "vips_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "flights" (
|
||||
"id" TEXT NOT NULL,
|
||||
"vipId" TEXT NOT NULL,
|
||||
"flightNumber" TEXT NOT NULL,
|
||||
"flightDate" TIMESTAMP(3) NOT NULL,
|
||||
"segment" INTEGER NOT NULL DEFAULT 1,
|
||||
"departureAirport" TEXT NOT NULL,
|
||||
"arrivalAirport" TEXT NOT NULL,
|
||||
"scheduledDeparture" TIMESTAMP(3),
|
||||
"scheduledArrival" TIMESTAMP(3),
|
||||
"actualDeparture" TIMESTAMP(3),
|
||||
"actualArrival" TIMESTAMP(3),
|
||||
"status" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "flights_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "drivers" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"phone" TEXT NOT NULL,
|
||||
"department" "Department",
|
||||
"userId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "drivers_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "schedule_events" (
|
||||
"id" TEXT NOT NULL,
|
||||
"vipId" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"location" TEXT,
|
||||
"startTime" TIMESTAMP(3) NOT NULL,
|
||||
"endTime" TIMESTAMP(3) NOT NULL,
|
||||
"description" TEXT,
|
||||
"type" "EventType" NOT NULL DEFAULT 'TRANSPORT',
|
||||
"status" "EventStatus" NOT NULL DEFAULT 'SCHEDULED',
|
||||
"driverId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "schedule_events_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_auth0Id_key" ON "users"("auth0Id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "flights_vipId_idx" ON "flights"("vipId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "flights_flightNumber_flightDate_idx" ON "flights"("flightNumber", "flightDate");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "drivers_userId_key" ON "drivers"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "schedule_events_vipId_idx" ON "schedule_events"("vipId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "schedule_events_driverId_idx" ON "schedule_events"("driverId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "schedule_events_startTime_endTime_idx" ON "schedule_events"("startTime", "endTime");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "flights" ADD CONSTRAINT "flights_vipId_fkey" FOREIGN KEY ("vipId") REFERENCES "vips"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "drivers" ADD CONSTRAINT "drivers_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "schedule_events" ADD CONSTRAINT "schedule_events_vipId_fkey" FOREIGN KEY ("vipId") REFERENCES "vips"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "schedule_events" ADD CONSTRAINT "schedule_events_driverId_fkey" FOREIGN KEY ("driverId") REFERENCES "drivers"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,50 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "VehicleType" AS ENUM ('VAN', 'SUV', 'SEDAN', 'BUS', 'GOLF_CART', 'TRUCK');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "VehicleStatus" AS ENUM ('AVAILABLE', 'IN_USE', 'MAINTENANCE', 'RESERVED');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "drivers" ADD COLUMN "isAvailable" BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN "shiftEndTime" TIMESTAMP(3),
|
||||
ADD COLUMN "shiftStartTime" TIMESTAMP(3);
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "schedule_events" ADD COLUMN "actualEndTime" TIMESTAMP(3),
|
||||
ADD COLUMN "actualStartTime" TIMESTAMP(3),
|
||||
ADD COLUMN "dropoffLocation" TEXT,
|
||||
ADD COLUMN "notes" TEXT,
|
||||
ADD COLUMN "pickupLocation" TEXT,
|
||||
ADD COLUMN "vehicleId" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "vehicles" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"type" "VehicleType" NOT NULL DEFAULT 'VAN',
|
||||
"licensePlate" TEXT,
|
||||
"seatCapacity" INTEGER NOT NULL,
|
||||
"status" "VehicleStatus" NOT NULL DEFAULT 'AVAILABLE',
|
||||
"currentDriverId" TEXT,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "vehicles_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "vehicles_currentDriverId_key" ON "vehicles"("currentDriverId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "schedule_events_vehicleId_idx" ON "schedule_events"("vehicleId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "schedule_events_status_idx" ON "schedule_events"("status");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "vehicles" ADD CONSTRAINT "vehicles_currentDriverId_fkey" FOREIGN KEY ("currentDriverId") REFERENCES "drivers"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "schedule_events" ADD CONSTRAINT "schedule_events_vehicleId_fkey" FOREIGN KEY ("vehicleId") REFERENCES "vehicles"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,74 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "schedule_events" ADD COLUMN "eventId" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "event_templates" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"defaultDuration" INTEGER NOT NULL DEFAULT 60,
|
||||
"location" TEXT,
|
||||
"type" "EventType" NOT NULL DEFAULT 'EVENT',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "event_templates_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "events" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"startTime" TIMESTAMP(3) NOT NULL,
|
||||
"endTime" TIMESTAMP(3) NOT NULL,
|
||||
"location" TEXT NOT NULL,
|
||||
"type" "EventType" NOT NULL DEFAULT 'EVENT',
|
||||
"templateId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "events_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "event_attendance" (
|
||||
"id" TEXT NOT NULL,
|
||||
"eventId" TEXT NOT NULL,
|
||||
"vipId" TEXT NOT NULL,
|
||||
"addedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "event_attendance_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "events_startTime_endTime_idx" ON "events"("startTime", "endTime");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "events_templateId_idx" ON "events"("templateId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_attendance_eventId_idx" ON "event_attendance"("eventId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_attendance_vipId_idx" ON "event_attendance"("vipId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "event_attendance_eventId_vipId_key" ON "event_attendance"("eventId", "vipId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "schedule_events_eventId_idx" ON "schedule_events"("eventId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "schedule_events" ADD CONSTRAINT "schedule_events_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "events"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "events" ADD CONSTRAINT "events_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "event_templates"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "event_attendance" ADD CONSTRAINT "event_attendance_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "events"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "event_attendance" ADD CONSTRAINT "event_attendance_vipId_fkey" FOREIGN KEY ("vipId") REFERENCES "vips"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `vipId` on the `schedule_events` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "schedule_events" DROP CONSTRAINT "schedule_events_vipId_fkey";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "schedule_events_vipId_idx";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "schedule_events" DROP COLUMN "vipId",
|
||||
ADD COLUMN "vipIds" TEXT[];
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Drop the event_attendance join table first (has foreign keys)
|
||||
DROP TABLE IF EXISTS "event_attendance" CASCADE;
|
||||
|
||||
-- Drop the events table (references event_templates)
|
||||
DROP TABLE IF EXISTS "events" CASCADE;
|
||||
|
||||
-- Drop the event_templates table
|
||||
DROP TABLE IF EXISTS "event_templates" CASCADE;
|
||||
|
||||
-- Drop the eventId column from schedule_events (referenced dropped events table)
|
||||
ALTER TABLE "schedule_events" DROP COLUMN IF EXISTS "eventId";
|
||||
3
backend/prisma/migrations/migration_lock.toml
Normal file
3
backend/prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
225
backend/prisma/schema.prisma
Normal file
225
backend/prisma/schema.prisma
Normal file
@@ -0,0 +1,225 @@
|
||||
// VIP Coordinator - Prisma Schema
|
||||
// This is your database schema (source of truth)
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// User Management
|
||||
// ============================================
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
auth0Id String @unique // Auth0 sub claim
|
||||
email String @unique
|
||||
name String?
|
||||
picture String?
|
||||
role Role @default(COORDINATOR)
|
||||
isApproved Boolean @default(false)
|
||||
driver Driver? // Optional linked driver account
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime? // Soft delete
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
enum Role {
|
||||
ADMINISTRATOR
|
||||
COORDINATOR
|
||||
DRIVER
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// VIP Management
|
||||
// ============================================
|
||||
|
||||
model VIP {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
organization String?
|
||||
department Department
|
||||
arrivalMode ArrivalMode
|
||||
expectedArrival DateTime? // For self-driving arrivals
|
||||
airportPickup Boolean @default(false)
|
||||
venueTransport Boolean @default(false)
|
||||
notes String? @db.Text
|
||||
flights Flight[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime? // Soft delete
|
||||
|
||||
@@map("vips")
|
||||
}
|
||||
|
||||
enum Department {
|
||||
OFFICE_OF_DEVELOPMENT
|
||||
ADMIN
|
||||
}
|
||||
|
||||
enum ArrivalMode {
|
||||
FLIGHT
|
||||
SELF_DRIVING
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Flight Tracking
|
||||
// ============================================
|
||||
|
||||
model Flight {
|
||||
id String @id @default(uuid())
|
||||
vipId String
|
||||
vip VIP @relation(fields: [vipId], references: [id], onDelete: Cascade)
|
||||
flightNumber String
|
||||
flightDate DateTime
|
||||
segment Int @default(1) // For multi-segment itineraries
|
||||
departureAirport String // IATA code (e.g., "JFK")
|
||||
arrivalAirport String // IATA code (e.g., "LAX")
|
||||
scheduledDeparture DateTime?
|
||||
scheduledArrival DateTime?
|
||||
actualDeparture DateTime?
|
||||
actualArrival DateTime?
|
||||
status String? // scheduled, delayed, landed, etc.
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("flights")
|
||||
@@index([vipId])
|
||||
@@index([flightNumber, flightDate])
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Driver Management
|
||||
// ============================================
|
||||
|
||||
model Driver {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
phone String
|
||||
department Department?
|
||||
userId String? @unique
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
|
||||
// Shift/Availability
|
||||
shiftStartTime DateTime? // When driver's shift starts
|
||||
shiftEndTime DateTime? // When driver's shift ends
|
||||
isAvailable Boolean @default(true)
|
||||
|
||||
events ScheduleEvent[]
|
||||
assignedVehicle Vehicle? @relation("AssignedDriver")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime? // Soft delete
|
||||
|
||||
@@map("drivers")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Vehicle Management
|
||||
// ============================================
|
||||
|
||||
model Vehicle {
|
||||
id String @id @default(uuid())
|
||||
name String // "Blue Van", "Suburban #3"
|
||||
type VehicleType @default(VAN)
|
||||
licensePlate String?
|
||||
seatCapacity Int // Total seats (e.g., 8)
|
||||
status VehicleStatus @default(AVAILABLE)
|
||||
|
||||
// Current assignment
|
||||
currentDriverId String? @unique
|
||||
currentDriver Driver? @relation("AssignedDriver", fields: [currentDriverId], references: [id])
|
||||
|
||||
// Relationships
|
||||
events ScheduleEvent[]
|
||||
|
||||
notes String? @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime? // Soft delete
|
||||
|
||||
@@map("vehicles")
|
||||
}
|
||||
|
||||
enum VehicleType {
|
||||
VAN // 7-15 seats
|
||||
SUV // 5-8 seats
|
||||
SEDAN // 4-5 seats
|
||||
BUS // 15+ seats
|
||||
GOLF_CART // 2-6 seats
|
||||
TRUCK // For equipment/supplies
|
||||
}
|
||||
|
||||
enum VehicleStatus {
|
||||
AVAILABLE // Ready to use
|
||||
IN_USE // Currently on a trip
|
||||
MAINTENANCE // Out of service
|
||||
RESERVED // Scheduled for upcoming trip
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Schedule & Event Management
|
||||
// ============================================
|
||||
|
||||
model ScheduleEvent {
|
||||
id String @id @default(uuid())
|
||||
vipIds String[] // Array of VIP IDs for multi-passenger trips
|
||||
title String
|
||||
|
||||
// Location details
|
||||
pickupLocation String?
|
||||
dropoffLocation String?
|
||||
location String? // For non-transport events
|
||||
|
||||
// Timing
|
||||
startTime DateTime
|
||||
endTime DateTime
|
||||
actualStartTime DateTime?
|
||||
actualEndTime DateTime?
|
||||
|
||||
description String? @db.Text
|
||||
type EventType @default(TRANSPORT)
|
||||
status EventStatus @default(SCHEDULED)
|
||||
|
||||
// Assignments
|
||||
driverId String?
|
||||
driver Driver? @relation(fields: [driverId], references: [id], onDelete: SetNull)
|
||||
vehicleId String?
|
||||
vehicle Vehicle? @relation(fields: [vehicleId], references: [id], onDelete: SetNull)
|
||||
|
||||
// Metadata
|
||||
notes String? @db.Text
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime? // Soft delete
|
||||
|
||||
@@map("schedule_events")
|
||||
@@index([driverId])
|
||||
@@index([vehicleId])
|
||||
@@index([startTime, endTime])
|
||||
@@index([status])
|
||||
}
|
||||
|
||||
enum EventType {
|
||||
TRANSPORT
|
||||
MEETING
|
||||
EVENT
|
||||
MEAL
|
||||
ACCOMMODATION
|
||||
}
|
||||
|
||||
enum EventStatus {
|
||||
SCHEDULED
|
||||
IN_PROGRESS
|
||||
COMPLETED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
354
backend/prisma/seed.ts
Normal file
354
backend/prisma/seed.ts
Normal file
@@ -0,0 +1,354 @@
|
||||
import { PrismaClient, Role, Department, ArrivalMode, EventType, EventStatus, VehicleType, VehicleStatus } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('🌱 Seeding database...');
|
||||
|
||||
// Clean up existing data (careful in production!)
|
||||
await prisma.scheduleEvent.deleteMany({});
|
||||
await prisma.flight.deleteMany({});
|
||||
await prisma.vehicle.deleteMany({});
|
||||
await prisma.driver.deleteMany({});
|
||||
await prisma.vIP.deleteMany({});
|
||||
await prisma.user.deleteMany({});
|
||||
|
||||
console.log('✅ Cleared existing data');
|
||||
|
||||
// Create sample users
|
||||
const admin = await prisma.user.create({
|
||||
data: {
|
||||
auth0Id: 'auth0|admin-sample-id',
|
||||
email: 'admin@example.com',
|
||||
name: 'Admin User',
|
||||
role: Role.ADMINISTRATOR,
|
||||
isApproved: true,
|
||||
},
|
||||
});
|
||||
|
||||
const coordinator = await prisma.user.create({
|
||||
data: {
|
||||
auth0Id: 'auth0|coordinator-sample-id',
|
||||
email: 'coordinator@example.com',
|
||||
name: 'Coordinator User',
|
||||
role: Role.COORDINATOR,
|
||||
isApproved: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Note: test@test.com user is auto-created and auto-approved on first login (see auth.service.ts)
|
||||
|
||||
console.log('✅ Created sample users');
|
||||
|
||||
// Create sample vehicles with capacity
|
||||
const blackSUV = await prisma.vehicle.create({
|
||||
data: {
|
||||
name: 'Black Suburban',
|
||||
type: VehicleType.SUV,
|
||||
licensePlate: 'ABC-1234',
|
||||
seatCapacity: 6,
|
||||
status: VehicleStatus.AVAILABLE,
|
||||
notes: 'Leather interior, tinted windows',
|
||||
},
|
||||
});
|
||||
|
||||
const whiteVan = await prisma.vehicle.create({
|
||||
data: {
|
||||
name: 'White Sprinter Van',
|
||||
type: VehicleType.VAN,
|
||||
licensePlate: 'XYZ-5678',
|
||||
seatCapacity: 12,
|
||||
status: VehicleStatus.AVAILABLE,
|
||||
notes: 'High roof, wheelchair accessible',
|
||||
},
|
||||
});
|
||||
|
||||
const blueSedan = await prisma.vehicle.create({
|
||||
data: {
|
||||
name: 'Blue Camry',
|
||||
type: VehicleType.SEDAN,
|
||||
licensePlate: 'DEF-9012',
|
||||
seatCapacity: 4,
|
||||
status: VehicleStatus.AVAILABLE,
|
||||
notes: 'Fuel efficient, good for short trips',
|
||||
},
|
||||
});
|
||||
|
||||
const grayBus = await prisma.vehicle.create({
|
||||
data: {
|
||||
name: 'Gray Charter Bus',
|
||||
type: VehicleType.BUS,
|
||||
licensePlate: 'BUS-0001',
|
||||
seatCapacity: 40,
|
||||
status: VehicleStatus.AVAILABLE,
|
||||
notes: 'Full size charter bus, A/C, luggage compartment',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✅ Created sample vehicles with capacities');
|
||||
|
||||
// Create sample drivers
|
||||
const driver1 = await prisma.driver.create({
|
||||
data: {
|
||||
name: 'John Smith',
|
||||
phone: '+1 (555) 123-4567',
|
||||
department: Department.OFFICE_OF_DEVELOPMENT,
|
||||
},
|
||||
});
|
||||
|
||||
const driver2 = await prisma.driver.create({
|
||||
data: {
|
||||
name: 'Jane Doe',
|
||||
phone: '+1 (555) 987-6543',
|
||||
department: Department.ADMIN,
|
||||
},
|
||||
});
|
||||
|
||||
const driver3 = await prisma.driver.create({
|
||||
data: {
|
||||
name: 'Amanda Washington',
|
||||
phone: '+1 (555) 234-5678',
|
||||
department: Department.OFFICE_OF_DEVELOPMENT,
|
||||
},
|
||||
});
|
||||
|
||||
const driver4 = await prisma.driver.create({
|
||||
data: {
|
||||
name: 'Michael Thompson',
|
||||
phone: '+1 (555) 876-5432',
|
||||
department: Department.ADMIN,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✅ Created sample drivers');
|
||||
|
||||
// Create sample VIPs
|
||||
const vip1 = await prisma.vIP.create({
|
||||
data: {
|
||||
name: 'Dr. Robert Johnson',
|
||||
organization: 'Tech Corporation',
|
||||
department: Department.OFFICE_OF_DEVELOPMENT,
|
||||
arrivalMode: ArrivalMode.FLIGHT,
|
||||
airportPickup: true,
|
||||
venueTransport: true,
|
||||
notes: 'Prefers window seat, dietary restriction: vegetarian',
|
||||
flights: {
|
||||
create: [
|
||||
{
|
||||
flightNumber: 'AA123',
|
||||
flightDate: new Date('2026-02-15'),
|
||||
segment: 1,
|
||||
departureAirport: 'JFK',
|
||||
arrivalAirport: 'LAX',
|
||||
scheduledDeparture: new Date('2026-02-15T08:00:00'),
|
||||
scheduledArrival: new Date('2026-02-15T11:30:00'),
|
||||
status: 'scheduled',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const vip2 = await prisma.vIP.create({
|
||||
data: {
|
||||
name: 'Ms. Sarah Williams',
|
||||
organization: 'Global Foundation',
|
||||
department: Department.ADMIN,
|
||||
arrivalMode: ArrivalMode.SELF_DRIVING,
|
||||
expectedArrival: new Date('2026-02-16T14:00:00'),
|
||||
airportPickup: false,
|
||||
venueTransport: true,
|
||||
notes: 'Bringing assistant',
|
||||
},
|
||||
});
|
||||
|
||||
const vip3 = await prisma.vIP.create({
|
||||
data: {
|
||||
name: 'Emily Richardson (Harvard University)',
|
||||
organization: 'Harvard University',
|
||||
department: Department.OFFICE_OF_DEVELOPMENT,
|
||||
arrivalMode: ArrivalMode.FLIGHT,
|
||||
airportPickup: true,
|
||||
venueTransport: true,
|
||||
notes: 'Board member, requires accessible vehicle',
|
||||
},
|
||||
});
|
||||
|
||||
const vip4 = await prisma.vIP.create({
|
||||
data: {
|
||||
name: 'David Chen (Stanford)',
|
||||
organization: 'Stanford University',
|
||||
department: Department.OFFICE_OF_DEVELOPMENT,
|
||||
arrivalMode: ArrivalMode.FLIGHT,
|
||||
airportPickup: true,
|
||||
venueTransport: true,
|
||||
notes: 'Keynote speaker',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✅ Created sample VIPs');
|
||||
|
||||
// Create sample schedule events (unified activities) - NOW WITH MULTIPLE VIPS!
|
||||
|
||||
// Multi-VIP rideshare to Campfire Night (3 VIPs in one SUV)
|
||||
await prisma.scheduleEvent.create({
|
||||
data: {
|
||||
vipIds: [vip3.id, vip4.id, vip1.id], // 3 VIPs sharing a ride
|
||||
title: 'Transport to Campfire Night',
|
||||
pickupLocation: 'Grand Hotel Lobby',
|
||||
dropoffLocation: 'Camp Amphitheater',
|
||||
startTime: new Date('2026-02-15T19:45:00'),
|
||||
endTime: new Date('2026-02-15T20:00:00'),
|
||||
description: 'Rideshare: Emily, David, and Dr. Johnson to campfire',
|
||||
type: EventType.TRANSPORT,
|
||||
status: EventStatus.SCHEDULED,
|
||||
driverId: driver3.id,
|
||||
vehicleId: blackSUV.id, // 3 VIPs in 6-seat SUV (3/6 seats used)
|
||||
},
|
||||
});
|
||||
|
||||
// Single VIP transport
|
||||
await prisma.scheduleEvent.create({
|
||||
data: {
|
||||
vipIds: [vip1.id],
|
||||
title: 'Airport Pickup - Dr. Johnson',
|
||||
pickupLocation: 'LAX Terminal 4',
|
||||
dropoffLocation: 'Grand Hotel',
|
||||
startTime: new Date('2026-02-15T11:30:00'),
|
||||
endTime: new Date('2026-02-15T12:30:00'),
|
||||
description: 'Pick up Dr. Johnson from LAX',
|
||||
type: EventType.TRANSPORT,
|
||||
status: EventStatus.SCHEDULED,
|
||||
driverId: driver1.id,
|
||||
vehicleId: blueSedan.id, // 1 VIP in 4-seat sedan (1/4 seats used)
|
||||
},
|
||||
});
|
||||
|
||||
// Two VIPs sharing lunch transport
|
||||
await prisma.scheduleEvent.create({
|
||||
data: {
|
||||
vipIds: [vip1.id, vip2.id],
|
||||
title: 'Transport to Lunch - Day 1',
|
||||
pickupLocation: 'Grand Hotel Lobby',
|
||||
dropoffLocation: 'Main Dining Hall',
|
||||
startTime: new Date('2026-02-15T11:45:00'),
|
||||
endTime: new Date('2026-02-15T12:00:00'),
|
||||
description: 'Rideshare: Dr. Johnson and Ms. Williams to lunch',
|
||||
type: EventType.TRANSPORT,
|
||||
status: EventStatus.SCHEDULED,
|
||||
driverId: driver2.id,
|
||||
vehicleId: blueSedan.id, // 2 VIPs in 4-seat sedan (2/4 seats used)
|
||||
},
|
||||
});
|
||||
|
||||
// Large group transport in van
|
||||
await prisma.scheduleEvent.create({
|
||||
data: {
|
||||
vipIds: [vip1.id, vip2.id, vip3.id, vip4.id],
|
||||
title: 'Morning Shuttle to Conference',
|
||||
pickupLocation: 'Grand Hotel Lobby',
|
||||
dropoffLocation: 'Conference Center',
|
||||
startTime: new Date('2026-02-15T08:00:00'),
|
||||
endTime: new Date('2026-02-15T08:30:00'),
|
||||
description: 'All VIPs to morning conference session',
|
||||
type: EventType.TRANSPORT,
|
||||
status: EventStatus.SCHEDULED,
|
||||
driverId: driver4.id,
|
||||
vehicleId: whiteVan.id, // 4 VIPs in 12-seat van (4/12 seats used)
|
||||
},
|
||||
});
|
||||
|
||||
// Non-transport activities (unified system)
|
||||
|
||||
// Opening Ceremony - all VIPs attending
|
||||
await prisma.scheduleEvent.create({
|
||||
data: {
|
||||
vipIds: [vip1.id, vip2.id, vip3.id, vip4.id],
|
||||
title: 'Opening Ceremony',
|
||||
location: 'Main Stage',
|
||||
startTime: new Date('2026-02-15T10:00:00'),
|
||||
endTime: new Date('2026-02-15T11:30:00'),
|
||||
description: 'Welcome and opening remarks',
|
||||
type: EventType.EVENT,
|
||||
status: EventStatus.SCHEDULED,
|
||||
},
|
||||
});
|
||||
|
||||
// Lunch - Day 1 (all VIPs)
|
||||
await prisma.scheduleEvent.create({
|
||||
data: {
|
||||
vipIds: [vip1.id, vip2.id, vip3.id, vip4.id],
|
||||
title: 'Lunch - Day 1',
|
||||
location: 'Main Dining Hall',
|
||||
startTime: new Date('2026-02-15T12:00:00'),
|
||||
endTime: new Date('2026-02-15T13:30:00'),
|
||||
description: 'Day 1 lunch for all attendees',
|
||||
type: EventType.MEAL,
|
||||
status: EventStatus.SCHEDULED,
|
||||
},
|
||||
});
|
||||
|
||||
// Campfire Night (all VIPs)
|
||||
await prisma.scheduleEvent.create({
|
||||
data: {
|
||||
vipIds: [vip1.id, vip2.id, vip3.id, vip4.id],
|
||||
title: 'Campfire Night',
|
||||
location: 'Camp Amphitheater',
|
||||
startTime: new Date('2026-02-15T20:00:00'),
|
||||
endTime: new Date('2026-02-15T22:00:00'),
|
||||
description: 'Evening campfire and networking',
|
||||
type: EventType.EVENT,
|
||||
status: EventStatus.SCHEDULED,
|
||||
},
|
||||
});
|
||||
|
||||
// Private meeting - just Dr. Johnson and Ms. Williams
|
||||
await prisma.scheduleEvent.create({
|
||||
data: {
|
||||
vipIds: [vip1.id, vip2.id],
|
||||
title: 'Donor Meeting',
|
||||
location: 'Conference Room A',
|
||||
startTime: new Date('2026-02-15T14:00:00'),
|
||||
endTime: new Date('2026-02-15T15:00:00'),
|
||||
description: 'Private meeting with development team',
|
||||
type: EventType.MEETING,
|
||||
status: EventStatus.SCHEDULED,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✅ Created sample schedule events with multi-VIP rideshares and activities');
|
||||
|
||||
console.log('\n🎉 Database seeded successfully!');
|
||||
console.log('\nSample Users:');
|
||||
console.log('- Admin: admin@example.com');
|
||||
console.log('- Coordinator: coordinator@example.com');
|
||||
console.log('\nSample VIPs:');
|
||||
console.log('- Dr. Robert Johnson (Flight arrival)');
|
||||
console.log('- Ms. Sarah Williams (Self-driving)');
|
||||
console.log('- Emily Richardson (Harvard University)');
|
||||
console.log('- David Chen (Stanford)');
|
||||
console.log('\nSample Drivers:');
|
||||
console.log('- John Smith');
|
||||
console.log('- Jane Doe');
|
||||
console.log('- Amanda Washington');
|
||||
console.log('- Michael Thompson');
|
||||
console.log('\nSample Vehicles:');
|
||||
console.log('- Black Suburban (SUV, 6 seats)');
|
||||
console.log('- White Sprinter Van (Van, 12 seats)');
|
||||
console.log('- Blue Camry (Sedan, 4 seats)');
|
||||
console.log('- Gray Charter Bus (Bus, 40 seats)');
|
||||
console.log('\nSchedule Tasks (Multi-VIP Examples):');
|
||||
console.log('- 3 VIPs sharing SUV to Campfire (3/6 seats)');
|
||||
console.log('- 2 VIPs sharing sedan to Lunch (2/4 seats)');
|
||||
console.log('- 4 VIPs in van to Conference (4/12 seats)');
|
||||
console.log('- 1 VIP solo in sedan from Airport (1/4 seats)');
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error('❌ Error seeding database:', e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
@@ -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: 'http://localhost:3000/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
14
backend/src/app.controller.ts
Normal file
14
backend/src/app.controller.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
import { Public } from './auth/decorators/public.decorator';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get('health')
|
||||
@Public() // Health check should be public
|
||||
getHealth() {
|
||||
return this.appService.getHealth();
|
||||
}
|
||||
}
|
||||
46
backend/src/app.module.ts
Normal file
46
backend/src/app.module.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { PrismaModule } from './prisma/prisma.module';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { UsersModule } from './users/users.module';
|
||||
import { VipsModule } from './vips/vips.module';
|
||||
import { DriversModule } from './drivers/drivers.module';
|
||||
import { VehiclesModule } from './vehicles/vehicles.module';
|
||||
import { EventsModule } from './events/events.module';
|
||||
import { FlightsModule } from './flights/flights.module';
|
||||
import { JwtAuthGuard } from './auth/guards/jwt-auth.guard';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
// Load environment variables
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: '.env',
|
||||
}),
|
||||
|
||||
// Core modules
|
||||
PrismaModule,
|
||||
AuthModule,
|
||||
|
||||
// Feature modules
|
||||
UsersModule,
|
||||
VipsModule,
|
||||
DriversModule,
|
||||
VehiclesModule,
|
||||
EventsModule,
|
||||
FlightsModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [
|
||||
AppService,
|
||||
// Apply JWT auth guard globally (unless @Public() is used)
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
useClass: JwtAuthGuard,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
14
backend/src/app.service.ts
Normal file
14
backend/src/app.service.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHealth() {
|
||||
return {
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
service: 'VIP Coordinator API',
|
||||
version: '1.0.0',
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
};
|
||||
}
|
||||
}
|
||||
88
backend/src/auth/abilities/ability.factory.ts
Normal file
88
backend/src/auth/abilities/ability.factory.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { AbilityBuilder, PureAbility, AbilityClass, ExtractSubjectType, InferSubjects } from '@casl/ability';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Role, User, VIP, Driver, ScheduleEvent, Flight, Vehicle } from '@prisma/client';
|
||||
|
||||
/**
|
||||
* Define all possible actions in the system
|
||||
*/
|
||||
export enum Action {
|
||||
Manage = 'manage', // Special: allows everything
|
||||
Create = 'create',
|
||||
Read = 'read',
|
||||
Update = 'update',
|
||||
Delete = 'delete',
|
||||
Approve = 'approve', // Special: for user approval
|
||||
UpdateStatus = 'update-status', // Special: for drivers to update event status
|
||||
}
|
||||
|
||||
/**
|
||||
* Define all subjects (resources) in the system
|
||||
*/
|
||||
export type Subjects =
|
||||
| 'User'
|
||||
| 'VIP'
|
||||
| 'Driver'
|
||||
| 'ScheduleEvent'
|
||||
| 'Flight'
|
||||
| 'Vehicle'
|
||||
| 'all';
|
||||
|
||||
/**
|
||||
* Define the AppAbility type
|
||||
*/
|
||||
export type AppAbility = PureAbility<[Action, Subjects]>;
|
||||
|
||||
@Injectable()
|
||||
export class AbilityFactory {
|
||||
/**
|
||||
* Define abilities for a user based on their role
|
||||
*/
|
||||
defineAbilitiesFor(user: User): AppAbility {
|
||||
const { can, cannot, build } = new AbilityBuilder<AppAbility>(
|
||||
PureAbility as AbilityClass<AppAbility>,
|
||||
);
|
||||
|
||||
// Define permissions based on role
|
||||
if (user.role === Role.ADMINISTRATOR) {
|
||||
// Administrators can do everything
|
||||
can(Action.Manage, 'all');
|
||||
} else if (user.role === Role.COORDINATOR) {
|
||||
// Coordinators have full access except user management
|
||||
can(Action.Read, ['VIP', 'Driver', 'ScheduleEvent', 'Flight', 'Vehicle']);
|
||||
can(Action.Create, ['VIP', 'Driver', 'ScheduleEvent', 'Flight', 'Vehicle']);
|
||||
can(Action.Update, ['VIP', 'Driver', 'ScheduleEvent', 'Flight', 'Vehicle']);
|
||||
can(Action.Delete, ['VIP', 'Driver', 'ScheduleEvent', 'Flight', 'Vehicle']);
|
||||
|
||||
// Cannot manage users
|
||||
cannot(Action.Create, 'User');
|
||||
cannot(Action.Update, 'User');
|
||||
cannot(Action.Delete, 'User');
|
||||
cannot(Action.Approve, 'User');
|
||||
} else if (user.role === Role.DRIVER) {
|
||||
// Drivers can only read most resources
|
||||
can(Action.Read, ['VIP', 'Driver', 'ScheduleEvent', 'Vehicle']);
|
||||
|
||||
// Drivers can update status of events (driver relationship checked in guard)
|
||||
can(Action.UpdateStatus, 'ScheduleEvent');
|
||||
|
||||
// Cannot access flights
|
||||
cannot(Action.Read, 'Flight');
|
||||
|
||||
// Cannot access users
|
||||
cannot(Action.Read, 'User');
|
||||
}
|
||||
|
||||
return build({
|
||||
// Detect subject type from string
|
||||
detectSubjectType: (item) => item as ExtractSubjectType<Subjects>,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can perform action on subject
|
||||
*/
|
||||
canUserPerform(user: User, action: Action, subject: Subjects): boolean {
|
||||
const ability = this.defineAbilitiesFor(user);
|
||||
return ability.can(action, subject);
|
||||
}
|
||||
}
|
||||
17
backend/src/auth/auth.controller.ts
Normal file
17
backend/src/auth/auth.controller.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Controller, Get, UseGuards } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { JwtAuthGuard } from './guards/jwt-auth.guard';
|
||||
import { CurrentUser } from './decorators/current-user.decorator';
|
||||
import { User } from '@prisma/client';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
@Get('profile')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async getProfile(@CurrentUser() user: User) {
|
||||
// Return user profile (password already excluded by Prisma)
|
||||
return user;
|
||||
}
|
||||
}
|
||||
30
backend/src/auth/auth.module.ts
Normal file
30
backend/src/auth/auth.module.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { JwtStrategy } from './strategies/jwt.strategy';
|
||||
import { AbilityFactory } from './abilities/ability.factory';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
HttpModule,
|
||||
PassportModule.register({ defaultStrategy: 'jwt' }),
|
||||
JwtModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => ({
|
||||
secret: configService.get('JWT_SECRET') || 'development-secret-key',
|
||||
signOptions: {
|
||||
expiresIn: '7d',
|
||||
},
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, JwtStrategy, AbilityFactory],
|
||||
exports: [AuthService, PassportModule, JwtModule, AbilityFactory],
|
||||
})
|
||||
export class AuthModule {}
|
||||
69
backend/src/auth/auth.service.ts
Normal file
69
backend/src/auth/auth.service.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { Role } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
private readonly logger = new Logger(AuthService.name);
|
||||
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
/**
|
||||
* Validate and get/create user from Auth0 token payload
|
||||
*/
|
||||
async validateUser(payload: any) {
|
||||
const namespace = 'https://vip-coordinator-api';
|
||||
const auth0Id = payload.sub;
|
||||
const email = payload[`${namespace}/email`] || payload.email || `${auth0Id}@auth0.local`;
|
||||
const name = payload[`${namespace}/name`] || payload.name || 'Unknown User';
|
||||
const picture = payload[`${namespace}/picture`] || payload.picture;
|
||||
|
||||
// Check if user exists
|
||||
let user = await this.prisma.user.findUnique({
|
||||
where: { auth0Id },
|
||||
include: { driver: true },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
// Check if this is the first user (auto-approve as admin)
|
||||
const userCount = await this.prisma.user.count();
|
||||
const isFirstUser = userCount === 0;
|
||||
|
||||
// Auto-approve test users for Playwright tests
|
||||
const isTestUser = email === 'test@test.com';
|
||||
|
||||
this.logger.log(
|
||||
`Creating new user: ${email} (isFirstUser: ${isFirstUser}, isTestUser: ${isTestUser})`,
|
||||
);
|
||||
|
||||
// Create new user
|
||||
user = await this.prisma.user.create({
|
||||
data: {
|
||||
auth0Id,
|
||||
email,
|
||||
name,
|
||||
picture,
|
||||
role: isFirstUser || isTestUser ? Role.ADMINISTRATOR : Role.DRIVER,
|
||||
isApproved: isFirstUser || isTestUser, // Auto-approve first user and test users
|
||||
},
|
||||
include: { driver: true },
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`User created: ${user.email} with role ${user.role} (approved: ${user.isApproved})`,
|
||||
);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user profile
|
||||
*/
|
||||
async getCurrentUser(auth0Id: string) {
|
||||
return this.prisma.user.findUnique({
|
||||
where: { auth0Id },
|
||||
include: { driver: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
39
backend/src/auth/decorators/check-ability.decorator.ts
Normal file
39
backend/src/auth/decorators/check-ability.decorator.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
import { Action, Subjects } from '../abilities/ability.factory';
|
||||
import { CHECK_ABILITY, RequiredPermission } from '../guards/abilities.guard';
|
||||
|
||||
/**
|
||||
* Decorator to check CASL abilities on a route
|
||||
*
|
||||
* @example
|
||||
* @CheckAbilities({ action: Action.Create, subject: 'VIP' })
|
||||
* async create(@Body() dto: CreateVIPDto) {
|
||||
* return this.service.create(dto);
|
||||
* }
|
||||
*
|
||||
* @example Multiple permissions (all must be satisfied)
|
||||
* @CheckAbilities(
|
||||
* { action: Action.Read, subject: 'VIP' },
|
||||
* { action: Action.Update, subject: 'VIP' }
|
||||
* )
|
||||
*/
|
||||
export const CheckAbilities = (...permissions: RequiredPermission[]) =>
|
||||
SetMetadata(CHECK_ABILITY, permissions);
|
||||
|
||||
/**
|
||||
* Helper functions for common permission checks
|
||||
*/
|
||||
export const CanCreate = (subject: Subjects) =>
|
||||
CheckAbilities({ action: Action.Create, subject });
|
||||
|
||||
export const CanRead = (subject: Subjects) =>
|
||||
CheckAbilities({ action: Action.Read, subject });
|
||||
|
||||
export const CanUpdate = (subject: Subjects) =>
|
||||
CheckAbilities({ action: Action.Update, subject });
|
||||
|
||||
export const CanDelete = (subject: Subjects) =>
|
||||
CheckAbilities({ action: Action.Delete, subject });
|
||||
|
||||
export const CanManage = (subject: Subjects) =>
|
||||
CheckAbilities({ action: Action.Manage, subject });
|
||||
8
backend/src/auth/decorators/current-user.decorator.ts
Normal file
8
backend/src/auth/decorators/current-user.decorator.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
|
||||
export const CurrentUser = createParamDecorator(
|
||||
(data: unknown, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
return request.user;
|
||||
},
|
||||
);
|
||||
4
backend/src/auth/decorators/public.decorator.ts
Normal file
4
backend/src/auth/decorators/public.decorator.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
|
||||
export const IS_PUBLIC_KEY = 'isPublic';
|
||||
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
|
||||
5
backend/src/auth/decorators/roles.decorator.ts
Normal file
5
backend/src/auth/decorators/roles.decorator.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
import { Role } from '@prisma/client';
|
||||
|
||||
export const ROLES_KEY = 'roles';
|
||||
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
|
||||
64
backend/src/auth/guards/abilities.guard.ts
Normal file
64
backend/src/auth/guards/abilities.guard.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { AbilityFactory, Action, Subjects } from '../abilities/ability.factory';
|
||||
|
||||
/**
|
||||
* Interface for required permissions
|
||||
*/
|
||||
export interface RequiredPermission {
|
||||
action: Action;
|
||||
subject: Subjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata key for permissions
|
||||
*/
|
||||
export const CHECK_ABILITY = 'check_ability';
|
||||
|
||||
/**
|
||||
* Guard that checks CASL abilities
|
||||
*/
|
||||
@Injectable()
|
||||
export class AbilitiesGuard implements CanActivate {
|
||||
constructor(
|
||||
private reflector: Reflector,
|
||||
private abilityFactory: AbilityFactory,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const requiredPermissions =
|
||||
this.reflector.get<RequiredPermission[]>(
|
||||
CHECK_ABILITY,
|
||||
context.getHandler(),
|
||||
) || [];
|
||||
|
||||
// If no permissions required, allow access
|
||||
if (requiredPermissions.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const user = request.user;
|
||||
|
||||
// User should be attached by JwtAuthGuard
|
||||
if (!user) {
|
||||
throw new ForbiddenException('User not authenticated');
|
||||
}
|
||||
|
||||
// Build abilities for user
|
||||
const ability = this.abilityFactory.defineAbilitiesFor(user);
|
||||
|
||||
// Check if user has all required permissions
|
||||
const hasPermission = requiredPermissions.every((permission) =>
|
||||
ability.can(permission.action, permission.subject),
|
||||
);
|
||||
|
||||
if (!hasPermission) {
|
||||
throw new ForbiddenException(
|
||||
`User does not have required permissions`,
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
25
backend/src/auth/guards/jwt-auth.guard.ts
Normal file
25
backend/src/auth/guards/jwt-auth.guard.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Injectable, ExecutionContext } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
constructor(private reflector: Reflector) {
|
||||
super();
|
||||
}
|
||||
|
||||
canActivate(context: ExecutionContext) {
|
||||
// Check if route is marked as public
|
||||
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
|
||||
if (isPublic) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.canActivate(context);
|
||||
}
|
||||
}
|
||||
23
backend/src/auth/guards/roles.guard.ts
Normal file
23
backend/src/auth/guards/roles.guard.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { Role } from '@prisma/client';
|
||||
import { ROLES_KEY } from '../decorators/roles.decorator';
|
||||
|
||||
@Injectable()
|
||||
export class RolesGuard implements CanActivate {
|
||||
constructor(private reflector: Reflector) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
|
||||
if (!requiredRoles) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { user } = context.switchToHttp().getRequest();
|
||||
return requiredRoles.some((role) => user.role === role);
|
||||
}
|
||||
}
|
||||
75
backend/src/auth/strategies/jwt.strategy.ts
Normal file
75
backend/src/auth/strategies/jwt.strategy.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Injectable, UnauthorizedException, Logger } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Strategy, ExtractJwt } from 'passport-jwt';
|
||||
import { passportJwtSecret } from 'jwks-rsa';
|
||||
import { AuthService } from '../auth.service';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
private readonly logger = new Logger(JwtStrategy.name);
|
||||
|
||||
constructor(
|
||||
private configService: ConfigService,
|
||||
private authService: AuthService,
|
||||
private httpService: HttpService,
|
||||
) {
|
||||
super({
|
||||
secretOrKeyProvider: passportJwtSecret({
|
||||
cache: true,
|
||||
rateLimit: true,
|
||||
jwksRequestsPerMinute: 5,
|
||||
jwksUri: `${configService.get('AUTH0_ISSUER')}.well-known/jwks.json`,
|
||||
}),
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
audience: configService.get('AUTH0_AUDIENCE'),
|
||||
issuer: configService.get('AUTH0_ISSUER'),
|
||||
algorithms: ['RS256'],
|
||||
passReqToCallback: true, // We need the request to get the token
|
||||
});
|
||||
}
|
||||
|
||||
async validate(req: any, payload: any) {
|
||||
// Extract token from Authorization header
|
||||
const token = req.headers.authorization?.replace('Bearer ', '');
|
||||
|
||||
// Fetch user info from Auth0 /userinfo endpoint
|
||||
try {
|
||||
const userInfoUrl = `${this.configService.get('AUTH0_ISSUER')}userinfo`;
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.get(userInfoUrl, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// Merge userinfo data into payload
|
||||
const userInfo = response.data;
|
||||
|
||||
payload.email = userInfo.email || payload.email;
|
||||
payload.name = userInfo.name || payload.name;
|
||||
payload.picture = userInfo.picture || payload.picture;
|
||||
payload.email_verified = userInfo.email_verified;
|
||||
} catch (error) {
|
||||
this.logger.warn(`Failed to fetch user info: ${error.message}`);
|
||||
// Continue with payload-only data (fallbacks will apply)
|
||||
}
|
||||
|
||||
// Get or create user from Auth0 token
|
||||
const user = await this.authService.validateUser(payload);
|
||||
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('User not found');
|
||||
}
|
||||
|
||||
if (!user.isApproved) {
|
||||
throw new UnauthorizedException('User account pending approval');
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
63
backend/src/common/filters/all-exceptions.filter.ts
Normal file
63
backend/src/common/filters/all-exceptions.filter.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
ExceptionFilter,
|
||||
Catch,
|
||||
ArgumentsHost,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
/**
|
||||
* Catch-all exception filter for unhandled errors
|
||||
* This ensures all errors return a consistent format
|
||||
*/
|
||||
@Catch()
|
||||
export class AllExceptionsFilter implements ExceptionFilter {
|
||||
private readonly logger = new Logger(AllExceptionsFilter.name);
|
||||
|
||||
catch(exception: unknown, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
const request = ctx.getRequest<Request>();
|
||||
|
||||
let status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
let message = 'Internal server error';
|
||||
let stack: string | undefined;
|
||||
|
||||
if (exception instanceof HttpException) {
|
||||
status = exception.getStatus();
|
||||
const exceptionResponse = exception.getResponse();
|
||||
message =
|
||||
typeof exceptionResponse === 'string'
|
||||
? exceptionResponse
|
||||
: (exceptionResponse as any).message || exception.message;
|
||||
stack = exception.stack;
|
||||
} else if (exception instanceof Error) {
|
||||
message = exception.message;
|
||||
stack = exception.stack;
|
||||
}
|
||||
|
||||
const errorResponse = {
|
||||
statusCode: status,
|
||||
timestamp: new Date().toISOString(),
|
||||
path: request.url,
|
||||
method: request.method,
|
||||
message,
|
||||
error: HttpStatus[status],
|
||||
};
|
||||
|
||||
// Log the error
|
||||
this.logger.error(
|
||||
`[${request.method}] ${request.url} - ${status} - ${message}`,
|
||||
stack,
|
||||
);
|
||||
|
||||
// In development, include stack trace in response
|
||||
if (process.env.NODE_ENV === 'development' && stack) {
|
||||
(errorResponse as any).stack = stack;
|
||||
}
|
||||
|
||||
response.status(status).json(errorResponse);
|
||||
}
|
||||
}
|
||||
88
backend/src/common/filters/http-exception.filter.ts
Normal file
88
backend/src/common/filters/http-exception.filter.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
ExceptionFilter,
|
||||
Catch,
|
||||
ArgumentsHost,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
/**
|
||||
* Global exception filter that catches all HTTP exceptions
|
||||
* and formats them consistently with proper logging
|
||||
*/
|
||||
@Catch(HttpException)
|
||||
export class HttpExceptionFilter implements ExceptionFilter {
|
||||
private readonly logger = new Logger(HttpExceptionFilter.name);
|
||||
|
||||
catch(exception: HttpException, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
const request = ctx.getRequest<Request>();
|
||||
const status = exception.getStatus();
|
||||
const exceptionResponse = exception.getResponse();
|
||||
|
||||
// Extract error details
|
||||
const errorDetails =
|
||||
typeof exceptionResponse === 'string'
|
||||
? { message: exceptionResponse }
|
||||
: (exceptionResponse as any);
|
||||
|
||||
// Build standardized error response
|
||||
const errorResponse = {
|
||||
statusCode: status,
|
||||
timestamp: new Date().toISOString(),
|
||||
path: request.url,
|
||||
method: request.method,
|
||||
message: errorDetails.message || exception.message,
|
||||
error: errorDetails.error || HttpStatus[status],
|
||||
...(errorDetails.details && { details: errorDetails.details }),
|
||||
...(errorDetails.conflicts && { conflicts: errorDetails.conflicts }),
|
||||
};
|
||||
|
||||
// Log error with appropriate level
|
||||
const logMessage = `[${request.method}] ${request.url} - ${status} - ${errorResponse.message}`;
|
||||
|
||||
if (status >= 500) {
|
||||
this.logger.error(logMessage, exception.stack);
|
||||
} else if (status >= 400) {
|
||||
this.logger.warn(logMessage);
|
||||
} else {
|
||||
this.logger.log(logMessage);
|
||||
}
|
||||
|
||||
// Log request details for debugging (exclude sensitive data)
|
||||
if (status >= 400) {
|
||||
const sanitizedBody = this.sanitizeRequestBody(request.body);
|
||||
this.logger.debug(
|
||||
`Request details: ${JSON.stringify({
|
||||
params: request.params,
|
||||
query: request.query,
|
||||
body: sanitizedBody,
|
||||
user: (request as any).user?.email,
|
||||
})}`,
|
||||
);
|
||||
}
|
||||
|
||||
response.status(status).json(errorResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove sensitive fields from request body before logging
|
||||
*/
|
||||
private sanitizeRequestBody(body: any): any {
|
||||
if (!body) return body;
|
||||
|
||||
const sensitiveFields = ['password', 'token', 'apiKey', 'secret'];
|
||||
const sanitized = { ...body };
|
||||
|
||||
sensitiveFields.forEach((field) => {
|
||||
if (sanitized[field]) {
|
||||
sanitized[field] = '***REDACTED***';
|
||||
}
|
||||
});
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
}
|
||||
2
backend/src/common/filters/index.ts
Normal file
2
backend/src/common/filters/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './http-exception.filter';
|
||||
export * from './all-exceptions.filter';
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Pool } from 'pg';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const pool = new 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);
|
||||
});
|
||||
|
||||
export default pool;
|
||||
@@ -1,57 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
// Define the environment schema
|
||||
const envSchema = z.object({
|
||||
// Database
|
||||
DATABASE_URL: z.string().url().describe('PostgreSQL connection string'),
|
||||
|
||||
// Redis
|
||||
REDIS_URL: z.string().url().describe('Redis connection string'),
|
||||
|
||||
// Google OAuth
|
||||
GOOGLE_CLIENT_ID: z.string().min(1).describe('Google OAuth Client ID'),
|
||||
GOOGLE_CLIENT_SECRET: z.string().min(1).describe('Google OAuth Client Secret'),
|
||||
GOOGLE_REDIRECT_URI: z.string().url().describe('Google OAuth redirect URI'),
|
||||
|
||||
// Application
|
||||
FRONTEND_URL: z.string().url().describe('Frontend application URL'),
|
||||
JWT_SECRET: z.string().min(32).describe('JWT signing secret (min 32 chars)'),
|
||||
|
||||
// Server
|
||||
PORT: z.string().transform(Number).default('3000'),
|
||||
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
||||
});
|
||||
|
||||
// Validate and export environment variables
|
||||
export const env = (() => {
|
||||
try {
|
||||
return envSchema.parse(process.env);
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
console.error('❌ Invalid environment variables:');
|
||||
console.error(error.format());
|
||||
|
||||
const missingVars = error.errors
|
||||
.filter(err => err.code === 'invalid_type' && err.received === 'undefined')
|
||||
.map(err => err.path.join('.'));
|
||||
|
||||
if (missingVars.length > 0) {
|
||||
console.error('\n📋 Missing required environment variables:');
|
||||
missingVars.forEach(varName => {
|
||||
console.error(` - ${varName}`);
|
||||
});
|
||||
console.error('\n💡 Create a .env file based on .env.example');
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
|
||||
// Type-safe environment variables
|
||||
export type Env = z.infer<typeof envSchema>;
|
||||
@@ -1,177 +0,0 @@
|
||||
// Mock database for when PostgreSQL is not available
|
||||
interface MockUser {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
role: string;
|
||||
google_id?: string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
interface MockVIP {
|
||||
id: string;
|
||||
name: string;
|
||||
organization?: string;
|
||||
department: string;
|
||||
transport_mode: string;
|
||||
expected_arrival?: string;
|
||||
needs_airport_pickup: boolean;
|
||||
needs_venue_transport: boolean;
|
||||
notes?: string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
class MockDatabase {
|
||||
private users: Map<string, MockUser> = new Map();
|
||||
private vips: Map<string, MockVIP> = new Map();
|
||||
private drivers: Map<string, any> = new Map();
|
||||
private scheduleEvents: Map<string, any> = new Map();
|
||||
private adminSettings: Map<string, string> = new Map();
|
||||
|
||||
constructor() {
|
||||
// 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: string, params?: any[]): Promise<any> {
|
||||
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: MockUser = {
|
||||
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: MockVIP = {
|
||||
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: string, callback: Function) {
|
||||
if (event === 'connect') {
|
||||
setTimeout(() => callback(), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MockDatabase;
|
||||
@@ -1,23 +0,0 @@
|
||||
import { createClient } from 'redis';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const redisClient = createClient({
|
||||
url: process.env.REDIS_URL || 'redis://localhost:6379'
|
||||
});
|
||||
|
||||
redisClient.on('connect', () => {
|
||||
console.log('✅ Connected to Redis');
|
||||
});
|
||||
|
||||
redisClient.on('error', (err: Error) => {
|
||||
console.error('❌ Redis connection error:', err);
|
||||
});
|
||||
|
||||
// Connect to Redis
|
||||
redisClient.connect().catch((err: Error) => {
|
||||
console.error('❌ Failed to connect to Redis:', err);
|
||||
});
|
||||
|
||||
export default redisClient;
|
||||
@@ -1,130 +0,0 @@
|
||||
-- VIP Coordinator Database Schema
|
||||
|
||||
-- Create VIPs table
|
||||
CREATE TABLE IF NOT EXISTS vips (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
organization VARCHAR(255) NOT NULL,
|
||||
department VARCHAR(255) DEFAULT 'Office of Development',
|
||||
transport_mode VARCHAR(50) NOT NULL CHECK (transport_mode IN ('flight', 'self-driving')),
|
||||
expected_arrival TIMESTAMP,
|
||||
needs_airport_pickup BOOLEAN DEFAULT false,
|
||||
needs_venue_transport BOOLEAN DEFAULT true,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create flights table (for VIPs with flight transport)
|
||||
CREATE TABLE IF NOT EXISTS flights (
|
||||
id SERIAL PRIMARY KEY,
|
||||
vip_id VARCHAR(255) REFERENCES vips(id) ON DELETE CASCADE,
|
||||
flight_number VARCHAR(50) NOT NULL,
|
||||
flight_date DATE NOT NULL,
|
||||
segment INTEGER NOT NULL,
|
||||
departure_airport VARCHAR(10),
|
||||
arrival_airport VARCHAR(10),
|
||||
scheduled_departure TIMESTAMP,
|
||||
scheduled_arrival TIMESTAMP,
|
||||
actual_departure TIMESTAMP,
|
||||
actual_arrival TIMESTAMP,
|
||||
status VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create drivers table
|
||||
CREATE TABLE IF NOT EXISTS drivers (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
phone VARCHAR(50) NOT NULL,
|
||||
department VARCHAR(255) DEFAULT 'Office of Development',
|
||||
user_id VARCHAR(255) REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create schedule_events table
|
||||
CREATE TABLE IF NOT EXISTS schedule_events (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
vip_id VARCHAR(255) REFERENCES vips(id) ON DELETE CASCADE,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
location VARCHAR(255) NOT NULL,
|
||||
start_time TIMESTAMP NOT NULL,
|
||||
end_time TIMESTAMP NOT NULL,
|
||||
description TEXT,
|
||||
assigned_driver_id VARCHAR(255) REFERENCES drivers(id) ON DELETE SET NULL,
|
||||
status VARCHAR(50) DEFAULT 'scheduled' CHECK (status IN ('scheduled', 'in-progress', 'completed', 'cancelled')),
|
||||
event_type VARCHAR(50) NOT NULL CHECK (event_type IN ('transport', 'meeting', 'event', 'meal', 'accommodation')),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create users table for authentication
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
google_id VARCHAR(255) UNIQUE NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(50) NOT NULL CHECK (role IN ('driver', 'coordinator', 'administrator')),
|
||||
profile_picture_url TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create system_setup table for tracking initial setup
|
||||
CREATE TABLE IF NOT EXISTS system_setup (
|
||||
id SERIAL PRIMARY KEY,
|
||||
setup_completed BOOLEAN DEFAULT false,
|
||||
first_admin_created BOOLEAN DEFAULT false,
|
||||
setup_date TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create admin_settings table
|
||||
CREATE TABLE IF NOT EXISTS admin_settings (
|
||||
id SERIAL PRIMARY KEY,
|
||||
setting_key VARCHAR(255) UNIQUE NOT NULL,
|
||||
setting_value TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create indexes for better performance
|
||||
CREATE INDEX IF NOT EXISTS idx_vips_transport_mode ON vips(transport_mode);
|
||||
CREATE INDEX IF NOT EXISTS idx_flights_vip_id ON flights(vip_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_flights_date ON flights(flight_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_schedule_events_vip_id ON schedule_events(vip_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_schedule_events_driver_id ON schedule_events(assigned_driver_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_schedule_events_start_time ON schedule_events(start_time);
|
||||
CREATE INDEX IF NOT EXISTS idx_schedule_events_status ON schedule_events(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_google_id ON users(google_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
|
||||
CREATE INDEX IF NOT EXISTS idx_drivers_user_id ON drivers(user_id);
|
||||
|
||||
-- Create updated_at trigger function
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
-- Create triggers for updated_at (drop if exists first)
|
||||
DROP TRIGGER IF EXISTS update_vips_updated_at ON vips;
|
||||
DROP TRIGGER IF EXISTS update_flights_updated_at ON flights;
|
||||
DROP TRIGGER IF EXISTS update_drivers_updated_at ON drivers;
|
||||
DROP TRIGGER IF EXISTS update_schedule_events_updated_at ON schedule_events;
|
||||
DROP TRIGGER IF EXISTS update_users_updated_at ON users;
|
||||
DROP TRIGGER IF EXISTS update_admin_settings_updated_at ON admin_settings;
|
||||
|
||||
CREATE TRIGGER update_vips_updated_at BEFORE UPDATE ON vips FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_flights_updated_at BEFORE UPDATE ON flights FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_drivers_updated_at BEFORE UPDATE ON drivers FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_schedule_events_updated_at BEFORE UPDATE ON schedule_events FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_admin_settings_updated_at BEFORE UPDATE ON admin_settings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -1,236 +0,0 @@
|
||||
import jwtKeyManager, { User } from '../services/jwtKeyManager';
|
||||
|
||||
// JWT Key Manager now handles all token operations with automatic rotation
|
||||
// No more static JWT_SECRET needed!
|
||||
|
||||
export { User } from '../services/jwtKeyManager';
|
||||
|
||||
export function generateToken(user: User): string {
|
||||
return jwtKeyManager.generateToken(user);
|
||||
}
|
||||
|
||||
export function verifyToken(token: string): User | null {
|
||||
return jwtKeyManager.verifyToken(token);
|
||||
}
|
||||
|
||||
// Simple Google OAuth2 client using fetch
|
||||
export async function verifyGoogleToken(googleToken: string): Promise<any> {
|
||||
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
|
||||
export function getGoogleAuthUrl(): string {
|
||||
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
|
||||
export async function exchangeCodeForTokens(code: string): Promise<any> {
|
||||
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
|
||||
export async function getGoogleUserInfo(accessToken: string): Promise<any> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
63
backend/src/drivers/drivers.controller.ts
Normal file
63
backend/src/drivers/drivers.controller.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Patch,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { DriversService } from './drivers.service';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import { RolesGuard } from '../auth/guards/roles.guard';
|
||||
import { Roles } from '../auth/decorators/roles.decorator';
|
||||
import { Role } from '@prisma/client';
|
||||
import { CreateDriverDto, UpdateDriverDto } from './dto';
|
||||
|
||||
@Controller('drivers')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
export class DriversController {
|
||||
constructor(private readonly driversService: DriversService) {}
|
||||
|
||||
@Post()
|
||||
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR)
|
||||
create(@Body() createDriverDto: CreateDriverDto) {
|
||||
return this.driversService.create(createDriverDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR, Role.DRIVER)
|
||||
findAll() {
|
||||
return this.driversService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR, Role.DRIVER)
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.driversService.findOne(id);
|
||||
}
|
||||
|
||||
@Get(':id/schedule')
|
||||
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR, Role.DRIVER)
|
||||
getSchedule(@Param('id') id: string) {
|
||||
return this.driversService.getSchedule(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR)
|
||||
update(@Param('id') id: string, @Body() updateDriverDto: UpdateDriverDto) {
|
||||
return this.driversService.update(id, updateDriverDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR)
|
||||
remove(
|
||||
@Param('id') id: string,
|
||||
@Query('hard') hard?: string,
|
||||
) {
|
||||
const isHardDelete = hard === 'true';
|
||||
return this.driversService.remove(id, isHardDelete);
|
||||
}
|
||||
}
|
||||
10
backend/src/drivers/drivers.module.ts
Normal file
10
backend/src/drivers/drivers.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DriversController } from './drivers.controller';
|
||||
import { DriversService } from './drivers.service';
|
||||
|
||||
@Module({
|
||||
controllers: [DriversController],
|
||||
providers: [DriversService],
|
||||
exports: [DriversService],
|
||||
})
|
||||
export class DriversModule {}
|
||||
89
backend/src/drivers/drivers.service.ts
Normal file
89
backend/src/drivers/drivers.service.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Injectable, NotFoundException, Logger } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateDriverDto, UpdateDriverDto } from './dto';
|
||||
|
||||
@Injectable()
|
||||
export class DriversService {
|
||||
private readonly logger = new Logger(DriversService.name);
|
||||
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async create(createDriverDto: CreateDriverDto) {
|
||||
this.logger.log(`Creating driver: ${createDriverDto.name}`);
|
||||
|
||||
return this.prisma.driver.create({
|
||||
data: createDriverDto,
|
||||
include: { user: true },
|
||||
});
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
return this.prisma.driver.findMany({
|
||||
where: { deletedAt: null },
|
||||
include: {
|
||||
user: true,
|
||||
events: {
|
||||
where: { deletedAt: null },
|
||||
include: { vehicle: true, driver: true },
|
||||
orderBy: { startTime: 'asc' },
|
||||
},
|
||||
},
|
||||
orderBy: { name: 'asc' },
|
||||
});
|
||||
}
|
||||
|
||||
async findOne(id: string) {
|
||||
const driver = await this.prisma.driver.findFirst({
|
||||
where: { id, deletedAt: null },
|
||||
include: {
|
||||
user: true,
|
||||
events: {
|
||||
where: { deletedAt: null },
|
||||
include: { vehicle: true, driver: true },
|
||||
orderBy: { startTime: 'asc' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!driver) {
|
||||
throw new NotFoundException(`Driver with ID ${id} not found`);
|
||||
}
|
||||
|
||||
return driver;
|
||||
}
|
||||
|
||||
async update(id: string, updateDriverDto: UpdateDriverDto) {
|
||||
const driver = await this.findOne(id);
|
||||
|
||||
this.logger.log(`Updating driver ${id}: ${driver.name}`);
|
||||
|
||||
return this.prisma.driver.update({
|
||||
where: { id: driver.id },
|
||||
data: updateDriverDto,
|
||||
include: { user: true },
|
||||
});
|
||||
}
|
||||
|
||||
async remove(id: string, hardDelete = false) {
|
||||
const driver = await this.findOne(id);
|
||||
|
||||
if (hardDelete) {
|
||||
this.logger.log(`Hard deleting driver: ${driver.name}`);
|
||||
return this.prisma.driver.delete({
|
||||
where: { id: driver.id },
|
||||
});
|
||||
}
|
||||
|
||||
this.logger.log(`Soft deleting driver: ${driver.name}`);
|
||||
return this.prisma.driver.update({
|
||||
where: { id: driver.id },
|
||||
data: { deletedAt: new Date() },
|
||||
});
|
||||
}
|
||||
|
||||
async getSchedule(id: string) {
|
||||
const driver = await this.findOne(id);
|
||||
|
||||
return driver.events;
|
||||
}
|
||||
}
|
||||
18
backend/src/drivers/dto/create-driver.dto.ts
Normal file
18
backend/src/drivers/dto/create-driver.dto.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { IsString, IsEnum, IsOptional, IsUUID } from 'class-validator';
|
||||
import { Department } from '@prisma/client';
|
||||
|
||||
export class CreateDriverDto {
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
phone: string;
|
||||
|
||||
@IsEnum(Department)
|
||||
@IsOptional()
|
||||
department?: Department;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
userId?: string;
|
||||
}
|
||||
2
backend/src/drivers/dto/index.ts
Normal file
2
backend/src/drivers/dto/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './create-driver.dto';
|
||||
export * from './update-driver.dto';
|
||||
4
backend/src/drivers/dto/update-driver.dto.ts
Normal file
4
backend/src/drivers/dto/update-driver.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateDriverDto } from './create-driver.dto';
|
||||
|
||||
export class UpdateDriverDto extends PartialType(CreateDriverDto) {}
|
||||
16
backend/src/events/dto/add-vips-to-event.dto.ts
Normal file
16
backend/src/events/dto/add-vips-to-event.dto.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { IsArray, IsUUID, IsString, IsOptional, IsInt, Min } from 'class-validator';
|
||||
|
||||
export class AddVipsToEventDto {
|
||||
@IsArray()
|
||||
@IsUUID('4', { each: true })
|
||||
vipIds: string[];
|
||||
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@IsOptional()
|
||||
pickupMinutesBeforeEvent?: number; // How many minutes before event should pickup happen (default: 15)
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
pickupLocationOverride?: string; // Override default pickup location
|
||||
}
|
||||
58
backend/src/events/dto/create-event.dto.ts
Normal file
58
backend/src/events/dto/create-event.dto.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
IsString,
|
||||
IsEnum,
|
||||
IsOptional,
|
||||
IsUUID,
|
||||
IsDateString,
|
||||
} from 'class-validator';
|
||||
import { EventType, EventStatus } from '@prisma/client';
|
||||
|
||||
export class CreateEventDto {
|
||||
@IsUUID('4', { each: true })
|
||||
vipIds: string[]; // Array of VIP IDs for multi-passenger trips
|
||||
|
||||
@IsString()
|
||||
title: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
location?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
pickupLocation?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
dropoffLocation?: string;
|
||||
|
||||
@IsDateString()
|
||||
startTime: string;
|
||||
|
||||
@IsDateString()
|
||||
endTime: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
notes?: string;
|
||||
|
||||
@IsEnum(EventType)
|
||||
@IsOptional()
|
||||
type?: EventType;
|
||||
|
||||
@IsEnum(EventStatus)
|
||||
@IsOptional()
|
||||
status?: EventStatus;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
driverId?: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
vehicleId?: string;
|
||||
}
|
||||
4
backend/src/events/dto/index.ts
Normal file
4
backend/src/events/dto/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './create-event.dto';
|
||||
export * from './update-event.dto';
|
||||
export * from './update-event-status.dto';
|
||||
export * from './add-vips-to-event.dto';
|
||||
7
backend/src/events/dto/update-event-status.dto.ts
Normal file
7
backend/src/events/dto/update-event-status.dto.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { IsEnum } from 'class-validator';
|
||||
import { EventStatus } from '@prisma/client';
|
||||
|
||||
export class UpdateEventStatusDto {
|
||||
@IsEnum(EventStatus)
|
||||
status: EventStatus;
|
||||
}
|
||||
9
backend/src/events/dto/update-event.dto.ts
Normal file
9
backend/src/events/dto/update-event.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { IsBoolean, IsOptional } from 'class-validator';
|
||||
import { CreateEventDto } from './create-event.dto';
|
||||
|
||||
export class UpdateEventDto extends PartialType(CreateEventDto) {
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
forceAssign?: boolean; // Allow double-booking drivers with confirmation
|
||||
}
|
||||
66
backend/src/events/events.controller.ts
Normal file
66
backend/src/events/events.controller.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Patch,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { EventsService } from './events.service';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import { RolesGuard } from '../auth/guards/roles.guard';
|
||||
import { Roles } from '../auth/decorators/roles.decorator';
|
||||
import { Role } from '@prisma/client';
|
||||
import { CreateEventDto, UpdateEventDto, UpdateEventStatusDto } from './dto';
|
||||
|
||||
@Controller('events')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
export class EventsController {
|
||||
constructor(private readonly eventsService: EventsService) {}
|
||||
|
||||
@Post()
|
||||
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR)
|
||||
create(@Body() createEventDto: CreateEventDto) {
|
||||
return this.eventsService.create(createEventDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR, Role.DRIVER)
|
||||
findAll() {
|
||||
return this.eventsService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR, Role.DRIVER)
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.eventsService.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR)
|
||||
update(@Param('id') id: string, @Body() updateEventDto: UpdateEventDto) {
|
||||
return this.eventsService.update(id, updateEventDto);
|
||||
}
|
||||
|
||||
@Patch(':id/status')
|
||||
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR, Role.DRIVER)
|
||||
updateStatus(
|
||||
@Param('id') id: string,
|
||||
@Body() updateEventStatusDto: UpdateEventStatusDto,
|
||||
) {
|
||||
return this.eventsService.updateStatus(id, updateEventStatusDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR)
|
||||
remove(
|
||||
@Param('id') id: string,
|
||||
@Query('hard') hard?: string,
|
||||
) {
|
||||
const isHardDelete = hard === 'true';
|
||||
return this.eventsService.remove(id, isHardDelete);
|
||||
}
|
||||
}
|
||||
16
backend/src/events/events.module.ts
Normal file
16
backend/src/events/events.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { EventsController } from './events.controller';
|
||||
import { EventsService } from './events.service';
|
||||
|
||||
@Module({
|
||||
controllers: [
|
||||
EventsController,
|
||||
],
|
||||
providers: [
|
||||
EventsService,
|
||||
],
|
||||
exports: [
|
||||
EventsService,
|
||||
],
|
||||
})
|
||||
export class EventsModule {}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user