6.1 KiB
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}envelopePUT /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 JWTUserID/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 booleannotifications.push: must be a booleannotifications.digest: must be one of"none","daily","weekly"
GETfor a user with no saved preferences returns a200with default values (not a 404)- Default preference values:
theme: "system",language: "en",notifications.email: true,notifications.push: true,notifications.digest: "weekly" PUTwith unknown preference keys returns400 Bad Requestwith a descriptive errorPUTwith invalid preference values returns400 Bad Requestwith 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
PreferenceRepositoryport interface is defined, with a PostgreSQL adapter implementation - A
PreferenceServiceorchestrates 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 rawjson.NewDecoder - Error handling: Handlers return
error, wrapped withapp.Wrap(). Usehttperror.BadRequest,httperror.NotFound,httperror.Forbiddenetc. - Response format: All responses use the
{data, meta}envelope viahttpresponse.OK,httpresponse.Created, etc. - Auth identity: User identity is extracted from JWT via
auth.GetUser(ctx)which returns*auth.Userwith anIDfield - Database: PostgreSQL, connection details via
config.DatabaseConfig(environment variableDATABASE_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 andauth.GetUser()for extracting authenticated user identitypkg/app--app.Wrap(),app.Bind(),app.BindAndValidate()for handler patternspkg/httperror-- Typed HTTP error constructorspkg/httpresponse-- Response envelope helperspkg/openapi-- OpenAPI spec builder for documentationpkg/config--DatabaseConfigfor PostgreSQL connection parameters- PostgreSQL database instance (local dev via
docker-composeorscripts/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
- 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. - 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.
- Partial updates (PATCH): Should a
PATCHendpoint be added for updating individual preference keys without sending the full set, or isPUT(full replace) sufficient for MVP? - Rate limiting: Should preference updates be rate-limited to prevent abuse, or is auth sufficient protection?