feat: add /api/lab/precons and /api/lab/precons/deck endpoints

Fixes 404 errors from lan-client ai-bridge.js which calls these routes
to populate the deck selector. Routes were missing from lab.py — only
precon_router (routes/precon.py) existed but served different paths.

Also added PRECON_DECKS_DIR env var alias in lab_api.py _parse_args()
to match the .env variable name users commonly set.
This commit is contained in:
KoalaTrapLord committed 2026-04-03 11:08:12 -04:00
1 parent 370d4cd92f
commit f29a5a31b5
1 file changed
+69
+69
View File
@@ -14,6 +14,8 @@ Batch simulation & lab management endpoints:
GET /api/lab/trends/{deck_name}
GET /api/lab/log
GET /api/lab/debug-log
GET /api/lab/precons
GET /api/lab/precons/deck
"""
from __future__ import annotations
@@ -468,6 +470,73 @@ async def get_debug_log():
return {"path": None, "content": "No debug log found. Run a simulation first."}
# ════════════════════════════════════════════════════════════
# Precon deck endpoints (used by lan-client ai-bridge.js)
# ════════════════════════════════════════════════════════════
@router.get("/api/lab/precons")
async def list_precons():
"""Return precon deck metadata for the LAN client deck selector."""
precon_dir = Path(CFG.precon_dir) if CFG.precon_dir else None
if not precon_dir or not precon_dir.is_dir():
log.warning(f"Precon dir not found or not configured: {CFG.precon_dir!r}")
return {"precons": []}
precons = []
for f in sorted(precon_dir.glob("*.dck")):
deck_info = {
"fileName": f.name,
"deckName": f.stem,
"commander": "",
"colors": [],
"set": "",
}
# Extract commander name from the [Commander] section
try:
with open(f, "r", encoding="utf-8") as fh:
in_commander = False
for line in fh:
line = line.strip()
if line == "[Commander]":
in_commander = True
continue
if line.startswith("[") and line.endswith("]"):
in_commander = False
continue
if in_commander:
m = re.match(r"^\d+\s+(.+?)(?:\|.+)?$", line)
if m:
deck_info["commander"] = m.group(1).strip()
break
except Exception as exc:
log.debug(f"Could not parse commander from {f.name}: {exc}")
precons.append(deck_info)
return {"precons": precons}
@router.get("/api/lab/precons/deck")
async def get_precon_deck(fileName: str):
"""Return the full card list for a specific precon .dck file."""
precon_dir = Path(CFG.precon_dir) if CFG.precon_dir else None
if not precon_dir or not precon_dir.is_dir():
raise HTTPException(404, f"Precon directory not configured or missing: {CFG.precon_dir!r}")
# Sanitize — prevent path traversal
safe_name = Path(fileName).name
if not safe_name.endswith(".dck"):
raise HTTPException(400, "fileName must end with .dck")
deck_path = precon_dir / safe_name
if not deck_path.exists():
raise HTTPException(404, f"Precon deck not found: {safe_name}")
try:
deck_data = parse_dck_file(str(deck_path))
return deck_data
except Exception as e:
raise HTTPException(500, f"Failed to parse deck: {e}")
@router.post('/api/lab/start-deepseek')
async def start_batch_deepseek(request: FastAPIRequest, background_tasks: BackgroundTasks):