@oj-module/cli 0.2.47 → 0.2.49

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.
Files changed (101) hide show
  1. package/devkit/README.md +1 -1
  2. package/devkit/SKILL.md +23 -4
  3. package/devkit/framework-dev.md +2 -2
  4. package/devkit/manual.md +221 -14
  5. package/devkit/module-dev-and-cdn-playground.md +2 -2
  6. package/devkit/wecom-daikaifa.md +242 -14
  7. package/package.json +2 -2
  8. package/shell-dist/assets/runtime.js +2 -2
  9. package/shell-dist/index.html +2 -2
  10. package/shell-dist/versions.json +3 -3
  11. package/src/__tests__/build-backend.test.ts +80 -0
  12. package/src/__tests__/templates-tenant-scope.test.ts +7 -2
  13. package/src/build.ts +50 -0
  14. package/src/init.ts +1 -1
  15. package/src/market/__tests__/bootstrap.test.ts +1 -1
  16. package/src/market/__tests__/setup.test.ts +7 -7
  17. package/src/market/bootstrap.ts +1 -1
  18. package/templates/README.md +1 -1
  19. package/templates/init.include.yaml +12 -0
  20. package/templates/openapi.json +2863 -549
  21. package/templates/src/_platform/api/_shared/service/domain/contacts.ts +7 -0
  22. package/templates/src/_platform/api/_shared/service/domain/org-read.ts +451 -0
  23. package/templates/src/_platform/api/_shared/service/domain/org.ts +566 -0
  24. package/templates/src/_platform/api/_shared/service/domain/users.ts +140 -2
  25. package/templates/src/_platform/api/_shared/service/infra/query.ts +2 -1
  26. package/templates/src/_platform/api/fixtures/l1-test-user.sql +1 -1
  27. package/templates/src/_platform/api/manifest.yaml +3 -0
  28. package/templates/src/_platform/api/migrations/0016__org.mysql.sql +41 -0
  29. package/templates/src/_platform/api/migrations/0016__org.pg.sql +40 -0
  30. package/templates/src/_platform/api/migrations/0016__org.sql +62 -0
  31. package/templates/src/_platform/api/org/departments/api.ts +51 -0
  32. package/templates/src/_platform/api/org/is-member/api.ts +33 -0
  33. package/templates/src/_platform/api/org/members/api.ts +55 -0
  34. package/templates/src/_platform/api/org/picker/api.ts +59 -0
  35. package/templates/src/_platform/api/org/resolve/api.ts +38 -0
  36. package/templates/src/_platform/api/org/tree/api.ts +37 -0
  37. package/templates/src/_platform/api/org/user/api.ts +46 -0
  38. package/templates/src/_platform/api/schema.yaml +48 -0
  39. package/templates/src/_platform/api/seed.mysql.sql +10 -5
  40. package/templates/src/_platform/api/seed.pg.sql +10 -5
  41. package/templates/src/_platform/api/seed.sql +13 -5
  42. package/templates/src/_platform/web/__client/api.schemas.ts +126 -0
  43. package/templates/src/_platform/web/__client/api.ts +170 -1
  44. package/templates/src/auth/api/_shared/ldap-login.ts +2 -0
  45. package/templates/src/auth/api/_shared/session.ts +5 -1
  46. package/templates/src/auth/api/_shared/wecom/admin.ts +3 -1
  47. package/templates/src/auth/api/_shared/wecom/client.ts +280 -32
  48. package/templates/src/auth/api/_shared/wecom/config.ts +12 -1
  49. package/templates/src/auth/api/_shared/wecom/contacts.ts +263 -0
  50. package/templates/src/auth/api/_shared/wecom/crypt.ts +17 -7
  51. package/templates/src/auth/api/_shared/wecom/events.ts +111 -26
  52. package/templates/src/auth/api/_shared/wecom/login.ts +188 -5
  53. package/templates/src/auth/api/_shared/wecom/payload.ts +116 -0
  54. package/templates/src/auth/api/_shared/wecom/replay.ts +44 -3
  55. package/templates/src/auth/api/_shared/wecom/store.ts +65 -0
  56. package/templates/src/auth/api/_shared/wecom/sync.ts +501 -0
  57. package/templates/src/auth/api/manifest.yaml +3 -0
  58. package/templates/src/auth/api/migrations/0002__org_sync.mysql.sql +28 -0
  59. package/templates/src/auth/api/migrations/0002__org_sync.pg.sql +27 -0
  60. package/templates/src/auth/api/migrations/0002__org_sync.sql +41 -0
  61. package/templates/src/auth/api/schema.yaml +31 -0
  62. package/templates/src/auth/api/wecom/admin/api.ts +3 -1
  63. package/templates/src/auth/api/wecom/api.ts +155 -35
  64. package/templates/src/auth/api/wecom/sync/api.ts +199 -0
  65. package/templates/src/auth/web/__client/api.schemas.ts +97 -0
  66. package/templates/src/auth/web/__client/api.ts +73 -1
  67. package/templates/src/login/web/locales/en-US.json +2 -0
  68. package/templates/src/login/web/locales/zh-CN.json +2 -0
  69. package/templates/src/login/web/pages/login.tsx +39 -3
  70. package/templates/src/notification/api/seed.sql +1 -1
  71. package/templates/src/system/api/departments/api.ts +142 -0
  72. package/templates/src/system/api/departments/detach/api.ts +34 -0
  73. package/templates/src/system/api/departments/members/api.ts +142 -0
  74. package/templates/src/system/api/departments/move/api.ts +39 -0
  75. package/templates/src/system/api/users/api.ts +45 -5
  76. package/templates/src/system/api/users/profile/api.ts +44 -0
  77. package/templates/src/system/web/__client/api.schemas.ts +143 -0
  78. package/templates/src/system/web/__client/api.ts +293 -1
  79. package/templates/src/system/web/entry.ts +6 -0
  80. package/templates/src/system/web/locales/en-US.json +112 -6
  81. package/templates/src/system/web/locales/zh-CN.json +112 -6
  82. package/templates/src/system/web/pages/departments/index.tsx +346 -0
  83. package/templates/src/system/web/pages/departments/sync-bar.tsx +257 -0
  84. package/templates/src/system/web/pages/departments/tree.ts +66 -0
  85. package/templates/src/system/web/pages/errors.ts +1 -1
  86. package/templates/src/system/web/pages/users/index.tsx +49 -7
  87. package/templates/src/system/web/pages/wecom/index.tsx +14 -0
  88. package/templates/tests/l1-builder.test.ts +1 -1
  89. package/templates/tests/org-departments.spec.ts +311 -0
  90. package/templates/tests/org-read.spec.ts +469 -0
  91. package/templates/tests/rbac-contacts.spec.ts +13 -3
  92. package/templates/tests/rbac-seed.spec.ts +11 -0
  93. package/templates/tests/seed-role-accounts.spec.ts +6 -2
  94. package/templates/tests/wecom-admin.spec.ts +1 -0
  95. package/templates/tests/wecom-connector.spec.ts +146 -10
  96. package/templates/tests/wecom-crypt.spec.ts +12 -3
  97. package/templates/tests/wecom-endpoints.spec.ts +247 -19
  98. package/templates/tests/wecom-org-sync.spec.ts +598 -0
  99. package/templates/tests/wecom-sync.spec.ts +286 -0
  100. package/vendor/host-versions.json +3 -3
  101. package/templates/src/auth/api/_shared/wecom/xml.ts +0 -36
