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 https://github.com/payloadcms/payload/issues/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>
This commit is contained in:
@@ -11,6 +11,7 @@ export const Posts: CollectionConfig = {
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
trash: true,
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
|
||||
@@ -534,4 +534,67 @@ describe('@payloadcms/plugin-search', () => {
|
||||
|
||||
expect(totalAfterReindex).toBe(totalBeforeReindex)
|
||||
})
|
||||
|
||||
it('should sync trashed documents correctly with search plugin', async () => {
|
||||
// Create a published post
|
||||
const publishedPost = await payload.create({
|
||||
collection: postsSlug,
|
||||
data: {
|
||||
title: 'Post to be trashed',
|
||||
excerpt: 'This post will be soft deleted',
|
||||
_status: 'published',
|
||||
},
|
||||
})
|
||||
|
||||
// Wait for the search document to be created
|
||||
await wait(200)
|
||||
|
||||
// Verify the search document was created
|
||||
const { docs: initialSearchResults } = await payload.find({
|
||||
collection: 'search',
|
||||
depth: 0,
|
||||
where: {
|
||||
'doc.value': {
|
||||
equals: publishedPost.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(initialSearchResults).toHaveLength(1)
|
||||
expect(initialSearchResults[0]?.title).toBe('Post to be trashed')
|
||||
|
||||
// Soft delete the post (move to trash)
|
||||
await payload.update({
|
||||
collection: postsSlug,
|
||||
id: publishedPost.id,
|
||||
data: {
|
||||
deletedAt: new Date().toISOString(),
|
||||
},
|
||||
})
|
||||
|
||||
// Wait for the search plugin to sync the trashed document
|
||||
await wait(200)
|
||||
|
||||
// Verify the search document still exists but is properly synced
|
||||
// The search document should remain and be updated correctly
|
||||
const { docs: trashedSearchResults } = await payload.find({
|
||||
collection: 'search',
|
||||
depth: 0,
|
||||
where: {
|
||||
'doc.value': {
|
||||
equals: publishedPost.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// The search document should still exist
|
||||
expect(trashedSearchResults).toHaveLength(0)
|
||||
|
||||
// Clean up by permanently deleting the trashed post
|
||||
await payload.delete({
|
||||
collection: postsSlug,
|
||||
id: publishedPost.id,
|
||||
trash: true, // permanently delete
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -168,6 +168,7 @@ export interface Post {
|
||||
slug?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
deletedAt?: string | null;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
@@ -336,6 +337,7 @@ export interface PostsSelect<T extends boolean = true> {
|
||||
slug?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
deletedAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user