cabloy 5.1.97 → 5.1.98
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/CHANGELOG.md +27 -0
- package/cabloy-docs/.vitepress/config.mjs +7 -0
- package/cabloy-docs/backend/image-guide.md +544 -0
- 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 +2 -0
- package/cabloy-docs/frontend/model-state-guide.md +2 -0
- package/cabloy-docs/frontend/reading-zova-for-vue-developers.md +10 -8
- 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/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/vona/package.original.json +0 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/pnpm-lock.yaml +424 -47
- 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 +66 -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 +196 -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 +167 -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/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 +59 -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 +24 -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/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-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/package.json +1 -1
- package/zova/pnpm-lock.yaml +389 -397
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +0 -9
package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/bean/imageProvider.cloudflare.ts
CHANGED
|
@@ -43,7 +43,11 @@ export interface IImageProviderOptionsCloudflare extends IDecoratorImageProvider
|
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
45
|
})
|
|
46
|
-
export class ImageProviderCloudflare
|
|
46
|
+
export class ImageProviderCloudflare
|
|
47
|
+
extends BeanBase
|
|
48
|
+
implements
|
|
49
|
+
IImageProviderExecute<IImageProviderCloudflareClientOptions, IImageProviderOptionsCloudflare>
|
|
50
|
+
{
|
|
47
51
|
async upload(
|
|
48
52
|
input: IImageUploadInput,
|
|
49
53
|
clientOptions: IImageProviderCloudflareClientOptions,
|
|
@@ -68,6 +72,14 @@ export class ImageProviderCloudflare extends BeanBase implements IImageProviderE
|
|
|
68
72
|
return await this.scope.service.imageCloudflare.createDirectUpload(input, clientOptions);
|
|
69
73
|
}
|
|
70
74
|
|
|
75
|
+
async finalizeDirectUpload(
|
|
76
|
+
image: EntityImage,
|
|
77
|
+
clientOptions: IImageProviderCloudflareClientOptions,
|
|
78
|
+
_options: IImageProviderOptionsCloudflare,
|
|
79
|
+
) {
|
|
80
|
+
return await this.scope.service.imageCloudflare.finalizeDirectUpload(image, clientOptions);
|
|
81
|
+
}
|
|
82
|
+
|
|
71
83
|
async get(
|
|
72
84
|
image: EntityImage,
|
|
73
85
|
clientOptions: IImageProviderCloudflareClientOptions,
|
package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts
CHANGED
|
@@ -38,6 +38,12 @@ interface ICloudflareImageResult {
|
|
|
38
38
|
draft?: boolean;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
interface ICloudflareImageDetailsResult extends ICloudflareImageResult {
|
|
42
|
+
meta?: {
|
|
43
|
+
filename?: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
interface ICloudflareClientOptionsNormalized extends IImageProviderCloudflareClientOptions {
|
|
42
48
|
accountId: string;
|
|
43
49
|
apiToken: string;
|
|
@@ -130,6 +136,26 @@ export class ServiceImageCloudflare extends BeanBase {
|
|
|
130
136
|
};
|
|
131
137
|
}
|
|
132
138
|
|
|
139
|
+
async finalizeDirectUpload(
|
|
140
|
+
image: Pick<EntityImage, 'resourceId' | 'filename' | 'meta' | 'requireSignedURLs'>,
|
|
141
|
+
options: IImageProviderCloudflareClientOptions,
|
|
142
|
+
): Promise<IImageProviderResource | undefined> {
|
|
143
|
+
const normalized = this._normalizeClientOptions(options);
|
|
144
|
+
const result = await this._request<ICloudflareImageDetailsResult>(
|
|
145
|
+
normalized,
|
|
146
|
+
`/images/v1/${encodeURIComponent(image.resourceId)}`,
|
|
147
|
+
{
|
|
148
|
+
method: 'GET',
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
if (result.draft) return undefined;
|
|
152
|
+
return this._mapImageResource(result, normalized, {
|
|
153
|
+
filename: result.filename ?? result.meta?.filename ?? image.filename,
|
|
154
|
+
meta: image.meta,
|
|
155
|
+
requireSignedURLs: image.requireSignedURLs,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
133
159
|
async remove(
|
|
134
160
|
image: Pick<EntityImage, 'resourceId'>,
|
|
135
161
|
options: IImageProviderCloudflareClientOptions,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona-module-image-native",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Vona Module"
|
|
@@ -46,7 +46,11 @@
|
|
|
46
46
|
"title": "image-native",
|
|
47
47
|
"vonaModule": {
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"a-
|
|
49
|
+
"a-core": "5.0.0",
|
|
50
|
+
"a-image": "5.0.0",
|
|
51
|
+
"a-upload": "5.0.0",
|
|
52
|
+
"a-user": "5.0.0",
|
|
53
|
+
"a-web": "5.0.0"
|
|
50
54
|
},
|
|
51
55
|
"globalDependencies": {
|
|
52
56
|
"sharp": true
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// eslint-disable
|
|
2
|
+
import type { TypeControllerOptionsActions } from 'vona-module-a-openapi';
|
|
2
3
|
/** service: begin */
|
|
3
4
|
export * from '../service/imageNative.ts';
|
|
4
5
|
|
|
@@ -72,6 +73,42 @@ export interface IModuleImageProvider {
|
|
|
72
73
|
'native': ImageProviderNative;
|
|
73
74
|
}
|
|
74
75
|
/** imageProvider: end */
|
|
76
|
+
/** controller: begin */
|
|
77
|
+
export * from '../controller/image.ts';
|
|
78
|
+
import type { IControllerOptionsImage } from '../controller/image.ts';
|
|
79
|
+
import 'vona-module-a-web';
|
|
80
|
+
declare module 'vona-module-a-web' {
|
|
81
|
+
export interface IControllerRecord {
|
|
82
|
+
'image-native:image': IControllerOptionsImage;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
declare module 'vona-module-image-native' {
|
|
86
|
+
export interface ControllerImage {
|
|
87
|
+
/** @internal */
|
|
88
|
+
get scope(): ScopeModuleImageNative;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ControllerImage {
|
|
92
|
+
get $beanFullName(): 'image-native.controller.image';
|
|
93
|
+
get $onionName(): 'image-native:image';
|
|
94
|
+
get $onionOptions(): IControllerOptionsImage;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** controller: end */
|
|
98
|
+
/** controller: begin */
|
|
99
|
+
// @ts-ignore ignore
|
|
100
|
+
import type { ControllerImage } from '../controller/image.ts';
|
|
101
|
+
declare module 'vona-module-image-native' {
|
|
102
|
+
export interface IControllerOptionsImage {
|
|
103
|
+
actions?: TypeControllerOptionsActions<ControllerImage>;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
declare module 'vona-module-a-web' {
|
|
107
|
+
export interface IApiPathPostRecord {
|
|
108
|
+
'/image-native/direct-upload/:resourceId': undefined;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** controller: end */
|
|
75
112
|
/** config: begin */
|
|
76
113
|
export * from '../config/config.ts';
|
|
77
114
|
import type { config } from '../config/config.ts';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
IDecoratorImageProviderOptions,
|
|
3
3
|
IImageDeliveryOptions,
|
|
4
|
+
IImageDirectUploadInput,
|
|
4
5
|
IImageDownloadResult,
|
|
5
6
|
IImageProviderClientOptions,
|
|
6
7
|
IImageProviderClientRecord,
|
|
@@ -34,7 +35,10 @@ export interface IImageProviderOptionsNative extends IDecoratorImageProviderOpti
|
|
|
34
35
|
signedDeliveryKind: 'proxy',
|
|
35
36
|
},
|
|
36
37
|
})
|
|
37
|
-
export class ImageProviderNative
|
|
38
|
+
export class ImageProviderNative
|
|
39
|
+
extends BeanBase
|
|
40
|
+
implements IImageProviderExecute<IImageProviderNativeClientOptions, IImageProviderOptionsNative>
|
|
41
|
+
{
|
|
38
42
|
async upload(
|
|
39
43
|
input: IImageUploadInput,
|
|
40
44
|
clientOptions: IImageProviderNativeClientOptions,
|
|
@@ -49,6 +53,22 @@ export class ImageProviderNative extends BeanBase implements IImageProviderExecu
|
|
|
49
53
|
};
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
async createDirectUpload(
|
|
57
|
+
input: IImageDirectUploadInput,
|
|
58
|
+
clientOptions: IImageProviderNativeClientOptions,
|
|
59
|
+
_options: IImageProviderOptionsNative,
|
|
60
|
+
) {
|
|
61
|
+
return await this.scope.service.imageNative.createDirectUpload(input, clientOptions);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async finalizeDirectUpload(
|
|
65
|
+
image: EntityImage,
|
|
66
|
+
clientOptions: IImageProviderNativeClientOptions,
|
|
67
|
+
_options: IImageProviderOptionsNative,
|
|
68
|
+
) {
|
|
69
|
+
return await this.scope.service.imageNative.finalizeDirectUpload(image, clientOptions);
|
|
70
|
+
}
|
|
71
|
+
|
|
52
72
|
async get(
|
|
53
73
|
image: EntityImage,
|
|
54
74
|
clientOptions: IImageProviderNativeClientOptions,
|
|
@@ -0,0 +1,57 @@
|
|
|
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 { Passport } from 'vona-module-a-user';
|
|
8
|
+
import { Arg, Controller, Web } from 'vona-module-a-web';
|
|
9
|
+
|
|
10
|
+
export interface IControllerOptionsImage extends IDecoratorControllerOptions {}
|
|
11
|
+
|
|
12
|
+
@Controller<IControllerOptionsImage>({ path: 'image-native' })
|
|
13
|
+
export class ControllerImage extends BeanBase {
|
|
14
|
+
@Web.post('direct-upload/:resourceId')
|
|
15
|
+
@Passport.public()
|
|
16
|
+
@Core.fileUpload()
|
|
17
|
+
async directUpload(
|
|
18
|
+
@Arg.param('resourceId') resourceId: string,
|
|
19
|
+
@Arg.query('token') token: string,
|
|
20
|
+
@Arg.file('image') file: IUploadFile,
|
|
21
|
+
) {
|
|
22
|
+
const payload = await this.bean.imageUploadPolicy.verifyDirectUploadToken(
|
|
23
|
+
token,
|
|
24
|
+
this.ctx.route.routePathRaw,
|
|
25
|
+
);
|
|
26
|
+
if (payload.resourceId !== resourceId) {
|
|
27
|
+
return this.app.throw(401);
|
|
28
|
+
}
|
|
29
|
+
const modelImage = this.app.bean._getBean<any>('a-image.model.image' as never);
|
|
30
|
+
const image = await modelImage.get({
|
|
31
|
+
providerName: 'image-native:native',
|
|
32
|
+
resourceId,
|
|
33
|
+
});
|
|
34
|
+
if (!image || image.status !== 'draft') {
|
|
35
|
+
return this.app.throw(403, `image is not draft: ${resourceId}`);
|
|
36
|
+
}
|
|
37
|
+
if (!image.imageScene) {
|
|
38
|
+
throw new Error(`image scene missing: ${resourceId}`);
|
|
39
|
+
}
|
|
40
|
+
const stat = await fse.stat(file.file);
|
|
41
|
+
const policy = await this.bean.imageUploadPolicy.resolveUploadPolicy({
|
|
42
|
+
imageScene: image.imageScene,
|
|
43
|
+
size: Number(stat.size),
|
|
44
|
+
mimeType: file.info.mimeType,
|
|
45
|
+
});
|
|
46
|
+
await this.bean.imageUploadPolicy.validateUploadFile(
|
|
47
|
+
{
|
|
48
|
+
file: file.file,
|
|
49
|
+
filename: file.info.filename,
|
|
50
|
+
mimeType: file.info.mimeType,
|
|
51
|
+
},
|
|
52
|
+
policy,
|
|
53
|
+
);
|
|
54
|
+
await this.scope.service.imageNative.uploadDirectFile(image, file);
|
|
55
|
+
return { ok: true };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -2,13 +2,15 @@ import type { SharpConstructor } from 'sharp';
|
|
|
2
2
|
import type {
|
|
3
3
|
EntityImage,
|
|
4
4
|
IImageDeliveryOptions,
|
|
5
|
-
|
|
5
|
+
IImageDirectUploadInput,
|
|
6
|
+
IImageProviderDirectUploadResource,
|
|
6
7
|
IImageProviderResolvedVariant,
|
|
7
8
|
IImageProviderResource,
|
|
8
9
|
IImageTransformOptions,
|
|
9
10
|
IImageUploadInput,
|
|
10
11
|
IImageVariantRequest,
|
|
11
12
|
} from 'vona-module-a-image';
|
|
13
|
+
import type { IUploadFile } from 'vona-module-a-upload';
|
|
12
14
|
|
|
13
15
|
import fse from 'fs-extra';
|
|
14
16
|
import { createHash } from 'node:crypto';
|
|
@@ -24,6 +26,20 @@ type IImageNativeStoredImage = Pick<
|
|
|
24
26
|
'resourceId' | 'filename' | 'storagePath' | 'variants'
|
|
25
27
|
>;
|
|
26
28
|
|
|
29
|
+
type IImageNativeDraftImage = Pick<
|
|
30
|
+
EntityImage,
|
|
31
|
+
| 'resourceId'
|
|
32
|
+
| 'filename'
|
|
33
|
+
| 'contentType'
|
|
34
|
+
| 'meta'
|
|
35
|
+
| 'imageScene'
|
|
36
|
+
| 'requireSignedURLs'
|
|
37
|
+
| 'storagePath'
|
|
38
|
+
| 'deliveryBaseUrl'
|
|
39
|
+
| 'variants'
|
|
40
|
+
| 'clientName'
|
|
41
|
+
>;
|
|
42
|
+
|
|
27
43
|
@Service()
|
|
28
44
|
export class ServiceImageNative extends BeanBase {
|
|
29
45
|
private _sharpInstance: SharpConstructor;
|
|
@@ -39,39 +55,90 @@ export class ServiceImageNative extends BeanBase {
|
|
|
39
55
|
input: IImageUploadInput,
|
|
40
56
|
options: IImageProviderNativeClientOptions,
|
|
41
57
|
): Promise<IImageProviderResource> {
|
|
42
|
-
const publicPath = await this.app.util.getPublicPathPhysical(
|
|
43
|
-
path.join('image-native', options.subdir ?? 'default'),
|
|
44
|
-
true,
|
|
45
|
-
);
|
|
46
58
|
const resourceId = uuidv4();
|
|
47
|
-
const
|
|
48
|
-
const targetName = `${resourceId}${ext}`;
|
|
49
|
-
const targetPath = path.join(publicPath, targetName);
|
|
59
|
+
const targetPath = await this._getFinalPath(resourceId, input.filename, options, input.file);
|
|
50
60
|
await fse.copy(input.file, targetPath);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
options,
|
|
61
|
+
return await this._buildStoredResource(targetPath, {
|
|
62
|
+
resourceId,
|
|
63
|
+
filename: input.filename,
|
|
64
|
+
contentType: input.contentType,
|
|
65
|
+
meta: input.meta,
|
|
66
|
+
requireSignedURLs: input.requireSignedURLs ?? options.requireSignedURLs,
|
|
67
|
+
variants: options.variants ?? this.scope.config.imageNative.variants,
|
|
68
|
+
deliveryBaseUrl: options.deliveryBaseUrl,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async createDirectUpload(
|
|
73
|
+
input: IImageDirectUploadInput,
|
|
74
|
+
options: IImageProviderNativeClientOptions,
|
|
75
|
+
): Promise<IImageProviderDirectUploadResource> {
|
|
76
|
+
const resourceId = this._sanitizeResourceId(input.customId) ?? uuidv4();
|
|
77
|
+
const storagePath = await this._getFinalPath(resourceId, input.filename, options);
|
|
78
|
+
const routePath = this.scope.util.combineApiPath(
|
|
79
|
+
`image-native/direct-upload/${encodeURIComponent(resourceId)}`,
|
|
80
|
+
false,
|
|
81
|
+
true,
|
|
58
82
|
);
|
|
83
|
+
const tokenPath = this.scope.util.combineApiPath(
|
|
84
|
+
'image-native/direct-upload/:resourceId',
|
|
85
|
+
false,
|
|
86
|
+
true,
|
|
87
|
+
);
|
|
88
|
+
const tokenPayload = await this.bean.imageUploadPolicy.createDirectUploadToken({
|
|
89
|
+
resourceId,
|
|
90
|
+
path: tokenPath,
|
|
91
|
+
});
|
|
92
|
+
const uploadUrl = new URL(this.app.util.getAbsoluteUrlByApiPath(routePath));
|
|
93
|
+
uploadUrl.searchParams.set('token', tokenPayload.token);
|
|
59
94
|
return {
|
|
60
95
|
resourceId,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
storagePath: targetPath,
|
|
65
|
-
deliveryBaseUrl: options.deliveryBaseUrl,
|
|
96
|
+
filename: input.filename,
|
|
97
|
+
contentType: input.contentType,
|
|
98
|
+
meta: input.meta,
|
|
66
99
|
requireSignedURLs: input.requireSignedURLs ?? options.requireSignedURLs,
|
|
67
|
-
variants,
|
|
100
|
+
variants: options.variants ?? this.scope.config.imageNative.variants,
|
|
101
|
+
deliveryBaseUrl: options.deliveryBaseUrl,
|
|
102
|
+
storagePath,
|
|
103
|
+
uploadUrl: uploadUrl.toString(),
|
|
104
|
+
draft: true,
|
|
68
105
|
};
|
|
69
106
|
}
|
|
70
107
|
|
|
108
|
+
async uploadDirectFile(image: IImageNativeDraftImage, file: IUploadFile) {
|
|
109
|
+
const draftPath = this._getDraftPath(image.storagePath, image.resourceId);
|
|
110
|
+
await fse.ensureDir(path.dirname(draftPath));
|
|
111
|
+
await fse.move(file.file, draftPath, { overwrite: true });
|
|
112
|
+
return draftPath;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async finalizeDirectUpload(
|
|
116
|
+
image: IImageNativeDraftImage,
|
|
117
|
+
options: IImageProviderNativeClientOptions,
|
|
118
|
+
): Promise<IImageProviderResource | undefined> {
|
|
119
|
+
const storagePath =
|
|
120
|
+
image.storagePath ?? (await this._getFinalPath(image.resourceId, image.filename, options));
|
|
121
|
+
const draftPath = this._getDraftPath(storagePath, image.resourceId);
|
|
122
|
+
if (!(await fse.pathExists(draftPath))) return undefined;
|
|
123
|
+
await fse.ensureDir(path.dirname(storagePath));
|
|
124
|
+
await fse.move(draftPath, storagePath, { overwrite: true });
|
|
125
|
+
return await this._buildStoredResource(storagePath, {
|
|
126
|
+
resourceId: image.resourceId,
|
|
127
|
+
filename: image.filename,
|
|
128
|
+
contentType: image.contentType,
|
|
129
|
+
meta: image.meta,
|
|
130
|
+
requireSignedURLs: image.requireSignedURLs ?? options.requireSignedURLs,
|
|
131
|
+
variants: image.variants ?? options.variants ?? this.scope.config.imageNative.variants,
|
|
132
|
+
deliveryBaseUrl: image.deliveryBaseUrl ?? options.deliveryBaseUrl,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
71
136
|
async remove(image: IImageNativeStoredImage) {
|
|
72
137
|
if (!image.storagePath) return;
|
|
73
138
|
const dir = path.dirname(image.storagePath);
|
|
74
139
|
if (!(await fse.pathExists(dir))) return;
|
|
140
|
+
const draftPath = this._getDraftPath(image.storagePath, image.resourceId);
|
|
141
|
+
await fse.remove(draftPath);
|
|
75
142
|
const entries = await fse.readdir(dir);
|
|
76
143
|
await Promise.all(
|
|
77
144
|
entries
|
|
@@ -100,22 +167,57 @@ export class ServiceImageNative extends BeanBase {
|
|
|
100
167
|
return await this._buildUrl(targetPath, options.deliveryBaseUrl);
|
|
101
168
|
}
|
|
102
169
|
|
|
103
|
-
private async
|
|
104
|
-
|
|
170
|
+
private async _buildStoredResource(
|
|
171
|
+
targetPath: string,
|
|
172
|
+
data: {
|
|
173
|
+
resourceId: string;
|
|
174
|
+
filename?: string;
|
|
175
|
+
contentType?: string;
|
|
176
|
+
meta?: Record<string, unknown>;
|
|
177
|
+
requireSignedURLs?: boolean;
|
|
178
|
+
variants?: EntityImage['variants'];
|
|
179
|
+
deliveryBaseUrl?: string;
|
|
180
|
+
},
|
|
181
|
+
): Promise<IImageProviderResource> {
|
|
182
|
+
const sharp = await this.getSharp();
|
|
183
|
+
const metadata = await sharp(targetPath).metadata();
|
|
184
|
+
const stat = await fse.stat(targetPath);
|
|
185
|
+
return {
|
|
186
|
+
resourceId: data.resourceId,
|
|
187
|
+
filename: data.filename,
|
|
188
|
+
contentType: data.contentType,
|
|
189
|
+
size: Number(stat.size),
|
|
190
|
+
width: metadata.width,
|
|
191
|
+
height: metadata.height,
|
|
192
|
+
requireSignedURLs: data.requireSignedURLs,
|
|
193
|
+
variants: data.variants,
|
|
194
|
+
meta: data.meta,
|
|
195
|
+
storagePath: targetPath,
|
|
196
|
+
deliveryBaseUrl: data.deliveryBaseUrl,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private async _getFinalPath(
|
|
201
|
+
resourceId: string,
|
|
202
|
+
filename: string | undefined,
|
|
105
203
|
options: IImageProviderNativeClientOptions,
|
|
204
|
+
filePath?: string,
|
|
106
205
|
) {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
206
|
+
const publicPath = await this.app.util.getPublicPathPhysical(
|
|
207
|
+
path.join('image-native', options.subdir ?? 'default'),
|
|
208
|
+
true,
|
|
209
|
+
);
|
|
210
|
+
const ext = this._getOriginalExt(filename, filePath ?? resourceId);
|
|
211
|
+
return path.join(publicPath, `${resourceId}${ext}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private _getDraftPath(storagePath: string | undefined, resourceId: string) {
|
|
215
|
+
if (!storagePath) {
|
|
216
|
+
throw new Error(`Image storage path missing: ${resourceId}`);
|
|
116
217
|
}
|
|
117
|
-
|
|
118
|
-
|
|
218
|
+
const dir = path.dirname(storagePath);
|
|
219
|
+
const ext = path.extname(storagePath) || '.bin';
|
|
220
|
+
return path.join(dir, `${resourceId}__draft${ext}`);
|
|
119
221
|
}
|
|
120
222
|
|
|
121
223
|
private async _ensureVariantFile(
|
|
@@ -266,6 +368,12 @@ export class ServiceImageNative extends BeanBase {
|
|
|
266
368
|
return JSON.stringify(value);
|
|
267
369
|
}
|
|
268
370
|
|
|
371
|
+
private _sanitizeResourceId(resourceId: string | undefined) {
|
|
372
|
+
if (!resourceId) return undefined;
|
|
373
|
+
const sanitized = resourceId.replace(/[^\w.-]/g, '-');
|
|
374
|
+
return sanitized || undefined;
|
|
375
|
+
}
|
|
376
|
+
|
|
269
377
|
private _sanitizeVariantName(variantName: string) {
|
|
270
378
|
return variantName.replace(/[^\w.-]/g, '-');
|
|
271
379
|
}
|
|
@@ -11,7 +11,5 @@ export type TypeEntityMeta<T, N = string> = Required<{
|
|
|
11
11
|
export const SymbolKeyFieldsMore = Symbol('$fieldsMore');
|
|
12
12
|
export type TypeSymbolKeyFieldsMore = typeof SymbolKeyFieldsMore;
|
|
13
13
|
|
|
14
|
-
export type
|
|
15
|
-
export type
|
|
16
|
-
keyof TypeEntityStudentMetaSimple<T>
|
|
17
|
-
>;
|
|
14
|
+
export type TypeEntityMetaFields<T> = Omit<T, '$table' | '$comment' | '$default'>;
|
|
15
|
+
export type TypeEntityMetaFieldKeys<T> = PickString<keyof TypeEntityMetaFields<T>>;
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
IDecoratorEntityOptions,
|
|
5
5
|
ITableRecord,
|
|
6
6
|
TypeEntityMeta,
|
|
7
|
-
|
|
7
|
+
TypeEntityMetaFieldKeys,
|
|
8
8
|
} from 'vona-module-a-orm';
|
|
9
9
|
|
|
10
10
|
import { isClass } from '@cabloy/utils';
|
|
@@ -15,6 +15,9 @@ import {
|
|
|
15
15
|
getTargetDecoratorRules,
|
|
16
16
|
} from 'vona-module-a-openapiutils';
|
|
17
17
|
|
|
18
|
+
export type TypeEntityMetaCompositeFieldKeys<T> =
|
|
19
|
+
`${TypeEntityMetaFieldKeys<T>}+${TypeEntityMetaFieldKeys<T>}`;
|
|
20
|
+
|
|
18
21
|
export function $column<T>(key: keyof T): keyof T {
|
|
19
22
|
return key;
|
|
20
23
|
}
|
|
@@ -53,8 +56,9 @@ export function $columnsAll<T, TableName extends boolean, Meta extends boolean>(
|
|
|
53
56
|
export function $tableColumns<K extends keyof ITableRecord>(
|
|
54
57
|
tableName: K,
|
|
55
58
|
key?:
|
|
56
|
-
|
|
|
57
|
-
|
|
|
59
|
+
| TypeEntityMetaFieldKeys<ITableRecord[K]>
|
|
60
|
+
| TypeEntityMetaCompositeFieldKeys<ITableRecord[K]>
|
|
61
|
+
| TypeEntityMetaFieldKeys<ITableRecord[K]>[]
|
|
58
62
|
| undefined,
|
|
59
63
|
) {
|
|
60
64
|
return { [tableName]: key };
|