@@ -0,0 +1,142 @@
1
+ import type { OrgDeps } from "#/_platform/_shared/service/domain/org";
2
+ import { respond } from "#/_platform/_shared/respond";
3
+ import {
4
+ addMember,
5
+ listMembers,
6
+
7
+ removeMember,
8
+ updateMember,
9
+ } from "#/_platform/_shared/service/domain/org";
10
+ // /api/system/departments/members?id= —— 部门成员的增删查改(方案 A2)。
11
+ //
12
+ // 加人**不看部门的 origin**:「谁在这个部门」本站始终可调,即便部门本身由企微同步而来;
13
+ // 受 D13 限制的只有部门的**结构字段**(名称/层级/删除),两者不是一回事。
14
+ import { assertSystemAdmin } from "#/_platform/_shared/service/infra/access";
15
+
16
+ const orgDeps: OrgDeps = { now: () => Math.floor(Date.now() / 1000) };
17
+
18
+ const MEMBER_ITEM = {
19
+ type: "object",
20
+ required: ["userId", "deptId", "orderNo", "isLeader", "isMain", "username"],
21
+ properties: {
22
+ userId: { type: "number" },
23
+ deptId: { type: "number" },
24
+ orderNo: { type: "number" },
25
+ isLeader: { type: "boolean" },
26
+ isMain: { type: "boolean" },
27
+ username: { type: "string" },
28
+ displayName: { type: ["string", "null"] },
29
+ },
30
+ } as const;
31
+
32
+ // GET /api/system/departments/members?id= —— 成员列表(含 users 展示字段)
33
+ async function get() {
34
+ await respond(async () => {
35
+ await assertSystemAdmin();
36
+ return listMembers(Number((http.query as Record<string, unknown>).id));
37
+ });
38
+ }
39
+ get.schema = {
40
+ query: {
41
+ type: "object",
42
+ required: ["id"],
43
+ properties: { id: { type: "number" } },
44
+ },
45
+ };
46
+ get.response = { type: "array", items: MEMBER_ITEM };
47
+
48
+ // POST /api/system/departments/members?id= —— 加人
49
+ async function post() {
50
+ await respond(async () => {
51
+ await assertSystemAdmin();
52
+ const q = http.query as Record<string, unknown>;
53
+ const b = (http.body || {}) as { userId?: number, isLeader?: boolean, isMain?: boolean, orderNo?: number };
54
+ await addMember(Number(q.id), Number(b.userId), {
55
+ isLeader: b.isLeader,
56
+ isMain: b.isMain,
57
+ orderNo: b.orderNo,
58
+ }, orgDeps);
59
+ return { ok: true };
60
+ });
61
+ }
62
+ post.schema = {
63
+ query: {
64
+ type: "object",
65
+ required: ["id"],
66
+ properties: { id: { type: "number" } },
67
+ },
68
+ body: {
69
+ type: "object",
70
+ required: ["userId"],
71
+ properties: {
72
+ userId: { type: "number" },
73
+ isLeader: { type: "boolean" },
74
+ isMain: { type: "boolean" },
75
+ orderNo: { type: "number" },
76
+ },
77
+ },
78
+ };
79
+ post.response = {
80
+ type: "object",
81
+ required: ["ok"],
82
+ properties: { ok: { type: "boolean" } },
83
+ };
84
+
85
+ // PUT /api/system/departments/members?id=&userId= —— 改成员属性(负责人/主部门/排序)
86
+ async function put() {
87
+ await respond(async () => {
88
+ await assertSystemAdmin();
89
+ const q = http.query as Record<string, unknown>;
90
+ const b = (http.body || {}) as { isLeader?: boolean, isMain?: boolean, orderNo?: number };
91
+ await updateMember(Number(q.id), Number(q.userId), {
92
+ isLeader: b.isLeader,
93
+ isMain: b.isMain,
94
+ orderNo: b.orderNo,
95
+ }, orgDeps);
96
+ return { ok: true };
97
+ });
98
+ }
99
+ put.schema = {
100
+ query: {
101
+ type: "object",
102
+ required: ["id", "userId"],
103
+ properties: { id: { type: "number" }, userId: { type: "number" } },
104
+ },
105
+ body: {
106
+ type: "object",
107
+ properties: {
108
+ isLeader: { type: "boolean" },
109
+ isMain: { type: "boolean" },
110
+ orderNo: { type: "number" },
111
+ },
112
+ },
113
+ };
114
+ put.response = {
115
+ type: "object",
116
+ required: ["ok"],
117
+ properties: { ok: { type: "boolean" } },
118
+ };
119
+
120
+ // DELETE /api/system/departments/members?id=&userId= —— 移出部门
121
+ async function del() {
122
+ await respond(async () => {
123
+ await assertSystemAdmin();
124
+ const q = http.query as Record<string, unknown>;
125
+ await removeMember(Number(q.id), Number(q.userId), orgDeps);
126
+ return { ok: true };
127
+ });
128
+ }
129
+ del.schema = {
130
+ query: {
131
+ type: "object",
132
+ required: ["id", "userId"],
133
+ properties: { id: { type: "number" }, userId: { type: "number" } },
134
+ },
135
+ };
136
+ del.response = {
137
+ type: "object",
138
+ required: ["ok"],
139
+ properties: { ok: { type: "boolean" } },
140
+ };
141
+
142
+ export default { get, post, put, del };
@@ -0,0 +1,39 @@
1
+ import type { OrgDeps } from "#/_platform/_shared/service/domain/org";
2
+ import { respond } from "#/_platform/_shared/respond";
3
+ import { moveDepartment } from "#/_platform/_shared/service/domain/org";
4
+ // PUT /api/system/departments/move?id= —— 改父级(拖拽排序的数据出口)。
5
+ // parentId=null 即移到根下;自挂与「挂到自己的子孙」由领域层判环后 400。
6
+ import { assertSystemAdmin } from "#/_platform/_shared/service/infra/access";
7
+
8
+ const orgDeps: OrgDeps = { now: () => Math.floor(Date.now() / 1000) };
9
+
10
+ async function put() {
11
+ await respond(async () => {
12
+ await assertSystemAdmin();
13
+ const q = http.query as Record<string, unknown>;
14
+ const b = http.body || {};
15
+ const raw = (b as { parentId?: number | null }).parentId;
16
+ await moveDepartment(Number(q.id), raw == null ? null : Number(raw), orgDeps);
17
+ return { ok: true };
18
+ });
19
+ }
20
+ put.schema = {
21
+ query: {
22
+ type: "object",
23
+ required: ["id"],
24
+ properties: { id: { type: "number" } },
25
+ },
26
+ body: {
27
+ type: "object",
28
+ required: ["parentId"],
29
+ // 显式 null = 提到根;不给这个键按 schema 视为未提供
30
+ properties: { parentId: { type: ["number", "null"] } },
31
+ },
32
+ };
33
+ put.response = {
34
+ type: "object",
35
+ required: ["ok"],
36
+ properties: { ok: { type: "boolean" } },
37
+ };
38
+
39
+ export default { put };
@@ -1,5 +1,5 @@
1
1
  import { respond } from "#/_platform/_shared/respond";
