feat(setup): track unresolved folders for manual key resolution

When SetupService cannot auto-resolve a provider key for an anime folder,
the folder is now tracked in the new 'unresolved_folders' table instead of
being silently skipped. Users can then resolve these via the new API:

- GET /api/setup/unresolved - list unresolved folders with search suggestions
- POST /api/setup/unresolved/{folder}/resolve - provide key to resolve folder

The SetupService.run() now:
- Tracks unresolved folders instead of skipping them
- Re-creates AnimeSeries for previously unresolved folders that are now resolved
- Includes unresolved count in logs

New files:
- src/server/api/setup_endpoints.py - API endpoints for unresolved management
- tests/unit/test_unresolved_folder_service.py - service and model tests

Modified:
- src/server/database/models.py - add UnresolvedFolder model
- src/server/database/service.py - add UnresolvedFolderService
- src/server/services/setup_service.py - track unresolved folders
- src/server/fastapi_app.py - include setup router
This commit is contained in:
2026-06-05 21:07:52 +02:00
parent d9738ffb78
commit ecef21eec4
7 changed files with 944 additions and 40 deletions

View File

@@ -20,7 +20,7 @@ import structlog
from src.config.settings import settings
from src.server.database.connection import get_db_session
from src.server.database.service import AnimeSeriesService
from src.server.database.service import AnimeSeriesService, UnresolvedFolderService
from src.server.utils.dependencies import get_series_app
logger = structlog.get_logger(__name__)
@@ -281,6 +281,7 @@ class SetupService:
created_count = 0
skipped_existing = 0
unresolved_count = 0
try:
series_app = get_series_app()
@@ -300,6 +301,43 @@ class SetupService:
skipped_existing += 1
continue
# Check if already tracked as unresolved
existing_unresolved = await UnresolvedFolderService.get_by_folder_name(
db, folder_name
)
if existing_unresolved and existing_unresolved.is_resolved:
# Was previously unresolved but now resolved - create the series
resolved_key = existing_unresolved.provider_key
year = cls._extract_year_from_folder_name(folder_name)
title = cls._extract_title_from_folder_name(folder_name)
props = cls._get_series_properties(folder)
series = await AnimeSeriesService.create(
db=db,
key=resolved_key,
name=title,
site="https://aniworld.to",
folder=folder_name,
year=year,
loading_status="completed",
episodes_loaded=True,
logo_loaded=props.logo_loaded,
images_loaded=props.images_loaded,
has_nfo=props.has_nfo,
nfo_path=props.nfo_path,
nfo_created_at=props.nfo_created_at,
nfo_updated_at=props.nfo_updated_at,
)
created_count += 1
# Delete the unresolved tracking now that series is created
await UnresolvedFolderService.delete(db, folder_name)
continue
elif existing_unresolved:
# Already tracked as unresolved, skip
unresolved_count += 1
continue
# Extract title and year from folder name
year = cls._extract_year_from_folder_name(folder_name)
title = cls._extract_title_from_folder_name(folder_name)
@@ -315,8 +353,24 @@ class SetupService:
resolved_key = await cls._resolve_key_via_search(title)
if not resolved_key:
# Track unresolved folder for later manual resolution
import json
try:
series_results = await series_app.search(title)
search_result_json = json.dumps(series_results) if series_results else None
except Exception:
search_result_json = None
await UnresolvedFolderService.create(
db=db,
folder_name=folder_name,
title=title,
year=year,
search_attempts=1,
last_search_result=search_result_json,
)
logger.warning(
"Could not resolve series key for folder, skipping: %s",
"Could not resolve series key for folder, tracking as unresolved: %s",
folder_name
)
continue
@@ -357,7 +411,8 @@ class SetupService:
logger.info(
"Setup complete",
created=created_count,
skipped_existing=skipped_existing
skipped_existing=skipped_existing,
unresolved=unresolved_count
)
except Exception as e: