# 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:** ```json { "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):** ```json { "data": { "user_id": "uuid", "preferences": {}, "updated_at": null }, "meta": { ... } } ``` ### PUT /api/preferences-api/preferences/{user_id} **Request:** ```json { "preferences": { "theme": "dark", "notifications_enabled": false } } ``` **Response 200:** ```json { "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 ```sql 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?