@oj-module/cli 0.2.46 → 0.2.48

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 (97) hide show
  1. package/devkit/SKILL.md +1 -1
  2. package/devkit/framework-dev.md +2 -2
  3. package/devkit/manual.md +24 -14
  4. package/devkit/module-dev-and-cdn-playground.md +2 -2
  5. package/devkit/wecom-daikaifa.md +282 -27
  6. package/package.json +2 -2
  7. package/shell-dist/assets/runtime.js +2 -2
  8. package/shell-dist/index.html +2 -2
  9. package/shell-dist/versions.json +3 -3
  10. package/src/__tests__/templates-tenant-scope.test.ts +7 -2
  11. package/src/init.ts +1 -1
  12. package/src/market/__tests__/bootstrap.test.ts +1 -1
  13. package/src/market/__tests__/setup.test.ts +7 -7
  14. package/src/market/bootstrap.ts +1 -1
  15. package/templates/README.md +1 -1
  16. package/templates/config.yaml +26 -7
  17. package/templates/init.include.yaml +12 -0
  18. package/templates/openapi.json +2863 -549
  19. package/templates/src/_platform/api/_shared/service/domain/contacts.ts +7 -0
  20. package/templates/src/_platform/api/_shared/service/domain/org-read.ts +451 -0
  21. package/templates/src/_platform/api/_shared/service/domain/org.ts +566 -0
  22. package/templates/src/_platform/api/_shared/service/domain/users.ts +140 -2
  23. package/templates/src/_platform/api/_shared/service/infra/query.ts +2 -1
  24. package/templates/src/_platform/api/fixtures/l1-test-user.sql +1 -1
  25. package/templates/src/_platform/api/manifest.yaml +3 -0
  26. package/templates/src/_platform/api/migrations/0016__org.mysql.sql +41 -0
  27. package/templates/src/_platform/api/migrations/0016__org.pg.sql +40 -0
  28. package/templates/src/_platform/api/migrations/0016__org.sql +62 -0
  29. package/templates/src/_platform/api/org/departments/api.ts +51 -0
  30. package/templates/src/_platform/api/org/is-member/api.ts +33 -0
  31. package/templates/src/_platform/api/org/members/api.ts +55 -0
  32. package/templates/src/_platform/api/org/picker/api.ts +59 -0
  33. package/templates/src/_platform/api/org/resolve/api.ts +38 -0
  34. package/templates/src/_platform/api/org/tree/api.ts +37 -0
  35. package/templates/src/_platform/api/org/user/api.ts +46 -0
  36. package/templates/src/_platform/api/schema.yaml +48 -0
  37. package/templates/src/_platform/api/seed.mysql.sql +10 -5
  38. package/templates/src/_platform/api/seed.pg.sql +10 -5
  39. package/templates/src/_platform/api/seed.sql +13 -5
  40. package/templates/src/_platform/web/__client/api.schemas.ts +126 -0
  41. package/templates/src/_platform/web/__client/api.ts +170 -1
  42. package/templates/src/auth/api/_shared/ldap-login.ts +2 -0
  43. package/templates/src/auth/api/_shared/session.ts +5 -1
  44. package/templates/src/auth/api/_shared/wecom/admin.ts +17 -10
  45. package/templates/src/auth/api/_shared/wecom/client.ts +311 -40
  46. package/templates/src/auth/api/_shared/wecom/config.ts +40 -17
  47. package/templates/src/auth/api/_shared/wecom/contacts.ts +263 -0
  48. package/templates/src/auth/api/_shared/wecom/crypt.ts +17 -7
  49. package/templates/src/auth/api/_shared/wecom/events.ts +111 -26
  50. package/templates/src/auth/api/_shared/wecom/login.ts +152 -1
  51. package/templates/src/auth/api/_shared/wecom/payload.ts +116 -0
  52. package/templates/src/auth/api/_shared/wecom/store.ts +65 -0
  53. package/templates/src/auth/api/_shared/wecom/sync.ts +487 -0
  54. package/templates/src/auth/api/manifest.yaml +3 -0
  55. package/templates/src/auth/api/migrations/0002__org_sync.mysql.sql +28 -0
  56. package/templates/src/auth/api/migrations/0002__org_sync.pg.sql +27 -0
  57. package/templates/src/auth/api/migrations/0002__org_sync.sql +41 -0
  58. package/templates/src/auth/api/schema.yaml +31 -0
  59. package/templates/src/auth/api/wecom/admin/api.ts +7 -5
  60. package/templates/src/auth/api/wecom/api.ts +100 -18
  61. package/templates/src/auth/api/wecom/sync/api.ts +199 -0
  62. package/templates/src/auth/web/__client/api.schemas.ts +97 -0
  63. package/templates/src/auth/web/__client/api.ts +73 -1
  64. package/templates/src/login/web/auth-provider.ts +9 -1
  65. package/templates/src/notification/api/seed.sql +1 -1
  66. package/templates/src/system/api/departments/api.ts +142 -0
  67. package/templates/src/system/api/departments/detach/api.ts +34 -0
  68. package/templates/src/system/api/departments/members/api.ts +142 -0
  69. package/templates/src/system/api/departments/move/api.ts +39 -0
  70. package/templates/src/system/api/users/api.ts +45 -5
  71. package/templates/src/system/api/users/profile/api.ts +44 -0
  72. package/templates/src/system/web/__client/api.schemas.ts +143 -0
  73. package/templates/src/system/web/__client/api.ts +293 -1
  74. package/templates/src/system/web/entry.ts +6 -0
  75. package/templates/src/system/web/locales/en-US.json +113 -7
  76. package/templates/src/system/web/locales/zh-CN.json +113 -7
  77. package/templates/src/system/web/pages/departments/index.tsx +346 -0
  78. package/templates/src/system/web/pages/departments/sync-bar.tsx +257 -0
  79. package/templates/src/system/web/pages/departments/tree.ts +66 -0
  80. package/templates/src/system/web/pages/errors.ts +1 -1
  81. package/templates/src/system/web/pages/users/index.tsx +49 -7
  82. package/templates/src/system/web/pages/wecom/index.tsx +16 -1
  83. package/templates/tests/l1-builder.test.ts +1 -1
  84. package/templates/tests/org-departments.spec.ts +311 -0
  85. package/templates/tests/org-read.spec.ts +469 -0
  86. package/templates/tests/rbac-contacts.spec.ts +13 -3
  87. package/templates/tests/rbac-seed.spec.ts +11 -0
  88. package/templates/tests/seed-role-accounts.spec.ts +6 -2
  89. package/templates/tests/wecom-admin.spec.ts +64 -42
  90. package/templates/tests/wecom-client-timeout.spec.ts +99 -0
  91. package/templates/tests/wecom-connector.spec.ts +115 -11
  92. package/templates/tests/wecom-crypt.spec.ts +12 -3
  93. package/templates/tests/wecom-endpoints.spec.ts +182 -14
  94. package/templates/tests/wecom-org-sync.spec.ts +598 -0
  95. package/templates/tests/wecom-sync.spec.ts +286 -0
  96. package/vendor/host-versions.json +3 -3
  97. package/templates/src/auth/api/_shared/wecom/xml.ts +0 -36
