import uuid from datetime import datetime from sqlalchemy import ForeignKey, Index, String, UniqueConstraint, Uuid, func from sqlalchemy.orm import Mapped, mapped_column from app.db.base import Base, PkMixin, TimestampMixin, UTCDateTime class User(Base, PkMixin, TimestampMixin): __tablename__ = "users" email: Mapped[str] = mapped_column(String(320), nullable=False, index=True) __table_args__ = (Index("uq_users_email_casefold", func.lower(email), unique=True),) password_hash: Mapped[str] = mapped_column(String(256), nullable=False) display_name: Mapped[str | None] = mapped_column(String(120), nullable=True) locale: Mapped[str] = mapped_column(String(8), nullable=False, default="ru") timezone: Mapped[str] = mapped_column(String(64), nullable=False, default="Europe/Moscow") is_admin: Mapped[bool] = mapped_column(nullable=False, default=False) status: Mapped[str] = mapped_column(String(16), nullable=False, default="active") class Invitation(Base, PkMixin, TimestampMixin): __tablename__ = "invitations" __table_args__ = (UniqueConstraint("space_id", "email", name="uq_invitations_space_email"),) space_id: Mapped[uuid.UUID | None] = mapped_column( Uuid, ForeignKey("financial_spaces.id", ondelete="CASCADE"), nullable=True, index=True ) email: Mapped[str] = mapped_column(String(320), nullable=False, index=True) token_hash: Mapped[str] = mapped_column(String(64), nullable=False, unique=True) invited_by_user_id: Mapped[uuid.UUID | None] = mapped_column( Uuid, ForeignKey("users.id", ondelete="SET NULL"), nullable=True ) role: Mapped[str] = mapped_column(String(16), nullable=False, default="owner") expires_at: Mapped[datetime] = mapped_column(UTCDateTime(), nullable=False) accepted_at: Mapped[datetime | None] = mapped_column(UTCDateTime(), nullable=True) accepted_user_id: Mapped[uuid.UUID | None] = mapped_column( Uuid, ForeignKey("users.id", ondelete="SET NULL"), nullable=True ) class PasswordReset(Base, PkMixin, TimestampMixin): __tablename__ = "password_resets" user_id: Mapped[uuid.UUID] = mapped_column( Uuid, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True ) token_hash: Mapped[str] = mapped_column(String(64), nullable=False, unique=True) expires_at: Mapped[datetime] = mapped_column(UTCDateTime(), nullable=False) used_at: Mapped[datetime | None] = mapped_column(UTCDateTime(), nullable=True) class Session(Base, PkMixin, TimestampMixin): __tablename__ = "sessions" user_id: Mapped[uuid.UUID] = mapped_column( Uuid, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True ) token_hash: Mapped[str] = mapped_column(String(64), nullable=False, unique=True, index=True) csrf_token: Mapped[str] = mapped_column(String(64), nullable=False) expires_at: Mapped[datetime] = mapped_column(UTCDateTime(), nullable=False) revoked_at: Mapped[datetime | None] = mapped_column(UTCDateTime(), nullable=True) revoked_reason: Mapped[str | None] = mapped_column(String(120), nullable=True) last_seen_at: Mapped[datetime] = mapped_column(UTCDateTime(), nullable=False)