cabloy 5.1.95 → 5.1.97

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 (208) hide show
  1. package/.cabloy-version +1 -0
  2. package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +4 -1
  3. package/.claude/skills/cabloy-resource-field-update/SKILL.md +6 -3
  4. package/.github/workflows/vona-test-crud.yml +2 -0
  5. package/.github/workflows/vona-test-mysql.yml +2 -0
  6. package/.github/workflows/vona-test-pg.yml +2 -0
  7. package/.github/workflows/vona-test-sqlite3.yml +2 -0
  8. package/CHANGELOG.md +38 -0
  9. package/CLAUDE.md +1 -0
  10. package/cabloy-docs/ai/playbook-backend-module.md +6 -3
  11. package/cabloy-docs/fullstack/framework-performance.md +3 -3
  12. package/cabloy-docs/fullstack/quickstart.md +1 -1
  13. package/package.json +1 -1
  14. package/scripts/release.ts +27 -1
  15. package/vona/docker-compose-original/docker-compose-dockerfile-app +4 -0
  16. package/vona/docker-compose-original/docker-compose.original.yml +0 -1
  17. package/vona/env/.env +0 -1
  18. package/vona/env/.env.prod +0 -1
  19. package/vona/env/.env.prod.docker +0 -7
  20. package/vona/package.original.json +3 -3
  21. package/vona/packages-cli/cabloy-cli/package.json +2 -2
  22. package/vona/packages-cli/cli/package.json +1 -1
  23. package/vona/packages-cli/cli-set-api/package.json +2 -2
  24. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.build.ts +70 -2
  25. package/vona/packages-utils/babel-plugin-vona-bean-module/package.json +2 -2
  26. package/vona/packages-utils/module-glob/package.json +2 -2
  27. package/vona/packages-utils/module-info-pro/package.json +2 -2
  28. package/vona/packages-utils/utils/package.json +2 -2
  29. package/vona/packages-vona/vona/package.json +1 -1
  30. package/vona/packages-vona/vona-core/package.json +2 -2
  31. package/vona/packages-vona/vona-core/src/lib/addon/addon.ts +77 -0
  32. package/vona/packages-vona/vona-core/src/lib/addon/index.ts +3 -0
  33. package/vona/packages-vona/vona-core/src/lib/addon/sharp.ts +46 -0
  34. package/vona/packages-vona/vona-core/src/lib/addon/sqlite3.ts +23 -0
  35. package/vona/packages-vona/vona-core/src/lib/index.ts +1 -0
  36. package/vona/packages-vona/vona-core/src/lib/utils/index.ts +0 -1
  37. package/vona/packages-vona/vona-core/src/lib/utils/util.ts +5 -0
  38. package/vona/packages-vona/vona-mock/package.json +2 -2
  39. package/vona/pnpm-lock.yaml +441 -62
  40. package/vona/pnpm-workspace.yaml +1 -1
  41. package/vona/src/backend/cli.ts +2 -5
  42. package/vona/src/backend/config/config/config.ts +0 -4
  43. package/vona/src/suite/a-demo/modules/demo-basic/src/bean/ssrMenu.admin.ts +1 -1
  44. package/vona/src/suite/a-training/modules/training-record/src/.metadata/index.ts +26 -0
  45. package/vona/src/suite/a-training/modules/training-record/src/bean/imageScene.sceneImage.ts +11 -0
  46. package/vona/src/suite/a-training/modules/training-record/src/bean/meta.version.ts +1 -0
  47. package/vona/src/suite/a-training/modules/training-record/src/config/locale/en-us.ts +1 -0
  48. package/vona/src/suite/a-training/modules/training-record/src/config/locale/zh-cn.ts +1 -0
  49. package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectResItem.tsx +12 -0
  50. package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +12 -0
  51. package/vona/src/suite/a-training/modules/training-record/src/entity/record.tsx +26 -1
  52. package/vona/src/suite/a-training/modules/training-record/src/index.ts +1 -0
  53. package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +27 -1
  54. package/vona/src/suite/a-training/modules/training-student/src/bean/imageScene.studentImage.ts +10 -0
  55. package/vona/src/suite/a-training/modules/training-student/src/bean/meta.version.ts +1 -0
  56. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +1 -0
  57. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +1 -0
  58. package/vona/src/suite/a-training/modules/training-student/src/dto/detailRecordBase.tsx +16 -2
  59. package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +1 -1
  60. package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx +12 -0
  61. package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +1 -1
  62. package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +13 -1
  63. package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +26 -1
  64. package/vona/src/suite/a-training/modules/training-student/src/index.ts +1 -0
  65. package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +1 -0
  66. package/vona/src/suite-vendor/a-auth/modules/a-auth/package.json +1 -1
  67. package/vona/src/suite-vendor/a-auth/modules/a-auth/src/bean/bean.authProvider.ts +2 -5
  68. package/vona/src/suite-vendor/a-auth/package.json +1 -1
  69. package/vona/src/suite-vendor/a-image/modules/a-image/cli/imageProvider/boilerplate/{{sceneName}}.{{beanName}}.ts_ +23 -0
  70. package/vona/src/suite-vendor/a-image/modules/a-image/cli/imageScene/boilerplate/{{sceneName}}.{{beanName}}.ts_ +5 -0
  71. package/vona/src/suite-vendor/a-image/modules/a-image/package.json +68 -0
  72. package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/index.ts +482 -0
  73. package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/locales.ts +18 -0
  74. package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/this.ts +2 -0
  75. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.image.ts +443 -0
  76. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageProvider.ts +55 -0
  77. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +174 -0
  78. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.redlock.ts +11 -0
  79. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.version.ts +40 -0
  80. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/serializerTransform.resolveView.ts +44 -0
  81. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/serializerTransform.resolveViews.ts +44 -0
  82. package/vona/src/suite-vendor/a-image/modules/a-image/src/config/config.ts +17 -0
  83. package/vona/src/suite-vendor/a-image/modules/a-image/src/config/locale/en-us.ts +1 -0
  84. package/vona/src/suite-vendor/a-image/modules/a-image/src/config/locale/zh-cn.ts +1 -0
  85. package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +165 -0
  86. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDeliveryRequest.ts +20 -0
  87. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadRequest.ts +36 -0
  88. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadResponse.ts +38 -0
  89. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageTransformOptions.tsx +42 -0
  90. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadResponse.ts +62 -0
  91. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadTokenRequest.ts +24 -0
  92. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadTokenResponse.ts +16 -0
  93. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadUrlRequest.ts +33 -0
  94. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageView.ts +46 -0
  95. package/vona/src/suite-vendor/a-image/modules/a-image/src/entity/image.ts +58 -0
  96. package/vona/src/suite-vendor/a-image/modules/a-image/src/entity/imageProvider.ts +24 -0
  97. package/vona/src/suite-vendor/a-image/modules/a-image/src/index.ts +4 -0
  98. package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/imageProvider.ts +9 -0
  99. package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/imageScene.ts +7 -0
  100. package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/imageVariant.ts +46 -0
  101. package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/index.ts +3 -0
  102. package/vona/src/suite-vendor/a-image/modules/a-image/src/model/image.ts +10 -0
  103. package/vona/src/suite-vendor/a-image/modules/a-image/src/model/imageProvider.ts +10 -0
  104. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/image.ts +208 -0
  105. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageProvider.ts +110 -0
  106. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageScene.ts +52 -0
  107. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/index.ts +3 -0
  108. package/vona/src/suite-vendor/a-image/modules/a-image/tsconfig.build.json +11 -0
  109. package/vona/src/suite-vendor/a-image/modules/a-image/tsconfig.json +7 -0
  110. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/package.json +49 -0
  111. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/.metadata/index.ts +89 -0
  112. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/.metadata/this.ts +2 -0
  113. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/bean/imageProvider.cloudflare.ts +155 -0
  114. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/index.ts +1 -0
  115. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/lib/cloudflareImageUrl.ts +112 -0
  116. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts +273 -0
  117. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/tsconfig.build.json +11 -0
  118. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/tsconfig.json +7 -0
  119. package/vona/src/suite-vendor/a-image/modules/image-native/package.json +63 -0
  120. package/vona/src/suite-vendor/a-image/modules/image-native/src/.metadata/index.ts +114 -0
  121. package/vona/src/suite-vendor/a-image/modules/image-native/src/.metadata/this.ts +2 -0
  122. package/vona/src/suite-vendor/a-image/modules/image-native/src/bean/imageProvider.native.ts +131 -0
  123. package/vona/src/suite-vendor/a-image/modules/image-native/src/config/config.ts +11 -0
  124. package/vona/src/suite-vendor/a-image/modules/image-native/src/index.ts +1 -0
  125. package/vona/src/suite-vendor/a-image/modules/image-native/src/monkey.ts +33 -0
  126. package/vona/src/suite-vendor/a-image/modules/image-native/src/service/imageNative.ts +291 -0
  127. package/vona/src/suite-vendor/a-image/modules/image-native/tsconfig.build.json +11 -0
  128. package/vona/src/suite-vendor/a-image/modules/image-native/tsconfig.json +7 -0
  129. package/vona/src/suite-vendor/a-image/package.json +14 -0
  130. package/vona/src/suite-vendor/a-image/tsconfig.base.json +4 -0
  131. package/vona/src/suite-vendor/a-image/tsconfig.json +16 -0
  132. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +2 -2
  133. package/vona/src/suite-vendor/a-vona/modules/a-jwt/package.json +1 -1
  134. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/service/jwtClient.ts +5 -0
  135. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/types/jwt.ts +1 -0
  136. package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/package.json +1 -1
  137. package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/schema/makeSchemaLikes.ts +6 -3
  138. package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/utils.ts +13 -7
  139. package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
  140. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/database.ts +0 -1
  141. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +9 -1
  142. package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +1 -1
  143. package/vona/src/suite-vendor/a-vona/modules/a-upload/src/bean/interceptor.upload.ts +39 -7
  144. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  145. package/zova/packages-cli/cli/package.json +3 -3
  146. package/zova/packages-cli/cli-set-front/package.json +9 -9
  147. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.openapi.generate.ts +20 -10
  148. package/zova/packages-utils/babel-plugin-zova-bean-module/package.json +2 -2
  149. package/zova/packages-utils/babel-plugin-zova-bean-use/package.json +2 -2
  150. package/zova/packages-utils/babel-plugin-zova-behavior/package.json +2 -2
  151. package/zova/packages-utils/babel-plugin-zova-component/package.json +2 -2
  152. package/zova/packages-utils/zova-jsx/package.json +3 -3
  153. package/zova/packages-utils/zova-vite/package.json +7 -7
  154. package/zova/packages-zova/zova/package.json +3 -3
  155. package/zova/packages-zova/zova-core/package.json +3 -3
  156. package/zova/pnpm-lock.yaml +126 -74
  157. package/zova/src/suite/a-home/modules/home-api/src/api/captcha.ts +3 -1
  158. package/zova/src/suite/a-home/modules/home-api/src/api/home.ts +3 -1
  159. package/zova/src/suite/a-home/modules/home-api/src/api/homeBaseMenu.ts +3 -1
  160. package/zova/src/suite/a-home/modules/home-api/src/api/homeBasePermission.ts +3 -1
  161. package/zova/src/suite/a-home/modules/home-api/src/api/homeUserPassport.ts +22 -20
  162. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/schemas.ts +163 -53
  163. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +2060 -675
  164. package/zova/src/suite/a-home/modules/home-api/src/api/testSsrToolOne.ts +3 -1
  165. package/zova/src/suite/a-home/modules/home-api/src/apiSchema/captcha.ts +3 -1
  166. package/zova/src/suite/a-home/modules/home-api/src/apiSchema/home.ts +3 -1
  167. package/zova/src/suite/a-home/modules/home-api/src/apiSchema/homeBaseMenu.ts +3 -1
  168. package/zova/src/suite/a-home/modules/home-api/src/apiSchema/homeBasePermission.ts +3 -1
  169. package/zova/src/suite/a-home/modules/home-api/src/apiSchema/homeUserPassport.ts +13 -11
  170. package/zova/src/suite/a-home/modules/home-api/src/apiSchema/testSsrToolOne.ts +3 -1
  171. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +54 -16
  172. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +468 -8
  173. package/zova/src/suite/a-training/modules/training-student/src/apiSchema/trainingStudent.ts +3 -1
  174. package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionUpdate.tsx +1 -1
  175. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/actionCreate/controller.tsx +1 -1
  176. package/zova/src/suite/cabloy-basic/modules/basic-details/src/service/detail.tsx +53 -1
  177. package/zova/src/suite/cabloy-basic/modules/basic-image/cli/openapi.config.ts +9 -0
  178. package/zova/src/suite/cabloy-basic/modules/basic-image/package.json +60 -0
  179. package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/component/formFieldImage.ts +31 -0
  180. package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/index.ts +191 -0
  181. package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/locales.ts +7 -0
  182. package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/this.ts +2 -0
  183. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/image.ts +104 -0
  184. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/baseURL.ts +5 -0
  185. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/index.ts +3 -0
  186. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/schemas.ts +262 -0
  187. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/types.ts +5159 -0
  188. package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/image.ts +35 -0
  189. package/zova/src/suite/cabloy-basic/modules/basic-image/src/bean/tableCell.image.tsx +200 -0
  190. package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +781 -0
  191. package/zova/src/suite/cabloy-basic/modules/basic-image/src/config/locale/en-us.ts +18 -0
  192. package/zova/src/suite/cabloy-basic/modules/basic-image/src/config/locale/zh-cn.ts +18 -0
  193. package/zova/src/suite/cabloy-basic/modules/basic-image/src/index.ts +4 -0
  194. package/zova/src/suite/cabloy-basic/modules/basic-image/src/lib/index.ts +2 -0
  195. package/zova/src/suite/cabloy-basic/modules/basic-image/src/lib/preview.ts +105 -0
  196. package/zova/src/suite/cabloy-basic/modules/basic-image/src/lib/previewDialog.tsx +190 -0
  197. package/zova/src/suite/cabloy-basic/modules/basic-image/src/types/image.ts +5 -0
  198. package/zova/src/suite/cabloy-basic/modules/basic-image/src/types/index.ts +1 -0
  199. package/zova/src/suite/cabloy-basic/modules/basic-image/tsconfig.build.json +13 -0
  200. package/zova/src/suite/cabloy-basic/modules/basic-image/tsconfig.json +5 -0
  201. package/zova/src/suite/cabloy-basic/package.json +1 -0
  202. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  203. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/image.ts +1 -0
  204. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/index.ts +1 -0
  205. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +5 -5
  206. package/zova/src/suite-vendor/a-zova/package.json +3 -3
  207. package/vona/packages-vona/vona-core/src/lib/utils/sqlite3.ts +0 -50
  208. /package/vona/patches/{zova-core@5.1.75.patch → zova-core@5.1.76.patch} +0 -0
