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 03:10:22 +00:00
parent b449089fa0
commit ba23b27974
2 changed files with 168 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,167 @@
# Feature: User Preferences API
## Problem Statement
Users need the ability to store and retrieve personal preferences (theme, language, notification settings) so that their experience is personalized and consistent across sessions. Currently, the preferences-api service has only scaffolded example endpoints with no real preference management functionality.
## User Stories
- As an application user, I want to retrieve my preferences so that the UI reflects my chosen theme, language, and notification settings.
- As an application user, I want to update my preferences so that changes persist across sessions.
- As a frontend application, I want to fetch preferences by user ID on login so that I can render the correct theme and locale immediately.
- As a system administrator, I want preferences stored as structured key-value pairs so that new preference types can be added without schema changes.
## Acceptance Criteria
- [ ] `GET /api/preferences-api/preferences/{user_id}` returns all preferences for a user as key-value pairs
- [ ] `GET /api/preferences-api/preferences/{user_id}` returns `200` with empty preferences object when user has no stored preferences
- [ ] `GET /api/preferences-api/preferences/{user_id}` returns `404` only if user_id format is invalid (not a valid UUID)
- [ ] `PUT /api/preferences-api/preferences/{user_id}` creates or updates preferences (upsert behavior)
- [ ] `PUT /api/preferences-api/preferences/{user_id}` accepts a JSON body with a `preferences` object containing key-value pairs
- [ ] `PUT /api/preferences-api/preferences/{user_id}` returns `200` with the updated preferences
- [ ] `PUT /api/preferences-api/preferences/{user_id}` validates known preference keys against allowed values:
- `theme`: `"light"`, `"dark"`, `"system"`
- `language`: valid BCP-47 language tag (e.g., `"en"`, `"fr"`, `"es"`, `"de"`, `"ja"`)
- `notifications_enabled`: `true` or `false`
- [ ] `PUT /api/preferences-api/preferences/{user_id}` returns `400` with details when validation fails
- [ ] Unknown preference keys are accepted and stored (extensibility for future preference types)
- [ ] Preferences are persisted in PostgreSQL and survive service restarts
- [ ] All responses follow the `{data, meta}` envelope pattern
- [ ] OpenAPI spec documents both endpoints with request/response schemas
- [ ] Handler tests cover success paths and error cases
- [ ] Service-layer tests cover business logic (validation, upsert behavior)
- [ ] Database migration creates the preferences table
## 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_enabled": true
},
"updated_at": "2026-02-09T12:00:00Z"
},
"meta": {
"request_id": "...",
"timestamp": "..."
}
}
```
### PUT /api/preferences-api/preferences/{user_id}
**Request Body:**
```json
{
"preferences": {
"theme": "dark",
"language": "fr",
"notifications_enabled": false
}
}
```
**Response 200:**
```json
{
"data": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"preferences": {
"theme": "dark",
"language": "fr",
"notifications_enabled": false
},
"updated_at": "2026-02-09T12:01:00Z"
},
"meta": {
"request_id": "...",
"timestamp": "..."
}
}
```
**Response 400 (validation error):**
```json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid preference values",
"details": {
"theme": "must be one of: light, dark, system"
}
},
"meta": {
"request_id": "...",
"timestamp": "..."
}
}
```
## Technical Constraints
- Must follow existing hexagonal architecture: domain -> service -> port -> adapter
- Must use `app.Wrap()` handler pattern, `httpresponse.*` envelope, `httperror.*` error types
- Must use `{param}` brace syntax for chi URL parameters (never colon syntax)
- Database adapter uses `pkg/database` (sqlx) with embedded SQL migrations
- Preferences stored as JSONB column in PostgreSQL for flexible key-value storage
- `user_id` is a UUID path parameter (validated at handler level)
- PUT is idempotent: creates preferences row if none exists, updates if it does (upsert via `ON CONFLICT`)
- Service runs on port 8001 under route prefix `/api/preferences-api`
## Database Schema
```sql
-- migrations/001_create_preferences.sql
CREATE TABLE IF NOT EXISTS preferences (
user_id UUID PRIMARY KEY,
preferences JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_preferences_updated_at ON preferences (updated_at);
```
## Hexagonal Architecture Mapping
| Layer | File | Responsibility |
|-------|------|----------------|
| Domain | `internal/domain/preference.go` | `UserPreferences` model, validation rules for known keys |
| Port | `internal/port/preference.go` | `PreferenceRepository` interface (`Get`, `Upsert`) |
| Service | `internal/service/preference.go` | `PreferenceService` with validation + delegation to port |
| Adapter | `internal/adapter/postgres/preference.go` | PostgreSQL implementation of `PreferenceRepository` |
| Handler | `internal/api/handlers/preference.go` | HTTP handlers for GET/PUT endpoints |
| Routes | `internal/api/routes.go` | Register preference routes |
| Spec | `internal/api/spec.go` | OpenAPI documentation for preference endpoints |
## Dependencies
- PostgreSQL database (already configured via `DATABASE_URL` in `.env.example`)
- `pkg/database` package for connection pooling and migrations
- `pkg/app`, `pkg/httperror`, `pkg/httpresponse` for handler patterns
- `pkg/openapi` for API documentation
- Existing preferences-api service scaffolding
## Out of Scope
- Authentication/authorization enforcement (auth is opt-in per CLAUDE.md; can be layered on later)
- Per-preference-key endpoints (e.g., `GET /preferences/{user_id}/theme`) - full object only
- Preference defaults management (hardcoded defaults in frontend, not API)
- Preference change history/audit log
- Bulk preference operations across multiple users
- Real-time preference change notifications (WebSocket/SSE)
- DELETE endpoint (preferences are upserted, not deleted)
## Open Questions
1. **Should unknown preference keys have value-type validation?** Currently spec allows any JSON value for unknown keys. Should we restrict to strings/booleans/numbers only?
2. **Should there be a maximum number of preference keys per user?** A limit would prevent abuse but adds complexity.
3. **Should the API enforce that `user_id` corresponds to a real user?** This requires a call to an auth/user service. Current spec treats `user_id` as an opaque UUID with no cross-service validation.
4. **Should preference values have a max size limit?** JSONB columns can hold large values; a per-value or total-size limit may be prudent.