feat(M1): Universal-MiniApp-Registry — Plugin-Layer, permission fail-closed, Lifecycle, /api/v1/miniapps
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
- app/plugins/miniapp_registry.py: Registry aus kommunikation in Plugin-Layer gehoben (Plattform-Konzept, hosts chat/dashboard/window)
- MiniAppDef: permission (fail-closed) + settings_schema + col/row_span + hosts + component + order + builtin
- MiniAppContribution + FrontendDashboardWidget (Manifest-Schema) um M1-Felder erweitert — dashboard_widgets ist Alias von miniapps (ein Contribution-Typ, #359-Philosophie)
- BasePlugin.on_activate: automatische Manifest-Registrierung; on_deactivate: unregister_plugin (nur eigene Apps)
- GET /api/v1/miniapps (server-seitig permission-gefiltert, ?host=) + GET /api/v1/miniapps/{app_id} (403/404 fail-closed)
- kommunikation/miniapp_registry.py = Kompatibilitaets-Bruecke (Bestands-Importer unveraendert)
- Doku: api-documentation.md + plugin-development-guide.md (MiniApp-Beitragsmuster)
TDD: Rot 16 failed -> Gruen 16/16; Regressionen: contracts 23/23, lifecycle+route_order 4/4; ruff clean (M1-Dateien, Vorbestand per Stash bewiesen); create_app OK
This commit is contained in:
+56
-2
@@ -53,8 +53,10 @@ class BasePlugin(ABC):
|
||||
) -> None:
|
||||
"""Called when the plugin is activated.
|
||||
|
||||
Override to register event listeners and prepare runtime state.
|
||||
Default implementation subscribes to events listed in the manifest.
|
||||
Default implementation subscribes to events listed in the manifest and
|
||||
registers manifest MiniApps (Phase M1): ``miniapps`` contributions plus
|
||||
``dashboard_widgets`` entries (alias — one contribution type, #359
|
||||
philosophy). Registered automatically here; no per-plugin code needed.
|
||||
"""
|
||||
for event_name in self.manifest.events:
|
||||
handler = self._make_event_handler(event_name)
|
||||
@@ -62,6 +64,8 @@ class BasePlugin(ABC):
|
||||
event_bus.subscribe(event_name, handler)
|
||||
self._container = service_container
|
||||
|
||||
self._register_manifest_miniapps()
|
||||
|
||||
async def on_deactivate(
|
||||
self, db: AsyncSession, service_container: ServiceContainer, event_bus: EventBus
|
||||
) -> None:
|
||||
@@ -80,6 +84,56 @@ class BasePlugin(ABC):
|
||||
|
||||
get_hook_registry().unregister_all_for_plugin(self.manifest.name)
|
||||
|
||||
# Unregister MiniApps owned by this plugin (Phase M1)
|
||||
from app.plugins.miniapp_registry import get_miniapp_registry
|
||||
|
||||
get_miniapp_registry().unregister_plugin(self.manifest.name)
|
||||
|
||||
def _register_manifest_miniapps(self) -> None:
|
||||
"""Register manifest MiniApps in the universal registry (Phase M1).
|
||||
|
||||
Sources:
|
||||
- ``manifest.miniapps`` — native MiniApp contributions
|
||||
- ``manifest.dashboard_widgets`` — alias: FrontendDashboardWidget entries
|
||||
become MiniApps with component path + spans + permission so existing
|
||||
plugin manifests keep working without changes.
|
||||
"""
|
||||
from app.plugins.miniapp_registry import get_miniapp_registry
|
||||
|
||||
registry = get_miniapp_registry()
|
||||
name = self.manifest.name
|
||||
|
||||
for m in getattr(self.manifest, "miniapps", None) or []:
|
||||
registry.register(
|
||||
app_id=m.app_id,
|
||||
name=m.name,
|
||||
icon=m.icon,
|
||||
description=m.description,
|
||||
plugin_name=name,
|
||||
render_schema=m.render_schema,
|
||||
permission=getattr(m, "permission", ""),
|
||||
settings_schema=getattr(m, "settings_schema", {}),
|
||||
col_span=getattr(m, "col_span", 1),
|
||||
row_span=getattr(m, "row_span", 1),
|
||||
hosts=getattr(m, "hosts", None),
|
||||
order=getattr(m, "order", 100),
|
||||
)
|
||||
|
||||
for w in getattr(self.manifest, "dashboard_widgets", None) or []:
|
||||
registry.register(
|
||||
app_id=w.id,
|
||||
name=w.label or w.id,
|
||||
icon=w.icon,
|
||||
description="",
|
||||
plugin_name=name,
|
||||
permission=w.permission,
|
||||
col_span=w.col_span,
|
||||
row_span=w.row_span,
|
||||
hosts=["chat", "dashboard", "window"],
|
||||
component=w.component,
|
||||
order=w.order,
|
||||
)
|
||||
|
||||
async def on_uninstall(self, db: AsyncSession, service_container: ServiceContainer) -> None:
|
||||
"""Called when the plugin is uninstalled (before data tables are dropped).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user