build: /spec-feature user-preferences --requirements 'CRUD API for user pref...
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
9647e30a08
commit
b941ed2b5c
@ -22,7 +22,7 @@ artifacts:
|
||||
status: pending
|
||||
path: review.md
|
||||
spec:
|
||||
status: pending
|
||||
status: draft
|
||||
path: spec.md
|
||||
tasks:
|
||||
status: pending
|
||||
|
||||
154
.sdlc/features/user-preferences/spec.md
Normal file
154
.sdlc/features/user-preferences/spec.md
Normal 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.)
|
||||
Loading…
Reference in New Issue
Block a user