cd ppt-tool
# 2. Configure environment
cp .env.example .env
# Edit .env — set ANTHROPIC_API_KEY at minimum
# 3. Build and start
make dev
# 4. Run migrations
make migrate
# 5. Seed default data
make seed
```
The application is available at:
- **http://localhost** — Full application (via nginx)
- **http://localhost:3000** — Frontend directly
- **http://localhost:8000** — API directly
- **http://localhost/docs** — Swagger API documentation
### 2.3 Makefile Commands
| Command | Description |
|---------|-------------|
| `make dev` | Build and start all services with logs |
| `make build` | Build Docker images only |
| `make up` | Start services in background (detached) |
| `make down` | Stop and remove all containers |
| `make migrate` | Run Alembic database migrations |
| `make seed` | Seed default admin user and team |
| `make test` | Run backend pytest suite |
| `make test-e2e` | Run Cypress E2E tests |
| `make test-all` | Run all tests |
| `make logs` | Follow all container logs |
| `make shell-api` | Open bash shell in API container |
| `make shell-db` | Open psql shell in PostgreSQL |
### 2.4 Local Development (Without Docker)
#### Backend
```bash
cd backend
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
export DATABASE_URL="postgresql+asyncpg://deckforge:deckforge@localhost:5432/deckforge"
export REDIS_URL="redis://localhost:6379/0"
export APP_DATA_DIRECTORY="./data"
export ANTHROPIC_API_KEY="sk-ant-..."
# Start API
uvicorn api.main:app --reload --port 8000
# Start worker (separate terminal)
python -m arq workers.main.WorkerSettings
```
#### Frontend
```bash
cd frontend
npm install
npm run dev
```
The Next.js dev server proxies `/api/v1/` requests to `http://localhost:8000` via rewrites in `next.config.mjs`.
### 2.5 Docker Image Details
**Backend Dockerfile** (multi-stage):
- **Builder stage**: `python:3.11-slim-bookworm` + `uv` package manager
- **Runtime stage**: Includes LibreOffice (PDF conversion), Chromium (browser automation), fontconfig, ONNX models
- Exposes port 8000
**Frontend Dockerfile**:
- **Base**: `node:20-alpine`
- Includes Chromium for server-side Puppeteer PDF/PPTX export
- Environment: `PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser`
- Exposes port 3000
---
## 3. Environment Variables Reference
### 3.1 Database & Infrastructure
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `POSTGRES_PASSWORD` | No | `deckforge` | PostgreSQL password |
| `DATABASE_URL` | Auto | Set by docker-compose | Full async connection string |
| `REDIS_URL` | No | `redis://redis:6379/0` | Redis connection string |
| `APP_DATA_DIRECTORY` | No | `/app_data` | Path for images, exports, uploads |
| `TEMP_DIRECTORY` | No | `/tmp/deckforge` | Temporary file storage |
### 3.2 Authentication
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `JWT_SECRET_KEY` | **Yes** | `change-me-...` | Secret for JWT signing (256-bit+) |
| `AZURE_AD_TENANT_ID` | No | — | Azure AD tenant ID (blank = dev mode) |
| `AZURE_AD_CLIENT_ID` | No | — | Azure AD app client ID |
| `AZURE_AD_CLIENT_SECRET` | No | — | Azure AD app secret |
| `AZURE_AD_REDIRECT_URI` | No | `http://localhost/api/v1/auth/callback` | OAuth callback URL |
| `DEV_AUTH_PASSWORD` | No | `devpass123` | Dev bypass login password |
### 3.3 AI Providers
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `LLM` | No | `anthropic` | Primary LLM provider |
| `ANTHROPIC_API_KEY` | **Yes*** | — | Claude API key |
| `ANTHROPIC_MODEL` | No | `claude-sonnet-4-6` | Claude model ID |
| `OPENAI_API_KEY` | No | — | OpenAI API key |
| `OPENAI_MODEL` | No | — | OpenAI model ID |
| `GOOGLE_API_KEY` | No | — | Google Gemini API key |
| `GOOGLE_MODEL` | No | — | Google model ID |
| `OLLAMA_URL` | No | — | Ollama server URL |
| `OLLAMA_MODEL` | No | — | Ollama model name |
| `IMAGE_PROVIDER` | No | `nanobanana_pro` | Image generation provider |
| `DISABLE_IMAGE_GENERATION` | No | — | Set to disable image gen |
*Required if using Anthropic (default provider)
### 3.4 Application Settings
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `CAN_CHANGE_KEYS` | No | `false` | Allow runtime API key changes |
| `DISABLE_ANONYMOUS_TRACKING` | No | `true` | Disable analytics tracking |
| `SETTINGS_ENCRYPTION_KEY` | No | — | Fernet key for encrypting stored API keys |
| `EXTENDED_REASONING` | No | — | Enable LLM extended thinking |
| `TOOL_CALLS` | No | — | Enable LLM tool use |
| `WEB_GROUNDING` | No | — | Enable web search in generation |
| `NEXT_INTERNAL_URL` | No | `http://web:3000` | Backend → frontend URL (Docker) |
### 3.5 Supported AI Providers
**LLM Providers:**
| Provider | Value | Models |
|----------|-------|--------|
| Anthropic | `anthropic` | claude-opus-4-6, claude-sonnet-4-6, claude-sonnet-4-5, claude-haiku-4-5 |
| OpenAI | `openai` | gpt-4.1, gpt-4.1-mini, gpt-4o, o3, o4-mini |
| Google | `google` | gemini-2.5-flash, gemini-2.5-pro, gemini-2.0-flash |
| Ollama | `ollama` | Any locally installed model |
| Custom | `custom` | Any OpenAI-compatible endpoint |
**Image Providers:**
| Provider | Value | Requirements |
|----------|-------|-------------|
| NanoBanana Pro | `nanobanana_pro` | Google API key |
| Gemini Flash | `gemini_flash` | Google API key |
| DALL-E 3 | `dall-e-3` | OpenAI API key |
| GPT Image 1.5 | `gpt-image-1.5` | OpenAI API key |
| Pexels | `pexels` | Pexels API key |
| Pixabay | `pixabay` | Pixabay API key |
| ComfyUI | `comfyui` | Local ComfyUI instance |
---
## 4. Authentication Configuration
### 4.1 Authentication Flow
```mermaid
flowchart LR
A[Login Page] --> B{Azure AD?}
B -->|Yes| C[SSO] --> D[Callback] --> E{User exists?}
B -->|No| F[Dev Login] --> E
E -->|Yes| G[Update login]
E -->|No| H[Create user]
G --> I[JWT Cookie]
H --> I
```
### 4.2 Azure AD Setup (Production)
1. **Register an application** in Azure Portal > Azure AD > App Registrations
2. Set the **Redirect URI** to: `https://your-domain.com/api/v1/auth/callback`
3. Create a **client secret** under Certificates & Secrets
4. Configure environment variables:
```env
AZURE_AD_TENANT_ID=your-tenant-id
AZURE_AD_CLIENT_ID=your-client-id
AZURE_AD_CLIENT_SECRET=your-client-secret
AZURE_AD_REDIRECT_URI=https://your-domain.com/api/v1/auth/callback
```
5. Grant API permissions: `User.Read` (delegated)
### 4.3 Development Mode
When `AZURE_AD_TENANT_ID` is empty or not set, the system enables development authentication:
- Login form with email + password fields
- Password validated against `DEV_AUTH_PASSWORD` environment variable
- Users are auto-created on first login
- Default role: `user`
**Security Warning:** Development mode should never be used in production. Always configure Azure AD or another SSO provider for production deployments.
### 4.4 JWT Configuration
| Parameter | Value |
|-----------|-------|
| Algorithm | HS256 |
| Expiry | 24 hours |
| Storage | `session_token` HTTP cookie |
| Payload | `sub` (user UUID), `email`, `role`, `exp`, `iat` |
**Critical:** Change `JWT_SECRET_KEY` from the default value in production. Use a cryptographically random 256-bit key. All active sessions are invalidated when this key changes.
---
## 5. Role-Based Access Control
### 5.1 Role Hierarchy
```mermaid
graph LR
SA[Super Admin] --> CA[Client Admin] --> U[User]
SA -.->|Full access| ALL[All Clients]
CA -.->|Scoped| CLIENT[Assigned Clients]
U -.->|Basic| OWN[Own Data]
```
### 5.2 Permission Matrix
| Feature | Super Admin | Client Admin | User |
|---------|:-----------:|:------------:|:----:|
| **Presentations** | All | Client-scoped | Own only |
| **Admin Panel** | Full | Limited | None |
| **User Management** | CRUD all | View team members | — |
| **Client Management** | CRUD all | View/edit assigned | — |
| **Team Management** | All teams | Assigned client teams | — |
| **Master Decks** | All | Client-scoped | — |
| **Storage** | All clients | Client-scoped | — |
| **Analytics** | Global + per-client | Client-scoped | — |
| **Audit Logs** | All | Client-scoped | — |
| **System Settings** | Full access | — | — |
| **Brand Config** | All clients | Client-scoped | — |
### 5.3 Multi-Tenant Data Isolation
```mermaid
flowchart LR
A[Request] --> B[Auth] --> C[RBAC] --> D{Role}
D -->|super_admin| E[No filter]
D -->|client_admin| F[Team lookup → client_ids]
D -->|user| G[Own data + team clients]
```
The `_resolve_client_filter()` pattern is used throughout:
- **Super Admin with no client_id param** → Returns `None` (no filter, see all)
- **Super Admin with client_id param** → Filters to specific client
- **Client Admin** → Auto-scoped to accessible clients via `TeamMembershipModel`
- **User** → Scoped to own data within accessible clients
### 5.4 Admin Panel Navigation
The sidebar dynamically shows menu items based on role:
| Menu Item | Super Admin | Client Admin |
|-----------|:-----------:|:------------:|
| Users | Yes | — |
| Clients | Yes | Yes |
| Storage | Yes | Yes |
| Audit Log | Yes | Yes |
| Analytics | Yes | Yes |
| Settings | Yes | — |
---
## 6. Admin Panel Operations
### 6.1 User Management
**Path:** Admin > Users (Super Admin only)
#### Listing Users
- Table with columns: Name, Email, Role, Status, Last Login
- Filterable by active status and role
#### Changing User Roles
1. Find the user in the list
2. Click the role dropdown
3. Select new role: `super_admin`, `client_admin`, or `user`
You cannot change your own role. This prevents accidental lockout.
#### Deactivating Users
1. Click **Deactivate** on the user row
2. Confirm the action
3. User's `is_active` is set to false — they can no longer log in
4. Their presentations remain in the system
#### Transferring Ownership
Before deactivating a user (e.g., for GDPR compliance):
1. Use the transfer ownership endpoint to move all presentations to another user
2. Then deactivate the original user
### 6.2 Client Management
**Path:** Admin > Clients
#### Creating a Client
1. Click **"+ New Client"**
2. Enter the client name
3. A URL-safe slug is auto-generated
4. A default team is auto-created for the client
#### Client Settings
Each client has configurable:
| Setting | Description |
|---------|-------------|
| **Name** | Display name |
| **Slug** | URL-safe identifier (unique) |
| **Review Policy** | `self_approve` or `require_reviewer` |
| **Retention Days** | Auto-delete presentations after N days (optional) |
### 6.3 Team Management
**Path:** Admin > Clients > [Client] > Teams
Teams group users within a client:
- Each client has a **default team** (cannot be deleted)
- Users can belong to multiple teams
- Team membership determines client access for non-admin users
#### Adding Team Members
1. Navigate to the client's team page
2. Click **"+ Add Member"**
3. Search and select a user from the dropdown
4. The user now has access to this client's data
### 6.4 Brand Configuration
**Path:** Admin > Clients > [Client] > Brand Config
Configure branding per client:
| Setting | Description |
|---------|-------------|
| **Primary Colors** | Color picker, add/remove multiple colors |
| **Secondary Colors** | Color picker, add/remove multiple colors |
| **Fonts** | Heading, Body, and Accent font names |
| **Logos** | Upload multiple logo images |
| **Voice Rules** | Text guidelines for AI tone and style |
| **Voice Examples** | Good/bad example pairs for AI training |
| **Brand Guideline** | Upload a PDF/DOCX brand guide |
Brand configuration is injected into AI prompts during presentation generation to ensure brand consistency.
### 6.5 Storage Management
**Path:** Admin > Storage
#### Summary Dashboard
Four cards show:
- **Presentations** count
- **Export Files** count
- **Master Decks** count
- **Total Size** (formatted)
#### Client Selector (Super Admin)
Dropdown to filter by specific client or view all clients combined.
#### Presentation Table
- Columns: Title, Status, Created, Files, Size
- Checkboxes for bulk selection
- Per-row actions: Download PPTX, Delete
#### Bulk Operations
- Select multiple presentations via checkboxes
- Click **"Delete Selected"** for bulk soft-delete
#### Purge Files (Super Admin Only)
- Amber banner shows count of soft-deleted presentations
- **"Purge Files"** permanently removes files from disk
- Returns statistics: files purged, bytes freed
### 6.6 Analytics Dashboard
**Path:** Admin > Analytics
#### Overview Metrics
- Total Presentations (all-time)
- This Month / This Week (30/7-day counts)
- Active Users (distinct users, last 30 days)
- Approval Rate (% approved or in_review)
#### Usage Metrics
- **Presentations per Day** — 14-day bar chart
- **Top 10 Users** — ranked by presentation count
#### Quality Metrics
- **Status Distribution** — draft/in_review/approved breakdown
- **Presentations with Comments** — count
#### Performance Metrics
- **Average Generation Time** — job completion duration
- **Total Jobs** — all-time count
- **Error Rate** — % failed jobs
#### AI Usage (if tracking enabled)
- Total AI Calls, Input/Output Tokens
- Usage by Provider (bar chart)
- Usage by Model (top 10)
- Daily Usage Trend
### 6.7 Audit Logs
**Path:** Admin > Audit Log
All mutating API requests are logged automatically.
#### Query Filters
- **Action** — text search (e.g., "admin_delete")
- **User ID** — filter by specific user
- **Resource Type** — e.g., "presentation", "storage"
- **Client ID** — scope to a client
- **Date Range** — from/to date pickers
#### Export
- Click **"Export Audit Log"**
- Choose format: CSV or JSON
- Downloads up to 10,000 entries
#### Logged Actions
| Action | Trigger |
|--------|---------|
| `admin_delete` | Single presentation soft-delete |
| `admin_bulk_delete` | Bulk presentation delete |
| `admin_purge` | Hard-delete purged files |
| Role changes, team membership updates, etc. | Various admin operations |
---
## 7. Template Pipeline
### 7.1 Master Deck → Template Flow
```mermaid
flowchart LR
A[Upload PPTX] --> B[Enqueue Job]
B --> C{Parse Mode}
C -->|layouts| D[slideLayouts XML]
C -->|slides| E[slides XML]
D --> F[PDF → Screenshots]
E --> F
F --> G[LLM Vision: Screenshot+XML → TSX]
G --> H[Store Layouts] --> I[Register Template]
```
### 7.2 Upload & Parse
1. Navigate to **Admin > Clients > [Client] > Master Decks**
2. Click **"Upload PPTX"** and select a `.pptx` file
3. The deck enters **"pending"** status, then **"processing"**
4. Auto-polling every 5 seconds shows current status
5. On completion, status changes to **"completed"** and layouts appear
### 7.3 Parse Modes
| Mode | Source | Best For |
|------|--------|----------|
| **slides** (default) | Actual slides (`ppt/slides/`) | Decks with unique slide designs; 1:1 screenshot match |
| **layouts** | Slide layouts (`ppt/slideLayouts/`) | Decks with reusable layout templates; may produce more layouts |
### 7.4 Layout Management
After parsing, manage layouts in the expanded deck view:
**Filtering & Search:**
- Text search by layout name
- Type filter dropdown (auto-detected from layout types)
- Code filter: All / Has Code / Missing Code
**Individual Actions:**
- **Edit** — modify name, type, or React TSX code
- **Delete** — remove with confirmation dialog
**Bulk Actions:**
- Toggle **Select Mode** to show checkboxes
- **Select All** / **Deselect All**
- **Delete Selected** — bulk remove
After deleting layouts, the system automatically re-registers the template by recreating `PresentationLayoutCodeModel` records for the remaining layouts.
### 7.5 Reparsing
If layouts need to be regenerated (e.g., after an LLM model upgrade):
1. Click the reparse dropdown on the deck card
2. Choose **"Reparse (slides)"** or **"Reparse (layouts)"**
3. All existing layouts are replaced with freshly parsed versions
---
## 8. AI Provider Configuration
### 8.1 Settings Page
**Path:** Admin > Settings (Super Admin only)
```mermaid
flowchart LR
A[Select LLM Provider] --> B[Fetch Available Models]
B --> C[Select Model]
C --> D[Enter API Key]
D --> E[Test Connection]
E -->|OK| F[Save Settings]
E -->|Fail| G[Check Key / Network]
```
### 8.2 Configuring LLM Provider
1. Open **Admin > Settings**
2. Select the **LLM Provider** from the dropdown
3. The **Model** dropdown auto-populates with available models
4. Enter the **API Key** if not already set (shown as "Set" badge if configured)
5. Click **"Test"** to verify connectivity
6. Click **"Save Changes"**
### 8.3 Configuring Image Provider
1. Select the **Image Provider** from the dropdown
2. Ensure the required API key is set (e.g., Google API key for NanoBanana Pro)
3. Save changes
### 8.4 Connection Testing
The **"Test"** button performs a lightweight API call to validate the key:
| Result | Display |
|--------|---------|
| Success | Green check + latency in ms |
| Failure | Red X + error message |
### 8.5 Settings Persistence
Settings are persisted to the database via `KeyValueSqlModel`:
- Survive container restarts
- API keys optionally encrypted at rest (if `SETTINGS_ENCRYPTION_KEY` is set)
- Environment variables serve as defaults — database values override them
---
## 9. Database Administration
### 9.1 Schema Overview
```mermaid
erDiagram
UserModel ||--o{ TeamMembershipModel : "belongs to"
TeamModel ||--o{ TeamMembershipModel : "has"
ClientModel ||--o{ TeamModel : "owns"
ClientModel ||--o{ BrandConfigModel : "has"
ClientModel ||--o{ MasterDeckModel : "has"
UserModel ||--o{ PresentationModel : "creates"
ClientModel ||--o{ PresentationModel : "scopes"
PresentationModel ||--o{ SlideModel : "contains"
PresentationModel ||--o{ JobModel : "tracks"
UserModel ||--o{ AuditLogModel : "generates"
UserModel ||--o{ AIUsageModel : "tracks"
MasterDeckModel ||--o{ TemplateModel : "registers as"
TemplateModel ||--o{ PresentationLayoutCodeModel : "has layouts"
```
### 9.2 Core Tables
| Table | Purpose | Key Fields |
|-------|---------|------------|
| `usermodel` | User accounts | id, email, role, azure_oid, is_active |
| `clientmodel` | Tenant organizations | id, name, slug, retention_days, review_policy |
| `teammodel` | Team groupings | id, name, client_id, is_default |
| `teammembershipmodel` | User↔Team links | user_id, team_id, assigned_by |
| `presentationmodel` | Presentations | id, title, owner_id, client_id, status, content |
| `slidemodel` | Individual slides | id, presentation, index, content, layout |
| `jobmodel` | Background jobs | id, job_type, status, progress, error_message |
| `masterdeck` | Master PPTX decks | id, client_id, layouts (JSON), parse_status |
| `templatemodel` | Registered templates | id, name, description |
| `presentationlayoutcodemodel` | Layout TSX code | presentation, layout_name, layout_code |
| `brandconfigmodel` | Brand settings | client_id, colors, fonts, logos, voice_rules |
| `auditlogmodel` | Audit trail | user_id, action, resource_type, ip_address |
| `aiusagemodel` | AI usage metrics | provider, model, tokens, duration_ms |
| `keyvaluesqlmodel` | KV settings store | key, value (JSON) |
| `imageasset` | Generated images | id, path, is_uploaded |
### 9.3 Alembic Migrations
```bash
# View migration history
docker compose exec api alembic history
# Apply all pending migrations
make migrate
# or: docker compose exec api alembic upgrade head
# Generate new migration after model changes
docker compose exec api alembic revision --autogenerate -m "description"
# Rollback last migration
docker compose exec api alembic downgrade -1
# View current revision
docker compose exec api alembic current
```
Always review auto-generated migrations before applying. SQLAlchemy may miss rename operations (interpreting them as drop + create) or produce incorrect defaults.
### 9.4 Direct Database Access
```bash
# Interactive psql
make shell-db
# Common queries
SELECT COUNT(*) FROM usermodel;
SELECT COUNT(*) FROM presentationmodel WHERE deleted_at IS NULL;
SELECT status, COUNT(*) FROM jobmodel GROUP BY status;
SELECT provider, SUM(total_tokens) FROM aiusagemodel GROUP BY provider;
```
---
## 10. Background Jobs & Workers
### 10.1 ARQ Worker Configuration
```mermaid
flowchart LR
RD[(Redis)] --> W[ARQ Worker]
W --> A[generate_presentation]
W --> B[parse_master_deck]
W --> C[Cron: cleanup 2AM / purge Mon 3AM]
```
| Setting | Value | Description |
|---------|-------|-------------|
| `max_jobs` | 5 | Maximum concurrent background jobs |
| `job_timeout` | 1800s (30 min) | Per-job timeout |
| `max_tries` | 3 | Retry attempts on failure |
| `health_check_interval` | 30s | Health check frequency |
### 10.2 Job Types
#### Presentation Generation (`generate_presentation_task`)
1. Load request from PresentationModel
2. Fetch brand context (colors, fonts, voice rules)
3. Generate outlines via LLM
4. Generate per-slide structure and content
5. Run image generation for each slide
6. Save results to database
7. Update JobModel progress (0–100%)
#### Master Deck Parsing (`parse_master_deck_task`)
1. Extract XML layouts/slides from PPTX
2. Convert PPTX to PDF via LibreOffice
3. Split PDF into per-page screenshots
4. Send each screenshot + XML to LLM vision
5. Store generated React TSX code
6. Register as template
#### Retention Cleanup (Cron — Daily)
- Soft-deletes presentations exceeding client's `retention_days`
- Runs at 2:00 AM UTC
#### Retention Purge (Cron — Weekly)
- Permanently deletes files for presentations soft-deleted 30+ days ago
- Runs Monday 3:00 AM UTC
### 10.3 Monitoring Jobs
```bash
# View worker logs
docker compose logs -f worker
# Check job status in database
make shell-db
# Then: SELECT id, job_type, status, progress, error_message FROM jobmodel ORDER BY created_at DESC LIMIT 20;
```
### 10.4 Common Job Issues
| Issue | Cause | Solution |
|-------|-------|----------|
| Job stuck at 0% | Worker crashed or no workers running | Restart worker: `docker compose restart worker` |
| Job times out | LLM response too slow | Increase `job_timeout` in WorkerSettings |
| Job fails repeatedly | Invalid API key or model | Check Settings page, test connection |
| Queue backed up | Too many concurrent requests | Scale workers horizontally |
---
## 11. Nginx & Networking
### 11.1 Routing Rules
```mermaid
flowchart LR
A[Request :80] --> B{Path}
B -->|/api/v1/*| C[FastAPI :8000]
B -->|/app_data/*| D[Static files]
B -->|/static/*| D
B -->|/* catch-all| E[Next.js :3000]
```
### 11.2 Key Configuration
| Setting | Value | Purpose |
|---------|-------|---------|
| `client_max_body_size` | 100M | Allow large PPTX uploads |
| `proxy_read_timeout` | 30m | Long-running LLM operations |
| `proxy_connect_timeout` | 30m | Connection establishment |
| `proxy_buffering` | off | SSE streaming support |
| `chunked_transfer_encoding` | off | SSE streaming support |
### 11.3 SSL/TLS (Production)
The default `nginx.conf` serves HTTP only. For production, add SSL:
```nginx
server {
listen 443 ssl http2;
ssl_certificate /etc/ssl/certs/your-cert.pem;
ssl_certificate_key /etc/ssl/private/your-key.pem;
# ... existing location blocks ...
}
server {
listen 80;
return 301 https://$host$request_uri;
}
```
### 11.4 Inter-Service Communication
```mermaid
flowchart LR
API["api :8000"] -->|DATABASE_URL| PG["postgres :5432"]
API -->|REDIS_URL| RD["redis :6379"]
API -->|NEXT_INTERNAL_URL| WEB["web :3000"]
WORKER["worker"] -->|DATABASE_URL| PG
WORKER -->|REDIS_URL| RD
WEB -->|API_INTERNAL_URL| API
NG["nginx :80"] --> API
NG --> WEB
```
All services communicate via Docker Compose's internal network. No ports need to be exposed to the host except:
- **80** (nginx) — user access
- **5432** (postgres) — optional, for direct DB access
- **6379** (redis) — optional, for debugging
---
## 12. Storage & File Management
### 12.1 Directory Structure
```
/app_data/
├── images/ # AI-generated images (UUID-named PNG files)
├── exports/ # Generated PPTX/PDF export files
├── uploads/ # User-uploaded documents (DOCX, PDF, TXT)
├── fonts/ # Custom font files
└── master_decks/ # Master deck PPTX files and screenshots
└── {deck_id}/
├── original.pptx
├── screenshots/
│ ├── page_1.png
│ ├── page_2.png
│ └── ...
└── pdf/
└── deck.pdf
```
### 12.2 File Serving
| Context | Serving Method |
|---------|---------------|
| **Docker (production)** | nginx serves `/app_data/` directly from volume |
| **Local development** | FastAPI `StaticFiles` mount on `/app_data` |
| **Frontend access** | Next.js rewrites `/app_data/*` to backend |
### 12.3 Retention Policy
```mermaid
flowchart LR
A[Presentation Created] -->|retention_days exceeded| B[Soft Delete
deleted_at = now]
B -->|30 days later| C[Hard Purge
Files removed from disk]
```
- **Retention days** configured per client in `ClientModel.retention_days`
- **Soft delete** runs daily at 2:00 AM UTC (sets `deleted_at` timestamp)
- **Hard purge** runs weekly Monday at 3:00 AM UTC (removes files for items soft-deleted 30+ days ago)
- **Manual purge** available via Admin > Storage > "Purge Files" button
### 12.4 Disk Space Monitoring
Monitor the `app_data` volume:
```bash
# Check volume usage
docker compose exec api du -sh /app_data/*
# Check available disk space
docker compose exec api df -h /app_data
```
---
## 13. Monitoring & Logging
### 13.1 Log Access
```bash
# All services
make logs
# Specific service
docker compose logs -f api
docker compose logs -f worker
docker compose logs -f postgres
docker compose logs -f web
# Last N lines
docker compose logs --tail=100 api
```
### 13.2 Audit Trail
The `AuditMiddleware` automatically logs all mutating API requests:
| Field | Content |
|-------|---------|
| `user_id` | Authenticated user's UUID |
| `action` | Operation name (e.g., "admin_delete") |
| `resource_type` | Entity type (e.g., "presentation") |
| `resource_id` | Entity UUID |
| `client_id` | Tenant context |
| `details` | JSON with request/response metadata |
| `ip_address` | Client IP address |
| `created_at` | Timestamp (indexed for fast queries) |
Audit logging is fire-and-forget (non-blocking) via `asyncio.create_task()`.
### 13.3 AI Usage Tracking
The `AIUsageModel` tracks all LLM API calls:
| Metric | Description |
|--------|-------------|
| Provider | anthropic, openai, google, ollama |
| Model | Specific model ID |
| Call Type | outline, content, vision, etc. |
| Input Tokens | Tokens sent to the model |
| Output Tokens | Tokens received |
| Duration (ms) | Call latency |
| Error Details | Error message if failed |
View aggregated metrics at **Admin > Analytics > AI Usage**.
### 13.4 Health Checks
| Service | Method | Interval | Timeout |
|---------|--------|----------|---------|
| PostgreSQL | `pg_isready` | 5s | 5s, 5 retries |
| Redis | `redis-cli ping` | 5s | 5s, 5 retries |
| API | Lifespan startup | At boot | — |
| Worker | ARQ health check | 30s | — |
---
## 14. Backup & Recovery
### 14.1 Backup Components
| Component | Location | Strategy |
|-----------|----------|----------|
| Database | `postgres_data` volume | `pg_dump` to file |
| Redis | `redis_data` volume | Optional (transient queue data) |
| Files | `app_data` volume | File-level backup or snapshot |
| Settings | Database (KeyValueSqlModel) | Included in pg_dump |
| Migrations | `backend/migrations/` | In git repository |
### 14.2 Database Backup
```bash
# Full database dump
docker compose exec postgres pg_dump -U deckforge deckforge > backup_$(date +%Y%m%d).sql
# Compressed backup
docker compose exec postgres pg_dump -U deckforge deckforge | gzip > backup_$(date +%Y%m%d).sql.gz
# Restore from backup
docker compose exec -T postgres psql -U deckforge deckforge < backup.sql
```
### 14.3 File Backup
```bash
# Backup app_data volume
docker run --rm -v ppt-tool_app_data:/data -v $(pwd):/backup alpine \
tar czf /backup/app_data_$(date +%Y%m%d).tar.gz -C /data .
# Restore app_data
docker run --rm -v ppt-tool_app_data:/data -v $(pwd):/backup alpine \
tar xzf /backup/app_data_YYYYMMDD.tar.gz -C /data
```
### 14.4 Disaster Recovery
```mermaid
flowchart LR
A[Deploy Stack] --> B[Restore DB] --> C[alembic upgrade] --> D[Restore app_data] --> E[Start Services] --> F[Verify]
```
1. Deploy fresh Docker Compose stack
2. Start PostgreSQL and Redis first
3. Restore database from latest `pg_dump`
4. Run `alembic upgrade head` to ensure schema is current
5. Restore `app_data` volume from backup
6. Start remaining services
7. Verify data integrity via Admin Panel
---
## 15. Security Hardening
### 15.1 Production Checklist
Before deploying to production, complete every item:
| Item | Action | Priority |
|------|--------|----------|
| JWT Secret | Change `JWT_SECRET_KEY` to a random 256-bit key | Critical |
| Dev Auth | Set `AZURE_AD_TENANT_ID` to disable dev bypass | Critical |
| CORS | Restrict `allow_origins` from `*` to your domain | High |
| SSL/TLS | Configure nginx with SSL certificates | High |
| Database Password | Use a strong `POSTGRES_PASSWORD` | High |
| API Keys | Set `SETTINGS_ENCRYPTION_KEY` for at-rest encryption | High |
| Port Exposure | Remove host port mappings for postgres/redis | Medium |
| Secrets | Move `.env` to Docker secrets or vault | Medium |
| Rate Limiting | Add nginx rate limiting rules | Medium |
| Monitoring | Set up external monitoring and alerting | Medium |
### 15.2 CORS Configuration
The default CORS configuration allows all origins. For production, modify `backend/api/main.py`:
```python
origins = [
"https://your-domain.com",
"https://app.your-domain.com",
]
```
### 15.3 API Key Encryption
Enable at-rest encryption for stored API keys:
```bash
# Generate a Fernet key
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
# Add to .env
SETTINGS_ENCRYPTION_KEY=your-generated-fernet-key
```
### 15.4 Middleware Execution Order
```mermaid
flowchart LR
A[Request] --> B[1.CORS] --> C[2.Auth] --> D[3.Audit] --> E[4.UserConfig] --> F[Handler]
```
Middlewares are added in reverse order in FastAPI (last added = first executed).
---
## 16. Scaling & Performance
### 16.1 Horizontal Scaling
```mermaid
flowchart LR
LB[Load Balancer] --> API[API x N]
API --> PG[(Managed PostgreSQL)]
API --> RD[(Managed Redis)]
W[Workers x N] --> PG
W --> RD
API --> S3[S3 / CDN]
W --> S3
```
| Component | Scaling Strategy |
|-----------|-----------------|
| **API** | Run multiple replicas behind load balancer |
| **Worker** | Run multiple instances (ARQ handles job locking) |
| **PostgreSQL** | Use managed service (RDS, Cloud SQL) with read replicas |
| **Redis** | Use managed service (ElastiCache) with clustering |
| **Files** | Replace local volume with S3 + CDN |
| **nginx** | Replace with cloud load balancer (ALB, Cloud Load Balancing) |
### 16.2 Vertical Scaling
| Setting | Location | Effect |
|---------|----------|--------|
| `max_jobs` | `WorkerSettings` | More concurrent background jobs |
| `job_timeout` | `WorkerSettings` | Allow longer-running LLM operations |
| `worker_connections` | `nginx.conf` | More concurrent connections |
| `client_max_body_size` | `nginx.conf` | Larger file uploads |
### 16.3 Performance Optimization
- **Database indexes** already exist on:
- `auditlogmodel.created_at`
- `teammembershipmodel(user_id, team_id)` (unique)
- `usermodel.email` (unique)
- `clientmodel.slug` (unique)
- **Async operations**: All database queries use `asyncpg` (async PostgreSQL driver)
- **Sync LLM calls**: Wrapped in `asyncio.to_thread()` to avoid blocking the event loop
- **SSE streaming**: Server-Sent Events for real-time progress (no polling overhead)
- **Fire-and-forget audit**: Audit logs don't block request processing
---
## 17. Troubleshooting
### 17.1 Common Issues
| Issue | Cause | Solution |
|-------|-------|----------|
| API won't start | PostgreSQL not ready | Wait for health check (15–25s) |
| Worker jobs not processing | Redis down or worker crashed | `docker compose restart worker` |
| Auth failures after restart | JWT_SECRET_KEY changed | All users must re-login |
| File uploads fail | Disk full or wrong permissions | Check `du -sh /app_data/*` and permissions |
| PPTX export 500 | Puppeteer / Chromium issue | Restart web service, check memory |
| Slide edit timeout | LLM response too slow | Check provider status, increase timeouts |
| Master deck stuck "processing" | Worker died during parse | Restart worker, reparse the deck |
| Images not showing | Static files not served | Check FastAPI mounts and nginx config |
| SSE not working | Proxy buffering enabled | Ensure nginx `proxy_buffering off` |
### 17.2 Diagnostic Commands
```bash
# Service health
docker compose ps
# Container resource usage
docker stats
# API application logs
docker compose logs --tail=50 api
# Worker job processing logs
docker compose logs --tail=50 worker
# Database connection check
docker compose exec postgres pg_isready -U deckforge
# Redis connectivity
docker compose exec redis redis-cli ping
# Database query — check failed jobs
docker compose exec postgres psql -U deckforge -c \
"SELECT id, job_type, status, error_message FROM jobmodel WHERE status='failed' ORDER BY created_at DESC LIMIT 10;"
# Disk usage
docker compose exec api du -sh /app_data/*
```
### 17.3 Resetting the System
```bash
# Full reset (WARNING: destroys all data)
docker compose down -v
docker compose up --build -d
make migrate
make seed
```
---
## 18. API Reference
### 18.1 Authentication Endpoints
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/auth/login` | Redirect to Azure AD login |
| GET | `/api/v1/auth/callback` | OAuth callback handler |
| POST | `/api/v1/auth/dev-login` | Dev mode authentication |
| GET | `/api/v1/auth/dev-status` | Check if dev mode is enabled |
| POST | `/api/v1/auth/logout` | Clear session |
| GET | `/api/v1/auth/me` | Current user info |
### 18.2 Admin Endpoints
| Method | Path | Access |
|--------|------|--------|
| GET/PUT/DELETE | `/api/v1/admin/users/*` | Super Admin |
| POST/GET/PUT/DELETE | `/api/v1/admin/clients/*` | Admin |
| POST/GET/DELETE | `/api/v1/admin/teams/*` | Admin |
| GET/PUT | `/api/v1/admin/settings` | Super Admin |
| GET/POST | `/api/v1/admin/settings/models` | Super Admin |
| POST | `/api/v1/admin/settings/test-connection` | Super Admin |
| GET/DELETE/POST | `/api/v1/admin/storage/*` | Admin |
| GET | `/api/v1/admin/analytics/*` | Admin |
| GET | `/api/v1/admin/audit-log` | Admin |
| GET/PUT/POST/DELETE | `/api/v1/admin/master-decks/*` | Admin |
| GET/PUT/POST/DELETE | `/api/v1/admin/brand-config/*` | Admin |
### 18.3 Presentation Endpoints
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/ppt/presentation/create` | Create new presentation |
| GET | `/api/v1/ppt/presentation/all` | List presentations |
| GET | `/api/v1/ppt/presentation/{id}` | Get presentation detail |
| PUT | `/api/v1/ppt/presentation/{id}` | Update presentation |
| DELETE | `/api/v1/ppt/presentation/{id}` | Delete presentation |
| POST | `/api/v1/ppt/presentation/decompose` | Decompose content |
| POST | `/api/v1/ppt/presentation/prepare` | Prepare for generation |
| GET | `/api/v1/ppt/presentation/{id}/review` | Get review status |
| PUT | `/api/v1/ppt/presentation/{id}/status` | Change review status |
| POST | `/api/v1/ppt/presentation/{id}/comment` | Add review comment |
| POST | `/api/v1/ppt/jobs/generate` | Start generation job |
| GET | `/api/v1/ppt/jobs/{id}/status` | Job status (SSE) |
| POST | `/api/v1/ppt/export/pptx` | Export as PPTX |
| POST | `/api/v1/ppt/export/pdf` | Export as PDF |
---
**Oliver DeckForge** | System Administration Guide
Version 1.0 | © 2026 All Rights Reserved