--- 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 After completion, create `.planning/phases/01-core-infrastructure-security/01-02-SUMMARY.md`