feat(5.2): add User Preferences API with full-stack implementation

Backend:
- Create app/models/user_preference.py with TenantMixin (user_id, key, value JSONB)
- Create app/routes/user_preferences.py with GET/PUT/DELETE endpoints + RBAC
- Add user_preferences:read/write to CORE_PERMISSIONS
- Add user_preferences to legacy role permissions (admin/editor/viewer)
- Register route in app/main.py and app/routes/__init__.py
- Create alembic migration 0028_user_preferences
- Add UserPreference model to conftest.py for test schema
- Fix pre-existing conftest seed (Contact industry field removed in migration 0027)

Frontend:
- Create frontend/src/api/userPreferences.ts with React Query hooks
- Create frontend/src/hooks/useUserPreferences.ts syncing with uiStore
- Add i18n entries for de.json and en.json

Tests:
- 13 tests covering CRUD, tenant isolation, CSRF, unauthenticated access
- All tests passing
This commit is contained in:
Agent Zero
2026-07-23 20:39:42 +02:00
parent a9151b1159
commit 75a7063bff
13 changed files with 762 additions and 4 deletions
+74
View File
@@ -0,0 +1,74 @@
/**
* User Preferences API — per-user UI settings stored server-side.
* Matches backend endpoints from /api/v1/user/preferences.
*
* Backend: app/routes/user_preferences.py, app/models/user_preference.py
*/
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiGet, apiPut, apiDelete } from './client';
// ── Types ──
export interface PreferenceEntry {
key: string;
value: unknown;
updated_at?: string | null;
}
export interface PreferenceListResponse {
preferences: PreferenceEntry[];
}
// ── Hooks ──
export function useUserPreferences() {
return useQuery({
queryKey: ['userPreferences'],
queryFn: () =>
apiGet<PreferenceListResponse>('/user/preferences'),
});
}
export function useUserPreference(key: string) {
return useQuery({
queryKey: ['userPreferences', key],
queryFn: () =>
apiGet<PreferenceEntry>(`/user/preferences/${key}`),
enabled: !!key,
});
}
export function useUpsertUserPreference() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
key,
value,
}: {
key: string;
value: unknown;
}) =>
apiPut<PreferenceEntry>(`/user/preferences/${key}`, {
value,
}),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['userPreferences'],
});
},
});
}
export function useDeleteUserPreference() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (key: string) =>
apiDelete(`/user/preferences/${key}`),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['userPreferences'],
});
},
});
}