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
b449089fa0
commit
ba23b27974
@ -22,7 +22,7 @@ artifacts:
|
||||
status: pending
|
||||
path: review.md
|
||||
spec:
|
||||
status: pending
|
||||
status: draft
|
||||
path: spec.md
|
||||
tasks:
|
||||
status: pending
|
||||
|
||||
167
.sdlc/features/user-preferences/spec.md
Normal file
167
.sdlc/features/user-preferences/spec.md
Normal 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.
|
||||
Loading…
Reference in New Issue
Block a user