@@ -33,7 +33,7 @@ export function createSystemClient(ctx: ModuleContext) {
33
33
  }
34
34
  ctx.register.apiPrefix(API_PREFIX);
35
35
  bindRequest(ctx.utils.request);
36
- return { systemLoginMethodsGet, systemOplogGet, systemOplogPost, systemPermissionsGet, systemRolesGet, systemRolesPost, systemRolesDelete, systemRolesBindingsPut, systemSettingsGet, systemSettingsPut, systemUsersGet, systemUsersPost, systemUsersForbiddenPut, systemUsersPasswordPut, systemUsersRolesPut };
36
+ return { systemDepartmentsGet, systemDepartmentsPost, systemDepartmentsPut, systemDepartmentsDelete, systemDepartmentsMovePut, systemDepartmentsDetachPost, systemDepartmentsMembersGet, systemDepartmentsMembersPost, systemDepartmentsMembersPut, systemDepartmentsMembersDelete, systemLoginMethodsGet, systemOplogGet, systemOplogPost, systemPermissionsGet, systemRolesGet, systemRolesPost, systemRolesDelete, systemRolesBindingsPut, systemSettingsGet, systemSettingsPut, systemUsersGet, systemUsersPost, systemUsersDelete, systemUsersProfilePut, systemUsersForbiddenPut, systemUsersPasswordPut, systemUsersRolesPut };
37
37
  }
