@oj-module/cli 0.2.50 → 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.
Files changed (130) hide show
  1. package/devkit/SKILL.md +2 -2
  2. package/devkit/manual.md +32 -25
  3. package/devkit/wecom-internal-app.md +111 -4
  4. package/package.json +2 -2
  5. package/shell-dist/assets/{index-FYt9CjFd.js → index-e_Mp2mb5.js} +5 -1
  6. package/shell-dist/assets/runtime.js +29 -5
  7. package/shell-dist/index.html +9 -12
  8. package/shell-dist/versions.json +3 -3
  9. package/src/__tests__/templates-tenant-scope.test.ts +3 -2
  10. package/src/build.ts +19 -2
  11. package/src/config.ts +9 -0
  12. package/src/contract/__tests__/client-path.test.ts +30 -0
  13. package/src/contract/__tests__/emit-client.test.ts +18 -0
  14. package/src/contract/__tests__/handler-scan.test.ts +23 -0
  15. package/src/contract/check.ts +5 -5
  16. package/src/contract/client-path.ts +30 -0
  17. package/src/contract/emit-client.ts +18 -4
  18. package/src/contract/handler-scan.ts +40 -3
  19. package/src/contract/run.ts +5 -3
  20. package/src/init.ts +19 -1
  21. package/src/shared-deps.ts +27 -9
  22. package/src/site-base.ts +1 -1
  23. package/templates/README.md +40 -44
  24. package/templates/init.include.yaml +15 -14
  25. package/templates/openapi.json +3422 -1955
  26. package/templates/src/_platform/api/_shared/service/domain/users.ts +40 -4
  27. package/templates/src/_platform/api/contacts/api.ts +29 -1
  28. package/templates/src/_platform/api/contacts/detail/api.ts +25 -1
  29. package/templates/src/_platform/api/solutions/api.ts +60 -1
  30. package/templates/src/_platform/web/__client/api.schemas.ts +99 -35
  31. package/templates/src/_platform/web/__client/api.ts +103 -59
  32. package/templates/src/app/web/entry.ts +77 -0
  33. package/templates/src/{home/web → app/web/home}/__client/api.ts +5 -2
  34. package/templates/src/app/web/home/routes.ts +41 -0
  35. package/templates/src/{login/web → app/web/login}/pages/login.tsx +54 -24
  36. package/templates/src/app/web/login/routes.ts +71 -0
  37. package/templates/src/{notification/web → app/web/notification}/__client/api.schemas.ts +14 -0
  38. package/templates/src/{notification/web → app/web/notification}/__client/api.ts +67 -20
  39. package/templates/src/app/web/notification/routes.ts +70 -0
  40. package/templates/src/{personal-center/web → app/web/personal-center}/__client/api.ts +5 -2
  41. package/templates/src/app/web/personal-center/routes.ts +49 -0
  42. package/templates/src/{system/web → app/web/system}/__client/api.schemas.ts +28 -22
  43. package/templates/src/{system/web → app/web/system}/__client/api.ts +103 -58
  44. package/templates/src/{system/web → app/web/system}/locales/en-US.json +14 -0
  45. package/templates/src/{system/web → app/web/system}/locales/zh-CN.json +14 -0
  46. package/templates/src/{system/web → app/web/system}/pages/users/index.tsx +5 -2
  47. package/templates/src/{system/web → app/web/system}/pages/users/roles.tsx +14 -3
  48. package/templates/src/{system/web → app/web/system}/pages/wecom/index.tsx +139 -1
  49. package/templates/src/app/web/system/routes.ts +97 -0
  50. package/templates/src/auth/api/_shared/wecom/admin.ts +4 -0
  51. package/templates/src/auth/api/_shared/wecom/client.ts +9 -2
  52. package/templates/src/auth/api/_shared/wecom/events.ts +3 -0
  53. package/templates/src/auth/api/_shared/wecom/tenant-admin.ts +194 -0
  54. package/templates/src/auth/api/api.ts +59 -0
  55. package/templates/src/auth/api/ldap-login/api.ts +37 -1
  56. package/templates/src/auth/api/wecom/admin/tenant-admin/api.ts +41 -0
  57. package/templates/src/auth/api/wecom/admin/tenant-admin/password/api.ts +66 -0
  58. package/templates/src/auth/api/wecom/api.ts +23 -0
  59. package/templates/src/auth/web/__client/api.schemas.ts +133 -13
  60. package/templates/src/auth/web/__client/api.ts +129 -31
  61. package/templates/src/notification/api/api.ts +17 -0
  62. package/templates/src/notification/api/del/api.ts +2 -0
  63. package/templates/src/system/api/roles/api.ts +3 -0
  64. package/templates/src/system/api/roles/bindings/api.ts +3 -0
  65. package/templates/src/system/api/settings/api.ts +6 -3
  66. package/templates/src/system/api/users/forbidden/api.ts +3 -0
  67. package/templates/src/system/api/users/password/api.ts +3 -0
  68. package/templates/src/system/api/users/roles/api.ts +3 -0
  69. package/templates/src/web/api/get-async-routes/api.ts +16 -1
  70. package/templates/src/web/api/hello/api.ts +13 -1
  71. package/templates/src/web/api/user-info/api.ts +21 -1
  72. package/templates/src/web/web/__client/api.schemas.ts +28 -0
  73. package/templates/src/web/web/__client/api.ts +33 -9
  74. package/templates/tsconfig.paths.json +0 -1
  75. package/templates/web.config.ts +22 -17
  76. package/vendor/host-versions.json +3 -3
  77. package/shell-dist/assets/echarts-charts.js +0 -58571
  78. package/shell-dist/assets/echarts-features.js +0 -24057
  79. package/shell-dist/assets/echarts-for-react.js +0 -704
  80. package/shell-dist/assets/echarts.js +0 -82916
  81. package/shell-dist/assets/editor.js +0 -161457
  82. package/shell-dist/assets/vditor-index-css.js +0 -7
  83. package/shell-dist/assets/vditor.js +0 -15223
  84. package/templates/src/demo/api/manifest.yaml +0 -5
  85. package/templates/src/demo/api/migrations/0001__create_todos.sql +0 -9
  86. package/templates/src/demo/api/migrations/0002__tenant.mysql.sql +0 -2
  87. package/templates/src/demo/api/migrations/0002__tenant.sql +0 -2
  88. package/templates/src/demo/api/schema.yaml +0 -12
  89. package/templates/src/demo/api/seed.sql +0 -5
  90. package/templates/src/demo/api/todos/api.ts +0 -59
  91. package/templates/src/demo/web/__client/api.schemas.ts +0 -20
  92. package/templates/src/demo/web/__client/api.ts +0 -89
  93. package/templates/src/demo/web/entry.ts +0 -42
  94. package/templates/src/demo/web/locales/en-US.json +0 -10
  95. package/templates/src/demo/web/locales/zh-CN.json +0 -10
  96. package/templates/src/demo/web/pages/index.tsx +0 -45
  97. package/templates/src/home/web/entry.ts +0 -49
  98. package/templates/src/login/web/entry.ts +0 -79
  99. package/templates/src/notification/web/entry.ts +0 -84
  100. package/templates/src/personal-center/web/entry.ts +0 -60
  101. package/templates/src/system/web/entry.ts +0 -108
  102. /package/templates/src/{home/web → app/web/home}/__client/api.schemas.ts +0 -0
  103. /package/templates/src/{home/web → app/web/home}/locales/en-US.json +0 -0
  104. /package/templates/src/{home/web → app/web/home}/locales/zh-CN.json +0 -0
  105. /package/templates/src/{home/web → app/web/home}/pages/components/bar-chart.tsx +0 -0
  106. /package/templates/src/{home/web → app/web/home}/pages/components/card-list.tsx +0 -0
  107. /package/templates/src/{home/web → app/web/home}/pages/components/line-chart.tsx +0 -0
  108. /package/templates/src/{home/web → app/web/home}/pages/components/pie-chart.tsx +0 -0
  109. /package/templates/src/{home/web → app/web/home}/pages/index.tsx +0 -0
  110. /package/templates/src/{login/web → app/web/login}/auth-provider.ts +0 -0
  111. /package/templates/src/{login/web → app/web/login}/locales/en-US.json +0 -0
  112. /package/templates/src/{login/web → app/web/login}/locales/zh-CN.json +0 -0
  113. /package/templates/src/{login/web → app/web/login}/pages/change-password.tsx +0 -0
  114. /package/templates/src/{login/web → app/web/login}/wecom.ts +0 -0
  115. /package/templates/src/{notification/web → app/web/notification}/locales/en-US.json +0 -0
  116. /package/templates/src/{notification/web → app/web/notification}/locales/zh-CN.json +0 -0
  117. /package/templates/src/{notification/web → app/web/notification}/pages/center/index.tsx +0 -0
  118. /package/templates/src/{personal-center/web → app/web/personal-center}/__client/api.schemas.ts +0 -0
  119. /package/templates/src/{personal-center/web → app/web/personal-center}/locales/en-US.json +0 -0
  120. /package/templates/src/{personal-center/web → app/web/personal-center}/locales/zh-CN.json +0 -0
  121. /package/templates/src/{personal-center/web → app/web/personal-center}/pages/my-profile/index.tsx +0 -0
  122. /package/templates/src/{system/web → app/web/system}/pages/departments/index.tsx +0 -0
  123. /package/templates/src/{system/web → app/web/system}/pages/departments/sync-bar.tsx +0 -0
  124. /package/templates/src/{system/web → app/web/system}/pages/departments/tree.ts +0 -0
  125. /package/templates/src/{system/web → app/web/system}/pages/errors.ts +0 -0
  126. /package/templates/src/{system/web → app/web/system}/pages/forbidden.tsx +0 -0
  127. /package/templates/src/{system/web → app/web/system}/pages/login-methods/index.tsx +0 -0
  128. /package/templates/src/{system/web → app/web/system}/pages/oplog/index.tsx +0 -0
  129. /package/templates/src/{system/web → app/web/system}/pages/roles/index.tsx +0 -0
  130. /package/templates/src/{system/web → app/web/system}/pages/settings/index.tsx +0 -0
