import asyncio from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from ..dependencies import get_db, get_current_user, require_admin from ..models.user import User from ..models.media_item import LibraryItem from ..services.matcher import match_audiobook, search_for_item, _apply_match, _score_result from ..services.matching.musicbrainz import get_release_details from ..services.matching.open_library import get_work_details from ..services.matching.google_books import search_google_books from ..services.matching.base import MatchResult from datetime import datetime router = APIRouter(prefix="/api/items", tags=["matching"]) @router.post("/{item_id}/match") async def trigger_match( item_id: str, background_tasks: BackgroundTasks, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): result = await db.execute(select(LibraryItem).where(LibraryItem.id == item_id)) item = result.scalar_one_or_none() if not item: raise HTTPException(status_code=404, detail="Item not found") background_tasks.add_task(match_audiobook, item_id) return {"message": "Matching gestartet", "itemId": item_id} @router.get("/{item_id}/match/search") async def search_match( item_id: str, q: str | None = None, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): result = await db.execute(select(LibraryItem).where(LibraryItem.id == item_id)) item = result.scalar_one_or_none() if not item: raise HTTPException(status_code=404, detail="Item not found") query = q or item.title or "" author = item.author if not q else None results = await search_for_item(query, author) return {"results": results} @router.post("/{item_id}/match/apply") async def apply_match( item_id: str, body: dict, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """ Wendet einen manuell gewählten Match-Treffer an. body: { source, id, title, author, ... } """ result = await db.execute(select(LibraryItem).where(LibraryItem.id == item_id)) item = result.scalar_one_or_none() if not item: raise HTTPException(status_code=404, detail="Item not found") source = body.get("source", "manual") source_id = body.get("id", "") # Versuche Details zu laden wenn MusicBrainz/OpenLibrary match_result = None if source == "musicbrainz": match_result = await get_release_details(source_id) elif source == "open_library": from ..services.matching.open_library import get_work_details match_result = await get_work_details(source_id) if not match_result: # Fallback: nur die übergebenen Daten verwenden match_result = MatchResult( source=source, source_id=source_id, title=body.get("title", item.title or ""), author=body.get("author"), publish_year=body.get("publishYear"), cover_url=body.get("cover"), confidence=1.0, ) match_result.confidence = 1.0 # Manuell → immer akzeptieren await _apply_match(db, item, match_result, confidence=1.0) item.match_locked = True item.updated_at = datetime.utcnow() await db.commit() await db.refresh(item) from ..routers.items import _enrich_item_with_files return await _enrich_item_with_files(item, db) @router.delete("/{item_id}/match") async def clear_match( item_id: str, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): result = await db.execute(select(LibraryItem).where(LibraryItem.id == item_id)) item = result.scalar_one_or_none() if not item: raise HTTPException(status_code=404, detail="Item not found") item.matched_source = "none" item.matched_id = None item.match_confidence = 0.0 item.match_locked = False tags = item.tags or [] if "zu_prüfen" not in tags: tags.append("zu_prüfen") item.tags = tags item.updated_at = datetime.utcnow() await db.commit() return {"success": True}