cabloy 5.1.101 → 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 (128) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +36 -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/scripts/upgrade.ts +26 -10
  16. package/vona/packages-vona/vona/package.json +1 -1
  17. package/vona/src/suite/a-training/modules/training-record/src/.metadata/index.ts +30 -4
  18. package/vona/src/suite/a-training/modules/training-record/src/bean/fileScene.dossierFile.ts +1 -1
  19. package/vona/src/suite/a-training/modules/training-record/src/bean/imageScene.sceneImage.ts +1 -0
  20. package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectResItem.tsx +8 -6
  21. package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +8 -6
  22. package/vona/src/suite/a-training/modules/training-record/src/index.ts +0 -1
  23. package/vona/src/suite/a-training/modules/training-record/src/lib/index.ts +0 -1
  24. package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +10 -12
  25. package/vona/src/suite/a-training/modules/training-student/src/bean/imageScene.studentImage.ts +1 -0
  26. package/vona/src/suite/a-training/modules/training-student/src/dto/detailRecordBase.tsx +9 -10
  27. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +2 -2
  28. package/vona/src/suite-vendor/a-file/modules/a-file/package.json +3 -2
  29. package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/index.ts +155 -35
  30. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.file.ts +283 -54
  31. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileUploadPolicy.ts +46 -63
  32. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.index.ts +12 -0
  33. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.redlock.ts +1 -1
  34. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.version.ts +3 -0
  35. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/schedule.fileDraftPrune.ts +29 -0
  36. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/serializerTransform.resolveView.ts +45 -0
  37. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/serializerTransform.resolveViews.ts +45 -0
  38. package/vona/src/suite-vendor/a-file/modules/a-file/src/config/config.ts +6 -0
  39. package/vona/src/suite-vendor/a-file/modules/a-file/src/controller/file.ts +64 -56
  40. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadFinalizeRequest.ts +13 -0
  41. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadFinalizeResponse.ts +10 -0
  42. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadRequest.ts +0 -3
  43. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadResponse.ts +2 -14
  44. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDownloadRequest.ts +3 -0
  45. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadPolicyResponse.ts +3 -0
  46. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadResponse.ts +0 -25
  47. 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
  48. package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/file.ts +10 -0
  49. package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileUploadUrl.ts +329 -0
  50. package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/index.ts +1 -0
  51. package/vona/src/suite-vendor/a-file/modules/a-file/src/types/file.ts +51 -10
  52. package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileProvider.ts +8 -3
  53. package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileScene.ts +1 -1
  54. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/package.json +1 -1
  55. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/bean/fileProvider.cloudflare.ts +12 -4
  56. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/service/fileCloudflare.ts +52 -18
  57. package/vona/src/suite-vendor/a-file/modules/file-native/package.json +1 -1
  58. package/vona/src/suite-vendor/a-file/modules/file-native/src/bean/fileProvider.native.ts +5 -5
  59. package/vona/src/suite-vendor/a-file/modules/file-native/src/service/fileNative.ts +7 -8
  60. package/vona/src/suite-vendor/a-file/package.json +4 -4
  61. package/vona/src/suite-vendor/a-image/modules/a-image/package.json +1 -1
  62. package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/index.ts +16 -19
  63. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.image.ts +99 -74
  64. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +28 -68
  65. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.version.ts +1 -1
  66. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/serializerTransform.resolveView.ts +3 -0
  67. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/serializerTransform.resolveViews.ts +3 -0
  68. package/vona/src/suite-vendor/a-image/modules/a-image/src/config/config.ts +4 -0
  69. package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +78 -34
  70. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDeliveryRequest.ts +3 -0
  71. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadRequest.ts +0 -3
  72. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadResponse.ts +1 -27
  73. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadPolicyResponse.ts +6 -0
  74. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadResponse.ts +2 -36
  75. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadUrlRequest.ts +0 -3
  76. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageView.ts +4 -27
  77. package/vona/src/suite-vendor/a-image/modules/a-image/src/entity/image.ts +1 -1
  78. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/image.ts +36 -29
  79. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageProvider.ts +4 -4
  80. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageScene.ts +1 -0
  81. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/package.json +1 -1
  82. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/bean/imageProvider.cloudflare.ts +8 -11
  83. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/lib/cloudflareImageUrl.ts +4 -16
  84. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts +17 -16
  85. package/vona/src/suite-vendor/a-image/modules/image-native/package.json +2 -6
  86. package/vona/src/suite-vendor/a-image/modules/image-native/src/.metadata/index.ts +0 -37
  87. package/vona/src/suite-vendor/a-image/modules/image-native/src/bean/imageProvider.native.ts +13 -36
  88. package/vona/src/suite-vendor/a-image/modules/image-native/src/service/imageNative.ts +100 -113
  89. package/vona/src/suite-vendor/a-image/package.json +1 -1
  90. package/vona/src/suite-vendor/a-vona/modules/a-jwt/package.json +1 -1
  91. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/config/config.ts +3 -1
  92. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/service/jwtClient.ts +9 -4
  93. package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +1 -1
  94. package/vona/src/suite-vendor/a-vona/modules/a-upload/src/bean/interceptor.upload.ts +113 -64
  95. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  96. package/zova/packages-zova/zova/package.json +2 -2
  97. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/schemas.ts +50 -12
  98. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +610 -114
  99. package/zova/src/suite/a-home/modules/home-passport/src/model/passport.ts +44 -1
  100. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +50 -12
  101. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +610 -114
  102. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/index.ts +16 -12
  103. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/file.ts +25 -25
  104. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/schemas.ts +18 -24
  105. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/types.ts +228 -291
  106. package/zova/src/suite/cabloy-basic/modules/basic-file/src/apiSchema/file.ts +5 -5
  107. package/zova/src/suite/cabloy-basic/modules/basic-file/src/bean/tableCell.file.tsx +23 -5
  108. package/zova/src/suite/cabloy-basic/modules/basic-file/src/component/formFieldFile/controller.tsx +72 -43
  109. package/zova/src/suite/cabloy-basic/modules/basic-file/src/lib/file.ts +22 -4
  110. package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/index.ts +16 -42
  111. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/image.ts +5 -25
  112. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/schemas.ts +10 -24
  113. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/types.ts +216 -333
  114. package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/image.ts +0 -5
  115. package/zova/src/suite/cabloy-basic/modules/basic-image/src/bean/tableCell.image.tsx +38 -12
  116. package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +79 -32
  117. package/zova/src/suite/cabloy-basic/modules/basic-image/src/lib/preview.ts +22 -4
  118. package/zova/src/suite/cabloy-basic/modules/basic-image/src/types/image.ts +3 -4
  119. package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
  120. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/utils.ts +29 -1
  121. package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +2 -2
  122. package/zova/src/suite-vendor/a-zova/package.json +2 -2
  123. package/vona/src/suite/a-training/modules/training-record/src/lib/resolveDossierFiles.ts +0 -34
  124. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadTokenRequest.ts +0 -24
  125. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadTokenResponse.ts +0 -16
  126. package/vona/src/suite-vendor/a-image/modules/image-native/src/controller/image.ts +0 -57
  127. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/imageNativeImage.ts +0 -31
  128. package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/imageNativeImage.ts +0 -13