@@ -4,10 +4,89 @@
4
4
  import { z } from "@oj-module/runtime";
5
5
 
6
6
  export const schemas = {
7
+ "authLoginPost": {
8
+ body: z.object({
9
+ "username": z.string().min(1),
10
+ "password": z.string().min(1)
11
+ }),
12
+ data: z.object({
13
+ "access_token": z.string(),
14
+ "refresh_token": z.string(),
15
+ "expires_in": z.number(),
16
+ "restricted": z.boolean(),
17
+ "user": z.object({
18
+ "id": z.number(),
19
+ "username": z.string(),
20
+ "display_name": z.string().nullable(),
21
+ "avatar_base64": z.string().nullable(),
22
+ "must_change": z.boolean(),
23
+ "roles": z.array(z.string())
24
+ })
25
+ }),
26
+ },
27
+ "authLogoutPost": {
28
+ body: z.object({
29
+ "refresh_token": z.string().min(1)
30
+ }),
31
+ data: z.null(),
32
+ },
33
+ "authRefreshPost": {
34
+ body: z.object({
35
+ "refresh_token": z.string().min(1)
36
+ }),
37
+ data: z.object({
38
+ "access_token": z.string(),
39
+ "refresh_token": z.string(),
40
+ "expires_in": z.number(),
41
+ "restricted": z.boolean(),
42
+ "user": z.object({
43
+ "id": z.number(),
44
+ "username": z.string(),
45
+ "display_name": z.string().nullable(),
46
+ "avatar_base64": z.string().nullable(),
47
+ "must_change": z.boolean(),
48
+ "roles": z.array(z.string())
49
+ })
50
+ }),
51
+ },
7
52
  "authPasswordPut": {
8
53
  body: z.object({
9
54
  "old_password": z.string().min(1),
10
55
  "new_password": z.string().min(6)
56
+ }),
57
+ data: z.object({
58
+ "access_token": z.string(),
59
+ "refresh_token": z.string(),
60
+ "expires_in": z.number(),
61
+ "restricted": z.boolean(),
62
+ "user": z.object({
63
+ "id": z.number(),
64
+ "username": z.string(),
65
+ "display_name": z.string().nullable(),
66
+ "avatar_base64": z.string().nullable(),
67
+ "must_change": z.boolean(),
68
+ "roles": z.array(z.string())
69
+ })
70
+ }),
71
+ },
72
+ "authLdapLoginPost": {
73
+ body: z.object({
74
+ "username": z.string().min(1),
75
+ "password": z.string().min(1)
76
+ }),
77
+ data: z.object({
78
+ "access_token": z.string(),
79
+ "refresh_token": z.string(),
80
+ "expires_in": z.number(),
81
+ "restricted": z.boolean(),
82
+ "user": z.object({
83
+ "id": z.number(),
84
+ "username": z.string(),
85
+ "display_name": z.string().nullable(),
86
+ "avatar_base64": z.string().nullable(),
87
+ "must_change": z.boolean(),
88
+ "roles": z.array(z.string())
89
+ })
11
90
  }),
12
91
  },
13
92
  "authWecomAdminAppsGet": {
@@ -24,6 +103,16 @@ export const schemas = {
24
103
  "installed_at": z.number(),
25
104
  "update_time": z.number()
26
105
  })),
