Add filter for series with no downloaded episodes

- Added get_series_with_no_episodes() method to AnimeSeriesService
- Updated list_anime endpoint to support filter='no_episodes' parameter
- Added comprehensive unit tests for the new filtering functionality
- All tests passing successfully
This commit is contained in:
2026-01-23 18:55:04 +01:00
parent 2b904fd01e
commit c7bf232fe1
4 changed files with 977 additions and 661 deletions

View File

@@ -237,7 +237,9 @@ async def list_anime(
per_page: Items per page (must be positive, max 1000)
sort_by: Optional sorting parameter. Allowed: title, id, name,
missing_episodes
filter: Optional filter parameter (validated for security)
filter: Optional filter parameter. Allowed values:
- "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.
@@ -308,6 +310,14 @@ async def list_anime(
raise ValidationError(
message="Invalid filter parameter"
)
# Validate allowed filter values
allowed_filters = ["no_episodes"]
if filter not in allowed_filters:
allowed = ", ".join(allowed_filters)
raise ValidationError(
message=f"Invalid filter value. Allowed: {allowed}"
)
try:
# Get all series from series app
@@ -317,15 +327,24 @@ async def list_anime(
series = series_app.list.GetList()
summaries: List[AnimeSummary] = []
# Build a map of folder -> NFO data for efficient lookup
# 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 (
AnimeSeries as DBAnimeSeries,
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 = (
@@ -342,12 +361,42 @@ async def list_anime(
"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 NFO data from database: {e}")
# Continue without NFO data if database query fails
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
@@ -357,6 +406,12 @@ async def list_anime(
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
# Convert episode dict keys to strings for JSON serialization
missing_episodes = {str(k): v for k, v in episode_dict.items()}