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 bd.database import wait_for_db, engine, Base 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 DOCS_USER = os.getenv("DOCS_USERNAME", "admin") DOCS_PASS = os.getenv("DOCS_PASSWORD", "admin") _basic = HTTPBasic() 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 @asynccontextmanager async def lifespan(app: FastAPI): await wait_for_db() async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) await ensure_default_admin() yield app = FastAPI( title="News API", version="1.0.0", lifespan=lifespan, docs_url=None, redoc_url=None, openapi_url=None, ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) # Docs protected by HTTP Basic Auth from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html from fastapi.responses import JSONResponse @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"]) if __name__ == "__main__": import uvicorn uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=False)