weaver-work-cli 0.1.7 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/dist/cmd/action.js +76 -0
- package/dist/cmd/index.js +2 -0
- package/dist/cmd/refs/index.js +103 -0
- package/dist/cmd/register.js +31 -0
- package/dist/cmd/root.js +1 -0
- package/dist/cmd/setup/index.js +25 -5
- package/dist/cmd/skills/index.js +37 -5
- package/dist/core/connector.js +54 -0
- package/dist/core/context.js +2 -1
- package/dist/core/input.js +48 -0
- package/dist/core/output.js +298 -3
- package/dist/core/refs.js +432 -0
- package/dist/core/state.js +26 -0
- package/dist/internal/e10/auth/commands.js +2 -2
- package/dist/internal/e10/auth/session.js +14 -0
- package/dist/internal/e10/auth/xiaoe.js +116 -51
- package/dist/internal/e10/request-runtime.js +5 -0
- package/dist/internal/skills/install.js +148 -28
- package/dist/internal/skills/reader.js +12 -9
- package/dist/shortcuts/blog/continuation.js +74 -0
- package/dist/shortcuts/blog/errors.js +60 -0
- package/dist/shortcuts/blog/host.js +175 -0
- package/dist/shortcuts/blog/index.js +324 -0
- package/dist/shortcuts/blog/manifest.js +415 -0
- package/dist/shortcuts/blog/operations/comment.js +125 -0
- package/dist/shortcuts/blog/operations/comments.js +31 -0
- package/dist/shortcuts/blog/operations/edit.js +131 -0
- package/dist/shortcuts/blog/operations/export.js +46 -0
- package/dist/shortcuts/blog/operations/like.js +80 -0
- package/dist/shortcuts/blog/operations/publish.js +157 -0
- package/dist/shortcuts/blog/operations/query-user.js +58 -0
- package/dist/shortcuts/blog/operations/query.js +96 -0
- package/dist/shortcuts/blog/operations/registry.js +54 -0
- package/dist/shortcuts/blog/operations/remind.js +176 -0
- package/dist/shortcuts/blog/operations/shared.js +330 -0
- package/dist/shortcuts/blog/operations/types.js +16 -0
- package/dist/shortcuts/blog/operations.js +9 -0
- package/dist/shortcuts/caizhilian/continuation.js +70 -0
- package/dist/shortcuts/caizhilian/errors.js +53 -0
- package/dist/shortcuts/caizhilian/host.js +190 -0
- package/dist/shortcuts/caizhilian/index.js +186 -0
- package/dist/shortcuts/caizhilian/manifest.js +42 -0
- package/dist/shortcuts/caizhilian/operations/api.js +801 -0
- package/dist/shortcuts/caizhilian/operations/confirm.js +75 -0
- package/dist/shortcuts/caizhilian/operations/files.js +306 -0
- package/dist/shortcuts/caizhilian/operations/form.js +170 -0
- package/dist/shortcuts/caizhilian/operations/handlers/collab.js +611 -0
- package/dist/shortcuts/caizhilian/operations/handlers/document.js +890 -0
- package/dist/shortcuts/caizhilian/operations/handlers/file-collection.js +346 -0
- package/dist/shortcuts/caizhilian/operations/handlers/file-upload.js +192 -0
- package/dist/shortcuts/caizhilian/operations/handlers/fileops.js +570 -0
- package/dist/shortcuts/caizhilian/operations/handlers/folder.js +969 -0
- package/dist/shortcuts/caizhilian/operations/handlers/search.js +318 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-attach.js +294 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-backup.js +423 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-encrypt.js +238 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-storage.js +364 -0
- package/dist/shortcuts/caizhilian/operations/handlers/system.js +106 -0
- package/dist/shortcuts/caizhilian/operations/registry.js +151 -0
- package/dist/shortcuts/caizhilian/operations/shared.js +408 -0
- package/dist/shortcuts/caizhilian/operations/specs.js +623 -0
- package/dist/shortcuts/caizhilian/operations/types.js +57 -0
- package/dist/shortcuts/caizhilian/operations.js +21 -0
- package/dist/shortcuts/contract/continuation.js +70 -0
- package/dist/shortcuts/contract/errors.js +62 -0
- package/dist/shortcuts/contract/host.js +205 -0
- package/dist/shortcuts/contract/index.js +318 -0
- package/dist/shortcuts/contract/manifest.js +596 -0
- package/dist/shortcuts/contract/operations/clause-reference.js +78 -0
- package/dist/shortcuts/contract/operations/form-info.js +371 -0
- package/dist/shortcuts/contract/operations/read.js +118 -0
- package/dist/shortcuts/contract/operations/registry.js +43 -0
- package/dist/shortcuts/contract/operations/request.js +300 -0
- package/dist/shortcuts/contract/operations/shared.js +133 -0
- package/dist/shortcuts/contract/operations/subject.js +157 -0
- package/dist/shortcuts/contract/operations/template.js +81 -0
- package/dist/shortcuts/contract/operations/types.js +7 -0
- package/dist/shortcuts/contract/operations/workflow.js +52 -0
- package/dist/shortcuts/contract/operations/write.js +131 -0
- package/dist/shortcuts/contract/operations.js +8 -0
- package/dist/shortcuts/hrm/index.js +83 -2
- package/dist/shortcuts/hrm/operations/employee.js +160 -30
- package/dist/shortcuts/hrm/operations/org.js +17 -3
- package/dist/shortcuts/hrm/operations/position.js +18 -2
- package/dist/shortcuts/index.js +5 -1
- package/dist/shortcuts/invoice/index.js +74 -36
- package/dist/shortcuts/invoice/manifest.js +674 -4
- package/dist/shortcuts/invoice/markdown.js +172 -0
- package/dist/shortcuts/invoice/operations/add.js +3 -3
- package/dist/shortcuts/invoice/operations/delete.js +2 -2
- package/dist/shortcuts/invoice/operations/get.js +10 -2
- package/dist/shortcuts/invoice/operations/import.js +2 -2
- package/dist/shortcuts/invoice/operations/registry.js +8 -1
- package/dist/shortcuts/invoice/operations/reim-auto.js +719 -0
- package/dist/shortcuts/invoice/operations/reim.js +85 -2
- package/dist/shortcuts/invoice/operations/shared.js +75 -8
- package/dist/shortcuts/invoice/operations/update.js +2 -2
- package/dist/shortcuts/invoice/operations/upload-and-validate.js +93 -0
- package/dist/shortcuts/invoice/operations/validate-preview.js +2 -1
- package/dist/shortcuts/invoice/projection.js +328 -0
- package/dist/shortcuts/meeting/host.js +18 -1
- package/dist/shortcuts/meeting/manifest.js +30 -18
- package/dist/shortcuts/meeting/operations/eb/catalog.js +390 -0
- package/dist/shortcuts/meeting/operations/eb/conflict.js +389 -0
- package/dist/shortcuts/meeting/operations/eb/factory.js +389 -0
- package/dist/shortcuts/meeting/operations/eb/index.js +22 -0
- package/dist/shortcuts/meeting/operations/env.js +8 -1
- package/dist/shortcuts/meeting/operations/registry.js +2 -0
- package/dist/shortcuts/meeting/operations/signs.js +216 -2
- package/dist/shortcuts/workflow/endpoints.js +2 -1
- package/dist/shortcuts/workflow/errors.js +8 -2
- package/dist/shortcuts/workflow/host.js +6 -3
- package/dist/shortcuts/workflow/operations/form.js +931 -533
- package/dist/shortcuts/workflow/operations/read.js +462 -81
- package/dist/shortcuts/workflow/operations/shared.js +44 -0
- package/dist/shortcuts/workflow/operations/write.js +760 -56
- package/dist/shortcuts/workflow/operations.js +36 -10
- package/docs/_catalog.md +6 -0
- package/docs/agent-invoice.md +5 -1
- package/docs/agent-io-channel-design.md +468 -0
- package/docs/agent-skill-install.md +9 -4
- package/docs/blog.md +123 -0
- package/docs/caizhilian.md +304 -0
- package/docs/contract.md +91 -0
- package/docs/e10-auth.md +2 -2
- package/docs/hrm.md +19 -9
- package/docs/invoice.md +5 -1
- package/docs/meeting.md +1 -1
- package/docs/operation-manual.md +15 -3
- package/docs/skill-review-2026-09-19.md +2 -2
- package/package.json +2 -2
- package/scripts/package-skill.mjs +33 -20
- package/skill-template/business-info.json +113 -14
- package/skill-template/domains/blog.md +24 -0
- package/skill-template/domains/caizhilian.md +22 -0
- package/skill-template/domains/jinchengda.md +23 -0
- package/skill-template/domains/meeting.md +1 -1
- package/skill-template/domains/workflow.md +1 -1
- package/skill-template/skill-template.md +1 -1
- package/skills/weaver-e10-blog/SKILL.md +111 -0
- package/skills/weaver-e10-blog/references/blog-comment.md +51 -0
- package/skills/weaver-e10-blog/references/blog-comments.md +39 -0
- package/skills/weaver-e10-blog/references/blog-default-permission.md +38 -0
- package/skills/weaver-e10-blog/references/blog-disabled-capabilities.md +11 -0
- package/skills/weaver-e10-blog/references/blog-edit.md +51 -0
- package/skills/weaver-e10-blog/references/blog-export.md +42 -0
- package/skills/weaver-e10-blog/references/blog-like.md +50 -0
- package/skills/weaver-e10-blog/references/blog-publish.md +52 -0
- package/skills/weaver-e10-blog/references/blog-query-user.md +45 -0
- package/skills/weaver-e10-blog/references/blog-query.md +49 -0
- package/skills/weaver-e10-blog/references/blog-remind.md +51 -0
- package/skills/weaver-e10-blog/references/blog-resolve-users.md +38 -0
- package/skills/weaver-e10-blog/references/source-manifest.json +1540 -0
- package/skills/weaver-e10-caizhilian/SKILL.md +216 -0
- package/skills/weaver-e10-caizhilian/references/agent-entry.md +106 -0
- package/skills/weaver-e10-caizhilian/references/collaboration.md +112 -0
- package/skills/weaver-e10-caizhilian/references/document.md +111 -0
- package/skills/weaver-e10-caizhilian/references/file-collection.md +136 -0
- package/skills/weaver-e10-caizhilian/references/file-upload.md +309 -0
- package/skills/weaver-e10-caizhilian/references/files.md +120 -0
- package/skills/weaver-e10-caizhilian/references/folder.md +118 -0
- package/skills/weaver-e10-caizhilian/references/org-browser.md +104 -0
- package/skills/weaver-e10-caizhilian/references/setting-attach.md +410 -0
- package/skills/weaver-e10-caizhilian/references/setting-backup.md +372 -0
- package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +399 -0
- package/skills/weaver-e10-caizhilian/references/setting-storage.md +603 -0
- package/skills/weaver-e10-caizhilian/references/source-manifest.json +5079 -0
- package/skills/weaver-e10-caizhilian/references/withheld.md +93 -0
- package/skills/weaver-e10-caizhilian/references/write-protocol.md +120 -0
- package/skills/weaver-e10-calendar/SKILL.md +4 -3
- package/skills/weaver-e10-ebuilder-form/SKILL.md +4 -9
- package/skills/weaver-e10-ebuilder-form/references/context-routing.md +0 -9
- package/skills/weaver-e10-ebuilder-form/references/custom-api.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/fields-browser.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/list-nlist.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/openapi-read.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +1 -5
- package/skills/weaver-e10-esb/SKILL.md +2 -2
- package/skills/weaver-e10-esb/references/source-manifest.json +9 -9
- package/skills/weaver-e10-hrm/SKILL.md +54 -6
- package/skills/weaver-e10-hrm/references/employee.md +89 -15
- package/skills/weaver-e10-hrm/references/org.md +7 -4
- package/skills/weaver-e10-hrm/references/position.md +14 -7
- package/skills/weaver-e10-hrm/references/source-manifest.json +1 -1
- package/skills/weaver-e10-jinchengda/SKILL.md +166 -0
- package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +82 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +69 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +78 -0
- package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +27 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-status.md +71 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-write.md +94 -0
- package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +89 -0
- package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +77 -0
- package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +66 -0
- package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +94 -0
- package/skills/weaver-e10-jinchengda/references/source-manifest.json +259 -0
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -11
- package/skills/weaver-e10-jucailin/SKILL.md +4 -3
- package/skills/weaver-e10-mail/SKILL.md +4 -11
- package/skills/weaver-e10-meeting/SKILL.md +66 -32
- package/skills/weaver-e10-meeting/references/meeting-change.md +5 -2
- package/skills/weaver-e10-meeting/references/meeting-create.md +2 -1
- package/skills/weaver-e10-meeting/references/meeting-eb.md +114 -0
- package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +9 -8
- package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +35 -7
- package/skills/weaver-e10-meeting/references/meeting-update.md +1 -1
- package/skills/weaver-e10-okr/SKILL.md +6 -11
- package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
- package/skills/weaver-e10-plan/SKILL.md +3 -3
- package/skills/weaver-e10-plan/references/plan-collect-and-compose.md +1 -1
- package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +1 -1
- package/skills/weaver-e10-plan/references/source-manifest.json +13 -13
- package/skills/weaver-e10-qiyecheng/SKILL.md +4 -10
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +9 -9
- package/skills/weaver-e10-shared/SKILL.md +36 -25
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +10 -31
- package/skills/weaver-e10-shared/references/json-output-contract.md +20 -11
- package/skills/weaver-e10-shijingran/SKILL.md +2 -1
- package/skills/weaver-e10-shijingran/references/source-manifest.json +9 -9
- package/skills/weaver-e10-skill-maker/SKILL.md +4 -10
- package/skills/weaver-e10-skill-maker/references/business-skill-generation.md +5 -3
- package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +12 -27
- package/skills/weaver-e10-skill-maker/references/module-gap-audit-and-repair.md +17 -5
- package/skills/weaver-e10-skill-maker/references/source-update-detection.md +4 -27
- package/skills/weaver-e10-wenshuding/SKILL.md +5 -16
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +7 -7
- package/skills/weaver-e10-workflow/SKILL.md +32 -37
- package/skills/weaver-e10-workflow/references/source-manifest.json +40 -77
- package/skills/weaver-e10-workflow/references/workflow-batch.md +90 -29
- package/skills/weaver-e10-workflow/references/workflow-create.md +79 -109
- package/skills/weaver-e10-workflow/references/workflow-memory.md +19 -9
- package/skills/weaver-e10-workflow/references/workflow-operate.md +2 -2
- package/skills/weaver-e10-workflow/references/workflow-query.md +37 -20
- package/skills/weaver-e10-workflow/references/workflow-share.md +18 -2
- package/skills/weaver-e10-workflow/references/workflow-view.md +4 -4
- package/skills/weaver-e10-yepiaotong/SKILL.md +75 -39
- package/skills/weaver-e10-yepiaotong/references/invoice-detail.md +22 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +3 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing.md +5 -5
- package/skills/weaver-e10-yepiaotong/references/invoice-output-view.md +135 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +15 -3
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +1 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-summarize.py +217 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-upload-and-validate.md +88 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-validation-preview.md +7 -2
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +90 -10
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +1 -0
- package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +70 -49
- package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +119 -0
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +55 -36
- package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +4 -0
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +149 -0
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +125 -5
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +430 -63
- package/skills/weaver-e10-yimiaoban/SKILL.md +4 -12
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
- package/skills/weaver-e10-ziguanjia/SKILL.md +2 -1
- package/skills/weaver-e10-ziguanjia/references/ziguanjia-agent-entry.md +3 -3
- package/tools/weaver-skill-detector-1.0.13/SKILL.md +9 -9
- package/tools/weaver-skill-detector-1.0.13/references/checklist.md +3 -3
- package/tools/weaver-skill-detector-1.0.13/scripts/__pycache__/validate_skill.cpython-313.pyc +0 -0
- package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +98 -91
- package/scripts/sync-connector-skills.mjs +0 -172
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
import { CaizhilianCliError, caizhilianValidationError } from '../../errors.js';
|
|
2
|
+
import { asList, asRecord, assertToleratedBusinessSuccess, buildFolderPermissionAddBody, checkCreateFolderPermission, checkSetFolderPermission, fetchFolderDefaultShareEntries, fetchFolderPermissionAuth, fetchFolderPermissionEntries, fetchFolderPermissions, isAlreadyExistsResponse, updateFolderPermissionAuth, } from '../api.js';
|
|
3
|
+
import { issueWriteLease } from '../confirm.js';
|
|
4
|
+
import { DEFAULT_SEC_LEVEL, buildObjTypeList, parseCascadeRules, parseCascadeRulesOuter, parseFieldCascadeRules, parseObjTypeOptions, parseParamOptions, parseSeclevelAllowed, } from '../form.js';
|
|
5
|
+
import { fingerprint, isFalsyIds, isPlainObject, joinIds, parsePage, pyInt, pyRepr, requireId, requireIdList, requireNonEmpty, sortedJoin, } from '../shared.js';
|
|
6
|
+
import { PERMISSION_TYPES, SETTING_TYPES, FOLDER_PERMISSION_KEYS } from '../specs.js';
|
|
7
|
+
import { caizhilianPrepared, caizhilianSuccess } from '../types.js';
|
|
8
|
+
/**
|
|
9
|
+
* 文件夹域操作(1:1 对应源 scripts/folder_plugins.py 的 14 个 plugin
|
|
10
|
+
* + doc-business-operations 的 3 个 http_template:
|
|
11
|
+
* query_folders_by_name / fetch_folder_default_settings / query_folder_permissions)。
|
|
12
|
+
*
|
|
13
|
+
* 写操作按 weaver-work-cli 规范拆成 prepare(只读回查 + 组装请求体 + 签发票据)
|
|
14
|
+
* 与 apply(真正产生副作用),业务请求体与源保持逐字段一致。
|
|
15
|
+
*/
|
|
16
|
+
// ── 源常量 ────────────────────────────────────────────────
|
|
17
|
+
/** 源 CreateFolderPlugin._SEQ_MIN / _SEQ_MAX。 */
|
|
18
|
+
const SEQ_MIN = -9999;
|
|
19
|
+
const SEQ_MAX = 99999;
|
|
20
|
+
/** 源 CreateFolderPlugin 的 parent_type 取值。 */
|
|
21
|
+
const PARENT_TYPES = ['company', 'privy'];
|
|
22
|
+
const FOLDER_CREATE_PATH = '/api/doc/folders/create';
|
|
23
|
+
const QUERY_FOLDERS_BY_NAME_PATH = '/api/doc/folders/queryFoldersByName';
|
|
24
|
+
const FOLDER_SETTINGS_PATH = '/api/doc/folderSetting/getFolderSettings';
|
|
25
|
+
const DEFAULT_SHARE_ADD_PATH = '/api/doc/setting/default/share/add';
|
|
26
|
+
const DEFAULT_SHARE_DELETE_PATH = '/api/doc/setting/default/share/delete';
|
|
27
|
+
const PERMISSION_FORM_PATH = '/api/doc/permission/data/query';
|
|
28
|
+
const PERMISSION_ADD_PATH = '/api/doc/folderSetting/share/add';
|
|
29
|
+
const PERMISSION_DELETE_PATH = '/api/doc/folderSetting/share/delete';
|
|
30
|
+
const QUERY_FOLDER_COUNT_PATH = '/api/doc/folderpermission/queryFoldersWithPermissionsCount';
|
|
31
|
+
const QUERY_FOLDER_LIST_PATH = '/api/doc/folderpermission/queryFoldersWithPermissions';
|
|
32
|
+
/** 源 query_folders_by_name.json 的 kb_type lookup(default = 200)。 */
|
|
33
|
+
const KB_ROOT_FOLDER_ID = { company: '200', privy: '300', sys: '100' };
|
|
34
|
+
const KB_ROOT_FOLDER_ID_DEFAULT = '200';
|
|
35
|
+
const KB_TYPE_DEFAULT = 'company';
|
|
36
|
+
/** 源 fetch_folder_default_settings 模板的固定 type 变量。 */
|
|
37
|
+
const FOLDER_SETTING_TYPES_PARAM = 'folderDefaultShare,folderDefaultParticipants,defaultVirtualFolder';
|
|
38
|
+
/** 源 FetchFolderSetDefaultShareFormPlugin._CATEGORY_API。 */
|
|
39
|
+
const CATEGORY_API = {
|
|
40
|
+
creator: '/api/doc/permission/data/queryCreator',
|
|
41
|
+
generic: '/api/doc/permission/data/queryOther',
|
|
42
|
+
};
|
|
43
|
+
/** 源 AddFolderSetDefaultSharePlugin.user_overrides(顺序即写入顺序)。 */
|
|
44
|
+
const SHARE_USER_OVERRIDES = [
|
|
45
|
+
'download',
|
|
46
|
+
'department_attribute',
|
|
47
|
+
'role_level',
|
|
48
|
+
'position_level',
|
|
49
|
+
'position_attribute',
|
|
50
|
+
'virtualCompany',
|
|
51
|
+
'createrPositionLevel',
|
|
52
|
+
];
|
|
53
|
+
/** 源 AddFolderSetDefaultSharePlugin.id_fields。 */
|
|
54
|
+
const SHARE_ID_FIELDS = [
|
|
55
|
+
'obj_id',
|
|
56
|
+
'obj_id_department',
|
|
57
|
+
'obj_id_subcompany',
|
|
58
|
+
'obj_id_role',
|
|
59
|
+
'obj_id_group',
|
|
60
|
+
'obj_id_external',
|
|
61
|
+
'obj_id_customer',
|
|
62
|
+
'obj_id_customer_type',
|
|
63
|
+
'obj_id_position',
|
|
64
|
+
'obj_id_manager',
|
|
65
|
+
'obj_id_grade',
|
|
66
|
+
];
|
|
67
|
+
/** 源 folder_plugins._AUTH_CHECK_TYPES / _AUTH_TYPE_LABELS。 */
|
|
68
|
+
const AUTH_CHECK_TYPES = ['downloadPremisstion', 'deletePremisstion'];
|
|
69
|
+
const AUTH_TYPE_LABELS = {
|
|
70
|
+
downloadPremisstion: '下载权限',
|
|
71
|
+
deletePremisstion: '删除权限',
|
|
72
|
+
};
|
|
73
|
+
/** 源 folder_plugins._DETAIL_AUTH_TYPES / _DETAIL_TITLES / _DETAIL_AUTH_LABELS。 */
|
|
74
|
+
const DETAIL_AUTH_TYPES = ['downloadPremisstion', 'deletePremisstion'];
|
|
75
|
+
const DETAIL_TITLES = {
|
|
76
|
+
downloadPremisstion: '在此文件夹下能够下载附件',
|
|
77
|
+
deletePremisstion: '在此文件夹下能够删除文档',
|
|
78
|
+
newCreateDocPremisstion: '新建权限',
|
|
79
|
+
copyPermission: '复制权限',
|
|
80
|
+
movePermission: '移动权限',
|
|
81
|
+
settingPermission: '文件夹管理员',
|
|
82
|
+
newCreateFolderPremisstion: '文件夹维护权限',
|
|
83
|
+
folderDefaultParticipants: '默认参与人',
|
|
84
|
+
defaultShares: '默认共享人',
|
|
85
|
+
};
|
|
86
|
+
const DETAIL_AUTH_LABELS = {
|
|
87
|
+
downloadPremisstion: { true: '允许所有人下载', user: '允许以下人员下载', false: '不允许下载' },
|
|
88
|
+
deletePremisstion: { true: '允许完全控制权限人员删除文档', user: '允许以下人员删除文档' },
|
|
89
|
+
};
|
|
90
|
+
/** 源 query_folder_permissions.json 的固定 typeStr(白名单 4 项 + documentCreateSetting 派生)。 */
|
|
91
|
+
const FOLDER_PERMISSION_QUERY_FIELDS = [
|
|
92
|
+
'isCreateDoc',
|
|
93
|
+
'isCreateFolder',
|
|
94
|
+
'canMoveDoc',
|
|
95
|
+
'canCopyDoc',
|
|
96
|
+
'documentCreateSetting',
|
|
97
|
+
];
|
|
98
|
+
/** 源 QueryFoldersByPermissionPlugin 的对象类型集合(Python set → JS Set)。 */
|
|
99
|
+
const QUERY_FOLDER_BASIC_OBJECT_TYPES = new Set([
|
|
100
|
+
'resource',
|
|
101
|
+
'department',
|
|
102
|
+
'subcompany',
|
|
103
|
+
'role',
|
|
104
|
+
'group',
|
|
105
|
+
'position',
|
|
106
|
+
'all',
|
|
107
|
+
]);
|
|
108
|
+
const QUERY_FOLDER_REQUIRES_IDS = new Set([
|
|
109
|
+
'resource',
|
|
110
|
+
'department',
|
|
111
|
+
'subcompany',
|
|
112
|
+
'role',
|
|
113
|
+
'group',
|
|
114
|
+
'position',
|
|
115
|
+
'customer',
|
|
116
|
+
'customer_type',
|
|
117
|
+
]);
|
|
118
|
+
const QUERY_FOLDER_NO_IDS = new Set([
|
|
119
|
+
'all',
|
|
120
|
+
'allExternal',
|
|
121
|
+
'external',
|
|
122
|
+
'createrSelf',
|
|
123
|
+
'createrSuperior',
|
|
124
|
+
'createrSuperiorAll',
|
|
125
|
+
'createrDepartment',
|
|
126
|
+
'createrSubcompany',
|
|
127
|
+
'createrPosition',
|
|
128
|
+
]);
|
|
129
|
+
const QUERY_FOLDER_ALL_OBJECT_TYPES = new Set([...QUERY_FOLDER_REQUIRES_IDS, ...QUERY_FOLDER_NO_IDS]);
|
|
130
|
+
const QUERY_FOLDER_KEY_OBJECT_TYPES = {
|
|
131
|
+
folderDefaultShareNew: QUERY_FOLDER_ALL_OBJECT_TYPES,
|
|
132
|
+
folderDefaultParticipants: new Set(['resource']),
|
|
133
|
+
downloadPremisstion: new Set([...QUERY_FOLDER_BASIC_OBJECT_TYPES, 'customer', 'customer_type']),
|
|
134
|
+
};
|
|
135
|
+
for (const key of [
|
|
136
|
+
'newCreateFolderPremisstion',
|
|
137
|
+
'settingPermission',
|
|
138
|
+
'newCreateDocPremisstion',
|
|
139
|
+
'deletePremisstion',
|
|
140
|
+
'copyPermission',
|
|
141
|
+
'movePermission',
|
|
142
|
+
]) {
|
|
143
|
+
QUERY_FOLDER_KEY_OBJECT_TYPES[key] = QUERY_FOLDER_BASIC_OBJECT_TYPES;
|
|
144
|
+
}
|
|
145
|
+
const QUERY_FOLDER_KEY_NAMES = {
|
|
146
|
+
folderDefaultShareNew: '文件夹默认共享',
|
|
147
|
+
newCreateFolderPremisstion: '文件夹维护权限',
|
|
148
|
+
settingPermission: '文件夹管理员',
|
|
149
|
+
folderDefaultParticipants: '默认参与人',
|
|
150
|
+
newCreateDocPremisstion: '新建文档权限',
|
|
151
|
+
downloadPremisstion: '下载权限',
|
|
152
|
+
deletePremisstion: '删除权限',
|
|
153
|
+
copyPermission: '复制权限',
|
|
154
|
+
movePermission: '移动权限',
|
|
155
|
+
};
|
|
156
|
+
// ── 通用小工具(对齐源取值语义)──────────────────────────
|
|
157
|
+
/** 源 CreateFolderPlugin 的 inherit_parent 解析:字符串按 true/1,其余按 bool()。 */
|
|
158
|
+
function parseInheritParent(value) {
|
|
159
|
+
if (typeof value === 'string')
|
|
160
|
+
return ['true', '1'].includes(value.trim().toLowerCase());
|
|
161
|
+
if (value === undefined || value === null)
|
|
162
|
+
return false;
|
|
163
|
+
if (typeof value === 'boolean')
|
|
164
|
+
return value;
|
|
165
|
+
if (typeof value === 'number')
|
|
166
|
+
return value !== 0;
|
|
167
|
+
if (Array.isArray(value))
|
|
168
|
+
return value.length > 0;
|
|
169
|
+
if (isPlainObject(value))
|
|
170
|
+
return Object.keys(value).length > 0;
|
|
171
|
+
return Boolean(value);
|
|
172
|
+
}
|
|
173
|
+
// ── 表单配置查询与 seclevel 解析(源 folder_plugins)──────
|
|
174
|
+
/** 源 _query_form_config:固定 body 的表单配置查询。 */
|
|
175
|
+
async function queryFormConfig(env, folderId, apiPath, operation) {
|
|
176
|
+
const response = await env.host.request({
|
|
177
|
+
method: 'POST',
|
|
178
|
+
path: apiPath,
|
|
179
|
+
body: {
|
|
180
|
+
permissionId: 'doc_s01',
|
|
181
|
+
id: String(folderId),
|
|
182
|
+
isTemplate: false,
|
|
183
|
+
tableName: 'folder',
|
|
184
|
+
sourceId: String(folderId),
|
|
185
|
+
sourceType: 1,
|
|
186
|
+
},
|
|
187
|
+
operation,
|
|
188
|
+
});
|
|
189
|
+
return asRecord(response);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 源 `creator_data, _ = _query_form_config(...)`:失败被吞掉,按 None 处理。
|
|
193
|
+
*/
|
|
194
|
+
async function tryQueryFormConfig(env, folderId, apiPath, operation) {
|
|
195
|
+
try {
|
|
196
|
+
return await queryFormConfig(env, folderId, apiPath, operation);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/** 源 _parse_seclevel_args:默认 0~100,允许范围 -999~999,且 min<=max。 */
|
|
203
|
+
function parseSeclevelArgs(args) {
|
|
204
|
+
let secMin;
|
|
205
|
+
let secMax;
|
|
206
|
+
try {
|
|
207
|
+
secMin = pyInt(args.seclevel_min === '' ? undefined : args.seclevel_min, DEFAULT_SEC_LEVEL.min);
|
|
208
|
+
secMax = pyInt(args.seclevel_max === '' ? undefined : args.seclevel_max, DEFAULT_SEC_LEVEL.max);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
throw caizhilianValidationError('number_invalid', 'seclevel_min/seclevel_max 必须为整数');
|
|
212
|
+
}
|
|
213
|
+
for (const [name, value] of [
|
|
214
|
+
['seclevel_min', secMin],
|
|
215
|
+
['seclevel_max', secMax],
|
|
216
|
+
]) {
|
|
217
|
+
if (value < -999 || value > 999) {
|
|
218
|
+
throw caizhilianValidationError('range_invalid', `${name} 超出允许范围 -999~999: ${value}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (secMin > secMax) {
|
|
222
|
+
throw caizhilianValidationError('range_invalid', `seclevel_min 不能大于 seclevel_max(${secMin} > ${secMax})`);
|
|
223
|
+
}
|
|
224
|
+
return { min: secMin, max: secMax };
|
|
225
|
+
}
|
|
226
|
+
// ── 写操作 apply 名(continuation 与操作绑定,防跨操作串用)──
|
|
227
|
+
const FOLDER_CREATE_APPLY = 'caizhilian.folder.create.apply';
|
|
228
|
+
const DEFAULT_SHARE_ADD_APPLY = 'caizhilian.folder.default-share.add.apply';
|
|
229
|
+
const DEFAULT_SHARE_DELETE_APPLY = 'caizhilian.folder.default-share.delete.apply';
|
|
230
|
+
const PERMISSION_ADD_APPLY = 'caizhilian.folder.permission.add.apply';
|
|
231
|
+
const PERMISSION_DELETE_APPLY = 'caizhilian.folder.permission.delete.apply';
|
|
232
|
+
const PERMISSION_SET_AUTH_APPLY = 'caizhilian.folder.permission.set-auth.apply';
|
|
233
|
+
/** 源 CreateFolderPlugin.execute 的校验 + 请求体组装(含权限回查)。 */
|
|
234
|
+
async function planCreateFolder(args, env, action) {
|
|
235
|
+
let sequence;
|
|
236
|
+
try {
|
|
237
|
+
sequence = pyInt(args.sequence === undefined || args.sequence === null || args.sequence === '' ? undefined : args.sequence, 0);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
throw caizhilianValidationError('number_invalid', 'sequence 必须是整数');
|
|
241
|
+
}
|
|
242
|
+
const name = requireNonEmpty(args.name, '文件夹名称不能为空');
|
|
243
|
+
const parentId = requireNonEmpty(args.parent_id, 'parent_id 不能为空');
|
|
244
|
+
const parentTypeRaw = args.parent_type === undefined || args.parent_type === null ? '' : String(args.parent_type);
|
|
245
|
+
if (!PARENT_TYPES.includes(parentTypeRaw)) {
|
|
246
|
+
throw caizhilianValidationError('enum_invalid', `parent_type 只能是 company 或 privy,不支持: ${parentTypeRaw}`);
|
|
247
|
+
}
|
|
248
|
+
const inheritParentRaw = args.inherit_parent === undefined ? false : args.inherit_parent;
|
|
249
|
+
let inheritParent = parseInheritParent(inheritParentRaw);
|
|
250
|
+
// 源:个人库强制 sequence=0 且不继承父设置。
|
|
251
|
+
if (parentTypeRaw === 'privy') {
|
|
252
|
+
sequence = 0;
|
|
253
|
+
inheritParent = false;
|
|
254
|
+
}
|
|
255
|
+
if (sequence < SEQ_MIN || sequence > SEQ_MAX) {
|
|
256
|
+
throw caizhilianValidationError('range_invalid', `sequence 超出允许范围 [${SEQ_MIN}, ${SEQ_MAX}]`);
|
|
257
|
+
}
|
|
258
|
+
await checkCreateFolderPermission(env, parentId, action);
|
|
259
|
+
return {
|
|
260
|
+
name,
|
|
261
|
+
parentId,
|
|
262
|
+
parentType: parentTypeRaw,
|
|
263
|
+
sequence,
|
|
264
|
+
inheritParent,
|
|
265
|
+
body: {
|
|
266
|
+
folder: { type: parentTypeRaw, name, sequence },
|
|
267
|
+
parent: { id: parentId, type: parentTypeRaw },
|
|
268
|
+
inheritParentFolderSetUp: inheritParent,
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const createFolderHandler = {
|
|
273
|
+
targetKeys: ['parent_id', 'name', 'parent_type'],
|
|
274
|
+
async prepare(args, env) {
|
|
275
|
+
const action = 'caizhilian.folder.create';
|
|
276
|
+
const plan = await planCreateFolder(args, env, action);
|
|
277
|
+
const lease = await issueWriteLease(env, FOLDER_CREATE_APPLY, args, {
|
|
278
|
+
parent_id: plan.parentId,
|
|
279
|
+
name: plan.name,
|
|
280
|
+
parent_type: plan.parentType,
|
|
281
|
+
sequence: plan.sequence,
|
|
282
|
+
inherit_parent: plan.inheritParent,
|
|
283
|
+
body_fingerprint: fingerprint(plan.body),
|
|
284
|
+
});
|
|
285
|
+
return caizhilianPrepared(action, {
|
|
286
|
+
name: plan.name,
|
|
287
|
+
parent_id: plan.parentId,
|
|
288
|
+
parent_type: plan.parentType,
|
|
289
|
+
sequence: plan.sequence,
|
|
290
|
+
inherit_parent: plan.inheritParent,
|
|
291
|
+
request: { method: 'POST', path: FOLDER_CREATE_PATH, body: plan.body },
|
|
292
|
+
}, lease);
|
|
293
|
+
},
|
|
294
|
+
async apply(args, env, lease) {
|
|
295
|
+
const action = FOLDER_CREATE_APPLY;
|
|
296
|
+
const plan = await planCreateFolder(args, env, 'caizhilian.folder.create');
|
|
297
|
+
const expected = typeof lease.body_fingerprint === 'string' ? lease.body_fingerprint : '';
|
|
298
|
+
if (expected && expected !== fingerprint(plan.body)) {
|
|
299
|
+
throw caizhilianValidationError('plan_changed', `${action} 的请求体与 prepare 时不一致(父文件夹权限或入参可能已变化),请重新执行 prepare`);
|
|
300
|
+
}
|
|
301
|
+
const response = await env.host.request({
|
|
302
|
+
method: 'POST',
|
|
303
|
+
path: FOLDER_CREATE_PATH,
|
|
304
|
+
body: plan.body,
|
|
305
|
+
operation: action,
|
|
306
|
+
});
|
|
307
|
+
const folderId = String(asRecord(asRecord(response).data).folder === undefined ? '' : asRecord(asRecord(asRecord(response).data).folder).id ?? '').trim();
|
|
308
|
+
if (!folderId) {
|
|
309
|
+
throw caizhilianValidationError('folder_id_missing', '创建文件夹响应缺少 folder_id');
|
|
310
|
+
}
|
|
311
|
+
return caizhilianSuccess(action, {
|
|
312
|
+
folder_id: folderId,
|
|
313
|
+
name: plan.name,
|
|
314
|
+
type: plan.parentType,
|
|
315
|
+
parent_id: plan.parentId,
|
|
316
|
+
});
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
// ── caizhilian.folder.query-by-name ───────────────────────
|
|
320
|
+
/** 源 FlattenSearchResultFolderTree._traverse:递归展平 searchResultKeyNode 节点。 */
|
|
321
|
+
function traverseFolderTree(nodes, ancestors, results) {
|
|
322
|
+
for (const node of nodes) {
|
|
323
|
+
if (!isPlainObject(node))
|
|
324
|
+
continue;
|
|
325
|
+
const content = node.content || node.title || '';
|
|
326
|
+
const currentPath = [...ancestors, String(content)];
|
|
327
|
+
if (node.searchResultKeyNode) {
|
|
328
|
+
results.push({
|
|
329
|
+
id: String(node.id ?? ''),
|
|
330
|
+
name: content,
|
|
331
|
+
folderType: node.folderType ?? '',
|
|
332
|
+
parentId: String(node.parentId ?? ''),
|
|
333
|
+
path: currentPath.join('/'),
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
const children = node.children;
|
|
337
|
+
if (Array.isArray(children) && children.length > 0)
|
|
338
|
+
traverseFolderTree(children, currentPath, results);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async function queryFoldersByName(args, env) {
|
|
342
|
+
const action = 'caizhilian.folder.query-by-name';
|
|
343
|
+
const folderName = requireNonEmpty(args.folder_name, '缺少必填参数: folder_name');
|
|
344
|
+
const kbTypeRaw = args.kb_type === undefined || args.kb_type === null || args.kb_type === '' ? KB_TYPE_DEFAULT : String(args.kb_type);
|
|
345
|
+
const folderId = KB_ROOT_FOLDER_ID[kbTypeRaw] ?? KB_ROOT_FOLDER_ID_DEFAULT;
|
|
346
|
+
const session = await env.host.getSession();
|
|
347
|
+
const response = await env.host.request({
|
|
348
|
+
method: 'POST',
|
|
349
|
+
path: QUERY_FOLDERS_BY_NAME_PATH,
|
|
350
|
+
body: {
|
|
351
|
+
folder: { name: folderName, type: kbTypeRaw, id: folderId },
|
|
352
|
+
dimension: 'ALL',
|
|
353
|
+
userId: session.userId,
|
|
354
|
+
},
|
|
355
|
+
operation: action,
|
|
356
|
+
});
|
|
357
|
+
const results = [];
|
|
358
|
+
traverseFolderTree(asList(asRecord(asRecord(response).data).nodes), [], results);
|
|
359
|
+
// 源:完全同名的结果排到前面,其余保持遍历顺序。
|
|
360
|
+
const matched = results.filter((item) => item.name === folderName);
|
|
361
|
+
const unmatched = results.filter((item) => item.name !== folderName);
|
|
362
|
+
return caizhilianSuccess(action, { folders: [...matched, ...unmatched] }, { kb_type: kbTypeRaw, root_folder_id: folderId });
|
|
363
|
+
}
|
|
364
|
+
// ── caizhilian.folder.get-settings ────────────────────────
|
|
365
|
+
/** 源 Engine._apply_array_fields_whitelist:非 dict 元素丢弃,字段按白名单且在原对象中存在才保留。 */
|
|
366
|
+
function pickListFields(value, keys) {
|
|
367
|
+
if (!Array.isArray(value))
|
|
368
|
+
return value ?? null;
|
|
369
|
+
const output = [];
|
|
370
|
+
for (const item of value) {
|
|
371
|
+
if (!isPlainObject(item))
|
|
372
|
+
continue;
|
|
373
|
+
const picked = {};
|
|
374
|
+
for (const key of keys)
|
|
375
|
+
if (key in item)
|
|
376
|
+
picked[key] = item[key];
|
|
377
|
+
output.push(picked);
|
|
378
|
+
}
|
|
379
|
+
return output;
|
|
380
|
+
}
|
|
381
|
+
async function getFolderSettings(args, env) {
|
|
382
|
+
const action = 'caizhilian.folder.get-settings';
|
|
383
|
+
const folderId = requireNonEmpty(args.folder_id, '缺少必填参数: folder_id');
|
|
384
|
+
const response = await env.host.request({
|
|
385
|
+
method: 'POST',
|
|
386
|
+
path: FOLDER_SETTINGS_PATH,
|
|
387
|
+
body: { id: folderId, type: FOLDER_SETTING_TYPES_PARAM },
|
|
388
|
+
operation: action,
|
|
389
|
+
});
|
|
390
|
+
const data = asRecord(response).data;
|
|
391
|
+
return caizhilianSuccess(action, {
|
|
392
|
+
defaultVirtualFolder: Array.isArray(data) ? null : (asRecord(data).defaultVirtualFolder ?? null),
|
|
393
|
+
folderDefaultParticipants: pickListFields(asRecord(data).folderDefaultParticipants, ['id', 'content', '_entityType']),
|
|
394
|
+
currentFolder: asRecord(data).currentFolder ?? null,
|
|
395
|
+
folderDefaultShareNew: pickListFields(asRecord(data).folderDefaultShareNew, [
|
|
396
|
+
'id',
|
|
397
|
+
'content',
|
|
398
|
+
'_entityType',
|
|
399
|
+
'canDelete',
|
|
400
|
+
]),
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
// ── caizhilian.folder.list-default-shares ─────────────────
|
|
404
|
+
async function listDefaultShares(args, env) {
|
|
405
|
+
const action = 'caizhilian.folder.list-default-shares';
|
|
406
|
+
// 源入参名为 folderId;执行入口已按 deprecatedInputAliases 归一化为 canonical folder_id。
|
|
407
|
+
// schema 将该字段声明为数字 ID,运行时按同一约束校验。
|
|
408
|
+
const folderId = requireId(args.folder_id, 'folder_id');
|
|
409
|
+
const result = await fetchFolderDefaultShareEntries(env, folderId, action);
|
|
410
|
+
return caizhilianSuccess(action, { defaultShares: result.shares });
|
|
411
|
+
}
|
|
412
|
+
// ── caizhilian.folder.default-share.form ──────────────────
|
|
413
|
+
async function defaultShareForm(args, env) {
|
|
414
|
+
const action = 'caizhilian.folder.default-share.form';
|
|
415
|
+
const folderId = requireNonEmpty(args.folder_id, 'folder_id 不能为空');
|
|
416
|
+
const categoryRaw = args.category === undefined || args.category === null ? '' : String(args.category).trim();
|
|
417
|
+
if (!(categoryRaw in CATEGORY_API)) {
|
|
418
|
+
throw caizhilianValidationError('enum_invalid', `category 必须为 creator 或 generic,收到: ${pyRepr(categoryRaw)}`);
|
|
419
|
+
}
|
|
420
|
+
await checkSetFolderPermission(env, folderId, action);
|
|
421
|
+
const respData = asRecord((await queryFormConfig(env, folderId, CATEGORY_API[categoryRaw], action)).data);
|
|
422
|
+
const fieldCascadeRules = parseFieldCascadeRules(respData);
|
|
423
|
+
const { nameMap, availableIds } = parseObjTypeOptions(respData);
|
|
424
|
+
return caizhilianSuccess(action, {
|
|
425
|
+
category: categoryRaw,
|
|
426
|
+
obj_types: buildObjTypeList({
|
|
427
|
+
cascadeRulesOuter: parseCascadeRulesOuter(respData),
|
|
428
|
+
cascadeRules: parseCascadeRules(respData),
|
|
429
|
+
objTypeNames: nameMap,
|
|
430
|
+
availableIds,
|
|
431
|
+
fieldCascadeRules,
|
|
432
|
+
seclevelAllowed: parseSeclevelAllowed(respData),
|
|
433
|
+
}),
|
|
434
|
+
param_options: parseParamOptions(respData, fieldCascadeRules),
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
// ── caizhilian.folder.default-share.list ──────────────────
|
|
438
|
+
async function defaultShareList(args, env) {
|
|
439
|
+
const action = 'caizhilian.folder.default-share.list';
|
|
440
|
+
const folderId = requireNonEmpty(args.folder_id, 'folder_id 不能为空');
|
|
441
|
+
await checkSetFolderPermission(env, folderId, action);
|
|
442
|
+
const result = await fetchFolderDefaultShareEntries(env, folderId, action);
|
|
443
|
+
return caizhilianSuccess(action, { default_shares: result.shares });
|
|
444
|
+
}
|
|
445
|
+
/** 源 AddFolderSetDefaultSharePlugin.execute 的校验 + 级联规则合成 + 请求体组装。 */
|
|
446
|
+
async function planDefaultShareAdd(args, env, action) {
|
|
447
|
+
const folderId = requireNonEmpty(args.folder_id, 'folder_id 不能为空');
|
|
448
|
+
const objType = requireNonEmpty(args.obj_type, 'obj_type 不能为空');
|
|
449
|
+
const shareLevel = requireNonEmpty(args.share_level, 'share_level 不能为空');
|
|
450
|
+
const seclevelBody = parseSeclevelArgs(args);
|
|
451
|
+
await checkSetFolderPermission(env, folderId, action);
|
|
452
|
+
// 源:两个表单配置查询失败都被忽略,按空规则处理。
|
|
453
|
+
const creatorData = await tryQueryFormConfig(env, folderId, CATEGORY_API.creator, action);
|
|
454
|
+
const genericData = await tryQueryFormConfig(env, folderId, CATEGORY_API.generic, action);
|
|
455
|
+
const creatorRules = creatorData ? parseCascadeRules(asRecord(creatorData.data)) : {};
|
|
456
|
+
const genericRules = genericData ? parseCascadeRules(asRecord(genericData.data)) : {};
|
|
457
|
+
const creatorOuter = creatorData ? parseCascadeRulesOuter(asRecord(creatorData.data)) : {};
|
|
458
|
+
const genericOuter = genericData ? parseCascadeRulesOuter(asRecord(genericData.data)) : {};
|
|
459
|
+
let rule = creatorRules[objType] || genericRules[objType];
|
|
460
|
+
if (!rule) {
|
|
461
|
+
if (objType in creatorOuter || objType in genericOuter) {
|
|
462
|
+
rule = { datas: {} };
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
throw caizhilianValidationError('obj_type_not_configured', `未找到 obj_type=${pyRepr(objType)} 的配置,请检查 fetch_folder_default_share_form 返回的选项`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
const body = {
|
|
469
|
+
permissionId: 'doc_s01',
|
|
470
|
+
folderId,
|
|
471
|
+
isTemplate: false,
|
|
472
|
+
objType,
|
|
473
|
+
share_level: shareLevel,
|
|
474
|
+
seclevel: seclevelBody,
|
|
475
|
+
};
|
|
476
|
+
for (const [key, value] of Object.entries(asRecord(asRecord(rule).datas))) {
|
|
477
|
+
if (key === 'share_level' || key === 'download' || key === 'seclevel')
|
|
478
|
+
continue;
|
|
479
|
+
body[key] = value;
|
|
480
|
+
}
|
|
481
|
+
for (const key of SHARE_USER_OVERRIDES) {
|
|
482
|
+
if (key in args)
|
|
483
|
+
body[key] = args[key];
|
|
484
|
+
}
|
|
485
|
+
for (const field of SHARE_ID_FIELDS) {
|
|
486
|
+
if (!(field in args))
|
|
487
|
+
continue;
|
|
488
|
+
const value = args[field];
|
|
489
|
+
if (Array.isArray(value))
|
|
490
|
+
body[field] = value;
|
|
491
|
+
else if (typeof value === 'string' && value)
|
|
492
|
+
body[field] = [value];
|
|
493
|
+
}
|
|
494
|
+
if (shareLevel !== '1' && !('download' in body))
|
|
495
|
+
body.download = ['download'];
|
|
496
|
+
return { folderId, objType, shareLevel, body };
|
|
497
|
+
}
|
|
498
|
+
const defaultShareAddHandler = {
|
|
499
|
+
targetKeys: ['folder_id', 'obj_type', 'share_level'],
|
|
500
|
+
async prepare(args, env) {
|
|
501
|
+
const action = 'caizhilian.folder.default-share.add';
|
|
502
|
+
const plan = await planDefaultShareAdd(args, env, action);
|
|
503
|
+
const lease = await issueWriteLease(env, DEFAULT_SHARE_ADD_APPLY, args, {
|
|
504
|
+
folder_id: plan.folderId,
|
|
505
|
+
obj_type: plan.objType,
|
|
506
|
+
share_level: plan.shareLevel,
|
|
507
|
+
body_fingerprint: fingerprint(plan.body),
|
|
508
|
+
});
|
|
509
|
+
return caizhilianPrepared(action, {
|
|
510
|
+
folder_id: plan.folderId,
|
|
511
|
+
obj_type: plan.objType,
|
|
512
|
+
share_level: plan.shareLevel,
|
|
513
|
+
seclevel: plan.body.seclevel,
|
|
514
|
+
request: { method: 'POST', path: DEFAULT_SHARE_ADD_PATH, body: plan.body },
|
|
515
|
+
}, lease);
|
|
516
|
+
},
|
|
517
|
+
async apply(args, env, lease) {
|
|
518
|
+
const action = DEFAULT_SHARE_ADD_APPLY;
|
|
519
|
+
const plan = await planDefaultShareAdd(args, env, 'caizhilian.folder.default-share.add');
|
|
520
|
+
const expected = typeof lease.body_fingerprint === 'string' ? lease.body_fingerprint : '';
|
|
521
|
+
if (expected && expected !== fingerprint(plan.body)) {
|
|
522
|
+
throw caizhilianValidationError('plan_changed', `${action} 的请求体与 prepare 时不一致(表单级联规则可能已变化),请重新执行 prepare`);
|
|
523
|
+
}
|
|
524
|
+
const data = asRecord(await env.host.request({
|
|
525
|
+
method: 'POST',
|
|
526
|
+
path: DEFAULT_SHARE_ADD_PATH,
|
|
527
|
+
body: plan.body,
|
|
528
|
+
operation: action,
|
|
529
|
+
tolerateBusinessError: true,
|
|
530
|
+
}));
|
|
531
|
+
if (isAlreadyExistsResponse(data)) {
|
|
532
|
+
const message = data.message || data.msg || '已存在';
|
|
533
|
+
return caizhilianSuccess(action, { added: false, message: String(message) });
|
|
534
|
+
}
|
|
535
|
+
assertToleratedBusinessSuccess(data, action);
|
|
536
|
+
// 源:回读列表失败时仍算成功,只是列表为空。
|
|
537
|
+
let shares = [];
|
|
538
|
+
try {
|
|
539
|
+
shares = (await fetchFolderDefaultShareEntries(env, plan.folderId, action)).shares;
|
|
540
|
+
}
|
|
541
|
+
catch {
|
|
542
|
+
shares = [];
|
|
543
|
+
}
|
|
544
|
+
return caizhilianSuccess(action, { added: true, default_shares: shares });
|
|
545
|
+
},
|
|
546
|
+
};
|
|
547
|
+
function planDefaultShareDelete(args) {
|
|
548
|
+
const folderId = requireNonEmpty(args.folder_id, 'folder_id 不能为空');
|
|
549
|
+
if (isFalsyIds(args.ids))
|
|
550
|
+
throw caizhilianValidationError('field_required', 'ids 不能为空');
|
|
551
|
+
const ids = joinIds(args.ids);
|
|
552
|
+
return {
|
|
553
|
+
folderId,
|
|
554
|
+
ids,
|
|
555
|
+
body: {
|
|
556
|
+
sourceId: folderId,
|
|
557
|
+
permissionId: 'doc_s01',
|
|
558
|
+
sourceType: 1,
|
|
559
|
+
folderId,
|
|
560
|
+
isTemplate: false,
|
|
561
|
+
ids,
|
|
562
|
+
},
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
const defaultShareDeleteHandler = {
|
|
566
|
+
targetKeys: ['folder_id'],
|
|
567
|
+
async prepare(args, env) {
|
|
568
|
+
const action = 'caizhilian.folder.default-share.delete';
|
|
569
|
+
const plan = planDefaultShareDelete(args);
|
|
570
|
+
await checkSetFolderPermission(env, plan.folderId, action);
|
|
571
|
+
const lease = await issueWriteLease(env, DEFAULT_SHARE_DELETE_APPLY, args, {
|
|
572
|
+
folder_id: plan.folderId,
|
|
573
|
+
ids: plan.ids,
|
|
574
|
+
});
|
|
575
|
+
return caizhilianPrepared(action, {
|
|
576
|
+
folder_id: plan.folderId,
|
|
577
|
+
ids: plan.ids,
|
|
578
|
+
request: { method: 'POST', path: DEFAULT_SHARE_DELETE_PATH, body: plan.body },
|
|
579
|
+
}, lease);
|
|
580
|
+
},
|
|
581
|
+
async apply(args, env, _lease) {
|
|
582
|
+
const action = DEFAULT_SHARE_DELETE_APPLY;
|
|
583
|
+
const plan = planDefaultShareDelete(args);
|
|
584
|
+
await env.host.request({
|
|
585
|
+
method: 'POST',
|
|
586
|
+
path: DEFAULT_SHARE_DELETE_PATH,
|
|
587
|
+
body: plan.body,
|
|
588
|
+
operation: action,
|
|
589
|
+
});
|
|
590
|
+
let shares = [];
|
|
591
|
+
try {
|
|
592
|
+
shares = (await fetchFolderDefaultShareEntries(env, plan.folderId, action)).shares;
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
shares = [];
|
|
596
|
+
}
|
|
597
|
+
return caizhilianSuccess(action, { deleted: true, default_shares: shares });
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
// ── 文件夹文档权限设置(form / list / add / delete)────────
|
|
601
|
+
function requirePermissionType(value) {
|
|
602
|
+
const permissionType = value === undefined || value === null ? '' : String(value).trim();
|
|
603
|
+
if (!PERMISSION_TYPES.includes(permissionType)) {
|
|
604
|
+
throw caizhilianValidationError('enum_invalid', `permission_type 必须为 ${sortedJoin(PERMISSION_TYPES)} 之一,收到: ${pyRepr(permissionType)}`);
|
|
605
|
+
}
|
|
606
|
+
return permissionType;
|
|
607
|
+
}
|
|
608
|
+
/** 源 _check_doc_permission_auth:仅下载/删除权限需要先设为 user。 */
|
|
609
|
+
async function checkDocPermissionAuth(env, folderId, permissionType, action) {
|
|
610
|
+
if (!AUTH_CHECK_TYPES.includes(permissionType))
|
|
611
|
+
return;
|
|
612
|
+
const currentValue = await fetchFolderPermissionAuth(env, folderId, permissionType, action);
|
|
613
|
+
if (currentValue !== 'user') {
|
|
614
|
+
const label = AUTH_TYPE_LABELS[permissionType] ?? permissionType;
|
|
615
|
+
throw caizhilianValidationError('permission_auth_not_user', `当前${label}类型为 ${currentValue},不可操作具体人员列表。请先通过 update_folder_permission_auth 设为 user`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
async function permissionForm(args, env) {
|
|
619
|
+
const action = 'caizhilian.folder.permission.form';
|
|
620
|
+
const folderId = requireNonEmpty(args.folder_id, 'folder_id 不能为空');
|
|
621
|
+
const permissionType = requirePermissionType(args.permission_type);
|
|
622
|
+
const response = await env.host.request({
|
|
623
|
+
method: 'POST',
|
|
624
|
+
path: PERMISSION_FORM_PATH,
|
|
625
|
+
body: {
|
|
626
|
+
sourceId: folderId,
|
|
627
|
+
permissionId: 'doc_s01',
|
|
628
|
+
sourceType: 1,
|
|
629
|
+
id: folderId,
|
|
630
|
+
tableName: permissionType,
|
|
631
|
+
isTemplate: false,
|
|
632
|
+
},
|
|
633
|
+
operation: action,
|
|
634
|
+
});
|
|
635
|
+
const respData = asRecord(asRecord(response).data);
|
|
636
|
+
const fieldCascadeRules = parseFieldCascadeRules(respData);
|
|
637
|
+
const { nameMap, availableIds } = parseObjTypeOptions(respData);
|
|
638
|
+
let objTypes = buildObjTypeList({
|
|
639
|
+
cascadeRulesOuter: parseCascadeRulesOuter(respData),
|
|
640
|
+
cascadeRules: parseCascadeRules(respData),
|
|
641
|
+
objTypeNames: nameMap,
|
|
642
|
+
availableIds,
|
|
643
|
+
fieldCascadeRules,
|
|
644
|
+
permissionType,
|
|
645
|
+
});
|
|
646
|
+
// 源:默认参与人只允许 resource。
|
|
647
|
+
if (permissionType === 'folderDefaultParticipants')
|
|
648
|
+
objTypes = objTypes.filter((item) => item.id === 'resource');
|
|
649
|
+
return caizhilianSuccess(action, { obj_types: objTypes, param_options: parseParamOptions(respData, fieldCascadeRules) });
|
|
650
|
+
}
|
|
651
|
+
async function permissionList(args, env) {
|
|
652
|
+
const action = 'caizhilian.folder.permission.list';
|
|
653
|
+
const folderId = requireNonEmpty(args.folder_id, 'folder_id 不能为空');
|
|
654
|
+
const permissionType = requirePermissionType(args.permission_type);
|
|
655
|
+
await checkDocPermissionAuth(env, folderId, permissionType, action);
|
|
656
|
+
const result = await fetchFolderPermissionEntries(env, folderId, permissionType, action);
|
|
657
|
+
return caizhilianSuccess(action, { add_visible: result.add_visible, shares: result.shares });
|
|
658
|
+
}
|
|
659
|
+
/** 源 AddFolderPermissionPlugin.execute 的校验 + _build_folder_permission_add_body。 */
|
|
660
|
+
async function planPermissionAdd(args, env, action) {
|
|
661
|
+
const folderId = requireNonEmpty(args.folder_id, 'folder_id 不能为空');
|
|
662
|
+
const permissionType = requirePermissionType(args.permission_type);
|
|
663
|
+
const objType = requireNonEmpty(args.obj_type, 'obj_type 不能为空');
|
|
664
|
+
if (permissionType === 'folderDefaultParticipants' && objType !== 'resource') {
|
|
665
|
+
throw caizhilianValidationError('obj_type_not_supported', '默认参与人(folderDefaultParticipants)仅支持 obj_type=resource(人员)');
|
|
666
|
+
}
|
|
667
|
+
if ((objType === 'customer' || objType === 'customer_type') && permissionType !== 'downloadPremisstion') {
|
|
668
|
+
throw caizhilianValidationError('obj_type_not_supported', '客户和客户类型仅支持下载权限(downloadPremisstion)');
|
|
669
|
+
}
|
|
670
|
+
await checkSetFolderPermission(env, folderId, action);
|
|
671
|
+
await checkDocPermissionAuth(env, folderId, permissionType, action);
|
|
672
|
+
return {
|
|
673
|
+
folderId,
|
|
674
|
+
permissionType,
|
|
675
|
+
objType,
|
|
676
|
+
body: buildFolderPermissionAddBody(folderId, permissionType, objType, args),
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
const permissionAddHandler = {
|
|
680
|
+
targetKeys: ['folder_id', 'permission_type', 'obj_type'],
|
|
681
|
+
async prepare(args, env) {
|
|
682
|
+
const action = 'caizhilian.folder.permission.add';
|
|
683
|
+
const plan = await planPermissionAdd(args, env, action);
|
|
684
|
+
const lease = await issueWriteLease(env, PERMISSION_ADD_APPLY, args, {
|
|
685
|
+
folder_id: plan.folderId,
|
|
686
|
+
permission_type: plan.permissionType,
|
|
687
|
+
obj_type: plan.objType,
|
|
688
|
+
body_fingerprint: fingerprint(plan.body),
|
|
689
|
+
});
|
|
690
|
+
return caizhilianPrepared(action, {
|
|
691
|
+
folder_id: plan.folderId,
|
|
692
|
+
permission_type: plan.permissionType,
|
|
693
|
+
obj_type: plan.objType,
|
|
694
|
+
target_type: plan.body.targetType ?? null,
|
|
695
|
+
target_id: plan.body.targetId ?? null,
|
|
696
|
+
request: { method: 'POST', path: PERMISSION_ADD_PATH, body: plan.body },
|
|
697
|
+
}, lease);
|
|
698
|
+
},
|
|
699
|
+
async apply(args, env, lease) {
|
|
700
|
+
const action = PERMISSION_ADD_APPLY;
|
|
701
|
+
const plan = await planPermissionAdd(args, env, 'caizhilian.folder.permission.add');
|
|
702
|
+
const expected = typeof lease.body_fingerprint === 'string' ? lease.body_fingerprint : '';
|
|
703
|
+
if (expected && expected !== fingerprint(plan.body)) {
|
|
704
|
+
throw caizhilianValidationError('plan_changed', `${action} 的请求体与 prepare 时不一致,请重新执行 prepare`);
|
|
705
|
+
}
|
|
706
|
+
await env.host.request({ method: 'POST', path: PERMISSION_ADD_PATH, body: plan.body, operation: action });
|
|
707
|
+
let shares = [];
|
|
708
|
+
try {
|
|
709
|
+
shares = (await fetchFolderPermissionEntries(env, plan.folderId, plan.permissionType, action)).shares;
|
|
710
|
+
}
|
|
711
|
+
catch {
|
|
712
|
+
shares = [];
|
|
713
|
+
}
|
|
714
|
+
return caizhilianSuccess(action, { added: true, shares });
|
|
715
|
+
},
|
|
716
|
+
};
|
|
717
|
+
/** 源 DeleteFolderPermissionPlugin.execute:删除前回读列表校验条目存在。 */
|
|
718
|
+
async function planPermissionDelete(args, env, action) {
|
|
719
|
+
const folderId = requireNonEmpty(args.folder_id, 'folder_id 不能为空');
|
|
720
|
+
const permissionType = requirePermissionType(args.permission_type);
|
|
721
|
+
if (isFalsyIds(args.ids))
|
|
722
|
+
throw caizhilianValidationError('field_required', 'ids 不能为空');
|
|
723
|
+
const idList = Array.isArray(args.ids) ? args.ids.map((item) => String(item)) : [String(args.ids)];
|
|
724
|
+
await checkDocPermissionAuth(env, folderId, permissionType, action);
|
|
725
|
+
const existing = await fetchFolderPermissionEntries(env, folderId, permissionType, action);
|
|
726
|
+
const shareIds = new Set(existing.shares.map((item) => String(item.id ?? '')));
|
|
727
|
+
const notFound = idList.filter((id) => !shareIds.has(id));
|
|
728
|
+
if (notFound.length > 0) {
|
|
729
|
+
throw caizhilianValidationError('entries_not_found', `以下条目不存在: ${notFound.join(', ')}`);
|
|
730
|
+
}
|
|
731
|
+
const ids = idList.join(',');
|
|
732
|
+
return {
|
|
733
|
+
folderId,
|
|
734
|
+
permissionType,
|
|
735
|
+
idList,
|
|
736
|
+
ids,
|
|
737
|
+
body: {
|
|
738
|
+
sourceId: folderId,
|
|
739
|
+
permissionId: 'doc_s01',
|
|
740
|
+
sourceType: 1,
|
|
741
|
+
folderId,
|
|
742
|
+
docSettingType: permissionType,
|
|
743
|
+
isTemplate: false,
|
|
744
|
+
ids,
|
|
745
|
+
},
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
const permissionDeleteHandler = {
|
|
749
|
+
targetKeys: ['folder_id', 'permission_type'],
|
|
750
|
+
async prepare(args, env) {
|
|
751
|
+
const action = 'caizhilian.folder.permission.delete';
|
|
752
|
+
const plan = await planPermissionDelete(args, env, action);
|
|
753
|
+
const lease = await issueWriteLease(env, PERMISSION_DELETE_APPLY, args, {
|
|
754
|
+
folder_id: plan.folderId,
|
|
755
|
+
permission_type: plan.permissionType,
|
|
756
|
+
ids: plan.ids,
|
|
757
|
+
});
|
|
758
|
+
return caizhilianPrepared(action, {
|
|
759
|
+
folder_id: plan.folderId,
|
|
760
|
+
permission_type: plan.permissionType,
|
|
761
|
+
ids: plan.ids,
|
|
762
|
+
request: { method: 'POST', path: PERMISSION_DELETE_PATH, body: plan.body },
|
|
763
|
+
}, lease);
|
|
764
|
+
},
|
|
765
|
+
async apply(args, env, _lease) {
|
|
766
|
+
const action = PERMISSION_DELETE_APPLY;
|
|
767
|
+
const plan = await planPermissionDelete(args, env, 'caizhilian.folder.permission.delete');
|
|
768
|
+
await env.host.request({ method: 'POST', path: PERMISSION_DELETE_PATH, body: plan.body, operation: action });
|
|
769
|
+
let shares = [];
|
|
770
|
+
try {
|
|
771
|
+
shares = (await fetchFolderPermissionEntries(env, plan.folderId, plan.permissionType, action)).shares;
|
|
772
|
+
}
|
|
773
|
+
catch {
|
|
774
|
+
shares = [];
|
|
775
|
+
}
|
|
776
|
+
return caizhilianSuccess(action, { deleted: true, shares });
|
|
777
|
+
},
|
|
778
|
+
};
|
|
779
|
+
// ── caizhilian.folder.permission.get-auth / set-auth ──────
|
|
780
|
+
async function permissionGetAuth(args, env) {
|
|
781
|
+
const action = 'caizhilian.folder.permission.get-auth';
|
|
782
|
+
const folderId = requireNonEmpty(args.folder_id, 'folder_id 不能为空');
|
|
783
|
+
const permissionType = requireNonEmpty(args.permission_type, 'permission_type 不能为空');
|
|
784
|
+
const currentValue = await fetchFolderPermissionAuth(env, folderId, permissionType, action);
|
|
785
|
+
return caizhilianSuccess(action, { permission_type: permissionType, current_value: currentValue });
|
|
786
|
+
}
|
|
787
|
+
function planPermissionSetAuth(args) {
|
|
788
|
+
return {
|
|
789
|
+
folderId: requireNonEmpty(args.folder_id, 'folder_id 不能为空'),
|
|
790
|
+
permissionType: requireNonEmpty(args.permission_type, 'permission_type 不能为空'),
|
|
791
|
+
value: requireNonEmpty(args.value, 'value 不能为空'),
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
const permissionSetAuthHandler = {
|
|
795
|
+
targetKeys: ['folder_id', 'permission_type', 'value'],
|
|
796
|
+
async prepare(args, env) {
|
|
797
|
+
const action = 'caizhilian.folder.permission.set-auth';
|
|
798
|
+
const plan = planPermissionSetAuth(args);
|
|
799
|
+
await checkSetFolderPermission(env, plan.folderId, action);
|
|
800
|
+
const currentValue = await fetchFolderPermissionAuth(env, plan.folderId, plan.permissionType, action);
|
|
801
|
+
const lease = await issueWriteLease(env, PERMISSION_SET_AUTH_APPLY, args, {
|
|
802
|
+
folder_id: plan.folderId,
|
|
803
|
+
permission_type: plan.permissionType,
|
|
804
|
+
value: plan.value,
|
|
805
|
+
current_value: currentValue ?? '',
|
|
806
|
+
});
|
|
807
|
+
return caizhilianPrepared(action, {
|
|
808
|
+
folder_id: plan.folderId,
|
|
809
|
+
permission_type: plan.permissionType,
|
|
810
|
+
value: plan.value,
|
|
811
|
+
current_value: currentValue,
|
|
812
|
+
request: { method: 'POST', path: '/api/doc/folderBackstageSetting/docPermission' },
|
|
813
|
+
}, lease);
|
|
814
|
+
},
|
|
815
|
+
async apply(args, env) {
|
|
816
|
+
const action = PERMISSION_SET_AUTH_APPLY;
|
|
817
|
+
const plan = planPermissionSetAuth(args);
|
|
818
|
+
await updateFolderPermissionAuth(env, plan.folderId, plan.permissionType, plan.value, action);
|
|
819
|
+
// 源:回读失败不阻断,current_value 直接为 None。
|
|
820
|
+
let newValue = null;
|
|
821
|
+
try {
|
|
822
|
+
newValue = await fetchFolderPermissionAuth(env, plan.folderId, plan.permissionType, action);
|
|
823
|
+
}
|
|
824
|
+
catch {
|
|
825
|
+
newValue = null;
|
|
826
|
+
}
|
|
827
|
+
return caizhilianSuccess(action, {
|
|
828
|
+
permission_type: plan.permissionType,
|
|
829
|
+
value: plan.value,
|
|
830
|
+
current_value: newValue,
|
|
831
|
+
});
|
|
832
|
+
},
|
|
833
|
+
};
|
|
834
|
+
// ── caizhilian.folder.permission.detail ───────────────────
|
|
835
|
+
async function permissionDetail(args, env) {
|
|
836
|
+
const action = 'caizhilian.folder.permission.detail';
|
|
837
|
+
const folderId = requireNonEmpty(args.folder_id, 'folder_id 不能为空');
|
|
838
|
+
const settingTypeRaw = args.setting_type === undefined || args.setting_type === null ? '' : String(args.setting_type).trim();
|
|
839
|
+
if (!SETTING_TYPES.includes(settingTypeRaw)) {
|
|
840
|
+
throw caizhilianValidationError('enum_invalid', `setting_type 必须为 ${sortedJoin(SETTING_TYPES)} 之一,收到: ${pyRepr(settingTypeRaw)}`);
|
|
841
|
+
}
|
|
842
|
+
const title = DETAIL_TITLES[settingTypeRaw] ?? settingTypeRaw;
|
|
843
|
+
if (DETAIL_AUTH_TYPES.includes(settingTypeRaw)) {
|
|
844
|
+
const currentValue = await fetchFolderPermissionAuth(env, folderId, settingTypeRaw, action);
|
|
845
|
+
const labels = DETAIL_AUTH_LABELS[settingTypeRaw] ?? {};
|
|
846
|
+
const currentSetting = currentValue !== null && currentValue in labels ? labels[currentValue] : currentValue;
|
|
847
|
+
const listLabel = labels.user ?? '';
|
|
848
|
+
let items = [];
|
|
849
|
+
if (currentValue === 'user')
|
|
850
|
+
items = (await fetchFolderPermissionEntries(env, folderId, settingTypeRaw, action)).shares;
|
|
851
|
+
return caizhilianSuccess(action, { title, current_setting: currentSetting, list_label: listLabel, items });
|
|
852
|
+
}
|
|
853
|
+
if (settingTypeRaw === 'defaultShares') {
|
|
854
|
+
const result = await fetchFolderDefaultShareEntries(env, folderId, action);
|
|
855
|
+
return caizhilianSuccess(action, { title, items: result.shares });
|
|
856
|
+
}
|
|
857
|
+
const result = await fetchFolderPermissionEntries(env, folderId, settingTypeRaw, action);
|
|
858
|
+
return caizhilianSuccess(action, { title, items: result.shares });
|
|
859
|
+
}
|
|
860
|
+
// ── caizhilian.folder.permission.query ────────────────────
|
|
861
|
+
async function permissionQuery(args, env) {
|
|
862
|
+
const action = 'caizhilian.folder.permission.query';
|
|
863
|
+
const folderIds = requireIdList(args.folder_ids, 'folder_ids');
|
|
864
|
+
const permissions = await fetchFolderPermissions(env, folderIds, [...FOLDER_PERMISSION_QUERY_FIELDS], action);
|
|
865
|
+
return caizhilianSuccess(action, { permissions });
|
|
866
|
+
}
|
|
867
|
+
// ── caizhilian.folder.query-by-permission ─────────────────
|
|
868
|
+
/** 源 QueryFoldersByPermissionPlugin.execute 的 permissions 归一化与校验。 */
|
|
869
|
+
function normalizeQueryPermissions(permissionKey, permissionsRaw) {
|
|
870
|
+
const supported = QUERY_FOLDER_KEY_OBJECT_TYPES[permissionKey] ?? new Set();
|
|
871
|
+
const keyName = QUERY_FOLDER_KEY_NAMES[permissionKey] ?? permissionKey;
|
|
872
|
+
if (!Array.isArray(permissionsRaw) || permissionsRaw.length === 0) {
|
|
873
|
+
throw caizhilianValidationError('list_required', 'permissions 必须是非空数组,示例: [{"object_type": "all"}] 或 [{"object_type": "resource", "object_ids": ["123"]}]');
|
|
874
|
+
}
|
|
875
|
+
const apiPermissions = [];
|
|
876
|
+
permissionsRaw.forEach((item, index) => {
|
|
877
|
+
if (!isPlainObject(item)) {
|
|
878
|
+
throw caizhilianValidationError('input_not_object', `permissions[${index}] 必须是对象,格式: {"object_type": "...", "object_ids": [...]}`);
|
|
879
|
+
}
|
|
880
|
+
const objType = item.object_type === undefined || item.object_type === null ? '' : String(item.object_type).trim();
|
|
881
|
+
if (!objType) {
|
|
882
|
+
throw caizhilianValidationError('field_required', `permissions[${index}] 缺少必填字段 object_type`);
|
|
883
|
+
}
|
|
884
|
+
if (!QUERY_FOLDER_ALL_OBJECT_TYPES.has(objType)) {
|
|
885
|
+
throw caizhilianValidationError('enum_invalid', `permissions[${index}].object_type 不支持: ${pyRepr(objType)},可选值: ${sortedJoin(QUERY_FOLDER_ALL_OBJECT_TYPES)}`);
|
|
886
|
+
}
|
|
887
|
+
if (!supported.has(objType)) {
|
|
888
|
+
throw caizhilianValidationError('enum_invalid', `permissions[${index}].object_type=${pyRepr(objType)} 与 ${keyName}(${permissionKey})不兼容,该权限类型支持: ${sortedJoin(supported)}`);
|
|
889
|
+
}
|
|
890
|
+
const entry = { objectType: objType };
|
|
891
|
+
if (QUERY_FOLDER_REQUIRES_IDS.has(objType)) {
|
|
892
|
+
const objectIds = item.object_ids;
|
|
893
|
+
if (isFalsyIds(objectIds)) {
|
|
894
|
+
throw caizhilianValidationError('field_required', `permissions[${index}]:object_type=${pyRepr(objType)} 需要传 object_ids(具体对象 ID 列表)`);
|
|
895
|
+
}
|
|
896
|
+
if (!Array.isArray(objectIds)) {
|
|
897
|
+
throw caizhilianValidationError('list_invalid', `permissions[${index}].object_ids 必须是数组`);
|
|
898
|
+
}
|
|
899
|
+
entry.objectIds = objectIds.map((oid) => {
|
|
900
|
+
// 源用 int() 归一后按数字提交;这里必须保持字符串:E10 对象 ID 是 19 位雪花号,
|
|
901
|
+
// 超出 JS 安全整数范围(2^53),转成 number 会静默丢精度,服务端按错误 ID 匹配只会返回空。
|
|
902
|
+
const matched = /^\+?(\d+)$/.exec(oid === undefined || oid === null ? '' : String(oid).trim());
|
|
903
|
+
const digits = matched ? matched[1].replace(/^0+(?=\d)/, '') : '';
|
|
904
|
+
if (!digits || digits === '0') {
|
|
905
|
+
throw caizhilianValidationError('id_invalid', `permissions[${index}].object_ids 中的值必须为正整数,收到: ${pyRepr(oid)}`);
|
|
906
|
+
}
|
|
907
|
+
return digits;
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
apiPermissions.push(entry);
|
|
911
|
+
});
|
|
912
|
+
return apiPermissions;
|
|
913
|
+
}
|
|
914
|
+
/** 源直接用 client.execute 后自行判断 success/status,这里同样容忍业务失败再自行判定。 */
|
|
915
|
+
async function queryFolderPermissionPage(env, path, body, failureMessage, action) {
|
|
916
|
+
const data = asRecord(await env.host.request({ method: 'POST', path, body, operation: action, tolerateBusinessError: true }));
|
|
917
|
+
if (!(data.success || data.status)) {
|
|
918
|
+
throw new CaizhilianCliError('api', 'business_error', String(data.msg || data.message || failureMessage), {
|
|
919
|
+
code: data.code ?? data.status,
|
|
920
|
+
details: { action, message: String(data.msg || data.message || failureMessage) },
|
|
921
|
+
exitCode: 1,
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
return data;
|
|
925
|
+
}
|
|
926
|
+
async function queryFoldersByPermission(args, env) {
|
|
927
|
+
const action = 'caizhilian.folder.query-by-permission';
|
|
928
|
+
const permissionKey = requireNonEmpty(args.permission_key, 'permission_key 不能为空');
|
|
929
|
+
if (!FOLDER_PERMISSION_KEYS.includes(permissionKey)) {
|
|
930
|
+
throw caizhilianValidationError('enum_invalid', `permission_key 不支持: ${pyRepr(permissionKey)},可选值: ${sortedJoin(FOLDER_PERMISSION_KEYS)}`);
|
|
931
|
+
}
|
|
932
|
+
const apiPermissions = normalizeQueryPermissions(permissionKey, args.permissions);
|
|
933
|
+
const { pageNo, pageSize } = parsePage(args, 20);
|
|
934
|
+
const countData = await queryFolderPermissionPage(env, QUERY_FOLDER_COUNT_PATH, { folderPermissionKey: permissionKey, permissions: apiPermissions }, '查询文件夹数量失败', action);
|
|
935
|
+
const total = countData.data || 0;
|
|
936
|
+
const listData = await queryFolderPermissionPage(env, QUERY_FOLDER_LIST_PATH, { folderPermissionKey: permissionKey, permissions: apiPermissions, pageNo, pageSize }, '查询文件夹列表失败', action);
|
|
937
|
+
const folders = asList(listData.data).filter(isPlainObject).map((item) => ({
|
|
938
|
+
folder_id: String(item.folderId || ''),
|
|
939
|
+
folder_name: item.folderName || '',
|
|
940
|
+
folder_type: item.folderType || '',
|
|
941
|
+
}));
|
|
942
|
+
return caizhilianSuccess(action, {
|
|
943
|
+
total,
|
|
944
|
+
page_no: pageNo,
|
|
945
|
+
page_size: pageSize,
|
|
946
|
+
has_next: pageNo * pageSize < Number(total),
|
|
947
|
+
folders,
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
// ── 处理器汇总 ────────────────────────────────────────────
|
|
951
|
+
export const caizhilianFolderHandlers = {
|
|
952
|
+
'caizhilian.folder.create': createFolderHandler,
|
|
953
|
+
'caizhilian.folder.query-by-name': { execute: queryFoldersByName },
|
|
954
|
+
'caizhilian.folder.get-settings': { execute: getFolderSettings },
|
|
955
|
+
'caizhilian.folder.list-default-shares': { execute: listDefaultShares },
|
|
956
|
+
'caizhilian.folder.default-share.form': { execute: defaultShareForm },
|
|
957
|
+
'caizhilian.folder.default-share.list': { execute: defaultShareList },
|
|
958
|
+
'caizhilian.folder.default-share.add': defaultShareAddHandler,
|
|
959
|
+
'caizhilian.folder.default-share.delete': defaultShareDeleteHandler,
|
|
960
|
+
'caizhilian.folder.permission.form': { execute: permissionForm },
|
|
961
|
+
'caizhilian.folder.permission.list': { execute: permissionList },
|
|
962
|
+
'caizhilian.folder.permission.add': permissionAddHandler,
|
|
963
|
+
'caizhilian.folder.permission.delete': permissionDeleteHandler,
|
|
964
|
+
'caizhilian.folder.permission.get-auth': { execute: permissionGetAuth },
|
|
965
|
+
'caizhilian.folder.permission.set-auth': permissionSetAuthHandler,
|
|
966
|
+
'caizhilian.folder.permission.detail': { execute: permissionDetail },
|
|
967
|
+
'caizhilian.folder.permission.query': { execute: permissionQuery },
|
|
968
|
+
'caizhilian.folder.query-by-permission': { execute: queryFoldersByPermission },
|
|
969
|
+
};
|