2026-06-20 22:16:07 +08:00
|
|
|
|
"""AccessRule 子域 Router。
|
|
|
|
|
|
|
|
|
|
|
|
外部系统限界上下文的工具访问控制规则管理 API,覆盖访问规则 CRUD。
|
|
|
|
|
|
所有端点通过 ``create_use_cases_from_db`` 装配 use_cases,经
|
|
|
|
|
|
``access_rule_service`` 端口调用用例。
|
|
|
|
|
|
|
|
|
|
|
|
Request Schema 与 Input DTO 不共享类,Router 内显式构造 DTO,操作人字段
|
|
|
|
|
|
(``created_by`` / ``updated_by``)由 ``current_user.uid`` 填充。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
from typing import Any, Literal
|
|
|
|
|
|
|
2026-07-04 00:16:45 +08:00
|
|
|
|
from fastapi import APIRouter, Depends, Path, Query
|
2026-06-20 22:16:07 +08:00
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
|
|
|
|
|
|
from yuxi.external_systems.use_cases.dto.access_rule import (
|
|
|
|
|
|
BatchCloneAccessRulesInput,
|
|
|
|
|
|
BatchToggleAccessRulesInput,
|
|
|
|
|
|
CreateAccessRuleInput,
|
|
|
|
|
|
DeleteAccessRuleInput,
|
|
|
|
|
|
EvaluateAccessInput,
|
|
|
|
|
|
GetAccessRuleInput,
|
|
|
|
|
|
GetConditionsInput,
|
|
|
|
|
|
ListAccessRulesInput,
|
|
|
|
|
|
ReorderAccessRulesInput,
|
|
|
|
|
|
SimulateAccessInput,
|
|
|
|
|
|
UpdateAccessRuleInput,
|
|
|
|
|
|
UpdateConditionsInput,
|
|
|
|
|
|
)
|
|
|
|
|
|
from yuxi.storage.postgres.models_business import User
|
|
|
|
|
|
|
|
|
|
|
|
from server.utils.auth_middleware import get_admin_user, get_db, get_required_user
|
|
|
|
|
|
|
|
|
|
|
|
access_rule_router = APIRouter(prefix="/access-rules", tags=["external-systems-access-rule"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------- Request Schemas ----------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CreateAccessRuleRequest(BaseModel):
|
|
|
|
|
|
"""创建访问规则请求体。字段对齐 ``CreateAccessRuleInput``(不含 created_by)。
|
|
|
|
|
|
|
|
|
|
|
|
``created_by`` 由 Router 从 ``current_user.uid`` 注入。字段长度约束对齐
|
|
|
|
|
|
``ExternalToolAccessRule`` ORM 列定义,在边界层拦截非法输入。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
model_config = ConfigDict(frozen=True)
|
|
|
|
|
|
|
|
|
|
|
|
principal_type: str = Field(..., min_length=1, max_length=32)
|
|
|
|
|
|
principal_id: str = Field(..., min_length=1, max_length=64)
|
|
|
|
|
|
tool_slug: str | None = Field(default=None, max_length=128)
|
|
|
|
|
|
system_id: int | None = None
|
|
|
|
|
|
principal_name: str | None = Field(default=None, max_length=128)
|
|
|
|
|
|
env_key: str | None = Field(default=None, max_length=32)
|
|
|
|
|
|
effect: Literal["allow", "deny"] = "allow"
|
|
|
|
|
|
priority: int = 0
|
|
|
|
|
|
conditions: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
expires_at: datetime | None = None
|
|
|
|
|
|
enabled: bool = True
|
|
|
|
|
|
description: str | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UpdateAccessRuleRequest(BaseModel):
|
|
|
|
|
|
"""更新访问规则请求体。字段对齐 ``UpdateAccessRuleInput``(不含 id 与 updated_by)。
|
|
|
|
|
|
|
|
|
|
|
|
``id`` 由路径参数提供,``updated_by`` 由 Router 从 ``current_user.uid`` 注入。
|
|
|
|
|
|
所有字段均为可选,仅透传客户端显式设置的字段。字符串字段长度约束对齐
|
|
|
|
|
|
``ExternalToolAccessRule`` ORM 列定义。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
model_config = ConfigDict(frozen=True)
|
|
|
|
|
|
|
|
|
|
|
|
principal_name: str | None = Field(default=None, max_length=128)
|
|
|
|
|
|
effect: Literal["allow", "deny"] | None = None
|
|
|
|
|
|
priority: int | None = None
|
|
|
|
|
|
conditions: dict[str, Any] | None = None
|
|
|
|
|
|
expires_at: datetime | None = None
|
|
|
|
|
|
enabled: bool | None = None
|
|
|
|
|
|
description: str | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------- 扩展 Request Schemas ----------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class EvaluateAccessRequest(BaseModel):
|
|
|
|
|
|
"""评估访问规则请求体。字段对齐 EvaluateAccessInput(不含操作人字段)。"""
|
|
|
|
|
|
|
|
|
|
|
|
model_config = ConfigDict(frozen=True)
|
|
|
|
|
|
|
|
|
|
|
|
principal_type: str = Field(..., min_length=1, max_length=32)
|
|
|
|
|
|
principal_id: str = Field(..., min_length=1, max_length=64)
|
|
|
|
|
|
tool_slug: str | None = Field(default=None, max_length=128)
|
|
|
|
|
|
system_id: int | None = None
|
|
|
|
|
|
env_key: str | None = Field(default=None, max_length=32)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SimulateAccessRequest(BaseModel):
|
|
|
|
|
|
"""模拟规则执行请求体。字段对齐 SimulateAccessInput(不含操作人字段)。"""
|
|
|
|
|
|
|
|
|
|
|
|
model_config = ConfigDict(frozen=True)
|
|
|
|
|
|
|
|
|
|
|
|
principal_type: str = Field(..., min_length=1, max_length=32)
|
|
|
|
|
|
principal_id: str = Field(..., min_length=1, max_length=64)
|
|
|
|
|
|
tool_slug: str | None = Field(default=None, max_length=128)
|
|
|
|
|
|
system_id: int | None = None
|
|
|
|
|
|
env_key: str | None = Field(default=None, max_length=32)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BatchToggleAccessRulesRequest(BaseModel):
|
|
|
|
|
|
"""批量启停访问规则请求体。"""
|
|
|
|
|
|
|
|
|
|
|
|
model_config = ConfigDict(frozen=True)
|
|
|
|
|
|
|
2026-07-04 00:16:45 +08:00
|
|
|
|
rule_ids: list[int] = Field(..., min_length=1, max_length=100)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
enabled: bool
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ReorderItem(BaseModel):
|
|
|
|
|
|
"""单条规则优先级调整项。"""
|
|
|
|
|
|
|
|
|
|
|
|
model_config = ConfigDict(frozen=True)
|
|
|
|
|
|
|
|
|
|
|
|
rule_id: int
|
|
|
|
|
|
priority: int
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ReorderAccessRulesRequest(BaseModel):
|
|
|
|
|
|
"""批量调整规则优先级请求体。"""
|
|
|
|
|
|
|
|
|
|
|
|
model_config = ConfigDict(frozen=True)
|
|
|
|
|
|
|
2026-07-04 00:16:45 +08:00
|
|
|
|
orders: list[ReorderItem] = Field(..., min_length=1, max_length=100)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UpdateConditionsRequest(BaseModel):
|
|
|
|
|
|
"""更新规则条件请求体。"""
|
|
|
|
|
|
|
|
|
|
|
|
model_config = ConfigDict(frozen=True)
|
|
|
|
|
|
|
|
|
|
|
|
conditions: dict[str, Any]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BatchCloneAccessRulesRequest(BaseModel):
|
|
|
|
|
|
"""批量克隆访问规则请求体。"""
|
|
|
|
|
|
|
|
|
|
|
|
model_config = ConfigDict(frozen=True)
|
|
|
|
|
|
|
2026-07-04 00:16:45 +08:00
|
|
|
|
source_rule_ids: list[int] = Field(..., min_length=1, max_length=100)
|
2026-06-20 22:16:07 +08:00
|
|
|
|
override_config: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------- Endpoints ----------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@access_rule_router.get("", response_model=dict)
|
|
|
|
|
|
async def list_access_rules(
|
|
|
|
|
|
limit: int = Query(100, ge=1, le=500),
|
|
|
|
|
|
offset: int = Query(0, ge=0),
|
2026-07-04 00:16:45 +08:00
|
|
|
|
system_id: int | None = Query(None, ge=1),
|
|
|
|
|
|
tool_slug: str | None = Query(None, max_length=128),
|
|
|
|
|
|
principal_type: str | None = Query(None, max_length=32),
|
|
|
|
|
|
principal_id: str | None = Query(None, max_length=64),
|
|
|
|
|
|
env_key: str | None = Query(None, max_length=32),
|
|
|
|
|
|
effect: Literal["allow", "deny"] | None = Query(None, description="规则效果:allow/deny"),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
enabled: bool | None = Query(None),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""分页列出工具访问控制规则。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = ListAccessRulesInput(
|
|
|
|
|
|
page=offset // limit + 1,
|
|
|
|
|
|
page_size=limit,
|
|
|
|
|
|
system_id=system_id,
|
|
|
|
|
|
tool_slug=tool_slug,
|
|
|
|
|
|
principal_type=principal_type,
|
|
|
|
|
|
principal_id=principal_id,
|
|
|
|
|
|
env_key=env_key,
|
|
|
|
|
|
effect=effect,
|
|
|
|
|
|
enabled=enabled,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.access_rule_service.list_access_rules(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-02 03:29:06 +08:00
|
|
|
|
@access_rule_router.post("", response_model=dict)
|
|
|
|
|
|
async def create_access_rule(
|
|
|
|
|
|
payload: CreateAccessRuleRequest,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_admin_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""创建工具访问控制规则。``created_by`` 由当前管理员填充。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = CreateAccessRuleInput(
|
|
|
|
|
|
**payload.model_dump(),
|
|
|
|
|
|
created_by=current_user.uid,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.access_rule_service.create_access_rule(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-20 22:16:07 +08:00
|
|
|
|
# ---------------- 静态路径扩展端点(必须在 /{rule_id} 之前声明)----------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@access_rule_router.post("/evaluate", response_model=dict)
|
|
|
|
|
|
async def evaluate_access(
|
|
|
|
|
|
payload: EvaluateAccessRequest,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""评估访问规则(只读查询,不抛异常)。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = EvaluateAccessInput(
|
|
|
|
|
|
principal_type=payload.principal_type,
|
|
|
|
|
|
principal_id=payload.principal_id,
|
|
|
|
|
|
tool_slug=payload.tool_slug,
|
|
|
|
|
|
system_id=payload.system_id,
|
|
|
|
|
|
env_key=payload.env_key,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.access_rule_service.evaluate_access(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@access_rule_router.post("/simulate", response_model=dict)
|
|
|
|
|
|
async def simulate_access(
|
|
|
|
|
|
payload: SimulateAccessRequest,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_admin_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""模拟规则执行(只读查询,返回每条规则的评估详情)。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = SimulateAccessInput(
|
|
|
|
|
|
principal_type=payload.principal_type,
|
|
|
|
|
|
principal_id=payload.principal_id,
|
|
|
|
|
|
tool_slug=payload.tool_slug,
|
|
|
|
|
|
system_id=payload.system_id,
|
|
|
|
|
|
env_key=payload.env_key,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.access_rule_service.simulate_access(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@access_rule_router.post("/batch-toggle", response_model=dict)
|
|
|
|
|
|
async def batch_toggle_access_rules(
|
|
|
|
|
|
payload: BatchToggleAccessRulesRequest,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_admin_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""批量启停访问规则。``updated_by`` 由当前管理员填充。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = BatchToggleAccessRulesInput(
|
|
|
|
|
|
rule_ids=payload.rule_ids,
|
|
|
|
|
|
enabled=payload.enabled,
|
|
|
|
|
|
updated_by=current_user.uid,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.access_rule_service.batch_toggle_access_rules(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@access_rule_router.put("/reorder", response_model=dict)
|
|
|
|
|
|
async def reorder_access_rules(
|
|
|
|
|
|
payload: ReorderAccessRulesRequest,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_admin_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""批量调整规则优先级。``updated_by`` 由当前管理员填充。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = ReorderAccessRulesInput(
|
|
|
|
|
|
orders=[item.model_dump() for item in payload.orders],
|
|
|
|
|
|
updated_by=current_user.uid,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.access_rule_service.reorder_access_rules(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@access_rule_router.post("/batch-clone", response_model=dict)
|
|
|
|
|
|
async def batch_clone_access_rules(
|
|
|
|
|
|
payload: BatchCloneAccessRulesRequest,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_admin_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""批量克隆访问规则。``created_by`` 由当前管理员填充。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = BatchCloneAccessRulesInput(
|
|
|
|
|
|
source_rule_ids=payload.source_rule_ids,
|
|
|
|
|
|
override_config=payload.override_config,
|
|
|
|
|
|
created_by=current_user.uid,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.access_rule_service.batch_clone_access_rules(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@access_rule_router.get("/{rule_id}", response_model=dict)
|
|
|
|
|
|
async def get_access_rule(
|
2026-07-04 00:16:45 +08:00
|
|
|
|
rule_id: int = Path(ge=1),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""获取访问规则详情。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = GetAccessRuleInput(id=rule_id)
|
|
|
|
|
|
output = await use_cases.access_rule_service.get_access_rule(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@access_rule_router.put("/{rule_id}", response_model=dict)
|
|
|
|
|
|
async def update_access_rule(
|
|
|
|
|
|
payload: UpdateAccessRuleRequest,
|
2026-07-04 00:16:45 +08:00
|
|
|
|
rule_id: int = Path(ge=1),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_admin_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""更新工具访问控制规则。``updated_by`` 由当前管理员填充。仅透传显式设置的字段。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = UpdateAccessRuleInput(
|
|
|
|
|
|
id=rule_id,
|
|
|
|
|
|
updated_by=current_user.uid,
|
|
|
|
|
|
**payload.model_dump(exclude_unset=True),
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.access_rule_service.update_access_rule(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@access_rule_router.delete("/{rule_id}", response_model=dict)
|
|
|
|
|
|
async def delete_access_rule(
|
2026-07-04 00:16:45 +08:00
|
|
|
|
rule_id: int = Path(ge=1),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_admin_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""删除工具访问控制规则。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = DeleteAccessRuleInput(id=rule_id)
|
|
|
|
|
|
output = await use_cases.access_rule_service.delete_access_rule(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------- 动态路径扩展端点(/{rule_id}/conditions)----------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@access_rule_router.get("/{rule_id}/conditions", response_model=dict)
|
|
|
|
|
|
async def get_conditions(
|
2026-07-04 00:16:45 +08:00
|
|
|
|
rule_id: int = Path(ge=1),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_required_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""查询访问规则的附加条件。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = GetConditionsInput(rule_id=rule_id)
|
|
|
|
|
|
output = await use_cases.access_rule_service.get_conditions(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@access_rule_router.put("/{rule_id}/conditions", response_model=dict)
|
|
|
|
|
|
async def update_conditions(
|
|
|
|
|
|
payload: UpdateConditionsRequest,
|
2026-07-04 00:16:45 +08:00
|
|
|
|
rule_id: int = Path(ge=1),
|
2026-06-20 22:16:07 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(get_admin_user),
|
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
|
"""更新访问规则的附加条件。``updated_by`` 由当前管理员填充。"""
|
|
|
|
|
|
use_cases = create_use_cases_from_db(db)
|
|
|
|
|
|
input_dto = UpdateConditionsInput(
|
|
|
|
|
|
rule_id=rule_id,
|
|
|
|
|
|
conditions=payload.conditions,
|
|
|
|
|
|
updated_by=current_user.uid,
|
|
|
|
|
|
)
|
|
|
|
|
|
output = await use_cases.access_rule_service.update_conditions(input_dto)
|
|
|
|
|
|
return {"success": True, "data": output.model_dump()}
|