341d0c6f38
Backend (ruff): - F821: Add TYPE_CHECKING imports for Vehicle, Contact, File in models (file.py, retouch.py, sale.py, vehicle.py) - E741: Rename ambiguous variable to / (copilot_service.py, price_compare_service.py, openrouter.py) - F841: Remove unused variable in sale_service.py Frontend (ESLint): - react/no-unescaped-entities: Escape quotes in ContractPreview.tsx - @next/next/no-img-element: Replace <img> with <Image> from next/image (FileGallery.tsx, FileList.tsx, FilePreview.tsx, BeforeAfterSlider.tsx) Tests: 392 backend passed, 112 frontend passed, next build successful
78 lines
2.3 KiB
Python
78 lines
2.3 KiB
Python
"""SQLAlchemy User model with UUID primary key."""
|
|
|
|
import enum
|
|
import uuid
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import Boolean, DateTime, Enum, String, func
|
|
from sqlalchemy.dialects.postgresql import UUID
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.database import Base
|
|
|
|
|
|
class UserRole(str, enum.Enum):
|
|
"""User roles for RBAC."""
|
|
admin = "admin"
|
|
verkaeufer = "verkaeufer"
|
|
buchhaltung = "buchhaltung"
|
|
|
|
|
|
class User(Base):
|
|
"""User entity for authentication and authorization."""
|
|
|
|
__tablename__ = "users"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
primary_key=True,
|
|
default=uuid.uuid4,
|
|
)
|
|
email: Mapped[str] = mapped_column(
|
|
String(255), unique=True, nullable=False, index=True,
|
|
)
|
|
password_hash: Mapped[str] = mapped_column(
|
|
String(255), nullable=False,
|
|
)
|
|
full_name: Mapped[str] = mapped_column(
|
|
String(200), nullable=False,
|
|
)
|
|
role: Mapped[UserRole] = mapped_column(
|
|
Enum(UserRole, name="user_role"),
|
|
nullable=False,
|
|
default=UserRole.verkaeufer,
|
|
)
|
|
language: Mapped[str] = mapped_column(
|
|
String(5), nullable=False, default="de",
|
|
)
|
|
is_active: Mapped[bool] = mapped_column(
|
|
Boolean, nullable=False, default=True,
|
|
)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True),
|
|
nullable=False,
|
|
server_default=func.now(),
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True),
|
|
nullable=False,
|
|
server_default=func.now(),
|
|
onupdate=func.now(),
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<User id={self.id} email={self.email} role={self.role}>"
|
|
|
|
def to_dict(self) -> dict:
|
|
"""Serialize user for API responses (excludes password_hash)."""
|
|
return {
|
|
"id": str(self.id),
|
|
"email": self.email,
|
|
"full_name": self.full_name,
|
|
"role": self.role.value if isinstance(self.role, UserRole) else self.role,
|
|
"language": self.language,
|
|
"is_active": self.is_active,
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
|
}
|