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.
Files changed (127) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +29 -0
  3. package/CLAUDE.local.md +4 -0
  4. package/cabloy-docs/.vitepress/config.mjs +3 -0
  5. package/cabloy-docs/backend/file-guide.md +148 -0
  6. package/cabloy-docs/backend/image-guide.md +40 -40
  7. package/cabloy-docs/backend/jwt-guide.md +21 -0
  8. package/cabloy-docs/frontend/file-guide.md +147 -0
  9. package/cabloy-docs/frontend/image-guide.md +18 -9
  10. package/cabloy-docs/frontend/model-state-guide.md +12 -0
  11. package/cabloy-docs/frontend/use-state-data-best-practices.md +54 -0
  12. package/cabloy-docs/fullstack/file-workflow.md +135 -0
  13. package/cabloy-docs/fullstack/image-workflow.md +12 -3
  14. package/package.json +1 -1
  15. package/vona/packages-vona/vona/package.json +1 -1
  16. package/vona/src/suite/a-training/modules/training-record/src/.metadata/index.ts +30 -4
  17. package/vona/src/suite/a-training/modules/training-record/src/bean/fileScene.dossierFile.ts +1 -1
  18. package/vona/src/suite/a-training/modules/training-record/src/bean/imageScene.sceneImage.ts +1 -0
  19. package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectResItem.tsx +8 -6
  20. package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +8 -6
  21. package/vona/src/suite/a-training/modules/training-record/src/index.ts +0 -1
  22. package/vona/src/suite/a-training/modules/training-record/src/lib/index.ts +0 -1
  23. package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +10 -12
  24. package/vona/src/suite/a-training/modules/training-student/src/bean/imageScene.studentImage.ts +1 -0
  25. package/vona/src/suite/a-training/modules/training-student/src/dto/detailRecordBase.tsx +9 -10
  26. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +2 -2
  27. package/vona/src/suite-vendor/a-file/modules/a-file/package.json +3 -2
  28. package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/index.ts +155 -35
  29. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.file.ts +283 -54
  30. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileUploadPolicy.ts +46 -63
  31. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.index.ts +12 -0
  32. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.redlock.ts +1 -1
  33. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.version.ts +3 -0
  34. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/schedule.fileDraftPrune.ts +29 -0
  35. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/serializerTransform.resolveView.ts +45 -0
  36. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/serializerTransform.resolveViews.ts +45 -0
  37. package/vona/src/suite-vendor/a-file/modules/a-file/src/config/config.ts +6 -0
  38. package/vona/src/suite-vendor/a-file/modules/a-file/src/controller/file.ts +64 -56
  39. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadFinalizeRequest.ts +13 -0
  40. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadFinalizeResponse.ts +10 -0
  41. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadRequest.ts +0 -3
  42. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadResponse.ts +2 -14
  43. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDownloadRequest.ts +3 -0
  44. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadPolicyResponse.ts +3 -0
  45. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadResponse.ts +0 -25
  46. 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
  47. package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/file.ts +10 -0
  48. package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileUploadUrl.ts +329 -0
  49. package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/index.ts +1 -0
  50. package/vona/src/suite-vendor/a-file/modules/a-file/src/types/file.ts +51 -10
  51. package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileProvider.ts +8 -3
  52. package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileScene.ts +1 -1
  53. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/package.json +1 -1
  54. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/bean/fileProvider.cloudflare.ts +12 -4
  55. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/service/fileCloudflare.ts +52 -18
  56. package/vona/src/suite-vendor/a-file/modules/file-native/package.json +1 -1
  57. package/vona/src/suite-vendor/a-file/modules/file-native/src/bean/fileProvider.native.ts +5 -5
  58. package/vona/src/suite-vendor/a-file/modules/file-native/src/service/fileNative.ts +7 -8
  59. package/vona/src/suite-vendor/a-file/package.json +4 -4
  60. package/vona/src/suite-vendor/a-image/modules/a-image/package.json +1 -1
  61. package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/index.ts +16 -19
  62. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.image.ts +99 -74
  63. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +28 -68
  64. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.version.ts +1 -1
  65. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/serializerTransform.resolveView.ts +3 -0
  66. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/serializerTransform.resolveViews.ts +3 -0
  67. package/vona/src/suite-vendor/a-image/modules/a-image/src/config/config.ts +4 -0
  68. package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +78 -34
  69. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDeliveryRequest.ts +3 -0
  70. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadRequest.ts +0 -3
  71. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadResponse.ts +1 -27
  72. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadPolicyResponse.ts +6 -0
  73. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadResponse.ts +2 -36
  74. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadUrlRequest.ts +0 -3
  75. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageView.ts +4 -27
  76. package/vona/src/suite-vendor/a-image/modules/a-image/src/entity/image.ts +1 -1
  77. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/image.ts +36 -29
  78. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageProvider.ts +4 -4
  79. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageScene.ts +1 -0
  80. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/package.json +1 -1
  81. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/bean/imageProvider.cloudflare.ts +8 -11
  82. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/lib/cloudflareImageUrl.ts +4 -16
  83. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts +17 -16
  84. package/vona/src/suite-vendor/a-image/modules/image-native/package.json +2 -6
  85. package/vona/src/suite-vendor/a-image/modules/image-native/src/.metadata/index.ts +0 -37
  86. package/vona/src/suite-vendor/a-image/modules/image-native/src/bean/imageProvider.native.ts +13 -36
  87. package/vona/src/suite-vendor/a-image/modules/image-native/src/service/imageNative.ts +100 -113
  88. package/vona/src/suite-vendor/a-image/package.json +1 -1
  89. package/vona/src/suite-vendor/a-vona/modules/a-jwt/package.json +1 -1
  90. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/config/config.ts +3 -1
  91. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/service/jwtClient.ts +9 -4
  92. package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +1 -1
  93. package/vona/src/suite-vendor/a-vona/modules/a-upload/src/bean/interceptor.upload.ts +113 -64
  94. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  95. package/zova/packages-zova/zova/package.json +2 -2
  96. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/schemas.ts +50 -12
  97. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +610 -114
  98. package/zova/src/suite/a-home/modules/home-passport/src/model/passport.ts +44 -1
  99. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +50 -12
  100. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +610 -114
  101. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/index.ts +16 -12
  102. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/file.ts +25 -25
  103. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/schemas.ts +18 -24
  104. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/types.ts +228 -291
  105. package/zova/src/suite/cabloy-basic/modules/basic-file/src/apiSchema/file.ts +5 -5
  106. package/zova/src/suite/cabloy-basic/modules/basic-file/src/bean/tableCell.file.tsx +23 -5
  107. package/zova/src/suite/cabloy-basic/modules/basic-file/src/component/formFieldFile/controller.tsx +72 -43
  108. package/zova/src/suite/cabloy-basic/modules/basic-file/src/lib/file.ts +22 -4
  109. package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/index.ts +16 -42
  110. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/image.ts +5 -25
  111. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/schemas.ts +10 -24
  112. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/types.ts +216 -333
  113. package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/image.ts +0 -5
  114. package/zova/src/suite/cabloy-basic/modules/basic-image/src/bean/tableCell.image.tsx +38 -12
  115. package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +79 -32
  116. package/zova/src/suite/cabloy-basic/modules/basic-image/src/lib/preview.ts +22 -4
  117. package/zova/src/suite/cabloy-basic/modules/basic-image/src/types/image.ts +3 -4
  118. package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
  119. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/utils.ts +29 -1
  120. package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +2 -2
  121. package/zova/src/suite-vendor/a-zova/package.json +2 -2
  122. package/vona/src/suite/a-training/modules/training-record/src/lib/resolveDossierFiles.ts +0 -34
  123. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadTokenRequest.ts +0 -24
  124. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadTokenResponse.ts +0 -16
  125. package/vona/src/suite-vendor/a-image/modules/image-native/src/controller/image.ts +0 -57
  126. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/imageNativeImage.ts +0 -31
  127. 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 = never;
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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -11,6 +11,12 @@ export function config(_app: VonaApplication) {
11
11
  upload: {
12
12
  maxSize: 20 * 1024 * 1024,
13
13
  },
14
+ directUpload: {
15
+ draftExpiresIn: 30 * 60 * 1000,
16
+ },
17
+ delivery: {
18
+ audienceExpiresIn: 8 * 60 * 60,
19
+ },
14
20
  },
