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:
@@ -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'],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user