import os from pathlib import Path from typing import Optional from fastapi import Depends, FastAPI, Header, HTTPException, status from fastapi.responses import HTMLResponse from pydantic import BaseModel, Field from ddgs_api.config import ensure_ca_bundle, env_bool, env_int, env_str, load_env from ddgs_api.search import ddgs_search from ddgs_api.ui import render_homepage def require_auth(authorization: Optional[str] = Header(default=None)) -> None: expected = os.getenv("API_BEARER_TOKEN") if not expected: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="API_BEARER_TOKEN is not configured", ) if not authorization or not authorization.startswith("Bearer "): raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized") token = authorization.split(" ", 1)[1] if token != expected: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized") class SearchRequest(BaseModel): query: str = Field(..., min_length=1) region: str = Field(default_factory=lambda: env_str("DDGS_REGION", "us-en") or "us-en") safesearch: str = Field( default_factory=lambda: env_str("DDGS_SAFESEARCH", "moderate") or "moderate" ) timelimit: Optional[str] = Field(default_factory=lambda: env_str("DDGS_TIMELIMIT", None)) max_results: int = Field(default_factory=lambda: env_int("DDGS_MAX_RESULTS", 10) or 10, ge=1) backend: Optional[str] = Field(default_factory=lambda: env_str("DDGS_BACKEND", "auto")) proxy: Optional[str] = Field(default_factory=lambda: env_str("DDGS_PROXY", None)) timeout: int = Field(default_factory=lambda: env_int("DDGS_TIMEOUT", 30) or 30, ge=1) verify: bool = Field(default_factory=lambda: env_bool("DDGS_VERIFY", True)) def create_app(*, project_root: Path | None = None) -> FastAPI: load_env(project_root=project_root) ensure_ca_bundle(project_root=project_root) app = FastAPI(title="DDGS Search API", version="0.1.0") ui_defaults = { "region": env_str("DDGS_REGION", "us-en") or "us-en", "safesearch": env_str("DDGS_SAFESEARCH", "moderate") or "moderate", "timelimit": env_str("DDGS_TIMELIMIT", None), "max_results": env_int("DDGS_MAX_RESULTS", 10) or 10, "backend": env_str("DDGS_BACKEND", "auto") or "auto", "timeout": env_int("DDGS_TIMEOUT", 30) or 30, "verify": env_bool("DDGS_VERIFY", True), } @app.get("/health") def health() -> dict[str, str]: return {"status": "ok"} @app.get("/", response_class=HTMLResponse) def root() -> str: return render_homepage(ui_defaults) @app.post("/search") async def search(request: SearchRequest, _: None = Depends(require_auth)) -> dict[str, object]: results = ddgs_search( request.query, region=request.region, safesearch=request.safesearch, timelimit=request.timelimit, max_results=request.max_results, backend=request.backend, proxy=request.proxy, timeout=request.timeout, verify=request.verify, ) return {"query": request.query, "count": len(results), "results": results} return app app = create_app()