15 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. 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 (lines 18-33) 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 in 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. Fix cross-dialect UNIQUE constraint detection in pkg/diunwebhook/diunwebhook.go.
In the TagsHandler method, line 172, 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/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 -count=1 ./pkg/diunwebhook/ exits 0
</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. 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 ./... && docker compose config --quiet 2>&1; echo "exit: $?"
<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 -count=1 ./pkg/diunwebhook/ exits 0 (SQLite tests still pass, 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>