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,8 +1,8 @@
1
1
  import type {
2
2
  EntityFile,
3
3
  IDecoratorFileProviderOptions,
4
- IFileDeliveryOptions,
5
4
  IFileDownloadResult,
5
+ IFileProviderDeliveryOptions,
6
6
  IFileProviderClientOptions,
7
7
  IFileProviderClientRecord,
8
8
  IFileProviderExecute,
@@ -82,7 +82,7 @@ export class FileProviderNative
82
82
  file: EntityFile,
83
83
  clientOptions: IFileProviderNativeClientOptions,
84
84
  _options: IFileProviderOptionsNative,
85
- deliveryOptions?: IFileDeliveryOptions,
85
+ deliveryOptions?: IFileProviderDeliveryOptions,
86
86
  ) {
87
87
  return await this.scope.service.fileNative.getDownloadUrl(
88
88
  file,
@@ -100,9 +100,9 @@ export class FileProviderNative
100
100
  file: EntityFile,
101
101
  clientOptions: IFileProviderNativeClientOptions,
102
102
  options: IFileProviderOptionsNative,
103
- deliveryOptions?: IFileDeliveryOptions,
103
+ deliveryOptions?: IFileProviderDeliveryOptions,
104
104
  ): Promise<IFileDownloadResult> {
105
- if ((deliveryOptions?.responseMode ?? 'auto') !== 'url' && !deliveryOptions?.signed) {
105
+ if ((deliveryOptions?.responseMode ?? 'auto') !== 'url') {
106
106
  const buffer = file.storagePath ? await fse.readFile(file.storagePath) : undefined;
107
107
  if (buffer) {
108
108
  return {
@@ -119,7 +119,7 @@ export class FileProviderNative
119
119
  url: await this.getDownloadUrl(file, clientOptions, options, deliveryOptions),
120
120
  filename: file.filename,
121
121
  contentType: file.contentType,
122
- signed: !!deliveryOptions?.signed,
122
+ signed: !!deliveryOptions?.protected,
123
123
  };
124
124
  }
125
125
  }
@@ -1,6 +1,6 @@
1
1
  import type {
2
2
  EntityFile,
3
- IFileDeliveryOptions,
3
+ IFileProviderDeliveryOptions,
4
4
  IFileProviderResource,
5
5
  IFileUploadInput,
6
6
  } from 'vona-module-a-file';
@@ -54,18 +54,17 @@ export class ServiceFileNative extends BeanBase {
54
54
  async getDownloadUrl(
55
55
  file: IFileNativeStoredFile,
56
56
  options: IFileProviderNativeClientOptions,
57
- _deliveryOptions?: IFileDeliveryOptions,
57
+ _deliveryOptions?: IFileProviderDeliveryOptions,
58
58
  ) {
59
59
  if (!file.storagePath) {
60
60
  throw new Error(`File storage path missing: ${file.resourceId}`);
61
61
  }
62
62
  if (!(file.public ?? options.public)) {
63
- const routePath = this.scope.util.combineApiPath(
64
- `file/download/${file.id ?? file.resourceId}`,
65
- false,
66
- true,
67
- );
68
- return this.app.util.getAbsoluteUrlByApiPath(routePath);
63
+ const routePath = this.scope.util.combineApiPath('file/download', false, true);
64
+ const routeUrl = this.app.util.getAbsoluteUrlByApiPath(routePath);
65
+ const url = new URL(routeUrl);
66
+ url.searchParams.set('fileId', String(file.id ?? file.resourceId));
67
+ return url.toString();
69
68
  }
70
69
  return await this._buildUrl(file.storagePath, options.deliveryBaseUrl);
71
70
  }
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "vona-suite-a-file",
3
- "version": "5.0.4",
3
+ "version": "5.0.5",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "files": [],
7
7
  "type": "module",
8
8
  "dependencies": {
9
- "vona-module-a-file": "^5.0.4",
10
- "vona-module-file-cloudflare": "^5.0.3",
11
- "vona-module-file-native": "^5.0.3"
9
+ "vona-module-a-file": "^5.0.5",
10
+ "vona-module-file-cloudflare": "^5.0.4",
11
+ "vona-module-file-native": "^5.0.4"
12
12
  },
13
13
  "title": "a-file"
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-image",
3
- "version": "5.0.5",
3
+ "version": "5.0.6",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Vona Module"
@@ -220,7 +220,6 @@ export * from '../bean/meta.index.ts';
220
220
  export * from '../bean/meta.redlock.ts';
221
221
  export * from '../bean/meta.version.ts';
222
222
  import type { IMetaOptionsIndex } from 'vona-module-a-index';
223
-
224
223
  import 'vona-module-a-meta';
225
224
  declare module 'vona-module-a-meta' {
226
225
 
@@ -268,8 +267,6 @@ declare module 'vona-module-a-image' {
268
267
  }
269
268
  }
270
269
  /** meta: end */
271
- /** meta index: begin */
272
- /** meta index: end */
273
270
  /** meta redlock: begin */
274
271
  import type { MetaRedlock } from '../bean/meta.redlock.ts';
275
272
  /** meta redlock: end */
@@ -346,9 +343,9 @@ export * from '../dto/imageDirectUploadFinalizeResponse.ts';
346
343
  export * from '../dto/imageDirectUploadRequest.ts';
347
344
  export * from '../dto/imageDirectUploadResponse.ts';
348
345
  export * from '../dto/imageTransformOptions.tsx';
346
+ export * from '../dto/imageUploadPolicyRequest.ts';
347
+ export * from '../dto/imageUploadPolicyResponse.ts';
349
348
  export * from '../dto/imageUploadResponse.ts';
350
- export * from '../dto/imageUploadTokenRequest.ts';
351
- export * from '../dto/imageUploadTokenResponse.ts';
352
349
  export * from '../dto/imageUploadUrlRequest.ts';
353
350
  export * from '../dto/imageView.ts';
354
351
  import type { IDtoOptionsImageDeliveryRequest } from '../dto/imageDeliveryRequest.ts';
@@ -357,9 +354,9 @@ import type { IDtoOptionsImageDirectUploadFinalizeResponse } from '../dto/imageD
357
354
  import type { IDtoOptionsImageDirectUploadRequest } from '../dto/imageDirectUploadRequest.ts';
358
355
  import type { IDtoOptionsImageDirectUploadResponse } from '../dto/imageDirectUploadResponse.ts';
359
356
  import type { IDtoOptionsImageTransformOptions } from '../dto/imageTransformOptions.tsx';
357
+ import type { IDtoOptionsImageUploadPolicyRequest } from '../dto/imageUploadPolicyRequest.ts';
358
+ import type { IDtoOptionsImageUploadPolicyResponse } from '../dto/imageUploadPolicyResponse.ts';
360
359
  import type { IDtoOptionsImageUploadResponse } from '../dto/imageUploadResponse.ts';
361
- import type { IDtoOptionsImageUploadTokenRequest } from '../dto/imageUploadTokenRequest.ts';
362
- import type { IDtoOptionsImageUploadTokenResponse } from '../dto/imageUploadTokenResponse.ts';
363
360
  import type { IDtoOptionsImageUploadUrlRequest } from '../dto/imageUploadUrlRequest.ts';
364
361
  import type { IDtoOptionsImageView } from '../dto/imageView.ts';
365
362
  import 'vona-module-a-web';
@@ -372,9 +369,9 @@ declare module 'vona-module-a-web' {
372
369
  'a-image:imageDirectUploadRequest': IDtoOptionsImageDirectUploadRequest;
373
370
  'a-image:imageDirectUploadResponse': IDtoOptionsImageDirectUploadResponse;
374
371
  'a-image:imageTransformOptions': IDtoOptionsImageTransformOptions;
372
+ 'a-image:imageUploadPolicyRequest': IDtoOptionsImageUploadPolicyRequest;
373
+ 'a-image:imageUploadPolicyResponse': IDtoOptionsImageUploadPolicyResponse;
375
374
  'a-image:imageUploadResponse': IDtoOptionsImageUploadResponse;
376
- 'a-image:imageUploadTokenRequest': IDtoOptionsImageUploadTokenRequest;
377
- 'a-image:imageUploadTokenResponse': IDtoOptionsImageUploadTokenResponse;
378
375
  'a-image:imageUploadUrlRequest': IDtoOptionsImageUploadUrlRequest;
379
376
  'a-image:imageView': IDtoOptionsImageView;
380
377
  }
@@ -392,9 +389,9 @@ import type { DtoImageDirectUploadFinalizeResponse } from '../dto/imageDirectUpl
392
389
  import type { DtoImageDirectUploadRequest } from '../dto/imageDirectUploadRequest.ts';
393
390
  import type { DtoImageDirectUploadResponse } from '../dto/imageDirectUploadResponse.ts';
394
391
  import type { DtoImageTransformOptions } from '../dto/imageTransformOptions.tsx';
392
+ import type { DtoImageUploadPolicyRequest } from '../dto/imageUploadPolicyRequest.ts';
393
+ import type { DtoImageUploadPolicyResponse } from '../dto/imageUploadPolicyResponse.ts';
395
394
  import type { DtoImageUploadResponse } from '../dto/imageUploadResponse.ts';
396
- import type { DtoImageUploadTokenRequest } from '../dto/imageUploadTokenRequest.ts';
397
- import type { DtoImageUploadTokenResponse } from '../dto/imageUploadTokenResponse.ts';
398
395
  import type { DtoImageUploadUrlRequest } from '../dto/imageUploadUrlRequest.ts';
399
396
  import type { DtoImageView } from '../dto/imageView.ts';
400
397
  declare module 'vona-module-a-image' {
@@ -423,16 +420,16 @@ declare module 'vona-module-a-image' {
423
420
  fields?: TypeEntityOptionsFields<DtoImageTransformOptions, IDtoOptionsImageTransformOptions[TypeSymbolKeyFieldsMore]>;
424
421
  }
425
422
 
426
- export interface IDtoOptionsImageUploadResponse {
427
- fields?: TypeEntityOptionsFields<DtoImageUploadResponse, IDtoOptionsImageUploadResponse[TypeSymbolKeyFieldsMore]>;
423
+ export interface IDtoOptionsImageUploadPolicyRequest {
424
+ fields?: TypeEntityOptionsFields<DtoImageUploadPolicyRequest, IDtoOptionsImageUploadPolicyRequest[TypeSymbolKeyFieldsMore]>;
428
425
  }
429
426
 
430
- export interface IDtoOptionsImageUploadTokenRequest {
431
- fields?: TypeEntityOptionsFields<DtoImageUploadTokenRequest, IDtoOptionsImageUploadTokenRequest[TypeSymbolKeyFieldsMore]>;
427
+ export interface IDtoOptionsImageUploadPolicyResponse {
428
+ fields?: TypeEntityOptionsFields<DtoImageUploadPolicyResponse, IDtoOptionsImageUploadPolicyResponse[TypeSymbolKeyFieldsMore]>;
432
429
  }
433
430
 
434
- export interface IDtoOptionsImageUploadTokenResponse {
435
- fields?: TypeEntityOptionsFields<DtoImageUploadTokenResponse, IDtoOptionsImageUploadTokenResponse[TypeSymbolKeyFieldsMore]>;
431
+ export interface IDtoOptionsImageUploadResponse {
432
+ fields?: TypeEntityOptionsFields<DtoImageUploadResponse, IDtoOptionsImageUploadResponse[TypeSymbolKeyFieldsMore]>;
436
433
  }
437
434
 
438
435
  export interface IDtoOptionsImageUploadUrlRequest {
@@ -481,14 +478,14 @@ declare module 'vona-module-a-image' {
481
478
  }
482
479
  declare module 'vona-module-a-web' {
483
480
  export interface IApiPathPostRecord{
484
- '/image/upload-token': undefined;
481
+ '/image/upload-policy': undefined;
485
482
  '/image/upload': undefined;
486
483
  '/image/direct-upload': undefined;
487
484
  '/image/direct-upload/finalize': undefined;
488
485
  '/image/upload-url': undefined;
489
486
  }
490
487
  export interface IApiPathGetRecord{
491
- '/image/delivery/:imageId': undefined;
488
+ '/image/delivery': undefined;
492
489
  }
493
490
 
494
491
  }
@@ -5,10 +5,12 @@ import { Bean } from 'vona-module-a-bean';
5
5
 
6
6
  import type { EntityImage } from '../entity/image.ts';
7
7
  import type {
8
+ IImageActionResponse,
8
9
  IImageDeliveryOptions,
9
10
  IImageDirectUploadInput,
10
- IImageDirectUploadResult,
11
+ IImageDirectUploadResponse,
11
12
  IImageFinalizeDirectUploadResult,
13
+ IImageProviderDeliveryOptions,
12
14
  IImageProviderDirectUploadResource,
13
15
  IImageProviderResource,
14
16
  IImageResource,
@@ -35,11 +37,15 @@ interface IImageProviderContext<N extends keyof IImageProviderRecord = keyof IIm
35
37
  onionOptions: TypeImageProviderOptionsByName<N>;
36
38
  }
37
39
 
40
+ interface IImageDeliveryOptionsResolved extends IImageProviderDeliveryOptions {
41
+ audience: boolean;
42
+ }
43
+
38
44
  interface IImageDeliveryContext {
39
45
  image: EntityImage | IImageResource;
40
46
  providerImage: EntityImage;
41
47
  requestNormalized: IImageVariantRequest;
42
- deliveryOptionsResolved: IImageDeliveryOptions;
48
+ deliveryOptionsResolved: IImageDeliveryOptionsResolved;
43
49
  providerContext: IImageProviderContext;
44
50
  }
45
51
 
@@ -105,10 +111,11 @@ export class BeanImage extends BeanBase {
105
111
  providerName: N,
106
112
  input: IImageDirectUploadInput,
107
113
  options?: IImageUploadOptions<TypeImageProviderClientOptionsByName<N>>,
108
- ): Promise<IImageDirectUploadResult> {
114
+ ): Promise<IImageDirectUploadResponse> {
109
115
  const providerContext = await this._getProviderContextByInput(providerName, options);
110
116
  if (!providerContext.beanImageProvider.createDirectUpload) {
111
- throw new Error(
117
+ return this.app.throw(
118
+ 403,
112
119
  `Image provider does not support createDirectUpload: ${String(providerName)}`,
113
120
  );
114
121
  }
@@ -132,10 +139,11 @@ export class BeanImage extends BeanBase {
132
139
  },
133
140
  );
134
141
  return {
135
- ...this._combineImageResource(image, imageProviderResource),
142
+ id: image.id,
136
143
  uploadUrl: imageProviderResource.uploadUrl,
137
- draft: imageProviderResource.draft ?? true,
138
- };
144
+ filename: imageProviderResource.filename,
145
+ public: imageProviderResource.public ?? image.public,
146
+ } satisfies IImageDirectUploadResponse;
139
147
  }
140
148
 
141
149
  async finalizeDirectUpload(imageId: TableIdentity): Promise<IImageFinalizeDirectUploadResult> {
@@ -154,7 +162,8 @@ export class BeanImage extends BeanBase {
154
162
  }
155
163
  const providerContext = await this._getProviderContext(image);
156
164
  if (!providerContext.beanImageProvider.finalizeDirectUpload) {
157
- throw new Error(
165
+ return this.app.throw(
166
+ 403,
158
167
  `Image provider does not support finalizeDirectUpload: ${String(image.providerName)}`,
159
168
  );
160
169
  }
@@ -240,28 +249,23 @@ export class BeanImage extends BeanBase {
240
249
  signed: true,
241
250
  };
242
251
  }
243
- if (context.providerContext.beanImageProvider.download) {
244
- return await context.providerContext.beanImageProvider.download(
245
- context.providerImage,
246
- context.requestNormalized,
247
- context.providerContext.clientOptions,
248
- context.providerContext.onionOptions,
249
- context.deliveryOptionsResolved,
250
- );
251
- }
252
- return {
253
- kind: 'url' as const,
254
- url: await context.providerContext.beanImageProvider.getVariantUrl(
255
- context.providerImage,
256
- context.requestNormalized,
257
- context.providerContext.clientOptions,
258
- context.providerContext.onionOptions,
259
- context.deliveryOptionsResolved,
260
- ),
261
- filename: context.image.filename,
262
- contentType: context.image.contentType,
263
- signed: !!context.deliveryOptionsResolved.signed,
264
- };
252
+ return await this._downloadFromProvider(context);
253
+ }
254
+
255
+ async downloadForDelivery(
256
+ imageId: TableIdentity,
257
+ request?: TypeImageVariantInput,
258
+ options?: { protected?: boolean },
259
+ ) {
260
+ const context = await this._getDeliveryContext(imageId, request, { responseMode: 'buffer' });
261
+ return await this._downloadFromProvider({
262
+ ...context,
263
+ deliveryOptionsResolved: {
264
+ protected: options?.protected ?? !context.image.public,
265
+ audience: false,
266
+ responseMode: 'buffer',
267
+ },
268
+ });
265
269
  }
266
270
 
267
271
  async resolveView(
@@ -369,13 +373,19 @@ export class BeanImage extends BeanBase {
369
373
  image: IImageResource,
370
374
  request?: TypeImageVariantInput,
371
375
  deliveryOptions?: IImageDeliveryOptions,
372
- ) {
376
+ ): Promise<IImageActionResponse> {
373
377
  const context = await this._createDeliveryContext(image, request, deliveryOptions);
374
378
  return {
375
- ...image,
379
+ id: image.id,
380
+ filename: image.filename,
381
+ contentType: image.contentType,
382
+ size: image.size,
383
+ width: image.width,
384
+ height: image.height,
385
+ public: image.public,
376
386
  url: await this._getVariantUrlByContext(context),
377
- signed: !!context.deliveryOptionsResolved.signed,
378
- };
387
+ signed: context.deliveryOptionsResolved.protected,
388
+ } satisfies IImageActionResponse;
379
389
  }
380
390
 
381
391
  private _resolveDirectUploadDraftExpiresAt(expiry?: IImageDirectUploadInput['expiry']) {
@@ -427,6 +437,31 @@ export class BeanImage extends BeanBase {
427
437
  };
428
438
  }
429
439
 
440
+ private async _downloadFromProvider(context: IImageDeliveryContext) {
441
+ if (context.providerContext.beanImageProvider.download) {
442
+ return await context.providerContext.beanImageProvider.download(
443
+ context.providerImage,
444
+ context.requestNormalized,
445
+ context.providerContext.clientOptions,
446
+ context.providerContext.onionOptions,
447
+ context.deliveryOptionsResolved,
448
+ );
449
+ }
450
+ return {
451
+ kind: 'url' as const,
452
+ url: await context.providerContext.beanImageProvider.getVariantUrl(
453
+ context.providerImage,
454
+ context.requestNormalized,
455
+ context.providerContext.clientOptions,
456
+ context.providerContext.onionOptions,
457
+ context.deliveryOptionsResolved,
458
+ ),
459
+ filename: context.image.filename,
460
+ contentType: context.image.contentType,
461
+ signed: context.deliveryOptionsResolved.protected,
462
+ };
463
+ }
464
+
430
465
  private async _getVariantUrlByContext(context: IImageDeliveryContext) {
431
466
  if (this._shouldUseProxySignedDelivery(context)) {
432
467
  return await this._createSignedDeliveryUrl(context);
@@ -447,22 +482,17 @@ export class BeanImage extends BeanBase {
447
482
  filename: image.filename,
448
483
  width: image.width,
449
484
  height: image.height,
450
- provider: image.provider,
451
- clientName: image.clientName,
452
- imageScene: image.imageScene,
453
- status: image.status,
454
- draftExpiresAt: image.draftExpiresAt,
455
- finalizedAt: image.finalizedAt,
456
- uploadedAt: image.uploadedAt,
457
- variants: image.variants,
458
- signed: !!context.deliveryOptionsResolved.signed,
485
+ public: image.public,
486
+ signed: context.deliveryOptionsResolved.protected,
459
487
  } satisfies IImageView;
460
488
  }
461
489
 
462
490
  private _shouldUseProxySignedDelivery(context: IImageDeliveryContext) {
491
+ const { deliveryOptionsResolved, providerContext } = context;
463
492
  return (
464
- context.deliveryOptionsResolved.signed &&
465
- (context.providerContext.clientOptions.signedDeliveryKind ?? 'proxy') === 'proxy'
493
+ deliveryOptionsResolved.protected &&
494
+ (deliveryOptionsResolved.audience ||
495
+ (providerContext.clientOptions.signedDeliveryKind ?? 'proxy') === 'proxy')
466
496
  );
467
497
  }
468
498
 
@@ -486,50 +516,45 @@ export class BeanImage extends BeanBase {
486
516
  }
487
517
 
488
518
  private _mergeDeliveryOptions(
489
- image: Pick<IImageResource, 'requireSignedURLs'>,
519
+ image: Pick<IImageResource, 'public'>,
490
520
  request: IImageVariantRequest,
491
521
  deliveryOptions?: IImageDeliveryOptions,
492
- ): IImageDeliveryOptions {
493
- const signed = deliveryOptions?.signed ?? request.signed ?? image.requireSignedURLs ?? false;
494
- const expiresIn = deliveryOptions?.expiresIn ?? request.expiresIn;
495
- const expiresAt = deliveryOptions?.expiresAt ?? request.expiresAt;
496
- const responseMode = deliveryOptions?.responseMode ?? request.responseMode;
522
+ ): IImageDeliveryOptionsResolved {
523
+ const audience = deliveryOptions?.audience ?? request.audience ?? false;
497
524
  return {
498
- signed,
499
- expiresIn,
500
- expiresAt,
501
- responseMode,
525
+ protected: audience || !image.public,
526
+ expiresIn:
527
+ deliveryOptions?.expiresIn ??
528
+ request.expiresIn ??
529
+ (audience ? this.scope.config.image.delivery.audienceExpiresIn : undefined),
530
+ audience,
531
+ responseMode: deliveryOptions?.responseMode ?? request.responseMode,
502
532
  };
503
533
  }
504
534
 
505
535
  private async _createSignedDeliveryUrl(context: IImageDeliveryContext) {
506
- const targetUrl = await context.providerContext.beanImageProvider.getVariantUrl(
507
- context.providerImage,
508
- context.requestNormalized,
509
- context.providerContext.clientOptions,
510
- context.providerContext.onionOptions,
511
- {
512
- signed: false,
513
- responseMode: 'url',
514
- },
515
- );
516
- const routePath = this.scope.util.combineApiPath(
517
- `image/delivery/${context.image.id}`,
518
- false,
519
- true,
520
- );
536
+ const routePath = this.scope.util.combineApiPath('image/delivery', false, true);
537
+ const audienceUserId = this._resolveAudienceUserId(context.deliveryOptionsResolved);
521
538
  const tokenPayload = await this.bean.imageUploadPolicy.createDeliveryToken({
522
539
  imageId: context.image.id,
523
540
  request: context.requestNormalized,
524
- targetUrl,
525
541
  expiresIn: context.deliveryOptionsResolved.expiresIn,
542
+ audienceUserId,
526
543
  });
527
544
  const routeUrl = this.app.util.getAbsoluteUrlByApiPath(routePath);
528
545
  const url = new URL(routeUrl);
546
+ url.searchParams.set('imageId', String(context.image.id));
529
547
  url.searchParams.set('token', tokenPayload.token);
530
548
  return url.toString();
531
549
  }
532
550
 
551
+ private _resolveAudienceUserId(deliveryOptions: IImageDeliveryOptionsResolved) {
552
+ if (!deliveryOptions.audience) return;
553
+ const user = this.bean.passport.currentUser;
554
+ if (!user || user.anonymous) return this.app.throw(401);
555
+ return user.id;
556
+ }
557
+
533
558
  private _getProviderImage(image: EntityImage | IImageResource): EntityImage {
534
559
  if ('providerName' in image) return image;
535
560
  return {
@@ -542,7 +567,7 @@ export class BeanImage extends BeanBase {
542
567
  size: image.size,
543
568
  width: image.width,
544
569
  height: image.height,
545
- requireSignedURLs: image.requireSignedURLs,
570
+ public: image.public,
546
571
  variants: image.variants,
547
572
  meta: image.meta,
548
573
  storagePath: image.storagePath,
@@ -572,7 +597,7 @@ export class BeanImage extends BeanBase {
572
597
  size: imageProviderResource.size,
573
598
  width: imageProviderResource.width,
574
599
  height: imageProviderResource.height,
575
- requireSignedURLs: imageProviderResource.requireSignedURLs,
600
+ public: imageProviderResource.public,
576
601
  variants: imageProviderResource.variants,
577
602
  meta: imageProviderResource.meta,
578
603
  storagePath: imageProviderResource.storagePath,
@@ -615,7 +640,7 @@ export class BeanImage extends BeanBase {
615
640
  size: imageProviderResource?.size ?? image.size,
616
641
  width: imageProviderResource?.width ?? image.width,
617
642
  height: imageProviderResource?.height ?? image.height,
618
- requireSignedURLs: imageProviderResource?.requireSignedURLs ?? image.requireSignedURLs,
643
+ public: imageProviderResource?.public ?? image.public,
619
644
  variants: imageProviderResource?.variants ?? image.variants,
620
645
  meta: imageProviderResource?.meta ?? image.meta,
621
646
  storagePath: imageProviderResource?.storagePath ?? image.storagePath,
@@ -4,11 +4,10 @@ import { Bean } from 'vona-module-a-bean';
4
4
 
5
5
  import type {
6
6
  IImageDeliveryTokenPayload,
7
- IImageDirectUploadTokenPayload,
8
7
  IImageUploadContextResolved,
9
8
  IImageUploadPolicyResolved,
10
- IImageUploadTokenPayload,
11
9
  } from '../types/image.ts';
10
+ import type { IImageProviderExecute } from '../types/imageProvider.ts';
12
11
  import type {
13
12
  IDecoratorImageSceneOptions,
14
13
  IDecoratorImageSceneOptionsProvider,
@@ -19,55 +18,19 @@ import { getImageExtension, matchesImageMimeType } from '../lib/imageUploadValid
19
18
 
20
19
  @Bean()
21
20
  export class BeanImageUploadPolicy extends BeanBase {
22
- async createUploadToken(data: {
23
- imageScene: keyof IImageSceneRecord;
24
- size: number;
25
- mimeType: string;
26
- expiresIn?: number;
27
- }) {
28
- const payload = await this.resolveUploadPolicy(data);
29
- const path = this.scope.util.combineApiPath('image/upload', false, true);
30
- const token = await this.bean.jwt.createTempAuthToken(
31
- {
32
- kind: 'imageUpload',
33
- ...payload,
34
- } as IImageUploadTokenPayload,
35
- {
36
- path,
37
- expiresIn: data.expiresIn,
38
- },
39
- );
40
- return { token, expiresIn: data.expiresIn };
41
- }
42
-
43
- async createDirectUploadToken(data: { resourceId: string; path: string; expiresIn?: number }) {
44
- const path = data.path;
45
- const token = await this.bean.jwt.createTempAuthToken(
46
- {
47
- kind: 'imageDirectUpload',
48
- resourceId: data.resourceId,
49
- } as IImageDirectUploadTokenPayload,
50
- {
51
- path,
52
- expiresIn: data.expiresIn,
53
- },
54
- );
55
- return { token, expiresIn: data.expiresIn };
56
- }
57
-
58
21
  async createDeliveryToken(data: {
59
22
  imageId: number | string;
60
23
  request: IImageDeliveryTokenPayload['request'];
61
- targetUrl: string;
62
24
  expiresIn?: number;
25
+ audienceUserId?: IImageDeliveryTokenPayload['audienceUserId'];
63
26
  }) {
64
- const path = this.scope.util.combineApiPath(`image/delivery/${data.imageId}`, false, true);
27
+ const path = this.scope.util.combineApiPath('image/delivery', false, true);
65
28
  const token = await this.bean.jwt.createTempAuthToken(
66
29
  {
67
30
  kind: 'imageDelivery',
68
31
  imageId: data.imageId,
69
32
  request: data.request,
70
- targetUrl: data.targetUrl,
33
+ audienceUserId: data.audienceUserId,
71
34
  } as IImageDeliveryTokenPayload,
72
35
  {
73
36
  path,
@@ -77,26 +40,6 @@ export class BeanImageUploadPolicy extends BeanBase {
77
40
  return { token, expiresIn: data.expiresIn };
78
41
  }
79
42
 
80
- async verifyUploadToken(token: string | undefined, routePathRaw: string) {
81
- const payload = (await this.bean.jwt.get('access').verify(token, {
82
- path: routePathRaw,
83
- })) as IImageUploadTokenPayload | undefined;
84
- if (!payload || payload.kind !== 'imageUpload') {
85
- return this.app.throw(401);
86
- }
87
- return payload;
88
- }
89
-
90
- async verifyDirectUploadToken(token: string | undefined, routePathRaw: string) {
91
- const payload = (await this.bean.jwt.get('access').verify(token, {
92
- path: routePathRaw,
93
- })) as IImageDirectUploadTokenPayload | undefined;
94
- if (!payload || payload.kind !== 'imageDirectUpload') {
95
- return this.app.throw(401);
96
- }
97
- return payload;
98
- }
99
-
100
43
  async verifyDeliveryToken(token: string | undefined, routePathRaw: string) {
101
44
  const payload = (await this.bean.jwt.get('access').verify(token, {
102
45
  path: routePathRaw,
@@ -113,10 +56,15 @@ export class BeanImageUploadPolicy extends BeanBase {
113
56
  const imageScene = data.imageScene;
114
57
  const sceneOptions = this._getSceneOptions(imageScene);
115
58
  const { providerName, clientName } = await this._resolveProvider(sceneOptions);
59
+ const { clientOptions } = await this.bean.imageProvider.getClientOptions({
60
+ providerName,
61
+ clientName,
62
+ });
116
63
  return {
117
64
  imageScene,
118
65
  providerName,
119
66
  clientName,
67
+ public: this._resolvePublic(clientOptions, sceneOptions),
120
68
  meta: await this._resolveSceneMeta(sceneOptions),
121
69
  };
122
70
  }
@@ -126,13 +74,22 @@ export class BeanImageUploadPolicy extends BeanBase {
126
74
  const imageScene = data.imageScene;
127
75
  const sceneOptions = this._getSceneOptions(imageScene);
128
76
  const { providerName, clientName } = await this._resolveProvider(sceneOptions);
129
- const { entityImageProvider, disabled } = await this.bean.imageProvider.getClientOptions({
77
+ const {
78
+ entityImageProvider,
79
+ disabled,
80
+ beanFullName,
81
+ clientOptions: _clientOptions,
82
+ } = await this.bean.imageProvider.getClientOptions({
130
83
  providerName,
131
84
  clientName,
132
85
  });
133
86
  if (!entityImageProvider || disabled) {
134
87
  return this.app.throw(403, `Image provider unavailable: ${providerName}.${clientName}`);
135
88
  }
89
+ const imageProvider = this.app.bean._getBean<IImageProviderExecute>(beanFullName as never);
90
+ const directUpload =
91
+ typeof imageProvider.createDirectUpload === 'function' &&
92
+ typeof imageProvider.finalizeDirectUpload === 'function';
136
93
  const uploadOptions = {
137
94
  ...(imageConfig.upload ?? {}),
138
95
  ...(sceneOptions.upload ?? {}),
@@ -145,6 +102,8 @@ export class BeanImageUploadPolicy extends BeanBase {
145
102
  mimeTypes: mimeTypes.length > 0 ? mimeTypes : undefined,
146
103
  extensions: extensions.length > 0 ? extensions : undefined,
147
104
  multiple: uploadOptions.multiple,
105
+ public: this._resolvePublic(_clientOptions, sceneOptions),
106
+ directUpload,
148
107
  };
149
108
  }
150
109
 
@@ -201,16 +160,10 @@ export class BeanImageUploadPolicy extends BeanBase {
201
160
  ) {
202
161
  const stat = await fse.stat(file.file);
203
162
  const fileSize = Number(stat.size);
204
- if (fileSize !== payload.fileSize) {
205
- return this.app.throw(403, `image size mismatch: size=${fileSize}`);
206
- }
207
163
  if (payload.maxSize && fileSize > payload.maxSize) {
208
164
  return this.app.throw(403, `image too large: maxSize=${payload.maxSize}`);
209
165
  }
210
166
  const mimeType = file.mimeType.toLowerCase();
211
- if (mimeType !== payload.mimeType) {
212
- return this.app.throw(403, `image mimeType mismatch: mimeType=${mimeType}`);
213
- }
214
167
  if (payload.mimeTypes?.length && !matchesImageMimeType(mimeType, payload.mimeTypes)) {
215
168
  return this.app.throw(403, `unsupported image mimeType: ${mimeType}`);
216
169
  }
@@ -246,4 +199,11 @@ export class BeanImageUploadPolicy extends BeanBase {
246
199
  }
247
200
  return meta;
248
201
  }
202
+
203
+ private _resolvePublic(
204
+ clientOptions: { public?: boolean } | undefined,
205
+ sceneOptions: IDecoratorImageSceneOptions,
206
+ ) {
207
+ return sceneOptions.public ?? clientOptions?.public ?? this.scope.config.image.public ?? false;
208
+ }
249
209
  }