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 09:08:39 +00:00
parent 23a48af9da
commit 257ad77471
2 changed files with 129 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,128 @@
# Feature: User Preferences API
## Problem Statement
Users need the ability to persist and retrieve personal preferences (theme, language, notification settings) so that their experience is consistent across sessions and devices. Currently, the `preferences-api` service exists as scaffolding with only an example CRUD resource — there is no actual preferences domain model or persistence.
## 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 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 API call so that I can initialize the UI efficiently.
- As a **backend service**, I want to read a user's preferences to personalize behavior (e.g., notification delivery channel).
## API Design
### Endpoints
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `GET` | `/api/preferences-api/preferences/{user_id}` | Required | Get all preferences for a user |
| `PUT` | `/api/preferences-api/preferences/{user_id}` | Required | Create or replace all preferences for a user |
### Request: `PUT /api/preferences-api/preferences/{user_id}`
```json
{
"preferences": {
"theme": "dark",
"language": "en",
"notifications": {
"email": true,
"push": true,
"sms": false
}
}
}
```
### Response: `GET /api/preferences-api/preferences/{user_id}`
```json
{
"data": {
"user_id": "usr_abc123",
"preferences": {
"theme": "dark",
"language": "en",
"notifications": {
"email": true,
"push": true,
"sms": false
}
},
"updated_at": "2026-02-08T10:30:00Z"
},
"meta": {
"request_id": "req_xyz",
"timestamp": "2026-02-08T10:30:01Z"
}
}
```
### Storage Model
Preferences are stored as a JSON document per user (not individual key-value rows). This simplifies reads (single query) and writes (single upsert), and avoids N+1 patterns for users with many preferences.
### Known Preference Keys
| Key | Type | Default | Validation |
|-----|------|---------|------------|
| `theme` | string | `"system"` | One of: `"light"`, `"dark"`, `"system"` |
| `language` | string | `"en"` | BCP-47 language tag, max 10 chars |
| `notifications.email` | bool | `true` | — |
| `notifications.push` | bool | `true` | — |
| `notifications.sms` | bool | `false` | — |
The schema should be extensible — unknown keys are preserved but not validated, allowing new preference keys to be introduced without schema migrations.
## Acceptance Criteria
- [ ] `GET /api/preferences-api/preferences/{user_id}` returns the user's preferences with a `200` response in the standard `{data, meta}` envelope.
- [ ] `GET` returns default preferences (`theme: "system"`, `language: "en"`, `notifications: {email: true, push: true, sms: false}`) when no preferences have been saved for the user.
- [ ] `PUT /api/preferences-api/preferences/{user_id}` creates or fully replaces the user's preferences, returning `200`.
- [ ] `PUT` validates known preference keys: rejects invalid `theme` values, rejects `language` values exceeding 10 characters.
- [ ] `PUT` with invalid input returns `400 Bad Request` with a descriptive error in the standard error envelope.
- [ ] Both endpoints require authentication via `auth.Middleware()`.
- [ ] The authenticated user can only access their own preferences (the `user_id` in the path must match the authenticated user's ID), returning `403 Forbidden` otherwise.
- [ ] Preferences are persisted to PostgreSQL and survive service restarts.
- [ ] The database schema uses an upsert pattern — `PUT` to a new `user_id` creates the record, `PUT` to an existing `user_id` replaces it.
- [ ] OpenAPI spec is updated with both endpoints, request/response schemas, and examples.
- [ ] Handler, service, and domain layers follow existing hexagonal architecture patterns.
- [ ] Unit tests cover: handler request/response mapping, service business logic (defaults, validation, authorization), domain validation rules.
- [ ] URL parameters use brace syntax `{user_id}` (not colon syntax).
## Technical Constraints
- **Architecture**: Must follow the existing hexagonal architecture (domain → service → port → adapter) established in the preferences-api service.
- **Database**: PostgreSQL via `pkg/database`. Migrations embedded with `//go:embed`. Single table with JSONB column for preferences.
- **Auth**: Endpoints protected by `auth.Middleware()`. User ID extracted from JWT claims via `auth.GetUser(ctx)`.
- **Response format**: All responses use `httpresponse.OK/Created/NoContent` helpers for the `{data, meta}` envelope.
- **Error handling**: Domain errors mapped to HTTP errors in handlers via `mapDomainError()` pattern. Use `httperror.BadRequest`, `httperror.NotFound`, `httperror.Forbidden`.
- **Request binding**: Use `app.BindAndValidate()` for PUT request body.
- **Router**: chi router with `{param}` brace syntax for URL parameters.
- **Port**: Service runs on port 8001 (already configured).
## Dependencies
- `pkg/database` — PostgreSQL connection and migration support (already exists).
- `pkg/auth` — JWT middleware and user extraction (already exists).
- `pkg/app`, `pkg/httperror`, `pkg/httpresponse`, `pkg/httpvalidation` — HTTP framework (already exists).
- A running PostgreSQL instance for persistence (managed via `DATABASE_URL` env var).
## Out of Scope
- **Bulk preferences for multiple users** — Only single-user GET/PUT in this iteration.
- **PATCH (partial update)** — Full replacement via PUT only. Partial updates may be added later.
- **DELETE endpoint** — No need to delete preferences (they reset to defaults if the row is removed manually).
- **Preference history/audit log** — No tracking of preference changes over time.
- **Admin override** — No admin endpoint to modify another user's preferences.
- **Frontend integration** — API only; frontend changes are a separate feature.
- **Rate limiting** — Handled at the infrastructure layer, not in this feature.
## Open Questions
1. **Authorization model**: Should the authenticated user's ID come from the JWT `sub` claim, or is there a separate `user_id` field in the token? (Needs alignment with `auth.GetUser()` contract.)
2. **Unknown preference keys**: Should unknown keys be silently accepted and stored, or rejected with a validation error? (Spec currently assumes they are preserved.)
3. **Preference size limit**: Should there be a maximum size for the preferences JSON document to prevent abuse? (e.g., 10KB limit.)