"""MiniApps API — universal registry listing (Phase M1). Platform-level endpoint (the registry is a platform concept, not owned by a single plugin): lists MiniApps with server-side permission filtering (fail-closed) and optional host filter. """ from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from app.deps import get_current_user, require_workspace_scope from app.plugins.miniapp_registry import get_miniapp_registry, user_permits router = APIRouter(prefix="/api/v1/miniapps", tags=["miniapps"]) # Backward-compatible alias (the canonical helper lives in the registry # module since Phase M2 — shared with the personal dashboard seed). _user_permits = user_permits @router.get("") async def list_miniapps( host: str | None = None, current_user: dict = Depends(get_current_user), workspace_scope: dict | None = Depends(require_workspace_scope("dashboard")), ): """List MiniApps visible to the current user (permission-filtered). ``?host=chat|dashboard|window`` filters by the hosts declared on the MiniApp definition. Phase N4: a workspace scope with widget_app_ids limits the OFFERED widget types — only for host=dashboard (admin boundary). Personal layouts stay user-owned (Phase M split). """ registry = get_miniapp_registry() items = [a for a in registry.list_apps(host=host) if _user_permits(current_user, a)] if workspace_scope and host == "dashboard": widget_ids = workspace_scope.get("widget_app_ids") if isinstance(widget_ids, list) and widget_ids: allowed = set(widget_ids) items = [a for a in items if a.get("app_id") in allowed] items.sort(key=lambda a: a.get("order", 100)) return {"items": items, "total": len(items)} @router.get("/{app_id}") async def get_miniapp( app_id: str, current_user: dict = Depends(get_current_user), ): """Get a single MiniApp definition (403 without permission, 404 unknown).""" app = get_miniapp_registry().get_app(app_id) if app is None: raise HTTPException(404, detail={"detail": "MiniApp not found", "code": "not_found"}) data = app.model_dump() if not _user_permits(current_user, data): raise HTTPException( 403, detail={ "detail": f"Permission '{data['permission']}' required", "code": "forbidden", }, ) return data