@strapi/upload 5.55.1 → 5.57.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 (304) hide show
  1. package/dist/admin/hooks/useAIMetadataAvailability.js +14 -0
  2. package/dist/admin/hooks/useAIMetadataAvailability.js.map +1 -0
  3. package/dist/admin/hooks/useAIMetadataAvailability.mjs +12 -0
  4. package/dist/admin/hooks/useAIMetadataAvailability.mjs.map +1 -0
  5. package/dist/admin/hooks/useAIMetadataEnabled.js +7 -5
  6. package/dist/admin/hooks/useAIMetadataEnabled.js.map +1 -1
  7. package/dist/admin/hooks/useAIMetadataEnabled.mjs +7 -5
  8. package/dist/admin/hooks/useAIMetadataEnabled.mjs.map +1 -1
  9. package/dist/admin/hooks/useTracking.js +4 -10
  10. package/dist/admin/hooks/useTracking.js.map +1 -1
  11. package/dist/admin/hooks/useTracking.mjs +5 -10
  12. package/dist/admin/hooks/useTracking.mjs.map +1 -1
  13. package/dist/admin/legacy/hooks/useAIMetadataAvailability.js +14 -0
  14. package/dist/admin/legacy/hooks/useAIMetadataAvailability.js.map +1 -0
  15. package/dist/admin/legacy/hooks/useAIMetadataAvailability.mjs +12 -0
  16. package/dist/admin/legacy/hooks/useAIMetadataAvailability.mjs.map +1 -0
  17. package/dist/admin/legacy/hooks/useAIMetadataEnabled.js +23 -0
  18. package/dist/admin/legacy/hooks/useAIMetadataEnabled.js.map +1 -0
  19. package/dist/admin/legacy/hooks/useAIMetadataEnabled.mjs +21 -0
  20. package/dist/admin/legacy/hooks/useAIMetadataEnabled.mjs.map +1 -0
  21. package/dist/admin/legacy/hooks/useTracking.js +5 -3
  22. package/dist/admin/legacy/hooks/useTracking.js.map +1 -1
  23. package/dist/admin/legacy/hooks/useTracking.mjs +5 -3
  24. package/dist/admin/legacy/hooks/useTracking.mjs.map +1 -1
  25. package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.js +2 -1
  26. package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.js.map +1 -1
  27. package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.mjs +2 -1
  28. package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.mjs.map +1 -1
  29. package/dist/admin/legacy/pages/App/MediaLibrary.js +6 -5
  30. package/dist/admin/legacy/pages/App/MediaLibrary.js.map +1 -1
  31. package/dist/admin/legacy/pages/App/MediaLibrary.mjs +6 -5
  32. package/dist/admin/legacy/pages/App/MediaLibrary.mjs.map +1 -1
  33. package/dist/admin/pages/Assets/AssetsPage.js +24 -12
  34. package/dist/admin/pages/Assets/AssetsPage.js.map +1 -1
  35. package/dist/admin/pages/Assets/AssetsPage.mjs +25 -13
  36. package/dist/admin/pages/Assets/AssetsPage.mjs.map +1 -1
  37. package/dist/admin/pages/Assets/components/AssetActions.js +326 -0
  38. package/dist/admin/pages/Assets/components/AssetActions.js.map +1 -0
  39. package/dist/admin/pages/Assets/components/AssetActions.mjs +324 -0
  40. package/dist/admin/pages/Assets/components/AssetActions.mjs.map +1 -0
  41. package/dist/admin/pages/Assets/components/AssetActionsMenu.js +28 -309
  42. package/dist/admin/pages/Assets/components/AssetActionsMenu.js.map +1 -1
  43. package/dist/admin/pages/Assets/components/AssetActionsMenu.mjs +31 -312
  44. package/dist/admin/pages/Assets/components/AssetActionsMenu.mjs.map +1 -1
  45. package/dist/admin/pages/Assets/components/AssetContextMenu.js +79 -0
  46. package/dist/admin/pages/Assets/components/AssetContextMenu.js.map +1 -0
  47. package/dist/admin/pages/Assets/components/AssetContextMenu.mjs +77 -0
  48. package/dist/admin/pages/Assets/components/AssetContextMenu.mjs.map +1 -0
  49. package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.js +87 -5
  50. package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.js.map +1 -1
  51. package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.mjs +89 -7
  52. package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.mjs.map +1 -1
  53. package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +3 -3
  54. package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
  55. package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +3 -3
  56. package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
  57. package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.js +62 -5
  58. package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.js.map +1 -1
  59. package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.mjs +60 -6
  60. package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.mjs.map +1 -1
  61. package/dist/admin/pages/Assets/components/AssetsGrid.js +27 -4
  62. package/dist/admin/pages/Assets/components/AssetsGrid.js.map +1 -1
  63. package/dist/admin/pages/Assets/components/AssetsGrid.mjs +27 -4
  64. package/dist/admin/pages/Assets/components/AssetsGrid.mjs.map +1 -1
  65. package/dist/admin/pages/Assets/components/AssetsTable.js +27 -4
  66. package/dist/admin/pages/Assets/components/AssetsTable.js.map +1 -1
  67. package/dist/admin/pages/Assets/components/AssetsTable.mjs +27 -4
  68. package/dist/admin/pages/Assets/components/AssetsTable.mjs.map +1 -1
  69. package/dist/admin/pages/Assets/components/CursorAnchoredMenu.js +77 -0
  70. package/dist/admin/pages/Assets/components/CursorAnchoredMenu.js.map +1 -0
  71. package/dist/admin/pages/Assets/components/CursorAnchoredMenu.mjs +75 -0
  72. package/dist/admin/pages/Assets/components/CursorAnchoredMenu.mjs.map +1 -0
  73. package/dist/admin/pages/Assets/components/DeleteItemsDialog.js +4 -2
  74. package/dist/admin/pages/Assets/components/DeleteItemsDialog.js.map +1 -1
  75. package/dist/admin/pages/Assets/components/DeleteItemsDialog.mjs +4 -2
  76. package/dist/admin/pages/Assets/components/DeleteItemsDialog.mjs.map +1 -1
  77. package/dist/admin/pages/Assets/components/FolderActions.js +139 -0
  78. package/dist/admin/pages/Assets/components/FolderActions.js.map +1 -0
  79. package/dist/admin/pages/Assets/components/FolderActions.mjs +137 -0
  80. package/dist/admin/pages/Assets/components/FolderActions.mjs.map +1 -0
  81. package/dist/admin/pages/Assets/components/FolderActionsMenu.js +25 -119
  82. package/dist/admin/pages/Assets/components/FolderActionsMenu.js.map +1 -1
  83. package/dist/admin/pages/Assets/components/FolderActionsMenu.mjs +27 -121
  84. package/dist/admin/pages/Assets/components/FolderActionsMenu.mjs.map +1 -1
  85. package/dist/admin/pages/Assets/components/FolderContextMenu.js +67 -0
  86. package/dist/admin/pages/Assets/components/FolderContextMenu.js.map +1 -0
  87. package/dist/admin/pages/Assets/components/FolderContextMenu.mjs +65 -0
  88. package/dist/admin/pages/Assets/components/FolderContextMenu.mjs.map +1 -0
  89. package/dist/admin/pages/Assets/components/FolderTree/FolderTree.js +139 -45
  90. package/dist/admin/pages/Assets/components/FolderTree/FolderTree.js.map +1 -1
  91. package/dist/admin/pages/Assets/components/FolderTree/FolderTree.mjs +140 -47
  92. package/dist/admin/pages/Assets/components/FolderTree/FolderTree.mjs.map +1 -1
  93. package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.js +115 -0
  94. package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.js.map +1 -0
  95. package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.mjs +113 -0
  96. package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.mjs.map +1 -0
  97. package/dist/admin/pages/Assets/components/ItemContextMenu.js +125 -0
  98. package/dist/admin/pages/Assets/components/ItemContextMenu.js.map +1 -0
  99. package/dist/admin/pages/Assets/components/ItemContextMenu.mjs +122 -0
  100. package/dist/admin/pages/Assets/components/ItemContextMenu.mjs.map +1 -0
  101. package/dist/admin/pages/Assets/components/MainAreaContextMenu.js +88 -63
  102. package/dist/admin/pages/Assets/components/MainAreaContextMenu.js.map +1 -1
  103. package/dist/admin/pages/Assets/components/MainAreaContextMenu.mjs +88 -63
  104. package/dist/admin/pages/Assets/components/MainAreaContextMenu.mjs.map +1 -1
  105. package/dist/admin/pages/Assets/components/SelectionActions.js +111 -0
  106. package/dist/admin/pages/Assets/components/SelectionActions.js.map +1 -0
  107. package/dist/admin/pages/Assets/components/SelectionActions.mjs +109 -0
  108. package/dist/admin/pages/Assets/components/SelectionActions.mjs.map +1 -0
  109. package/dist/admin/pages/Assets/components/SelectionContextMenu.js +82 -0
  110. package/dist/admin/pages/Assets/components/SelectionContextMenu.js.map +1 -0
  111. package/dist/admin/pages/Assets/components/SelectionContextMenu.mjs +80 -0
  112. package/dist/admin/pages/Assets/components/SelectionContextMenu.mjs.map +1 -0
  113. package/dist/admin/pages/Assets/hooks/useAssetSelection.js +10 -0
  114. package/dist/admin/pages/Assets/hooks/useAssetSelection.js.map +1 -1
  115. package/dist/admin/pages/Assets/hooks/useAssetSelection.mjs +10 -0
  116. package/dist/admin/pages/Assets/hooks/useAssetSelection.mjs.map +1 -1
  117. package/dist/admin/pages/SettingsPage/SettingsPage.js +10 -3
  118. package/dist/admin/pages/SettingsPage/SettingsPage.js.map +1 -1
  119. package/dist/admin/pages/SettingsPage/SettingsPage.mjs +10 -3
  120. package/dist/admin/pages/SettingsPage/SettingsPage.mjs.map +1 -1
  121. package/dist/admin/services/api.js.map +1 -1
  122. package/dist/admin/services/api.mjs.map +1 -1
  123. package/dist/admin/src/hooks/useAIMetadataAvailability.d.ts +5 -0
  124. package/dist/admin/src/hooks/useAIMetadataEnabled.d.ts +5 -3
  125. package/dist/admin/src/hooks/useTracking.d.ts +0 -7
  126. package/dist/admin/src/legacy/components/EditAssetDialog/PreviewBox/PreviewComponents.d.ts +2 -2
  127. package/dist/admin/src/legacy/hooks/useAIMetadataAvailability.d.ts +5 -0
  128. package/dist/admin/src/legacy/hooks/useAIMetadataEnabled.d.ts +12 -0
  129. package/dist/admin/src/legacy/hooks/useSettings.d.ts +1 -0
  130. package/dist/admin/src/pages/Assets/components/ActionsMenuContent.d.ts +2 -2
  131. package/dist/admin/src/pages/Assets/components/AssetActions.d.ts +70 -0
  132. package/dist/admin/src/pages/Assets/components/AssetActionsMenu.d.ts +5 -14
  133. package/dist/admin/src/pages/Assets/components/AssetContextMenu.d.ts +27 -0
  134. package/dist/admin/src/pages/Assets/components/AssetDetails/useCropImg.d.ts +16 -0
  135. package/dist/admin/src/pages/Assets/components/CursorAnchoredMenu.d.ts +32 -0
  136. package/dist/admin/src/pages/Assets/components/FolderActions.d.ts +48 -0
  137. package/dist/admin/src/pages/Assets/components/FolderActionsMenu.d.ts +4 -8
  138. package/dist/admin/src/pages/Assets/components/FolderContextMenu.d.ts +21 -0
  139. package/dist/admin/src/pages/Assets/components/FolderTree/FolderTree.d.ts +5 -1
  140. package/dist/admin/src/pages/Assets/components/FolderTree/FolderTreeRowMenu.d.ts +25 -0
  141. package/dist/admin/src/pages/Assets/components/ItemContextMenu.d.ts +53 -0
  142. package/dist/admin/src/pages/Assets/components/MainAreaContextMenu.d.ts +15 -1
  143. package/dist/admin/src/pages/Assets/components/SelectionActions.d.ts +40 -0
  144. package/dist/admin/src/pages/Assets/components/SelectionContextMenu.d.ts +25 -0
  145. package/dist/admin/src/pages/Assets/hooks/useAssetSelection.d.ts +5 -0
  146. package/dist/admin/src/utils/mediaLibraryVersion.d.ts +18 -0
  147. package/dist/admin/translations/en.json.js +9 -3
  148. package/dist/admin/translations/en.json.js.map +1 -1
  149. package/dist/admin/translations/en.json.mjs +9 -3
  150. package/dist/admin/translations/en.json.mjs.map +1 -1
  151. package/dist/admin/utils/mediaLibraryVersion.js +24 -0
  152. package/dist/admin/utils/mediaLibraryVersion.js.map +1 -0
  153. package/dist/admin/utils/mediaLibraryVersion.mjs +20 -0
  154. package/dist/admin/utils/mediaLibraryVersion.mjs.map +1 -0
  155. package/dist/server/bootstrap.js +7 -0
  156. package/dist/server/bootstrap.js.map +1 -1
  157. package/dist/server/bootstrap.mjs +7 -0
  158. package/dist/server/bootstrap.mjs.map +1 -1
  159. package/dist/server/controllers/admin-file.js +2 -2
  160. package/dist/server/controllers/admin-file.js.map +1 -1
  161. package/dist/server/controllers/admin-file.mjs +2 -2
  162. package/dist/server/controllers/admin-file.mjs.map +1 -1
  163. package/dist/server/controllers/admin-folder-file.js +51 -4
  164. package/dist/server/controllers/admin-folder-file.js.map +1 -1
  165. package/dist/server/controllers/admin-folder-file.mjs +52 -5
  166. package/dist/server/controllers/admin-folder-file.mjs.map +1 -1
  167. package/dist/server/controllers/admin-folder.js +5 -5
  168. package/dist/server/controllers/admin-folder.js.map +1 -1
  169. package/dist/server/controllers/admin-folder.mjs +5 -5
  170. package/dist/server/controllers/admin-folder.mjs.map +1 -1
  171. package/dist/server/controllers/admin-settings.js +2 -1
  172. package/dist/server/controllers/admin-settings.js.map +1 -1
  173. package/dist/server/controllers/admin-settings.mjs +2 -1
  174. package/dist/server/controllers/admin-settings.mjs.map +1 -1
  175. package/dist/server/controllers/content-api.js +30 -7
  176. package/dist/server/controllers/content-api.js.map +1 -1
  177. package/dist/server/controllers/content-api.mjs +30 -7
  178. package/dist/server/controllers/content-api.mjs.map +1 -1
  179. package/dist/server/controllers/validation/admin/folder-file.js +4 -4
  180. package/dist/server/controllers/validation/admin/folder-file.js.map +1 -1
  181. package/dist/server/controllers/validation/admin/folder-file.mjs +2 -2
  182. package/dist/server/controllers/validation/admin/folder-file.mjs.map +1 -1
  183. package/dist/server/controllers/validation/admin/folder.js +3 -4
  184. package/dist/server/controllers/validation/admin/folder.js.map +1 -1
  185. package/dist/server/controllers/validation/admin/folder.mjs +3 -4
  186. package/dist/server/controllers/validation/admin/folder.mjs.map +1 -1
  187. package/dist/server/controllers/validation/admin/upload.js +1 -2
  188. package/dist/server/controllers/validation/admin/upload.js.map +1 -1
  189. package/dist/server/controllers/validation/admin/upload.mjs +1 -2
  190. package/dist/server/controllers/validation/admin/upload.mjs.map +1 -1
  191. package/dist/server/controllers/validation/admin/utils.js +1 -2
  192. package/dist/server/controllers/validation/admin/utils.js.map +1 -1
  193. package/dist/server/controllers/validation/admin/utils.mjs +1 -2
  194. package/dist/server/controllers/validation/admin/utils.mjs.map +1 -1
  195. package/dist/server/errors.js +12 -0
  196. package/dist/server/errors.js.map +1 -0
  197. package/dist/server/errors.mjs +10 -0
  198. package/dist/server/errors.mjs.map +1 -0
  199. package/dist/server/migrations/unsign-richtext-and-blocks-urls.js +2 -2
  200. package/dist/server/migrations/unsign-richtext-and-blocks-urls.js.map +1 -1
  201. package/dist/server/migrations/unsign-richtext-and-blocks-urls.mjs +1 -1
  202. package/dist/server/migrations/unsign-richtext-and-blocks-urls.mjs.map +1 -1
  203. package/dist/server/routes/content-api.js +3 -3
  204. package/dist/server/routes/content-api.js.map +1 -1
  205. package/dist/server/routes/content-api.mjs +3 -3
  206. package/dist/server/routes/content-api.mjs.map +1 -1
  207. package/dist/server/routes/validation/upload.js +8 -0
  208. package/dist/server/routes/validation/upload.js.map +1 -1
  209. package/dist/server/routes/validation/upload.mjs +8 -0
  210. package/dist/server/routes/validation/upload.mjs.map +1 -1
  211. package/dist/server/services/ai-metadata-provider.js +83 -0
  212. package/dist/server/services/ai-metadata-provider.js.map +1 -0
  213. package/dist/server/services/ai-metadata-provider.mjs +61 -0
  214. package/dist/server/services/ai-metadata-provider.mjs.map +1 -0
  215. package/dist/server/services/ai-metadata-strapi-managed.js +48 -0
  216. package/dist/server/services/ai-metadata-strapi-managed.js.map +1 -0
  217. package/dist/server/services/ai-metadata-strapi-managed.mjs +46 -0
  218. package/dist/server/services/ai-metadata-strapi-managed.mjs.map +1 -0
  219. package/dist/server/services/ai-metadata.js +5 -58
  220. package/dist/server/services/ai-metadata.js.map +1 -1
  221. package/dist/server/services/ai-metadata.mjs +6 -39
  222. package/dist/server/services/ai-metadata.mjs.map +1 -1
  223. package/dist/server/services/api-upload-folder.js +3 -3
  224. package/dist/server/services/api-upload-folder.js.map +1 -1
  225. package/dist/server/services/api-upload-folder.mjs +3 -3
  226. package/dist/server/services/api-upload-folder.mjs.map +1 -1
  227. package/dist/server/services/extensions/utils.js +19 -15
  228. package/dist/server/services/extensions/utils.js.map +1 -1
  229. package/dist/server/services/extensions/utils.mjs +19 -15
  230. package/dist/server/services/extensions/utils.mjs.map +1 -1
  231. package/dist/server/services/file.js +2 -2
  232. package/dist/server/services/file.js.map +1 -1
  233. package/dist/server/services/file.mjs +1 -1
  234. package/dist/server/services/file.mjs.map +1 -1
  235. package/dist/server/services/folder.js +9 -6
  236. package/dist/server/services/folder.js.map +1 -1
  237. package/dist/server/services/folder.mjs +9 -6
  238. package/dist/server/services/folder.mjs.map +1 -1
  239. package/dist/server/services/index.js +3 -1
  240. package/dist/server/services/index.js.map +1 -1
  241. package/dist/server/services/index.mjs +3 -1
  242. package/dist/server/services/index.mjs.map +1 -1
  243. package/dist/server/services/metrics.js +1 -1
  244. package/dist/server/services/metrics.js.map +1 -1
  245. package/dist/server/services/metrics.mjs +1 -1
  246. package/dist/server/services/metrics.mjs.map +1 -1
  247. package/dist/server/services/provider.js +3 -4
  248. package/dist/server/services/provider.js.map +1 -1
  249. package/dist/server/services/provider.mjs +3 -4
  250. package/dist/server/services/provider.mjs.map +1 -1
  251. package/dist/server/services/upload.js +5 -6
  252. package/dist/server/services/upload.js.map +1 -1
  253. package/dist/server/services/upload.mjs +4 -5
  254. package/dist/server/services/upload.mjs.map +1 -1
  255. package/dist/server/services/weekly-metrics.js +2 -2
  256. package/dist/server/services/weekly-metrics.js.map +1 -1
  257. package/dist/server/services/weekly-metrics.mjs +2 -2
  258. package/dist/server/services/weekly-metrics.mjs.map +1 -1
  259. package/dist/server/src/bootstrap.d.ts.map +1 -1
  260. package/dist/server/src/controllers/admin-folder-file.d.ts.map +1 -1
  261. package/dist/server/src/controllers/admin-folder.d.ts.map +1 -1
  262. package/dist/server/src/controllers/admin-settings.d.ts.map +1 -1
  263. package/dist/server/src/controllers/content-api.d.ts.map +1 -1
  264. package/dist/server/src/controllers/validation/admin/folder.d.ts.map +1 -1
  265. package/dist/server/src/controllers/validation/admin/upload.d.ts.map +1 -1
  266. package/dist/server/src/controllers/validation/admin/utils.d.ts.map +1 -1
  267. package/dist/server/src/errors.d.ts +6 -0
  268. package/dist/server/src/errors.d.ts.map +1 -0
  269. package/dist/server/src/index.d.ts +6 -1
  270. package/dist/server/src/index.d.ts.map +1 -1
  271. package/dist/server/src/mcp/ambient-instance.d.ts +3 -1
  272. package/dist/server/src/mcp/ambient-instance.d.ts.map +1 -1
  273. package/dist/server/src/routes/validation/upload.d.ts +4 -0
  274. package/dist/server/src/routes/validation/upload.d.ts.map +1 -1
  275. package/dist/server/src/services/ai-metadata-provider.d.ts +26 -0
  276. package/dist/server/src/services/ai-metadata-provider.d.ts.map +1 -0
  277. package/dist/server/src/services/ai-metadata-strapi-managed.d.ts +7 -0
  278. package/dist/server/src/services/ai-metadata-strapi-managed.d.ts.map +1 -0
  279. package/dist/server/src/services/ai-metadata.d.ts.map +1 -1
  280. package/dist/server/src/services/extensions/utils.d.ts +33 -1
  281. package/dist/server/src/services/extensions/utils.d.ts.map +1 -1
  282. package/dist/server/src/services/folder.d.ts +5 -2
  283. package/dist/server/src/services/folder.d.ts.map +1 -1
  284. package/dist/server/src/services/index.d.ts +6 -1
  285. package/dist/server/src/services/index.d.ts.map +1 -1
  286. package/dist/server/src/services/provider.d.ts.map +1 -1
  287. package/dist/server/src/services/upload.d.ts.map +1 -1
  288. package/dist/server/src/utils/images.d.ts +2 -2
  289. package/dist/server/src/utils/images.d.ts.map +1 -1
  290. package/dist/server/src/utils/index.d.ts +2 -0
  291. package/dist/server/src/utils/index.d.ts.map +1 -1
  292. package/dist/server/utils/images.js +6 -7
  293. package/dist/server/utils/images.js.map +1 -1
  294. package/dist/server/utils/images.mjs +6 -7
  295. package/dist/server/utils/images.mjs.map +1 -1
  296. package/dist/server/utils/index.js.map +1 -1
  297. package/dist/server/utils/index.mjs.map +1 -1
  298. package/dist/shared/contracts/settings.d.ts +6 -0
  299. package/package.json +11 -11
  300. package/dist/admin/legacy/hooks/useAiAvailability.js +0 -22
  301. package/dist/admin/legacy/hooks/useAiAvailability.js.map +0 -1
  302. package/dist/admin/legacy/hooks/useAiAvailability.mjs +0 -20
  303. package/dist/admin/legacy/hooks/useAiAvailability.mjs.map +0 -1
  304. package/dist/admin/src/legacy/hooks/useAiAvailability.d.ts +0 -4
