@oj-module/cli 0.2.53 → 0.2.56

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 (330) hide show
  1. package/devkit/SKILL.md +4 -4
  2. package/devkit/manual.md +79 -65
  3. package/package.json +2 -2
  4. package/shell-dist/assets/{index-e_Mp2mb5.js → index-CImwskAz.js} +24 -11
  5. package/shell-dist/assets/runtime.js +7 -4
  6. package/shell-dist/index.html +3 -3
  7. package/shell-dist/versions.json +3 -3
  8. package/src/__tests__/layout.test.ts +30 -1
  9. package/src/__tests__/templates-schema-columns.test.ts +99 -0
  10. package/src/__tests__/templates-tenant-scope.test.ts +3 -2
  11. package/src/alias-paths.ts +11 -12
  12. package/src/builtin.ts +10 -3
  13. package/src/config.ts +9 -0
  14. package/src/contract/__tests__/client-path.test.ts +30 -0
  15. package/src/contract/__tests__/emit-client.test.ts +20 -2
  16. package/src/contract/__tests__/handler-scan.test.ts +23 -0
  17. package/src/contract/__tests__/market-client.integration.test.ts +2 -2
  18. package/src/contract/__tests__/run-api.test.ts +2 -2
  19. package/src/contract/check.ts +11 -7
  20. package/src/contract/client-path.ts +30 -0
  21. package/src/contract/emit-client.ts +27 -6
  22. package/src/contract/handler-scan.ts +40 -3
  23. package/src/contract/run.ts +15 -5
  24. package/src/init.ts +29 -3
  25. package/src/layout.ts +72 -16
  26. package/src/lifecycle/__tests__/manifest-contract.test.ts +2 -2
  27. package/src/market/__tests__/bootstrap.test.ts +1 -1
  28. package/src/site.ts +1 -1
  29. package/templates/README.md +49 -43
  30. package/templates/init.include.yaml +31 -19
  31. package/templates/openapi.json +1488 -120
  32. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/users.ts +27 -0
  33. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/oplog.ts +4 -2
  34. package/templates/src/{_platform/api → app/api/_platform}/contacts/api.ts +29 -1
  35. package/templates/src/{_platform/api → app/api/_platform}/contacts/detail/api.ts +25 -1
  36. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.mysql.sql +7 -0
  37. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.pg.sql +4 -0
  38. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.sql +8 -0
  39. package/templates/src/{_platform/api → app/api/_platform}/schema.yaml +5 -0
  40. package/templates/src/{_platform/api → app/api/_platform}/solutions/api.ts +60 -1
  41. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/replay.ts +9 -3
  42. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/tenant-admin.ts +18 -27
  43. package/templates/src/{auth/api → app/api/auth}/api.ts +59 -0
  44. package/templates/src/{auth/api → app/api/auth}/ldap-login/api.ts +37 -1
  45. package/templates/src/{auth/api → app/api/auth}/wecom/api.ts +23 -0
  46. package/templates/src/{notification/api → app/api/notification}/api.ts +17 -0
  47. package/templates/src/{notification/api → app/api/notification}/del/api.ts +2 -0
  48. package/templates/src/{system/api → app/api/system}/oplog/api.ts +5 -3
  49. package/templates/src/{system/api → app/api/system}/roles/api.ts +3 -0
  50. package/templates/src/{system/api → app/api/system}/roles/bindings/api.ts +3 -0
  51. package/templates/src/{system/api → app/api/system}/settings/api.ts +3 -0
  52. package/templates/src/{system/api → app/api/system}/users/forbidden/api.ts +3 -0
  53. package/templates/src/{system/api → app/api/system}/users/password/api.ts +3 -0
  54. package/templates/src/{system/api → app/api/system}/users/roles/api.ts +3 -0
  55. package/templates/src/app/api/web/get-async-routes/api.ts +23 -0
  56. package/templates/src/app/api/web/hello/api.ts +22 -0
  57. package/templates/src/{web/api → app/api/web}/user-info/api.ts +21 -1
  58. package/templates/src/{_platform/web → app/web/_platform}/__client/api.schemas.ts +99 -35
  59. package/templates/src/{_platform/web/__client/api.ts → app/web/_platform/__client/index.ts} +108 -61
  60. package/templates/src/{auth/web → app/web/auth}/__client/api.schemas.ts +133 -13
  61. package/templates/src/{auth/web/__client/api.ts → app/web/auth/__client/index.ts} +134 -33
  62. package/templates/src/app/web/entry.ts +77 -0
  63. package/templates/src/{home/web/__client/api.ts → app/web/home/__client/index.ts} +5 -2
  64. package/templates/src/app/web/home/routes.ts +41 -0
  65. package/templates/src/app/web/login/routes.ts +71 -0
  66. package/templates/src/{notification/web → app/web/notification}/__client/api.schemas.ts +14 -0
  67. package/templates/src/{notification/web/__client/api.ts → app/web/notification/__client/index.ts} +67 -20
  68. package/templates/src/{notification/web → app/web/notification}/pages/center/index.tsx +1 -1
  69. package/templates/src/app/web/notification/routes.ts +70 -0
  70. package/templates/src/{personal-center/web/__client/api.ts → app/web/personal-center/__client/index.ts} +5 -2
  71. package/templates/src/{personal-center/web → app/web/personal-center}/pages/my-profile/index.tsx +1 -1
  72. package/templates/src/app/web/personal-center/routes.ts +49 -0
  73. package/templates/src/{system/web → app/web/system}/__client/api.schemas.ts +29 -22
  74. package/templates/src/{system/web/__client/api.ts → app/web/system/__client/index.ts} +103 -58
  75. package/templates/src/{system/web → app/web/system}/locales/en-US.json +2 -3
  76. package/templates/src/{system/web → app/web/system}/locales/zh-CN.json +2 -3
  77. package/templates/src/{system/web → app/web/system}/pages/departments/index.tsx +2 -2
  78. package/templates/src/{system/web → app/web/system}/pages/login-methods/index.tsx +1 -1
  79. package/templates/src/{system/web → app/web/system}/pages/oplog/index.tsx +44 -19
  80. package/templates/src/{system/web → app/web/system}/pages/roles/index.tsx +5 -3
  81. package/templates/src/app/web/system/pages/roles/visible-roles.ts +14 -0
  82. package/templates/src/{system/web → app/web/system}/pages/settings/index.tsx +2 -2
  83. package/templates/src/app/web/system/pages/users/can-manage.ts +21 -0
  84. package/templates/src/{system/web → app/web/system}/pages/users/index.tsx +23 -15
  85. package/templates/src/{system/web → app/web/system}/pages/users/roles.tsx +1 -1
  86. package/templates/src/{system/web → app/web/system}/pages/wecom/index.tsx +12 -3
  87. package/templates/src/{system/web/entry.ts → app/web/system/routes.ts} +25 -36
  88. package/templates/src/app/web/web/__client/api.schemas.ts +36 -0
  89. package/templates/src/{web/web/__client/api.ts → app/web/web/__client/index.ts} +38 -11
  90. package/templates/src/oj-market/web/api/defaultClient.ts +1 -1
  91. package/templates/tests/auth-ldap.spec.ts +2 -2
  92. package/templates/tests/auth-session.spec.ts +3 -3
  93. package/templates/tests/auth-throttle.spec.ts +1 -1
  94. package/templates/tests/fixture.ts +6 -4
  95. package/templates/tests/guard.spec.ts +1 -1
  96. package/templates/tests/lifecycle/atomic-state.spec.ts +3 -3
  97. package/templates/tests/lifecycle/bootstrap.spec.ts +3 -3
  98. package/templates/tests/lifecycle/deploy-smoke.spec.ts +8 -8
  99. package/templates/tests/lifecycle/installer.spec.ts +10 -10
  100. package/templates/tests/lifecycle/market-api.spec.ts +14 -14
  101. package/templates/tests/lifecycle/migrate.spec.ts +6 -6
  102. package/templates/tests/lifecycle/preflight.spec.ts +3 -3
  103. package/templates/tests/lifecycle/register.spec.ts +1 -1
  104. package/templates/tests/lifecycle/semver.spec.ts +1 -1
  105. package/templates/tests/lifecycle/sha384.spec.ts +1 -1
  106. package/templates/tests/lifecycle/solutions-api.spec.ts +1 -1
  107. package/templates/tests/lifecycle/source-seq.spec.ts +1 -1
  108. package/templates/tests/lifecycle/steps-fs.spec.ts +9 -9
  109. package/templates/tests/lifecycle/text-edit.spec.ts +1 -1
  110. package/templates/tests/lifecycle/tgz-guard.spec.ts +2 -2
  111. package/templates/tests/lifecycle/trust.spec.ts +5 -5
  112. package/templates/tests/lifecycle/uninstall.spec.ts +7 -7
  113. package/templates/tests/lifecycle/verify-checks.spec.ts +5 -5
  114. package/templates/tests/lifecycle/verify-report.spec.ts +4 -4
  115. package/templates/tests/org-departments.spec.ts +10 -6
  116. package/templates/tests/org-read.spec.ts +7 -7
  117. package/templates/tests/query-utils.spec.ts +1 -1
  118. package/templates/tests/rbac-audit.spec.ts +1 -1
  119. package/templates/tests/rbac-contacts.spec.ts +2 -2
  120. package/templates/tests/rbac-guard.spec.ts +1 -1
  121. package/templates/tests/rbac-roles.spec.ts +10 -2
  122. package/templates/tests/rbac-seed.spec.ts +1 -1
  123. package/templates/tests/rbac-settings.spec.ts +77 -7
  124. package/templates/tests/rbac-solution-perms.spec.ts +1 -1
  125. package/templates/tests/rbac-tables.spec.ts +29 -0
  126. package/templates/tests/rbac-users.spec.ts +55 -3
  127. package/templates/tests/roles-page.spec.ts +40 -0
  128. package/templates/tests/seed-role-accounts.spec.ts +1 -1
  129. package/templates/tests/tenant-access-cache.spec.ts +1 -1
  130. package/templates/tests/tenant-forgery.spec.ts +5 -5
  131. package/templates/tests/tenant-isolation.spec.ts +5 -4
  132. package/templates/tests/username-namespace.spec.ts +3 -3
  133. package/templates/tests/users-page.spec.ts +39 -0
  134. package/templates/tests/wecom-admin.spec.ts +6 -6
  135. package/templates/tests/wecom-client-timeout.spec.ts +1 -1
  136. package/templates/tests/wecom-connector.spec.ts +9 -9
  137. package/templates/tests/wecom-corp.spec.ts +9 -9
  138. package/templates/tests/wecom-crypt.spec.ts +1 -1
  139. package/templates/tests/wecom-endpoints.spec.ts +2 -2
  140. package/templates/tests/wecom-landing.spec.ts +1 -1
  141. package/templates/tests/wecom-org-sync.spec.ts +7 -7
  142. package/templates/tests/wecom-sync.spec.ts +3 -3
  143. package/templates/tsconfig.paths.json +11 -11
  144. package/templates/vitest.config.ts +2 -2
  145. package/templates/web.config.ts +24 -15
  146. package/vendor/host-versions.json +3 -3
  147. package/templates/src/home/web/entry.ts +0 -49
  148. package/templates/src/login/web/entry.ts +0 -79
  149. package/templates/src/notification/web/entry.ts +0 -84
  150. package/templates/src/personal-center/web/entry.ts +0 -60
  151. package/templates/src/web/api/get-async-routes/api.ts +0 -8
  152. package/templates/src/web/api/hello/api.ts +0 -10
  153. package/templates/src/web/web/__client/api.schemas.ts +0 -8
  154. package/templates/src/{_platform/api → app/api/_platform}/README.md +0 -0
  155. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/atomic.ts +0 -0
  156. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/history.ts +0 -0
  157. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/installer.ts +0 -0
  158. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/limits.ts +0 -0
  159. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/lock.ts +0 -0
  160. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/semver.ts +0 -0
  161. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/sha384.ts +0 -0
  162. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/source-seq.ts +0 -0
  163. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/state.ts +0 -0
  164. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/deploy-web.ts +0 -0
  165. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/download.ts +0 -0
  166. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/extract.ts +0 -0
  167. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/migrate.ts +0 -0
  168. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/preflight.ts +0 -0
  169. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/register.ts +0 -0
  170. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/reload.ts +0 -0
  171. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/smoke.ts +0 -0
  172. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/verify.ts +0 -0
  173. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/tasks.ts +0 -0
  174. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/text-edit.ts +0 -0
  175. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/tgz-guard.ts +0 -0
  176. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/trust.ts +0 -0
  177. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/types.ts +0 -0
  178. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/uninstall.ts +0 -0
  179. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/verify-checks.ts +0 -0
  180. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/verify-report.ts +0 -0
  181. package/templates/src/{_platform/api → app/api/_platform}/_shared/respond.ts +0 -0
  182. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/contacts.ts +0 -0
  183. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/org-read.ts +0 -0
  184. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/org.ts +0 -0
  185. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/roles.ts +0 -0
  186. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/settings.ts +0 -0
  187. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/solution-perms.ts +0 -0
  188. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/access.ts +0 -0
  189. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/audit.ts +0 -0
  190. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/guard.ts +0 -0
  191. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/query.ts +0 -0
  192. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/rate.ts +0 -0
  193. package/templates/src/{_platform/api → app/api/_platform}/_shared/site-facade.ts +0 -0
  194. package/templates/src/{_platform/api → app/api/_platform}/fixtures/l1-test-user.sql +0 -0
  195. package/templates/src/{_platform/api → app/api/_platform}/manifest.yaml +0 -0
  196. package/templates/src/{_platform/api → app/api/_platform}/market/api.ts +0 -0
  197. package/templates/src/{_platform/api → app/api/_platform}/market/bootstrap.ts +0 -0
  198. package/templates/src/{_platform/api → app/api/_platform}/market/index-source.ts +0 -0
  199. package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.mysql.sql +0 -0
  200. package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.pg.sql +0 -0
  201. package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.sql +0 -0
  202. package/templates/src/{_platform/api → app/api/_platform}/migrations/0002__add_users_avatar_base64.mysql.sql +0 -0
  203. package/templates/src/{_platform/api → app/api/_platform}/migrations/0002__add_users_avatar_base64.sql +0 -0
  204. package/templates/src/{_platform/api → app/api/_platform}/migrations/0003__users_source_display_name.mysql.sql +0 -0
  205. package/templates/src/{_platform/api → app/api/_platform}/migrations/0003__users_source_display_name.sql +0 -0
  206. package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.mysql.sql +0 -0
  207. package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.pg.sql +0 -0
  208. package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.sql +0 -0
  209. package/templates/src/{_platform/api → app/api/_platform}/migrations/0005__users_must_change.mysql.sql +0 -0
  210. package/templates/src/{_platform/api → app/api/_platform}/migrations/0005__users_must_change.sql +0 -0
  211. package/templates/src/{_platform/api → app/api/_platform}/migrations/0006__users_is_forbidden.mysql.sql +0 -0
  212. package/templates/src/{_platform/api → app/api/_platform}/migrations/0006__users_is_forbidden.sql +0 -0
  213. package/templates/src/{_platform/api → app/api/_platform}/migrations/0007__users_email.sql +0 -0
  214. package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.mysql.sql +0 -0
  215. package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.pg.sql +0 -0
  216. package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.sql +0 -0
  217. package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.mysql.sql +0 -0
  218. package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.pg.sql +0 -0
  219. package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.sql +0 -0
  220. package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.mysql.sql +0 -0
  221. package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.pg.sql +0 -0
  222. package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.sql +0 -0
  223. package/templates/src/{_platform/api → app/api/_platform}/migrations/0011__sys_setting_group_label.mysql.sql +0 -0
  224. package/templates/src/{_platform/api → app/api/_platform}/migrations/0011__sys_setting_group_label.sql +0 -0
  225. package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.mysql.sql +0 -0
  226. package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.pg.sql +0 -0
  227. package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.sql +0 -0
  228. package/templates/src/{_platform/api → app/api/_platform}/migrations/0013__users_profile.sql +0 -0
  229. package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.mysql.sql +0 -0
  230. package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.pg.sql +0 -0
  231. package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.sql +0 -0
  232. package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.mysql.sql +0 -0
  233. package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.pg.sql +0 -0
  234. package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.sql +0 -0
  235. package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.mysql.sql +0 -0
  236. package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.pg.sql +0 -0
  237. package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.sql +0 -0
  238. package/templates/src/{_platform/api → app/api/_platform}/org/departments/api.ts +0 -0
  239. package/templates/src/{_platform/api → app/api/_platform}/org/is-member/api.ts +0 -0
  240. package/templates/src/{_platform/api → app/api/_platform}/org/members/api.ts +0 -0
  241. package/templates/src/{_platform/api → app/api/_platform}/org/picker/api.ts +0 -0
  242. package/templates/src/{_platform/api → app/api/_platform}/org/resolve/api.ts +0 -0
  243. package/templates/src/{_platform/api → app/api/_platform}/org/tree/api.ts +0 -0
  244. package/templates/src/{_platform/api → app/api/_platform}/org/user/api.ts +0 -0
  245. package/templates/src/{_platform/api → app/api/_platform}/seed.mysql.sql +0 -0
  246. package/templates/src/{_platform/api → app/api/_platform}/seed.pg.sql +0 -0
  247. package/templates/src/{_platform/api → app/api/_platform}/seed.sql +0 -0
  248. package/templates/src/{auth/api → app/api/auth}/_shared/ldap-login.ts +0 -0
  249. package/templates/src/{auth/api → app/api/auth}/_shared/ldap.ts +0 -0
  250. package/templates/src/{auth/api → app/api/auth}/_shared/login-throttle.ts +0 -0
  251. package/templates/src/{auth/api → app/api/auth}/_shared/session.ts +0 -0
  252. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/admin.ts +1 -1
  253. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/client.ts +0 -0
  254. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/config.ts +0 -0
  255. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/contacts.ts +0 -0
  256. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/crypt.ts +0 -0
  257. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/events.ts +1 -1
  258. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/login.ts +0 -0
  259. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/payload.ts +0 -0
  260. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/store.ts +0 -0
  261. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/sync.ts +0 -0
  262. package/templates/src/{auth/api → app/api/auth}/manifest.yaml +0 -0
  263. package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.mysql.sql +0 -0
  264. package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.pg.sql +0 -0
  265. package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.sql +0 -0
  266. package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.mysql.sql +0 -0
  267. package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.pg.sql +0 -0
  268. package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.sql +0 -0
  269. package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.mysql.sql +0 -0
  270. package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.pg.sql +0 -0
  271. package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.sql +0 -0
  272. package/templates/src/{auth/api → app/api/auth}/schema.yaml +0 -0
  273. package/templates/src/{auth/api → app/api/auth}/wecom/admin/api.ts +0 -0
  274. package/templates/src/{auth/api → app/api/auth}/wecom/admin/tenant-admin/api.ts +1 -1
  275. package/templates/src/{auth/api → app/api/auth}/wecom/admin/tenant-admin/password/api.ts +1 -1
  276. /package/templates/src/{auth/api → app/api/auth}/wecom/sync/api.ts +0 -0
  277. /package/templates/src/{home/api → app/api/home}/line/api.ts +0 -0
  278. /package/templates/src/{home/api → app/api/home}/manifest.yaml +0 -0
  279. /package/templates/src/{home/api → app/api/home}/migrations/0001__create_home_tables.sql +0 -0
  280. /package/templates/src/{home/api → app/api/home}/migrations/0002__tenant.mysql.sql +0 -0
  281. /package/templates/src/{home/api → app/api/home}/migrations/0002__tenant.sql +0 -0
  282. /package/templates/src/{home/api → app/api/home}/pie/api.ts +0 -0
  283. /package/templates/src/{home/api → app/api/home}/schema.yaml +0 -0
  284. /package/templates/src/{home/api → app/api/home}/seed.sql +0 -0
  285. /package/templates/src/{notification/api → app/api/notification}/_shared/notify.ts +0 -0
  286. /package/templates/src/{notification/api → app/api/notification}/manifest.yaml +0 -0
  287. /package/templates/src/{notification/api → app/api/notification}/migrations/0001__create_notifications.sql +0 -0
  288. /package/templates/src/{notification/api → app/api/notification}/migrations/0002__notifications_user.sql +0 -0
  289. /package/templates/src/{notification/api → app/api/notification}/migrations/0003__notifications_broadcast.sql +0 -0
  290. /package/templates/src/{notification/api → app/api/notification}/migrations/0004__notifications_broadcast_hidden.sql +0 -0
  291. /package/templates/src/{notification/api → app/api/notification}/migrations/0005__tenant.mysql.sql +0 -0
  292. /package/templates/src/{notification/api → app/api/notification}/migrations/0005__tenant.sql +0 -0
  293. /package/templates/src/{notification/api → app/api/notification}/schema.yaml +0 -0
  294. /package/templates/src/{notification/api → app/api/notification}/seed.sql +0 -0
  295. /package/templates/src/{personal-center/api → app/api/personal-center}/manifest.yaml +0 -0
  296. /package/templates/src/{personal-center/api → app/api/personal-center}/profile/api.ts +0 -0
  297. /package/templates/src/{personal-center/api → app/api/personal-center}/upload/api.ts +0 -0
  298. /package/templates/src/{system/api → app/api/system}/departments/api.ts +0 -0
  299. /package/templates/src/{system/api → app/api/system}/departments/detach/api.ts +0 -0
  300. /package/templates/src/{system/api → app/api/system}/departments/members/api.ts +0 -0
  301. /package/templates/src/{system/api → app/api/system}/departments/move/api.ts +0 -0
  302. /package/templates/src/{system/api → app/api/system}/login-methods/api.ts +0 -0
  303. /package/templates/src/{system/api → app/api/system}/manifest.yaml +0 -0
  304. /package/templates/src/{system/api → app/api/system}/permissions/api.ts +0 -0
  305. /package/templates/src/{system/api → app/api/system}/users/api.ts +0 -0
  306. /package/templates/src/{system/api → app/api/system}/users/profile/api.ts +0 -0
  307. /package/templates/src/{web/api → app/api/web}/manifest.yaml +0 -0
  308. /package/templates/src/{home/web → app/web/home}/__client/api.schemas.ts +0 -0
  309. /package/templates/src/{home/web → app/web/home}/locales/en-US.json +0 -0
  310. /package/templates/src/{home/web → app/web/home}/locales/zh-CN.json +0 -0
  311. /package/templates/src/{home/web → app/web/home}/pages/components/bar-chart.tsx +0 -0
  312. /package/templates/src/{home/web → app/web/home}/pages/components/card-list.tsx +0 -0
  313. /package/templates/src/{home/web → app/web/home}/pages/components/line-chart.tsx +0 -0
  314. /package/templates/src/{home/web → app/web/home}/pages/components/pie-chart.tsx +0 -0
  315. /package/templates/src/{home/web → app/web/home}/pages/index.tsx +0 -0
  316. /package/templates/src/{login/web → app/web/login}/auth-provider.ts +0 -0
  317. /package/templates/src/{login/web → app/web/login}/locales/en-US.json +0 -0
  318. /package/templates/src/{login/web → app/web/login}/locales/zh-CN.json +0 -0
  319. /package/templates/src/{login/web → app/web/login}/pages/change-password.tsx +0 -0
  320. /package/templates/src/{login/web → app/web/login}/pages/login.tsx +0 -0
  321. /package/templates/src/{login/web → app/web/login}/wecom.ts +0 -0
  322. /package/templates/src/{notification/web → app/web/notification}/locales/en-US.json +0 -0
  323. /package/templates/src/{notification/web → app/web/notification}/locales/zh-CN.json +0 -0
  324. /package/templates/src/{personal-center/web → app/web/personal-center}/__client/api.schemas.ts +0 -0
  325. /package/templates/src/{personal-center/web → app/web/personal-center}/locales/en-US.json +0 -0
  326. /package/templates/src/{personal-center/web → app/web/personal-center}/locales/zh-CN.json +0 -0
  327. /package/templates/src/{system/web → app/web/system}/pages/departments/sync-bar.tsx +0 -0
  328. /package/templates/src/{system/web → app/web/system}/pages/departments/tree.ts +0 -0
  329. /package/templates/src/{system/web → app/web/system}/pages/errors.ts +0 -0
  330. /package/templates/src/{system/web → app/web/system}/pages/forbidden.tsx +0 -0
