"""Source model - platform URLs for subscriptions.""" from datetime import datetime from typing import Optional from sqlalchemy import String, Boolean, Integer, Text, ForeignKey, UniqueConstraint from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import Base, TimestampMixin, format_datetime class Source(Base, TimestampMixin): """A source represents a platform URL for a subscription. Sources belong to subscriptions - one subscription (artist) can have multiple sources (Patreon, SubscribeStar, etc.). """ __tablename__ = "sources" __table_args__ = (UniqueConstraint("platform", "url", name="uq_source_platform_url"),) id: Mapped[int] = mapped_column(primary_key=True) subscription_id: Mapped[int] = mapped_column( ForeignKey("subscriptions.id", ondelete="CASCADE"), nullable=False, index=True ) platform: Mapped[str] = mapped_column(String(50), nullable=False, index=True) url: Mapped[str] = mapped_column(Text, nullable=False) enabled: Mapped[bool] = mapped_column(Boolean, default=True) check_interval: Mapped[int] = mapped_column(Integer, default=28800) # 8 hours in seconds # Status tracking last_check: Mapped[Optional[datetime]] = mapped_column(nullable=True) last_success: Mapped[Optional[datetime]] = mapped_column(nullable=True) error_count: Mapped[int] = mapped_column(Integer, default=0) # Flexible metadata storage (per-source config overrides) metadata_: Mapped[dict] = mapped_column("metadata", JSONB, default=dict) # Relationships subscription: Mapped["Subscription"] = relationship(back_populates="sources") downloads: Mapped[list["Download"]] = relationship(back_populates="source") content_items: Mapped[list["ContentItem"]] = relationship(back_populates="source") def __repr__(self) -> str: return f"" def to_dict(self, include_subscription: bool = True) -> dict: """Convert to dictionary for API responses.""" result = { "id": self.id, "subscription_id": self.subscription_id, "platform": self.platform, "url": self.url, "enabled": self.enabled, "check_interval": self.check_interval, "last_check": format_datetime(self.last_check), "last_success": format_datetime(self.last_success), "error_count": self.error_count, "metadata": self.metadata_, "created_at": format_datetime(self.created_at), "updated_at": format_datetime(self.updated_at), } if include_subscription and self.subscription: result["subscription_name"] = self.subscription.name return result # Import for type hints (avoid circular imports) from typing import TYPE_CHECKING if TYPE_CHECKING: from app.models.subscription import Subscription from app.models.download import Download from app.models.content import ContentItem