5.8 KiB
5.8 KiB
Feature: User Preferences API
Problem Statement
Users need a way to persist and retrieve their personal preferences (theme, language, notification settings) so that their experience is consistent across sessions and devices. Currently, the preferences-api service exists as a scaffold with example CRUD endpoints but no real domain logic or database persistence. This feature replaces the example scaffolding with a real user preferences domain.
User Stories
- As a user, I want to save my theme preference so that the UI renders in my chosen theme across sessions.
- As a user, I want to save my language preference so that the application displays content in my preferred language.
- As a user, I want to configure my notification settings so that I only receive the notifications I care about.
- As a frontend application, I want to retrieve all preferences for a user in a single request so that I can initialize the UI efficiently.
- As a frontend application, I want to update one or more preferences in a single request so that partial updates are supported without overwriting unrelated settings.
API Design
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/preferences-api/preferences/{user_id} |
Retrieve all preferences for a user |
| PUT | /api/preferences-api/preferences/{user_id} |
Create or update preferences for a user (upsert) |
GET /preferences/{user_id}
Response 200:
{
"data": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"preferences": {
"theme": "dark",
"language": "en",
"notifications": {
"email": true,
"push": false,
"digest": "weekly"
}
},
"updated_at": "2026-02-09T12:00:00Z"
},
"meta": {
"request_id": "...",
"timestamp": "..."
}
}
Response 404: User has no preferences saved (returns default preferences instead — see Open Questions).
PUT /preferences/{user_id}
Upserts preferences. Merges provided keys with existing preferences (partial update). Keys not included in the request body are left unchanged.
Request Body:
{
"preferences": {
"theme": "light",
"notifications": {
"push": true
}
}
}
Response 200: Returns the full merged preference set after update.
Preference Keys
| Key | Type | Allowed Values | Default |
|---|---|---|---|
theme |
string | "light", "dark", "system" |
"system" |
language |
string | BCP 47 language tag (e.g., "en", "fr", "es") |
"en" |
notifications.email |
boolean | true, false |
true |
notifications.push |
boolean | true, false |
true |
notifications.digest |
string | "daily", "weekly", "never" |
"weekly" |
Acceptance Criteria
- GET
/preferences/{user_id}returns stored preferences for the given user - GET for a user with no saved preferences returns default preference values (not 404)
- PUT
/preferences/{user_id}creates preferences if none exist (upsert) - PUT
/preferences/{user_id}merges provided keys with existing preferences (partial update) - PUT validates
themeagainst allowed values (light,dark,system) - PUT validates
languageis a non-empty string - PUT validates
notifications.digestagainst allowed values (daily,weekly,never) - PUT validates
notifications.emailandnotifications.pushare booleans - Invalid preference values return 400 Bad Request with descriptive error details
- Invalid
user_idformat (non-UUID) returns 400 Bad Request - All responses use the standard
{data, meta}envelope - OpenAPI spec documents both endpoints with request/response schemas
- Preferences are persisted in PostgreSQL (survive service restarts)
- Database migration creates the preferences table
- Domain logic is separated from HTTP handlers (hexagonal architecture)
- Service and handler layers have unit tests
- Existing example scaffolding is removed and replaced with preferences domain
Technical Constraints
- Must follow the existing hexagonal architecture pattern: domain -> service -> port -> adapter
- Must use
app.Wrap()for handler error returns,app.BindAndValidate()for request binding - Must use
httpresponse.OK/httpresponse.Createdfor response envelope - Must use
httperror.*for HTTP error types - Must use
{param}brace syntax for chi URL parameters (not:param) - Must use
pkg/databasefor PostgreSQL connection - Database migration files go in
services/preferences-api/migrations/ - Preferences stored as JSONB in PostgreSQL for flexible schema
user_idis a UUID provided by the caller (no user management in this service)
Dependencies
pkg/databasepackage for PostgreSQL connectivitypkg/app,pkg/httpresponse,pkg/httperrorfor HTTP patterns (already in use)- PostgreSQL database instance (connection via
DATABASE_URLenv var, already in config) - No external service dependencies
Out of Scope
- User authentication/authorization (auth middleware exists but user identity validation is not part of this feature)
- User management or user creation
- Preference change history/audit log
- Preference schemas beyond theme, language, and notifications
- Real-time preference sync (WebSocket push on change)
- Bulk operations across multiple users
- Rate limiting
Open Questions
- Default preferences on GET for unknown user: Should GET for a user with no saved preferences return a 200 with defaults, or a 404? (Spec assumes 200 with defaults for better frontend DX.)
- Unknown preference keys: Should PUT reject unknown keys not in the defined schema, or silently accept them for forward compatibility?
- User ID validation beyond format: Should the service verify the user_id corresponds to a real user (via inter-service call), or accept any valid UUID?