# 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?
