diff --git a/app/config.py b/app/config.py index 7cdbbf9e..0caa373e 100644 --- a/app/config.py +++ b/app/config.py @@ -980,11 +980,11 @@ class Settings(BaseSettings): if self.WEB_API_DOCS_ENABLED: return { "docs_url": "/docs", - "redoc_url": "/redoc", + "scalar_url": "/scalar", "openapi_url": "/openapi.json", } - return {"docs_url": None, "redoc_url": None, "openapi_url": None} + return {"docs_url": None, "scalar_url": None, "openapi_url": None} def get_support_system_mode(self) -> str: mode = (self.SUPPORT_SYSTEM_MODE or "both").strip().lower() diff --git a/app/webapi/app.py b/app/webapi/app.py index dd3249f2..1959938f 100644 --- a/app/webapi/app.py +++ b/app/webapi/app.py @@ -2,6 +2,7 @@ from __future__ import annotations from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import HTMLResponse from app.config import settings @@ -18,6 +19,8 @@ from .routes import ( users, ) +from .docs import build_scalar_docs_html + def create_web_api_app() -> FastAPI: docs_config = settings.get_web_api_docs_config() @@ -26,7 +29,7 @@ def create_web_api_app() -> FastAPI: title=settings.WEB_API_TITLE, version=settings.WEB_API_VERSION, docs_url=docs_config.get("docs_url"), - redoc_url=docs_config.get("redoc_url"), + redoc_url=None, openapi_url=docs_config.get("openapi_url"), swagger_ui_parameters={"persistAuthorization": True}, ) @@ -53,4 +56,15 @@ def create_web_api_app() -> FastAPI: app.include_router(promo_groups.router, prefix="/promo-groups", tags=["promo-groups"]) app.include_router(tokens.router, prefix="/tokens", tags=["auth"]) + scalar_url = docs_config.get("scalar_url") + if scalar_url and app.openapi_url: + + @app.get(scalar_url, include_in_schema=False) + async def scalar_docs() -> HTMLResponse: + html_content = build_scalar_docs_html( + title=settings.WEB_API_TITLE, + openapi_url=app.openapi_url, + ) + return HTMLResponse(content=html_content) + return app diff --git a/app/webapi/docs.py b/app/webapi/docs.py new file mode 100644 index 00000000..66d2045f --- /dev/null +++ b/app/webapi/docs.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +import html + + +def build_scalar_docs_html(*, title: str, openapi_url: str, theme: str = "modern") -> str: + escaped_title = html.escape(title) + escaped_openapi_url = html.escape(openapi_url) + escaped_theme = html.escape(theme) + return f"""\n\n \n \n \n {escaped_title} API Reference\n \n \n \n \n \n \n\n""" diff --git a/docs/web-admin-integration.md b/docs/web-admin-integration.md index d5d2693d..ad6307e8 100644 --- a/docs/web-admin-integration.md +++ b/docs/web-admin-integration.md @@ -34,7 +34,7 @@ API разворачивается вместе с ботом, использу Чтобы открыть интерфейс Swagger UI на `/docs`, убедитесь, что одновременно заданы две переменные окружения: 1. `WEB_API_ENABLED=true` — включает само веб-API. -2. `WEB_API_DOCS_ENABLED=true` — публикует `/docs`, `/redoc` и `/openapi.json`. +2. `WEB_API_DOCS_ENABLED=true` — публикует `/docs`, `/scalar` и `/openapi.json`. После изменения значений перезапустите бота. Интерфейс будет доступен по адресу `http://:/docs`. @@ -128,7 +128,7 @@ curl -X POST "http://127.0.0.1:8080/tokens" \ | `DELETE` | `/promo-groups/{id}` | Удалить промо-группу. | `GET` | `/tokens` | Управление токенами доступа. -> Все списковые эндпоинты поддерживают пагинацию (`limit`, `offset`) и фильтры, описанные в OpenAPI спецификации. Если `WEB_API_DOCS_ENABLED=true`, документация доступна по `/docs`. В ответах `/settings` поле `choices` всегда массив: пустой список означает отсутствие предопределённых значений. +> Все списковые эндпоинты поддерживают пагинацию (`limit`, `offset`) и фильтры, описанные в OpenAPI спецификации. Если `WEB_API_DOCS_ENABLED=true`, интерактивные интерфейсы документации доступны по `/docs` (Swagger UI) и `/scalar` (Scalar). В ответах `/settings` поле `choices` всегда массив: пустой список означает отсутствие предопределённых значений. ## 7. Сценарий интеграции веб-админки