e45e578f54
Search results now report whether a hit is already saved (in_library,
track_id, availability). New RemoteLibraryService backs POST
/tracks/remote (idempotent placeholder save) and POST
/tracks/{id}/materialize (on-demand fetch via a new materialize_track
arq task, reusing in-flight jobs).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
58 lines
2.4 KiB
Python
58 lines
2.4 KiB
Python
"""External source endpoints: enumerate sources, search, and trigger imports.
|
|
|
|
Listing/health/search are read-only (any authenticated user). Scanning a source
|
|
is an admin action and runs in a worker — the endpoint only enqueues it.
|
|
"""
|
|
|
|
from fastapi import APIRouter, Query
|
|
|
|
from app.api.deps import CurrentUser, SourceRegistryDep, SuperUser, TrackRepoDep
|
|
from app.api.schemas.external_search import ExternalSearchResponse, ExternalSearchResultOut
|
|
from app.api.schemas.source import ScanResponse, SourceHealthOut, SourceInfoOut
|
|
from app.domain.errors import DependencyUnavailableError
|
|
from app.workers.queue import enqueue
|
|
|
|
router = APIRouter(prefix="/sources", tags=["sources"])
|
|
|
|
|
|
@router.get("")
|
|
async def list_sources(_: CurrentUser, registry: SourceRegistryDep) -> list[SourceInfoOut]:
|
|
return [SourceInfoOut.from_entity(info) for info in registry.infos()]
|
|
|
|
|
|
@router.post("/{source}/scan")
|
|
async def scan_source(source: str, user: SuperUser, registry: SourceRegistryDep) -> ScanResponse:
|
|
backend = registry.indexable(source) # 404 if unknown, 422 if not indexable
|
|
if not backend.is_available():
|
|
raise DependencyUnavailableError(f"Source {source!r} is not available.")
|
|
job_id = await enqueue("scan_local_folder", source=source, added_by=str(user.id))
|
|
return ScanResponse(source=source, job_id=job_id)
|
|
|
|
|
|
@router.get("/{source}/health")
|
|
async def source_health(
|
|
source: str, _: CurrentUser, registry: SourceRegistryDep
|
|
) -> SourceHealthOut:
|
|
backend = registry.get(source) # 404 if unknown
|
|
return SourceHealthOut(name=backend.name, available=backend.is_available())
|
|
|
|
|
|
@router.get("/{source}/search")
|
|
async def search_source(
|
|
source: str,
|
|
_: CurrentUser,
|
|
registry: SourceRegistryDep,
|
|
track_repo: TrackRepoDep,
|
|
q: str = Query(min_length=1),
|
|
limit: int = Query(20, ge=1, le=50),
|
|
) -> ExternalSearchResponse:
|
|
backend = registry.searchable(source) # 404 if unknown, 422 if not searchable
|
|
if not backend.is_available():
|
|
raise DependencyUnavailableError(f"Source {source!r} is not available.")
|
|
results = await backend.search(q, limit=limit)
|
|
out: list[ExternalSearchResultOut] = []
|
|
for r in results:
|
|
existing = await track_repo.get_by_source(r.source, r.source_id)
|
|
out.append(ExternalSearchResultOut.from_entity(r, existing=existing))
|
|
return ExternalSearchResponse(results=out, searched_sources=[source])
|