from __future__ import annotations import time from typing import Optional from fastapi import APIRouter, Depends, Query from app.api.deps import require_auth from app.core.logger import get_logger from app.models.schemas import ( WebSearchAutocompleteRequest, WebSearchAutocompleteResponse, WebSearchConfigResponse, WebSearchEngineDescriptionsResponse, WebSearchRequest, WebSearchResponse, WebSearchStatsResponse, ) from app.services.web_search_service import WebSearchService router = APIRouter() _logger = get_logger(__name__) def get_search_service() -> WebSearchService: return WebSearchService() @router.post( "/web-search", response_model=WebSearchResponse, summary="Search the web using SearXNG meta search engine", ) async def web_search_post( body: WebSearchRequest, token: str = Depends(require_auth), svc: WebSearchService = Depends(get_search_service), ) -> WebSearchResponse: _logger.info("Web search (POST): q=%s, cat=%s", body.q, body.categories) start = time.perf_counter() result = await svc.search( query=body.q, categories=body.categories, language=body.language, pageno=body.pageno, time_range=body.time_range, safesearch=body.safesearch, engines=body.engines, max_results=body.max_results, ) elapsed_ms = round((time.perf_counter() - start) * 1000, 2) return WebSearchResponse( success=result.get("success", False), time_ms=elapsed_ms, query=result.get("query", body.q), number_of_results=result.get("number_of_results", 0), results=result.get("results", []), suggestions=result.get("suggestions", []), infoboxes=result.get("infoboxes", []), error=result.get("error"), ) @router.get( "/web-search", response_model=WebSearchResponse, summary="Search the web via query parameters", ) async def web_search_get( q: str = Query(..., min_length=1, max_length=500, description="Search query"), categories: str = Query("general", description="Comma-separated categories"), language: str = Query("en", description="Language code"), pageno: int = Query(1, ge=1, le=100, description="Page number"), time_range: Optional[str] = Query(None, description="Time range: day, week, month, year"), safesearch: int = Query(0, ge=0, le=2, description="Safe search level"), engines: Optional[str] = Query(None, description="Comma-separated engines"), max_results: int = Query(10, ge=1, le=50, description="Max results"), token: str = Depends(require_auth), svc: WebSearchService = Depends(get_search_service), ) -> WebSearchResponse: _logger.info("Web search (GET): q=%s, cat=%s", q, categories) start = time.perf_counter() result = await svc.search( query=q, categories=categories, language=language, pageno=pageno, time_range=time_range, safesearch=safesearch, engines=engines, max_results=max_results, ) elapsed_ms = round((time.perf_counter() - start) * 1000, 2) return WebSearchResponse( success=result.get("success", False), time_ms=elapsed_ms, query=result.get("query", q), number_of_results=result.get("number_of_results", 0), results=result.get("results", []), suggestions=result.get("suggestions", []), infoboxes=result.get("infoboxes", []), error=result.get("error"), ) @router.get( "/web-search/config", response_model=WebSearchConfigResponse, summary="Get SearXNG instance configuration (engines, categories, plugins)", ) async def web_search_config( token: str = Depends(require_auth), svc: WebSearchService = Depends(get_search_service), ) -> WebSearchConfigResponse: _logger.info("Web search config request") start = time.perf_counter() result = await svc.get_config() elapsed_ms = round((time.perf_counter() - start) * 1000, 2) return WebSearchConfigResponse( success=result.get("success", False), time_ms=elapsed_ms, instance_name=result.get("instance_name"), version=result.get("version"), engines=result.get("engines", []), categories=result.get("categories", []), plugins=result.get("plugins", []), error=result.get("error"), ) @router.get( "/web-search/autocomplete", response_model=WebSearchAutocompleteResponse, summary="Get autocomplete suggestions for a query prefix", ) async def web_search_autocomplete( q: str = Query(..., min_length=1, max_length=200, description="Query prefix"), token: str = Depends(require_auth), svc: WebSearchService = Depends(get_search_service), ) -> WebSearchAutocompleteResponse: _logger.info("Web search autocomplete: q=%s", q) start = time.perf_counter() result = await svc.autocomplete(q) elapsed_ms = round((time.perf_counter() - start) * 1000, 2) return WebSearchAutocompleteResponse( success=result.get("success", False), time_ms=elapsed_ms, query=result.get("query", q), suggestions=result.get("suggestions", []), error=result.get("error"), ) @router.post( "/web-search/autocomplete", response_model=WebSearchAutocompleteResponse, summary="Get autocomplete suggestions (POST)", ) async def web_search_autocomplete_post( body: WebSearchAutocompleteRequest, token: str = Depends(require_auth), svc: WebSearchService = Depends(get_search_service), ) -> WebSearchAutocompleteResponse: _logger.info("Web search autocomplete (POST): q=%s", body.q) start = time.perf_counter() result = await svc.autocomplete(body.q) elapsed_ms = round((time.perf_counter() - start) * 1000, 2) return WebSearchAutocompleteResponse( success=result.get("success", False), time_ms=elapsed_ms, query=result.get("query", body.q), suggestions=result.get("suggestions", []), error=result.get("error"), ) @router.get( "/web-search/engine-descriptions", response_model=WebSearchEngineDescriptionsResponse, summary="Get all SearXNG engine descriptions", ) async def web_search_engine_descriptions( token: str = Depends(require_auth), svc: WebSearchService = Depends(get_search_service), ) -> WebSearchEngineDescriptionsResponse: _logger.info("Web search engine descriptions") start = time.perf_counter() result = await svc.get_engine_descriptions() elapsed_ms = round((time.perf_counter() - start) * 1000, 2) return WebSearchEngineDescriptionsResponse( success=result.get("success", False), time_ms=elapsed_ms, engines=result.get("engines"), error=result.get("error"), ) @router.get( "/web-search/stats", response_model=WebSearchStatsResponse, summary="Get SearXNG instance statistics", ) async def web_search_stats( token: str = Depends(require_auth), svc: WebSearchService = Depends(get_search_service), ) -> WebSearchStatsResponse: _logger.info("Web search stats request") start = time.perf_counter() result = await svc.get_stats() elapsed_ms = round((time.perf_counter() - start) * 1000, 2) return WebSearchStatsResponse( success=result.get("success", False), time_ms=elapsed_ms, stats=result.get("stats"), error=result.get("error"), ) @router.get( "/web-search/health", summary="Check SearXNG instance health", ) async def web_search_health( token: str = Depends(require_auth), svc: WebSearchService = Depends(get_search_service), ) -> dict: _logger.info("Web search health check") result = await svc.health() return result