# 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 ```python 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