docs(01): create phase 1 foundation plans
Phase 01: Foundation - 3 plans in 3 waves (sequential dependency) - Plan 01: Mill build + ZIO HTTP server - Plan 02: PostgreSQL + Quill + Flyway migrations - Plan 03: Repository pattern + Pulumi/Besom deployment Ready for execution
This commit is contained in:
228
.planning/phases/01-foundation/01-02-PLAN.md
Normal file
228
.planning/phases/01-foundation/01-02-PLAN.md
Normal file
@@ -0,0 +1,228 @@
|
||||
---
|
||||
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"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/jin/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/jin/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<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
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Create Quill PostgreSQL context</name>
|
||||
<files>src/db/QuillContext.scala</files>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>`mill compile` succeeds with Quill context</verify>
|
||||
<done>QuillContext.scala exports dataSourceLayer and quillLayer, compiles without errors</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Create Flyway migrator service</name>
|
||||
<files>
|
||||
src/db/Migrator.scala
|
||||
resources/db/migration/V1__create_summer_users.sql
|
||||
</files>
|
||||
<action>
|
||||
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
|
||||
</action>
|
||||
<verify>`mill compile` succeeds, migration file exists at correct path</verify>
|
||||
<done>Migrator service defined with migrate/status methods, V1 migration creates summer_users</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Add /ready endpoint with database check</name>
|
||||
<files>src/api/HealthRoutes.scala</files>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>
|
||||
With PostgreSQL running: `curl localhost:8080/ready` returns "ready"
|
||||
Without PostgreSQL: `curl localhost:8080/ready` returns 503
|
||||
</verify>
|
||||
<done>/ready endpoint exists, returns 200 when DB connected, 503 when not</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
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"`
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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)
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user