feat(core): support explicit public module routes

This commit is contained in:
2026-07-22 02:58:03 +02:00
parent a18499cbb5
commit 0946bc84a9
10 changed files with 308 additions and 16 deletions

View File

@@ -70,6 +70,15 @@ class FrontendRoute:
order: int = 100
@dataclass(frozen=True, slots=True)
class PublicFrontendRoute:
"""Explicitly allowlisted route that can render without authentication."""
path: str
component: str
order: int = 100
@dataclass(frozen=True, slots=True)
class FrontendModule:
module_id: str
@@ -80,6 +89,7 @@ class FrontendModule:
asset_manifest_integrity: str | None = None
asset_manifest_contract_version: str = "1"
routes: tuple[FrontendRoute, ...] = ()
public_routes: tuple[PublicFrontendRoute, ...] = ()
nav_items: tuple[NavItem, ...] = ()
settings_routes: tuple[FrontendRoute, ...] = ()

View File

@@ -16,6 +16,7 @@ from govoplan_core.core.modules import (
ModuleManifest,
NavItem,
PermissionDefinition,
PublicFrontendRoute,
ResourceAclProvider,
RoleTemplate,
SUPPORTED_FRONTEND_ASSET_MANIFEST_CONTRACT_VERSION,
@@ -195,6 +196,7 @@ class PlatformRegistry:
available_capabilities=available_capabilities,
)
permissions = _collect_manifest_permissions(ordered)
_validate_public_frontend_route_uniqueness(ordered)
_validate_interface_closure(ordered)
_validate_role_template_scopes(ordered, known_scopes=set(permissions))
@@ -434,7 +436,7 @@ def _validate_manifest_frontend(manifest: ModuleManifest) -> None:
)
if frontend.package_name is not None and not _NPM_PACKAGE_RE.match(frontend.package_name):
raise RegistryError(f"Module {manifest.id!r} has invalid frontend package name {frontend.package_name!r}")
for route in (*frontend.routes, *frontend.settings_routes):
for route in (*frontend.routes, *frontend.settings_routes, *frontend.public_routes):
_validate_frontend_route(manifest.id, route.path, route.component)
for item in frontend.nav_items:
_validate_nav_item(manifest.id, item)
@@ -447,6 +449,24 @@ def _validate_frontend_route(module_id: str, path: str, component: str) -> None:
raise RegistryError(f"Frontend route {path!r} for module {module_id!r} must declare a component")
def _validate_public_frontend_route_uniqueness(
manifests: tuple[ModuleManifest, ...],
) -> None:
owners: dict[str, tuple[str, PublicFrontendRoute]] = {}
for manifest in manifests:
if manifest.frontend is None:
continue
for route in manifest.frontend.public_routes:
previous = owners.get(route.path)
if previous is not None:
previous_module, _previous_route = previous
raise RegistryError(
f"Duplicate public frontend route {route.path!r} in modules "
f"{previous_module!r} and {manifest.id!r}"
)
owners[route.path] = (manifest.id, route)
def _validate_interface_closure(manifests: tuple[ModuleManifest, ...]) -> None:
providers: dict[str, list[tuple[ModuleManifest, ModuleInterfaceProvider]]] = defaultdict(list)
for manifest in manifests: