Files
headquarter/openspec/changes/archive/2026-05-17-database-models/design.md
T

2.9 KiB

Design: Database Models

Technology Choices

  • SQLAlchemy 2.0: Modern async ORM with type annotations
  • asyncpg: High-performance async PostgreSQL driver
  • Alembic: Database migration tool
  • UUID: All primary keys use UUID for distributed safety

Architecture

Base Model

All models inherit from a common base with:

  • id: UUID primary key (default=uuid4)
  • created_at: Timestamp
  • updated_at: Timestamp (auto-updated)

Models

  1. User

    • id: UUID PK
    • email: str, unique, indexed
    • name: str
    • authentik_id: str, unique (external auth reference)
    • avatar_url: str | None
    • created_at, updated_at
  2. Project

    • id: UUID PK
    • name: str
    • description: str | None
    • owner_id: UUID → User
    • default_ssh_key_id: UUID → SSHKey | None
    • created_at, updated_at
  3. GitRepository

    • id: UUID PK
    • name: str
    • path: str (filesystem path to bare repo)
    • project_id: UUID → Project
    • owner_id: UUID → User
    • is_mirror: bool
    • remote_url: str | None
    • last_push: datetime | None
    • created_at
  4. SSHKey

    • id: UUID PK
    • name: str
    • public_key: str
    • private_key_encrypted: str (Fernet encrypted)
    • user_id: UUID → User
    • project_id: UUID → Project | None
    • created_at
  5. UserConfig

    • id: UUID PK
    • user_id: UUID → User
    • config: JSONB (PostgreSQL native JSON)
    • created_at, updated_at

Relationships

User 1--N Project
User 1--N SSHKey
User 1--1 UserConfig
Project 1--N GitRepository
Project N--1 SSHKey (default_ssh_key)

File Structure

apps/api/
├── src/
│   ├── models/
│   │   ├── __init__.py
│   │   ├── base.py          # DeclarativeBase + common columns
│   │   ├── user.py
│   │   ├── project.py
│   │   ├── git_repository.py
│   │   ├── ssh_key.py
│   │   └── user_config.py
│   ├── database.py          # Async engine + session
│   └── config.py            # Settings with pydantic-settings
├── alembic/
│   ├── env.py
│   ├── script.py.mako
│   └── versions/
│       └── 001_initial.py
├── tests/
│   └── test_models.py
└── scripts/
    └── seed.py

Async Pattern

from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker

engine = create_async_engine(DATABASE_URL)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession)

Migration Strategy

  • Single initial migration creating all tables
  • Future migrations use alembic revision --autogenerate
  • Run with make migrate (docker compose exec api alembic upgrade head)

Seed Data

  • Create a test user with sample data
  • Run via docker compose exec api python scripts/seed.py

Quality Gates

  • pytest with async test support
  • mypy strict mode
  • ruff for linting
  • All models have type annotations