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:
@@ -23,7 +23,7 @@ Requirements for this milestone. Each maps to roadmap phases.
|
||||
### Database
|
||||
|
||||
- [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)
|
||||
|
||||
### Bulk Actions
|
||||
@@ -99,7 +99,7 @@ Which phases cover which requirements. Updated during roadmap creation.
|
||||
| REFAC-02 | Phase 2 | Complete |
|
||||
| REFAC-03 | Phase 2 | Complete |
|
||||
| DB-01 | Phase 3 | Complete |
|
||||
| DB-02 | Phase 3 | Pending |
|
||||
| DB-02 | Phase 3 | Complete |
|
||||
| DB-03 | Phase 3 | Complete |
|
||||
| BULK-01 | Phase 4 | Pending |
|
||||
| BULK-02 | Phase 4 | Pending |
|
||||
|
||||
@@ -62,7 +62,7 @@ Plans:
|
||||
|
||||
Plans:
|
||||
- [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
|
||||
**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
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
gsd_state_version: 1.0
|
||||
milestone: v1.0
|
||||
milestone_name: milestone
|
||||
status: Ready to execute
|
||||
stopped_at: Completed 03-01-PLAN.md
|
||||
last_updated: "2026-03-24T08:10:37.889Z"
|
||||
status: Phase complete — ready for verification
|
||||
stopped_at: Completed 03-02-PLAN.md
|
||||
last_updated: "2026-03-24T08:14:20.006Z"
|
||||
progress:
|
||||
total_phases: 4
|
||||
completed_phases: 2
|
||||
completed_phases: 3
|
||||
total_plans: 6
|
||||
completed_plans: 5
|
||||
completed_plans: 6
|
||||
---
|
||||
|
||||
# Project State
|
||||
@@ -51,6 +51,7 @@ Plan: 2 of 2
|
||||
| Phase 02-backend-refactor P01 | 7min | 2 tasks | 7 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 P02 | 2min | 2 tasks | 5 files |
|
||||
|
||||
## 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 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]: 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
|
||||
|
||||
@@ -85,6 +89,6 @@ None yet.
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-03-24T08:10:37.887Z
|
||||
Stopped at: Completed 03-01-PLAN.md
|
||||
Last session: 2026-03-24T08:14:20.004Z
|
||||
Stopped at: Completed 03-02-PLAN.md
|
||||
Resume file: None
|
||||
|
||||
88
.planning/phases/03-postgresql-support/03-02-SUMMARY.md
Normal file
88
.planning/phases/03-postgresql-support/03-02-SUMMARY.md
Normal 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
|
||||
Reference in New Issue
Block a user