diff --git a/custom_components/bahmcloud_store/views.py b/custom_components/bahmcloud_store/views.py new file mode 100644 index 0000000..eb1d7e8 --- /dev/null +++ b/custom_components/bahmcloud_store/views.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +from pathlib import Path + +from aiohttp import web +from homeassistant.components.http import HomeAssistantView + +from .core import BCSCore + + +class StaticAssetsView(HomeAssistantView): + """ + Static panel assets MUST be public (no auth), because Home Assistant loads + custom panel JS modules without auth headers. + """ + requires_auth = False + name = "bahmcloud_store_static" + url = "/api/bahmcloud_store_static/{path:.*}" + + async def get(self, request, path): + base = Path(__file__).resolve().parent / "panel" + if not path: + path = "panel.js" + + f = (base / path).resolve() + + # Prevent path traversal + if not str(f).startswith(str(base)) or not f.exists() or not f.is_file(): + return web.Response(status=404, text="Not found") + + suffix = f.suffix.lower() + if suffix == ".js": + return web.Response(body=f.read_bytes(), content_type="application/javascript") + if suffix == ".css": + return web.Response(body=f.read_bytes(), content_type="text/css") + if suffix in (".html", ".htm"): + return web.Response(body=f.read_bytes(), content_type="text/html") + + return web.Response(body=f.read_bytes(), content_type="application/octet-stream") + + +class BCSApiView(HomeAssistantView): + """ + BCS API (auth required) + - GET /api/bcs + - POST /api/bcs (op=add_custom_repo) + """ + requires_auth = True + name = "bcs_api" + url = "/api/bcs" + + def __init__(self, core: BCSCore) -> None: + self.core = core + + async def get(self, request): + return self.json( + { + "repos": self.core.list_repos_public(), + "store_url": self.core.config.store_url, + "refresh_seconds": self.core.refresh_seconds, + "version": "0.2.0", + } + ) + + async def post(self, request): + data = await request.json() + op = str(data.get("op") or "").strip() + + if op != "add_custom_repo": + return self.json({"error": "unknown op"}, status_code=400) + + url = str(data.get("url") or "").strip() + name = data.get("name") + name = str(name).strip() if isinstance(name, str) and name.strip() else None + + if not url: + return self.json({"error": "url missing"}, status_code=400) + + repo = await self.core.add_custom_repo(url=url, name=name) + return self.json({"ok": True, "repo": {"id": repo.id, "url": repo.url, "name": repo.name}})