- Add asyncpg connection pool (db/pool.py) with JSONB codec registration - Add schema.sql with users, clients, dropdown_categories, export_templates, sheets tables - Add migrate_json.py one-time migration script for existing JSON data - Rewrite user_store, sheets/manager, api/clients, api/dropdowns, api/export as async DB-backed - Update all callers (auth, sheets, admin, ai_command, export) to await async functions - Add postgres:16-alpine service to docker-compose with named volume and health check - App container depends_on postgres; DATABASE_URL injected via env - Schema applied automatically on startup; global categories seeded if DB is empty Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
139 lines
5.2 KiB
YAML
139 lines
5.2 KiB
YAML
# Apache reverse proxy config (add inside your VirtualHost block):
|
|
#
|
|
# # Required modules: a2enmod proxy proxy_http proxy_wstunnel
|
|
#
|
|
# # Proxy API requests to the Docker container
|
|
# <Location /ac-helper/api/>
|
|
# ProxyPass http://localhost:8100/api/
|
|
# ProxyPassReverse http://localhost:8100/api/
|
|
# </Location>
|
|
#
|
|
# # Proxy WebSocket
|
|
# ProxyPass /ac-helper/ws ws://localhost:8100/ws
|
|
# ProxyPassReverse /ac-helper/ws ws://localhost:8100/ws
|
|
#
|
|
# # Serve frontend static files directly from disk
|
|
# Alias /ac-helper/ /var/www/html/ac-helper/
|
|
# <Directory /var/www/html/ac-helper>
|
|
# Options -Indexes
|
|
# AllowOverride None
|
|
# Require all granted
|
|
# FallbackResource /ac-helper/index.html
|
|
# </Directory>
|
|
#
|
|
# Apache serves static files; Docker handles /api and /ws only.
|
|
# APP_PORT in .env controls the host port (default: 8100).
|
|
|
|
version: '3.9'
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
container_name: ac-tool-db
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_DB: achelper
|
|
POSTGRES_USER: achelper
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-achelper_secret}
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U achelper -d achelper"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
app:
|
|
build: .
|
|
container_name: ac-tool
|
|
restart: unless-stopped
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
ports:
|
|
- "${APP_PORT:-8100}:8000"
|
|
volumes:
|
|
- ./data:/app/data
|
|
environment:
|
|
DATABASE_URL: postgresql://achelper:${POSTGRES_PASSWORD:-achelper_secret}@postgres:5432/achelper
|
|
# Auth — AZURE_* names in .env, MSAL_* names read by server/config_runtime.py
|
|
AZURE_TENANT_ID: ${AZURE_TENANT_ID:-e519c2e6-bc6d-4fdf-8d9c-923c2f002385}
|
|
AZURE_CLIENT_ID: ${AZURE_CLIENT_ID:-9079054c-9620-4757-a256-23413042f1ef}
|
|
AZURE_REDIRECT_URI: ${AZURE_REDIRECT_URI:-https://ai-sandbox.oliver.solutions/ac-helper/}
|
|
MSAL_TENANT_ID: ${AZURE_TENANT_ID:-e519c2e6-bc6d-4fdf-8d9c-923c2f002385}
|
|
MSAL_CLIENT_ID: ${AZURE_CLIENT_ID:-9079054c-9620-4757-a256-23413042f1ef}
|
|
MSAL_REDIRECT_URI: ${AZURE_REDIRECT_URI:-https://ai-sandbox.oliver.solutions/ac-helper/}
|
|
SESSION_SECRET: ${SESSION_SECRET:-change-me-in-production}
|
|
|
|
# Dev mode (set to false in production)
|
|
DEV_MODE: ${DEV_MODE:-false}
|
|
DEV_USER_ID: ${DEV_USER_ID:-dev-user-001}
|
|
DEV_USER_ROLE: ${DEV_USER_ROLE:-admin}
|
|
|
|
# Admin bootstrap
|
|
ADMIN_EMAIL: ${ADMIN_EMAIL:-daveporter@oliver.agency}
|
|
ADMIN_EMAILS: ${ADMIN_EMAILS:-daveporter@oliver.agency,vadymsamoilenko@oliver.agency}
|
|
|
|
# Emergency access (bypass SSO) — set a long random string to enable
|
|
EMERGENCY_TOKEN: ${EMERGENCY_TOKEN:-}
|
|
EMERGENCY_USER_EMAIL: ${EMERGENCY_USER_EMAIL:-daveporter@oliver.agency}
|
|
EMERGENCY_USER_NAME: ${EMERGENCY_USER_NAME:-Emergency Access}
|
|
|
|
# OpenAI
|
|
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
|
OPENAI_MODEL: ${OPENAI_MODEL:-gpt-4.1}
|
|
OPENAI_REASONING_EFFORT: ${OPENAI_REASONING_EFFORT:-medium}
|
|
OPENAI_TIMEOUT: ${OPENAI_TIMEOUT:-3600}
|
|
OPENAI_MAX_RETRIES: ${OPENAI_MAX_RETRIES:-2}
|
|
|
|
# Google Gemini
|
|
GEMINI_API_KEY: ${GEMINI_API_KEY:-}
|
|
GEMINI_MODEL: ${GEMINI_MODEL:-gemini-3-flash-preview}
|
|
GOOGLE_MODEL: ${GOOGLE_MODEL:-gemini-3.1-pro-preview}
|
|
GOOGLE_TEMPERATURE: ${GOOGLE_TEMPERATURE:-0.7}
|
|
GOOGLE_MAX_OUTPUT_TOKENS: ${GOOGLE_MAX_OUTPUT_TOKENS:-100000}
|
|
GOOGLE_THINKING_BUDGET: ${GOOGLE_THINKING_BUDGET:-12000}
|
|
GOOGLE_TIMEOUT: ${GOOGLE_TIMEOUT:-3600}
|
|
|
|
# Anthropic
|
|
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
|
ANTHROPIC_MODEL_OPUS: ${ANTHROPIC_MODEL_OPUS:-claude-opus-4-5-20251101}
|
|
ANTHROPIC_MODEL_SONNET: ${ANTHROPIC_MODEL_SONNET:-claude-sonnet-4-5-20250929}
|
|
ANTHROPIC_TEMPERATURE: ${ANTHROPIC_TEMPERATURE:-1}
|
|
ANTHROPIC_MAX_TOKENS: ${ANTHROPIC_MAX_TOKENS:-32000}
|
|
ANTHROPIC_THINKING_BUDGET: ${ANTHROPIC_THINKING_BUDGET:-12000}
|
|
ANTHROPIC_TIMEOUT: ${ANTHROPIC_TIMEOUT:-300}
|
|
|
|
# LlamaCloud
|
|
LLAMA_CLOUD_API_KEY: ${LLAMA_CLOUD_API_KEY:-}
|
|
|
|
# Brief extraction
|
|
DEFAULT_PRIMARY_MODELS: ${DEFAULT_PRIMARY_MODELS:-anthropic-sonnet45,google-gemini20}
|
|
DEFAULT_CONSOLIDATION_MODEL: ${DEFAULT_CONSOLIDATION_MODEL:-anthropic-sonnet45}
|
|
MINIMUM_SUCCESS_THRESHOLD: ${MINIMUM_SUCCESS_THRESHOLD:-1}
|
|
ENABLE_COST_ESTIMATION: ${ENABLE_COST_ESTIMATION:-true}
|
|
MAX_PROCESSING_COST_USD: ${MAX_PROCESSING_COST_USD:-10.00}
|
|
MAX_CONCURRENT_JOBS: ${MAX_CONCURRENT_JOBS:-5}
|
|
|
|
# File upload
|
|
MAX_UPLOAD_SIZE_MB: ${MAX_UPLOAD_SIZE_MB:-200}
|
|
FILE_RETENTION_HOURS: ${FILE_RETENTION_HOURS:-24}
|
|
WS_PING_INTERVAL_SECONDS: ${WS_PING_INTERVAL_SECONDS:-30}
|
|
|
|
# Paths
|
|
DATA_DIR: /app/data
|
|
UPLOADS_DIR: /app/data/uploads
|
|
OUTPUTS_DIR: /app/data/outputs
|
|
SHEETS_DIR: /app/data/sheets
|
|
USERS_FILE: /app/data/users.json
|
|
DROPDOWNS_FILE: /app/data/dropdowns.json
|
|
|
|
healthcheck:
|
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
volumes:
|
|
postgres_data:
|