Fix matching: add missing subtitle field, proper error logging, match-all endpoint
- MatchResult was missing subtitle field, causing AttributeError in _apply_match that silently killed every background match task - Wrap _apply_match in try/except with exc_info logging so failures are visible in docker compose logs backend - New POST /api/libraries/:id/match-all endpoint to trigger matching for all unlocked items (useful for items scanned before the fix) - Admin UI: Match button per library next to the Scan button Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -219,3 +219,33 @@ async def scan_library(
|
||||
background_tasks.add_task(scan_library_task, library_id, job.id)
|
||||
|
||||
return {"id": job.id, "type": "scan", "libraryId": library_id}
|
||||
|
||||
|
||||
@router.post("/{library_id}/match-all")
|
||||
async def match_all_items(
|
||||
library_id: str,
|
||||
background_tasks: BackgroundTasks,
|
||||
admin: User = Depends(require_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Startet Matching für alle nicht-gematchten Items der Library."""
|
||||
result = await db.execute(select(Library).where(Library.id == library_id))
|
||||
if not result.scalar_one_or_none():
|
||||
raise HTTPException(status_code=404, detail="Library not found")
|
||||
|
||||
items_result = await db.execute(
|
||||
select(LibraryItem).where(
|
||||
LibraryItem.library_id == library_id,
|
||||
LibraryItem.match_locked == False,
|
||||
)
|
||||
)
|
||||
items = items_result.scalars().all()
|
||||
item_ids = [i.id for i in items]
|
||||
|
||||
async def _run_all():
|
||||
from ..services.matcher import match_audiobook
|
||||
for iid in item_ids:
|
||||
await match_audiobook(iid)
|
||||
|
||||
background_tasks.add_task(_run_all)
|
||||
return {"queued": len(item_ids), "libraryId": library_id}
|
||||
|
||||
@@ -233,12 +233,15 @@ async def match_audiobook(item_id: str):
|
||||
logger.warning(f"{source_name} Fehler: {e}")
|
||||
|
||||
if best and best_score >= UNCERTAIN_THRESHOLD:
|
||||
await _apply_match(db, item, best, best_score)
|
||||
logger.info(f"Match angewendet: '{item.title}' ← {best.source} ({best_score:.2f})")
|
||||
try:
|
||||
await _apply_match(db, item, best, best_score)
|
||||
await db.commit()
|
||||
logger.info(f"Match angewendet: '{item.title}' ← {best.source} ({best_score:.2f})")
|
||||
except Exception as e:
|
||||
logger.error(f"_apply_match fehlgeschlagen für '{title}': {e}", exc_info=True)
|
||||
else:
|
||||
logger.info(f"Kein Match für '{title}' (beste Konfidenz: {best_score:.2f})")
|
||||
|
||||
await db.commit()
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def search_for_item(title: str, author: str | None = None) -> list[dict]:
|
||||
|
||||
@@ -7,6 +7,7 @@ class MatchResult:
|
||||
source: str # musicbrainz / open_library / google_books
|
||||
source_id: str
|
||||
title: str
|
||||
subtitle: str | None = None
|
||||
author: str | None = None
|
||||
narrator: str | None = None
|
||||
description: str | None = None
|
||||
|
||||
Reference in New Issue
Block a user