16 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 03-postgresql-support | 02 | execute | 2 |
|
|
true |
|
|
Purpose: Connects the PostgresStore (built in Plan 01) to the startup path, adds Docker Compose profiles for PostgreSQL deployments, creates build-tagged integration test helpers, and fixes the UNIQUE constraint detection to work across both database backends. Also updates all call sites that still reference the old RunMigrations name (renamed to RunSQLiteMigrations in Plan 01).
Output: Updated main.go with DATABASE_URL branching, compose files with postgres profiles, build-tagged test helper, cross-dialect error handling fix.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-postgresql-support/03-CONTEXT.md @.planning/phases/03-postgresql-support/03-RESEARCH.md @.planning/phases/03-postgresql-support/03-01-SUMMARY.md@cmd/diunwebhook/main.go @pkg/diunwebhook/diunwebhook.go @pkg/diunwebhook/export_test.go @compose.yml @compose.dev.yml
From pkg/diunwebhook/postgres_store.go: ```go func NewPostgresStore(db *sql.DB) *PostgresStore ```From pkg/diunwebhook/migrate.go:
func RunSQLiteMigrations(db *sql.DB) error
func RunPostgresMigrations(db *sql.DB) error
From pkg/diunwebhook/store.go:
type Store interface { ... } // 9 methods
From pkg/diunwebhook/diunwebhook.go:
func NewServer(store Store, webhookSecret string) *Server
Replace the current database setup block with DATABASE_URL branching. The full main function should:
package main
import (
"context"
"database/sql"
"errors"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
diun "awesomeProject/pkg/diunwebhook"
_ "github.com/jackc/pgx/v5/stdlib"
_ "modernc.org/sqlite"
)
func main() {
databaseURL := os.Getenv("DATABASE_URL")
var store diun.Store
if databaseURL != "" {
db, err := sql.Open("pgx", databaseURL)
if err != nil {
log.Fatalf("sql.Open postgres: %v", err)
}
if err := diun.RunPostgresMigrations(db); err != nil {
log.Fatalf("RunPostgresMigrations: %v", err)
}
store = diun.NewPostgresStore(db)
log.Println("Using PostgreSQL database")
} else {
dbPath := os.Getenv("DB_PATH")
if dbPath == "" {
dbPath = "./diun.db"
}
db, err := sql.Open("sqlite", dbPath)
if err != nil {
log.Fatalf("sql.Open sqlite: %v", err)
}
if err := diun.RunSQLiteMigrations(db); err != nil {
log.Fatalf("RunSQLiteMigrations: %v", err)
}
store = diun.NewSQLiteStore(db)
log.Printf("Using SQLite database at %s", dbPath)
}
// ... rest of main unchanged (secret, server, mux, httpSrv, graceful shutdown)
}
Key changes:
- Add blank import
_ "github.com/jackc/pgx/v5/stdlib"to register "pgx" driver name DATABASE_URLpresent ->sql.Open("pgx", databaseURL)->RunPostgresMigrations->NewPostgresStoreDATABASE_URLabsent -> existing SQLite path withRunSQLiteMigrations(renamed fromRunMigrationsin Plan 01)- Log
"Using PostgreSQL database"or"Using SQLite database at %s"per D-09 - Keep all existing code after the store setup unchanged (secret, server, mux, httpSrv, shutdown)
2. Update pkg/diunwebhook/export_test.go to use the renamed function.
Change all occurrences of RunMigrations(db) to RunSQLiteMigrations(db) in export_test.go. This completes the rename that Plan 01 started in migrate.go.
3. Fix cross-dialect UNIQUE constraint detection in pkg/diunwebhook/diunwebhook.go.
In the TagsHandler method, change:
if strings.Contains(err.Error(), "UNIQUE") {
to:
if strings.Contains(strings.ToLower(err.Error()), "unique") {
Why: SQLite errors contain uppercase "UNIQUE" (e.g., UNIQUE constraint failed: tags.name). PostgreSQL/pgx errors contain lowercase "unique" (e.g., duplicate key value violates unique constraint "tags_name_key"). Case-insensitive matching ensures 409 Conflict is returned for both backends.
cd /home/jean-luc-makiola/Development/projects/DiunDashboard && go build ./... && go test -v -count=1 ./pkg/diunwebhook/ 2>&1 | tail -5
<acceptance_criteria>
- cmd/diunwebhook/main.go contains databaseURL := os.Getenv("DATABASE_URL")
- cmd/diunwebhook/main.go contains sql.Open("pgx", databaseURL)
- cmd/diunwebhook/main.go contains diun.RunPostgresMigrations(db)
- cmd/diunwebhook/main.go contains diun.NewPostgresStore(db)
- cmd/diunwebhook/main.go contains log.Println("Using PostgreSQL database")
- cmd/diunwebhook/main.go contains log.Printf("Using SQLite database at %s", dbPath)
- cmd/diunwebhook/main.go contains _ "github.com/jackc/pgx/v5/stdlib"
- cmd/diunwebhook/main.go contains diun.RunSQLiteMigrations(db) (not RunMigrations)
- pkg/diunwebhook/export_test.go contains RunSQLiteMigrations (not RunMigrations)
- pkg/diunwebhook/diunwebhook.go contains strings.Contains(strings.ToLower(err.Error()), "unique")
- pkg/diunwebhook/diunwebhook.go does NOT contain strings.Contains(err.Error(), "UNIQUE") (old pattern removed)
- go build ./... exits 0
- go test -v -count=1 ./pkg/diunwebhook/ exits 0 (full test suite passes)
</acceptance_criteria>
main.go branches on DATABASE_URL to select PostgreSQL or SQLite. pgx/v5/stdlib is blank-imported to register the driver. Startup log identifies the active backend. export_test.go updated with RunSQLiteMigrations. UNIQUE detection is case-insensitive for cross-dialect compatibility. All existing tests pass.
# Minimum Docker Compose v2.20 required for depends_on.required
services:
app:
image: gitea.jeanlucmakiola.de/makiolaj/diundashboard:latest
ports:
- "8080:8080"
environment:
- WEBHOOK_SECRET=${WEBHOOK_SECRET:-}
- PORT=${PORT:-8080}
- DB_PATH=/data/diun.db
- DATABASE_URL=${DATABASE_URL:-}
volumes:
- diun-data:/data
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
required: false
postgres:
image: postgres:17-alpine
profiles:
- postgres
environment:
POSTGRES_USER: ${POSTGRES_USER:-diun}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-diun}
POSTGRES_DB: ${POSTGRES_DB:-diundashboard}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-diun}"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
volumes:
diun-data:
postgres-data:
Default docker compose up still uses SQLite (DATABASE_URL is empty string, app falls back to DB_PATH).
docker compose --profile postgres up starts the postgres service; user sets DATABASE_URL=postgres://diun:diun@postgres:5432/diundashboard?sslmode=disable in .env.
2. Update compose.dev.yml to add postgres profile for local development:
services:
app:
build: .
ports:
- "8080:8080"
environment:
- WEBHOOK_SECRET=${WEBHOOK_SECRET:-}
- DATABASE_URL=${DATABASE_URL:-}
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
required: false
postgres:
image: postgres:17-alpine
profiles:
- postgres
ports:
- "5432:5432"
environment:
POSTGRES_USER: ${POSTGRES_USER:-diun}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-diun}
POSTGRES_DB: ${POSTGRES_DB:-diundashboard}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-diun}"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
volumes:
postgres-data:
Dev compose exposes port 5432 on host for direct psql access during development.
3. Create pkg/diunwebhook/postgres_test.go with build tag per D-17, D-19:
//go:build postgres
package diunwebhook
import (
"database/sql"
"os"
_ "github.com/jackc/pgx/v5/stdlib"
)
// NewTestPostgresServer constructs a Server backed by a PostgreSQL database.
// Requires a running PostgreSQL instance. Set TEST_DATABASE_URL to override
// the default connection string.
func NewTestPostgresServer() (*Server, error) {
databaseURL := os.Getenv("TEST_DATABASE_URL")
if databaseURL == "" {
databaseURL = "postgres://diun:diun@localhost:5432/diundashboard_test?sslmode=disable"
}
db, err := sql.Open("pgx", databaseURL)
if err != nil {
return nil, err
}
if err := RunPostgresMigrations(db); err != nil {
return nil, err
}
store := NewPostgresStore(db)
return NewServer(store, ""), nil
}
This file is in the diunwebhook package (internal, same as export_test.go pattern). The //go:build postgres tag ensures it only compiles when explicitly requested with go test -tags postgres. Without the tag, go test ./pkg/diunwebhook/ skips this file entirely -- no pgx import, no PostgreSQL dependency.
cd /home/jean-luc-makiola/Development/projects/DiunDashboard && go build ./... && go test -v -count=1 ./pkg/diunwebhook/ 2>&1 | tail -5
<acceptance_criteria>
- compose.yml contains profiles: under the postgres service
- compose.yml contains - postgres (profile name)
- compose.yml contains postgres:17-alpine
- compose.yml contains pg_isready
- compose.yml contains required: false (conditional depends_on)
- compose.yml contains DATABASE_URL=${DATABASE_URL:-}
- compose.yml contains postgres-data: in volumes
- compose.dev.yml contains profiles: under the postgres service
- compose.dev.yml contains - postgres (profile name)
- compose.dev.yml contains "5432:5432" (exposed for dev)
- compose.dev.yml contains required: false
- compose.dev.yml contains DATABASE_URL=${DATABASE_URL:-}
- pkg/diunwebhook/postgres_test.go contains //go:build postgres
- pkg/diunwebhook/postgres_test.go contains func NewTestPostgresServer()
- pkg/diunwebhook/postgres_test.go contains sql.Open("pgx", databaseURL)
- pkg/diunwebhook/postgres_test.go contains RunPostgresMigrations(db)
- pkg/diunwebhook/postgres_test.go contains NewPostgresStore(db)
- pkg/diunwebhook/postgres_test.go contains TEST_DATABASE_URL
- go build ./... exits 0 (postgres_test.go is not compiled without build tag)
- go test -v -count=1 ./pkg/diunwebhook/ exits 0 (full SQLite test suite passes, postgres_test.go skipped)
</acceptance_criteria>
Docker Compose files support optional PostgreSQL via profiles. Default deploy remains SQLite-only. Build-tagged test helper exists for PostgreSQL integration testing. Dockerfile needs no changes (pgx/v5 is pure Go).
<success_criteria>
- DATABASE_URL present: app opens pgx connection, runs PostgreSQL migrations, creates PostgresStore, logs "Using PostgreSQL database"
- DATABASE_URL absent: app opens sqlite connection, runs SQLite migrations, creates SQLiteStore, logs "Using SQLite database at {path}"
docker compose up(no profile) works with SQLite onlydocker compose --profile postgres upstarts PostgreSQL service with health check- Build-tagged test helper available for PostgreSQL integration tests
- UNIQUE constraint detection works for both SQLite and PostgreSQL error messages
- All existing SQLite tests continue to pass </success_criteria>