@@ -6,11 +6,11 @@
6
6
  // 命令构造为纯函数,argv 逐字断言(与平台 exec 白名单逐字对齐,S1-2 基座);
7
7
  // exec/fetch/sha256 面经 ctx 注入 stub,不真跑网络;合法夹具集成用例的 exec stub 落到真实系统 tar
8
8
  // (与生产同路径:isolate 不内嵌解压,exec.run 系统 tar)。
9
- import type { FsFace } from "../../src/_platform/api/_lifecycle/atomic";
10
- import type { StateStore } from "../../src/_platform/api/_lifecycle/state";
11
- import type { DownloadContext } from "../../src/_platform/api/_lifecycle/steps/download";
12
- import type { ExecFace, ExtractContext } from "../../src/_platform/api/_lifecycle/steps/extract";
13
- import type { VerifyContext } from "../../src/_platform/api/_lifecycle/steps/verify";
9
+ import type { FsFace } from "../../src/app/api/_platform/_lifecycle/atomic";
10
+ import type { StateStore } from "../../src/app/api/_platform/_lifecycle/state";
11
+ import type { DownloadContext } from "../../src/app/api/_platform/_lifecycle/steps/download";
12
+ import type { ExecFace, ExtractContext } from "../../src/app/api/_platform/_lifecycle/steps/extract";
13
+ import type { VerifyContext } from "../../src/app/api/_platform/_lifecycle/steps/verify";
14
14
  import { execFile as execFileCb } from "node:child_process";
