"""搜索接口路由。""" from __future__ import annotations import logging from fastapi import APIRouter, Query from app.response import ApiResponse, BusinessError, ResponseCode from app.schemas import SearchData, SearchRequest from app.services.cache import faq_cache from app.services.zendesk_client import ZendeskError, search_articles logger = logging.getLogger(__name__) router = APIRouter(tags=["search"]) async def _do_search(req: SearchRequest) -> ApiResponse[SearchData]: """共享的搜索逻辑:使用内存中的 FAQ sec_ids 调用 Zendesk。""" snapshot = faq_cache.snapshot() sec_ids: list[int] = snapshot["sec_ids"] if not sec_ids: logger.warning("FAQ sec_ids 缓存为空,本次搜索不带 section 过滤") try: data = await search_articles( query=req.query, section_ids=sec_ids, locale=req.locale, page=req.page, per_page=req.per_page, ) except ZendeskError as exc: raise BusinessError(ResponseCode.UPSTREAM_ERROR) from exc return ApiResponse.ok( data=SearchData( count=int(data.get("count", 0)), page=req.page, per_page=req.per_page, is_next_page=bool(data.get("next_page")), results=data.get("results", []), ) ) @router.get( "/search", response_model=ApiResponse[SearchData], summary="按 QUERY 搜索 FAQ 文章", ) async def search_get( query: str = Query(..., min_length=1, description="搜索关键词"), locale: str | None = Query(default=None), page: int = Query(default=1, ge=1), per_page: int = Query(default=25, ge=1, le=100), ) -> ApiResponse[SearchData]: """GET 版本,便于浏览器直接测试。""" return await _do_search( SearchRequest( query=query, locale=locale, page=page, per_page=per_page ) ) @router.post( "/search", response_model=ApiResponse[SearchData], summary="按 QUERY 搜索 FAQ 文章 (POST)", ) async def search_post(req: SearchRequest) -> ApiResponse[SearchData]: """POST 版本,请求体:{"query": "...", "locale": "...", ...}。""" return await _do_search(req)