8.1 KiB
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
UserPreferencesdomain entity withPreferencesandNotificationSettingsstructs,UserIDtype,DefaultPreferences()factory,Validate()method, and domain errors (ErrInvalidTheme,ErrInvalidLanguage,ErrInvalidDigest). Updateerrors.goto 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
- Create
- Depends on: None
- Acceptance criteria:
UserID,Preferences,NotificationSettings,UserPreferencestypes definedDefaultPreferences()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 stringValidate()rejects invalid digest values (not daily/weekly/never)Validate()accepts valid combinations- Domain errors
ErrInvalidTheme,ErrInvalidLanguage,ErrInvalidDigestdefined - Example-specific errors (
ErrExampleNotFound,ErrDuplicateExample,ErrInvalidExampleName) removed fromerrors.go example.godeleted
T2: Port layer - PreferencesRepository interface (task-002)
- Scope: Define the
PreferencesRepositoryinterface withGetandUpsertmethods. Remove theExampleRepositoryinterface. - Files:
- Create
services/preferences-api/internal/port/preferences.go - Delete
services/preferences-api/internal/port/example.go
- Create
- Depends on: T1 (uses domain types)
- Acceptance criteria:
PreferencesRepositoryinterface defined withGet(ctx, UserID) (*UserPreferences, error)andUpsert(ctx, *UserPreferences) error- Interface uses domain types (
domain.UserID,domain.UserPreferences) example.godeleted from port package
T3: Service layer - PreferencesService with deep merge, get/update logic, and unit tests (task-003)
- Scope: Implement
PreferencesServicewithGetPreferences(returns defaults for unknown users) andUpdatePreferences(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
- Create
- Depends on: T1 (domain types, validation), T2 (port interface)
- Acceptance criteria:
NewPreferencesService(repo, logger)constructorGetPreferences(ctx, userID)returns stored preferences or defaults for unknown usersUpdatePreferences(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_preferencestable migration and implement the PostgreSQL adapter forPreferencesRepository. 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
- Create
- Depends on: T1 (domain types), T2 (port interface)
- Acceptance criteria:
- Migration creates
user_preferencestable withuser_id UUID PRIMARY KEY,preferences JSONB NOT NULL DEFAULT '{}',created_at TIMESTAMPTZ,updated_at TIMESTAMPTZ Get(ctx, userID)executesSELECTby user_id, returnsnil, nil(or similar sentinel) for not-found to allow service to return defaultsUpsert(ctx, prefs)executesINSERT ... ON CONFLICT (user_id) DO UPDATEwith 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
- Migration creates
T5: HTTP handlers - GET and PUT preferences with request/response types and unit tests (task-005)
- Scope: Implement
PreferencesHandlerwithGetandUpsertmethods. Define request types (UpdatePreferencesRequestwith 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
- Create
- Depends on: T1 (domain types, errors), T3 (service interface for mocking)
- Acceptance criteria:
Gethandler: extractsuser_idfrom URL path, validates UUID format, calls service, returnshttpresponse.OKwithPreferencesResponseGethandler: returns 400 for invalid UUID formatUpserthandler: extractsuser_id, validates UUID, binds request body withapp.BindAndValidate(), calls service, returnshttpresponse.OKUpserthandler: returns 400 for invalid UUID, invalid body, or domain validation errorsPreferencesInputuses pointer fields (*string,*bool) for partial update semantics- Domain errors (
ErrInvalidTheme,ErrInvalidLanguage,ErrInvalidDigest) mapped tohttperror.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.goto connect to PostgreSQL, run migrations, wire the PostgreSQL adapter and preferences service. Updateroutes.goto registerGETandPUT /preferences/{user_id}. Updatespec.gowith 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
- Modify
- Depends on: T1-T5 (all layers must exist for wiring)
- Acceptance criteria:
main.goconnects to PostgreSQL viadatabase.Connect()usingDATABASE_URLfrom configmain.goruns migrations on startup viadatabase.MustRunMigrations()main.gocreates PostgreSQL adapter, service, and handler with proper dependency injectionroutes.goregistersGET /api/preferences-api/preferences/{user_id}(public)routes.goregistersPUT /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.godefinesUserPreferences,UpdatePreferencesRequest,PreferencesResponseschemasspec.godocuments GET and PUT endpoints with path parameters, request bodies, and response types- All example OpenAPI definitions removed from
spec.go go test -v ./...passes fromservices/preferences-api/