@@ -5,9 +5,9 @@ import { ApiSchema } from 'zova-module-a-api';
5
5
 
6
6
  import {
7
7
  ApiApiFilegetUploadPolicyPath,
8
- ApiApiFilecreateUploadTokenPath,
9
8
  ApiApiFileuploadPath,
10
9
  ApiApiFilecreateDirectUploadPath,
10
+ ApiApiFilefinalizeDirectUploadPath,
11
11
  ApiApiFileuploadUrlPath,
12
12
  ApiApiFiledownloadPath,
13
13
  } from '../api/file.js';
@@ -18,10 +18,6 @@ export class ApiSchemaFile extends BeanBase {
18
18
  return this.$sdk.createApiSchemas(ApiApiFilegetUploadPolicyPath, 'post', options);
19
19
  }
20
20
 
21
- createUploadToken(options?: IApiSchemaOptions) {
22
- return this.$sdk.createApiSchemas(ApiApiFilecreateUploadTokenPath, 'post', options);
23
- }
24
-
25
21
  upload(options?: IApiSchemaOptions) {
26
22
  return this.$sdk.createApiSchemas(ApiApiFileuploadPath, 'post', options);
27
23
  }
@@ -30,6 +26,10 @@ export class ApiSchemaFile extends BeanBase {
30
26
  return this.$sdk.createApiSchemas(ApiApiFilecreateDirectUploadPath, 'post', options);
31
27
  }
32
28
 
29
+ finalizeDirectUpload(options?: IApiSchemaOptions) {
30
+ return this.$sdk.createApiSchemas(ApiApiFilefinalizeDirectUploadPath, 'post', options);
31
+ }
32
+
33
33
  uploadUrl(options?: IApiSchemaOptions) {
34
34
  return this.$sdk.createApiSchemas(ApiApiFileuploadUrlPath, 'post', options);
35
35
  }