106
+ },
107
+ "authWecomAdminOverviewGet": {
108
+ data: z.object({
109
+ "kinds": z.array(z.enum(["suite", "corp"])),
110
+ "configError": z.string().nullable(),
111
+ "callbackUrl": z.string().nullable(),
112
+ "corpId": z.string().nullable(),
113
+ "agentId": z.string().nullable(),
114
+ "corpCallbackConfigured": z.boolean()
115
+ }),
27
116
  },
28
117
  "authWecomAdminStatusPost": {
29
118
  body: z.object({
@@ -41,16 +130,6 @@ export const schemas = {
41
130
  "installUrl": z.string(),
42
131
  "callbackUrl": z.string(),
43
132
  "expiresIn": z.number()
44
- }),
45
- },
46
- "authWecomAdminOverviewGet": {
47
- data: z.object({
48
- "kinds": z.array(z.enum(["suite", "corp"])),
49
- "configError": z.string().nullable(),
50
- "callbackUrl": z.string().nullable(),
51
- "corpId": z.string().nullable(),
52
- "agentId": z.string().nullable(),
53
- "corpCallbackConfigured": z.boolean()
54
133
  }),
55
134
  },
56
135
  "authWecomAdminConnectPost": {
@@ -59,11 +138,48 @@ export const schemas = {
59
138
  "agentId": z.string(),
60
139
  "departments": z.number(),
61
140
  "members": z.number()
141
+ }),
142
+ },
143
+ "authWecomAdminTenantAdminGet": {
144
+ query: z.object({
145
+ "corpId": z.string().min(1)
146
+ }),
147
+ data: z.object({
148
+ "corpId": z.string(),
149
+ "username": z.string(),
150
+ "exists": z.boolean(),
151
+ "mustChange": z.boolean()
152
+ }),
153
+ },
154
+ "authWecomAdminTenantAdminPasswordPost": {
155
+ body: z.object({
156
+ "corpId": z.string().min(1),
157
+ "password": z.string().min(6)
158
+ }),
159
+ data: z.object({
160
+ "corpId": z.string(),
161
+ "username": z.string(),
162
+ "exists": z.boolean(),
163
+ "mustChange": z.boolean()
62
164
  }),
63
165
  },
