"""Admin routes: index, favicon, browse-dirs, robots.""" from __future__ import annotations from pathlib import Path from fastapi import APIRouter, HTTPException from fastapi.responses import FileResponse, PlainTextResponse, Response from core.constants import STATIC_DIR, __version__ router = APIRouter() _BROWSE_BLOCKED: frozenset[str] = frozenset({ "/proc", "/sys", "/dev", "/run", "/boot", }) @router.get("/") async def index(): return FileResponse( STATIC_DIR / "index.html", headers={"Cache-Control": "no-store, max-age=0"}, ) @router.get("/favicon.ico") async def favicon(): return Response(status_code=204) @router.get("/api/browse-dirs") async def browse_dirs(path: str = "/"): p = Path(path).resolve() p_str = str(p) if any(p_str == b or p_str.startswith(b + "/") for b in _BROWSE_BLOCKED): raise HTTPException(403, "Access to this path is not permitted") if not p.is_dir(): raise HTTPException(404, "Not a directory") try: entries = sorted( [d.name for d in p.iterdir() if d.is_dir() and not d.name.startswith(".")], key=str.lower, ) except PermissionError: raise HTTPException(403, "Permission denied") parent = str(p.parent) if p.parent != p else None return {"path": str(p), "parent": parent, "dirs": entries} @router.get("/api/version") async def get_version(): return {"version": __version__} @router.get("/api/changelog", response_class=PlainTextResponse) async def get_changelog(): cl = Path(__file__).parent.parent / "CHANGELOG.md" if not cl.is_file(): raise HTTPException(404, "CHANGELOG.md not found") return cl.read_text(encoding="utf-8") @router.get("/robots.txt", response_class=PlainTextResponse) async def robots_txt(): return "User-agent: *\nDisallow: /"