--- phase: 01-foundation plan: 02 type: execute wave: 2 depends_on: ["01-01"] files_modified: - src/db/QuillContext.scala - src/db/Migrator.scala - resources/db/migration/V1__create_summer_users.sql - src/api/HealthRoutes.scala autonomous: true must_haves: truths: - "Quill compiles SQL queries at compile time (type errors for bad queries)" - "Flyway migrations run via ZIO effect" - "Migration creates summer_users table in PostgreSQL" - "GET /ready returns 200 when database is connected, 503 when not" artifacts: - path: "src/db/QuillContext.scala" provides: "Quill PostgreSQL context with ZIO integration" exports: ["quillLayer", "dataSourceLayer"] - path: "src/db/Migrator.scala" provides: "Flyway wrapper as ZIO service" exports: ["Migrator", "MigrationStatus"] - path: "resources/db/migration/V1__create_summer_users.sql" provides: "Initial database schema" contains: "CREATE TABLE summer_users" key_links: - from: "src/db/QuillContext.scala" to: "resources/application.conf" via: "Quill.DataSource.fromPrefix" pattern: "fromPrefix.*database" - from: "src/db/Migrator.scala" to: "src/db/QuillContext.scala" via: "ZLayer dependency on DataSource" pattern: "DataSource" - from: "src/api/HealthRoutes.scala" to: "src/db/QuillContext.scala" via: "/ready endpoint checks connection" pattern: "/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 and a /ready endpoint that verifies connectivity. @/home/jin/.claude/get-shit-done/workflows/execute-plan.md @/home/jin/.claude/get-shit-done/templates/summary.md @.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: ```hocon 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) 2. resources/db/migration/V1__create_summer_users.sql: ```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: ```scala 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 - On startup, run Migrator.migrate and log result - Startup sequence: migrate -> start server Note: The /ready endpoint will return 503 if no database is running, which is correct behavior. 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 and runs migrations - Logs show "Applied N migrations" - `curl localhost:8080/ready` returns "ready" 3. Check database: `psql -d summercms -c "\\dt"` shows summer_users table 4. Check migrations table: `psql -d summercms -c "SELECT * FROM summer_migrations"` - Quill context exists with PostgreSQL configuration - Flyway migrator runs as ZIO effect on startup - 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) After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md`