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 01:43:39 +00:00
parent 9c45486bef
commit a167ae7c25
2 changed files with 181 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,180 @@
# 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 there is no preferences storage — the `preferences-api` service exists as a skeleton with only the example CRUD scaffold. This feature replaces the example entity with a real user preferences system.
## User Stories
- As an authenticated user, I want to save my theme preference so that the UI renders in my chosen theme across sessions.
- As an authenticated user, I want to save my language preference so that content is displayed in my preferred language.
- As an authenticated user, I want to configure my notification settings so that I only receive the alerts I care about.
- As an authenticated user, I want to retrieve all my preferences in a single call so that the frontend can initialize quickly.
- As an API consumer, I want to update individual preference keys without overwriting unrelated settings.
## API Design
### Endpoints
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/preferences-api/preferences/{user_id}` | Required | Retrieve all preferences for a user |
| PUT | `/api/preferences-api/preferences/{user_id}` | Required | Create or update preferences for a user (merge semantics) |
### Request / Response
**GET /api/preferences-api/preferences/{user_id}**
Response `200 OK`:
```json
{
"data": {
"user_id": "uuid",
"preferences": {
"theme": "dark",
"language": "en",
"notifications": {
"email": true,
"push": false,
"digest": "weekly"
}
},
"updated_at": "2026-02-08T00:00:00Z"
},
"meta": { "request_id": "...", "timestamp": "..." }
}
```
Response `404 Not Found` (no preferences saved yet):
```json
{
"error": { "code": "NOT_FOUND", "message": "preferences not found" },
"meta": { "request_id": "...", "timestamp": "..." }
}
```
**PUT /api/preferences-api/preferences/{user_id}**
Request body:
```json
{
"preferences": {
"theme": "light",
"notifications": {
"email": false
}
}
}
```
Response `200 OK` (returns merged result):
```json
{
"data": {
"user_id": "uuid",
"preferences": {
"theme": "light",
"language": "en",
"notifications": {
"email": false,
"push": false,
"digest": "weekly"
}
},
"updated_at": "2026-02-08T00:00:01Z"
},
"meta": { "request_id": "...", "timestamp": "..." }
}
```
### Merge Semantics
PUT performs a **shallow merge** at the top-level keys (`theme`, `language`, `notifications`). Nested objects like `notifications` are replaced entirely when provided. This keeps behavior predictable without requiring JSON Patch complexity.
## Acceptance Criteria
- [ ] GET `/api/preferences-api/preferences/{user_id}` returns `200` with stored preferences
- [ ] GET returns `404` when no preferences exist for the user
- [ ] PUT `/api/preferences-api/preferences/{user_id}` creates preferences if none exist (upsert)
- [ ] PUT merges provided keys with existing preferences (shallow merge)
- [ ] PUT returns `200` with the full merged preference set
- [ ] PUT validates that `preferences` field is present and is a JSON object
- [ ] PUT rejects unknown top-level preference keys with `400 Bad Request`
- [ ] Both endpoints require authentication via `auth.Middleware()`
- [ ] Authenticated user can only access their own preferences (user_id in path must match token subject), unless they have an admin role
- [ ] `user_id` path parameter is validated as a UUID
- [ ] Preferences are persisted in-memory via the existing adapter pattern (database adapter deferred)
- [ ] OpenAPI spec documents both endpoints with schemas, examples, and error responses
- [ ] Domain model defines allowed preference keys and validation rules
- [ ] Handler tests cover success paths, validation errors, auth failures, and not-found cases
- [ ] Service tests cover merge logic, create-on-first-PUT, and authorization checks
- [ ] All existing `example` scaffold code is removed and replaced with preferences code
## Data Model
### Domain Entity: `Preference`
```go
type UserID string
type Preferences struct {
UserID UserID
Theme string // "light", "dark", "system"
Language string // ISO 639-1 code: "en", "es", "fr", etc.
Notifications NotificationSettings
UpdatedAt time.Time
}
type NotificationSettings struct {
Email bool
Push bool
Digest string // "daily", "weekly", "never"
}
```
### Allowed Values
| Key | Type | Allowed Values | Default |
|-----|------|----------------|---------|
| `theme` | string | `light`, `dark`, `system` | `system` |
| `language` | string | ISO 639-1 codes | `en` |
| `notifications.email` | bool | `true`, `false` | `true` |
| `notifications.push` | bool | `true`, `false` | `true` |
| `notifications.digest` | string | `daily`, `weekly`, `never` | `weekly` |
## Technical Constraints
- Must follow the existing hexagonal architecture: domain → service → port → adapter
- Must use `app.Wrap()`, `app.BindAndValidate()`, `httpresponse.*`, `httperror.*` patterns
- Must use `auth.Middleware()` for protected routes
- In-memory adapter for initial implementation (matches existing pattern); database migration deferred to a follow-up feature
- Preference values must be validated against allowed values in the domain layer
- OpenAPI spec must be updated to replace example endpoints with preference endpoints
- Route base path remains `/api/preferences-api`
## Dependencies
- `pkg/app` — handler wrapping, request binding
- `pkg/auth` — JWT middleware, context user extraction
- `pkg/httperror` — typed HTTP errors
- `pkg/httpresponse` — response envelope
- `pkg/httpvalidation` — struct validation
- `pkg/openapi` — spec builder
- `pkg/logging` — structured logging
- Existing `preferences-api` service skeleton (to be modified in-place)
## Out of Scope
- Database persistence (PostgreSQL adapter) — separate follow-up feature
- Bulk preference operations across multiple users
- Preference history / audit log
- Preference defaults management API (defaults are hardcoded in domain)
- Frontend integration (consuming the API from apps/)
- Rate limiting
- Preference change webhooks / event publishing
## Open Questions
1. **Authorization model**: Should any user be able to read another user's preferences, or is it strictly own-user-only? The spec assumes own-user + admin override, but this needs confirmation.
2. **Additional preference keys**: Are `theme`, `language`, and `notifications` the complete set, or should the schema be extensible for future keys without code changes?
3. **Default preferences**: When GET returns 404, should we instead return a `200` with default values? This simplifies the frontend but changes the semantic contract.