# Feature: User Preferences API ## Problem Statement Users need a way to persist and retrieve their application preferences (theme, language, notification settings) across sessions and devices. Currently, no preference storage exists — any customization is lost when a user logs out or switches devices. The preferences-api service exists as a scaffold with example CRUD patterns but has no preference-specific domain logic. ## User Stories - As an authenticated user, I want to save my theme preference so that the UI matches my preference across sessions. - As an authenticated user, I want to set my preferred language so that the application displays in my chosen locale. - As an authenticated user, I want to configure my notification settings so that I only receive alerts I care about. - As a frontend application, I want to retrieve all preferences for a user in a single request so that the UI can be initialized efficiently. - As an authenticated user, I want to update individual preferences without overwriting all others so that partial updates are safe. ## Acceptance Criteria - [ ] `GET /api/preferences-api/preferences/{user_id}` returns all preferences for a user as key-value pairs in the `{data, meta}` envelope - [ ] `PUT /api/preferences-api/preferences/{user_id}` creates or updates preferences for a user (upsert semantics) - [ ] Supported preference keys: `theme` (values: `light`, `dark`, `system`), `language` (ISO 639-1 codes, e.g. `en`, `fr`, `es`), `notifications_enabled` (boolean as string: `true`, `false`) - [ ] Unknown preference keys are rejected with a `400 Bad Request` and descriptive error - [ ] Invalid preference values are rejected with a `400 Bad Request` and descriptive error (e.g., `theme: "blue"` is invalid) - [ ] PUT is idempotent — sending the same request twice produces the same result - [ ] PUT supports partial updates — sending only `{"theme": "dark"}` does not clear `language` or `notifications_enabled` - [ ] GET for a user with no preferences returns `200` with an empty object `{}` (not 404) - [ ] Both endpoints require authentication via JWT (auth middleware) - [ ] Authenticated users can only access their own preferences (user_id in path must match JWT subject), returning `403 Forbidden` otherwise - [ ] Preferences are persisted in PostgreSQL with a migration - [ ] OpenAPI spec documents both endpoints with request/response schemas - [ ] Handler tests cover success, validation errors, not-found, and authorization cases - [ ] Service-layer tests cover business logic with mock repository - [ ] Response times < 50ms at p99 for single-user preference reads ## Technical Constraints - Must follow existing hexagonal architecture: domain → service → port (interface) → adapter (implementation) - Handlers must return `error`, wrapped with `app.Wrap()` - Request binding must use `app.Bind()` or `app.BindAndValidate()` - Errors must use `httperror.BadRequest`, `httperror.NotFound`, `httperror.Forbidden` — never bare `http.Error()` - Responses must use `httpresponse.OK`, `httpresponse.Created`, `httpresponse.NoContent` with `{data, meta}` envelope - Routes mount under `/api/preferences-api` to match ingress routing - Database connection uses `pkg/database` with embedded SQL migrations - Auth middleware uses `pkg/auth` with JWT validation - The existing example CRUD code should be replaced, not left alongside preference code - `user_id` is a UUID, validated at the handler layer ## Dependencies - PostgreSQL database available (connection via `DATABASE_URL` environment variable) - `pkg/database` package for connection pooling and migration support - `pkg/auth` package for JWT middleware and user extraction - JWT secret configured via `JWT_SECRET` environment variable - `AUTH_ENABLED=true` in production environments ## Out of Scope - User registration or authentication (handled by a separate auth service) - Preference inheritance or defaults cascade (e.g., org-level defaults) - Real-time preference sync via WebSockets - Preference history or audit trail - Bulk preference operations across multiple users - Frontend UI for preference management - Rate limiting on preference endpoints - Preference schema versioning or migration ## Open Questions 1. **Default values**: Should `GET` return system defaults for keys that haven't been explicitly set (e.g., `{"theme": "system", "language": "en", "notifications_enabled": "true"}`) or only return keys the user has explicitly set? 2. **DELETE support**: Should there be a `DELETE /preferences/{user_id}` to reset all preferences, or a `DELETE /preferences/{user_id}/{key}` to reset individual ones? 3. **Extensibility**: When new preference keys are added in the future, should they be added to a configuration file/constant list, or is a code change acceptable? 4. **Admin access**: Should admin users be allowed to read/write preferences for other users, or is self-access the only mode?