---
phase: 01-core-infrastructure-security
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
- backend/app/db/__init__.py
- backend/app/db/base.py
- backend/app/db/session.py
- backend/app/db/models/__init__.py
- backend/app/db/models/build.py
- backend/alembic.ini
- backend/alembic/env.py
- backend/alembic/script.py.mako
- backend/alembic/versions/.gitkeep
- docker-compose.yml
autonomous: true
must_haves:
truths:
- "PostgreSQL container starts and accepts connections"
- "Alembic migrations run without errors"
- "Database session factory creates async sessions"
- "Build model persists to database"
artifacts:
- path: "backend/app/db/session.py"
provides: "Async database session factory"
contains: "async_sessionmaker"
- path: "backend/app/db/base.py"
provides: "SQLAlchemy declarative base"
contains: "DeclarativeBase"
- path: "backend/app/db/models/build.py"
provides: "Build tracking model"
contains: "class Build"
- path: "backend/alembic/env.py"
provides: "Alembic migration environment"
contains: "run_migrations_online"
- path: "docker-compose.yml"
provides: "PostgreSQL container configuration"
contains: "postgres"
key_links:
- from: "backend/app/db/session.py"
to: "backend/app/core/config.py"
via: "settings.database_url"
pattern: "settings\\.database_url"
- from: "backend/alembic/env.py"
to: "backend/app/db/base.py"
via: "target_metadata"
pattern: "target_metadata.*Base\\.metadata"
---
Set up PostgreSQL database with async SQLAlchemy, Alembic migrations, and initial build tracking model.
Purpose: Establish the data persistence layer that tracks builds, users, and configurations.
Output: Running PostgreSQL instance, async session factory, and migration infrastructure with initial Build model.
@/home/mikkel/.claude/get-shit-done/workflows/execute-plan.md
@/home/mikkel/.claude/get-shit-done/templates/summary.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/01-core-infrastructure-security/01-RESEARCH.md (Pattern 1: Async Database Session Management, Code Examples: Database Migrations with Alembic)
Task 1: Set up PostgreSQL with Docker and async session factory
docker-compose.yml
backend/app/db/__init__.py
backend/app/db/base.py
backend/app/db/session.py
Create docker-compose.yml:
- PostgreSQL 18 service (postgres:18-alpine image if available, or postgres:16-alpine)
- Container name: debate-postgres
- Environment: POSTGRES_USER=debate, POSTGRES_PASSWORD=debate_dev, POSTGRES_DB=debate
- Port: 5432:5432
- Volume: postgres_data for persistence
- Health check on pg_isready
backend/app/db/__init__.py:
- Empty or re-export key items
backend/app/db/base.py:
- Create SQLAlchemy 2.0 DeclarativeBase
- Import all models (for Alembic autogenerate)
- Pattern: `class Base(DeclarativeBase): pass`
backend/app/db/session.py:
- Import settings from core.config
- Create async engine with connection pooling (from research):
- pool_size=10
- max_overflow=20
- pool_timeout=30
- pool_recycle=1800
- pool_pre_ping=True
- Create async_sessionmaker factory
- Create `get_db` async generator dependency for FastAPI
Update .env.example (if not already done):
- DATABASE_URL=postgresql+asyncpg://debate:debate_dev@localhost:5432/debate
Run: `cd /home/mikkel/repos/debate && docker compose up -d`
Wait 5 seconds for postgres to start.
Run: `docker compose exec postgres pg_isready -U debate`
Expected: "accepting connections"
PostgreSQL container running, async session factory configured with connection pooling.
Task 2: Configure Alembic and create Build model
backend/alembic.ini
backend/alembic/env.py
backend/alembic/script.py.mako
backend/alembic/versions/.gitkeep
backend/app/db/models/__init__.py
backend/app/db/models/build.py
Initialize Alembic in backend directory:
```bash
cd backend && alembic init alembic
```
Modify backend/alembic.ini:
- Set script_location = alembic
- Remove sqlalchemy.url (we'll set it from config)
Modify backend/alembic/env.py:
- Import asyncio, async_engine_from_config
- Import settings from app.core.config
- Import Base from app.db.base (this imports all models)
- Set sqlalchemy.url from settings.database_url
- Implement run_migrations_online() as async function (from research)
- Use asyncio.run() for async migrations
Create backend/app/db/models/__init__.py:
- Import all models for Alembic discovery
Create backend/app/db/models/build.py:
- Build model with fields:
- id: UUID primary key (use uuid.uuid4)
- config_hash: String(64), unique, indexed (SHA-256 of configuration)
- status: Enum (pending, building, completed, failed, cached)
- iso_path: Optional String (path to generated ISO)
- error_message: Optional Text (for failed builds)
- build_log: Optional Text (full build output)
- started_at: DateTime (nullable, set when build starts)
- completed_at: DateTime (nullable, set when build finishes)
- created_at: DateTime with server default now()
- updated_at: DateTime with onupdate
- Add index on status for queue queries
- Add index on config_hash for cache lookups
Update backend/app/db/base.py to import Build model.
Generate and run initial migration:
```bash
cd backend && alembic revision --autogenerate -m "Create build table"
cd backend && alembic upgrade head
```
Run: `cd /home/mikkel/repos/debate/backend && alembic current`
Expected: Shows current migration head.
Run: `docker compose exec postgres psql -U debate -d debate -c "\\dt"`
Expected: Shows "builds" table.
Alembic configured for async, Build model created with migration applied.
1. `docker compose ps` shows postgres container running and healthy
2. `cd backend && alembic current` shows migration applied
3. `docker compose exec postgres psql -U debate -d debate -c "SELECT * FROM builds LIMIT 1;"` succeeds (empty result OK)
4. `ruff check backend/app/db/` passes
5. Database has builds table with correct columns
- PostgreSQL 18 running in Docker with health checks
- Async session factory with proper connection pooling
- Alembic configured for async migrations
- Build model exists with config_hash, status, timestamps
- Initial migration applied successfully