111 lines
4.6 KiB
Python
111 lines
4.6 KiB
Python
import os
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.responses import HTMLResponse, FileResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.middleware.gzip import GZipMiddleware
|
|
from app.config import settings
|
|
from app.api.v1.audio import router as audio_router
|
|
from app.api.v1.tasks import router as tasks_router
|
|
from app.api.v1.multitrack import router as multitrack_router
|
|
from app.api.v1.auth import router as auth_router
|
|
from app.api.v1.admin import router as admin_router
|
|
from app.api.v1.projects import router as projects_router
|
|
from app.api.v1.user_config import router as user_config_router
|
|
from app.api.v1.ai_proxy import router as ai_proxy_router
|
|
from app.api.v1.ai_presets import router as ai_presets_router
|
|
from app.api.v1.plugins import router as plugins_router
|
|
from app.api.v1.media import router as media_router
|
|
from app.core.auth import seed_admin
|
|
from app.core.soundfont_scanner import SoundFontAutoScanner
|
|
|
|
# Ensure storage directories exist
|
|
os.makedirs(settings.UPLOADS_DIR, exist_ok=True)
|
|
os.makedirs(settings.PROCESSED_DIR, exist_ok=True)
|
|
|
|
_SF_SCANNER_STOP = None
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
# Startup
|
|
seed_admin()
|
|
scanner = SoundFontAutoScanner()
|
|
global _SF_SCANNER_STOP
|
|
_SF_SCANNER_STOP = scanner.start_background(interval=30)
|
|
yield
|
|
# Shutdown
|
|
if _SF_SCANNER_STOP is not None:
|
|
_SF_SCANNER_STOP.set()
|
|
|
|
|
|
app = FastAPI(title="SonicForge API Engine", lifespan=lifespan)
|
|
|
|
app.add_middleware(GZipMiddleware, minimum_size=500)
|
|
|
|
# Auth is token/cookie based (no cookies required for CORS), so credentials are
|
|
# disabled — "*" + allow_credentials=True is rejected by browsers anyway.
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=False,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Mount storage directory (must come before general /static mount)
|
|
app.mount("/static/audio", StaticFiles(directory=settings.STORAGE_DIR), name="audio")
|
|
# Mount app static files (js, css)
|
|
STATIC_DIR = os.path.join(os.path.dirname(__file__), "static")
|
|
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
|
|
|
|
# Include routers
|
|
app.include_router(audio_router, prefix="/api/v1/audio", tags=["audio"])
|
|
app.include_router(tasks_router, prefix="/api/v1/audio", tags=["tasks"])
|
|
app.include_router(multitrack_router, prefix="/api/v1/multitrack", tags=["multitrack"])
|
|
app.include_router(auth_router, prefix="/api/v1/auth", tags=["auth"])
|
|
app.include_router(admin_router, prefix="/api/v1/admin", tags=["admin"])
|
|
app.include_router(projects_router, prefix="/api/v1/projects", tags=["projects"])
|
|
app.include_router(user_config_router, prefix="/api/v1/user", tags=["user_config"])
|
|
app.include_router(ai_proxy_router, prefix="/api/v1/ai", tags=["ai"])
|
|
app.include_router(ai_presets_router, prefix="/api/v1/ai", tags=["ai"])
|
|
app.include_router(plugins_router, prefix="/api/v1/plugins", tags=["plugins"])
|
|
app.include_router(media_router, prefix="/api/v1/media", tags=["media"])
|
|
|
|
|
|
@app.get("/health")
|
|
async def health():
|
|
return {"status": "ok"}
|
|
|
|
@app.get("/", response_class=HTMLResponse)
|
|
async def get_index():
|
|
index_path = os.path.join(settings.TEMPLATES_DIR, "index.html")
|
|
if not os.path.exists(index_path):
|
|
return HTMLResponse(content=f"<h1>SonicForge Studio: index.html not found at {index_path}</h1>", status_code=404)
|
|
with open(index_path, "r", encoding="utf-8") as file:
|
|
resp = HTMLResponse(content=file.read(), status_code=200)
|
|
# no-cache: index.html PHẢI luôn mới (các bundle JS dùng ?v= để bust) —
|
|
# nếu browser cache HTML cũ → stamp cũ → tải bundle cũ (bug "không load
|
|
# được bundle mới" ở incognito — cache heuristic không có Cache-Control).
|
|
resp.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
|
return resp
|
|
|
|
|
|
@app.get("/favicon.svg")
|
|
async def get_favicon():
|
|
favicon_path = os.path.join(settings.TEMPLATES_DIR, "favicon.svg")
|
|
if os.path.exists(favicon_path):
|
|
return FileResponse(favicon_path, media_type="image/svg+xml")
|
|
return HTMLResponse(content="", status_code=404)
|
|
|
|
|
|
@app.get("/ai-prompt-generator", response_class=HTMLResponse)
|
|
async def get_ai_prompt_generator():
|
|
md_path = os.path.join(settings.BASE_DIR, "md", "49_AI_PROMPT_GENERATOR.md")
|
|
if not os.path.exists(md_path):
|
|
return HTMLResponse(content="<h1>File not found</h1>", status_code=404)
|
|
with open(md_path, "r", encoding="utf-8") as file:
|
|
return HTMLResponse(content=file.read(), status_code=200)
|