@oj-module/cli 0.2.53 → 0.2.54
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/devkit/SKILL.md +2 -2
- package/devkit/manual.md +32 -25
- package/package.json +2 -2
- package/shell-dist/assets/runtime.js +7 -4
- package/shell-dist/index.html +3 -3
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/templates-tenant-scope.test.ts +3 -2
- package/src/config.ts +9 -0
- package/src/contract/__tests__/client-path.test.ts +30 -0
- package/src/contract/__tests__/emit-client.test.ts +18 -0
- package/src/contract/__tests__/handler-scan.test.ts +23 -0
- package/src/contract/check.ts +5 -5
- package/src/contract/client-path.ts +30 -0
- package/src/contract/emit-client.ts +18 -4
- package/src/contract/handler-scan.ts +40 -3
- package/src/contract/run.ts +5 -3
- package/src/init.ts +19 -1
- package/templates/README.md +40 -44
- package/templates/init.include.yaml +12 -8
- package/templates/openapi.json +1393 -34
- package/templates/src/_platform/api/contacts/api.ts +29 -1
- package/templates/src/_platform/api/contacts/detail/api.ts +25 -1
- package/templates/src/_platform/api/solutions/api.ts +60 -1
- package/templates/src/_platform/web/__client/api.schemas.ts +99 -35
- package/templates/src/_platform/web/__client/api.ts +103 -59
- package/templates/src/app/web/entry.ts +77 -0
- package/templates/src/{home/web → app/web/home}/__client/api.ts +5 -2
- package/templates/src/app/web/home/routes.ts +41 -0
- package/templates/src/app/web/login/routes.ts +71 -0
- package/templates/src/{notification/web → app/web/notification}/__client/api.schemas.ts +14 -0
- package/templates/src/{notification/web → app/web/notification}/__client/api.ts +67 -20
- package/templates/src/app/web/notification/routes.ts +70 -0
- package/templates/src/{personal-center/web → app/web/personal-center}/__client/api.ts +5 -2
- package/templates/src/app/web/personal-center/routes.ts +49 -0
- package/templates/src/{system/web → app/web/system}/__client/api.schemas.ts +28 -22
- package/templates/src/{system/web → app/web/system}/__client/api.ts +103 -58
- package/templates/src/{system/web → app/web/system}/pages/users/index.tsx +3 -3
- package/templates/src/{system/web → app/web/system}/pages/wecom/index.tsx +12 -3
- package/templates/src/{system/web/entry.ts → app/web/system/routes.ts} +24 -35
- package/templates/src/auth/api/api.ts +59 -0
- package/templates/src/auth/api/ldap-login/api.ts +37 -1
- package/templates/src/auth/api/wecom/api.ts +23 -0
- package/templates/src/auth/web/__client/api.schemas.ts +133 -13
- package/templates/src/auth/web/__client/api.ts +129 -31
- package/templates/src/notification/api/api.ts +17 -0
- package/templates/src/notification/api/del/api.ts +2 -0
- package/templates/src/system/api/roles/api.ts +3 -0
- package/templates/src/system/api/roles/bindings/api.ts +3 -0
- package/templates/src/system/api/settings/api.ts +3 -0
- package/templates/src/system/api/users/forbidden/api.ts +3 -0
- package/templates/src/system/api/users/password/api.ts +3 -0
- package/templates/src/system/api/users/roles/api.ts +3 -0
- package/templates/src/web/api/get-async-routes/api.ts +16 -1
- package/templates/src/web/api/hello/api.ts +13 -1
- package/templates/src/web/api/user-info/api.ts +21 -1
- package/templates/src/web/web/__client/api.schemas.ts +28 -0
- package/templates/src/web/web/__client/api.ts +33 -9
- package/templates/web.config.ts +21 -15
- package/vendor/host-versions.json +3 -3
- package/templates/src/home/web/entry.ts +0 -49
- package/templates/src/login/web/entry.ts +0 -79
- package/templates/src/notification/web/entry.ts +0 -84
- package/templates/src/personal-center/web/entry.ts +0 -60
- /package/templates/src/{home/web → app/web/home}/__client/api.schemas.ts +0 -0
- /package/templates/src/{home/web → app/web/home}/locales/en-US.json +0 -0
- /package/templates/src/{home/web → app/web/home}/locales/zh-CN.json +0 -0
- /package/templates/src/{home/web → app/web/home}/pages/components/bar-chart.tsx +0 -0
- /package/templates/src/{home/web → app/web/home}/pages/components/card-list.tsx +0 -0
- /package/templates/src/{home/web → app/web/home}/pages/components/line-chart.tsx +0 -0
- /package/templates/src/{home/web → app/web/home}/pages/components/pie-chart.tsx +0 -0
- /package/templates/src/{home/web → app/web/home}/pages/index.tsx +0 -0
- /package/templates/src/{login/web → app/web/login}/auth-provider.ts +0 -0
- /package/templates/src/{login/web → app/web/login}/locales/en-US.json +0 -0
- /package/templates/src/{login/web → app/web/login}/locales/zh-CN.json +0 -0
- /package/templates/src/{login/web → app/web/login}/pages/change-password.tsx +0 -0
- /package/templates/src/{login/web → app/web/login}/pages/login.tsx +0 -0
- /package/templates/src/{login/web → app/web/login}/wecom.ts +0 -0
- /package/templates/src/{notification/web → app/web/notification}/locales/en-US.json +0 -0
- /package/templates/src/{notification/web → app/web/notification}/locales/zh-CN.json +0 -0
- /package/templates/src/{notification/web → app/web/notification}/pages/center/index.tsx +0 -0
- /package/templates/src/{personal-center/web → app/web/personal-center}/__client/api.schemas.ts +0 -0
- /package/templates/src/{personal-center/web → app/web/personal-center}/locales/en-US.json +0 -0
- /package/templates/src/{personal-center/web → app/web/personal-center}/locales/zh-CN.json +0 -0
- /package/templates/src/{personal-center/web → app/web/personal-center}/pages/my-profile/index.tsx +0 -0
- /package/templates/src/{system/web → app/web/system}/locales/en-US.json +0 -0
- /package/templates/src/{system/web → app/web/system}/locales/zh-CN.json +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/departments/index.tsx +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/departments/sync-bar.tsx +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/departments/tree.ts +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/errors.ts +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/forbidden.tsx +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/login-methods/index.tsx +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/oplog/index.tsx +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/roles/index.tsx +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/settings/index.tsx +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/users/roles.tsx +0 -0
|
@@ -48,17 +48,6 @@ export const schemas = {
|
|
|
48
48
|
}),
|
|
49
49
|
data: z.object({
|
|
50
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
51
|
}),
|
|
63
52
|
},
|
|
64
53
|
"systemDepartmentsDetachPost": {
|
|
@@ -118,6 +107,17 @@ export const schemas = {
|
|
|
118
107
|
}),
|
|
119
108
|
data: z.object({
|
|
120
109
|
"ok": z.boolean()
|
|
110
|
+
}),
|
|
111
|
+
},
|
|
112
|
+
"systemDepartmentsMovePut": {
|
|
113
|
+
query: z.object({
|
|
114
|
+
"id": z.number()
|
|
115
|
+
}),
|
|
116
|
+
body: z.object({
|
|
117
|
+
"parentId": z.number().nullable()
|
|
118
|
+
}),
|
|
119
|
+
data: z.object({
|
|
120
|
+
"ok": z.boolean()
|
|
121
121
|
}),
|
|
122
122
|
},
|
|
123
123
|
"systemLoginMethodsGet": {
|
|
@@ -197,6 +197,7 @@ export const schemas = {
|
|
|
197
197
|
query: z.object({
|
|
198
198
|
"name": z.string().min(1)
|
|
199
199
|
}),
|
|
200
|
+
data: z.null(),
|
|
200
201
|
},
|
|
201
202
|
"systemRolesBindingsPut": {
|
|
202
203
|
query: z.object({
|
|
@@ -205,6 +206,7 @@ export const schemas = {
|
|
|
205
206
|
body: z.object({
|
|
206
207
|
"permissions": z.array(z.string())
|
|
207
208
|
}),
|
|
209
|
+
data: z.null(),
|
|
208
210
|
},
|
|
209
211
|
"systemSettingsGet": {
|
|
210
212
|
data: z.array(z.object({
|
|
@@ -228,6 +230,7 @@ export const schemas = {
|
|
|
228
230
|
"key": z.string().min(1),
|
|
229
231
|
"value": z.string()
|
|
230
232
|
}),
|
|
233
|
+
data: z.null(),
|
|
231
234
|
},
|
|
232
235
|
"systemUsersGet": {
|
|
233
236
|
data: z.array(z.object({
|
|
@@ -265,33 +268,35 @@ export const schemas = {
|
|
|
265
268
|
"ok": z.boolean()
|
|
266
269
|
}),
|
|
267
270
|
},
|
|
268
|
-
"
|
|
271
|
+
"systemUsersForbiddenPut": {
|
|
269
272
|
query: z.object({
|
|
270
273
|
"uid": z.number()
|
|
271
274
|
}),
|
|
272
275
|
body: z.object({
|
|
273
|
-
"
|
|
274
|
-
"email": z.string().nullable().optional(),
|
|
275
|
-
"position": z.string().nullable().optional()
|
|
276
|
-
}),
|
|
277
|
-
data: z.object({
|
|
278
|
-
"ok": z.boolean()
|
|
276
|
+
"forbidden": z.boolean()
|
|
279
277
|
}),
|
|
278
|
+
data: z.null(),
|
|
280
279
|
},
|
|
281
|
-
"
|
|
280
|
+
"systemUsersPasswordPut": {
|
|
282
281
|
query: z.object({
|
|
283
282
|
"uid": z.number()
|
|
284
283
|
}),
|
|
285
284
|
body: z.object({
|
|
286
|
-
"
|
|
285
|
+
"password": z.string().min(6)
|
|
287
286
|
}),
|
|
287
|
+
data: z.null(),
|
|
288
288
|
},
|
|
289
|
-
"
|
|
289
|
+
"systemUsersProfilePut": {
|
|
290
290
|
query: z.object({
|
|
291
291
|
"uid": z.number()
|
|
292
292
|
}),
|
|
293
293
|
body: z.object({
|
|
294
|
-
"
|
|
294
|
+
"displayName": z.string().nullable().optional(),
|
|
295
|
+
"email": z.string().nullable().optional(),
|
|
296
|
+
"position": z.string().nullable().optional()
|
|
297
|
+
}),
|
|
298
|
+
data: z.object({
|
|
299
|
+
"ok": z.boolean()
|
|
295
300
|
}),
|
|
296
301
|
},
|
|
297
302
|
"systemUsersRolesPut": {
|
|
@@ -301,5 +306,6 @@ export const schemas = {
|
|
|
301
306
|
body: z.object({
|
|
302
307
|
"roles": z.array(z.string())
|
|
303
308
|
}),
|
|
309
|
+
data: z.null(),
|
|
304
310
|
},
|
|
305
311
|
};
|
|
@@ -17,6 +17,9 @@ export const API_PREFIX = "/system";
|
|
|
17
17
|
/** 认领身份前缀(契约 apiPrefix,字面等于模块目录名):跨模块认领护栏对比基准 */
|
|
18
18
|
export const MODULE_PREFIX = "/system";
|
|
19
19
|
|
|
20
|
+
/** 认领方模块(web.config clientOwners 指定):跨模块认领护栏对比基准 */
|
|
21
|
+
export const MODULE_OWNER = "/app";
|
|
22
|
+
|
|
20
23
|
/**
|
|
21
24
|
* 模块 entry 的 onInit 里一行创建(构造即 install):完成 apiPrefix 登记 +
|
|
22
25
|
* scoped request 绑定,返回绑好前缀的端点函数集合。一模块一 client;
|
|
@@ -28,12 +31,12 @@ export function createSystemClient(ctx: ModuleContext) {
|
|
|
28
31
|
// 单槽、卸载互相耦合;跨模块需求应由本模块创建后经 provider 暴露。
|
|
29
32
|
// 注意对比基准是 MODULE_PREFIX(目录名)而非 API_PREFIX——D-M8 映射时线上前缀
|
|
30
33
|
// 属于别的命名空间,认领身份仍按目录名。
|
|
31
|
-
if (import.meta.env.DEV && ctx.module.name !==
|
|
32
|
-
console.warn(`[ojm-api] 模块 "${ctx.module.name}" 正在认领 ${MODULE_PREFIX} 的 client——跨模块认领会共享 request 单槽、卸载互相耦合;应由 "${
|
|
34
|
+
if (import.meta.env.DEV && ctx.module.name !== MODULE_OWNER.slice(1)) {
|
|
35
|
+
console.warn(`[ojm-api] 模块 "${ctx.module.name}" 正在认领 ${MODULE_PREFIX} 的 client——跨模块认领会共享 request 单槽、卸载互相耦合;应由 "${MODULE_OWNER.slice(1)}" 模块在自身 onInit 创建,跨模块需求经 provider 暴露。`);
|
|
33
36
|
}
|
|
34
37
|
ctx.register.apiPrefix(API_PREFIX);
|
|
35
38
|
bindRequest(ctx.utils.request);
|
|
36
|
-
return { systemDepartmentsGet, systemDepartmentsPost, systemDepartmentsPut, systemDepartmentsDelete,
|
|
39
|
+
return { systemDepartmentsGet, systemDepartmentsPost, systemDepartmentsPut, systemDepartmentsDelete, systemDepartmentsDetachPost, systemDepartmentsMembersGet, systemDepartmentsMembersPost, systemDepartmentsMembersPut, systemDepartmentsMembersDelete, systemDepartmentsMovePut, systemLoginMethodsGet, systemOplogGet, systemOplogPost, systemPermissionsGet, systemRolesGet, systemRolesPost, systemRolesDelete, systemRolesBindingsPut, systemSettingsGet, systemSettingsPut, systemUsersGet, systemUsersPost, systemUsersDelete, systemUsersForbiddenPut, systemUsersPasswordPut, systemUsersProfilePut, systemUsersRolesPut };
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
/**
|
|
@@ -160,31 +163,6 @@ export async function systemDepartmentsDelete(query: SystemDepartmentsDeleteQuer
|
|
|
160
163
|
}
|
|
161
164
|
}
|
|
162
165
|
|
|
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
166
|
export type SystemDepartmentsDetachPostQuery = z.input<(typeof schemas)["systemDepartmentsDetachPost"]["query"]>;
|
|
189
167
|
export type SystemDepartmentsDetachPostData = z.infer<(typeof schemas)["systemDepartmentsDetachPost"]["data"]>;
|
|
190
168
|
|
|
@@ -307,6 +285,31 @@ export async function systemDepartmentsMembersDelete(query: SystemDepartmentsMem
|
|
|
307
285
|
}
|
|
308
286
|
}
|
|
309
287
|
|
|
288
|
+
export type SystemDepartmentsMovePutQuery = z.input<(typeof schemas)["systemDepartmentsMovePut"]["query"]>;
|
|
289
|
+
export type SystemDepartmentsMovePutBody = z.input<(typeof schemas)["systemDepartmentsMovePut"]["body"]>;
|
|
290
|
+
export type SystemDepartmentsMovePutData = z.infer<(typeof schemas)["systemDepartmentsMovePut"]["data"]>;
|
|
291
|
+
|
|
292
|
+
export async function systemDepartmentsMovePut(input: { query: SystemDepartmentsMovePutQuery, body: SystemDepartmentsMovePutBody }): Promise<SystemDepartmentsMovePutData> {
|
|
293
|
+
const client = ensureReq();
|
|
294
|
+
try {
|
|
295
|
+
const env = await client.put(`system/departments/move`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemDepartmentsMovePutData>>();
|
|
296
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
297
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
298
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
299
|
+
const data = env.data as SystemDepartmentsMovePutData;
|
|
300
|
+
if (import.meta.env.DEV) {
|
|
301
|
+
const { schemas } = await import("./api.schemas");
|
|
302
|
+
const r = schemas["systemDepartmentsMovePut"].data.safeParse(data);
|
|
303
|
+
if (!r.success)
|
|
304
|
+
throw new ContractApiError(-1, `[契约违例] systemDepartmentsMovePut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
305
|
+
}
|
|
306
|
+
return data;
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
throw await toApiError(e);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
310
313
|
export type SystemLoginMethodsGetData = z.infer<(typeof schemas)["systemLoginMethodsGet"]["data"]>;
|
|
311
314
|
|
|
312
315
|
export async function systemLoginMethodsGet(): Promise<SystemLoginMethodsGetData> {
|
|
@@ -449,15 +452,22 @@ export async function systemRolesPost(body: SystemRolesPostBody): Promise<System
|
|
|
449
452
|
}
|
|
450
453
|
|
|
451
454
|
export type SystemRolesDeleteQuery = z.input<(typeof schemas)["systemRolesDelete"]["query"]>;
|
|
455
|
+
export type SystemRolesDeleteData = z.infer<(typeof schemas)["systemRolesDelete"]["data"]>;
|
|
452
456
|
|
|
453
|
-
export async function systemRolesDelete(query: SystemRolesDeleteQuery): Promise<
|
|
457
|
+
export async function systemRolesDelete(query: SystemRolesDeleteQuery): Promise<SystemRolesDeleteData> {
|
|
454
458
|
const client = ensureReq();
|
|
455
459
|
try {
|
|
456
|
-
const env = await client.delete(`system/roles`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<
|
|
460
|
+
const env = await client.delete(`system/roles`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<SystemRolesDeleteData>>();
|
|
457
461
|
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
458
462
|
if (typeof env.code === "number" && env.code !== 0)
|
|
459
463
|
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
460
|
-
const data = env.data as
|
|
464
|
+
const data = env.data as SystemRolesDeleteData;
|
|
465
|
+
if (import.meta.env.DEV) {
|
|
466
|
+
const { schemas } = await import("./api.schemas");
|
|
467
|
+
const r = schemas["systemRolesDelete"].data.safeParse(data);
|
|
468
|
+
if (!r.success)
|
|
469
|
+
throw new ContractApiError(-1, `[契约违例] systemRolesDelete 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
470
|
+
}
|
|
461
471
|
return data;
|
|
462
472
|
}
|
|
463
473
|
catch (e) {
|
|
@@ -467,15 +477,22 @@ export async function systemRolesDelete(query: SystemRolesDeleteQuery): Promise<
|
|
|
467
477
|
|
|
468
478
|
export type SystemRolesBindingsPutQuery = z.input<(typeof schemas)["systemRolesBindingsPut"]["query"]>;
|
|
469
479
|
export type SystemRolesBindingsPutBody = z.input<(typeof schemas)["systemRolesBindingsPut"]["body"]>;
|
|
480
|
+
export type SystemRolesBindingsPutData = z.infer<(typeof schemas)["systemRolesBindingsPut"]["data"]>;
|
|
470
481
|
|
|
471
|
-
export async function systemRolesBindingsPut(input: { query: SystemRolesBindingsPutQuery, body: SystemRolesBindingsPutBody }): Promise<
|
|
482
|
+
export async function systemRolesBindingsPut(input: { query: SystemRolesBindingsPutQuery, body: SystemRolesBindingsPutBody }): Promise<SystemRolesBindingsPutData> {
|
|
472
483
|
const client = ensureReq();
|
|
473
484
|
try {
|
|
474
|
-
const env = await client.put(`system/roles/bindings`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<
|
|
485
|
+
const env = await client.put(`system/roles/bindings`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemRolesBindingsPutData>>();
|
|
475
486
|
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
476
487
|
if (typeof env.code === "number" && env.code !== 0)
|
|
477
488
|
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
478
|
-
const data = env.data as
|
|
489
|
+
const data = env.data as SystemRolesBindingsPutData;
|
|
490
|
+
if (import.meta.env.DEV) {
|
|
491
|
+
const { schemas } = await import("./api.schemas");
|
|
492
|
+
const r = schemas["systemRolesBindingsPut"].data.safeParse(data);
|
|
493
|
+
if (!r.success)
|
|
494
|
+
throw new ContractApiError(-1, `[契约违例] systemRolesBindingsPut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
495
|
+
}
|
|
479
496
|
return data;
|
|
480
497
|
}
|
|
481
498
|
catch (e) {
|
|
@@ -507,15 +524,22 @@ export async function systemSettingsGet(): Promise<SystemSettingsGetData> {
|
|
|
507
524
|
}
|
|
508
525
|
|
|
509
526
|
export type SystemSettingsPutBody = z.input<(typeof schemas)["systemSettingsPut"]["body"]>;
|
|
527
|
+
export type SystemSettingsPutData = z.infer<(typeof schemas)["systemSettingsPut"]["data"]>;
|
|
510
528
|
|
|
511
|
-
export async function systemSettingsPut(body: SystemSettingsPutBody): Promise<
|
|
529
|
+
export async function systemSettingsPut(body: SystemSettingsPutBody): Promise<SystemSettingsPutData> {
|
|
512
530
|
const client = ensureReq();
|
|
513
531
|
try {
|
|
514
|
-
const env = await client.put(`system/settings`, { json: body }).json<OjEnvelope<
|
|
532
|
+
const env = await client.put(`system/settings`, { json: body }).json<OjEnvelope<SystemSettingsPutData>>();
|
|
515
533
|
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
516
534
|
if (typeof env.code === "number" && env.code !== 0)
|
|
517
535
|
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
518
|
-
const data = env.data as
|
|
536
|
+
const data = env.data as SystemSettingsPutData;
|
|
537
|
+
if (import.meta.env.DEV) {
|
|
538
|
+
const { schemas } = await import("./api.schemas");
|
|
539
|
+
const r = schemas["systemSettingsPut"].data.safeParse(data);
|
|
540
|
+
if (!r.success)
|
|
541
|
+
throw new ContractApiError(-1, `[契约违例] systemSettingsPut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
542
|
+
}
|
|
519
543
|
return data;
|
|
520
544
|
}
|
|
521
545
|
catch (e) {
|
|
@@ -594,23 +618,23 @@ export async function systemUsersDelete(query: SystemUsersDeleteQuery): Promise<
|
|
|
594
618
|
}
|
|
595
619
|
}
|
|
596
620
|
|
|
597
|
-
export type
|
|
598
|
-
export type
|
|
599
|
-
export type
|
|
621
|
+
export type SystemUsersForbiddenPutQuery = z.input<(typeof schemas)["systemUsersForbiddenPut"]["query"]>;
|
|
622
|
+
export type SystemUsersForbiddenPutBody = z.input<(typeof schemas)["systemUsersForbiddenPut"]["body"]>;
|
|
623
|
+
export type SystemUsersForbiddenPutData = z.infer<(typeof schemas)["systemUsersForbiddenPut"]["data"]>;
|
|
600
624
|
|
|
601
|
-
export async function
|
|
625
|
+
export async function systemUsersForbiddenPut(input: { query: SystemUsersForbiddenPutQuery, body: SystemUsersForbiddenPutBody }): Promise<SystemUsersForbiddenPutData> {
|
|
602
626
|
const client = ensureReq();
|
|
603
627
|
try {
|
|
604
|
-
const env = await client.put(`system/users/
|
|
628
|
+
const env = await client.put(`system/users/forbidden`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemUsersForbiddenPutData>>();
|
|
605
629
|
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
606
630
|
if (typeof env.code === "number" && env.code !== 0)
|
|
607
631
|
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
608
|
-
const data = env.data as
|
|
632
|
+
const data = env.data as SystemUsersForbiddenPutData;
|
|
609
633
|
if (import.meta.env.DEV) {
|
|
610
634
|
const { schemas } = await import("./api.schemas");
|
|
611
|
-
const r = schemas["
|
|
635
|
+
const r = schemas["systemUsersForbiddenPut"].data.safeParse(data);
|
|
612
636
|
if (!r.success)
|
|
613
|
-
throw new ContractApiError(-1, `[契约违例]
|
|
637
|
+
throw new ContractApiError(-1, `[契约违例] systemUsersForbiddenPut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
614
638
|
}
|
|
615
639
|
return data;
|
|
616
640
|
}
|
|
@@ -619,17 +643,24 @@ export async function systemUsersProfilePut(input: { query: SystemUsersProfilePu
|
|
|
619
643
|
}
|
|
620
644
|
}
|
|
621
645
|
|
|
622
|
-
export type
|
|
623
|
-
export type
|
|
646
|
+
export type SystemUsersPasswordPutQuery = z.input<(typeof schemas)["systemUsersPasswordPut"]["query"]>;
|
|
647
|
+
export type SystemUsersPasswordPutBody = z.input<(typeof schemas)["systemUsersPasswordPut"]["body"]>;
|
|
648
|
+
export type SystemUsersPasswordPutData = z.infer<(typeof schemas)["systemUsersPasswordPut"]["data"]>;
|
|
624
649
|
|
|
625
|
-
export async function
|
|
650
|
+
export async function systemUsersPasswordPut(input: { query: SystemUsersPasswordPutQuery, body: SystemUsersPasswordPutBody }): Promise<SystemUsersPasswordPutData> {
|
|
626
651
|
const client = ensureReq();
|
|
627
652
|
try {
|
|
628
|
-
const env = await client.put(`system/users/
|
|
653
|
+
const env = await client.put(`system/users/password`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemUsersPasswordPutData>>();
|
|
629
654
|
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
630
655
|
if (typeof env.code === "number" && env.code !== 0)
|
|
631
656
|
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
632
|
-
const data = env.data as
|
|
657
|
+
const data = env.data as SystemUsersPasswordPutData;
|
|
658
|
+
if (import.meta.env.DEV) {
|
|
659
|
+
const { schemas } = await import("./api.schemas");
|
|
660
|
+
const r = schemas["systemUsersPasswordPut"].data.safeParse(data);
|
|
661
|
+
if (!r.success)
|
|
662
|
+
throw new ContractApiError(-1, `[契约违例] systemUsersPasswordPut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
663
|
+
}
|
|
633
664
|
return data;
|
|
634
665
|
}
|
|
635
666
|
catch (e) {
|
|
@@ -637,17 +668,24 @@ export async function systemUsersForbiddenPut(input: { query: SystemUsersForbidd
|
|
|
637
668
|
}
|
|
638
669
|
}
|
|
639
670
|
|
|
640
|
-
export type
|
|
641
|
-
export type
|
|
671
|
+
export type SystemUsersProfilePutQuery = z.input<(typeof schemas)["systemUsersProfilePut"]["query"]>;
|
|
672
|
+
export type SystemUsersProfilePutBody = z.input<(typeof schemas)["systemUsersProfilePut"]["body"]>;
|
|
673
|
+
export type SystemUsersProfilePutData = z.infer<(typeof schemas)["systemUsersProfilePut"]["data"]>;
|
|
642
674
|
|
|
643
|
-
export async function
|
|
675
|
+
export async function systemUsersProfilePut(input: { query: SystemUsersProfilePutQuery, body: SystemUsersProfilePutBody }): Promise<SystemUsersProfilePutData> {
|
|
644
676
|
const client = ensureReq();
|
|
645
677
|
try {
|
|
646
|
-
const env = await client.put(`system/users/
|
|
678
|
+
const env = await client.put(`system/users/profile`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemUsersProfilePutData>>();
|
|
647
679
|
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
648
680
|
if (typeof env.code === "number" && env.code !== 0)
|
|
649
681
|
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
650
|
-
const data = env.data as
|
|
682
|
+
const data = env.data as SystemUsersProfilePutData;
|
|
683
|
+
if (import.meta.env.DEV) {
|
|
684
|
+
const { schemas } = await import("./api.schemas");
|
|
685
|
+
const r = schemas["systemUsersProfilePut"].data.safeParse(data);
|
|
686
|
+
if (!r.success)
|
|
687
|
+
throw new ContractApiError(-1, `[契约违例] systemUsersProfilePut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
688
|
+
}
|
|
651
689
|
return data;
|
|
652
690
|
}
|
|
653
691
|
catch (e) {
|
|
@@ -657,15 +695,22 @@ export async function systemUsersPasswordPut(input: { query: SystemUsersPassword
|
|
|
657
695
|
|
|
658
696
|
export type SystemUsersRolesPutQuery = z.input<(typeof schemas)["systemUsersRolesPut"]["query"]>;
|
|
659
697
|
export type SystemUsersRolesPutBody = z.input<(typeof schemas)["systemUsersRolesPut"]["body"]>;
|
|
698
|
+
export type SystemUsersRolesPutData = z.infer<(typeof schemas)["systemUsersRolesPut"]["data"]>;
|
|
660
699
|
|
|
661
|
-
export async function systemUsersRolesPut(input: { query: SystemUsersRolesPutQuery, body: SystemUsersRolesPutBody }): Promise<
|
|
700
|
+
export async function systemUsersRolesPut(input: { query: SystemUsersRolesPutQuery, body: SystemUsersRolesPutBody }): Promise<SystemUsersRolesPutData> {
|
|
662
701
|
const client = ensureReq();
|
|
663
702
|
try {
|
|
664
|
-
const env = await client.put(`system/users/roles`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<
|
|
703
|
+
const env = await client.put(`system/users/roles`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemUsersRolesPutData>>();
|
|
665
704
|
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
666
705
|
if (typeof env.code === "number" && env.code !== 0)
|
|
667
706
|
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
668
|
-
const data = env.data as
|
|
707
|
+
const data = env.data as SystemUsersRolesPutData;
|
|
708
|
+
if (import.meta.env.DEV) {
|
|
709
|
+
const { schemas } = await import("./api.schemas");
|
|
710
|
+
const r = schemas["systemUsersRolesPut"].data.safeParse(data);
|
|
711
|
+
if (!r.success)
|
|
712
|
+
throw new ContractApiError(-1, `[契约违例] systemUsersRolesPut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
713
|
+
}
|
|
669
714
|
return data;
|
|
670
715
|
}
|
|
671
716
|
catch (e) {
|
|
@@ -132,9 +132,9 @@ export default function UsersPage() {
|
|
|
132
132
|
<Button size="small" disabled={String(row.id) === me} onClick={() => setPwdTarget(row)}>
|
|
133
133
|
{t("system:users.resetPwd")}
|
|
134
134
|
</Button>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
{canManageMembership
|
|
136
|
+
? (
|
|
137
|
+
<Button size="small" type="link" onClick={() => navigate(`/system/users/${row.id}/roles`)}>
|
|
138
138
|
{t("system:users.membership")}
|
|
139
139
|
</Button>
|
|
140
140
|
)
|
|
@@ -191,7 +191,7 @@ export default function WecomAdminPage() {
|
|
|
191
191
|
queryKey: ["system", "wecom-tenant-admin", corps.map(c => c.corpId).join(",")],
|
|
192
192
|
queryFn: async () => {
|
|
193
193
|
const out: Record<string, TenantAdminState> = {};
|
|
194
|
-
await Promise.all(corps.map(async c => {
|
|
194
|
+
await Promise.all(corps.map(async (c) => {
|
|
195
195
|
out[c.corpId] = await fetchTenantAdmin(c.corpId);
|
|
196
196
|
}));
|
|
197
197
|
return out;
|
|
@@ -244,7 +244,13 @@ export default function WecomAdminPage() {
|
|
|
244
244
|
title: t("system:wecom.tenantAdmin.action"),
|
|
245
245
|
key: "action",
|
|
246
246
|
render: (_: unknown, row: { corpId: string, name: string | null }) => (
|
|
247
|
-
<Button
|
|
247
|
+
<Button
|
|
248
|
+
size="small"
|
|
249
|
+
onClick={() => {
|
|
250
|
+
setResetTarget(row);
|
|
251
|
+
setResetPwd("");
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
248
254
|
{t("system:wecom.tenantAdmin.reset")}
|
|
249
255
|
</Button>
|
|
250
256
|
),
|
|
@@ -433,7 +439,10 @@ export default function WecomAdminPage() {
|
|
|
433
439
|
<Modal
|
|
434
440
|
open={resetTarget != null}
|
|
435
441
|
title={t("system:wecom.tenantAdmin.resetTitle")}
|
|
436
|
-
onCancel={() => {
|
|
442
|
+
onCancel={() => {
|
|
443
|
+
setResetTarget(null);
|
|
444
|
+
setResetPwd("");
|
|
445
|
+
}}
|
|
437
446
|
onOk={() => resetMut.mutate({ corpId: resetTarget!.corpId, password: resetPwd })}
|
|
438
447
|
confirmLoading={resetMut.isPending}
|
|
439
448
|
okButtonProps={{ disabled: resetPwd.length < 6 }}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { AppRouteRecordRaw } from "@oj-module/runtime";
|
|
1
|
+
import type { AppRouteRecordRaw, ModuleContext } from "@oj-module/runtime";
|
|
2
2
|
|
|
3
3
|
import { SettingOutlined } from "@ant-design/icons";
|
|
4
|
-
import { defineModule } from "@oj-module/runtime";
|
|
5
4
|
|
|
6
5
|
import { createElement, lazy } from "react";
|
|
7
6
|
|
|
@@ -16,7 +15,24 @@ const Settings = lazy(() => import("./pages/settings"));
|
|
|
16
15
|
const LoginMethods = lazy(() => import("./pages/login-methods"));
|
|
17
16
|
const WecomAdmin = lazy(() => import("./pages/wecom"));
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* `/system` 根的角色门禁(对齐后端 assertSystemAdmin,access.ts:87)——
|
|
20
|
+
* superadmin **或** admin 均可,两者必须同时保留:D12(2026-10-22)起账号名=角色名,
|
|
21
|
+
* admin 与 superadmin 是两个独立账号(seed.sql),只留 ["admin"] 会把 superadmin
|
|
22
|
+
* 挡在门外(注册期过滤 → 路由消失 → 登录后 404 兜底)。改这两个值前先跑
|
|
23
|
+
* tests/system-guard.regression.spec.ts。
|
|
24
|
+
*/
|
|
25
|
+
const SYSTEM_ROLES = ["admin", "superadmin"];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 系统管理根 `/system`:框架管理面(用户/成员资格/角色权限/操作日志/站点设置/登录方式),
|
|
29
|
+
* 数据源为 oj 后端 /api/system/*(模板 api 模块)。__client 由 `ojm api` 生成。
|
|
30
|
+
*
|
|
31
|
+
* 路由级门禁(B16)挂在根上:非系统管理员整棵子树不注入、菜单不可见(等价于原先的
|
|
32
|
+
* 模块级 requiredRoles——聚合后模块级会把 /home 一起挡掉,故必须下移到根路由);
|
|
33
|
+
* API 级 403 由 assertSystemAdmin + service 层硬 enforcement 兜底。
|
|
34
|
+
*/
|
|
35
|
+
export const routes: AppRouteRecordRaw[] = [
|
|
20
36
|
{
|
|
21
37
|
path: "/system",
|
|
22
38
|
handle: {
|
|
@@ -25,6 +41,7 @@ const routes: AppRouteRecordRaw[] = [
|
|
|
25
41
|
// 负 order = 底部固定区:系统管理 pin 在侧边栏底部(2026-10-20 序轴约定)
|
|
26
42
|
order: -100,
|
|
27
43
|
layout: "container",
|
|
44
|
+
roles: SYSTEM_ROLES,
|
|
28
45
|
},
|
|
29
46
|
children: [
|
|
30
47
|
{
|
|
@@ -74,35 +91,7 @@ const routes: AppRouteRecordRaw[] = [
|
|
|
74
91
|
},
|
|
75
92
|
];
|
|
76
93
|
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
* devkit manual §2.2)。
|
|
82
|
-
*/
|
|
83
|
-
export default defineModule({
|
|
84
|
-
name: "system",
|
|
85
|
-
description: "系统管理模块(框架管理面)",
|
|
86
|
-
version: "0.1.0",
|
|
87
|
-
peerRuntime: ">=0.0.0",
|
|
88
|
-
// 路由级门禁(B16):非系统管理员不注入 /system 路由、菜单不可见;
|
|
89
|
-
// API 级 403 由 assertSystemAdmin + service 层硬 enforcement 兜底。
|
|
90
|
-
// 角色口径对齐后端 assertSystemAdmin(access.ts:87)——superadmin **或**
|
|
91
|
-
// admin 均可,两者必须同时保留:D12(2026-10-22)起账号名=角色名,admin 与
|
|
92
|
-
// superadmin 是两个独立账号(seed.sql),只留 ["admin"] 会把 superadmin 挡在
|
|
93
|
-
// 门外(注册期过滤 → 路由消失 → 登录后 404 兜底)。改这两个值前先跑
|
|
94
|
-
// tests/system-guard.regression.spec.ts(只留 ["admin"] 会立刻变红)。
|
|
95
|
-
config: { requiredRoles: ["admin", "superadmin"] },
|
|
96
|
-
routes,
|
|
97
|
-
i18n: {
|
|
98
|
-
"zh-CN": () => import("./locales/zh-CN.json"),
|
|
99
|
-
"en-US": () => import("./locales/en-US.json"),
|
|
100
|
-
},
|
|
101
|
-
lifecycle: {
|
|
102
|
-
async onInit(ctx) {
|
|
103
|
-
// 构造即接线:登记 /system 前缀 + 绑定 scoped request(D11 前缀收敛),
|
|
104
|
-
// 页面直接 import 生成的端点函数(模块级单槽 req 已由本调用绑定)。
|
|
105
|
-
createSystemClient(ctx);
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
});
|
|
94
|
+
/** 构造即接线:登记 /system 前缀 + 绑定 scoped request(D11 前缀收敛)。 */
|
|
95
|
+
export async function onInit(ctx: ModuleContext) {
|
|
96
|
+
createSystemClient(ctx);
|
|
97
|
+
}
|
|
@@ -9,6 +9,30 @@ import { createSession } from "./_shared/session";
|
|
|
9
9
|
// 模块级禁止 createSession():oj build 的 introspect 在无 jwt 的空沙箱里评估模块(§9.4),
|
|
10
10
|
// runtimeDeps() 读 g.jwt 会直接炸构建——session 在 handler 内按需构造(无状态,纯闭包)。
|
|
11
11
|
|
|
12
|
+
/** 会话票据(TokenBundle):登录 / 刷新 / 改密三处同形(D14 + AC1-27) */
|
|
13
|
+
const tokenBundle = {
|
|
14
|
+
type: "object",
|
|
15
|
+
required: ["access_token", "refresh_token", "expires_in", "restricted", "user"],
|
|
16
|
+
properties: {
|
|
17
|
+
access_token: { type: "string" },
|
|
18
|
+
refresh_token: { type: "string" },
|
|
19
|
+
expires_in: { type: "number" },
|
|
20
|
+
restricted: { type: "boolean" },
|
|
21
|
+
user: {
|
|
22
|
+
type: "object",
|
|
23
|
+
required: ["id", "username", "display_name", "avatar_base64", "must_change", "roles"],
|
|
24
|
+
properties: {
|
|
25
|
+
id: { type: "number" },
|
|
26
|
+
username: { type: "string" },
|
|
27
|
+
display_name: { type: ["string", "null"] },
|
|
28
|
+
avatar_base64: { type: ["string", "null"] },
|
|
29
|
+
must_change: { type: "boolean" },
|
|
30
|
+
roles: { type: "array", items: { type: "string" } },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
12
36
|
const login = {
|
|
13
37
|
// POST /api/auth/login —— 本地登录(D14:先验口令后查禁用,三种失败统一 401)。
|
|
14
38
|
// B5/AC1-26:(IP, username) 连败阈值 → 429 + 指数 backoff;粗粒度 per-IP 窗口防撞库扫描。
|
|
@@ -73,4 +97,39 @@ password.put.schema = {
|
|
|
73
97
|
},
|
|
74
98
|
};
|
|
75
99
|
|
|
100
|
+
// 入参:本地账号口令(两种失败统一 401,不泄露账号状态——D14)
|
|
101
|
+
login.post.schema = {
|
|
102
|
+
body: {
|
|
103
|
+
type: "object",
|
|
104
|
+
required: ["username", "password"],
|
|
105
|
+
properties: {
|
|
106
|
+
username: { type: "string", minLength: 1 },
|
|
107
|
+
password: { type: "string", minLength: 1 },
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
login.post.response = tokenBundle;
|
|
112
|
+
|
|
113
|
+
// 登出:吊销给定 refresh 串(无业务返回体)
|
|
114
|
+
logout.post.schema = {
|
|
115
|
+
body: {
|
|
116
|
+
type: "object",
|
|
117
|
+
required: ["refresh_token"],
|
|
118
|
+
properties: { refresh_token: { type: "string", minLength: 1 } },
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
logout.post.response = { type: "null" };
|
|
122
|
+
|
|
123
|
+
// 刷新:旧串轮换(reuse 检测在 service 内)
|
|
124
|
+
refresh.post.schema = {
|
|
125
|
+
body: {
|
|
126
|
+
type: "object",
|
|
127
|
+
required: ["refresh_token"],
|
|
128
|
+
properties: { refresh_token: { type: "string", minLength: 1 } },
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
refresh.post.response = tokenBundle;
|
|
132
|
+
|
|
133
|
+
password.put.response = tokenBundle;
|
|
134
|
+
|
|
76
135
|
export default { login, logout, refresh, password };
|
|
@@ -7,7 +7,7 @@ import { createSession } from "../_shared/session";
|
|
|
7
7
|
|
|
8
8
|
// 模块级禁止 createSession()/runtimeLdapGateway():oj build introspect 的空沙箱无 jwt/axis(§9.4)。
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
const handler = {
|
|
11
11
|
async post() {
|
|
12
12
|
const body = http.body || {};
|
|
13
13
|
const username = String(body.username ?? "");
|
|
@@ -27,3 +27,39 @@ export default {
|
|
|
27
27
|
});
|
|
28
28
|
},
|
|
29
29
|
};
|
|
30
|
+
|
|
31
|
+
// 入参与本地登录同形(username/password);出参同 TokenBundle
|
|
32
|
+
handler.post.schema = {
|
|
33
|
+
body: {
|
|
34
|
+
type: "object",
|
|
35
|
+
required: ["username", "password"],
|
|
36
|
+
properties: {
|
|
37
|
+
username: { type: "string", minLength: 1 },
|
|
38
|
+
password: { type: "string", minLength: 1 },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
handler.post.response = {
|
|
43
|
+
type: "object",
|
|
44
|
+
required: ["access_token", "refresh_token", "expires_in", "restricted", "user"],
|
|
45
|
+
properties: {
|
|
46
|
+
access_token: { type: "string" },
|
|
47
|
+
refresh_token: { type: "string" },
|
|
48
|
+
expires_in: { type: "number" },
|
|
49
|
+
restricted: { type: "boolean" },
|
|
50
|
+
user: {
|
|
51
|
+
type: "object",
|
|
52
|
+
required: ["id", "username", "display_name", "avatar_base64", "must_change", "roles"],
|
|
53
|
+
properties: {
|
|
54
|
+
id: { type: "number" },
|
|
55
|
+
username: { type: "string" },
|
|
56
|
+
display_name: { type: ["string", "null"] },
|
|
57
|
+
avatar_base64: { type: ["string", "null"] },
|
|
58
|
+
must_change: { type: "boolean" },
|
|
59
|
+
roles: { type: "array", items: { type: "string" } },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default handler;
|