Files
leocrm/app/models/user_preference.py
T
Agent Zero 75a7063bff 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
2026-07-23 20:39:42 +02:00

49 lines
1.5 KiB
Python

"""User Preference model — per-user UI settings stored as key/value JSONB, tenant-scoped."""
from __future__ import annotations
import uuid
from typing import Any
from sqlalchemy import ForeignKey, Index, String, UniqueConstraint
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.db import Base, TenantMixin
class UserPreference(Base, TenantMixin):
"""Per-user preference entry — stores a single UI preference as JSONB value.
Keys are arbitrary strings (e.g. 'sidebar_collapsed', 'theme', 'active_tab').
Values are JSONB to support complex preference structures.
"""
__tablename__ = "user_preferences"
__table_args__ = (
UniqueConstraint(
"tenant_id",
"user_id",
"key",
name="uq_user_prefs_tenant_user_key",
),
Index(
"ix_user_prefs_tenant_user",
"tenant_id",
"user_id",
),
)
id: Mapped[uuid.UUID] = mapped_column(
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
)
user_id: Mapped[uuid.UUID] = mapped_column(
PGUUID(as_uuid=True),
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
key: Mapped[str] = mapped_column(String(100), nullable=False)
value: Mapped[Any] = mapped_column(JSONB, nullable=False, default=dict)