cabloy 5.1.97 → 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 (240) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/hooks/contract-loop-gate.ts +151 -44
  3. package/CHANGELOG.md +48 -0
  4. package/CLAUDE.md +2 -0
  5. package/cabloy-docs/.vitepress/config.mjs +11 -0
  6. package/cabloy-docs/backend/controller-aop-guide.md +0 -1
  7. package/cabloy-docs/backend/image-guide.md +544 -0
  8. package/cabloy-docs/frontend/a-openapi-under-the-hood.md +1 -1
  9. package/cabloy-docs/frontend/generated-contract-consumption-entry-branch.md +1 -1
  10. package/cabloy-docs/frontend/generated-contract-consumption-list-branch.md +1 -1
  11. package/cabloy-docs/frontend/generated-contract-consumption-verify-playbook.md +1 -1
  12. package/cabloy-docs/frontend/image-guide.md +512 -0
  13. package/cabloy-docs/frontend/introduction.md +2 -0
  14. package/cabloy-docs/frontend/ioc-and-beans.md +34 -3
  15. package/cabloy-docs/frontend/model-architecture.md +2 -0
  16. package/cabloy-docs/frontend/model-resource-owner-pattern.md +3 -1
  17. package/cabloy-docs/frontend/model-state-guide.md +5 -1
  18. package/cabloy-docs/frontend/reading-zova-for-vue-developers.md +10 -8
  19. package/cabloy-docs/frontend/resource-entry-page-deep-dive.md +2 -2
  20. package/cabloy-docs/frontend/resource-list-page-deep-dive.md +1 -1
  21. package/cabloy-docs/frontend/rest-resource-source-reading-map.md +2 -2
  22. package/cabloy-docs/frontend/rest-resource-under-the-hood.md +4 -4
  23. package/cabloy-docs/frontend/server-data.md +2 -0
  24. package/cabloy-docs/frontend/state-architecture-for-vue-developers.md +218 -0
  25. package/cabloy-docs/frontend/system-startup-guide.md +2 -0
  26. package/cabloy-docs/frontend/use-state-data-best-practices.md +384 -0
  27. package/cabloy-docs/frontend/zova-vs-vue3-comparison.md +11 -10
  28. package/cabloy-docs/fullstack/image-workflow.md +403 -0
  29. package/cabloy-docs/fullstack/introduction.md +1 -0
  30. package/package.json +1 -1
  31. package/scripts/upgrade.ts +2 -0
  32. package/vona/package.original.json +0 -1
  33. package/vona/packages-cli/cli/package.json +1 -1
  34. package/vona/packages-cli/cli-set-api/package.json +1 -1
  35. package/vona/packages-vona/vona/package.json +1 -1
  36. package/vona/packages-vona/vona-core/package.json +1 -1
  37. package/vona/packages-vona/vona-core/src/lib/bean/beanBase.ts +9 -9
  38. package/vona/packages-vona/vona-mock/package.json +1 -1
  39. package/vona/pnpm-lock.yaml +424 -47
  40. package/vona/src/suite/a-training/modules/training-record/src/bean/fileScene.dossierFile.ts +22 -0
  41. package/vona/src/suite/a-training/modules/training-record/src/bean/meta.version.ts +1 -0
  42. package/vona/src/suite/a-training/modules/training-record/src/config/locale/en-us.ts +1 -0
  43. package/vona/src/suite/a-training/modules/training-record/src/config/locale/zh-cn.ts +1 -0
  44. package/vona/src/suite/a-training/modules/training-record/src/dto/recordDossierFileView.ts +47 -0
  45. package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectResItem.tsx +13 -0
  46. package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +13 -0
  47. package/vona/src/suite/a-training/modules/training-record/src/entity/record.tsx +16 -5
  48. package/vona/src/suite/a-training/modules/training-record/src/index.ts +2 -0
  49. package/vona/src/suite/a-training/modules/training-record/src/lib/index.ts +1 -0
  50. package/vona/src/suite/a-training/modules/training-record/src/lib/resolveDossierFiles.ts +34 -0
  51. package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +209 -123
  52. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +1 -0
  53. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +1 -0
  54. package/vona/src/suite/a-training/modules/training-student/src/dto/detailRecordBase.tsx +18 -1
  55. package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +0 -2
  56. package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +1 -0
  57. package/vona/src/suite/cabloy-basic/modules/basic-siteadmin/src/bean/ssrSite.admin.ts +4 -0
  58. package/vona/src/suite/cabloy-basic/modules/basic-siteweb/src/bean/ssrSite.web.ts +4 -0
  59. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/cli/ssrSite/boilerplate/{{sceneName}}.{{beanName}}.ts_ +3 -0
  60. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
  61. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/service/ssrHandler.ts +11 -2
  62. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/types/ssrSite.ts +6 -0
  63. package/vona/src/suite-vendor/a-cabloy/package.json +1 -1
  64. package/vona/src/suite-vendor/a-file/modules/a-file/cli/fileProvider/boilerplate/{{sceneName}}.{{beanName}}.ts_ +23 -0
  65. package/vona/src/suite-vendor/a-file/modules/a-file/cli/fileScene/boilerplate/{{sceneName}}.{{beanName}}.ts_ +5 -0
  66. package/vona/src/suite-vendor/a-file/modules/a-file/package.json +68 -0
  67. package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/index.ts +425 -0
  68. package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/locales.ts +18 -0
  69. package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/this.ts +2 -0
  70. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.file.ts +322 -0
  71. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileProvider.ts +62 -0
  72. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileUploadPolicy.ts +224 -0
  73. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.redlock.ts +11 -0
  74. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.version.ts +38 -0
  75. package/vona/src/suite-vendor/a-file/modules/a-file/src/config/config.ts +16 -0
  76. package/vona/src/suite-vendor/a-file/modules/a-file/src/config/locale/en-us.ts +1 -0
  77. package/vona/src/suite-vendor/a-file/modules/a-file/src/config/locale/zh-cn.ts +1 -0
  78. package/vona/src/suite-vendor/a-file/modules/a-file/src/controller/file.ts +175 -0
  79. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadRequest.ts +33 -0
  80. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadResponse.ts +41 -0
  81. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDownloadRequest.ts +12 -0
  82. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadPolicyRequest.ts +15 -0
  83. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadPolicyResponse.ts +30 -0
  84. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadResponse.ts +59 -0
  85. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadTokenRequest.ts +24 -0
  86. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadTokenResponse.ts +16 -0
  87. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadUrlRequest.ts +33 -0
  88. package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/file.ts +55 -0
  89. package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/fileProvider.ts +24 -0
  90. package/vona/src/suite-vendor/a-file/modules/a-file/src/index.ts +4 -0
  91. package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileProvider.ts +7 -0
  92. package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileScene.ts +7 -0
  93. package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileUploadValidation.ts +15 -0
  94. package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/index.ts +3 -0
  95. package/vona/src/suite-vendor/a-file/modules/a-file/src/model/file.ts +10 -0
  96. package/vona/src/suite-vendor/a-file/modules/a-file/src/model/fileProvider.ts +10 -0
  97. package/vona/src/suite-vendor/a-file/modules/a-file/src/types/file.ts +144 -0
  98. package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileProvider.ts +111 -0
  99. package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileScene.ts +53 -0
  100. package/vona/src/suite-vendor/a-file/modules/a-file/src/types/index.ts +3 -0
  101. package/vona/src/suite-vendor/a-file/modules/a-file/tsconfig.build.json +11 -0
  102. package/vona/src/suite-vendor/a-file/modules/a-file/tsconfig.json +7 -0
  103. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/package.json +53 -0
  104. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/.metadata/index.ts +104 -0
  105. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/.metadata/this.ts +2 -0
  106. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/bean/fileProvider.cloudflare.ts +123 -0
  107. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/index.ts +1 -0
  108. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/service/fileCloudflare.ts +245 -0
  109. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/tsconfig.build.json +11 -0
  110. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/tsconfig.json +7 -0
  111. package/vona/src/suite-vendor/a-file/modules/file-native/package.json +50 -0
  112. package/vona/src/suite-vendor/a-file/modules/file-native/src/.metadata/index.ts +111 -0
  113. package/vona/src/suite-vendor/a-file/modules/file-native/src/.metadata/this.ts +2 -0
  114. package/vona/src/suite-vendor/a-file/modules/file-native/src/bean/fileProvider.native.ts +125 -0
  115. package/vona/src/suite-vendor/a-file/modules/file-native/src/config/config.ts +7 -0
  116. package/vona/src/suite-vendor/a-file/modules/file-native/src/index.ts +1 -0
  117. package/vona/src/suite-vendor/a-file/modules/file-native/src/service/fileNative.ts +125 -0
  118. package/vona/src/suite-vendor/a-file/modules/file-native/tsconfig.build.json +11 -0
  119. package/vona/src/suite-vendor/a-file/modules/file-native/tsconfig.json +7 -0
  120. package/vona/src/suite-vendor/a-file/package.json +14 -0
  121. package/vona/src/suite-vendor/a-file/tsconfig.base.json +4 -0
  122. package/vona/src/suite-vendor/a-file/tsconfig.json +16 -0
  123. package/vona/src/suite-vendor/a-image/modules/a-image/package.json +4 -1
  124. package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/index.ts +61 -3
  125. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.image.ts +336 -152
  126. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageProvider.ts +21 -14
  127. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +86 -11
  128. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.index.ts +12 -0
  129. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.redlock.ts +1 -1
  130. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.version.ts +3 -0
  131. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/schedule.imageDraftPrune.ts +29 -0
  132. package/vona/src/suite-vendor/a-image/modules/a-image/src/config/config.ts +3 -0
  133. package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +32 -54
  134. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadFinalizeRequest.ts +13 -0
  135. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadFinalizeResponse.ts +10 -0
  136. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadResponse.ts +10 -0
  137. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadPolicyRequest.ts +15 -0
  138. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadPolicyResponse.ts +27 -0
  139. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadResponse.ts +10 -1
  140. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageView.ts +10 -1
  141. package/vona/src/suite-vendor/a-image/modules/a-image/src/entity/image.ts +10 -1
  142. package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/imageUploadValidation.ts +15 -0
  143. package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/index.ts +1 -0
  144. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/image.ts +18 -0
  145. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageProvider.ts +28 -2
  146. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/package.json +1 -1
  147. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/bean/imageProvider.cloudflare.ts +13 -1
  148. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts +26 -0
  149. package/vona/src/suite-vendor/a-image/modules/image-native/package.json +6 -2
  150. package/vona/src/suite-vendor/a-image/modules/image-native/src/.metadata/index.ts +37 -0
  151. package/vona/src/suite-vendor/a-image/modules/image-native/src/bean/imageProvider.native.ts +21 -1
  152. package/vona/src/suite-vendor/a-image/modules/image-native/src/controller/image.ts +57 -0
  153. package/vona/src/suite-vendor/a-image/modules/image-native/src/service/imageNative.ts +142 -34
  154. package/vona/src/suite-vendor/a-image/package.json +1 -1
  155. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  156. package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
  157. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/entity.ts +2 -4
  158. package/vona/src/suite-vendor/a-vona/modules/a-ormutils/package.json +1 -1
  159. package/vona/src/suite-vendor/a-vona/modules/a-ormutils/src/lib/columns.ts +7 -3
  160. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  161. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/.metadata/index.ts +24 -9
  162. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +87 -73
  163. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permissionActionByRoles.ts +27 -0
  164. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permissionUser.ts +20 -0
  165. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  166. package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +4 -3
  167. package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +1 -1
  168. package/vona/src/suite-vendor/a-vona/modules/a-upload/src/bean/interceptor.upload.ts +5 -1
  169. package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
  170. package/vona/src/suite-vendor/a-vona/modules/a-user/src/.metadata/index.ts +0 -13
  171. package/vona/src/suite-vendor/a-vona/modules/a-user/src/lib/passport.ts +0 -7
  172. package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
  173. package/vona/src/suite-vendor/a-vona/modules/a-version/src/bean/meta.version.ts +2 -24
  174. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  175. package/zova/packages-utils/zova-jsx/package.json +2 -2
  176. package/zova/packages-zova/zova/package.json +3 -3
  177. package/zova/packages-zova/zova-core/package.json +1 -1
  178. package/zova/packages-zova/zova-core/src/bean/beanBase.ts +9 -13
  179. package/zova/pnpm-lock.yaml +418 -411
  180. package/zova/src/suite/a-demo/modules/demo-basic/src/page/toolOne/controller.tsx +2 -2
  181. package/zova/src/suite/a-demo/modules/demo-todo/src/page/todo/controller.tsx +2 -2
  182. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx +2 -2
  183. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx +2 -2
  184. package/zova/src/suite/a-home/modules/home-login/src/page/login/controller.tsx +2 -2
  185. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockDetails/controller.tsx +5 -1
  186. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockTable/controller.tsx +20 -15
  187. package/zova/src/suite/cabloy-basic/modules/basic-details/src/service/detail.tsx +6 -1
  188. package/zova/src/suite/cabloy-basic/modules/basic-file/cli/openapi.config.ts +9 -0
  189. package/zova/src/suite/cabloy-basic/modules/basic-file/package.json +58 -0
  190. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/component/formFieldFile.ts +31 -0
  191. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/index.ts +222 -0
  192. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/locales.ts +7 -0
  193. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/this.ts +2 -0
  194. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/file.ts +121 -0
  195. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/baseURL.ts +5 -0
  196. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/index.ts +3 -0
  197. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/schemas.ts +306 -0
  198. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/types.ts +5718 -0
  199. package/zova/src/suite/cabloy-basic/modules/basic-file/src/apiSchema/file.ts +40 -0
  200. package/zova/src/suite/cabloy-basic/modules/basic-file/src/bean/tableCell.file.tsx +187 -0
  201. package/zova/src/suite/cabloy-basic/modules/basic-file/src/component/formFieldFile/controller.tsx +534 -0
  202. package/zova/src/suite/cabloy-basic/modules/basic-file/src/config/locale/en-us.ts +15 -0
  203. package/zova/src/suite/cabloy-basic/modules/basic-file/src/config/locale/zh-cn.ts +15 -0
  204. package/zova/src/suite/cabloy-basic/modules/basic-file/src/index.ts +4 -0
  205. package/zova/src/suite/cabloy-basic/modules/basic-file/src/lib/file.ts +74 -0
  206. package/zova/src/suite/cabloy-basic/modules/basic-file/src/lib/index.ts +1 -0
  207. package/zova/src/suite/cabloy-basic/modules/basic-file/src/model/file.ts +23 -0
  208. package/zova/src/suite/cabloy-basic/modules/basic-file/src/types/file.ts +12 -0
  209. package/zova/src/suite/cabloy-basic/modules/basic-file/src/types/index.ts +1 -0
  210. package/zova/src/suite/cabloy-basic/modules/basic-file/tsconfig.build.json +13 -0
  211. package/zova/src/suite/cabloy-basic/modules/basic-file/tsconfig.json +5 -0
  212. package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/index.ts +61 -0
  213. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/image.ts +38 -1
  214. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/imageNativeImage.ts +31 -0
  215. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/schemas.ts +52 -0
  216. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/types.ts +614 -1
  217. package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/image.ts +10 -0
  218. package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/imageNativeImage.ts +13 -0
  219. package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +126 -62
  220. package/zova/src/suite/cabloy-basic/modules/basic-image/src/model/image.ts +23 -0
  221. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +44 -4
  222. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx +17 -12
  223. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +2 -2
  224. package/zova/src/suite/cabloy-basic/modules/basic-resource/src/component/formFieldResourcePicker/controller.tsx +2 -2
  225. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/controller.tsx +7 -2
  226. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +34 -33
  227. package/zova/src/suite/cabloy-basic/package.json +1 -0
  228. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  229. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +5 -3
  230. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entry/controller.tsx +2 -2
  231. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controller.tsx +2 -2
  232. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  233. package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
  234. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/utils.ts +17 -17
  235. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  236. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +8 -3
  237. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  238. package/zova/src/suite-vendor/a-zova/package.json +4 -4
  239. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permission.ts +0 -17
  240. package/vona/src/suite-vendor/a-vona/modules/a-user/src/bean/guard.userName.ts +0 -35
