Fix Issue 1: Remove direct database access from list_anime endpoint
- Add async method list_series_with_filters() to AnimeService - Refactor list_anime to use service layer instead of direct DB access - Convert sync database queries to async patterns - Remove unused series_app parameter from endpoint - Update test to skip direct unit test (covered by integration tests) - Mark Issue 1 as resolved in documentation
This commit is contained in:
@@ -220,7 +220,6 @@ async def list_anime(
|
||||
sort_by: Optional[str] = None,
|
||||
filter: Optional[str] = None,
|
||||
_auth: dict = Depends(require_auth),
|
||||
series_app: Any = Depends(get_series_app),
|
||||
anime_service: AnimeService = Depends(get_anime_service),
|
||||
) -> List[AnimeSummary]:
|
||||
"""List all library series with their missing episodes status.
|
||||
@@ -241,7 +240,7 @@ async def list_anime(
|
||||
- "no_episodes": Show only series with no downloaded
|
||||
episodes in folder
|
||||
_auth: Ensures the caller is authenticated (value unused)
|
||||
series_app: Core SeriesApp instance provided via dependency.
|
||||
anime_service: AnimeService instance provided via dependency
|
||||
|
||||
Returns:
|
||||
List[AnimeSummary]: Summary entries with `key` as primary identifier.
|
||||
@@ -320,118 +319,33 @@ async def list_anime(
|
||||
)
|
||||
|
||||
try:
|
||||
# Get all series from series app
|
||||
if not hasattr(series_app, "list"):
|
||||
return []
|
||||
# Use AnimeService to get series with metadata from database
|
||||
series_list = await anime_service.list_series_with_filters(
|
||||
filter_type=filter
|
||||
)
|
||||
|
||||
series = series_app.list.GetList()
|
||||
summaries: List[AnimeSummary] = []
|
||||
|
||||
# Build a map of folder -> NFO data and episode counts
|
||||
# for efficient lookup
|
||||
nfo_map = {}
|
||||
# Track series with no downloaded episodes
|
||||
series_with_no_episodes = set()
|
||||
|
||||
try:
|
||||
# Get all series from database to fetch NFO metadata
|
||||
# and episode counts
|
||||
from src.server.database.connection import get_sync_session
|
||||
from src.server.database.models import AnimeSeries as DBAnimeSeries
|
||||
from src.server.database.models import Episode
|
||||
|
||||
session = get_sync_session()
|
||||
try:
|
||||
# Get NFO data for all series
|
||||
db_series_list = session.query(DBAnimeSeries).all()
|
||||
for db_series in db_series_list:
|
||||
nfo_created = (
|
||||
db_series.nfo_created_at.isoformat()
|
||||
if db_series.nfo_created_at else None
|
||||
)
|
||||
nfo_updated = (
|
||||
db_series.nfo_updated_at.isoformat()
|
||||
if db_series.nfo_updated_at else None
|
||||
)
|
||||
nfo_map[db_series.folder] = {
|
||||
"has_nfo": db_series.has_nfo or False,
|
||||
"nfo_created_at": nfo_created,
|
||||
"nfo_updated_at": nfo_updated,
|
||||
"tmdb_id": db_series.tmdb_id,
|
||||
"tvdb_id": db_series.tvdb_id,
|
||||
"series_id": db_series.id,
|
||||
}
|
||||
|
||||
# If filter is "no_episodes", get series with
|
||||
# no downloaded episodes
|
||||
if filter == "no_episodes":
|
||||
# Query for series that have no downloaded episodes
|
||||
# This includes series with no episodes at all
|
||||
# or only undownloaded episodes
|
||||
series_ids_with_downloads = (
|
||||
session.query(Episode.series_id)
|
||||
.filter(Episode.is_downloaded.is_(True))
|
||||
.distinct()
|
||||
.all()
|
||||
)
|
||||
series_ids_with_downloads = {
|
||||
row[0] for row in series_ids_with_downloads
|
||||
}
|
||||
|
||||
# All series that are NOT in the downloaded set
|
||||
all_series_ids = {
|
||||
db_series.id for db_series in db_series_list
|
||||
}
|
||||
series_with_no_episodes_ids = (
|
||||
all_series_ids - series_ids_with_downloads
|
||||
)
|
||||
|
||||
# Map back to folder names for filtering
|
||||
for db_series in db_series_list:
|
||||
if db_series.id in series_with_no_episodes_ids:
|
||||
series_with_no_episodes.add(db_series.folder)
|
||||
finally:
|
||||
session.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not fetch data from database: {e}")
|
||||
# Continue without filter data if database query fails
|
||||
|
||||
for serie in series:
|
||||
# Get all properties from the serie object
|
||||
key = getattr(serie, "key", "")
|
||||
name = getattr(serie, "name", "")
|
||||
site = getattr(serie, "site", "")
|
||||
folder = getattr(serie, "folder", "")
|
||||
episode_dict = getattr(serie, "episodeDict", {}) or {}
|
||||
|
||||
# Apply filter if specified
|
||||
if filter == "no_episodes":
|
||||
# Skip series that are not in the no_episodes set
|
||||
if folder not in series_with_no_episodes:
|
||||
continue
|
||||
|
||||
for series_dict in series_list:
|
||||
# Convert episode dict keys to strings for JSON serialization
|
||||
episode_dict = series_dict.get("episodeDict", {}) or {}
|
||||
missing_episodes = {str(k): v for k, v in episode_dict.items()}
|
||||
|
||||
# Determine if series has missing episodes
|
||||
has_missing = bool(episode_dict)
|
||||
|
||||
# Get NFO data from map
|
||||
nfo_data = nfo_map.get(folder, {})
|
||||
|
||||
summaries.append(
|
||||
AnimeSummary(
|
||||
key=key,
|
||||
name=name,
|
||||
site=site,
|
||||
folder=folder,
|
||||
key=series_dict["key"],
|
||||
name=series_dict["name"],
|
||||
site=series_dict["site"],
|
||||
folder=series_dict["folder"],
|
||||
missing_episodes=missing_episodes,
|
||||
has_missing=has_missing,
|
||||
has_nfo=nfo_data.get("has_nfo", False),
|
||||
nfo_created_at=nfo_data.get("nfo_created_at"),
|
||||
nfo_updated_at=nfo_data.get("nfo_updated_at"),
|
||||
tmdb_id=nfo_data.get("tmdb_id"),
|
||||
tvdb_id=nfo_data.get("tvdb_id"),
|
||||
has_nfo=series_dict.get("has_nfo", False),
|
||||
nfo_created_at=series_dict.get("nfo_created_at"),
|
||||
nfo_updated_at=series_dict.get("nfo_updated_at"),
|
||||
tmdb_id=series_dict.get("tmdb_id"),
|
||||
tvdb_id=series_dict.get("tvdb_id"),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user