@@ -1 +1 @@
1
- {"version":3,"file":"ai-metadata.js","sources":["../../../server/src/services/ai-metadata.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\nimport * as z from 'zod/v4';\nimport { InputFile, File } from '../types';\nimport { Settings } from '../controllers/validation/admin/settings';\nimport { getService } from '../utils';\nimport { buildFormDataFromFiles } from '../utils/images';\nimport { AI_METADATA_CHUNK_SIZE, AI_METADATA_SUPPORTED_IMAGE_TYPES } from '../constants';\n\nimport { isAIMetadataSupportedMime } from '../../../shared/constants';\n\nimport type { GenerateAIMetadata } from '../../../shared/contracts/files';\n\nexport type AIMetadataFileResult = GenerateAIMetadata.FileResult;\n\n/**\n * Supported image types for AI metadata generation. Lives in `shared/` so the\n * admin panel gates the bulk action on exactly what the provider accepts.\n */\nconst SUPPORTED_IMAGE_TYPES = AI_METADATA_SUPPORTED_IMAGE_TYPES;\n\nconst isSupportedImage = (file: File): boolean => isAIMetadataSupportedMime(file.mime);\n\nconst chunk = <T>(items: T[], size: number): T[][] => {\n const chunks: T[][] = [];\n\n for (let i = 0; i < items.length; i += size) {\n chunks.push(items.slice(i, i + size));\n }\n\n return chunks;\n};\n\nconst createAIMetadataService = ({ strapi }: { strapi: Core.Strapi }) => {\n const aiServerUrl = process.env.STRAPI_AI_URL || 'https://strapi-ai.apps.strapi.io';\n\n return {\n async isEnabled() {\n if (strapi.ai.admin.isStrapiManagedAiEnabled() === false) {\n return false;\n }\n const settings: Settings = await strapi.plugin('upload').service('upload').getSettings();\n return settings.aiMetadata ?? true;\n },\n\n async countImagesWithoutMetadata() {\n const imagesWithoutMetadataCountPromise = strapi.db.query('plugin::upload.file').count({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n $or: [\n { alternativeText: { $null: true } },\n { alternativeText: '' },\n { caption: { $null: true } },\n { caption: '' },\n ],\n },\n });\n\n const totalImagesPromise = strapi.db.query('plugin::upload.file').count({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n },\n });\n\n const [imagesWithoutMetadataCount, totalImages] = await Promise.all([\n imagesWithoutMetadataCountPromise,\n totalImagesPromise,\n ]);\n\n return { imagesWithoutMetadataCount, totalImages };\n },\n\n /**\n * Update files with AI-generated metadata\n * Shared logic used by both upload flow and retroactive processing\n */\n async updateFilesWithAIMetadata(\n files: File[],\n metadataResults: Array<{ altText: string; caption: string } | null>,\n user: { id: string | number }\n ) {\n const uploadService = strapi.plugin('upload').service('upload');\n\n await Promise.all(\n files.map(async (file, index) => {\n const aiMetadata = metadataResults[index];\n if (aiMetadata) {\n // Only update fields that are missing (null or empty string)\n const updateData: { alternativeText?: string; caption?: string } = {};\n\n if (!file.alternativeText || file.alternativeText === '') {\n updateData.alternativeText = aiMetadata.altText;\n }\n\n if (!file.caption || file.caption === '') {\n updateData.caption = aiMetadata.caption;\n }\n\n // Only update if there are fields to update\n if (Object.keys(updateData).length > 0) {\n await uploadService.updateFileInfo(file.id, updateData, { user });\n\n // Update in-memory file object (needed for upload flow response)\n if (updateData.alternativeText !== undefined) {\n file.alternativeText = updateData.alternativeText;\n }\n if (updateData.caption !== undefined) {\n file.caption = updateData.caption;\n }\n }\n }\n })\n );\n },\n\n /**\n * Process existing files with job tracking for progress updates\n */\n async processExistingFiles(jobId: number, user: { id: string | number }): Promise<void> {\n const jobService = getService('aiMetadataJobs');\n\n try {\n // Mark as processing\n await jobService.updateJob(jobId, { status: 'processing' });\n\n // Query all images without metadata\n const files: File[] = await strapi.db.query('plugin::upload.file').findMany({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n $or: [\n { alternativeText: { $null: true } },\n { alternativeText: '' },\n { caption: { $null: true } },\n { caption: '' },\n ],\n },\n });\n\n if (files.length === 0) {\n await jobService.updateJob(jobId, {\n status: 'completed',\n completedAt: new Date(),\n });\n return;\n }\n\n // Process all files at once\n const metadataResults = await this.processFiles(files);\n await this.updateFilesWithAIMetadata(files, metadataResults, user);\n\n // Mark as completed\n await jobService.updateJob(jobId, {\n status: 'completed',\n completedAt: new Date(),\n });\n } catch (error) {\n strapi.log.error('AI metadata job failed', {\n jobId,\n error: error instanceof Error ? error.message : String(error),\n });\n\n await jobService.updateJob(jobId, {\n status: 'failed',\n completedAt: new Date(),\n });\n }\n },\n\n /**\n * Generate AI metadata for an explicit selection of files, synchronously.\n *\n * Unlike `processExistingFiles`, this does not create a job and does not\n * filter on missing metadata — the selection is what the user asked for.\n * Existing alt text / captions are still preserved by\n * `updateFilesWithAIMetadata`, so a file that already has both is reported\n * as `success` without being modified.\n *\n * Images are processed in sequential chunks so one failing chunk only\n * affects its own files; every other chunk still runs.\n *\n * @experimental\n */\n async generateForFiles(\n fileIds: Array<number | string>,\n user: { id: string | number }\n ): Promise<AIMetadataFileResult[]> {\n // `yup.strapiID()` accepts both numbers and numeric strings without\n // coercing, so normalise here — otherwise a request sending `[\"1\"]`\n // would never match the numeric ids coming back from the database.\n const normalisedIds = fileIds.map(Number);\n const uniqueIds = [...new Set(normalisedIds)];\n\n const files: File[] = await strapi.db.query('plugin::upload.file').findMany({\n where: { id: { $in: uniqueIds } },\n });\n\n const filesById = new Map(files.map((file) => [file.id, file]));\n\n // Keyed by input id so the response order matches the request order.\n const resultsById = new Map<number, AIMetadataFileResult>();\n const images: File[] = [];\n\n uniqueIds.forEach((id) => {\n const file = filesById.get(id);\n\n if (!file) {\n resultsById.set(id, { id, status: 'error', error: 'File not found' });\n return;\n }\n\n // Only the formats the AI provider understands are sent; anything else\n // (non-images, but also exotic image formats like SVG or TIFF) is\n // reported as skipped rather than failed.\n if (!isSupportedImage(file)) {\n resultsById.set(id, { id, status: 'skipped' });\n return;\n }\n\n images.push(file);\n });\n\n for (const imageChunk of chunk(images, AI_METADATA_CHUNK_SIZE)) {\n try {\n const metadataResults = await this.processFiles(imageChunk);\n await this.updateFilesWithAIMetadata(imageChunk, metadataResults, user);\n\n imageChunk.forEach((file, index) => {\n resultsById.set(file.id, {\n id: file.id,\n status: metadataResults[index] ? 'success' : 'error',\n ...(metadataResults[index]\n ? {}\n : { error: 'AI metadata generation returned no result' }),\n });\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'AI metadata generation failed';\n\n strapi.log.error('AI metadata generation failed for a chunk of files', {\n fileIds: imageChunk.map((file) => file.id),\n message,\n });\n\n imageChunk.forEach((file) => {\n resultsById.set(file.id, { id: file.id, status: 'error', error: message });\n });\n }\n }\n\n return normalisedIds.map(\n (id) => resultsById.get(id) ?? { id, status: 'error', error: 'File not processed' }\n );\n },\n\n /**\n * Processes provided files for AI metadata generation\n */\n async processFiles(files: File[]): Promise<Array<{ altText: string; caption: string } | null>> {\n if (!(await this.isEnabled()) || !aiServerUrl) {\n throw new Error('AI Metadata service is not enabled');\n }\n\n // Filter for image files only and track their original positions\n // We need to maintain the original indices so we can map AI results back correctly\n const imageFiles = files\n .map((file, index) => ({ file, originalIndex: index }))\n .filter(({ file }) => file.mime?.startsWith('image/'));\n\n // Convert filtered image files to InputFile format (uses thumbnails when available)\n const imageInputFiles = imageFiles.map(({ file }) => {\n const thumbnail = (file.formats as any)?.thumbnail;\n return {\n filepath: thumbnail?.url || file.url || '',\n mimetype: file.mime,\n originalFilename: file.name,\n size: thumbnail?.size || file.size,\n provider: file.provider,\n } as InputFile;\n });\n const createEmptyMetadataResults = (): Array<{ altText: string; caption: string } | null> =>\n Array.from({ length: files.length }, () => null);\n\n // If no image files, return sparse array with all nulls to avoid calling the AI server\n // This maintains the same array length as input files for proper index alignment\n if (imageFiles.length === 0) {\n return createEmptyMetadataResults();\n }\n\n const formData = await buildFormDataFromFiles(\n imageInputFiles,\n strapi.config.get('server.absoluteUrl'),\n strapi.log\n );\n\n let token: string;\n try {\n const tokenData = await strapi.ai.admin.getAiToken();\n token = tokenData.token;\n } catch (error) {\n throw new Error('Failed to retrieve AI token', {\n cause: error instanceof Error ? error : undefined,\n });\n }\n\n strapi.log.http('Contacting AI Server for media metadata generation', {\n aiServerUrl,\n imageCount: imageFiles.length,\n });\n\n const res = await fetch(`${aiServerUrl}/media-library/generate-metadata`, {\n method: 'POST',\n body: formData,\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!res.ok) {\n const errorText = await res.text();\n throw Error(`AI metadata generation failed`, { cause: errorText });\n }\n\n const responseSchema = z.object({\n results: z.array(\n z.object({\n altText: z.string(),\n caption: z.string(),\n })\n ),\n });\n\n const { results } = responseSchema.parse(await res.json());\n strapi.log.http(`AI generated metadata successfully for ${results.length} files`);\n\n // Create sparse array with results at original indices\n // Example: files=[img1, pdf, img2] -> imageFiles=[{img1, index:0}, {img2, index:2}]\n // AI results=[meta1, meta2] -> sparse=[meta1, null, meta2]\n // This ensures metadata[i] corresponds to files[i], with null for non-images\n return imageFiles.reduce((sparseResults, { originalIndex }, resultIndex) => {\n sparseResults[originalIndex] = results[resultIndex];\n return sparseResults;\n }, createEmptyMetadataResults());\n },\n };\n};\n\nexport { createAIMetadataService };\n"],"names":["SUPPORTED_IMAGE_TYPES","AI_METADATA_SUPPORTED_IMAGE_TYPES","isSupportedImage","file","isAIMetadataSupportedMime","mime","chunk","items","size","chunks","i","length","push","slice","createAIMetadataService","strapi","aiServerUrl","process","env","STRAPI_AI_URL","isEnabled","ai","admin","isStrapiManagedAiEnabled","settings","plugin","service","getSettings","aiMetadata","countImagesWithoutMetadata","imagesWithoutMetadataCountPromise","db","query","count","where","$in","$or","alternativeText","$null","caption","totalImagesPromise","imagesWithoutMetadataCount","totalImages","Promise","all","updateFilesWithAIMetadata","files","metadataResults","user","uploadService","map","index","updateData","altText","Object","keys","updateFileInfo","id","undefined","processExistingFiles","jobId","jobService","getService","updateJob","status","findMany","completedAt","Date","processFiles","error","log","Error","message","String","generateForFiles","fileIds","normalisedIds","Number","uniqueIds","Set","filesById","Map","resultsById","images","forEach","get","set","imageChunk","AI_METADATA_CHUNK_SIZE","imageFiles","originalIndex","filter","startsWith","imageInputFiles","thumbnail","formats","filepath","url","mimetype","originalFilename","name","provider","createEmptyMetadataResults","Array","from","formData","buildFormDataFromFiles","config","token","tokenData","getAiToken","cause","http","imageCount","res","fetch","method","body","headers","Authorization","ok","errorText","text","responseSchema","z","object","results","array","string","parse","json","reduce","sparseResults","resultIndex"],"mappings":"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"}
1
+ {"version":3,"file":"ai-metadata.js","sources":["../../../server/src/services/ai-metadata.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\nimport { InputFile, File } from '../types';\nimport { Settings } from '../controllers/validation/admin/settings';\nimport { getService } from '../utils';\nimport { fetchImagesAsBlobs } from '../utils/images';\nimport { AI_METADATA_CHUNK_SIZE, AI_METADATA_SUPPORTED_IMAGE_TYPES } from '../constants';\n\nimport { isAIMetadataSupportedMime } from '../../../shared/constants';\n\nimport type { GenerateAIMetadata } from '../../../shared/contracts/files';\n\nexport type AIMetadataFileResult = GenerateAIMetadata.FileResult;\n\n/**\n * Supported image types for AI metadata generation. Lives in `shared/` so the\n * admin panel gates the bulk action on exactly what the provider accepts.\n */\nconst SUPPORTED_IMAGE_TYPES = AI_METADATA_SUPPORTED_IMAGE_TYPES;\n\nconst isSupportedImage = (file: File): boolean => isAIMetadataSupportedMime(file.mime);\n\nconst chunk = <T>(items: T[], size: number): T[][] => {\n const chunks: T[][] = [];\n\n for (let i = 0; i < items.length; i += size) {\n chunks.push(items.slice(i, i + size));\n }\n\n return chunks;\n};\n\nconst createAIMetadataService = ({ strapi }: { strapi: Core.Strapi }) => {\n return {\n async isEnabled() {\n if (!getService('aiMetadataProvider').hasProvider()) {\n return false;\n }\n const settings: Settings = await strapi.plugin('upload').service('upload').getSettings();\n return settings.aiMetadata ?? true;\n },\n\n async countImagesWithoutMetadata() {\n const imagesWithoutMetadataCountPromise = strapi.db.query('plugin::upload.file').count({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n $or: [\n { alternativeText: { $null: true } },\n { alternativeText: '' },\n { caption: { $null: true } },\n { caption: '' },\n ],\n },\n });\n\n const totalImagesPromise = strapi.db.query('plugin::upload.file').count({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n },\n });\n\n const [imagesWithoutMetadataCount, totalImages] = await Promise.all([\n imagesWithoutMetadataCountPromise,\n totalImagesPromise,\n ]);\n\n return { imagesWithoutMetadataCount, totalImages };\n },\n\n /**\n * Update files with AI-generated metadata\n * Shared logic used by both upload flow and retroactive processing\n */\n async updateFilesWithAIMetadata(\n files: File[],\n metadataResults: Array<{ altText: string; caption: string } | null>,\n user: { id: string | number }\n ) {\n const uploadService = strapi.plugin('upload').service('upload');\n\n await Promise.all(\n files.map(async (file, index) => {\n const aiMetadata = metadataResults[index];\n if (aiMetadata) {\n // Only update fields that are missing (null or empty string)\n const updateData: { alternativeText?: string; caption?: string } = {};\n\n if (!file.alternativeText || file.alternativeText === '') {\n updateData.alternativeText = aiMetadata.altText;\n }\n\n if (!file.caption || file.caption === '') {\n updateData.caption = aiMetadata.caption;\n }\n\n // Only update if there are fields to update\n if (Object.keys(updateData).length > 0) {\n await uploadService.updateFileInfo(file.id, updateData, { user });\n\n // Update in-memory file object (needed for upload flow response)\n if (updateData.alternativeText !== undefined) {\n file.alternativeText = updateData.alternativeText;\n }\n if (updateData.caption !== undefined) {\n file.caption = updateData.caption;\n }\n }\n }\n })\n );\n },\n\n /**\n * Process existing files with job tracking for progress updates\n */\n async processExistingFiles(jobId: number, user: { id: string | number }): Promise<void> {\n const jobService = getService('aiMetadataJobs');\n\n try {\n // Mark as processing\n await jobService.updateJob(jobId, { status: 'processing' });\n\n // Query all images without metadata\n const files: File[] = await strapi.db.query('plugin::upload.file').findMany({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n $or: [\n { alternativeText: { $null: true } },\n { alternativeText: '' },\n { caption: { $null: true } },\n { caption: '' },\n ],\n },\n });\n\n if (files.length === 0) {\n await jobService.updateJob(jobId, {\n status: 'completed',\n completedAt: new Date(),\n });\n return;\n }\n\n // Process all files at once\n const metadataResults = await this.processFiles(files);\n await this.updateFilesWithAIMetadata(files, metadataResults, user);\n\n // Mark as completed\n await jobService.updateJob(jobId, {\n status: 'completed',\n completedAt: new Date(),\n });\n } catch (error) {\n strapi.log.error('AI metadata job failed', {\n jobId,\n error: error instanceof Error ? error.message : String(error),\n });\n\n await jobService.updateJob(jobId, {\n status: 'failed',\n completedAt: new Date(),\n });\n }\n },\n\n /**\n * Generate AI metadata for an explicit selection of files, synchronously.\n *\n * Unlike `processExistingFiles`, this does not create a job and does not\n * filter on missing metadata — the selection is what the user asked for.\n * Existing alt text / captions are still preserved by\n * `updateFilesWithAIMetadata`, so a file that already has both is reported\n * as `success` without being modified.\n *\n * Images are processed in sequential chunks so one failing chunk only\n * affects its own files; every other chunk still runs.\n *\n * @experimental\n */\n async generateForFiles(\n fileIds: Array<number | string>,\n user: { id: string | number }\n ): Promise<AIMetadataFileResult[]> {\n // `yup.strapiID()` accepts both numbers and numeric strings without\n // coercing, so normalise here — otherwise a request sending `[\"1\"]`\n // would never match the numeric ids coming back from the database.\n const normalisedIds = fileIds.map(Number);\n const uniqueIds = [...new Set(normalisedIds)];\n\n const files: File[] = await strapi.db.query('plugin::upload.file').findMany({\n where: { id: { $in: uniqueIds } },\n });\n\n const filesById = new Map(files.map((file) => [file.id, file]));\n\n // Keyed by input id so the response order matches the request order.\n const resultsById = new Map<number, AIMetadataFileResult>();\n const images: File[] = [];\n\n uniqueIds.forEach((id) => {\n const file = filesById.get(id);\n\n if (!file) {\n resultsById.set(id, { id, status: 'error', error: 'File not found' });\n return;\n }\n\n // Only the formats the AI provider understands are sent; anything else\n // (non-images, but also exotic image formats like SVG or TIFF) is\n // reported as skipped rather than failed.\n if (!isSupportedImage(file)) {\n resultsById.set(id, { id, status: 'skipped' });\n return;\n }\n\n images.push(file);\n });\n\n for (const imageChunk of chunk(images, AI_METADATA_CHUNK_SIZE)) {\n try {\n const metadataResults = await this.processFiles(imageChunk);\n await this.updateFilesWithAIMetadata(imageChunk, metadataResults, user);\n\n imageChunk.forEach((file, index) => {\n resultsById.set(file.id, {\n id: file.id,\n status: metadataResults[index] ? 'success' : 'error',\n ...(metadataResults[index]\n ? {}\n : { error: 'AI metadata generation returned no result' }),\n });\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'AI metadata generation failed';\n\n strapi.log.error('AI metadata generation failed for a chunk of files', {\n fileIds: imageChunk.map((file) => file.id),\n message,\n });\n\n imageChunk.forEach((file) => {\n resultsById.set(file.id, { id: file.id, status: 'error', error: message });\n });\n }\n }\n\n return normalisedIds.map(\n (id) => resultsById.get(id) ?? { id, status: 'error', error: 'File not processed' }\n );\n },\n\n /**\n * Processes provided files for AI metadata generation\n */\n async processFiles(files: File[]): Promise<Array<{ altText: string; caption: string } | null>> {\n if (!(await this.isEnabled())) {\n throw new Error('AI Metadata service is not enabled');\n }\n\n // Filter for image files only and track their original positions\n // We need to maintain the original indices so we can map AI results back correctly\n const imageFiles = files\n .map((file, index) => ({ file, originalIndex: index }))\n .filter(({ file }) => file.mime?.startsWith('image/'));\n\n // Convert filtered image files to InputFile format (uses thumbnails when available)\n const imageInputFiles = imageFiles.map(({ file }) => {\n const thumbnail = (file.formats as any)?.thumbnail;\n return {\n filepath: thumbnail?.url || file.url || '',\n mimetype: file.mime,\n originalFilename: file.name,\n size: thumbnail?.size || file.size,\n provider: file.provider,\n } as InputFile;\n });\n const createEmptyMetadataResults = (): Array<{ altText: string; caption: string } | null> =>\n Array.from({ length: files.length }, () => null);\n\n // If no image files, return sparse array with all nulls to avoid calling the AI server\n // This maintains the same array length as input files for proper index alignment\n if (imageFiles.length === 0) {\n return createEmptyMetadataResults();\n }\n\n const images = await fetchImagesAsBlobs(\n imageInputFiles,\n strapi.config.get('server.absoluteUrl'),\n strapi.log\n );\n\n const { results } = await getService('aiMetadataProvider').generateMetadata({ images });\n\n // Create sparse array with results at original indices\n // Example: files=[img1, pdf, img2] -> imageFiles=[{img1, index:0}, {img2, index:2}]\n // AI results=[meta1, meta2] -> sparse=[meta1, null, meta2]\n // This ensures metadata[i] corresponds to files[i], with null for non-images\n return imageFiles.reduce((sparseResults, { originalIndex }, resultIndex) => {\n sparseResults[originalIndex] = results[resultIndex];\n return sparseResults;\n }, createEmptyMetadataResults());\n },\n };\n};\n\nexport { createAIMetadataService };\n"],"names":["SUPPORTED_IMAGE_TYPES","AI_METADATA_SUPPORTED_IMAGE_TYPES","isSupportedImage","file","isAIMetadataSupportedMime","mime","chunk","items","size","chunks","i","length","push","slice","createAIMetadataService","strapi","isEnabled","getService","hasProvider","settings","plugin","service","getSettings","aiMetadata","countImagesWithoutMetadata","imagesWithoutMetadataCountPromise","db","query","count","where","$in","$or","alternativeText","$null","caption","totalImagesPromise","imagesWithoutMetadataCount","totalImages","Promise","all","updateFilesWithAIMetadata","files","metadataResults","user","uploadService","map","index","updateData","altText","Object","keys","updateFileInfo","id","undefined","processExistingFiles","jobId","jobService","updateJob","status","findMany","completedAt","Date","processFiles","error","log","Error","message","String","generateForFiles","fileIds","normalisedIds","Number","uniqueIds","Set","filesById","Map","resultsById","images","forEach","get","set","imageChunk","AI_METADATA_CHUNK_SIZE","imageFiles","originalIndex","filter","startsWith","imageInputFiles","thumbnail","formats","filepath","url","mimetype","originalFilename","name","provider","createEmptyMetadataResults","Array","from","fetchImagesAsBlobs","config","results","generateMetadata","reduce","sparseResults","resultIndex"],"mappings":"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"}
@@ -1,6 +1,5 @@
1
- import * as z from 'zod/v4';
2
1
  import { getService } from '../utils/index.mjs';
