mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 23:01:09 +02:00
- Add settings module to mana-core-auth with REST API endpoints - Create user_settings table with globalSettings and appOverrides (JSONB) - Add createUserSettingsStore() factory in shared-theme package - Integrate user settings in all app layouts (calendar, chat, contacts, etc.) - Support for nav position, theme, locale settings with per-app overrides - Optimistic updates with localStorage caching for offline support - Add comprehensive documentation in docs/USER_SETTINGS.md API Endpoints: - GET /api/v1/settings - Get all user settings - PATCH /api/v1/settings/global - Update global settings - PATCH /api/v1/settings/app/:appId - Set app override - DELETE /api/v1/settings/app/:appId - Remove app override 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
357 lines
11 KiB
Markdown
357 lines
11 KiB
Markdown
# Zentrale User Settings
|
|
|
|
Die User Settings werden zentral in `mana-core-auth` gespeichert und über alle Apps synchronisiert. Dies ermöglicht eine konsistente Benutzererfahrung über das gesamte Mana-Ökosystem.
|
|
|
|
## Architektur
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ mana-core-auth │
|
|
│ (Port 3001) │
|
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
│ │ auth.user_settings Table │ │
|
|
│ │ ┌─────────────────────────────────────────────────┐ │ │
|
|
│ │ │ user_id │ globalSettings │ appOverrides │ │ │
|
|
│ │ │─────────│────────────────│──────────────────────│ │ │
|
|
│ │ │ uuid │ {nav, theme, │ {"calendar": {...}, │ │ │
|
|
│ │ │ │ locale} │ "chat": {...}} │ │ │
|
|
│ │ └─────────────────────────────────────────────────┘ │ │
|
|
│ └─────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
│
|
|
│ REST API (JWT Auth)
|
|
│
|
|
┌──────────────────────┼──────────────────────┐
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
│Calendar │ │ Chat │ │ Picture │
|
|
│ App │ │ App │ │ App │
|
|
└─────────┘ └─────────┘ └─────────┘
|
|
```
|
|
|
|
## Datenstruktur
|
|
|
|
### Global Settings
|
|
|
|
Globale Einstellungen gelten für **alle Apps** als Standardwerte:
|
|
|
|
```typescript
|
|
interface GlobalSettings {
|
|
nav: {
|
|
desktopPosition: 'top' | 'bottom'; // Position der Navigation auf Desktop
|
|
sidebarCollapsed: boolean; // Sidebar eingeklappt?
|
|
};
|
|
theme: {
|
|
mode: 'light' | 'dark' | 'system'; // Theme-Modus
|
|
colorScheme: string; // 'ocean' | 'nature' | 'lume' | 'stone'
|
|
};
|
|
locale: string; // 'de' | 'en' | 'fr' | 'es' | 'it'
|
|
}
|
|
```
|
|
|
|
**Standardwerte:**
|
|
```typescript
|
|
{
|
|
nav: { desktopPosition: 'top', sidebarCollapsed: false },
|
|
theme: { mode: 'system', colorScheme: 'ocean' },
|
|
locale: 'de'
|
|
}
|
|
```
|
|
|
|
### App Overrides
|
|
|
|
Jede App kann die globalen Einstellungen überschreiben:
|
|
|
|
```typescript
|
|
interface AppOverride {
|
|
nav?: Partial<NavSettings>;
|
|
theme?: Partial<ThemeSettings>;
|
|
}
|
|
|
|
// Beispiel: Calendar hat eigene Einstellungen
|
|
{
|
|
"calendar": {
|
|
nav: { desktopPosition: 'bottom' },
|
|
theme: { colorScheme: 'nature' }
|
|
},
|
|
"chat": {
|
|
theme: { mode: 'dark' }
|
|
}
|
|
}
|
|
```
|
|
|
|
### Cascading / Auflösung
|
|
|
|
Die effektiven Settings einer App werden durch Merging berechnet:
|
|
|
|
```
|
|
Effektive Settings = Global Settings + App Override (falls vorhanden)
|
|
```
|
|
|
|
Beispiel:
|
|
- Global: `{ theme: { mode: 'system', colorScheme: 'ocean' } }`
|
|
- Calendar Override: `{ theme: { colorScheme: 'nature' } }`
|
|
- **Effektiv für Calendar:** `{ theme: { mode: 'system', colorScheme: 'nature' } }`
|
|
|
|
## Backend API
|
|
|
|
### Endpoints
|
|
|
|
Alle Endpoints erfordern JWT-Authentifizierung via `Authorization: Bearer <token>`.
|
|
|
|
| Endpoint | Method | Beschreibung |
|
|
|----------|--------|--------------|
|
|
| `/api/v1/settings` | GET | Alle Settings des Users abrufen |
|
|
| `/api/v1/settings/global` | PATCH | Globale Settings aktualisieren |
|
|
| `/api/v1/settings/app/:appId` | PATCH | App-Override setzen/aktualisieren |
|
|
| `/api/v1/settings/app/:appId` | DELETE | App-Override löschen (zurück zu Global) |
|
|
|
|
### Beispiel-Requests
|
|
|
|
```bash
|
|
# Token holen
|
|
TOKEN=$(curl -s -X POST http://localhost:3001/api/v1/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email": "user@example.com", "password": "password"}' | jq -r '.accessToken')
|
|
|
|
# Alle Settings abrufen
|
|
curl http://localhost:3001/api/v1/settings \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
|
|
# Response:
|
|
{
|
|
"success": true,
|
|
"globalSettings": {
|
|
"nav": { "desktopPosition": "top", "sidebarCollapsed": false },
|
|
"theme": { "mode": "system", "colorScheme": "ocean" },
|
|
"locale": "de"
|
|
},
|
|
"appOverrides": {}
|
|
}
|
|
|
|
# Sprache global ändern
|
|
curl -X PATCH http://localhost:3001/api/v1/settings/global \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"locale": "en"}'
|
|
|
|
# Theme nur für Calendar überschreiben
|
|
curl -X PATCH http://localhost:3001/api/v1/settings/app/calendar \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"theme": {"colorScheme": "nature"}}'
|
|
|
|
# Calendar-Override löschen (zurück zu Global)
|
|
curl -X DELETE http://localhost:3001/api/v1/settings/app/calendar \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
```
|
|
|
|
## Frontend Integration
|
|
|
|
### Store erstellen
|
|
|
|
Jede App erstellt einen eigenen User Settings Store in `src/lib/stores/user-settings.svelte.ts`:
|
|
|
|
```typescript
|
|
import { createUserSettingsStore } from '@manacore/shared-theme';
|
|
import { authStore } from './auth.svelte';
|
|
|
|
export const userSettings = createUserSettingsStore({
|
|
appId: 'calendar', // Eindeutige App-ID
|
|
authUrl: 'http://localhost:3001',
|
|
getAccessToken: () => authStore.getAccessToken()
|
|
});
|
|
```
|
|
|
|
### Store laden
|
|
|
|
Im Root-Layout nach erfolgreicher Authentifizierung:
|
|
|
|
```svelte
|
|
<script lang="ts">
|
|
import { userSettings } from '$lib/stores/user-settings.svelte';
|
|
import { authStore } from '$lib/stores/auth.svelte';
|
|
import { onMount } from 'svelte';
|
|
|
|
onMount(async () => {
|
|
await authStore.initialize();
|
|
|
|
if (authStore.isAuthenticated) {
|
|
await userSettings.load();
|
|
}
|
|
});
|
|
</script>
|
|
```
|
|
|
|
### Settings lesen
|
|
|
|
```svelte
|
|
<script lang="ts">
|
|
import { userSettings } from '$lib/stores/user-settings.svelte';
|
|
</script>
|
|
|
|
<!-- Resolved Settings (Global + App Override) -->
|
|
<p>Nav Position: {userSettings.nav.desktopPosition}</p>
|
|
<p>Theme Mode: {userSettings.theme.mode}</p>
|
|
<p>Locale: {userSettings.locale}</p>
|
|
|
|
<!-- Status -->
|
|
<p>Loading: {userSettings.syncing}</p>
|
|
<p>Has Override: {userSettings.hasAppOverride}</p>
|
|
```
|
|
|
|
### Settings ändern
|
|
|
|
```typescript
|
|
// Globale Settings ändern (gilt für alle Apps)
|
|
await userSettings.updateGlobal({
|
|
locale: 'en',
|
|
theme: { mode: 'dark' }
|
|
});
|
|
|
|
// App-Override setzen (nur diese App)
|
|
await userSettings.updateAppOverride({
|
|
nav: { desktopPosition: 'bottom' },
|
|
theme: { colorScheme: 'nature' }
|
|
});
|
|
|
|
// App-Override löschen (zurück zu Global)
|
|
await userSettings.removeAppOverride();
|
|
```
|
|
|
|
### Beispiel: PillNavigation
|
|
|
|
```svelte
|
|
<PillNavigation
|
|
desktopPosition={userSettings.nav.desktopPosition}
|
|
...
|
|
/>
|
|
```
|
|
|
|
## Store API Referenz
|
|
|
|
### Properties (readonly)
|
|
|
|
| Property | Type | Beschreibung |
|
|
|----------|------|--------------|
|
|
| `nav` | `NavSettings` | Resolved Navigation Settings |
|
|
| `theme` | `ThemeSettings` | Resolved Theme Settings |
|
|
| `locale` | `string` | Aktuelle Sprache |
|
|
| `globalSettings` | `GlobalSettings` | Rohe globale Settings |
|
|
| `hasAppOverride` | `boolean` | Hat diese App einen Override? |
|
|
| `syncing` | `boolean` | Wird gerade synchronisiert? |
|
|
| `loaded` | `boolean` | Wurden Settings geladen? |
|
|
|
|
### Methods
|
|
|
|
| Method | Beschreibung |
|
|
|--------|--------------|
|
|
| `load()` | Settings vom Server laden |
|
|
| `updateGlobal(settings)` | Globale Settings aktualisieren |
|
|
| `updateAppOverride(settings)` | App-Override setzen |
|
|
| `removeAppOverride()` | App-Override löschen |
|
|
|
|
## Features
|
|
|
|
### Optimistic Updates
|
|
|
|
Änderungen werden sofort in der UI angezeigt, bevor die Server-Antwort eintrifft. Bei einem Fehler wird automatisch auf den vorherigen Zustand zurückgesetzt.
|
|
|
|
### localStorage Cache
|
|
|
|
Settings werden lokal gecached für:
|
|
- Schnelle UI beim App-Start (keine Wartezeit auf Server)
|
|
- Offline-Unterstützung (letzte bekannte Settings)
|
|
|
|
Cache-Key: `manacore-user-settings-{appId}`
|
|
|
|
### Deep Merge
|
|
|
|
Partielle Updates werden automatisch gemerged:
|
|
|
|
```typescript
|
|
// Nur colorScheme ändern, mode bleibt erhalten
|
|
await userSettings.updateGlobal({
|
|
theme: { colorScheme: 'nature' }
|
|
});
|
|
// Ergebnis: { theme: { mode: 'system', colorScheme: 'nature' } }
|
|
```
|
|
|
|
## Datenbank-Schema
|
|
|
|
Tabelle: `auth.user_settings`
|
|
|
|
| Column | Type | Beschreibung |
|
|
|--------|------|--------------|
|
|
| `user_id` | TEXT (PK) | Referenz zu auth.users |
|
|
| `global_settings` | JSONB | Globale Einstellungen |
|
|
| `app_overrides` | JSONB | Pro-App Überschreibungen |
|
|
| `created_at` | TIMESTAMP | Erstellungszeitpunkt |
|
|
| `updated_at` | TIMESTAMP | Letzte Aktualisierung |
|
|
|
|
## Dateien
|
|
|
|
### Backend (mana-core-auth)
|
|
|
|
| Datei | Beschreibung |
|
|
|-------|--------------|
|
|
| `src/settings/settings.module.ts` | NestJS Module |
|
|
| `src/settings/settings.controller.ts` | REST API Endpoints |
|
|
| `src/settings/settings.service.ts` | Business Logic |
|
|
| `src/settings/dto/index.ts` | DTOs für Requests |
|
|
| `src/db/schema/auth.schema.ts` | Drizzle Schema (`userSettings`) |
|
|
|
|
### Shared Package
|
|
|
|
| Datei | Beschreibung |
|
|
|-------|--------------|
|
|
| `packages/shared-theme/src/user-settings-store.svelte.ts` | Svelte 5 Store Factory |
|
|
| `packages/shared-theme/src/types.ts` | TypeScript Interfaces |
|
|
|
|
### App Integration (Beispiel Calendar)
|
|
|
|
| Datei | Beschreibung |
|
|
|-------|--------------|
|
|
| `apps/calendar/apps/web/src/lib/stores/user-settings.svelte.ts` | Store-Instanz |
|
|
| `apps/calendar/apps/web/src/routes/+layout.svelte` | Integration in Layout |
|
|
|
|
## Integrierte Apps
|
|
|
|
Folgende Apps nutzen bereits die zentralen User Settings:
|
|
|
|
- Calendar (`calendar`)
|
|
- Chat (`chat`)
|
|
- Contacts (`contacts`)
|
|
- ManaCore (`manacore`)
|
|
- ManaDeck (`manadeck`)
|
|
- Picture (`picture`)
|
|
- Presi (`presi`)
|
|
- Storage (`storage`)
|
|
- Zitare (`zitare`)
|
|
|
|
## Neue App integrieren
|
|
|
|
1. **Store erstellen** in `src/lib/stores/user-settings.svelte.ts`:
|
|
```typescript
|
|
import { createUserSettingsStore } from '@manacore/shared-theme';
|
|
import { authStore } from './auth.svelte';
|
|
|
|
export const userSettings = createUserSettingsStore({
|
|
appId: 'my-app', // Eindeutige ID
|
|
authUrl: import.meta.env.PUBLIC_MANA_CORE_AUTH_URL,
|
|
getAccessToken: () => authStore.getAccessToken()
|
|
});
|
|
```
|
|
|
|
2. **Im Layout laden** nach Auth-Check:
|
|
```typescript
|
|
if (authStore.isAuthenticated) {
|
|
await userSettings.load();
|
|
}
|
|
```
|
|
|
|
3. **Settings verwenden**:
|
|
```svelte
|
|
<PillNavigation desktopPosition={userSettings.nav.desktopPosition} />
|
|
```
|