feat(260928-ujj): Dashboard-Hintergrund pro Benutzer in der Datenbank
- Spalte User.dashboardBackground (JSONB) samt Migration - PATCH /users/me/dashboard-background, geprueft mit parseDashboardBackground aus @tessera/shared (Allowlist, UUID-Bildkennung) - getMe liefert dashboardBackground normalisiert neben accentColor - Web liest die Wahl aus dem Auth-Store, speichert ueber die Server-Aktion, alte localStorage-Wahl wird einmalig uebernommen - Hinweistext: gilt auf jedem Geraet Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -194,3 +194,65 @@ export interface ReleaseNoticeResponse {
|
||||
currentRelease: string | null;
|
||||
lastSeenReleaseVersion: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dashboard-Hintergrund pro Benutzer (quick-260928-ujj) — EINE Pruefregel
|
||||
* fuer API und Web.
|
||||
*
|
||||
* Gespeichert in `User.dashboardBackground` (JSONB), geschrieben nur ueber
|
||||
* `PATCH /users/me/dashboard-background`, gelesen mit der Sitzungsantwort
|
||||
* (`GET /auth/me`, neben `accentColor`). Das Web rendert die Wahl als
|
||||
* CSS-Hintergrund (`url("...")`) — deshalb ist die Pruefung streng
|
||||
* (T-ujj-01): `kind` und Preset-Kennung aus einer festen Liste, `imageId`
|
||||
* nur als UUID (Kennung eines Bilderrahmen-Bildes, `DashboardImage.id`).
|
||||
*
|
||||
* Laufzeit-Import aus `@tessera/shared` (siehe Warnkommentar ueber
|
||||
* `WIDGET_TYPES`): nur loeschbare Syntax, keine relativen Importe.
|
||||
*/
|
||||
export const DASHBOARD_BACKGROUND_PRESET_IDS = [
|
||||
'mist',
|
||||
'pebble',
|
||||
'bloom',
|
||||
'dunes',
|
||||
'mosaic',
|
||||
] as const;
|
||||
|
||||
export type DashboardBackgroundPresetId = (typeof DASHBOARD_BACKGROUND_PRESET_IDS)[number];
|
||||
|
||||
export type DashboardBackground =
|
||||
| { kind: 'none' }
|
||||
| { kind: 'preset'; id: DashboardBackgroundPresetId }
|
||||
| { kind: 'image'; imageId: string };
|
||||
|
||||
const DASHBOARD_BACKGROUND_IMAGE_ID_PATTERN =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
/**
|
||||
* Liefert eine gueltige Wahl als FRISCH aufgebautes Objekt (nur die
|
||||
* erlaubten Felder, Zusatzschluessel fallen weg) oder `null`, wenn der Wert
|
||||
* keine gueltige Wahl ist. `null` bedeutet beim Lesen „nie gewaehlt“ und
|
||||
* beim Schreiben „abweisen“.
|
||||
*/
|
||||
export function parseDashboardBackground(value: unknown): DashboardBackground | null {
|
||||
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
||||
return null;
|
||||
}
|
||||
const v = value as Record<string, unknown>;
|
||||
if (v.kind === 'none') {
|
||||
return { kind: 'none' };
|
||||
}
|
||||
if (v.kind === 'preset') {
|
||||
const id = v.id;
|
||||
if (typeof id !== 'string') return null;
|
||||
const known = DASHBOARD_BACKGROUND_PRESET_IDS.find((p) => p === id);
|
||||
return known ? { kind: 'preset', id: known } : null;
|
||||
}
|
||||
if (v.kind === 'image') {
|
||||
const imageId = v.imageId;
|
||||
if (typeof imageId !== 'string' || !DASHBOARD_BACKGROUND_IMAGE_ID_PATTERN.test(imageId)) {
|
||||
return null;
|
||||
}
|
||||
return { kind: 'image', imageId };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user