Skip to content

Commit 400293b

Browse files
authored
fix: duplicate with upload collections (#8552)
Fixes the duplicate operation with uploads Enables duplicate for upload collections by default
1 parent e4a413e commit 400293b

File tree

8 files changed

+67
-11
lines changed

8 files changed

+67
-11
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,6 @@ test/app/(payload)/admin/importMap.js
314314
/test/app/(payload)/admin/importMap.js
315315
test/pnpm-lock.yaml
316316
test/databaseAdapter.js
317+
/filename-compound-index
318+
/media-with-relation-preview
319+
/media-without-relation-preview

packages/payload/src/collections/config/sanitize.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,6 @@ export const sanitizeCollection = async (
130130
// sanitize fields for reserved names
131131
sanitizeUploadFields(sanitized.fields, sanitized)
132132

133-
// disable duplicate for uploads by default
134-
sanitized.disableDuplicate = sanitized.disableDuplicate || true
135-
136133
sanitized.upload.bulkUpload = sanitized.upload?.bulkUpload ?? true
137134
sanitized.upload.staticDir = sanitized.upload.staticDir || sanitized.slug
138135
sanitized.admin.useAsTitle =
@@ -162,7 +159,7 @@ export const sanitizeCollection = async (
162159
}
163160

164161
// disable duplicate for auth enabled collections by default
165-
sanitized.disableDuplicate = sanitized.disableDuplicate || true
162+
sanitized.disableDuplicate = sanitized.disableDuplicate ?? true
166163

167164
if (!sanitized.auth.strategies) {
168165
sanitized.auth.strategies = []

packages/payload/src/collections/operations/duplicate.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { afterRead } from '../../fields/hooks/afterRead/index.js'
1616
import { beforeChange } from '../../fields/hooks/beforeChange/index.js'
1717
import { beforeDuplicate } from '../../fields/hooks/beforeDuplicate/index.js'
1818
import { beforeValidate } from '../../fields/hooks/beforeValidate/index.js'
19+
import { generateFileData } from '../../uploads/generateFileData.js'
20+
import { uploadFiles } from '../../uploads/uploadFiles.js'
1921
import { commitTransaction } from '../../utilities/commitTransaction.js'
2022
import { initTransaction } from '../../utilities/initTransaction.js'
2123
import { killTransaction } from '../../utilities/killTransaction.js'
@@ -129,7 +131,7 @@ export const duplicateOperation = async <TSlug extends CollectionSlug>(
129131

130132
let result
131133

132-
const originalDoc = await afterRead({
134+
let originalDoc = await afterRead({
133135
collection: collectionConfig,
134136
context: req.context,
135137
depth: 0,
@@ -143,6 +145,18 @@ export const duplicateOperation = async <TSlug extends CollectionSlug>(
143145
showHiddenFields: true,
144146
})
145147

148+
const { data: newFileData, files: filesToUpload } = await generateFileData({
149+
collection: args.collection,
150+
config: req.payload.config,
151+
data: originalDoc,
152+
operation: 'create',
153+
overwriteExistingFiles: 'forceDisable',
154+
req,
155+
throwOnMissingFile: true,
156+
})
157+
158+
originalDoc = newFileData
159+
146160
// /////////////////////////////////////
147161
// Create Access
148162
// /////////////////////////////////////
@@ -231,6 +245,14 @@ export const duplicateOperation = async <TSlug extends CollectionSlug>(
231245
// Create / Update
232246
// /////////////////////////////////////
233247

248+
// /////////////////////////////////////
249+
// Write files to local storage
250+
// /////////////////////////////////////
251+
252+
if (!collectionConfig.upload.disableLocalStorage) {
253+
await uploadFiles(payload, filesToUpload, req)
254+
}
255+
234256
const versionDoc = await payload.db.create({
235257
collection: collectionConfig.slug,
236258
data: result,

packages/payload/src/collections/operations/local/duplicate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export async function duplicate<TSlug extends CollectionSlug>(
4444
)
4545
}
4646

47-
if (collection.config.disableDuplicate === false) {
47+
if (collection.config.disableDuplicate === true) {
4848
throw new APIError(
4949
`The collection with slug ${String(collectionSlug)} cannot be duplicated.`,
5050
400,

packages/payload/src/uploads/generateFileData.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ type Args<T> = {
2727
data: T
2828
operation: 'create' | 'update'
2929
originalDoc?: T
30-
overwriteExistingFiles?: boolean
30+
/** pass forceDisable to not overwrite existing files even if they already exist in `data` */
31+
overwriteExistingFiles?: 'forceDisable' | boolean
3132
req: PayloadRequest
3233
throwOnMissingFile?: boolean
3334
}
@@ -85,20 +86,28 @@ export const generateFileData = async <T>({
8586
const filePath = `${staticPath}/${filename}`
8687
const response = await getFileByPath(filePath)
8788
file = response
88-
overwriteExistingFiles = true
89+
if (overwriteExistingFiles !== 'forceDisable') {
90+
overwriteExistingFiles = true
91+
}
8992
} else if (filename && url) {
9093
file = await getExternalFile({
9194
data: data as FileData,
9295
req,
9396
uploadConfig: collectionConfig.upload,
9497
})
95-
overwriteExistingFiles = true
98+
if (overwriteExistingFiles !== 'forceDisable') {
99+
overwriteExistingFiles = true
100+
}
96101
}
97102
} catch (err: unknown) {
98103
throw new FileRetrievalError(req.t, err instanceof Error ? err.message : undefined)
99104
}
100105
}
101106

107+
if (overwriteExistingFiles === 'forceDisable') {
108+
overwriteExistingFiles = false
109+
}
110+
102111
if (!file) {
103112
if (throwOnMissingFile) {
104113
throw new MissingFile(req.t)

packages/ui/src/elements/DocumentControls/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ export const DocumentControls: React.FC<{
279279
)}
280280
</Fragment>
281281
)}
282-
{!collectionConfig.disableDuplicate && isEditing && (
282+
{collectionConfig.disableDuplicate !== true && isEditing && (
283283
<DuplicateDocument
284284
id={id.toString()}
285285
onDuplicate={onDuplicate}

test/uploads/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import {
1717
enlargeSlug,
1818
focalNoSizesSlug,
1919
mediaSlug,
20-
mediaWithRelationPreviewSlug,
2120
mediaWithoutRelationPreviewSlug,
21+
mediaWithRelationPreviewSlug,
2222
reduceSlug,
2323
relationPreviewSlug,
2424
relationSlug,

test/uploads/int.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,31 @@ describe('Collections - Uploads', () => {
779779
)
780780
})
781781
})
782+
783+
describe('Duplicate', () => {
784+
it('should duplicate upload collection doc', async () => {
785+
const filePath = path.resolve(dirname, './image.png')
786+
const file = await getFileByPath(filePath)
787+
file.name = 'file-to-duplicate.png'
788+
789+
const mediaDoc = await payload.create({
790+
collection: 'media',
791+
data: {},
792+
file,
793+
})
794+
795+
expect(mediaDoc).toBeDefined()
796+
797+
const duplicatedDoc = await payload.duplicate({
798+
collection: 'media',
799+
id: mediaDoc.id,
800+
})
801+
802+
const expectedPath = path.join(dirname, './media')
803+
804+
expect(await fileExists(path.join(expectedPath, duplicatedDoc.filename))).toBe(true)
805+
})
806+
})
782807
})
783808

784809
async function fileExists(fileName: string): Promise<boolean> {

0 commit comments

Comments
 (0)