updated apps

This commit is contained in:
2026-07-14 23:57:03 -04:00
parent 6cc7212cef
commit 010e828e9c
797 changed files with 45153 additions and 4246 deletions
@@ -25,7 +25,7 @@ from aiohttp import hdrs, web
from homeassistant.core import HomeAssistant
from homeassistant.helpers.http import HomeAssistantView
from .const import DOMAIN, GLOBAL_UNIQUE_ID
from .const import DOMAIN, GLOBAL_UNIQUE_ID, MAX_DOCS_PER_OBJECT
from .helpers import documents as docmod
from .helpers.documents import KIND_FILE
from .helpers.permissions import user_can_write
@@ -33,6 +33,8 @@ from .helpers.permissions import user_can_write
UPLOAD_URL = "/api/maintenance_supporter/document/upload"
SERVE_URL = "/api/maintenance_supporter/document/{doc_id}"
EXCERPT_URL = "/api/maintenance_supporter/document/{doc_id}/excerpt"
# Documents archive (ZIP with blobs) — the one export carrying file contents.
DOCS_ARCHIVE_URL = "/api/maintenance_supporter/documents/archive"
# Survives the hass.data[DOMAIN] pop on last-entry unload (see registrar).
_VIEWS_REGISTERED_KEY = f"{DOMAIN}_views_registered"
@@ -112,7 +114,12 @@ class DocumentUploadView(HomeAssistantView):
title=title.strip() if isinstance(title, str) and title.strip() else None,
tags=tags,
)
except ValueError:
except ValueError as err:
if str(err) == "too_many_documents":
return self.json_message(
f"At most {MAX_DOCS_PER_OBJECT} documents per object",
HTTPStatus.CONFLICT,
)
return self.json_message("File too large", HTTPStatus.REQUEST_ENTITY_TOO_LARGE)
return self.json(doc)
@@ -236,6 +243,61 @@ class DocumentExcerptView(HomeAssistantView):
)
class DocumentsArchiveView(HomeAssistantView):
"""Export/import the documents archive — a ZIP that carries file BLOBS.
``GET`` streams a ZIP (manifest + blobs) for all objects, or a selection
via ``?entry_ids=a,b``. ``POST`` a ZIP restores blobs + re-attaches
metadata. Admin-only both ways (it moves file contents in and out).
"""
url = DOCS_ARCHIVE_URL
name = "api:maintenance_supporter:documents:archive"
def __init__(self, hass: HomeAssistant) -> None:
"""Store the hass reference (the view outlives any single request)."""
self.hass = hass
def _require_admin(self, request: web.Request) -> bool:
user = request.get("hass_user")
return bool(user and user.is_admin)
async def get(self, request: web.Request) -> web.StreamResponse:
"""Stream the documents archive ZIP (all objects or ?entry_ids=…)."""
if not self._require_admin(request):
return web.Response(status=HTTPStatus.FORBIDDEN)
from .helpers.doc_archive import build_documents_archive
raw = request.query.get("entry_ids")
entry_ids = {e for e in raw.split(",") if e} if raw else None
content = await self.hass.async_add_executor_job(build_documents_archive, self.hass, entry_ids)
return web.Response(
body=content,
content_type="application/zip",
headers={
hdrs.CONTENT_DISPOSITION: _content_disposition("maintenance-documents.zip", inline=False),
"X-Content-Type-Options": "nosniff",
},
)
async def post(self, request: web.Request) -> web.Response:
"""Restore a documents archive ZIP (blobs + metadata)."""
if not self._require_admin(request):
return self.json_message("Not authorized", HTTPStatus.FORBIDDEN)
from .helpers.doc_archive import MAX_ARCHIVE_BYTES, import_documents_archive
request._client_max_size = MAX_ARCHIVE_BYTES + 1024 * 1024
data = await request.post()
file_field = data.get("file")
if not isinstance(file_field, web.FileField):
return self.json_message("file is required", HTTPStatus.BAD_REQUEST)
content = await self.hass.async_add_executor_job(file_field.file.read)
result = await import_documents_archive(self.hass, content)
if "error" in result:
return self.json_message(result["error"], HTTPStatus.BAD_REQUEST)
return self.json(result)
def async_register_document_views(hass: HomeAssistant) -> None:
"""Register the upload + serve views on the HTTP component.
@@ -248,4 +310,5 @@ def async_register_document_views(hass: HomeAssistant) -> None:
hass.http.register_view(DocumentUploadView(hass))
hass.http.register_view(DocumentServeView(hass))
hass.http.register_view(DocumentExcerptView(hass))
hass.http.register_view(DocumentsArchiveView(hass))
hass.data[_VIEWS_REGISTERED_KEY] = True