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

5.7 KiB

Feature: User Preferences API

Problem Statement

Users need a way to persist and retrieve their application preferences (theme, language, notification settings) so that their experience is consistent across sessions and devices. Currently, the preferences-api service contains only scaffold code with no preference management functionality.

User Stories

  • As a user, I want to save my theme preference (light/dark) so that the UI renders consistently across sessions.
  • As a user, I want to save my language preference so that the application displays content in my chosen language.
  • As a user, I want to configure my notification settings so that I only receive the notifications I care about.
  • As a user, I want to retrieve all my preferences in a single request so that the frontend can initialize quickly.
  • As a user, I want to update individual preferences without overwriting others so that partial updates are safe.

Acceptance Criteria

  • GET /api/preferences-api/preferences/{user_id} returns all preferences for the given user as key-value pairs
  • GET /api/preferences-api/preferences/{user_id} returns an empty preferences object (not 404) for users with no saved preferences
  • PUT /api/preferences-api/preferences/{user_id} creates or updates preferences for the given user (upsert semantics)
  • PUT /api/preferences-api/preferences/{user_id} supports partial updates — only provided keys are changed, omitted keys are preserved
  • Supported preference keys: theme (values: light, dark), language (ISO 639-1 codes, e.g. en, es, fr), notifications_enabled (boolean)
  • Invalid preference values are rejected with a 400 Bad Request and descriptive error message
  • Unknown preference keys are rejected with a 400 Bad Request
  • Both endpoints require authentication — unauthenticated requests receive 401
  • Users can only access their own preferences — mismatched user ID returns 403
  • Preferences are persisted to PostgreSQL (not in-memory)
  • All endpoints return responses in the standard {data, meta} envelope
  • Endpoints are documented in OpenAPI spec via spec.go
  • Domain layer validates preference keys and values independently of HTTP layer
  • Service, handler, and repository layers follow existing hexagonal architecture patterns
  • Unit tests cover service layer business logic (valid/invalid keys, partial updates, authorization)
  • Integration tests cover handler layer HTTP behavior (status codes, response format, error cases)

API Design

GET /api/preferences-api/preferences/{user_id}

Response 200:

{
  "data": {
    "user_id": "uuid",
    "preferences": {
      "theme": "dark",
      "language": "en",
      "notifications_enabled": true
    },
    "updated_at": "2026-02-08T12:00:00Z"
  },
  "meta": {
    "request_id": "...",
    "timestamp": "..."
  }
}

Response 200 (no preferences saved yet):

{
  "data": {
    "user_id": "uuid",
    "preferences": {},
    "updated_at": null
  },
  "meta": { ... }
}

PUT /api/preferences-api/preferences/{user_id}

Request:

{
  "preferences": {
    "theme": "dark",
    "notifications_enabled": false
  }
}

Response 200:

{
  "data": {
    "user_id": "uuid",
    "preferences": {
      "theme": "dark",
      "language": "en",
      "notifications_enabled": false
    },
    "updated_at": "2026-02-08T12:00:05Z"
  },
  "meta": { ... }
}

Technical Constraints

  • Must use PostgreSQL for persistence (migration in services/preferences-api/migrations/)
  • Must follow the existing hexagonal architecture: domain → service → port → adapter
  • Must use app.Wrap() handler pattern, app.BindAndValidate() for request binding
  • Must use httperror.* for error responses, httpresponse.* for success responses
  • Must use chi router with {param} brace syntax for URL parameters
  • Must use auth.Middleware() for authentication and validate user ownership
  • Preference keys are a closed set — only theme, language, notifications_enabled are accepted
  • Preference values must be validated per-key (enum for theme, ISO code pattern for language, boolean for notifications)

Database Schema

CREATE TABLE user_preferences (
    user_id     UUID PRIMARY KEY,
    preferences JSONB NOT NULL DEFAULT '{}',
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Storing preferences as JSONB allows flexible key-value storage while the application layer enforces the allowed key set and value validation.

Dependencies

  • PostgreSQL database accessible from the service (connection config via DatabaseConfig)
  • Authentication system functional (auth.Middleware() with JWT validation)
  • Existing pkg/app, pkg/httperror, pkg/httpresponse, pkg/auth, pkg/openapi packages

Out of Scope

  • Preference defaults / fallback values at the API level (frontend handles defaults)
  • Preference history or versioning
  • Bulk preference operations across multiple users
  • Admin endpoints to manage other users' preferences
  • Real-time preference change notifications (WebSocket/SSE)
  • Custom/user-defined preference keys beyond the initial set

Open Questions

  1. Language validation strictness: Should language values be validated against a specific list of supported languages, or accept any valid ISO 639-1 code?
  2. Default preferences: Should the API return default values for unset preferences (e.g., theme: "light" if never set), or let the frontend handle defaults?
  3. Rate limiting: Should preference updates be rate-limited to prevent abuse?
  4. Removing the example scaffold: Should the existing Example entity/handlers/routes be removed as part of this feature, or left for reference?