15
15
  import { createHash } from "node:crypto";
16
16
  import * as fsp from "node:fs/promises";
@@ -19,10 +19,10 @@ import { join } from "node:path";
19
19
  import { promisify } from "node:util";
20
20
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
21
21
  import { defaultTestKey, nodeVerifyEd25519, signedArchiveFor } from "../../../src/lifecycle/__tests__/helpers";
22
- import { archiveFileOf, download } from "../../src/_platform/api/_lifecycle/steps/download";
23
- import { buildExtractCmd, buildListCmd, checkModuleManifest, extract } from "../../src/_platform/api/_lifecycle/steps/extract";
24
- import { verify } from "../../src/_platform/api/_lifecycle/steps/verify";
25
- import { createTrustStore } from "../../src/_platform/api/_lifecycle/trust";
22
+ import { archiveFileOf, download } from "../../src/app/api/_platform/_lifecycle/steps/download";
23
+ import { buildExtractCmd, buildListCmd, checkModuleManifest, extract } from "../../src/app/api/_platform/_lifecycle/steps/extract";
24
+ import { verify } from "../../src/app/api/_platform/_lifecycle/steps/verify";
25
+ import { createTrustStore } from "../../src/app/api/_platform/_lifecycle/trust";
26
26
 
27
27
  const execFileP = promisify(execFileCb);
