| import mimetypes |
| import os |
| import subprocess |
| import threading |
| import time |
| from pathlib import Path |
|
|
| from dotenv import load_dotenv |
| load_dotenv(Path(__file__).resolve().parent / ".env") |
|
|
| from fastapi import FastAPI, Request |
| from fastapi.middleware.cors import CORSMiddleware |
| from fastapi.staticfiles import StaticFiles |
|
|
| from core.config import GRADIO_SPACE_URL, logger |
| from routers import auth, catalog, media, pages, segmentation, sessions, share |
| from routers.catalog import seed_catalog |
| from services.sam2_service import lifespan |
|
|
| mimetypes.add_type("application/javascript", ".js", strict=True) |
| mimetypes.add_type("text/css", ".css", strict=True) |
| mimetypes.add_type("image/svg+xml", ".svg", strict=True) |
|
|
| logger.info("[STARTUP] GRADIO_SPACE_URL=%s", GRADIO_SPACE_URL or "(not set — using local SAM2)") |
|
|
| app = FastAPI(title="Hyper Reality Backend", lifespan=lifespan) |
|
|
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_credentials=True, |
| allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], |
| allow_headers=["*"], |
| ) |
|
|
|
|
| @app.middleware("http") |
| async def remove_x_frame_options(request: Request, call_next): |
| response = await call_next(request) |
| if "x-frame-options" in response.headers: |
| del response.headers["x-frame-options"] |
| response.headers["Content-Security-Policy"] = "frame-ancestors *" |
| return response |
|
|
|
|
| |
| app.include_router(pages.router) |
| app.include_router(auth.router) |
| app.include_router(share.router) |
| app.include_router(media.router) |
| app.include_router(catalog.router) |
| app.include_router(sessions.router) |
| app.include_router(segmentation.router) |
|
|
| |
| BASE_DIR = Path(__file__).resolve().parent |
| UPLOADS_DIR = BASE_DIR / "uploads" |
| FRONTEND_DIST = BASE_DIR.parent / "frontend" / "dist" |
|
|
| UPLOADS_DIR.mkdir(parents=True, exist_ok=True) |
| app.mount("/uploads", StaticFiles(directory=UPLOADS_DIR), name="uploads") |
|
|
| if (FRONTEND_DIST / "index.html").exists(): |
| |
| app.mount("/", StaticFiles(directory=FRONTEND_DIST, html=True), name="frontend") |
|
|
|
|
| |
| FRONTEND_DIR = BASE_DIR.parent / "frontend" |
| FRONTEND_SRC = FRONTEND_DIR / "src" |
|
|
|
|
| def scan_frontend_sources() -> dict: |
| if not FRONTEND_SRC.exists(): |
| return {} |
| files = {} |
| for path in FRONTEND_SRC.rglob("*"): |
| if path.is_file() and path.suffix in {".ts", ".tsx", ".js", ".jsx", ".css", ".json", ".html"}: |
| files[path] = path.stat().st_mtime |
| for extra in [FRONTEND_DIR / "vite.config.ts", FRONTEND_DIR / "package.json", FRONTEND_DIR / "tsconfig.json"]: |
| if extra.exists(): |
| files[extra] = extra.stat().st_mtime |
| return files |
|
|
|
|
| def run_frontend_build() -> None: |
| if not FRONTEND_DIR.exists(): |
| return |
| print("[backend] Ejecutando build del frontend...") |
| result = subprocess.run(["npm", "run", "build"], cwd=str(FRONTEND_DIR), capture_output=True, text=True) |
| if result.returncode != 0: |
| print("[backend] Build falló:") |
| print(result.stdout) |
| print(result.stderr) |
| else: |
| print("[backend] Build completado correctamente.") |
|
|
|
|
| def watch_frontend_changes(interval: float = 2.0) -> None: |
| last_state = scan_frontend_sources() |
| while True: |
| time.sleep(interval) |
| current_state = scan_frontend_sources() |
| if current_state != last_state: |
| if last_state: |
| print("[backend] Cambio detectado en frontend. Reconstruyendo...") |
| run_frontend_build() |
| last_state = current_state |
|
|
|
|
| @app.on_event("startup") |
| async def startup_seed_catalog(): |
| if MONGODB_URI := os.getenv("MONGODB_URI", ""): |
| try: |
| await seed_catalog() |
| except Exception as exc: |
| logger.warning("[STARTUP] seed_catalog falló: %s", exc) |
|
|
|
|
| @app.on_event("startup") |
| async def startup_watch_frontend(): |
| thread = threading.Thread(target=watch_frontend_changes, daemon=True) |
| thread.start() |
|
|
|
|
| if __name__ == "__main__": |
| import uvicorn |
| uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info") |
|
|