101 lines
4.0 KiB
Python
101 lines
4.0 KiB
Python
import os
|
|
import secrets
|
|
import tomllib
|
|
from pathlib import Path
|
|
from fastapi import Depends, FastAPI, HTTPException, status
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
|
|
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
|
from route import base, holland_crud, klimov_crud, init_data_base, groups_crud, organizations_crud, users_crud, question_crud, choice_crud, response_crud, poll_crud, scale_crud, radar_crud
|
|
from route import auth
|
|
from bd import wait_for_db
|
|
import uvicorn
|
|
|
|
# Читаем версию из pyproject.toml
|
|
try:
|
|
pyproject_path = Path(__file__).parent / "pyproject.toml"
|
|
with open(pyproject_path, "rb") as f:
|
|
pyproject = tomllib.load(f)
|
|
VERSION = pyproject["project"]["version"]
|
|
except Exception as e:
|
|
print(f"Warning: Could not read version from pyproject.toml: {e}")
|
|
VERSION = "0.1.0"
|
|
|
|
APP_PATH = os.getenv("APP_PATH", "/home/user/api-copp")
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Защита документации HTTP Basic Auth
|
|
# ---------------------------------------------------------------------------
|
|
_basic = HTTPBasic()
|
|
|
|
|
|
def _verify_docs(credentials: HTTPBasicCredentials = Depends(_basic)):
|
|
"""Проверяет логин/пароль для доступа к /docs и /redoc."""
|
|
docs_user = os.getenv("DOCS_USERNAME", "")
|
|
docs_pass = os.getenv("DOCS_PASSWORD", "")
|
|
if not docs_user or not docs_pass:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail="API docs are disabled: DOCS_USERNAME / DOCS_PASSWORD not configured",
|
|
)
|
|
# secrets.compare_digest защищает от timing-атак (перебора по времени ответа)
|
|
ok_user = secrets.compare_digest(credentials.username.encode(), docs_user.encode())
|
|
ok_pass = secrets.compare_digest(credentials.password.encode(), docs_pass.encode())
|
|
if not (ok_user and ok_pass):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid credentials",
|
|
headers={"WWW-Authenticate": "Basic"},
|
|
)
|
|
|
|
|
|
# docs_url/openapi_url/redoc_url отключены — добавляем защищённые маршруты вручную
|
|
app = FastAPI(title="api", version=VERSION, docs_url=None, openapi_url=None, redoc_url=None)
|
|
# Разрешаем CORS для фронтенда (dev). При необходимости сузить список origins.
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["http://localhost", "http://127.0.0.1"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
app.include_router(auth.router)
|
|
app.include_router(base.router)
|
|
app.include_router(init_data_base.router)
|
|
app.include_router(groups_crud.router)
|
|
app.include_router(organizations_crud.router)
|
|
app.include_router(users_crud.router)
|
|
app.include_router(question_crud.router)
|
|
app.include_router(choice_crud.router)
|
|
app.include_router(response_crud.router)
|
|
app.include_router(poll_crud.router)
|
|
app.include_router(holland_crud.router)
|
|
app.include_router(klimov_crud.router)
|
|
app.include_router(scale_crud.router)
|
|
app.include_router(radar_crud.router)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Защищённые маршруты документации
|
|
# ---------------------------------------------------------------------------
|
|
@app.get("/docs", include_in_schema=False)
|
|
def get_docs(_: None = Depends(_verify_docs)):
|
|
return get_swagger_ui_html(openapi_url="/openapi.json", title="API Docs")
|
|
|
|
|
|
@app.get("/redoc", include_in_schema=False)
|
|
def get_redoc(_: None = Depends(_verify_docs)):
|
|
return get_redoc_html(openapi_url="/openapi.json", title="API Docs")
|
|
|
|
|
|
@app.get("/openapi.json", include_in_schema=False)
|
|
def get_openapi_schema(_: None = Depends(_verify_docs)):
|
|
return app.openapi()
|
|
|
|
|
|
print(f"Application path: {APP_PATH}")
|
|
wait_for_db()
|
|
|
|
if __name__ == "__main__":
|
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|