Backend: User-Preferences-Speicher (JSONB-Tabelle + API) #214

Open
opened 2026-07-03 20:12:35 +02:00 by niboer · 2 comments
Owner

Motivation: Es gibt bisher keinen zentralen Speicher fuer Benutzereinstellungen (z. B. Locale/Sprache, UI-Praferenzen, Benachrichtigungseinstellungen). Ein separates JSONB-Feld in der persons-Tabelle zu ergaenzen wuerde diese unnötig aufblähen und bei jedem GET /persons Call mitgeliefert werden.

Vorschlag: Separate Tabelle userPreferences

userPreferences
├── personId   UUID (PK, FK → persons, onDelete: Cascade)
└── preferences JSONB (NOT NULL, DEFAULT '{}')

Vorteile:

  • Kein Einfluss auf bestehende Person-API (kein zusätzliches Feld in Responses)
  • Beliebig erweiterbar ohne Schema-Migration — JSONB ist flexibel
  • Separat ladbar: Nur bei Bedarf abrufen

API:

Methode Endpunkt Beschreibung
GET /api/v1/persons/{person_id}/preferences Preferences abrufen (Eigendaten oder admin)
PUT /api/v1/persons/{person_id}/preferences Preferences ueberschreiben (merged)

Nicht Teil dieses Issues:

  • Die tatsaechliche Nutzung der Preferences im Frontend (Locale-Switcher, UI-Einstellungen)
  • Validierung einzelner Preference-Keys (zunaechst beliebiges JSON)

