"""Document model — uploaded PDFs with soft-delete support.""" import uuid from datetime import datetime, timezone from sqlalchemy import Index, Integer, Text from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db import Base def _uuid() -> str: return str(uuid.uuid4()) def _now_iso() -> str: return datetime.now(timezone.utc).isoformat() class Document(Base): """ A single uploaded PDF document. Supports soft deletion via `deleted_at` — when set, the document is in the trash and can be restored or permanently purged. """ __tablename__ = "documents" id: Mapped[str] = mapped_column(Text, primary_key=True, default=_uuid) title: Mapped[str] = mapped_column(Text, nullable=False) original_filename: Mapped[str] = mapped_column(Text, nullable=False) file_path: Mapped[str] = mapped_column(Text, nullable=False) thumbnail_path: Mapped[str | None] = mapped_column(Text, nullable=True, default=None) size_bytes: Mapped[int] = mapped_column(Integer, nullable=False) page_count: Mapped[int] = mapped_column(Integer, nullable=False) created_at: Mapped[str] = mapped_column(Text, default=_now_iso) updated_at: Mapped[str] = mapped_column(Text, default=_now_iso, onupdate=_now_iso) deleted_at: Mapped[str | None] = mapped_column(Text, nullable=True, default=None) @property def in_trash(self) -> bool: return self.deleted_at is not None @property def filename(self) -> str: return self.original_filename @property def mime_type(self) -> str: return "application/pdf" # Relationships annotation_state: Mapped["AnnotationState"] = relationship( "AnnotationState", back_populates="document", uselist=False, cascade="all, delete-orphan", ) versions: Mapped[list["Version"]] = relationship( "Version", back_populates="document", cascade="all, delete-orphan", order_by="Version.created_at.desc()", ) __table_args__ = ( Index("ix_documents_updated_at", "updated_at"), Index("ix_documents_deleted_at", "deleted_at"), )