Files
summercms-initial-research/.planning/phases/01-foundation/01-02-PLAN.md
Jakub Zych db76c5dd91 fix(01): reconcile migration policy - manual via CLI, not auto on startup
- Updated 01-02-PLAN.md Task 3 to NOT run migrations on startup
- Updated must_haves truth: 'run via ZIO effect when invoked' (not 'on startup')
- Updated verification/success_criteria to reflect manual migration
- Updated ROADMAP success criteria #3 to match CONTEXT.md decision

Per CONTEXT.md line 22-23: migrations run manually via CLI command
2026-02-04 17:25:27 +01:00

7.8 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
phase plan type wave depends_on files_modified autonomous must_haves
01-foundation 02 execute 2
01-01
src/db/QuillContext.scala
src/db/Migrator.scala
resources/db/migration/V1__create_summer_users.sql
src/api/HealthRoutes.scala
true
truths artifacts key_links
Quill compiles SQL queries at compile time (type errors for bad queries)
Flyway migrations run via ZIO effect when invoked
Migration creates summer_users table in PostgreSQL
GET /ready returns 200 when database is connected, 503 when not
path provides exports
src/db/QuillContext.scala Quill PostgreSQL context with ZIO integration
quillLayer
dataSourceLayer
path provides exports
src/db/Migrator.scala Flyway wrapper as ZIO service
Migrator
MigrationStatus
path provides contains
resources/db/migration/V1__create_summer_users.sql Initial database schema CREATE TABLE summer_users
from to via pattern
src/db/QuillContext.scala resources/application.conf Quill.DataSource.fromPrefix fromPrefix.*database
from to via pattern
src/db/Migrator.scala src/db/QuillContext.scala ZLayer dependency on DataSource DataSource
from to via pattern
src/api/HealthRoutes.scala src/db/QuillContext.scala /ready endpoint checks connection /ready
Integrate PostgreSQL via Quill with compile-time SQL validation and Flyway migrations wrapped in ZIO effects.

Purpose: Establish database connectivity with type-safe queries and version-controlled schema changes. Output: Working database layer with migrations service and a /ready endpoint that verifies connectivity.

<execution_context> @/home/jin/.claude/get-shit-done/workflows/execute-plan.md @/home/jin/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/01-foundation/01-CONTEXT.md @.planning/phases/01-foundation/01-RESEARCH.md @.planning/phases/01-foundation/01-01-SUMMARY.md Task 1: Create Quill PostgreSQL context src/db/QuillContext.scala Create Quill context for PostgreSQL with ZIO integration.

src/db/QuillContext.scala:

  • Import io.getquill.* and io.getquill.jdbczio.Quill
  • Create dataSourceLayer: ZLayer[Any, Throwable, javax.sql.DataSource]
    • Use Quill.DataSource.fromPrefix("database") to read from HOCON config
  • Create quillLayer: ZLayer[javax.sql.DataSource, Nothing, Quill.Postgres[SnakeCase]]
    • Use Quill.Postgres.fromNamingStrategy(SnakeCase)
  • Export combined layer: dataSourceLayer >>> quillLayer

IMPORTANT: Never use "io" as a variable name in this file (Quill package conflict).

Configuration note: Quill reads HikariCP config from the "database" prefix in application.conf. The existing database config structure should work, but may need adjustment to match HikariCP's expected format:

database {
  dataSourceClassName = "org.postgresql.ds.PGSimpleDataSource"
  dataSource {
    serverName = "localhost"
    portNumber = 5432
    databaseName = "summercms"
    user = "summercms"
    password = "summercms"
  }
}

Update resources/application.conf to use HikariCP-compatible format if needed. mill compile succeeds with Quill context QuillContext.scala exports dataSourceLayer and quillLayer, compiles without errors

Task 2: Create Flyway migrator service src/db/Migrator.scala resources/db/migration/V1__create_summer_users.sql Create Flyway wrapper as ZIO service and initial migration.
  1. src/db/Migrator.scala:
  • Case class MigrationStatus(current: Option[String], pending: Int)
  • Trait Migrator with methods:
    • def migrate: Task[Int] (returns count of migrations run)
    • def status: Task[MigrationStatus]
  • Object Migrator with:
    • val live: ZLayer[javax.sql.DataSource, Nothing, Migrator]
    • Implementation configures Flyway with:
      • dataSource from layer
      • locations: "classpath:db/migration"
      • table: "summer_migrations" (not default flyway_schema_history)
      • baselineOnMigrate: true (for existing databases)
  1. resources/db/migration/V1__create_summer_users.sql:
-- Summer CMS initial schema
-- Creates the base users table for future auth

CREATE TABLE summer_users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_summer_users_email ON summer_users(email);

-- Trigger for updated_at
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_summer_users_updated_at
    BEFORE UPDATE ON summer_users
    FOR EACH ROW
    EXECUTE FUNCTION update_updated_at_column();

Naming follows conventions from CONTEXT.md:

  • snake_case columns
  • summer_ prefix for core tables
  • id, created_at, updated_at by default mill compile succeeds, migration file exists at correct path Migrator service defined with migrate/status methods, V1 migration creates summer_users
Task 3: Add /ready endpoint with database check src/api/HealthRoutes.scala Update HealthRoutes to include database connectivity check.

Modify src/api/HealthRoutes.scala:

  • Keep existing GET /health -> Response.text("ok") (always 200)
  • Add GET /ready endpoint:
    • Requires DataSource from ZLayer
    • Attempts: ZIO.attempt(ds.getConnection.close())
    • Success: Response.text("ready") with 200
    • Failure: Response.status(Status.ServiceUnavailable) with 503

Pattern:

Method.GET / "ready" -> handler {
  ZIO.serviceWithZIO[javax.sql.DataSource] { ds =>
    ZIO.attempt {
      val conn = ds.getConnection
      conn.close()
    }.as(Response.text("ready"))
     .catchAll(_ => ZIO.succeed(Response.status(Status.ServiceUnavailable)))
  }
}

Update Main.scala to:

  • Provide dataSourceLayer to the server
  • DO NOT run migrations on startup (per CONTEXT.md: migrations run manually via CLI)
  • The Migrator service is available but not auto-invoked

Note: The /ready endpoint will return 503 if no database is running, which is correct behavior. Migrations will be triggered via CLI command in a later phase (Phase 5: CLI Scaffolding). With PostgreSQL running: curl localhost:8080/ready returns "ready" Without PostgreSQL: curl localhost:8080/ready returns 503 /ready endpoint exists, returns 200 when DB connected, 503 when not

1. `mill compile` succeeds (Quill context compiles) 2. With PostgreSQL running: - `mill run` starts server (does NOT auto-run migrations) - `curl localhost:8080/ready` returns "ready" 3. Migrations can be run manually via Migrator service (CLI integration in Phase 5) 4. After manual migration: `psql -d summercms -c "\\dt"` shows summer_users table 5. Check migrations table: `psql -d summercms -c "SELECT * FROM summer_migrations"`

<success_criteria>

  • Quill context exists with PostgreSQL configuration
  • Flyway migrator service exists as ZIO effect (available for CLI invocation)
  • V1 migration creates summer_users table with proper schema
  • /ready endpoint verifies database connectivity
  • Compile-time SQL validation works (intentional bad query causes compile error)
  • Server starts WITHOUT auto-running migrations </success_criteria>
After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md`