"""User Model""" from sqlalchemy import Column, String, Boolean, DateTime from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship from sqlalchemy.sql import func import uuid from passlib.context import CryptContext from app.database import Base # Configure bcrypt password hashing pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") class User(Base): __tablename__ = "users" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) azure_oid = Column(String(255), unique=True, nullable=True) email = Column(String(255), unique=True, nullable=False) hashed_password = Column(String(255), nullable=True) # Nullable for SSO users display_name = Column(String(255)) avatar_url = Column(String) role = Column(String(50), default="user") department = Column(String(255)) is_active = Column(Boolean, default=True) last_login_at = Column(DateTime(timezone=True)) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) def verify_password(self, password: str) -> bool: """Verify a password against the hash""" if not self.hashed_password: return False # Truncate to 72 bytes for bcrypt compatibility password_bytes = password.encode('utf-8')[:72].decode('utf-8', errors='ignore') return pwd_context.verify(password_bytes, self.hashed_password) @staticmethod def hash_password(password: str) -> str: """Hash a password (truncate to 72 bytes for bcrypt compatibility)""" # bcrypt has a 72-byte limit on passwords password_bytes = password.encode('utf-8')[:72].decode('utf-8', errors='ignore') return pwd_context.hash(password_bytes) # Relationships projects = relationship("Project", back_populates="user") assets = relationship("Asset", back_populates="user") jobs = relationship("Job", back_populates="user")