build: /breakdown-feature user-preferences
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
rdev-worker 2026-02-07 23:34:51 +00:00
parent 37e6dbe519
commit 2c87b1b618
2 changed files with 141 additions and 1 deletions

View File

@ -25,5 +25,25 @@ artifacts:
status: draft
path: spec.md
tasks:
status: pending
status: draft
path: tasks.md
total: 6
tasks:
- id: task-001
title: Remove example scaffold code
status: pending
- id: task-002
title: Implement domain layer - preference types, validation, and errors
status: pending
- id: task-003
title: Implement port interface and PostgreSQL adapter with migration
status: pending
- id: task-004
title: Implement service layer with business logic and tests
status: pending
- id: task-005
title: Implement HTTP handlers with auth ownership check and tests
status: pending
- id: task-006
title: Wire routes, OpenAPI spec, and main.go integration
status: pending

View File

@ -0,0 +1,120 @@
# Tasks: User Preferences API
## Task Order (dependency sequence)
```
T1 → T2 → T3 → T4 → T5 → T6
```
All tasks are sequential — each builds on the previous layer of the hexagonal architecture.
---
### T1: Remove example scaffold code (task-001)
- **Scope:** Delete all example-related files from the preferences-api service. This clears the scaffold to make room for preference-specific domain logic. The health handler, config, Makefile, Dockerfile, and component.yaml are preserved.
- **Files:**
- Delete `internal/domain/example.go`
- Delete `internal/domain/errors.go`
- Delete `internal/port/example.go`
- Delete `internal/service/example.go`
- Delete `internal/service/example_test.go`
- Delete `internal/adapter/memory/example.go`
- Delete `internal/api/handlers/example.go`
- Delete `internal/api/handlers/example_test.go`
- **Depends on:** None
- **Acceptance criteria:**
- [ ] All 8 example files are deleted
- [ ] `internal/api/handlers/health.go` is untouched
- [ ] `internal/config/config.go` is untouched
- [ ] Code compiles after removing example references from `routes.go` and `main.go` (temporary stubs or comment-outs are acceptable since later tasks will replace them)
---
### T2: Implement domain layer - preference types, validation, and errors (task-002)
- **Scope:** Create the preference domain model with `Preference` type, `AllowedKeys` map, key/value validation functions, and domain error types. Include unit tests for all validation logic.
- **Files:**
- Create `internal/domain/preference.go``Preference` struct, `AllowedKeys` map, `Validate()`, `ValidateKey()`, `ValidateValue()` functions
- Create `internal/domain/errors.go``ErrUnknownKey`, `ErrInvalidValue`, `ErrForbidden`
- Create `internal/domain/preference_test.go` — Unit tests for validation
- **Depends on:** T1
- **Acceptance criteria:**
- [ ] `AllowedKeys` includes `theme` (light, dark, system), `language` (ISO 639-1 regex `^[a-z]{2}$`), `notifications_enabled` (true, false)
- [ ] `ValidateKey("theme")` returns nil; `ValidateKey("unknown")` returns `ErrUnknownKey`
- [ ] `ValidateValue("theme", "dark")` returns nil; `ValidateValue("theme", "blue")` returns `ErrInvalidValue`
- [ ] `ValidateValue("language", "en")` returns nil; `ValidateValue("language", "english")` returns `ErrInvalidValue`
- [ ] `ValidateValue("notifications_enabled", "true")` returns nil; `ValidateValue("notifications_enabled", "yes")` returns `ErrInvalidValue`
- [ ] Domain errors use `errors.New` and are exported for use in error mapping
- [ ] All tests pass: `go test -v ./internal/domain/...`
---
### T3: Implement port interface and PostgreSQL adapter with migration (task-003)
- **Scope:** Define the `PreferenceRepository` port interface and implement the PostgreSQL adapter. Create the SQL migration for the `user_preferences` table.
- **Files:**
- Create `internal/port/preference.go``PreferenceRepository` interface with `GetByUserID` and `Upsert` methods
- Create `internal/adapter/postgres/preference.go` — PostgreSQL implementation using `sqlx`
- Create `migrations/001_create_user_preferences.sql` — Table DDL with composite PK and index
- **Depends on:** T2
- **Acceptance criteria:**
- [ ] `PreferenceRepository` interface defines `GetByUserID(ctx, userID) (map[string]string, error)` and `Upsert(ctx, userID, prefs map[string]string) error`
- [ ] PostgreSQL adapter `GetByUserID` returns empty map (not nil) when no rows exist
- [ ] PostgreSQL adapter `Upsert` uses `INSERT ... ON CONFLICT (user_id, key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()` within a transaction
- [ ] Migration creates `user_preferences` table with composite PK `(user_id, key)` and index on `user_id`
- [ ] All queries use parameterized statements (no SQL injection risk)
- [ ] Code compiles successfully
---
### T4: Implement service layer with business logic and tests (task-004)
- **Scope:** Create the `PreferenceService` that orchestrates get/upsert operations, performs domain validation, and delegates persistence to the repository port. Write comprehensive service-layer tests using a mock repository.
- **Files:**
- Create `internal/service/preference.go``PreferenceService` with `Get` and `Upsert` methods, constructor with DI
- Create `internal/service/preference_test.go` — Table-driven tests with mock repository
- **Depends on:** T3
- **Acceptance criteria:**
- [ ] `Get(ctx, userID)` delegates to repository `GetByUserID` and returns the result
- [ ] `Upsert(ctx, userID, prefs)` validates all keys/values using domain validation before persisting
- [ ] `Upsert` with an unknown key returns an error wrapping `ErrUnknownKey`
- [ ] `Upsert` with an invalid value returns an error wrapping `ErrInvalidValue`
- [ ] `Upsert` returns the full preference set after the update (calls `GetByUserID` after successful upsert)
- [ ] Service accepts `PreferenceRepository` interface (not concrete type) for testability
- [ ] Tests use a mock repository implementing `PreferenceRepository`
- [ ] Tests cover: successful get, get empty, successful upsert, upsert with unknown key, upsert with invalid value, repository error propagation
- [ ] All tests pass: `go test -v ./internal/service/...`
---
### T5: Implement HTTP handlers with auth ownership check and tests (task-005)
- **Scope:** Create GET and PUT preference handlers with UUID validation, JWT ownership check, request binding, domain-to-HTTP error mapping, and comprehensive handler tests.
- **Files:**
- Create `internal/api/handlers/preference.go``PreferenceHandler` with `Get` and `Update` methods, `mapDomainError` helper
- Create `internal/api/handlers/preference_test.go` — Handler tests covering success, validation, auth, and ownership cases
- **Depends on:** T4
- **Acceptance criteria:**
- [ ] GET handler extracts `user_id` from URL path, validates UUID format, checks ownership against JWT subject, returns preferences via `httpresponse.OK`
- [ ] PUT handler binds JSON body to `map[string]string`, validates UUID, checks ownership, calls service `Upsert`, returns updated preferences via `httpresponse.OK`
- [ ] Invalid UUID returns `httperror.BadRequest("invalid user ID format")`
- [ ] JWT subject mismatch returns `httperror.Forbidden("cannot access preferences for another user")`
- [ ] Empty PUT body returns `httperror.BadRequest("request body is required")`
- [ ] `ErrUnknownKey` maps to 400; `ErrInvalidValue` maps to 400 with descriptive message
- [ ] All handlers return `error` and are compatible with `app.Wrap()`
- [ ] Tests cover: successful GET, GET empty prefs, GET invalid UUID, GET forbidden, successful PUT, PUT invalid key, PUT invalid value, PUT empty body, PUT forbidden
- [ ] All tests pass: `go test -v ./internal/api/handlers/...`
---
### T6: Wire routes, OpenAPI spec, and main.go integration (task-006)
- **Scope:** Update route registration to mount preference endpoints with mandatory auth, replace the OpenAPI spec with preference endpoint documentation, and update main.go to initialize DB pool, run migrations, and wire all dependencies.
- **Files:**
- Modify `internal/api/routes.go` — Replace example routes with preference routes under auth middleware group
- Replace `internal/api/spec.go` — OpenAPI docs for GET/PUT `/preferences/{user_id}` with request/response schemas
- Modify `cmd/server/main.go` — Add database pool initialization, migration runner, wire `PreferenceService` with PostgreSQL adapter
- **Depends on:** T5
- **Acceptance criteria:**
- [ ] Routes mount `GET /api/preferences-api/preferences/{user_id}` and `PUT /api/preferences-api/preferences/{user_id}` under auth middleware
- [ ] Auth middleware is mandatory for preference routes (not conditional on `AUTH_ENABLED`)
- [ ] Health endpoint remains unprotected
- [ ] OpenAPI spec documents both endpoints with request/response schemas, security requirements, and error responses (400, 401, 403)
- [ ] `main.go` initializes database pool using `pkg/database`, runs embedded migrations, creates PostgreSQL adapter, wires into service
- [ ] Application starts and routes are accessible (compile + basic wiring test)
- [ ] Full test suite passes: `cd services/preferences-api && go test -v ./...`