""" =============================================================================== 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 from routers.presence import router as presence_router from routers.reports import router as reports_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) app.include_router(presence_router) app.include_router(reports_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")