mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-09-21 20:40:47 +02:00
118 lines
4.6 KiB
Python
118 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.responses import HTMLResponse, FileResponse, PlainTextResponse, JSONResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
from fastapi.staticfiles import StaticFiles
|
|
from pathlib import Path
|
|
import os
|
|
import json as _json
|
|
|
|
# Resolve template/static dirs relative to this file
|
|
_THIS_DIR = Path(__file__).resolve().parent
|
|
_TEMPLATES_DIR = _THIS_DIR / "templates"
|
|
_STATIC_DIR = _THIS_DIR / "static"
|
|
|
|
app = FastAPI(title="MTG Deckbuilder Web UI")
|
|
|
|
# Mount static if present
|
|
if _STATIC_DIR.exists():
|
|
app.mount("/static", StaticFiles(directory=str(_STATIC_DIR)), name="static")
|
|
|
|
# Jinja templates
|
|
templates = Jinja2Templates(directory=str(_TEMPLATES_DIR))
|
|
|
|
# Global template flags (env-driven)
|
|
def _as_bool(val: str | None, default: bool = False) -> bool:
|
|
if val is None:
|
|
return default
|
|
return val.strip().lower() in {"1", "true", "yes", "on"}
|
|
|
|
SHOW_LOGS = _as_bool(os.getenv("SHOW_LOGS"), False)
|
|
SHOW_SETUP = _as_bool(os.getenv("SHOW_SETUP"), True)
|
|
|
|
# Expose as Jinja globals so all templates can reference without passing per-view
|
|
templates.env.globals.update({
|
|
"show_logs": SHOW_LOGS,
|
|
"show_setup": SHOW_SETUP,
|
|
})
|
|
|
|
|
|
@app.get("/", response_class=HTMLResponse)
|
|
async def home(request: Request) -> HTMLResponse:
|
|
return templates.TemplateResponse("home.html", {"request": request, "version": os.getenv("APP_VERSION", "dev")})
|
|
|
|
|
|
# Simple health check
|
|
@app.get("/healthz")
|
|
async def healthz():
|
|
return {"status": "ok"}
|
|
|
|
# Lightweight setup/tagging status endpoint
|
|
@app.get("/status/setup")
|
|
async def setup_status():
|
|
try:
|
|
p = Path("csv_files/.setup_status.json")
|
|
if p.exists():
|
|
with p.open("r", encoding="utf-8") as f:
|
|
data = _json.load(f)
|
|
# Attach a small log tail if available
|
|
try:
|
|
log_path = Path('logs/deck_builder.log')
|
|
if log_path.exists():
|
|
tail_lines = []
|
|
with log_path.open('r', encoding='utf-8', errors='ignore') as lf:
|
|
# Read last ~100 lines efficiently
|
|
from collections import deque
|
|
tail = deque(lf, maxlen=100)
|
|
tail_lines = list(tail)
|
|
# Reduce noise: keep lines related to setup/tagging; fallback to last 30 if too few remain
|
|
try:
|
|
lowered = [ln for ln in tail_lines]
|
|
keywords = ["setup", "tag", "color", "csv", "initial setup", "tagging", "load_dataframe"]
|
|
filtered = [ln for ln in lowered if any(kw in ln.lower() for kw in keywords)]
|
|
if len(filtered) >= 5:
|
|
use_lines = filtered[-60:]
|
|
else:
|
|
use_lines = tail_lines[-30:]
|
|
data["log_tail"] = "".join(use_lines).strip()
|
|
except Exception:
|
|
data["log_tail"] = "".join(tail_lines).strip()
|
|
except Exception:
|
|
pass
|
|
return JSONResponse(data)
|
|
return JSONResponse({"running": False, "phase": "idle"})
|
|
except Exception:
|
|
return JSONResponse({"running": False, "phase": "error"})
|
|
|
|
# Routers
|
|
from .routes import build as build_routes # noqa: E402
|
|
from .routes import configs as config_routes # noqa: E402
|
|
from .routes import decks as decks_routes # noqa: E402
|
|
from .routes import setup as setup_routes # noqa: E402
|
|
app.include_router(build_routes.router)
|
|
app.include_router(config_routes.router)
|
|
app.include_router(decks_routes.router)
|
|
app.include_router(setup_routes.router)
|
|
|
|
# Lightweight file download endpoint for exports
|
|
@app.get("/files")
|
|
async def get_file(path: str):
|
|
try:
|
|
p = Path(path)
|
|
if not p.exists() or not p.is_file():
|
|
return PlainTextResponse("File not found", status_code=404)
|
|
# Only allow returning files within the workspace directory for safety
|
|
# (best-effort: require relative to current working directory)
|
|
try:
|
|
cwd = Path.cwd().resolve()
|
|
if cwd not in p.resolve().parents and p.resolve() != cwd:
|
|
# Still allow if under deck_files or config
|
|
allowed = any(seg in ("deck_files", "config", "logs") for seg in p.parts)
|
|
if not allowed:
|
|
return PlainTextResponse("Access denied", status_code=403)
|
|
except Exception:
|
|
pass
|
|
return FileResponse(path)
|
|
except Exception:
|
|
return PlainTextResponse("Error serving file", status_code=500)
|