2
- import { createUser, listUsers } from "#/_platform/_shared/service/domain/users";
2
+ import { createUser, deleteUser, listUsers } from "#/_platform/_shared/service/domain/users";
3
3
  // 管理域端点组 /api/system/users(AC1-7~10):handler 只编排——assertSystemAdmin 守卫 → service → json。
4
4
  // 保护红线在 service 层硬 enforcement:禁止操作自己、admin 不碰成员资格、最后 superadmin 兜底。
5
5
  // 子端点:roles/password/forbidden 各为独立目录(wiki per-endpoint 先例)。
@@ -18,12 +18,26 @@ get.response = {
18
18
  type: "array",
19
19
  items: {
20
20
  type: "object",
21
- required: ["id", "username", "display_name", "email", "is_forbidden", "must_change", "roles"],
21
+ required: ["id", "username", "display_name", "email", "position", "departments", "is_forbidden", "must_change", "roles"],
22
22
  properties: {
23
23
  id: { type: "number" },
24
24
  username: { type: "string" },
25
25
  display_name: { type: ["string", "null"] },
26
26
  email: { type: ["string", "null"] },
27
+ position: { type: ["string", "null"] },
28
+ // 所属部门(主部门在前);未加入任何部门即空数组。带 id 供前端按部门筛选
29
+ departments: {
30
+ type: "array",
31
+ items: {
32
+ type: "object",
33
+ required: ["id", "name", "isMain"],
34
+ properties: {
35
+ id: { type: "number" },
36
+ name: { type: ["string", "null"] },
37
+ isMain: { type: "boolean" },
38
+ },
39
+ },
40
+ },
27
41
  // service 已做 Number(x)===1 布尔归一,此处是真 boolean
28
42
  is_forbidden: { type: "boolean" },
29
43
  must_change: { type: "boolean" },
@@ -33,9 +47,13 @@ get.response = {
33
47
  };
34
48
 
35
49
  // POST /api/system/users —— 建号(B6 加固;默认 must_change=1 首登改密)。
50
+ // 必须走 respond 收口:createUser 的业务拒绝是 `fail(400, …)`,不收口会被 oj 一律抹成
51
+ // 500(实测:重名建号返回 500「username exists」,前端拿不到可展示的 400 文案)。
36
52
  async function post() {
37
- await assertSystemAdmin();
38
- json.ok({ id: await createUser(http.body || {}, bcryptDeps) });
53
+ await respond(async () => {
54
+ await assertSystemAdmin();
55
+ return { id: await createUser(http.body || {}, bcryptDeps) };
56
+ });
39
57
  }
40
58
  post.schema = {
41
59
  body: {
@@ -55,4 +73,26 @@ post.response = {
55
73
  properties: { id: { type: "number" } },
56
74
  };
57
75
 
58
- export default { get, post };
76
+ // DELETE /api/system/users?uid= —— 删号。级联清部门成员/角色/会话,最后 superadmin 拒删。
77
+ async function del() {
78
+ await respond(async () => {
79
+ await assertSystemAdmin();
80
+ const q = http.query as Record<string, unknown>;
81
+ await deleteUser(Number(http.user?.id), Number(q.uid));
82
+ return { ok: true };
83
+ });
84
+ }
85
+ del.schema = {
86
+ query: {
87
+ type: "object",
88
+ required: ["uid"],
89
+ properties: { uid: { type: "number" } },
90
+ },
91
+ };
92
+ del.response = {
93
+ type: "object",
94
+ required: ["ok"],
95
+ properties: { ok: { type: "boolean" } },
96
+ };
97
+
98
+ export default { get, post, del };
@@ -0,0 +1,44 @@
1
+ import { respond } from "#/_platform/_shared/respond";
2
+ import { updateUserProfile } from "#/_platform/_shared/service/domain/users";
3
+ // PUT /api/system/users/profile?uid= —— 改资料(显示名 / 邮箱 / 职务)。
4
+ //
5
+ // 用户名**不在可改之列**:它是外部身份的主键(`wecom:<userid>` 这类),改名会让下一次
6
+ // 登录认错人(B6 命名空间抢占,见 RESERVED_USERNAME_PREFIX)。启停走 users/forbidden、
7
+ // 改密走 users/password——那两者带「级联删 refresh」的副作用,不混进通用改资料。
8
+ import { assertSystemAdmin } from "#/_platform/_shared/service/infra/access";
9
+
10
+ async function put() {
11
+ await respond(async () => {
12
+ await assertSystemAdmin();
13
+ const q = http.query as Record<string, unknown>;
14
+ const b = (http.body || {}) as { displayName?: string | null, email?: string | null, position?: string | null };
15
+ await updateUserProfile(Number(http.user?.id), Number(q.uid), {
16
+ displayName: b.displayName,
17
+ email: b.email,
18
+ position: b.position,
19
+ });
20
+ return { ok: true };
21
+ });
22
+ }
23
+ put.schema = {
24
+ query: {
25
+ type: "object",
26
+ required: ["uid"],
27
+ properties: { uid: { type: "number" } },
28
+ },
29
+ body: {
30
+ type: "object",
31
+ properties: {
32
+ displayName: { type: ["string", "null"] },
33
+ email: { type: ["string", "null"] },
34
+ position: { type: ["string", "null"] },
35
+ },
36
+ },
37
+ };
38
+ put.response = {
39
+ type: "object",
40
+ required: ["ok"],
41
+ properties: { ok: { type: "boolean" } },
42
+ };
43
+
44
+ export default { put };
@@ -4,6 +4,122 @@
4
4
  import { z } from "@oj-module/runtime";
5
5
 
6
6
  export const schemas = {
7
+ "systemDepartmentsGet": {
8
+ data: z.array(z.object({
9
+ "id": z.number(),
10
+ "origin": z.string(),
11
+ "originId": z.string(),
12
+ "parentId": z.number().nullable(),
13
+ "name": z.string().nullable(),
14
+ "nameEn": z.string().nullable().optional(),
15
+ "orderNo": z.number(),
16
+ "syncVisible": z.boolean(),
17
+ "deleted": z.boolean(),
18
+ "memberCount": z.number()
19
+ })),
20
+ },
21
+ "systemDepartmentsPost": {
22
+ body: z.object({
23
+ "name": z.string().min(1),
24
+ "parentId": z.number().nullable().optional(),
25
+ "nameEn": z.string().nullable().optional(),
26
+ "orderNo": z.number().optional()
27
+ }),
28
+ data: z.object({
29
+ "id": z.number()
30
+ }),
31
+ },
32
+ "systemDepartmentsPut": {
33
+ query: z.object({
34
+ "id": z.number()
35
+ }),
36
+ body: z.object({
37
+ "name": z.string().min(1).optional(),
38
+ "nameEn": z.string().nullable().optional(),
39
+ "orderNo": z.number().optional()
40
+ }),
41
+ data: z.object({
42
+ "ok": z.boolean()
43
+ }),
44
+ },
45
+ "systemDepartmentsDelete": {
46
+ query: z.object({
47
+ "id": z.number()
48
+ }),
49
+ data: z.object({
50
+ "ok": z.boolean()
51
+ }),
52
+ },
53
+ "systemDepartmentsMovePut": {
54
+ query: z.object({
55
+ "id": z.number()
56
+ }),
57
+ body: z.object({
58
+ "parentId": z.number().nullable()
59
+ }),
60
+ data: z.object({
61
+ "ok": z.boolean()
62
+ }),
63
+ },
64
+ "systemDepartmentsDetachPost": {
65
+ query: z.object({
66
+ "id": z.number()
67
+ }),
68
+ data: z.object({
69
+ "ok": z.boolean()
70
+ }),
71
+ },
72
+ "systemDepartmentsMembersGet": {
73
+ query: z.object({
74
+ "id": z.number()
75
+ }),
76
+ data: z.array(z.object({
77
+ "userId": z.number(),
78
+ "deptId": z.number(),
79
+ "orderNo": z.number(),
80
+ "isLeader": z.boolean(),
81
+ "isMain": z.boolean(),
82
+ "username": z.string(),
83
+ "displayName": z.string().nullable().optional()
84
+ })),
85
+ },
86
+ "systemDepartmentsMembersPost": {
87
+ query: z.object({
88
+ "id": z.number()
89
+ }),
90
+ body: z.object({
91
+ "userId": z.number(),
92
+ "isLeader": z.boolean().optional(),
93
+ "isMain": z.boolean().optional(),
94
+ "orderNo": z.number().optional()
95
+ }),
96
+ data: z.object({
97
+ "ok": z.boolean()
98
+ }),
99
+ },
100
+ "systemDepartmentsMembersPut": {
101
+ query: z.object({
102
+ "id": z.number(),
103
+ "userId": z.number()
104
+ }),
105
+ body: z.object({
106
+ "isLeader": z.boolean().optional(),
107
+ "isMain": z.boolean().optional(),
108
+ "orderNo": z.number().optional()
109
+ }),
110
+ data: z.object({
111
+ "ok": z.boolean()
112
+ }),
113
+ },
114
+ "systemDepartmentsMembersDelete": {
115
+ query: z.object({
116
+ "id": z.number(),
117
+ "userId": z.number()
118
+ }),
119
+ data: z.object({
120
+ "ok": z.boolean()
121
+ }),
122
+ },
7
123
  "systemLoginMethodsGet": {
8
124
  data: z.object({
9
125
  "methods": z.array(z.string())
@@ -119,6 +235,12 @@ export const schemas = {
119
235
  "username": z.string(),
120
236
  "display_name": z.string().nullable(),
121
237
  "email": z.string().nullable(),
238
+ "position": z.string().nullable(),
239
+ "departments": z.array(z.object({
240
+ "id": z.number(),
241
+ "name": z.string().nullable(),
242
+ "isMain": z.boolean()
243
+ })),
122
244
  "is_forbidden": z.boolean(),
123
245
  "must_change": z.boolean(),
124
246
  "roles": z.array(z.string())
@@ -133,6 +255,27 @@ export const schemas = {
133
255
  }),
134
256
  data: z.object({
135
257
  "id": z.number()
258
+ }),
259
+ },
260
+ "systemUsersDelete": {
261
+ query: z.object({
262
+ "uid": z.number()
263
+ }),
264
+ data: z.object({
265
+ "ok": z.boolean()
266
+ }),
267
+ },
268
+ "systemUsersProfilePut": {
269
+ query: z.object({
270
+ "uid": z.number()
271
+ }),
272
+ body: z.object({
273
+ "displayName": z.string().nullable().optional(),
274
+ "email": z.string().nullable().optional(),
275
+ "position": z.string().nullable().optional()
276
+ }),
277
+ data: z.object({
278
+ "ok": z.boolean()
136
279
  }),
137
280
  },
138
281
  "systemUsersForbiddenPut": {