From b2e88a92a3d2682a0d9de5cb10573cf38cc99e38 Mon Sep 17 00:00:00 2001 From: rdev-worker Date: Sun, 8 Feb 2026 18:18:39 +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 | 77 +++++++++++++++++++ 2 files changed, 78 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 3a0a97b..6e9f98d 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..74165e7 --- /dev/null +++ b/.sdlc/features/user-preferences/spec.md @@ -0,0 +1,77 @@ +# Feature: User Preferences API + +## Problem Statement + +Users of the platform need a way to persist and retrieve personal preferences (theme, language, notification settings) so their experience is consistent across sessions and devices. Currently, the `preferences-api` service exists as a scaffold with only example CRUD endpoints and no actual preference management capability. + +## User Stories + +- As an authenticated user, I want to retrieve my preferences so that the UI reflects my saved settings when I log in. +- As an authenticated user, I want to update my preferences so that changes to theme, language, or notification settings are persisted. +- As a frontend application, I want to fetch preferences for the current user so I can apply theme/language/notification configuration on page load. +- As an admin or service, I want to retrieve preferences for a specific user by user ID so I can provide user-specific experiences in server-rendered contexts. + +## Acceptance Criteria + +- [ ] `GET /api/preferences-api/preferences/{user_id}` returns the user's preferences as a key-value map within the standard `{data, meta}` envelope +- [ ] `PUT /api/preferences-api/preferences/{user_id}` creates or fully replaces the user's preferences (upsert semantics) +- [ ] Both endpoints require authentication via the existing `auth.Middleware()` +- [ ] Authenticated users can only access their own preferences (the `{user_id}` in the URL must match the JWT `UserID`/`Subject`) +- [ ] Preferences are stored as key-value pairs with string keys and JSON-compatible values +- [ ] The following preference keys are supported with validation: + - `theme`: must be one of `"light"`, `"dark"`, `"system"` + - `language`: must be a valid BCP-47 language tag (e.g., `"en"`, `"fr"`, `"es"`, `"de"`, `"ja"`) + - `notifications.email`: must be a boolean + - `notifications.push`: must be a boolean + - `notifications.digest`: must be one of `"none"`, `"daily"`, `"weekly"` +- [ ] `GET` for a user with no saved preferences returns a `200` with default values (not a 404) +- [ ] Default preference values: `theme: "system"`, `language: "en"`, `notifications.email: true`, `notifications.push: true`, `notifications.digest: "weekly"` +- [ ] `PUT` with unknown preference keys returns `400 Bad Request` with a descriptive error +- [ ] `PUT` with invalid preference values returns `400 Bad Request` with per-field validation errors +- [ ] Preferences are persisted in PostgreSQL (using the existing `DatabaseConfig`) +- [ ] The domain layer contains pure preference models with validation logic, following hexagonal architecture +- [ ] A `PreferenceRepository` port interface is defined, with a PostgreSQL adapter implementation +- [ ] A `PreferenceService` orchestrates business logic between handler and repository +- [ ] OpenAPI spec is updated to document both endpoints with schemas, examples, and error responses +- [ ] Unit tests cover domain validation, service logic, and handler request/response mapping +- [ ] Integration-style tests verify handler behavior using the in-memory repository adapter +- [ ] The existing example CRUD scaffolding is removed and replaced with preference endpoints + +## Technical Constraints + +- **Architecture**: Must follow the established hexagonal architecture pattern (domain -> service -> port -> adapter) already present in `services/preferences-api/` +- **URL parameters**: Must use brace syntax `{user_id}` (not `:user_id`) per chi router requirements +- **Request binding**: Must use `app.Bind()` / `app.BindAndValidate()` -- never raw `json.NewDecoder` +- **Error handling**: Handlers return `error`, wrapped with `app.Wrap()`. Use `httperror.BadRequest`, `httperror.NotFound`, `httperror.Forbidden` etc. +- **Response format**: All responses use the `{data, meta}` envelope via `httpresponse.OK`, `httpresponse.Created`, etc. +- **Auth identity**: User identity is extracted from JWT via `auth.GetUser(ctx)` which returns `*auth.User` with an `ID` field +- **Database**: PostgreSQL, connection details via `config.DatabaseConfig` (environment variable `DATABASE_URL`) +- **Port**: The service runs on port 8001 (default) +- **Route prefix**: All routes must be under `/api/preferences-api/` to match ingress routing + +## Dependencies + +- `pkg/auth` -- JWT middleware and `auth.GetUser()` for extracting authenticated user identity +- `pkg/app` -- `app.Wrap()`, `app.Bind()`, `app.BindAndValidate()` for handler patterns +- `pkg/httperror` -- Typed HTTP error constructors +- `pkg/httpresponse` -- Response envelope helpers +- `pkg/openapi` -- OpenAPI spec builder for documentation +- `pkg/config` -- `DatabaseConfig` for PostgreSQL connection parameters +- PostgreSQL database instance (local dev via `docker-compose` or `scripts/dev.sh`) + +## Out of Scope + +- **Bulk operations**: No endpoint for fetching/updating preferences for multiple users at once +- **Preference history/audit log**: No versioning or history of preference changes +- **Real-time sync**: No WebSocket or push notification when preferences change on another device +- **Custom/arbitrary preference keys**: Only the defined keys (theme, language, notifications.*) are supported -- extensibility is deferred +- **DELETE endpoint**: Preferences are reset to defaults via `PUT`, not deleted +- **Pagination**: Preferences are a fixed small set per user, no pagination needed +- **Admin override**: No endpoint for admins to modify another user's preferences (admin read is permitted for server-rendered contexts but write is not) + +## Open Questions + +1. **Admin read access**: Should admin users (identified by a role in JWT) be allowed to read other users' preferences, or should the `{user_id}` always be strictly self-only? The spec currently allows admin read but this needs confirmation. +2. **Preference key extensibility**: Should the API accept and store unknown preference keys (with a max key count limit), or strictly reject them? The spec currently rejects unknown keys. +3. **Partial updates (PATCH)**: Should a `PATCH` endpoint be added for updating individual preference keys without sending the full set, or is `PUT` (full replace) sufficient for MVP? +4. **Rate limiting**: Should preference updates be rate-limited to prevent abuse, or is auth sufficient protection?