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-09 02:18:10 +00:00
parent 79f35aff6b
commit 07294fcf8f
2 changed files with 133 additions and 1 deletions

View File

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

View File

@ -0,0 +1,132 @@
# 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:**
```json
{
"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:**
```json
{
"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 `theme` against allowed values (`light`, `dark`, `system`)
- [ ] PUT validates `language` is a non-empty string
- [ ] PUT validates `notifications.digest` against allowed values (`daily`, `weekly`, `never`)
- [ ] PUT validates `notifications.email` and `notifications.push` are booleans
- [ ] Invalid preference values return 400 Bad Request with descriptive error details
- [ ] Invalid `user_id` format (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.Created` for response envelope
- Must use `httperror.*` for HTTP error types
- Must use `{param}` brace syntax for chi URL parameters (not `:param`)
- Must use `pkg/database` for PostgreSQL connection
- Database migration files go in `services/preferences-api/migrations/`
- Preferences stored as JSONB in PostgreSQL for flexible schema
- `user_id` is a UUID provided by the caller (no user management in this service)
## Dependencies
- `pkg/database` package for PostgreSQL connectivity
- `pkg/app`, `pkg/httpresponse`, `pkg/httperror` for HTTP patterns (already in use)
- PostgreSQL database instance (connection via `DATABASE_URL` env 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
1. **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.)
2. **Unknown preference keys:** Should PUT reject unknown keys not in the defined schema, or silently accept them for forward compatibility?
3. **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?