docs(03-02): complete wire postgresql support plan

- Add 03-02-SUMMARY.md with execution results
- Update STATE.md: advance plan, record metrics, add decisions, update session
- Update ROADMAP.md: phase 03 complete (2/2 plans, all summaries present)
- Update REQUIREMENTS.md: mark DB-02 complete
This commit is contained in:
2026-03-24 09:14:50 +01:00
parent f611545ae5
commit cf788930e0
4 changed files with 102 additions and 10 deletions

View File

@@ -23,7 +23,7 @@ Requirements for this milestone. Each maps to roadmap phases.
### Database ### Database
- [x] **DB-01**: PostgreSQL is supported as an alternative to SQLite via pgx v5 driver - [x] **DB-01**: PostgreSQL is supported as an alternative to SQLite via pgx v5 driver
- [ ] **DB-02**: Database backend is selected via DATABASE_URL env var (present = PostgreSQL, absent = SQLite with DB_PATH) - [x] **DB-02**: Database backend is selected via DATABASE_URL env var (present = PostgreSQL, absent = SQLite with DB_PATH)
- [x] **DB-03**: Existing SQLite users can upgrade without data loss (baseline migration represents current schema) - [x] **DB-03**: Existing SQLite users can upgrade without data loss (baseline migration represents current schema)
### Bulk Actions ### Bulk Actions
@@ -99,7 +99,7 @@ Which phases cover which requirements. Updated during roadmap creation.
| REFAC-02 | Phase 2 | Complete | | REFAC-02 | Phase 2 | Complete |
| REFAC-03 | Phase 2 | Complete | | REFAC-03 | Phase 2 | Complete |
| DB-01 | Phase 3 | Complete | | DB-01 | Phase 3 | Complete |
| DB-02 | Phase 3 | Pending | | DB-02 | Phase 3 | Complete |
| DB-03 | Phase 3 | Complete | | DB-03 | Phase 3 | Complete |
| BULK-01 | Phase 4 | Pending | | BULK-01 | Phase 4 | Pending |
| BULK-02 | Phase 4 | Pending | | BULK-02 | Phase 4 | Pending |

View File

@@ -62,7 +62,7 @@ Plans:
Plans: Plans:
- [x] 03-01-PLAN.md — Create PostgresStore (9 Store methods), PostgreSQL migration files, rename RunMigrations to RunSQLiteMigrations, add RunPostgresMigrations - [x] 03-01-PLAN.md — Create PostgresStore (9 Store methods), PostgreSQL migration files, rename RunMigrations to RunSQLiteMigrations, add RunPostgresMigrations
- [ ] 03-02-PLAN.md — Wire DATABASE_URL branching in main.go, fix cross-dialect UNIQUE detection, add Docker Compose postgres profiles, create build-tagged test helper - [x] 03-02-PLAN.md — Wire DATABASE_URL branching in main.go, fix cross-dialect UNIQUE detection, add Docker Compose postgres profiles, create build-tagged test helper
### Phase 4: UX Improvements ### Phase 4: UX Improvements
**Goal**: Users can manage a large list of updates efficiently — dismissing many at once, finding specific images quickly, and seeing new arrivals without manual refreshes **Goal**: Users can manage a large list of updates efficiently — dismissing many at once, finding specific images quickly, and seeing new arrivals without manual refreshes

View File

