rapid interations of server side app and firefox extension
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
"""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
|
||||
|
||||
|
||||
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": self.last_check.isoformat() if self.last_check else None,
|
||||
"last_success": self.last_success.isoformat() if self.last_success else None,
|
||||
"error_count": self.error_count,
|
||||
"metadata": self.metadata_,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
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
|
||||
Reference in New Issue
Block a user