@@ -2,7 +2,12 @@ import { BeanBase, deepExtend } from 'vona';
2
2
  import { Bean } from 'vona-module-a-bean';
3
3
 
4
4
  import type { EntityImageProvider } from '../entity/imageProvider.ts';
5
- import type { IImageProviderClientOptions, TypeImageProviderPick } from '../types/imageProvider.ts';
5
+ import type {
6
+ IImageProviderRecord,
7
+ TypeImageProviderClientOptionsByName,
8
+ TypeImageProviderOptionsByName,
9
+ TypeImageProviderPick,
10
+ } from '../types/imageProvider.ts';
6
11
 
7
12
  @Bean()
8
13
  export class BeanImageProvider extends BeanBase {
@@ -29,27 +34,29 @@ export class BeanImageProvider extends BeanBase {
29
34
  return await this.scope.model.imageProvider.insert(dataNew);
30
35
  }
31
36
 
32
- async getClientOptions<T extends IImageProviderClientOptions>(
33
- data: TypeImageProviderPick,
34
- clientOptionsCustom?: T,
35
- clientOptionsDefault?: T,
37
+ async getClientOptions<N extends keyof IImageProviderRecord>(
38
+ data: TypeImageProviderPick & { providerName: N },
39
+ clientOptionsCustom?: TypeImageProviderClientOptionsByName<N>,
40
+ clientOptionsDefault?: TypeImageProviderClientOptionsByName<N>,
36
41
  ) {
37
42
  const entityImageProvider = await this.bean.imageProvider.get(data);
38
43
  if (!entityImageProvider) return { entityImageProvider: undefined };
39
44
  const disabled = entityImageProvider.disabled;
40
- const imageProviderName = entityImageProvider.providerName;
45
+ const imageProviderName = entityImageProvider.providerName as N;
41
46
  const clientName = entityImageProvider.clientName ?? 'default';
42
47
  const onionSlice = this.bean.onion.imageProvider.getOnionSliceEnabled(true, imageProviderName);
43
48
  if (!onionSlice) throw new Error(`Image provider not found: ${imageProviderName}`);
44
- const beanFullName = onionSlice.beanOptions.beanFullName;
45
- const onionOptions = onionSlice.beanOptions.options ?? {};
46
- const clientOptions: IImageProviderClientOptions = deepExtend(
47
- clientOptionsDefault ?? {},
48
- onionOptions?.base,
49
- onionOptions?.clients?.[clientName],
50
- entityImageProvider.clientOptions,
49
+ const beanFullName = onionSlice.beanOptions.beanFullName as N;
50
+ const onionOptions =
51
+ (onionSlice.beanOptions.options as TypeImageProviderOptionsByName<N> | undefined) ??
52
+ ({} as TypeImageProviderOptionsByName<N>);
53
+ const clientOptions = deepExtend(
54
+ (clientOptionsDefault ?? {}) as TypeImageProviderClientOptionsByName<N>,
55
+ onionOptions.base,
56
+ onionOptions.clients?.[clientName as keyof typeof onionOptions.clients],
57
+ entityImageProvider.clientOptions as TypeImageProviderClientOptionsByName<N> | undefined,
51
58
  clientOptionsCustom,
52
- );
59
+ ) as TypeImageProviderClientOptionsByName<N>;
53
60
  return { entityImageProvider, disabled, beanFullName, onionOptions, clientOptions };
54
61
  }
55
62
  }
@@ -1,8 +1,10 @@
1
+ import fse from 'fs-extra';
1
2
  import { BeanBase } from 'vona';
2
3
  import { Bean } from 'vona-module-a-bean';
3
4
 
4
5
  import type {
5
6
  IImageDeliveryTokenPayload,
7
+ IImageDirectUploadTokenPayload,
6
8
  IImageUploadContextResolved,
7
9
  IImageUploadPolicyResolved,
8
10
  IImageUploadTokenPayload,
@@ -13,6 +15,8 @@ import type {
13
15
  IImageSceneRecord,
14
16
  } from '../types/imageScene.ts';
15
17
 
18
+ import { getImageExtension, matchesImageMimeType } from '../lib/imageUploadValidation.ts';
19
+
16
20
  @Bean()
17
21
  export class BeanImageUploadPolicy extends BeanBase {
18
22
  async createUploadToken(data: {
@@ -36,6 +40,21 @@ export class BeanImageUploadPolicy extends BeanBase {
36
40
  return { token, expiresIn: data.expiresIn };
37
41
  }
38
42
 
43
+ async createDirectUploadToken(data: { resourceId: string; path: string; expiresIn?: number }) {
44
+ const path = data.path;
45
+ const token = await this.bean.jwt.createTempAuthToken(
46
+ {
47
+ kind: 'imageDirectUpload',
48
+ resourceId: data.resourceId,
49
+ } as IImageDirectUploadTokenPayload,
50
+ {
51
+ path,
52
+ expiresIn: data.expiresIn,
53
+ },
54
+ );
55
+ return { token, expiresIn: data.expiresIn };
56
+ }
57
+
39
58
  async createDeliveryToken(data: {
40
59
  imageId: number | string;
41
60
  request: IImageDeliveryTokenPayload['request'];
@@ -68,6 +87,16 @@ export class BeanImageUploadPolicy extends BeanBase {
68
87
  return payload;
69
88
  }
70
89
 
90
+ async verifyDirectUploadToken(token: string | undefined, routePathRaw: string) {
91
+ const payload = (await this.bean.jwt.get('access').verify(token, {
92
+ path: routePathRaw,
93
+ })) as IImageDirectUploadTokenPayload | undefined;
94
+ if (!payload || payload.kind !== 'imageDirectUpload') {
95
+ return this.app.throw(401);
96
+ }
97
+ return payload;
98
+ }
99
+
71
100
  async verifyDeliveryToken(token: string | undefined, routePathRaw: string) {
72
101
  const payload = (await this.bean.jwt.get('access').verify(token, {
73
102
  path: routePathRaw,
@@ -92,6 +121,33 @@ export class BeanImageUploadPolicy extends BeanBase {
92
121
  };
93
122
  }
94
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
+
95
151
  async resolveUploadPolicy(data: {
96
152
  imageScene: keyof IImageSceneRecord;
97
153
  size: number;
@@ -121,7 +177,7 @@ export class BeanImageUploadPolicy extends BeanBase {
121
177
  if (maxSize && data.size > maxSize) {
122
178
  return this.app.throw(403, `image too large: maxSize=${maxSize}`);
123
179
  }
124
- if (mimeTypes.length > 0 && !this._matchesMimeType(mimeType, mimeTypes)) {
180
+ if (mimeTypes.length > 0 && !matchesImageMimeType(mimeType, mimeTypes)) {
125
181
  return this.app.throw(403, `unsupported image mimeType: ${mimeType}`);
126
182
  }
127
183
  return {
@@ -135,6 +191,35 @@ export class BeanImageUploadPolicy extends BeanBase {
135
191
  };
136
192
  }
137
193
 
194
+ async validateUploadFile(
195
+ file: {
196
+ file: string;
197
+ filename?: string;
198
+ mimeType: string;
199
+ },
200
+ payload: IImageUploadPolicyResolved,
201
+ ) {
202
+ const stat = await fse.stat(file.file);
203
+ const fileSize = Number(stat.size);
204
+ if (fileSize !== payload.fileSize) {
205
+ return this.app.throw(403, `image size mismatch: size=${fileSize}`);
206
+ }
207
+ if (payload.maxSize && fileSize > payload.maxSize) {
208
+ return this.app.throw(403, `image too large: maxSize=${payload.maxSize}`);
209
+ }
210
+ const mimeType = file.mimeType.toLowerCase();
211
+ if (mimeType !== payload.mimeType) {
212
+ return this.app.throw(403, `image mimeType mismatch: mimeType=${mimeType}`);
213
+ }
214
+ if (payload.mimeTypes?.length && !matchesImageMimeType(mimeType, payload.mimeTypes)) {
215
+ return this.app.throw(403, `unsupported image mimeType: ${mimeType}`);
216
+ }
217
+ const extension = getImageExtension(file.filename);
218
+ if (payload.extensions?.length && extension && !payload.extensions.includes(extension)) {
219
+ return this.app.throw(403, `unsupported image extension: ${extension}`);
220
+ }
221
+ }
222
+
138
223
  private _getSceneOptions(imageScene: keyof IImageSceneRecord): IDecoratorImageSceneOptions {
139
224
  const onionSlice = this.bean.onion.imageScene.getOnionSlice(imageScene);
140
225
  if (!onionSlice) throw new Error(`not found image scene: ${imageScene}`);
@@ -161,14 +246,4 @@ export class BeanImageUploadPolicy extends BeanBase {
161
246
  }
162
247
  return meta;
163
248
  }
164
-
165
- private _matchesMimeType(mimeType: string, mimeTypes: string[]) {
166
- return mimeTypes.some(item => {
167
- if (item === mimeType) return true;
168
- if (item.endsWith('/*')) {
169
- return mimeType.startsWith(`${item.slice(0, -1)}`);
170
- }
171
- return false;
172
- });
173
- }
174
249
  }
@@ -0,0 +1,12 @@
1
+ import type { IMetaOptionsIndex } from 'vona-module-a-index';
2
+
3
+ import { BeanBase } from 'vona';
4
+ import { Meta } from 'vona-module-a-meta';
5
+ import { $tableColumns } from 'vona-module-a-ormutils';
6
+
7
+ @Meta<IMetaOptionsIndex>({
8
+ indexes: {
9
+ ...$tableColumns('aImage', 'status+draftExpiresAt'),
10
+ },
11
+ })
12
+ export class MetaIndex extends BeanBase {}
@@ -1,7 +1,7 @@
1
1
  import { Meta } from 'vona-module-a-meta';
2
2
  import { BeanRedlockBase } from 'vona-module-a-redlock';
3
3
 
4
- export type TypeRedlockLockResource = never;
4
+ export type TypeRedlockLockResource = `image.directUpload.${string}`;
5
5
  export type TypeRedlockLockIsolateResource = 'imageProvider.register';
6
6
 
7
7
  @Meta()
@@ -34,6 +34,9 @@ export class MetaVersion extends BeanBase implements IMetaVersionUpdate {
34
34
  table.text(entityImage.storagePath);
35
35
  table.text(entityImage.deliveryBaseUrl);
36
36
  table.string(entityImage.imageScene, 255);
37
+ table.string(entityImage.status, 255).defaultTo('ready');
38
+ table.dateTime(entityImage.draftExpiresAt);
39
+ table.dateTime(entityImage.finalizedAt);
37
40
  });
38
41
  }
39
42
  }
@@ -0,0 +1,29 @@
1
+ import type { IScheduleExecute } from 'vona-module-a-schedule';
2
+
3
+ import { BeanBase } from 'vona';
4
+ import { Schedule } from 'vona-module-a-schedule';
5
+
6
+ const PruneBatchLimit = 100;
7
+
8
+ @Schedule({ repeat: { every: 30 * 60 * 1000 } })
9
+ export class ScheduleImageDraftPrune extends BeanBase implements IScheduleExecute {
10
+ async execute() {
11
+ const items = await this.scope.model.image.select({
12
+ columns: ['id'],
13
+ where: {
14
+ status: 'draft',
15
+ draftExpiresAt: {
16
+ _lt_: new Date(),
17
+ },
18
+ },
19
+ orders: [
20
+ ['draftExpiresAt', 'asc'],
21
+ ['id', 'asc'],
22
+ ],
23
+ limit: PruneBatchLimit,
24
+ });
25
+ for (const item of items) {
26
+ await this.bean.image.expireDraftImage(item.id);
27
+ }
28
+ }
29
+ }
@@ -12,6 +12,9 @@ export function config(_app: VonaApplication) {
12
12
  maxSize: 2 * 1024 * 1024,
13
13
  mimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
14
14
  },
15
+ directUpload: {
16
+ draftExpiresIn: 30 * 60 * 1000,
17
+ },
15
18
  },
16
19
  };
17
20
  }
@@ -1,17 +1,19 @@
1
1
  import type { IUploadFile } from 'vona-module-a-upload';
2
2
  import type { IDecoratorControllerOptions } from 'vona-module-a-web';
3
3
 
4
- import fse from 'fs-extra';
5
4
  import { BeanBase } from 'vona';
6
5
  import { Core } from 'vona-module-a-core';
7
6
  import { Api, v } from 'vona-module-a-openapiutils';
7
+ import { Passport } from 'vona-module-a-user';
8
8
  import { Arg, Controller, Web } from 'vona-module-a-web';
9
9
 
10
- import type { IImageUploadTokenPayload } from '../types/image.ts';
11
-
12
10
  import { DtoImageDeliveryRequest } from '../dto/imageDeliveryRequest.ts';
11
+ import { DtoImageDirectUploadFinalizeRequest } from '../dto/imageDirectUploadFinalizeRequest.ts';
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) {
@@ -36,7 +44,14 @@ export class ControllerImage extends BeanBase {
36
44
  token,
37
45
  this.ctx.route.routePathRaw,
38
46
  );
39
- await this._validateUploadFile(file, payload);
47
+ await this.bean.imageUploadPolicy.validateUploadFile(
48
+ {
49
+ file: file.file,
50
+ filename: file.info.filename,
51
+ mimeType: file.info.mimeType,
52
+ },
53
+ payload,
54
+ );
40
55
  const image = await this.bean.image.upload(
41
56
  payload.providerName,
42
57
  {
@@ -50,11 +65,7 @@ export class ControllerImage extends BeanBase {
50
65
  imageScene: payload.imageScene,
51
66
  },
52
67
  );
53
- return {
54
- ...image,
55
- url: await this.bean.image.getVariantUrl(image.id),
56
- signed: !!image.requireSignedURLs,
57
- };
68
+ return await this.bean.image.createImageActionResponse(image);
58
69
  }
59
70
 
60
71
  @Web.post('direct-upload')
@@ -70,6 +81,7 @@ export class ControllerImage extends BeanBase {
70
81
  {
71
82
  filename: data.filename,
72
83
  contentType: data.contentType,
84
+ requireSignedURLs: data.requireSignedURLs,
73
85
  expiry: data.expiry,
74
86
  customId: data.customId,
75
87
  },
@@ -81,6 +93,13 @@ export class ControllerImage extends BeanBase {
81
93
  );
82
94
  }
83
95
 
96
+ @Web.post('direct-upload/finalize')
97
+ @Api.body(DtoImageDirectUploadFinalizeResponse)
98
+ async finalizeDirectUpload(@Arg.body() data: DtoImageDirectUploadFinalizeRequest) {
99
+ const image = await this.bean.image.finalizeDirectUpload(data.imageId);
100
+ return await this.bean.image.createImageActionResponse(image);
101
+ }
102
+
84
103
  @Web.post('upload-url')
85
104
  @Api.body(DtoImageUploadResponse)
86
105
  async uploadUrl(@Arg.body() data: DtoImageUploadUrlRequest) {
@@ -95,6 +114,7 @@ export class ControllerImage extends BeanBase {
95
114
  url: data.url,
96
115
  filename: data.filename,
97
116
  contentType: data.contentType,
117
+ requireSignedURLs: data.requireSignedURLs,
98
118
  },
99
119
  {
100
120
  clientName: policy.clientName,
@@ -102,64 +122,22 @@ export class ControllerImage extends BeanBase {
102
122
  imageScene: policy.imageScene,
103
123
  },
104
124
  );
105
- return {
106
- ...image,
107
- url: await this.bean.image.getVariantUrl(image.id),
108
- signed: !!image.requireSignedURLs,
109
- };
125
+ return await this.bean.image.createImageActionResponse(image);
110
126
  }
111
127
 
112
128
  @Web.get('delivery/:imageId')
129
+ @Passport.public()
113
130
  async delivery(
114
131
  @Arg.param('imageId', v.tableIdentity()) imageId: number,
115
132
  @Arg.query(v.object(DtoImageDeliveryRequest)) query: DtoImageDeliveryRequest,
116
133
  ) {
117
134
  const payload = await this.bean.imageUploadPolicy.verifyDeliveryToken(
118
135
  query.token,
119
- this.ctx.route.routePathRaw,
136
+ this.scope.util.combineApiPath(`image/delivery/${imageId}`, false, true),
120
137
  );
121
138
  if (String(payload.imageId) !== String(imageId)) {
122
139
  return this.app.throw(401);
123
140
  }
124
141
  this.ctx.redirect(payload.targetUrl);
125
142
  }
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
143
  }
@@ -0,0 +1,13 @@
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
+
7
+ export interface IDtoOptionsImageDirectUploadFinalizeRequest extends IDecoratorDtoOptions {}
8
+
9
+ @Dto<IDtoOptionsImageDirectUploadFinalizeRequest>()
10
+ export class DtoImageDirectUploadFinalizeRequest {
11
+ @Api.field(v.tableIdentity())
12
+ imageId: TableIdentity;
13
+ }
@@ -0,0 +1,10 @@
1
+ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
2
+
3
+ import { Dto } from 'vona-module-a-web';
4
+
5
+ import { DtoImageUploadResponse } from './imageUploadResponse.ts';
6
+
7
+ export interface IDtoOptionsImageDirectUploadFinalizeResponse extends IDecoratorDtoOptions {}
8
+
9
+ @Dto<IDtoOptionsImageDirectUploadFinalizeResponse>()
10
+ export class DtoImageDirectUploadFinalizeResponse extends DtoImageUploadResponse {}
@@ -5,6 +5,7 @@ import { Api, v } from 'vona-module-a-openapiutils';
5
5
  import { Dto } from 'vona-module-a-web';
6
6
  import z from 'zod';
7
7
 
8
+ import type { TypeImageStatus } from '../types/image.ts';
8
9
  import type { IImageProviderRecord } from '../types/imageProvider.ts';
9
10
  import type { IImageSceneRecord } from '../types/imageScene.ts';
10
11
 
@@ -33,6 +34,15 @@ export class DtoImageDirectUploadResponse {
33
34
  @Api.field(v.optional())
34
35
  filename?: string;
35
36
 
37
+ @Api.field(v.optional())
38
+ requireSignedURLs?: boolean;
39
+
40
+ @Api.field(v.optional(), z.string())
41
+ status?: TypeImageStatus;
42
+
43
+ @Api.field(v.optional())
44
+ draftExpiresAt?: Date;
45
+
36
46
  @Api.field(v.optional(), z.string())
37
47
  imageScene?: keyof IImageSceneRecord;
38
48
  }
@@ -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
+ }
@@ -5,7 +5,7 @@ import { $schema, Api, v } from 'vona-module-a-openapiutils';
5
5
  import { Dto } from 'vona-module-a-web';
6
6
  import z from 'zod';
7
7
 
8
- import type { IImageNamedVariants } from '../types/image.ts';
8
+ import type { IImageNamedVariants, TypeImageStatus } from '../types/image.ts';
9
9
  import type { IImageProviderRecord } from '../types/imageProvider.ts';
10
10
  import type { IImageSceneRecord } from '../types/imageScene.ts';
11
11
 
@@ -51,6 +51,15 @@ export class DtoImageUploadResponse {
51
51
  @Api.field(v.optional(), z.string())
52
52
  imageScene?: keyof IImageSceneRecord;
53
53
 
54
+ @Api.field(v.optional(), z.string())
55
+ status?: TypeImageStatus;
56
+
57
+ @Api.field(v.optional())
58
+ draftExpiresAt?: Date;
59
+
60
+ @Api.field(v.optional())
61
+ finalizedAt?: Date;
62
+
54
63
  @Api.field(v.optional())
55
64
  uploadedAt?: Date;
56
65
 
@@ -5,7 +5,7 @@ import { $schema, Api, v } from 'vona-module-a-openapiutils';
5
5
  import { Dto } from 'vona-module-a-web';
6
6
  import z from 'zod';
7
7
 
8
- import type { IImageNamedVariants } from '../types/image.ts';
8
+ import type { IImageNamedVariants, TypeImageStatus } from '../types/image.ts';
9
9
  import type { IImageSceneRecord } from '../types/imageScene.ts';
10
10
 
11
11
  import { DtoImageTransformOptions } from './imageTransformOptions.tsx';
@@ -38,6 +38,15 @@ export class DtoImageView {
38
38
  @Api.field(v.optional())
39
39
  imageScene?: keyof IImageSceneRecord | string;
40
40
 
41
+ @Api.field(v.optional(), z.string())
42
+ status?: TypeImageStatus;
43
+
44
+ @Api.field(v.optional())
45
+ draftExpiresAt?: Date;
46
+
47
+ @Api.field(v.optional())
48
+ finalizedAt?: Date;
49
+
41
50
  @Api.field(v.optional())
42
51
  uploadedAt?: Date;
43
52
 
@@ -4,7 +4,7 @@ import { $schema, Api, v } from 'vona-module-a-openapiutils';
4
4
  import { Entity, EntityBase } from 'vona-module-a-orm';
5
5
  import z from 'zod';
6
6
 
7
- import type { IImageNamedVariants } from '../types/image.ts';
7
+ import type { IImageNamedVariants, TypeImageStatus } from '../types/image.ts';
8
8
  import type { IImageProviderRecord } from '../types/imageProvider.ts';
9
9
  import type { IImageSceneRecord } from '../types/imageScene.ts';
10
10
 
@@ -55,4 +55,13 @@ export class EntityImage extends EntityBase {
55
55
 
56
56
  @Api.field(v.optional(), z.string())
57
57
  imageScene?: keyof IImageSceneRecord;
58
+
59
+ @Api.field(v.optional(), z.string())
60
+ status?: TypeImageStatus;
61
+
62
+ @Api.field(v.optional())
63
+ draftExpiresAt?: Date;
64
+
65
+ @Api.field(v.optional())
66
+ finalizedAt?: Date;
58
67
  }
@@ -0,0 +1,15 @@
1
+ import path from 'node:path';
2
+
3
+ export function matchesImageMimeType(mimeType: string, mimeTypes: string[]) {
4
+ return mimeTypes.some(item => {
5
+ if (item === mimeType) return true;
6
+ if (item.endsWith('/*')) {
7
+ return mimeType.startsWith(`${item.slice(0, -1)}`);
8
+ }
9
+ return false;
10
+ });
11
+ }
12
+
13
+ export function getImageExtension(filename?: string) {
14
+ return path.extname(filename ?? '').toLowerCase();
15
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './imageProvider.ts';
2
2
  export * from './imageScene.ts';
3
+ export * from './imageUploadValidation.ts';
3
4
  export * from './imageVariant.ts';