64
166
  "authWecomTicketPost": {
65
167
  body: z.object({
66
168
  "ticket": z.string().min(32).max(128)
169
+ }),
170
+ data: z.object({
171
+ "access_token": z.string(),
172
+ "refresh_token": z.string(),
173
+ "expires_in": z.number(),
174
+ "restricted": z.boolean(),
175
+ "user": z.object({
176
+ "id": z.number(),
177
+ "username": z.string(),
178
+ "display_name": z.string().nullable(),
179
+ "avatar_base64": z.string().nullable(),
180
+ "must_change": z.boolean(),
181
+ "roles": z.array(z.string())
182
+ })
67
183
  }),
68
184
  },
69
185
  "authWecomSyncStartPost": {
@@ -74,6 +190,8 @@ export const schemas = {
74
190
  "connected": z.boolean(),
75
191
  "authMode": z.number(),
76
192
  "profileStatus": z.string().nullable(),
193
+ "agentId": z.string().nullable(),
194
+ "credentialKind": z.union([z.literal("suite"), z.literal("corp"), z.literal(null)]).nullable(),
77
195
  "status": z.enum(["idle", "running", "done", "failed", "cancelled"]),
78
196
  "phase": z.enum(["departments", "members", "reconcile"]),
79
197
  "stats": z.object({
@@ -106,6 +224,8 @@ export const schemas = {
106
224
  "connected": z.boolean(),
107
225
  "authMode": z.number(),
108
226
  "profileStatus": z.string().nullable(),
227
+ "agentId": z.string().nullable(),
228
+ "credentialKind": z.union([z.literal("suite"), z.literal("corp"), z.literal(null)]).nullable(),
109
229
  "status": z.enum(["idle", "running", "done", "failed", "cancelled"]),
110
230
  "phase": z.enum(["departments", "members", "reconcile"]),
111
231
  "stats": z.object({
@@ -127,9 +247,7 @@ export const schemas = {
127
247
  }).nullable(),
128
248
  "startedAt": z.number(),
129
249
  "finishedAt": z.number(),
130
- "updateTime": z.number(),
131
- "agentId": z.string().nullable(),
132
- "credentialKind": z.union([z.literal("suite"), z.literal("corp"), z.literal(null)]).nullable()
250
+ "updateTime": z.number()
133
251
  }),
134
252
  },
135
253
  "authWecomSyncCancelPost": {
@@ -140,6 +258,8 @@ export const schemas = {
140
258
  "connected": z.boolean(),
141
259
  "authMode": z.number(),
142
260
  "profileStatus": z.string().nullable(),
261
+ "agentId": z.string().nullable(),
262
+ "credentialKind": z.union([z.literal("suite"), z.literal("corp"), z.literal(null)]).nullable(),
143
263
  "status": z.enum(["idle", "running", "done", "failed", "cancelled"]),
144
264
  "phase": z.enum(["departments", "members", "reconcile"]),
145
265
  "stats": z.object({
@@ -33,7 +33,7 @@ export function createAuthClient(ctx: ModuleContext) {
33
33
  }
34
34
  ctx.register.apiPrefix(API_PREFIX);
35
35
  bindRequest(ctx.utils.request);
36
- return { authLoginPost, authLogoutPost, authRefreshPost, authPasswordPut, authLdapLoginPost, authWecomAdminAppsGet, authWecomAdminStatusPost, authWecomAdminInstallGet, authWecomAdminOverviewGet, authWecomAdminConnectPost, authWecomLoginGet, authWecomCallbackGet, authWecomTicketPost, authWecomEventGet, authWecomEventPost, authWecomSyncStartPost, authWecomSyncStatusGet, authWecomSyncCancelPost };
36
+ return { authLoginPost, authLogoutPost, authRefreshPost, authPasswordPut, authLdapLoginPost, authWecomAdminAppsGet, authWecomAdminOverviewGet, authWecomAdminStatusPost, authWecomAdminInstallGet, authWecomAdminConnectPost, authWecomAdminTenantAdminGet, authWecomAdminTenantAdminPasswordPost, authWecomLoginGet, authWecomCallbackGet, authWecomTicketPost, authWecomEventGet, authWecomEventPost, authWecomSyncStartPost, authWecomSyncStatusGet, authWecomSyncCancelPost };
37
37
  }
38
38
 
39
39
  /**
@@ -64,14 +64,23 @@ async function toApiError(e: unknown): Promise<unknown> {
64
64
  return e;
65
65
  }
66
66
 
67
- export async function authLoginPost(): Promise<unknown> {
67
+ export type AuthLoginPostBody = z.input<(typeof schemas)["authLoginPost"]["body"]>;
68
+ export type AuthLoginPostData = z.infer<(typeof schemas)["authLoginPost"]["data"]>;
69
+
70
+ export async function authLoginPost(body: AuthLoginPostBody): Promise<AuthLoginPostData> {
68
71
  const client = ensureReq();
69
72
  try {
70
- const env = await client.post(`auth/login`).json<OjEnvelope<unknown>>();
73
+ const env = await client.post(`auth/login`, { json: body }).json<OjEnvelope<AuthLoginPostData>>();
71
74
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
72
75
  if (typeof env.code === "number" && env.code !== 0)
73
76
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
74
- const data = env.data as unknown;
77
+ const data = env.data as AuthLoginPostData;
78
+ if (import.meta.env.DEV) {
79
+ const { schemas } = await import("./api.schemas");
80
+ const r = schemas["authLoginPost"].data.safeParse(data);
81
+ if (!r.success)
82
+ throw new ContractApiError(-1, `[契约违例] authLoginPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
83
+ }
75
84
  return data;
76
85
  }
77
86
  catch (e) {
@@ -79,14 +88,23 @@ export async function authLoginPost(): Promise<unknown> {
79
88
  }
80
89
  }
81
90
 
82
- export async function authLogoutPost(): Promise<unknown> {
91
+ export type AuthLogoutPostBody = z.input<(typeof schemas)["authLogoutPost"]["body"]>;
92
+ export type AuthLogoutPostData = z.infer<(typeof schemas)["authLogoutPost"]["data"]>;
93
+
94
+ export async function authLogoutPost(body: AuthLogoutPostBody): Promise<AuthLogoutPostData> {
83
95
  const client = ensureReq();
84
96
  try {
85
- const env = await client.post(`auth/logout`).json<OjEnvelope<unknown>>();
97
+ const env = await client.post(`auth/logout`, { json: body }).json<OjEnvelope<AuthLogoutPostData>>();
86
98
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
87
99
  if (typeof env.code === "number" && env.code !== 0)
88
100
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
89
- const data = env.data as unknown;
101
+ const data = env.data as AuthLogoutPostData;
102
+ if (import.meta.env.DEV) {
103
+ const { schemas } = await import("./api.schemas");
104
+ const r = schemas["authLogoutPost"].data.safeParse(data);
105
+ if (!r.success)
106
+ throw new ContractApiError(-1, `[契约违例] authLogoutPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
107
+ }
90
108
  return data;
91
109
  }
92
110
  catch (e) {
@@ -94,14 +112,23 @@ export async function authLogoutPost(): Promise<unknown> {
94
112
  }
95
113
  }
96
114
 
97
- export async function authRefreshPost(): Promise<unknown> {
115
+ export type AuthRefreshPostBody = z.input<(typeof schemas)["authRefreshPost"]["body"]>;
116
+ export type AuthRefreshPostData = z.infer<(typeof schemas)["authRefreshPost"]["data"]>;
117
+
118
+ export async function authRefreshPost(body: AuthRefreshPostBody): Promise<AuthRefreshPostData> {
98
119
  const client = ensureReq();
99
120
  try {
100
- const env = await client.post(`auth/refresh`).json<OjEnvelope<unknown>>();
121
+ const env = await client.post(`auth/refresh`, { json: body }).json<OjEnvelope<AuthRefreshPostData>>();
101
122
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
102
123
  if (typeof env.code === "number" && env.code !== 0)
103
124
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
104
- const data = env.data as unknown;
125
+ const data = env.data as AuthRefreshPostData;
126
+ if (import.meta.env.DEV) {
127
+ const { schemas } = await import("./api.schemas");
128
+ const r = schemas["authRefreshPost"].data.safeParse(data);
129
+ if (!r.success)
130
+ throw new ContractApiError(-1, `[契约违例] authRefreshPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
131
+ }
105
132
  return data;
106
133
  }
107
134
  catch (e) {
@@ -110,15 +137,22 @@ export async function authRefreshPost(): Promise<unknown> {
110
137
  }
111
138
 
112
139
  export type AuthPasswordPutBody = z.input<(typeof schemas)["authPasswordPut"]["body"]>;
140
+ export type AuthPasswordPutData = z.infer<(typeof schemas)["authPasswordPut"]["data"]>;
113
141
 
114
- export async function authPasswordPut(body: AuthPasswordPutBody): Promise<unknown> {
142
+ export async function authPasswordPut(body: AuthPasswordPutBody): Promise<AuthPasswordPutData> {
115
143
  const client = ensureReq();
116
144
  try {
117
- const env = await client.put(`auth/password`, { json: body }).json<OjEnvelope<unknown>>();
145
+ const env = await client.put(`auth/password`, { json: body }).json<OjEnvelope<AuthPasswordPutData>>();
118
146
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
119
147
  if (typeof env.code === "number" && env.code !== 0)
120
148
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
121
- const data = env.data as unknown;
149
+ const data = env.data as AuthPasswordPutData;
150
+ if (import.meta.env.DEV) {
151
+ const { schemas } = await import("./api.schemas");
152
+ const r = schemas["authPasswordPut"].data.safeParse(data);
153
+ if (!r.success)
154
+ throw new ContractApiError(-1, `[契约违例] authPasswordPut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
155
+ }
122
156
  return data;
123
157
  }
124
158
  catch (e) {
@@ -126,14 +160,23 @@ export async function authPasswordPut(body: AuthPasswordPutBody): Promise<unknow
126
160
  }
127
161
  }
128
162
 
129
- export async function authLdapLoginPost(): Promise<unknown> {
163
+ export type AuthLdapLoginPostBody = z.input<(typeof schemas)["authLdapLoginPost"]["body"]>;
164
+ export type AuthLdapLoginPostData = z.infer<(typeof schemas)["authLdapLoginPost"]["data"]>;
165
+
166
+ export async function authLdapLoginPost(body: AuthLdapLoginPostBody): Promise<AuthLdapLoginPostData> {
130
167
  const client = ensureReq();
131
168
  try {
132
- const env = await client.post(`auth/ldap-login`).json<OjEnvelope<unknown>>();
169
+ const env = await client.post(`auth/ldap-login`, { json: body }).json<OjEnvelope<AuthLdapLoginPostData>>();
133
170
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
134
171
  if (typeof env.code === "number" && env.code !== 0)
135
172
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
136
- const data = env.data as unknown;
173
+ const data = env.data as AuthLdapLoginPostData;
174
+ if (import.meta.env.DEV) {
175
+ const { schemas } = await import("./api.schemas");
176
+ const r = schemas["authLdapLoginPost"].data.safeParse(data);
177
+ if (!r.success)
178
+ throw new ContractApiError(-1, `[契约违例] authLdapLoginPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
179
+ }
137
180
  return data;
138
181
  }
139
182
  catch (e) {
@@ -164,6 +207,29 @@ export async function authWecomAdminAppsGet(): Promise<AuthWecomAdminAppsGetData
164
207
  }
165
208
  }
166
209
 
210
+ export type AuthWecomAdminOverviewGetData = z.infer<(typeof schemas)["authWecomAdminOverviewGet"]["data"]>;
211
+
212
+ export async function authWecomAdminOverviewGet(): Promise<AuthWecomAdminOverviewGetData> {
213
+ const client = ensureReq();
214
+ try {
215
+ const env = await client.get(`auth/wecom/admin/overview`).json<OjEnvelope<AuthWecomAdminOverviewGetData>>();
216
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
217
+ if (typeof env.code === "number" && env.code !== 0)
218
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
219
+ const data = env.data as AuthWecomAdminOverviewGetData;
220
+ if (import.meta.env.DEV) {
221
+ const { schemas } = await import("./api.schemas");
222
+ const r = schemas["authWecomAdminOverviewGet"].data.safeParse(data);
223
+ if (!r.success)
224
+ throw new ContractApiError(-1, `[契约违例] authWecomAdminOverviewGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
225
+ }
226
+ return data;
227
+ }
228
+ catch (e) {
229
+ throw await toApiError(e);
230
+ }
231
+ }
232
+
167
233
  export type AuthWecomAdminStatusPostBody = z.input<(typeof schemas)["authWecomAdminStatusPost"]["body"]>;
168
234
  export type AuthWecomAdminStatusPostData = z.infer<(typeof schemas)["authWecomAdminStatusPost"]["data"]>;
169
235
 
@@ -211,21 +277,21 @@ export async function authWecomAdminInstallGet(): Promise<AuthWecomAdminInstallG
211
277
  }
212
278
  }
213
279
 
214
- export type AuthWecomAdminOverviewGetData = z.infer<(typeof schemas)["authWecomAdminOverviewGet"]["data"]>;
280
+ export type AuthWecomAdminConnectPostData = z.infer<(typeof schemas)["authWecomAdminConnectPost"]["data"]>;
215
281
 
216
- export async function authWecomAdminOverviewGet(): Promise<AuthWecomAdminOverviewGetData> {
282
+ export async function authWecomAdminConnectPost(): Promise<AuthWecomAdminConnectPostData> {
217
283
  const client = ensureReq();
218
284
  try {
219
- const env = await client.get(`auth/wecom/admin/overview`).json<OjEnvelope<AuthWecomAdminOverviewGetData>>();
285
+ const env = await client.post(`auth/wecom/admin/connect`).json<OjEnvelope<AuthWecomAdminConnectPostData>>();
220
286
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
221
287
  if (typeof env.code === "number" && env.code !== 0)
222
288
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
223
- const data = env.data as AuthWecomAdminOverviewGetData;
289
+ const data = env.data as AuthWecomAdminConnectPostData;
224
290
  if (import.meta.env.DEV) {
225
291
  const { schemas } = await import("./api.schemas");
226
- const r = schemas["authWecomAdminOverviewGet"].data.safeParse(data);
292
+ const r = schemas["authWecomAdminConnectPost"].data.safeParse(data);
227
293
  if (!r.success)
228
- throw new ContractApiError(-1, `[契约违例] authWecomAdminOverviewGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
294
+ throw new ContractApiError(-1, `[契约违例] authWecomAdminConnectPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
229
295
  }
230
296
  return data;
231
297
  }
@@ -234,21 +300,46 @@ export async function authWecomAdminOverviewGet(): Promise<AuthWecomAdminOvervie
234
300
  }
235
301
  }
236
302
 
237
- export type AuthWecomAdminConnectPostData = z.infer<(typeof schemas)["authWecomAdminConnectPost"]["data"]>;
303
+ export type AuthWecomAdminTenantAdminGetQuery = z.input<(typeof schemas)["authWecomAdminTenantAdminGet"]["query"]>;
304
+ export type AuthWecomAdminTenantAdminGetData = z.infer<(typeof schemas)["authWecomAdminTenantAdminGet"]["data"]>;
238
305
 
239
- export async function authWecomAdminConnectPost(): Promise<AuthWecomAdminConnectPostData> {
306
+ export async function authWecomAdminTenantAdminGet(query: AuthWecomAdminTenantAdminGetQuery): Promise<AuthWecomAdminTenantAdminGetData> {
240
307
  const client = ensureReq();
241
308
  try {
242
- const env = await client.post(`auth/wecom/admin/connect`).json<OjEnvelope<AuthWecomAdminConnectPostData>>();
309
+ const env = await client.get(`auth/wecom/admin/tenant-admin`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<AuthWecomAdminTenantAdminGetData>>();
243
310
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
244
311
  if (typeof env.code === "number" && env.code !== 0)
245
312
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
246
- const data = env.data as AuthWecomAdminConnectPostData;
313
+ const data = env.data as AuthWecomAdminTenantAdminGetData;
247
314
  if (import.meta.env.DEV) {
248
315
  const { schemas } = await import("./api.schemas");
249
- const r = schemas["authWecomAdminConnectPost"].data.safeParse(data);
316
+ const r = schemas["authWecomAdminTenantAdminGet"].data.safeParse(data);
250
317
  if (!r.success)
251
- throw new ContractApiError(-1, `[契约违例] authWecomAdminConnectPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
318
+ throw new ContractApiError(-1, `[契约违例] authWecomAdminTenantAdminGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
319
+ }
320
+ return data;
321
+ }
322
+ catch (e) {
323
+ throw await toApiError(e);
324
+ }
325
+ }
326
+
327
+ export type AuthWecomAdminTenantAdminPasswordPostBody = z.input<(typeof schemas)["authWecomAdminTenantAdminPasswordPost"]["body"]>;
328
+ export type AuthWecomAdminTenantAdminPasswordPostData = z.infer<(typeof schemas)["authWecomAdminTenantAdminPasswordPost"]["data"]>;
329
+
330
+ export async function authWecomAdminTenantAdminPasswordPost(body: AuthWecomAdminTenantAdminPasswordPostBody): Promise<AuthWecomAdminTenantAdminPasswordPostData> {
331
+ const client = ensureReq();
332
+ try {
333
+ const env = await client.post(`auth/wecom/admin/tenant-admin/password`, { json: body }).json<OjEnvelope<AuthWecomAdminTenantAdminPasswordPostData>>();
334
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
335
+ if (typeof env.code === "number" && env.code !== 0)
336
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
337
+ const data = env.data as AuthWecomAdminTenantAdminPasswordPostData;
338
+ if (import.meta.env.DEV) {
339
+ const { schemas } = await import("./api.schemas");
340
+ const r = schemas["authWecomAdminTenantAdminPasswordPost"].data.safeParse(data);
341
+ if (!r.success)
342
+ throw new ContractApiError(-1, `[契约违例] authWecomAdminTenantAdminPasswordPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
252
343
  }
253
344
  return data;
254
345
  }
@@ -288,15 +379,22 @@ export async function authWecomCallbackGet(): Promise<unknown> {
288
379
  }
289
380
 
290
381
  export type AuthWecomTicketPostBody = z.input<(typeof schemas)["authWecomTicketPost"]["body"]>;
382
+ export type AuthWecomTicketPostData = z.infer<(typeof schemas)["authWecomTicketPost"]["data"]>;
291
383
 
292
- export async function authWecomTicketPost(body: AuthWecomTicketPostBody): Promise<unknown> {
384
+ export async function authWecomTicketPost(body: AuthWecomTicketPostBody): Promise<AuthWecomTicketPostData> {
293
385
  const client = ensureReq();
294
386
  try {
295
- const env = await client.post(`auth/wecom/ticket`, { json: body }).json<OjEnvelope<unknown>>();
387
+ const env = await client.post(`auth/wecom/ticket`, { json: body }).json<OjEnvelope<AuthWecomTicketPostData>>();
296
388
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
297
389
  if (typeof env.code === "number" && env.code !== 0)
298
390
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
299
- const data = env.data as unknown;
391
+ const data = env.data as AuthWecomTicketPostData;
392
+ if (import.meta.env.DEV) {
393
+ const { schemas } = await import("./api.schemas");
394
+ const r = schemas["authWecomTicketPost"].data.safeParse(data);
395
+ if (!r.success)
396
+ throw new ContractApiError(-1, `[契约违例] authWecomTicketPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
397
+ }
300
398
  return data;
301
399
  }
302
400
  catch (e) {
@@ -137,6 +137,11 @@ notifications.read.post.schema = {
137
137
  properties: { id: { type: "string" } },
138
138
  },
139
139
  };
140
+ notifications.read.post.response = { type: "null" };
141
+
142
+ // read-all / clear:无入参、无返回体(json.ok(null))——只声明出参,入参留空。
143
+ notifications["read-all"].post.response = { type: "null" };
144
+ notifications.clear.post.response = { type: "null" };
140
145
 
141
146
  // POST /api/notification/push —— 给指定用户推一条按人通知(发射原语 HTTP 入口)。
142
147
  // 真正逻辑在 _shared/notify.pushNotice(任一后端模块也可直接 import 调用,无需走 HTTP)。
@@ -239,6 +244,12 @@ const broadcast = {
239
244
  },
240
245
  };
241
246
 
247
+ push.post.response = {
248
+ type: "object",
249
+ required: ["id"],
250
+ properties: { id: { type: "string" } },
251
+ };
252
+
242
253
  broadcast.post.schema = {
243
254
  body: {
244
255
  type: "object",
@@ -253,4 +264,10 @@ broadcast.post.schema = {
253
264
  },
254
265
  };
255
266
 
267
+ broadcast.post.response = {
268
+ type: "object",
269
+ required: ["id"],
270
+ properties: { id: { type: "string" } },
271
+ };
272
+
256
273
  export default { notifications, push, "push-broadcast": broadcast };
@@ -49,4 +49,6 @@ handler.post.schema = {
49
49
  },
50
50
  };
51
51
 
52
+ handler.post.response = { type: "null" };
53
+
52
54
  export default handler;
@@ -72,4 +72,7 @@ del.schema = {
72
72
  },
73
73
  };
74
74
 
75
+ // 出参无业务载荷(service 返回 void → data 为 null)
76
+ del.response = { type: "null" };
77
+
75
78
  export default { get, post, del };
@@ -26,4 +26,7 @@ put.schema = {
26
26
  },
27
27
  };
28
28
 
29
+ // 出参无业务载荷(service 返回 void → data 为 null)
30
+ put.response = { type: "null" };
31
+
29
32
  export default { put };
@@ -1,7 +1,7 @@
1
1
  import { respond } from "#/_platform/_shared/respond";
2
2
  import { listSettings, updateSetting } from "#/_platform/_shared/service/domain/settings";
3
3
  // /api/system/settings —— 系统设置(PRD §4.3):GET 按组列出(secret 掩码),PUT 写值(写审计)。
4
- import { assertSystemAdmin } from "#/_platform/_shared/service/infra/access";
4
+ import { assertSuperAdmin } from "#/_platform/_shared/service/infra/access";
5
5
 
6
6
  const settingItemSchema = {
7
7
  type: "object",
@@ -23,7 +23,7 @@ const settingItemSchema = {
23
23
 
24
24
  async function get() {
25
25
  await respond(async () => {
26
- await assertSystemAdmin();
26
+ await assertSuperAdmin();
27
27
  return listSettings();
28
28
  });
29
29
  }
@@ -41,7 +41,7 @@ get.response = {
41
41
 
42
42
  async function put() {
43
43
  await respond(async () => {
44
- await assertSystemAdmin();
44
+ await assertSuperAdmin();
45
45
  const body = http.body || {};
46
46
  return updateSetting(Number(http.user?.id), String(body.key ?? ""), String(body.value ?? ""));
47
47
  });
@@ -57,4 +57,7 @@ put.schema = {
57
57
  },
58
58
  };
59
59
 
60
+ // 出参无业务载荷(service 返回 void → data 为 null)
61
+ put.response = { type: "null" };
62
+
60
63
  export default { get, put };
@@ -23,4 +23,7 @@ put.schema = {
23
23
  },
24
24
  };
25
25
 
26
+ // 出参无业务载荷(service 返回 void → data 为 null)
27
+ put.response = { type: "null" };
28
+
26
29
  export default { put };
@@ -23,4 +23,7 @@ put.schema = {
23
23
  },
24
24
  };
25
25
 
26
+ // 出参无业务载荷(service 返回 void → data 为 null)
27
+ put.response = { type: "null" };
28
+
26
29
  export default { put };
@@ -24,4 +24,7 @@ put.schema = {
24
24
  },
25
25
  };
26
26
 
27
+ // 出参无业务载荷(service 返回 void → data 为 null)
28
+ put.response = { type: "null" };
29
+
27
30
  export default { put };
@@ -1,8 +1,23 @@
1
1
  // GET /api/web/get-async-routes —— 后端动态路由(Bearer 守卫保护)。
2
2
  // demo 返回空数组:模块路由由 web/src 静态注入;需要后端路由时按
3
3
  // 宿主 RouteObject 形状返回。
4
- export default {
4
+ const handler = {
5
5
  get() {
6
6
  json.ok([]);
7
7
  },
8
8
  };
9
+
10
+ // 出参:宿主 RouteObject 形状的数组(demo 恒空数组;页面路由由前端静态注入)
11
+ handler.get.response = {
12
+ type: "array",
13
+ items: {
14
+ type: "object",
15
+ properties: {
16
+ path: { type: "string" },
17
+ component: { type: "string" },
18
+ children: { type: "array", items: { type: "object" } },
19
+ },
20
+ },
21
+ };
22
+
23
+ export default handler;
@@ -1,6 +1,6 @@
1
1
  // GET /api/web/hello —— 演示业务端点(Bearer 守卫保护)。
2
2
  // 改完保存即生效(oj dev/ts 热更);新增/删除模块目录需重启 ojm dev。
3
- export default {
3
+ const handler = {
4
4
  get() {
5
5
  json.ok({
6
6
  message: "hello from oj",
@@ -8,3 +8,15 @@ export default {
8
8
  });
9
9
  },
10
10
  };
11
+
12
+ // 出参:演示端点(hello 文案 + 当前用户 id)
13
+ handler.get.response = {
14
+ type: "object",
15
+ required: ["message", "user"],
16
+ properties: {
17
+ message: { type: "string" },
18
+ user: { type: ["number", "null"] },
19
+ },
20
+ };
21
+
22
+ export default handler;