"""Serves the built Vue SPA from frontend/dist/ with history-mode fallback, and the on-disk image library + thumbnails from /images. """ from pathlib import Path from quart import Blueprint, abort, send_from_directory FRONTEND_DIST = Path(__file__).resolve().parent.parent.parent / "frontend" / "dist" IMAGES_ROOT = Path("/images") frontend_bp = Blueprint("frontend", __name__) @frontend_bp.route("/images/") async def serve_image(subpath: str): """Serve a file from the /images volume (originals + thumbnails). Without this route the SPA catch-all below would swallow image requests and return index.html, leaving the browser to render the aspect-ratio-shaped grey placeholder. """ target = (IMAGES_ROOT / subpath).resolve() # Defend against path-traversal: refuse anything that escapes /images. try: target.relative_to(IMAGES_ROOT) except ValueError: abort(404) if not target.is_file(): abort(404) return await send_from_directory(IMAGES_ROOT, subpath) @frontend_bp.route("/") @frontend_bp.route("/") async def serve_spa(subpath: str = ""): if subpath and (FRONTEND_DIST / subpath).is_file(): return await send_from_directory(FRONTEND_DIST, subpath) return await send_from_directory(FRONTEND_DIST, "index.html")