"""Отдача README.md и файлов ``app/docs/*.md`` для клиентского рендера Markdown (marked в static). Автор: Сергей Антропов Сайт: https://devops.org.ru """ from __future__ import annotations import asyncio import logging from fastapi import APIRouter, HTTPException, Query from fastapi.responses import PlainTextResponse from core.readme_doc import read_app_docs_file_text, read_readme_text logger = logging.getLogger("kind_k8s.api.docs_readme") router = APIRouter(tags=["documentation"]) @router.get( "/docs/readme", response_class=PlainTextResponse, summary="README.md как текст (Markdown)", responses={404: {"description": "Файл не найден"}}, ) async def get_readme_markdown() -> PlainTextResponse: """ Тело ответа — содержимое README в кодировке UTF-8. Разбор Markdown выполняется в браузере скриптами из ``/static/js/vendor/`` (marked + DOMPurify). """ try: def _read() -> str: return read_readme_text() text = await asyncio.to_thread(_read) except FileNotFoundError: logger.info("GET /docs/readme: файл README не найден") raise HTTPException( status_code=404, detail=( "README.md не найден. Укажите KIND_K8S_README_PATH, смонтируйте в compose " "./README.md:/opt/kind-k8s/README.md:ro или пересоберите образ (COPY README.md)." ), ) from None logger.debug("GET /docs/readme: отдано %s символов", len(text)) return PlainTextResponse( content=text, media_type="text/markdown; charset=utf-8", ) @router.get( "/docs/file", response_class=PlainTextResponse, summary="Файл Markdown под app/docs/", responses={400: {"description": "Некорректный путь"}, 403: {"description": "Вне app/docs"}, 404: {"description": "Нет файла"}}, ) async def get_app_docs_file( path: str = Query( ..., description="Относительный путь, например app/docs/api_routes.md", examples=["app/docs/api_routes.md"], ), ) -> PlainTextResponse: """ Только файлы ``*.md`` с префиксом ``app/docs/`` (без ``..``). Ссылки из README ведут на ``/documentation?path=...``; клиент запрашивает этот эндпоинт. """ try: def _read() -> str: return read_app_docs_file_text(path) text = await asyncio.to_thread(_read) except FileNotFoundError: logger.info("GET /docs/file: не найден или запрещён путь %s", path) raise HTTPException( status_code=404, detail="Файл не найден или путь не разрешён (ожидается app/docs/<имя>.md).", ) from None logger.debug("GET /docs/file: отдан %s", path) return PlainTextResponse( content=text, media_type="text/markdown; charset=utf-8", )