import hashlib import json import threading from typing import Any from fastapi import APIRouter from fastapi.responses import JSONResponse from . import runtime, search_api from .search_logic import SearchRequest from .storage_lock import serving_lock router = APIRouter() bootstrap_cache: tuple[tuple[Any, ...], dict[str, Any]] | None = None bootstrap_lock = threading.Lock() def generation_id(generation: tuple[Any, ...]) -> str: return hashlib.sha256(repr(generation).encode()).hexdigest()[:16] @router.get("/api/bootstrap") def bootstrap(): global bootstrap_cache with bootstrap_lock: generation = runtime.serving_generation_token() if runtime.index_status() == "ready" and bootstrap_cache and bootstrap_cache[0] == generation: return JSONResponse(bootstrap_cache[1], headers={"Cache-Control": "no-store"}) if not runtime.serving_generation_ready(): return JSONResponse({"error": "serving generation is not ready"}, status_code=503) for _attempt in range(2): generation = runtime.serving_generation_token() search_response = search_api.search(SearchRequest()) if not isinstance(search_response, JSONResponse) or search_response.status_code != 200: return JSONResponse({"error": "default search is unavailable"}, status_code=503) search_payload = json.loads(search_response.body) with serving_lock(): sources = runtime.cached_sources_payload() facets = { kind: runtime.cached_facet_payload(kind, 1, 200, "") for kind in ("source", "author", "tag") } if runtime.index_status() != "ready" or runtime.serving_generation_token() != generation: continue payload = { "generation": generation_id(generation), "search": search_payload, "sources": sources, "facets": facets, } bootstrap_cache = (generation, payload) return JSONResponse(payload, headers={"Cache-Control": "no-store"}) return JSONResponse({"error": "serving generation changed during bootstrap"}, status_code=503)