"""Abstract light-app registry persistence API. Global / shared store: no ``user_id`` scoping. Reads are open to any caller; the router gates writes behind admin. ``created_by`` / ``updated_by`` are audit-only. """ from __future__ import annotations from abc import ABC, abstractmethod from typing import Any class DuplicateLightAppNameError(ValueError): """Raised when creating/updating an app would collide on the unique name.""" class LightAppStore(ABC): async def list_sidebar_apps(self) -> list[dict[str, Any]]: """Return only the columns needed to render iframe app menu entries.""" rows = await self.list_apps(app_type="iframe", enabled_only=True) return [ { "app_id": row.get("app_id"), "name": row.get("name"), "mount_location": row.get("mount_location"), "sort_order": row.get("sort_order", 0), } for row in rows ] @abstractmethod async def list_apps( self, *, app_type: str | None = None, mount_location: str | None = None, enabled_only: bool = False, ) -> list[dict[str, Any]]: """Return registered apps (newest first), optionally filtered.""" raise NotImplementedError @abstractmethod async def get_app(self, app_id: str) -> dict[str, Any] | None: raise NotImplementedError @abstractmethod async def create_app(self, data: dict[str, Any]) -> dict[str, Any]: """Create an app. Raises ``DuplicateLightAppNameError`` on a name clash.""" raise NotImplementedError @abstractmethod async def update_app(self, app_id: str, data: dict[str, Any]) -> dict[str, Any] | None: """Partial update. Returns None if the app does not exist. Raises ``DuplicateLightAppNameError`` when renaming onto an existing name.""" raise NotImplementedError @abstractmethod async def delete_app(self, app_id: str) -> bool: raise NotImplementedError