cabloy 5.1.98 → 5.1.99

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 (139) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/hooks/contract-loop-gate.ts +151 -44
  3. package/CHANGELOG.md +21 -0
  4. package/CLAUDE.md +2 -0
  5. package/cabloy-docs/.vitepress/config.mjs +4 -0
  6. package/cabloy-docs/backend/controller-aop-guide.md +0 -1
  7. package/cabloy-docs/frontend/a-openapi-under-the-hood.md +1 -1
  8. package/cabloy-docs/frontend/generated-contract-consumption-entry-branch.md +1 -1
  9. package/cabloy-docs/frontend/generated-contract-consumption-list-branch.md +1 -1
  10. package/cabloy-docs/frontend/generated-contract-consumption-verify-playbook.md +1 -1
  11. package/cabloy-docs/frontend/model-resource-owner-pattern.md +1 -1
  12. package/cabloy-docs/frontend/model-state-guide.md +3 -1
  13. package/cabloy-docs/frontend/resource-entry-page-deep-dive.md +2 -2
  14. package/cabloy-docs/frontend/resource-list-page-deep-dive.md +1 -1
  15. package/cabloy-docs/frontend/rest-resource-source-reading-map.md +2 -2
  16. package/cabloy-docs/frontend/rest-resource-under-the-hood.md +4 -4
  17. package/cabloy-docs/frontend/use-state-data-best-practices.md +384 -0
  18. package/package.json +1 -1
  19. package/scripts/upgrade.ts +2 -0
  20. package/vona/packages-cli/cli/package.json +1 -1
  21. package/vona/packages-cli/cli-set-api/package.json +1 -1
  22. package/vona/packages-vona/vona/package.json +1 -1
  23. package/vona/packages-vona/vona-core/package.json +1 -1
  24. package/vona/packages-vona/vona-core/src/lib/bean/beanBase.ts +9 -9
  25. package/vona/packages-vona/vona-mock/package.json +1 -1
  26. package/vona/pnpm-lock.yaml +4 -4
  27. package/vona/src/suite/a-training/modules/training-record/src/bean/fileScene.dossierFile.ts +22 -0
  28. package/vona/src/suite/a-training/modules/training-record/src/bean/meta.version.ts +1 -0
  29. package/vona/src/suite/a-training/modules/training-record/src/config/locale/en-us.ts +1 -0
  30. package/vona/src/suite/a-training/modules/training-record/src/config/locale/zh-cn.ts +1 -0
  31. package/vona/src/suite/a-training/modules/training-record/src/dto/recordDossierFileView.ts +47 -0
  32. package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectResItem.tsx +13 -0
  33. package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +13 -0
  34. package/vona/src/suite/a-training/modules/training-record/src/entity/record.tsx +16 -5
  35. package/vona/src/suite/a-training/modules/training-record/src/index.ts +2 -0
  36. package/vona/src/suite/a-training/modules/training-record/src/lib/index.ts +1 -0
  37. package/vona/src/suite/a-training/modules/training-record/src/lib/resolveDossierFiles.ts +34 -0
  38. package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +209 -123
  39. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +1 -0
  40. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +1 -0
  41. package/vona/src/suite/a-training/modules/training-student/src/dto/detailRecordBase.tsx +18 -1
  42. package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +0 -2
  43. package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +1 -0
  44. package/vona/src/suite-vendor/a-file/modules/a-file/package.json +5 -3
  45. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileUploadPolicy.ts +28 -0
  46. package/vona/src/suite-vendor/a-file/modules/a-file/src/controller/file.ts +8 -0
  47. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadPolicyRequest.ts +15 -0
  48. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadPolicyResponse.ts +30 -0
  49. package/vona/src/suite-vendor/a-file/package.json +2 -2
  50. package/vona/src/suite-vendor/a-image/modules/a-image/package.json +1 -1
  51. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +27 -0
  52. package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +8 -0
  53. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadPolicyRequest.ts +15 -0
  54. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadPolicyResponse.ts +27 -0
  55. package/vona/src/suite-vendor/a-image/modules/image-native/package.json +1 -1
  56. package/vona/src/suite-vendor/a-image/modules/image-native/src/controller/image.ts +1 -1
  57. package/vona/src/suite-vendor/a-image/package.json +1 -1
  58. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  59. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  60. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/.metadata/index.ts +24 -9
  61. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +87 -73
  62. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permissionActionByRoles.ts +27 -0
  63. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permissionUser.ts +20 -0
  64. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  65. package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +4 -3
  66. package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +1 -1
  67. package/vona/src/suite-vendor/a-vona/modules/a-upload/src/bean/interceptor.upload.ts +5 -1
  68. package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
  69. package/vona/src/suite-vendor/a-vona/modules/a-user/src/.metadata/index.ts +0 -13
  70. package/vona/src/suite-vendor/a-vona/modules/a-user/src/lib/passport.ts +0 -7
  71. package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
  72. package/vona/src/suite-vendor/a-vona/modules/a-version/src/bean/meta.version.ts +2 -24
  73. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  74. package/zova/packages-utils/zova-jsx/package.json +2 -2
  75. package/zova/packages-zova/zova/package.json +3 -3
  76. package/zova/packages-zova/zova-core/package.json +1 -1
  77. package/zova/packages-zova/zova-core/src/bean/beanBase.ts +9 -13
  78. package/zova/pnpm-lock.yaml +29 -14
  79. package/zova/src/suite/a-demo/modules/demo-basic/src/page/toolOne/controller.tsx +2 -2
  80. package/zova/src/suite/a-demo/modules/demo-todo/src/page/todo/controller.tsx +2 -2
  81. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx +2 -2
  82. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx +2 -2
  83. package/zova/src/suite/a-home/modules/home-login/src/page/login/controller.tsx +2 -2
  84. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockDetails/controller.tsx +5 -1
  85. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockTable/controller.tsx +20 -15
  86. package/zova/src/suite/cabloy-basic/modules/basic-details/src/service/detail.tsx +6 -1
  87. package/zova/src/suite/cabloy-basic/modules/basic-file/cli/openapi.config.ts +9 -0
  88. package/zova/src/suite/cabloy-basic/modules/basic-file/package.json +58 -0
  89. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/component/formFieldFile.ts +31 -0
  90. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/index.ts +222 -0
  91. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/locales.ts +7 -0
  92. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/this.ts +2 -0
  93. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/file.ts +121 -0
  94. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/baseURL.ts +5 -0
  95. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/index.ts +3 -0
  96. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/schemas.ts +306 -0
  97. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/types.ts +5718 -0
  98. package/zova/src/suite/cabloy-basic/modules/basic-file/src/apiSchema/file.ts +40 -0
  99. package/zova/src/suite/cabloy-basic/modules/basic-file/src/bean/tableCell.file.tsx +187 -0
  100. package/zova/src/suite/cabloy-basic/modules/basic-file/src/component/formFieldFile/controller.tsx +534 -0
  101. package/zova/src/suite/cabloy-basic/modules/basic-file/src/config/locale/en-us.ts +15 -0
  102. package/zova/src/suite/cabloy-basic/modules/basic-file/src/config/locale/zh-cn.ts +15 -0
  103. package/zova/src/suite/cabloy-basic/modules/basic-file/src/index.ts +4 -0
  104. package/zova/src/suite/cabloy-basic/modules/basic-file/src/lib/file.ts +74 -0
  105. package/zova/src/suite/cabloy-basic/modules/basic-file/src/lib/index.ts +1 -0
  106. package/zova/src/suite/cabloy-basic/modules/basic-file/src/model/file.ts +23 -0
  107. package/zova/src/suite/cabloy-basic/modules/basic-file/src/types/file.ts +12 -0
  108. package/zova/src/suite/cabloy-basic/modules/basic-file/src/types/index.ts +1 -0
  109. package/zova/src/suite/cabloy-basic/modules/basic-file/tsconfig.build.json +13 -0
  110. package/zova/src/suite/cabloy-basic/modules/basic-file/tsconfig.json +5 -0
  111. package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/index.ts +61 -0
  112. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/image.ts +38 -1
  113. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/imageNativeImage.ts +31 -0
  114. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/schemas.ts +52 -0
  115. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/types.ts +614 -1
  116. package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/image.ts +10 -0
  117. package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/imageNativeImage.ts +13 -0
  118. package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +126 -53
  119. package/zova/src/suite/cabloy-basic/modules/basic-image/src/model/image.ts +23 -0
  120. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +44 -4
  121. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx +17 -12
  122. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +2 -2
  123. package/zova/src/suite/cabloy-basic/modules/basic-resource/src/component/formFieldResourcePicker/controller.tsx +2 -2
  124. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/controller.tsx +7 -2
  125. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +34 -33
  126. package/zova/src/suite/cabloy-basic/package.json +1 -0
  127. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  128. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +5 -3
  129. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entry/controller.tsx +2 -2
  130. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controller.tsx +2 -2
  131. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  132. package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
  133. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/utils.ts +17 -17
  134. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  135. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +8 -3
  136. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  137. package/zova/src/suite-vendor/a-zova/package.json +4 -4
  138. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permission.ts +0 -17
  139. package/vona/src/suite-vendor/a-vona/modules/a-user/src/bean/guard.userName.ts +0 -35
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "vona-suite-a-file",
3
- "version": "5.0.3",
3
+ "version": "5.0.4",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "files": [],
7
7
  "type": "module",
