# Feature: User Preferences API

## Problem Statement

Users need the ability to persist and retrieve personal preferences (theme, language, notification settings) so that their experience is consistent across sessions and devices. Currently, the `preferences-api` service exists as scaffolding with only an example CRUD resource — there is no actual preferences domain model or persistence.

## 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 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 API call so that I can initialize the UI efficiently.
- As a **backend service**, I want to read a user's preferences to personalize behavior (e.g., notification delivery channel).

## API Design

### Endpoints

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `GET` | `/api/preferences-api/preferences/{user_id}` | Required | Get all preferences for a user |
| `PUT` | `/api/preferences-api/preferences/{user_id}` | Required | Create or replace all preferences for a user |

### Request: `PUT /api/preferences-api/preferences/{user_id}`

```json
{
  "preferences": {
    "theme": "dark",
    "language": "en",
    "notifications": {
      "email": true,
      "push": true,
      "sms": false
    }
  }
}
```

### Response: `GET /api/preferences-api/preferences/{user_id}`

```json
{
  "data": {
    "user_id": "usr_abc123",
    "preferences": {
      "theme": "dark",
      "language": "en",
      "notifications": {
        "email": true,
        "push": true,
        "sms": false
      }
    },
    "updated_at": "2026-02-08T10:30:00Z"
  },
  "meta": {
    "request_id": "req_xyz",
    "timestamp": "2026-02-08T10:30:01Z"
  }
}
```

### Storage Model

Preferences are stored as a JSON document per user (not individual key-value rows). This simplifies reads (single query) and writes (single upsert), and avoids N+1 patterns for users with many preferences.

### Known Preference Keys

| Key | Type | Default | Validation |
|-----|------|---------|------------|
| `theme` | string | `"system"` | One of: `"light"`, `"dark"`, `"system"` |
| `language` | string | `"en"` | BCP-47 language tag, max 10 chars |
| `notifications.email` | bool | `true` | — |
| `notifications.push` | bool | `true` | — |
| `notifications.sms` | bool | `false` | — |

The schema should be extensible — unknown keys are preserved but not validated, allowing new preference keys to be introduced without schema migrations.

## Acceptance Criteria

- [ ] `GET /api/preferences-api/preferences/{user_id}` returns the user's preferences with a `200` response in the standard `{data, meta}` envelope.
- [ ] `GET` returns default preferences (`theme: "system"`, `language: "en"`, `notifications: {email: true, push: true, sms: false}`) when no preferences have been saved for the user.
- [ ] `PUT /api/preferences-api/preferences/{user_id}` creates or fully replaces the user's preferences, returning `200`.
- [ ] `PUT` validates known preference keys: rejects invalid `theme` values, rejects `language` values exceeding 10 characters.
- [ ] `PUT` with invalid input returns `400 Bad Request` with a descriptive error in the standard error envelope.
- [ ] Both endpoints require authentication via `auth.Middleware()`.
- [ ] The authenticated user can only access their own preferences (the `user_id` in the path must match the authenticated user's ID), returning `403 Forbidden` otherwise.
- [ ] Preferences are persisted to PostgreSQL and survive service restarts.
- [ ] The database schema uses an upsert pattern — `PUT` to a new `user_id` creates the record, `PUT` to an existing `user_id` replaces it.
- [ ] OpenAPI spec is updated with both endpoints, request/response schemas, and examples.
- [ ] Handler, service, and domain layers follow existing hexagonal architecture patterns.
- [ ] Unit tests cover: handler request/response mapping, service business logic (defaults, validation, authorization), domain validation rules.
- [ ] URL parameters use brace syntax `{user_id}` (not colon syntax).

## Technical Constraints

- **Architecture**: Must follow the existing hexagonal architecture (domain → service → port → adapter) established in the preferences-api service.
- **Database**: PostgreSQL via `pkg/database`. Migrations embedded with `//go:embed`. Single table with JSONB column for preferences.
- **Auth**: Endpoints protected by `auth.Middleware()`. User ID extracted from JWT claims via `auth.GetUser(ctx)`.
- **Response format**: All responses use `httpresponse.OK/Created/NoContent` helpers for the `{data, meta}` envelope.
- **Error handling**: Domain errors mapped to HTTP errors in handlers via `mapDomainError()` pattern. Use `httperror.BadRequest`, `httperror.NotFound`, `httperror.Forbidden`.
- **Request binding**: Use `app.BindAndValidate()` for PUT request body.
- **Router**: chi router with `{param}` brace syntax for URL parameters.
- **Port**: Service runs on port 8001 (already configured).

## Dependencies

- `pkg/database` — PostgreSQL connection and migration support (already exists).
- `pkg/auth` — JWT middleware and user extraction (already exists).
- `pkg/app`, `pkg/httperror`, `pkg/httpresponse`, `pkg/httpvalidation` — HTTP framework (already exists).
- A running PostgreSQL instance for persistence (managed via `DATABASE_URL` env var).

## Out of Scope

- **Bulk preferences for multiple users** — Only single-user GET/PUT in this iteration.
- **PATCH (partial update)** — Full replacement via PUT only. Partial updates may be added later.
- **DELETE endpoint** — No need to delete preferences (they reset to defaults if the row is removed manually).
- **Preference history/audit log** — No tracking of preference changes over time.
- **Admin override** — No admin endpoint to modify another user's preferences.
- **Frontend integration** — API only; frontend changes are a separate feature.
- **Rate limiting** — Handled at the infrastructure layer, not in this feature.

## Open Questions

1. **Authorization model**: Should the authenticated user's ID come from the JWT `sub` claim, or is there a separate `user_id` field in the token? (Needs alignment with `auth.GetUser()` contract.)
2. **Unknown preference keys**: Should unknown keys be silently accepted and stored, or rejected with a validation error? (Spec currently assumes they are preserved.)
3. **Preference size limit**: Should there be a maximum size for the preferences JSON document to prevent abuse? (e.g., 10KB limit.)