@@ -2,14 +2,14 @@
gsd_state_version: 1.0 gsd_state_version: 1.0
milestone: v1.0 milestone: v1.0
milestone_name: milestone milestone_name: milestone
status: Ready to execute status: Phase complete — ready for verification
stopped_at: Completed 03-01-PLAN.md stopped_at: Completed 03-02-PLAN.md
last_updated: "2026-03-24T08:10:37.889Z" last_updated: "2026-03-24T08:14:20.006Z"
progress: progress:
total_phases: 4 total_phases: 4
completed_phases: 2 completed_phases: 3
total_plans: 6 total_plans: 6
completed_plans: 5 completed_plans: 6
--- ---
# Project State # Project State
@@ -51,6 +51,7 @@ Plan: 2 of 2
| Phase 02-backend-refactor P01 | 7min | 2 tasks | 7 files | | Phase 02-backend-refactor P01 | 7min | 2 tasks | 7 files |
| Phase 02-backend-refactor P02 | 3min | 2 tasks | 4 files | | Phase 02-backend-refactor P02 | 3min | 2 tasks | 4 files |
| Phase 03-postgresql-support P01 | 3min | 2 tasks | 7 files | | Phase 03-postgresql-support P01 | 3min | 2 tasks | 7 files |
| Phase 03-postgresql-support P02 | 2min | 2 tasks | 5 files |
## Accumulated Context ## Accumulated Context
@@ -73,6 +74,9 @@ Recent decisions affecting current work:
- [Phase 02-backend-refactor]: NewTestServer pattern: each test gets its own in-memory SQLite DB (RunMigrations + NewSQLiteStore + NewServer) - eliminates shared global state between tests - [Phase 02-backend-refactor]: NewTestServer pattern: each test gets its own in-memory SQLite DB (RunMigrations + NewSQLiteStore + NewServer) - eliminates shared global state between tests
- [Phase 03-postgresql-support]: PostgresStore uses *sql.DB via pgx/v5/stdlib adapter with no mutex; TEXT timestamps match SQLiteStore scan logic - [Phase 03-postgresql-support]: PostgresStore uses *sql.DB via pgx/v5/stdlib adapter with no mutex; TEXT timestamps match SQLiteStore scan logic
- [Phase 03-postgresql-support]: CreateTag uses RETURNING id in PostgresStore (pgx does not support LastInsertId); AssignTag uses ON CONFLICT DO UPDATE - [Phase 03-postgresql-support]: CreateTag uses RETURNING id in PostgresStore (pgx does not support LastInsertId); AssignTag uses ON CONFLICT DO UPDATE
- [Phase 03-postgresql-support]: DATABASE_URL presence-check activates PostgreSQL; absent falls back to SQLite — simpler UX than a separate DB_DRIVER var
- [Phase 03-postgresql-support]: postgres Docker service uses profiles: [postgres] with required: false depends_on — default compose up unchanged, SQLite only
- [Phase 03-postgresql-support]: UNIQUE constraint detection uses strings.ToLower for case-insensitive matching across SQLite (uppercase UNIQUE) and PostgreSQL (lowercase unique)
### Pending Todos ### Pending Todos
@@ -85,6 +89,6 @@ None yet.
## Session Continuity ## Session Continuity
Last session: 2026-03-24T08:10:37.887Z Last session: 2026-03-24T08:14:20.004Z
Stopped at: Completed 03-01-PLAN.md Stopped at: Completed 03-02-PLAN.md
Resume file: None Resume file: None

View File