8
8
  "dependencies": {
9
- "vona-module-a-file": "^5.0.3",
9
+ "vona-module-a-file": "^5.0.4",
10
10
  "vona-module-file-cloudflare": "^5.0.3",
11
11
  "vona-module-file-native": "^5.0.3"
12
12
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-image",
3
- "version": "5.0.4",
3
+ "version": "5.0.5",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Vona Module"
@@ -121,6 +121,33 @@ export class BeanImageUploadPolicy extends BeanBase {
121
121
  };
122
122
  }
123
123
 
124
+ async resolveSceneUploadPolicy(data: { imageScene: keyof IImageSceneRecord }) {
125
+ const imageConfig = this.scope.config.image;
126
+ const imageScene = data.imageScene;
127
+ const sceneOptions = this._getSceneOptions(imageScene);
128
+ const { providerName, clientName } = await this._resolveProvider(sceneOptions);
129
+ const { entityImageProvider, disabled } = await this.bean.imageProvider.getClientOptions({
130
+ providerName,
131
+ clientName,
132
+ });
133
+ if (!entityImageProvider || disabled) {
134
+ return this.app.throw(403, `Image provider unavailable: ${providerName}.${clientName}`);
135
+ }
136
+ const uploadOptions = {
137
+ ...(imageConfig.upload ?? {}),
138
+ ...(sceneOptions.upload ?? {}),
139
+ };
140
+ const mimeTypes = [...(uploadOptions.mimeTypes ?? [])];
141
+ const extensions = [...(uploadOptions.extensions ?? [])];
142
+ return {
143
+ imageScene,
144
+ maxSize: uploadOptions.maxSize,
145
+ mimeTypes: mimeTypes.length > 0 ? mimeTypes : undefined,
146
+ extensions: extensions.length > 0 ? extensions : undefined,
147
+ multiple: uploadOptions.multiple,
148
+ };
149
+ }
150
+
124
151
  async resolveUploadPolicy(data: {
125
152
  imageScene: keyof IImageSceneRecord;
126
153
  size: number;
@@ -12,6 +12,8 @@ import { DtoImageDirectUploadFinalizeRequest } from '../dto/imageDirectUploadFin
12
12
  import { DtoImageDirectUploadFinalizeResponse } from '../dto/imageDirectUploadFinalizeResponse.ts';
13
13
  import { DtoImageDirectUploadRequest } from '../dto/imageDirectUploadRequest.ts';
14
14
  import { DtoImageDirectUploadResponse } from '../dto/imageDirectUploadResponse.ts';
15
+ import { DtoImageUploadPolicyRequest } from '../dto/imageUploadPolicyRequest.ts';
16
+ import { DtoImageUploadPolicyResponse } from '../dto/imageUploadPolicyResponse.ts';
15
17
  import { DtoImageUploadResponse } from '../dto/imageUploadResponse.ts';
16
18
  import { DtoImageUploadTokenRequest } from '../dto/imageUploadTokenRequest.ts';
17
19
  import { DtoImageUploadTokenResponse } from '../dto/imageUploadTokenResponse.ts';
@@ -21,6 +23,12 @@ export interface IControllerOptionsImage extends IDecoratorControllerOptions {}
21
23
 
22
24
  @Controller<IControllerOptionsImage>({ path: 'image' })
23
25
  export class ControllerImage extends BeanBase {
26
+ @Web.post('upload-policy')
27
+ @Api.body(DtoImageUploadPolicyResponse)
28
+ async getUploadPolicy(@Arg.body() data: DtoImageUploadPolicyRequest) {
29
+ return await this.bean.imageUploadPolicy.resolveSceneUploadPolicy(data);
30
+ }
31
+
24
32
  @Web.post('upload-token')
25
33
  @Api.body(DtoImageUploadTokenResponse)
26
34
  async createUploadToken(@Arg.body() data: DtoImageUploadTokenRequest) {
@@ -0,0 +1,15 @@
1
+ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
2
+
3
+ import { Api } from 'vona-module-a-openapiutils';
4
+ import { Dto } from 'vona-module-a-web';
5
+ import z from 'zod';
6
+
7
+ import type { IImageSceneRecord } from '../types/imageScene.ts';
8
+
9
+ export interface IDtoOptionsImageUploadPolicyRequest extends IDecoratorDtoOptions {}
10
+
11
+ @Dto<IDtoOptionsImageUploadPolicyRequest>()
12
+ export class DtoImageUploadPolicyRequest {
13
+ @Api.field(z.string())
14
+ imageScene: keyof IImageSceneRecord;
15
+ }
@@ -0,0 +1,27 @@
1
+ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
2
+
3
+ import { Api, v } from 'vona-module-a-openapiutils';
4
+ import { Dto } from 'vona-module-a-web';
5
+ import z from 'zod';
6
+
7
+ import type { IImageSceneRecord } from '../types/imageScene.ts';
8
+
9
+ export interface IDtoOptionsImageUploadPolicyResponse extends IDecoratorDtoOptions {}
10
+
11
+ @Dto<IDtoOptionsImageUploadPolicyResponse>()
12
+ export class DtoImageUploadPolicyResponse {
13
+ @Api.field(z.string())
14
+ imageScene: keyof IImageSceneRecord;
15
+
16
+ @Api.field(v.optional(), z.number().int().positive())
17
+ maxSize?: number;
18
+
19
+ @Api.field(v.optional(), z.array(z.string()))
20
+ mimeTypes?: string[];
21
+
22
+ @Api.field(v.optional(), z.array(z.string()))
23
+ extensions?: string[];
24
+
25
+ @Api.field(v.optional(), z.boolean())
26
+ multiple?: boolean;
27
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-image-native",
3
- "version": "5.0.4",
3
+ "version": "5.0.5",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Vona Module"
@@ -39,7 +39,7 @@ export class ControllerImage extends BeanBase {
39
39
  }
40
40
  const stat = await fse.stat(file.file);
41
41
  const policy = await this.bean.imageUploadPolicy.resolveUploadPolicy({
42
- imageScene: image.imageScene,
42
+ imageScene: image.imageScene as never,
43
43
  size: Number(stat.size),
44
44
  mimeType: file.info.mimeType,
45
45
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-suite-a-image",
3
- "version": "5.0.4",
3
+ "version": "5.0.5",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "files": [],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-core",
3
- "version": "5.1.42",
3
+ "version": "5.1.43",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-permission",
3
- "version": "5.1.13",
3
+ "version": "5.1.14",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -19,7 +19,7 @@ declare module 'vona-module-a-permission' {
19
19
  /** bean: end */
20
20
  /** bean: begin */
21
21
  import type { BeanPermission } from '../bean/bean.permission.ts';
22
- import 'vona';
22
+ import 'vona';
23
23
  declare module 'vona' {
24
24
  export interface IBeanRecordGlobal {
25
25
  'permission': BeanPermission;
@@ -64,35 +64,50 @@ declare module 'vona-module-a-event' {
64
64
  }
65
65
  /** event: end */
66
66
  /** summerCache: begin */
67
- export * from '../bean/summerCache.permission.ts';
67
+ export * from '../bean/summerCache.permissionActionByRoles.ts';
68
+ export * from '../bean/summerCache.permissionUser.ts';
68
69
 
69
70
  import { type IDecoratorSummerCacheOptions } from 'vona-module-a-summer';
70
71
  declare module 'vona-module-a-summer' {
71
72
 
72
73
  export interface ISummerCacheRecord {
73
- 'a-permission:permission': IDecoratorSummerCacheOptions;
74
+ 'a-permission:permissionActionByRoles': IDecoratorSummerCacheOptions;
75
+ 'a-permission:permissionUser': IDecoratorSummerCacheOptions;
74
76
  }
75
77
 
76
78
 
77
79
  }
78
80
  declare module 'vona-module-a-permission' {
79
81
 
80
- export interface SummerCachePermission {
82
+ export interface SummerCachePermissionActionByRoles {
83
+ /** @internal */
84
+ get scope(): ScopeModuleAPermission;
85
+ }
86
+
87
+ export interface SummerCachePermissionActionByRoles {
88
+ get $beanFullName(): 'a-permission.summerCache.permissionActionByRoles';
89
+ get $onionName(): 'a-permission:permissionActionByRoles';
90
+ get $onionOptions(): IDecoratorSummerCacheOptions;
91
+ }
92
+
93
+ export interface SummerCachePermissionUser {
81
94
  /** @internal */
82
95
  get scope(): ScopeModuleAPermission;
83
96
  }
84
97
 
85
- export interface SummerCachePermission {
86
- get $beanFullName(): 'a-permission.summerCache.permission';
87
- get $onionName(): 'a-permission:permission';
98
+ export interface SummerCachePermissionUser {
99
+ get $beanFullName(): 'a-permission.summerCache.permissionUser';
100
+ get $onionName(): 'a-permission:permissionUser';
88
101
  get $onionOptions(): IDecoratorSummerCacheOptions;
89
102
  }
90
103
  }
91
104
  /** summerCache: end */
92
105
  /** summerCache: begin */
93
- import type { SummerCachePermission } from '../bean/summerCache.permission.ts';
106
+ import type { SummerCachePermissionActionByRoles } from '../bean/summerCache.permissionActionByRoles.ts';
107
+ import type { SummerCachePermissionUser } from '../bean/summerCache.permissionUser.ts';
94
108
  export interface IModuleSummerCache {
95
- 'permission': SummerCachePermission;
109
+ 'permissionActionByRoles': SummerCachePermissionActionByRoles;
110
+ 'permissionUser': SummerCachePermissionUser;
96
111
  }
97
112
  /** summerCache: end */
98
113
  /** dto: begin */
@@ -4,23 +4,19 @@ import type {
4
4
  IOpenapiPermissions,
5
5
  IResourceRecord,
6
6
  } from 'vona-module-a-openapi';
7
- import type { IGuardOptionsPassport, IGuardOptionsRoleName } from 'vona-module-a-user';
8
- import type { ContextRouteMetadata, IRecordResourceNameToRoutePathItem } from 'vona-module-a-web';
7
+ import type { IGuardOptionsPassport } from 'vona-module-a-user';
8
+ import type { ContextRoute, IRecordResourceNameToRoutePathItem } from 'vona-module-a-web';
9
9
 
10
- import { appMetadata, appResource, BeanBase, beanFullNameFromOnionName } from 'vona';
10
+ import { appResource, BeanBase, beanFullNameFromOnionName } from 'vona';
11
11
  import { Bean } from 'vona-module-a-bean';
12
12
  import { Caching } from 'vona-module-a-caching';
13
- import { SymbolUseOnionOptionsRouteReal } from 'vona-module-a-onion';
14
- import { recordResourceNameToRoutePath } from 'vona-module-a-web';
13
+ import {
14
+ composeGuards,
15
+ getCacheControllerRoutes,
16
+ recordResourceNameToRoutePath,
17
+ } from 'vona-module-a-web';
15
18
 
16
19
  const BeanFullNameGuardPassport = beanFullNameFromOnionName('a-user:passport', 'guard');
17
- const BeanFullNameGuardRoleName = beanFullNameFromOnionName('a-user:roleName', 'guard');
18
- const BeanFullNameGuardUserName = beanFullNameFromOnionName('a-user:userName', 'guard');
19
- const BeanFullNamesGuardSupported = new Set<string>([
20
- BeanFullNameGuardPassport,
21
- BeanFullNameGuardRoleName,
22
- BeanFullNameGuardUserName,
23
- ]);
24
20
  const GuardOptionsPassportDefault: IGuardOptionsPassport = {
25
21
  public: false,
26
22
  activated: true,
@@ -32,10 +28,14 @@ type TGuardMeta = Record<string, unknown>;
32
28
  @Bean()
33
29
  export class BeanPermission extends BeanBase {
34
30
  public async clearAllCaches(): Promise<void> {
35
- const cachePermission = this.bean.summer.cache(
36
- beanFullNameFromOnionName('a-permission:permission', 'summerCache'),
31
+ const cachePermissionUser = this.bean.summer.cache(
32
+ beanFullNameFromOnionName('a-permission:permissionUser', 'summerCache'),
37
33
  );
38
- await cachePermission.clear();
34
+ const cachePermissionActionByRoles = this.bean.summer.cache(
35
+ beanFullNameFromOnionName('a-permission:permissionActionByRoles', 'summerCache'),
36
+ );
37
+ await cachePermissionUser.clear();
38
+ await cachePermissionActionByRoles.clear();
39
39
  }
40
40
 
41
41
  protected retrievePermissionsCacheKey(info: ICachingActionKeyInfo): string {
@@ -44,21 +44,22 @@ export class BeanPermission extends BeanBase {
44
44
  return `user:${resource}_${userId}`;
45
45
  }
46
46
 
47
- protected retrievePermissionsDefaultCacheKey(info: ICachingActionKeyInfo): string {
48
- return `default:${this._buildPermissionProfileKey(info.args[0] as never)}`;
47
+ protected retrievePermissionActionCacheKey(info: ICachingActionKeyInfo): string {
48
+ const [resource, actionKey] = info.args as [keyof IResourceRecord, string];
49
+ const roleIdsKey = this._extractCurrentRoleIdsSorted().join(',') || 'none';
50
+ return `action:${resource}:${actionKey}:roles:${roleIdsKey}`;
49
51
  }
50
52
 
51
- @Caching.get({ cacheName: 'a-permission:permission', cacheKeyFn: 'retrievePermissionsCacheKey' })
53
+ @Caching.get({
54
+ cacheName: 'a-permission:permissionUser',
55
+ cacheKeyFn: 'retrievePermissionsCacheKey',
56
+ })
52
57
  async retrievePermissions(resource: keyof IResourceRecord): Promise<IOpenapiPermissions> {
53
58
  return await this.scope.event.retrievePermissions.emit({ resource }, async () => {
54
59
  return await this.retrievePermissionsDefault(resource);
55
60
  });
56
61
  }
57
62
 
58
- @Caching.get({
59
- cacheName: 'a-permission:permission',
60
- cacheKeyFn: 'retrievePermissionsDefaultCacheKey',
61
- })
62
63
  protected async retrievePermissionsDefault(
63
64
  resource: keyof IResourceRecord,
64
65
  ): Promise<IOpenapiPermissions> {
@@ -66,67 +67,72 @@ export class BeanPermission extends BeanBase {
66
67
  }
67
68
 
68
69
  async getPermissionsDefault(resource: keyof IResourceRecord): Promise<IOpenapiPermissions> {
69
- const routePathInfo: IRecordResourceNameToRoutePathItem =
70
- recordResourceNameToRoutePath[resource];
71
- if (!routePathInfo) throw new Error(`not found routePath of resource: ${resource}`);
72
- const controller = routePathInfo.controller;
73
- if (!appResource.getBean(controller)) throw new Error('invalid controller');
74
- const descs = Object.getOwnPropertyDescriptors(controller.prototype);
75
70
  const actionsIgnore = this.scope.config.permission.actionsIgnore;
76
- const actionKeys = Object.keys(descs).filter(
77
- actionKey => actionKey !== 'constructor' && !actionsIgnore.includes(actionKey),
71
+ const actionRoutes = this._getControllerRoutes(resource).filter(
72
+ route => !actionsIgnore.includes(route.action),
78
73
  );
79
74
  const permissionsActions: IOpenapiPermissionModeActionActions = {};
80
- for (const actionKey of actionKeys) {
81
- const desc = descs[actionKey];
82
- if (!desc.value || typeof desc.value !== 'function') continue;
83
- const routeReal: ContextRouteMetadata | undefined = appMetadata.getMetadata(
84
- SymbolUseOnionOptionsRouteReal,
85
- controller.prototype,
86
- actionKey,
75
+ for (const route of actionRoutes) {
76
+ permissionsActions[route.action] = await this.retrievePermissionAction(
77
+ resource,
78
+ route.action,
87
79
  );
88
- permissionsActions[actionKey] = this._getPermissionOfActionByMetadata(routeReal);
89
80
  }
90
81
  return { actions: permissionsActions };
91
82
  }
92
83
 
93
- private _buildPermissionProfileKey(resource: keyof IResourceRecord): string {
94
- if (!this.bean.passport.isAuthenticated) return `${resource}__anon`;
95
- const activated = this.bean.passport.isActivated ? 1 : 0;
96
- const roleNames = this._extractCurrentRoleNames();
97
- return `${resource}__auth__act:${activated}__roles:${roleNames.join(',')}`;
84
+ public async retrievePermissionAction(
85
+ resource: keyof IResourceRecord,
86
+ actionKey: string,
87
+ ): Promise<boolean> {
88
+ const route = this._getControllerActionRoute(resource, actionKey);
89
+ if (!route?.route?.meta) return false;
90
+ if (!this._matchPassportMeta(route.route.meta)) return false;
91
+ return await this.retrievePermissionActionByRoles(resource, actionKey);
98
92
  }
99
93
 
100
- private _extractCurrentRoleNames(): string[] {
101
- const roleNames = this.bean.passport.currentRoles?.map(item => item.name) ?? [];
102
- return Array.from(new Set(roleNames)).sort();
94
+ @Caching.get({
95
+ cacheName: 'a-permission:permissionActionByRoles',
96
+ cacheKeyFn: 'retrievePermissionActionCacheKey',
97
+ })
98
+ protected async retrievePermissionActionByRoles(
99
+ resource: keyof IResourceRecord,
100
+ actionKey: string,
101
+ ): Promise<boolean> {
102
+ const route = this._getControllerActionRoute(resource, actionKey);
103
+ if (!route?.route?.meta) return false;
104
+ return await this._evaluatePermissionAction(route);
103
105
  }
104
106
 
105
- private _getPermissionOfActionByMetadata(routeReal: ContextRouteMetadata | undefined): boolean {
106
- if (!routeReal?.meta) return false;
107
- return this._evaluatePassportGuardsStatically(routeReal.meta);
107
+ private _getRoutePathInfo(resource: keyof IResourceRecord): IRecordResourceNameToRoutePathItem {
108
+ const routePathInfo = recordResourceNameToRoutePath[resource];
109
+ if (!routePathInfo) throw new Error(`not found routePath of resource: ${resource}`);
110
+ return routePathInfo;
108
111
  }
109
112
 
110
- private _evaluatePassportGuardsStatically(meta: TGuardMeta): boolean {
111
- if (this._hasUnknownGuard(meta)) return false;
112
- if (this._hasDynamicUserNameGuard(meta)) return false;
113
- const passportOptions = this._getPassportOptions(meta);
114
- if (!this._matchPassportOptions(passportOptions)) return false;
115
- const roleNameOptions = meta[BeanFullNameGuardRoleName] as
116
- | Partial<IGuardOptionsRoleName>
117
- | undefined;
118
- if (!this._matchRoleNameOptions(roleNameOptions)) return false;
119
- return true;
113
+ private _getControllerRoutes(resource: keyof IResourceRecord): ContextRoute[] {
114
+ const routePathInfo = this._getRoutePathInfo(resource);
115
+ const controller = routePathInfo.controller;
116
+ const beanOptions = appResource.getBean(controller);
117
+ if (!beanOptions) throw new Error('invalid controller');
118
+ return getCacheControllerRoutes(this.app)[beanOptions.beanFullName] ?? [];
120
119
  }
121
120
 
122
- private _hasUnknownGuard(meta: TGuardMeta): boolean {
123
- return Object.keys(meta).some(
124
- key => key.includes('.guard.') && !BeanFullNamesGuardSupported.has(key),
125
- );
121
+ private _getControllerActionRoute(
122
+ resource: keyof IResourceRecord,
123
+ actionKey: string,
124
+ ): ContextRoute | undefined {
125
+ return this._getControllerRoutes(resource).find(route => route.action === actionKey);
126
126
  }
127
127
 
128
- private _hasDynamicUserNameGuard(meta: TGuardMeta): boolean {
129
- return BeanFullNameGuardUserName in meta;
128
+ private _extractCurrentRoleIdsSorted(): string[] {
129
+ const roleIds = this.bean.passport.currentRoles?.map(item => String(item.id)) ?? [];
130
+ return Array.from(new Set(roleIds)).sort();
131
+ }
132
+
133
+ private _matchPassportMeta(meta: TGuardMeta): boolean {
134
+ const passportOptions = this._getPassportOptions(meta);
135
+ return this._matchPassportOptions(passportOptions);
130
136
  }
131
137
 
132
138
  private _getPassportOptions(meta: TGuardMeta): IGuardOptionsPassport {
@@ -147,13 +153,21 @@ export class BeanPermission extends BeanBase {
147
153
  return true;
148
154
  }
149
155
 
150
- private _matchRoleNameOptions(options: Partial<IGuardOptionsRoleName> | undefined): boolean {
151
- if (!options) return true;
152
- if (!options.name) return false;
153
- if (options.passWhenMatched === false) return false;
154
- if (options.rejectWhenDismatched === false) return false;
155
- const roleNamesCurrent = this._extractCurrentRoleNames();
156
- const roleNamesRequired = Array.isArray(options.name) ? options.name : [options.name];
157
- return roleNamesCurrent.some(roleName => roleNamesRequired.includes(roleName as any));
156
+ private async _evaluatePermissionAction(route: ContextRoute): Promise<boolean> {
157
+ const ctx = this.ctx as any;
158
+ const routePrevious = ctx.route;
159
+ const innerAccessPrevious = ctx.innerAccess;
160
+ try {
161
+ ctx.route = route;
162
+ ctx.innerAccess = false;
163
+ const result = await composeGuards(this.app, route)(this.ctx);
164
+ return result !== false;
165
+ } catch (err: any) {
166
+ if ([401, 403].includes(err?.code)) return false;
167
+ throw err;
168
+ } finally {
169
+ ctx.route = routePrevious;
170
+ ctx.innerAccess = innerAccessPrevious;
171
+ }
158
172
  }
159
173
  }
@@ -0,0 +1,27 @@
1
+ import type { ISummerCacheGet, TSummerCacheActionOptions } from 'vona-module-a-summer';
2
+
3
+ import { ServiceSummerCacheBase, SummerCache } from 'vona-module-a-summer';
4
+
5
+ export type TSummerCachePermissionActionByRolesKey = any;
6
+ export type TSummerCachePermissionActionByRolesData = any;
7
+
8
+ @SummerCache()
9
+ export class SummerCachePermissionActionByRoles
10
+ extends ServiceSummerCacheBase<
11
+ TSummerCachePermissionActionByRolesKey,
12
+ TSummerCachePermissionActionByRolesData
13
+ >
14
+ implements
15
+ ISummerCacheGet<
16
+ TSummerCachePermissionActionByRolesKey,
17
+ TSummerCachePermissionActionByRolesData
18
+ >
19
+ {
20
+ async getNative(
21
+ _key?: TSummerCachePermissionActionByRolesKey,
22
+ _options?: TSummerCacheActionOptions<
23
+ TSummerCachePermissionActionByRolesKey,
24
+ TSummerCachePermissionActionByRolesData
25
+ >,
26
+ ): Promise<TSummerCachePermissionActionByRolesData | undefined> {}
27
+ }
@@ -0,0 +1,20 @@
1
+ import type { ISummerCacheGet, TSummerCacheActionOptions } from 'vona-module-a-summer';
2
+
3
+ import { ServiceSummerCacheBase, SummerCache } from 'vona-module-a-summer';
4
+
5
+ export type TSummerCachePermissionUserKey = any;
6
+ export type TSummerCachePermissionUserData = any;
7
+
8
+ @SummerCache()
9
+ export class SummerCachePermissionUser
10
+ extends ServiceSummerCacheBase<TSummerCachePermissionUserKey, TSummerCachePermissionUserData>
11
+ implements ISummerCacheGet<TSummerCachePermissionUserKey, TSummerCachePermissionUserData>
12
+ {
13
+ async getNative(
14
+ _key?: TSummerCachePermissionUserKey,
15
+ _options?: TSummerCacheActionOptions<
16
+ TSummerCachePermissionUserKey,
17
+ TSummerCachePermissionUserData
18
+ >,
19
+ ): Promise<TSummerCachePermissionUserData | undefined> {}
20
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-startup",
3
- "version": "5.1.13",
3
+ "version": "5.1.14",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -3,8 +3,7 @@ import type { IOnionSlice } from 'vona-module-a-onion';
3
3
 
4
4
  import { isNil } from '@cabloy/utils';
5
5
  import fse from 'fs-extra';
6
- import path from 'node:path';
7
- import { BeanBase, cast } from 'vona';
6
+ import { BeanBase, cast, getRuntimePathPhysicalRoot } from 'vona';
8
7
  import { Service } from 'vona-module-a-bean';
9
8
 
10
9
  import type {
@@ -176,6 +175,8 @@ export class ServiceStartup extends BeanBase {
176
175
  // redis
177
176
  await this.$scope.redis.service.redis.clearAllData();
178
177
  // .app/public
179
- await fse.remove(path.join(this.app.config.server.publicDir, '1'));
178
+ await fse.remove(this.app.config.server.publicDir);
179
+ // .app/runtime
180
+ await fse.remove(getRuntimePathPhysicalRoot(this.app));
180
181
  }
181
182
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-upload",
3
- "version": "5.1.13",
3
+ "version": "5.1.14",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -52,7 +52,11 @@ export class InterceptorUpload extends BeanBase implements IInterceptorExecute {
52
52
  reject(err);
53
53
  };
54
54
  // bb
55
- const bb = Busboy(Object.assign({}, options.busboy, { headers: this.ctx.req.headers }));
55
+ const bb = Busboy(
56
+ Object.assign({ defParamCharset: 'utf8' }, options.busboy, {
57
+ headers: this.ctx.req.headers,
58
+ }),
59
+ );
56
60
  bb.on('file', (name, file, info) => {
57
61
  // temp
58
62
  const fileTempObj = tmp.fileSync({
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-user",
3
- "version": "5.1.17",
3
+ "version": "5.1.18",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -2,10 +2,8 @@
2
2
  /** guard: begin */
3
3
  export * from '../bean/guard.passport.ts';
4
4
  export * from '../bean/guard.roleName.ts';
5
- export * from '../bean/guard.userName.ts';
6
5
  import type { IGuardOptionsPassport } from '../bean/guard.passport.ts';
7
6
  import type { IGuardOptionsRoleName } from '../bean/guard.roleName.ts';
8
- import type { IGuardOptionsUserName } from '../bean/guard.userName.ts';
9
7
  import 'vona-module-a-aspect';
10
8
  declare module 'vona-module-a-aspect' {
11
9
 
@@ -16,7 +14,6 @@ declare module 'vona-module-a-aspect' {
16
14
 
17
15
  export interface IGuardRecordLocal {
18
16
  'a-user:roleName': IGuardOptionsRoleName;
19
- 'a-user:userName': IGuardOptionsUserName;
20
17
  }
21
18
 
22
19
  }
@@ -44,16 +41,6 @@ declare module 'vona-module-a-user' {
44
41
  get $onionOptions(): IGuardOptionsRoleName;
45
42
  }
46
43
 
47
- export interface GuardUserName {
48
- /** @internal */
49
- get scope(): ScopeModuleAUser;
50
- }
51
-
52
- export interface GuardUserName {
53
- get $beanFullName(): 'a-user.guard.userName';
54
- get $onionName(): 'a-user:userName';
55
- get $onionOptions(): IGuardOptionsUserName;
56
- }
57
44
  }
58
45
  /** guard: end */
59
46
  /** bean: begin */
@@ -1,7 +1,6 @@
1
1
  import { Aspect } from 'vona-module-a-aspect';
2
2
 
3
3
  import type { IGuardOptionsRoleName } from '../bean/guard.roleName.ts';
4
- import type { IGuardOptionsUserName } from '../bean/guard.userName.ts';
5
4
 
6
5
  function Public(_public: boolean = true): ClassDecorator & MethodDecorator {
7
6
  return Aspect.guardGlobal('a-user:passport', { public: _public });
@@ -12,10 +11,6 @@ function Activated(activated?: boolean): ClassDecorator & MethodDecorator {
12
11
  return Aspect.guardGlobal('a-user:passport', { activated });
13
12
  }
14
13
 
15
- function UserName(options?: Partial<IGuardOptionsUserName>): ClassDecorator & MethodDecorator {
16
- return Aspect.guard('a-user:userName', options);
17
- }
18
-
19
14
  function RoleName(options?: Partial<IGuardOptionsRoleName>): ClassDecorator & MethodDecorator {
20
15
  return Aspect.guard('a-user:roleName', options);
21
16
  }
@@ -29,7 +24,6 @@ function Admin(
29
24
  export interface IDecoratorGroupPassport {
30
25
  public: typeof Public;
31
26
  activated: typeof Activated;
32
- userName: typeof UserName;
33
27
  roleName: typeof RoleName;
34
28
  admin: typeof Admin;
35
29
  }
@@ -37,7 +31,6 @@ export interface IDecoratorGroupPassport {
37
31
  export const Passport: IDecoratorGroupPassport = {
38
32
  public: Public,
39
33
  activated: Activated,
40
- userName: UserName,
41
34
  roleName: RoleName,
42
35
  admin: Admin,
43
36
  } as unknown as IDecoratorGroupPassport;