build: /spec-feature user-preferences --requirements 'CRUD API for user pref...
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
rdev-worker 2026-02-08 05:52:34 +00:00
parent 66fcd0cab1
commit 208033482e
2 changed files with 151 additions and 1 deletions

View File

@ -22,7 +22,7 @@ artifacts:
status: pending
path: review.md
spec:
status: pending
status: draft
path: spec.md
tasks:
status: pending

View File

@ -0,0 +1,150 @@
# Feature: User Preferences API
## Problem Statement
Users of the platform need a way to persist and retrieve their personal preferences (theme, language, notification settings) across sessions and devices. Currently the preferences-api service exists as a scaffold with only example CRUD endpoints and an in-memory store. There is no real preferences domain, no database persistence, and no API for managing user preferences.
Application frontends need a reliable backend API to read and write per-user preference key-value pairs so that UI settings survive page refreshes, device switches, and service restarts.
## User Stories
- As a **frontend application**, I want to GET a user's preferences so that I can render the UI with their chosen theme, language, and notification settings.
- As a **frontend application**, I want to PUT (upsert) a user's preferences so that changes to settings are persisted immediately.
- As a **platform operator**, I want preferences stored in PostgreSQL so that they survive service restarts and are backed up with the rest of the data.
- As a **developer**, I want default preference values defined server-side so that new users get sensible defaults without client-side logic.
## Acceptance Criteria
- [ ] `GET /api/preferences-api/preferences/{user_id}` returns all preferences for the given user as a JSON object
- [ ] `GET` for a user with no stored preferences returns server-defined defaults (not 404)
- [ ] `PUT /api/preferences-api/preferences/{user_id}` creates or updates preferences for the given user (full replace of provided keys)
- [ ] `PUT` is idempotent -- calling it twice with the same body produces the same result
- [ ] Preferences are stored as key-value pairs in PostgreSQL
- [ ] The following preference keys are supported with validation:
- `theme` -- string, one of: `light`, `dark`, `system` (default: `system`)
- `language` -- string, BCP 47 language tag, validated format (default: `en`)
- `notifications_enabled` -- boolean (default: `true`)
- [ ] Unknown preference keys in a PUT request are rejected with 400 Bad Request
- [ ] `user_id` path parameter is validated as a non-empty string (UUID format)
- [ ] All responses use the standard `{data, meta}` envelope
- [ ] OpenAPI spec is updated with the new endpoints and schemas
- [ ] Database migration creates the `user_preferences` table
- [ ] Existing example CRUD endpoints and domain are removed (replaced by preferences)
- [ ] Handler, service, domain, port, and adapter layers follow hexagonal architecture
- [ ] Unit tests cover service logic (defaults, validation, upsert behavior)
- [ ] Handler tests cover HTTP layer (request binding, error responses, status codes)
## Technical Constraints
- **Database**: PostgreSQL via `pkg/database` (sqlx). Migration files in `services/preferences-api/migrations/`.
- **Port**: Service runs on port 8001 (already configured).
- **URL routing**: Must use brace syntax `{user_id}` (chi router). Never colon syntax.
- **Error handling**: Domain errors mapped to HTTP errors via `httperror.*`. Handlers return `error`, wrapped with `app.Wrap()`.
- **Request binding**: Use `app.BindAndValidate()` for PUT request body.
- **Response format**: Use `httpresponse.OK()` / `httpresponse.NoContent()` for responses.
- **Auth**: Auth middleware is opt-in via config. Endpoints should be in an auth-protectable route group, but auth enforcement is not required for this feature (configurable via `AUTH_ENABLED`).
- **Preference storage model**: Each preference is a row in the `user_preferences` table with columns: `user_id`, `key`, `value`, `created_at`, `updated_at`. This allows adding new preference keys without schema changes.
- **Defaults**: When a stored preference is missing, the API merges server-defined defaults so the response always contains all known keys.
## API Design
### GET /api/preferences-api/preferences/{user_id}
**Response 200:**
```json
{
"data": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"preferences": {
"theme": "dark",
"language": "en",
"notifications_enabled": true
}
},
"meta": {
"request_id": "...",
"timestamp": "..."
}
}
```
### PUT /api/preferences-api/preferences/{user_id}
**Request body:**
```json
{
"preferences": {
"theme": "dark",
"language": "fr"
}
}
```
Only provided keys are updated. Omitted keys retain their current value (or default if never set).
**Response 200:**
```json
{
"data": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"preferences": {
"theme": "dark",
"language": "fr",
"notifications_enabled": true
}
},
"meta": {
"request_id": "...",
"timestamp": "..."
}
}
```
**Error 400 (unknown key):**
```json
{
"error": {
"code": "BAD_REQUEST",
"message": "unknown preference key: font_size"
},
"meta": { "..." }
}
```
## Database Schema
```sql
CREATE TABLE user_preferences (
user_id UUID NOT NULL,
key VARCHAR(64) NOT NULL,
value TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (user_id, key)
);
CREATE INDEX idx_user_preferences_user_id ON user_preferences (user_id);
```
## Dependencies
- PostgreSQL database accessible via `DATABASE_URL` environment variable
- `pkg/database` for connection pooling and migrations
- `pkg/app`, `pkg/httperror`, `pkg/httpresponse`, `pkg/httpvalidation` for HTTP layer
- `pkg/auth` for optional authentication middleware
## Out of Scope
- Per-preference-key access control (all preferences for a user are readable/writable as a unit)
- DELETE endpoint for individual preferences (not in requirements)
- Preference history / audit log
- Bulk operations across multiple users
- WebSocket push for real-time preference sync
- Admin endpoints for managing preference definitions
- Frontend integration (separate feature)
## Open Questions
1. **Authorization model**: Should the API enforce that `user_id` in the path matches the authenticated user's ID? Or is cross-user preference access allowed (e.g., for admin tools)? *Current assumption: no enforcement, auth is opt-in via config.*
2. **Preference value types**: Should values be typed (string/bool/number) at the API level, or stored/returned as strings with client-side parsing? *Current assumption: typed in API response (theme as string, notifications_enabled as boolean), stored as TEXT in DB with serialization.*
3. **Partial vs full update semantics**: PUT currently does partial update (merge). Should it be full replace (all keys must be provided)? *Current assumption: partial merge -- only provided keys are updated, missing keys retain current values.*