diff --git a/.copilot-instructions.md b/.copilot-instructions.md new file mode 100644 index 0000000..5791bea --- /dev/null +++ b/.copilot-instructions.md @@ -0,0 +1,255 @@ +# PPR API Documentation for Copilot + +General Information + +As you perform more tests, document in this file the steps you are taking to avoid repetition. + +## API Base URL +- Development: `http://localhost:8001/api/v1` +- Authentication: Bearer token required for most endpoints + +## Authentication + +### Get Access Token +```bash +curl -X POST "http://localhost:8001/api/v1/auth/login" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=admin&password=admin123" +``` + +**Response:** +```json +{ + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "token_type": "bearer" +} +``` + +## PPR Management Endpoints + +### Create New PPR Entry + +**Endpoint:** `POST /api/v1/pprs/` + +**Headers:** +- `Content-Type: application/json` +- `Authorization: Bearer {access_token}` + +**Required Fields:** +- `ac_reg` (string): Aircraft registration (e.g., "G-TEST") +- `ac_type` (string): Aircraft type (e.g., "C172") +- `captain` (string): Captain's name +- `in_from` (string): Origin airport ICAO code (e.g., "EGKB") +- `eta` (datetime): Estimated time of arrival (ISO format: "2025-10-21T14:30:00") +- `pob_in` (integer): Persons on board inbound + +**Optional Fields:** +- `ac_call` (string): Aircraft callsign +- `fuel` (string): Fuel requirements +- `out_to` (string): Destination airport ICAO code +- `etd` (datetime): Estimated time of departure +- `pob_out` (integer): Persons on board outbound +- `email` (string): Contact email +- `phone` (string): Contact phone +- `notes` (string): Additional notes + +**Example Request:** +```bash +curl -X POST "http://localhost:8001/api/v1/pprs/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer {token}" \ + -d '{ + "ac_reg": "G-TEST", + "ac_type": "C172", + "ac_call": "TEST01", + "captain": "John Smith", + "fuel": "FULL", + "in_from": "EGKB", + "eta": "2025-10-21T14:30:00", + "pob_in": 2, + "out_to": "EGGW", + "etd": "2025-10-21T16:00:00", + "pob_out": 2, + "email": "john.smith@example.com", + "phone": "+44123456789", + "notes": "Test PPR entry created via API" + }' +``` + +**Success Response (201):** +```json +{ + "ac_reg": "G-TEST", + "ac_type": "C172", + "ac_call": "TEST01", + "captain": "John Smith", + "fuel": "FULL", + "in_from": "EGKB", + "eta": "2025-10-21T14:30:00", + "pob_in": 2, + "out_to": "EGGW", + "etd": "2025-10-21T16:00:00", + "pob_out": 2, + "email": "john.smith@example.com", + "phone": "+44123456789", + "notes": "Test PPR entry created via API", + "id": 2, + "status": "NEW", + "landed_dt": null, + "departed_dt": null, + "created_by": "admin", + "submitted_dt": "2025-10-21T19:45:46" +} +``` + +### Get All PPRs + +**Endpoint:** `GET /api/v1/pprs/` + +**Query Parameters:** +- `skip` (int): Number of records to skip (default: 0) +- `limit` (int): Maximum records to return (default: 100) +- `status` (string): Filter by status (NEW, CONFIRMED, CANCELED, LANDED, DELETED, DEPARTED) +- `date_from` (date): Filter from date (YYYY-MM-DD) +- `date_to` (date): Filter to date (YYYY-MM-DD) + +### Get Specific PPR + +**Endpoint:** `GET /api/v1/pprs/{ppr_id}` + +### Update PPR + +**Endpoint:** `PUT /api/v1/pprs/{ppr_id}` (full update) +**Endpoint:** `PATCH /api/v1/pprs/{ppr_id}` (partial update) + +### Update PPR Status + +**Endpoint:** `PATCH /api/v1/pprs/{ppr_id}/status` + +**Request Body:** +```json +{ + "status": "LANDED" +} +``` + +**Available Statuses:** +- `NEW`: Newly submitted PPR +- `CONFIRMED`: PPR confirmed by tower +- `CANCELED`: PPR canceled +- `LANDED`: Aircraft has landed +- `DEPARTED`: Aircraft has departed +- `DELETED`: PPR deleted (soft delete) + +### Delete PPR + +**Endpoint:** `DELETE /api/v1/pprs/{ppr_id}` (soft delete - sets status to DELETED) + +## Public Endpoints (No Authentication Required) + +### Get Today's Arrivals + +**Endpoint:** `GET /api/v1/public/arrivals` + +Returns all PPRs with status "NEW" and ETA for today. + +### Get Today's Departures + +**Endpoint:** `GET /api/v1/public/departures` + +Returns all PPRs with status "LANDED" and ETD for today. + +## Data Models + +### PPR Status Enum +``` +NEW -> CONFIRMED -> LANDED -> DEPARTED + -> CANCELED + -> DELETED +``` + +### Datetime Format +All datetime fields use ISO 8601 format: `YYYY-MM-DDTHH:MM:SS` + +### Airport Codes +Use ICAO 4-letter codes (e.g., EGKB, EGGW, EGLL) for airport identifiers. + +## Error Responses + +**401 Unauthorized:** +```json +{ + "detail": "Could not validate credentials" +} +``` + +**404 Not Found:** +```json +{ + "detail": "PPR record not found" +} +``` + +**422 Validation Error:** +```json +{ + "detail": [ + { + "loc": ["body", "ac_reg"], + "msg": "Aircraft registration is required", + "type": "value_error" + } + ] +} +``` + +## WebSocket Real-time Updates + +The API sends real-time updates via WebSocket for: +- `ppr_created`: New PPR created +- `ppr_updated`: PPR updated +- `status_update`: PPR status changed +- `ppr_deleted`: PPR deleted + +## Default Users + +- **Username:** `admin` +- **Password:** `admin123` + +## Development URLs + +- API Base: http://localhost:8001/api/v1 +- Public Web Interface: http://localhost:8082 +- API Documentation: http://localhost:8001/docs +- Database: localhost:3307 (MySQL) + +## Example Workflow + +1. Get access token +2. Create PPR entry (status: NEW) → Shows on arrivals board +3. Update status to LANDED → Shows on departures board +4. Update status to DEPARTED → Removes from departures board + +## Testing Commands + +```bash +# Store token +TOKEN=$(curl -s -X POST "http://localhost:8001/api/v1/auth/login" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=admin&password=admin123" | jq -r '.access_token') + +# Create PPR +curl -X POST "http://localhost:8001/api/v1/pprs/" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d '{"ac_reg":"G-TEST","ac_type":"C172","captain":"Test Pilot","in_from":"EGKB","eta":"2025-10-21T14:30:00","pob_in":2}' + +# Check arrivals +curl -s "http://localhost:8001/api/v1/public/arrivals" | jq . + +# Update status to LANDED +curl -X PATCH "http://localhost:8001/api/v1/pprs/1/status" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d '{"status":"LANDED"}' +``` \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 431e3b7..21bbf89 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -57,6 +57,21 @@ services: networks: - ppr_network + # Nginx web server for public frontend + web: + image: nginx:alpine + container_name: ppr_nextgen_web + restart: unless-stopped + ports: + - "8082:80" # Public web interface + volumes: + - ./web:/usr/share/nginx/html + - ./nginx.conf:/etc/nginx/nginx.conf + depends_on: + - api + networks: + - ppr_network + volumes: mysql_data: diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..8684f39 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,65 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + # Basic settings + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; + + server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Serve static files + location / { + try_files $uri $uri/ /index.html; + } + + # Proxy API requests to FastAPI backend + location /api/ { + proxy_pass http://api:8000; + 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; + } + + # WebSocket support for real-time updates + location /ws/ { + proxy_pass http://api:8000; + 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; + } + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; + } +} \ No newline at end of file diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..b1d0df8 --- /dev/null +++ b/web/index.html @@ -0,0 +1,388 @@ + + +
+ + +Real-time Arrivals & Departures Board
+