# -*- coding: utf-8 -*- """pbl_domain_ext 模块挂载入口。 应用侧调用 ``load_pbl_domain_ext()`` 完成: 1. 注册 13 个 dspy 契约路由(wwwroot/api/*.dspy -> api.py 契约函数); 2. 注册 RBAC 权限点(owner.pbl / tenant 管理员可写,其余只读); 3. 注入 sqlor 句柄与库名(ServerEnv().get_module_dbname); 4. 幂等保障扩展列名为设计 §J1 权威名 ``ext_json``(旧名 ext_json 自动 RENAME)。 铁律:本模块**不创建/不修改** world、scene、entity 三张基础域基表。 """ from . import api, db from .base import TABLE, EXT_FIELD, REF_TYPES, BIND_STATES from .errors import PblDomainExtError __all__ = ["load_pbl_domain_ext", "MODULE_NAME", "API_ROUTES", "PERMISSIONS", "TABLE", "EXT_FIELD", "REF_TYPES", "BIND_STATES"] MODULE_NAME = "pbl_domain_ext" #: dspy 契约路由表:url 后缀 -> (action 名, 是否写操作) API_ROUTES = [ ("pbl_domain_ref_bind", "pbl_domain_ref_bind", True), ("pbl_domain_ref_unbind", "pbl_domain_ref_unbind", True), ("pbl_domain_ref_update", "pbl_domain_ref_update", True), ("pbl_domain_ref_get", "pbl_domain_ref_get", False), ("pbl_domain_ref_list", "pbl_domain_ref_list", False), ("pbl_domain_ref_check_access", "pbl_domain_ref_check_access", False), ("pbl_world_list_by_tenant", "pbl_world_list_by_tenant", False), ("pbl_world_get_context", "pbl_world_get_context", False), ("pbl_scene_list_by_world", "pbl_scene_list_by_world", False), ("pbl_entity_list_by_scene", "pbl_entity_list_by_scene", False), ("pbl_team_bind_world", "pbl_team_bind_world", True), ("pbl_team_world_list", "pbl_team_world_list", False), ("pbl_team_list_by_class", "pbl_team_list_by_class", False), ] #: RBAC 权限点(写 4 个 + 读 1 个聚合) PERMISSIONS = [ {"code": "pbl_domain_ext:ref:write", "name": "PBL基础域关联-写", "note": "绑定/解绑/更新 pbl_domain_ref"}, {"code": "pbl_domain_ext:ref:read", "name": "PBL基础域关联-读", "note": "查询关联记录与基础域只读视图"}, {"code": "pbl_domain_ext:team:bind", "name": "PBL团队绑定世界", "note": "team/class 与 world 的绑定关系维护"}, ] def _register_routes(app=None): """注册 dspy 路由(平台 register_dspy 可用时走平台,否则仅返回路由表)。""" registered = [] for name, action, is_write in API_ROUTES: url = "/%s/api/%s.dspy" % (MODULE_NAME, name) registered.append({"url": url, "action": action, "write": is_write, "handler": getattr(api, action, None)}) if app is not None and hasattr(app, "register_dspy"): for item in registered: try: app.register_dspy(item["url"], item["handler"]) except Exception: # noqa: BLE001 平台版本差异不阻断挂载 pass return registered def _register_permissions(app=None): """注册 RBAC 权限点(幂等)。""" if app is not None and hasattr(app, "register_permissions"): try: app.register_permissions(MODULE_NAME, PERMISSIONS) except Exception: # noqa: BLE001 pass return list(PERMISSIONS) def load_pbl_domain_ext(app=None, sor=None, ensure_schema=True): """挂载模块。 :param app: 应用对象(可选,用于注册路由/权限) :param sor: sqlor 句柄(可选;缺省时由 db.get_sor() 惰性从 ServerEnv 取) :param ensure_schema: 是否执行 ext_json 列名幂等保障(默认 True) :return: 挂载信息 dict """ if sor is not None: db.set_sor(sor) routes = _register_routes(app) perms = _register_permissions(app) schema_action = "skip" if ensure_schema: try: schema_action = db.ensure_ext_column() except PblDomainExtError as exc: schema_action = "skip(%s)" % exc.code return { "module": MODULE_NAME, "table": TABLE, "ext_field": EXT_FIELD, "ref_types": list(REF_TYPES), "bind_states": list(BIND_STATES), "routes": routes, "permissions": perms, "schema_action": schema_action, "base_tables_readonly": ["world", "scene", "entity"], }