115 lines
4.2 KiB
Python
115 lines
4.2 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.manual_absences import router as manual_absences_router
|
|
|
|
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)
|
|
app.include_router(manual_absences_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") |