@@ -0,0 +1,40 @@
1
+ import type { IMetaVersionUpdate, IMetaVersionUpdateOptions } from 'vona-module-a-version';
2
+
3
+ import { BeanBase } from 'vona';
4
+ import { Meta } from 'vona-module-a-meta';
5
+
6
+ @Meta()
7
+ export class MetaVersion extends BeanBase implements IMetaVersionUpdate {
8
+ async update(options: IMetaVersionUpdateOptions) {
9
+ if (options.version === 1) {
10
+ // aImageProvider
11
+ const entityImageProvider = this.scope.entity.imageProvider;
12
+ await this.bean.model.createTable(entityImageProvider.$table, table => {
13
+ table.basicFieldsSimple();
14
+ table.boolean(entityImageProvider.disabled).defaultTo(false);
15
+ table.string(entityImageProvider.providerName, 255);
16
+ table.string(entityImageProvider.clientName, 255);
17
+ table.json(entityImageProvider.clientOptions);
18
+ });
19
+ // aImage
20
+ const entityImage = this.scope.entity.image;
21
+ await this.bean.model.createTable(entityImage.$table, table => {
22
+ table.basicFields();
23
+ table.string(entityImage.providerName, 255);
24
+ table.string(entityImage.clientName, 255);
25
+ table.string(entityImage.resourceId, 255);
26
+ table.string(entityImage.filename, 255);
27
+ table.string(entityImage.contentType, 255);
28
+ table.integer(entityImage.size);
29
+ table.integer(entityImage.width);
30
+ table.integer(entityImage.height);
31
+ table.boolean(entityImage.requireSignedURLs).defaultTo(false);
32
+ table.json(entityImage.variants);
33
+ table.json(entityImage.meta);
34
+ table.text(entityImage.storagePath);
35
+ table.text(entityImage.deliveryBaseUrl);
36
+ table.string(entityImage.imageScene, 255);
37
+ });
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,44 @@
1
+ import type {
2
+ IDecoratorSerializerTransformOptions,
3
+ ISerializerTransform,
4
+ } from 'vona-module-a-serialization';
5
+
6
+ import { BeanBase } from 'vona';
7
+ import { SerializerTransform } from 'vona-module-a-serialization';
8
+
9
+ import type { DtoImageView } from '../dto/imageView.ts';
10
+ import type { IImageSceneRecord } from '../types/imageScene.ts';
11
+
12
+ export type TypeSerializerTransformResolveViewValue = unknown;
13
+
14
+ export type TypeSerializerTransformResolveViewData = object;
15
+
16
+ export type TypeSerializerTransformResolveViewResult = DtoImageView | undefined;
17
+
18
+ export interface ISerializerTransformOptionsResolveView extends IDecoratorSerializerTransformOptions {
19
+ fieldName: string;
20
+ imageScene?: keyof IImageSceneRecord;
21
+ }
22
+
23
+ @SerializerTransform<ISerializerTransformOptionsResolveView>()
24
+ export class SerializerTransformResolveView
25
+ extends BeanBase
26
+ implements
27
+ ISerializerTransform<
28
+ TypeSerializerTransformResolveViewValue,
29
+ TypeSerializerTransformResolveViewData,
30
+ TypeSerializerTransformResolveViewResult
31
+ >
32
+ {
33
+ async transform(
34
+ _value: TypeSerializerTransformResolveViewValue,
35
+ data: TypeSerializerTransformResolveViewData,
36
+ options: ISerializerTransformOptionsResolveView,
37
+ ): Promise<TypeSerializerTransformResolveViewResult> {
38
+ return await this.bean.image.resolveView(
39
+ data[options.fieldName],
40
+ undefined,
41
+ options.imageScene,
42
+ );
43
+ }
44
+ }
@@ -0,0 +1,44 @@
1
+ import type {
2
+ IDecoratorSerializerTransformOptions,
3
+ ISerializerTransform,
4
+ } from 'vona-module-a-serialization';
5
+
6
+ import { BeanBase } from 'vona';
7
+ import { SerializerTransform } from 'vona-module-a-serialization';
8
+
9
+ import type { DtoImageView } from '../dto/imageView.ts';
10
+ import type { IImageSceneRecord } from '../types/imageScene.ts';
11
+
12
+ export type TypeSerializerTransformResolveViewsValue = unknown;
13
+
14
+ export type TypeSerializerTransformResolveViewsData = object;
15
+
16
+ export type TypeSerializerTransformResolveViewsResult = DtoImageView[] | undefined;
17
+
18
+ export interface ISerializerTransformOptionsResolveViews extends IDecoratorSerializerTransformOptions {
19
+ fieldName: string;
20
+ imageScene?: keyof IImageSceneRecord;
21
+ }
22
+
23
+ @SerializerTransform<ISerializerTransformOptionsResolveViews>()
24
+ export class SerializerTransformResolveViews
25
+ extends BeanBase
26
+ implements
27
+ ISerializerTransform<
28
+ TypeSerializerTransformResolveViewsValue,
29
+ TypeSerializerTransformResolveViewsData,
30
+ TypeSerializerTransformResolveViewsResult
31
+ >
32
+ {
33
+ async transform(
34
+ _value: TypeSerializerTransformResolveViewsValue,
35
+ data: TypeSerializerTransformResolveViewsData,
36
+ options: ISerializerTransformOptionsResolveViews,
37
+ ): Promise<TypeSerializerTransformResolveViewsResult> {
38
+ return await this.bean.image.resolveViews(
39
+ data[options.fieldName],
40
+ undefined,
41
+ options.imageScene,
42
+ );
43
+ }
44
+ }
@@ -0,0 +1,17 @@
1
+ import type { VonaApplication } from 'vona';
2
+
3
+ import type { IImageProviderRecord } from '../types/imageProvider.ts';
4
+
5
+ export function config(_app: VonaApplication) {
6
+ return {
7
+ image: {
8
+ defaultVariant: 'original',
9
+ defaultProvider: 'image-native:native' as keyof IImageProviderRecord,
10
+ defaultClientName: 'default',
11
+ upload: {
12
+ maxSize: 2 * 1024 * 1024,
13
+ mimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
14
+ },
15
+ },
16
+ };
17
+ }
@@ -0,0 +1,165 @@
1
+ import type { IUploadFile } from 'vona-module-a-upload';
2
+ import type { IDecoratorControllerOptions } from 'vona-module-a-web';
3
+
4
+ import fse from 'fs-extra';
5
+ import { BeanBase } from 'vona';
6
+ import { Core } from 'vona-module-a-core';
7
+ import { Api, v } from 'vona-module-a-openapiutils';
8
+ import { Arg, Controller, Web } from 'vona-module-a-web';
9
+
10
+ import type { IImageUploadTokenPayload } from '../types/image.ts';
11
+
12
+ import { DtoImageDeliveryRequest } from '../dto/imageDeliveryRequest.ts';
13
+ import { DtoImageDirectUploadRequest } from '../dto/imageDirectUploadRequest.ts';
14
+ import { DtoImageDirectUploadResponse } from '../dto/imageDirectUploadResponse.ts';
15
+ import { DtoImageUploadResponse } from '../dto/imageUploadResponse.ts';
16
+ import { DtoImageUploadTokenRequest } from '../dto/imageUploadTokenRequest.ts';
17
+ import { DtoImageUploadTokenResponse } from '../dto/imageUploadTokenResponse.ts';
18
+ import { DtoImageUploadUrlRequest } from '../dto/imageUploadUrlRequest.ts';
19
+
20
+ export interface IControllerOptionsImage extends IDecoratorControllerOptions {}
21
+
22
+ @Controller<IControllerOptionsImage>({ path: 'image' })
23
+ export class ControllerImage extends BeanBase {
24
+ @Web.post('upload-token')
25
+ @Api.body(DtoImageUploadTokenResponse)
26
+ async createUploadToken(@Arg.body() data: DtoImageUploadTokenRequest) {
27
+ return await this.bean.imageUploadPolicy.createUploadToken(data);
28
+ }
29
+
30
+ @Web.post('upload')
31
+ @Core.fileUpload()
32
+ @Api.body(DtoImageUploadResponse)
33
+ @Api.contentType('application/json')
34
+ async upload(@Arg.field('token') token: string, @Arg.file('image') file: IUploadFile) {
35
+ const payload = await this.bean.imageUploadPolicy.verifyUploadToken(
36
+ token,
37
+ this.ctx.route.routePathRaw,
38
+ );
39
+ await this._validateUploadFile(file, payload);
40
+ const image = await this.bean.image.upload(
41
+ payload.providerName,
42
+ {
43
+ file: file.file,
44
+ filename: file.info.filename,
45
+ contentType: file.info.mimeType,
46
+ },
47
+ {
48
+ clientName: payload.clientName,
49
+ meta: payload.meta,
50
+ imageScene: payload.imageScene,
51
+ },
52
+ );
53
+ return {
54
+ ...image,
55
+ url: await this.bean.image.getVariantUrl(image.id),
56
+ signed: !!image.requireSignedURLs,
57
+ };
58
+ }
59
+
60
+ @Web.post('direct-upload')
61
+ @Api.body(DtoImageDirectUploadResponse)
62
+ async createDirectUpload(@Arg.body() data: DtoImageDirectUploadRequest) {
63
+ const policy = await this.bean.imageUploadPolicy.resolveUploadPolicy({
64
+ imageScene: data.imageScene,
65
+ size: data.size,
66
+ mimeType: data.mimeType,
67
+ });
68
+ return await this.bean.image.createDirectUpload(
69
+ policy.providerName,
70
+ {
71
+ filename: data.filename,
72
+ contentType: data.contentType,
73
+ expiry: data.expiry,
74
+ customId: data.customId,
75
+ },
76
+ {
77
+ clientName: policy.clientName,
78
+ meta: policy.meta,
79
+ imageScene: policy.imageScene,
80
+ },
81
+ );
82
+ }
83
+
84
+ @Web.post('upload-url')
85
+ @Api.body(DtoImageUploadResponse)
86
+ async uploadUrl(@Arg.body() data: DtoImageUploadUrlRequest) {
87
+ const policy = await this.bean.imageUploadPolicy.resolveUploadPolicy({
88
+ imageScene: data.imageScene,
89
+ size: data.size,
90
+ mimeType: data.mimeType,
91
+ });
92
+ const image = await this.bean.image.uploadUrl(
93
+ policy.providerName,
94
+ {
95
+ url: data.url,
96
+ filename: data.filename,
97
+ contentType: data.contentType,
98
+ },
99
+ {
100
+ clientName: policy.clientName,
101
+ meta: policy.meta,
102
+ imageScene: policy.imageScene,
103
+ },
104
+ );
105
+ return {
106
+ ...image,
107
+ url: await this.bean.image.getVariantUrl(image.id),
108
+ signed: !!image.requireSignedURLs,
109
+ };
110
+ }
111
+
112
+ @Web.get('delivery/:imageId')
113
+ async delivery(
114
+ @Arg.param('imageId', v.tableIdentity()) imageId: number,
115
+ @Arg.query(v.object(DtoImageDeliveryRequest)) query: DtoImageDeliveryRequest,
116
+ ) {
117
+ const payload = await this.bean.imageUploadPolicy.verifyDeliveryToken(
118
+ query.token,
119
+ this.ctx.route.routePathRaw,
120
+ );
121
+ if (String(payload.imageId) !== String(imageId)) {
122
+ return this.app.throw(401);
123
+ }
124
+ this.ctx.redirect(payload.targetUrl);
125
+ }
126
+
127
+ private async _validateUploadFile(file: IUploadFile, payload: IImageUploadTokenPayload) {
128
+ const stat = await fse.stat(file.file);
129
+ const fileSize = Number(stat.size);
130
+ if (fileSize !== payload.fileSize) {
131
+ return this.app.throw(403, `image size mismatch: size=${fileSize}`);
132
+ }
133
+ if (payload.maxSize && fileSize > payload.maxSize) {
134
+ return this.app.throw(403, `image too large: maxSize=${payload.maxSize}`);
135
+ }
136
+ const mimeType = file.info.mimeType.toLowerCase();
137
+ if (mimeType !== payload.mimeType) {
138
+ return this.app.throw(403, `image mimeType mismatch: mimeType=${mimeType}`);
139
+ }
140
+ if (payload.mimeTypes?.length && !this._matchesMimeType(mimeType, payload.mimeTypes)) {
141
+ return this.app.throw(403, `unsupported image mimeType: ${mimeType}`);
142
+ }
143
+ const extension = this._getExtension(file.info.filename);
144
+ if (payload.extensions?.length && extension && !payload.extensions.includes(extension)) {
145
+ return this.app.throw(403, `unsupported image extension: ${extension}`);
146
+ }
147
+ }
148
+
149
+ private _matchesMimeType(mimeType: string, mimeTypes: string[]) {
150
+ return mimeTypes.some(item => {
151
+ if (item === mimeType) return true;
152
+ if (item.endsWith('/*')) {
153
+ return mimeType.startsWith(`${item.slice(0, -1)}`);
154
+ }
155
+ return false;
156
+ });
157
+ }
158
+
159
+ private _getExtension(filename?: string) {
160
+ if (!filename) return '';
161
+ const index = filename.lastIndexOf('.');
162
+ if (index === -1) return '';
163
+ return filename.slice(index).toLowerCase();
164
+ }
165
+ }
@@ -0,0 +1,20 @@
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
+
6
+ import { DtoImageTransformOptions } from './imageTransformOptions.tsx';
7
+
8
+ export interface IDtoOptionsImageDeliveryRequest extends IDecoratorDtoOptions {}
9
+
10
+ @Dto<IDtoOptionsImageDeliveryRequest>()
11
+ export class DtoImageDeliveryRequest {
12
+ @Api.field(v.optional())
13
+ variantName?: string;
14
+
15
+ @Api.field(v.optional(), v.object(DtoImageTransformOptions))
16
+ transformOptions?: Record<string, unknown>;
17
+
18
+ @Api.field(v.optional())
19
+ token?: string;
20
+ }
@@ -0,0 +1,36 @@
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 IDtoOptionsImageDirectUploadRequest extends IDecoratorDtoOptions {}
10
+
11
+ @Dto<IDtoOptionsImageDirectUploadRequest>()
12
+ export class DtoImageDirectUploadRequest {
13
+ @Api.field(z.string())
14
+ imageScene: keyof IImageSceneRecord;
15
+
16
+ @Api.field(v.optional())
17
+ filename?: string;
18
+
19
+ @Api.field(z.number().int().positive())
20
+ size: number;
21
+
22
+ @Api.field(z.string())
23
+ mimeType: string;
24
+
25
+ @Api.field(v.optional())
26
+ contentType?: string;
27
+
28
+ @Api.field(v.optional())
29
+ requireSignedURLs?: boolean;
30
+
31
+ @Api.field(v.optional())
32
+ expiry?: string;
33
+
34
+ @Api.field(v.optional())
35
+ customId?: string;
36
+ }
@@ -0,0 +1,38 @@
1
+ import type { TableIdentity } from 'table-identity';
2
+ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
3
+
4
+ import { Api, v } from 'vona-module-a-openapiutils';
5
+ import { Dto } from 'vona-module-a-web';
6
+ import z from 'zod';
7
+
8
+ import type { IImageProviderRecord } from '../types/imageProvider.ts';
9
+ import type { IImageSceneRecord } from '../types/imageScene.ts';
10
+
11
+ export interface IDtoOptionsImageDirectUploadResponse extends IDecoratorDtoOptions {}
12
+
13
+ @Dto<IDtoOptionsImageDirectUploadResponse>()
14
+ export class DtoImageDirectUploadResponse {
15
+ @Api.field(v.tableIdentity())
16
+ id: TableIdentity;
17
+
18
+ @Api.field(z.string())
19
+ provider: keyof IImageProviderRecord;
20
+
21
+ @Api.field()
22
+ clientName: string;
23
+
24
+ @Api.field()
25
+ resourceId: string;
26
+
27
+ @Api.field()
28
+ uploadUrl: string;
29
+
30
+ @Api.field(v.optional())
31
+ draft?: boolean;
32
+
33
+ @Api.field(v.optional())
34
+ filename?: string;
35
+
36
+ @Api.field(v.optional(), z.string())
37
+ imageScene?: keyof IImageSceneRecord;
38
+ }
@@ -0,0 +1,42 @@
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 { IImageTransformOptions } from '../types/image.ts';
8
+
9
+ export interface IDtoOptionsImageTransformOptions extends IDecoratorDtoOptions {}
10
+
11
+ @Dto<IDtoOptionsImageTransformOptions>()
12
+ export class DtoImageTransformOptions implements IImageTransformOptions {
13
+ @Api.field(v.optional())
14
+ width?: number;
15
+
16
+ @Api.field(v.optional())
17
+ height?: number;
18
+
19
+ @Api.field(v.optional(), z.enum(['scale-down', 'contain', 'cover', 'crop', 'pad']))
20
+ fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
21
+
22
+ @Api.field(v.optional(), z.enum(['auto', 'center', 'top', 'bottom', 'left', 'right']))
23
+ gravity?: 'auto' | 'center' | 'top' | 'bottom' | 'left' | 'right';
24
+
25
+ @Api.field(v.optional())
26
+ background?: string;
27
+
28
+ @Api.field(v.optional())
29
+ quality?: number;
30
+
31
+ @Api.field(v.optional(), z.enum(['auto', 'avif', 'webp', 'jpeg', 'png']))
32
+ format?: 'auto' | 'avif' | 'webp' | 'jpeg' | 'png';
33
+
34
+ @Api.field(v.optional())
35
+ dpr?: number;
36
+
37
+ @Api.field(v.optional())
38
+ rotate?: number;
39
+
40
+ @Api.field(v.optional())
41
+ sharpen?: number;
42
+ }
@@ -0,0 +1,62 @@
1
+ import type { TableIdentity } from 'table-identity';
2
+ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
3
+
4
+ import { $schema, Api, v } from 'vona-module-a-openapiutils';
5
+ import { Dto } from 'vona-module-a-web';
6
+ import z from 'zod';
7
+
8
+ import type { IImageNamedVariants } from '../types/image.ts';
9
+ import type { IImageProviderRecord } from '../types/imageProvider.ts';
10
+ import type { IImageSceneRecord } from '../types/imageScene.ts';
11
+
12
+ import { DtoImageTransformOptions } from './imageTransformOptions.tsx';
13
+
14
+ export interface IDtoOptionsImageUploadResponse extends IDecoratorDtoOptions {}
15
+
16
+ @Dto<IDtoOptionsImageUploadResponse>()
17
+ export class DtoImageUploadResponse {
18
+ @Api.field(v.tableIdentity())
19
+ id: TableIdentity;
20
+
21
+ @Api.field(z.string())
22
+ provider: keyof IImageProviderRecord;
23
+
24
+ @Api.field()
25
+ clientName: string;
26
+
27
+ @Api.field()
28
+ resourceId: string;
29
+
30
+ @Api.field(v.optional())
31
+ filename?: string;
32
+
33
+ @Api.field(v.optional())
34
+ contentType?: string;
35
+
36
+ @Api.field(v.optional())
37
+ size?: number;
38
+
39
+ @Api.field(v.optional())
40
+ width?: number;
41
+
42
+ @Api.field(v.optional())
43
+ height?: number;
44
+
45
+ @Api.field(v.optional())
46
+ requireSignedURLs?: boolean;
47
+
48
+ @Api.field(v.optional(), z.record(z.string(), $schema(DtoImageTransformOptions)))
49
+ variants?: IImageNamedVariants;
50
+
51
+ @Api.field(v.optional(), z.string())
52
+ imageScene?: keyof IImageSceneRecord;
53
+
54
+ @Api.field(v.optional())
55
+ uploadedAt?: Date;
56
+
57
+ @Api.field(v.optional())
58
+ url?: string;
59
+
60
+ @Api.field(v.optional())
61
+ signed?: boolean;
62
+ }
@@ -0,0 +1,24 @@
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 IDtoOptionsImageUploadTokenRequest extends IDecoratorDtoOptions {}
10
+
11
+ @Dto<IDtoOptionsImageUploadTokenRequest>()
12
+ export class DtoImageUploadTokenRequest {
13
+ @Api.field(z.string())
14
+ imageScene: keyof IImageSceneRecord;
15
+
16
+ @Api.field(z.number().int().positive())
17
+ size: number;
18
+
19
+ @Api.field(z.string())
20
+ mimeType: string;
21
+
22
+ @Api.field(v.optional(), z.number().int().positive())
23
+ expiresIn?: number;
24
+ }
@@ -0,0 +1,16 @@
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
+ export interface IDtoOptionsImageUploadTokenResponse extends IDecoratorDtoOptions {}
8
+
9
+ @Dto<IDtoOptionsImageUploadTokenResponse>()
10
+ export class DtoImageUploadTokenResponse {
11
+ @Api.field(z.string())
12
+ token: string;
13
+
14
+ @Api.field(v.optional(), z.number().int().positive())
15
+ expiresIn?: number;
16
+ }
@@ -0,0 +1,33 @@
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 IDtoOptionsImageUploadUrlRequest extends IDecoratorDtoOptions {}
10
+
11
+ @Dto<IDtoOptionsImageUploadUrlRequest>()
12
+ export class DtoImageUploadUrlRequest {
13
+ @Api.field(z.string())
14
+ imageScene: keyof IImageSceneRecord;
15
+
16
+ @Api.field(z.string().url())
17
+ url: string;
18
+
19
+ @Api.field(z.number().int().positive())
20
+ size: number;
21
+
22
+ @Api.field(z.string())
23
+ mimeType: string;
24
+
25
+ @Api.field(v.optional())
26
+ filename?: string;
27
+
28
+ @Api.field(v.optional())
29
+ contentType?: string;
30
+
31
+ @Api.field(v.optional())
32
+ requireSignedURLs?: boolean;
33
+ }
@@ -0,0 +1,46 @@
1
+ import type { TableIdentity } from 'table-identity';
2
+ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
3
+
4
+ import { $schema, Api, v } from 'vona-module-a-openapiutils';
5
+ import { Dto } from 'vona-module-a-web';
6
+ import z from 'zod';
7
+
8
+ import type { IImageNamedVariants } from '../types/image.ts';
9
+ import type { IImageSceneRecord } from '../types/imageScene.ts';
10
+
11
+ import { DtoImageTransformOptions } from './imageTransformOptions.tsx';
12
+
13
+ export interface IDtoOptionsImageView extends IDecoratorDtoOptions {}
14
+
15
+ @Dto<IDtoOptionsImageView>()
16
+ export class DtoImageView {
17
+ @Api.field(v.tableIdentity())
18
+ id: TableIdentity;
19
+
20
+ @Api.field(z.string())
21
+ url: string;
22
+
23
+ @Api.field(v.optional())
24
+ filename?: string;
25
+
26
+ @Api.field(v.optional())
27
+ width?: number;
28
+
29
+ @Api.field(v.optional())
30
+ height?: number;
31
+
32
+ @Api.field(z.string())
33
+ provider: string;
34
+
35
+ @Api.field(z.string())
36
+ clientName: string;
37
+
38
+ @Api.field(v.optional())
39
+ imageScene?: keyof IImageSceneRecord | string;
40
+
41
+ @Api.field(v.optional())
42
+ uploadedAt?: Date;
43
+
44
+ @Api.field(v.optional(), z.record(z.string(), $schema(DtoImageTransformOptions)))
45
+ variants?: IImageNamedVariants;
46
+ }