Files
leocrm/app/models/user.py
T
Agent Zero 2fd4bd123d fix: restore API response formats + ai-ui-control ws + profile fields
- Restore {plugins: [...], total: N} for /plugins and /plugins/active-manifests
  (flat array broke frontend PluginRegistry → no menu items → empty UI)
- Restore {count: N} for /notifications/unread-count
  (scalar broke frontend notification badge)
- Fix ai_ui_control: on_install → on_activate for WS manager registration
  (WS 403 on every reconnect after container restart)
- Fix double /api/v1 prefix in useAIContext.ts and SuggestionBadge.tsx
- Add first_name, last_name, avatar_url to User model + migration 0032
- Extend UserUpdate schema with profile + password change fields
- Extend user_service.update_user with profile fields + password change
- Extend frontend UserResponse/UserUpdate types
2026-07-24 17:17:53 +02:00

67 lines
2.6 KiB
Python

"""User and UserTenant models."""
from __future__ import annotations
import uuid
from datetime import datetime
from typing import Any
from sqlalchemy import Boolean, DateTime, ForeignKey, String, UniqueConstraint, func
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 User(Base, TenantMixin):
"""User entity — belongs to a tenant, can be member of multiple tenants."""
__tablename__ = "users"
__table_args__ = (UniqueConstraint("tenant_id", "email", name="uq_users_tenant_email"),)
id: Mapped[uuid.UUID] = mapped_column(
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
)
email: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
name: Mapped[str] = mapped_column(String(200), nullable=False)
first_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
last_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
avatar_url: Mapped[str | None] = mapped_column(String(500), nullable=True)
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
role: Mapped[str] = mapped_column(String(50), nullable=False, default="viewer")
role_id: Mapped[uuid.UUID | None] = mapped_column(
PGUUID(as_uuid=True),
ForeignKey("roles.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
preferences: Mapped[dict[str, Any]] = mapped_column(JSONB, default=dict, nullable=False)
is_system_admin: Mapped[bool] = mapped_column(
Boolean, default=False, nullable=False, server_default="false"
)
class UserTenant(Base):
"""N:M association — user membership in tenants."""
__tablename__ = "user_tenants"
user_id: Mapped[uuid.UUID] = mapped_column(
PGUUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), primary_key=True
)
tenant_id: Mapped[uuid.UUID] = mapped_column(
PGUUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"), primary_key=True
)
is_default: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
role_id: Mapped[uuid.UUID | None] = mapped_column(
PGUUID(as_uuid=True),
ForeignKey("roles.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)