Milestone: MS-REST (#32)
Labels: component/backend, prio/medium, type/feature

**Motivation:** Es gibt bisher keinen zentralen Speicher fuer Benutzereinstellungen (z. B. Locale/Sprache, UI-Praferenzen, Benachrichtigungseinstellungen). Ein separates JSONB-Feld in der `persons`-Tabelle zu ergaenzen wuerde diese unnötig aufblähen und bei jedem `GET /persons` Call mitgeliefert werden. **Vorschlag: Separate Tabelle `userPreferences`** ``` userPreferences ├── personId UUID (PK, FK → persons, onDelete: Cascade) └── preferences JSONB (NOT NULL, DEFAULT '{}') ``` **Vorteile:** - Kein Einfluss auf bestehende Person-API (kein zusätzliches Feld in Responses) - Beliebig erweiterbar ohne Schema-Migration — JSONB ist flexibel - Separat ladbar: Nur bei Bedarf abrufen **API:** | Methode | Endpunkt | Beschreibung | |---------|----------|-------------| | GET | `/api/v1/persons/{person_id}/preferences` | Preferences abrufen (Eigendaten oder admin) | | PUT | `/api/v1/persons/{person_id}/preferences` | Preferences ueberschreiben (merged) | **Nicht Teil dieses Issues:** - Die tatsaechliche Nutzung der Preferences im Frontend (Locale-Switcher, UI-Einstellungen) - Validierung einzelner Preference-Keys (zunaechst beliebiges JSON) **Milestone:** MS-REST (#32) **Labels:** component/backend, prio/medium, type/feature
niboer self-assigned this 2026-07-03 20:12:35 +02:00
Author
Owner

Erweiterung aus der Diskussion:

  • GET /api/v1/persons/me soll preferences-Feld mitliefern (Default {})
  • Erste konkrete Nutzung: Sprachauswahl in den Account-Einstellungen
    • Dropdown: Deutsch (DE, Default) / English (EN)
    • Persistiert via PUT /api/v1/persons/:person_id/preferences (merged)
    • Vorhandenes ui_locale-Cookie bleibt als Cache, Preferences sind Source of Truth

Update Account-Seite (app/account):

  • Sprach-Card bereits vorhanden, aber nur client-seitig per Cookie
  • Neu: Speichert zusaetzlich im Backend via Preferences-API
  • App-Start: Locale aus /api/v1/persons/me lesen (falls vorhanden) → Cookie → "de"

Preferences-Schema (JSONB): {"locale": "de"}

Betrifft jetzt auch Frontend (component/frontend).

**Erweiterung aus der Diskussion:** - `GET /api/v1/persons/me` soll preferences-Feld mitliefern (Default `{}`) - Erste konkrete Nutzung: Sprachauswahl in den Account-Einstellungen - Dropdown: Deutsch (DE, Default) / English (EN) - Persistiert via `PUT /api/v1/persons/:person_id/preferences` (merged) - Vorhandenes `ui_locale`-Cookie bleibt als Cache, Preferences sind Source of Truth **Update Account-Seite (`app/account`):** - Sprach-Card bereits vorhanden, aber nur client-seitig per Cookie - Neu: Speichert zusaetzlich im Backend via Preferences-API - App-Start: Locale aus `/api/v1/persons/me` lesen (falls vorhanden) → Cookie → "de" **Preferences-Schema (JSONB):** `{"locale": "de"}` **Betrifft jetzt auch Frontend** (component/frontend).
Author
Owner

Zusaetzliche Anforderung aus Diskussion:

Auth: Preferences-Endpunkte nur fuer die Person selbst (Eigendaten):

  • GET /api/v1/persons/{person_id}/preferences: requireAuth + requireOwnData('person_id')
  • PUT /api/v1/persons/{person_id}/preferences: requireAuth + requireOwnData('person_id')
  • Kein Admin-Zugriff — nur die Person selbst darf ihre Preferences lesen/schreiben.

PUT-Logik — Keycloak-Sync bei Locale-Aenderung:

  • Wenn newPreferences.locale vorhanden ist UND person.keycloakId gesetzt ist:
    • PUT /admin/realms/{realm}/users/{keycloakId} mit { attributes: { locale: [newPreferences.locale] } }
  • Dadurch werden auch Keycloak-E-Mails (z. B. Passwort-vergessen) in der gewaehlten Sprache versendet.
  • Preferences werden per Merge gespeichert (bestehende Keys bleiben erhalten).

Betroffene Dateien:

  • backend/src/routes/persons.ts — neue Route + Handler + kcApi-Import
  • backend/src/__tests__/persons.test.ts — Tests fuer neuen Preferences-Endpunkt
  • backend/prisma/schema.prisma — neues Model UserPreferences
  • DB-Migration (0001_full_schema ueberschreiben)
  • docs/design/03-autorisierungsmatrix.md — neuen Endpunkt eintragen
  • backend/openapi.yaml — Preferences-Schema + Endpunkt
  • frontend/src/routes/app/account/+page.svelte — Sprach-Select speichert via Preferences-API statt nur Cookie
**Zusaetzliche Anforderung aus Diskussion:** **Auth:** Preferences-Endpunkte nur fuer die Person selbst (Eigendaten): - `GET /api/v1/persons/{person_id}/preferences`: `requireAuth` + `requireOwnData('person_id')` - `PUT /api/v1/persons/{person_id}/preferences`: `requireAuth` + `requireOwnData('person_id')` - Kein Admin-Zugriff — nur die Person selbst darf ihre Preferences lesen/schreiben. **PUT-Logik — Keycloak-Sync bei Locale-Aenderung:** - Wenn `newPreferences.locale` vorhanden ist UND `person.keycloakId` gesetzt ist: - `PUT /admin/realms/{realm}/users/{keycloakId}` mit `{ attributes: { locale: [newPreferences.locale] } }` - Dadurch werden auch Keycloak-E-Mails (z. B. Passwort-vergessen) in der gewaehlten Sprache versendet. - Preferences werden per Merge gespeichert (bestehende Keys bleiben erhalten). **Betroffene Dateien:** - `backend/src/routes/persons.ts` — neue Route + Handler + kcApi-Import - `backend/src/__tests__/persons.test.ts` — Tests fuer neuen Preferences-Endpunkt - `backend/prisma/schema.prisma` — neues Model `UserPreferences` - DB-Migration (0001_full_schema ueberschreiben) - `docs/design/03-autorisierungsmatrix.md` — neuen Endpunkt eintragen - `backend/openapi.yaml` — Preferences-Schema + Endpunkt - `frontend/src/routes/app/account/+page.svelte` — Sprach-Select speichert via Preferences-API statt nur Cookie
Sign in to join this conversation.
No description provided.