60 lines
2.0 KiB
Python
60 lines
2.0 KiB
Python
"""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
|