diff --git a/.sdlc/features/user-preferences/manifest.yaml b/.sdlc/features/user-preferences/manifest.yaml index a1e743a..7c0b3e5 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..22fe359 --- /dev/null +++ b/.sdlc/features/user-preferences/spec.md @@ -0,0 +1,141 @@ +# Feature: User Preferences API + +## Problem Statement + +Users need a way to persist and retrieve their application preferences (theme, language, notification settings) so that their experience is consistent across sessions and devices. Currently, the `preferences-api` service contains only scaffold code with no preference management functionality. + +## User Stories + +- As a user, I want to save my theme preference (light/dark) so that the UI renders consistently across sessions. +- As a user, I want to save my language preference so that the application displays content in my chosen language. +- As a user, I want to configure my notification settings so that I only receive the notifications I care about. +- As a user, I want to retrieve all my preferences in a single request so that the frontend can initialize quickly. +- As a user, I want to update individual preferences without overwriting others so that partial updates are safe. + +## Acceptance Criteria + +- [ ] `GET /api/preferences-api/preferences/{user_id}` returns all preferences for the given user as key-value pairs +- [ ] `GET /api/preferences-api/preferences/{user_id}` returns an empty preferences object (not 404) for users with no saved preferences +- [ ] `PUT /api/preferences-api/preferences/{user_id}` creates or updates preferences for the given user (upsert semantics) +- [ ] `PUT /api/preferences-api/preferences/{user_id}` supports partial updates — only provided keys are changed, omitted keys are preserved +- [ ] Supported preference keys: `theme` (values: `light`, `dark`), `language` (ISO 639-1 codes, e.g. `en`, `es`, `fr`), `notifications_enabled` (boolean) +- [ ] Invalid preference values are rejected with a 400 Bad Request and descriptive error message +- [ ] Unknown preference keys are rejected with a 400 Bad Request +- [ ] Both endpoints require authentication — unauthenticated requests receive 401 +- [ ] Users can only access their own preferences — mismatched user ID returns 403 +- [ ] Preferences are persisted to PostgreSQL (not in-memory) +- [ ] All endpoints return responses in the standard `{data, meta}` envelope +- [ ] Endpoints are documented in OpenAPI spec via `spec.go` +- [ ] Domain layer validates preference keys and values independently of HTTP layer +- [ ] Service, handler, and repository layers follow existing hexagonal architecture patterns +- [ ] Unit tests cover service layer business logic (valid/invalid keys, partial updates, authorization) +- [ ] Integration tests cover handler layer HTTP behavior (status codes, response format, error cases) + +## API Design + +### GET /api/preferences-api/preferences/{user_id} + +**Response 200:** +```json +{ + "data": { + "user_id": "uuid", + "preferences": { + "theme": "dark", + "language": "en", + "notifications_enabled": true + }, + "updated_at": "2026-02-08T12:00:00Z" + }, + "meta": { + "request_id": "...", + "timestamp": "..." + } +} +``` + +**Response 200 (no preferences saved yet):** +```json +{ + "data": { + "user_id": "uuid", + "preferences": {}, + "updated_at": null + }, + "meta": { ... } +} +``` + +### PUT /api/preferences-api/preferences/{user_id} + +**Request:** +```json +{ + "preferences": { + "theme": "dark", + "notifications_enabled": false + } +} +``` + +**Response 200:** +```json +{ + "data": { + "user_id": "uuid", + "preferences": { + "theme": "dark", + "language": "en", + "notifications_enabled": false + }, + "updated_at": "2026-02-08T12:00:05Z" + }, + "meta": { ... } +} +``` + +## Technical Constraints + +- Must use PostgreSQL for persistence (migration in `services/preferences-api/migrations/`) +- Must follow the existing hexagonal architecture: domain → service → port → adapter +- Must use `app.Wrap()` handler pattern, `app.BindAndValidate()` for request binding +- Must use `httperror.*` for error responses, `httpresponse.*` for success responses +- Must use chi router with `{param}` brace syntax for URL parameters +- Must use `auth.Middleware()` for authentication and validate user ownership +- Preference keys are a closed set — only `theme`, `language`, `notifications_enabled` are accepted +- Preference values must be validated per-key (enum for theme, ISO code pattern for language, boolean for notifications) + +## Database Schema + +```sql +CREATE TABLE user_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() +); +``` + +Storing preferences as JSONB allows flexible key-value storage while the application layer enforces the allowed key set and value validation. + +## Dependencies + +- PostgreSQL database accessible from the service (connection config via `DatabaseConfig`) +- Authentication system functional (`auth.Middleware()` with JWT validation) +- Existing `pkg/app`, `pkg/httperror`, `pkg/httpresponse`, `pkg/auth`, `pkg/openapi` packages + +## Out of Scope + +- Preference defaults / fallback values at the API level (frontend handles defaults) +- Preference history or versioning +- Bulk preference operations across multiple users +- Admin endpoints to manage other users' preferences +- Real-time preference change notifications (WebSocket/SSE) +- Custom/user-defined preference keys beyond the initial set + +## Open Questions + +1. **Language validation strictness:** Should language values be validated against a specific list of supported languages, or accept any valid ISO 639-1 code? +2. **Default preferences:** Should the API return default values for unset preferences (e.g., `theme: "light"` if never set), or let the frontend handle defaults? +3. **Rate limiting:** Should preference updates be rate-limited to prevent abuse? +4. **Removing the example scaffold:** Should the existing Example entity/handlers/routes be removed as part of this feature, or left for reference?