Files
TieMeasureFlow/src/backend/models/orm/user.py
T

60 lines
2.3 KiB
Python

"""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"<User {self.username} roles={self.roles}>"