# Feature: User Preferences API

## Problem Statement

Users of the platform need a way to persist and retrieve their personal preferences (theme, language, notification settings) across sessions and devices. Currently the preferences-api service exists as a scaffold with only example CRUD endpoints and an in-memory store. There is no real preferences domain, no database persistence, and no API for managing user preferences.

Application frontends need a reliable backend API to read and write per-user preference key-value pairs so that UI settings survive page refreshes, device switches, and service restarts.

## User Stories

- As a **frontend application**, I want to GET a user's preferences so that I can render the UI with their chosen theme, language, and notification settings.
- As a **frontend application**, I want to PUT (upsert) a user's preferences so that changes to settings are persisted immediately.
- As a **platform operator**, I want preferences stored in PostgreSQL so that they survive service restarts and are backed up with the rest of the data.
- As a **developer**, I want default preference values defined server-side so that new users get sensible defaults without client-side logic.

## Acceptance Criteria

- [ ] `GET /api/preferences-api/preferences/{user_id}` returns all preferences for the given user as a JSON object
- [ ] `GET` for a user with no stored preferences returns server-defined defaults (not 404)
- [ ] `PUT /api/preferences-api/preferences/{user_id}` creates or updates preferences for the given user (full replace of provided keys)
- [ ] `PUT` is idempotent -- calling it twice with the same body produces the same result
- [ ] Preferences are stored as key-value pairs in PostgreSQL
- [ ] The following preference keys are supported with validation:
  - `theme` -- string, one of: `light`, `dark`, `system` (default: `system`)
  - `language` -- string, BCP 47 language tag, validated format (default: `en`)
  - `notifications_enabled` -- boolean (default: `true`)
- [ ] Unknown preference keys in a PUT request are rejected with 400 Bad Request
- [ ] `user_id` path parameter is validated as a non-empty string (UUID format)
- [ ] All responses use the standard `{data, meta}` envelope
- [ ] OpenAPI spec is updated with the new endpoints and schemas
- [ ] Database migration creates the `user_preferences` table
- [ ] Existing example CRUD endpoints and domain are removed (replaced by preferences)
- [ ] Handler, service, domain, port, and adapter layers follow hexagonal architecture
- [ ] Unit tests cover service logic (defaults, validation, upsert behavior)
- [ ] Handler tests cover HTTP layer (request binding, error responses, status codes)

## Technical Constraints

- **Database**: PostgreSQL via `pkg/database` (sqlx). Migration files in `services/preferences-api/migrations/`.
- **Port**: Service runs on port 8001 (already configured).
- **URL routing**: Must use brace syntax `{user_id}` (chi router). Never colon syntax.
- **Error handling**: Domain errors mapped to HTTP errors via `httperror.*`. Handlers return `error`, wrapped with `app.Wrap()`.
- **Request binding**: Use `app.BindAndValidate()` for PUT request body.
- **Response format**: Use `httpresponse.OK()` / `httpresponse.NoContent()` for responses.
- **Auth**: Auth middleware is opt-in via config. Endpoints should be in an auth-protectable route group, but auth enforcement is not required for this feature (configurable via `AUTH_ENABLED`).
- **Preference storage model**: Each preference is a row in the `user_preferences` table with columns: `user_id`, `key`, `value`, `created_at`, `updated_at`. This allows adding new preference keys without schema changes.
- **Defaults**: When a stored preference is missing, the API merges server-defined defaults so the response always contains all known keys.

## API Design

### GET /api/preferences-api/preferences/{user_id}

**Response 200:**
```json
{
  "data": {
    "user_id": "550e8400-e29b-41d4-a716-446655440000",
    "preferences": {
      "theme": "dark",
      "language": "en",
      "notifications_enabled": true
    }
  },
  "meta": {
    "request_id": "...",
    "timestamp": "..."
  }
}
```

### PUT /api/preferences-api/preferences/{user_id}

**Request body:**
```json
{
  "preferences": {
    "theme": "dark",
    "language": "fr"
  }
}
```

Only provided keys are updated. Omitted keys retain their current value (or default if never set).

**Response 200:**
```json
{
  "data": {
    "user_id": "550e8400-e29b-41d4-a716-446655440000",
    "preferences": {
      "theme": "dark",
      "language": "fr",
      "notifications_enabled": true
    }
  },
  "meta": {
    "request_id": "...",
    "timestamp": "..."
  }
}
```

**Error 400 (unknown key):**
```json
{
  "error": {
    "code": "BAD_REQUEST",
    "message": "unknown preference key: font_size"
  },
  "meta": { "..." }
}
```

## Database Schema

```sql
CREATE TABLE user_preferences (
    user_id     UUID NOT NULL,
    key         VARCHAR(64) NOT NULL,
    value       TEXT NOT NULL,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (user_id, key)
);

CREATE INDEX idx_user_preferences_user_id ON user_preferences (user_id);
```

## Dependencies

- PostgreSQL database accessible via `DATABASE_URL` environment variable
- `pkg/database` for connection pooling and migrations
- `pkg/app`, `pkg/httperror`, `pkg/httpresponse`, `pkg/httpvalidation` for HTTP layer
- `pkg/auth` for optional authentication middleware

## Out of Scope

- Per-preference-key access control (all preferences for a user are readable/writable as a unit)
- DELETE endpoint for individual preferences (not in requirements)
- Preference history / audit log
- Bulk operations across multiple users
- WebSocket push for real-time preference sync
- Admin endpoints for managing preference definitions
- Frontend integration (separate feature)

## Open Questions

1. **Authorization model**: Should the API enforce that `user_id` in the path matches the authenticated user's ID? Or is cross-user preference access allowed (e.g., for admin tools)? *Current assumption: no enforcement, auth is opt-in via config.*
2. **Preference value types**: Should values be typed (string/bool/number) at the API level, or stored/returned as strings with client-side parsing? *Current assumption: typed in API response (theme as string, notifications_enabled as boolean), stored as TEXT in DB with serialization.*
3. **Partial vs full update semantics**: PUT currently does partial update (merge). Should it be full replace (all keys must be provided)? *Current assumption: partial merge -- only provided keys are updated, missing keys retain current values.*
