Skip to content

Commit de352a6

Browse files
jancbeckCopilotPatrikKozak
authored
fix(plugin-search): handle trashed documents in search plugin sync (#13836)
### What? Prevents "not found" error when trashing search-enabled documents in localized site. ### Why? **See issue #13835 for details and reproduction of bug.** When a document is soft-deleted (has `deletedAt` timestamp), the search plugin's `afterChange` hook tries to sync the document but fails because `payload.findByID()` excludes trashed documents by default. **Original buggy code** in `packages/plugin-search/src/utilities/syncDocAsSearchIndex.ts` at lines 46-51: ```typescript docToSyncWith = await payload.findByID({ id, collection, locale: syncLocale, req, // MISSING: trash parameter! }) ``` ### How? Added detection for trashed documents and include `trash: true` parameter: ```typescript // Check if document is trashed (has deletedAt field) const isTrashDocument = doc && 'deletedAt' in doc && doc.deletedAt docToSyncWith = await payload.findByID({ id, collection, locale: syncLocale, req, // Include trashed documents when the document being synced is trashed trash: isTrashDocument, }) ``` ### Test Coverage Added - **Enabled trash functionality** in Posts collection for plugin-search tests - **Added comprehensive e2e test case** in `test/plugin-search/int.spec.ts` that verifies: 1. Creates a published post and verifies search document creation 2. Soft deletes the post (moves to trash) 3. Verifies search document is properly synced after trash operation 4. Cleans up by permanently deleting the trashed document --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
1 parent 7eacd39 commit de352a6

File tree

4 files changed

+92
-1
lines changed

4 files changed

+92
-1
lines changed

packages/plugin-search/src/utilities/syncDocAsSearchIndex.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,16 @@ export const syncDocAsSearchIndex = async ({
4343
if (typeof beforeSync === 'function') {
4444
let docToSyncWith = doc
4545
if (payload.config?.localization) {
46+
// Check if document is trashed (has deletedAt field)
47+
const isTrashDocument = doc && 'deletedAt' in doc && doc.deletedAt
48+
4649
docToSyncWith = await payload.findByID({
4750
id,
4851
collection,
4952
locale: syncLocale,
5053
req,
54+
// Include trashed documents when the document being synced is trashed
55+
trash: isTrashDocument,
5156
})
5257
}
5358
dataToSave = await beforeSync({
@@ -157,6 +162,26 @@ export const syncDocAsSearchIndex = async ({
157162
payload.logger.error({ err, msg: `Error updating ${searchSlug} document.` })
158163
}
159164
}
165+
166+
// Check if document is trashed and delete from search
167+
const isTrashDocument = doc && 'deletedAt' in doc && doc.deletedAt
168+
169+
if (isTrashDocument) {
170+
try {
171+
await payload.delete({
172+
id: searchDocID,
173+
collection: searchSlug,
174+
depth: 0,
175+
req,
176+
})
177+
} catch (err: unknown) {
178+
payload.logger.error({
179+
err,
180+
msg: `Error deleting ${searchSlug} document for trashed doc.`,
181+
})
182+
}
183+
}
184+
160185
if (deleteDrafts && status === 'draft') {
161186
// Check to see if there's a published version of the doc
162187
// We don't want to remove the search doc if there is a published version but a new draft has been created
@@ -186,7 +211,7 @@ export const syncDocAsSearchIndex = async ({
186211
},
187212
})
188213

189-
if (!docWithPublish) {
214+
if (!docWithPublish && !isTrashDocument) {
190215
// do not include draft docs in search results, so delete the record
191216
try {
192217
await payload.delete({

test/plugin-search/collections/Posts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const Posts: CollectionConfig = {
1111
admin: {
1212
useAsTitle: 'title',
1313
},
14+
trash: true,
1415
versions: {
1516
drafts: true,
1617
},

test/plugin-search/int.spec.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,4 +534,67 @@ describe('@payloadcms/plugin-search', () => {
534534

535535
expect(totalAfterReindex).toBe(totalBeforeReindex)
536536
})
537+
538+
it('should sync trashed documents correctly with search plugin', async () => {
539+
// Create a published post
540+
const publishedPost = await payload.create({
541+
collection: postsSlug,
542+
data: {
543+
title: 'Post to be trashed',
544+
excerpt: 'This post will be soft deleted',
545+
_status: 'published',
546+
},
547+
})
548+
549+
// Wait for the search document to be created
550+
await wait(200)
551+
552+
// Verify the search document was created
553+
const { docs: initialSearchResults } = await payload.find({
554+
collection: 'search',
555+
depth: 0,
556+
where: {
557+
'doc.value': {
558+
equals: publishedPost.id,
559+
},
560+
},
561+
})
562+
563+
expect(initialSearchResults).toHaveLength(1)
564+
expect(initialSearchResults[0]?.title).toBe('Post to be trashed')
565+
566+
// Soft delete the post (move to trash)
567+
await payload.update({
568+
collection: postsSlug,
569+
id: publishedPost.id,
570+
data: {
571+
deletedAt: new Date().toISOString(),
572+
},
573+
})
574+
575+
// Wait for the search plugin to sync the trashed document
576+
await wait(200)
577+
578+
// Verify the search document still exists but is properly synced
579+
// The search document should remain and be updated correctly
580+
const { docs: trashedSearchResults } = await payload.find({
581+
collection: 'search',
582+
depth: 0,
583+
where: {
584+
'doc.value': {
585+
equals: publishedPost.id,
586+
},
587+
},
588+
})
589+
590+
// The search document should still exist
591+
expect(trashedSearchResults).toHaveLength(0)
592+
593+
// Clean up by permanently deleting the trashed post
594+
await payload.delete({
595+
collection: postsSlug,
596+
id: publishedPost.id,
597+
trash: true, // permanently delete
598+
})
599+
})
537600
})

test/plugin-search/payload-types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ export interface Post {
168168
slug?: string | null;
169169
updatedAt: string;
170170
createdAt: string;
171+
deletedAt?: string | null;
171172
_status?: ('draft' | 'published') | null;
172173
}
173174
/**
@@ -336,6 +337,7 @@ export interface PostsSelect<T extends boolean = true> {
336337
slug?: T;
337338
updatedAt?: T;
338339
createdAt?: T;
340+
deletedAt?: T;
339341
_status?: T;
340342
}
341343
/**

0 commit comments

Comments
 (0)