"""External source endpoints: enumerate sources and trigger imports. Listing/health are read-only (any authenticated user). Scanning a source is an admin action and runs in a worker — the endpoint only enqueues it. """ from typing import Any from fastapi import APIRouter from app.api.deps import CurrentUser, SourceRegistryDep, SuperUser 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) -> Any: # Search is for fetch-style sources (youtube, …) — not yet implemented. ...