@@ -0,0 +1,88 @@
---
phase: 03-postgresql-support
plan: "02"
subsystem: wiring
tags: [postgresql, sqlite, database, docker-compose, branching]
dependency_graph:
requires: [03-01]
provides: [DATABASE_URL branching, postgres docker profile, NewTestPostgresServer]
affects: [cmd/diunwebhook/main.go, compose.yml, compose.dev.yml, pkg/diunwebhook/diunwebhook.go]
tech_stack:
added: []
patterns: [DATABASE_URL env var branching, Docker Compose profiles, build-tagged test helpers]
key_files:
created:
- pkg/diunwebhook/postgres_test.go
modified:
- cmd/diunwebhook/main.go
- pkg/diunwebhook/diunwebhook.go
- compose.yml
- compose.dev.yml
decisions:
- "DATABASE_URL present activates PostgreSQL path; absent falls back to SQLite with DB_PATH"
- "postgres Docker service uses profiles: [postgres] so default compose up remains SQLite-only"
- "UNIQUE detection uses strings.ToLower for case-insensitive matching across SQLite and PostgreSQL"
- "Build tag //go:build postgres gates postgres_test.go so standard test runs have no pgx dependency"
metrics:
duration: "~2 minutes"
completed: "2026-03-24T08:13:21Z"
tasks_completed: 2
files_changed: 5
---
# Phase 03 Plan 02: Wire PostgreSQL Support and Deployment Infrastructure Summary
DATABASE_URL branching in main.go routes to PostgresStore or SQLiteStore at startup; Docker Compose postgres profile enables optional PostgreSQL; build-tagged test helper and cross-dialect UNIQUE detection complete the integration.
## What Was Built
### Updated main.go (cmd/diunwebhook/main.go)
- `DATABASE_URL` env var check: when set, opens pgx connection, runs `RunPostgresMigrations`, creates `NewPostgresStore`, logs `"Using PostgreSQL database"`
- When absent: existing SQLite path using `RunSQLiteMigrations` (renamed in Plan 01), `NewSQLiteStore`, logs `"Using SQLite database at {path}"`
- Blank import `_ "github.com/jackc/pgx/v5/stdlib"` registers the `"pgx"` driver name
- All route wiring and graceful shutdown logic unchanged
### Cross-dialect UNIQUE detection (pkg/diunwebhook/diunwebhook.go)
- `TagsHandler` now uses `strings.Contains(strings.ToLower(err.Error()), "unique")` for 409 Conflict detection
- SQLite errors: `UNIQUE constraint failed: tags.name` (uppercase UNIQUE)
- PostgreSQL errors: `duplicate key value violates unique constraint "tags_name_key"` (lowercase unique)
- Both backends now return 409 correctly
### Docker Compose postgres profiles
- `compose.yml`: postgres service added with `profiles: [postgres]`, healthcheck via `pg_isready`, `DATABASE_URL` env var in app service, conditional `depends_on` with `required: false`, `postgres-data` volume
- `compose.dev.yml`: same postgres service with port 5432 exposed on host for direct psql access during development
- Default `docker compose up` (no profile) unchanged — SQLite only, no new services start
### Build-tagged test helper (pkg/diunwebhook/postgres_test.go)
- `//go:build postgres` tag — only compiled with `go test -tags postgres`
- `NewTestPostgresServer()` constructs a `*Server` backed by PostgreSQL using `TEST_DATABASE_URL` env var (defaults to `postgres://diun:diun@localhost:5432/diundashboard_test?sslmode=disable`)
- Calls `RunPostgresMigrations` and `NewPostgresStore` — mirrors the production startup path
## Decisions Made
| Decision | Rationale |
|----------|-----------|
| DATABASE_URL presence-check (not a separate DB_DRIVER var) | Simpler UX; empty string = SQLite, any value = PostgreSQL |
| profiles: [postgres] in compose files | Standard Docker Compose pattern for optional services; default deploy unchanged |
| required: false in depends_on | App can start without postgres service (SQLite fallback); Docker Compose v2.20+ required |
| //go:build postgres tag on test helper | Prevents pgx import at test time for standard `go test ./...` runs; explicit opt-in |
| strings.ToLower for UNIQUE check | SQLite and PostgreSQL use different cases in constraint error messages |
## Deviations from Plan
None — plan executed exactly as written. The `export_test.go` rename (RunMigrations -> RunSQLiteMigrations) was already completed as a deviation in Plan 01, as noted in the objective.
## Verification Results
- `go build ./...` exits 0
- `go test -count=1 ./pkg/diunwebhook/` passes (all 20+ SQLite tests, postgres_test.go skipped)
- `docker compose config` validates without errors
- `docker compose --profile postgres config` shows postgres service
- `grep -c "DATABASE_URL" cmd/diunwebhook/main.go` returns 1
- `grep "strings.ToLower" pkg/diunwebhook/diunwebhook.go` shows case-insensitive UNIQUE check
## Known Stubs
None — this plan wires implementation code, no UI stubs.
## Self-Check: PASSED