"""Misc endpoints: health, i18n, public settings.""" from __future__ import annotations from fastapi import APIRouter, Depends from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import text from app import __version__ from app.config import get_settings from app.core.embeddings import HashEmbedder from app.core.logging import get_logger from app.core.qdrant_client import ping_qdrant from app.core.settings_service import get_all_settings, get_setting from app.db import get_db from app.schemas import HealthResponse _logger = get_logger(__name__) router = APIRouter(prefix="/api", tags=["misc"]) @router.get("/health", response_model=HealthResponse) async def health(db: AsyncSession = Depends(get_db)) -> HealthResponse: """Health-check endpoint — no auth required.""" db_ok = False qdrant_ok = False try: await db.execute(text("SELECT 1")) db_ok = True except Exception as e: # noqa: BLE001 _logger.warning("health_db_failed", error=str(e)) try: qdrant_ok = await ping_qdrant() except Exception as e: # noqa: BLE001 _logger.warning("health_qdrant_failed", error=str(e)) cfg = get_settings() llm_ok = bool(cfg.llm_api_url) embeddings_ok = True if cfg.embeddings_provider == "offline_hash": try: embedder = HashEmbedder(dimension=cfg.embeddings_dimension) _ = await embedder.embed(["ping"]) except Exception: embeddings_ok = False status_str = "ok" if (db_ok and qdrant_ok) else "degraded" return HealthResponse( status=status_str, db=db_ok, qdrant=qdrant_ok, llm=llm_ok, embeddings=embeddings_ok, version=__version__, ) @router.get("/settings/public") async def public_settings(db: AsyncSession = Depends(get_db)) -> dict: """Return UI-relevant settings (no secrets). No auth required. The frontend uses this on app load to set the page title, favicon, and logo. """ page_title = await get_setting(db, "ui.page_title") favicon_url = await get_setting(db, "ui.favicon_url") logo_url = await get_setting(db, "ui.logo_url") og_image_url = await get_setting(db, "ui.og_image_url") header_title = await get_setting(db, "ui.header_title") return { "page_title": page_title or "AI-RPG", "favicon_url": favicon_url or "/icon.png", "logo_url": logo_url or "/icon.png", "og_image_url": og_image_url or "", "header_title": header_title or page_title or "AI-RPG", } @router.get("/names/{language}") async def get_name_bank( language: str, db: AsyncSession = Depends(get_db), ) -> dict: """Return a random character name for the given language. No auth required — used by the world builder form's "random name" button. """ import random key = f"character_names.{language}" names = await get_setting(db, key) if not names or not isinstance(names, list): # Fallback to English names = await get_setting(db, "character_names.en") or ["Hero"] pick = random.choice(names) if names else "Hero" return {"name": pick, "language": language, "count": len(names)} @router.get("/i18n/{lang}") async def i18n(lang: str) -> dict: """Return translation JSON for the given language.""" if lang not in ("en", "ru"): return {"error": "unsupported language"} return {"language": lang}