@@ -86,7 +86,9 @@ export class TableCellFile extends BeanBase implements ITableCellRender {
86
86
  target="_blank"
87
87
  rel="noreferrer"
88
88
  onClick={event => {
89
+ event.preventDefault();
89
90
  event.stopPropagation();
91
+ this._openDownloadUrl(downloadUrl);
90
92
  }}
91
93
  >
92
94
  {contentNode}
@@ -192,19 +194,35 @@ export class TableCellFile extends BeanBase implements ITableCellRender {
192
194
  return '';
193
195
  }
194
196
 
195
- private _openDownloadUrl(downloadUrl: string) {
197
+ private async _openDownloadUrl(downloadUrl: string) {
198
+ if (!process.env.CLIENT) return;
199
+ const passportCode = await this._getDownloadPassportCode();
200
+ const url = this._resolveDownloadUrl(downloadUrl, passportCode);
201
+ if (!url) return;
196
202
  const anchor = globalThis.document?.createElement('a');
197
203
  if (!anchor) {
198
- globalThis.open?.(downloadUrl, '_blank');
204
+ globalThis.open?.(url, '_blank');
199
205
  return;
200
206
  }
201
- anchor.href = downloadUrl;
207
+ anchor.href = url;
202
208
  anchor.target = '_blank';
203
209
  anchor.rel = 'noreferrer';
204
210
  anchor.click();
205
211
  }
206
212
 
207
- private _resolveDownloadUrl(item: IFilePreviewItem): string | undefined {
208
- return resolveFileDownloadUrl(item.downloadUrl, this.sys.config.api.baseURL);
213
+ private async _getDownloadPassportCode() {
214
+ const apiPrefix = this.sys.config.api.prefix ?? '/api';
215
+ return await this.$passport.ensureFreshTempAuthToken({
216
+ path: `${apiPrefix}/file/download`,
217
+ staleTime: 30 * 1000,
218
+ });
219
+ }
220
+
221
+ private _resolveDownloadUrl(
222
+ item: IFilePreviewItem | string,
223
+ passportCode?: string,
224
+ ): string | undefined {
225
+ const url = typeof item === 'string' ? item : item.downloadUrl;
226
+ return resolveFileDownloadUrl(url, this.sys.config.api.baseURL, passportCode);
209
227
  }
210
228
  }
@@ -1,9 +1,8 @@
1
1
  import type { TableIdentity } from 'table-identity';
2
- import type { IComponentOptions, TypeControllerInnerProps } from 'zova';
2
+ import type { IComponentOptions } from 'zova';
3
3
  import type { IJsxRenderContextFormField } from 'zova-module-a-form';
4
4
  import type { ControllerFormField, IFormFieldComponentOptions } from 'zova-module-a-form';
5
5
  import type { IResourceFormFieldOptionsBase } from 'zova-module-a-openapi';
6
- import type { ScopeModuleBasicFile } from 'zova-module-basic-file';
7
6
 
8
7
  import { classes } from 'typestyle';
9
8
  import { BeanControllerBase, ClientOnly, Use } from 'zova';
@@ -77,12 +76,12 @@ export class ControllerFormFieldFile extends BeanControllerBase {
77
76
  protected async __init__() {}
78
77
 
79
78
  protected render() {
80
- if (this._props.readonly) {
79
+ if (this.$props.readonly) {
81
80
  return this._renderReadonlyPreset();
82
81
  }
83
82
  return (
84
83
  <ZFormField
85
- {...this._props}
84
+ {...this.$props}
86
85
  slotDefault={({ propsBucket, props }, $$formField) => {
87
86
  this.$$formField = $$formField;
88
87
  const fieldOptions = (propsBucket.options ?? {}) as IResourceFormFieldFileOptions;
@@ -137,12 +136,12 @@ export class ControllerFormFieldFile extends BeanControllerBase {
137
136
  {this.isUploading && (
138
137
  <span class="inline-flex items-center gap-2 text-sm text-base-content/70">
139
138
  <span class="loading loading-spinner loading-sm text-primary"></span>
140
- {this._scope.locale.Uploading()}
139
+ {this.scope.locale.Uploading()}
141
140
  </span>
142
141
  )}
143
142
  {!items.length && !this.isUploading && (
144
143
  <span class="text-sm text-base-content/60">
145
- {fieldOptions.placeholder ?? this._scope.locale.NoFileSelected()}
144
+ {fieldOptions.placeholder ?? this.scope.locale.NoFileSelected()}
146
145
  </span>
147
146
  )}
148
147
  </div>
@@ -161,21 +160,10 @@ export class ControllerFormFieldFile extends BeanControllerBase {
161
160
  );
162
161
  }
163
162
 
164
- private get _props() {
165
- return this.$props as TypeControllerInnerProps<
166
- ControllerFormFieldFileProps,
167
- keyof typeof ControllerFormFieldFile.$propsDefault
168
- >;
169
- }
170
-
171
- private get _scope() {
172
- return this.scope as unknown as ScopeModuleBasicFile;
173
- }
174
-
175
163
  private _renderReadonlyPreset() {
176
164
  return (
177
165
  <ZFormField
178
- {...this._props}
166
+ {...this.$props}
179
167
  slotDefault={({ propsBucket, props }) => {
180
168
  const fieldOptions = (propsBucket.options ?? {}) as IResourceFormFieldFileOptions;
181
169
  const fieldValue = propsBucket.value as unknown;
@@ -191,9 +179,7 @@ export class ControllerFormFieldFile extends BeanControllerBase {
191
179
 
192
180
  private _renderReadonlyItems(items: IFilePreviewItem[]) {
193
181
  if (!items.length) {
194
- return (
195
- <span class="text-sm text-base-content/60">{this._scope.locale.NoFileSelected()}</span>
196
- );
182
+ return <span class="text-sm text-base-content/60">{this.scope.locale.NoFileSelected()}</span>;
197
183
  }
198
184
  return (
199
185
  <div class="space-y-3">
@@ -223,13 +209,21 @@ export class ControllerFormFieldFile extends BeanControllerBase {
223
209
  {item.contentType ?? '-'} · {formatFileSize(item.size)}
224
210
  </div>
225
211
  <div class="text-xs text-base-content/60">
226
- {this._scope.locale.UploadedAt()}: {formatFileDate(item.uploadedAt)}
212
+ {this.scope.locale.UploadedAt()}: {formatFileDate(item.uploadedAt)}
227
213
  </div>
228
214
  </div>
229
215
  <div class="flex flex-wrap gap-2">
230
216
  {downloadUrl && (
231
- <a class="btn btn-sm btn-outline" href={downloadUrl} target="_blank" rel="noreferrer">
232
- {this._scope.locale.DownloadFile()}
217
+ <a
218
+ class="btn btn-sm btn-outline"
219
+ href={downloadUrl}
220
+ target="_blank"
221
+ rel="noreferrer"
222
+ onClick={event => {
223
+ this._openDownloadUrl(event, downloadUrl);
224
+ }}
225
+ >
226
+ {this.scope.locale.DownloadFile()}
233
227
  </a>
234
228
  )}
235
229
  {!readonly && (
@@ -240,7 +234,7 @@ export class ControllerFormFieldFile extends BeanControllerBase {
240
234
  this._removeItem(item.id, disableNotifyChanged);
241
235
  }}
242
236
  >
243
- {this._scope.locale.RemoveFile()}
237
+ {this.scope.locale.RemoveFile()}
244
238
  </button>
245
239
  )}
246
240
  </div>
@@ -254,9 +248,9 @@ export class ControllerFormFieldFile extends BeanControllerBase {
254
248
  multiple = this._getEffectiveMultiple(this.currentOptions),
255
249
  ) {
256
250
  if (multiple) {
257
- return itemCount > 0 ? this._scope.locale.AddFile() : this._scope.locale.SelectFile();
251
+ return itemCount > 0 ? this.scope.locale.AddFile() : this.scope.locale.SelectFile();
258
252
  }
259
- return itemCount > 0 ? this._scope.locale.ReplaceFile() : this._scope.locale.SelectFile();
253
+ return itemCount > 0 ? this.scope.locale.ReplaceFile() : this.scope.locale.SelectFile();
260
254
  }
261
255
 
262
256
  private _getUploadPolicyQuery(options?: IResourceFormFieldFileOptions) {
@@ -313,7 +307,7 @@ export class ControllerFormFieldFile extends BeanControllerBase {
313
307
  ? currentIds.length + filesToHandle.length
314
308
  : filesToHandle.length;
315
309
  if (nextCountCandidate > maxCount) {
316
- this.errorMessage = this._scope.locale.TooManyFiles(maxCount);
310
+ this.errorMessage = this.scope.locale.TooManyFiles(maxCount);
317
311
  return;
318
312
  }
319
313
  const uploadedItems: IFilePreviewItem[] = [];
@@ -323,15 +317,11 @@ export class ControllerFormFieldFile extends BeanControllerBase {
323
317
  this.errorMessage = validationMessage;
324
318
  continue;
325
319
  }
326
- const tokenRes = await this._scope.api.file.createUploadToken({
327
- ...uploadTarget,
328
- size: file.size,
329
- mimeType: file.type || 'application/octet-stream',
330
- });
331
- const uploaded = await this._scope.api.file.upload({
332
- token: tokenRes.token,
320
+ const uploaded = await this._uploadFile(
321
+ uploadTarget.fileScene,
333
322
  file,
334
- });
323
+ this._getCachedUploadPolicy(options)?.directUpload ?? false,
324
+ );
335
325
  const item: IFilePreviewItem = {
336
326
  id: uploaded.id as TableIdentity,
337
327
  filename: uploaded.filename,
@@ -352,12 +342,35 @@ export class ControllerFormFieldFile extends BeanControllerBase {
352
342
  this._setFieldValue(nextIds, disableNotifyChanged, multiple);
353
343
  this.errorMessage = undefined;
354
344
  } catch (err: any) {
355
- this.errorMessage = err?.message ?? this._scope.locale.FileUploadFailed();
345
+ this.errorMessage = err?.message ?? this.scope.locale.FileUploadFailed();
356
346
  } finally {
357
347
  this.isUploading = false;
358
348
  }
359
349
  }
360
350
 
351
+ private async _uploadFile(fileScene: string, file: File, directUpload: boolean) {
352
+ if (!directUpload) {
353
+ return await this.scope.api.file.upload({ fileScene, file });
354
+ }
355
+ const directUploadResponse = await this.scope.api.file.createDirectUpload({
356
+ fileScene,
357
+ filename: file.name,
358
+ size: file.size,
359
+ mimeType: file.type || 'application/octet-stream',
360
+ });
361
+ const providerResponse = await fetch(directUploadResponse.uploadUrl, {
362
+ method: directUploadResponse.method ?? 'PUT',
363
+ headers: directUploadResponse.headers,
364
+ body: file,
365
+ });
366
+ if (!providerResponse.ok) {
367
+ throw new Error(`file direct upload failed: ${providerResponse.status}`);
368
+ }
369
+ return await this.scope.api.file.finalizeDirectUpload({
370
+ fileId: directUploadResponse.id,
371
+ });
372
+ }
373
+
361
374
  private _resolveUploadTarget() {
362
375
  const fileScene = this.currentOptions.fileScene;
363
376
  if (!fileScene) {
@@ -444,7 +457,7 @@ export class ControllerFormFieldFile extends BeanControllerBase {
444
457
  }
445
458
 
446
459
  private _getRelationName() {
447
- return inferFileRelationName(this._props.name, this.currentOptions.relationName);
460
+ return inferFileRelationName(this.$props.name, this.currentOptions.relationName);
448
461
  }
449
462
 
450
463
  private _normalizeValueToFileIds(value: unknown, multiple: boolean): TableIdentity[] {
@@ -481,14 +494,14 @@ export class ControllerFormFieldFile extends BeanControllerBase {
481
494
  private _validateFile(file: File, options: IResourceFormFieldFileOptions) {
482
495
  const acceptTokens = this._collectAcceptTokens(options);
483
496
  if (acceptTokens.length > 0 && !this._matchesAccept(file, acceptTokens)) {
484
- return this._scope.locale.InvalidFileType();
497
+ return this.scope.locale.InvalidFileType();
485
498
  }
486
499
  const maxSize = options.maxSize ?? this._getCachedUploadPolicy(options)?.maxSize;
487
500
  if (typeof maxSize === 'number' && file.size > maxSize) {
488
- return this._scope.locale.FileTooLarge(formatFileSize(maxSize));
501
+ return this.scope.locale.FileTooLarge(formatFileSize(maxSize));
489
502
  }
490
503
  if (typeof options.minSize === 'number' && file.size < options.minSize) {
491
- return this._scope.locale.FileTooSmall(formatFileSize(options.minSize));
504
+ return this.scope.locale.FileTooSmall(formatFileSize(options.minSize));
492
505
  }
493
506
  return undefined;
494
507
  }
@@ -528,7 +541,23 @@ export class ControllerFormFieldFile extends BeanControllerBase {
528
541
  return filename.slice(index).toLowerCase();
529
542
  }
530
543
 
531
- private _resolveDownloadUrl(url: string) {
532
- return resolveFileDownloadUrl(url, this.sys.config.api.baseURL);
544
+ private async _openDownloadUrl(event: MouseEvent, downloadUrl: string) {
545
+ if (!process.env.CLIENT) return;
546
+ event.preventDefault();
547
+ const passportCode = await this._getDownloadPassportCode();
548
+ const url = this._resolveDownloadUrl(downloadUrl, passportCode);
549
+ if (url) globalThis.open?.(url, '_blank', 'noopener,noreferrer');
550
+ }
551
+
552
+ private async _getDownloadPassportCode() {
553
+ const apiPrefix = this.sys.config.api.prefix ?? '/api';
554
+ return await this.$passport.ensureFreshTempAuthToken({
555
+ path: `${apiPrefix}/file/download`,
556
+ staleTime: 30 * 1000,
557
+ });
558
+ }
559
+
560
+ private _resolveDownloadUrl(url: string, passportCode?: string) {
561
+ return resolveFileDownloadUrl(url, this.sys.config.api.baseURL, passportCode);
533
562
  }
534
563
  }
@@ -1,3 +1,5 @@
1
+ import { $protocolKey } from 'zova';
2
+
1
3
  import type { IFilePreviewItem } from '../types/file.js';
2
4
 
3
5
  export function inferFileRelationName(fieldName?: string, relationName?: string) {
@@ -12,10 +14,26 @@ export function inferFileRelationName(fieldName?: string, relationName?: string)
12
14
  return undefined;
13
15
  }
14
16
 
15
- export function resolveFileDownloadUrl(url: string | undefined, baseURL?: string) {
16
- if (!url || !url.startsWith('/api/')) return url;
17
- if (!baseURL) return url;
18
- return `${baseURL.replace(/\/$/, '')}${url}`;
17
+ export function resolveFileDownloadUrl(
18
+ url: string | undefined,
19
+ baseURL?: string,
20
+ passportCode?: string,
21
+ ) {
22
+ if (!url) return url;
23
+ const resolvedUrl =
24
+ url.startsWith('/api/') && baseURL ? `${baseURL.replace(/\/$/, '')}${url}` : url;
25
+ if (!passportCode) return resolvedUrl;
26
+ const apiBaseUrl = new URL(baseURL ?? 'http://localhost', 'http://localhost');
27
+ const parsedUrl = new URL(resolvedUrl, apiBaseUrl);
28
+ if (parsedUrl.origin !== apiBaseUrl.origin || !parsedUrl.pathname.startsWith('/api/')) {
29
+ return resolvedUrl;
30
+ }
31
+ const passportCodeKey = $protocolKey('x-vona-passport-code');
32
+ if (!parsedUrl.searchParams.has(passportCodeKey)) {
33
+ parsedUrl.searchParams.set(passportCodeKey, passportCode);
34
+ }
35
+ if (!baseURL) return `${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`;
36
+ return parsedUrl.toString();
19
37
  }
20
38
 
21
39
  export function summarizeFileRelationPreviewValue(value: unknown): {
@@ -4,21 +4,25 @@ export * from '../model/image.js';
4
4
  import { IModelOptionsImage } from '../model/image.js';
5
5
  import 'zova-module-a-model';
6
6
  declare module 'zova-module-a-model' {
7
- export interface IModelRecord {
8
- 'basic-image:image': IModelOptionsImage;
9
- }
7
+
8
+ export interface IModelRecord {
9
+ 'basic-image:image': IModelOptionsImage;
10
+ }
11
+
12
+
10
13
  }
11
14
  declare module 'zova-module-basic-image' {
12
- export interface ModelImage {
13
- /** @internal */
14
- get scope(): ScopeModuleBasicImage;
15
- }
15
+
16
+ export interface ModelImage {
17
+ /** @internal */
18
+ get scope(): ScopeModuleBasicImage;
19
+ }
16
20
 
17
- export interface ModelImage {
18
- get $beanFullName(): 'basic-image.model.image';
19
- get $onionName(): 'basic-image:image';
20
- get $onionOptions(): IModelOptionsImage;
21
- }
21
+ export interface ModelImage {
22
+ get $beanFullName(): 'basic-image.model.image';
23
+ get $onionName(): 'basic-image:image';
24
+ get $onionOptions(): IModelOptionsImage;
25
+ }
22
26
  }
23
27
  /** model: end */
24
28
  /** model: begin */
@@ -32,7 +36,6 @@ declare module 'zova' {
32
36
  /** model: end */
33
37
  /** api: begin */
34
38
  export * from '../api/image.js';
35
- export * from '../api/imageNativeImage.js';
36
39
 
37
40
  import 'zova';
38
41
  declare module 'zova' {
@@ -50,26 +53,13 @@ declare module 'zova-module-basic-image' {
50
53
  get $beanFullName(): 'basic-image.api.image';
51
54
  get $onionName(): 'basic-image:image';
52
55
 
53
- }
54
-
55
- export interface ApiImageNativeImage {
56
- /** @internal */
57
- get scope(): ScopeModuleBasicImage;
58
- }
59
-
60
- export interface ApiImageNativeImage {
61
- get $beanFullName(): 'basic-image.api.imageNativeImage';
62
- get $onionName(): 'basic-image:imageNativeImage';
63
-
64
56
  }
65
57
  }
66
58
  /** api: end */
67
59
  /** api: begin */
68
60
  import { ApiImage } from '../api/image.js';
69
- import { ApiImageNativeImage } from '../api/imageNativeImage.js';
70
61
  export interface IModuleApi {
71
62
  'image': ApiImage;
72
- 'imageNativeImage': ApiImageNativeImage;
73
63
  }
74
64
  /** api: end */
75
65
  /** api: begin */
@@ -78,7 +68,6 @@ import 'zova';
78
68
  declare module 'zova' {
79
69
  export interface IBeanRecordGeneral {
80
70
  'basic-image.api.image': ApiImage;
81
- 'basic-image.api.imageNativeImage': ApiImageNativeImage;
82
71
  }
83
72
  }
84
73
  /** api: end */
@@ -87,7 +76,6 @@ export * from '../api/openapi/index.js';
87
76
  /** openapi: end */
88
77
  /** apiSchema: begin */
89
78
  export * from '../apiSchema/image.js';
90
- export * from '../apiSchema/imageNativeImage.js';
91
79
 
92
80
  import 'zova';
93
81
  declare module 'zova' {
@@ -105,26 +93,13 @@ declare module 'zova-module-basic-image' {
105
93
  get $beanFullName(): 'basic-image.apiSchema.image';
106
94
  get $onionName(): 'basic-image:image';
107
95
 
108
- }
109
-
110
- export interface ApiSchemaImageNativeImage {
111
- /** @internal */
112
- get scope(): ScopeModuleBasicImage;
113
- }
114
-
115
- export interface ApiSchemaImageNativeImage {
116
- get $beanFullName(): 'basic-image.apiSchema.imageNativeImage';
117
- get $onionName(): 'basic-image:imageNativeImage';
118
-
119
96
  }
120
97
  }
121
98
  /** apiSchema: end */
122
99
  /** apiSchema: begin */
123
100
  import { ApiSchemaImage } from '../apiSchema/image.js';
124
- import { ApiSchemaImageNativeImage } from '../apiSchema/imageNativeImage.js';
125
101
  export interface IModuleApiSchema {
126
102
  'image': ApiSchemaImage;
127
- 'imageNativeImage': ApiSchemaImageNativeImage;
128
103
  }
129
104
  /** apiSchema: end */
130
105
  /** apiSchema: begin */
@@ -133,7 +108,6 @@ import 'zova';
133
108
  declare module 'zova' {
134
109
  export interface IBeanRecordGeneral {
135
110
  'basic-image.apiSchema.image': ApiSchemaImage;
136
- 'basic-image.apiSchema.imageNativeImage': ApiSchemaImageNativeImage;
137
111
  }
138
112
  }
139
113
  /** apiSchema: end */
@@ -15,21 +15,12 @@ export type ApiApiImagegetUploadPolicyRequestBody =
15
15
  export type ApiApiImagegetUploadPolicyResponseBody =
16
16
  paths[ApiApiImagegetUploadPolicyPath][ApiApiImagegetUploadPolicyMethod]['responses']['200']['content']['application/json']['data'];
17
17
 
18
- /** Image_createUploadToken */
19
- export const ApiApiImagecreateUploadTokenPath = '/api/image/upload-token';
20
- export type ApiApiImagecreateUploadTokenPath = '/api/image/upload-token';
21
- export type ApiApiImagecreateUploadTokenMethod = 'post';
22
- export type ApiApiImagecreateUploadTokenRequestBody =
23
- components['schemas']['a-image.dto.imageUploadTokenRequest'];
24
- export type ApiApiImagecreateUploadTokenResponseBody =
25
- paths[ApiApiImagecreateUploadTokenPath][ApiApiImagecreateUploadTokenMethod]['responses']['200']['content']['application/json']['data'];
26
-
27
18
  /** Image_upload */
28
19
  export const ApiApiImageuploadPath = '/api/image/upload';
29
20
  export type ApiApiImageuploadPath = '/api/image/upload';
30
21
  export type ApiApiImageuploadMethod = 'post';
31
22
  export type ApiApiImageuploadRequestBody = {
32
- token: string;
23
+ imageScene: string;
33
24
  /** Format: binary */
34
25
  image: Blob;
35
26
  };
@@ -64,11 +55,9 @@ export type ApiApiImageuploadUrlResponseBody =
64
55
  paths[ApiApiImageuploadUrlPath][ApiApiImageuploadUrlMethod]['responses']['200']['content']['application/json']['data'];
65
56
 
66
57
  /** Image_delivery */
67
- export const ApiApiImagedeliveryPath = '/api/image/delivery/{imageId}';
68
- export type ApiApiImagedeliveryPath = '/api/image/delivery/{imageId}';
58
+ export const ApiApiImagedeliveryPath = '/api/image/delivery';
59
+ export type ApiApiImagedeliveryPath = '/api/image/delivery';
69
60
  export type ApiApiImagedeliveryMethod = 'get';
70
- export type ApiApiImagedeliveryRequestParams =
71
- paths[ApiApiImagedeliveryPath][ApiApiImagedeliveryMethod]['parameters']['path'];
72
61
  export type ApiApiImagedeliveryRequestQuery =
73
62
  paths[ApiApiImagedeliveryPath][ApiApiImagedeliveryMethod]['parameters']['query'];
74
63
  export type ApiApiImagedeliveryResponseBody =
@@ -84,14 +73,6 @@ export class ApiImage extends BeanApiBase {
84
73
  );
85
74
  }
86
75
 
87
- createUploadToken(body: ApiApiImagecreateUploadTokenRequestBody, options?: IApiActionOptions) {
88
- return this.$fetch.post<any, ApiApiImagecreateUploadTokenResponseBody>(
89
- ApiApiImagecreateUploadTokenPath,
90
- body,
91
- this.$configPrepare(OpenApiBaseURL(this.sys), options, true),
92
- );
93
- }
94
-
95
76
  upload(body: ApiApiImageuploadRequestBody, options?: IApiActionOptions) {
96
77
  return this.$fetch.post<any, ApiApiImageuploadResponseBody>(
97
78
  ApiApiImageuploadPath,
@@ -129,12 +110,11 @@ export class ApiImage extends BeanApiBase {
129
110
 
130
111
  delivery(
131
112
  options: {
132
- params: ApiApiImagedeliveryRequestParams;
133
- query?: ApiApiImagedeliveryRequestQuery;
113
+ query: ApiApiImagedeliveryRequestQuery;
134
114
  } & IApiActionOptions,
135
115
  ) {
136
116
  return this.$fetch.get<any, ApiApiImagedeliveryResponseBody>(
137
- this.$pathTranslate(ApiApiImagedeliveryPath, options.params),
117
+ ApiApiImagedeliveryPath,
138
118
  this.$configPrepare(OpenApiBaseURL(this.sys), options),
139
119
  );
140
120
  }
@@ -67,14 +67,8 @@ export type ApiSchemaTrainingRecordDtoRecordSelectResItemPartial =
67
67
  Partial<ApiSchemaTrainingRecordDtoRecordSelectResItem>;
68
68
  export type ApiSchemaAImageDtoImageView = components['schemas']['a-image.dto.imageView'];
69
69
  export type ApiSchemaAImageDtoImageViewPartial = Partial<ApiSchemaAImageDtoImageView>;
70
- export type ApiSchemaAImageDtoImageTransformOptions =
71
- components['schemas']['a-image.dto.imageTransformOptions'];
72
- export type ApiSchemaAImageDtoImageTransformOptionsPartial =
73
- Partial<ApiSchemaAImageDtoImageTransformOptions>;
74
- export type ApiSchemaTrainingRecordDtoRecordDossierFileView =
75
- components['schemas']['training-record.dto.recordDossierFileView'];
76
- export type ApiSchemaTrainingRecordDtoRecordDossierFileViewPartial =
77
- Partial<ApiSchemaTrainingRecordDtoRecordDossierFileView>;
70
+ export type ApiSchemaAFileDtoFileView = components['schemas']['a-file.dto.fileView'];
71
+ export type ApiSchemaAFileDtoFileViewPartial = Partial<ApiSchemaAFileDtoFileView>;
78
72
  export type ApiSchemaTrainingRecordDtoRecordView_2d063d28bc7243bed02ebd8bddf1212a93c6305b_425dbecccd52e19e24888f99e1b1670233afa875 =
79
73
  components['schemas']['training-record.dto.recordView_2d063d28bc7243bed02ebd8bddf1212a93c6305b_425dbecccd52e19e24888f99e1b1670233afa875'];
80
74
  export type ApiSchemaTrainingRecordDtoRecordView_2d063d28bc7243bed02ebd8bddf1212a93c6305b_425dbecccd52e19e24888f99e1b1670233afa875Partial =
@@ -123,14 +117,6 @@ export type ApiSchemaAFileDtoFileUploadPolicyRequest =
123
117
  components['schemas']['a-file.dto.fileUploadPolicyRequest'];
124
118
  export type ApiSchemaAFileDtoFileUploadPolicyRequestPartial =
125
119
  Partial<ApiSchemaAFileDtoFileUploadPolicyRequest>;
126
- export type ApiSchemaAFileDtoFileUploadTokenResponse =
127
- components['schemas']['a-file.dto.fileUploadTokenResponse'];
128
- export type ApiSchemaAFileDtoFileUploadTokenResponsePartial =
129
- Partial<ApiSchemaAFileDtoFileUploadTokenResponse>;
130
- export type ApiSchemaAFileDtoFileUploadTokenRequest =
131
- components['schemas']['a-file.dto.fileUploadTokenRequest'];
132
- export type ApiSchemaAFileDtoFileUploadTokenRequestPartial =
133
- Partial<ApiSchemaAFileDtoFileUploadTokenRequest>;
134
120
  export type ApiSchemaAFileDtoFileUploadResponse =
135
121
  components['schemas']['a-file.dto.fileUploadResponse'];
136
122
  export type ApiSchemaAFileDtoFileUploadResponsePartial =
@@ -143,6 +129,14 @@ export type ApiSchemaAFileDtoFileDirectUploadRequest =
143
129
  components['schemas']['a-file.dto.fileDirectUploadRequest'];
144
130
  export type ApiSchemaAFileDtoFileDirectUploadRequestPartial =
145
131
  Partial<ApiSchemaAFileDtoFileDirectUploadRequest>;
132
+ export type ApiSchemaAFileDtoFileDirectUploadFinalizeResponse =
133
+ components['schemas']['a-file.dto.fileDirectUploadFinalizeResponse'];
134
+ export type ApiSchemaAFileDtoFileDirectUploadFinalizeResponsePartial =
135
+ Partial<ApiSchemaAFileDtoFileDirectUploadFinalizeResponse>;
136
+ export type ApiSchemaAFileDtoFileDirectUploadFinalizeRequest =
137
+ components['schemas']['a-file.dto.fileDirectUploadFinalizeRequest'];
138
+ export type ApiSchemaAFileDtoFileDirectUploadFinalizeRequestPartial =
139
+ Partial<ApiSchemaAFileDtoFileDirectUploadFinalizeRequest>;
146
140
  export type ApiSchemaAFileDtoFileUploadUrlRequest =
147
141
  components['schemas']['a-file.dto.fileUploadUrlRequest'];
148
142
  export type ApiSchemaAFileDtoFileUploadUrlRequestPartial =
@@ -155,14 +149,6 @@ export type ApiSchemaAImageDtoImageUploadPolicyRequest =
155
149
  components['schemas']['a-image.dto.imageUploadPolicyRequest'];
156
150
  export type ApiSchemaAImageDtoImageUploadPolicyRequestPartial =
157
151
  Partial<ApiSchemaAImageDtoImageUploadPolicyRequest>;
158
- export type ApiSchemaAImageDtoImageUploadTokenResponse =
159
- components['schemas']['a-image.dto.imageUploadTokenResponse'];
160
- export type ApiSchemaAImageDtoImageUploadTokenResponsePartial =
161
- Partial<ApiSchemaAImageDtoImageUploadTokenResponse>;
162
- export type ApiSchemaAImageDtoImageUploadTokenRequest =
163
- components['schemas']['a-image.dto.imageUploadTokenRequest'];
164
- export type ApiSchemaAImageDtoImageUploadTokenRequestPartial =
165
- Partial<ApiSchemaAImageDtoImageUploadTokenRequest>;
166
152
  export type ApiSchemaAImageDtoImageUploadResponse =
167
153
  components['schemas']['a-image.dto.imageUploadResponse'];
168
154
  export type ApiSchemaAImageDtoImageUploadResponsePartial =