Files
ai.allucanget.biz/docs/8-crosscutting-concepts.md
T

4.8 KiB

8. Cross-cutting Concepts

Describes crosscutting concepts (practices, patterns, regulations or solution ideas). Such concepts are often related to multiple building blocks. They may include many different topics such as domain models, architecture patterns, rules for using specific technology, security, logging, and error handling.

Pick only the most-needed topics for your system.

Security

  • All API endpoints (except /auth/login) require a valid JWT in the Authorization: Bearer header.
  • HTTPS enforced in production via reverse proxy (nginx or Caddy).
  • Passwords stored as bcrypt hashes.

Logging

  • Structured JSON logs from FastAPI via Python logging + structlog.
  • OpenTelemetry traces exported for observability.
  • Log level configurable via environment variable LOG_LEVEL.

Error Handling

  • All API errors return a unified JSON shape: { "error": "<code>", "message": "<description>" }.
  • HTTP status codes follow REST conventions (400, 401, 403, 404, 422, 500).

Configuration

  • All secrets (API keys, DB path, JWT secret) loaded from environment variables or .env file.
  • No secrets committed to source control.

DuckDB Concurrency and Storage

Single Writer Per Process

DuckDB allows only one process to open the database file in read-write mode at a time. The FastAPI backend must be run with a single worker (uvicorn --workers 1). Running multiple workers against the same DuckDB file will cause startup errors.

asyncio.Lock for Writes

All database write operations (INSERT, UPDATE, DELETE) in the FastAPI async context are wrapped in a single asyncio.Lock (get_write_lock() from backend/app/db.py). This prevents concurrent coroutines from issuing overlapping writes within the single process, which would otherwise raise DuckDB optimistic concurrency errors.

Read operations (SELECT) do not require the lock — DuckDB's MVCC provides consistent read snapshots.

Schema

CREATE TABLE users (
    id            UUID      DEFAULT uuid() PRIMARY KEY,
    email         VARCHAR   NOT NULL UNIQUE,
    password_hash VARCHAR   NOT NULL,
    role          VARCHAR   DEFAULT 'user',
    created_at    TIMESTAMP DEFAULT now(),
    updated_at    TIMESTAMP DEFAULT now()
);

CREATE TABLE refresh_tokens (
    jti        UUID      DEFAULT uuid() PRIMARY KEY,
    user_id    UUID      NOT NULL,           -- soft FK to users.id
    issued_at  TIMESTAMP DEFAULT now(),
    expires_at TIMESTAMP NOT NULL,
    revoked    BOOLEAN   DEFAULT false
);

The REFERENCES users(id) foreign key is intentionally omitted from refresh_tokens. DuckDB fires FK checks on UPDATE of the parent table (including email changes), causing false constraint violations. Referential integrity is enforced manually: deleting a user also deletes their refresh tokens in the same write transaction.

Access Tokens

Access tokens are stateless JWTs — not stored in the database. They are validated by signature and expiry claim only. The short TTL (15 minutes) limits the blast radius if a token is leaked.

Refresh Tokens

Refresh tokens store a JTI (JWT ID) UUID in the refresh_tokens table. On each use the old JTI is revoked and a new one issued (rotation). On logout the JTI is immediately revoked. Expired and revoked tokens can be purged via POST /admin/tokens/purge.

Future: AI Generation History

AI generation metadata (model, prompt, cost, result URLs) can be stored as JSON columns in a future generation_history table in DuckDB, enabling per-user analytics and usage dashboards at zero extra infrastructure cost.

OpenRouter API Integration

Image Generation

Image generation uses two different OpenRouter endpoints depending on the model:

  • Legacy endpoint (/images/generations): Used by DALL-E 3 and similar models. Returns data[].url and data[].b64_json.
  • Chat completions (/chat/completions with modalities: ["image"]): Used by FLUX.2 Klein 4B and GPT-5 Image Mini. Returns choices[0].message.images[].image_url.url as base64 data URLs.

The router auto-detects the model type and routes accordingly. Image configuration (aspect_ratio, image_size) is passed via image_config for chat-based models.

Video Generation

Video generation uses OpenRouter's /api/v1/videos endpoint with a submit-and-poll pattern:

  1. POST /api/v1/videos with model, prompt, aspect_ratio, resolution, duration_seconds
  2. Response: {"id": "job_id", "polling_url": "https://..."} with status: "queued"
  3. Poll GET polling_url every 5 seconds until status is "completed" or "failed"
  4. Completed response includes unsigned_urls: [str] array with video download URLs

Supported models: openai/sora-2-pro, google/veo-3.1-fast. Both text-to-video and image-to-video use the same /api/v1/videos endpoint (image-to-video includes image_url in the request body).