slack5-1770603014/.sdlc/features/user-preferences/tasks.md
rdev-worker 9a069d3f71
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
build: /breakdown-feature user-preferences
2026-02-09 02:26:07 +00:00

8.1 KiB

Tasks: User Preferences API

Task Order (dependency sequence)

T1: Domain layer - preferences entity, validation, defaults, and errors (task-001)

  • Scope: Create the UserPreferences domain entity with Preferences and NotificationSettings structs, UserID type, DefaultPreferences() factory, Validate() method, and domain errors (ErrInvalidTheme, ErrInvalidLanguage, ErrInvalidDigest). Update errors.go to remove example-specific errors.
  • Files:
    • Create services/preferences-api/internal/domain/preferences.go
    • Modify services/preferences-api/internal/domain/errors.go
    • Delete services/preferences-api/internal/domain/example.go
  • Depends on: None
  • Acceptance criteria:
    • UserID, Preferences, NotificationSettings, UserPreferences types defined
    • DefaultPreferences() returns correct defaults (theme=system, language=en, notifications: email=true, push=true, digest=weekly)
    • Validate() rejects invalid theme values (not light/dark/system)
    • Validate() rejects empty language string
    • Validate() rejects invalid digest values (not daily/weekly/never)
    • Validate() accepts valid combinations
    • Domain errors ErrInvalidTheme, ErrInvalidLanguage, ErrInvalidDigest defined
    • Example-specific errors (ErrExampleNotFound, ErrDuplicateExample, ErrInvalidExampleName) removed from errors.go
    • example.go deleted

T2: Port layer - PreferencesRepository interface (task-002)

  • Scope: Define the PreferencesRepository interface with Get and Upsert methods. Remove the ExampleRepository interface.
  • Files:
    • Create services/preferences-api/internal/port/preferences.go
    • Delete services/preferences-api/internal/port/example.go
  • Depends on: T1 (uses domain types)
  • Acceptance criteria:
    • PreferencesRepository interface defined with Get(ctx, UserID) (*UserPreferences, error) and Upsert(ctx, *UserPreferences) error
    • Interface uses domain types (domain.UserID, domain.UserPreferences)
    • example.go deleted from port package

T3: Service layer - PreferencesService with deep merge, get/update logic, and unit tests (task-003)

  • Scope: Implement PreferencesService with GetPreferences (returns defaults for unknown users) and UpdatePreferences (fetch existing → deep merge incoming partial data → validate merged result → upsert). Include comprehensive unit tests with a mock repository.
  • Files:
    • Create services/preferences-api/internal/service/preferences.go
    • Create services/preferences-api/internal/service/preferences_test.go
    • Delete services/preferences-api/internal/service/example.go
    • Delete services/preferences-api/internal/service/example_test.go
  • Depends on: T1 (domain types, validation), T2 (port interface)
  • Acceptance criteria:
    • NewPreferencesService(repo, logger) constructor
    • GetPreferences(ctx, userID) returns stored preferences or defaults for unknown users
    • UpdatePreferences(ctx, userID, partialInput) performs deep merge with existing preferences
    • Deep merge handles: theme only, language only, notifications only, nested notification fields individually, all fields together
    • Validation runs on the merged result before persisting
    • Invalid values after merge return domain validation errors
    • Unit tests cover: get existing user, get unknown user returns defaults, update creates new preferences, update merges partial data, update with invalid theme rejected, update with invalid digest rejected, update with empty language rejected
    • Example service files deleted

T4: Database migration and PostgreSQL adapter (task-004)

  • Scope: Create the user_preferences table migration and implement the PostgreSQL adapter for PreferencesRepository. Remove the in-memory example adapter.
  • Files:
    • Create services/preferences-api/migrations/001_create_user_preferences.sql
    • Create services/preferences-api/internal/adapter/postgres/preferences.go
    • Delete services/preferences-api/internal/adapter/memory/example.go
  • Depends on: T1 (domain types), T2 (port interface)
  • Acceptance criteria:
    • Migration creates user_preferences table with user_id UUID PRIMARY KEY, preferences JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ, updated_at TIMESTAMPTZ
    • Get(ctx, userID) executes SELECT by user_id, returns nil, nil (or similar sentinel) for not-found to allow service to return defaults
    • Upsert(ctx, prefs) executes INSERT ... ON CONFLICT (user_id) DO UPDATE with JSONB preferences and updated timestamps
    • All SQL uses parameterized queries ($1, $2) — no string interpolation
    • Preferences marshaled to/from JSONB via encoding/json
    • In-memory example adapter deleted

T5: HTTP handlers - GET and PUT preferences with request/response types and unit tests (task-005)

  • Scope: Implement PreferencesHandler with Get and Upsert methods. Define request types (UpdatePreferencesRequest with pointer fields for partial updates) and response type (PreferencesResponse). Map domain errors to HTTP errors. Include handler unit tests with mock service.
  • Files:
    • Create services/preferences-api/internal/api/handlers/preferences.go
    • Create services/preferences-api/internal/api/handlers/preferences_test.go
    • Delete services/preferences-api/internal/api/handlers/example.go
    • Delete services/preferences-api/internal/api/handlers/example_test.go
  • Depends on: T1 (domain types, errors), T3 (service interface for mocking)
  • Acceptance criteria:
    • Get handler: extracts user_id from URL path, validates UUID format, calls service, returns httpresponse.OK with PreferencesResponse
    • Get handler: returns 400 for invalid UUID format
    • Upsert handler: extracts user_id, validates UUID, binds request body with app.BindAndValidate(), calls service, returns httpresponse.OK
    • Upsert handler: returns 400 for invalid UUID, invalid body, or domain validation errors
    • PreferencesInput uses pointer fields (*string, *bool) for partial update semantics
    • Domain errors (ErrInvalidTheme, ErrInvalidLanguage, ErrInvalidDigest) mapped to httperror.BadRequest
    • Unit tests cover: GET success, GET unknown user returns defaults, GET invalid UUID, PUT success full update, PUT partial update, PUT invalid UUID, PUT invalid theme, PUT empty body
    • Example handler files deleted

T6: Wiring, routes, OpenAPI spec, and example scaffolding removal (task-006)

  • Scope: Update main.go to connect to PostgreSQL, run migrations, wire the PostgreSQL adapter and preferences service. Update routes.go to register GET and PUT /preferences/{user_id}. Update spec.go with OpenAPI definitions for preferences endpoints. Remove all example route and spec definitions.
  • Files:
    • Modify services/preferences-api/cmd/server/main.go
    • Modify services/preferences-api/internal/api/routes.go
    • Modify services/preferences-api/internal/api/spec.go
  • Depends on: T1-T5 (all layers must exist for wiring)
  • Acceptance criteria:
    • main.go connects to PostgreSQL via database.Connect() using DATABASE_URL from config
    • main.go runs migrations on startup via database.MustRunMigrations()
    • main.go creates PostgreSQL adapter, service, and handler with proper dependency injection
    • routes.go registers GET /api/preferences-api/preferences/{user_id} (public)
    • routes.go registers PUT /api/preferences-api/preferences/{user_id} (public, per spec — no auth for this feature)
    • Health endpoint preserved unchanged
    • All example routes and example-related code removed from routes.go
    • spec.go defines UserPreferences, UpdatePreferencesRequest, PreferencesResponse schemas
    • spec.go documents GET and PUT endpoints with path parameters, request bodies, and response types
    • All example OpenAPI definitions removed from spec.go
    • go test -v ./... passes from services/preferences-api/