"""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 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, 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) return ExternalSearchResponse( results=[ExternalSearchResultOut.from_entity(r) for r in results], searched_sources=[source], )