"""Models for Watchlist and Auto-Download system with SQLModel support""" import uuid import json from pydantic import BaseModel, Field as PydanticField from typing import Optional, Literal, List from datetime import datetime from enum import Enum from sqlmodel import SQLModel, Field, Relationship, Column, String class WatchlistStatus(str, Enum): """Status of a watchlist item""" ACTIVE = "active" # Currently tracking for new episodes PAUSED = "paused" # Temporarily paused COMPLETED = "completed" # Anime completed, no longer tracking ARCHIVED = "archived" # Archived but kept for history class QualityPreference(str, Enum): """Preferred video quality""" AUTO = "auto" # Let provider decide P1080 = "1080p" # Full HD P720 = "720p" # HD P480 = "480p" # SD class WatchlistItemBase(SQLModel): """Base schema for watchlist items""" anime_title: str = Field(index=True) anime_url: str provider_id: str lang: str = Field(default="vostfr") # Tracking state last_checked: Optional[datetime] = None last_episode_downloaded: int = Field(default=0) total_episodes: Optional[int] = None # Settings auto_download: bool = Field(default=True) quality_preference: QualityPreference = Field(default=QualityPreference.AUTO) status: WatchlistStatus = Field(default=WatchlistStatus.ACTIVE) # Metadata poster_image: Optional[str] = None cover_image: Optional[str] = None synopsis: Optional[str] = None # Timestamps added_at: datetime = Field(default_factory=datetime.now) updated_at: datetime = Field(default_factory=datetime.now) class WatchlistItemTable(WatchlistItemBase, table=True): """Database table for watchlist items""" __tablename__ = "watchlist_items" id: str = Field( default_factory=lambda: str(uuid.uuid4()), primary_key=True, index=True, nullable=False ) user_id: str = Field(foreign_key="users.id", index=True) # Store list as JSON string in SQLite genres_json: Optional[str] = Field(default="[]", sa_column=Column(String)) @property def genres(self) -> List[str]: return json.loads(self.genres_json or "[]") @genres.setter def genres(self, value: List[str]): self.genres_json = json.dumps(value or []) # Relationships - Using string reference user: Optional["UserTable"] = Relationship(back_populates="watchlist_items") class WatchlistItem(BaseModel): """An anime being tracked for automatic episode downloads (API Response)""" id: str user_id: str anime_title: str anime_url: str provider_id: str lang: str last_checked: Optional[datetime] = None last_episode_downloaded: int = 0 total_episodes: Optional[int] = None auto_download: bool = True quality_preference: QualityPreference = QualityPreference.AUTO status: WatchlistStatus = WatchlistStatus.ACTIVE poster_image: Optional[str] = None cover_image: Optional[str] = None synopsis: Optional[str] = None genres: List[str] = [] added_at: datetime updated_at: datetime class Config: json_encoders = { datetime: lambda v: v.isoformat() } class WatchlistItemCreate(BaseModel): """Model for creating a new watchlist item""" anime_title: str anime_url: str provider_id: str lang: Literal["vostfr", "vf"] = "vostfr" auto_download: bool = True quality_preference: QualityPreference = QualityPreference.AUTO poster_image: Optional[str] = None cover_image: Optional[str] = None synopsis: Optional[str] = None genres: List[str] = [] class WatchlistItemUpdate(BaseModel): """Model for updating a watchlist item""" auto_download: Optional[bool] = None quality_preference: Optional[QualityPreference] = None status: Optional[WatchlistStatus] = None last_episode_downloaded: Optional[int] = None total_episodes: Optional[int] = None class NewEpisodeInfo(BaseModel): """Information about a newly detected episode""" episode_number: int episode_title: Optional[str] = None episode_url: str season_number: Optional[int] = None anime_title: str provider_id: str class AutoDownloadResult(BaseModel): """Result of an automatic download check""" watchlist_item_id: str anime_title: str new_episodes_found: int episodes_downloaded: list[int] = PydanticField(default_factory=list) episodes_failed: list[tuple[int, str]] = PydanticField(default_factory=list) checked_at: datetime = PydanticField(default_factory=datetime.now) class WatchlistSettingsBase(SQLModel): check_interval_hours: int = Field(default=6) auto_download_enabled: bool = Field(default=True) max_concurrent_auto_downloads: int = Field(default=2) notify_on_new_episodes: bool = Field(default=False) include_completed_anime: bool = Field(default=False) class WatchlistSettingsTable(WatchlistSettingsBase, table=True): """Database table for global watchlist settings""" __tablename__ = "watchlist_settings" id: str = Field( default_factory=lambda: str(uuid.uuid4()), primary_key=True, index=True, nullable=False ) user_id: str = Field(foreign_key="users.id", index=True, default="default") class WatchlistSettings(BaseModel): """Global watchlist settings""" check_interval_hours: int = PydanticField(default=6, ge=1, le=168) auto_download_enabled: bool = PydanticField(default=True) max_concurrent_auto_downloads: int = PydanticField(default=2, ge=1, le=10) notify_on_new_episodes: bool = PydanticField(default=False) include_completed_anime: bool = PydanticField(default=False) # Import UserTable here to resolve SQLModel Relationship mappings from .auth import UserTable