""" User model - superadmin and organization users. """ from datetime import datetime from sqlalchemy import Boolean, DateTime, ForeignKey, String from sqlalchemy.dialects.postgresql import INET from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import Base class User(Base): """ User model for both superadmin and organization users. Roles: - superadmin: Full system access (organization_id = NULL) - owner: Organization owner (can manage users) - admin: Full organization access (cannot manage users) - manager: Read-only analytics access - operator: Access to devices and beacons - viewer: Read-only access """ __tablename__ = "users" id: Mapped[int] = mapped_column(primary_key=True) email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False) hashed_password: Mapped[str] = mapped_column(String(255), nullable=False) full_name: Mapped[str | None] = mapped_column(String(255)) phone: Mapped[str | None] = mapped_column(String(50)) # Role: superadmin (cloud), admin (cloud), owner (org), user (org) role: Mapped[str] = mapped_column(String(20), nullable=False) # Status: pending, active, suspended, deleted status: Mapped[str] = mapped_column( String(20), default="pending", nullable=False ) # Organization (NULL for superadmin/admin) organization_id: Mapped[int | None] = mapped_column( ForeignKey("organizations.id", ondelete="CASCADE") ) # Admin notes (visible only to superadmin) notes: Mapped[str | None] = mapped_column(String) # Email verification email_verified: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) email_verification_token: Mapped[str | None] = mapped_column(String(255)) email_verified_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) # Password reset password_reset_token: Mapped[str | None] = mapped_column(String(255)) password_reset_expires: Mapped[datetime | None] = mapped_column( DateTime(timezone=True) ) # Login tracking last_login_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) last_login_ip: Mapped[str | None] = mapped_column(INET) # Relationships organization: Mapped["Organization | None"] = relationship( "Organization", back_populates="users" ) refresh_tokens: Mapped[list["RefreshToken"]] = relationship( "RefreshToken", back_populates="user", cascade="all, delete-orphan" ) audit_logs: Mapped[list["AuditLog"]] = relationship( "AuditLog", back_populates="user" ) @property def is_superadmin(self) -> bool: """Check if user is superadmin.""" return self.role == "superadmin" @property def is_owner(self) -> bool: """Check if user is organization owner.""" return self.role == "owner" def __repr__(self) -> str: return f""