81 lines
2.3 KiB
Python
81 lines
2.3 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.responses import FileResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
|
|
SANDBOX_DIR = Path(__file__).resolve().parent
|
|
PROJECT_ROOT = SANDBOX_DIR.parents[2]
|
|
STATIC_DIR = SANDBOX_DIR / "static"
|
|
SHARED_STATIC_DIR = PROJECT_ROOT / "apps" / "web" / "vue3" / "static"
|
|
DATA_ROOT = Path(
|
|
os.environ.get(
|
|
"AF3_SANDBOX_DATA_ROOT",
|
|
"/home/xzq/5090/00test/af3/af3_outputs/gcn4_dimer_demo",
|
|
)
|
|
).resolve()
|
|
ALLOWED_SUFFIXES = {".cif", ".mmcif"}
|
|
MOLSTAR_VERSION = (
|
|
STATIC_DIR / "vendor" / "molstar" / "VERSION"
|
|
).read_text(encoding="utf-8").strip()
|
|
|
|
app = FastAPI(title="AF3 Molstar Sandbox")
|
|
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
|
|
app.mount(
|
|
"/shared-static",
|
|
StaticFiles(directory=SHARED_STATIC_DIR),
|
|
name="shared-static",
|
|
)
|
|
|
|
|
|
def resolve_structure(filename: str) -> Path:
|
|
requested = Path(filename)
|
|
if requested.name != filename or requested.suffix.lower() not in ALLOWED_SUFFIXES:
|
|
raise HTTPException(status_code=404, detail="structure not found")
|
|
candidate = (DATA_ROOT / filename).resolve()
|
|
if candidate.parent != DATA_ROOT or not candidate.is_file():
|
|
raise HTTPException(status_code=404, detail="structure not found")
|
|
return candidate
|
|
|
|
|
|
@app.get("/")
|
|
def index() -> FileResponse:
|
|
return FileResponse(SANDBOX_DIR / "index.html")
|
|
|
|
|
|
@app.get("/api/health")
|
|
def health() -> dict[str, object]:
|
|
return {
|
|
"status": "ok",
|
|
"data_root": str(DATA_ROOT),
|
|
"data_root_available": DATA_ROOT.is_dir(),
|
|
"molstar_version": MOLSTAR_VERSION,
|
|
}
|
|
|
|
|
|
@app.get("/api/structures")
|
|
def list_structures() -> dict[str, list[dict[str, object]]]:
|
|
if not DATA_ROOT.is_dir():
|
|
return {"structures": []}
|
|
structures = [
|
|
{"name": path.name, "size": path.stat().st_size}
|
|
for path in DATA_ROOT.iterdir()
|
|
if path.is_file() and path.suffix.lower() in ALLOWED_SUFFIXES
|
|
]
|
|
structures.sort(key=lambda item: str(item["name"]))
|
|
return {"structures": structures}
|
|
|
|
|
|
@app.get("/api/structures/{filename}")
|
|
def get_structure(filename: str) -> FileResponse:
|
|
path = resolve_structure(filename)
|
|
return FileResponse(
|
|
path,
|
|
media_type="chemical/x-mmcif",
|
|
filename=path.name,
|
|
)
|