From 07294fcf8f893fdc751ca80ea2cc39e61e6c50c8 Mon Sep 17 00:00:00 2001 From: rdev-worker Date: Mon, 9 Feb 2026 02:18:10 +0000 Subject: [PATCH] build: /spec-feature user-preferences --requirements 'CRUD API for user pref... --- .sdlc/features/user-preferences/manifest.yaml | 2 +- .sdlc/features/user-preferences/spec.md | 132 ++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 .sdlc/features/user-preferences/spec.md diff --git a/.sdlc/features/user-preferences/manifest.yaml b/.sdlc/features/user-preferences/manifest.yaml index 683c304..ba067b7 100644 --- a/.sdlc/features/user-preferences/manifest.yaml +++ b/.sdlc/features/user-preferences/manifest.yaml @@ -22,7 +22,7 @@ artifacts: status: pending path: review.md spec: - status: pending + status: draft path: spec.md tasks: status: pending diff --git a/.sdlc/features/user-preferences/spec.md b/.sdlc/features/user-preferences/spec.md new file mode 100644 index 0000000..af9f131 --- /dev/null +++ b/.sdlc/features/user-preferences/spec.md @@ -0,0 +1,132 @@ +# Feature: User Preferences API + +## Problem Statement + +Users need a way to persist and retrieve their personal preferences (theme, language, notification settings) so that their experience is consistent across sessions and devices. Currently, the `preferences-api` service exists as a scaffold with example CRUD endpoints but no real domain logic or database persistence. This feature replaces the example scaffolding with a real user preferences domain. + +## 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 my 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 request so that I can initialize the UI efficiently. +- As a frontend application, I want to update one or more preferences in a single request so that partial updates are supported without overwriting unrelated settings. + +## API Design + +### Endpoints + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/preferences-api/preferences/{user_id}` | Retrieve all preferences for a user | +| PUT | `/api/preferences-api/preferences/{user_id}` | Create or update preferences for a user (upsert) | + +### GET /preferences/{user_id} + +**Response 200:** +```json +{ + "data": { + "user_id": "550e8400-e29b-41d4-a716-446655440000", + "preferences": { + "theme": "dark", + "language": "en", + "notifications": { + "email": true, + "push": false, + "digest": "weekly" + } + }, + "updated_at": "2026-02-09T12:00:00Z" + }, + "meta": { + "request_id": "...", + "timestamp": "..." + } +} +``` + +**Response 404:** User has no preferences saved (returns default preferences instead — see Open Questions). + +### PUT /preferences/{user_id} + +Upserts preferences. Merges provided keys with existing preferences (partial update). Keys not included in the request body are left unchanged. + +**Request Body:** +```json +{ + "preferences": { + "theme": "light", + "notifications": { + "push": true + } + } +} +``` + +**Response 200:** Returns the full merged preference set after update. + +## Preference Keys + +| Key | Type | Allowed Values | Default | +|-----|------|---------------|---------| +| `theme` | string | `"light"`, `"dark"`, `"system"` | `"system"` | +| `language` | string | BCP 47 language tag (e.g., `"en"`, `"fr"`, `"es"`) | `"en"` | +| `notifications.email` | boolean | `true`, `false` | `true` | +| `notifications.push` | boolean | `true`, `false` | `true` | +| `notifications.digest` | string | `"daily"`, `"weekly"`, `"never"` | `"weekly"` | + +## Acceptance Criteria + +- [ ] GET `/preferences/{user_id}` returns stored preferences for the given user +- [ ] GET for a user with no saved preferences returns default preference values (not 404) +- [ ] PUT `/preferences/{user_id}` creates preferences if none exist (upsert) +- [ ] PUT `/preferences/{user_id}` merges provided keys with existing preferences (partial update) +- [ ] PUT validates `theme` against allowed values (`light`, `dark`, `system`) +- [ ] PUT validates `language` is a non-empty string +- [ ] PUT validates `notifications.digest` against allowed values (`daily`, `weekly`, `never`) +- [ ] PUT validates `notifications.email` and `notifications.push` are booleans +- [ ] Invalid preference values return 400 Bad Request with descriptive error details +- [ ] Invalid `user_id` format (non-UUID) returns 400 Bad Request +- [ ] All responses use the standard `{data, meta}` envelope +- [ ] OpenAPI spec documents both endpoints with request/response schemas +- [ ] Preferences are persisted in PostgreSQL (survive service restarts) +- [ ] Database migration creates the preferences table +- [ ] Domain logic is separated from HTTP handlers (hexagonal architecture) +- [ ] Service and handler layers have unit tests +- [ ] Existing example scaffolding is removed and replaced with preferences domain + +## Technical Constraints + +- Must follow the existing hexagonal architecture pattern: domain -> service -> port -> adapter +- Must use `app.Wrap()` for handler error returns, `app.BindAndValidate()` for request binding +- Must use `httpresponse.OK` / `httpresponse.Created` for response envelope +- Must use `httperror.*` for HTTP error types +- Must use `{param}` brace syntax for chi URL parameters (not `:param`) +- Must use `pkg/database` for PostgreSQL connection +- Database migration files go in `services/preferences-api/migrations/` +- Preferences stored as JSONB in PostgreSQL for flexible schema +- `user_id` is a UUID provided by the caller (no user management in this service) + +## Dependencies + +- `pkg/database` package for PostgreSQL connectivity +- `pkg/app`, `pkg/httpresponse`, `pkg/httperror` for HTTP patterns (already in use) +- PostgreSQL database instance (connection via `DATABASE_URL` env var, already in config) +- No external service dependencies + +## Out of Scope + +- User authentication/authorization (auth middleware exists but user identity validation is not part of this feature) +- User management or user creation +- Preference change history/audit log +- Preference schemas beyond theme, language, and notifications +- Real-time preference sync (WebSocket push on change) +- Bulk operations across multiple users +- Rate limiting + +## Open Questions + +1. **Default preferences on GET for unknown user:** Should GET for a user with no saved preferences return a 200 with defaults, or a 404? (Spec assumes 200 with defaults for better frontend DX.) +2. **Unknown preference keys:** Should PUT reject unknown keys not in the defined schema, or silently accept them for forward compatibility? +3. **User ID validation beyond format:** Should the service verify the user_id corresponds to a real user (via inter-service call), or accept any valid UUID?