Files
scud_ai/modules/web_api/main.py
T

113 lines
4.1 KiB
Python

"""
===============================================================================
FILE: modules/web_api/main.py
PROJECT: SCUD Orion AI (Unified Repository)
MODULE: web_api (Main Application Entry Point)
ROLE: Инициализация FastAPI приложения, подключение роутеров и статики.
===============================================================================
"""
# ANCHOR[APP_INIT_IMPORTS]
import os
import sys
import logging
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
if CURRENT_DIR not in sys.path:
sys.path.insert(0, CURRENT_DIR)
ROOT_DIR = os.path.abspath(os.path.join(CURRENT_DIR, "../../"))
for p in [ROOT_DIR, CURRENT_DIR]:
if p not in sys.path:
sys.path.insert(0, p)
from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, JSONResponse
from fastapi.exceptions import RequestValidationError
from routers.auth import router as auth_router
from routers.admin import router as admin_router
from routers.tasks import router as tasks_router
from routers.chat import router as chat_router
from routers.files import router as files_router
from routers.exceptions import router as exceptions_router
from routers.snapshots import router as snapshots_router
from routers.remote_workers import router as remote_workers_router
from routers.context import router as context_router
# ANCHOR[APP_CONFIG]
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[logging.StreamHandler()]
)
STATIC_DIR = os.path.join(CURRENT_DIR, "static")
app = FastAPI(title="SCUD Orion AI Context API", version="2.5")
if os.path.exists(STATIC_DIR):
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
logging.error(f"❌ ОШИБКА ВАЛИДАЦИИ 422 НА {request.url}: {exc.errors()}")
return JSONResponse(
status_code=422,
content={"detail": exc.errors(), "body": str(exc)}
)
# ANCHOR[ROUTER_REGISTRATION]
app.include_router(auth_router)
app.include_router(admin_router)
app.include_router(tasks_router)
app.include_router(chat_router)
app.include_router(files_router)
app.include_router(exceptions_router)
app.include_router(snapshots_router)
app.include_router(remote_workers_router)
app.include_router(context_router)
# ANCHOR[ROOT_STATIC_ROUTES]
@app.get("/")
def read_root():
index_path = os.path.join(STATIC_DIR, "index.html")
if os.path.exists(index_path):
return FileResponse(index_path)
raise HTTPException(status_code=404, detail="Frontend index.html not found")
@app.get("/favicon.ico")
async def favicon():
file_path = os.path.join(STATIC_DIR, "favicon.ico")
if os.path.exists(file_path):
return FileResponse(file_path)
raise HTTPException(status_code=404)
@app.get("/{file_path:path}")
def serve_static_fallback(file_path: str):
clean_path = file_path.lstrip("/")
# Жесткая блокировка скрытых файлов (.env, .git) и служебных форматов
forbidden_patterns = [".env", ".git", ".yml", ".yaml", ".json", ".sql", ".php", ".bak"]
if clean_path.startswith(".") or any(p in clean_path.lower() for p in forbidden_patterns):
raise HTTPException(status_code=403, detail="Access denied")
target = os.path.join(STATIC_DIR, clean_path)
if os.path.isfile(target):
if clean_path.endswith(".js"):
return FileResponse(target, media_type="application/javascript")
elif clean_path.endswith(".css"):
return FileResponse(target, media_type="text/css")
return FileResponse(target)
filename = os.path.basename(clean_path)
for root, _, files in os.walk(STATIC_DIR):
if filename in files:
full_path = os.path.join(root, filename)
media = "application/javascript" if filename.endswith(".js") else "text/css"
return FileResponse(full_path, media_type=media)
raise HTTPException(status_code=404, detail="File not found")