3
- import { buildFormDataFromFiles } from '../utils/images.mjs';
2
+ import { fetchImagesAsBlobs } from '../utils/images.mjs';
4
3
  import { AI_METADATA_SUPPORTED_IMAGE_TYPES, isAIMetadataSupportedMime, AI_METADATA_CHUNK_SIZE } from '../shared/constants.mjs';
5
4
 
6
5
  /**
@@ -16,10 +15,9 @@ const chunk = (items, size)=>{
16
15
  return chunks;
17
16
  };
18
17
  const createAIMetadataService = ({ strapi })=>{
19
- const aiServerUrl = process.env.STRAPI_AI_URL || 'https://strapi-ai.apps.strapi.io';
20
18
  return {
21
19
  async isEnabled () {
22
- if (strapi.ai.admin.isStrapiManagedAiEnabled() === false) {
20
+ if (!getService('aiMetadataProvider').hasProvider()) {
23
21
  return false;
24
22
  }
25
23
  const settings = await strapi.plugin('upload').service('upload').getSettings();
@@ -254,7 +252,7 @@ const createAIMetadataService = ({ strapi })=>{
254
252
  /**
255
253
  * Processes provided files for AI metadata generation
256
254
  */ async processFiles (files) {
257
- if (!await this.isEnabled() || !aiServerUrl) {
255
+ if (!await this.isEnabled()) {
258
256
  throw new Error('AI Metadata service is not enabled');
259
257
  }
260
258
  // Filter for image files only and track their original positions
@@ -282,41 +280,10 @@ const createAIMetadataService = ({ strapi })=>{
282
280
  if (imageFiles.length === 0) {
283
281
  return createEmptyMetadataResults();
284
282
  }
285
- const formData = await buildFormDataFromFiles(imageInputFiles, strapi.config.get('server.absoluteUrl'), strapi.log);
286
- let token;
287
- try {
288
- const tokenData = await strapi.ai.admin.getAiToken();
289
- token = tokenData.token;
290
- } catch (error) {
291
- throw new Error('Failed to retrieve AI token', {
292
- cause: error instanceof Error ? error : undefined
293
- });
294
- }
295
- strapi.log.http('Contacting AI Server for media metadata generation', {
296
- aiServerUrl,
297
- imageCount: imageFiles.length
298
- });
299
- const res = await fetch(`${aiServerUrl}/media-library/generate-metadata`, {
300
- method: 'POST',
301
- body: formData,
302
- headers: {
303
- Authorization: `Bearer ${token}`
304
- }
305
- });
306
- if (!res.ok) {
307
- const errorText = await res.text();
308
- throw Error(`AI metadata generation failed`, {
309
- cause: errorText
310
- });
311
- }
312
- const responseSchema = z.object({
313
- results: z.array(z.object({
314
- altText: z.string(),
315
- caption: z.string()
316
- }))
283
+ const images = await fetchImagesAsBlobs(imageInputFiles, strapi.config.get('server.absoluteUrl'), strapi.log);
284
+ const { results } = await getService('aiMetadataProvider').generateMetadata({
285
+ images
317
286
  });
318
- const { results } = responseSchema.parse(await res.json());
319
- strapi.log.http(`AI generated metadata successfully for ${results.length} files`);
320
287
  // Create sparse array with results at original indices
321
288
  // Example: files=[img1, pdf, img2] -> imageFiles=[{img1, index:0}, {img2, index:2}]
322
289
  // AI results=[meta1, meta2] -> sparse=[meta1, null, meta2]
@@ -1 +1 @@
1
- {"version":3,"file":"ai-metadata.mjs","sources":["../../../server/src/services/ai-metadata.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\nimport * as z from 'zod/v4';\nimport { InputFile, File } from '../types';\nimport { Settings } from '../controllers/validation/admin/settings';\nimport { getService } from '../utils';\nimport { buildFormDataFromFiles } from '../utils/images';\nimport { AI_METADATA_CHUNK_SIZE, AI_METADATA_SUPPORTED_IMAGE_TYPES } from '../constants';\n\nimport { isAIMetadataSupportedMime } from '../../../shared/constants';\n\nimport type { GenerateAIMetadata } from '../../../shared/contracts/files';\n\nexport type AIMetadataFileResult = GenerateAIMetadata.FileResult;\n\n/**\n * Supported image types for AI metadata generation. Lives in `shared/` so the\n * admin panel gates the bulk action on exactly what the provider accepts.\n */\nconst SUPPORTED_IMAGE_TYPES = AI_METADATA_SUPPORTED_IMAGE_TYPES;\n\nconst isSupportedImage = (file: File): boolean => isAIMetadataSupportedMime(file.mime);\n\nconst chunk = <T>(items: T[], size: number): T[][] => {\n const chunks: T[][] = [];\n\n for (let i = 0; i < items.length; i += size) {\n chunks.push(items.slice(i, i + size));\n }\n\n return chunks;\n};\n\nconst createAIMetadataService = ({ strapi }: { strapi: Core.Strapi }) => {\n const aiServerUrl = process.env.STRAPI_AI_URL || 'https://strapi-ai.apps.strapi.io';\n\n return {\n async isEnabled() {\n if (strapi.ai.admin.isStrapiManagedAiEnabled() === false) {\n return false;\n }\n const settings: Settings = await strapi.plugin('upload').service('upload').getSettings();\n return settings.aiMetadata ?? true;\n },\n\n async countImagesWithoutMetadata() {\n const imagesWithoutMetadataCountPromise = strapi.db.query('plugin::upload.file').count({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n $or: [\n { alternativeText: { $null: true } },\n { alternativeText: '' },\n { caption: { $null: true } },\n { caption: '' },\n ],\n },\n });\n\n const totalImagesPromise = strapi.db.query('plugin::upload.file').count({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n },\n });\n\n const [imagesWithoutMetadataCount, totalImages] = await Promise.all([\n imagesWithoutMetadataCountPromise,\n totalImagesPromise,\n ]);\n\n return { imagesWithoutMetadataCount, totalImages };\n },\n\n /**\n * Update files with AI-generated metadata\n * Shared logic used by both upload flow and retroactive processing\n */\n async updateFilesWithAIMetadata(\n files: File[],\n metadataResults: Array<{ altText: string; caption: string } | null>,\n user: { id: string | number }\n ) {\n const uploadService = strapi.plugin('upload').service('upload');\n\n await Promise.all(\n files.map(async (file, index) => {\n const aiMetadata = metadataResults[index];\n if (aiMetadata) {\n // Only update fields that are missing (null or empty string)\n const updateData: { alternativeText?: string; caption?: string } = {};\n\n if (!file.alternativeText || file.alternativeText === '') {\n updateData.alternativeText = aiMetadata.altText;\n }\n\n if (!file.caption || file.caption === '') {\n updateData.caption = aiMetadata.caption;\n }\n\n // Only update if there are fields to update\n if (Object.keys(updateData).length > 0) {\n await uploadService.updateFileInfo(file.id, updateData, { user });\n\n // Update in-memory file object (needed for upload flow response)\n if (updateData.alternativeText !== undefined) {\n file.alternativeText = updateData.alternativeText;\n }\n if (updateData.caption !== undefined) {\n file.caption = updateData.caption;\n }\n }\n }\n })\n );\n },\n\n /**\n * Process existing files with job tracking for progress updates\n */\n async processExistingFiles(jobId: number, user: { id: string | number }): Promise<void> {\n const jobService = getService('aiMetadataJobs');\n\n try {\n // Mark as processing\n await jobService.updateJob(jobId, { status: 'processing' });\n\n // Query all images without metadata\n const files: File[] = await strapi.db.query('plugin::upload.file').findMany({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n $or: [\n { alternativeText: { $null: true } },\n { alternativeText: '' },\n { caption: { $null: true } },\n { caption: '' },\n ],\n },\n });\n\n if (files.length === 0) {\n await jobService.updateJob(jobId, {\n status: 'completed',\n completedAt: new Date(),\n });\n return;\n }\n\n // Process all files at once\n const metadataResults = await this.processFiles(files);\n await this.updateFilesWithAIMetadata(files, metadataResults, user);\n\n // Mark as completed\n await jobService.updateJob(jobId, {\n status: 'completed',\n completedAt: new Date(),\n });\n } catch (error) {\n strapi.log.error('AI metadata job failed', {\n jobId,\n error: error instanceof Error ? error.message : String(error),\n });\n\n await jobService.updateJob(jobId, {\n status: 'failed',\n completedAt: new Date(),\n });\n }\n },\n\n /**\n * Generate AI metadata for an explicit selection of files, synchronously.\n *\n * Unlike `processExistingFiles`, this does not create a job and does not\n * filter on missing metadata — the selection is what the user asked for.\n * Existing alt text / captions are still preserved by\n * `updateFilesWithAIMetadata`, so a file that already has both is reported\n * as `success` without being modified.\n *\n * Images are processed in sequential chunks so one failing chunk only\n * affects its own files; every other chunk still runs.\n *\n * @experimental\n */\n async generateForFiles(\n fileIds: Array<number | string>,\n user: { id: string | number }\n ): Promise<AIMetadataFileResult[]> {\n // `yup.strapiID()` accepts both numbers and numeric strings without\n // coercing, so normalise here — otherwise a request sending `[\"1\"]`\n // would never match the numeric ids coming back from the database.\n const normalisedIds = fileIds.map(Number);\n const uniqueIds = [...new Set(normalisedIds)];\n\n const files: File[] = await strapi.db.query('plugin::upload.file').findMany({\n where: { id: { $in: uniqueIds } },\n });\n\n const filesById = new Map(files.map((file) => [file.id, file]));\n\n // Keyed by input id so the response order matches the request order.\n const resultsById = new Map<number, AIMetadataFileResult>();\n const images: File[] = [];\n\n uniqueIds.forEach((id) => {\n const file = filesById.get(id);\n\n if (!file) {\n resultsById.set(id, { id, status: 'error', error: 'File not found' });\n return;\n }\n\n // Only the formats the AI provider understands are sent; anything else\n // (non-images, but also exotic image formats like SVG or TIFF) is\n // reported as skipped rather than failed.\n if (!isSupportedImage(file)) {\n resultsById.set(id, { id, status: 'skipped' });\n return;\n }\n\n images.push(file);\n });\n\n for (const imageChunk of chunk(images, AI_METADATA_CHUNK_SIZE)) {\n try {\n const metadataResults = await this.processFiles(imageChunk);\n await this.updateFilesWithAIMetadata(imageChunk, metadataResults, user);\n\n imageChunk.forEach((file, index) => {\n resultsById.set(file.id, {\n id: file.id,\n status: metadataResults[index] ? 'success' : 'error',\n ...(metadataResults[index]\n ? {}\n : { error: 'AI metadata generation returned no result' }),\n });\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'AI metadata generation failed';\n\n strapi.log.error('AI metadata generation failed for a chunk of files', {\n fileIds: imageChunk.map((file) => file.id),\n message,\n });\n\n imageChunk.forEach((file) => {\n resultsById.set(file.id, { id: file.id, status: 'error', error: message });\n });\n }\n }\n\n return normalisedIds.map(\n (id) => resultsById.get(id) ?? { id, status: 'error', error: 'File not processed' }\n );\n },\n\n /**\n * Processes provided files for AI metadata generation\n */\n async processFiles(files: File[]): Promise<Array<{ altText: string; caption: string } | null>> {\n if (!(await this.isEnabled()) || !aiServerUrl) {\n throw new Error('AI Metadata service is not enabled');\n }\n\n // Filter for image files only and track their original positions\n // We need to maintain the original indices so we can map AI results back correctly\n const imageFiles = files\n .map((file, index) => ({ file, originalIndex: index }))\n .filter(({ file }) => file.mime?.startsWith('image/'));\n\n // Convert filtered image files to InputFile format (uses thumbnails when available)\n const imageInputFiles = imageFiles.map(({ file }) => {\n const thumbnail = (file.formats as any)?.thumbnail;\n return {\n filepath: thumbnail?.url || file.url || '',\n mimetype: file.mime,\n originalFilename: file.name,\n size: thumbnail?.size || file.size,\n provider: file.provider,\n } as InputFile;\n });\n const createEmptyMetadataResults = (): Array<{ altText: string; caption: string } | null> =>\n Array.from({ length: files.length }, () => null);\n\n // If no image files, return sparse array with all nulls to avoid calling the AI server\n // This maintains the same array length as input files for proper index alignment\n if (imageFiles.length === 0) {\n return createEmptyMetadataResults();\n }\n\n const formData = await buildFormDataFromFiles(\n imageInputFiles,\n strapi.config.get('server.absoluteUrl'),\n strapi.log\n );\n\n let token: string;\n try {\n const tokenData = await strapi.ai.admin.getAiToken();\n token = tokenData.token;\n } catch (error) {\n throw new Error('Failed to retrieve AI token', {\n cause: error instanceof Error ? error : undefined,\n });\n }\n\n strapi.log.http('Contacting AI Server for media metadata generation', {\n aiServerUrl,\n imageCount: imageFiles.length,\n });\n\n const res = await fetch(`${aiServerUrl}/media-library/generate-metadata`, {\n method: 'POST',\n body: formData,\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!res.ok) {\n const errorText = await res.text();\n throw Error(`AI metadata generation failed`, { cause: errorText });\n }\n\n const responseSchema = z.object({\n results: z.array(\n z.object({\n altText: z.string(),\n caption: z.string(),\n })\n ),\n });\n\n const { results } = responseSchema.parse(await res.json());\n strapi.log.http(`AI generated metadata successfully for ${results.length} files`);\n\n // Create sparse array with results at original indices\n // Example: files=[img1, pdf, img2] -> imageFiles=[{img1, index:0}, {img2, index:2}]\n // AI results=[meta1, meta2] -> sparse=[meta1, null, meta2]\n // This ensures metadata[i] corresponds to files[i], with null for non-images\n return imageFiles.reduce((sparseResults, { originalIndex }, resultIndex) => {\n sparseResults[originalIndex] = results[resultIndex];\n return sparseResults;\n }, createEmptyMetadataResults());\n },\n };\n};\n\nexport { createAIMetadataService };\n"],"names":["SUPPORTED_IMAGE_TYPES","AI_METADATA_SUPPORTED_IMAGE_TYPES","isSupportedImage","file","isAIMetadataSupportedMime","mime","chunk","items","size","chunks","i","length","push","slice","createAIMetadataService","strapi","aiServerUrl","process","env","STRAPI_AI_URL","isEnabled","ai","admin","isStrapiManagedAiEnabled","settings","plugin","service","getSettings","aiMetadata","countImagesWithoutMetadata","imagesWithoutMetadataCountPromise","db","query","count","where","$in","$or","alternativeText","$null","caption","totalImagesPromise","imagesWithoutMetadataCount","totalImages","Promise","all","updateFilesWithAIMetadata","files","metadataResults","user","uploadService","map","index","updateData","altText","Object","keys","updateFileInfo","id","undefined","processExistingFiles","jobId","jobService","getService","updateJob","status","findMany","completedAt","Date","processFiles","error","log","Error","message","String","generateForFiles","fileIds","normalisedIds","Number","uniqueIds","Set","filesById","Map","resultsById","images","forEach","get","set","imageChunk","AI_METADATA_CHUNK_SIZE","imageFiles","originalIndex","filter","startsWith","imageInputFiles","thumbnail","formats","filepath","url","mimetype","originalFilename","name","provider","createEmptyMetadataResults","Array","from","formData","buildFormDataFromFiles","config","token","tokenData","getAiToken","cause","http","imageCount","res","fetch","method","body","headers","Authorization","ok","errorText","text","responseSchema","z","object","results","array","string","parse","json","reduce","sparseResults","resultIndex"],"mappings":"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"}
1
+ {"version":3,"file":"ai-metadata.mjs","sources":["../../../server/src/services/ai-metadata.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\nimport { InputFile, File } from '../types';\nimport { Settings } from '../controllers/validation/admin/settings';\nimport { getService } from '../utils';\nimport { fetchImagesAsBlobs } from '../utils/images';\nimport { AI_METADATA_CHUNK_SIZE, AI_METADATA_SUPPORTED_IMAGE_TYPES } from '../constants';\n\nimport { isAIMetadataSupportedMime } from '../../../shared/constants';\n\nimport type { GenerateAIMetadata } from '../../../shared/contracts/files';\n\nexport type AIMetadataFileResult = GenerateAIMetadata.FileResult;\n\n/**\n * Supported image types for AI metadata generation. Lives in `shared/` so the\n * admin panel gates the bulk action on exactly what the provider accepts.\n */\nconst SUPPORTED_IMAGE_TYPES = AI_METADATA_SUPPORTED_IMAGE_TYPES;\n\nconst isSupportedImage = (file: File): boolean => isAIMetadataSupportedMime(file.mime);\n\nconst chunk = <T>(items: T[], size: number): T[][] => {\n const chunks: T[][] = [];\n\n for (let i = 0; i < items.length; i += size) {\n chunks.push(items.slice(i, i + size));\n }\n\n return chunks;\n};\n\nconst createAIMetadataService = ({ strapi }: { strapi: Core.Strapi }) => {\n return {\n async isEnabled() {\n if (!getService('aiMetadataProvider').hasProvider()) {\n return false;\n }\n const settings: Settings = await strapi.plugin('upload').service('upload').getSettings();\n return settings.aiMetadata ?? true;\n },\n\n async countImagesWithoutMetadata() {\n const imagesWithoutMetadataCountPromise = strapi.db.query('plugin::upload.file').count({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n $or: [\n { alternativeText: { $null: true } },\n { alternativeText: '' },\n { caption: { $null: true } },\n { caption: '' },\n ],\n },\n });\n\n const totalImagesPromise = strapi.db.query('plugin::upload.file').count({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n },\n });\n\n const [imagesWithoutMetadataCount, totalImages] = await Promise.all([\n imagesWithoutMetadataCountPromise,\n totalImagesPromise,\n ]);\n\n return { imagesWithoutMetadataCount, totalImages };\n },\n\n /**\n * Update files with AI-generated metadata\n * Shared logic used by both upload flow and retroactive processing\n */\n async updateFilesWithAIMetadata(\n files: File[],\n metadataResults: Array<{ altText: string; caption: string } | null>,\n user: { id: string | number }\n ) {\n const uploadService = strapi.plugin('upload').service('upload');\n\n await Promise.all(\n files.map(async (file, index) => {\n const aiMetadata = metadataResults[index];\n if (aiMetadata) {\n // Only update fields that are missing (null or empty string)\n const updateData: { alternativeText?: string; caption?: string } = {};\n\n if (!file.alternativeText || file.alternativeText === '') {\n updateData.alternativeText = aiMetadata.altText;\n }\n\n if (!file.caption || file.caption === '') {\n updateData.caption = aiMetadata.caption;\n }\n\n // Only update if there are fields to update\n if (Object.keys(updateData).length > 0) {\n await uploadService.updateFileInfo(file.id, updateData, { user });\n\n // Update in-memory file object (needed for upload flow response)\n if (updateData.alternativeText !== undefined) {\n file.alternativeText = updateData.alternativeText;\n }\n if (updateData.caption !== undefined) {\n file.caption = updateData.caption;\n }\n }\n }\n })\n );\n },\n\n /**\n * Process existing files with job tracking for progress updates\n */\n async processExistingFiles(jobId: number, user: { id: string | number }): Promise<void> {\n const jobService = getService('aiMetadataJobs');\n\n try {\n // Mark as processing\n await jobService.updateJob(jobId, { status: 'processing' });\n\n // Query all images without metadata\n const files: File[] = await strapi.db.query('plugin::upload.file').findMany({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n $or: [\n { alternativeText: { $null: true } },\n { alternativeText: '' },\n { caption: { $null: true } },\n { caption: '' },\n ],\n },\n });\n\n if (files.length === 0) {\n await jobService.updateJob(jobId, {\n status: 'completed',\n completedAt: new Date(),\n });\n return;\n }\n\n // Process all files at once\n const metadataResults = await this.processFiles(files);\n await this.updateFilesWithAIMetadata(files, metadataResults, user);\n\n // Mark as completed\n await jobService.updateJob(jobId, {\n status: 'completed',\n completedAt: new Date(),\n });\n } catch (error) {\n strapi.log.error('AI metadata job failed', {\n jobId,\n error: error instanceof Error ? error.message : String(error),\n });\n\n await jobService.updateJob(jobId, {\n status: 'failed',\n completedAt: new Date(),\n });\n }\n },\n\n /**\n * Generate AI metadata for an explicit selection of files, synchronously.\n *\n * Unlike `processExistingFiles`, this does not create a job and does not\n * filter on missing metadata — the selection is what the user asked for.\n * Existing alt text / captions are still preserved by\n * `updateFilesWithAIMetadata`, so a file that already has both is reported\n * as `success` without being modified.\n *\n * Images are processed in sequential chunks so one failing chunk only\n * affects its own files; every other chunk still runs.\n *\n * @experimental\n */\n async generateForFiles(\n fileIds: Array<number | string>,\n user: { id: string | number }\n ): Promise<AIMetadataFileResult[]> {\n // `yup.strapiID()` accepts both numbers and numeric strings without\n // coercing, so normalise here — otherwise a request sending `[\"1\"]`\n // would never match the numeric ids coming back from the database.\n const normalisedIds = fileIds.map(Number);\n const uniqueIds = [...new Set(normalisedIds)];\n\n const files: File[] = await strapi.db.query('plugin::upload.file').findMany({\n where: { id: { $in: uniqueIds } },\n });\n\n const filesById = new Map(files.map((file) => [file.id, file]));\n\n // Keyed by input id so the response order matches the request order.\n const resultsById = new Map<number, AIMetadataFileResult>();\n const images: File[] = [];\n\n uniqueIds.forEach((id) => {\n const file = filesById.get(id);\n\n if (!file) {\n resultsById.set(id, { id, status: 'error', error: 'File not found' });\n return;\n }\n\n // Only the formats the AI provider understands are sent; anything else\n // (non-images, but also exotic image formats like SVG or TIFF) is\n // reported as skipped rather than failed.\n if (!isSupportedImage(file)) {\n resultsById.set(id, { id, status: 'skipped' });\n return;\n }\n\n images.push(file);\n });\n\n for (const imageChunk of chunk(images, AI_METADATA_CHUNK_SIZE)) {\n try {\n const metadataResults = await this.processFiles(imageChunk);\n await this.updateFilesWithAIMetadata(imageChunk, metadataResults, user);\n\n imageChunk.forEach((file, index) => {\n resultsById.set(file.id, {\n id: file.id,\n status: metadataResults[index] ? 'success' : 'error',\n ...(metadataResults[index]\n ? {}\n : { error: 'AI metadata generation returned no result' }),\n });\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'AI metadata generation failed';\n\n strapi.log.error('AI metadata generation failed for a chunk of files', {\n fileIds: imageChunk.map((file) => file.id),\n message,\n });\n\n imageChunk.forEach((file) => {\n resultsById.set(file.id, { id: file.id, status: 'error', error: message });\n });\n }\n }\n\n return normalisedIds.map(\n (id) => resultsById.get(id) ?? { id, status: 'error', error: 'File not processed' }\n );\n },\n\n /**\n * Processes provided files for AI metadata generation\n */\n async processFiles(files: File[]): Promise<Array<{ altText: string; caption: string } | null>> {\n if (!(await this.isEnabled())) {\n throw new Error('AI Metadata service is not enabled');\n }\n\n // Filter for image files only and track their original positions\n // We need to maintain the original indices so we can map AI results back correctly\n const imageFiles = files\n .map((file, index) => ({ file, originalIndex: index }))\n .filter(({ file }) => file.mime?.startsWith('image/'));\n\n // Convert filtered image files to InputFile format (uses thumbnails when available)\n const imageInputFiles = imageFiles.map(({ file }) => {\n const thumbnail = (file.formats as any)?.thumbnail;\n return {\n filepath: thumbnail?.url || file.url || '',\n mimetype: file.mime,\n originalFilename: file.name,\n size: thumbnail?.size || file.size,\n provider: file.provider,\n } as InputFile;\n });\n const createEmptyMetadataResults = (): Array<{ altText: string; caption: string } | null> =>\n Array.from({ length: files.length }, () => null);\n\n // If no image files, return sparse array with all nulls to avoid calling the AI server\n // This maintains the same array length as input files for proper index alignment\n if (imageFiles.length === 0) {\n return createEmptyMetadataResults();\n }\n\n const images = await fetchImagesAsBlobs(\n imageInputFiles,\n strapi.config.get('server.absoluteUrl'),\n strapi.log\n );\n\n const { results } = await getService('aiMetadataProvider').generateMetadata({ images });\n\n // Create sparse array with results at original indices\n // Example: files=[img1, pdf, img2] -> imageFiles=[{img1, index:0}, {img2, index:2}]\n // AI results=[meta1, meta2] -> sparse=[meta1, null, meta2]\n // This ensures metadata[i] corresponds to files[i], with null for non-images\n return imageFiles.reduce((sparseResults, { originalIndex }, resultIndex) => {\n sparseResults[originalIndex] = results[resultIndex];\n return sparseResults;\n }, createEmptyMetadataResults());\n },\n };\n};\n\nexport { createAIMetadataService };\n"],"names":["SUPPORTED_IMAGE_TYPES","AI_METADATA_SUPPORTED_IMAGE_TYPES","isSupportedImage","file","isAIMetadataSupportedMime","mime","chunk","items","size","chunks","i","length","push","slice","createAIMetadataService","strapi","isEnabled","getService","hasProvider","settings","plugin","service","getSettings","aiMetadata","countImagesWithoutMetadata","imagesWithoutMetadataCountPromise","db","query","count","where","$in","$or","alternativeText","$null","caption","totalImagesPromise","imagesWithoutMetadataCount","totalImages","Promise","all","updateFilesWithAIMetadata","files","metadataResults","user","uploadService","map","index","updateData","altText","Object","keys","updateFileInfo","id","undefined","processExistingFiles","jobId","jobService","updateJob","status","findMany","completedAt","Date","processFiles","error","log","Error","message","String","generateForFiles","fileIds","normalisedIds","Number","uniqueIds","Set","filesById","Map","resultsById","images","forEach","get","set","imageChunk","AI_METADATA_CHUNK_SIZE","imageFiles","originalIndex","filter","startsWith","imageInputFiles","thumbnail","formats","filepath","url","mimetype","originalFilename","name","provider","createEmptyMetadataResults","Array","from","fetchImagesAsBlobs","config","results","generateMetadata","reduce","sparseResults","resultIndex"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var fp = require('lodash/fp');
3
+ var _ = require('lodash');
4
4
  var index = require('../utils/index.js');
5
5
  var constants = require('../constants.js');
6
6
 
@@ -36,13 +36,13 @@ const createApiUploadFolder = async ()=>{
36
36
  };
37
37
  const getAPIUploadFolder = async ()=>{
38
38
  const storeValue = await getStore().get({});
39
- const folderId = fp.get('id', storeValue);
39
+ const folderId = _.get(storeValue, 'id');
40
40
  const folder = folderId ? await strapi.db.query(constants.FOLDER_MODEL_UID).findOne({
41
41
  where: {
42
42
  id: folderId
43
43
  }
44
44
  }) : null;
45
- return fp.isNil(folder) ? createApiUploadFolder() : folder;
45
+ return folder == null ? createApiUploadFolder() : folder;
46
46
  };
47
47
  var apiUploadFolder = {
48
48
  getAPIUploadFolder
@@ -1 +1 @@
1
- {"version":3,"file":"api-upload-folder.js","sources":["../../../server/src/services/api-upload-folder.ts"],"sourcesContent":["import { isNil, get } from 'lodash/fp';\nimport { getService } from '../utils';\nimport { FOLDER_MODEL_UID, API_UPLOAD_FOLDER_BASE_NAME } from '../constants';\n\nconst getStore = () => strapi.store({ type: 'plugin', name: 'upload', key: 'api-folder' });\n\nconst createApiUploadFolder = async () => {\n let name = API_UPLOAD_FOLDER_BASE_NAME;\n const folderService = getService('folder');\n\n let exists = true;\n let index = 1;\n while (exists) {\n exists = await folderService.exists({ name, parent: null });\n if (exists) {\n name = `${API_UPLOAD_FOLDER_BASE_NAME} (${index})`;\n index += 1;\n }\n }\n\n const folder = await folderService.create({ name });\n\n await getStore().set({ value: { id: folder.id } });\n\n return folder;\n};\n\nconst getAPIUploadFolder = async () => {\n const storeValue = await getStore().get({});\n const folderId = get('id', storeValue);\n\n const folder = folderId\n ? await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } })\n : null;\n\n return isNil(folder) ? createApiUploadFolder() : folder;\n};\n\nexport default {\n getAPIUploadFolder,\n};\n"],"names":["getStore","strapi","store","type","name","key","createApiUploadFolder","API_UPLOAD_FOLDER_BASE_NAME","folderService","getService","exists","index","parent","folder","create","set","value","id","getAPIUploadFolder","storeValue","get","folderId","db","query","FOLDER_MODEL_UID","findOne","where","isNil"],"mappings":";;;;;;AAIA,MAAMA,QAAAA,GAAW,IAAMC,MAAAA,CAAOC,KAAK,CAAC;QAAEC,IAAAA,EAAM,QAAA;QAAUC,IAAAA,EAAM,QAAA;QAAUC,GAAAA,EAAK;AAAa,KAAA,CAAA;AAExF,MAAMC,qBAAAA,GAAwB,UAAA;AAC5B,IAAA,IAAIF,IAAAA,GAAOG,qCAAAA;AACX,IAAA,MAAMC,gBAAgBC,gBAAAA,CAAW,QAAA,CAAA;AAEjC,IAAA,IAAIC,MAAAA,GAAS,IAAA;AACb,IAAA,IAAIC,OAAAA,GAAQ,CAAA;AACZ,IAAA,MAAOD,MAAAA,CAAQ;QACbA,MAAAA,GAAS,MAAMF,aAAAA,CAAcE,MAAM,CAAC;AAAEN,YAAAA,IAAAA;YAAMQ,MAAAA,EAAQ;AAAK,SAAA,CAAA;AACzD,QAAA,IAAIF,MAAAA,EAAQ;AACVN,YAAAA,IAAAA,GAAO,GAAGG,qCAAAA,CAA4B,EAAE,EAAEI,OAAAA,CAAM,CAAC,CAAC;YAClDA,OAAAA,IAAS,CAAA;AACX,QAAA;AACF,IAAA;AAEA,IAAA,MAAME,MAAAA,GAAS,MAAML,aAAAA,CAAcM,MAAM,CAAC;AAAEV,QAAAA;AAAK,KAAA,CAAA;IAEjD,MAAMJ,QAAAA,EAAAA,CAAWe,GAAG,CAAC;QAAEC,KAAAA,EAAO;AAAEC,YAAAA,EAAAA,EAAIJ,OAAOI;AAAG;AAAE,KAAA,CAAA;IAEhD,OAAOJ,MAAAA;AACT,CAAA;AAEA,MAAMK,kBAAAA,GAAqB,UAAA;AACzB,IAAA,MAAMC,UAAAA,GAAa,MAAMnB,QAAAA,EAAAA,CAAWoB,GAAG,CAAC,EAAC,CAAA;IACzC,MAAMC,QAAAA,GAAWD,OAAI,IAAA,EAAMD,UAAAA,CAAAA;IAE3B,MAAMN,MAAAA,GAASQ,QAAAA,GACX,MAAMpB,MAAAA,CAAOqB,EAAE,CAACC,KAAK,CAACC,0BAAAA,CAAAA,CAAkBC,OAAO,CAAC;QAAEC,KAAAA,EAAO;YAAET,EAAAA,EAAII;AAAS;KAAE,CAAA,GAC1E,IAAA;IAEJ,OAAOM,QAAAA,CAAMd,UAAUP,qBAAAA,EAAAA,GAA0BO,MAAAA;AACnD,CAAA;AAEA,sBAAe;AACbK,IAAAA;AACF,CAAA;;;;"}
1
+ {"version":3,"file":"api-upload-folder.js","sources":["../../../server/src/services/api-upload-folder.ts"],"sourcesContent":["import { get } from 'lodash';\nimport { getService } from '../utils';\nimport { FOLDER_MODEL_UID, API_UPLOAD_FOLDER_BASE_NAME } from '../constants';\n\nconst getStore = () => strapi.store({ type: 'plugin', name: 'upload', key: 'api-folder' });\n\nconst createApiUploadFolder = async () => {\n let name = API_UPLOAD_FOLDER_BASE_NAME;\n const folderService = getService('folder');\n\n let exists = true;\n let index = 1;\n while (exists) {\n exists = await folderService.exists({ name, parent: null });\n if (exists) {\n name = `${API_UPLOAD_FOLDER_BASE_NAME} (${index})`;\n index += 1;\n }\n }\n\n const folder = await folderService.create({ name });\n\n await getStore().set({ value: { id: folder.id } });\n\n return folder;\n};\n\nconst getAPIUploadFolder = async () => {\n const storeValue = await getStore().get({});\n const folderId = get(storeValue, 'id');\n\n const folder = folderId\n ? await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } })\n : null;\n\n return folder == null ? createApiUploadFolder() : folder;\n};\n\nexport default {\n getAPIUploadFolder,\n};\n"],"names":["getStore","strapi","store","type","name","key","createApiUploadFolder","API_UPLOAD_FOLDER_BASE_NAME","folderService","getService","exists","index","parent","folder","create","set","value","id","getAPIUploadFolder","storeValue","get","folderId","db","query","FOLDER_MODEL_UID","findOne","where"],"mappings":";;;;;;AAIA,MAAMA,QAAAA,GAAW,IAAMC,MAAAA,CAAOC,KAAK,CAAC;QAAEC,IAAAA,EAAM,QAAA;QAAUC,IAAAA,EAAM,QAAA;QAAUC,GAAAA,EAAK;AAAa,KAAA,CAAA;AAExF,MAAMC,qBAAAA,GAAwB,UAAA;AAC5B,IAAA,IAAIF,IAAAA,GAAOG,qCAAAA;AACX,IAAA,MAAMC,gBAAgBC,gBAAAA,CAAW,QAAA,CAAA;AAEjC,IAAA,IAAIC,MAAAA,GAAS,IAAA;AACb,IAAA,IAAIC,OAAAA,GAAQ,CAAA;AACZ,IAAA,MAAOD,MAAAA,CAAQ;QACbA,MAAAA,GAAS,MAAMF,aAAAA,CAAcE,MAAM,CAAC;AAAEN,YAAAA,IAAAA;YAAMQ,MAAAA,EAAQ;AAAK,SAAA,CAAA;AACzD,QAAA,IAAIF,MAAAA,EAAQ;AACVN,YAAAA,IAAAA,GAAO,GAAGG,qCAAAA,CAA4B,EAAE,EAAEI,OAAAA,CAAM,CAAC,CAAC;YAClDA,OAAAA,IAAS,CAAA;AACX,QAAA;AACF,IAAA;AAEA,IAAA,MAAME,MAAAA,GAAS,MAAML,aAAAA,CAAcM,MAAM,CAAC;AAAEV,QAAAA;AAAK,KAAA,CAAA;IAEjD,MAAMJ,QAAAA,EAAAA,CAAWe,GAAG,CAAC;QAAEC,KAAAA,EAAO;AAAEC,YAAAA,EAAAA,EAAIJ,OAAOI;AAAG;AAAE,KAAA,CAAA;IAEhD,OAAOJ,MAAAA;AACT,CAAA;AAEA,MAAMK,kBAAAA,GAAqB,UAAA;AACzB,IAAA,MAAMC,UAAAA,GAAa,MAAMnB,QAAAA,EAAAA,CAAWoB,GAAG,CAAC,EAAC,CAAA;IACzC,MAAMC,QAAAA,GAAWD,MAAID,UAAAA,EAAY,IAAA,CAAA;IAEjC,MAAMN,MAAAA,GAASQ,QAAAA,GACX,MAAMpB,MAAAA,CAAOqB,EAAE,CAACC,KAAK,CAACC,0BAAAA,CAAAA,CAAkBC,OAAO,CAAC;QAAEC,KAAAA,EAAO;YAAET,EAAAA,EAAII;AAAS;KAAE,CAAA,GAC1E,IAAA;IAEJ,OAAOR,MAAAA,IAAU,OAAOP,qBAAAA,EAAAA,GAA0BO,MAAAA;AACpD,CAAA;AAEA,sBAAe;AACbK,IAAAA;AACF,CAAA;;;;"}
@@ -1,4 +1,4 @@
1
- import { get, isNil } from 'lodash/fp';
1
+ import { get } from 'lodash';
2
2
  import { getService } from '../utils/index.mjs';
3
3
  import { FOLDER_MODEL_UID, API_UPLOAD_FOLDER_BASE_NAME } from '../constants.mjs';
4
4
 
@@ -34,13 +34,13 @@ const createApiUploadFolder = async ()=>{
34
34
  };
35
35
  const getAPIUploadFolder = async ()=>{
36
36
  const storeValue = await getStore().get({});
37
- const folderId = get('id', storeValue);
37
+ const folderId = get(storeValue, 'id');
38
38
  const folder = folderId ? await strapi.db.query(FOLDER_MODEL_UID).findOne({
39
39
  where: {
40
40
  id: folderId
41
41
  }
42
42
  }) : null;
43
- return isNil(folder) ? createApiUploadFolder() : folder;
43
+ return folder == null ? createApiUploadFolder() : folder;
44
44
  };
45
45
  var apiUploadFolder = {
46
46
  getAPIUploadFolder
@@ -1 +1 @@
1
- {"version":3,"file":"api-upload-folder.mjs","sources":["../../../server/src/services/api-upload-folder.ts"],"sourcesContent":["import { isNil, get } from 'lodash/fp';\nimport { getService } from '../utils';\nimport { FOLDER_MODEL_UID, API_UPLOAD_FOLDER_BASE_NAME } from '../constants';\n\nconst getStore = () => strapi.store({ type: 'plugin', name: 'upload', key: 'api-folder' });\n\nconst createApiUploadFolder = async () => {\n let name = API_UPLOAD_FOLDER_BASE_NAME;\n const folderService = getService('folder');\n\n let exists = true;\n let index = 1;\n while (exists) {\n exists = await folderService.exists({ name, parent: null });\n if (exists) {\n name = `${API_UPLOAD_FOLDER_BASE_NAME} (${index})`;\n index += 1;\n }\n }\n\n const folder = await folderService.create({ name });\n\n await getStore().set({ value: { id: folder.id } });\n\n return folder;\n};\n\nconst getAPIUploadFolder = async () => {\n const storeValue = await getStore().get({});\n const folderId = get('id', storeValue);\n\n const folder = folderId\n ? await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } })\n : null;\n\n return isNil(folder) ? createApiUploadFolder() : folder;\n};\n\nexport default {\n getAPIUploadFolder,\n};\n"],"names":["getStore","strapi","store","type","name","key","createApiUploadFolder","API_UPLOAD_FOLDER_BASE_NAME","folderService","getService","exists","index","parent","folder","create","set","value","id","getAPIUploadFolder","storeValue","get","folderId","db","query","FOLDER_MODEL_UID","findOne","where","isNil"],"mappings":";;;;AAIA,MAAMA,QAAAA,GAAW,IAAMC,MAAAA,CAAOC,KAAK,CAAC;QAAEC,IAAAA,EAAM,QAAA;QAAUC,IAAAA,EAAM,QAAA;QAAUC,GAAAA,EAAK;AAAa,KAAA,CAAA;AAExF,MAAMC,qBAAAA,GAAwB,UAAA;AAC5B,IAAA,IAAIF,IAAAA,GAAOG,2BAAAA;AACX,IAAA,MAAMC,gBAAgBC,UAAAA,CAAW,QAAA,CAAA;AAEjC,IAAA,IAAIC,MAAAA,GAAS,IAAA;AACb,IAAA,IAAIC,KAAAA,GAAQ,CAAA;AACZ,IAAA,MAAOD,MAAAA,CAAQ;QACbA,MAAAA,GAAS,MAAMF,aAAAA,CAAcE,MAAM,CAAC;AAAEN,YAAAA,IAAAA;YAAMQ,MAAAA,EAAQ;AAAK,SAAA,CAAA;AACzD,QAAA,IAAIF,MAAAA,EAAQ;AACVN,YAAAA,IAAAA,GAAO,GAAGG,2BAAAA,CAA4B,EAAE,EAAEI,KAAAA,CAAM,CAAC,CAAC;YAClDA,KAAAA,IAAS,CAAA;AACX,QAAA;AACF,IAAA;AAEA,IAAA,MAAME,MAAAA,GAAS,MAAML,aAAAA,CAAcM,MAAM,CAAC;AAAEV,QAAAA;AAAK,KAAA,CAAA;IAEjD,MAAMJ,QAAAA,EAAAA,CAAWe,GAAG,CAAC;QAAEC,KAAAA,EAAO;AAAEC,YAAAA,EAAAA,EAAIJ,OAAOI;AAAG;AAAE,KAAA,CAAA;IAEhD,OAAOJ,MAAAA;AACT,CAAA;AAEA,MAAMK,kBAAAA,GAAqB,UAAA;AACzB,IAAA,MAAMC,UAAAA,GAAa,MAAMnB,QAAAA,EAAAA,CAAWoB,GAAG,CAAC,EAAC,CAAA;IACzC,MAAMC,QAAAA,GAAWD,IAAI,IAAA,EAAMD,UAAAA,CAAAA;IAE3B,MAAMN,MAAAA,GAASQ,QAAAA,GACX,MAAMpB,MAAAA,CAAOqB,EAAE,CAACC,KAAK,CAACC,gBAAAA,CAAAA,CAAkBC,OAAO,CAAC;QAAEC,KAAAA,EAAO;YAAET,EAAAA,EAAII;AAAS;KAAE,CAAA,GAC1E,IAAA;IAEJ,OAAOM,KAAAA,CAAMd,UAAUP,qBAAAA,EAAAA,GAA0BO,MAAAA;AACnD,CAAA;AAEA,sBAAe;AACbK,IAAAA;AACF,CAAA;;;;"}
1
+ {"version":3,"file":"api-upload-folder.mjs","sources":["../../../server/src/services/api-upload-folder.ts"],"sourcesContent":["import { get } from 'lodash';\nimport { getService } from '../utils';\nimport { FOLDER_MODEL_UID, API_UPLOAD_FOLDER_BASE_NAME } from '../constants';\n\nconst getStore = () => strapi.store({ type: 'plugin', name: 'upload', key: 'api-folder' });\n\nconst createApiUploadFolder = async () => {\n let name = API_UPLOAD_FOLDER_BASE_NAME;\n const folderService = getService('folder');\n\n let exists = true;\n let index = 1;\n while (exists) {\n exists = await folderService.exists({ name, parent: null });\n if (exists) {\n name = `${API_UPLOAD_FOLDER_BASE_NAME} (${index})`;\n index += 1;\n }\n }\n\n const folder = await folderService.create({ name });\n\n await getStore().set({ value: { id: folder.id } });\n\n return folder;\n};\n\nconst getAPIUploadFolder = async () => {\n const storeValue = await getStore().get({});\n const folderId = get(storeValue, 'id');\n\n const folder = folderId\n ? await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } })\n : null;\n\n return folder == null ? createApiUploadFolder() : folder;\n};\n\nexport default {\n getAPIUploadFolder,\n};\n"],"names":["getStore","strapi","store","type","name","key","createApiUploadFolder","API_UPLOAD_FOLDER_BASE_NAME","folderService","getService","exists","index","parent","folder","create","set","value","id","getAPIUploadFolder","storeValue","get","folderId","db","query","FOLDER_MODEL_UID","findOne","where"],"mappings":";;;;AAIA,MAAMA,QAAAA,GAAW,IAAMC,MAAAA,CAAOC,KAAK,CAAC;QAAEC,IAAAA,EAAM,QAAA;QAAUC,IAAAA,EAAM,QAAA;QAAUC,GAAAA,EAAK;AAAa,KAAA,CAAA;AAExF,MAAMC,qBAAAA,GAAwB,UAAA;AAC5B,IAAA,IAAIF,IAAAA,GAAOG,2BAAAA;AACX,IAAA,MAAMC,gBAAgBC,UAAAA,CAAW,QAAA,CAAA;AAEjC,IAAA,IAAIC,MAAAA,GAAS,IAAA;AACb,IAAA,IAAIC,KAAAA,GAAQ,CAAA;AACZ,IAAA,MAAOD,MAAAA,CAAQ;QACbA,MAAAA,GAAS,MAAMF,aAAAA,CAAcE,MAAM,CAAC;AAAEN,YAAAA,IAAAA;YAAMQ,MAAAA,EAAQ;AAAK,SAAA,CAAA;AACzD,QAAA,IAAIF,MAAAA,EAAQ;AACVN,YAAAA,IAAAA,GAAO,GAAGG,2BAAAA,CAA4B,EAAE,EAAEI,KAAAA,CAAM,CAAC,CAAC;YAClDA,KAAAA,IAAS,CAAA;AACX,QAAA;AACF,IAAA;AAEA,IAAA,MAAME,MAAAA,GAAS,MAAML,aAAAA,CAAcM,MAAM,CAAC;AAAEV,QAAAA;AAAK,KAAA,CAAA;IAEjD,MAAMJ,QAAAA,EAAAA,CAAWe,GAAG,CAAC;QAAEC,KAAAA,EAAO;AAAEC,YAAAA,EAAAA,EAAIJ,OAAOI;AAAG;AAAE,KAAA,CAAA;IAEhD,OAAOJ,MAAAA;AACT,CAAA;AAEA,MAAMK,kBAAAA,GAAqB,UAAA;AACzB,IAAA,MAAMC,UAAAA,GAAa,MAAMnB,QAAAA,EAAAA,CAAWoB,GAAG,CAAC,EAAC,CAAA;IACzC,MAAMC,QAAAA,GAAWD,IAAID,UAAAA,EAAY,IAAA,CAAA;IAEjC,MAAMN,MAAAA,GAASQ,QAAAA,GACX,MAAMpB,MAAAA,CAAOqB,EAAE,CAACC,KAAK,CAACC,gBAAAA,CAAAA,CAAkBC,OAAO,CAAC;QAAEC,KAAAA,EAAO;YAAET,EAAAA,EAAII;AAAS;KAAE,CAAA,GAC1E,IAAA;IAEJ,OAAOR,MAAAA,IAAU,OAAOP,qBAAAA,EAAAA,GAA0BO,MAAAA;AACpD,CAAA;AAEA,sBAAe;AACbK,IAAAA;AACF,CAAA;;;;"}
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  var path = require('path');
4
- var fp = require('lodash/fp');
5
4
  var utils = require('@strapi/utils');
6
5
  var index = require('../../utils/index.js');
7
6
  var constants = require('../../constants.js');
@@ -241,24 +240,29 @@ const getImageFormats = (image)=>Object.entries(image.formats ?? {});
241
240
  return image;
242
241
  }
243
242
  const names = getQueryParamNames(signedUrl);
244
- const result = fp.omit([
245
- 'isUrlSigned'
246
- ], {
243
+ const { isUrlSigned: _isUrlSigned, ...result } = {
247
244
  ...image,
248
245
  url: removeQueryParams(image.url ?? '', names)
249
- });
246
+ };
250
247
  if (image.formats) {
251
- result.formats = Object.fromEntries(getImageFormats(image).map(([key, format])=>[
248
+ result.formats = Object.fromEntries(getImageFormats(image).map(([key, format])=>{
249
+ if (!format || typeof format !== 'object') {
250
+ return [
251
+ key,
252
+ format
253
+ ];
254
+ }
255
+ const { isUrlSigned: _formatIsUrlSigned, ...unsignedFormat } = {
256
+ ...format,
257
+ ...format.url ? {
258
+ url: removeQueryParams(format.url, names)
259
+ } : {}
260
+ };
261
+ return [
252
262
  key,
253
- format && typeof format === 'object' ? fp.omit([
254
- 'isUrlSigned'
255
- ], {
256
- ...format,
257
- ...format.url ? {
258
- url: removeQueryParams(format.url, names)
259
- } : {}
260
- }) : format
261
- ]));
263
+ unsignedFormat
264
+ ];
265
+ }));
262
266
  }
263
267
  return result;
264
268
  };
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sources":["../../../../server/src/services/extensions/utils.ts"],"sourcesContent":["import path from 'path';\n\nimport { omit } from 'lodash/fp';\nimport { async, traverseEntity } from '@strapi/utils';\n\nimport type { Schema, UID } from '@strapi/types';\n\nimport { getService } from '../../utils';\nimport { FILE_MODEL_UID } from '../../constants';\n\nimport type { Config, File } from '../../types';\n\ntype SignEntityMediaVisitor = (\n args: {\n key: string;\n value: unknown;\n attribute: Schema.Attribute.AnyAttribute;\n },\n utils: {\n set: (key: string, value: unknown) => void;\n }\n) => Promise<void>;\n\n/**\n * One presign per distinct URL, keyed by the bare (unsigned) URL. The promise\n * is stored rather than its result so concurrent occurrences of the same URL\n * (the `async.map` in `mapRichtextUrls`, or several entries of a `findMany`)\n * share a single provider call.\n *\n * A cache must never outlive the document service call or migration run that\n * created it: a signed URL is only as fresh as the request that produced it.\n */\ntype SignCache = Map<string, Promise<string | null>>;\n\nconst createSignCache = (): SignCache => new Map();\n\n/**\n * URL bearing syntaxes found in a richtext (markdown) value. Each pattern\n * captures a prefix (group 1) and the URL (group 2) so the rebuild loop in\n * `mapRichtextUrls` can splice a new URL in place without touching anything\n * else.\n *\n * - markdown image / link: `![alt](url)` or `[text](url)`. The URL is captured\n * up to the first whitespace or closing parenthesis, which also covers the\n * optional title form `[text](url \"title\")`.\n * - raw HTML `src` / `href` attributes, double or single quoted, on the\n * elements the editor lets users write by hand (`<img src=\"...\" width=\"200\">`\n * is the usual way to size an image).\n */\nconst RICHTEXT_URL_REGEXES = [\n /(!?\\[[^\\]]*\\]\\()([^)\\s]+)/g,\n /(<(?:img|a|source|video|audio)\\b[^>]*?\\b(?:src|href)=\")([^\"]+)/gi,\n /(<(?:img|a|source|video|audio)\\b[^>]*?\\b(?:src|href)=')([^']+)/gi,\n];\n\n/** Cheap short circuit for richtext values that cannot contain a URL we would rewrite. */\nconst RICHTEXT_URL_HINTS = ['](', 'src=', 'href='];\n\ntype BlocksNode = {\n type?: string;\n image?: File;\n children?: unknown;\n [key: string]: unknown;\n};\n\ntype ImageFormat = { url?: string; isUrlSigned?: boolean; [key: string]: unknown };\n\nfunction isFile(value: unknown, attribute: Schema.Attribute.AnyAttribute): value is File {\n if (!value || attribute.type !== 'media') {\n return false;\n }\n\n return true;\n}\n\nconst stripQueryString = (url: string) => url.split('?')[0];\n\nconst decodeQueryParamName = (pair: string) => {\n const name = pair.split('=')[0];\n\n try {\n return decodeURIComponent(name);\n } catch {\n return name;\n }\n};\n\n/**\n * Split a URL into what precedes the `?`, the raw `key=value` pairs of its\n * query string and the `#fragment`, if any. Plain string work on purpose:\n * `new URL` throws on the relative `/uploads/...` paths a local provider\n * writes, and would re-encode the pairs we want to hand back untouched.\n */\nconst splitUrl = (url: string) => {\n const hashIndex = url.indexOf('#');\n const fragment = hashIndex === -1 ? '' : url.slice(hashIndex);\n const withoutFragment = hashIndex === -1 ? url : url.slice(0, hashIndex);\n\n const queryIndex = withoutFragment.indexOf('?');\n\n if (queryIndex === -1) {\n return { base: withoutFragment, pairs: [] as string[], fragment };\n }\n\n return {\n base: withoutFragment.slice(0, queryIndex),\n pairs: withoutFragment\n .slice(queryIndex + 1)\n .split('&')\n .filter(Boolean),\n fragment,\n };\n};\n\n/** Names of the query parameters of `url`, decoded, in order. */\nconst getQueryParamNames = (url: string): string[] => splitUrl(url).pairs.map(decodeQueryParamName);\n\n/**\n * `url` with every query parameter whose name is in `names` removed. The\n * surviving pairs keep their order and encoding; the `?` is dropped when\n * nothing is left.\n */\nconst removeQueryParams = (url: string, names: string[]): string => {\n const { base, pairs, fragment } = splitUrl(url);\n\n if (pairs.length === 0 || names.length === 0) {\n return url;\n }\n\n const kept = pairs.filter((pair) => !names.includes(decodeQueryParamName(pair)));\n\n return kept.length === 0 ? `${base}${fragment}` : `${base}?${kept.join('&')}${fragment}`;\n};\n\n/**\n * Build the minimal file shape `signFileUrls` needs out of a bare URL, as found\n * in a richtext value.\n *\n * `hash` and `ext` are taken from the last path segment and `path` is left\n * unset, which rebuilds the S3 key for the default layout and for the\n * `rootPath` provider option (the provider prefixes it itself). A URL whose key\n * has extra segments (a file uploaded with the per-file `path` option, or an\n * older key layout) is still recognised as ours by the provider, but it is\n * signed with a key that does not exist and the link returns 403. Media\n * signing has the same limit because `path` is not persisted on the file\n * (`content-types/file.ts`). Not addressed here.\n */\nconst getFileFromUrl = (url: string): File => {\n const unsignedUrl = stripQueryString(url);\n const { provider } = strapi.config.get<Config>('plugin::upload');\n const { name, ext } = path.parse(unsignedUrl.split('/').pop() ?? '');\n\n return { url: unsignedUrl, hash: name, ext, provider } as File;\n};\n\n/**\n * Ask the provider to sign `file` and return the signed URL if the provider\n * owns it, otherwise `null`. Memoised in `cache` by the bare URL.\n *\n * Ownership is decided by whether the provider changed the URL: `signFileUrls`\n * flags every file of the configured provider as signed, but the provider\n * itself returns URLs it does not own untouched (an S3 URL from another\n * bucket, an external site, a local `/uploads` path), so a changed URL is the\n * only reliable signal. `file.url` must already be the bare URL so the\n * comparison is meaningful.\n */\nconst signFileIfOwned = (file: File, cache: SignCache): Promise<string | null> => {\n const bareUrl = file.url ?? '';\n const cached = cache.get(bareUrl);\n\n if (cached) {\n return cached;\n }\n\n const pending = (async () => {\n const { signFileUrls } = getService('file');\n const signedFile = await signFileUrls(file);\n\n return signedFile.url && signedFile.url !== bareUrl ? signedFile.url : null;\n })();\n\n cache.set(bareUrl, pending);\n\n return pending;\n};\n\n/**\n * Signs a richtext URL if the configured provider owns it, otherwise `null`.\n * See `signFileIfOwned` for how ownership is decided. A single predicate means\n * the read path (sign) and the write path (unsign) can never disagree about\n * what they own.\n */\nconst signIfOwned = (url: string, cache: SignCache): Promise<string | null> =>\n signFileIfOwned(getFileFromUrl(url), cache);\n\n/**\n * Returns a richtext URL with its signature removed, if we own it.\n *\n * Only the query parameters the provider's own fresh signature introduces are\n * removed (for S3, the `X-Amz-*` family), every other parameter is kept. That\n * way a provider that signs whatever it is handed cannot make us drop `?v=x`\n * from a link to an external site. A parameter of an old signature that the\n * fresh one no longer carries (an `X-Amz-Security-Token` from credentials no\n * longer in use, say) survives in the row; it is not a signature, and the read\n * path replaces the whole URL with the provider output anyway.\n */\nconst stripSignedUrl = async (url: string, cache: SignCache) => {\n // Nothing to strip, and no reason to ask the provider\n if (!url.includes('?')) {\n return url;\n }\n\n const signedUrl = await signIfOwned(url, cache);\n\n if (signedUrl === null) {\n return url;\n }\n\n return removeQueryParams(url, getQueryParamNames(signedUrl));\n};\n\n/** Returns a freshly signed richtext URL, if we own it. */\nconst signUrl = async (url: string, cache: SignCache) => (await signIfOwned(url, cache)) ?? url;\n\n/**\n * Apply `mapUrl` to every markdown image / link URL and every raw HTML\n * `src` / `href` URL of a richtext value.\n */\nconst mapRichtextUrls = async (value: unknown, mapUrl: (url: string) => Promise<string>) => {\n if (typeof value !== 'string' || !RICHTEXT_URL_HINTS.some((hint) => value.includes(hint))) {\n return value;\n }\n\n // The rebuild loop splices by match index, so matches must be in ascending order\n const matches = RICHTEXT_URL_REGEXES.flatMap((regex) => Array.from(value.matchAll(regex))).sort(\n (a, b) => (a.index as number) - (b.index as number)\n );\n\n if (matches.length === 0) {\n return value;\n }\n\n const urls: string[] = await async.map(matches, (match: RegExpMatchArray) => mapUrl(match[2]));\n\n let result = '';\n let cursor = 0;\n\n matches.forEach((match, index) => {\n const urlStart = (match.index as number) + match[1].length;\n\n // Two patterns matched overlapping text: keep the first, never splice twice\n if (urlStart < cursor) {\n return;\n }\n\n result += value.slice(cursor, urlStart) + urls[index];\n cursor = urlStart + match[2].length;\n });\n\n return result + value.slice(cursor);\n};\n\n/**\n * Apply `mapImage` to every `image` node of a blocks value, recursively.\n * A new array is always returned, the original value is never mutated.\n */\nconst mapBlocksImages = async (\n value: unknown,\n mapImage: (image: File) => Promise<File>\n): Promise<unknown> => {\n if (!Array.isArray(value)) {\n return value;\n }\n\n return async.map(value, async (node: BlocksNode) => {\n if (!node || typeof node !== 'object') {\n return node;\n }\n\n let result = node;\n\n if (node.type === 'image' && node.image) {\n result = { ...result, image: await mapImage(node.image) };\n }\n\n if (Array.isArray(node.children)) {\n result = { ...result, children: await mapBlocksImages(node.children, mapImage) };\n }\n\n return result;\n });\n};\n\nconst getImageFormats = (image: File): [string, ImageFormat][] =>\n Object.entries(image.formats ?? {}) as [string, ImageFormat][];\n\n/** True when the node still carries anything `unsignImage` would remove. */\nconst hasSignatureTraces = (image: File) => {\n if (image.isUrlSigned !== undefined || (image.url ?? '').includes('?')) {\n return true;\n }\n\n return getImageFormats(image).some(\n ([, format]) => format?.isUrlSigned !== undefined || (format?.url ?? '').includes('?')\n );\n};\n\n/**\n * Remove the signature from a blocks `image` node: the signature parameters on\n * the file URL and on every format URL (the same presigner signs them all, so\n * the same parameter names apply), and the `isUrlSigned` flag `signFileUrls`\n * sets on the file and on each format. The node carries a full file object, so\n * the ownership check is made with it rather than with a URL derived stand in.\n */\nconst unsignImage = async (image: File, cache: SignCache = createSignCache()) => {\n // Already bare: nothing to do, and no reason to ask the provider\n if (!hasSignatureTraces(image)) {\n return image;\n }\n\n const bareUrl = stripQueryString(image.url ?? '');\n const signedUrl = await signFileIfOwned({ ...image, url: bareUrl }, cache);\n\n // Not one of ours (different provider, external URL, public provider): the\n // provider hands the URL back unchanged, leave the node as is\n if (signedUrl === null) {\n return image;\n }\n\n const names = getQueryParamNames(signedUrl);\n const result: File = omit(['isUrlSigned'], {\n ...image,\n url: removeQueryParams(image.url ?? '', names),\n });\n\n if (image.formats) {\n result.formats = Object.fromEntries(\n getImageFormats(image).map(([key, format]) => [\n key,\n format && typeof format === 'object'\n ? omit(['isUrlSigned'], {\n ...format,\n ...(format.url ? { url: removeQueryParams(format.url, names) } : {}),\n })\n : format,\n ])\n );\n }\n\n return result;\n};\n\n/**\n * Visitor function to sign media URLs\n */\nconst createSignEntityMediaVisitor =\n (cache: SignCache): SignEntityMediaVisitor =>\n async ({ key, value, attribute }, { set }) => {\n const { signFileUrls } = getService('file');\n\n if (!attribute) {\n return;\n }\n\n switch (attribute.type) {\n case 'blocks':\n set(key, await mapBlocksImages(value, signFileUrls));\n return;\n\n case 'richtext':\n set(key, await mapRichtextUrls(value, (url) => signUrl(url, cache)));\n return;\n\n case 'media': {\n if (isFile(value, attribute)) {\n // If the attribute is repeatable sign each file\n if (attribute.multiple) {\n const signedFiles = await async.map(value, signFileUrls);\n set(key, signedFiles);\n return;\n }\n\n // If the attribute is not repeatable only sign a single file\n const signedFile = await signFileUrls(value);\n set(key, signedFile);\n }\n break;\n }\n\n default:\n break;\n }\n };\n\n/**\n * Visitor function to remove the signature from richtext / blocks URLs before\n * they are persisted. Media attributes are left alone: the files table already\n * holds the unsigned URL.\n */\nconst createUnsignEntityMediaVisitor =\n (cache: SignCache): SignEntityMediaVisitor =>\n async ({ key, value, attribute }, { set }) => {\n if (!attribute) {\n return;\n }\n\n if (attribute.type === 'blocks') {\n set(key, await mapBlocksImages(value, (image) => unsignImage(image, cache)));\n return;\n }\n\n if (attribute.type === 'richtext') {\n set(key, await mapRichtextUrls(value, (url) => stripSignedUrl(url, cache)));\n }\n };\n\n/**\n *\n * Iterate through an entity manager result\n * Check which modelAttributes are media and pre sign the image URLs\n * if they are from the current upload provider\n *\n * @param {Object} entity\n * @param {Object} modelAttributes\n * @param {Map} cache one per document service call, see `SignCache`\n * @returns\n */\nconst signEntityMedia = async (\n entity: any,\n uid: UID.Schema,\n cache: SignCache = createSignCache()\n) => {\n if (!entity) {\n return entity;\n }\n\n // If the entity itself is a file, sign it directly\n if (uid === FILE_MODEL_UID) {\n const { signFileUrls } = getService('file');\n return signFileUrls(entity);\n }\n\n // If the entity is a regular content type, look for media attributes\n const model = strapi.getModel(uid);\n return traverseEntity(\n // @ts-expect-error - FIXME: fix traverseEntity using wrong types\n createSignEntityMediaVisitor(cache),\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n entity\n );\n};\n\n/**\n * Iterate through the input data of a create / update / clone and replace\n * every signed provider URL found in a richtext or blocks attribute with its\n * unsigned form, so that a short lived signature is never persisted.\n */\nconst unsignEntityMedia = async (\n data: any,\n uid: UID.Schema,\n cache: SignCache = createSignCache()\n) => {\n if (!data || uid === FILE_MODEL_UID) {\n return data;\n }\n\n const model = strapi.getModel(uid);\n\n return traverseEntity(\n // @ts-expect-error - FIXME: fix traverseEntity using wrong types\n createUnsignEntityMediaVisitor(cache),\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n data\n );\n};\n\nexport type { SignCache };\n\nexport {\n createSignCache,\n signEntityMedia,\n unsignEntityMedia,\n mapRichtextUrls,\n mapBlocksImages,\n stripSignedUrl,\n unsignImage,\n};\n"],"names":["createSignCache","Map","RICHTEXT_URL_REGEXES","RICHTEXT_URL_HINTS","isFile","value","attribute","type","stripQueryString","url","split","decodeQueryParamName","pair","name","decodeURIComponent","splitUrl","hashIndex","indexOf","fragment","slice","withoutFragment","queryIndex","base","pairs","filter","Boolean","getQueryParamNames","map","removeQueryParams","names","length","kept","includes","join","getFileFromUrl","unsignedUrl","provider","strapi","config","get","ext","path","parse","pop","hash","signFileIfOwned","file","cache","bareUrl","cached","pending","signFileUrls","getService","signedFile","set","signIfOwned","stripSignedUrl","signedUrl","signUrl","mapRichtextUrls","mapUrl","some","hint","matches","flatMap","regex","Array","from","matchAll","sort","a","b","index","urls","async","match","result","cursor","forEach","urlStart","mapBlocksImages","mapImage","isArray","node","image","children","getImageFormats","Object","entries","formats","hasSignatureTraces","isUrlSigned","undefined","format","unsignImage","omit","fromEntries","key","createSignEntityMediaVisitor","multiple","signedFiles","createUnsignEntityMediaVisitor","signEntityMedia","entity","uid","FILE_MODEL_UID","model","getModel","traverseEntity","schema","bind","unsignEntityMedia","data"],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","sources":["../../../../server/src/services/extensions/utils.ts"],"sourcesContent":["import path from 'path';\n\nimport { async, traverseEntity } from '@strapi/utils';\n\nimport type { Schema, UID } from '@strapi/types';\n\nimport { getService } from '../../utils';\nimport { FILE_MODEL_UID } from '../../constants';\n\nimport type { Config, File } from '../../types';\n\ntype SignEntityMediaVisitor = (\n args: {\n key: string;\n value: unknown;\n attribute: Schema.Attribute.AnyAttribute;\n },\n utils: {\n set: (key: string, value: unknown) => void;\n }\n) => Promise<void>;\n\n/**\n * One presign per distinct URL, keyed by the bare (unsigned) URL. The promise\n * is stored rather than its result so concurrent occurrences of the same URL\n * (the `async.map` in `mapRichtextUrls`, or several entries of a `findMany`)\n * share a single provider call.\n *\n * A cache must never outlive the document service call or migration run that\n * created it: a signed URL is only as fresh as the request that produced it.\n */\ntype SignCache = Map<string, Promise<string | null>>;\n\nconst createSignCache = (): SignCache => new Map();\n\n/**\n * URL bearing syntaxes found in a richtext (markdown) value. Each pattern\n * captures a prefix (group 1) and the URL (group 2) so the rebuild loop in\n * `mapRichtextUrls` can splice a new URL in place without touching anything\n * else.\n *\n * - markdown image / link: `![alt](url)` or `[text](url)`. The URL is captured\n * up to the first whitespace or closing parenthesis, which also covers the\n * optional title form `[text](url \"title\")`.\n * - raw HTML `src` / `href` attributes, double or single quoted, on the\n * elements the editor lets users write by hand (`<img src=\"...\" width=\"200\">`\n * is the usual way to size an image).\n */\nconst RICHTEXT_URL_REGEXES = [\n /(!?\\[[^\\]]*\\]\\()([^)\\s]+)/g,\n /(<(?:img|a|source|video|audio)\\b[^>]*?\\b(?:src|href)=\")([^\"]+)/gi,\n /(<(?:img|a|source|video|audio)\\b[^>]*?\\b(?:src|href)=')([^']+)/gi,\n];\n\n/** Cheap short circuit for richtext values that cannot contain a URL we would rewrite. */\nconst RICHTEXT_URL_HINTS = ['](', 'src=', 'href='];\n\ntype BlocksNode = {\n type?: string;\n image?: File;\n children?: unknown;\n [key: string]: unknown;\n};\n\ntype ImageFormat = { url?: string; isUrlSigned?: boolean; [key: string]: unknown };\n\nfunction isFile(value: unknown, attribute: Schema.Attribute.AnyAttribute): value is File {\n if (!value || attribute.type !== 'media') {\n return false;\n }\n\n return true;\n}\n\nconst stripQueryString = (url: string) => url.split('?')[0];\n\nconst decodeQueryParamName = (pair: string) => {\n const name = pair.split('=')[0];\n\n try {\n return decodeURIComponent(name);\n } catch {\n return name;\n }\n};\n\n/**\n * Split a URL into what precedes the `?`, the raw `key=value` pairs of its\n * query string and the `#fragment`, if any. Plain string work on purpose:\n * `new URL` throws on the relative `/uploads/...` paths a local provider\n * writes, and would re-encode the pairs we want to hand back untouched.\n */\nconst splitUrl = (url: string) => {\n const hashIndex = url.indexOf('#');\n const fragment = hashIndex === -1 ? '' : url.slice(hashIndex);\n const withoutFragment = hashIndex === -1 ? url : url.slice(0, hashIndex);\n\n const queryIndex = withoutFragment.indexOf('?');\n\n if (queryIndex === -1) {\n return { base: withoutFragment, pairs: [] as string[], fragment };\n }\n\n return {\n base: withoutFragment.slice(0, queryIndex),\n pairs: withoutFragment\n .slice(queryIndex + 1)\n .split('&')\n .filter(Boolean),\n fragment,\n };\n};\n\n/** Names of the query parameters of `url`, decoded, in order. */\nconst getQueryParamNames = (url: string): string[] => splitUrl(url).pairs.map(decodeQueryParamName);\n\n/**\n * `url` with every query parameter whose name is in `names` removed. The\n * surviving pairs keep their order and encoding; the `?` is dropped when\n * nothing is left.\n */\nconst removeQueryParams = (url: string, names: string[]): string => {\n const { base, pairs, fragment } = splitUrl(url);\n\n if (pairs.length === 0 || names.length === 0) {\n return url;\n }\n\n const kept = pairs.filter((pair) => !names.includes(decodeQueryParamName(pair)));\n\n return kept.length === 0 ? `${base}${fragment}` : `${base}?${kept.join('&')}${fragment}`;\n};\n\n/**\n * Build the minimal file shape `signFileUrls` needs out of a bare URL, as found\n * in a richtext value.\n *\n * `hash` and `ext` are taken from the last path segment and `path` is left\n * unset, which rebuilds the S3 key for the default layout and for the\n * `rootPath` provider option (the provider prefixes it itself). A URL whose key\n * has extra segments (a file uploaded with the per-file `path` option, or an\n * older key layout) is still recognised as ours by the provider, but it is\n * signed with a key that does not exist and the link returns 403. Media\n * signing has the same limit because `path` is not persisted on the file\n * (`content-types/file.ts`). Not addressed here.\n */\nconst getFileFromUrl = (url: string): File => {\n const unsignedUrl = stripQueryString(url);\n const { provider } = strapi.config.get<Config>('plugin::upload');\n const { name, ext } = path.parse(unsignedUrl.split('/').pop() ?? '');\n\n return { url: unsignedUrl, hash: name, ext, provider } as File;\n};\n\n/**\n * Ask the provider to sign `file` and return the signed URL if the provider\n * owns it, otherwise `null`. Memoised in `cache` by the bare URL.\n *\n * Ownership is decided by whether the provider changed the URL: `signFileUrls`\n * flags every file of the configured provider as signed, but the provider\n * itself returns URLs it does not own untouched (an S3 URL from another\n * bucket, an external site, a local `/uploads` path), so a changed URL is the\n * only reliable signal. `file.url` must already be the bare URL so the\n * comparison is meaningful.\n */\nconst signFileIfOwned = (file: File, cache: SignCache): Promise<string | null> => {\n const bareUrl = file.url ?? '';\n const cached = cache.get(bareUrl);\n\n if (cached) {\n return cached;\n }\n\n const pending = (async () => {\n const { signFileUrls } = getService('file');\n const signedFile = await signFileUrls(file);\n\n return signedFile.url && signedFile.url !== bareUrl ? signedFile.url : null;\n })();\n\n cache.set(bareUrl, pending);\n\n return pending;\n};\n\n/**\n * Signs a richtext URL if the configured provider owns it, otherwise `null`.\n * See `signFileIfOwned` for how ownership is decided. A single predicate means\n * the read path (sign) and the write path (unsign) can never disagree about\n * what they own.\n */\nconst signIfOwned = (url: string, cache: SignCache): Promise<string | null> =>\n signFileIfOwned(getFileFromUrl(url), cache);\n\n/**\n * Returns a richtext URL with its signature removed, if we own it.\n *\n * Only the query parameters the provider's own fresh signature introduces are\n * removed (for S3, the `X-Amz-*` family), every other parameter is kept. That\n * way a provider that signs whatever it is handed cannot make us drop `?v=x`\n * from a link to an external site. A parameter of an old signature that the\n * fresh one no longer carries (an `X-Amz-Security-Token` from credentials no\n * longer in use, say) survives in the row; it is not a signature, and the read\n * path replaces the whole URL with the provider output anyway.\n */\nconst stripSignedUrl = async (url: string, cache: SignCache) => {\n // Nothing to strip, and no reason to ask the provider\n if (!url.includes('?')) {\n return url;\n }\n\n const signedUrl = await signIfOwned(url, cache);\n\n if (signedUrl === null) {\n return url;\n }\n\n return removeQueryParams(url, getQueryParamNames(signedUrl));\n};\n\n/** Returns a freshly signed richtext URL, if we own it. */\nconst signUrl = async (url: string, cache: SignCache) => (await signIfOwned(url, cache)) ?? url;\n\n/**\n * Apply `mapUrl` to every markdown image / link URL and every raw HTML\n * `src` / `href` URL of a richtext value.\n */\nconst mapRichtextUrls = async (value: unknown, mapUrl: (url: string) => Promise<string>) => {\n if (typeof value !== 'string' || !RICHTEXT_URL_HINTS.some((hint) => value.includes(hint))) {\n return value;\n }\n\n // The rebuild loop splices by match index, so matches must be in ascending order\n const matches = RICHTEXT_URL_REGEXES.flatMap((regex) => Array.from(value.matchAll(regex))).sort(\n (a, b) => (a.index as number) - (b.index as number)\n );\n\n if (matches.length === 0) {\n return value;\n }\n\n const urls: string[] = await async.map(matches, (match: RegExpMatchArray) => mapUrl(match[2]));\n\n let result = '';\n let cursor = 0;\n\n matches.forEach((match, index) => {\n const urlStart = (match.index as number) + match[1].length;\n\n // Two patterns matched overlapping text: keep the first, never splice twice\n if (urlStart < cursor) {\n return;\n }\n\n result += value.slice(cursor, urlStart) + urls[index];\n cursor = urlStart + match[2].length;\n });\n\n return result + value.slice(cursor);\n};\n\n/**\n * Apply `mapImage` to every `image` node of a blocks value, recursively.\n * A new array is always returned, the original value is never mutated.\n */\nconst mapBlocksImages = async (\n value: unknown,\n mapImage: (image: File) => Promise<File>\n): Promise<unknown> => {\n if (!Array.isArray(value)) {\n return value;\n }\n\n return async.map(value, async (node: BlocksNode) => {\n if (!node || typeof node !== 'object') {\n return node;\n }\n\n let result = node;\n\n if (node.type === 'image' && node.image) {\n result = { ...result, image: await mapImage(node.image) };\n }\n\n if (Array.isArray(node.children)) {\n result = { ...result, children: await mapBlocksImages(node.children, mapImage) };\n }\n\n return result;\n });\n};\n\nconst getImageFormats = (image: File): [string, ImageFormat][] =>\n Object.entries(image.formats ?? {}) as [string, ImageFormat][];\n\n/** True when the node still carries anything `unsignImage` would remove. */\nconst hasSignatureTraces = (image: File) => {\n if (image.isUrlSigned !== undefined || (image.url ?? '').includes('?')) {\n return true;\n }\n\n return getImageFormats(image).some(\n ([, format]) => format?.isUrlSigned !== undefined || (format?.url ?? '').includes('?')\n );\n};\n\n/**\n * Remove the signature from a blocks `image` node: the signature parameters on\n * the file URL and on every format URL (the same presigner signs them all, so\n * the same parameter names apply), and the `isUrlSigned` flag `signFileUrls`\n * sets on the file and on each format. The node carries a full file object, so\n * the ownership check is made with it rather than with a URL derived stand in.\n */\nconst unsignImage = async (image: File, cache: SignCache = createSignCache()) => {\n // Already bare: nothing to do, and no reason to ask the provider\n if (!hasSignatureTraces(image)) {\n return image;\n }\n\n const bareUrl = stripQueryString(image.url ?? '');\n const signedUrl = await signFileIfOwned({ ...image, url: bareUrl }, cache);\n\n // Not one of ours (different provider, external URL, public provider): the\n // provider hands the URL back unchanged, leave the node as is\n if (signedUrl === null) {\n return image;\n }\n\n const names = getQueryParamNames(signedUrl);\n const { isUrlSigned: _isUrlSigned, ...result }: File = {\n ...image,\n url: removeQueryParams(image.url ?? '', names),\n };\n\n if (image.formats) {\n result.formats = Object.fromEntries(\n getImageFormats(image).map(([key, format]) => {\n if (!format || typeof format !== 'object') {\n return [key, format];\n }\n\n const { isUrlSigned: _formatIsUrlSigned, ...unsignedFormat } = {\n ...format,\n ...(format.url ? { url: removeQueryParams(format.url, names) } : {}),\n };\n\n return [key, unsignedFormat];\n })\n );\n }\n\n return result;\n};\n\n/**\n * Visitor function to sign media URLs\n */\nconst createSignEntityMediaVisitor =\n (cache: SignCache): SignEntityMediaVisitor =>\n async ({ key, value, attribute }, { set }) => {\n const { signFileUrls } = getService('file');\n\n if (!attribute) {\n return;\n }\n\n switch (attribute.type) {\n case 'blocks':\n set(key, await mapBlocksImages(value, signFileUrls));\n return;\n\n case 'richtext':\n set(key, await mapRichtextUrls(value, (url) => signUrl(url, cache)));\n return;\n\n case 'media': {\n if (isFile(value, attribute)) {\n // If the attribute is repeatable sign each file\n if (attribute.multiple) {\n const signedFiles = await async.map(value, signFileUrls);\n set(key, signedFiles);\n return;\n }\n\n // If the attribute is not repeatable only sign a single file\n const signedFile = await signFileUrls(value);\n set(key, signedFile);\n }\n break;\n }\n\n default:\n break;\n }\n };\n\n/**\n * Visitor function to remove the signature from richtext / blocks URLs before\n * they are persisted. Media attributes are left alone: the files table already\n * holds the unsigned URL.\n */\nconst createUnsignEntityMediaVisitor =\n (cache: SignCache): SignEntityMediaVisitor =>\n async ({ key, value, attribute }, { set }) => {\n if (!attribute) {\n return;\n }\n\n if (attribute.type === 'blocks') {\n set(key, await mapBlocksImages(value, (image) => unsignImage(image, cache)));\n return;\n }\n\n if (attribute.type === 'richtext') {\n set(key, await mapRichtextUrls(value, (url) => stripSignedUrl(url, cache)));\n }\n };\n\n/**\n *\n * Iterate through an entity manager result\n * Check which modelAttributes are media and pre sign the image URLs\n * if they are from the current upload provider\n *\n * @param {Object} entity\n * @param {Object} modelAttributes\n * @param {Map} cache one per document service call, see `SignCache`\n * @returns\n */\nconst signEntityMedia = async (\n entity: any,\n uid: UID.Schema,\n cache: SignCache = createSignCache()\n) => {\n if (!entity) {\n return entity;\n }\n\n // If the entity itself is a file, sign it directly\n if (uid === FILE_MODEL_UID) {\n const { signFileUrls } = getService('file');\n return signFileUrls(entity);\n }\n\n // If the entity is a regular content type, look for media attributes\n const model = strapi.getModel(uid);\n return traverseEntity(\n // @ts-expect-error - FIXME: fix traverseEntity using wrong types\n createSignEntityMediaVisitor(cache),\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n entity\n );\n};\n\n/**\n * Iterate through the input data of a create / update / clone and replace\n * every signed provider URL found in a richtext or blocks attribute with its\n * unsigned form, so that a short lived signature is never persisted.\n */\nconst unsignEntityMedia = async (\n data: any,\n uid: UID.Schema,\n cache: SignCache = createSignCache()\n) => {\n if (!data || uid === FILE_MODEL_UID) {\n return data;\n }\n\n const model = strapi.getModel(uid);\n\n return traverseEntity(\n // @ts-expect-error - FIXME: fix traverseEntity using wrong types\n createUnsignEntityMediaVisitor(cache),\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n data\n );\n};\n\nexport type { SignCache };\n\nexport {\n createSignCache,\n signEntityMedia,\n unsignEntityMedia,\n mapRichtextUrls,\n mapBlocksImages,\n stripSignedUrl,\n unsignImage,\n};\n"],"names":["createSignCache","Map","RICHTEXT_URL_REGEXES","RICHTEXT_URL_HINTS","isFile","value","attribute","type","stripQueryString","url","split","decodeQueryParamName","pair","name","decodeURIComponent","splitUrl","hashIndex","indexOf","fragment","slice","withoutFragment","queryIndex","base","pairs","filter","Boolean","getQueryParamNames","map","removeQueryParams","names","length","kept","includes","join","getFileFromUrl","unsignedUrl","provider","strapi","config","get","ext","path","parse","pop","hash","signFileIfOwned","file","cache","bareUrl","cached","pending","signFileUrls","getService","signedFile","set","signIfOwned","stripSignedUrl","signedUrl","signUrl","mapRichtextUrls","mapUrl","some","hint","matches","flatMap","regex","Array","from","matchAll","sort","a","b","index","urls","async","match","result","cursor","forEach","urlStart","mapBlocksImages","mapImage","isArray","node","image","children","getImageFormats","Object","entries","formats","hasSignatureTraces","isUrlSigned","undefined","format","unsignImage","_isUrlSigned","fromEntries","key","_formatIsUrlSigned","unsignedFormat","createSignEntityMediaVisitor","multiple","signedFiles","createUnsignEntityMediaVisitor","signEntityMedia","entity","uid","FILE_MODEL_UID","model","getModel","traverseEntity","schema","bind","unsignEntityMedia","data"],"mappings":"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"}