@strapi/upload 5.36.0 → 5.37.0

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 (119) hide show
  1. package/dist/admin/future/App.js +5 -12
  2. package/dist/admin/future/App.js.map +1 -1
  3. package/dist/admin/future/App.mjs +5 -12
  4. package/dist/admin/future/App.mjs.map +1 -1
  5. package/dist/admin/future/components/UploadProgressDialog.js +494 -0
  6. package/dist/admin/future/components/UploadProgressDialog.js.map +1 -0
  7. package/dist/admin/future/components/UploadProgressDialog.mjs +473 -0
  8. package/dist/admin/future/components/UploadProgressDialog.mjs.map +1 -0
  9. package/dist/admin/future/pages/Assets/AssetsPage.js +183 -181
  10. package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
  11. package/dist/admin/future/pages/Assets/AssetsPage.mjs +190 -188
  12. package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
  13. package/dist/admin/future/pages/Assets/components/AssetsGrid.js +95 -13
  14. package/dist/admin/future/pages/Assets/components/AssetsGrid.js.map +1 -1
  15. package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs +97 -15
  16. package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs.map +1 -1
  17. package/dist/admin/future/pages/Assets/components/AssetsTable.js +99 -6
  18. package/dist/admin/future/pages/Assets/components/AssetsTable.js.map +1 -1
  19. package/dist/admin/future/pages/Assets/components/AssetsTable.mjs +100 -7
  20. package/dist/admin/future/pages/Assets/components/AssetsTable.mjs.map +1 -1
  21. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZone.js +127 -0
  22. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZone.js.map +1 -0
  23. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZone.mjs +105 -0
  24. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZone.mjs.map +1 -0
  25. package/dist/admin/future/pages/Assets/hooks/useFolderInfo.js +50 -0
  26. package/dist/admin/future/pages/Assets/hooks/useFolderInfo.js.map +1 -0
  27. package/dist/admin/future/pages/Assets/hooks/useFolderInfo.mjs +48 -0
  28. package/dist/admin/future/pages/Assets/hooks/useFolderInfo.mjs.map +1 -0
  29. package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.js +20 -0
  30. package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.js.map +1 -0
  31. package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.mjs +18 -0
  32. package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.mjs.map +1 -0
  33. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js +77 -0
  34. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js.map +1 -0
  35. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs +74 -0
  36. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs.map +1 -0
  37. package/dist/admin/future/services/api.js +419 -9
  38. package/dist/admin/future/services/api.js.map +1 -1
  39. package/dist/admin/future/services/api.mjs +417 -9
  40. package/dist/admin/future/services/api.mjs.map +1 -1
  41. package/dist/admin/future/services/assets.js +32 -3
  42. package/dist/admin/future/services/assets.js.map +1 -1
  43. package/dist/admin/future/services/assets.mjs +32 -3
  44. package/dist/admin/future/services/assets.mjs.map +1 -1
  45. package/dist/admin/future/services/folders.js +101 -0
  46. package/dist/admin/future/services/folders.js.map +1 -0
  47. package/dist/admin/future/services/folders.mjs +98 -0
  48. package/dist/admin/future/services/folders.mjs.map +1 -0
  49. package/dist/admin/future/store/hooks.js +10 -0
  50. package/dist/admin/future/store/hooks.js.map +1 -0
  51. package/dist/admin/future/store/hooks.mjs +7 -0
  52. package/dist/admin/future/store/hooks.mjs.map +1 -0
  53. package/dist/admin/future/store/uploadProgress.js +156 -0
  54. package/dist/admin/future/store/uploadProgress.js.map +1 -0
  55. package/dist/admin/future/store/uploadProgress.mjs +143 -0
  56. package/dist/admin/future/store/uploadProgress.mjs.map +1 -0
  57. package/dist/admin/index.js +11 -0
  58. package/dist/admin/index.js.map +1 -1
  59. package/dist/admin/index.mjs +11 -0
  60. package/dist/admin/index.mjs.map +1 -1
  61. package/dist/admin/package.json.js +11 -10
  62. package/dist/admin/package.json.js.map +1 -1
  63. package/dist/admin/package.json.mjs +11 -10
  64. package/dist/admin/package.json.mjs.map +1 -1
  65. package/dist/admin/src/future/components/UploadProgressDialog.d.ts +1 -0
  66. package/dist/admin/src/future/pages/Assets/components/AssetsGrid.d.ts +3 -1
  67. package/dist/admin/src/future/pages/Assets/components/AssetsTable.d.ts +3 -1
  68. package/dist/admin/src/future/pages/Assets/components/DropZone/UploadDropZone.d.ts +10 -0
  69. package/dist/admin/src/future/pages/Assets/hooks/useFolderInfo.d.ts +5 -0
  70. package/dist/admin/src/future/pages/Assets/hooks/useFolderNavigation.d.ts +5 -0
  71. package/dist/admin/src/future/pages/Assets/hooks/useInfiniteAssets.d.ts +17 -0
  72. package/dist/admin/src/future/services/api.d.ts +21 -3
  73. package/dist/admin/src/future/services/folders.d.ts +16 -0
  74. package/dist/admin/src/future/store/hooks.d.ts +6 -0
  75. package/dist/admin/src/future/store/uploadProgress.d.ts +46 -0
  76. package/dist/admin/translations/en.json.js +24 -0
  77. package/dist/admin/translations/en.json.js.map +1 -1
  78. package/dist/admin/translations/en.json.mjs +24 -0
  79. package/dist/admin/translations/en.json.mjs.map +1 -1
  80. package/dist/server/controllers/admin-upload.js +151 -2
  81. package/dist/server/controllers/admin-upload.js.map +1 -1
  82. package/dist/server/controllers/admin-upload.mjs +151 -2
  83. package/dist/server/controllers/admin-upload.mjs.map +1 -1
  84. package/dist/server/controllers/content-api.js +14 -6
  85. package/dist/server/controllers/content-api.js.map +1 -1
  86. package/dist/server/controllers/content-api.mjs +15 -7
  87. package/dist/server/controllers/content-api.mjs.map +1 -1
  88. package/dist/server/routes/admin.js +10 -0
  89. package/dist/server/routes/admin.js.map +1 -1
  90. package/dist/server/routes/admin.mjs +10 -0
  91. package/dist/server/routes/admin.mjs.map +1 -1
  92. package/dist/server/src/controllers/admin-upload.d.ts +12 -0
  93. package/dist/server/src/controllers/admin-upload.d.ts.map +1 -1
  94. package/dist/server/src/controllers/content-api.d.ts.map +1 -1
  95. package/dist/server/src/controllers/index.d.ts +1 -0
  96. package/dist/server/src/controllers/index.d.ts.map +1 -1
  97. package/dist/server/src/index.d.ts +1 -0
  98. package/dist/server/src/index.d.ts.map +1 -1
  99. package/dist/server/src/routes/admin.d.ts.map +1 -1
  100. package/dist/server/src/utils/mime-validation.d.ts +5 -0
  101. package/dist/server/src/utils/mime-validation.d.ts.map +1 -1
  102. package/dist/server/utils/mime-validation.js +7 -4
  103. package/dist/server/utils/mime-validation.js.map +1 -1
  104. package/dist/server/utils/mime-validation.mjs +7 -4
  105. package/dist/server/utils/mime-validation.mjs.map +1 -1
  106. package/dist/shared/contracts/files.d.ts +52 -0
  107. package/dist/shared/contracts/files.d.ts.map +1 -0
  108. package/dist/shared/contracts/folders.d.ts +2 -0
  109. package/package.json +11 -10
  110. package/dist/admin/future/pages/AIGenerationPage.js +0 -24
  111. package/dist/admin/future/pages/AIGenerationPage.js.map +0 -1
  112. package/dist/admin/future/pages/AIGenerationPage.mjs +0 -22
  113. package/dist/admin/future/pages/AIGenerationPage.mjs.map +0 -1
  114. package/dist/admin/future/pages/Assets/components/DropZone/DropZoneWithOverlay.js +0 -33
  115. package/dist/admin/future/pages/Assets/components/DropZone/DropZoneWithOverlay.js.map +0 -1
  116. package/dist/admin/future/pages/Assets/components/DropZone/DropZoneWithOverlay.mjs +0 -31
  117. package/dist/admin/future/pages/Assets/components/DropZone/DropZoneWithOverlay.mjs.map +0 -1
  118. package/dist/admin/src/future/pages/AIGenerationPage.d.ts +0 -1
  119. package/dist/admin/src/future/pages/Assets/components/DropZone/DropZoneWithOverlay.d.ts +0 -4
