slack5-1770574304/.sdlc/features/user-preferences/spec.md
rdev-worker b2e88a92a3
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
build: /spec-feature user-preferences --requirements 'CRUD API for user pref...
2026-02-08 18:18:39 +00:00

6.1 KiB

Feature: User Preferences API

Problem Statement

Users of the platform need a way to persist and retrieve personal preferences (theme, language, notification settings) so their experience is consistent across sessions and devices. Currently, the preferences-api service exists as a scaffold with only example CRUD endpoints and no actual preference management capability.

User Stories

  • As an authenticated user, I want to retrieve my preferences so that the UI reflects my saved settings when I log in.
  • As an authenticated user, I want to update my preferences so that changes to theme, language, or notification settings are persisted.
  • As a frontend application, I want to fetch preferences for the current user so I can apply theme/language/notification configuration on page load.
  • As an admin or service, I want to retrieve preferences for a specific user by user ID so I can provide user-specific experiences in server-rendered contexts.

Acceptance Criteria

  • GET /api/preferences-api/preferences/{user_id} returns the user's preferences as a key-value map within the standard {data, meta} envelope
  • PUT /api/preferences-api/preferences/{user_id} creates or fully replaces the user's preferences (upsert semantics)
  • Both endpoints require authentication via the existing auth.Middleware()
  • Authenticated users can only access their own preferences (the {user_id} in the URL must match the JWT UserID/Subject)
  • Preferences are stored as key-value pairs with string keys and JSON-compatible values
  • The following preference keys are supported with validation:
    • theme: must be one of "light", "dark", "system"
    • language: must be a valid BCP-47 language tag (e.g., "en", "fr", "es", "de", "ja")
    • notifications.email: must be a boolean
    • notifications.push: must be a boolean
    • notifications.digest: must be one of "none", "daily", "weekly"
  • GET for a user with no saved preferences returns a 200 with default values (not a 404)
  • Default preference values: theme: "system", language: "en", notifications.email: true, notifications.push: true, notifications.digest: "weekly"
  • PUT with unknown preference keys returns 400 Bad Request with a descriptive error
  • PUT with invalid preference values returns 400 Bad Request with per-field validation errors
  • Preferences are persisted in PostgreSQL (using the existing DatabaseConfig)
  • The domain layer contains pure preference models with validation logic, following hexagonal architecture
  • A PreferenceRepository port interface is defined, with a PostgreSQL adapter implementation
  • A PreferenceService orchestrates business logic between handler and repository
  • OpenAPI spec is updated to document both endpoints with schemas, examples, and error responses
  • Unit tests cover domain validation, service logic, and handler request/response mapping
  • Integration-style tests verify handler behavior using the in-memory repository adapter
  • The existing example CRUD scaffolding is removed and replaced with preference endpoints

Technical Constraints

  • Architecture: Must follow the established hexagonal architecture pattern (domain -> service -> port -> adapter) already present in services/preferences-api/
  • URL parameters: Must use brace syntax {user_id} (not :user_id) per chi router requirements
  • Request binding: Must use app.Bind() / app.BindAndValidate() -- never raw json.NewDecoder
  • Error handling: Handlers return error, wrapped with app.Wrap(). Use httperror.BadRequest, httperror.NotFound, httperror.Forbidden etc.
  • Response format: All responses use the {data, meta} envelope via httpresponse.OK, httpresponse.Created, etc.
  • Auth identity: User identity is extracted from JWT via auth.GetUser(ctx) which returns *auth.User with an ID field
  • Database: PostgreSQL, connection details via config.DatabaseConfig (environment variable DATABASE_URL)
  • Port: The service runs on port 8001 (default)
  • Route prefix: All routes must be under /api/preferences-api/ to match ingress routing

Dependencies

  • pkg/auth -- JWT middleware and auth.GetUser() for extracting authenticated user identity
  • pkg/app -- app.Wrap(), app.Bind(), app.BindAndValidate() for handler patterns
  • pkg/httperror -- Typed HTTP error constructors
  • pkg/httpresponse -- Response envelope helpers
  • pkg/openapi -- OpenAPI spec builder for documentation
  • pkg/config -- DatabaseConfig for PostgreSQL connection parameters
  • PostgreSQL database instance (local dev via docker-compose or scripts/dev.sh)

Out of Scope

  • Bulk operations: No endpoint for fetching/updating preferences for multiple users at once
  • Preference history/audit log: No versioning or history of preference changes
  • Real-time sync: No WebSocket or push notification when preferences change on another device
  • Custom/arbitrary preference keys: Only the defined keys (theme, language, notifications.*) are supported -- extensibility is deferred
  • DELETE endpoint: Preferences are reset to defaults via PUT, not deleted
  • Pagination: Preferences are a fixed small set per user, no pagination needed
  • Admin override: No endpoint for admins to modify another user's preferences (admin read is permitted for server-rendered contexts but write is not)

Open Questions

  1. Admin read access: Should admin users (identified by a role in JWT) be allowed to read other users' preferences, or should the {user_id} always be strictly self-only? The spec currently allows admin read but this needs confirmation.
  2. Preference key extensibility: Should the API accept and store unknown preference keys (with a max key count limit), or strictly reject them? The spec currently rejects unknown keys.
  3. Partial updates (PATCH): Should a PATCH endpoint be added for updating individual preference keys without sending the full set, or is PUT (full replace) sufficient for MVP?
  4. Rate limiting: Should preference updates be rate-limited to prevent abuse, or is auth sufficient protection?