from datetime import UTC, datetime from uuid import UUID, uuid4 from sqlalchemy import Boolean, DateTime, Integer, String, Uuid from sqlalchemy.orm import Mapped, mapped_column from app.db.base import Base def _utcnow() -> datetime: return datetime.now(UTC) class User(Base): __tablename__ = "users" id: Mapped[UUID] = mapped_column(Uuid, primary_key=True, default=uuid4) email: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False) hashed_password: Mapped[str] = mapped_column(String(255), nullable=False) is_verified: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) # Nullable so users who registered before names were required keep working. # New registrations require them at the schema layer. first_name: Mapped[str | None] = mapped_column(String(100), nullable=True) last_name: Mapped[str | None] = mapped_column(String(100), nullable=True) # Codes are stored bcrypt-hashed; cleared on use or supersede. verification_code_hash: Mapped[str | None] = mapped_column(String(255), nullable=True) verification_code_expires_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) password_reset_code_hash: Mapped[str | None] = mapped_column(String(255), nullable=True) password_reset_code_expires_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) # Chat preferences (Settings page). rag_mode: "strict" | "regular". rag_mode: Mapped[str] = mapped_column( String(16), default="strict", server_default="strict", nullable=False ) web_max_results: Mapped[int] = mapped_column( Integer, default=5, server_default="5", nullable=False ) # Answer-model preference. NULL = use the app-wide default # (settings.groq_reasoning_model). Value must be an id in # app.agents.models.CHAT_MODELS at the API layer. chat_model: Mapped[str | None] = mapped_column(String(64), nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=_utcnow, nullable=False ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=_utcnow, onupdate=_utcnow, nullable=False, )