From 8aabf71f798ebb3332528e6a015b8eedc0a95809 Mon Sep 17 00:00:00 2001 From: "Junv (via Hermes)" Date: Mon, 13 Jul 2026 11:19:46 +1000 Subject: [PATCH] fix: add fallback for root-level static files in site-content handler When an HTML page references static files (images, CSS, JS) via relative URLs, the browser resolves them as root-level paths (e.g. /image.jpg from page /site-name). Without a trailing slash on the page URL, these resolve to /image.jpg instead of /site-name/image.jpg. The API's /site-content/{path} handler interprets the first path segment as the site name, so /dule_yuan_15k.jpg was treated as a site name lookup rather than a file within the dule-yuan site. Added _find_static_file() fallback: when no site matches the first path segment, search all site directories for a matching filename. Only triggered for common static file extensions to avoid ambiguous lookups. --- home-apps/demo-service/service.py | 35 ++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/home-apps/demo-service/service.py b/home-apps/demo-service/service.py index 81d5e8e..df97fa6 100644 --- a/home-apps/demo-service/service.py +++ b/home-apps/demo-service/service.py @@ -127,6 +127,29 @@ def error_resp(status_code: int = 404) -> HTMLResponse: return resp +# ── Static file fallback ───────────────────────────── +def _find_static_file(path: str): + """Try to locate a static file (image, css, js, etc.) referenced + from a site's HTML as a root-relative URL (e.g. /image.jpg from page /site-name). + Searches all site directories for a matching filename. + Returns (site_name, sub_path) or None.""" + filename = path.rsplit("/", 1)[-1] + if not filename or "." not in filename: + return None + ext = filename.rsplit(".", 1)[-1].lower() + if ext not in ("jpg", "jpeg", "png", "gif", "webp", "css", "js", "svg", "ico", "woff2", "pdf"): + return None + meta = _db.load_all() + for name in sorted(meta.get("sites", {}).keys()): + site_dir = DEMO_ROOT / name + if not site_dir.is_dir(): + continue + candidate = site_dir / filename + if candidate.exists() and candidate.is_file(): + return (name, filename) + return None + + # ─── File Helpers (NFS safe — no rename) ───────────── def sync_visibility(name: str): """Sync the on-disk index.html based on visibility mode.""" @@ -186,7 +209,17 @@ async def site_content(path: str = "", request: Request = None): info = _db.get(site_name) if not info: - return error_resp(404) + # ── Fallback: if the path doesn't match a site name, it might be + # a static file (image, css, js) referenced from a site's HTML. + # Try to find it in any site directory. + found = _find_static_file(path) + if found: + site_name, sub = found + info = _db.get(site_name) + if not info: + return error_resp(404) + else: + return error_resp(404) visibility = info["visibility"] user = check_auth(request)