"""Authenticated catalog endpoints; writes inherit the application origin guard.""" from typing import Annotated from fastapi import APIRouter, Depends, Query, Request from ..schemas import JobOutput from ..security import require_auth from .contracts import ( UNIVERSES, CatalogFilters, CatalogJobInput, CatalogPage, CollectionOutput, EntryOutput, InputOutput, InputPreparation, NoteInput, NoteOutput, Scope, ) from .service import Catalog router = APIRouter(prefix="/api/v1/catalog", tags=["catalog"], dependencies=[Depends(require_auth)]) @router.get("/scopes") async def scopes() -> dict[str, list[str]]: return UNIVERSES @router.get("/datasets", response_model=CatalogPage) async def datasets(request: Request, filters: Annotated[CatalogFilters, Query()]): async with request.app.state.sessions() as db: return await Catalog(db).search(filters) @router.get("/datasets/{dataset_id}", response_model=EntryOutput) async def detail(request: Request, dataset_id: str, scope: Annotated[Scope, Query()]): async with request.app.state.sessions() as db: return await Catalog(db).detail(scope, dataset_id) @router.get("/datasets/{dataset_id}/fields", response_model=CatalogPage) async def fields(request: Request, dataset_id: str, filters: Annotated[CatalogFilters, Query()]): async with request.app.state.sessions() as db: return await Catalog(db).search(filters, dataset_id) @router.get("/datasets/{dataset_id}/fields/{field_id}", response_model=EntryOutput) async def field(request: Request, dataset_id: str, field_id: str, scope: Annotated[Scope, Query()]): async with request.app.state.sessions() as db: return await Catalog(db).detail(scope, dataset_id, field_id) @router.patch("/datasets/{dataset_id}/research", response_model=NoteOutput) async def note(request: Request, dataset_id: str, scope: Annotated[Scope, Query()], body: NoteInput): async with request.app.state.sessions.begin() as db: return await Catalog(db).save_note(scope, dataset_id, "", body) @router.patch("/datasets/{dataset_id}/fields/{field_id}/research", response_model=NoteOutput) async def field_note( request: Request, dataset_id: str, field_id: str, scope: Annotated[Scope, Query()], body: NoteInput ): async with request.app.state.sessions.begin() as db: return await Catalog(db).save_note(scope, dataset_id, field_id, body) @router.post("/sync-jobs", status_code=202, response_model=JobOutput) async def sync(request: Request, body: CatalogJobInput): async with request.app.state.sessions.begin() as db: result = await Catalog(db).create_job(body) request.app.state.runner.wake.set() return result @router.post("/inputs", status_code=201, response_model=InputOutput) async def prepare(request: Request, body: InputPreparation): async with request.app.state.sessions.begin() as db: return await Catalog(db).prepare(body) @router.get("/inputs", response_model=list[InputOutput]) async def inputs(request: Request, scope: Annotated[Scope, Query()]): async with request.app.state.sessions() as db: return await Catalog(db).inputs(scope) @router.get("/inputs/{input_id}", response_model=InputOutput) async def get_input(request: Request, input_id: str): async with request.app.state.sessions() as db: return await Catalog(db).input(input_id) @router.get("/datasets/{dataset_id}/collection", response_model=CollectionOutput) async def collection(request: Request, dataset_id: str, scope: Annotated[Scope, Query()]): async with request.app.state.sessions() as db: return await Catalog(db).collection(scope, dataset_id)