112 lines
4.0 KiB
Python
112 lines
4.0 KiB
Python
import os
|
|
import secrets
|
|
from contextlib import asynccontextmanager
|
|
from fastapi import FastAPI, Depends, HTTPException, status
|
|
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse
|
|
from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html
|
|
from slowapi import _rate_limit_exceeded_handler
|
|
from slowapi.errors import RateLimitExceeded
|
|
from rate_limiter import limiter
|
|
|
|
from bd.database import wait_for_db
|
|
from route.public import router as public_router
|
|
from route.admin_auth import router as auth_router, ensure_default_admin
|
|
from route.admin_articles import router as articles_router
|
|
from route.admin_categories import router as categories_router
|
|
from route.admin_media import router as media_router
|
|
from route.admin_vk import router as vk_router
|
|
from route.admin_system import router as system_router
|
|
import scheduler
|
|
|
|
DOCS_USER = os.getenv("DOCS_USERNAME", "admin")
|
|
DOCS_PASS = os.getenv("DOCS_PASSWORD", "admin")
|
|
_basic = HTTPBasic()
|
|
|
|
# Rate limiter — ключ по IP-адресу
|
|
def _verify_docs(creds: HTTPBasicCredentials = Depends(_basic)):
|
|
ok_u = secrets.compare_digest(creds.username.encode(), DOCS_USER.encode())
|
|
ok_p = secrets.compare_digest(creds.password.encode(), DOCS_PASS.encode())
|
|
if not (ok_u and ok_p):
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, headers={"WWW-Authenticate": "Basic"})
|
|
return creds.username
|
|
|
|
|
|
def _run_migrations():
|
|
import subprocess, sys
|
|
result = subprocess.run(
|
|
["alembic", "upgrade", "head"],
|
|
capture_output=True, text=True
|
|
)
|
|
if result.returncode != 0:
|
|
print(f"[Alembic] Ошибка миграции:\n{result.stderr}", file=sys.stderr)
|
|
else:
|
|
print(f"[Alembic] {result.stdout.strip() or 'Миграции применены'}")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
await wait_for_db()
|
|
_run_migrations()
|
|
await ensure_default_admin()
|
|
scheduler.start()
|
|
yield
|
|
scheduler.shutdown()
|
|
|
|
|
|
app = FastAPI(
|
|
title="News API",
|
|
version="1.0.0",
|
|
lifespan=lifespan,
|
|
docs_url=None,
|
|
redoc_url=None,
|
|
openapi_url=None,
|
|
)
|
|
|
|
app.state.limiter = limiter
|
|
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
|
|
|
# CORS: разрешаем только наш домен (меняй на продакшн-домен)
|
|
_ALLOWED_ORIGINS = [o.strip() for o in os.getenv("ALLOWED_ORIGINS", "http://localhost,http://localhost:80").split(",") if o.strip()]
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=_ALLOWED_ORIGINS,
|
|
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
allow_headers=["Authorization", "Content-Type"],
|
|
)
|
|
|
|
|
|
@app.get("/docs", include_in_schema=False)
|
|
async def docs(username: str = Depends(_verify_docs)):
|
|
return get_swagger_ui_html(openapi_url="/openapi.json", title="News API Docs")
|
|
|
|
|
|
@app.get("/redoc", include_in_schema=False)
|
|
async def redoc(username: str = Depends(_verify_docs)):
|
|
return get_redoc_html(openapi_url="/openapi.json", title="News API Redoc")
|
|
|
|
|
|
@app.get("/openapi.json", include_in_schema=False)
|
|
async def openapi(username: str = Depends(_verify_docs)):
|
|
return JSONResponse(app.openapi())
|
|
|
|
|
|
@app.get("/health", include_in_schema=False)
|
|
async def health():
|
|
return {"ok": True}
|
|
|
|
|
|
app.include_router(public_router, prefix="/news", tags=["public"])
|
|
app.include_router(auth_router, prefix="/admin", tags=["admin-auth"])
|
|
app.include_router(articles_router, prefix="/admin/articles", tags=["admin-articles"])
|
|
app.include_router(categories_router,prefix="/admin/categories",tags=["admin-categories"])
|
|
app.include_router(media_router, prefix="/admin/media", tags=["admin-media"])
|
|
app.include_router(vk_router, prefix="/admin/vk", tags=["admin-vk"])
|
|
app.include_router(system_router, prefix="/admin/system", tags=["admin-system"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=False)
|