diff --git a/packages/next/src/elements/DocumentLocked/index.tsx b/packages/next/src/elements/DocumentLocked/index.tsx index d839fda573..a02eede8af 100644 --- a/packages/next/src/elements/DocumentLocked/index.tsx +++ b/packages/next/src/elements/DocumentLocked/index.tsx @@ -25,13 +25,13 @@ const formatDate = (date) => { } export const DocumentLocked: React.FC<{ - editedAt?: null | number handleGoBack: () => void isActive: boolean onReadOnly: () => void onTakeOver: () => void + updatedAt?: null | number user?: ClientUser -}> = ({ editedAt, handleGoBack, isActive, onReadOnly, onTakeOver, user }) => { +}> = ({ handleGoBack, isActive, onReadOnly, onTakeOver, updatedAt, user }) => { const { closeModal, openModal } = useModal() const { t } = useTranslation() @@ -52,7 +52,7 @@ export const DocumentLocked: React.FC<{ {user?.email ?? user?.id} {t('general:currentlyEditing')}

- {t('general:editedSince')} {formatDate(editedAt)} + {t('general:editedSince')} {formatDate(updatedAt)}

diff --git a/packages/next/src/views/Edit/Default/index.tsx b/packages/next/src/views/Edit/Default/index.tsx index 35416bc834..3763e2c11f 100644 --- a/packages/next/src/views/Edit/Default/index.tsx +++ b/packages/next/src/views/Edit/Default/index.tsx @@ -421,7 +421,6 @@ export const DefaultEditView: React.FC = () => { {BeforeDocument} {isLockingEnabled && shouldShowDocumentLockedModal && !isReadOnlyForIncomingUser && ( { @@ -429,6 +428,7 @@ export const DefaultEditView: React.FC = () => { setShowTakeOverModal(false) }} onTakeOver={handleTakeOver} + updatedAt={lastUpdateTime} user={currentEditor} /> )} diff --git a/packages/payload/src/lockedDocuments/lockedDocumentsCollection.ts b/packages/payload/src/lockedDocuments/lockedDocumentsCollection.ts index 9cc0f1cd73..e2508ac3df 100644 --- a/packages/payload/src/lockedDocuments/lockedDocumentsCollection.ts +++ b/packages/payload/src/lockedDocuments/lockedDocumentsCollection.ts @@ -14,17 +14,15 @@ export const getLockedDocumentsCollection = (config: Config): CollectionConfig = maxDepth: 0, relationTo: [...config.collections.map((collectionConfig) => collectionConfig.slug)], }, - { - name: 'editedAt', - type: 'date', - }, { name: 'globalSlug', type: 'text', + index: true, }, { name: 'user', type: 'relationship', + maxDepth: 1, relationTo: config.collections .filter((collectionConfig) => collectionConfig.auth) .map((collectionConfig) => collectionConfig.slug), diff --git a/packages/payload/src/utilities/checkDocumentLockStatus.ts b/packages/payload/src/utilities/checkDocumentLockStatus.ts index 4924f60d86..ff0dece07b 100644 --- a/packages/payload/src/utilities/checkDocumentLockStatus.ts +++ b/packages/payload/src/utilities/checkDocumentLockStatus.ts @@ -55,9 +55,8 @@ export const checkDocumentLockStatus = async ({ const finalLockErrorMessage = lockErrorMessage || defaultLockErrorMessage - const lockedDocumentResult: PaginatedDocs = await payload.find({ + const lockedDocumentResult: PaginatedDocs = await payload.db.find({ collection: 'payload-locked-documents', - depth: 1, limit: 1, pagination: false, req, @@ -68,8 +67,8 @@ export const checkDocumentLockStatus = async ({ // If there's a locked document, check lock conditions const lockedDoc = lockedDocumentResult?.docs[0] if (lockedDoc) { - const lastEditedAt = new Date(lockedDoc?.editedAt) - const now = new Date() + const lastEditedAt = new Date(lockedDoc?.updatedAt).getTime() + const now = new Date().getTime() const lockDuration = typeof lockDocumentsProp === 'object' ? lockDocumentsProp.duration : lockDurationDefault @@ -79,8 +78,8 @@ export const checkDocumentLockStatus = async ({ // document is locked by another user and the lock hasn't expired if ( - lockedDoc.user?.value?.id !== currentUserId && - now.getTime() - lastEditedAt.getTime() <= lockDurationInMilliseconds + lockedDoc.user?.value !== currentUserId && + now - lastEditedAt <= lockDurationInMilliseconds ) { throw new Locked(finalLockErrorMessage) } diff --git a/packages/ui/src/providers/DocumentInfo/index.tsx b/packages/ui/src/providers/DocumentInfo/index.tsx index d4da463400..3c0ad5815b 100644 --- a/packages/ui/src/providers/DocumentInfo/index.tsx +++ b/packages/ui/src/providers/DocumentInfo/index.tsx @@ -193,7 +193,6 @@ const DocumentInfo: React.FC< // Send a patch request to update the _lastEdited info await requests.patch(`${serverURL}${api}/payload-locked-documents/${lockId}`, { body: JSON.stringify({ - editedAt: new Date(), user: { relationTo: user?.collection, value: user?.id }, }), headers: { diff --git a/packages/ui/src/utilities/buildFormState.ts b/packages/ui/src/utilities/buildFormState.ts index 7c0a74d42b..f20249b3e8 100644 --- a/packages/ui/src/utilities/buildFormState.ts +++ b/packages/ui/src/utilities/buildFormState.ts @@ -265,9 +265,7 @@ export const buildFormState = async ({ await req.payload.db.updateOne({ id: lockedDocument.docs[0].id, collection: 'payload-locked-documents', - data: { - editedAt: new Date().toISOString(), - }, + data: {}, req, }) } @@ -284,7 +282,6 @@ export const buildFormState = async ({ value: id, } : undefined, - editedAt: new Date().toISOString(), globalSlug: globalSlug ? globalSlug : undefined, user: { relationTo: [req.user.collection], diff --git a/test/locked-documents/e2e.spec.ts b/test/locked-documents/e2e.spec.ts index 68d7b2319c..d982c81c38 100644 --- a/test/locked-documents/e2e.spec.ts +++ b/test/locked-documents/e2e.spec.ts @@ -98,7 +98,6 @@ describe('locked documents', () => { relationTo: 'posts', value: postDoc.id, }, - editedAt: new Date().toISOString(), globalSlug: undefined, user: { relationTo: 'users', @@ -318,7 +317,6 @@ describe('locked documents', () => { relationTo: 'posts', value: postDoc.id, }, - editedAt: new Date().toISOString(), globalSlug: undefined, user: { relationTo: 'users', @@ -422,7 +420,6 @@ describe('locked documents', () => { relationTo: 'posts', value: postDoc.id, }, - editedAt: new Date().toISOString(), globalSlug: undefined, user: { relationTo: 'users', @@ -512,7 +509,6 @@ describe('locked documents', () => { relationTo: 'posts', value: postDoc.id, }, - editedAt: new Date().toISOString(), globalSlug: undefined, user: { relationTo: 'users', @@ -784,7 +780,6 @@ describe('locked documents', () => { collection: lockedDocumentCollection, data: { document: undefined, - editedAt: new Date().toISOString(), globalSlug: 'menu', user: { relationTo: 'users', diff --git a/test/locked-documents/int.spec.ts b/test/locked-documents/int.spec.ts index 370118d4e1..8d23a60175 100644 --- a/test/locked-documents/int.spec.ts +++ b/test/locked-documents/int.spec.ts @@ -1,5 +1,8 @@ +import type { Payload, SanitizedCollectionConfig } from 'payload' + import path from 'path' -import { Locked, NotFound, type Payload } from 'payload' +import { Locked, NotFound } from 'payload' +import { wait } from 'payload/shared' import { fileURLToPath } from 'url' import type { NextRESTClient } from '../helpers/NextRESTClient.js' @@ -26,10 +29,13 @@ describe('Locked documents', () => { let menu: Menu let user: any let user2: any + let postConfig: SanitizedCollectionConfig beforeAll(async () => { ;({ payload, restClient } = await initPayloadInt(dirname)) + postConfig = payload.config.collections.find(({ slug }) => slug === postsSlug) + const loginResult = await payload.login({ collection: 'users', data: { @@ -77,6 +83,10 @@ describe('Locked documents', () => { } }) + afterEach(() => { + postConfig.lockDocuments = { duration: 300 } + }) + it('should update unlocked document - collection', async () => { const updatedPost = await payload.update({ collection: postsSlug, @@ -129,16 +139,13 @@ describe('Locked documents', () => { }, }) - // Subtract 3.5 minutes (210 seconds) from the current time - const pastEditedAt = new Date() - pastEditedAt.setMinutes(pastEditedAt.getMinutes() - 3) - pastEditedAt.setSeconds(pastEditedAt.getSeconds() - 30) + // Set lock duration to 1 second for testing purposes + postConfig.lockDocuments = { duration: 1 } // Give locking ownership to another user const lockedDocInstance = await payload.create({ collection: lockedDocumentCollection, data: { - editedAt: pastEditedAt.toISOString(), user: { relationTo: 'users', value: user2.id, @@ -151,6 +158,8 @@ describe('Locked documents', () => { }, }) + await wait(1100) + const updateLockedDoc = await payload.update({ collection: postsSlug, data: { @@ -159,6 +168,7 @@ describe('Locked documents', () => { overrideLock: false, id: newPost2.id, }) + postConfig.lockDocuments = { duration: 300 } // Should allow update since editedAt date is past expiration duration. // Therefore the document is considered stale @@ -187,16 +197,13 @@ describe('Locked documents', () => { }) it('should allow update of stale locked document - global', async () => { - // Subtract 5.5 minutes (330 seconds) from the current time - const pastEditedAt = new Date() - pastEditedAt.setMinutes(pastEditedAt.getMinutes() - 5) - pastEditedAt.setSeconds(pastEditedAt.getSeconds() - 30) - + // Set lock duration to 1 second for testing purposes + const globalConfig = payload.config.globals.find(({ slug }) => slug === menuSlug) + globalConfig.lockDocuments = { duration: 1 } // Give locking ownership to another user const lockedGlobalInstance = await payload.create({ collection: lockedDocumentCollection, data: { - editedAt: pastEditedAt.toISOString(), // stale date user: { relationTo: 'users', value: user2.id, @@ -206,6 +213,8 @@ describe('Locked documents', () => { }, }) + await wait(1100) + const updateGlobalLockedDoc = await payload.updateGlobal({ data: { globalText: 'global text 2', @@ -213,6 +222,7 @@ describe('Locked documents', () => { overrideLock: false, slug: menuSlug, }) + globalConfig.lockDocuments = { duration: 300 } // Should allow update since editedAt date is past expiration duration. // Therefore the document is considered stale @@ -379,16 +389,13 @@ describe('Locked documents', () => { }, }) - // Subtract 3.5 minutes (210 seconds) from the current time - const pastEditedAt = new Date() - pastEditedAt.setMinutes(pastEditedAt.getMinutes() - 3) - pastEditedAt.setSeconds(pastEditedAt.getSeconds() - 30) + // Set lock duration to 1 second for testing purposes + postConfig.lockDocuments = { duration: 1 } // Give locking ownership to another user const lockedDocInstance = await payload.create({ collection: lockedDocumentCollection, data: { - editedAt: pastEditedAt.toISOString(), // stale date user: { relationTo: 'users', value: user2.id, @@ -401,6 +408,8 @@ describe('Locked documents', () => { }, }) + await wait(1100) + await payload.delete({ collection: postsSlug, id: newPost4.id, diff --git a/test/locked-documents/payload-types.ts b/test/locked-documents/payload-types.ts index 44597368e8..229f66aa6d 100644 --- a/test/locked-documents/payload-types.ts +++ b/test/locked-documents/payload-types.ts @@ -111,7 +111,6 @@ export interface PayloadLockedDocument { relationTo: 'users'; value: string | User; }; - editedAt?: string | null; updatedAt: string; createdAt: string; }