- Fix 28 ruff errors: E501 line length, B904 raise-from, F401 unused import - Fix SQLAlchemy Row.count() ambiguity with tuple indexing (Pyright) - Replace composite column notation with accessor functions in MCP tools (topocentric/equatorial/pass_event are C-level base types, not composites) - Fix satellite_pass: use time window (start + end) not count parameter to match predict_passes(tle, observer, start_ts, end_ts, min_el) signature
77 lines
2.2 KiB
Python
77 lines
2.2 KiB
Python
from fastapi import APIRouter, Depends, Query
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from orrery_search.db import get_db
|
|
from orrery_search.models.document import Document
|
|
from orrery_search.schemas.search import (
|
|
SearchResponse,
|
|
SearchResult,
|
|
SectionCount,
|
|
SectionsResponse,
|
|
)
|
|
from orrery_search.services.search import search_documents
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("", response_model=SearchResponse)
|
|
async def search(
|
|
q: str = Query(..., min_length=1, max_length=500),
|
|
content_type: str | None = Query(None, description="Filter by content type"),
|
|
section: str | None = Query(
|
|
None, max_length=200, description="Section prefix filter"
|
|
),
|
|
limit: int = Query(10, ge=1, le=50),
|
|
mode: str = Query("hybrid", pattern="^(hybrid|semantic|text)$"),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Search pg_orrery documentation using semantic and/or text matching."""
|
|
output = await search_documents(
|
|
q=q,
|
|
db=db,
|
|
mode=mode,
|
|
content_type=content_type,
|
|
section=section,
|
|
limit=limit,
|
|
)
|
|
|
|
return SearchResponse(
|
|
query=output.query,
|
|
results=[
|
|
SearchResult(
|
|
title=r.title,
|
|
slug=r.slug,
|
|
section=r.section,
|
|
content_type=r.content_type,
|
|
description=r.description,
|
|
snippet=r.snippet,
|
|
url=r.url,
|
|
score=r.score,
|
|
source=r.source,
|
|
)
|
|
for r in output.results
|
|
],
|
|
count=output.count,
|
|
mode=output.mode,
|
|
)
|
|
|
|
|
|
@router.get("/sections", response_model=SectionsResponse)
|
|
async def list_sections(
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""List all sections with document counts for the filter UI."""
|
|
stmt = (
|
|
select(Document.section, func.count(Document.id).label("count"))
|
|
.group_by(Document.section)
|
|
.order_by(Document.section)
|
|
)
|
|
result = await db.execute(stmt)
|
|
sections = [
|
|
SectionCount(section=row[0], count=row[1])
|
|
for row in result
|
|
if row[0]
|
|
]
|
|
return SectionsResponse(sections=sections)
|