"""User model with combinable roles and admin flag.""" from datetime import datetime from typing import Optional from sqlalchemy import Boolean, DateTime, Enum, Integer, JSON, String, func from sqlalchemy.orm import Mapped, mapped_column from src.backend.database import Base class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) username: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, index=True) password_hash: Mapped[str] = mapped_column(String(255), nullable=False) email: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) display_name: Mapped[str] = mapped_column(String(255), nullable=False) # Roles: combinable JSON array of "Maker", "MeasurementTec", "Metrologist" roles: Mapped[list] = mapped_column(JSON, nullable=False, default=list) # Admin flag separate from roles is_admin: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) # PIN a 4 cifre (#59): solo l'hash; contatore errori e fine blocco per il lockout pin_hash: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) pin_failed_count: Mapped[int] = mapped_column( Integer, nullable=False, default=0, server_default="0" ) pin_locked_until: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) # User preferences language_pref: Mapped[str] = mapped_column( Enum("it", "en", name="language_enum"), nullable=False, default="it" ) theme_pref: Mapped[str] = mapped_column( Enum("light", "dark", name="theme_enum"), nullable=False, default="light" ) # Status active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) # Timestamps created_at: Mapped[datetime] = mapped_column( DateTime, nullable=False, server_default=func.now() ) last_login: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) @property def has_pin(self) -> bool: """True se l'utente ha un PIN impostato (#59); serve a UserResponse.has_pin.""" return self.pin_hash is not None def has_role(self, role: str) -> bool: """Check if user has a specific role.""" return role in (self.roles or []) def __repr__(self) -> str: return f""