feat(core): support explicit public module routes
This commit is contained in:
@@ -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, ...] = ()
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user