38
38
 
39
39
  /**
@@ -64,6 +64,249 @@ async function toApiError(e: unknown): Promise<unknown> {
64
64
  return e;
65
65
  }
66
66
 
67
+ export type SystemDepartmentsGetData = z.infer<(typeof schemas)["systemDepartmentsGet"]["data"]>;
68
+
69
+ export async function systemDepartmentsGet(): Promise<SystemDepartmentsGetData> {
70
+ const client = ensureReq();
71
+ try {
72
+ const env = await client.get(`system/departments`).json<OjEnvelope<SystemDepartmentsGetData>>();
73
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
74
+ if (typeof env.code === "number" && env.code !== 0)
75
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
76
+ const data = env.data as SystemDepartmentsGetData;
77
+ if (import.meta.env.DEV) {
78
+ const { schemas } = await import("./api.schemas");
79
+ const r = schemas["systemDepartmentsGet"].data.safeParse(data);
80
+ if (!r.success)
81
+ throw new ContractApiError(-1, `[契约违例] systemDepartmentsGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
82
+ }
83
+ return data;
84
+ }
85
+ catch (e) {
86
+ throw await toApiError(e);
87
+ }
88
+ }
89
+
90
+ export type SystemDepartmentsPostBody = z.input<(typeof schemas)["systemDepartmentsPost"]["body"]>;
91
+ export type SystemDepartmentsPostData = z.infer<(typeof schemas)["systemDepartmentsPost"]["data"]>;
92
+
93
+ export async function systemDepartmentsPost(body: SystemDepartmentsPostBody): Promise<SystemDepartmentsPostData> {
94
+ const client = ensureReq();
95
+ try {
96
+ const env = await client.post(`system/departments`, { json: body }).json<OjEnvelope<SystemDepartmentsPostData>>();
97
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
98
+ if (typeof env.code === "number" && env.code !== 0)
99
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
100
+ const data = env.data as SystemDepartmentsPostData;
101
+ if (import.meta.env.DEV) {
102
+ const { schemas } = await import("./api.schemas");
103
+ const r = schemas["systemDepartmentsPost"].data.safeParse(data);
104
+ if (!r.success)
105
+ throw new ContractApiError(-1, `[契约违例] systemDepartmentsPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
106
+ }
107
+ return data;
108
+ }
109
+ catch (e) {
110
+ throw await toApiError(e);
111
+ }
112
+ }
113
+
114
+ export type SystemDepartmentsPutQuery = z.input<(typeof schemas)["systemDepartmentsPut"]["query"]>;
115
+ export type SystemDepartmentsPutBody = z.input<(typeof schemas)["systemDepartmentsPut"]["body"]>;
116
+ export type SystemDepartmentsPutData = z.infer<(typeof schemas)["systemDepartmentsPut"]["data"]>;
117
+
118
+ export async function systemDepartmentsPut(input: { query: SystemDepartmentsPutQuery, body: SystemDepartmentsPutBody }): Promise<SystemDepartmentsPutData> {
119
+ const client = ensureReq();
120
+ try {
121
+ const env = await client.put(`system/departments`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemDepartmentsPutData>>();
122
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
123
+ if (typeof env.code === "number" && env.code !== 0)
124
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
125
+ const data = env.data as SystemDepartmentsPutData;
126
+ if (import.meta.env.DEV) {
127
+ const { schemas } = await import("./api.schemas");
128
+ const r = schemas["systemDepartmentsPut"].data.safeParse(data);
129
+ if (!r.success)
130
+ throw new ContractApiError(-1, `[契约违例] systemDepartmentsPut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
131
+ }
132
+ return data;
133
+ }
134
+ catch (e) {
135
+ throw await toApiError(e);
136
+ }
137
+ }
138
+
139
+ export type SystemDepartmentsDeleteQuery = z.input<(typeof schemas)["systemDepartmentsDelete"]["query"]>;
140
+ export type SystemDepartmentsDeleteData = z.infer<(typeof schemas)["systemDepartmentsDelete"]["data"]>;
141
+
142
+ export async function systemDepartmentsDelete(query: SystemDepartmentsDeleteQuery): Promise<SystemDepartmentsDeleteData> {
143
+ const client = ensureReq();
144
+ try {
145
+ const env = await client.delete(`system/departments`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<SystemDepartmentsDeleteData>>();
146
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
147
+ if (typeof env.code === "number" && env.code !== 0)
148
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
149
+ const data = env.data as SystemDepartmentsDeleteData;
150
+ if (import.meta.env.DEV) {
151
+ const { schemas } = await import("./api.schemas");
152
+ const r = schemas["systemDepartmentsDelete"].data.safeParse(data);
153
+ if (!r.success)
154
+ throw new ContractApiError(-1, `[契约违例] systemDepartmentsDelete 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
155
+ }
156
+ return data;
157
+ }
158
+ catch (e) {
159
+ throw await toApiError(e);
160
+ }
161
+ }
162
+
163
+ export type SystemDepartmentsMovePutQuery = z.input<(typeof schemas)["systemDepartmentsMovePut"]["query"]>;
164
+ export type SystemDepartmentsMovePutBody = z.input<(typeof schemas)["systemDepartmentsMovePut"]["body"]>;
165
+ export type SystemDepartmentsMovePutData = z.infer<(typeof schemas)["systemDepartmentsMovePut"]["data"]>;
166
+
167
+ export async function systemDepartmentsMovePut(input: { query: SystemDepartmentsMovePutQuery, body: SystemDepartmentsMovePutBody }): Promise<SystemDepartmentsMovePutData> {
168
+ const client = ensureReq();
169
+ try {
170
+ const env = await client.put(`system/departments/move`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemDepartmentsMovePutData>>();
171
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
172
+ if (typeof env.code === "number" && env.code !== 0)
173
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
174
+ const data = env.data as SystemDepartmentsMovePutData;
175
+ if (import.meta.env.DEV) {
176
+ const { schemas } = await import("./api.schemas");
177
+ const r = schemas["systemDepartmentsMovePut"].data.safeParse(data);
178
+ if (!r.success)
179
+ throw new ContractApiError(-1, `[契约违例] systemDepartmentsMovePut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
180
+ }
181
+ return data;
182
+ }
183
+ catch (e) {
184
+ throw await toApiError(e);
185
+ }
186
+ }
187
+
188
+ export type SystemDepartmentsDetachPostQuery = z.input<(typeof schemas)["systemDepartmentsDetachPost"]["query"]>;
189
+ export type SystemDepartmentsDetachPostData = z.infer<(typeof schemas)["systemDepartmentsDetachPost"]["data"]>;
190
+
191
+ export async function systemDepartmentsDetachPost(query: SystemDepartmentsDetachPostQuery): Promise<SystemDepartmentsDetachPostData> {
192
+ const client = ensureReq();
193
+ try {
194
+ const env = await client.post(`system/departments/detach`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<SystemDepartmentsDetachPostData>>();
195
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
196
+ if (typeof env.code === "number" && env.code !== 0)
197
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
198
+ const data = env.data as SystemDepartmentsDetachPostData;
199
+ if (import.meta.env.DEV) {
200
+ const { schemas } = await import("./api.schemas");
201
+ const r = schemas["systemDepartmentsDetachPost"].data.safeParse(data);
202
+ if (!r.success)
203
+ throw new ContractApiError(-1, `[契约违例] systemDepartmentsDetachPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
204
+ }
205
+ return data;
206
+ }
207
+ catch (e) {
208
+ throw await toApiError(e);
209
+ }
210
+ }
211
+
212
+ export type SystemDepartmentsMembersGetQuery = z.input<(typeof schemas)["systemDepartmentsMembersGet"]["query"]>;
213
+ export type SystemDepartmentsMembersGetData = z.infer<(typeof schemas)["systemDepartmentsMembersGet"]["data"]>;
214
+
215
+ export async function systemDepartmentsMembersGet(query: SystemDepartmentsMembersGetQuery): Promise<SystemDepartmentsMembersGetData> {
216
+ const client = ensureReq();
217
+ try {
218
+ const env = await client.get(`system/departments/members`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<SystemDepartmentsMembersGetData>>();
219
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
220
+ if (typeof env.code === "number" && env.code !== 0)
221
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
222
+ const data = env.data as SystemDepartmentsMembersGetData;
223
+ if (import.meta.env.DEV) {
224
+ const { schemas } = await import("./api.schemas");
225
+ const r = schemas["systemDepartmentsMembersGet"].data.safeParse(data);
226
+ if (!r.success)
227
+ throw new ContractApiError(-1, `[契约违例] systemDepartmentsMembersGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
228
+ }
229
+ return data;
230
+ }
231
+ catch (e) {
232
+ throw await toApiError(e);
233
+ }
234
+ }
235
+
236
+ export type SystemDepartmentsMembersPostQuery = z.input<(typeof schemas)["systemDepartmentsMembersPost"]["query"]>;
237
+ export type SystemDepartmentsMembersPostBody = z.input<(typeof schemas)["systemDepartmentsMembersPost"]["body"]>;
238
+ export type SystemDepartmentsMembersPostData = z.infer<(typeof schemas)["systemDepartmentsMembersPost"]["data"]>;
239
+
240
+ export async function systemDepartmentsMembersPost(input: { query: SystemDepartmentsMembersPostQuery, body: SystemDepartmentsMembersPostBody }): Promise<SystemDepartmentsMembersPostData> {
241
+ const client = ensureReq();
242
+ try {
243
+ const env = await client.post(`system/departments/members`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemDepartmentsMembersPostData>>();
244
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
245
+ if (typeof env.code === "number" && env.code !== 0)
246
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
247
+ const data = env.data as SystemDepartmentsMembersPostData;
248
+ if (import.meta.env.DEV) {
249
+ const { schemas } = await import("./api.schemas");
250
+ const r = schemas["systemDepartmentsMembersPost"].data.safeParse(data);
251
+ if (!r.success)
252
+ throw new ContractApiError(-1, `[契约违例] systemDepartmentsMembersPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
253
+ }
254
+ return data;
255
+ }
256
+ catch (e) {
257
+ throw await toApiError(e);
258
+ }
259
+ }
260
+
261
+ export type SystemDepartmentsMembersPutQuery = z.input<(typeof schemas)["systemDepartmentsMembersPut"]["query"]>;
262
+ export type SystemDepartmentsMembersPutBody = z.input<(typeof schemas)["systemDepartmentsMembersPut"]["body"]>;
263
+ export type SystemDepartmentsMembersPutData = z.infer<(typeof schemas)["systemDepartmentsMembersPut"]["data"]>;
264
+
265
+ export async function systemDepartmentsMembersPut(input: { query: SystemDepartmentsMembersPutQuery, body: SystemDepartmentsMembersPutBody }): Promise<SystemDepartmentsMembersPutData> {
266
+ const client = ensureReq();
267
+ try {
268
+ const env = await client.put(`system/departments/members`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemDepartmentsMembersPutData>>();
269
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
270
+ if (typeof env.code === "number" && env.code !== 0)
271
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
272
+ const data = env.data as SystemDepartmentsMembersPutData;
273
+ if (import.meta.env.DEV) {
274
+ const { schemas } = await import("./api.schemas");
275
+ const r = schemas["systemDepartmentsMembersPut"].data.safeParse(data);
276
+ if (!r.success)
277
+ throw new ContractApiError(-1, `[契约违例] systemDepartmentsMembersPut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
278
+ }
279
+ return data;
280
+ }
281
+ catch (e) {
282
+ throw await toApiError(e);
283
+ }
284
+ }
285
+
286
+ export type SystemDepartmentsMembersDeleteQuery = z.input<(typeof schemas)["systemDepartmentsMembersDelete"]["query"]>;
287
+ export type SystemDepartmentsMembersDeleteData = z.infer<(typeof schemas)["systemDepartmentsMembersDelete"]["data"]>;
288
+
289
+ export async function systemDepartmentsMembersDelete(query: SystemDepartmentsMembersDeleteQuery): Promise<SystemDepartmentsMembersDeleteData> {
290
+ const client = ensureReq();
291
+ try {
292
+ const env = await client.delete(`system/departments/members`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<SystemDepartmentsMembersDeleteData>>();
293
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
294
+ if (typeof env.code === "number" && env.code !== 0)
295
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
296
+ const data = env.data as SystemDepartmentsMembersDeleteData;
297
+ if (import.meta.env.DEV) {
298
+ const { schemas } = await import("./api.schemas");
299
+ const r = schemas["systemDepartmentsMembersDelete"].data.safeParse(data);
300
+ if (!r.success)
301
+ throw new ContractApiError(-1, `[契约违例] systemDepartmentsMembersDelete 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
302
+ }
303
+ return data;
304
+ }
305
+ catch (e) {
306
+ throw await toApiError(e);
307
+ }
308
+ }
309
+
67
310
  export type SystemLoginMethodsGetData = z.infer<(typeof schemas)["systemLoginMethodsGet"]["data"]>;
68
311
 
69
312
  export async function systemLoginMethodsGet(): Promise<SystemLoginMethodsGetData> {
@@ -327,6 +570,55 @@ export async function systemUsersPost(body: SystemUsersPostBody): Promise<System
327
570
  }
328
571
  }
329
572
 
573
+ export type SystemUsersDeleteQuery = z.input<(typeof schemas)["systemUsersDelete"]["query"]>;
574
+ export type SystemUsersDeleteData = z.infer<(typeof schemas)["systemUsersDelete"]["data"]>;
575
+
576
+ export async function systemUsersDelete(query: SystemUsersDeleteQuery): Promise<SystemUsersDeleteData> {
577
+ const client = ensureReq();
578
+ try {
579
+ const env = await client.delete(`system/users`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<SystemUsersDeleteData>>();
580
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
581
+ if (typeof env.code === "number" && env.code !== 0)
582
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
583
+ const data = env.data as SystemUsersDeleteData;
584
+ if (import.meta.env.DEV) {
585
+ const { schemas } = await import("./api.schemas");
586
+ const r = schemas["systemUsersDelete"].data.safeParse(data);
587
+ if (!r.success)
588
+ throw new ContractApiError(-1, `[契约违例] systemUsersDelete 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
589
+ }
590
+ return data;
591
+ }
592
+ catch (e) {
593
+ throw await toApiError(e);
594
+ }
595
+ }
596
+
597
+ export type SystemUsersProfilePutQuery = z.input<(typeof schemas)["systemUsersProfilePut"]["query"]>;
598
+ export type SystemUsersProfilePutBody = z.input<(typeof schemas)["systemUsersProfilePut"]["body"]>;
599
+ export type SystemUsersProfilePutData = z.infer<(typeof schemas)["systemUsersProfilePut"]["data"]>;
600
+
601
+ export async function systemUsersProfilePut(input: { query: SystemUsersProfilePutQuery, body: SystemUsersProfilePutBody }): Promise<SystemUsersProfilePutData> {
602
+ const client = ensureReq();
603
+ try {
604
+ const env = await client.put(`system/users/profile`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemUsersProfilePutData>>();
605
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
606
+ if (typeof env.code === "number" && env.code !== 0)
607
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
608
+ const data = env.data as SystemUsersProfilePutData;
609
+ if (import.meta.env.DEV) {
610
+ const { schemas } = await import("./api.schemas");
611
+ const r = schemas["systemUsersProfilePut"].data.safeParse(data);
612
+ if (!r.success)
613
+ throw new ContractApiError(-1, `[契约违例] systemUsersProfilePut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
614
+ }
615
+ return data;
616
+ }
617
+ catch (e) {
618
+ throw await toApiError(e);
619
+ }
620
+ }
621
+
330
622
  export type SystemUsersForbiddenPutQuery = z.input<(typeof schemas)["systemUsersForbiddenPut"]["query"]>;
331
623
  export type SystemUsersForbiddenPutBody = z.input<(typeof schemas)["systemUsersForbiddenPut"]["body"]>;
332
624
 
@@ -9,6 +9,7 @@ import { createSystemClient } from "./__client/api";
9
9
 
10
10
  const Users = lazy(() => import("./pages/users"));
11
11
  const UserRoles = lazy(() => import("./pages/users/roles"));
12
+ const Departments = lazy(() => import("./pages/departments"));
12
13
  const Roles = lazy(() => import("./pages/roles"));
13
14
  const Oplog = lazy(() => import("./pages/oplog"));
14
15
  const Settings = lazy(() => import("./pages/settings"));
@@ -37,6 +38,11 @@ const routes: AppRouteRecordRaw[] = [
37
38
  Component: UserRoles,
38
39
  handle: { title: "system:menu.userRoles", hideInMenu: true },
39
40
  },
41
+ {
42
+ path: "/system/departments",
43
+ Component: Departments,
44
+ handle: { title: "system:menu.departments" },
45
+ },
40
46
  {
41
47
  path: "/system/roles",
42
48
  Component: Roles,
@@ -7,7 +7,8 @@
7
7
  "oplog": "Audit Log",
8
8
  "settings": "Site Settings",
9
9
  "loginMethods": "Login Methods",
10
- "wecom": "WeCom Integration"
10
+ "wecom": "WeCom Integration",
11
+ "departments": "Departments"
11
12
  },
12
13
  "common": {
13
14
  "confirm": "Confirm",
@@ -16,7 +17,9 @@
16
17
  "saved": "Saved",
17
18
  "search": "Search",
18
19
  "reset": "Reset",
19
- "create": "Create"
20
+ "create": "Create",
21
+ "edit": "Edit",
22
+ "delete": "Delete"
20
23
  },
21
24
  "users": {
22
25
  "create": "Create User",
@@ -32,8 +35,12 @@
32
35
  "email": "Email",
33
36
  "roles": "Roles",
34
37
  "status": "Account status (forbidden / password change)",
35
- "actions": "Actions"
36
- }
38
+ "actions": "Actions",
39
+ "position": "Position",
40
+ "departments": "Departments"
41
+ },
42
+ "mainDept": "Main dept",
43
+ "filterDept": "Filter by department (incl. sub-depts)"
37
44
  },
38
45
  "userRoles": {
39
46
  "title": "Membership",
@@ -50,6 +57,46 @@
50
57
  "titleKey": "Title Key",
51
58
  "readonlyHint": "Read-only view: roles and bindings are managed by solution tooling (upsertSolutionPerms)"
52
59
  },
60
+ "departments": {
61
+ "create": "New Department",
62
+ "edit": "Edit Department",
63
+ "view": "View",
64
+ "createHint": "Org structure is shared baseline data; WeCom/LDAP are only adapters writing into it",
65
+ "members": "Members",
66
+ "memberCount": "Members",
67
+ "detach": "Manage locally",
68
+ "detachHint": "Once detached, sync no longer rewrites this department — rename/move/delete all open up",
69
+ "readonlyHint": "This department is maintained by an external source; structural fields are read-only",
70
+ "unnamed": "(unnamed)",
71
+ "hidden": "Out of visible scope",
72
+ "removed": "Deleted at source",
73
+ "root": "(top level)",
74
+ "selectUser": "Select user",
75
+ "addMember": "Add member",
76
+ "removeMember": "Remove",
77
+ "noMembers": "No members",
78
+ "confirmDelete": "Delete this department? Its member links are removed too",
79
+ "confirmRemoveMember": "Remove this user from the department?",
80
+ "yes": "Yes",
81
+ "no": "No",
82
+ "origin": {
83
+ "local": "Local",
84
+ "wecom": "WeCom",
85
+ "ldap": "LDAP",
86
+ "sso": "SSO"
87
+ },
88
+ "columns": {
89
+ "name": "Name",
90
+ "nameEn": "English name",
91
+ "parent": "Parent"
92
+ },
93
+ "memberColumns": {
94
+ "user": "User",
95
+ "main": "Main",
96
+ "leader": "Leader",
97
+ "actions": "Actions"
98
+ }
99
+ },
53
100
  "oplog": {
54
101
  "filterAction": "Action",
55
102
  "filterUsername": "Username (prefix)",
@@ -79,7 +126,7 @@
79
126
  },
80
127
  "wecom": {
81
128
  "title": "Onboard a corporation",
82
- "hint": "Click \"Generate install link\" and send the link or QR code to the corporation's admin for approval. The callback on the corporation side writes the row into this table after approval. The link expires in 10 minutes and works once; the QR code greys out when it does, so regenerate it.",
129
+ "hint": "Click \"Generate install link\" and send the link or QR code to the corporation's admin to scan/open for approval. On success this system auto-registers the corporation via the create_auth callback (writes wecom_app_profile). The link is generated by WeCom get_customized_auth_url; its validity follows WeCom's response (up to 10 days). The QR code greys out when it expires, so regenerate it.",
83
130
  "appsTitle": "Onboarded corporations",
84
131
  "genInstall": "Generate install link",
85
132
  "installOk": "Generated. Send the link to the corporation's admin.",
@@ -93,7 +140,8 @@
93
140
  "env": "Env",
94
141
  "status": "Status",
95
142
  "installedAt": "Onboarded at",
96
- "action": "Actions"
143
+ "action": "Actions",
144
+ "authMode": "Auth mode"
97
145
  },
98
146
  "statusActive": "Active",
99
147
  "statusDisabled": "Disabled",
@@ -103,7 +151,61 @@
103
151
  "enable": "Enable",
104
152
  "disableConfirm": "Disable this app? Members of this corporation will no longer be able to sign in with it.",
105
153
  "enableConfirm": "Enable this app?",
106
- "empty": "No corporations onboarded yet"
154
+ "empty": "No corporations onboarded yet",
155
+ "authModeAdmin": "Admin-authorized",
156
+ "authModeMember": "Member-authorized",
157
+ "authModeHint": "In member-authorized mode the app has no contacts permission, so this corporation's org structure cannot be synced. The corporation admin must switch it to admin-authorized in the WeCom console and re-authorize.",
158
+ "sync": {
159
+ "updatedAt": "WeCom org structure updated {{time}}",
160
+ "neverSynced": "WeCom org structure has never been synced",
161
+ "auto": "auto sync",
162
+ "syncNow": "Sync now",
163
+ "retry": "Retry",
164
+ "resume": "Resume sync",
165
+ "cancel": "Cancel sync",
166
+ "syncing": "Reading WeCom org structure…",
167
+ "syncingProgress": "Syncing: {{done}}/{{total}} departments processed",
168
+ "done": "Sync finished: {{depts}} departments / {{members}} members",
169
+ "paused": "Sync paused ({{depts}} departments processed). Click \"Resume sync\" to continue.",
170
+ "skipped": "{{count}} departments skipped due to permission (normal for departments outside the visible scope)",
171
+ "cancelled": "Sync cancelled",
172
+ "ago": {
173
+ "justNow": "just now",
174
+ "minutes": "{{count}} minutes ago",
175
+ "hours": "{{count}} hours ago",
176
+ "days": "{{count}} days ago"
177
+ },
178
+ "memberAuth": {
179
+ "title": "This corporation uses member-authorized mode: the app has no contacts permission",
180
+ "hint": "Ask the corporation admin to switch to admin-authorized mode in the WeCom console and re-authorize; then sync becomes available."
181
+ },
182
+ "frozen": {
183
+ "title": "Authorization was revoked or the app is disabled: sync is frozen",
184
+ "hint": "The data below is the snapshot from the last successful sync. Ask the service provider to re-onboard the corporation."
185
+ },
186
+ "err": {
187
+ "permission": {
188
+ "title": "The app lacks contacts permission (error {{code}})",
189
+ "hint": "Corporation admin: WeCom console → App management → this app → Permissions, enable the contacts scopes and submit."
190
+ },
191
+ "rateLimited": {
192
+ "title": "WeCom API rate limited (error {{code}})",
193
+ "hint": "Backoff already applied. Click \"Resume sync\" in a moment."
194
+ },
195
+ "ipWhitelist": {
196
+ "title": "Server egress IP is not whitelisted by WeCom (error {{code}})",
197
+ "hint": "The service provider must add the trusted IP in the WeCom console."
198
+ },
199
+ "network": {
200
+ "title": "Cannot reach WeCom ({{code}})",
201
+ "hint": "Check server network access to WeCom and retry."
202
+ },
203
+ "unknown": {
204
+ "title": "Sync failed (error {{code}})",
205
+ "hint": "Contact the service provider to investigate, then retry."
206
+ }
207
+ }
208
+ }
107
209
  },
108
210
  "err": {
109
211
  "forbidden": "Forbidden",
@@ -125,5 +227,9 @@
125
227
  "ldap_base_dn": "LDAP Base DN",
126
228
  "ldap_uid_attr": "LDAP UID Attribute",
127
229
  "ldap_email_attr": "LDAP Email Attribute"
230
+ },
231
+ "perm": {
232
+ "platform_org_read_all": "Org directory: read all",
233
+ "auth_wecom_sync": "WeCom org sync"
128
234
  }
129
235
  }