@strapi/upload 5.37.1 → 5.38.1
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.
- package/dist/admin/ai/components/AIAssetCard.js.map +1 -1
- package/dist/admin/ai/components/AIAssetCard.mjs +1 -1
- package/dist/admin/ai/components/AIAssetCard.mjs.map +1 -1
- package/dist/admin/ai/components/AIUploadModal.js.map +1 -1
- package/dist/admin/ai/components/AIUploadModal.mjs +1 -1
- package/dist/admin/ai/components/AIUploadModal.mjs.map +1 -1
- package/dist/admin/components/AssetCard/AssetCard.js.map +1 -1
- package/dist/admin/components/AssetCard/AssetCard.mjs.map +1 -1
- package/dist/admin/components/AssetCard/AssetCardBase.js.map +1 -1
- package/dist/admin/components/AssetCard/AssetCardBase.mjs.map +1 -1
- package/dist/admin/components/AssetCard/AudioAssetCard.js.map +1 -1
- package/dist/admin/components/AssetCard/AudioAssetCard.mjs.map +1 -1
- package/dist/admin/components/AssetCard/AudioPreview.js.map +1 -1
- package/dist/admin/components/AssetCard/AudioPreview.mjs.map +1 -1
- package/dist/admin/components/AssetCard/DocAssetCard.js.map +1 -1
- package/dist/admin/components/AssetCard/DocAssetCard.mjs.map +1 -1
- package/dist/admin/components/AssetCard/ImageAssetCard.js.map +1 -1
- package/dist/admin/components/AssetCard/ImageAssetCard.mjs.map +1 -1
- package/dist/admin/components/AssetCard/UploadingAssetCard.js.map +1 -1
- package/dist/admin/components/AssetCard/UploadingAssetCard.mjs.map +1 -1
- package/dist/admin/components/AssetCard/VideoAssetCard.js.map +1 -1
- package/dist/admin/components/AssetCard/VideoAssetCard.mjs.map +1 -1
- package/dist/admin/components/AssetCard/VideoPreview.js.map +1 -1
- package/dist/admin/components/AssetCard/VideoPreview.mjs.map +1 -1
- package/dist/admin/components/AssetDialog/AssetDialog.js.map +1 -1
- package/dist/admin/components/AssetDialog/AssetDialog.mjs.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/BrowseStep.js +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/BrowseStep.js.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/BrowseStep.mjs +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/BrowseStep.mjs.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/Filters.js.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/Filters.mjs.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/PageSize.js.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/PageSize.mjs.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/PaginationFooter/Pagination.js.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/PaginationFooter/Pagination.mjs.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/PaginationFooter/PaginationFooter.js.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/PaginationFooter/PaginationFooter.mjs.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/SearchAsset/SearchAsset.js.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/SearchAsset/SearchAsset.mjs.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/utils/isSelectable.js.map +1 -1
- package/dist/admin/components/AssetDialog/BrowseStep/utils/isSelectable.mjs.map +1 -1
- package/dist/admin/components/AssetDialog/DialogFooter.js.map +1 -1
- package/dist/admin/components/AssetDialog/DialogFooter.mjs.map +1 -1
- package/dist/admin/components/AssetDialog/SelectedStep/SelectedStep.js.map +1 -1
- package/dist/admin/components/AssetDialog/SelectedStep/SelectedStep.mjs.map +1 -1
- package/dist/admin/components/AssetGridList/AssetGridList.js.map +1 -1
- package/dist/admin/components/AssetGridList/AssetGridList.mjs.map +1 -1
- package/dist/admin/components/AssetGridList/Draggable.js.map +1 -1
- package/dist/admin/components/AssetGridList/Draggable.mjs.map +1 -1
- package/dist/admin/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/admin/components/Breadcrumbs/Breadcrumbs.mjs.map +1 -1
- package/dist/admin/components/Breadcrumbs/CrumbSimpleMenuAsync.js.map +1 -1
- package/dist/admin/components/Breadcrumbs/CrumbSimpleMenuAsync.mjs.map +1 -1
- package/dist/admin/components/BulkMoveDialog/BulkMoveDialog.js.map +1 -1
- package/dist/admin/components/BulkMoveDialog/BulkMoveDialog.mjs.map +1 -1
- package/dist/admin/components/ContextInfo/ContextInfo.js.map +1 -1
- package/dist/admin/components/ContextInfo/ContextInfo.mjs.map +1 -1
- package/dist/admin/components/CopyLinkButton/CopyLinkButton.js.map +1 -1
- package/dist/admin/components/CopyLinkButton/CopyLinkButton.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/DialogHeader.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/DialogHeader.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/EditAssetContent.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/EditAssetContent.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/AssetPreview.js +1 -0
- package/dist/admin/components/EditAssetDialog/PreviewBox/AssetPreview.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/AssetPreview.mjs +1 -0
- package/dist/admin/components/EditAssetDialog/PreviewBox/AssetPreview.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/CroppingActions.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/CroppingActions.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/FocalPointActions.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/FocalPointActions.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewBox.js +1 -4
- package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewBox.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewBox.mjs +1 -4
- package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewBox.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewComponents.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewComponents.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/RemoveAssetDialog.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/RemoveAssetDialog.mjs.map +1 -1
- package/dist/admin/components/EditAssetDialog/ReplaceMediaButton.js.map +1 -1
- package/dist/admin/components/EditAssetDialog/ReplaceMediaButton.mjs.map +1 -1
- package/dist/admin/components/EditFolderDialog/EditFolderDialog.js.map +1 -1
- package/dist/admin/components/EditFolderDialog/EditFolderDialog.mjs.map +1 -1
- package/dist/admin/components/EditFolderDialog/ModalHeader/ModalHeader.js.map +1 -1
- package/dist/admin/components/EditFolderDialog/ModalHeader/ModalHeader.mjs.map +1 -1
- package/dist/admin/components/EditFolderDialog/RemoveFolderDialog.js.map +1 -1
- package/dist/admin/components/EditFolderDialog/RemoveFolderDialog.mjs.map +1 -1
- package/dist/admin/components/EmptyAssets/EmptyAssetGrid.js.map +1 -1
- package/dist/admin/components/EmptyAssets/EmptyAssetGrid.mjs.map +1 -1
- package/dist/admin/components/EmptyAssets/EmptyAssets.js.map +1 -1
- package/dist/admin/components/EmptyAssets/EmptyAssets.mjs.map +1 -1
- package/dist/admin/components/FilterList/FilterList.js.map +1 -1
- package/dist/admin/components/FilterList/FilterList.mjs.map +1 -1
- package/dist/admin/components/FilterList/FilterTag.js.map +1 -1
- package/dist/admin/components/FilterList/FilterTag.mjs.map +1 -1
- package/dist/admin/components/FilterPopover/FilterPopover.js.map +1 -1
- package/dist/admin/components/FilterPopover/FilterPopover.mjs.map +1 -1
- package/dist/admin/components/FilterPopover/FilterValueInput.js.map +1 -1
- package/dist/admin/components/FilterPopover/FilterValueInput.mjs.map +1 -1
- package/dist/admin/components/FilterPopover/utils/getFilterList.js.map +1 -1
- package/dist/admin/components/FilterPopover/utils/getFilterList.mjs.map +1 -1
- package/dist/admin/components/FolderCard/FolderCard/FolderCard.js.map +1 -1
- package/dist/admin/components/FolderCard/FolderCard/FolderCard.mjs.map +1 -1
- package/dist/admin/components/FolderCard/FolderCardBody/FolderCardBody.js.map +1 -1
- package/dist/admin/components/FolderCard/FolderCardBody/FolderCardBody.mjs.map +1 -1
- package/dist/admin/components/FolderCard/FolderCardBodyAction/FolderCardBodyAction.js.map +1 -1
- package/dist/admin/components/FolderCard/FolderCardBodyAction/FolderCardBodyAction.mjs.map +1 -1
- package/dist/admin/components/FolderCard/FolderCardCheckbox/FolderCardCheckbox.js.map +1 -1
- package/dist/admin/components/FolderCard/FolderCardCheckbox/FolderCardCheckbox.mjs.map +1 -1
- package/dist/admin/components/FolderCard/contexts/FolderCard.js.map +1 -1
- package/dist/admin/components/FolderCard/contexts/FolderCard.mjs.map +1 -1
- package/dist/admin/components/FolderGridList/FolderGridList.js.map +1 -1
- package/dist/admin/components/FolderGridList/FolderGridList.mjs.map +1 -1
- package/dist/admin/components/MediaLibraryDialog/MediaLibraryDialog.js.map +1 -1
- package/dist/admin/components/MediaLibraryDialog/MediaLibraryDialog.mjs.map +1 -1
- package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAsset.js.map +1 -1
- package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAsset.mjs.map +1 -1
- package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAssetActions.js.map +1 -1
- package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAssetActions.mjs.map +1 -1
- package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAssets.js.map +1 -1
- package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAssets.mjs.map +1 -1
- package/dist/admin/components/MediaLibraryInput/Carousel/EmptyStateAsset.js.map +1 -1
- package/dist/admin/components/MediaLibraryInput/Carousel/EmptyStateAsset.mjs.map +1 -1
- package/dist/admin/components/MediaLibraryInput/MediaLibraryInput.js.map +1 -1
- package/dist/admin/components/MediaLibraryInput/MediaLibraryInput.mjs.map +1 -1
- package/dist/admin/components/SelectTree/Option.js.map +1 -1
- package/dist/admin/components/SelectTree/Option.mjs.map +1 -1
- package/dist/admin/components/SelectTree/SelectTree.js.map +1 -1
- package/dist/admin/components/SelectTree/SelectTree.mjs +1 -1
- package/dist/admin/components/SelectTree/SelectTree.mjs.map +1 -1
- package/dist/admin/components/SelectTree/utils/flattenTree.js.map +1 -1
- package/dist/admin/components/SelectTree/utils/flattenTree.mjs.map +1 -1
- package/dist/admin/components/SelectTree/utils/getOpenValues.js.map +1 -1
- package/dist/admin/components/SelectTree/utils/getOpenValues.mjs.map +1 -1
- package/dist/admin/components/SelectTree/utils/getValuesToClose.js.map +1 -1
- package/dist/admin/components/SelectTree/utils/getValuesToClose.mjs.map +1 -1
- package/dist/admin/components/SortPicker/SortPicker.js.map +1 -1
- package/dist/admin/components/SortPicker/SortPicker.mjs.map +1 -1
- package/dist/admin/components/TableList/CellContent.js.map +1 -1
- package/dist/admin/components/TableList/CellContent.mjs.map +1 -1
- package/dist/admin/components/TableList/PreviewCell.js.map +1 -1
- package/dist/admin/components/TableList/PreviewCell.mjs +1 -1
- package/dist/admin/components/TableList/PreviewCell.mjs.map +1 -1
- package/dist/admin/components/TableList/TableList.js.map +1 -1
- package/dist/admin/components/TableList/TableList.mjs +1 -1
- package/dist/admin/components/TableList/TableList.mjs.map +1 -1
- package/dist/admin/components/TableList/TableRows.js.map +1 -1
- package/dist/admin/components/TableList/TableRows.mjs.map +1 -1
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/AddAssetStep.js.map +1 -1
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/AddAssetStep.mjs.map +1 -1
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromComputerForm.js.map +1 -1
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromComputerForm.mjs.map +1 -1
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.js.map +1 -1
- package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.mjs.map +1 -1
- package/dist/admin/components/UploadAssetDialog/PendingAssetStep/PendingAssetStep.js.map +1 -1
- package/dist/admin/components/UploadAssetDialog/PendingAssetStep/PendingAssetStep.mjs.map +1 -1
- package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.js.map +1 -1
- package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.mjs.map +1 -1
- package/dist/admin/components/UploadProgress/UploadProgress.js.map +1 -1
- package/dist/admin/components/UploadProgress/UploadProgress.mjs.map +1 -1
- package/dist/admin/constants.js.map +1 -1
- package/dist/admin/constants.mjs.map +1 -1
- package/dist/admin/future/App.js.map +1 -1
- package/dist/admin/future/App.mjs.map +1 -1
- package/dist/admin/future/components/Drawer.js +189 -0
- package/dist/admin/future/components/Drawer.js.map +1 -0
- package/dist/admin/future/components/Drawer.mjs +166 -0
- package/dist/admin/future/components/Drawer.mjs.map +1 -0
- package/dist/admin/future/components/UploadProgressDialog.js +60 -101
- package/dist/admin/future/components/UploadProgressDialog.js.map +1 -1
- package/dist/admin/future/components/UploadProgressDialog.mjs +62 -84
- package/dist/admin/future/components/UploadProgressDialog.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/AssetsPage.js +155 -100
- package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
- package/dist/admin/future/pages/Assets/AssetsPage.mjs +160 -105
- package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +406 -0
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +384 -0
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.js +215 -0
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.mjs +194 -0
- package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/AssetsGrid.js +37 -8
- package/dist/admin/future/pages/Assets/components/AssetsGrid.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs +38 -9
- package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsTable.js +35 -5
- package/dist/admin/future/pages/Assets/components/AssetsTable.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsTable.mjs +37 -7
- package/dist/admin/future/pages/Assets/components/AssetsTable.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/CreateFolderDialog.js +143 -0
- package/dist/admin/future/pages/Assets/components/CreateFolderDialog.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/CreateFolderDialog.mjs +141 -0
- package/dist/admin/future/pages/Assets/components/CreateFolderDialog.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZone.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZone.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/ImportFromUrlDialog.js +127 -0
- package/dist/admin/future/pages/Assets/components/ImportFromUrlDialog.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/ImportFromUrlDialog.mjs +106 -0
- package/dist/admin/future/pages/Assets/components/ImportFromUrlDialog.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/constants.js.map +1 -1
- package/dist/admin/future/pages/Assets/constants.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useFolderInfo.js.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useFolderInfo.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.js.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js.map +1 -1
- package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs.map +1 -1
- package/dist/admin/future/services/api.js +181 -97
- package/dist/admin/future/services/api.js.map +1 -1
- package/dist/admin/future/services/api.mjs +181 -98
- package/dist/admin/future/services/api.mjs.map +1 -1
- package/dist/admin/future/services/assets.js +14 -1
- package/dist/admin/future/services/assets.js.map +1 -1
- package/dist/admin/future/services/assets.mjs +14 -2
- package/dist/admin/future/services/assets.mjs.map +1 -1
- package/dist/admin/future/services/folders.js +16 -1
- package/dist/admin/future/services/folders.js.map +1 -1
- package/dist/admin/future/services/folders.mjs +16 -2
- package/dist/admin/future/services/folders.mjs.map +1 -1
- package/dist/admin/future/store/hooks.js.map +1 -1
- package/dist/admin/future/store/hooks.mjs.map +1 -1
- package/dist/admin/future/store/uploadProgress.js +7 -4
- package/dist/admin/future/store/uploadProgress.js.map +1 -1
- package/dist/admin/future/store/uploadProgress.mjs +7 -4
- package/dist/admin/future/store/uploadProgress.mjs.map +1 -1
- package/dist/admin/future/utils/files.js +105 -3
- package/dist/admin/future/utils/files.js.map +1 -1
- package/dist/admin/future/utils/files.mjs +104 -4
- package/dist/admin/future/utils/files.mjs.map +1 -1
- package/dist/admin/future/utils/getAssetIcon.js +3 -3
- package/dist/admin/future/utils/getAssetIcon.js.map +1 -1
- package/dist/admin/future/utils/getAssetIcon.mjs +4 -4
- package/dist/admin/future/utils/getAssetIcon.mjs.map +1 -1
- package/dist/admin/future/utils/translations.js.map +1 -1
- package/dist/admin/future/utils/translations.mjs.map +1 -1
- package/dist/admin/hooks/useAIMetadataJob.js.map +1 -1
- package/dist/admin/hooks/useAIMetadataJob.mjs.map +1 -1
- package/dist/admin/hooks/useAiAvailability.js.map +1 -1
- package/dist/admin/hooks/useAiAvailability.mjs.map +1 -1
- package/dist/admin/hooks/useAssets.js.map +1 -1
- package/dist/admin/hooks/useAssets.mjs.map +1 -1
- package/dist/admin/hooks/useBulkEdit.js.map +1 -1
- package/dist/admin/hooks/useBulkEdit.mjs.map +1 -1
- package/dist/admin/hooks/useBulkMove.js.map +1 -1
- package/dist/admin/hooks/useBulkMove.mjs.map +1 -1
- package/dist/admin/hooks/useBulkRemove.js.map +1 -1
- package/dist/admin/hooks/useBulkRemove.mjs.map +1 -1
- package/dist/admin/hooks/useConfig.js.map +1 -1
- package/dist/admin/hooks/useConfig.mjs.map +1 -1
- package/dist/admin/hooks/useCropImg.js.map +1 -1
- package/dist/admin/hooks/useCropImg.mjs.map +1 -1
- package/dist/admin/hooks/useEditAsset.js.map +1 -1
- package/dist/admin/hooks/useEditAsset.mjs.map +1 -1
- package/dist/admin/hooks/useEditFolder.js.map +1 -1
- package/dist/admin/hooks/useEditFolder.mjs.map +1 -1
- package/dist/admin/hooks/useFolder.js.map +1 -1
- package/dist/admin/hooks/useFolder.mjs.map +1 -1
- package/dist/admin/hooks/useFolderStructure.js.map +1 -1
- package/dist/admin/hooks/useFolderStructure.mjs.map +1 -1
- package/dist/admin/hooks/useFolders.js.map +1 -1
- package/dist/admin/hooks/useFolders.mjs.map +1 -1
- package/dist/admin/hooks/useMediaLibraryPermissions.js.map +1 -1
- package/dist/admin/hooks/useMediaLibraryPermissions.mjs.map +1 -1
- package/dist/admin/hooks/useModalQueryParams.js.map +1 -1
- package/dist/admin/hooks/useModalQueryParams.mjs.map +1 -1
- package/dist/admin/hooks/usePersistentState.js.map +1 -1
- package/dist/admin/hooks/usePersistentState.mjs.map +1 -1
- package/dist/admin/hooks/useRemoveAsset.js.map +1 -1
- package/dist/admin/hooks/useRemoveAsset.mjs.map +1 -1
- package/dist/admin/hooks/useSelectionState.js.map +1 -1
- package/dist/admin/hooks/useSelectionState.mjs.map +1 -1
- package/dist/admin/hooks/useSettings.js.map +1 -1
- package/dist/admin/hooks/useSettings.mjs.map +1 -1
- package/dist/admin/hooks/useTracking.js.map +1 -1
- package/dist/admin/hooks/useTracking.mjs.map +1 -1
- package/dist/admin/hooks/useUpload.js.map +1 -1
- package/dist/admin/hooks/useUpload.mjs.map +1 -1
- package/dist/admin/hooks/utils/renameKeys.js.map +1 -1
- package/dist/admin/hooks/utils/renameKeys.mjs.map +1 -1
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs.map +1 -1
- package/dist/admin/package.json.js +1 -152
- package/dist/admin/package.json.js.map +1 -1
- package/dist/admin/package.json.mjs +2 -141
- package/dist/admin/package.json.mjs.map +1 -1
- package/dist/admin/pages/App/App.js.map +1 -1
- package/dist/admin/pages/App/App.mjs.map +1 -1
- package/dist/admin/pages/App/ConfigureTheView/ConfigureTheView.js.map +1 -1
- package/dist/admin/pages/App/ConfigureTheView/ConfigureTheView.mjs +3 -3
- package/dist/admin/pages/App/ConfigureTheView/ConfigureTheView.mjs.map +1 -1
- package/dist/admin/pages/App/ConfigureTheView/components/Settings.js.map +1 -1
- package/dist/admin/pages/App/ConfigureTheView/components/Settings.mjs.map +1 -1
- package/dist/admin/pages/App/ConfigureTheView/state/actionTypes.js.map +1 -1
- package/dist/admin/pages/App/ConfigureTheView/state/actionTypes.mjs.map +1 -1
- package/dist/admin/pages/App/ConfigureTheView/state/actions.js.map +1 -1
- package/dist/admin/pages/App/ConfigureTheView/state/actions.mjs.map +1 -1
- package/dist/admin/pages/App/ConfigureTheView/state/init.js.map +1 -1
- package/dist/admin/pages/App/ConfigureTheView/state/init.mjs.map +1 -1
- package/dist/admin/pages/App/ConfigureTheView/state/reducer.js.map +1 -1
- package/dist/admin/pages/App/ConfigureTheView/state/reducer.mjs +1 -1
- package/dist/admin/pages/App/ConfigureTheView/state/reducer.mjs.map +1 -1
- package/dist/admin/pages/App/MediaLibrary.js +1 -1
- package/dist/admin/pages/App/MediaLibrary.js.map +1 -1
- package/dist/admin/pages/App/MediaLibrary.mjs +2 -2
- package/dist/admin/pages/App/MediaLibrary.mjs.map +1 -1
- package/dist/admin/pages/App/components/BulkActions.js.map +1 -1
- package/dist/admin/pages/App/components/BulkActions.mjs.map +1 -1
- package/dist/admin/pages/App/components/BulkDeleteButton.js.map +1 -1
- package/dist/admin/pages/App/components/BulkDeleteButton.mjs.map +1 -1
- package/dist/admin/pages/App/components/BulkMoveButton.js.map +1 -1
- package/dist/admin/pages/App/components/BulkMoveButton.mjs.map +1 -1
- package/dist/admin/pages/App/components/EmptyOrNoPermissions.js.map +1 -1
- package/dist/admin/pages/App/components/EmptyOrNoPermissions.mjs.map +1 -1
- package/dist/admin/pages/App/components/Filters.js.map +1 -1
- package/dist/admin/pages/App/components/Filters.mjs.map +1 -1
- package/dist/admin/pages/App/components/Header.js.map +1 -1
- package/dist/admin/pages/App/components/Header.mjs +2 -2
- package/dist/admin/pages/App/components/Header.mjs.map +1 -1
- package/dist/admin/pages/SettingsPage/SettingsPage.js.map +1 -1
- package/dist/admin/pages/SettingsPage/SettingsPage.mjs.map +1 -1
- package/dist/admin/pages/SettingsPage/reducer.js.map +1 -1
- package/dist/admin/pages/SettingsPage/reducer.mjs.map +1 -1
- package/dist/admin/pluginId.js.map +1 -1
- package/dist/admin/pluginId.mjs.map +1 -1
- package/dist/admin/src/future/components/Drawer.d.ts +32 -0
- package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.d.ts +8 -0
- package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetPreview.d.ts +6 -0
- package/dist/admin/src/future/pages/Assets/components/AssetsGrid.d.ts +2 -1
- package/dist/admin/src/future/pages/Assets/components/AssetsTable.d.ts +2 -1
- package/dist/admin/src/future/pages/Assets/components/CreateFolderDialog.d.ts +8 -0
- package/dist/admin/src/future/pages/Assets/components/ImportFromUrlDialog.d.ts +7 -0
- package/dist/admin/src/future/services/api.d.ts +10 -1
- package/dist/admin/src/future/services/assets.d.ts +2 -2
- package/dist/admin/src/future/services/folders.d.ts +1 -1
- package/dist/admin/src/future/store/uploadProgress.d.ts +1 -1
- package/dist/admin/src/future/utils/files.d.ts +70 -0
- package/dist/admin/translations/en.json.js +35 -3
- package/dist/admin/translations/en.json.js.map +1 -1
- package/dist/admin/translations/en.json.mjs +35 -3
- package/dist/admin/translations/en.json.mjs.map +1 -1
- package/dist/admin/translations/es.json.js +26 -2
- package/dist/admin/translations/es.json.js.map +1 -1
- package/dist/admin/translations/es.json.mjs +26 -2
- package/dist/admin/translations/es.json.mjs.map +1 -1
- package/dist/admin/utils/appendSearchParamsToUrl.js.map +1 -1
- package/dist/admin/utils/appendSearchParamsToUrl.mjs.map +1 -1
- package/dist/admin/utils/containsAssetFilter.js.map +1 -1
- package/dist/admin/utils/containsAssetFilter.mjs.map +1 -1
- package/dist/admin/utils/createAssetUrl.js.map +1 -1
- package/dist/admin/utils/createAssetUrl.mjs.map +1 -1
- package/dist/admin/utils/displayedFilters.js.map +1 -1
- package/dist/admin/utils/displayedFilters.mjs.map +1 -1
- package/dist/admin/utils/downloadFile.js.map +1 -1
- package/dist/admin/utils/downloadFile.mjs.map +1 -1
- package/dist/admin/utils/findRecursiveFolderByValue.js.map +1 -1
- package/dist/admin/utils/findRecursiveFolderByValue.mjs.map +1 -1
- package/dist/admin/utils/formatBytes.js.map +1 -1
- package/dist/admin/utils/formatBytes.mjs.map +1 -1
- package/dist/admin/utils/formatDuration.js.map +1 -1
- package/dist/admin/utils/formatDuration.mjs.map +1 -1
- package/dist/admin/utils/getAPIInnerErrors.js.map +1 -1
- package/dist/admin/utils/getAPIInnerErrors.mjs.map +1 -1
- package/dist/admin/utils/getAllowedFiles.js.map +1 -1
- package/dist/admin/utils/getAllowedFiles.mjs.map +1 -1
- package/dist/admin/utils/getBreadcrumbDataCM.js.map +1 -1
- package/dist/admin/utils/getBreadcrumbDataCM.mjs.map +1 -1
- package/dist/admin/utils/getBreadcrumbDataML.js.map +1 -1
- package/dist/admin/utils/getBreadcrumbDataML.mjs.map +1 -1
- package/dist/admin/utils/getFileExtension.js.map +1 -1
- package/dist/admin/utils/getFileExtension.mjs.map +1 -1
- package/dist/admin/utils/getFolderParents.js.map +1 -1
- package/dist/admin/utils/getFolderParents.mjs.map +1 -1
- package/dist/admin/utils/getFolderURL.js.map +1 -1
- package/dist/admin/utils/getFolderURL.mjs.map +1 -1
- package/dist/admin/utils/getTrad.js.map +1 -1
- package/dist/admin/utils/getTrad.mjs.map +1 -1
- package/dist/admin/utils/icons.js.map +1 -1
- package/dist/admin/utils/icons.mjs +1 -1
- package/dist/admin/utils/icons.mjs.map +1 -1
- package/dist/admin/utils/moveElement.js.map +1 -1
- package/dist/admin/utils/moveElement.mjs.map +1 -1
- package/dist/admin/utils/normalizeAPIError.js.map +1 -1
- package/dist/admin/utils/normalizeAPIError.mjs.map +1 -1
- package/dist/admin/utils/prefixFileUrlWithBackendUrl.js.map +1 -1
- package/dist/admin/utils/prefixFileUrlWithBackendUrl.mjs.map +1 -1
- package/dist/admin/utils/prefixPluginTranslations.js.map +1 -1
- package/dist/admin/utils/prefixPluginTranslations.mjs.map +1 -1
- package/dist/admin/utils/rawFileToAsset.js.map +1 -1
- package/dist/admin/utils/rawFileToAsset.mjs.map +1 -1
- package/dist/admin/utils/toSingularTypes.js.map +1 -1
- package/dist/admin/utils/toSingularTypes.mjs.map +1 -1
- package/dist/admin/utils/typeFromMime.js.map +1 -1
- package/dist/admin/utils/typeFromMime.mjs.map +1 -1
- package/dist/admin/utils/urlYupSchema.js.map +1 -1
- package/dist/admin/utils/urlYupSchema.mjs.map +1 -1
- package/dist/admin/utils/urlsToAssets.js.map +1 -1
- package/dist/admin/utils/urlsToAssets.mjs.map +1 -1
- package/dist/server/bootstrap.js.map +1 -1
- package/dist/server/bootstrap.mjs.map +1 -1
- package/dist/server/config.js.map +1 -1
- package/dist/server/config.mjs.map +1 -1
- package/dist/server/constants.js.map +1 -1
- package/dist/server/constants.mjs.map +1 -1
- package/dist/server/content-types/file.js.map +1 -1
- package/dist/server/content-types/file.mjs.map +1 -1
- package/dist/server/content-types/folder.js.map +1 -1
- package/dist/server/content-types/folder.mjs +1 -1
- package/dist/server/content-types/folder.mjs.map +1 -1
- package/dist/server/content-types/index.js.map +1 -1
- package/dist/server/content-types/index.mjs.map +1 -1
- package/dist/server/controllers/admin-file.js.map +1 -1
- package/dist/server/controllers/admin-file.mjs +1 -1
- package/dist/server/controllers/admin-file.mjs.map +1 -1
- package/dist/server/controllers/admin-folder-file.js.map +1 -1
- package/dist/server/controllers/admin-folder-file.mjs +2 -2
- package/dist/server/controllers/admin-folder-file.mjs.map +1 -1
- package/dist/server/controllers/admin-folder.js.map +1 -1
- package/dist/server/controllers/admin-folder.mjs +1 -1
- package/dist/server/controllers/admin-folder.mjs.map +1 -1
- package/dist/server/controllers/admin-settings.js.map +1 -1
- package/dist/server/controllers/admin-settings.mjs.map +1 -1
- package/dist/server/controllers/admin-upload.js +144 -0
- package/dist/server/controllers/admin-upload.js.map +1 -1
- package/dist/server/controllers/admin-upload.mjs +147 -3
- package/dist/server/controllers/admin-upload.mjs.map +1 -1
- package/dist/server/controllers/content-api.js.map +1 -1
- package/dist/server/controllers/content-api.mjs.map +1 -1
- package/dist/server/controllers/index.js.map +1 -1
- package/dist/server/controllers/index.mjs.map +1 -1
- package/dist/server/controllers/utils/find-entity-and-check-permissions.js.map +1 -1
- package/dist/server/controllers/utils/find-entity-and-check-permissions.mjs.map +1 -1
- package/dist/server/controllers/utils/folders.js.map +1 -1
- package/dist/server/controllers/utils/folders.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/configureView.js.map +1 -1
- package/dist/server/controllers/validation/admin/configureView.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/folder-file.js.map +1 -1
- package/dist/server/controllers/validation/admin/folder-file.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/folder.js.map +1 -1
- package/dist/server/controllers/validation/admin/folder.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/settings.js.map +1 -1
- package/dist/server/controllers/validation/admin/settings.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/upload.js.map +1 -1
- package/dist/server/controllers/validation/admin/upload.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/utils.js.map +1 -1
- package/dist/server/controllers/validation/admin/utils.mjs.map +1 -1
- package/dist/server/controllers/validation/content-api/upload.js.map +1 -1
- package/dist/server/controllers/validation/content-api/upload.mjs.map +1 -1
- package/dist/server/controllers/view-configuration.js.map +1 -1
- package/dist/server/controllers/view-configuration.mjs.map +1 -1
- package/dist/server/graphql.js.map +1 -1
- package/dist/server/graphql.mjs.map +1 -1
- package/dist/server/index.js +8 -8
- package/dist/server/middlewares/upload.js.map +1 -1
- package/dist/server/middlewares/upload.mjs.map +1 -1
- package/dist/server/models/ai-metadata-job.js.map +1 -1
- package/dist/server/models/ai-metadata-job.mjs.map +1 -1
- package/dist/server/register.js.map +1 -1
- package/dist/server/register.mjs.map +1 -1
- package/dist/server/routes/admin.js +10 -0
- package/dist/server/routes/admin.js.map +1 -1
- package/dist/server/routes/admin.mjs +10 -0
- package/dist/server/routes/admin.mjs.map +1 -1
- package/dist/server/routes/content-api.js.map +1 -1
- package/dist/server/routes/content-api.mjs.map +1 -1
- package/dist/server/routes/index.js.map +1 -1
- package/dist/server/routes/index.mjs +4 -4
- package/dist/server/routes/index.mjs.map +1 -1
- package/dist/server/routes/validation/upload.js.map +1 -1
- package/dist/server/routes/validation/upload.mjs.map +1 -1
- package/dist/server/routes/view-configuration.js.map +1 -1
- package/dist/server/routes/view-configuration.mjs.map +1 -1
- package/dist/server/services/ai-metadata-jobs.js.map +1 -1
- package/dist/server/services/ai-metadata-jobs.mjs.map +1 -1
- package/dist/server/services/ai-metadata.js.map +1 -1
- package/dist/server/services/ai-metadata.mjs.map +1 -1
- package/dist/server/services/api-upload-folder.js.map +1 -1
- package/dist/server/services/api-upload-folder.mjs.map +1 -1
- package/dist/server/services/extensions/index.js.map +1 -1
- package/dist/server/services/extensions/index.mjs.map +1 -1
- package/dist/server/services/extensions/utils.js.map +1 -1
- package/dist/server/services/extensions/utils.mjs.map +1 -1
- package/dist/server/services/file.js +120 -1
- package/dist/server/services/file.js.map +1 -1
- package/dist/server/services/file.mjs +122 -3
- package/dist/server/services/file.mjs.map +1 -1
- package/dist/server/services/folder.js.map +1 -1
- package/dist/server/services/folder.mjs +1 -1
- package/dist/server/services/folder.mjs.map +1 -1
- package/dist/server/services/image-manipulation.js.map +1 -1
- package/dist/server/services/image-manipulation.mjs +1 -1
- package/dist/server/services/image-manipulation.mjs.map +1 -1
- package/dist/server/services/index.js.map +1 -1
- package/dist/server/services/index.mjs.map +1 -1
- package/dist/server/services/metrics.js.map +1 -1
- package/dist/server/services/metrics.mjs.map +1 -1
- package/dist/server/services/provider.js.map +1 -1
- package/dist/server/services/provider.mjs.map +1 -1
- package/dist/server/services/upload.js +6 -1
- package/dist/server/services/upload.js.map +1 -1
- package/dist/server/services/upload.mjs +7 -2
- package/dist/server/services/upload.mjs.map +1 -1
- package/dist/server/services/weekly-metrics.js.map +1 -1
- package/dist/server/services/weekly-metrics.mjs.map +1 -1
- package/dist/server/src/controllers/admin-upload.d.ts +13 -0
- package/dist/server/src/controllers/admin-upload.d.ts.map +1 -1
- package/dist/server/src/controllers/index.d.ts +1 -0
- package/dist/server/src/controllers/index.d.ts.map +1 -1
- package/dist/server/src/index.d.ts +2 -0
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/routes/admin.d.ts.map +1 -1
- package/dist/server/src/services/file.d.ts +15 -0
- package/dist/server/src/services/file.d.ts.map +1 -1
- package/dist/server/src/services/index.d.ts +1 -0
- package/dist/server/src/services/index.d.ts.map +1 -1
- package/dist/server/src/services/upload.d.ts.map +1 -1
- package/dist/server/src/utils/mime-validation.d.ts +1 -0
- package/dist/server/src/utils/mime-validation.d.ts.map +1 -1
- package/dist/server/utils/cron.js.map +1 -1
- package/dist/server/utils/cron.mjs.map +1 -1
- package/dist/server/utils/images.js.map +1 -1
- package/dist/server/utils/images.mjs.map +1 -1
- package/dist/server/utils/index.js.map +1 -1
- package/dist/server/utils/index.mjs.map +1 -1
- package/dist/server/utils/mime-validation.js +184 -47
- package/dist/server/utils/mime-validation.js.map +1 -1
- package/dist/server/utils/mime-validation.mjs +185 -47
- package/dist/server/utils/mime-validation.mjs.map +1 -1
- package/dist/shared/contracts/files.d.ts +11 -0
- package/dist/shared/contracts/files.d.ts.map +1 -1
- package/package.json +19 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-metadata.mjs","sources":["../../../server/src/services/ai-metadata.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\nimport { z } from 'zod';\nimport { InputFile, File } from '../types';\nimport { Settings } from '../controllers/validation/admin/settings';\nimport { getService } from '../utils';\nimport { buildFormDataFromFiles } from '../utils/images';\n\n/**\n * Supported image types for AI metadata generation\n * @see https://ai.google.dev/gemini-api/docs/image-understanding\n */\nconst SUPPORTED_IMAGE_TYPES = [\n 'image/png',\n 'image/jpeg',\n 'image/webp',\n 'image/heic',\n 'image/heif',\n] as const;\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 // Check if user disabled AI features globally\n const isAIEnabled = strapi.config.get('admin.ai.enabled', true);\n if (!isAIEnabled) {\n return false;\n }\n\n // Check if the user's license grants access to AI features\n const hasAccess = strapi.ee.features.isEnabled('cms-ai');\n if (!hasAccess) {\n return false;\n }\n\n // Check if feature is specifically enabled, defaulting to true\n const settings: Settings = await strapi.plugin('upload').service('upload').getSettings();\n const aiMetadata: boolean = settings.aiMetadata ?? true;\n\n return aiMetadata;\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 * 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\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 new Array(files.length).fill(null);\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.get('ai').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 }, new Array(files.length).fill(null));\n },\n };\n};\n\nexport { createAIMetadataService };\n"],"names":["SUPPORTED_IMAGE_TYPES","createAIMetadataService","strapi","aiServerUrl","process","env","STRAPI_AI_URL","isEnabled","isAIEnabled","config","get","hasAccess","ee","features","settings","plugin","service","getSettings","aiMetadata","countImagesWithoutMetadata","imagesWithoutMetadataCountPromise","db","query","count","where","mime","$in","$or","alternativeText","$null","caption","totalImagesPromise","imagesWithoutMetadataCount","totalImages","Promise","all","updateFilesWithAIMetadata","files","metadataResults","user","uploadService","map","file","index","updateData","altText","Object","keys","length","updateFileInfo","id","undefined","processExistingFiles","jobId","jobService","getService","updateJob","status","findMany","completedAt","Date","processFiles","error","log","Error","message","String","imageFiles","originalIndex","filter","startsWith","imageInputFiles","thumbnail","formats","filepath","url","mimetype","originalFilename","name","size","provider","Array","fill","formData","buildFormDataFromFiles","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":";;;;AAOA;;;AAGC,IACD,MAAMA,qBAAwB,GAAA;AAC5B,IAAA,WAAA;AACA,IAAA,YAAA;AACA,IAAA,YAAA;AACA,IAAA,YAAA;AACA,IAAA;AACD,CAAA;AAED,MAAMC,uBAA0B,GAAA,CAAC,EAAEC,MAAM,EAA2B,GAAA;AAClE,IAAA,MAAMC,WAAcC,GAAAA,OAAAA,CAAQC,GAAG,CAACC,aAAa,IAAI,kCAAA;IAEjD,OAAO;QACL,MAAMC,SAAAA,CAAAA,GAAAA;;AAEJ,YAAA,MAAMC,cAAcN,MAAOO,CAAAA,MAAM,CAACC,GAAG,CAAC,kBAAoB,EAAA,IAAA,CAAA;AAC1D,YAAA,IAAI,CAACF,WAAa,EAAA;gBAChB,OAAO,KAAA;AACT;;AAGA,YAAA,MAAMG,YAAYT,MAAOU,CAAAA,EAAE,CAACC,QAAQ,CAACN,SAAS,CAAC,QAAA,CAAA;AAC/C,YAAA,IAAI,CAACI,SAAW,EAAA;gBACd,OAAO,KAAA;AACT;;YAGA,MAAMG,QAAAA,GAAqB,MAAMZ,MAAOa,CAAAA,MAAM,CAAC,QAAUC,CAAAA,CAAAA,OAAO,CAAC,QAAA,CAAA,CAAUC,WAAW,EAAA;YACtF,MAAMC,UAAAA,GAAsBJ,QAASI,CAAAA,UAAU,IAAI,IAAA;YAEnD,OAAOA,UAAAA;AACT,SAAA;QAEA,MAAMC,0BAAAA,CAAAA,GAAAA;YACJ,MAAMC,iCAAAA,GAAoClB,OAAOmB,EAAE,CAACC,KAAK,CAAC,qBAAA,CAAA,CAAuBC,KAAK,CAAC;gBACrFC,KAAO,EAAA;oBACLC,IAAM,EAAA;wBACJC,GAAK1B,EAAAA;AACP,qBAAA;oBACA2B,GAAK,EAAA;AACH,wBAAA;4BAAEC,eAAiB,EAAA;gCAAEC,KAAO,EAAA;AAAK;AAAE,yBAAA;AACnC,wBAAA;4BAAED,eAAiB,EAAA;AAAG,yBAAA;AACtB,wBAAA;4BAAEE,OAAS,EAAA;gCAAED,KAAO,EAAA;AAAK;AAAE,yBAAA;AAC3B,wBAAA;4BAAEC,OAAS,EAAA;AAAG;AACf;AACH;AACF,aAAA,CAAA;YAEA,MAAMC,kBAAAA,GAAqB7B,OAAOmB,EAAE,CAACC,KAAK,CAAC,qBAAA,CAAA,CAAuBC,KAAK,CAAC;gBACtEC,KAAO,EAAA;oBACLC,IAAM,EAAA;wBACJC,GAAK1B,EAAAA;AACP;AACF;AACF,aAAA,CAAA;AAEA,YAAA,MAAM,CAACgC,0BAA4BC,EAAAA,WAAAA,CAAY,GAAG,MAAMC,OAAAA,CAAQC,GAAG,CAAC;AAClEf,gBAAAA,iCAAAA;AACAW,gBAAAA;AACD,aAAA,CAAA;YAED,OAAO;AAAEC,gBAAAA,0BAAAA;AAA4BC,gBAAAA;AAAY,aAAA;AACnD,SAAA;AAEA;;;AAGC,QACD,MAAMG,yBACJC,CAAAA,CAAAA,KAAa,EACbC,eAAmE,EACnEC,IAA6B,EAAA;AAE7B,YAAA,MAAMC,gBAAgBtC,MAAOa,CAAAA,MAAM,CAAC,QAAA,CAAA,CAAUC,OAAO,CAAC,QAAA,CAAA;AAEtD,YAAA,MAAMkB,QAAQC,GAAG,CACfE,MAAMI,GAAG,CAAC,OAAOC,IAAMC,EAAAA,KAAAA,GAAAA;gBACrB,MAAMzB,UAAAA,GAAaoB,eAAe,CAACK,KAAM,CAAA;AACzC,gBAAA,IAAIzB,UAAY,EAAA;;AAEd,oBAAA,MAAM0B,aAA6D,EAAC;AAEpE,oBAAA,IAAI,CAACF,IAAKd,CAAAA,eAAe,IAAIc,IAAKd,CAAAA,eAAe,KAAK,EAAI,EAAA;wBACxDgB,UAAWhB,CAAAA,eAAe,GAAGV,UAAAA,CAAW2B,OAAO;AACjD;AAEA,oBAAA,IAAI,CAACH,IAAKZ,CAAAA,OAAO,IAAIY,IAAKZ,CAAAA,OAAO,KAAK,EAAI,EAAA;wBACxCc,UAAWd,CAAAA,OAAO,GAAGZ,UAAAA,CAAWY,OAAO;AACzC;;AAGA,oBAAA,IAAIgB,OAAOC,IAAI,CAACH,UAAYI,CAAAA,CAAAA,MAAM,GAAG,CAAG,EAAA;AACtC,wBAAA,MAAMR,cAAcS,cAAc,CAACP,IAAKQ,CAAAA,EAAE,EAAEN,UAAY,EAAA;AAAEL,4BAAAA;AAAK,yBAAA,CAAA;;wBAG/D,IAAIK,UAAAA,CAAWhB,eAAe,KAAKuB,SAAW,EAAA;4BAC5CT,IAAKd,CAAAA,eAAe,GAAGgB,UAAAA,CAAWhB,eAAe;AACnD;wBACA,IAAIgB,UAAAA,CAAWd,OAAO,KAAKqB,SAAW,EAAA;4BACpCT,IAAKZ,CAAAA,OAAO,GAAGc,UAAAA,CAAWd,OAAO;AACnC;AACF;AACF;AACF,aAAA,CAAA,CAAA;AAEJ,SAAA;AAEA;;AAEC,QACD,MAAMsB,oBAAAA,CAAAA,CAAqBC,KAAa,EAAEd,IAA6B,EAAA;AACrE,YAAA,MAAMe,aAAaC,UAAW,CAAA,gBAAA,CAAA;YAE9B,IAAI;;gBAEF,MAAMD,UAAAA,CAAWE,SAAS,CAACH,KAAO,EAAA;oBAAEI,MAAQ,EAAA;AAAa,iBAAA,CAAA;;gBAGzD,MAAMpB,KAAAA,GAAgB,MAAMnC,MAAOmB,CAAAA,EAAE,CAACC,KAAK,CAAC,qBAAuBoC,CAAAA,CAAAA,QAAQ,CAAC;oBAC1ElC,KAAO,EAAA;wBACLC,IAAM,EAAA;4BACJC,GAAK1B,EAAAA;AACP,yBAAA;wBACA2B,GAAK,EAAA;AACH,4BAAA;gCAAEC,eAAiB,EAAA;oCAAEC,KAAO,EAAA;AAAK;AAAE,6BAAA;AACnC,4BAAA;gCAAED,eAAiB,EAAA;AAAG,6BAAA;AACtB,4BAAA;gCAAEE,OAAS,EAAA;oCAAED,KAAO,EAAA;AAAK;AAAE,6BAAA;AAC3B,4BAAA;gCAAEC,OAAS,EAAA;AAAG;AACf;AACH;AACF,iBAAA,CAAA;gBAEA,IAAIO,KAAAA,CAAMW,MAAM,KAAK,CAAG,EAAA;oBACtB,MAAMM,UAAAA,CAAWE,SAAS,CAACH,KAAO,EAAA;wBAChCI,MAAQ,EAAA,WAAA;AACRE,wBAAAA,WAAAA,EAAa,IAAIC,IAAAA;AACnB,qBAAA,CAAA;AACA,oBAAA;AACF;;AAGA,gBAAA,MAAMtB,eAAkB,GAAA,MAAM,IAAI,CAACuB,YAAY,CAACxB,KAAAA,CAAAA;AAChD,gBAAA,MAAM,IAAI,CAACD,yBAAyB,CAACC,OAAOC,eAAiBC,EAAAA,IAAAA,CAAAA;;gBAG7D,MAAMe,UAAAA,CAAWE,SAAS,CAACH,KAAO,EAAA;oBAChCI,MAAQ,EAAA,WAAA;AACRE,oBAAAA,WAAAA,EAAa,IAAIC,IAAAA;AACnB,iBAAA,CAAA;AACF,aAAA,CAAE,OAAOE,KAAO,EAAA;AACd5D,gBAAAA,MAAAA,CAAO6D,GAAG,CAACD,KAAK,CAAC,wBAA0B,EAAA;AACzCT,oBAAAA,KAAAA;AACAS,oBAAAA,KAAAA,EAAOA,KAAiBE,YAAAA,KAAAA,GAAQF,KAAMG,CAAAA,OAAO,GAAGC,MAAOJ,CAAAA,KAAAA;AACzD,iBAAA,CAAA;gBAEA,MAAMR,UAAAA,CAAWE,SAAS,CAACH,KAAO,EAAA;oBAChCI,MAAQ,EAAA,QAAA;AACRE,oBAAAA,WAAAA,EAAa,IAAIC,IAAAA;AACnB,iBAAA,CAAA;AACF;AACF,SAAA;AAEA;;QAGA,MAAMC,cAAaxB,KAAa,EAAA;AAC9B,YAAA,IAAI,CAAE,MAAM,IAAI,CAAC9B,SAAS,EAAA,IAAO,CAACJ,WAAa,EAAA;AAC7C,gBAAA,MAAM,IAAI6D,KAAM,CAAA,oCAAA,CAAA;AAClB;;;AAIA,YAAA,MAAMG,aAAa9B,KAChBI,CAAAA,GAAG,CAAC,CAACC,IAAAA,EAAMC,SAAW;AAAED,oBAAAA,IAAAA;oBAAM0B,aAAezB,EAAAA;iBAAM,CAAA,CAAA,CACnD0B,MAAM,CAAC,CAAC,EAAE3B,IAAI,EAAE,GAAKA,IAAAA,CAAKjB,IAAI,EAAE6C,UAAW,CAAA,QAAA,CAAA,CAAA;;AAG9C,YAAA,MAAMC,kBAAkBJ,UAAW1B,CAAAA,GAAG,CAAC,CAAC,EAAEC,IAAI,EAAE,GAAA;gBAC9C,MAAM8B,SAAAA,GAAa9B,IAAK+B,CAAAA,OAAO,EAAUD,SAAAA;gBACzC,OAAO;AACLE,oBAAAA,QAAAA,EAAUF,SAAWG,EAAAA,GAAAA,IAAOjC,IAAKiC,CAAAA,GAAG,IAAI,EAAA;AACxCC,oBAAAA,QAAAA,EAAUlC,KAAKjB,IAAI;AACnBoD,oBAAAA,gBAAAA,EAAkBnC,KAAKoC,IAAI;oBAC3BC,IAAMP,EAAAA,SAAAA,EAAWO,IAAQrC,IAAAA,IAAAA,CAAKqC,IAAI;AAClCC,oBAAAA,QAAAA,EAAUtC,KAAKsC;AACjB,iBAAA;AACF,aAAA,CAAA;;;YAIA,IAAIb,UAAAA,CAAWnB,MAAM,KAAK,CAAG,EAAA;AAC3B,gBAAA,OAAO,IAAIiC,KAAM5C,CAAAA,KAAAA,CAAMW,MAAM,CAAA,CAAEkC,IAAI,CAAC,IAAA,CAAA;AACtC;YAEA,MAAMC,QAAAA,GAAW,MAAMC,sBAAAA,CACrBb,eACArE,EAAAA,MAAAA,CAAOO,MAAM,CAACC,GAAG,CAAC,oBAClBR,CAAAA,EAAAA,MAAAA,CAAO6D,GAAG,CAAA;YAGZ,IAAIsB,KAAAA;YACJ,IAAI;AACF,gBAAA,MAAMC,YAAY,MAAMpF,MAAAA,CAAOQ,GAAG,CAAC,MAAM6E,UAAU,EAAA;AACnDF,gBAAAA,KAAAA,GAAQC,UAAUD,KAAK;AACzB,aAAA,CAAE,OAAOvB,KAAO,EAAA;gBACd,MAAM,IAAIE,MAAM,6BAA+B,EAAA;oBAC7CwB,KAAO1B,EAAAA,KAAAA,YAAiBE,QAAQF,KAAQX,GAAAA;AAC1C,iBAAA,CAAA;AACF;AAEAjD,YAAAA,MAAAA,CAAO6D,GAAG,CAAC0B,IAAI,CAAC,oDAAsD,EAAA;AACpEtF,gBAAAA,WAAAA;AACAuF,gBAAAA,UAAAA,EAAYvB,WAAWnB;AACzB,aAAA,CAAA;AAEA,YAAA,MAAM2C,MAAM,MAAMC,KAAAA,CAAM,GAAGzF,WAAY,CAAA,gCAAgC,CAAC,EAAE;gBACxE0F,MAAQ,EAAA,MAAA;gBACRC,IAAMX,EAAAA,QAAAA;gBACNY,OAAS,EAAA;oBACPC,aAAe,EAAA,CAAC,OAAO,EAAEX,KAAO,CAAA;AAClC;AACF,aAAA,CAAA;YAEA,IAAI,CAACM,GAAIM,CAAAA,EAAE,EAAE;gBACX,MAAMC,SAAAA,GAAY,MAAMP,GAAAA,CAAIQ,IAAI,EAAA;AAChC,gBAAA,MAAMnC,KAAM,CAAA,CAAC,6BAA6B,CAAC,EAAE;oBAAEwB,KAAOU,EAAAA;AAAU,iBAAA,CAAA;AAClE;YAEA,MAAME,cAAAA,GAAiBC,CAAEC,CAAAA,MAAM,CAAC;AAC9BC,gBAAAA,OAAAA,EAASF,CAAEG,CAAAA,KAAK,CACdH,CAAAA,CAAEC,MAAM,CAAC;AACPzD,oBAAAA,OAAAA,EAASwD,EAAEI,MAAM,EAAA;AACjB3E,oBAAAA,OAAAA,EAASuE,EAAEI,MAAM;AACnB,iBAAA,CAAA;AAEJ,aAAA,CAAA;YAEA,MAAM,EAAEF,OAAO,EAAE,GAAGH,eAAeM,KAAK,CAAC,MAAMf,GAAAA,CAAIgB,IAAI,EAAA,CAAA;YACvDzG,MAAO6D,CAAAA,GAAG,CAAC0B,IAAI,CAAC,CAAC,uCAAuC,EAAEc,OAAQvD,CAAAA,MAAM,CAAC,MAAM,CAAC,CAAA;;;;;YAMhF,OAAOmB,UAAAA,CAAWyC,MAAM,CAAC,CAACC,eAAe,EAAEzC,aAAa,EAAE,EAAE0C,WAAAA,GAAAA;AAC1DD,gBAAAA,aAAa,CAACzC,aAAAA,CAAc,GAAGmC,OAAO,CAACO,WAAY,CAAA;gBACnD,OAAOD,aAAAA;AACT,aAAA,EAAG,IAAI5B,KAAM5C,CAAAA,KAAAA,CAAMW,MAAM,CAAA,CAAEkC,IAAI,CAAC,IAAA,CAAA,CAAA;AAClC;AACF,KAAA;AACF;;;;"}
|
|
1
|
+
{"version":3,"file":"ai-metadata.mjs","sources":["../../../server/src/services/ai-metadata.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\nimport { z } from 'zod';\nimport { InputFile, File } from '../types';\nimport { Settings } from '../controllers/validation/admin/settings';\nimport { getService } from '../utils';\nimport { buildFormDataFromFiles } from '../utils/images';\n\n/**\n * Supported image types for AI metadata generation\n * @see https://ai.google.dev/gemini-api/docs/image-understanding\n */\nconst SUPPORTED_IMAGE_TYPES = [\n 'image/png',\n 'image/jpeg',\n 'image/webp',\n 'image/heic',\n 'image/heif',\n] as const;\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 // Check if user disabled AI features globally\n const isAIEnabled = strapi.config.get('admin.ai.enabled', true);\n if (!isAIEnabled) {\n return false;\n }\n\n // Check if the user's license grants access to AI features\n const hasAccess = strapi.ee.features.isEnabled('cms-ai');\n if (!hasAccess) {\n return false;\n }\n\n // Check if feature is specifically enabled, defaulting to true\n const settings: Settings = await strapi.plugin('upload').service('upload').getSettings();\n const aiMetadata: boolean = settings.aiMetadata ?? true;\n\n return aiMetadata;\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 * 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\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 new Array(files.length).fill(null);\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.get('ai').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 }, new Array(files.length).fill(null));\n },\n };\n};\n\nexport { createAIMetadataService };\n"],"names":["SUPPORTED_IMAGE_TYPES","createAIMetadataService","strapi","aiServerUrl","process","env","STRAPI_AI_URL","isEnabled","isAIEnabled","config","get","hasAccess","ee","features","settings","plugin","service","getSettings","aiMetadata","countImagesWithoutMetadata","imagesWithoutMetadataCountPromise","db","query","count","where","mime","$in","$or","alternativeText","$null","caption","totalImagesPromise","imagesWithoutMetadataCount","totalImages","Promise","all","updateFilesWithAIMetadata","files","metadataResults","user","uploadService","map","file","index","updateData","altText","Object","keys","length","updateFileInfo","id","undefined","processExistingFiles","jobId","jobService","getService","updateJob","status","findMany","completedAt","Date","processFiles","error","log","Error","message","String","imageFiles","originalIndex","filter","startsWith","imageInputFiles","thumbnail","formats","filepath","url","mimetype","originalFilename","name","size","provider","Array","fill","formData","buildFormDataFromFiles","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":";;;;AAOA;;;AAGC,IACD,MAAMA,qBAAAA,GAAwB;AAC5B,IAAA,WAAA;AACA,IAAA,YAAA;AACA,IAAA,YAAA;AACA,IAAA,YAAA;AACA,IAAA;AACD,CAAA;AAED,MAAMC,uBAAAA,GAA0B,CAAC,EAAEC,MAAM,EAA2B,GAAA;AAClE,IAAA,MAAMC,WAAAA,GAAcC,OAAAA,CAAQC,GAAG,CAACC,aAAa,IAAI,kCAAA;IAEjD,OAAO;QACL,MAAMC,SAAAA,CAAAA,GAAAA;;AAEJ,YAAA,MAAMC,cAAcN,MAAAA,CAAOO,MAAM,CAACC,GAAG,CAAC,kBAAA,EAAoB,IAAA,CAAA;AAC1D,YAAA,IAAI,CAACF,WAAAA,EAAa;gBAChB,OAAO,KAAA;AACT,YAAA;;AAGA,YAAA,MAAMG,YAAYT,MAAAA,CAAOU,EAAE,CAACC,QAAQ,CAACN,SAAS,CAAC,QAAA,CAAA;AAC/C,YAAA,IAAI,CAACI,SAAAA,EAAW;gBACd,OAAO,KAAA;AACT,YAAA;;YAGA,MAAMG,QAAAA,GAAqB,MAAMZ,MAAAA,CAAOa,MAAM,CAAC,QAAA,CAAA,CAAUC,OAAO,CAAC,QAAA,CAAA,CAAUC,WAAW,EAAA;YACtF,MAAMC,UAAAA,GAAsBJ,QAAAA,CAASI,UAAU,IAAI,IAAA;YAEnD,OAAOA,UAAAA;AACT,QAAA,CAAA;QAEA,MAAMC,0BAAAA,CAAAA,GAAAA;YACJ,MAAMC,iCAAAA,GAAoClB,OAAOmB,EAAE,CAACC,KAAK,CAAC,qBAAA,CAAA,CAAuBC,KAAK,CAAC;gBACrFC,KAAAA,EAAO;oBACLC,IAAAA,EAAM;wBACJC,GAAAA,EAAK1B;AACP,qBAAA;oBACA2B,GAAAA,EAAK;AACH,wBAAA;4BAAEC,eAAAA,EAAiB;gCAAEC,KAAAA,EAAO;AAAK;AAAE,yBAAA;AACnC,wBAAA;4BAAED,eAAAA,EAAiB;AAAG,yBAAA;AACtB,wBAAA;4BAAEE,OAAAA,EAAS;gCAAED,KAAAA,EAAO;AAAK;AAAE,yBAAA;AAC3B,wBAAA;4BAAEC,OAAAA,EAAS;AAAG;AACf;AACH;AACF,aAAA,CAAA;YAEA,MAAMC,kBAAAA,GAAqB7B,OAAOmB,EAAE,CAACC,KAAK,CAAC,qBAAA,CAAA,CAAuBC,KAAK,CAAC;gBACtEC,KAAAA,EAAO;oBACLC,IAAAA,EAAM;wBACJC,GAAAA,EAAK1B;AACP;AACF;AACF,aAAA,CAAA;AAEA,YAAA,MAAM,CAACgC,0BAAAA,EAA4BC,WAAAA,CAAY,GAAG,MAAMC,OAAAA,CAAQC,GAAG,CAAC;AAClEf,gBAAAA,iCAAAA;AACAW,gBAAAA;AACD,aAAA,CAAA;YAED,OAAO;AAAEC,gBAAAA,0BAAAA;AAA4BC,gBAAAA;AAAY,aAAA;AACnD,QAAA,CAAA;AAEA;;;AAGC,QACD,MAAMG,yBAAAA,CAAAA,CACJC,KAAa,EACbC,eAAmE,EACnEC,IAA6B,EAAA;AAE7B,YAAA,MAAMC,gBAAgBtC,MAAAA,CAAOa,MAAM,CAAC,QAAA,CAAA,CAAUC,OAAO,CAAC,QAAA,CAAA;AAEtD,YAAA,MAAMkB,QAAQC,GAAG,CACfE,MAAMI,GAAG,CAAC,OAAOC,IAAAA,EAAMC,KAAAA,GAAAA;gBACrB,MAAMzB,UAAAA,GAAaoB,eAAe,CAACK,KAAAA,CAAM;AACzC,gBAAA,IAAIzB,UAAAA,EAAY;;AAEd,oBAAA,MAAM0B,aAA6D,EAAC;AAEpE,oBAAA,IAAI,CAACF,IAAAA,CAAKd,eAAe,IAAIc,IAAAA,CAAKd,eAAe,KAAK,EAAA,EAAI;wBACxDgB,UAAAA,CAAWhB,eAAe,GAAGV,UAAAA,CAAW2B,OAAO;AACjD,oBAAA;AAEA,oBAAA,IAAI,CAACH,IAAAA,CAAKZ,OAAO,IAAIY,IAAAA,CAAKZ,OAAO,KAAK,EAAA,EAAI;wBACxCc,UAAAA,CAAWd,OAAO,GAAGZ,UAAAA,CAAWY,OAAO;AACzC,oBAAA;;AAGA,oBAAA,IAAIgB,OAAOC,IAAI,CAACH,UAAAA,CAAAA,CAAYI,MAAM,GAAG,CAAA,EAAG;AACtC,wBAAA,MAAMR,cAAcS,cAAc,CAACP,IAAAA,CAAKQ,EAAE,EAAEN,UAAAA,EAAY;AAAEL,4BAAAA;AAAK,yBAAA,CAAA;;wBAG/D,IAAIK,UAAAA,CAAWhB,eAAe,KAAKuB,SAAAA,EAAW;4BAC5CT,IAAAA,CAAKd,eAAe,GAAGgB,UAAAA,CAAWhB,eAAe;AACnD,wBAAA;wBACA,IAAIgB,UAAAA,CAAWd,OAAO,KAAKqB,SAAAA,EAAW;4BACpCT,IAAAA,CAAKZ,OAAO,GAAGc,UAAAA,CAAWd,OAAO;AACnC,wBAAA;AACF,oBAAA;AACF,gBAAA;AACF,YAAA,CAAA,CAAA,CAAA;AAEJ,QAAA,CAAA;AAEA;;AAEC,QACD,MAAMsB,oBAAAA,CAAAA,CAAqBC,KAAa,EAAEd,IAA6B,EAAA;AACrE,YAAA,MAAMe,aAAaC,UAAAA,CAAW,gBAAA,CAAA;YAE9B,IAAI;;gBAEF,MAAMD,UAAAA,CAAWE,SAAS,CAACH,KAAAA,EAAO;oBAAEI,MAAAA,EAAQ;AAAa,iBAAA,CAAA;;gBAGzD,MAAMpB,KAAAA,GAAgB,MAAMnC,MAAAA,CAAOmB,EAAE,CAACC,KAAK,CAAC,qBAAA,CAAA,CAAuBoC,QAAQ,CAAC;oBAC1ElC,KAAAA,EAAO;wBACLC,IAAAA,EAAM;4BACJC,GAAAA,EAAK1B;AACP,yBAAA;wBACA2B,GAAAA,EAAK;AACH,4BAAA;gCAAEC,eAAAA,EAAiB;oCAAEC,KAAAA,EAAO;AAAK;AAAE,6BAAA;AACnC,4BAAA;gCAAED,eAAAA,EAAiB;AAAG,6BAAA;AACtB,4BAAA;gCAAEE,OAAAA,EAAS;oCAAED,KAAAA,EAAO;AAAK;AAAE,6BAAA;AAC3B,4BAAA;gCAAEC,OAAAA,EAAS;AAAG;AACf;AACH;AACF,iBAAA,CAAA;gBAEA,IAAIO,KAAAA,CAAMW,MAAM,KAAK,CAAA,EAAG;oBACtB,MAAMM,UAAAA,CAAWE,SAAS,CAACH,KAAAA,EAAO;wBAChCI,MAAAA,EAAQ,WAAA;AACRE,wBAAAA,WAAAA,EAAa,IAAIC,IAAAA;AACnB,qBAAA,CAAA;AACA,oBAAA;AACF,gBAAA;;AAGA,gBAAA,MAAMtB,eAAAA,GAAkB,MAAM,IAAI,CAACuB,YAAY,CAACxB,KAAAA,CAAAA;AAChD,gBAAA,MAAM,IAAI,CAACD,yBAAyB,CAACC,OAAOC,eAAAA,EAAiBC,IAAAA,CAAAA;;gBAG7D,MAAMe,UAAAA,CAAWE,SAAS,CAACH,KAAAA,EAAO;oBAChCI,MAAAA,EAAQ,WAAA;AACRE,oBAAAA,WAAAA,EAAa,IAAIC,IAAAA;AACnB,iBAAA,CAAA;AACF,YAAA,CAAA,CAAE,OAAOE,KAAAA,EAAO;AACd5D,gBAAAA,MAAAA,CAAO6D,GAAG,CAACD,KAAK,CAAC,wBAAA,EAA0B;AACzCT,oBAAAA,KAAAA;AACAS,oBAAAA,KAAAA,EAAOA,KAAAA,YAAiBE,KAAAA,GAAQF,KAAAA,CAAMG,OAAO,GAAGC,MAAAA,CAAOJ,KAAAA;AACzD,iBAAA,CAAA;gBAEA,MAAMR,UAAAA,CAAWE,SAAS,CAACH,KAAAA,EAAO;oBAChCI,MAAAA,EAAQ,QAAA;AACRE,oBAAAA,WAAAA,EAAa,IAAIC,IAAAA;AACnB,iBAAA,CAAA;AACF,YAAA;AACF,QAAA,CAAA;AAEA;;QAGA,MAAMC,cAAaxB,KAAa,EAAA;AAC9B,YAAA,IAAI,CAAE,MAAM,IAAI,CAAC9B,SAAS,EAAA,IAAO,CAACJ,WAAAA,EAAa;AAC7C,gBAAA,MAAM,IAAI6D,KAAAA,CAAM,oCAAA,CAAA;AAClB,YAAA;;;AAIA,YAAA,MAAMG,aAAa9B,KAAAA,CAChBI,GAAG,CAAC,CAACC,IAAAA,EAAMC,SAAW;AAAED,oBAAAA,IAAAA;oBAAM0B,aAAAA,EAAezB;iBAAM,CAAA,CAAA,CACnD0B,MAAM,CAAC,CAAC,EAAE3B,IAAI,EAAE,GAAKA,IAAAA,CAAKjB,IAAI,EAAE6C,UAAAA,CAAW,QAAA,CAAA,CAAA;;AAG9C,YAAA,MAAMC,kBAAkBJ,UAAAA,CAAW1B,GAAG,CAAC,CAAC,EAAEC,IAAI,EAAE,GAAA;gBAC9C,MAAM8B,SAAAA,GAAa9B,IAAAA,CAAK+B,OAAO,EAAUD,SAAAA;gBACzC,OAAO;AACLE,oBAAAA,QAAAA,EAAUF,SAAAA,EAAWG,GAAAA,IAAOjC,IAAAA,CAAKiC,GAAG,IAAI,EAAA;AACxCC,oBAAAA,QAAAA,EAAUlC,KAAKjB,IAAI;AACnBoD,oBAAAA,gBAAAA,EAAkBnC,KAAKoC,IAAI;oBAC3BC,IAAAA,EAAMP,SAAAA,EAAWO,IAAAA,IAAQrC,IAAAA,CAAKqC,IAAI;AAClCC,oBAAAA,QAAAA,EAAUtC,KAAKsC;AACjB,iBAAA;AACF,YAAA,CAAA,CAAA;;;YAIA,IAAIb,UAAAA,CAAWnB,MAAM,KAAK,CAAA,EAAG;AAC3B,gBAAA,OAAO,IAAIiC,KAAAA,CAAM5C,KAAAA,CAAMW,MAAM,CAAA,CAAEkC,IAAI,CAAC,IAAA,CAAA;AACtC,YAAA;YAEA,MAAMC,QAAAA,GAAW,MAAMC,sBAAAA,CACrBb,eAAAA,EACArE,MAAAA,CAAOO,MAAM,CAACC,GAAG,CAAC,oBAAA,CAAA,EAClBR,MAAAA,CAAO6D,GAAG,CAAA;YAGZ,IAAIsB,KAAAA;YACJ,IAAI;AACF,gBAAA,MAAMC,YAAY,MAAMpF,MAAAA,CAAOQ,GAAG,CAAC,MAAM6E,UAAU,EAAA;AACnDF,gBAAAA,KAAAA,GAAQC,UAAUD,KAAK;AACzB,YAAA,CAAA,CAAE,OAAOvB,KAAAA,EAAO;gBACd,MAAM,IAAIE,MAAM,6BAAA,EAA+B;oBAC7CwB,KAAAA,EAAO1B,KAAAA,YAAiBE,QAAQF,KAAAA,GAAQX;AAC1C,iBAAA,CAAA;AACF,YAAA;AAEAjD,YAAAA,MAAAA,CAAO6D,GAAG,CAAC0B,IAAI,CAAC,oDAAA,EAAsD;AACpEtF,gBAAAA,WAAAA;AACAuF,gBAAAA,UAAAA,EAAYvB,WAAWnB;AACzB,aAAA,CAAA;AAEA,YAAA,MAAM2C,MAAM,MAAMC,KAAAA,CAAM,GAAGzF,WAAAA,CAAY,gCAAgC,CAAC,EAAE;gBACxE0F,MAAAA,EAAQ,MAAA;gBACRC,IAAAA,EAAMX,QAAAA;gBACNY,OAAAA,EAAS;oBACPC,aAAAA,EAAe,CAAC,OAAO,EAAEX,KAAAA,CAAAA;AAC3B;AACF,aAAA,CAAA;YAEA,IAAI,CAACM,GAAAA,CAAIM,EAAE,EAAE;gBACX,MAAMC,SAAAA,GAAY,MAAMP,GAAAA,CAAIQ,IAAI,EAAA;AAChC,gBAAA,MAAMnC,KAAAA,CAAM,CAAC,6BAA6B,CAAC,EAAE;oBAAEwB,KAAAA,EAAOU;AAAU,iBAAA,CAAA;AAClE,YAAA;YAEA,MAAME,cAAAA,GAAiBC,CAAAA,CAAEC,MAAM,CAAC;AAC9BC,gBAAAA,OAAAA,EAASF,CAAAA,CAAEG,KAAK,CACdH,CAAAA,CAAEC,MAAM,CAAC;AACPzD,oBAAAA,OAAAA,EAASwD,EAAEI,MAAM,EAAA;AACjB3E,oBAAAA,OAAAA,EAASuE,EAAEI,MAAM;AACnB,iBAAA,CAAA;AAEJ,aAAA,CAAA;YAEA,MAAM,EAAEF,OAAO,EAAE,GAAGH,eAAeM,KAAK,CAAC,MAAMf,GAAAA,CAAIgB,IAAI,EAAA,CAAA;YACvDzG,MAAAA,CAAO6D,GAAG,CAAC0B,IAAI,CAAC,CAAC,uCAAuC,EAAEc,OAAAA,CAAQvD,MAAM,CAAC,MAAM,CAAC,CAAA;;;;;YAMhF,OAAOmB,UAAAA,CAAWyC,MAAM,CAAC,CAACC,eAAe,EAAEzC,aAAa,EAAE,EAAE0C,WAAAA,GAAAA;AAC1DD,gBAAAA,aAAa,CAACzC,aAAAA,CAAc,GAAGmC,OAAO,CAACO,WAAAA,CAAY;gBACnD,OAAOD,aAAAA;AACT,YAAA,CAAA,EAAG,IAAI5B,KAAAA,CAAM5C,KAAAA,CAAMW,MAAM,CAAA,CAAEkC,IAAI,CAAC,IAAA,CAAA,CAAA;AAClC,QAAA;AACF,KAAA;AACF;;;;"}
|
|
@@ -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,
|
|
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 +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,
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../server/src/services/extensions/index.ts"],"sourcesContent":["import { async } from '@strapi/utils';\nimport { signEntityMedia } from './utils';\n\nconst signFileUrlsOnDocumentService = async () => {\n const { provider } = strapi.plugins.upload;\n const isPrivate = await provider.isPrivate();\n\n // We only need to sign the file urls if the provider is private\n if (!isPrivate) {\n return;\n }\n\n strapi.documents.use(async (ctx, next) => {\n const uid = ctx.uid;\n const result: any = await next();\n\n if (ctx.action === 'findMany') {\n // Shape: [ entry ]\n return async.map(result, (entry: any) => signEntityMedia(entry, uid));\n }\n\n if (\n ctx.action === 'findFirst' ||\n ctx.action === 'findOne' ||\n ctx.action === 'create' ||\n ctx.action === 'update'\n ) {\n // Shape: entry\n return signEntityMedia(result, uid);\n }\n\n if (\n ctx.action === 'delete' ||\n ctx.action === 'clone' ||\n ctx.action === 'publish' ||\n ctx.action === 'unpublish' ||\n ctx.action === 'discardDraft'\n ) {\n // Shape: { entries: [ entry ] }\n // ...\n return {\n ...result,\n entries: await async.map(result.entries, (entry: any) => signEntityMedia(entry, uid)),\n };\n }\n\n return result;\n });\n};\n\nexport default {\n signFileUrlsOnDocumentService,\n};\n"],"names":["signFileUrlsOnDocumentService","provider","strapi","plugins","upload","isPrivate","documents","use","ctx","next","uid","result","action","async","map","entry","signEntityMedia","entries"],"mappings":";;;;;AAGA,MAAMA,
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../server/src/services/extensions/index.ts"],"sourcesContent":["import { async } from '@strapi/utils';\nimport { signEntityMedia } from './utils';\n\nconst signFileUrlsOnDocumentService = async () => {\n const { provider } = strapi.plugins.upload;\n const isPrivate = await provider.isPrivate();\n\n // We only need to sign the file urls if the provider is private\n if (!isPrivate) {\n return;\n }\n\n strapi.documents.use(async (ctx, next) => {\n const uid = ctx.uid;\n const result: any = await next();\n\n if (ctx.action === 'findMany') {\n // Shape: [ entry ]\n return async.map(result, (entry: any) => signEntityMedia(entry, uid));\n }\n\n if (\n ctx.action === 'findFirst' ||\n ctx.action === 'findOne' ||\n ctx.action === 'create' ||\n ctx.action === 'update'\n ) {\n // Shape: entry\n return signEntityMedia(result, uid);\n }\n\n if (\n ctx.action === 'delete' ||\n ctx.action === 'clone' ||\n ctx.action === 'publish' ||\n ctx.action === 'unpublish' ||\n ctx.action === 'discardDraft'\n ) {\n // Shape: { entries: [ entry ] }\n // ...\n return {\n ...result,\n entries: await async.map(result.entries, (entry: any) => signEntityMedia(entry, uid)),\n };\n }\n\n return result;\n });\n};\n\nexport default {\n signFileUrlsOnDocumentService,\n};\n"],"names":["signFileUrlsOnDocumentService","provider","strapi","plugins","upload","isPrivate","documents","use","ctx","next","uid","result","action","async","map","entry","signEntityMedia","entries"],"mappings":";;;;;AAGA,MAAMA,6BAAAA,GAAgC,UAAA;AACpC,IAAA,MAAM,EAAEC,QAAQ,EAAE,GAAGC,MAAAA,CAAOC,OAAO,CAACC,MAAM;IAC1C,MAAMC,SAAAA,GAAY,MAAMJ,QAAAA,CAASI,SAAS,EAAA;;AAG1C,IAAA,IAAI,CAACA,SAAAA,EAAW;AACd,QAAA;AACF,IAAA;AAEAH,IAAAA,MAAAA,CAAOI,SAAS,CAACC,GAAG,CAAC,OAAOC,GAAAA,EAAKC,IAAAA,GAAAA;QAC/B,MAAMC,GAAAA,GAAMF,IAAIE,GAAG;AACnB,QAAA,MAAMC,SAAc,MAAMF,IAAAA,EAAAA;QAE1B,IAAID,GAAAA,CAAII,MAAM,KAAK,UAAA,EAAY;;AAE7B,YAAA,OAAOC,YAAMC,GAAG,CAACH,QAAQ,CAACI,KAAAA,GAAeC,wBAAgBD,KAAAA,EAAOL,GAAAA,CAAAA,CAAAA;AAClE,QAAA;AAEA,QAAA,IACEF,GAAAA,CAAII,MAAM,KAAK,WAAA,IACfJ,IAAII,MAAM,KAAK,SAAA,IACfJ,GAAAA,CAAII,MAAM,KAAK,QAAA,IACfJ,GAAAA,CAAII,MAAM,KAAK,QAAA,EACf;;AAEA,YAAA,OAAOI,wBAAgBL,MAAAA,EAAQD,GAAAA,CAAAA;AACjC,QAAA;AAEA,QAAA,IACEF,IAAII,MAAM,KAAK,YACfJ,GAAAA,CAAII,MAAM,KAAK,OAAA,IACfJ,GAAAA,CAAII,MAAM,KAAK,SAAA,IACfJ,IAAII,MAAM,KAAK,eACfJ,GAAAA,CAAII,MAAM,KAAK,cAAA,EACf;;;YAGA,OAAO;AACL,gBAAA,GAAGD,MAAM;gBACTM,OAAAA,EAAS,MAAMJ,WAAAA,CAAMC,GAAG,CAACH,MAAAA,CAAOM,OAAO,EAAE,CAACF,KAAAA,GAAeC,uBAAAA,CAAgBD,KAAAA,EAAOL,GAAAA,CAAAA;AAClF,aAAA;AACF,QAAA;QAEA,OAAOC,MAAAA;AACT,IAAA,CAAA,CAAA;AACF,CAAA;AAEA,iBAAe;AACbX,IAAAA;AACF,CAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../../../server/src/services/extensions/index.ts"],"sourcesContent":["import { async } from '@strapi/utils';\nimport { signEntityMedia } from './utils';\n\nconst signFileUrlsOnDocumentService = async () => {\n const { provider } = strapi.plugins.upload;\n const isPrivate = await provider.isPrivate();\n\n // We only need to sign the file urls if the provider is private\n if (!isPrivate) {\n return;\n }\n\n strapi.documents.use(async (ctx, next) => {\n const uid = ctx.uid;\n const result: any = await next();\n\n if (ctx.action === 'findMany') {\n // Shape: [ entry ]\n return async.map(result, (entry: any) => signEntityMedia(entry, uid));\n }\n\n if (\n ctx.action === 'findFirst' ||\n ctx.action === 'findOne' ||\n ctx.action === 'create' ||\n ctx.action === 'update'\n ) {\n // Shape: entry\n return signEntityMedia(result, uid);\n }\n\n if (\n ctx.action === 'delete' ||\n ctx.action === 'clone' ||\n ctx.action === 'publish' ||\n ctx.action === 'unpublish' ||\n ctx.action === 'discardDraft'\n ) {\n // Shape: { entries: [ entry ] }\n // ...\n return {\n ...result,\n entries: await async.map(result.entries, (entry: any) => signEntityMedia(entry, uid)),\n };\n }\n\n return result;\n });\n};\n\nexport default {\n signFileUrlsOnDocumentService,\n};\n"],"names":["signFileUrlsOnDocumentService","provider","strapi","plugins","upload","isPrivate","documents","use","ctx","next","uid","result","action","async","map","entry","signEntityMedia","entries"],"mappings":";;;AAGA,MAAMA,
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../../../server/src/services/extensions/index.ts"],"sourcesContent":["import { async } from '@strapi/utils';\nimport { signEntityMedia } from './utils';\n\nconst signFileUrlsOnDocumentService = async () => {\n const { provider } = strapi.plugins.upload;\n const isPrivate = await provider.isPrivate();\n\n // We only need to sign the file urls if the provider is private\n if (!isPrivate) {\n return;\n }\n\n strapi.documents.use(async (ctx, next) => {\n const uid = ctx.uid;\n const result: any = await next();\n\n if (ctx.action === 'findMany') {\n // Shape: [ entry ]\n return async.map(result, (entry: any) => signEntityMedia(entry, uid));\n }\n\n if (\n ctx.action === 'findFirst' ||\n ctx.action === 'findOne' ||\n ctx.action === 'create' ||\n ctx.action === 'update'\n ) {\n // Shape: entry\n return signEntityMedia(result, uid);\n }\n\n if (\n ctx.action === 'delete' ||\n ctx.action === 'clone' ||\n ctx.action === 'publish' ||\n ctx.action === 'unpublish' ||\n ctx.action === 'discardDraft'\n ) {\n // Shape: { entries: [ entry ] }\n // ...\n return {\n ...result,\n entries: await async.map(result.entries, (entry: any) => signEntityMedia(entry, uid)),\n };\n }\n\n return result;\n });\n};\n\nexport default {\n signFileUrlsOnDocumentService,\n};\n"],"names":["signFileUrlsOnDocumentService","provider","strapi","plugins","upload","isPrivate","documents","use","ctx","next","uid","result","action","async","map","entry","signEntityMedia","entries"],"mappings":";;;AAGA,MAAMA,6BAAAA,GAAgC,UAAA;AACpC,IAAA,MAAM,EAAEC,QAAQ,EAAE,GAAGC,MAAAA,CAAOC,OAAO,CAACC,MAAM;IAC1C,MAAMC,SAAAA,GAAY,MAAMJ,QAAAA,CAASI,SAAS,EAAA;;AAG1C,IAAA,IAAI,CAACA,SAAAA,EAAW;AACd,QAAA;AACF,IAAA;AAEAH,IAAAA,MAAAA,CAAOI,SAAS,CAACC,GAAG,CAAC,OAAOC,GAAAA,EAAKC,IAAAA,GAAAA;QAC/B,MAAMC,GAAAA,GAAMF,IAAIE,GAAG;AACnB,QAAA,MAAMC,SAAc,MAAMF,IAAAA,EAAAA;QAE1B,IAAID,GAAAA,CAAII,MAAM,KAAK,UAAA,EAAY;;AAE7B,YAAA,OAAOC,MAAMC,GAAG,CAACH,QAAQ,CAACI,KAAAA,GAAeC,gBAAgBD,KAAAA,EAAOL,GAAAA,CAAAA,CAAAA;AAClE,QAAA;AAEA,QAAA,IACEF,GAAAA,CAAII,MAAM,KAAK,WAAA,IACfJ,IAAII,MAAM,KAAK,SAAA,IACfJ,GAAAA,CAAII,MAAM,KAAK,QAAA,IACfJ,GAAAA,CAAII,MAAM,KAAK,QAAA,EACf;;AAEA,YAAA,OAAOI,gBAAgBL,MAAAA,EAAQD,GAAAA,CAAAA;AACjC,QAAA;AAEA,QAAA,IACEF,IAAII,MAAM,KAAK,YACfJ,GAAAA,CAAII,MAAM,KAAK,OAAA,IACfJ,GAAAA,CAAII,MAAM,KAAK,SAAA,IACfJ,IAAII,MAAM,KAAK,eACfJ,GAAAA,CAAII,MAAM,KAAK,cAAA,EACf;;;YAGA,OAAO;AACL,gBAAA,GAAGD,MAAM;gBACTM,OAAAA,EAAS,MAAMJ,KAAAA,CAAMC,GAAG,CAACH,MAAAA,CAAOM,OAAO,EAAE,CAACF,KAAAA,GAAeC,eAAAA,CAAgBD,KAAAA,EAAOL,GAAAA,CAAAA;AAClF,aAAA;AACF,QAAA;QAEA,OAAOC,MAAAA;AACT,IAAA,CAAA,CAAA;AACF,CAAA;AAEA,iBAAe;AACbX,IAAAA;AACF,CAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../../../server/src/services/extensions/utils.ts"],"sourcesContent":["import { 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 { 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\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\n/**\n * Visitor function to sign media URLs\n */\nconst signEntityMediaVisitor: SignEntityMediaVisitor = async (\n { key, value, attribute },\n { set }\n) => {\n const { signFileUrls } = getService('file');\n\n if (!attribute) {\n return;\n }\n\n if (attribute.type !== 'media') {\n return;\n }\n\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};\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 * @returns\n */\nconst signEntityMedia = async (entity: any, uid: UID.Schema) => {\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 signEntityMediaVisitor,\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n entity\n );\n};\n\nexport { signEntityMedia };\n"],"names":["isFile","value","attribute","type","signEntityMediaVisitor","key","set","signFileUrls","getService","multiple","signedFiles","async","map","signedFile","signEntityMedia","entity","uid","FILE_MODEL_UID","model","strapi","getModel","traverseEntity","schema","bind"],"mappings":";;;;;;AAoBA,SAASA,MAAAA,CAAOC,KAAc,EAAEC,SAAwC,EAAA;AACtE,IAAA,IAAI,CAACD,KAAAA,IAASC,
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../../../server/src/services/extensions/utils.ts"],"sourcesContent":["import { 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 { 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\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\n/**\n * Visitor function to sign media URLs\n */\nconst signEntityMediaVisitor: SignEntityMediaVisitor = async (\n { key, value, attribute },\n { set }\n) => {\n const { signFileUrls } = getService('file');\n\n if (!attribute) {\n return;\n }\n\n if (attribute.type !== 'media') {\n return;\n }\n\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};\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 * @returns\n */\nconst signEntityMedia = async (entity: any, uid: UID.Schema) => {\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 signEntityMediaVisitor,\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n entity\n );\n};\n\nexport { signEntityMedia };\n"],"names":["isFile","value","attribute","type","signEntityMediaVisitor","key","set","signFileUrls","getService","multiple","signedFiles","async","map","signedFile","signEntityMedia","entity","uid","FILE_MODEL_UID","model","strapi","getModel","traverseEntity","schema","bind"],"mappings":";;;;;;AAoBA,SAASA,MAAAA,CAAOC,KAAc,EAAEC,SAAwC,EAAA;AACtE,IAAA,IAAI,CAACD,KAAAA,IAASC,SAAAA,CAAUC,IAAI,KAAK,OAAA,EAAS;QACxC,OAAO,KAAA;AACT,IAAA;IAEA,OAAO,IAAA;AACT;AAEA;;AAEC,IACD,MAAMC,sBAAAA,GAAiD,OACrD,EAAEC,GAAG,EAAEJ,KAAK,EAAEC,SAAS,EAAE,EACzB,EAAEI,GAAG,EAAE,GAAA;AAEP,IAAA,MAAM,EAAEC,YAAY,EAAE,GAAGC,gBAAAA,CAAW,MAAA,CAAA;AAEpC,IAAA,IAAI,CAACN,SAAAA,EAAW;AACd,QAAA;AACF,IAAA;IAEA,IAAIA,SAAAA,CAAUC,IAAI,KAAK,OAAA,EAAS;AAC9B,QAAA;AACF,IAAA;IAEA,IAAIH,MAAAA,CAAOC,OAAOC,SAAAA,CAAAA,EAAY;;QAE5B,IAAIA,SAAAA,CAAUO,QAAQ,EAAE;AACtB,YAAA,MAAMC,WAAAA,GAAc,MAAMC,WAAAA,CAAMC,GAAG,CAACX,KAAAA,EAAOM,YAAAA,CAAAA;AAC3CD,YAAAA,GAAAA,CAAID,GAAAA,EAAKK,WAAAA,CAAAA;AACT,YAAA;AACF,QAAA;;QAGA,MAAMG,UAAAA,GAAa,MAAMN,YAAAA,CAAaN,KAAAA,CAAAA;AACtCK,QAAAA,GAAAA,CAAID,GAAAA,EAAKQ,UAAAA,CAAAA;AACX,IAAA;AACF,CAAA;AAEA;;;;;;;;;IAUA,MAAMC,eAAAA,GAAkB,OAAOC,MAAAA,EAAaC,GAAAA,GAAAA;AAC1C,IAAA,IAAI,CAACD,MAAAA,EAAQ;QACX,OAAOA,MAAAA;AACT,IAAA;;AAGA,IAAA,IAAIC,QAAQC,wBAAAA,EAAgB;AAC1B,QAAA,MAAM,EAAEV,YAAY,EAAE,GAAGC,gBAAAA,CAAW,MAAA,CAAA;AACpC,QAAA,OAAOD,YAAAA,CAAaQ,MAAAA,CAAAA;AACtB,IAAA;;IAGA,MAAMG,KAAAA,GAAQC,MAAAA,CAAOC,QAAQ,CAACJ,GAAAA,CAAAA;AAC9B,IAAA,OAAOK;IAELjB,sBAAAA,EACA;QAAEkB,MAAAA,EAAQJ,KAAAA;AAAOE,QAAAA,QAAAA,EAAUD,MAAAA,CAAOC,QAAQ,CAACG,IAAI,CAACJ,MAAAA;KAAQ,EACxDJ,MAAAA,CAAAA;AAEJ;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.mjs","sources":["../../../../server/src/services/extensions/utils.ts"],"sourcesContent":["import { 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 { 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\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\n/**\n * Visitor function to sign media URLs\n */\nconst signEntityMediaVisitor: SignEntityMediaVisitor = async (\n { key, value, attribute },\n { set }\n) => {\n const { signFileUrls } = getService('file');\n\n if (!attribute) {\n return;\n }\n\n if (attribute.type !== 'media') {\n return;\n }\n\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};\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 * @returns\n */\nconst signEntityMedia = async (entity: any, uid: UID.Schema) => {\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 signEntityMediaVisitor,\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n entity\n );\n};\n\nexport { signEntityMedia };\n"],"names":["isFile","value","attribute","type","signEntityMediaVisitor","key","set","signFileUrls","getService","multiple","signedFiles","async","map","signedFile","signEntityMedia","entity","uid","FILE_MODEL_UID","model","strapi","getModel","traverseEntity","schema","bind"],"mappings":";;;;AAoBA,SAASA,MAAAA,CAAOC,KAAc,EAAEC,SAAwC,EAAA;AACtE,IAAA,IAAI,CAACD,KAAAA,IAASC,
|
|
1
|
+
{"version":3,"file":"utils.mjs","sources":["../../../../server/src/services/extensions/utils.ts"],"sourcesContent":["import { 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 { 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\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\n/**\n * Visitor function to sign media URLs\n */\nconst signEntityMediaVisitor: SignEntityMediaVisitor = async (\n { key, value, attribute },\n { set }\n) => {\n const { signFileUrls } = getService('file');\n\n if (!attribute) {\n return;\n }\n\n if (attribute.type !== 'media') {\n return;\n }\n\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};\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 * @returns\n */\nconst signEntityMedia = async (entity: any, uid: UID.Schema) => {\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 signEntityMediaVisitor,\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n entity\n );\n};\n\nexport { signEntityMedia };\n"],"names":["isFile","value","attribute","type","signEntityMediaVisitor","key","set","signFileUrls","getService","multiple","signedFiles","async","map","signedFile","signEntityMedia","entity","uid","FILE_MODEL_UID","model","strapi","getModel","traverseEntity","schema","bind"],"mappings":";;;;AAoBA,SAASA,MAAAA,CAAOC,KAAc,EAAEC,SAAwC,EAAA;AACtE,IAAA,IAAI,CAACD,KAAAA,IAASC,SAAAA,CAAUC,IAAI,KAAK,OAAA,EAAS;QACxC,OAAO,KAAA;AACT,IAAA;IAEA,OAAO,IAAA;AACT;AAEA;;AAEC,IACD,MAAMC,sBAAAA,GAAiD,OACrD,EAAEC,GAAG,EAAEJ,KAAK,EAAEC,SAAS,EAAE,EACzB,EAAEI,GAAG,EAAE,GAAA;AAEP,IAAA,MAAM,EAAEC,YAAY,EAAE,GAAGC,UAAAA,CAAW,MAAA,CAAA;AAEpC,IAAA,IAAI,CAACN,SAAAA,EAAW;AACd,QAAA;AACF,IAAA;IAEA,IAAIA,SAAAA,CAAUC,IAAI,KAAK,OAAA,EAAS;AAC9B,QAAA;AACF,IAAA;IAEA,IAAIH,MAAAA,CAAOC,OAAOC,SAAAA,CAAAA,EAAY;;QAE5B,IAAIA,SAAAA,CAAUO,QAAQ,EAAE;AACtB,YAAA,MAAMC,WAAAA,GAAc,MAAMC,KAAAA,CAAMC,GAAG,CAACX,KAAAA,EAAOM,YAAAA,CAAAA;AAC3CD,YAAAA,GAAAA,CAAID,GAAAA,EAAKK,WAAAA,CAAAA;AACT,YAAA;AACF,QAAA;;QAGA,MAAMG,UAAAA,GAAa,MAAMN,YAAAA,CAAaN,KAAAA,CAAAA;AACtCK,QAAAA,GAAAA,CAAID,GAAAA,EAAKQ,UAAAA,CAAAA;AACX,IAAA;AACF,CAAA;AAEA;;;;;;;;;IAUA,MAAMC,eAAAA,GAAkB,OAAOC,MAAAA,EAAaC,GAAAA,GAAAA;AAC1C,IAAA,IAAI,CAACD,MAAAA,EAAQ;QACX,OAAOA,MAAAA;AACT,IAAA;;AAGA,IAAA,IAAIC,QAAQC,cAAAA,EAAgB;AAC1B,QAAA,MAAM,EAAEV,YAAY,EAAE,GAAGC,UAAAA,CAAW,MAAA,CAAA;AACpC,QAAA,OAAOD,YAAAA,CAAaQ,MAAAA,CAAAA;AACtB,IAAA;;IAGA,MAAMG,KAAAA,GAAQC,MAAAA,CAAOC,QAAQ,CAACJ,GAAAA,CAAAA;AAC9B,IAAA,OAAOK;IAELjB,sBAAAA,EACA;QAAEkB,MAAAA,EAAQJ,KAAAA;AAAOE,QAAAA,QAAAA,EAAUD,MAAAA,CAAOC,QAAQ,CAACG,IAAI,CAACJ,MAAAA;KAAQ,EACxDJ,MAAAA,CAAAA;AAEJ;;;;"}
|
|
@@ -1,10 +1,128 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var path = require('path');
|
|
4
|
+
var dns = require('dns/promises');
|
|
5
|
+
var net = require('net');
|
|
6
|
+
var fse = require('fs-extra');
|
|
3
7
|
var fp = require('lodash/fp');
|
|
4
8
|
var utils = require('@strapi/utils');
|
|
5
9
|
var constants = require('../constants.js');
|
|
6
10
|
var index = require('../utils/index.js');
|
|
7
11
|
|
|
12
|
+
const { ApplicationError } = utils.errors;
|
|
13
|
+
const FETCH_TIMEOUT_MS = 60000; // 60 seconds
|
|
14
|
+
// Blocks loopback, link-local (cloud metadata), and RFC-1918 private ranges to prevent SSRF
|
|
15
|
+
const SSRF_BLOCK_LIST = new net.BlockList();
|
|
16
|
+
SSRF_BLOCK_LIST.addSubnet('127.0.0.0', 8); // loopback
|
|
17
|
+
SSRF_BLOCK_LIST.addSubnet('10.0.0.0', 8); // RFC-1918
|
|
18
|
+
SSRF_BLOCK_LIST.addSubnet('172.16.0.0', 12); // RFC-1918
|
|
19
|
+
SSRF_BLOCK_LIST.addSubnet('192.168.0.0', 16); // RFC-1918
|
|
20
|
+
SSRF_BLOCK_LIST.addSubnet('169.254.0.0', 16); // link-local / cloud metadata (AWS, GCP, Azure)
|
|
21
|
+
SSRF_BLOCK_LIST.addSubnet('::1', 128, 'ipv6'); // IPv6 loopback
|
|
22
|
+
SSRF_BLOCK_LIST.addSubnet('fc00::', 7, 'ipv6'); // IPv6 unique local
|
|
23
|
+
SSRF_BLOCK_LIST.addSubnet('fe80::', 10, 'ipv6'); // IPv6 link-local
|
|
24
|
+
/**
|
|
25
|
+
* Extracts filename from a URL path or Content-Disposition header
|
|
26
|
+
*/ const getFilenameFromUrl = (url, contentDisposition)=>{
|
|
27
|
+
// Try Content-Disposition header first
|
|
28
|
+
if (contentDisposition) {
|
|
29
|
+
// Extracts filename from Content-Disposition header (e.g. filename="photo.jpg" or filename*=UTF-8''photo.jpg)
|
|
30
|
+
const filenameMatch = contentDisposition.match(/filename\*?=['"]?(?:UTF-\d['"]*)?([^;\r\n"']*)['"]?/i);
|
|
31
|
+
if (filenameMatch?.[1]) {
|
|
32
|
+
// Use path.basename to prevent path traversal attacks
|
|
33
|
+
return path.basename(decodeURIComponent(filenameMatch[1]));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Fall back to URL path
|
|
37
|
+
try {
|
|
38
|
+
const urlObj = new URL(url);
|
|
39
|
+
const pathname = urlObj.pathname;
|
|
40
|
+
const filename = pathname.split('/').pop();
|
|
41
|
+
if (filename && filename.length > 0) {
|
|
42
|
+
// Use path.basename to prevent path traversal attacks (e.g., URL-encoded separators)
|
|
43
|
+
return path.basename(decodeURIComponent(filename));
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
// Invalid URL, use default
|
|
47
|
+
}
|
|
48
|
+
// Generate a timestamp-based default filename
|
|
49
|
+
const now = new Date();
|
|
50
|
+
const date = now.toISOString().split('T')[0]; // 2024-02-23
|
|
51
|
+
const time = now.toTimeString().split(' ')[0].replace(/:/g, ''); // 143052
|
|
52
|
+
return `untitled_${date}_${time}`;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Fetches a URL and saves it as a temporary file
|
|
56
|
+
* Returns an InputFile-compatible object for use with the upload pipeline
|
|
57
|
+
*/ const fetchUrlToInputFile = async (url, tmpWorkingDirectory, sizeLimit)=>{
|
|
58
|
+
// Validate URL protocol
|
|
59
|
+
let parsedUrl;
|
|
60
|
+
try {
|
|
61
|
+
parsedUrl = new URL(url);
|
|
62
|
+
} catch {
|
|
63
|
+
throw new ApplicationError(`Invalid URL: ${url}`);
|
|
64
|
+
}
|
|
65
|
+
if (![
|
|
66
|
+
'http:',
|
|
67
|
+
'https:'
|
|
68
|
+
].includes(parsedUrl.protocol)) {
|
|
69
|
+
throw new ApplicationError(`Invalid URL protocol. Only http and https are allowed: ${url}`);
|
|
70
|
+
}
|
|
71
|
+
// Resolve hostname and block private/internal IP ranges to prevent SSRF
|
|
72
|
+
try {
|
|
73
|
+
const { address, family } = await dns.lookup(parsedUrl.hostname);
|
|
74
|
+
const type = family === 6 ? 'ipv6' : 'ipv4';
|
|
75
|
+
if (SSRF_BLOCK_LIST.check(address, type)) {
|
|
76
|
+
throw new ApplicationError(`URL resolves to a blocked address: ${url}`);
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error instanceof ApplicationError) throw error;
|
|
80
|
+
throw new ApplicationError(`Could not resolve hostname: ${parsedUrl.hostname}`);
|
|
81
|
+
}
|
|
82
|
+
// Fetch the URL with timeout
|
|
83
|
+
let response;
|
|
84
|
+
try {
|
|
85
|
+
response = await fetch(url, {
|
|
86
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
87
|
+
});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (error instanceof Error && error.name === 'TimeoutError') {
|
|
90
|
+
throw new ApplicationError(`Request timed out while fetching URL: ${url}`);
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
throw new ApplicationError(`Failed to fetch URL: ${url} (${response.status} ${response.statusText})`);
|
|
96
|
+
}
|
|
97
|
+
// Check Content-Length header for early rejection of large files
|
|
98
|
+
if (sizeLimit) {
|
|
99
|
+
const contentLength = response.headers.get('content-length');
|
|
100
|
+
if (contentLength && parseInt(contentLength, 10) > sizeLimit) {
|
|
101
|
+
throw new ApplicationError(`File too large: maximum allowed size is ${Math.round(sizeLimit / (1024 * 1024))}MB`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Get content type and filename
|
|
105
|
+
const contentType = response.headers.get('content-type')?.split(';')[0] || 'application/octet-stream';
|
|
106
|
+
const contentDisposition = response.headers.get('content-disposition');
|
|
107
|
+
const filename = getFilenameFromUrl(response.url, contentDisposition);
|
|
108
|
+
// Read response body
|
|
109
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
110
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
111
|
+
// Write to temp file
|
|
112
|
+
const tmpFilePath = path.join(tmpWorkingDirectory, filename);
|
|
113
|
+
await fse.writeFile(tmpFilePath, buffer);
|
|
114
|
+
// Create file object compatible with upload pipeline
|
|
115
|
+
const fetchedFile = {
|
|
116
|
+
filepath: tmpFilePath,
|
|
117
|
+
originalFilename: filename,
|
|
118
|
+
mimetype: contentType,
|
|
119
|
+
size: buffer.length,
|
|
120
|
+
tmpWorkingDirectory
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
file: fetchedFile
|
|
124
|
+
};
|
|
125
|
+
};
|
|
8
126
|
const getFolderPath = async (folderId)=>{
|
|
9
127
|
if (!folderId) return '/';
|
|
10
128
|
const parentFolder = await strapi.db.query(constants.FOLDER_MODEL_UID).findOne({
|
|
@@ -50,7 +168,8 @@ const signFileUrls = async (file)=>{
|
|
|
50
168
|
var file = {
|
|
51
169
|
getFolderPath,
|
|
52
170
|
deleteByIds,
|
|
53
|
-
signFileUrls
|
|
171
|
+
signFileUrls,
|
|
172
|
+
fetchUrlToInputFile
|
|
54
173
|
};
|
|
55
174
|
|
|
56
175
|
module.exports = file;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.js","sources":["../../../server/src/services/file.ts"],"sourcesContent":["import { cloneDeep } from 'lodash/fp';\nimport { async } from '@strapi/utils';\n\nimport { FOLDER_MODEL_UID, FILE_MODEL_UID } from '../constants';\nimport { getService } from '../utils';\n\nimport { Config, type File } from '../types';\n\nconst getFolderPath = async (folderId?: number | null) => {\n if (!folderId) return '/';\n\n const parentFolder = await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } });\n\n return parentFolder.path;\n};\n\nconst deleteByIds = async (ids: number[] = []) => {\n const filesToDelete = await strapi.db\n .query(FILE_MODEL_UID)\n .findMany({ where: { id: { $in: ids } } });\n\n await Promise.all(filesToDelete.map((file: File) => getService('upload').remove(file)));\n\n return filesToDelete;\n};\n\nconst signFileUrls = async (file: File) => {\n const { provider } = strapi.plugins.upload;\n const { provider: providerConfig } = strapi.config.get<Config>('plugin::upload');\n const isPrivate = await provider.isPrivate();\n file.isUrlSigned = false;\n\n // Check file provider and if provider is private\n if (file.provider !== providerConfig || !isPrivate) {\n return file;\n }\n\n const signUrl = async (file: File) => {\n const signedUrl = await provider.getSignedUrl(file);\n file.url = signedUrl.url;\n file.isUrlSigned = true;\n };\n\n const signedFile = cloneDeep(file);\n\n // Sign each file format\n await signUrl(signedFile);\n if (file.formats) {\n await async.map(Object.values(signedFile.formats ?? {}), signUrl);\n }\n\n return signedFile;\n};\n\nexport default { getFolderPath, deleteByIds, signFileUrls };\n"],"names":["getFolderPath","folderId","parentFolder","strapi","db","query","FOLDER_MODEL_UID","findOne","where","id","path","deleteByIds","ids","filesToDelete","FILE_MODEL_UID","findMany","$in","Promise","all","map","file","getService","remove","signFileUrls","provider","plugins","upload","providerConfig","config","get","isPrivate","isUrlSigned","signUrl","signedUrl","getSignedUrl","url","signedFile","cloneDeep","formats","async","Object","values"],"mappings":";;;;;;;AAQA,MAAMA,gBAAgB,OAAOC,QAAAA,GAAAA;IAC3B,IAAI,CAACA,UAAU,OAAO,GAAA;IAEtB,MAAMC,YAAAA,GAAe,MAAMC,MAAOC,CAAAA,EAAE,CAACC,KAAK,CAACC,0BAAkBC,CAAAA,CAAAA,OAAO,CAAC;QAAEC,KAAO,EAAA;YAAEC,EAAIR,EAAAA;AAAS;AAAE,KAAA,CAAA;AAE/F,IAAA,OAAOC,aAAaQ,IAAI;AAC1B,CAAA;AAEA,MAAMC,WAAAA,GAAc,OAAOC,GAAAA,GAAgB,EAAE,GAAA;IAC3C,MAAMC,aAAAA,GAAgB,MAAMV,MAAOC,CAAAA,EAAE,CAClCC,KAAK,CAACS,wBACNC,CAAAA,CAAAA,QAAQ,CAAC;QAAEP,KAAO,EAAA;YAAEC,EAAI,EAAA;gBAAEO,GAAKJ,EAAAA;AAAI;AAAE;AAAE,KAAA,CAAA;IAE1C,MAAMK,OAAAA,CAAQC,GAAG,CAACL,aAAcM,CAAAA,GAAG,CAAC,CAACC,IAAeC,GAAAA,gBAAAA,CAAW,QAAUC,CAAAA,CAAAA,MAAM,CAACF,IAAAA,CAAAA,CAAAA,CAAAA;IAEhF,OAAOP,aAAAA;AACT,CAAA;AAEA,MAAMU,eAAe,OAAOH,IAAAA,GAAAA;AAC1B,IAAA,MAAM,EAAEI,QAAQ,EAAE,GAAGrB,MAAOsB,CAAAA,OAAO,CAACC,MAAM;IAC1C,MAAM,EAAEF,UAAUG,cAAc,EAAE,GAAGxB,MAAOyB,CAAAA,MAAM,CAACC,GAAG,CAAS,gBAAA,CAAA;IAC/D,MAAMC,SAAAA,GAAY,MAAMN,QAAAA,CAASM,SAAS,EAAA;AAC1CV,IAAAA,IAAAA,CAAKW,WAAW,GAAG,KAAA;;AAGnB,IAAA,IAAIX,IAAKI,CAAAA,QAAQ,KAAKG,cAAAA,IAAkB,CAACG,SAAW,EAAA;QAClD,OAAOV,IAAAA;AACT;AAEA,IAAA,MAAMY,UAAU,OAAOZ,IAAAA,GAAAA;AACrB,QAAA,MAAMa,SAAY,GAAA,MAAMT,QAASU,CAAAA,YAAY,CAACd,IAAAA,CAAAA;QAC9CA,IAAKe,CAAAA,GAAG,GAAGF,SAAAA,CAAUE,GAAG;AACxBf,QAAAA,IAAAA,CAAKW,WAAW,GAAG,IAAA;AACrB,KAAA;AAEA,IAAA,MAAMK,aAAaC,YAAUjB,CAAAA,IAAAA,CAAAA;;AAG7B,IAAA,MAAMY,OAAQI,CAAAA,UAAAA,CAAAA;IACd,IAAIhB,IAAAA,CAAKkB,OAAO,EAAE;QAChB,MAAMC,WAAAA,CAAMpB,GAAG,CAACqB,MAAOC,CAAAA,MAAM,CAACL,UAAWE,CAAAA,OAAO,IAAI,EAAKN,CAAAA,EAAAA,OAAAA,CAAAA;AAC3D;IAEA,OAAOI,UAAAA;AACT,CAAA;AAEA,WAAe;AAAEpC,IAAAA,aAAAA;AAAeW,IAAAA,WAAAA;AAAaY,IAAAA;AAAa,CAAE;;;;"}
|
|
1
|
+
{"version":3,"file":"file.js","sources":["../../../server/src/services/file.ts"],"sourcesContent":["import path from 'path';\nimport dns from 'dns/promises';\nimport net from 'net';\nimport fse from 'fs-extra';\nimport { cloneDeep } from 'lodash/fp';\nimport { async, errors } from '@strapi/utils';\n\nimport { FOLDER_MODEL_UID, FILE_MODEL_UID } from '../constants';\nimport { getService } from '../utils';\n\nimport { Config, type File } from '../types';\n\nconst { ApplicationError } = errors;\n\nconst FETCH_TIMEOUT_MS = 60_000; // 60 seconds\n\n// Blocks loopback, link-local (cloud metadata), and RFC-1918 private ranges to prevent SSRF\nconst SSRF_BLOCK_LIST = new net.BlockList();\nSSRF_BLOCK_LIST.addSubnet('127.0.0.0', 8); // loopback\nSSRF_BLOCK_LIST.addSubnet('10.0.0.0', 8); // RFC-1918\nSSRF_BLOCK_LIST.addSubnet('172.16.0.0', 12); // RFC-1918\nSSRF_BLOCK_LIST.addSubnet('192.168.0.0', 16); // RFC-1918\nSSRF_BLOCK_LIST.addSubnet('169.254.0.0', 16); // link-local / cloud metadata (AWS, GCP, Azure)\nSSRF_BLOCK_LIST.addSubnet('::1', 128, 'ipv6'); // IPv6 loopback\nSSRF_BLOCK_LIST.addSubnet('fc00::', 7, 'ipv6'); // IPv6 unique local\nSSRF_BLOCK_LIST.addSubnet('fe80::', 10, 'ipv6'); // IPv6 link-local\n\n/**\n * Represents a file fetched from a URL, compatible with the upload pipeline\n */\ninterface UrlFetchedFile {\n filepath: string;\n originalFilename: string;\n mimetype: string;\n size: number;\n tmpWorkingDirectory?: string;\n}\n\ninterface FetchUrlResult {\n file: UrlFetchedFile;\n}\n\n/**\n * Extracts filename from a URL path or Content-Disposition header\n */\nconst getFilenameFromUrl = (url: string, contentDisposition?: string | null): string => {\n // Try Content-Disposition header first\n if (contentDisposition) {\n // Extracts filename from Content-Disposition header (e.g. filename=\"photo.jpg\" or filename*=UTF-8''photo.jpg)\n const filenameMatch = contentDisposition.match(\n /filename\\*?=['\"]?(?:UTF-\\d['\"]*)?([^;\\r\\n\"']*)['\"]?/i\n );\n if (filenameMatch?.[1]) {\n // Use path.basename to prevent path traversal attacks\n return path.basename(decodeURIComponent(filenameMatch[1]));\n }\n }\n\n // Fall back to URL path\n try {\n const urlObj = new URL(url);\n const pathname = urlObj.pathname;\n const filename = pathname.split('/').pop();\n if (filename && filename.length > 0) {\n // Use path.basename to prevent path traversal attacks (e.g., URL-encoded separators)\n return path.basename(decodeURIComponent(filename));\n }\n } catch {\n // Invalid URL, use default\n }\n\n // Generate a timestamp-based default filename\n const now = new Date();\n const date = now.toISOString().split('T')[0]; // 2024-02-23\n const time = now.toTimeString().split(' ')[0].replace(/:/g, ''); // 143052\n return `untitled_${date}_${time}`;\n};\n\n/**\n * Fetches a URL and saves it as a temporary file\n * Returns an InputFile-compatible object for use with the upload pipeline\n */\nconst fetchUrlToInputFile = async (\n url: string,\n tmpWorkingDirectory: string,\n sizeLimit?: number\n): Promise<FetchUrlResult> => {\n // Validate URL protocol\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(url);\n } catch {\n throw new ApplicationError(`Invalid URL: ${url}`);\n }\n\n if (!['http:', 'https:'].includes(parsedUrl.protocol)) {\n throw new ApplicationError(`Invalid URL protocol. Only http and https are allowed: ${url}`);\n }\n\n // Resolve hostname and block private/internal IP ranges to prevent SSRF\n try {\n const { address, family } = await dns.lookup(parsedUrl.hostname);\n const type = family === 6 ? 'ipv6' : 'ipv4';\n if (SSRF_BLOCK_LIST.check(address, type)) {\n throw new ApplicationError(`URL resolves to a blocked address: ${url}`);\n }\n } catch (error) {\n if (error instanceof ApplicationError) throw error;\n throw new ApplicationError(`Could not resolve hostname: ${parsedUrl.hostname}`);\n }\n\n // Fetch the URL with timeout\n let response: Response;\n try {\n response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\n } catch (error) {\n if (error instanceof Error && error.name === 'TimeoutError') {\n throw new ApplicationError(`Request timed out while fetching URL: ${url}`);\n }\n throw error;\n }\n\n if (!response.ok) {\n throw new ApplicationError(\n `Failed to fetch URL: ${url} (${response.status} ${response.statusText})`\n );\n }\n\n // Check Content-Length header for early rejection of large files\n if (sizeLimit) {\n const contentLength = response.headers.get('content-length');\n if (contentLength && parseInt(contentLength, 10) > sizeLimit) {\n throw new ApplicationError(\n `File too large: maximum allowed size is ${Math.round(sizeLimit / (1024 * 1024))}MB`\n );\n }\n }\n\n // Get content type and filename\n const contentType =\n response.headers.get('content-type')?.split(';')[0] || 'application/octet-stream';\n const contentDisposition = response.headers.get('content-disposition');\n const filename = getFilenameFromUrl(response.url, contentDisposition);\n\n // Read response body\n const arrayBuffer = await response.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n // Write to temp file\n const tmpFilePath = path.join(tmpWorkingDirectory, filename);\n await fse.writeFile(tmpFilePath, buffer);\n\n // Create file object compatible with upload pipeline\n const fetchedFile: UrlFetchedFile = {\n filepath: tmpFilePath,\n originalFilename: filename,\n mimetype: contentType,\n size: buffer.length,\n tmpWorkingDirectory,\n };\n\n return { file: fetchedFile };\n};\n\nconst getFolderPath = async (folderId?: number | null) => {\n if (!folderId) return '/';\n\n const parentFolder = await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } });\n\n return parentFolder.path;\n};\n\nconst deleteByIds = async (ids: number[] = []) => {\n const filesToDelete = await strapi.db\n .query(FILE_MODEL_UID)\n .findMany({ where: { id: { $in: ids } } });\n\n await Promise.all(filesToDelete.map((file: File) => getService('upload').remove(file)));\n\n return filesToDelete;\n};\n\nconst signFileUrls = async (file: File) => {\n const { provider } = strapi.plugins.upload;\n const { provider: providerConfig } = strapi.config.get<Config>('plugin::upload');\n const isPrivate = await provider.isPrivate();\n file.isUrlSigned = false;\n\n // Check file provider and if provider is private\n if (file.provider !== providerConfig || !isPrivate) {\n return file;\n }\n\n const signUrl = async (file: File) => {\n const signedUrl = await provider.getSignedUrl(file);\n file.url = signedUrl.url;\n file.isUrlSigned = true;\n };\n\n const signedFile = cloneDeep(file);\n\n // Sign each file format\n await signUrl(signedFile);\n if (file.formats) {\n await async.map(Object.values(signedFile.formats ?? {}), signUrl);\n }\n\n return signedFile;\n};\n\nexport type { UrlFetchedFile, FetchUrlResult };\nexport default { getFolderPath, deleteByIds, signFileUrls, fetchUrlToInputFile };\n"],"names":["ApplicationError","errors","FETCH_TIMEOUT_MS","SSRF_BLOCK_LIST","net","BlockList","addSubnet","getFilenameFromUrl","url","contentDisposition","filenameMatch","match","path","basename","decodeURIComponent","urlObj","URL","pathname","filename","split","pop","length","now","Date","date","toISOString","time","toTimeString","replace","fetchUrlToInputFile","tmpWorkingDirectory","sizeLimit","parsedUrl","includes","protocol","address","family","dns","lookup","hostname","type","check","error","response","fetch","signal","AbortSignal","timeout","Error","name","ok","status","statusText","contentLength","headers","get","parseInt","Math","round","contentType","arrayBuffer","buffer","Buffer","from","tmpFilePath","join","fse","writeFile","fetchedFile","filepath","originalFilename","mimetype","size","file","getFolderPath","folderId","parentFolder","strapi","db","query","FOLDER_MODEL_UID","findOne","where","id","deleteByIds","ids","filesToDelete","FILE_MODEL_UID","findMany","$in","Promise","all","map","getService","remove","signFileUrls","provider","plugins","upload","providerConfig","config","isPrivate","isUrlSigned","signUrl","signedUrl","getSignedUrl","signedFile","cloneDeep","formats","async","Object","values"],"mappings":"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"}
|
|
@@ -1,8 +1,126 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import dns from 'dns/promises';
|
|
3
|
+
import net from 'net';
|
|
4
|
+
import fse from 'fs-extra';
|
|
1
5
|
import { cloneDeep } from 'lodash/fp';
|
|
2
|
-
import { async } from '@strapi/utils';
|
|
3
|
-
import {
|
|
6
|
+
import { errors, async } from '@strapi/utils';
|
|
7
|
+
import { FILE_MODEL_UID, FOLDER_MODEL_UID } from '../constants.mjs';
|
|
4
8
|
import { getService } from '../utils/index.mjs';
|
|
5
9
|
|
|
10
|
+
const { ApplicationError } = errors;
|
|
11
|
+
const FETCH_TIMEOUT_MS = 60000; // 60 seconds
|
|
12
|
+
// Blocks loopback, link-local (cloud metadata), and RFC-1918 private ranges to prevent SSRF
|
|
13
|
+
const SSRF_BLOCK_LIST = new net.BlockList();
|
|
14
|
+
SSRF_BLOCK_LIST.addSubnet('127.0.0.0', 8); // loopback
|
|
15
|
+
SSRF_BLOCK_LIST.addSubnet('10.0.0.0', 8); // RFC-1918
|
|
16
|
+
SSRF_BLOCK_LIST.addSubnet('172.16.0.0', 12); // RFC-1918
|
|
17
|
+
SSRF_BLOCK_LIST.addSubnet('192.168.0.0', 16); // RFC-1918
|
|
18
|
+
SSRF_BLOCK_LIST.addSubnet('169.254.0.0', 16); // link-local / cloud metadata (AWS, GCP, Azure)
|
|
19
|
+
SSRF_BLOCK_LIST.addSubnet('::1', 128, 'ipv6'); // IPv6 loopback
|
|
20
|
+
SSRF_BLOCK_LIST.addSubnet('fc00::', 7, 'ipv6'); // IPv6 unique local
|
|
21
|
+
SSRF_BLOCK_LIST.addSubnet('fe80::', 10, 'ipv6'); // IPv6 link-local
|
|
22
|
+
/**
|
|
23
|
+
* Extracts filename from a URL path or Content-Disposition header
|
|
24
|
+
*/ const getFilenameFromUrl = (url, contentDisposition)=>{
|
|
25
|
+
// Try Content-Disposition header first
|
|
26
|
+
if (contentDisposition) {
|
|
27
|
+
// Extracts filename from Content-Disposition header (e.g. filename="photo.jpg" or filename*=UTF-8''photo.jpg)
|
|
28
|
+
const filenameMatch = contentDisposition.match(/filename\*?=['"]?(?:UTF-\d['"]*)?([^;\r\n"']*)['"]?/i);
|
|
29
|
+
if (filenameMatch?.[1]) {
|
|
30
|
+
// Use path.basename to prevent path traversal attacks
|
|
31
|
+
return path.basename(decodeURIComponent(filenameMatch[1]));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Fall back to URL path
|
|
35
|
+
try {
|
|
36
|
+
const urlObj = new URL(url);
|
|
37
|
+
const pathname = urlObj.pathname;
|
|
38
|
+
const filename = pathname.split('/').pop();
|
|
39
|
+
if (filename && filename.length > 0) {
|
|
40
|
+
// Use path.basename to prevent path traversal attacks (e.g., URL-encoded separators)
|
|
41
|
+
return path.basename(decodeURIComponent(filename));
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// Invalid URL, use default
|
|
45
|
+
}
|
|
46
|
+
// Generate a timestamp-based default filename
|
|
47
|
+
const now = new Date();
|
|
48
|
+
const date = now.toISOString().split('T')[0]; // 2024-02-23
|
|
49
|
+
const time = now.toTimeString().split(' ')[0].replace(/:/g, ''); // 143052
|
|
50
|
+
return `untitled_${date}_${time}`;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Fetches a URL and saves it as a temporary file
|
|
54
|
+
* Returns an InputFile-compatible object for use with the upload pipeline
|
|
55
|
+
*/ const fetchUrlToInputFile = async (url, tmpWorkingDirectory, sizeLimit)=>{
|
|
56
|
+
// Validate URL protocol
|
|
57
|
+
let parsedUrl;
|
|
58
|
+
try {
|
|
59
|
+
parsedUrl = new URL(url);
|
|
60
|
+
} catch {
|
|
61
|
+
throw new ApplicationError(`Invalid URL: ${url}`);
|
|
62
|
+
}
|
|
63
|
+
if (![
|
|
64
|
+
'http:',
|
|
65
|
+
'https:'
|
|
66
|
+
].includes(parsedUrl.protocol)) {
|
|
67
|
+
throw new ApplicationError(`Invalid URL protocol. Only http and https are allowed: ${url}`);
|
|
68
|
+
}
|
|
69
|
+
// Resolve hostname and block private/internal IP ranges to prevent SSRF
|
|
70
|
+
try {
|
|
71
|
+
const { address, family } = await dns.lookup(parsedUrl.hostname);
|
|
72
|
+
const type = family === 6 ? 'ipv6' : 'ipv4';
|
|
73
|
+
if (SSRF_BLOCK_LIST.check(address, type)) {
|
|
74
|
+
throw new ApplicationError(`URL resolves to a blocked address: ${url}`);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error instanceof ApplicationError) throw error;
|
|
78
|
+
throw new ApplicationError(`Could not resolve hostname: ${parsedUrl.hostname}`);
|
|
79
|
+
}
|
|
80
|
+
// Fetch the URL with timeout
|
|
81
|
+
let response;
|
|
82
|
+
try {
|
|
83
|
+
response = await fetch(url, {
|
|
84
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
85
|
+
});
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (error instanceof Error && error.name === 'TimeoutError') {
|
|
88
|
+
throw new ApplicationError(`Request timed out while fetching URL: ${url}`);
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
throw new ApplicationError(`Failed to fetch URL: ${url} (${response.status} ${response.statusText})`);
|
|
94
|
+
}
|
|
95
|
+
// Check Content-Length header for early rejection of large files
|
|
96
|
+
if (sizeLimit) {
|
|
97
|
+
const contentLength = response.headers.get('content-length');
|
|
98
|
+
if (contentLength && parseInt(contentLength, 10) > sizeLimit) {
|
|
99
|
+
throw new ApplicationError(`File too large: maximum allowed size is ${Math.round(sizeLimit / (1024 * 1024))}MB`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Get content type and filename
|
|
103
|
+
const contentType = response.headers.get('content-type')?.split(';')[0] || 'application/octet-stream';
|
|
104
|
+
const contentDisposition = response.headers.get('content-disposition');
|
|
105
|
+
const filename = getFilenameFromUrl(response.url, contentDisposition);
|
|
106
|
+
// Read response body
|
|
107
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
108
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
109
|
+
// Write to temp file
|
|
110
|
+
const tmpFilePath = path.join(tmpWorkingDirectory, filename);
|
|
111
|
+
await fse.writeFile(tmpFilePath, buffer);
|
|
112
|
+
// Create file object compatible with upload pipeline
|
|
113
|
+
const fetchedFile = {
|
|
114
|
+
filepath: tmpFilePath,
|
|
115
|
+
originalFilename: filename,
|
|
116
|
+
mimetype: contentType,
|
|
117
|
+
size: buffer.length,
|
|
118
|
+
tmpWorkingDirectory
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
file: fetchedFile
|
|
122
|
+
};
|
|
123
|
+
};
|
|
6
124
|
const getFolderPath = async (folderId)=>{
|
|
7
125
|
if (!folderId) return '/';
|
|
8
126
|
const parentFolder = await strapi.db.query(FOLDER_MODEL_UID).findOne({
|
|
@@ -48,7 +166,8 @@ const signFileUrls = async (file)=>{
|
|
|
48
166
|
var file = {
|
|
49
167
|
getFolderPath,
|
|
50
168
|
deleteByIds,
|
|
51
|
-
signFileUrls
|
|
169
|
+
signFileUrls,
|
|
170
|
+
fetchUrlToInputFile
|
|
52
171
|
};
|
|
53
172
|
|
|
54
173
|
export { file as default };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.mjs","sources":["../../../server/src/services/file.ts"],"sourcesContent":["import { cloneDeep } from 'lodash/fp';\nimport { async } from '@strapi/utils';\n\nimport { FOLDER_MODEL_UID, FILE_MODEL_UID } from '../constants';\nimport { getService } from '../utils';\n\nimport { Config, type File } from '../types';\n\nconst getFolderPath = async (folderId?: number | null) => {\n if (!folderId) return '/';\n\n const parentFolder = await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } });\n\n return parentFolder.path;\n};\n\nconst deleteByIds = async (ids: number[] = []) => {\n const filesToDelete = await strapi.db\n .query(FILE_MODEL_UID)\n .findMany({ where: { id: { $in: ids } } });\n\n await Promise.all(filesToDelete.map((file: File) => getService('upload').remove(file)));\n\n return filesToDelete;\n};\n\nconst signFileUrls = async (file: File) => {\n const { provider } = strapi.plugins.upload;\n const { provider: providerConfig } = strapi.config.get<Config>('plugin::upload');\n const isPrivate = await provider.isPrivate();\n file.isUrlSigned = false;\n\n // Check file provider and if provider is private\n if (file.provider !== providerConfig || !isPrivate) {\n return file;\n }\n\n const signUrl = async (file: File) => {\n const signedUrl = await provider.getSignedUrl(file);\n file.url = signedUrl.url;\n file.isUrlSigned = true;\n };\n\n const signedFile = cloneDeep(file);\n\n // Sign each file format\n await signUrl(signedFile);\n if (file.formats) {\n await async.map(Object.values(signedFile.formats ?? {}), signUrl);\n }\n\n return signedFile;\n};\n\nexport default { getFolderPath, deleteByIds, signFileUrls };\n"],"names":["getFolderPath","folderId","parentFolder","strapi","db","query","FOLDER_MODEL_UID","findOne","where","id","path","deleteByIds","ids","filesToDelete","FILE_MODEL_UID","findMany","$in","Promise","all","map","file","getService","remove","signFileUrls","provider","plugins","upload","providerConfig","config","get","isPrivate","isUrlSigned","signUrl","signedUrl","getSignedUrl","url","signedFile","cloneDeep","formats","async","Object","values"],"mappings":";;;;;AAQA,MAAMA,gBAAgB,OAAOC,QAAAA,GAAAA;IAC3B,IAAI,CAACA,UAAU,OAAO,GAAA;IAEtB,MAAMC,YAAAA,GAAe,MAAMC,MAAOC,CAAAA,EAAE,CAACC,KAAK,CAACC,gBAAkBC,CAAAA,CAAAA,OAAO,CAAC;QAAEC,KAAO,EAAA;YAAEC,EAAIR,EAAAA;AAAS;AAAE,KAAA,CAAA;AAE/F,IAAA,OAAOC,aAAaQ,IAAI;AAC1B,CAAA;AAEA,MAAMC,WAAAA,GAAc,OAAOC,GAAAA,GAAgB,EAAE,GAAA;IAC3C,MAAMC,aAAAA,GAAgB,MAAMV,MAAOC,CAAAA,EAAE,CAClCC,KAAK,CAACS,cACNC,CAAAA,CAAAA,QAAQ,CAAC;QAAEP,KAAO,EAAA;YAAEC,EAAI,EAAA;gBAAEO,GAAKJ,EAAAA;AAAI;AAAE;AAAE,KAAA,CAAA;IAE1C,MAAMK,OAAAA,CAAQC,GAAG,CAACL,aAAcM,CAAAA,GAAG,CAAC,CAACC,IAAeC,GAAAA,UAAAA,CAAW,QAAUC,CAAAA,CAAAA,MAAM,CAACF,IAAAA,CAAAA,CAAAA,CAAAA;IAEhF,OAAOP,aAAAA;AACT,CAAA;AAEA,MAAMU,eAAe,OAAOH,IAAAA,GAAAA;AAC1B,IAAA,MAAM,EAAEI,QAAQ,EAAE,GAAGrB,MAAOsB,CAAAA,OAAO,CAACC,MAAM;IAC1C,MAAM,EAAEF,UAAUG,cAAc,EAAE,GAAGxB,MAAOyB,CAAAA,MAAM,CAACC,GAAG,CAAS,gBAAA,CAAA;IAC/D,MAAMC,SAAAA,GAAY,MAAMN,QAAAA,CAASM,SAAS,EAAA;AAC1CV,IAAAA,IAAAA,CAAKW,WAAW,GAAG,KAAA;;AAGnB,IAAA,IAAIX,IAAKI,CAAAA,QAAQ,KAAKG,cAAAA,IAAkB,CAACG,SAAW,EAAA;QAClD,OAAOV,IAAAA;AACT;AAEA,IAAA,MAAMY,UAAU,OAAOZ,IAAAA,GAAAA;AACrB,QAAA,MAAMa,SAAY,GAAA,MAAMT,QAASU,CAAAA,YAAY,CAACd,IAAAA,CAAAA;QAC9CA,IAAKe,CAAAA,GAAG,GAAGF,SAAAA,CAAUE,GAAG;AACxBf,QAAAA,IAAAA,CAAKW,WAAW,GAAG,IAAA;AACrB,KAAA;AAEA,IAAA,MAAMK,aAAaC,SAAUjB,CAAAA,IAAAA,CAAAA;;AAG7B,IAAA,MAAMY,OAAQI,CAAAA,UAAAA,CAAAA;IACd,IAAIhB,IAAAA,CAAKkB,OAAO,EAAE;QAChB,MAAMC,KAAAA,CAAMpB,GAAG,CAACqB,MAAOC,CAAAA,MAAM,CAACL,UAAWE,CAAAA,OAAO,IAAI,EAAKN,CAAAA,EAAAA,OAAAA,CAAAA;AAC3D;IAEA,OAAOI,UAAAA;AACT,CAAA;AAEA,WAAe;AAAEpC,IAAAA,aAAAA;AAAeW,IAAAA,WAAAA;AAAaY,IAAAA;AAAa,CAAE;;;;"}
|
|
1
|
+
{"version":3,"file":"file.mjs","sources":["../../../server/src/services/file.ts"],"sourcesContent":["import path from 'path';\nimport dns from 'dns/promises';\nimport net from 'net';\nimport fse from 'fs-extra';\nimport { cloneDeep } from 'lodash/fp';\nimport { async, errors } from '@strapi/utils';\n\nimport { FOLDER_MODEL_UID, FILE_MODEL_UID } from '../constants';\nimport { getService } from '../utils';\n\nimport { Config, type File } from '../types';\n\nconst { ApplicationError } = errors;\n\nconst FETCH_TIMEOUT_MS = 60_000; // 60 seconds\n\n// Blocks loopback, link-local (cloud metadata), and RFC-1918 private ranges to prevent SSRF\nconst SSRF_BLOCK_LIST = new net.BlockList();\nSSRF_BLOCK_LIST.addSubnet('127.0.0.0', 8); // loopback\nSSRF_BLOCK_LIST.addSubnet('10.0.0.0', 8); // RFC-1918\nSSRF_BLOCK_LIST.addSubnet('172.16.0.0', 12); // RFC-1918\nSSRF_BLOCK_LIST.addSubnet('192.168.0.0', 16); // RFC-1918\nSSRF_BLOCK_LIST.addSubnet('169.254.0.0', 16); // link-local / cloud metadata (AWS, GCP, Azure)\nSSRF_BLOCK_LIST.addSubnet('::1', 128, 'ipv6'); // IPv6 loopback\nSSRF_BLOCK_LIST.addSubnet('fc00::', 7, 'ipv6'); // IPv6 unique local\nSSRF_BLOCK_LIST.addSubnet('fe80::', 10, 'ipv6'); // IPv6 link-local\n\n/**\n * Represents a file fetched from a URL, compatible with the upload pipeline\n */\ninterface UrlFetchedFile {\n filepath: string;\n originalFilename: string;\n mimetype: string;\n size: number;\n tmpWorkingDirectory?: string;\n}\n\ninterface FetchUrlResult {\n file: UrlFetchedFile;\n}\n\n/**\n * Extracts filename from a URL path or Content-Disposition header\n */\nconst getFilenameFromUrl = (url: string, contentDisposition?: string | null): string => {\n // Try Content-Disposition header first\n if (contentDisposition) {\n // Extracts filename from Content-Disposition header (e.g. filename=\"photo.jpg\" or filename*=UTF-8''photo.jpg)\n const filenameMatch = contentDisposition.match(\n /filename\\*?=['\"]?(?:UTF-\\d['\"]*)?([^;\\r\\n\"']*)['\"]?/i\n );\n if (filenameMatch?.[1]) {\n // Use path.basename to prevent path traversal attacks\n return path.basename(decodeURIComponent(filenameMatch[1]));\n }\n }\n\n // Fall back to URL path\n try {\n const urlObj = new URL(url);\n const pathname = urlObj.pathname;\n const filename = pathname.split('/').pop();\n if (filename && filename.length > 0) {\n // Use path.basename to prevent path traversal attacks (e.g., URL-encoded separators)\n return path.basename(decodeURIComponent(filename));\n }\n } catch {\n // Invalid URL, use default\n }\n\n // Generate a timestamp-based default filename\n const now = new Date();\n const date = now.toISOString().split('T')[0]; // 2024-02-23\n const time = now.toTimeString().split(' ')[0].replace(/:/g, ''); // 143052\n return `untitled_${date}_${time}`;\n};\n\n/**\n * Fetches a URL and saves it as a temporary file\n * Returns an InputFile-compatible object for use with the upload pipeline\n */\nconst fetchUrlToInputFile = async (\n url: string,\n tmpWorkingDirectory: string,\n sizeLimit?: number\n): Promise<FetchUrlResult> => {\n // Validate URL protocol\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(url);\n } catch {\n throw new ApplicationError(`Invalid URL: ${url}`);\n }\n\n if (!['http:', 'https:'].includes(parsedUrl.protocol)) {\n throw new ApplicationError(`Invalid URL protocol. Only http and https are allowed: ${url}`);\n }\n\n // Resolve hostname and block private/internal IP ranges to prevent SSRF\n try {\n const { address, family } = await dns.lookup(parsedUrl.hostname);\n const type = family === 6 ? 'ipv6' : 'ipv4';\n if (SSRF_BLOCK_LIST.check(address, type)) {\n throw new ApplicationError(`URL resolves to a blocked address: ${url}`);\n }\n } catch (error) {\n if (error instanceof ApplicationError) throw error;\n throw new ApplicationError(`Could not resolve hostname: ${parsedUrl.hostname}`);\n }\n\n // Fetch the URL with timeout\n let response: Response;\n try {\n response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\n } catch (error) {\n if (error instanceof Error && error.name === 'TimeoutError') {\n throw new ApplicationError(`Request timed out while fetching URL: ${url}`);\n }\n throw error;\n }\n\n if (!response.ok) {\n throw new ApplicationError(\n `Failed to fetch URL: ${url} (${response.status} ${response.statusText})`\n );\n }\n\n // Check Content-Length header for early rejection of large files\n if (sizeLimit) {\n const contentLength = response.headers.get('content-length');\n if (contentLength && parseInt(contentLength, 10) > sizeLimit) {\n throw new ApplicationError(\n `File too large: maximum allowed size is ${Math.round(sizeLimit / (1024 * 1024))}MB`\n );\n }\n }\n\n // Get content type and filename\n const contentType =\n response.headers.get('content-type')?.split(';')[0] || 'application/octet-stream';\n const contentDisposition = response.headers.get('content-disposition');\n const filename = getFilenameFromUrl(response.url, contentDisposition);\n\n // Read response body\n const arrayBuffer = await response.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n // Write to temp file\n const tmpFilePath = path.join(tmpWorkingDirectory, filename);\n await fse.writeFile(tmpFilePath, buffer);\n\n // Create file object compatible with upload pipeline\n const fetchedFile: UrlFetchedFile = {\n filepath: tmpFilePath,\n originalFilename: filename,\n mimetype: contentType,\n size: buffer.length,\n tmpWorkingDirectory,\n };\n\n return { file: fetchedFile };\n};\n\nconst getFolderPath = async (folderId?: number | null) => {\n if (!folderId) return '/';\n\n const parentFolder = await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } });\n\n return parentFolder.path;\n};\n\nconst deleteByIds = async (ids: number[] = []) => {\n const filesToDelete = await strapi.db\n .query(FILE_MODEL_UID)\n .findMany({ where: { id: { $in: ids } } });\n\n await Promise.all(filesToDelete.map((file: File) => getService('upload').remove(file)));\n\n return filesToDelete;\n};\n\nconst signFileUrls = async (file: File) => {\n const { provider } = strapi.plugins.upload;\n const { provider: providerConfig } = strapi.config.get<Config>('plugin::upload');\n const isPrivate = await provider.isPrivate();\n file.isUrlSigned = false;\n\n // Check file provider and if provider is private\n if (file.provider !== providerConfig || !isPrivate) {\n return file;\n }\n\n const signUrl = async (file: File) => {\n const signedUrl = await provider.getSignedUrl(file);\n file.url = signedUrl.url;\n file.isUrlSigned = true;\n };\n\n const signedFile = cloneDeep(file);\n\n // Sign each file format\n await signUrl(signedFile);\n if (file.formats) {\n await async.map(Object.values(signedFile.formats ?? {}), signUrl);\n }\n\n return signedFile;\n};\n\nexport type { UrlFetchedFile, FetchUrlResult };\nexport default { getFolderPath, deleteByIds, signFileUrls, fetchUrlToInputFile };\n"],"names":["ApplicationError","errors","FETCH_TIMEOUT_MS","SSRF_BLOCK_LIST","net","BlockList","addSubnet","getFilenameFromUrl","url","contentDisposition","filenameMatch","match","path","basename","decodeURIComponent","urlObj","URL","pathname","filename","split","pop","length","now","Date","date","toISOString","time","toTimeString","replace","fetchUrlToInputFile","tmpWorkingDirectory","sizeLimit","parsedUrl","includes","protocol","address","family","dns","lookup","hostname","type","check","error","response","fetch","signal","AbortSignal","timeout","Error","name","ok","status","statusText","contentLength","headers","get","parseInt","Math","round","contentType","arrayBuffer","buffer","Buffer","from","tmpFilePath","join","fse","writeFile","fetchedFile","filepath","originalFilename","mimetype","size","file","getFolderPath","folderId","parentFolder","strapi","db","query","FOLDER_MODEL_UID","findOne","where","id","deleteByIds","ids","filesToDelete","FILE_MODEL_UID","findMany","$in","Promise","all","map","getService","remove","signFileUrls","provider","plugins","upload","providerConfig","config","isPrivate","isUrlSigned","signUrl","signedUrl","getSignedUrl","signedFile","cloneDeep","formats","async","Object","values"],"mappings":"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"}
|