15
21
  };
16
22
  }
@@ -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(@Arg.field('token') token: string, @Arg.file('file') file: IUploadFile) {
41
- const payload = await this.bean.fileUploadPolicy.verifyUploadToken(
42
- token,
43
- this.ctx.route.routePathRaw,
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
- payload,
62
+ policy,
52
63
  );
53
64
  const uploadedFile = await this.bean.file.upload(
54
- payload.providerName,
65
+ policy.providerName,
55
66
  {
56
67
  file: file.file,
57
68
  filename: file.info.filename,
58
- contentType: file.info.mimeType,
59
- public: payload.public,
69
+ contentType: policy.mimeType,
70
+ public: policy.public,
60
71
  },
61
72
  {
62
- clientName: payload.clientName,
63
- meta: payload.meta,
64
- public: payload.public,
65
- fileScene: payload.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: data.contentType,
88
- size: data.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.resolveUploadPolicy({
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/:fileId')
139
+ @Web.get('download')
135
140
  @Passport.public()
136
- async download(
137
- @Arg.param('fileId', v.tableIdentity()) fileId: number,
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(`file/download/${fileId}`, false, true),
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.download(
152
- fileId,
153
- file.public
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}`);
@@ -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
+ }
@@ -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 {}
@@ -25,9 +25,6 @@ export class DtoFileDirectUploadRequest {
25
25
  @Api.field(v.optional())
26
26
  contentType?: string;
27
27
 
28
- @Api.field(v.optional())
29
- objectKey?: string;
30
-
31
28
  @Api.field(v.optional())
32
29
  expiry?: string;
33
30
  }
@@ -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(), z.string())
40
- fileScene?: keyof IFileSceneRecord;
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
  }
@@ -27,4 +27,7 @@ export class DtoFileUploadPolicyResponse {
27
27
 
28
28
  @Api.field(v.optional(), z.boolean())
29
29
  public?: boolean;
30
+
31
+ @Api.field(z.boolean())
32
+ directUpload: boolean;
30
33
  }
@@ -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 IDtoOptionsRecordDossierFileView extends IDecoratorDtoOptions {}
8
+ export interface IDtoOptionsFileView extends IDecoratorDtoOptions {}
9
9
 
10
- @Dto<IDtoOptionsRecordDossierFileView>()
11
- export class DtoRecordDossierFileView {
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
  }