build: /spec-feature user-preferences --requirements 'CRUD API for user pref...
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
rdev-worker 2026-02-08 04:30:13 +00:00
parent 9647e30a08
commit b941ed2b5c
2 changed files with 155 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,154 @@
# Feature: User Preferences API
## Problem Statement
Users of the platform have no way to persist their personal settings (theme, language, notification preferences). Without a preferences API, every client must manage settings locally, leading to inconsistent experiences across devices and sessions. The `preferences-api` service currently contains only scaffold/example code and needs to be replaced with real preference management functionality.
## User Stories
- As a user, I want to save my preferred theme so that the UI looks the same every time I log in.
- As a user, I want to set my preferred language so that content is displayed in my language across devices.
- As a user, I want to configure my notification settings so that I only receive the alerts I care about.
- As a frontend developer, I want a simple GET/PUT API to read and write user preferences so that I can build settings pages without managing local storage.
## Acceptance Criteria
- [ ] `GET /api/preferences-api/preferences/{user_id}` returns all preferences for a user as key-value pairs
- [ ] `GET` returns a default set of preferences (`theme`, `language`, `notifications`) when the user has no saved preferences
- [ ] `PUT /api/preferences-api/preferences/{user_id}` creates or updates preferences for a user (upsert semantics)
- [ ] `PUT` validates that `theme` is one of: `light`, `dark`, `system`
- [ ] `PUT` validates that `language` is a valid BCP-47 language tag (e.g., `en`, `fr`, `es`, `de`, `ja`)
- [ ] `PUT` validates that `notifications` is an object with boolean fields: `email`, `push`, `in_app`
- [ ] `PUT` supports partial updates — omitted keys retain their current values
- [ ] All responses follow the `{data, meta}` envelope pattern
- [ ] Preferences are persisted in PostgreSQL (not in-memory)
- [ ] Database migration creates the preferences table
- [ ] `user_id` is validated as a UUID format
- [ ] `PUT` on protected route group (requires auth when `AUTH_ENABLED=true`)
- [ ] `GET` on protected route group (requires auth when `AUTH_ENABLED=true`)
- [ ] Handler tests cover success, validation errors, not-found defaults, and invalid user ID
- [ ] Service-layer tests cover business logic with mock repository
- [ ] Domain model enforces preference value constraints
- [ ] OpenAPI spec documents both endpoints with request/response schemas
- [ ] Existing Example CRUD scaffold code is removed
## 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": {
"email": true,
"push": false,
"in_app": true
}
},
"updated_at": "2026-01-15T10:30:00Z"
},
"meta": {
"request_id": "...",
"timestamp": "..."
}
}
```
### PUT /api/preferences-api/preferences/{user_id}
**Request body (partial update):**
```json
{
"theme": "dark",
"notifications": {
"email": false
}
}
```
**Response 200:**
```json
{
"data": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"preferences": {
"theme": "dark",
"language": "en",
"notifications": {
"email": false,
"push": true,
"in_app": true
}
},
"updated_at": "2026-02-08T12:00:00Z"
},
"meta": {
"request_id": "...",
"timestamp": "..."
}
}
```
## Default Preference Values
| Key | Default Value |
|-----------------|---------------|
| `theme` | `system` |
| `language` | `en` |
| `notifications.email` | `true` |
| `notifications.push` | `true` |
| `notifications.in_app` | `true` |
## Technical Constraints
- **Database:** PostgreSQL via `pkg/database` (connection pool, migrations, transactions)
- **Architecture:** Hexagonal — domain, service, port (interface), adapter (postgres implementation)
- **Router:** chi with `{param}` brace syntax for URL parameters
- **Validation:** `pkg/httpvalidation` with `go-playground/validator` struct tags
- **Error handling:** Domain errors mapped to HTTP errors via `httperror.*` factories
- **Response format:** All responses wrapped via `httpresponse.OK`, `httpresponse.Created`, etc.
- **Auth:** Opt-in via `auth.Middleware()` in route group, controlled by `AUTH_ENABLED` env var
- **Migrations:** SQL files in `services/preferences-api/migrations/` using `pkg/database.RunMigrations`
- **Port:** Service runs on port 8001 (existing allocation)
## Database Schema
```sql
CREATE TABLE IF NOT EXISTS user_preferences (
user_id UUID PRIMARY KEY,
theme VARCHAR(10) NOT NULL DEFAULT 'system',
language VARCHAR(10) NOT NULL DEFAULT 'en',
notif_email BOOLEAN NOT NULL DEFAULT true,
notif_push BOOLEAN NOT NULL DEFAULT true,
notif_in_app BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
## Dependencies
- PostgreSQL database available (connection string via `DATABASE_URL` env var)
- `pkg/database` package for connection pooling and migrations
- `pkg/app`, `pkg/httperror`, `pkg/httpresponse`, `pkg/httpvalidation` packages (all exist)
- `pkg/auth` for JWT middleware (exists)
## Out of Scope
- User creation/management (user IDs come from an external auth system)
- Preference history/audit log
- Preference schemas beyond `theme`, `language`, `notifications`
- Admin endpoints to manage preferences for other users
- Real-time preference sync via WebSocket
- Preference import/export
## Open Questions
1. **Authorization model:** Should `GET /preferences/{user_id}` be restricted so users can only read their own preferences, or can any authenticated user read any user's preferences? (Spec assumes auth-protected but no ownership check — simplest to start.)
2. **Extensible keys:** Should the API support arbitrary preference keys beyond the three defined, or should it be a fixed schema? (Spec assumes fixed schema for type safety and validation.)
3. **Rate limiting:** Should PUT be rate-limited to prevent abuse? (Spec assumes no — can be added later via middleware.)