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/source.py
T
2026-01-25 18:47:06 -05:00

76 lines
3.0 KiB
Python

"""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=3600) # 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"<Source {self.subscription.name if self.subscription else '?'}/{self.platform}>"
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