Add governed module and interface controls
This commit is contained in:
@@ -5,9 +5,17 @@ from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from govoplan_core.admin.models import SystemSettings
|
||||
from govoplan_core.admin.settings import SYSTEM_SETTINGS_ID
|
||||
from govoplan_core.auth import ApiPrincipal, get_api_principal
|
||||
from govoplan_core.auth import ApiPrincipal, get_api_principal, require_any_scope
|
||||
from govoplan_core.core.maintenance import saved_maintenance_mode
|
||||
from govoplan_core.core.module_entitlements import (
|
||||
module_entitlement_payload,
|
||||
tenant_module_entitlement_state,
|
||||
)
|
||||
from govoplan_core.core.modules import FrontendModule, FrontendRoute, ModuleManifest, NavItem, PublicFrontendRoute
|
||||
from govoplan_core.core.platform_interfaces import (
|
||||
manifest_interface_catalog,
|
||||
platform_interface_catalog,
|
||||
)
|
||||
from govoplan_core.core.registry import PlatformRegistry, manifest_view_surfaces
|
||||
from govoplan_core.core.views import (
|
||||
VIEW_SURFACE_CONTRACT_VERSION,
|
||||
@@ -17,6 +25,7 @@ from govoplan_core.core.views import (
|
||||
)
|
||||
from govoplan_core.db.session import get_database
|
||||
from govoplan_core.i18n import system_i18n_payload
|
||||
from govoplan_core.tenancy.scope import Tenant
|
||||
|
||||
|
||||
def _registry(request: Request) -> PlatformRegistry:
|
||||
@@ -26,6 +35,49 @@ def _registry(request: Request) -> PlatformRegistry:
|
||||
return registry
|
||||
|
||||
|
||||
def _effective_manifest_state(
|
||||
request: Request,
|
||||
principal: ApiPrincipal,
|
||||
) -> tuple[PlatformRegistry, tuple[ModuleManifest, ...], object | None]:
|
||||
"""Resolve only manifests available in the principal's active context."""
|
||||
|
||||
registry = _registry(request)
|
||||
manifests = tuple(registry.manifests())
|
||||
entitlement = None
|
||||
principal_ref = getattr(principal, "principal", None)
|
||||
tenant_id = getattr(principal_ref, "tenant_id", None)
|
||||
if tenant_id is not None:
|
||||
try:
|
||||
with get_database().session() as session:
|
||||
tenant = session.get(Tenant, tenant_id)
|
||||
if tenant is None:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="The active tenant is unavailable.",
|
||||
)
|
||||
manifest_map = {manifest.id: manifest for manifest in manifests}
|
||||
entitlement = tenant_module_entitlement_state(
|
||||
tenant.settings or {},
|
||||
manifest_map,
|
||||
runtime_active_modules=manifest_map,
|
||||
)
|
||||
except (RuntimeError, SQLAlchemyError) as exc:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="Tenant module entitlement could not be resolved.",
|
||||
) from exc
|
||||
effective_ids = (
|
||||
set(entitlement.effective_modules)
|
||||
if entitlement is not None
|
||||
else {manifest.id for manifest in manifests}
|
||||
)
|
||||
return (
|
||||
registry,
|
||||
tuple(manifest for manifest in manifests if manifest.id in effective_ids),
|
||||
entitlement,
|
||||
)
|
||||
|
||||
|
||||
def _nav_item_payload(item: NavItem, module_id: str | None = None) -> dict[str, object]:
|
||||
return {
|
||||
"path": item.path,
|
||||
@@ -156,9 +208,14 @@ def create_platform_router(settings: object | None = None) -> APIRouter:
|
||||
@router.get("/modules")
|
||||
def modules(
|
||||
request: Request,
|
||||
_principal: ApiPrincipal = Depends(get_api_principal),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
):
|
||||
registry = _registry(request)
|
||||
registry, manifests, entitlement = _effective_manifest_state(
|
||||
request,
|
||||
principal,
|
||||
)
|
||||
principal_ref = getattr(principal, "principal", None)
|
||||
tenant_id = getattr(principal_ref, "tenant_id", None)
|
||||
return {
|
||||
"modules": [
|
||||
{
|
||||
@@ -178,13 +235,36 @@ def create_platform_router(settings: object | None = None) -> APIRouter:
|
||||
for declaration in manifest.external_providers
|
||||
],
|
||||
"runtime_ui_capabilities": _runtime_ui_capabilities(manifest.id, settings, registry),
|
||||
"interface_catalog": {
|
||||
key: value
|
||||
for key, value in manifest_interface_catalog(manifest).items()
|
||||
if key != "declarations"
|
||||
},
|
||||
"nav": [_nav_item_payload(item, manifest.id) for item in manifest.nav_items],
|
||||
"frontend": _frontend_payload(manifest),
|
||||
}
|
||||
for manifest in registry.manifests()
|
||||
]
|
||||
for manifest in manifests
|
||||
],
|
||||
"module_entitlement": (
|
||||
module_entitlement_payload(tenant_id, entitlement)
|
||||
if tenant_id is not None and entitlement is not None
|
||||
else None
|
||||
),
|
||||
}
|
||||
|
||||
@router.get("/interface-catalog")
|
||||
def interface_catalog(
|
||||
request: Request,
|
||||
principal: ApiPrincipal = Depends(
|
||||
require_any_scope("admin:module:read", "system:settings:read")
|
||||
),
|
||||
):
|
||||
_registry_item, manifests, _entitlement = _effective_manifest_state(
|
||||
request,
|
||||
principal,
|
||||
)
|
||||
return platform_interface_catalog(manifests)
|
||||
|
||||
@router.get("/public-modules")
|
||||
def public_modules(request: Request):
|
||||
registry = _registry(request)
|
||||
|
||||
Reference in New Issue
Block a user