This repository has been archived on 2026-05-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
GallerySubscriber/backend/app/models/content.py
T
2026-01-24 22:52:51 -05:00

84 lines
3.0 KiB
Python

"""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"<ContentItem {self.id}: {self.title or 'untitled'}>"
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