build: /spec-feature user-preferences --requirements 'CRUD API for user pref...
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
This commit is contained in:
parent
79f35aff6b
commit
07294fcf8f
@ -22,7 +22,7 @@ artifacts:
|
||||
status: pending
|
||||
path: review.md
|
||||
spec:
|
||||
status: pending
|
||||
status: draft
|
||||
path: spec.md
|
||||
tasks:
|
||||
status: pending
|
||||
|
||||
132
.sdlc/features/user-preferences/spec.md
Normal file
132
.sdlc/features/user-preferences/spec.md
Normal 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?
|
||||
Loading…
Reference in New Issue
Block a user