@@ -4,6 +4,28 @@ var en = {
4
4
  "apiError.FileTooBig": "The uploaded file exceeds the maximum allowed asset size.",
5
5
  "assets.uploaded": "{number, plural, one {# asset} other {# assets}} uploaded successfully",
6
6
  "upload.generic-error": "An error occurred while uploading the file.",
7
+ "upload.progress": "Upload progress",
8
+ "upload.progress.failed": "Upload failed",
9
+ "upload.progress.failed.subtitle": "Please try to upload files again",
10
+ "upload.progress.success": "Upload successful!",
11
+ "upload.progress.success.subtitle": "{count, plural, one {# file uploaded successfully} other {# files uploaded successfully}}",
12
+ "upload.progress.uploading.withCount": "Uploading {total} items ({percentage}%)",
13
+ "upload.progress.uploading-files": "Uploading {count, plural, one {# file} other {# files}}",
14
+ "upload.progress.indicator.in-progress": "Upload in progress indicator",
15
+ "upload.progress.indicator.complete": "Upload complete indicator",
16
+ "upload.progress.label": "Upload",
17
+ "upload.progress.errors.title": "Failed to upload:",
18
+ "upload.progress.cancel": "Cancel",
19
+ "upload.progress.canceled": "Uploads canceled",
20
+ "upload.progress.canceled.subtitle": "Some files were not uploaded",
21
+ "upload.progress.canceled.subtitle.all": "All uploads were canceled",
22
+ "upload.progress.retry": "Retry",
23
+ "upload.progress.file.uploading": "Uploading...",
24
+ "upload.progress.file.canceled": "Canceled",
25
+ "upload.progress.file.uploaded": "Uploaded",
26
+ "upload.progress.maximize": "Maximize",
27
+ "upload.progress.minimize": "Minimize",
28
+ "upload.progress.close": "Close",
7
29
  "bulk.select.label": "Select all assets",
8
30
  "button.next": "Next",
9
31
  "new": "New",
@@ -41,6 +63,7 @@ var en = {
41
63
  "header.actions.upload-new-asset": "Upload new asset",
42
64
  "header.content.assets-empty": "No assets",
43
65
  "header.content.assets": "{numberFolders, plural, one {1 folder} other {# folders}} - {numberAssets, plural, one {1 asset} other {# assets}}",
66
+ "header.content.item-count": "{count, plural, =1 {# item} other {# items}}",
44
67
  "import-files": "Import files",
45
68
  "input.button.label": "Browse files",
46
69
  "input.label": "Drag & Drop here or",
@@ -105,6 +128,7 @@ var en = {
105
128
  "plugin.description.long": "Media file management.",
106
129
  "plugin.description.short": "Media file management.",
107
130
  "plugin.name": "Media Library",
131
+ "plugin.home": "Home",
108
132
  "search.clear.label": "Clear the search",
109
133
  "search.label": "Search for an asset",
110
134
  "search.placeholder": "e.g: the first dog on the moon",
@@ -1 +1 @@
1
- {"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -2,6 +2,28 @@ var en = {
2
2
  "apiError.FileTooBig": "The uploaded file exceeds the maximum allowed asset size.",
3
3
  "assets.uploaded": "{number, plural, one {# asset} other {# assets}} uploaded successfully",
4
4
  "upload.generic-error": "An error occurred while uploading the file.",
5
+ "upload.progress": "Upload progress",
6
+ "upload.progress.failed": "Upload failed",
7
+ "upload.progress.failed.subtitle": "Please try to upload files again",
8
+ "upload.progress.success": "Upload successful!",
9
+ "upload.progress.success.subtitle": "{count, plural, one {# file uploaded successfully} other {# files uploaded successfully}}",
10
+ "upload.progress.uploading.withCount": "Uploading {total} items ({percentage}%)",
11
+ "upload.progress.uploading-files": "Uploading {count, plural, one {# file} other {# files}}",
12
+ "upload.progress.indicator.in-progress": "Upload in progress indicator",
13
+ "upload.progress.indicator.complete": "Upload complete indicator",
14
+ "upload.progress.label": "Upload",
15
+ "upload.progress.errors.title": "Failed to upload:",
16
+ "upload.progress.cancel": "Cancel",
17
+ "upload.progress.canceled": "Uploads canceled",
18
+ "upload.progress.canceled.subtitle": "Some files were not uploaded",
19
+ "upload.progress.canceled.subtitle.all": "All uploads were canceled",
20
+ "upload.progress.retry": "Retry",
21
+ "upload.progress.file.uploading": "Uploading...",
22
+ "upload.progress.file.canceled": "Canceled",
23
+ "upload.progress.file.uploaded": "Uploaded",
24
+ "upload.progress.maximize": "Maximize",
25
+ "upload.progress.minimize": "Minimize",
26
+ "upload.progress.close": "Close",
5
27
  "bulk.select.label": "Select all assets",
6
28
  "button.next": "Next",
7
29
  "new": "New",
@@ -39,6 +61,7 @@ var en = {
39
61
  "header.actions.upload-new-asset": "Upload new asset",
40
62
  "header.content.assets-empty": "No assets",
41
63
  "header.content.assets": "{numberFolders, plural, one {1 folder} other {# folders}} - {numberAssets, plural, one {1 asset} other {# assets}}",
64
+ "header.content.item-count": "{count, plural, =1 {# item} other {# items}}",
42
65
  "import-files": "Import files",
43
66
  "input.button.label": "Browse files",
44
67
  "input.label": "Drag & Drop here or",
@@ -103,6 +126,7 @@ var en = {
103
126
  "plugin.description.long": "Media file management.",
104
127
  "plugin.description.short": "Media file management.",
105
128
  "plugin.name": "Media Library",
129
+ "plugin.home": "Home",
106
130
  "search.clear.label": "Clear the search",
107
131
  "search.label": "Search for an asset",
108
132
  "search.placeholder": "e.g: the first dog on the moon",
@@ -1 +1 @@
1
- {"version":3,"file":"en.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"en.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -49,7 +49,10 @@ var adminUpload = {
49
49
  if (Array.isArray(files)) {
50
50
  throw new utils.errors.ApplicationError('Cannot replace a file with multiple ones');
51
51
  }
52
- const { validFiles, filteredBody } = await mimeValidation.prepareUploadRequest(files, body, strapi);
52
+ const { validFiles, filteredBody, errors: validationErrors } = await mimeValidation.prepareUploadRequest(files, body, strapi);
53
+ if (validFiles.length === 0) {
54
+ throw new utils.errors.ValidationError(validationErrors[0].message);
55
+ }
53
56
  const data = await upload.validateUploadBody(filteredBody);
54
57
  const replacedFile = await uploadService.replace(id, {
55
58
  data,
@@ -74,7 +77,10 @@ var adminUpload = {
74
77
  if (!pm.isAllowed) {
75
78
  return ctx.forbidden();
76
79
  }
77
- const { validFiles, filteredBody } = await mimeValidation.prepareUploadRequest(files, body, strapi);
80
+ const { validFiles, filteredBody, errors: validationErrors } = await mimeValidation.prepareUploadRequest(files, body, strapi);
81
+ if (validFiles.length === 0) {
82
+ throw new utils.errors.ValidationError(validationErrors[0].message);
83
+ }
78
84
  const isMultipleFiles = validFiles.length > 1;
79
85
  const data = await upload.validateUploadBody(filteredBody, isMultipleFiles);
80
86
  let filesArray = validFiles;
@@ -115,6 +121,149 @@ var adminUpload = {
115
121
  });
116
122
  ctx.status = 201;
117
123
  },
124
+ /**
125
+ * @experimental
126
+ * Stream upload files with SSE streaming for per-file progress
127
+ *
128
+ * Streams Server-Sent Events as each file is validated and uploaded:
129
+ * - file:uploading — when processing starts for a file
130
+ * - file:complete — when a file is successfully uploaded
131
+ * - file:error — when a file fails validation or upload
132
+ * - stream:complete — final summary with all results
133
+ *
134
+ */ async unstable_uploadFilesStream (ctx) {
135
+ const { state: { userAbility, user }, request: { body, files: { files } = {} } } = ctx;
136
+ const uploadService = index.getService('upload');
137
+ const pm = strapi.service('admin::permission').createPermissionsManager({
138
+ ability: userAbility,
139
+ action: constants.ACTIONS.create,
140
+ model: constants.FILE_MODEL_UID
141
+ });
142
+ if (!pm.isAllowed) {
143
+ return ctx.forbidden();
144
+ }
145
+ if (_.isEmpty(files) || !Array.isArray(files) && files.size === 0) {
146
+ throw new utils.errors.ApplicationError('Files are empty');
147
+ }
148
+ // Take manual control of the response for SSE streaming
149
+ ctx.respond = false;
150
+ const res = ctx.res;
151
+ res.writeHead(200, {
152
+ 'Content-Type': 'text/event-stream',
153
+ 'Cache-Control': 'no-cache',
154
+ Connection: 'keep-alive'
155
+ });
156
+ const writeSSE = (event, data)=>{
157
+ res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
158
+ };
159
+ // Normalize files to an array
160
+ const filesArray = Array.isArray(files) ? files : [
161
+ files
162
+ ];
163
+ const total = filesArray.length;
164
+ // Parse fileInfo from body
165
+ // Multipart forms send fileInfo as either:
166
+ // - An array of JSON strings (one per file)
167
+ // - A single JSON string (one file, or a JSON-encoded array)
168
+ let parsedFileInfo = [];
169
+ if (body?.fileInfo) {
170
+ const raw = body.fileInfo;
171
+ if (Array.isArray(raw)) {
172
+ parsedFileInfo = raw.map((fi)=>typeof fi === 'string' ? JSON.parse(fi) : fi);
173
+ } else if (typeof raw === 'string') {
174
+ const parsed = JSON.parse(raw);
175
+ // Handle case where a single string contains a JSON array
176
+ parsedFileInfo = Array.isArray(parsed) ? parsed : [
177
+ parsed
178
+ ];
179
+ } else {
180
+ parsedFileInfo = [
181
+ raw
182
+ ];
183
+ }
184
+ }
185
+ const uploadErrors = [];
186
+ const successfulFiles = [];
187
+ // Process each file sequentially with inline validation
188
+ for(let i = 0; i < filesArray.length; i += 1){
189
+ const file = filesArray[i];
190
+ const fileName = file.originalFilename || 'unknown';
191
+ const fileInfo = parsedFileInfo[i] || {
192
+ name: fileName,
193
+ caption: null,
194
+ alternativeText: null,
195
+ folder: null
196
+ };
197
+ writeSSE('file:uploading', {
198
+ name: fileName,
199
+ index: i,
200
+ total,
201
+ size: file.size || 0
202
+ });
203
+ try {
204
+ // Validate this single file using security checks
205
+ const { validFiles, errors: validationErrors } = await mimeValidation.prepareUploadRequest(file, {
206
+ fileInfo: JSON.stringify(fileInfo)
207
+ }, strapi);
208
+ if (validFiles.length === 0) {
209
+ const errorMessage = validationErrors[0]?.message || 'Validation failed';
210
+ uploadErrors.push({
211
+ name: fileName,
212
+ message: errorMessage
213
+ });
214
+ writeSSE('file:error', {
215
+ name: fileName,
216
+ index: i,
217
+ message: errorMessage
218
+ });
219
+ } else {
220
+ // Validate using the already-parsed single fileInfo object directly
221
+ const data = await upload.validateUploadBody({
222
+ fileInfo
223
+ }, false);
224
+ const [uploadedFile] = await uploadService.upload({
225
+ data,
226
+ files: [
227
+ validFiles[0]
228
+ ]
229
+ }, {
230
+ user
231
+ });
232
+ // Sign file url
233
+ const signedFile = await index.getService('file').signFileUrls(uploadedFile);
234
+ successfulFiles.push(signedFile);
235
+ writeSSE('file:complete', {
236
+ name: fileName,
237
+ index: i,
238
+ file: signedFile
239
+ });
240
+ }
241
+ } catch (error) {
242
+ const errorMessage = error instanceof Error ? error.message : String(error);
243
+ uploadErrors.push({
244
+ name: fileName,
245
+ message: errorMessage
246
+ });
247
+ writeSSE('file:error', {
248
+ name: fileName,
249
+ index: i,
250
+ message: errorMessage
251
+ });
252
+ }
253
+ }
254
+ // Track image upload metric once if any images were uploaded
255
+ if (successfulFiles.some((file)=>file.mime?.startsWith('image/'))) {
256
+ await index.getService('metrics').trackUsage('didUploadImage');
257
+ }
258
+ // Send final stream summary
259
+ writeSSE('stream:complete', {
260
+ data: await pm.sanitizeOutput(successfulFiles, {
261
+ action: constants.ACTIONS.read
262
+ }),
263
+ errors: uploadErrors
264
+ });
265
+ res.end();
266
+ },
118
267
  // TODO: split into multiple endpoints
119
268
  async upload (ctx) {
120
269
  const { query: { id }, request: { files: { files } = {} } } = ctx;
@@ -1 +1 @@
1
- {"version":3,"file":"admin-upload.js","sources":["../../../server/src/controllers/admin-upload.ts"],"sourcesContent":["import _ from 'lodash';\nimport { errors, async } from '@strapi/utils';\n\nimport type { Context } from 'koa';\n\nimport { getService } from '../utils';\nimport { ACTIONS, FILE_MODEL_UID } from '../constants';\nimport { validateBulkUpdateBody, validateUploadBody } from './validation/admin/upload';\nimport { findEntityAndCheckPermissions } from './utils/find-entity-and-check-permissions';\nimport { FileInfo } from '../types';\nimport { prepareUploadRequest } from '../utils/mime-validation';\n\nexport default {\n async bulkUpdateFileInfo(ctx: Context) {\n const {\n state: { userAbility, user },\n request: { body },\n } = ctx;\n\n const { updates } = await validateBulkUpdateBody(body);\n const uploadService = getService('upload');\n\n const results = await async.map(\n updates,\n async ({ id, fileInfo }: { id: number; fileInfo: FileInfo }) => {\n const { pm } = await findEntityAndCheckPermissions(\n userAbility,\n ACTIONS.update,\n FILE_MODEL_UID,\n id\n );\n\n const updated = await uploadService.updateFileInfo(id, fileInfo as any, { user });\n return pm.sanitizeOutput(updated, { action: ACTIONS.read });\n }\n );\n\n ctx.body = results;\n },\n\n async updateFileInfo(ctx: Context) {\n const {\n state: { userAbility, user },\n query: { id },\n request: { body },\n } = ctx;\n\n if (typeof id !== 'string') {\n throw new errors.ValidationError('File id is required');\n }\n\n const uploadService = getService('upload');\n const { pm } = await findEntityAndCheckPermissions(\n userAbility,\n ACTIONS.update,\n FILE_MODEL_UID,\n id\n );\n\n const data = await validateUploadBody(body);\n\n const file = await uploadService.updateFileInfo(id, data.fileInfo as any, { user });\n\n ctx.body = await pm.sanitizeOutput(file, { action: ACTIONS.read });\n },\n\n async replaceFile(ctx: Context) {\n const {\n state: { userAbility, user },\n query: { id },\n request: { body, files: { files } = {} },\n } = ctx;\n\n if (typeof id !== 'string') {\n throw new errors.ValidationError('File id is required');\n }\n\n const uploadService = getService('upload');\n const { pm } = await findEntityAndCheckPermissions(\n userAbility,\n ACTIONS.update,\n FILE_MODEL_UID,\n id\n );\n\n if (Array.isArray(files)) {\n throw new errors.ApplicationError('Cannot replace a file with multiple ones');\n }\n\n const { validFiles, filteredBody } = await prepareUploadRequest(files, body, strapi);\n\n const data = (await validateUploadBody(filteredBody)) as { fileInfo: FileInfo };\n const replacedFile = await uploadService.replace(id, { data, file: validFiles[0] }, { user });\n\n // Sign file urls for private providers\n const signedFile = await getService('file').signFileUrls(replacedFile);\n\n ctx.body = await pm.sanitizeOutput(signedFile, { action: ACTIONS.read });\n },\n\n async uploadFiles(ctx: Context) {\n const {\n state: { userAbility, user },\n request: { body, files: { files } = {} },\n } = ctx;\n\n const uploadService = getService('upload');\n const pm = strapi.service('admin::permission').createPermissionsManager({\n ability: userAbility,\n action: ACTIONS.create,\n model: FILE_MODEL_UID,\n });\n\n if (!pm.isAllowed) {\n return ctx.forbidden();\n }\n\n const { validFiles, filteredBody } = await prepareUploadRequest(files, body, strapi);\n\n const isMultipleFiles = validFiles.length > 1;\n const data = await validateUploadBody(filteredBody, isMultipleFiles);\n\n let filesArray = validFiles;\n\n if (\n data.fileInfo &&\n Array.isArray(data.fileInfo) &&\n filesArray.length === data.fileInfo.length\n ) {\n // Reorder filesArray to match data.fileInfo order\n const alignedFilesArray = data.fileInfo\n .map((info) => {\n return filesArray.find((file) => file.originalFilename === info.name);\n })\n .filter(Boolean) as any[];\n\n filesArray = alignedFilesArray;\n }\n\n // Upload files first to get thumbnails\n const uploadedFiles = await uploadService.upload({ data, files: filesArray }, { user });\n if (uploadedFiles.some((file) => file.mime?.startsWith('image/'))) {\n await getService('metrics').trackUsage('didUploadImage');\n }\n\n const aiMetadataService = getService('aiMetadata');\n\n // AFTER upload - generate AI metadata for images\n if (await aiMetadataService.isEnabled()) {\n try {\n const metadataResults = await aiMetadataService.processFiles(uploadedFiles);\n // Update the uploaded files with AI metadata\n await aiMetadataService.updateFilesWithAIMetadata(uploadedFiles, metadataResults, user);\n } catch (error) {\n strapi.log.warn('AI metadata generation failed, proceeding without AI enhancements', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n // Sign file urls for private providers\n const signedFiles = await async.map(uploadedFiles, getService('file').signFileUrls);\n\n ctx.body = await pm.sanitizeOutput(signedFiles, { action: ACTIONS.read });\n ctx.status = 201;\n },\n\n // TODO: split into multiple endpoints\n async upload(ctx: Context) {\n const {\n query: { id },\n request: { files: { files } = {} },\n } = ctx;\n\n if (_.isEmpty(files) || (!Array.isArray(files) && files.size === 0)) {\n if (id) {\n return this.updateFileInfo(ctx);\n }\n\n throw new errors.ApplicationError('Files are empty');\n }\n\n await (id ? this.replaceFile : this.uploadFiles)(ctx);\n },\n};\n"],"names":["bulkUpdateFileInfo","ctx","state","userAbility","user","request","body","updates","validateBulkUpdateBody","uploadService","getService","results","async","map","id","fileInfo","pm","findEntityAndCheckPermissions","ACTIONS","update","FILE_MODEL_UID","updated","updateFileInfo","sanitizeOutput","action","read","query","errors","ValidationError","data","validateUploadBody","file","replaceFile","files","Array","isArray","ApplicationError","validFiles","filteredBody","prepareUploadRequest","strapi","replacedFile","replace","signedFile","signFileUrls","uploadFiles","service","createPermissionsManager","ability","create","model","isAllowed","forbidden","isMultipleFiles","length","filesArray","alignedFilesArray","info","find","originalFilename","name","filter","Boolean","uploadedFiles","upload","some","mime","startsWith","trackUsage","aiMetadataService","isEnabled","metadataResults","processFiles","updateFilesWithAIMetadata","error","log","warn","Error","message","String","signedFiles","status","_","isEmpty","size"],"mappings":";;;;;;;;;;AAYA,kBAAe;AACb,IAAA,MAAMA,oBAAmBC,GAAY,EAAA;AACnC,QAAA,MAAM,EACJC,KAAAA,EAAO,EAAEC,WAAW,EAAEC,IAAI,EAAE,EAC5BC,OAAS,EAAA,EAAEC,IAAI,EAAE,EAClB,GAAGL,GAAAA;AAEJ,QAAA,MAAM,EAAEM,OAAO,EAAE,GAAG,MAAMC,6BAAuBF,CAAAA,IAAAA,CAAAA;AACjD,QAAA,MAAMG,gBAAgBC,gBAAW,CAAA,QAAA,CAAA;QAEjC,MAAMC,OAAAA,GAAU,MAAMC,WAAAA,CAAMC,GAAG,CAC7BN,OACA,EAAA,OAAO,EAAEO,EAAE,EAAEC,QAAQ,EAAsC,GAAA;YACzD,MAAM,EAAEC,EAAE,EAAE,GAAG,MAAMC,4DACnBd,WACAe,EAAAA,iBAAAA,CAAQC,MAAM,EACdC,wBACAN,EAAAA,EAAAA,CAAAA;AAGF,YAAA,MAAMO,UAAU,MAAMZ,aAAAA,CAAca,cAAc,CAACR,IAAIC,QAAiB,EAAA;AAAEX,gBAAAA;AAAK,aAAA,CAAA;YAC/E,OAAOY,EAAAA,CAAGO,cAAc,CAACF,OAAS,EAAA;AAAEG,gBAAAA,MAAAA,EAAQN,kBAAQO;AAAK,aAAA,CAAA;AAC3D,SAAA,CAAA;AAGFxB,QAAAA,GAAAA,CAAIK,IAAI,GAAGK,OAAAA;AACb,KAAA;AAEA,IAAA,MAAMW,gBAAerB,GAAY,EAAA;AAC/B,QAAA,MAAM,EACJC,KAAO,EAAA,EAAEC,WAAW,EAAEC,IAAI,EAAE,EAC5BsB,KAAAA,EAAO,EAAEZ,EAAE,EAAE,EACbT,OAAAA,EAAS,EAAEC,IAAI,EAAE,EAClB,GAAGL,GAAAA;QAEJ,IAAI,OAAOa,OAAO,QAAU,EAAA;YAC1B,MAAM,IAAIa,YAAOC,CAAAA,eAAe,CAAC,qBAAA,CAAA;AACnC;AAEA,QAAA,MAAMnB,gBAAgBC,gBAAW,CAAA,QAAA,CAAA;QACjC,MAAM,EAAEM,EAAE,EAAE,GAAG,MAAMC,4DACnBd,WACAe,EAAAA,iBAAAA,CAAQC,MAAM,EACdC,wBACAN,EAAAA,EAAAA,CAAAA;QAGF,MAAMe,IAAAA,GAAO,MAAMC,yBAAmBxB,CAAAA,IAAAA,CAAAA;QAEtC,MAAMyB,IAAAA,GAAO,MAAMtB,aAAca,CAAAA,cAAc,CAACR,EAAIe,EAAAA,IAAAA,CAAKd,QAAQ,EAAS;AAAEX,YAAAA;AAAK,SAAA,CAAA;AAEjFH,QAAAA,GAAAA,CAAIK,IAAI,GAAG,MAAMU,EAAGO,CAAAA,cAAc,CAACQ,IAAM,EAAA;AAAEP,YAAAA,MAAAA,EAAQN,kBAAQO;AAAK,SAAA,CAAA;AAClE,KAAA;AAEA,IAAA,MAAMO,aAAY/B,GAAY,EAAA;QAC5B,MAAM,EACJC,KAAO,EAAA,EAAEC,WAAW,EAAEC,IAAI,EAAE,EAC5BsB,KAAAA,EAAO,EAAEZ,EAAE,EAAE,EACbT,SAAS,EAAEC,IAAI,EAAE2B,KAAAA,EAAO,EAAEA,KAAK,EAAE,GAAG,EAAE,EAAE,EACzC,GAAGhC,GAAAA;QAEJ,IAAI,OAAOa,OAAO,QAAU,EAAA;YAC1B,MAAM,IAAIa,YAAOC,CAAAA,eAAe,CAAC,qBAAA,CAAA;AACnC;AAEA,QAAA,MAAMnB,gBAAgBC,gBAAW,CAAA,QAAA,CAAA;QACjC,MAAM,EAAEM,EAAE,EAAE,GAAG,MAAMC,4DACnBd,WACAe,EAAAA,iBAAAA,CAAQC,MAAM,EACdC,wBACAN,EAAAA,EAAAA,CAAAA;QAGF,IAAIoB,KAAAA,CAAMC,OAAO,CAACF,KAAQ,CAAA,EAAA;YACxB,MAAM,IAAIN,YAAOS,CAAAA,gBAAgB,CAAC,0CAAA,CAAA;AACpC;QAEA,MAAM,EAAEC,UAAU,EAAEC,YAAY,EAAE,GAAG,MAAMC,mCAAqBN,CAAAA,KAAAA,EAAO3B,IAAMkC,EAAAA,MAAAA,CAAAA;QAE7E,MAAMX,IAAAA,GAAQ,MAAMC,yBAAmBQ,CAAAA,YAAAA,CAAAA;AACvC,QAAA,MAAMG,YAAe,GAAA,MAAMhC,aAAciC,CAAAA,OAAO,CAAC5B,EAAI,EAAA;AAAEe,YAAAA,IAAAA;YAAME,IAAMM,EAAAA,UAAU,CAAC,CAAE;SAAI,EAAA;AAAEjC,YAAAA;AAAK,SAAA,CAAA;;AAG3F,QAAA,MAAMuC,UAAa,GAAA,MAAMjC,gBAAW,CAAA,MAAA,CAAA,CAAQkC,YAAY,CAACH,YAAAA,CAAAA;AAEzDxC,QAAAA,GAAAA,CAAIK,IAAI,GAAG,MAAMU,EAAGO,CAAAA,cAAc,CAACoB,UAAY,EAAA;AAAEnB,YAAAA,MAAAA,EAAQN,kBAAQO;AAAK,SAAA,CAAA;AACxE,KAAA;AAEA,IAAA,MAAMoB,aAAY5C,GAAY,EAAA;QAC5B,MAAM,EACJC,OAAO,EAAEC,WAAW,EAAEC,IAAI,EAAE,EAC5BC,OAAS,EAAA,EAAEC,IAAI,EAAE2B,KAAAA,EAAO,EAAEA,KAAK,EAAE,GAAG,EAAE,EAAE,EACzC,GAAGhC,GAAAA;AAEJ,QAAA,MAAMQ,gBAAgBC,gBAAW,CAAA,QAAA,CAAA;AACjC,QAAA,MAAMM,KAAKwB,MAAOM,CAAAA,OAAO,CAAC,mBAAA,CAAA,CAAqBC,wBAAwB,CAAC;YACtEC,OAAS7C,EAAAA,WAAAA;AACTqB,YAAAA,MAAAA,EAAQN,kBAAQ+B,MAAM;YACtBC,KAAO9B,EAAAA;AACT,SAAA,CAAA;QAEA,IAAI,CAACJ,EAAGmC,CAAAA,SAAS,EAAE;AACjB,YAAA,OAAOlD,IAAImD,SAAS,EAAA;AACtB;QAEA,MAAM,EAAEf,UAAU,EAAEC,YAAY,EAAE,GAAG,MAAMC,mCAAqBN,CAAAA,KAAAA,EAAO3B,IAAMkC,EAAAA,MAAAA,CAAAA;QAE7E,MAAMa,eAAAA,GAAkBhB,UAAWiB,CAAAA,MAAM,GAAG,CAAA;QAC5C,MAAMzB,IAAAA,GAAO,MAAMC,yBAAAA,CAAmBQ,YAAce,EAAAA,eAAAA,CAAAA;AAEpD,QAAA,IAAIE,UAAalB,GAAAA,UAAAA;AAEjB,QAAA,IACER,KAAKd,QAAQ,IACbmB,KAAMC,CAAAA,OAAO,CAACN,IAAKd,CAAAA,QAAQ,CAC3BwC,IAAAA,UAAAA,CAAWD,MAAM,KAAKzB,IAAAA,CAAKd,QAAQ,CAACuC,MAAM,EAC1C;;AAEA,YAAA,MAAME,oBAAoB3B,IAAKd,CAAAA,QAAQ,CACpCF,GAAG,CAAC,CAAC4C,IAAAA,GAAAA;gBACJ,OAAOF,UAAAA,CAAWG,IAAI,CAAC,CAAC3B,OAASA,IAAK4B,CAAAA,gBAAgB,KAAKF,IAAAA,CAAKG,IAAI,CAAA;AACtE,aAAA,CAAA,CACCC,MAAM,CAACC,OAAAA,CAAAA;YAEVP,UAAaC,GAAAA,iBAAAA;AACf;;AAGA,QAAA,MAAMO,aAAgB,GAAA,MAAMtD,aAAcuD,CAAAA,MAAM,CAAC;AAAEnC,YAAAA,IAAAA;YAAMI,KAAOsB,EAAAA;SAAc,EAAA;AAAEnD,YAAAA;AAAK,SAAA,CAAA;QACrF,IAAI2D,aAAAA,CAAcE,IAAI,CAAC,CAAClC,OAASA,IAAKmC,CAAAA,IAAI,EAAEC,UAAAA,CAAW,QAAY,CAAA,CAAA,EAAA;YACjE,MAAMzD,gBAAAA,CAAW,SAAW0D,CAAAA,CAAAA,UAAU,CAAC,gBAAA,CAAA;AACzC;AAEA,QAAA,MAAMC,oBAAoB3D,gBAAW,CAAA,YAAA,CAAA;;QAGrC,IAAI,MAAM2D,iBAAkBC,CAAAA,SAAS,EAAI,EAAA;YACvC,IAAI;AACF,gBAAA,MAAMC,eAAkB,GAAA,MAAMF,iBAAkBG,CAAAA,YAAY,CAACT,aAAAA,CAAAA;;AAE7D,gBAAA,MAAMM,iBAAkBI,CAAAA,yBAAyB,CAACV,aAAAA,EAAeQ,eAAiBnE,EAAAA,IAAAA,CAAAA;AACpF,aAAA,CAAE,OAAOsE,KAAO,EAAA;AACdlC,gBAAAA,MAAAA,CAAOmC,GAAG,CAACC,IAAI,CAAC,mEAAqE,EAAA;AACnFF,oBAAAA,KAAAA,EAAOA,KAAiBG,YAAAA,KAAAA,GAAQH,KAAMI,CAAAA,OAAO,GAAGC,MAAOL,CAAAA,KAAAA;AACzD,iBAAA,CAAA;AACF;AACF;;QAGA,MAAMM,WAAAA,GAAc,MAAMpE,WAAMC,CAAAA,GAAG,CAACkD,aAAerD,EAAAA,gBAAAA,CAAW,QAAQkC,YAAY,CAAA;AAElF3C,QAAAA,GAAAA,CAAIK,IAAI,GAAG,MAAMU,EAAGO,CAAAA,cAAc,CAACyD,WAAa,EAAA;AAAExD,YAAAA,MAAAA,EAAQN,kBAAQO;AAAK,SAAA,CAAA;AACvExB,QAAAA,GAAAA,CAAIgF,MAAM,GAAG,GAAA;AACf,KAAA;;AAGA,IAAA,MAAMjB,QAAO/D,GAAY,EAAA;AACvB,QAAA,MAAM,EACJyB,KAAO,EAAA,EAAEZ,EAAE,EAAE,EACbT,OAAS,EAAA,EAAE4B,KAAO,EAAA,EAAEA,KAAK,EAAE,GAAG,EAAE,EAAE,EACnC,GAAGhC,GAAAA;AAEJ,QAAA,IAAIiF,CAAEC,CAAAA,OAAO,CAAClD,KAAAA,CAAAA,IAAW,CAACC,KAAAA,CAAMC,OAAO,CAACF,KAAUA,CAAAA,IAAAA,KAAAA,CAAMmD,IAAI,KAAK,CAAI,EAAA;AACnE,YAAA,IAAItE,EAAI,EAAA;gBACN,OAAO,IAAI,CAACQ,cAAc,CAACrB,GAAAA,CAAAA;AAC7B;YAEA,MAAM,IAAI0B,YAAOS,CAAAA,gBAAgB,CAAC,iBAAA,CAAA;AACpC;QAEA,MAAOtB,CAAAA,EAAAA,GAAK,IAAI,CAACkB,WAAW,GAAG,IAAI,CAACa,WAAU,EAAG5C,GAAAA,CAAAA;AACnD;AACF,CAAE;;;;"}
1
+ {"version":3,"file":"admin-upload.js","sources":["../../../server/src/controllers/admin-upload.ts"],"sourcesContent":["import _ from 'lodash';\nimport { errors, async } from '@strapi/utils';\n\nimport type { Context } from 'koa';\n\nimport { getService } from '../utils';\nimport { ACTIONS, FILE_MODEL_UID } from '../constants';\nimport { validateBulkUpdateBody, validateUploadBody } from './validation/admin/upload';\nimport { findEntityAndCheckPermissions } from './utils/find-entity-and-check-permissions';\nimport { FileInfo } from '../types';\nimport { prepareUploadRequest, type FileUploadError } from '../utils/mime-validation';\nimport type { UploadFileInfo } from '../../../shared/contracts/files';\n\nexport default {\n async bulkUpdateFileInfo(ctx: Context) {\n const {\n state: { userAbility, user },\n request: { body },\n } = ctx;\n\n const { updates } = await validateBulkUpdateBody(body);\n const uploadService = getService('upload');\n\n const results = await async.map(\n updates,\n async ({ id, fileInfo }: { id: number; fileInfo: FileInfo }) => {\n const { pm } = await findEntityAndCheckPermissions(\n userAbility,\n ACTIONS.update,\n FILE_MODEL_UID,\n id\n );\n\n const updated = await uploadService.updateFileInfo(id, fileInfo as any, { user });\n return pm.sanitizeOutput(updated, { action: ACTIONS.read });\n }\n );\n\n ctx.body = results;\n },\n\n async updateFileInfo(ctx: Context) {\n const {\n state: { userAbility, user },\n query: { id },\n request: { body },\n } = ctx;\n\n if (typeof id !== 'string') {\n throw new errors.ValidationError('File id is required');\n }\n\n const uploadService = getService('upload');\n const { pm } = await findEntityAndCheckPermissions(\n userAbility,\n ACTIONS.update,\n FILE_MODEL_UID,\n id\n );\n\n const data = await validateUploadBody(body);\n\n const file = await uploadService.updateFileInfo(id, data.fileInfo as any, { user });\n\n ctx.body = await pm.sanitizeOutput(file, { action: ACTIONS.read });\n },\n\n async replaceFile(ctx: Context) {\n const {\n state: { userAbility, user },\n query: { id },\n request: { body, files: { files } = {} },\n } = ctx;\n\n if (typeof id !== 'string') {\n throw new errors.ValidationError('File id is required');\n }\n\n const uploadService = getService('upload');\n const { pm } = await findEntityAndCheckPermissions(\n userAbility,\n ACTIONS.update,\n FILE_MODEL_UID,\n id\n );\n\n if (Array.isArray(files)) {\n throw new errors.ApplicationError('Cannot replace a file with multiple ones');\n }\n\n const {\n validFiles,\n filteredBody,\n errors: validationErrors,\n } = await prepareUploadRequest(files, body, strapi);\n if (validFiles.length === 0) {\n throw new errors.ValidationError(validationErrors[0].message);\n }\n\n const data = (await validateUploadBody(filteredBody)) as { fileInfo: FileInfo };\n const replacedFile = await uploadService.replace(id, { data, file: validFiles[0] }, { user });\n\n // Sign file urls for private providers\n const signedFile = await getService('file').signFileUrls(replacedFile);\n\n ctx.body = await pm.sanitizeOutput(signedFile, { action: ACTIONS.read });\n },\n\n async uploadFiles(ctx: Context) {\n const {\n state: { userAbility, user },\n request: { body, files: { files } = {} },\n } = ctx;\n\n const uploadService = getService('upload');\n const pm = strapi.service('admin::permission').createPermissionsManager({\n ability: userAbility,\n action: ACTIONS.create,\n model: FILE_MODEL_UID,\n });\n\n if (!pm.isAllowed) {\n return ctx.forbidden();\n }\n\n const {\n validFiles,\n filteredBody,\n errors: validationErrors,\n } = await prepareUploadRequest(files, body, strapi);\n if (validFiles.length === 0) {\n throw new errors.ValidationError(validationErrors[0].message);\n }\n\n const isMultipleFiles = validFiles.length > 1;\n const data = await validateUploadBody(filteredBody, isMultipleFiles);\n\n let filesArray = validFiles;\n\n if (\n data.fileInfo &&\n Array.isArray(data.fileInfo) &&\n filesArray.length === data.fileInfo.length\n ) {\n // Reorder filesArray to match data.fileInfo order\n const alignedFilesArray = data.fileInfo\n .map((info) => {\n return filesArray.find((file) => file.originalFilename === info.name);\n })\n .filter(Boolean) as any[];\n\n filesArray = alignedFilesArray;\n }\n\n // Upload files first to get thumbnails\n const uploadedFiles = await uploadService.upload({ data, files: filesArray }, { user });\n if (uploadedFiles.some((file) => file.mime?.startsWith('image/'))) {\n await getService('metrics').trackUsage('didUploadImage');\n }\n\n const aiMetadataService = getService('aiMetadata');\n\n // AFTER upload - generate AI metadata for images\n if (await aiMetadataService.isEnabled()) {\n try {\n const metadataResults = await aiMetadataService.processFiles(uploadedFiles);\n // Update the uploaded files with AI metadata\n await aiMetadataService.updateFilesWithAIMetadata(uploadedFiles, metadataResults, user);\n } catch (error) {\n strapi.log.warn('AI metadata generation failed, proceeding without AI enhancements', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n // Sign file urls for private providers\n const signedFiles = await async.map(uploadedFiles, getService('file').signFileUrls);\n\n ctx.body = await pm.sanitizeOutput(signedFiles, { action: ACTIONS.read });\n ctx.status = 201;\n },\n\n /**\n * @experimental\n * Stream upload files with SSE streaming for per-file progress\n *\n * Streams Server-Sent Events as each file is validated and uploaded:\n * - file:uploading — when processing starts for a file\n * - file:complete — when a file is successfully uploaded\n * - file:error — when a file fails validation or upload\n * - stream:complete — final summary with all results\n *\n */\n async unstable_uploadFilesStream(ctx: Context) {\n const {\n state: { userAbility, user },\n request: { body, files: { files } = {} },\n } = ctx;\n\n const uploadService = getService('upload');\n const pm = strapi.service('admin::permission').createPermissionsManager({\n ability: userAbility,\n action: ACTIONS.create,\n model: FILE_MODEL_UID,\n });\n\n if (!pm.isAllowed) {\n return ctx.forbidden();\n }\n\n if (_.isEmpty(files) || (!Array.isArray(files) && files.size === 0)) {\n throw new errors.ApplicationError('Files are empty');\n }\n\n // Take manual control of the response for SSE streaming\n ctx.respond = false;\n const res = ctx.res;\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n });\n\n const writeSSE = (event: string, data: Record<string, unknown>) => {\n res.write(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n };\n\n // Normalize files to an array\n const filesArray = Array.isArray(files) ? files : [files];\n const total = filesArray.length;\n\n // Parse fileInfo from body\n // Multipart forms send fileInfo as either:\n // - An array of JSON strings (one per file)\n // - A single JSON string (one file, or a JSON-encoded array)\n let parsedFileInfo: UploadFileInfo[] = [];\n if (body?.fileInfo) {\n const raw = body.fileInfo;\n if (Array.isArray(raw)) {\n parsedFileInfo = raw.map((fi: unknown) =>\n typeof fi === 'string' ? JSON.parse(fi) : fi\n ) as UploadFileInfo[];\n } else if (typeof raw === 'string') {\n const parsed = JSON.parse(raw);\n // Handle case where a single string contains a JSON array\n parsedFileInfo = (Array.isArray(parsed) ? parsed : [parsed]) as UploadFileInfo[];\n } else {\n parsedFileInfo = [raw as UploadFileInfo];\n }\n }\n\n const uploadErrors: FileUploadError[] = [];\n const successfulFiles: any[] = [];\n\n // Process each file sequentially with inline validation\n for (let i = 0; i < filesArray.length; i += 1) {\n const file = filesArray[i];\n const fileName = file.originalFilename || 'unknown';\n const fileInfo: UploadFileInfo = parsedFileInfo[i] || {\n name: fileName,\n caption: null,\n alternativeText: null,\n folder: null,\n };\n\n writeSSE('file:uploading', { name: fileName, index: i, total, size: file.size || 0 });\n\n try {\n // Validate this single file using security checks\n const { validFiles, errors: validationErrors } = await prepareUploadRequest(\n file,\n { fileInfo: JSON.stringify(fileInfo) },\n strapi\n );\n\n if (validFiles.length === 0) {\n const errorMessage = validationErrors[0]?.message || 'Validation failed';\n uploadErrors.push({ name: fileName, message: errorMessage });\n writeSSE('file:error', { name: fileName, index: i, message: errorMessage });\n } else {\n // Validate using the already-parsed single fileInfo object directly\n const data = await validateUploadBody({ fileInfo }, false);\n const [uploadedFile] = await uploadService.upload(\n { data, files: [validFiles[0]] },\n { user }\n );\n\n // Sign file url\n const signedFile = await getService('file').signFileUrls(uploadedFile);\n successfulFiles.push(signedFile);\n\n writeSSE('file:complete', { name: fileName, index: i, file: signedFile });\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n uploadErrors.push({ name: fileName, message: errorMessage });\n writeSSE('file:error', { name: fileName, index: i, message: errorMessage });\n }\n }\n\n // Track image upload metric once if any images were uploaded\n if (successfulFiles.some((file) => file.mime?.startsWith('image/'))) {\n await getService('metrics').trackUsage('didUploadImage');\n }\n\n // Send final stream summary\n writeSSE('stream:complete', {\n data: await pm.sanitizeOutput(successfulFiles, { action: ACTIONS.read }),\n errors: uploadErrors,\n });\n\n res.end();\n },\n\n // TODO: split into multiple endpoints\n async upload(ctx: Context) {\n const {\n query: { id },\n request: { files: { files } = {} },\n } = ctx;\n\n if (_.isEmpty(files) || (!Array.isArray(files) && files.size === 0)) {\n if (id) {\n return this.updateFileInfo(ctx);\n }\n\n throw new errors.ApplicationError('Files are empty');\n }\n\n await (id ? this.replaceFile : this.uploadFiles)(ctx);\n },\n};\n"],"names":["bulkUpdateFileInfo","ctx","state","userAbility","user","request","body","updates","validateBulkUpdateBody","uploadService","getService","results","async","map","id","fileInfo","pm","findEntityAndCheckPermissions","ACTIONS","update","FILE_MODEL_UID","updated","updateFileInfo","sanitizeOutput","action","read","query","errors","ValidationError","data","validateUploadBody","file","replaceFile","files","Array","isArray","ApplicationError","validFiles","filteredBody","validationErrors","prepareUploadRequest","strapi","length","message","replacedFile","replace","signedFile","signFileUrls","uploadFiles","service","createPermissionsManager","ability","create","model","isAllowed","forbidden","isMultipleFiles","filesArray","alignedFilesArray","info","find","originalFilename","name","filter","Boolean","uploadedFiles","upload","some","mime","startsWith","trackUsage","aiMetadataService","isEnabled","metadataResults","processFiles","updateFilesWithAIMetadata","error","log","warn","Error","String","signedFiles","status","unstable_uploadFilesStream","_","isEmpty","size","respond","res","writeHead","Connection","writeSSE","event","write","JSON","stringify","total","parsedFileInfo","raw","fi","parse","parsed","uploadErrors","successfulFiles","i","fileName","caption","alternativeText","folder","index","errorMessage","push","uploadedFile","end"],"mappings":"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"}
@@ -47,7 +47,10 @@ var adminUpload = {
47
47
  if (Array.isArray(files)) {
48
48
  throw new errors.ApplicationError('Cannot replace a file with multiple ones');
49
49
  }
50
- const { validFiles, filteredBody } = await prepareUploadRequest(files, body, strapi);
50
+ const { validFiles, filteredBody, errors: validationErrors } = await prepareUploadRequest(files, body, strapi);
51
+ if (validFiles.length === 0) {
52
+ throw new errors.ValidationError(validationErrors[0].message);
53
+ }
51
54
  const data = await validateUploadBody(filteredBody);
52
55
  const replacedFile = await uploadService.replace(id, {
53
56
  data,
@@ -72,7 +75,10 @@ var adminUpload = {
72
75
  if (!pm.isAllowed) {
73
76
  return ctx.forbidden();
74
77
  }
75
- const { validFiles, filteredBody } = await prepareUploadRequest(files, body, strapi);
78
+ const { validFiles, filteredBody, errors: validationErrors } = await prepareUploadRequest(files, body, strapi);
79
+ if (validFiles.length === 0) {
80
+ throw new errors.ValidationError(validationErrors[0].message);
81
+ }
76
82
  const isMultipleFiles = validFiles.length > 1;
77
83
  const data = await validateUploadBody(filteredBody, isMultipleFiles);
78
84
  let filesArray = validFiles;
@@ -113,6 +119,149 @@ var adminUpload = {
113
119
  });
114
120
  ctx.status = 201;
115
121
  },
122
+ /**
123
+ * @experimental
124
+ * Stream upload files with SSE streaming for per-file progress
125
+ *
126
+ * Streams Server-Sent Events as each file is validated and uploaded:
127
+ * - file:uploading — when processing starts for a file
128
+ * - file:complete — when a file is successfully uploaded
129
+ * - file:error — when a file fails validation or upload
130
+ * - stream:complete — final summary with all results
131
+ *
132
+ */ async unstable_uploadFilesStream (ctx) {
133
+ const { state: { userAbility, user }, request: { body, files: { files } = {} } } = ctx;
134
+ const uploadService = getService('upload');
135
+ const pm = strapi.service('admin::permission').createPermissionsManager({
136
+ ability: userAbility,
137
+ action: ACTIONS.create,
138
+ model: FILE_MODEL_UID
139
+ });
140
+ if (!pm.isAllowed) {
141
+ return ctx.forbidden();
142
+ }
143
+ if (_.isEmpty(files) || !Array.isArray(files) && files.size === 0) {
144
+ throw new errors.ApplicationError('Files are empty');
145
+ }
146
+ // Take manual control of the response for SSE streaming
147
+ ctx.respond = false;
148
+ const res = ctx.res;
149
+ res.writeHead(200, {
150
+ 'Content-Type': 'text/event-stream',
151
+ 'Cache-Control': 'no-cache',
152
+ Connection: 'keep-alive'
153
+ });
154
+ const writeSSE = (event, data)=>{
155
+ res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
156
+ };
157
+ // Normalize files to an array
158
+ const filesArray = Array.isArray(files) ? files : [
159
+ files
160
+ ];
161
+ const total = filesArray.length;
162
+ // Parse fileInfo from body
163
+ // Multipart forms send fileInfo as either:
164
+ // - An array of JSON strings (one per file)
165
+ // - A single JSON string (one file, or a JSON-encoded array)
166
+ let parsedFileInfo = [];
167
+ if (body?.fileInfo) {
168
+ const raw = body.fileInfo;
169
+ if (Array.isArray(raw)) {
170
+ parsedFileInfo = raw.map((fi)=>typeof fi === 'string' ? JSON.parse(fi) : fi);
171
+ } else if (typeof raw === 'string') {
172
+ const parsed = JSON.parse(raw);
173
+ // Handle case where a single string contains a JSON array
174
+ parsedFileInfo = Array.isArray(parsed) ? parsed : [
175
+ parsed
176
+ ];
177
+ } else {
178
+ parsedFileInfo = [
179
+ raw
180
+ ];
181
+ }
182
+ }
183
+ const uploadErrors = [];
184
+ const successfulFiles = [];
185
+ // Process each file sequentially with inline validation
186
+ for(let i = 0; i < filesArray.length; i += 1){
187
+ const file = filesArray[i];
188
+ const fileName = file.originalFilename || 'unknown';
189
+ const fileInfo = parsedFileInfo[i] || {
190
+ name: fileName,
191
+ caption: null,
192
+ alternativeText: null,
193
+ folder: null
194
+ };
195
+ writeSSE('file:uploading', {
196
+ name: fileName,
197
+ index: i,
198
+ total,
199
+ size: file.size || 0
200
+ });
201
+ try {
202
+ // Validate this single file using security checks
203
+ const { validFiles, errors: validationErrors } = await prepareUploadRequest(file, {
204
+ fileInfo: JSON.stringify(fileInfo)
205
+ }, strapi);
206
+ if (validFiles.length === 0) {
207
+ const errorMessage = validationErrors[0]?.message || 'Validation failed';
208
+ uploadErrors.push({
209
+ name: fileName,
210
+ message: errorMessage
211
+ });
212
+ writeSSE('file:error', {
213
+ name: fileName,
214
+ index: i,
215
+ message: errorMessage
216
+ });
217
+ } else {
218
+ // Validate using the already-parsed single fileInfo object directly
219
+ const data = await validateUploadBody({
220
+ fileInfo
221
+ }, false);
222
+ const [uploadedFile] = await uploadService.upload({
223
+ data,
224
+ files: [
225
+ validFiles[0]
226
+ ]
227
+ }, {
228
+ user
229
+ });
230
+ // Sign file url
231
+ const signedFile = await getService('file').signFileUrls(uploadedFile);
232
+ successfulFiles.push(signedFile);
233
+ writeSSE('file:complete', {
234
+ name: fileName,
235
+ index: i,
236
+ file: signedFile
237
+ });
238
+ }
239
+ } catch (error) {
240
+ const errorMessage = error instanceof Error ? error.message : String(error);
241
+ uploadErrors.push({
242
+ name: fileName,
243
+ message: errorMessage
244
+ });
245
+ writeSSE('file:error', {
246
+ name: fileName,
247
+ index: i,
248
+ message: errorMessage
249
+ });
250
+ }
251
+ }
252
+ // Track image upload metric once if any images were uploaded
253
+ if (successfulFiles.some((file)=>file.mime?.startsWith('image/'))) {
254
+ await getService('metrics').trackUsage('didUploadImage');
255
+ }
256
+ // Send final stream summary
257
+ writeSSE('stream:complete', {
258
+ data: await pm.sanitizeOutput(successfulFiles, {
259
+ action: ACTIONS.read
260
+ }),
261
+ errors: uploadErrors
262
+ });
263
+ res.end();
264
+ },
116
265
  // TODO: split into multiple endpoints
117
266
  async upload (ctx) {
118
267
  const { query: { id }, request: { files: { files } = {} } } = ctx;
@@ -1 +1 @@
1
- {"version":3,"file":"admin-upload.mjs","sources":["../../../server/src/controllers/admin-upload.ts"],"sourcesContent":["import _ from 'lodash';\nimport { errors, async } from '@strapi/utils';\n\nimport type { Context } from 'koa';\n\nimport { getService } from '../utils';\nimport { ACTIONS, FILE_MODEL_UID } from '../constants';\nimport { validateBulkUpdateBody, validateUploadBody } from './validation/admin/upload';\nimport { findEntityAndCheckPermissions } from './utils/find-entity-and-check-permissions';\nimport { FileInfo } from '../types';\nimport { prepareUploadRequest } from '../utils/mime-validation';\n\nexport default {\n async bulkUpdateFileInfo(ctx: Context) {\n const {\n state: { userAbility, user },\n request: { body },\n } = ctx;\n\n const { updates } = await validateBulkUpdateBody(body);\n const uploadService = getService('upload');\n\n const results = await async.map(\n updates,\n async ({ id, fileInfo }: { id: number; fileInfo: FileInfo }) => {\n const { pm } = await findEntityAndCheckPermissions(\n userAbility,\n ACTIONS.update,\n FILE_MODEL_UID,\n id\n );\n\n const updated = await uploadService.updateFileInfo(id, fileInfo as any, { user });\n return pm.sanitizeOutput(updated, { action: ACTIONS.read });\n }\n );\n\n ctx.body = results;\n },\n\n async updateFileInfo(ctx: Context) {\n const {\n state: { userAbility, user },\n query: { id },\n request: { body },\n } = ctx;\n\n if (typeof id !== 'string') {\n throw new errors.ValidationError('File id is required');\n }\n\n const uploadService = getService('upload');\n const { pm } = await findEntityAndCheckPermissions(\n userAbility,\n ACTIONS.update,\n FILE_MODEL_UID,\n id\n );\n\n const data = await validateUploadBody(body);\n\n const file = await uploadService.updateFileInfo(id, data.fileInfo as any, { user });\n\n ctx.body = await pm.sanitizeOutput(file, { action: ACTIONS.read });\n },\n\n async replaceFile(ctx: Context) {\n const {\n state: { userAbility, user },\n query: { id },\n request: { body, files: { files } = {} },\n } = ctx;\n\n if (typeof id !== 'string') {\n throw new errors.ValidationError('File id is required');\n }\n\n const uploadService = getService('upload');\n const { pm } = await findEntityAndCheckPermissions(\n userAbility,\n ACTIONS.update,\n FILE_MODEL_UID,\n id\n );\n\n if (Array.isArray(files)) {\n throw new errors.ApplicationError('Cannot replace a file with multiple ones');\n }\n\n const { validFiles, filteredBody } = await prepareUploadRequest(files, body, strapi);\n\n const data = (await validateUploadBody(filteredBody)) as { fileInfo: FileInfo };\n const replacedFile = await uploadService.replace(id, { data, file: validFiles[0] }, { user });\n\n // Sign file urls for private providers\n const signedFile = await getService('file').signFileUrls(replacedFile);\n\n ctx.body = await pm.sanitizeOutput(signedFile, { action: ACTIONS.read });\n },\n\n async uploadFiles(ctx: Context) {\n const {\n state: { userAbility, user },\n request: { body, files: { files } = {} },\n } = ctx;\n\n const uploadService = getService('upload');\n const pm = strapi.service('admin::permission').createPermissionsManager({\n ability: userAbility,\n action: ACTIONS.create,\n model: FILE_MODEL_UID,\n });\n\n if (!pm.isAllowed) {\n return ctx.forbidden();\n }\n\n const { validFiles, filteredBody } = await prepareUploadRequest(files, body, strapi);\n\n const isMultipleFiles = validFiles.length > 1;\n const data = await validateUploadBody(filteredBody, isMultipleFiles);\n\n let filesArray = validFiles;\n\n if (\n data.fileInfo &&\n Array.isArray(data.fileInfo) &&\n filesArray.length === data.fileInfo.length\n ) {\n // Reorder filesArray to match data.fileInfo order\n const alignedFilesArray = data.fileInfo\n .map((info) => {\n return filesArray.find((file) => file.originalFilename === info.name);\n })\n .filter(Boolean) as any[];\n\n filesArray = alignedFilesArray;\n }\n\n // Upload files first to get thumbnails\n const uploadedFiles = await uploadService.upload({ data, files: filesArray }, { user });\n if (uploadedFiles.some((file) => file.mime?.startsWith('image/'))) {\n await getService('metrics').trackUsage('didUploadImage');\n }\n\n const aiMetadataService = getService('aiMetadata');\n\n // AFTER upload - generate AI metadata for images\n if (await aiMetadataService.isEnabled()) {\n try {\n const metadataResults = await aiMetadataService.processFiles(uploadedFiles);\n // Update the uploaded files with AI metadata\n await aiMetadataService.updateFilesWithAIMetadata(uploadedFiles, metadataResults, user);\n } catch (error) {\n strapi.log.warn('AI metadata generation failed, proceeding without AI enhancements', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n // Sign file urls for private providers\n const signedFiles = await async.map(uploadedFiles, getService('file').signFileUrls);\n\n ctx.body = await pm.sanitizeOutput(signedFiles, { action: ACTIONS.read });\n ctx.status = 201;\n },\n\n // TODO: split into multiple endpoints\n async upload(ctx: Context) {\n const {\n query: { id },\n request: { files: { files } = {} },\n } = ctx;\n\n if (_.isEmpty(files) || (!Array.isArray(files) && files.size === 0)) {\n if (id) {\n return this.updateFileInfo(ctx);\n }\n\n throw new errors.ApplicationError('Files are empty');\n }\n\n await (id ? this.replaceFile : this.uploadFiles)(ctx);\n },\n};\n"],"names":["bulkUpdateFileInfo","ctx","state","userAbility","user","request","body","updates","validateBulkUpdateBody","uploadService","getService","results","async","map","id","fileInfo","pm","findEntityAndCheckPermissions","ACTIONS","update","FILE_MODEL_UID","updated","updateFileInfo","sanitizeOutput","action","read","query","errors","ValidationError","data","validateUploadBody","file","replaceFile","files","Array","isArray","ApplicationError","validFiles","filteredBody","prepareUploadRequest","strapi","replacedFile","replace","signedFile","signFileUrls","uploadFiles","service","createPermissionsManager","ability","create","model","isAllowed","forbidden","isMultipleFiles","length","filesArray","alignedFilesArray","info","find","originalFilename","name","filter","Boolean","uploadedFiles","upload","some","mime","startsWith","trackUsage","aiMetadataService","isEnabled","metadataResults","processFiles","updateFilesWithAIMetadata","error","log","warn","Error","message","String","signedFiles","status","_","isEmpty","size"],"mappings":"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"}
1
+ {"version":3,"file":"admin-upload.mjs","sources":["../../../server/src/controllers/admin-upload.ts"],"sourcesContent":["import _ from 'lodash';\nimport { errors, async } from '@strapi/utils';\n\nimport type { Context } from 'koa';\n\nimport { getService } from '../utils';\nimport { ACTIONS, FILE_MODEL_UID } from '../constants';\nimport { validateBulkUpdateBody, validateUploadBody } from './validation/admin/upload';\nimport { findEntityAndCheckPermissions } from './utils/find-entity-and-check-permissions';\nimport { FileInfo } from '../types';\nimport { prepareUploadRequest, type FileUploadError } from '../utils/mime-validation';\nimport type { UploadFileInfo } from '../../../shared/contracts/files';\n\nexport default {\n async bulkUpdateFileInfo(ctx: Context) {\n const {\n state: { userAbility, user },\n request: { body },\n } = ctx;\n\n const { updates } = await validateBulkUpdateBody(body);\n const uploadService = getService('upload');\n\n const results = await async.map(\n updates,\n async ({ id, fileInfo }: { id: number; fileInfo: FileInfo }) => {\n const { pm } = await findEntityAndCheckPermissions(\n userAbility,\n ACTIONS.update,\n FILE_MODEL_UID,\n id\n );\n\n const updated = await uploadService.updateFileInfo(id, fileInfo as any, { user });\n return pm.sanitizeOutput(updated, { action: ACTIONS.read });\n }\n );\n\n ctx.body = results;\n },\n\n async updateFileInfo(ctx: Context) {\n const {\n state: { userAbility, user },\n query: { id },\n request: { body },\n } = ctx;\n\n if (typeof id !== 'string') {\n throw new errors.ValidationError('File id is required');\n }\n\n const uploadService = getService('upload');\n const { pm } = await findEntityAndCheckPermissions(\n userAbility,\n ACTIONS.update,\n FILE_MODEL_UID,\n id\n );\n\n const data = await validateUploadBody(body);\n\n const file = await uploadService.updateFileInfo(id, data.fileInfo as any, { user });\n\n ctx.body = await pm.sanitizeOutput(file, { action: ACTIONS.read });\n },\n\n async replaceFile(ctx: Context) {\n const {\n state: { userAbility, user },\n query: { id },\n request: { body, files: { files } = {} },\n } = ctx;\n\n if (typeof id !== 'string') {\n throw new errors.ValidationError('File id is required');\n }\n\n const uploadService = getService('upload');\n const { pm } = await findEntityAndCheckPermissions(\n userAbility,\n ACTIONS.update,\n FILE_MODEL_UID,\n id\n );\n\n if (Array.isArray(files)) {\n throw new errors.ApplicationError('Cannot replace a file with multiple ones');\n }\n\n const {\n validFiles,\n filteredBody,\n errors: validationErrors,\n } = await prepareUploadRequest(files, body, strapi);\n if (validFiles.length === 0) {\n throw new errors.ValidationError(validationErrors[0].message);\n }\n\n const data = (await validateUploadBody(filteredBody)) as { fileInfo: FileInfo };\n const replacedFile = await uploadService.replace(id, { data, file: validFiles[0] }, { user });\n\n // Sign file urls for private providers\n const signedFile = await getService('file').signFileUrls(replacedFile);\n\n ctx.body = await pm.sanitizeOutput(signedFile, { action: ACTIONS.read });\n },\n\n async uploadFiles(ctx: Context) {\n const {\n state: { userAbility, user },\n request: { body, files: { files } = {} },\n } = ctx;\n\n const uploadService = getService('upload');\n const pm = strapi.service('admin::permission').createPermissionsManager({\n ability: userAbility,\n action: ACTIONS.create,\n model: FILE_MODEL_UID,\n });\n\n if (!pm.isAllowed) {\n return ctx.forbidden();\n }\n\n const {\n validFiles,\n filteredBody,\n errors: validationErrors,\n } = await prepareUploadRequest(files, body, strapi);\n if (validFiles.length === 0) {\n throw new errors.ValidationError(validationErrors[0].message);\n }\n\n const isMultipleFiles = validFiles.length > 1;\n const data = await validateUploadBody(filteredBody, isMultipleFiles);\n\n let filesArray = validFiles;\n\n if (\n data.fileInfo &&\n Array.isArray(data.fileInfo) &&\n filesArray.length === data.fileInfo.length\n ) {\n // Reorder filesArray to match data.fileInfo order\n const alignedFilesArray = data.fileInfo\n .map((info) => {\n return filesArray.find((file) => file.originalFilename === info.name);\n })\n .filter(Boolean) as any[];\n\n filesArray = alignedFilesArray;\n }\n\n // Upload files first to get thumbnails\n const uploadedFiles = await uploadService.upload({ data, files: filesArray }, { user });\n if (uploadedFiles.some((file) => file.mime?.startsWith('image/'))) {\n await getService('metrics').trackUsage('didUploadImage');\n }\n\n const aiMetadataService = getService('aiMetadata');\n\n // AFTER upload - generate AI metadata for images\n if (await aiMetadataService.isEnabled()) {\n try {\n const metadataResults = await aiMetadataService.processFiles(uploadedFiles);\n // Update the uploaded files with AI metadata\n await aiMetadataService.updateFilesWithAIMetadata(uploadedFiles, metadataResults, user);\n } catch (error) {\n strapi.log.warn('AI metadata generation failed, proceeding without AI enhancements', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n // Sign file urls for private providers\n const signedFiles = await async.map(uploadedFiles, getService('file').signFileUrls);\n\n ctx.body = await pm.sanitizeOutput(signedFiles, { action: ACTIONS.read });\n ctx.status = 201;\n },\n\n /**\n * @experimental\n * Stream upload files with SSE streaming for per-file progress\n *\n * Streams Server-Sent Events as each file is validated and uploaded:\n * - file:uploading — when processing starts for a file\n * - file:complete — when a file is successfully uploaded\n * - file:error — when a file fails validation or upload\n * - stream:complete — final summary with all results\n *\n */\n async unstable_uploadFilesStream(ctx: Context) {\n const {\n state: { userAbility, user },\n request: { body, files: { files } = {} },\n } = ctx;\n\n const uploadService = getService('upload');\n const pm = strapi.service('admin::permission').createPermissionsManager({\n ability: userAbility,\n action: ACTIONS.create,\n model: FILE_MODEL_UID,\n });\n\n if (!pm.isAllowed) {\n return ctx.forbidden();\n }\n\n if (_.isEmpty(files) || (!Array.isArray(files) && files.size === 0)) {\n throw new errors.ApplicationError('Files are empty');\n }\n\n // Take manual control of the response for SSE streaming\n ctx.respond = false;\n const res = ctx.res;\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n });\n\n const writeSSE = (event: string, data: Record<string, unknown>) => {\n res.write(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n };\n\n // Normalize files to an array\n const filesArray = Array.isArray(files) ? files : [files];\n const total = filesArray.length;\n\n // Parse fileInfo from body\n // Multipart forms send fileInfo as either:\n // - An array of JSON strings (one per file)\n // - A single JSON string (one file, or a JSON-encoded array)\n let parsedFileInfo: UploadFileInfo[] = [];\n if (body?.fileInfo) {\n const raw = body.fileInfo;\n if (Array.isArray(raw)) {\n parsedFileInfo = raw.map((fi: unknown) =>\n typeof fi === 'string' ? JSON.parse(fi) : fi\n ) as UploadFileInfo[];\n } else if (typeof raw === 'string') {\n const parsed = JSON.parse(raw);\n // Handle case where a single string contains a JSON array\n parsedFileInfo = (Array.isArray(parsed) ? parsed : [parsed]) as UploadFileInfo[];\n } else {\n parsedFileInfo = [raw as UploadFileInfo];\n }\n }\n\n const uploadErrors: FileUploadError[] = [];\n const successfulFiles: any[] = [];\n\n // Process each file sequentially with inline validation\n for (let i = 0; i < filesArray.length; i += 1) {\n const file = filesArray[i];\n const fileName = file.originalFilename || 'unknown';\n const fileInfo: UploadFileInfo = parsedFileInfo[i] || {\n name: fileName,\n caption: null,\n alternativeText: null,\n folder: null,\n };\n\n writeSSE('file:uploading', { name: fileName, index: i, total, size: file.size || 0 });\n\n try {\n // Validate this single file using security checks\n const { validFiles, errors: validationErrors } = await prepareUploadRequest(\n file,\n { fileInfo: JSON.stringify(fileInfo) },\n strapi\n );\n\n if (validFiles.length === 0) {\n const errorMessage = validationErrors[0]?.message || 'Validation failed';\n uploadErrors.push({ name: fileName, message: errorMessage });\n writeSSE('file:error', { name: fileName, index: i, message: errorMessage });\n } else {\n // Validate using the already-parsed single fileInfo object directly\n const data = await validateUploadBody({ fileInfo }, false);\n const [uploadedFile] = await uploadService.upload(\n { data, files: [validFiles[0]] },\n { user }\n );\n\n // Sign file url\n const signedFile = await getService('file').signFileUrls(uploadedFile);\n successfulFiles.push(signedFile);\n\n writeSSE('file:complete', { name: fileName, index: i, file: signedFile });\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n uploadErrors.push({ name: fileName, message: errorMessage });\n writeSSE('file:error', { name: fileName, index: i, message: errorMessage });\n }\n }\n\n // Track image upload metric once if any images were uploaded\n if (successfulFiles.some((file) => file.mime?.startsWith('image/'))) {\n await getService('metrics').trackUsage('didUploadImage');\n }\n\n // Send final stream summary\n writeSSE('stream:complete', {\n data: await pm.sanitizeOutput(successfulFiles, { action: ACTIONS.read }),\n errors: uploadErrors,\n });\n\n res.end();\n },\n\n // TODO: split into multiple endpoints\n async upload(ctx: Context) {\n const {\n query: { id },\n request: { files: { files } = {} },\n } = ctx;\n\n if (_.isEmpty(files) || (!Array.isArray(files) && files.size === 0)) {\n if (id) {\n return this.updateFileInfo(ctx);\n }\n\n throw new errors.ApplicationError('Files are empty');\n }\n\n await (id ? this.replaceFile : this.uploadFiles)(ctx);\n },\n};\n"],"names":["bulkUpdateFileInfo","ctx","state","userAbility","user","request","body","updates","validateBulkUpdateBody","uploadService","getService","results","async","map","id","fileInfo","pm","findEntityAndCheckPermissions","ACTIONS","update","FILE_MODEL_UID","updated","updateFileInfo","sanitizeOutput","action","read","query","errors","ValidationError","data","validateUploadBody","file","replaceFile","files","Array","isArray","ApplicationError","validFiles","filteredBody","validationErrors","prepareUploadRequest","strapi","length","message","replacedFile","replace","signedFile","signFileUrls","uploadFiles","service","createPermissionsManager","ability","create","model","isAllowed","forbidden","isMultipleFiles","filesArray","alignedFilesArray","info","find","originalFilename","name","filter","Boolean","uploadedFiles","upload","some","mime","startsWith","trackUsage","aiMetadataService","isEnabled","metadataResults","processFiles","updateFilesWithAIMetadata","error","log","warn","Error","String","signedFiles","status","unstable_uploadFilesStream","_","isEmpty","size","respond","res","writeHead","Connection","writeSSE","event","write","JSON","stringify","total","parsedFileInfo","raw","fi","parse","parsed","uploadErrors","successfulFiles","i","fileName","caption","alternativeText","folder","index","errorMessage","push","uploadedFile","end"],"mappings":"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"}