Phase 2 Complete: SQL migration with SQLModel and Alembic
This commit is contained in:
@@ -63,3 +63,9 @@ class AnimeSearchResult(BaseModel):
|
||||
cover_image: Optional[str] = None
|
||||
type: str # "search_result" or "direct"
|
||||
metadata: Optional[AnimeMetadata] = None
|
||||
|
||||
# Import all SQLModel tables here to ensure they are registered together
|
||||
from .auth import UserTable
|
||||
from .watchlist import WatchlistItemTable, WatchlistSettingsTable
|
||||
from .favorites import FavoriteTable
|
||||
from .sonarr import SonarrMappingTable, SonarrConfigTable
|
||||
|
||||
+4
-1
@@ -28,7 +28,7 @@ class UserTable(UserBase, table=True):
|
||||
created_at: datetime = Field(default_factory=datetime.now)
|
||||
last_login: Optional[datetime] = None
|
||||
|
||||
# Relationships
|
||||
# Relationships - Using string reference to avoid circular import errors
|
||||
watchlist_items: List["WatchlistItemTable"] = Relationship(back_populates="user")
|
||||
|
||||
|
||||
@@ -60,3 +60,6 @@ class Token(BaseModel):
|
||||
class UserInDB(User):
|
||||
"""Schema for user stored in database (with hashed password)"""
|
||||
hashed_password: str
|
||||
|
||||
# Import WatchlistItemTable here to resolve SQLModel Relationship mappings
|
||||
from .watchlist import WatchlistItemTable
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Models for Favorites system with SQLModel support"""
|
||||
import uuid
|
||||
import json
|
||||
from typing import Optional, Dict, List
|
||||
from datetime import datetime
|
||||
from sqlmodel import SQLModel, Field, Column, String
|
||||
|
||||
class FavoriteBase(SQLModel):
|
||||
"""Base schema for favorite anime"""
|
||||
anime_id: str = Field(index=True)
|
||||
title: str = Field(index=True)
|
||||
url: str
|
||||
provider: str
|
||||
poster_url: Optional[str] = None
|
||||
|
||||
# Timestamps
|
||||
created_at: datetime = Field(default_factory=datetime.now)
|
||||
updated_at: datetime = Field(default_factory=datetime.now)
|
||||
|
||||
class FavoriteTable(FavoriteBase, table=True):
|
||||
"""Database table for favorites"""
|
||||
__tablename__ = "favorites"
|
||||
|
||||
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")
|
||||
|
||||
# Store metadata dictionary as JSON string in SQLite
|
||||
metadata_json: Optional[str] = Field(default="{}", sa_column=Column(String))
|
||||
|
||||
@property
|
||||
def anime_metadata(self) -> Dict:
|
||||
try:
|
||||
return json.loads(self.metadata_json or "{}")
|
||||
except json.JSONDecodeError:
|
||||
return {}
|
||||
|
||||
@anime_metadata.setter
|
||||
def anime_metadata(self, value: Dict):
|
||||
self.metadata_json = json.dumps(value or {})
|
||||
+55
-6
@@ -1,8 +1,10 @@
|
||||
"""Pydantic models for Sonarr webhook integration"""
|
||||
from pydantic import BaseModel, Field, validator
|
||||
from pydantic import BaseModel, Field as PydanticField, validator
|
||||
from typing import Optional, Dict, Any, List
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from sqlmodel import SQLModel, Field
|
||||
import uuid
|
||||
|
||||
|
||||
class SonarrEventType(str, Enum):
|
||||
@@ -45,7 +47,7 @@ class SonarrEpisodeFile(BaseModel):
|
||||
|
||||
class SonarrSeries(BaseModel):
|
||||
"""Series information from Sonarr"""
|
||||
tvdbId: int = Field(..., alias="tvdbId")
|
||||
tvdbId: int = PydanticField(..., alias="tvdbId")
|
||||
title: str
|
||||
sortTitle: str
|
||||
status: str
|
||||
@@ -129,8 +131,33 @@ class SonarrWebhookPayload(BaseModel):
|
||||
return v
|
||||
|
||||
|
||||
class SonarrMappingBase(SQLModel):
|
||||
sonarr_series_id: int = Field(index=True, unique=True)
|
||||
sonarr_title: str
|
||||
anime_provider: str
|
||||
anime_url: str
|
||||
anime_title: str
|
||||
lang: str = Field(default="vostfr")
|
||||
quality_preference: Optional[str] = None
|
||||
auto_download: bool = Field(default=True)
|
||||
created_at: datetime = Field(default_factory=datetime.now)
|
||||
updated_at: datetime = Field(default_factory=datetime.now)
|
||||
|
||||
|
||||
class SonarrMappingTable(SonarrMappingBase, table=True):
|
||||
"""Database table for Sonarr mappings"""
|
||||
__tablename__ = "sonarr_mappings"
|
||||
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 SonarrMapping(BaseModel):
|
||||
"""Mapping between Sonarr series and anime providers"""
|
||||
"""Mapping between Sonarr series and anime providers (API model)"""
|
||||
sonarr_series_id: int
|
||||
sonarr_title: str
|
||||
anime_provider: str # 'anime-sama', 'neko-sama', etc.
|
||||
@@ -139,8 +166,8 @@ class SonarrMapping(BaseModel):
|
||||
lang: str = "vostfr"
|
||||
quality_preference: Optional[str] = None # '1080p', '720p', etc.
|
||||
auto_download: bool = True
|
||||
created_at: datetime = Field(default_factory=datetime.now)
|
||||
updated_at: datetime = Field(default_factory=datetime.now)
|
||||
created_at: datetime = PydanticField(default_factory=datetime.now)
|
||||
updated_at: datetime = PydanticField(default_factory=datetime.now)
|
||||
|
||||
class Config:
|
||||
json_encoders = {
|
||||
@@ -148,8 +175,30 @@ class SonarrMapping(BaseModel):
|
||||
}
|
||||
|
||||
|
||||
class SonarrConfigBase(SQLModel):
|
||||
webhook_enabled: bool = Field(default=False)
|
||||
webhook_secret: Optional[str] = None
|
||||
auto_download_enabled: bool = Field(default=True)
|
||||
default_language: str = Field(default="vostfr")
|
||||
default_quality: Optional[str] = None
|
||||
default_provider: str = Field(default="anime-sama")
|
||||
verify_hmac: bool = Field(default=False)
|
||||
log_webhooks: bool = Field(default=True)
|
||||
|
||||
|
||||
class SonarrConfigTable(SonarrConfigBase, table=True):
|
||||
"""Database table for Sonarr configuration (singleton)"""
|
||||
__tablename__ = "sonarr_config"
|
||||
id: str = Field(
|
||||
default_factory=lambda: str(uuid.uuid4()),
|
||||
primary_key=True,
|
||||
index=True,
|
||||
nullable=False
|
||||
)
|
||||
|
||||
|
||||
class SonarrConfig(BaseModel):
|
||||
"""Sonarr webhook configuration"""
|
||||
"""Sonarr webhook configuration (API Model)"""
|
||||
webhook_enabled: bool = False
|
||||
webhook_secret: Optional[str] = None # HMAC SHA256 secret
|
||||
auto_download_enabled: bool = True
|
||||
|
||||
+22
-1
@@ -74,7 +74,7 @@ class WatchlistItemTable(WatchlistItemBase, table=True):
|
||||
def genres(self, value: List[str]):
|
||||
self.genres_json = json.dumps(value or [])
|
||||
|
||||
# Relationships
|
||||
# Relationships - Using string reference
|
||||
user: Optional["UserTable"] = Relationship(back_populates="watchlist_items")
|
||||
|
||||
|
||||
@@ -148,6 +148,24 @@ class AutoDownloadResult(BaseModel):
|
||||
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)
|
||||
@@ -155,3 +173,6 @@ class WatchlistSettings(BaseModel):
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user