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.
- package/.cabloy-version +1 -1
- package/.claude/hooks/contract-loop-gate.ts +151 -44
- package/CHANGELOG.md +48 -0
- package/CLAUDE.md +2 -0
- package/cabloy-docs/.vitepress/config.mjs +11 -0
- package/cabloy-docs/backend/controller-aop-guide.md +0 -1
- package/cabloy-docs/backend/image-guide.md +544 -0
- package/cabloy-docs/frontend/a-openapi-under-the-hood.md +1 -1
- package/cabloy-docs/frontend/generated-contract-consumption-entry-branch.md +1 -1
- package/cabloy-docs/frontend/generated-contract-consumption-list-branch.md +1 -1
- package/cabloy-docs/frontend/generated-contract-consumption-verify-playbook.md +1 -1
- package/cabloy-docs/frontend/image-guide.md +512 -0
- package/cabloy-docs/frontend/introduction.md +2 -0
- package/cabloy-docs/frontend/ioc-and-beans.md +34 -3
- package/cabloy-docs/frontend/model-architecture.md +2 -0
- package/cabloy-docs/frontend/model-resource-owner-pattern.md +3 -1
- package/cabloy-docs/frontend/model-state-guide.md +5 -1
- package/cabloy-docs/frontend/reading-zova-for-vue-developers.md +10 -8
- package/cabloy-docs/frontend/resource-entry-page-deep-dive.md +2 -2
- package/cabloy-docs/frontend/resource-list-page-deep-dive.md +1 -1
- package/cabloy-docs/frontend/rest-resource-source-reading-map.md +2 -2
- package/cabloy-docs/frontend/rest-resource-under-the-hood.md +4 -4
- package/cabloy-docs/frontend/server-data.md +2 -0
- package/cabloy-docs/frontend/state-architecture-for-vue-developers.md +218 -0
- package/cabloy-docs/frontend/system-startup-guide.md +2 -0
- package/cabloy-docs/frontend/use-state-data-best-practices.md +384 -0
- package/cabloy-docs/frontend/zova-vs-vue3-comparison.md +11 -10
- package/cabloy-docs/fullstack/image-workflow.md +403 -0
- package/cabloy-docs/fullstack/introduction.md +1 -0
- package/package.json +1 -1
- package/scripts/upgrade.ts +2 -0
- package/vona/package.original.json +0 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/bean/beanBase.ts +9 -9
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +424 -47
- package/vona/src/suite/a-training/modules/training-record/src/bean/fileScene.dossierFile.ts +22 -0
- package/vona/src/suite/a-training/modules/training-record/src/bean/meta.version.ts +1 -0
- package/vona/src/suite/a-training/modules/training-record/src/config/locale/en-us.ts +1 -0
- package/vona/src/suite/a-training/modules/training-record/src/config/locale/zh-cn.ts +1 -0
- package/vona/src/suite/a-training/modules/training-record/src/dto/recordDossierFileView.ts +47 -0
- package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectResItem.tsx +13 -0
- package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +13 -0
- package/vona/src/suite/a-training/modules/training-record/src/entity/record.tsx +16 -5
- package/vona/src/suite/a-training/modules/training-record/src/index.ts +2 -0
- package/vona/src/suite/a-training/modules/training-record/src/lib/index.ts +1 -0
- package/vona/src/suite/a-training/modules/training-record/src/lib/resolveDossierFiles.ts +34 -0
- package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +209 -123
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/detailRecordBase.tsx +18 -1
- package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +0 -2
- package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +1 -0
- package/vona/src/suite/cabloy-basic/modules/basic-siteadmin/src/bean/ssrSite.admin.ts +4 -0
- package/vona/src/suite/cabloy-basic/modules/basic-siteweb/src/bean/ssrSite.web.ts +4 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/cli/ssrSite/boilerplate/{{sceneName}}.{{beanName}}.ts_ +3 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/service/ssrHandler.ts +11 -2
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/types/ssrSite.ts +6 -0
- package/vona/src/suite-vendor/a-cabloy/package.json +1 -1
- package/vona/src/suite-vendor/a-file/modules/a-file/cli/fileProvider/boilerplate/{{sceneName}}.{{beanName}}.ts_ +23 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/cli/fileScene/boilerplate/{{sceneName}}.{{beanName}}.ts_ +5 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/package.json +68 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/index.ts +425 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/locales.ts +18 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.file.ts +322 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileProvider.ts +62 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileUploadPolicy.ts +224 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.redlock.ts +11 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.version.ts +38 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/config/config.ts +16 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/config/locale/en-us.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/config/locale/zh-cn.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/controller/file.ts +175 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadRequest.ts +33 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadResponse.ts +41 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDownloadRequest.ts +12 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadPolicyRequest.ts +15 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadPolicyResponse.ts +30 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadResponse.ts +59 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadTokenRequest.ts +24 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadTokenResponse.ts +16 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadUrlRequest.ts +33 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/file.ts +55 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/fileProvider.ts +24 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/index.ts +4 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileProvider.ts +7 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileScene.ts +7 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileUploadValidation.ts +15 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/index.ts +3 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/model/file.ts +10 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/model/fileProvider.ts +10 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/file.ts +144 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileProvider.ts +111 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileScene.ts +53 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/index.ts +3 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/package.json +53 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/.metadata/index.ts +104 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/bean/fileProvider.cloudflare.ts +123 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/service/fileCloudflare.ts +245 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/package.json +50 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/.metadata/index.ts +111 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/bean/fileProvider.native.ts +125 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/config/config.ts +7 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/service/fileNative.ts +125 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-file/package.json +14 -0
- package/vona/src/suite-vendor/a-file/tsconfig.base.json +4 -0
- package/vona/src/suite-vendor/a-file/tsconfig.json +16 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/package.json +4 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/index.ts +61 -3
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.image.ts +336 -152
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageProvider.ts +21 -14
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +86 -11
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.index.ts +12 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.redlock.ts +1 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.version.ts +3 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/schedule.imageDraftPrune.ts +29 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/config/config.ts +3 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +32 -54
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadFinalizeRequest.ts +13 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadFinalizeResponse.ts +10 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadResponse.ts +10 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadPolicyRequest.ts +15 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadPolicyResponse.ts +27 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadResponse.ts +10 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageView.ts +10 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/entity/image.ts +10 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/imageUploadValidation.ts +15 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/index.ts +1 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/types/image.ts +18 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageProvider.ts +28 -2
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/package.json +1 -1
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/bean/imageProvider.cloudflare.ts +13 -1
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts +26 -0
- package/vona/src/suite-vendor/a-image/modules/image-native/package.json +6 -2
- package/vona/src/suite-vendor/a-image/modules/image-native/src/.metadata/index.ts +37 -0
- package/vona/src/suite-vendor/a-image/modules/image-native/src/bean/imageProvider.native.ts +21 -1
- package/vona/src/suite-vendor/a-image/modules/image-native/src/controller/image.ts +57 -0
- package/vona/src/suite-vendor/a-image/modules/image-native/src/service/imageNative.ts +142 -34
- package/vona/src/suite-vendor/a-image/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/entity.ts +2 -4
- package/vona/src/suite-vendor/a-vona/modules/a-ormutils/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormutils/src/lib/columns.ts +7 -3
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-permission/src/.metadata/index.ts +24 -9
- package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +87 -73
- package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permissionActionByRoles.ts +27 -0
- package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permissionUser.ts +20 -0
- package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +4 -3
- package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-upload/src/bean/interceptor.upload.ts +5 -1
- package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/.metadata/index.ts +0 -13
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/lib/passport.ts +0 -7
- package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-version/src/bean/meta.version.ts +2 -24
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +1 -1
- package/zova/packages-zova/zova-core/src/bean/beanBase.ts +9 -13
- package/zova/pnpm-lock.yaml +418 -411
- package/zova/src/suite/a-demo/modules/demo-basic/src/page/toolOne/controller.tsx +2 -2
- package/zova/src/suite/a-demo/modules/demo-todo/src/page/todo/controller.tsx +2 -2
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx +2 -2
- package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx +2 -2
- package/zova/src/suite/a-home/modules/home-login/src/page/login/controller.tsx +2 -2
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockDetails/controller.tsx +5 -1
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockTable/controller.tsx +20 -15
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/service/detail.tsx +6 -1
- package/zova/src/suite/cabloy-basic/modules/basic-file/cli/openapi.config.ts +9 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/package.json +58 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/component/formFieldFile.ts +31 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/index.ts +222 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/locales.ts +7 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/this.ts +2 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/file.ts +121 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/baseURL.ts +5 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/index.ts +3 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/schemas.ts +306 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/types.ts +5718 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/apiSchema/file.ts +40 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/bean/tableCell.file.tsx +187 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/component/formFieldFile/controller.tsx +534 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/config/locale/en-us.ts +15 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/config/locale/zh-cn.ts +15 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/index.ts +4 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/lib/file.ts +74 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/lib/index.ts +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/model/file.ts +23 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/types/file.ts +12 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/types/index.ts +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/tsconfig.build.json +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-file/tsconfig.json +5 -0
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/index.ts +61 -0
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/image.ts +38 -1
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/imageNativeImage.ts +31 -0
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/schemas.ts +52 -0
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/types.ts +614 -1
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/image.ts +10 -0
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/imageNativeImage.ts +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +126 -62
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/model/image.ts +23 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +44 -4
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx +17 -12
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +2 -2
- package/zova/src/suite/cabloy-basic/modules/basic-resource/src/component/formFieldResourcePicker/controller.tsx +2 -2
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/controller.tsx +7 -2
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +34 -33
- package/zova/src/suite/cabloy-basic/package.json +1 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +5 -3
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entry/controller.tsx +2 -2
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controller.tsx +2 -2
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/utils.ts +17 -17
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +8 -3
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +4 -4
- package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permission.ts +0 -17
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/bean/guard.userName.ts +0 -35
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EntityFile,
|
|
3
|
+
IFileDeliveryOptions,
|
|
4
|
+
IFileDirectUploadInput,
|
|
5
|
+
IFileProviderDirectUploadResource,
|
|
6
|
+
IFileProviderResource,
|
|
7
|
+
IFileUploadInput,
|
|
8
|
+
IFileUploadUrlInput,
|
|
9
|
+
} from 'vona-module-a-file';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
DeleteObjectCommand,
|
|
13
|
+
GetObjectCommand,
|
|
14
|
+
HeadObjectCommand,
|
|
15
|
+
PutObjectCommand,
|
|
16
|
+
S3Client,
|
|
17
|
+
} from '@aws-sdk/client-s3';
|
|
18
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
19
|
+
import fse from 'fs-extra';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { BeanBase, uuidv4 } from 'vona';
|
|
22
|
+
import { Service } from 'vona-module-a-bean';
|
|
23
|
+
|
|
24
|
+
import type { IFileProviderCloudflareClientOptions } from '../bean/fileProvider.cloudflare.ts';
|
|
25
|
+
|
|
26
|
+
interface IFileCloudflareClientOptionsNormalized extends IFileProviderCloudflareClientOptions {
|
|
27
|
+
endpoint: string;
|
|
28
|
+
region: string;
|
|
29
|
+
accessKeyId: string;
|
|
30
|
+
secretAccessKey: string;
|
|
31
|
+
bucket: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Service()
|
|
35
|
+
export class ServiceFileCloudflare extends BeanBase {
|
|
36
|
+
async upload(
|
|
37
|
+
input: IFileUploadInput,
|
|
38
|
+
options: IFileProviderCloudflareClientOptions,
|
|
39
|
+
): Promise<IFileProviderResource> {
|
|
40
|
+
const normalized = this._normalizeClientOptions(options);
|
|
41
|
+
const objectKey = input.objectKey ?? this._createObjectKey(input.filename);
|
|
42
|
+
const body = await fse.readFile(input.file);
|
|
43
|
+
const command = new PutObjectCommand({
|
|
44
|
+
Bucket: normalized.bucket,
|
|
45
|
+
Key: objectKey,
|
|
46
|
+
Body: body,
|
|
47
|
+
ContentType: input.contentType,
|
|
48
|
+
});
|
|
49
|
+
const result = await this._getClient(normalized).send(command);
|
|
50
|
+
const stat = await fse.stat(input.file);
|
|
51
|
+
return {
|
|
52
|
+
resourceId: uuidv4(),
|
|
53
|
+
bucket: normalized.bucket,
|
|
54
|
+
objectKey,
|
|
55
|
+
filename: input.filename ?? path.basename(objectKey),
|
|
56
|
+
contentType: input.contentType,
|
|
57
|
+
size: input.size ?? Number(stat.size),
|
|
58
|
+
etag: this._normalizeEtag(result.ETag),
|
|
59
|
+
deliveryBaseUrl: normalized.deliveryBaseUrl,
|
|
60
|
+
public: input.public ?? normalized.public,
|
|
61
|
+
meta: input.meta,
|
|
62
|
+
raw: result,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async uploadUrl(
|
|
67
|
+
input: IFileUploadUrlInput,
|
|
68
|
+
options: IFileProviderCloudflareClientOptions,
|
|
69
|
+
): Promise<IFileProviderResource> {
|
|
70
|
+
const response = await fetch(input.url);
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw new Error(`Remote file fetch failed: ${response.status}`);
|
|
73
|
+
}
|
|
74
|
+
const tempDir = await this.app.util.getPublicPathPhysical('.temp-file-cloudflare', true);
|
|
75
|
+
const tempFile = path.join(
|
|
76
|
+
tempDir,
|
|
77
|
+
`${uuidv4()}${path.extname(input.filename ?? new URL(input.url).pathname)}`,
|
|
78
|
+
);
|
|
79
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
80
|
+
await fse.writeFile(tempFile, Buffer.from(arrayBuffer));
|
|
81
|
+
try {
|
|
82
|
+
return await this.upload(
|
|
83
|
+
{
|
|
84
|
+
file: tempFile,
|
|
85
|
+
filename: input.filename ?? path.basename(new URL(input.url).pathname),
|
|
86
|
+
contentType: input.contentType ?? response.headers.get('content-type') ?? undefined,
|
|
87
|
+
size: input.size,
|
|
88
|
+
objectKey: input.objectKey,
|
|
89
|
+
public: input.public,
|
|
90
|
+
meta: input.meta,
|
|
91
|
+
},
|
|
92
|
+
options,
|
|
93
|
+
);
|
|
94
|
+
} finally {
|
|
95
|
+
await fse.remove(tempFile);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async createDirectUpload(
|
|
100
|
+
input: IFileDirectUploadInput,
|
|
101
|
+
options: IFileProviderCloudflareClientOptions,
|
|
102
|
+
): Promise<IFileProviderDirectUploadResource> {
|
|
103
|
+
const normalized = this._normalizeClientOptions(options);
|
|
104
|
+
const objectKey = input.objectKey ?? this._createObjectKey(input.filename);
|
|
105
|
+
const command = new PutObjectCommand({
|
|
106
|
+
Bucket: normalized.bucket,
|
|
107
|
+
Key: objectKey,
|
|
108
|
+
ContentType: input.contentType,
|
|
109
|
+
});
|
|
110
|
+
const uploadUrl = await getSignedUrl(this._getClient(normalized), command, {
|
|
111
|
+
expiresIn: this._resolveExpiresIn(input.expiry, normalized.presignExpiresIn ?? 600),
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
resourceId: uuidv4(),
|
|
115
|
+
bucket: normalized.bucket,
|
|
116
|
+
objectKey,
|
|
117
|
+
filename: input.filename ?? path.basename(objectKey),
|
|
118
|
+
contentType: input.contentType,
|
|
119
|
+
size: input.size,
|
|
120
|
+
deliveryBaseUrl: normalized.deliveryBaseUrl,
|
|
121
|
+
public: input.public ?? normalized.public,
|
|
122
|
+
meta: input.meta,
|
|
123
|
+
uploadUrl,
|
|
124
|
+
method: 'PUT',
|
|
125
|
+
headers: input.contentType ? { 'content-type': input.contentType } : undefined,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async remove(
|
|
130
|
+
file: Pick<EntityFile, 'bucket' | 'objectKey'>,
|
|
131
|
+
options: IFileProviderCloudflareClientOptions,
|
|
132
|
+
) {
|
|
133
|
+
const normalized = this._normalizeClientOptions(options);
|
|
134
|
+
if (!file.objectKey) return;
|
|
135
|
+
await this._getClient(normalized).send(
|
|
136
|
+
new DeleteObjectCommand({
|
|
137
|
+
Bucket: file.bucket ?? normalized.bucket,
|
|
138
|
+
Key: file.objectKey,
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async getDownloadUrl(
|
|
144
|
+
file: Pick<EntityFile, 'bucket' | 'objectKey' | 'deliveryBaseUrl' | 'public'>,
|
|
145
|
+
options: IFileProviderCloudflareClientOptions,
|
|
146
|
+
deliveryOptions?: IFileDeliveryOptions,
|
|
147
|
+
) {
|
|
148
|
+
const normalized = this._normalizeClientOptions(options);
|
|
149
|
+
const bucket = file.bucket ?? normalized.bucket;
|
|
150
|
+
const objectKey = file.objectKey;
|
|
151
|
+
if (!objectKey) {
|
|
152
|
+
throw new Error('File objectKey is required');
|
|
153
|
+
}
|
|
154
|
+
const isPublic = file.public ?? normalized.public;
|
|
155
|
+
const deliveryBaseUrl = file.deliveryBaseUrl ?? normalized.deliveryBaseUrl;
|
|
156
|
+
if (isPublic && deliveryBaseUrl) {
|
|
157
|
+
return this._buildPublicUrl(deliveryBaseUrl, objectKey);
|
|
158
|
+
}
|
|
159
|
+
const command = new GetObjectCommand({
|
|
160
|
+
Bucket: bucket,
|
|
161
|
+
Key: objectKey,
|
|
162
|
+
});
|
|
163
|
+
return await getSignedUrl(this._getClient(normalized), command, {
|
|
164
|
+
expiresIn: deliveryOptions?.expiresIn ?? normalized.presignExpiresIn ?? 600,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async getMetadata(
|
|
169
|
+
file: Pick<EntityFile, 'bucket' | 'objectKey'>,
|
|
170
|
+
options: IFileProviderCloudflareClientOptions,
|
|
171
|
+
) {
|
|
172
|
+
const normalized = this._normalizeClientOptions(options);
|
|
173
|
+
if (!file.objectKey) return undefined;
|
|
174
|
+
return await this._getClient(normalized).send(
|
|
175
|
+
new HeadObjectCommand({
|
|
176
|
+
Bucket: file.bucket ?? normalized.bucket,
|
|
177
|
+
Key: file.objectKey,
|
|
178
|
+
}),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private _normalizeClientOptions(options: IFileProviderCloudflareClientOptions) {
|
|
183
|
+
const endpoint = options.endpoint;
|
|
184
|
+
const accessKeyId = options.accessKeyId;
|
|
185
|
+
const secretAccessKey = options.secretAccessKey;
|
|
186
|
+
const bucket = options.bucket;
|
|
187
|
+
if (!endpoint) throw new Error('Cloudflare R2 endpoint is required');
|
|
188
|
+
if (!accessKeyId) throw new Error('Cloudflare R2 accessKeyId is required');
|
|
189
|
+
if (!secretAccessKey) throw new Error('Cloudflare R2 secretAccessKey is required');
|
|
190
|
+
if (!bucket) throw new Error('Cloudflare R2 bucket is required');
|
|
191
|
+
return {
|
|
192
|
+
...options,
|
|
193
|
+
endpoint: endpoint.replace(/\/$/, ''),
|
|
194
|
+
region: options.region ?? 'auto',
|
|
195
|
+
accessKeyId,
|
|
196
|
+
secretAccessKey,
|
|
197
|
+
bucket,
|
|
198
|
+
} as IFileCloudflareClientOptionsNormalized;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private _getClient(options: IFileCloudflareClientOptionsNormalized) {
|
|
202
|
+
return new S3Client({
|
|
203
|
+
region: options.region,
|
|
204
|
+
endpoint: options.endpoint,
|
|
205
|
+
forcePathStyle: options.forcePathStyle ?? true,
|
|
206
|
+
credentials: {
|
|
207
|
+
accessKeyId: options.accessKeyId,
|
|
208
|
+
secretAccessKey: options.secretAccessKey,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private _createObjectKey(filename?: string) {
|
|
214
|
+
const ext = path.extname(filename ?? '') || '.bin';
|
|
215
|
+
return `${uuidv4()}${ext}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private _normalizeEtag(etag?: string) {
|
|
219
|
+
return etag?.replace(/^"|"$/g, '');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private _resolveExpiresIn(expiry: IFileDirectUploadInput['expiry'], fallback: number) {
|
|
223
|
+
if (expiry === undefined) return fallback;
|
|
224
|
+
const expiresAt =
|
|
225
|
+
expiry instanceof Date
|
|
226
|
+
? expiry.getTime()
|
|
227
|
+
: typeof expiry === 'string'
|
|
228
|
+
? new Date(expiry).getTime()
|
|
229
|
+
: Number(expiry);
|
|
230
|
+
if (!Number.isFinite(expiresAt)) {
|
|
231
|
+
throw new TypeError(`Invalid direct upload expiry: ${String(expiry)}`);
|
|
232
|
+
}
|
|
233
|
+
const expiresIn = Math.max(1, Math.floor((expiresAt - Date.now()) / 1000));
|
|
234
|
+
return expiresIn;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private _buildPublicUrl(deliveryBaseUrl: string, objectKey: string) {
|
|
238
|
+
const base = deliveryBaseUrl.replace(/\/$/, '');
|
|
239
|
+
const key = objectKey
|
|
240
|
+
.split('/')
|
|
241
|
+
.map(segment => encodeURIComponent(segment))
|
|
242
|
+
.join('/');
|
|
243
|
+
return `${base}/${key}`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vona-module-file-native",
|
|
3
|
+
"version": "5.0.3",
|
|
4
|
+
"description": "",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"Vona Module"
|
|
7
|
+
],
|
|
8
|
+
"author": "",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": [
|
|
17
|
+
"./src/index.ts",
|
|
18
|
+
"./dist/index.d.ts"
|
|
19
|
+
],
|
|
20
|
+
"import": "./src/index.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"clean": "rimraf dist tsconfig.build.tsbuildinfo",
|
|
27
|
+
"tsc:publish": "npm run clean && node ../../../../../packages-cli/cli/src/bin/vona.ts :bin:buildModule --sourcemap && tsc -p tsconfig.build.json",
|
|
28
|
+
"prepublishOnly": "npm run tsc:publish",
|
|
29
|
+
"prepack": "clean-package",
|
|
30
|
+
"postpack": "clean-package restore"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"clean-package": "^2.2.0",
|
|
35
|
+
"rimraf": "^6.1.3"
|
|
36
|
+
},
|
|
37
|
+
"clean-package": {
|
|
38
|
+
"indent": 2,
|
|
39
|
+
"remove": [
|
|
40
|
+
"clean-package",
|
|
41
|
+
"exports.\\..import"
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"title": "file-native",
|
|
45
|
+
"vonaModule": {
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"a-file": "5.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// eslint-disable
|
|
2
|
+
/** service: begin */
|
|
3
|
+
export * from '../service/fileNative.ts';
|
|
4
|
+
|
|
5
|
+
import 'vona-module-a-bean';
|
|
6
|
+
declare module 'vona-module-a-bean' {
|
|
7
|
+
|
|
8
|
+
export interface IServiceRecord {
|
|
9
|
+
'file-native:fileNative': never;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
}
|
|
14
|
+
declare module 'vona-module-file-native' {
|
|
15
|
+
|
|
16
|
+
export interface ServiceFileNative {
|
|
17
|
+
/** @internal */
|
|
18
|
+
get scope(): ScopeModuleFileNative;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ServiceFileNative {
|
|
22
|
+
get $beanFullName(): 'file-native.service.fileNative';
|
|
23
|
+
get $onionName(): 'file-native:fileNative';
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** service: end */
|
|
28
|
+
/** service: begin */
|
|
29
|
+
import type { ServiceFileNative } from '../service/fileNative.ts';
|
|
30
|
+
export interface IModuleService {
|
|
31
|
+
'fileNative': ServiceFileNative;
|
|
32
|
+
}
|
|
33
|
+
/** service: end */
|
|
34
|
+
/** service: begin */
|
|
35
|
+
|
|
36
|
+
import 'vona';
|
|
37
|
+
declare module 'vona' {
|
|
38
|
+
export interface IBeanRecordGeneral {
|
|
39
|
+
'file-native.service.fileNative': ServiceFileNative;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** service: end */
|
|
43
|
+
/** fileProvider: begin */
|
|
44
|
+
export * from '../bean/fileProvider.native.ts';
|
|
45
|
+
import type { IFileProviderOptionsNative } from '../bean/fileProvider.native.ts';
|
|
46
|
+
import 'vona-module-a-file';
|
|
47
|
+
declare module 'vona-module-a-file' {
|
|
48
|
+
|
|
49
|
+
export interface IFileProviderRecord {
|
|
50
|
+
'file-native:native': IFileProviderOptionsNative;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
declare module 'vona-module-file-native' {
|
|
56
|
+
|
|
57
|
+
export interface FileProviderNative {
|
|
58
|
+
/** @internal */
|
|
59
|
+
get scope(): ScopeModuleFileNative;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface FileProviderNative {
|
|
63
|
+
get $beanFullName(): 'file-native.fileProvider.native';
|
|
64
|
+
get $onionName(): 'file-native:native';
|
|
65
|
+
get $onionOptions(): IFileProviderOptionsNative;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** fileProvider: end */
|
|
69
|
+
/** fileProvider: begin */
|
|
70
|
+
import type { FileProviderNative } from '../bean/fileProvider.native.ts';
|
|
71
|
+
export interface IModuleFileProvider {
|
|
72
|
+
'native': FileProviderNative;
|
|
73
|
+
}
|
|
74
|
+
/** fileProvider: end */
|
|
75
|
+
/** config: begin */
|
|
76
|
+
export * from '../config/config.ts';
|
|
77
|
+
import type { config } from '../config/config.ts';
|
|
78
|
+
/** config: end */
|
|
79
|
+
/** scope: begin */
|
|
80
|
+
import { BeanScopeBase, type BeanScopeUtil, type TypeModuleConfig } from 'vona';
|
|
81
|
+
import { Scope } from 'vona-module-a-bean';
|
|
82
|
+
|
|
83
|
+
@Scope()
|
|
84
|
+
export class ScopeModuleFileNative extends BeanScopeBase {}
|
|
85
|
+
|
|
86
|
+
export interface ScopeModuleFileNative {
|
|
87
|
+
util: BeanScopeUtil;
|
|
88
|
+
config: TypeModuleConfig<typeof config>;
|
|
89
|
+
service: IModuleService;
|
|
90
|
+
fileProvider: IModuleFileProvider;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
import 'vona';
|
|
94
|
+
declare module 'vona' {
|
|
95
|
+
export interface IBeanScopeRecord {
|
|
96
|
+
'file-native': ScopeModuleFileNative;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface IBeanScopeContainer {
|
|
100
|
+
fileNative: ScopeModuleFileNative;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface IBeanScopeConfig {
|
|
104
|
+
'file-native': ReturnType<typeof config>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
/** scope: end */
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EntityFile,
|
|
3
|
+
IDecoratorFileProviderOptions,
|
|
4
|
+
IFileDeliveryOptions,
|
|
5
|
+
IFileDownloadResult,
|
|
6
|
+
IFileProviderClientOptions,
|
|
7
|
+
IFileProviderClientRecord,
|
|
8
|
+
IFileProviderExecute,
|
|
9
|
+
IFileProviderResource,
|
|
10
|
+
IFileUploadInput,
|
|
11
|
+
} from 'vona-module-a-file';
|
|
12
|
+
|
|
13
|
+
import fse from 'fs-extra';
|
|
14
|
+
import { BeanBase } from 'vona';
|
|
15
|
+
import { FileProvider } from 'vona-module-a-file';
|
|
16
|
+
|
|
17
|
+
export interface IFileProviderNativeClientRecord extends IFileProviderClientRecord {}
|
|
18
|
+
|
|
19
|
+
export interface IFileProviderNativeClientOptions extends IFileProviderClientOptions {
|
|
20
|
+
subdir?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface IFileProviderOptionsNative extends IDecoratorFileProviderOptions<
|
|
24
|
+
IFileProviderNativeClientRecord,
|
|
25
|
+
IFileProviderNativeClientOptions
|
|
26
|
+
> {}
|
|
27
|
+
|
|
28
|
+
@FileProvider<IFileProviderOptionsNative>({
|
|
29
|
+
base: {
|
|
30
|
+
subdir: 'default',
|
|
31
|
+
signedDeliveryKind: 'proxy',
|
|
32
|
+
public: false,
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
export class FileProviderNative
|
|
36
|
+
extends BeanBase
|
|
37
|
+
implements IFileProviderExecute<IFileProviderNativeClientOptions, IFileProviderOptionsNative>
|
|
38
|
+
{
|
|
39
|
+
async upload(
|
|
40
|
+
input: IFileUploadInput,
|
|
41
|
+
clientOptions: IFileProviderNativeClientOptions,
|
|
42
|
+
_options: IFileProviderOptionsNative,
|
|
43
|
+
): Promise<IFileProviderResource> {
|
|
44
|
+
const resource = await this.scope.service.fileNative.upload(input, clientOptions);
|
|
45
|
+
return {
|
|
46
|
+
...resource,
|
|
47
|
+
filename: input.filename,
|
|
48
|
+
contentType: input.contentType,
|
|
49
|
+
meta: input.meta,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async get(
|
|
54
|
+
file: EntityFile,
|
|
55
|
+
clientOptions: IFileProviderNativeClientOptions,
|
|
56
|
+
_options: IFileProviderOptionsNative,
|
|
57
|
+
) {
|
|
58
|
+
return {
|
|
59
|
+
resourceId: file.resourceId,
|
|
60
|
+
bucket: file.bucket,
|
|
61
|
+
objectKey: file.objectKey,
|
|
62
|
+
filename: file.filename,
|
|
63
|
+
contentType: file.contentType,
|
|
64
|
+
size: file.size,
|
|
65
|
+
etag: file.etag,
|
|
66
|
+
public: file.public ?? clientOptions.public,
|
|
67
|
+
meta: file.meta,
|
|
68
|
+
storagePath: file.storagePath,
|
|
69
|
+
deliveryBaseUrl: file.deliveryBaseUrl ?? clientOptions.deliveryBaseUrl,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async delete(
|
|
74
|
+
file: EntityFile,
|
|
75
|
+
_clientOptions: IFileProviderNativeClientOptions,
|
|
76
|
+
_options: IFileProviderOptionsNative,
|
|
77
|
+
) {
|
|
78
|
+
await this.scope.service.fileNative.remove(file);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async getDownloadUrl(
|
|
82
|
+
file: EntityFile,
|
|
83
|
+
clientOptions: IFileProviderNativeClientOptions,
|
|
84
|
+
_options: IFileProviderOptionsNative,
|
|
85
|
+
deliveryOptions?: IFileDeliveryOptions,
|
|
86
|
+
) {
|
|
87
|
+
return await this.scope.service.fileNative.getDownloadUrl(
|
|
88
|
+
file,
|
|
89
|
+
{
|
|
90
|
+
...clientOptions,
|
|
91
|
+
subdir: clientOptions.subdir ?? 'default',
|
|
92
|
+
deliveryBaseUrl: file.deliveryBaseUrl ?? clientOptions.deliveryBaseUrl,
|
|
93
|
+
public: file.public ?? clientOptions.public,
|
|
94
|
+
},
|
|
95
|
+
deliveryOptions,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async download(
|
|
100
|
+
file: EntityFile,
|
|
101
|
+
clientOptions: IFileProviderNativeClientOptions,
|
|
102
|
+
options: IFileProviderOptionsNative,
|
|
103
|
+
deliveryOptions?: IFileDeliveryOptions,
|
|
104
|
+
): Promise<IFileDownloadResult> {
|
|
105
|
+
if ((deliveryOptions?.responseMode ?? 'auto') !== 'url' && !deliveryOptions?.signed) {
|
|
106
|
+
const buffer = file.storagePath ? await fse.readFile(file.storagePath) : undefined;
|
|
107
|
+
if (buffer) {
|
|
108
|
+
return {
|
|
109
|
+
kind: 'buffer',
|
|
110
|
+
buffer,
|
|
111
|
+
filename: file.filename,
|
|
112
|
+
contentType: file.contentType,
|
|
113
|
+
signed: false,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
kind: 'url',
|
|
119
|
+
url: await this.getDownloadUrl(file, clientOptions, options, deliveryOptions),
|
|
120
|
+
filename: file.filename,
|
|
121
|
+
contentType: file.contentType,
|
|
122
|
+
signed: !!deliveryOptions?.signed,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './.metadata/index.ts';
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EntityFile,
|
|
3
|
+
IFileDeliveryOptions,
|
|
4
|
+
IFileProviderResource,
|
|
5
|
+
IFileUploadInput,
|
|
6
|
+
} from 'vona-module-a-file';
|
|
7
|
+
|
|
8
|
+
import fse from 'fs-extra';
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { BeanBase, getRuntimePathPhysicalRoot, uuidv4 } from 'vona';
|
|
12
|
+
import { Service } from 'vona-module-a-bean';
|
|
13
|
+
|
|
14
|
+
import type { IFileProviderNativeClientOptions } from '../bean/fileProvider.native.ts';
|
|
15
|
+
|
|
16
|
+
type IFileNativeStoredFile = Pick<
|
|
17
|
+
EntityFile,
|
|
18
|
+
'id' | 'resourceId' | 'filename' | 'storagePath' | 'public' | 'objectKey' | 'deliveryBaseUrl'
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
@Service()
|
|
22
|
+
export class ServiceFileNative extends BeanBase {
|
|
23
|
+
async upload(
|
|
24
|
+
input: IFileUploadInput,
|
|
25
|
+
options: IFileProviderNativeClientOptions,
|
|
26
|
+
): Promise<IFileProviderResource> {
|
|
27
|
+
const resourceId = uuidv4();
|
|
28
|
+
const objectKey = this._getObjectKey(resourceId, input, options);
|
|
29
|
+
const targetPath = await this._getTargetPath(
|
|
30
|
+
objectKey,
|
|
31
|
+
input.public ?? options.public,
|
|
32
|
+
options,
|
|
33
|
+
);
|
|
34
|
+
await fse.ensureDir(path.dirname(targetPath));
|
|
35
|
+
await fse.copy(input.file, targetPath);
|
|
36
|
+
const stat = await fse.stat(targetPath);
|
|
37
|
+
return {
|
|
38
|
+
resourceId,
|
|
39
|
+
objectKey,
|
|
40
|
+
size: Number(stat.size),
|
|
41
|
+
etag: await this._calculateEtag(targetPath),
|
|
42
|
+
storagePath: targetPath,
|
|
43
|
+
deliveryBaseUrl: options.deliveryBaseUrl,
|
|
44
|
+
public: input.public ?? options.public,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async remove(file: IFileNativeStoredFile) {
|
|
49
|
+
if (!file.storagePath) return;
|
|
50
|
+
if (!(await fse.pathExists(file.storagePath))) return;
|
|
51
|
+
await fse.remove(file.storagePath);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async getDownloadUrl(
|
|
55
|
+
file: IFileNativeStoredFile,
|
|
56
|
+
options: IFileProviderNativeClientOptions,
|
|
57
|
+
_deliveryOptions?: IFileDeliveryOptions,
|
|
58
|
+
) {
|
|
59
|
+
if (!file.storagePath) {
|
|
60
|
+
throw new Error(`File storage path missing: ${file.resourceId}`);
|
|
61
|
+
}
|
|
62
|
+
if (!(file.public ?? options.public)) {
|
|
63
|
+
const routePath = this.scope.util.combineApiPath(
|
|
64
|
+
`file/download/${file.id ?? file.resourceId}`,
|
|
65
|
+
false,
|
|
66
|
+
true,
|
|
67
|
+
);
|
|
68
|
+
return this.app.util.getAbsoluteUrlByApiPath(routePath);
|
|
69
|
+
}
|
|
70
|
+
return await this._buildUrl(file.storagePath, options.deliveryBaseUrl);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async _getTargetPath(
|
|
74
|
+
objectKey: string,
|
|
75
|
+
isPublic: boolean | undefined,
|
|
76
|
+
options: IFileProviderNativeClientOptions,
|
|
77
|
+
) {
|
|
78
|
+
if (isPublic) {
|
|
79
|
+
const publicPath = await this.app.util.getPublicPathPhysical(
|
|
80
|
+
path.join('file-native', options.subdir ?? 'default'),
|
|
81
|
+
true,
|
|
82
|
+
);
|
|
83
|
+
return path.join(publicPath, objectKey);
|
|
84
|
+
}
|
|
85
|
+
const runtimeRoot = getRuntimePathPhysicalRoot(this.app);
|
|
86
|
+
const basePath = path.join(
|
|
87
|
+
runtimeRoot,
|
|
88
|
+
this.ctx.instance.id.toString(),
|
|
89
|
+
'file-native',
|
|
90
|
+
options.subdir ?? 'default',
|
|
91
|
+
);
|
|
92
|
+
await fse.ensureDir(basePath);
|
|
93
|
+
return path.join(basePath, objectKey);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private _getObjectKey(
|
|
97
|
+
resourceId: string,
|
|
98
|
+
input: IFileUploadInput,
|
|
99
|
+
options: IFileProviderNativeClientOptions,
|
|
100
|
+
) {
|
|
101
|
+
if (input.objectKey) return input.objectKey.replace(/^\/+/, '');
|
|
102
|
+
const ext = path.extname(input.filename ?? '') || path.extname(input.file) || '.bin';
|
|
103
|
+
const prefix = options.subdir ? `${options.subdir.replace(/\/+$/, '')}/` : '';
|
|
104
|
+
return `${prefix}${resourceId}${ext}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private async _buildUrl(targetPath: string, deliveryBaseUrl?: string) {
|
|
108
|
+
const staticPath = await this._getStaticPath(targetPath);
|
|
109
|
+
if (deliveryBaseUrl) {
|
|
110
|
+
return `${deliveryBaseUrl.replace(/\/$/, '')}/${staticPath}`;
|
|
111
|
+
}
|
|
112
|
+
return this.app.util.combineStaticPath(`/${staticPath}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private async _getStaticPath(targetPath: string) {
|
|
116
|
+
const publicRoot = await this.app.util.getPublicPathPhysical(undefined, true);
|
|
117
|
+
const relativePath = path.relative(publicRoot, targetPath).split(path.sep).join(path.posix.sep);
|
|
118
|
+
return path.posix.join('public', this.ctx.instance.id.toString(), relativePath);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private async _calculateEtag(targetPath: string) {
|
|
122
|
+
const buffer = await fse.readFile(targetPath);
|
|
123
|
+
return createHash('md5').update(buffer).digest('hex');
|
|
124
|
+
}
|
|
125
|
+
}
|