Skip to content

Commit

Permalink
fix: properly handles locked documents when read access control exist…
Browse files Browse the repository at this point in the history
…s on users
  • Loading branch information
PatrikKozak committed Oct 30, 2024
1 parent 88c18d2 commit 4f78a89
Show file tree
Hide file tree
Showing 56 changed files with 189 additions and 64 deletions.
8 changes: 6 additions & 2 deletions packages/next/src/elements/DocumentLocked/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { ClientUser } from 'payload'

import { Button, Modal, useModal, useTranslation } from '@payloadcms/ui'
import { isClientUserObject } from '@payloadcms/ui/shared'
import React, { useEffect } from 'react'

import './index.scss'
Expand Down Expand Up @@ -30,7 +31,7 @@ export const DocumentLocked: React.FC<{
onReadOnly: () => void
onTakeOver: () => void
updatedAt?: null | number
user?: ClientUser
user?: ClientUser | number | string
}> = ({ handleGoBack, isActive, onReadOnly, onTakeOver, updatedAt, user }) => {
const { closeModal, openModal } = useModal()
const { t } = useTranslation()
Expand All @@ -49,7 +50,10 @@ export const DocumentLocked: React.FC<{
<div className={`${baseClass}__content`}>
<h1>{t('general:documentLocked')}</h1>
<p>
<strong>{user?.email ?? user?.id}</strong> {t('general:currentlyEditing')}
<strong>
{isClientUserObject(user) ? (user.email ?? user.id) : `${t('general:user')}: ${user}`}
</strong>{' '}
{t('general:currentlyEditing')}
</p>
<p>
{t('general:editedSince')} <strong>{formatDate(updatedAt)}</strong>
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/views/Dashboard/Default/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const baseClass = 'dashboard'

export type DashboardProps = {
globalData: Array<{
data: { _isLocked: boolean; _lastEditedAt: string; _userEditing: ClientUser | null }
data: { _isLocked: boolean; _lastEditedAt: string; _userEditing: ClientUser | number | string }
lockDuration?: number
slug: string
}>
Expand Down
62 changes: 33 additions & 29 deletions packages/next/src/views/Dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@ export const Dashboard: React.FC<AdminViewProps> = async ({
payload,
user,
},
req,
visibleEntities,
} = initPageResult

const lockDurationDefault = 300 // Default 5 minutes in seconds

const CustomDashboardComponent = config.admin.components?.views?.Dashboard

const collections = config.collections.filter(
Expand All @@ -50,39 +49,44 @@ export const Dashboard: React.FC<AdminViewProps> = async ({
visibleEntities.globals.includes(global.slug),
)

const globalConfigs = config.globals.map((global) => ({
slug: global.slug,
lockDuration:
global.lockDocuments === false
? null // Set lockDuration to null if locking is disabled
: typeof global.lockDocuments === 'object'
// Query locked global documents only if there are globals in the config
let globalData = []

if (config.globals.length > 0) {
const lockedDocuments = await payload.find({
collection: 'payload-locked-documents',
depth: 1,
overrideAccess: false,
pagination: false,
req,
where: {
globalSlug: {
exists: true,
},
},
})

// Map over globals to include `lockDuration` and lock data for each global slug
globalData = config.globals.map((global) => {
const lockDurationDefault = 300
const lockDuration =
typeof global.lockDocuments === 'object'
? global.lockDocuments.duration
: lockDurationDefault,
}))

// Filter the slugs based on permissions and visibility
const filteredGlobalConfigs = globalConfigs.filter(
({ slug, lockDuration }) =>
lockDuration !== null && // Ensure lockDuration is valid
permissions?.globals?.[slug]?.read?.permission &&
visibleEntities.globals.includes(slug),
)
: lockDurationDefault

const globalData = await Promise.all(
filteredGlobalConfigs.map(async ({ slug, lockDuration }) => {
const data = await payload.findGlobal({
slug,
depth: 0,
includeLockStatus: true,
})
const lockedDoc = lockedDocuments.docs.find((doc) => doc.globalSlug === global.slug)

return {
slug,
data,
slug: global.slug,
data: {
_isLocked: !!lockedDoc,
_lastEditedAt: lockedDoc?.updatedAt ?? null,
_userEditing: lockedDoc?.user?.value ?? null,
},
lockDuration,
}
}),
)
})
}

const navGroups = groupNavItems(
[
Expand Down
17 changes: 13 additions & 4 deletions packages/next/src/views/Edit/Default/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export const DefaultEditView: React.FC = () => {
const documentLockStateRef = useRef<{
hasShownLockedModal: boolean
isLocked: boolean
user: ClientUser
user: ClientUser | number | string
} | null>({
hasShownLockedModal: false,
isLocked: false,
Expand Down Expand Up @@ -268,7 +268,10 @@ export const DefaultEditView: React.FC = () => {
setDocumentIsLocked(true)

if (isLockingEnabled) {
const previousOwnerId = documentLockStateRef.current?.user?.id
const previousOwnerId =
typeof documentLockStateRef.current?.user === 'object'
? documentLockStateRef.current?.user?.id
: documentLockStateRef.current?.user

if (lockedState) {
if (!documentLockStateRef.current || lockedState.user.id !== previousOwnerId) {
Expand Down Expand Up @@ -328,7 +331,11 @@ export const DefaultEditView: React.FC = () => {
// Unlock the document only if we're actually navigating away from the document
if (documentId && documentIsLocked && !isStayingWithinDocument) {
// Check if this user is still the current editor
if (documentLockStateRef.current?.user?.id === user?.id) {
if (
typeof documentLockStateRef.current?.user === 'object'
? documentLockStateRef.current?.user?.id === user?.id
: documentLockStateRef.current?.user === user?.id
) {
void unlockDocument(id, collectionSlug ?? globalSlug)
setDocumentIsLocked(false)
setCurrentEditor(null)
Expand All @@ -352,7 +359,9 @@ export const DefaultEditView: React.FC = () => {
const shouldShowDocumentLockedModal =
documentIsLocked &&
currentEditor &&
currentEditor.id !== user?.id &&
(typeof currentEditor === 'object'
? currentEditor.id !== user?.id
: currentEditor !== user?.id) &&
!isReadOnlyForIncomingUser &&
!showTakeOverModal &&
!documentLockStateRef.current?.hasShownLockedModal &&
Expand Down
17 changes: 13 additions & 4 deletions packages/next/src/views/LivePreview/index.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ const PreviewView: React.FC<Props> = ({
const documentLockStateRef = useRef<{
hasShownLockedModal: boolean
isLocked: boolean
user: ClientUser
user: ClientUser | number | string
} | null>({
hasShownLockedModal: false,
isLocked: false,
Expand Down Expand Up @@ -208,7 +208,10 @@ const PreviewView: React.FC<Props> = ({
setDocumentIsLocked(true)

if (isLockingEnabled) {
const previousOwnerId = documentLockStateRef.current?.user?.id
const previousOwnerId =
typeof documentLockStateRef.current?.user === 'object'
? documentLockStateRef.current?.user?.id
: documentLockStateRef.current?.user

if (lockedState) {
if (!documentLockStateRef.current || lockedState.user.id !== previousOwnerId) {
Expand Down Expand Up @@ -267,7 +270,11 @@ const PreviewView: React.FC<Props> = ({
// Unlock the document only if we're actually navigating away from the document
if (documentId && documentIsLocked && !isStayingWithinDocument) {
// Check if this user is still the current editor
if (documentLockStateRef.current?.user?.id === user?.id) {
if (
typeof documentLockStateRef.current?.user === 'object'
? documentLockStateRef.current?.user?.id === user?.id
: documentLockStateRef.current?.user === user?.id
) {
void unlockDocument(id, collectionSlug ?? globalSlug)
setDocumentIsLocked(false)
setCurrentEditor(null)
Expand All @@ -291,7 +298,9 @@ const PreviewView: React.FC<Props> = ({
const shouldShowDocumentLockedModal =
documentIsLocked &&
currentEditor &&
currentEditor.id !== user.id &&
(typeof currentEditor === 'object'
? currentEditor.id !== user?.id
: currentEditor !== user?.id) &&
!isReadOnlyForIncomingUser &&
!showTakeOverModal &&
// eslint-disable-next-line react-compiler/react-compiler
Expand Down
1 change: 1 addition & 0 deletions packages/payload/src/collections/operations/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export const findOperation = async <TSlug extends CollectionSlug>(
collection: 'payload-locked-documents',
depth: 1,
limit: sanitizedLimit,
overrideAccess: false,
pagination: false,
req,
where: {
Expand Down
1 change: 1 addition & 0 deletions packages/payload/src/collections/operations/findByID.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export const findByIDOperation = async <TSlug extends CollectionSlug>(
collection: 'payload-locked-documents',
depth: 1,
limit: 1,
overrideAccess: false,
pagination: false,
req,
where: {
Expand Down
25 changes: 20 additions & 5 deletions packages/payload/src/globals/operations/findOne.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,37 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
// /////////////////////////////////////
// Include Lock Status if required
// /////////////////////////////////////

if (includeLockStatus && slug) {
let lockStatus = null

try {
const lockDocumentsProp = globalConfig?.lockDocuments

const lockDurationDefault = 300 // Default 5 minutes in seconds
const lockDuration =
typeof lockDocumentsProp === 'object' ? lockDocumentsProp.duration : lockDurationDefault
const lockDurationInMilliseconds = lockDuration * 1000

const lockedDocument = await req.payload.find({
collection: 'payload-locked-documents',
depth: 1,
limit: 1,
overrideAccess: false,
pagination: false,
req,
where: {
globalSlug: {
equals: slug,
},
and: [
{
globalSlug: {
equals: slug,
},
},
{
updatedAt: {
greater_than: new Date(new Date().getTime() - lockDurationInMilliseconds),
},
},
],
},
})

Expand All @@ -87,7 +103,6 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
}

doc._isLocked = !!lockStatus
doc._lastEditedAt = lockStatus?.updatedAt ?? null
doc._userEditing = lockStatus?.user?.value ?? null
}

Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/clientKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export const clientTranslationKeys = createClientTranslationKeys([
'general:addFilter',
'general:adminTheme',
'general:and',
'general:anotherUser',
'general:anotherUserTakenOver',
'general:applyChanges',
'general:ascending',
Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/languages/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export const arTranslations: DefaultTranslationsObject = {
addFilter: 'أضف فلتر',
adminTheme: 'شكل واجهة المستخدم',
and: 'و',
anotherUser: 'مستخدم آخر',
anotherUserTakenOver: 'قام مستخدم آخر بالاستيلاء على تحرير هذا المستند.',
applyChanges: 'طبق التغييرات',
ascending: 'تصاعدي',
Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/languages/az.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export const azTranslations: DefaultTranslationsObject = {
addFilter: 'Filter əlavə et',
adminTheme: 'Admin Mövzusu',
and: 'Və',
anotherUser: 'Başqa bir istifadəçi',
anotherUserTakenOver: 'Başqa bir istifadəçi bu sənədin redaktəsini ələ keçirdi.',
applyChanges: 'Dəyişiklikləri Tətbiq Edin',
ascending: 'Artan',
Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/languages/bg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export const bgTranslations: DefaultTranslationsObject = {
addFilter: 'Добави филтър',
adminTheme: 'Цветова тема',
and: 'И',
anotherUser: 'Друг потребител',
anotherUserTakenOver: 'Друг потребител пое редактирането на този документ.',
applyChanges: 'Приложи промените',
ascending: 'Възходящ',
Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/languages/cs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export const csTranslations: DefaultTranslationsObject = {
addFilter: 'Přidat filtr',
adminTheme: 'Motiv administračního rozhraní',
and: 'a',
anotherUser: 'Jiný uživatel',
anotherUserTakenOver: 'Jiný uživatel převzal úpravy tohoto dokumentu.',
applyChanges: 'Použít změny',
ascending: 'Vzestupně',
Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/languages/da.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export const daTranslations: DefaultTranslationsObject = {
addFilter: 'Tilføj filter',
adminTheme: 'Admin tema',
and: 'Og',
anotherUser: 'En anden bruger',
anotherUserTakenOver: 'En anden bruger har overtaget denne ressource.',
applyChanges: 'Tilføj ændringer',
ascending: 'Stigende',
Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export const deTranslations: DefaultTranslationsObject = {
addFilter: 'Filter hinzufügen',
adminTheme: 'Admin-Farbthema',
and: 'Und',
anotherUser: 'Ein anderer Benutzer',
anotherUserTakenOver: 'Ein anderer Benutzer hat die Bearbeitung dieses Dokuments übernommen.',
applyChanges: 'Änderungen anwenden',
ascending: 'Aufsteigend',
Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export const enTranslations = {
addFilter: 'Add Filter',
adminTheme: 'Admin Theme',
and: 'And',
anotherUser: 'Another user',
anotherUserTakenOver: 'Another user has taken over editing this document.',
applyChanges: 'Apply Changes',
ascending: 'Ascending',
Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export const esTranslations: DefaultTranslationsObject = {
addFilter: 'Añadir filtro',
adminTheme: 'Tema del admin',
and: 'Y',
anotherUser: 'Otro usuario',
anotherUserTakenOver: 'Otro usuario ha tomado el control de la edición de este documento.',
applyChanges: 'Aplicar Cambios',
ascending: 'Ascendente',
Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/languages/fa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export const faTranslations: DefaultTranslationsObject = {
addFilter: 'افزودن علامت',
adminTheme: 'پوسته پیشخوان',
and: 'و',
anotherUser: 'کاربر دیگر',
anotherUserTakenOver: 'کاربر دیگری ویرایش این سند را به دست گرفته است.',
applyChanges: 'اعمال تغییرات',
ascending: 'صعودی',
Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ export const frTranslations: DefaultTranslationsObject = {
addFilter: 'Ajouter un filtre',
adminTheme: 'Thème d’administration',
and: 'Et',
anotherUser: 'Un autre utilisateur',
anotherUserTakenOver: 'Un autre utilisateur a pris en charge la modification de ce document.',
applyChanges: 'Appliquer les modifications',
ascending: 'Ascendant',
Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/languages/he.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export const heTranslations: DefaultTranslationsObject = {
addFilter: 'הוסף מסנן',
adminTheme: 'ערכת נושא ממשק הניהול',
and: 'וגם',
anotherUser: 'משתמש אחר',
anotherUserTakenOver: 'משתמש אחר השתלט על עריכת מסמך זה.',
applyChanges: 'החל שינויים',
ascending: 'בסדר עולה',
Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/languages/hr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export const hrTranslations: DefaultTranslationsObject = {
addFilter: 'Dodaj filter',
adminTheme: 'Administratorska tema',
and: 'i',
anotherUser: 'Drugi korisnik',
anotherUserTakenOver: 'Drugi korisnik je preuzeo uređivanje ovog dokumenta.',
applyChanges: 'Primijeni promjene',
ascending: 'Uzlazno',
Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/languages/hu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ export const huTranslations: DefaultTranslationsObject = {
addFilter: 'Szűrő hozzáadása',
adminTheme: 'Admin téma',
and: 'És',
anotherUser: 'Egy másik felhasználó',
anotherUserTakenOver: 'Egy másik felhasználó átvette ennek a dokumentumnak a szerkesztését.',
applyChanges: 'Változtatások alkalmazása',
ascending: 'Növekvő',
Expand Down
1 change: 1 addition & 0 deletions packages/translations/src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export const itTranslations: DefaultTranslationsObject = {
addFilter: 'Aggiungi Filtro',
adminTheme: 'Tema Admin',
and: 'E',
anotherUser: 'Un altro utente',
anotherUserTakenOver:
'Un altro utente ha preso il controllo della modifica di questo documento.',
applyChanges: 'Applica modifiche',
Expand Down
Loading

0 comments on commit 4f78a89

Please sign in to comment.