"""ContentItem model - discovered files/content.""" from datetime import datetime from typing import Optional from sqlalchemy import String, Integer, Text, ForeignKey, BigInteger, UniqueConstraint from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import Base class ContentType: """Content type constants.""" IMAGE = "image" VIDEO = "video" ATTACHMENT = "attachment" AUDIO = "audio" OTHER = "other" class ContentItem(Base): """A content item represents a discovered/downloaded file.""" __tablename__ = "content_items" __table_args__ = ( UniqueConstraint("source_id", "external_id", name="uq_content_source_external"), ) id: Mapped[int] = mapped_column(primary_key=True) source_id: Mapped[int] = mapped_column( ForeignKey("sources.id", ondelete="CASCADE"), nullable=False, index=True ) download_id: Mapped[Optional[int]] = mapped_column( ForeignKey("downloads.id", ondelete="SET NULL"), nullable=True ) # Content identification external_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) title: Mapped[Optional[str]] = mapped_column(String(500), nullable=True) content_type: Mapped[str] = mapped_column(String(50), default=ContentType.OTHER) # File information file_path: Mapped[Optional[str]] = mapped_column(String(500), nullable=True) file_size: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True) thumbnail_path: Mapped[Optional[str]] = mapped_column(String(500), nullable=True) # Timestamps published_at: Mapped[Optional[datetime]] = mapped_column(nullable=True) discovered_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) # Flexible metadata metadata_: Mapped[dict] = mapped_column("metadata", JSONB, default=dict) # Relationships source: Mapped["Source"] = relationship(back_populates="content_items") download: Mapped[Optional["Download"]] = relationship(back_populates="content_items") def __repr__(self) -> str: return f"" def to_dict(self) -> dict: """Convert to dictionary for API responses.""" return { "id": self.id, "source_id": self.source_id, "download_id": self.download_id, "external_id": self.external_id, "title": self.title, "content_type": self.content_type, "file_path": self.file_path, "file_size": self.file_size, "thumbnail_path": self.thumbnail_path, "published_at": self.published_at.isoformat() if self.published_at else None, "discovered_at": self.discovered_at.isoformat() if self.discovered_at else None, "metadata": self.metadata_, } # Import for type hints from typing import TYPE_CHECKING if TYPE_CHECKING: from app.models.source import Source from app.models.download import Download