eduardo4547's picture
Upload 150 files
cb5d9d0 verified
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
# Routers
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)
# Static files
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():
# Montado en "/" como catch-all para SPA — los routers de API tienen prioridad
app.mount("/", StaticFiles(directory=FRONTEND_DIST, html=True), name="frontend")
# Frontend watcher (development helper)
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")