142 lines
5.7 KiB
Markdown
142 lines
5.7 KiB
Markdown
# 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?
|