28
28
  const g = globalThis as any;
@@ -5,7 +5,7 @@
5
5
  import * as fsp from "node:fs/promises";
6
6
  import { describe, expect, it } from "vitest";
7
7
 
8
- import { appendAnonymousPaths, bumpInstallSeq, ensureReloadApi, hasAnchor, insertAnchor } from "../../src/_platform/api/_lifecycle/text-edit";
8
+ import { appendAnonymousPaths, bumpInstallSeq, ensureReloadApi, hasAnchor, insertAnchor } from "../../src/app/api/_platform/_lifecycle/text-edit";
9
9
 
10
10
  describe("insertAnchor(R18)", () => {
11
11
  it("带注释的 config.yaml 插入后注释/缩进/空行/键序逐字保留", () => {
@@ -4,8 +4,8 @@
4
4
  // 拒绝:绝对路径、任何 `..` 段、l/h 条目、空成员名、反斜杠路径;`./` 前缀相对合法。
5
5
  import { describe, expect, it } from "vitest";
6
6
 
7
- import { MAX_ENTRIES, MAX_PACKAGE_BYTES, MAX_SINGLE_ENTRY_BYTES, MAX_UNPACKED_BYTES } from "../../src/_platform/api/_lifecycle/limits";
8
- import { accumulateTarVolume, checkTarVolumeLimits, extractTarEntrySize, validateTarListing } from "../../src/_platform/api/_lifecycle/tgz-guard";
7
+ import { MAX_ENTRIES, MAX_PACKAGE_BYTES, MAX_SINGLE_ENTRY_BYTES, MAX_UNPACKED_BYTES } from "../../src/app/api/_platform/_lifecycle/limits";
8
+ import { accumulateTarVolume, checkTarVolumeLimits, extractTarEntrySize, validateTarListing } from "../../src/app/api/_platform/_lifecycle/tgz-guard";
9
9
 
10
10
  describe("validateTarListing(R1)", () => {
11
11
  it("正常成员列表通过", () => {
@@ -2,17 +2,17 @@
2
2
  // store 层:空库/幂等/首次绑定不可改/多 key/吊销/损坏 fail-closed/.bak 回落/并发读不见半截/审计。
3
3
  // 端点层:GET /market/trust、POST /market/trust/key、DELETE /market/trust/key?key_id=(del 动词 + query,system/roles 同款先例)。
4
4
  /* eslint-disable test/prefer-lowercase-title -- 端点动词/路径是验收锚点,保留大写(market-api.spec 同约定) */
5
- import type { FsFace } from "../../src/_platform/api/_lifecycle/atomic";
6
- import type { MarketEnv } from "../../src/_platform/api/market/api";
5
+ import type { FsFace } from "../../src/app/api/_platform/_lifecycle/atomic";
6
+ import type { MarketEnv } from "../../src/app/api/_platform/market/api";
7
7
  import { Buffer } from "node:buffer";
8
8
  import { createHash } from "node:crypto";
9
9
  import * as fsp from "node:fs/promises";
10
10
  import { tmpdir } from "node:os";
11
11
  import { join } from "node:path";
12
12
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
13
- import { createTrustStore, isValidKeyId } from "../../src/_platform/api/_lifecycle/trust";
14
- import { invalidateAllUsers } from "../../src/_platform/api/_shared/service/infra/access";
15
- import { createMarketApi, marketEnv } from "../../src/_platform/api/market/api";
13
+ import { createTrustStore, isValidKeyId } from "../../src/app/api/_platform/_lifecycle/trust";
14
+ import { invalidateAllUsers } from "../../src/app/api/_platform/_shared/service/infra/access";
15
+ import { createMarketApi, marketEnv } from "../../src/app/api/_platform/market/api";
16
16
  import { installDb } from "../fixture";
17
17
 
18
18
  const g = globalThis as any;
@@ -5,17 +5,17 @@
5
5
  // 夹具:本仓范式(node:sqlite 真实迁移建表 + 真实 fs 面 g.fs);market_history 表由平台迁移
6
6
  // 0012(IF NOT EXISTS)随 installDb 重放建立,守卫语义(表不存在静默跳过)单列一例。
7
7
  /* eslint-disable test/prefer-lowercase-title -- 端点动词/路径是验收锚点,保留大写(market-api.spec 同约定) */
8
- import type { FsFace } from "../../src/_platform/api/_lifecycle/atomic";
9
- import type { MarketEnv } from "../../src/_platform/api/market/api";
8
+ import type { FsFace } from "../../src/app/api/_platform/_lifecycle/atomic";
9
+ import type { MarketEnv } from "../../src/app/api/_platform/market/api";
10
10
  import * as fsp from "node:fs/promises";
11
11
  import { tmpdir } from "node:os";
12
12
  import { dirname, join } from "node:path";
13
13
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
14
- import { readLock, writeLock } from "../../src/_platform/api/_lifecycle/lock";
15
- import { createStateStore } from "../../src/_platform/api/_lifecycle/state";
16
- import { collectRetainedTables, moduleTablePrefix } from "../../src/_platform/api/_lifecycle/uninstall";
17
- import { invalidateAllUsers } from "../../src/_platform/api/_shared/service/infra/access";
18
- import { createMarketApi } from "../../src/_platform/api/market/api";
14
+ import { readLock, writeLock } from "../../src/app/api/_platform/_lifecycle/lock";
15
+ import { createStateStore } from "../../src/app/api/_platform/_lifecycle/state";
16
+ import { collectRetainedTables, moduleTablePrefix } from "../../src/app/api/_platform/_lifecycle/uninstall";
17
+ import { invalidateAllUsers } from "../../src/app/api/_platform/_shared/service/infra/access";
18
+ import { createMarketApi } from "../../src/app/api/_platform/market/api";
19
19
  import { installDb } from "../fixture";
20
20
 
21
21
  const g = globalThis as any;
@@ -1,8 +1,8 @@
1
1
  // BDD:深检后端四项 + 端点(P2-7,C2-42)+ SSRF 三钉。
2
2
  // 纯 diff 函数 / 探针分类 / SSRF 钉(出站拦截 / header 清洗 / 不跟随重定向)+ 端点门(401/403/404)+ 编排。
3
3
  /* eslint-disable test/prefer-lowercase-title -- 端点动词/路径是验收锚点,保留大写(market-api.spec 同约定) */
4
- import type { FsFace } from "../../src/_platform/api/_lifecycle/atomic";
5
- import type { SolutionRouteRow } from "../../src/_platform/api/_lifecycle/verify-checks";
4
+ import type { FsFace } from "../../src/app/api/_platform/_lifecycle/atomic";
5
+ import type { SolutionRouteRow } from "../../src/app/api/_platform/_lifecycle/verify-checks";
6
6
  import * as fsp from "node:fs/promises";
7
7
  import { tmpdir } from "node:os";
8
8
  import { join } from "node:path";
@@ -18,9 +18,9 @@ import {
18
18
  sha384B64,
19
19
 
20
20
  verifyRouteReachability,
21
- } from "../../src/_platform/api/_lifecycle/verify-checks";
22
- import { siteOrigin } from "../../src/_platform/api/market/api";
23
- import { createSolutionsApi, solutionsEnv } from "../../src/_platform/api/solutions/api";
21
+ } from "../../src/app/api/_platform/_lifecycle/verify-checks";
22
+ import { siteOrigin } from "../../src/app/api/_platform/market/api";
23
+ import { createSolutionsApi, solutionsEnv } from "../../src/app/api/_platform/solutions/api";
24
24
  import { installDb } from "../fixture";
25
25
 
26
26
  const g = globalThis as any;
@@ -1,8 +1,8 @@
1
1
  // BDD:P2-8 深检报告结构 + 留存 N=10 + 可信性项(trust_state)+ i18n 降级项 + 设值不进报告。
2
2
  // 纯模块单测(verify-report.ts)+ 端点编排(persist-on-POST / GET verify/last / 降级项 / secret 不泄漏)。
3
3
  /* eslint-disable test/prefer-lowercase-title -- 端点动词/路径是验收锚点,保留大写(verify-checks.spec 同约定) */
4
- import type { FsFace } from "../../src/_platform/api/_lifecycle/atomic";
5
- import type { VerifyReport } from "../../src/_platform/api/_lifecycle/verify-report";
4
+ import type { FsFace } from "../../src/app/api/_platform/_lifecycle/atomic";
5
+ import type { VerifyReport } from "../../src/app/api/_platform/_lifecycle/verify-report";
6
6
  import * as fsp from "node:fs/promises";
7
7
  import { tmpdir } from "node:os";
8
8
  import { join } from "node:path";
@@ -12,8 +12,8 @@ import {
12
12
  persistReport,
13
13
  VERIFY_REPORTS_KEEP,
14
14
  verifyReportsDirOf,
15
- } from "../../src/_platform/api/_lifecycle/verify-report";
16
- import { createSolutionsApi, solutionsEnv } from "../../src/_platform/api/solutions/api";
15
+ } from "../../src/app/api/_platform/_lifecycle/verify-report";
16
+ import { createSolutionsApi, solutionsEnv } from "../../src/app/api/_platform/solutions/api";
17
17
  import { installDb } from "../fixture";
18
18
 
19
19
  const g = globalThis as any;
@@ -1,4 +1,4 @@
1
- import type { OrgDeps } from "../src/_platform/api/_shared/service/domain/org";
1
+ import type { OrgDeps } from "../src/app/api/_platform/_shared/service/domain/org";
2
2
 
3
3
  // BDD:组织架构领域(0016,方案 A1+A2)——CRUD 基线 + 归属权(D13)+ 保底出口(D14)。
4
4
  //
@@ -23,8 +23,8 @@ import {
23
23
  updateDepartment,
24
24
  updateMember,
25
25
  upsertDepartment,
26
- } from "../src/_platform/api/_shared/service/domain/org";
27
- import { deleteUser, listUsers, updateUserProfile } from "../src/_platform/api/_shared/service/domain/users";
26
+ } from "../src/app/api/_platform/_shared/service/domain/org";
27
+ import { deleteUser, listUsers, updateUserProfile } from "../src/app/api/_platform/_shared/service/domain/users";
28
28
  import { ADMIN, installDb } from "./fixture";
29
29
 
30
30
  let dbo: ReturnType<typeof installDb>;
@@ -263,12 +263,16 @@ describe("用户侧(改资料 / 删号)", () => {
263
263
  expect(dbo.prepare("SELECT COUNT(*) c FROM sys_refresh_token WHERE user_id = ?").get(uid)).toMatchObject({ c: 0 });
264
264
  });
265
265
 
266
- it("删号:禁止删自己;最后一个 superadmin 拒删", async () => {
266
+ it("删号:禁止删自己;非 superadmin 删 superadmin → 403(最后超管的实际防线)", async () => {
267
267
  await expect(deleteUser(ADMIN, ADMIN)).rejects.toMatchObject({ status: 400 });
268
268
  const solo = mkUser("u_sup");
269
269
  dbo.prepare("INSERT INTO sys_user_role (user_id, role_name) VALUES (?, 'superadmin')").run(solo);
270
- await expect(deleteUser(ADMIN, solo)).rejects.toMatchObject({ status: 400 });
271
- // 有两个 superadmin 时允许删(兜底只保最后一个)
270
+ // 目标门禁先于「最后一个 superadmin」计数:普通 admin 连碰都碰不到它。
271
+ // 计数兜底(400)因此在真实路径上不可达——操作者本身是 superadmin 时,
272
+ // 库里必然已有 ≥2 个 superadmin——保留它只为挡住未来的新写路径。
273
+ await expect(deleteUser(ADMIN, solo)).rejects.toMatchObject({ status: 403 });
274
+ expect(dbo.prepare("SELECT COUNT(*) c FROM users WHERE id = ?").get(solo)).toMatchObject({ c: 1 });
275
+ // 操作者升为 superadmin 后允许删(第二个超管在场)
272
276
  dbo.prepare("INSERT INTO sys_user_role (user_id, role_name) VALUES (?, 'superadmin')").run(ADMIN);
273
277
  await deleteUser(ADMIN, solo);
274
278
  expect(dbo.prepare("SELECT COUNT(*) c FROM users WHERE id = ?").get(solo)).toMatchObject({ c: 0 });
@@ -8,15 +8,15 @@
8
8
  // 反过来也钉住:返回字段与 scope 无关,消费模块不该按档位分支解析。
9
9
  //
10
10
  // 另外钉住 §9.1 阻断项:通讯录只出 activated=1,同步灌进来的「从未登录」账号不得被检索到。
11
- import type { OrgDeps } from "../src/_platform/api/_shared/service/domain/org";
11
+ import type { OrgDeps } from "../src/app/api/_platform/_shared/service/domain/org";
12
12
 
13
13
  import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
14
- import { contactDetail, searchContacts } from "../src/_platform/api/_shared/service/domain/contacts";
14
+ import { contactDetail, searchContacts } from "../src/app/api/_platform/_shared/service/domain/contacts";
15
15
  import {
16
16
  addMember,
17
17
  createDepartment,
18
18
  deleteDepartment,
19
- } from "../src/_platform/api/_shared/service/domain/org";
19
+ } from "../src/app/api/_platform/_shared/service/domain/org";
20
20
  import {
21
21
  isMemberOf,
22
22
  pageDepartments,
@@ -28,10 +28,10 @@ import {
28
28
  resolveReader,
29
29
  resolveReadFace,
30
30
  resolveUserIds,
31
- } from "../src/_platform/api/_shared/service/domain/org-read";
32
- import { createUser } from "../src/_platform/api/_shared/service/domain/users";
33
- import { invalidateUser } from "../src/_platform/api/_shared/service/infra/access";
34
- import { ensureWecomUser } from "../src/auth/api/_shared/wecom/login";
31
+ } from "../src/app/api/_platform/_shared/service/domain/org-read";
32
+ import { createUser } from "../src/app/api/_platform/_shared/service/domain/users";
33
+ import { invalidateUser } from "../src/app/api/_platform/_shared/service/infra/access";
34
+ import { ensureWecomUser } from "../src/app/api/auth/_shared/wecom/login";
35
35
  import { ADMIN, installDb } from "./fixture";
36
36
 
37
37
  let dbo: ReturnType<typeof installDb>;
@@ -3,7 +3,7 @@
3
3
  /* eslint-disable test/prefer-lowercase-title -- AC 编号是验收锚点,保留大写 */
4
4
  import { beforeAll, describe, expect, it } from "vitest";
5
5
 
6
- import { likePattern, paged, pagedFactory } from "../src/_platform/api/_shared/service/infra/query";
6
+ import { likePattern, paged, pagedFactory } from "../src/app/api/_platform/_shared/service/infra/query";
7
7
  import { installDb } from "./fixture";
8
8
 
9
9
  let dbo: ReturnType<typeof installDb>;
@@ -3,7 +3,7 @@
3
3
  /* eslint-disable test/prefer-lowercase-title -- AC 编号是验收锚点,保留大写 */
4
4
  import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
5
5
 
6
- import { auditDeny, auditOp, recordOp, resetDenyAudit } from "../src/_platform/api/_shared/service/infra/audit";
6
+ import { auditDeny, auditOp, recordOp, resetDenyAudit } from "../src/app/api/_platform/_shared/service/infra/audit";
7
7
  import { installDb } from "./fixture";
8
8
 
9
9
  let dbo: ReturnType<typeof installDb>;
@@ -5,7 +5,7 @@
5
5
  /* eslint-disable test/prefer-lowercase-title -- AC 编号是验收锚点,保留大写 */
6
6
  import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
7
7
 
8
- import { contactDetail, searchContacts } from "../src/_platform/api/_shared/service/domain/contacts";
8
+ import { contactDetail, searchContacts } from "../src/app/api/_platform/_shared/service/domain/contacts";
9
9
  import { installDb } from "./fixture";
10
10
 
11
11
  let dbo: ReturnType<typeof installDb>;
@@ -84,7 +84,7 @@ describe("详情与限速", () => {
84
84
  it("接口级限速:同用户突发超限 → 429", async () => {
85
85
  const uid = mkUser("c_rate");
86
86
  g.http = { user: { id: uid } };
87
- const { rateLimit } = await import("../src/_platform/api/_shared/service/infra/rate");
87
+ const { rateLimit } = await import("../src/app/api/_platform/_shared/service/infra/rate");
88
88
  // 窗口 60s / 限额 3(测试参数;生产端点各自定额)
89
89
  for (let i = 0; i < 3; i++)
90
90
  await rateLimit(`contacts:${uid}`, 3, 60_000);
@@ -4,7 +4,7 @@
4
4
  /* eslint-disable test/prefer-lowercase-title -- AC 编号是验收锚点,保留大写 */
5
5
  import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
6
6
 
7
- import { assertPermission, assertSuperAdmin, assertSystemAdmin, currentAccess, invalidateUser } from "../src/_platform/api/_shared/service/infra/access";
7
+ import { assertPermission, assertSuperAdmin, assertSystemAdmin, currentAccess, invalidateUser } from "../src/app/api/_platform/_shared/service/infra/access";
8
8
  import { installDb } from "./fixture";
9
9
 
10
10
  let dbo: ReturnType<typeof installDb>;
@@ -4,8 +4,8 @@
4
4
  /* eslint-disable test/prefer-lowercase-title -- AC 编号是验收锚点,保留大写 */
5
5
  import { afterEach, beforeAll, describe, expect, it } from "vitest";
6
6
 
7
- import { createRole, deleteRole, listPermissions, listRoles, updateBindings } from "../src/_platform/api/_shared/service/domain/roles";
8
- import { assertPermission } from "../src/_platform/api/_shared/service/infra/access";
7
+ import { createRole, deleteRole, listPermissions, listRoles, updateBindings } from "../src/app/api/_platform/_shared/service/domain/roles";
8
+ import { assertPermission } from "../src/app/api/_platform/_shared/service/infra/access";
9
9
  import { installDb } from "./fixture";
10
10
 
11
11
  let dbo: ReturnType<typeof installDb>;
@@ -156,4 +156,12 @@ describe("列表支撑(C2 页面数据源)", () => {
156
156
  expect(perms.find(p => p.code === "wiki.old")?.deprecated).toBe(true);
157
157
  expect(perms.find(p => p.code === "wiki.new")?.deprecated).toBe(false);
158
158
  });
159
+
160
+ it("listRoles 原样返回 superadmin(只在 /system/roles 展示层隐去)", async () => {
161
+ // 成员资格页(users/roles.tsx)把 superadmin 当可授予项(仅 superadmin 可勾选),
162
+ // 页面数据源里少了它会让「授予超级管理员」直接不可用。过滤只做在展示层。
163
+ dbo.prepare("INSERT INTO sys_role (name, module, builtin) VALUES ('superadmin', 'framework', 1)").run();
164
+ const roles = await listRoles();
165
+ expect(roles.map(r => r.name)).toContain("superadmin");
166
+ });
159
167
  });
@@ -8,7 +8,7 @@ import { beforeAll, describe, expect, it } from "vitest";
8
8
 
9
9
  import { installDb } from "./fixture";
10
10
 
11
- const SEED = join(dirname(fileURLToPath(import.meta.url)), "..", "src", "_platform", "api", "seed.sql");
11
+ const SEED = join(dirname(fileURLToPath(import.meta.url)), "..", "src", "app", "api", "_platform", "seed.sql");
12
12
  let dbo: ReturnType<typeof installDb>;
13
13
 
14
14
  beforeAll(() => {
@@ -2,8 +2,9 @@
2
2
  /* eslint-disable test/prefer-lowercase-title -- AC 编号是验收锚点,保留大写 */
3
3
  import { afterEach, beforeAll, describe, expect, it } from "vitest";
4
4
 
5
- import { listLoginMethods, listSettings, updateSetting } from "../src/_platform/api/_shared/service/domain/settings";
6
- import { searchOplog } from "../src/_platform/api/_shared/service/infra/oplog";
5
+ import { listLoginMethods, listSettings, updateSetting } from "../src/app/api/_platform/_shared/service/domain/settings";
6
+ import { recordOp } from "../src/app/api/_platform/_shared/service/infra/audit";
7
+ import { searchOplog } from "../src/app/api/_platform/_shared/service/infra/oplog";
7
8
  import { installDb } from "./fixture";
8
9
 
9
10
  let dbo: ReturnType<typeof installDb>;
@@ -27,6 +28,8 @@ describe("AC1-29/D13:操作日志查询(admin 可查)", () => {
27
28
  // 按 api-manual §1153 契约建模(引擎语义的忠实复刻,非新逻辑)。
28
29
  function fakeTable() {
29
30
  const conds: Array<{ test: unknown, cond: any }> = [];
31
+ // 分页同样只记录不执行:count 查询不该带 limit/offset,list 查询必须带上
32
+ const paging: { limit: number | null, offset: number | null } = { limit: null, offset: null };
30
33
  const b: any = {
31
34
  whereIf: (test: unknown, cond: any) => {
32
35
  if (test !== undefined && test !== null && test !== false && !(Array.isArray(test) && test.length === 0))
@@ -35,17 +38,23 @@ describe("AC1-29/D13:操作日志查询(admin 可查)", () => {
35
38
  },
36
39
  select: () => b,
37
40
  orderBy: () => b,
38
- limit: () => b,
39
- offset: () => b,
41
+ limit: (n: number) => {
42
+ paging.limit = n;
43
+ return b;
44
+ },
45
+ offset: (n: number) => {
46
+ paging.offset = n;
47
+ return b;
48
+ },
40
49
  // 固定非零 total:保证 count/list 两条路径都构造(total=0 跳过 list 的行为
41
50
  // 由 query-utils.spec 的 pagedFactory 用例覆盖,这里只关心条件发射)
42
51
  all: async () => [{ c: 42 }],
43
52
  };
44
- return { b, conds };
53
+ return { b, conds, paging };
45
54
  }
46
55
 
47
- /** 以假构造器跑 searchOplog,回传 count 构造发射的条件(并校验 count/list 同条件) */
48
- async function captureConds(filter: Parameters<typeof searchOplog>[0]) {
56
+ /** 以假构造器跑 searchOplog,回传 count/list 两次构造([0]=count,[1]=list) */
57
+ async function captureBuilt(filter: Parameters<typeof searchOplog>[0]) {
49
58
  const built: Array<ReturnType<typeof fakeTable>> = [];
50
59
  const prevDb = g.db;
51
60
  g.db = {
@@ -63,6 +72,12 @@ describe("AC1-29/D13:操作日志查询(admin 可查)", () => {
63
72
  }
64
73
  expect(built).toHaveLength(2); // count + list 各构造一次
65
74
  expect(built[1].conds).toEqual(built[0].conds); // mk 工厂:两组条件一致
75
+ return built;
76
+ }
77
+
78
+ /** 回传 count 构造发射的条件 */
79
+ async function captureConds(filter: Parameters<typeof searchOplog>[0]) {
80
+ const built = await captureBuilt(filter);
66
81
  return built[0].conds;
67
82
  }
68
83
 
@@ -87,6 +102,61 @@ describe("AC1-29/D13:操作日志查询(admin 可查)", () => {
87
102
  ]);
88
103
  expect(conds.some(c => (c.cond as { field?: string }).field === "level")).toBe(false);
89
104
  });
105
+
106
+ it("page/limit 只作用在 list 查询:limit=size、offset=(page-1)*size,count 不带分页", async () => {
107
+ const [count, list] = await captureBuilt({ action: "user.create", page: 3, limit: 10 });
108
+ expect(count!.paging).toEqual({ limit: null, offset: null });
109
+ expect(list!.paging).toEqual({ limit: 10, offset: 20 });
110
+ });
111
+
112
+ it("缺省/非法分页参数:第 1 页 20 条,size 静默钳 200", async () => {
113
+ expect((await captureBuilt({}))[1]!.paging).toEqual({ limit: 20, offset: 0 });
114
+ expect((await captureBuilt({ page: 0, limit: "abc" }))[1]!.paging).toEqual({ limit: 20, offset: 0 });
115
+ expect((await captureBuilt({ page: 2, limit: 10_000 }))[1]!.paging).toEqual({ limit: 200, offset: 200 });
116
+ });
117
+ });
118
+
119
+ // 真库分页语义:上面用假构造器只钉「参数怎么发」,这里钉「切片/总数/还剩多少」算得对——
120
+ // 页导航能否正确显示页数全靠 total,靠 truncated 提示「结果被截断」的旧交互已删除。
121
+ describe("AC1-29:操作日志分页(真库切片 + total)", () => {
122
+ async function seedOplog(n: number) {
123
+ for (let i = 1; i <= n; i++)
124
+ await recordOp({ action: "test.page", module: "framework", target: `t${i}` });
125
+ }
126
+
127
+ it("按 id 倒序切页:total 是全量而不是本页条数", async () => {
128
+ await seedOplog(5);
129
+ const p1 = await searchOplog({ page: 1, limit: 2 });
130
+ expect(p1.total).toBe(5);
131
+ expect(p1.list.map(r => r.target)).toEqual(["t5", "t4"]);
132
+ expect(p1.truncated).toBe(true);
133
+
134
+ const p2 = await searchOplog({ page: 2, limit: 2 });
135
+ expect(p2.list.map(r => r.target)).toEqual(["t3", "t2"]);
136
+ expect(p2.truncated).toBe(true);
137
+
138
+ // 末页:5 条 / 每页 2 → 第 3 页只剩 1 条,truncated=false(页导航据此停在第 3 页)
139
+ const p3 = await searchOplog({ page: 3, limit: 2 });
140
+ expect(p3.list.map(r => r.target)).toEqual(["t1"]);
141
+ expect(p3.truncated).toBe(false);
142
+ });
143
+
144
+ it("越界页:list 为空但 total 仍为全量(前端据此纠正页码,而不是显示 0 条)", async () => {
145
+ await seedOplog(3);
146
+ const far = await searchOplog({ page: 99, limit: 20 });
147
+ expect(far.list).toEqual([]);
148
+ expect(far.total).toBe(3);
149
+ expect(far.truncated).toBe(false);
150
+ });
151
+
152
+ it("过滤与分页叠加:total 按过滤后的集合算", async () => {
153
+ await seedOplog(5);
154
+ await recordOp({ action: "other.act", module: "framework", target: "x" });
155
+ const r = await searchOplog({ action: "test.page", page: 1, limit: 2 });
156
+ expect(r.total).toBe(5);
157
+ expect(r.list).toHaveLength(2);
158
+ expect(r.list.every(x => x.action === "test.page")).toBe(true);
159
+ });
90
160
  });
91
161
 
92
162
  describe("PRD §4.3:系统设置", () => {
@@ -4,7 +4,7 @@
4
4
  /* eslint-disable test/prefer-lowercase-title -- AC 编号是验收锚点,保留大写 */
5
5
  import { afterEach, beforeAll, describe, expect, it } from "vitest";
6
6
 
7
- import { uninstallSolution, upsertSolutionPerms } from "../src/_platform/api/_shared/service/domain/solution-perms";
7
+ import { uninstallSolution, upsertSolutionPerms } from "../src/app/api/_platform/_shared/service/domain/solution-perms";
8
8
  import { installDb } from "./fixture";
9
9
 
10
10
  let dbo: ReturnType<typeof installDb>;
@@ -61,3 +61,32 @@ describe("users 表扩展(PRD §4.2 数据模型)", () => {
61
61
  expect(String(source?.dflt_value)).toContain("local");
62
62
  });
63
63
  });
64
+
65
+ // 回归:0017 给 users 加工号列。事故形态是「列进了库、schema.yaml 白名单漏了」,
66
+ // 构造器在发 SQL 前就拒成 `unknown column 'employee_no' in on 'users'`,日志里没有 SQL 可查。
67
+ // 白名单那半边在 packages/cli/src/__tests__/templates-schema-columns.test.ts 钉;
68
+ // 这里钉库侧语义:列真的落库、租户内唯一、NULL 不占位、跨租户可重复。
69
+ describe("users.employee_no(0017 工号列)", () => {
70
+ function addUser(username: string, tenant: string, employeeNo: string | null) {
71
+ dbo.prepare("INSERT INTO users (username, password_hash, tenant_id, employee_no) VALUES (?, 'x', ?, ?)")
72
+ .run(username, tenant, employeeNo);
73
+ }
74
+
75
+ it("迁移落出 employee_no 列", () => {
76
+ expect(colsOf("users")).toContain("employee_no");
77
+ });
78
+
79
+ it("唯一索引按 (tenant_id, employee_no) 且是部分索引(NULL 不占位)", () => {
80
+ const idx = dbo.prepare("SELECT sql FROM sqlite_master WHERE type = 'index' AND name = 'uk_users_employee_no'").get() as { sql: string | null } | undefined;
81
+ expect(idx?.sql).toContain("tenant_id");
82
+ expect(idx?.sql).toContain("employee_no IS NOT NULL");
83
+ });
84
+
85
+ it("同租户工号重复被拒,跨租户可重复,NULL 可多行", () => {
86
+ addUser("eno-a", "acme", "E-100");
87
+ expect(() => addUser("eno-b", "acme", "E-100")).toThrow(/UNIQUE/);
88
+ expect(() => addUser("eno-c", "globex", "E-100")).not.toThrow();
89
+ expect(() => addUser("eno-d", "acme", null)).not.toThrow();
90
+ expect(() => addUser("eno-e", "acme", null)).not.toThrow();
91
+ });
92
+ });
@@ -3,11 +3,11 @@
3
3
  // 禁止操作自己(AC1-10 注);roles 写 sys_user_role 唯一权威源 + 写时失效(AC1-7/D10);
4
4
  // 禁用/重置口令级联删 refresh(§3.1 吊销联动)。
5
5
  /* eslint-disable test/prefer-lowercase-title -- AC 编号是验收锚点,保留大写 */
6
- import type { SessionDeps } from "../src/auth/api/_shared/session";
6
+ import type { SessionDeps } from "../src/app/api/auth/_shared/session";
7
7
 
8
8
  import { afterEach, beforeAll, describe, expect, it } from "vitest";
9
- import { changePasswordByAdmin, createUser, listUsers, setForbidden, updateUserRoles } from "../src/_platform/api/_shared/service/domain/users";
10
- import { createSession } from "../src/auth/api/_shared/session";
9
+ import { changePasswordByAdmin, createUser, deleteUser, listUsers, setForbidden, updateUserProfile, updateUserRoles } from "../src/app/api/_platform/_shared/service/domain/users";
10
+ import { createSession } from "../src/app/api/auth/_shared/session";
11
11
  import { installDb } from "./fixture";
12
12
 
13
13
  let dbo: ReturnType<typeof installDb>;
@@ -150,6 +150,58 @@ describe("B7:口令重置与禁用(级联吊销)", () => {
150
150
  });
151
151
  });
152
152
 
153
+ describe("红线:只有 superadmin 能改 superadmin 账号", () => {
154
+ function mkUserWithRole(username: string, role: string): number {
155
+ seedRole(role);
156
+ const uid = mkUser(username);
157
+ dbo.prepare("INSERT INTO sys_user_role (user_id, role_name) VALUES (?, ?)").run(uid, role);
158
+ return uid;
159
+ }
160
+
161
+ it("admin 重置 superadmin 口令 / 启停 / 改资料 / 删除 → 403,且库里一个字段都没动", async () => {
162
+ seedRole("admin");
163
+ const actor = mkUserWithRole("u_admin1", "admin");
164
+ const target = mkUserWithRole("u_super1", "superadmin");
165
+ await expect(changePasswordByAdmin(actor, target, "newpw123", bcryptDeps)).rejects.toMatchObject({ status: 403 });
166
+ await expect(setForbidden(actor, target, true)).rejects.toMatchObject({ status: 403 });
167
+ await expect(updateUserProfile(actor, target, { displayName: "renamed", email: "x@y.z" })).rejects.toMatchObject({ status: 403 });
168
+ await expect(deleteUser(actor, target)).rejects.toMatchObject({ status: 403 });
169
+ const row = dbo.prepare("SELECT password_hash, is_forbidden, display_name, email FROM users WHERE id = ?").get(target) as { password_hash: string, is_forbidden: number, display_name: string | null, email: string | null };
170
+ expect(row.password_hash).toBe("x");
171
+ expect(Number(row.is_forbidden)).toBe(0);
172
+ expect(row.display_name).toBeNull();
173
+ expect(row.email).toBeNull();
174
+ });
175
+
176
+ it("admin 清空 superadmin 的角色 → 403(成员资格只拦授予方向,反向降级靠这条)", async () => {
177
+ seedRole("admin");
178
+ const actor = mkUserWithRole("u_admin2", "admin");
179
+ const target = mkUserWithRole("u_super2", "superadmin");
180
+ await expect(updateUserRoles(actor, target, [])).rejects.toMatchObject({ status: 403 });
181
+ await expect(updateUserRoles(actor, target, ["viewer"])).rejects.toMatchObject({ status: 403 });
182
+ expect(rolesOf(target)).toEqual(["superadmin"]);
183
+ });
184
+
185
+ it("superadmin 操作 superadmin → 放行(红线不是禁止改超管,而是限定谁能改)", async () => {
186
+ const actor = mkUserWithRole("u_super3", "superadmin");
187
+ const target = mkUserWithRole("u_super4", "superadmin");
188
+ await changePasswordByAdmin(actor, target, "newpw123", bcryptDeps);
189
+ await setForbidden(actor, target, true);
190
+ const row = dbo.prepare("SELECT password_hash, is_forbidden FROM users WHERE id = ?").get(target) as { password_hash: string, is_forbidden: number };
191
+ expect(row.password_hash).toBe("hashed:newpw123");
192
+ expect(Number(row.is_forbidden)).toBe(1);
193
+ });
194
+
195
+ it("admin 操作普通用户照旧放行(门禁只盯 superadmin 目标,不能把整页封死)", async () => {
196
+ seedRole("admin");
197
+ const actor = mkUserWithRole("u_admin3", "admin");
198
+ const target = mkUser("u_plain");
199
+ await changePasswordByAdmin(actor, target, "newpw123", bcryptDeps);
200
+ await setForbidden(actor, target, true);
201
+ expect(Number((dbo.prepare("SELECT is_forbidden FROM users WHERE id = ?").get(target) as { is_forbidden: number }).is_forbidden)).toBe(1);
202
+ });
203
+ });
204
+
153
205
  describe("B7:列表(用户管理页数据源)", () => {
154
206
  it("listUsers 带角色数组,排除口令哈希", async () => {
155
207
  seedRole("editor");
@@ -0,0 +1,40 @@
1
+ // BDD:/system/roles 展示页隐去 superadmin(事故:角色页把平台级角色当成站点可分配角色列出)。
2
+ //
3
+ // 过滤只发生在**展示层**,所以这里测纯函数而不是整页:页面本身只做 query + 渲染,
4
+ // 真正会回归的是「哪些行算可见」。后端那半边(listRoles 必须原样返回 superadmin)在
5
+ // tests/rbac-roles.spec.ts 里钉,两处合起来才防住「顺手把后端也过滤掉」的改法。
6
+ import { describe, expect, it } from "vitest";
7
+
8
+ import { HIDDEN_ROLE, visibleRoles } from "../src/app/web/system/pages/roles/visible-roles";
9
+
10
+ const ROWS = [
11
+ { name: "superadmin", module: "_platform" },
12
+ { name: "admin", module: "_platform" },
13
+ { name: "custom.wiki", module: "wiki" },
14
+ ];
15
+
16
+ describe("角色展示页:隐去 superadmin、其余原样", () => {
17
+ it("不列 superadmin,其余顺序不变", () => {
18
+ expect(visibleRoles(ROWS).map(r => r.name)).toEqual(["admin", "custom.wiki"]);
19
+ });
20
+
21
+ it("只按名字精确匹配:superadmin.* / 前缀相近的角色照常显示", () => {
22
+ const rows = [...ROWS, { name: "superadmin.readonly", module: "_platform" }, { name: "Admin", module: "_platform" }];
23
+ expect(visibleRoles(rows).map(r => r.name)).toEqual(["admin", "custom.wiki", "superadmin.readonly", "Admin"]);
24
+ });
25
+
26
+ it("不改入参(useQuery 缓存对象不能被就地改写)", () => {
27
+ const input = ROWS.map(r => ({ ...r }));
28
+ const before = input.map(r => r.name);
29
+ visibleRoles(input);
30
+ expect(input.map(r => r.name)).toEqual(before);
31
+ });
32
+
33
+ it("空列表不炸(查询未返回时的形态)", () => {
34
+ expect(visibleRoles([])).toEqual([]);
35
+ });
36
+
37
+ it("隐藏项与后端保留角色名同值(改名必须两处一起改)", () => {
38
+ expect(HIDDEN_ROLE).toBe("superadmin");
39
+ });
40
+ });
@@ -16,7 +16,7 @@ import { beforeAll, describe, expect, it } from "vitest";
16
16
  const req = createRequire(import.meta.url);
17
17
  const { DatabaseSync: Db } = req("node:sqlite") as { DatabaseSync: typeof DatabaseSync };
18
18
 
19
- const PLATFORM_API = join(dirname(fileURLToPath(import.meta.url)), "..", "src", "_platform", "api");
19
+ const PLATFORM_API = join(dirname(fileURLToPath(import.meta.url)), "..", "src", "app", "api", "_platform");
20
20
 
21
21
  let dbo: InstanceType<typeof DatabaseSync>;
22
22
 
@@ -11,7 +11,7 @@
11
11
  // 这里把「同一 uid 换租户必须重查」钉死,顺手钉住写时失效不得漏掉别的租户。
12
12
  import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
13
13
 
14
- import { currentAccess, invalidateAllUsers, invalidateUser, isSelfTenant } from "../src/_platform/api/_shared/service/infra/access";
14
+ import { currentAccess, invalidateAllUsers, invalidateUser, isSelfTenant } from "../src/app/api/_platform/_shared/service/infra/access";
15
15
  import { installDb } from "./fixture";
16
16
 
17
17
  const g = globalThis as any;