cabloy 5.1.102 → 5.1.103
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 +29 -0
- package/CLAUDE.local.md +4 -0
- package/cabloy-docs/.vitepress/config.mjs +3 -0
- package/cabloy-docs/backend/file-guide.md +148 -0
- package/cabloy-docs/backend/image-guide.md +40 -40
- package/cabloy-docs/backend/jwt-guide.md +21 -0
- package/cabloy-docs/frontend/file-guide.md +147 -0
- package/cabloy-docs/frontend/image-guide.md +18 -9
- package/cabloy-docs/frontend/model-state-guide.md +12 -0
- package/cabloy-docs/frontend/use-state-data-best-practices.md +54 -0
- package/cabloy-docs/fullstack/file-workflow.md +135 -0
- package/cabloy-docs/fullstack/image-workflow.md +12 -3
- package/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/src/suite/a-training/modules/training-record/src/.metadata/index.ts +30 -4
- package/vona/src/suite/a-training/modules/training-record/src/bean/fileScene.dossierFile.ts +1 -1
- package/vona/src/suite/a-training/modules/training-record/src/bean/imageScene.sceneImage.ts +1 -0
- package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectResItem.tsx +8 -6
- package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +8 -6
- package/vona/src/suite/a-training/modules/training-record/src/index.ts +0 -1
- package/vona/src/suite/a-training/modules/training-record/src/lib/index.ts +0 -1
- package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +10 -12
- package/vona/src/suite/a-training/modules/training-student/src/bean/imageScene.studentImage.ts +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/detailRecordBase.tsx +9 -10
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +2 -2
- package/vona/src/suite-vendor/a-file/modules/a-file/package.json +3 -2
- package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/index.ts +155 -35
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.file.ts +283 -54
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileUploadPolicy.ts +46 -63
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.index.ts +12 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.redlock.ts +1 -1
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.version.ts +3 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/schedule.fileDraftPrune.ts +29 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/serializerTransform.resolveView.ts +45 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/serializerTransform.resolveViews.ts +45 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/config/config.ts +6 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/controller/file.ts +64 -56
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadFinalizeRequest.ts +13 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadFinalizeResponse.ts +10 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadRequest.ts +0 -3
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadResponse.ts +2 -14
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDownloadRequest.ts +3 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadPolicyResponse.ts +3 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadResponse.ts +0 -25
- package/vona/src/{suite/a-training/modules/training-record/src/dto/recordDossierFileView.ts → suite-vendor/a-file/modules/a-file/src/dto/fileView.ts} +3 -15
- package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/file.ts +10 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileUploadUrl.ts +329 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/index.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/file.ts +51 -10
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileProvider.ts +8 -3
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileScene.ts +1 -1
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/package.json +1 -1
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/bean/fileProvider.cloudflare.ts +12 -4
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/service/fileCloudflare.ts +52 -18
- package/vona/src/suite-vendor/a-file/modules/file-native/package.json +1 -1
- package/vona/src/suite-vendor/a-file/modules/file-native/src/bean/fileProvider.native.ts +5 -5
- package/vona/src/suite-vendor/a-file/modules/file-native/src/service/fileNative.ts +7 -8
- package/vona/src/suite-vendor/a-file/package.json +4 -4
- package/vona/src/suite-vendor/a-image/modules/a-image/package.json +1 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/index.ts +16 -19
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.image.ts +99 -74
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +28 -68
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.version.ts +1 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/serializerTransform.resolveView.ts +3 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/serializerTransform.resolveViews.ts +3 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/config/config.ts +4 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +78 -34
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDeliveryRequest.ts +3 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadRequest.ts +0 -3
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadResponse.ts +1 -27
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadPolicyResponse.ts +6 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadResponse.ts +2 -36
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadUrlRequest.ts +0 -3
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageView.ts +4 -27
- package/vona/src/suite-vendor/a-image/modules/a-image/src/entity/image.ts +1 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/types/image.ts +36 -29
- package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageProvider.ts +4 -4
- package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageScene.ts +1 -0
- 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 +8 -11
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/lib/cloudflareImageUrl.ts +4 -16
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts +17 -16
- package/vona/src/suite-vendor/a-image/modules/image-native/package.json +2 -6
- package/vona/src/suite-vendor/a-image/modules/image-native/src/.metadata/index.ts +0 -37
- package/vona/src/suite-vendor/a-image/modules/image-native/src/bean/imageProvider.native.ts +13 -36
- package/vona/src/suite-vendor/a-image/modules/image-native/src/service/imageNative.ts +100 -113
- package/vona/src/suite-vendor/a-image/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/config/config.ts +3 -1
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/service/jwtClient.ts +9 -4
- 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 +113 -64
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/src/suite/a-home/modules/home-api/src/api/openapi/schemas.ts +50 -12
- package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +610 -114
- package/zova/src/suite/a-home/modules/home-passport/src/model/passport.ts +44 -1
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +50 -12
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +610 -114
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/index.ts +16 -12
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/file.ts +25 -25
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/schemas.ts +18 -24
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/types.ts +228 -291
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/apiSchema/file.ts +5 -5
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/bean/tableCell.file.tsx +23 -5
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/component/formFieldFile/controller.tsx +72 -43
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/lib/file.ts +22 -4
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/index.ts +16 -42
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/image.ts +5 -25
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/schemas.ts +10 -24
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/types.ts +216 -333
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/image.ts +0 -5
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/bean/tableCell.image.tsx +38 -12
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +79 -32
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/lib/preview.ts +22 -4
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/types/image.ts +3 -4
- 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 +29 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
- package/vona/src/suite/a-training/modules/training-record/src/lib/resolveDossierFiles.ts +0 -34
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadTokenRequest.ts +0 -24
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadTokenResponse.ts +0 -16
- package/vona/src/suite-vendor/a-image/modules/image-native/src/controller/image.ts +0 -57
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/imageNativeImage.ts +0 -31
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/imageNativeImage.ts +0 -13
|
@@ -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 =
|
|
4
|
+
export type TypeRedlockLockResource = `file.directUpload.${string}`;
|
|
5
5
|
export type TypeRedlockLockIsolateResource = 'fileProvider.register';
|
|
6
6
|
|
|
7
7
|
@Meta()
|
|
@@ -32,6 +32,9 @@ export class MetaVersion extends BeanBase implements IMetaVersionUpdate {
|
|
|
32
32
|
table.text(entityFile.storagePath);
|
|
33
33
|
table.text(entityFile.deliveryBaseUrl);
|
|
34
34
|
table.string(entityFile.fileScene, 255);
|
|
35
|
+
table.string(entityFile.status, 16).defaultTo('ready');
|
|
36
|
+
table.dateTime(entityFile.draftExpiresAt);
|
|
37
|
+
table.dateTime(entityFile.finalizedAt);
|
|
35
38
|
});
|
|
36
39
|
}
|
|
37
40
|
}
|
|
@@ -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 ScheduleFileDraftPrune extends BeanBase implements IScheduleExecute {
|
|
10
|
+
async execute() {
|
|
11
|
+
const items = await this.scope.model.file.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.file.expireDraftFile(item.id);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/serializerTransform.resolveView.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IDecoratorSerializerTransformOptions,
|
|
3
|
+
ISerializerTransform,
|
|
4
|
+
} from 'vona-module-a-serialization';
|
|
5
|
+
|
|
6
|
+
import { BeanBase } from 'vona';
|
|
7
|
+
import { SerializerTransform } from 'vona-module-a-serialization';
|
|
8
|
+
|
|
9
|
+
import type { IFileDeliveryOptions, IFileView } from '../types/file.ts';
|
|
10
|
+
import type { IFileSceneRecord } from '../types/fileScene.ts';
|
|
11
|
+
|
|
12
|
+
export type TypeSerializerTransformResolveViewValue = unknown;
|
|
13
|
+
|
|
14
|
+
export type TypeSerializerTransformResolveViewData = object;
|
|
15
|
+
|
|
16
|
+
export type TypeSerializerTransformResolveViewResult = IFileView | undefined;
|
|
17
|
+
|
|
18
|
+
export interface ISerializerTransformOptionsResolveView extends IDecoratorSerializerTransformOptions {
|
|
19
|
+
fieldName: string;
|
|
20
|
+
fileScene?: keyof IFileSceneRecord;
|
|
21
|
+
deliveryOptions?: IFileDeliveryOptions;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@SerializerTransform<ISerializerTransformOptionsResolveView>()
|
|
25
|
+
export class SerializerTransformResolveView
|
|
26
|
+
extends BeanBase
|
|
27
|
+
implements
|
|
28
|
+
ISerializerTransform<
|
|
29
|
+
TypeSerializerTransformResolveViewValue,
|
|
30
|
+
TypeSerializerTransformResolveViewData,
|
|
31
|
+
TypeSerializerTransformResolveViewResult
|
|
32
|
+
>
|
|
33
|
+
{
|
|
34
|
+
async transform(
|
|
35
|
+
_value: TypeSerializerTransformResolveViewValue,
|
|
36
|
+
data: TypeSerializerTransformResolveViewData,
|
|
37
|
+
options: ISerializerTransformOptionsResolveView,
|
|
38
|
+
): Promise<TypeSerializerTransformResolveViewResult> {
|
|
39
|
+
return await this.bean.file.resolveView(
|
|
40
|
+
data[options.fieldName],
|
|
41
|
+
options.fileScene,
|
|
42
|
+
options.deliveryOptions,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/serializerTransform.resolveViews.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IDecoratorSerializerTransformOptions,
|
|
3
|
+
ISerializerTransform,
|
|
4
|
+
} from 'vona-module-a-serialization';
|
|
5
|
+
|
|
6
|
+
import { BeanBase } from 'vona';
|
|
7
|
+
import { SerializerTransform } from 'vona-module-a-serialization';
|
|
8
|
+
|
|
9
|
+
import type { IFileDeliveryOptions, IFileView } from '../types/file.ts';
|
|
10
|
+
import type { IFileSceneRecord } from '../types/fileScene.ts';
|
|
11
|
+
|
|
12
|
+
export type TypeSerializerTransformResolveViewsValue = unknown;
|
|
13
|
+
|
|
14
|
+
export type TypeSerializerTransformResolveViewsData = object;
|
|
15
|
+
|
|
16
|
+
export type TypeSerializerTransformResolveViewsResult = IFileView[] | undefined;
|
|
17
|
+
|
|
18
|
+
export interface ISerializerTransformOptionsResolveViews extends IDecoratorSerializerTransformOptions {
|
|
19
|
+
fieldName: string;
|
|
20
|
+
fileScene?: keyof IFileSceneRecord;
|
|
21
|
+
deliveryOptions?: IFileDeliveryOptions;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@SerializerTransform<ISerializerTransformOptionsResolveViews>()
|
|
25
|
+
export class SerializerTransformResolveViews
|
|
26
|
+
extends BeanBase
|
|
27
|
+
implements
|
|
28
|
+
ISerializerTransform<
|
|
29
|
+
TypeSerializerTransformResolveViewsValue,
|
|
30
|
+
TypeSerializerTransformResolveViewsData,
|
|
31
|
+
TypeSerializerTransformResolveViewsResult
|
|
32
|
+
>
|
|
33
|
+
{
|
|
34
|
+
async transform(
|
|
35
|
+
_value: TypeSerializerTransformResolveViewsValue,
|
|
36
|
+
data: TypeSerializerTransformResolveViewsData,
|
|
37
|
+
options: ISerializerTransformOptionsResolveViews,
|
|
38
|
+
): Promise<TypeSerializerTransformResolveViewsResult> {
|
|
39
|
+
return await this.bean.file.resolveViews(
|
|
40
|
+
data[options.fieldName],
|
|
41
|
+
options.fileScene,
|
|
42
|
+
options.deliveryOptions,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,20 +1,24 @@
|
|
|
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';
|
|
4
5
|
import { BeanBase } from 'vona';
|
|
5
6
|
import { Core } from 'vona-module-a-core';
|
|
6
7
|
import { Api, v } from 'vona-module-a-openapiutils';
|
|
7
8
|
import { Passport } from 'vona-module-a-user';
|
|
8
9
|
import { Arg, Controller, Web } from 'vona-module-a-web';
|
|
10
|
+
import z from 'zod';
|
|
9
11
|
|
|
12
|
+
import type { IFileSceneRecord } from '../types/fileScene.ts';
|
|
13
|
+
|
|
14
|
+
import { DtoFileDirectUploadFinalizeRequest } from '../dto/fileDirectUploadFinalizeRequest.ts';
|
|
15
|
+
import { DtoFileDirectUploadFinalizeResponse } from '../dto/fileDirectUploadFinalizeResponse.ts';
|
|
10
16
|
import { DtoFileDirectUploadRequest } from '../dto/fileDirectUploadRequest.ts';
|
|
11
17
|
import { DtoFileDirectUploadResponse } from '../dto/fileDirectUploadResponse.ts';
|
|
12
18
|
import { DtoFileDownloadRequest } from '../dto/fileDownloadRequest.ts';
|
|
13
19
|
import { DtoFileUploadPolicyRequest } from '../dto/fileUploadPolicyRequest.ts';
|
|
14
20
|
import { DtoFileUploadPolicyResponse } from '../dto/fileUploadPolicyResponse.ts';
|
|
15
21
|
import { DtoFileUploadResponse } from '../dto/fileUploadResponse.ts';
|
|
16
|
-
import { DtoFileUploadTokenRequest } from '../dto/fileUploadTokenRequest.ts';
|
|
17
|
-
import { DtoFileUploadTokenResponse } from '../dto/fileUploadTokenResponse.ts';
|
|
18
22
|
import { DtoFileUploadUrlRequest } from '../dto/fileUploadUrlRequest.ts';
|
|
19
23
|
|
|
20
24
|
export interface IControllerOptionsFile extends IDecoratorControllerOptions {}
|
|
@@ -27,49 +31,52 @@ export class ControllerFile extends BeanBase {
|
|
|
27
31
|
return await this.bean.fileUploadPolicy.resolveSceneUploadPolicy(data);
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
@Web.post('upload-token')
|
|
31
|
-
@Api.body(DtoFileUploadTokenResponse)
|
|
32
|
-
async createUploadToken(@Arg.body() data: DtoFileUploadTokenRequest) {
|
|
33
|
-
return await this.bean.fileUploadPolicy.createUploadToken(data);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
34
|
@Web.post('upload')
|
|
37
|
-
@Core.fileUpload(
|
|
35
|
+
@Core.fileUpload({
|
|
36
|
+
busboy: {
|
|
37
|
+
limits: {
|
|
38
|
+
fields: 1,
|
|
39
|
+
files: 1,
|
|
40
|
+
parts: 3,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
})
|
|
38
44
|
@Api.body(DtoFileUploadResponse)
|
|
39
45
|
@Api.contentType('application/json')
|
|
40
|
-
async upload(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
);
|
|
46
|
+
async upload(
|
|
47
|
+
@Arg.field('fileScene', v.required(), z.string()) fileScene: keyof IFileSceneRecord,
|
|
48
|
+
@Arg.file('file', v.required()) file: IUploadFile,
|
|
49
|
+
) {
|
|
50
|
+
const stat = await fse.stat(file.file);
|
|
51
|
+
const policy = await this.bean.fileUploadPolicy.resolveUploadPolicy({
|
|
52
|
+
fileScene,
|
|
53
|
+
size: Number(stat.size),
|
|
54
|
+
mimeType: file.info.mimeType,
|
|
55
|
+
});
|
|
45
56
|
await this.bean.fileUploadPolicy.validateUploadFile(
|
|
46
57
|
{
|
|
47
58
|
file: file.file,
|
|
48
59
|
filename: file.info.filename,
|
|
49
60
|
mimeType: file.info.mimeType,
|
|
50
61
|
},
|
|
51
|
-
|
|
62
|
+
policy,
|
|
52
63
|
);
|
|
53
64
|
const uploadedFile = await this.bean.file.upload(
|
|
54
|
-
|
|
65
|
+
policy.providerName,
|
|
55
66
|
{
|
|
56
67
|
file: file.file,
|
|
57
68
|
filename: file.info.filename,
|
|
58
|
-
contentType:
|
|
59
|
-
public:
|
|
69
|
+
contentType: policy.mimeType,
|
|
70
|
+
public: policy.public,
|
|
60
71
|
},
|
|
61
72
|
{
|
|
62
|
-
clientName:
|
|
63
|
-
meta:
|
|
64
|
-
public:
|
|
65
|
-
fileScene:
|
|
73
|
+
clientName: policy.clientName,
|
|
74
|
+
meta: policy.meta,
|
|
75
|
+
public: policy.public,
|
|
76
|
+
fileScene: policy.fileScene,
|
|
66
77
|
},
|
|
67
78
|
);
|
|
68
|
-
return
|
|
69
|
-
...uploadedFile,
|
|
70
|
-
url: await this.bean.file.getDownloadUrl(uploadedFile.id),
|
|
71
|
-
signed: !uploadedFile.public,
|
|
72
|
-
};
|
|
79
|
+
return await this.bean.file.createFileActionResponse(uploadedFile);
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
@Web.post('direct-upload')
|
|
@@ -84,9 +91,8 @@ export class ControllerFile extends BeanBase {
|
|
|
84
91
|
policy.providerName,
|
|
85
92
|
{
|
|
86
93
|
filename: data.filename,
|
|
87
|
-
contentType:
|
|
88
|
-
size:
|
|
89
|
-
objectKey: data.objectKey,
|
|
94
|
+
contentType: policy.mimeType,
|
|
95
|
+
size: policy.fileSize,
|
|
90
96
|
public: policy.public,
|
|
91
97
|
expiry: data.expiry,
|
|
92
98
|
},
|
|
@@ -99,22 +105,25 @@ export class ControllerFile extends BeanBase {
|
|
|
99
105
|
);
|
|
100
106
|
}
|
|
101
107
|
|
|
108
|
+
@Web.post('direct-upload/finalize')
|
|
109
|
+
@Api.body(DtoFileDirectUploadFinalizeResponse)
|
|
110
|
+
async finalizeDirectUpload(@Arg.body() data: DtoFileDirectUploadFinalizeRequest) {
|
|
111
|
+
const file = await this.bean.file.finalizeDirectUpload(data.fileId);
|
|
112
|
+
return await this.bean.file.createFileActionResponse(file);
|
|
113
|
+
}
|
|
114
|
+
|
|
102
115
|
@Web.post('upload-url')
|
|
103
116
|
@Api.body(DtoFileUploadResponse)
|
|
104
117
|
async uploadUrl(@Arg.body() data: DtoFileUploadUrlRequest) {
|
|
105
|
-
const policy = await this.bean.fileUploadPolicy.
|
|
118
|
+
const policy = await this.bean.fileUploadPolicy.resolveUploadUrlPolicy({
|
|
106
119
|
fileScene: data.fileScene,
|
|
107
|
-
size: data.size,
|
|
108
|
-
mimeType: data.mimeType,
|
|
109
120
|
});
|
|
110
121
|
const uploadedFile = await this.bean.file.uploadUrl(
|
|
111
122
|
policy.providerName,
|
|
112
123
|
{
|
|
113
124
|
url: data.url,
|
|
125
|
+
policy,
|
|
114
126
|
filename: data.filename,
|
|
115
|
-
contentType: data.contentType,
|
|
116
|
-
size: data.size,
|
|
117
|
-
objectKey: data.objectKey,
|
|
118
127
|
public: policy.public,
|
|
119
128
|
},
|
|
120
129
|
{
|
|
@@ -124,39 +133,38 @@ export class ControllerFile extends BeanBase {
|
|
|
124
133
|
fileScene: policy.fileScene,
|
|
125
134
|
},
|
|
126
135
|
);
|
|
127
|
-
return
|
|
128
|
-
...uploadedFile,
|
|
129
|
-
url: await this.bean.file.getDownloadUrl(uploadedFile.id),
|
|
130
|
-
signed: !uploadedFile.public,
|
|
131
|
-
};
|
|
136
|
+
return await this.bean.file.createFileActionResponse(uploadedFile);
|
|
132
137
|
}
|
|
133
138
|
|
|
134
|
-
@Web.get('download
|
|
139
|
+
@Web.get('download')
|
|
135
140
|
@Passport.public()
|
|
136
|
-
async download(
|
|
137
|
-
|
|
138
|
-
@Arg.query(v.object(DtoFileDownloadRequest)) query: DtoFileDownloadRequest,
|
|
139
|
-
) {
|
|
141
|
+
async download(@Arg.query(v.object(DtoFileDownloadRequest)) query: DtoFileDownloadRequest) {
|
|
142
|
+
const { fileId } = query;
|
|
140
143
|
const file = await this.bean.file.get(fileId);
|
|
141
144
|
if (!file) return this.app.throw(404);
|
|
142
145
|
if (!file.public) {
|
|
143
146
|
const payload = await this.bean.fileUploadPolicy.verifyDownloadToken(
|
|
144
147
|
query.token,
|
|
145
|
-
this.scope.util.combineApiPath(
|
|
148
|
+
this.scope.util.combineApiPath('file/download', false, true),
|
|
146
149
|
);
|
|
147
150
|
if (String(payload.fileId) !== String(fileId)) {
|
|
148
151
|
return this.app.throw(401);
|
|
149
152
|
}
|
|
153
|
+
if (payload.audienceUserId !== undefined) {
|
|
154
|
+
const auth = await this.bean.passport.checkAuthToken(
|
|
155
|
+
this.bean.jwt.extractAuthTokenFromAllWays(),
|
|
156
|
+
undefined,
|
|
157
|
+
{ path: this.ctx.path },
|
|
158
|
+
);
|
|
159
|
+
if (!auth) return this.app.throw(401);
|
|
160
|
+
const user = auth.passport.user;
|
|
161
|
+
if (!user || user.anonymous) return this.app.throw(401);
|
|
162
|
+
if (String(user.id) !== String(payload.audienceUserId)) return this.app.throw(403);
|
|
163
|
+
}
|
|
150
164
|
}
|
|
151
|
-
const result = await this.bean.file.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
? undefined
|
|
155
|
-
: {
|
|
156
|
-
signed: false,
|
|
157
|
-
responseMode: 'buffer',
|
|
158
|
-
},
|
|
159
|
-
);
|
|
165
|
+
const result = await this.bean.file.downloadForDelivery(fileId, {
|
|
166
|
+
protected: !file.public,
|
|
167
|
+
});
|
|
160
168
|
if (result.kind === 'url') {
|
|
161
169
|
if (!result.url) {
|
|
162
170
|
throw new Error(`file download url missing: ${fileId}`);
|
package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadFinalizeRequest.ts
ADDED
|
@@ -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 IDtoOptionsFileDirectUploadFinalizeRequest extends IDecoratorDtoOptions {}
|
|
8
|
+
|
|
9
|
+
@Dto<IDtoOptionsFileDirectUploadFinalizeRequest>()
|
|
10
|
+
export class DtoFileDirectUploadFinalizeRequest {
|
|
11
|
+
@Api.field(v.tableIdentity())
|
|
12
|
+
fileId: TableIdentity;
|
|
13
|
+
}
|
package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadFinalizeResponse.ts
ADDED
|
@@ -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 { DtoFileUploadResponse } from './fileUploadResponse.ts';
|
|
6
|
+
|
|
7
|
+
export interface IDtoOptionsFileDirectUploadFinalizeResponse extends IDecoratorDtoOptions {}
|
|
8
|
+
|
|
9
|
+
@Dto<IDtoOptionsFileDirectUploadFinalizeResponse>()
|
|
10
|
+
export class DtoFileDirectUploadFinalizeResponse extends DtoFileUploadResponse {}
|
|
@@ -5,9 +5,6 @@ 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 { IFileProviderRecord } from '../types/fileProvider.ts';
|
|
9
|
-
import type { IFileSceneRecord } from '../types/fileScene.ts';
|
|
10
|
-
|
|
11
8
|
export interface IDtoOptionsFileDirectUploadResponse extends IDecoratorDtoOptions {}
|
|
12
9
|
|
|
13
10
|
@Dto<IDtoOptionsFileDirectUploadResponse>()
|
|
@@ -15,15 +12,6 @@ export class DtoFileDirectUploadResponse {
|
|
|
15
12
|
@Api.field(v.tableIdentity())
|
|
16
13
|
id: TableIdentity;
|
|
17
14
|
|
|
18
|
-
@Api.field(z.string())
|
|
19
|
-
provider: keyof IFileProviderRecord;
|
|
20
|
-
|
|
21
|
-
@Api.field()
|
|
22
|
-
clientName: string;
|
|
23
|
-
|
|
24
|
-
@Api.field()
|
|
25
|
-
resourceId: string;
|
|
26
|
-
|
|
27
15
|
@Api.field()
|
|
28
16
|
uploadUrl: string;
|
|
29
17
|
|
|
@@ -36,6 +24,6 @@ export class DtoFileDirectUploadResponse {
|
|
|
36
24
|
@Api.field(v.optional())
|
|
37
25
|
filename?: string;
|
|
38
26
|
|
|
39
|
-
@Api.field(v.optional()
|
|
40
|
-
|
|
27
|
+
@Api.field(v.optional())
|
|
28
|
+
public?: boolean;
|
|
41
29
|
}
|
|
@@ -7,6 +7,9 @@ export interface IDtoOptionsFileDownloadRequest extends IDecoratorDtoOptions {}
|
|
|
7
7
|
|
|
8
8
|
@Dto<IDtoOptionsFileDownloadRequest>()
|
|
9
9
|
export class DtoFileDownloadRequest {
|
|
10
|
+
@Api.field(v.tableIdentity())
|
|
11
|
+
fileId: number | string;
|
|
12
|
+
|
|
10
13
|
@Api.field(v.optional())
|
|
11
14
|
token?: string;
|
|
12
15
|
}
|
|
@@ -3,10 +3,6 @@ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
|
|
|
3
3
|
|
|
4
4
|
import { Api, v } from 'vona-module-a-openapiutils';
|
|
5
5
|
import { Dto } from 'vona-module-a-web';
|
|
6
|
-
import z from 'zod';
|
|
7
|
-
|
|
8
|
-
import type { IFileProviderRecord } from '../types/fileProvider.ts';
|
|
9
|
-
import type { IFileSceneRecord } from '../types/fileScene.ts';
|
|
10
6
|
|
|
11
7
|
export interface IDtoOptionsFileUploadResponse extends IDecoratorDtoOptions {}
|
|
12
8
|
|
|
@@ -15,21 +11,6 @@ export class DtoFileUploadResponse {
|
|
|
15
11
|
@Api.field(v.tableIdentity())
|
|
16
12
|
id: TableIdentity;
|
|
17
13
|
|
|
18
|
-
@Api.field(z.string())
|
|
19
|
-
provider: keyof IFileProviderRecord;
|
|
20
|
-
|
|
21
|
-
@Api.field()
|
|
22
|
-
clientName: string;
|
|
23
|
-
|
|
24
|
-
@Api.field()
|
|
25
|
-
resourceId: string;
|
|
26
|
-
|
|
27
|
-
@Api.field(v.optional())
|
|
28
|
-
bucket?: string;
|
|
29
|
-
|
|
30
|
-
@Api.field(v.optional())
|
|
31
|
-
objectKey?: string;
|
|
32
|
-
|
|
33
14
|
@Api.field(v.optional())
|
|
34
15
|
filename?: string;
|
|
35
16
|
|
|
@@ -39,15 +20,9 @@ export class DtoFileUploadResponse {
|
|
|
39
20
|
@Api.field(v.optional())
|
|
40
21
|
size?: number;
|
|
41
22
|
|
|
42
|
-
@Api.field(v.optional())
|
|
43
|
-
etag?: string;
|
|
44
|
-
|
|
45
23
|
@Api.field(v.optional())
|
|
46
24
|
public?: boolean;
|
|
47
25
|
|
|
48
|
-
@Api.field(v.optional(), z.string())
|
|
49
|
-
fileScene?: keyof IFileSceneRecord;
|
|
50
|
-
|
|
51
26
|
@Api.field(v.optional())
|
|
52
27
|
uploadedAt?: Date;
|
|
53
28
|
|
|
@@ -5,22 +5,13 @@ 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
|
-
export interface
|
|
8
|
+
export interface IDtoOptionsFileView extends IDecoratorDtoOptions {}
|
|
9
9
|
|
|
10
|
-
@Dto<
|
|
11
|
-
export class
|
|
10
|
+
@Dto<IDtoOptionsFileView>()
|
|
11
|
+
export class DtoFileView {
|
|
12
12
|
@Api.field(v.tableIdentity())
|
|
13
13
|
id: TableIdentity;
|
|
14
14
|
|
|
15
|
-
@Api.field(z.string())
|
|
16
|
-
provider: string;
|
|
17
|
-
|
|
18
|
-
@Api.field(z.string())
|
|
19
|
-
clientName: string;
|
|
20
|
-
|
|
21
|
-
@Api.field(v.optional(), z.string())
|
|
22
|
-
fileScene?: string;
|
|
23
|
-
|
|
24
15
|
@Api.field(v.optional())
|
|
25
16
|
filename?: string;
|
|
26
17
|
|
|
@@ -36,9 +27,6 @@ export class DtoRecordDossierFileView {
|
|
|
36
27
|
@Api.field(v.optional())
|
|
37
28
|
uploadedAt?: Date;
|
|
38
29
|
|
|
39
|
-
@Api.field(v.optional(), z.record(z.string(), z.unknown()))
|
|
40
|
-
meta?: Record<string, unknown>;
|
|
41
|
-
|
|
42
30
|
@Api.field(z.string())
|
|
43
31
|
downloadUrl: string;
|
|
44
32
|
|
|
@@ -4,6 +4,7 @@ import { 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 { TypeFileStatus } from '../types/file.ts';
|
|
7
8
|
import type { IFileProviderRecord } from '../types/fileProvider.ts';
|
|
8
9
|
import type { IFileSceneRecord } from '../types/fileScene.ts';
|
|
9
10
|
|
|
@@ -52,4 +53,13 @@ export class EntityFile extends EntityBase {
|
|
|
52
53
|
|
|
53
54
|
@Api.field(v.optional(), z.string())
|
|
54
55
|
fileScene?: keyof IFileSceneRecord;
|
|
56
|
+
|
|
57
|
+
@Api.field(z.enum(['draft', 'ready', 'expired']))
|
|
58
|
+
status: TypeFileStatus;
|
|
59
|
+
|
|
60
|
+
@Api.field(v.optional(), z.date())
|
|
61
|
+
draftExpiresAt?: Date;
|
|
62
|
+
|
|
63
|
+
@Api.field(v.optional(), z.date())
|
|
64
|
+
finalizedAt?: Date;
|
|
55
65
|
}
|