fix(next,ui): fixes global doc permissions and optimizes publish access data loading (#6451)
This commit is contained in:
@@ -6,15 +6,29 @@ import type { BaseRouteHandler } from '../types.js'
|
|||||||
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
import { headersWithCors } from '../../../utilities/headersWithCors.js'
|
||||||
|
|
||||||
export const access: BaseRouteHandler = async ({ req }) => {
|
export const access: BaseRouteHandler = async ({ req }) => {
|
||||||
|
const headers = headersWithCors({
|
||||||
|
headers: new Headers(),
|
||||||
|
req,
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
const results = await accessOperation({
|
const results = await accessOperation({
|
||||||
req,
|
req,
|
||||||
})
|
})
|
||||||
|
|
||||||
return Response.json(results, {
|
return Response.json(results, {
|
||||||
headers: headersWithCors({
|
headers,
|
||||||
headers: new Headers(),
|
|
||||||
req,
|
|
||||||
}),
|
|
||||||
status: httpStatus.OK,
|
status: httpStatus.OK,
|
||||||
})
|
})
|
||||||
|
} catch (e: unknown) {
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
error: e,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers,
|
||||||
|
status: httpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,21 @@ import { FormQueryParamsProvider } from '@payloadcms/ui/providers/FormQueryParam
|
|||||||
import { notFound } from 'next/navigation.js'
|
import { notFound } from 'next/navigation.js'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
|
import { getDocumentPermissions } from '../Document/getDocumentPermissions.js'
|
||||||
import { EditView } from '../Edit/index.js'
|
import { EditView } from '../Edit/index.js'
|
||||||
import { Settings } from './Settings/index.js'
|
import { Settings } from './Settings/index.js'
|
||||||
|
|
||||||
export { generateAccountMetadata } from './meta.js'
|
export { generateAccountMetadata } from './meta.js'
|
||||||
|
|
||||||
export const Account: React.FC<AdminViewProps> = ({ initPageResult, params, searchParams }) => {
|
export const Account: React.FC<AdminViewProps> = async ({
|
||||||
|
initPageResult,
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}) => {
|
||||||
const {
|
const {
|
||||||
locale,
|
locale,
|
||||||
permissions,
|
permissions,
|
||||||
|
req,
|
||||||
req: {
|
req: {
|
||||||
i18n,
|
i18n,
|
||||||
payload,
|
payload,
|
||||||
@@ -32,11 +38,17 @@ export const Account: React.FC<AdminViewProps> = ({ initPageResult, params, sear
|
|||||||
serverURL,
|
serverURL,
|
||||||
} = config
|
} = config
|
||||||
|
|
||||||
const collectionPermissions = permissions?.collections?.[userSlug]
|
|
||||||
|
|
||||||
const collectionConfig = config.collections.find((collection) => collection.slug === userSlug)
|
const collectionConfig = config.collections.find((collection) => collection.slug === userSlug)
|
||||||
|
|
||||||
if (collectionConfig) {
|
if (collectionConfig) {
|
||||||
|
const { docPermissions, hasPublishPermission, hasSavePermission } =
|
||||||
|
await getDocumentPermissions({
|
||||||
|
id: user.id,
|
||||||
|
collectionConfig,
|
||||||
|
data: user,
|
||||||
|
req,
|
||||||
|
})
|
||||||
|
|
||||||
const viewComponentProps: ServerSideEditViewProps = {
|
const viewComponentProps: ServerSideEditViewProps = {
|
||||||
initPageResult,
|
initPageResult,
|
||||||
params,
|
params,
|
||||||
@@ -50,9 +62,10 @@ export const Account: React.FC<AdminViewProps> = ({ initPageResult, params, sear
|
|||||||
action={`${serverURL}${api}/${userSlug}${user?.id ? `/${user.id}` : ''}`}
|
action={`${serverURL}${api}/${userSlug}${user?.id ? `/${user.id}` : ''}`}
|
||||||
apiURL={`${serverURL}${api}/${userSlug}${user?.id ? `/${user.id}` : ''}`}
|
apiURL={`${serverURL}${api}/${userSlug}${user?.id ? `/${user.id}` : ''}`}
|
||||||
collectionSlug={userSlug}
|
collectionSlug={userSlug}
|
||||||
docPermissions={collectionPermissions}
|
docPermissions={docPermissions}
|
||||||
hasSavePermission={collectionPermissions?.update?.permission}
|
hasPublishPermission={hasPublishPermission}
|
||||||
id={user?.id}
|
hasSavePermission={hasSavePermission}
|
||||||
|
id={user?.id.toString()}
|
||||||
isEditing
|
isEditing
|
||||||
>
|
>
|
||||||
<DocumentHeader
|
<DocumentHeader
|
||||||
|
|||||||
41
packages/next/src/views/Document/getDocumentData.tsx
Normal file
41
packages/next/src/views/Document/getDocumentData.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import type {
|
||||||
|
Data,
|
||||||
|
Payload,
|
||||||
|
PayloadRequest,
|
||||||
|
SanitizedCollectionConfig,
|
||||||
|
SanitizedGlobalConfig,
|
||||||
|
} from 'payload/types'
|
||||||
|
|
||||||
|
export const getDocumentData = async (args: {
|
||||||
|
collectionConfig?: SanitizedCollectionConfig
|
||||||
|
globalConfig?: SanitizedGlobalConfig
|
||||||
|
id?: number | string
|
||||||
|
locale: Locale
|
||||||
|
payload: Payload
|
||||||
|
req: PayloadRequest
|
||||||
|
}): Promise<Data> => {
|
||||||
|
const { id, collectionConfig, globalConfig, locale, payload, req } = args
|
||||||
|
|
||||||
|
let data: Data
|
||||||
|
|
||||||
|
if (collectionConfig && id !== undefined && id !== null) {
|
||||||
|
data = await payload.findByID({
|
||||||
|
id,
|
||||||
|
collection: collectionConfig.slug,
|
||||||
|
depth: 0,
|
||||||
|
locale: locale.code,
|
||||||
|
req,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalConfig) {
|
||||||
|
data = await payload.findGlobal({
|
||||||
|
slug: globalConfig.slug,
|
||||||
|
depth: 0,
|
||||||
|
locale: locale.code,
|
||||||
|
req,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
105
packages/next/src/views/Document/getDocumentPermissions.tsx
Normal file
105
packages/next/src/views/Document/getDocumentPermissions.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import type { DocumentPermissions } from 'payload/auth'
|
||||||
|
import type {
|
||||||
|
Data,
|
||||||
|
PayloadRequest,
|
||||||
|
SanitizedCollectionConfig,
|
||||||
|
SanitizedGlobalConfig,
|
||||||
|
} from 'payload/types'
|
||||||
|
|
||||||
|
import { hasSavePermission as getHasSavePermission } from '@payloadcms/ui/utilities/hasSavePermission'
|
||||||
|
import { isEditing as getIsEditing } from '@payloadcms/ui/utilities/isEditing'
|
||||||
|
import { docAccessOperation, docAccessOperationGlobal } from 'payload/operations'
|
||||||
|
|
||||||
|
export const getDocumentPermissions = async (args: {
|
||||||
|
collectionConfig?: SanitizedCollectionConfig
|
||||||
|
data: Data
|
||||||
|
globalConfig?: SanitizedGlobalConfig
|
||||||
|
id?: number | string
|
||||||
|
req: PayloadRequest
|
||||||
|
}): Promise<{
|
||||||
|
docPermissions: DocumentPermissions
|
||||||
|
hasPublishPermission: boolean
|
||||||
|
hasSavePermission: boolean
|
||||||
|
}> => {
|
||||||
|
const { id, collectionConfig, data = {}, globalConfig, req } = args
|
||||||
|
|
||||||
|
let docPermissions: DocumentPermissions
|
||||||
|
let hasPublishPermission = false
|
||||||
|
|
||||||
|
if (collectionConfig) {
|
||||||
|
try {
|
||||||
|
docPermissions = await docAccessOperation({
|
||||||
|
id: id?.toString(),
|
||||||
|
collection: {
|
||||||
|
config: collectionConfig,
|
||||||
|
},
|
||||||
|
req: {
|
||||||
|
...req,
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (collectionConfig.versions?.drafts) {
|
||||||
|
hasPublishPermission = await docAccessOperation({
|
||||||
|
id: id?.toString(),
|
||||||
|
collection: {
|
||||||
|
config: collectionConfig,
|
||||||
|
},
|
||||||
|
req: {
|
||||||
|
...req,
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
_status: 'published',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).then(({ update }) => update?.permission)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error) // eslint-disable-line no-console
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalConfig) {
|
||||||
|
try {
|
||||||
|
docPermissions = await docAccessOperationGlobal({
|
||||||
|
globalConfig,
|
||||||
|
req: {
|
||||||
|
...req,
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (globalConfig.versions?.drafts) {
|
||||||
|
hasPublishPermission = await docAccessOperationGlobal({
|
||||||
|
globalConfig,
|
||||||
|
req: {
|
||||||
|
...req,
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
_status: 'published',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).then(({ update }) => update?.permission)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error) // eslint-disable-line no-console
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSavePermission = getHasSavePermission({
|
||||||
|
collectionSlug: collectionConfig?.slug,
|
||||||
|
docPermissions,
|
||||||
|
globalSlug: globalConfig?.slug,
|
||||||
|
isEditing: getIsEditing({
|
||||||
|
id,
|
||||||
|
collectionSlug: collectionConfig?.slug,
|
||||||
|
globalSlug: globalConfig?.slug,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
docPermissions,
|
||||||
|
hasPublishPermission,
|
||||||
|
hasSavePermission,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { EditViewComponent } from 'payload/config'
|
import type { EditViewComponent } from 'payload/config'
|
||||||
import type { AdminViewComponent, ServerSideEditViewProps } from 'payload/types'
|
import type { AdminViewComponent, ServerSideEditViewProps } from 'payload/types'
|
||||||
import type { DocumentPermissions } from 'payload/types'
|
|
||||||
import type { AdminViewProps } from 'payload/types'
|
import type { AdminViewProps } from 'payload/types'
|
||||||
|
|
||||||
import { DocumentHeader } from '@payloadcms/ui/elements/DocumentHeader'
|
import { DocumentHeader } from '@payloadcms/ui/elements/DocumentHeader'
|
||||||
@@ -9,15 +8,15 @@ import { RenderCustomComponent } from '@payloadcms/ui/elements/RenderCustomCompo
|
|||||||
import { DocumentInfoProvider } from '@payloadcms/ui/providers/DocumentInfo'
|
import { DocumentInfoProvider } from '@payloadcms/ui/providers/DocumentInfo'
|
||||||
import { EditDepthProvider } from '@payloadcms/ui/providers/EditDepth'
|
import { EditDepthProvider } from '@payloadcms/ui/providers/EditDepth'
|
||||||
import { FormQueryParamsProvider } from '@payloadcms/ui/providers/FormQueryParams'
|
import { FormQueryParamsProvider } from '@payloadcms/ui/providers/FormQueryParams'
|
||||||
import { hasSavePermission as getHasSavePermission } from '@payloadcms/ui/utilities/hasSavePermission'
|
|
||||||
import { isEditing as getIsEditing } from '@payloadcms/ui/utilities/isEditing'
|
import { isEditing as getIsEditing } from '@payloadcms/ui/utilities/isEditing'
|
||||||
import { notFound, redirect } from 'next/navigation.js'
|
import { notFound, redirect } from 'next/navigation.js'
|
||||||
import { docAccessOperation } from 'payload/operations'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import type { GenerateEditViewMetadata } from './getMetaBySegment.js'
|
import type { GenerateEditViewMetadata } from './getMetaBySegment.js'
|
||||||
|
|
||||||
import { NotFoundView } from '../NotFound/index.js'
|
import { NotFoundView } from '../NotFound/index.js'
|
||||||
|
import { getDocumentData } from './getDocumentData.js'
|
||||||
|
import { getDocumentPermissions } from './getDocumentPermissions.js'
|
||||||
import { getMetaBySegment } from './getMetaBySegment.js'
|
import { getMetaBySegment } from './getMetaBySegment.js'
|
||||||
import { getViewsFromConfig } from './getViewsFromConfig.js'
|
import { getViewsFromConfig } from './getViewsFromConfig.js'
|
||||||
|
|
||||||
@@ -61,32 +60,33 @@ export const Document: React.FC<AdminViewProps> = async ({
|
|||||||
let DefaultView: EditViewComponent
|
let DefaultView: EditViewComponent
|
||||||
let ErrorView: AdminViewComponent
|
let ErrorView: AdminViewComponent
|
||||||
|
|
||||||
let docPermissions: DocumentPermissions
|
|
||||||
let hasSavePermission: boolean
|
|
||||||
let apiURL: string
|
let apiURL: string
|
||||||
let action: string
|
let action: string
|
||||||
|
|
||||||
|
const data = await getDocumentData({
|
||||||
|
id,
|
||||||
|
collectionConfig,
|
||||||
|
globalConfig,
|
||||||
|
locale,
|
||||||
|
payload,
|
||||||
|
req,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { docPermissions, hasPublishPermission, hasSavePermission } = await getDocumentPermissions({
|
||||||
|
id,
|
||||||
|
collectionConfig,
|
||||||
|
data,
|
||||||
|
globalConfig,
|
||||||
|
req,
|
||||||
|
})
|
||||||
|
|
||||||
if (collectionConfig) {
|
if (collectionConfig) {
|
||||||
if (!visibleEntities?.collections?.find((visibleSlug) => visibleSlug === collectionSlug)) {
|
if (!visibleEntities?.collections?.find((visibleSlug) => visibleSlug === collectionSlug)) {
|
||||||
notFound()
|
notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
docPermissions = await docAccessOperation({
|
|
||||||
id,
|
|
||||||
collection: {
|
|
||||||
config: collectionConfig,
|
|
||||||
},
|
|
||||||
req,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
notFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
action = `${serverURL}${apiRoute}/${collectionSlug}${isEditing ? `/${id}` : ''}`
|
action = `${serverURL}${apiRoute}/${collectionSlug}${isEditing ? `/${id}` : ''}`
|
||||||
|
|
||||||
hasSavePermission = getHasSavePermission({ collectionSlug, docPermissions, isEditing })
|
|
||||||
|
|
||||||
apiURL = `${serverURL}${apiRoute}/${collectionSlug}/${id}?locale=${locale.code}${
|
apiURL = `${serverURL}${apiRoute}/${collectionSlug}/${id}?locale=${locale.code}${
|
||||||
collectionConfig.versions?.drafts ? '&draft=true' : ''
|
collectionConfig.versions?.drafts ? '&draft=true' : ''
|
||||||
}`
|
}`
|
||||||
@@ -117,9 +117,6 @@ export const Document: React.FC<AdminViewProps> = async ({
|
|||||||
notFound()
|
notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
docPermissions = permissions?.globals?.[globalSlug]
|
|
||||||
hasSavePermission = getHasSavePermission({ docPermissions, globalSlug, isEditing })
|
|
||||||
|
|
||||||
action = `${serverURL}${apiRoute}/globals/${globalSlug}`
|
action = `${serverURL}${apiRoute}/globals/${globalSlug}`
|
||||||
|
|
||||||
apiURL = `${serverURL}${apiRoute}/${globalSlug}?locale=${locale.code}${
|
apiURL = `${serverURL}${apiRoute}/${globalSlug}?locale=${locale.code}${
|
||||||
@@ -191,6 +188,7 @@ export const Document: React.FC<AdminViewProps> = async ({
|
|||||||
disableActions={false}
|
disableActions={false}
|
||||||
docPermissions={docPermissions}
|
docPermissions={docPermissions}
|
||||||
globalSlug={globalConfig?.slug}
|
globalSlug={globalConfig?.slug}
|
||||||
|
hasPublishPermission={hasPublishPermission}
|
||||||
hasSavePermission={hasSavePermission}
|
hasSavePermission={hasSavePermission}
|
||||||
id={id}
|
id={id}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ export const DefaultEditView: React.FC = () => {
|
|||||||
disableActions,
|
disableActions,
|
||||||
disableLeaveWithoutSaving,
|
disableLeaveWithoutSaving,
|
||||||
docPermissions,
|
docPermissions,
|
||||||
getDocPermissions,
|
|
||||||
getDocPreferences,
|
getDocPreferences,
|
||||||
getVersions,
|
getVersions,
|
||||||
globalSlug,
|
globalSlug,
|
||||||
|
hasPublishPermission,
|
||||||
hasSavePermission,
|
hasSavePermission,
|
||||||
initialData: data,
|
initialData: data,
|
||||||
initialState,
|
initialState,
|
||||||
@@ -115,7 +115,6 @@ export const DefaultEditView: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void getVersions()
|
void getVersions()
|
||||||
void getDocPermissions()
|
|
||||||
|
|
||||||
if (typeof onSaveFromContext === 'function') {
|
if (typeof onSaveFromContext === 'function') {
|
||||||
void onSaveFromContext({
|
void onSaveFromContext({
|
||||||
@@ -147,7 +146,6 @@ export const DefaultEditView: React.FC = () => {
|
|||||||
depth,
|
depth,
|
||||||
collectionSlug,
|
collectionSlug,
|
||||||
getVersions,
|
getVersions,
|
||||||
getDocPermissions,
|
|
||||||
isEditing,
|
isEditing,
|
||||||
refreshCookieAsync,
|
refreshCookieAsync,
|
||||||
adminRoute,
|
adminRoute,
|
||||||
@@ -221,6 +219,7 @@ export const DefaultEditView: React.FC = () => {
|
|||||||
apiURL={apiURL}
|
apiURL={apiURL}
|
||||||
data={data}
|
data={data}
|
||||||
disableActions={disableActions}
|
disableActions={disableActions}
|
||||||
|
hasPublishPermission={hasPublishPermission}
|
||||||
hasSavePermission={hasSavePermission}
|
hasSavePermission={hasSavePermission}
|
||||||
id={id}
|
id={id}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ const PreviewView: React.FC<Props> = ({
|
|||||||
docPermissions,
|
docPermissions,
|
||||||
getDocPreferences,
|
getDocPreferences,
|
||||||
globalSlug,
|
globalSlug,
|
||||||
|
hasPublishPermission,
|
||||||
hasSavePermission,
|
hasSavePermission,
|
||||||
initialData,
|
initialData,
|
||||||
initialState,
|
initialState,
|
||||||
@@ -160,6 +161,7 @@ const PreviewView: React.FC<Props> = ({
|
|||||||
apiURL={apiURL}
|
apiURL={apiURL}
|
||||||
data={initialData}
|
data={initialData}
|
||||||
disableActions={disableActions}
|
disableActions={disableActions}
|
||||||
|
hasPublishPermission={hasPublishPermission}
|
||||||
hasSavePermission={hasSavePermission}
|
hasSavePermission={hasSavePermission}
|
||||||
id={id}
|
id={id}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export const DocumentControls: React.FC<{
|
|||||||
apiURL: string
|
apiURL: string
|
||||||
data?: any
|
data?: any
|
||||||
disableActions?: boolean
|
disableActions?: boolean
|
||||||
|
hasPublishPermission?: boolean
|
||||||
hasSavePermission?: boolean
|
hasSavePermission?: boolean
|
||||||
id?: number | string
|
id?: number | string
|
||||||
isAccountView?: boolean
|
isAccountView?: boolean
|
||||||
|
|||||||
@@ -111,12 +111,6 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
|
|||||||
collectionSlug={collectionConfig.slug}
|
collectionSlug={collectionConfig.slug}
|
||||||
disableActions
|
disableActions
|
||||||
disableLeaveWithoutSaving
|
disableLeaveWithoutSaving
|
||||||
// Do NOT pass in the docPermissions we have here. This is because the permissions we have here do not have their where: { } returns resolved.
|
|
||||||
// If we set it to null though, the DocumentInfoProvider will fully-fetch the permissions from the server, and the where: { } returns will be resolved.
|
|
||||||
docPermissions={null}
|
|
||||||
// Same reason as above. We need to fully-fetch the docPreferences from the server. This is done in DocumentInfoProvider if we set it to null here.
|
|
||||||
hasSavePermission={null}
|
|
||||||
// isLoading,
|
|
||||||
id={docID}
|
id={docID}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
onLoadError={onLoadError}
|
onLoadError={onLoadError}
|
||||||
|
|||||||
@@ -1,26 +1,18 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import qs from 'qs'
|
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
|
|
||||||
import { useForm, useFormModified } from '../../forms/Form/context.js'
|
import { useForm, useFormModified } from '../../forms/Form/context.js'
|
||||||
import { FormSubmit } from '../../forms/Submit/index.js'
|
import { FormSubmit } from '../../forms/Submit/index.js'
|
||||||
import { useConfig } from '../../providers/Config/index.js'
|
|
||||||
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
|
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
|
||||||
import { useLocale } from '../../providers/Locale/index.js'
|
|
||||||
import { useTranslation } from '../../providers/Translation/index.js'
|
import { useTranslation } from '../../providers/Translation/index.js'
|
||||||
|
|
||||||
export const DefaultPublishButton: React.FC<{ label?: string }> = ({ label: labelProp }) => {
|
export const DefaultPublishButton: React.FC<{ label?: string }> = ({ label: labelProp }) => {
|
||||||
const { code } = useLocale()
|
const { hasPublishPermission, publishedDoc, unpublishedVersions } = useDocumentInfo()
|
||||||
const { id, collectionSlug, globalSlug, publishedDoc, unpublishedVersions } = useDocumentInfo()
|
|
||||||
const [hasPublishPermission, setHasPublishPermission] = React.useState(false)
|
const { submit } = useForm()
|
||||||
const { getData, submit } = useForm()
|
|
||||||
const modified = useFormModified()
|
const modified = useFormModified()
|
||||||
|
|
||||||
const {
|
|
||||||
routes: { api },
|
|
||||||
serverURL,
|
|
||||||
} = useConfig()
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const label = labelProp || t('version:publishChanges')
|
const label = labelProp || t('version:publishChanges')
|
||||||
|
|
||||||
@@ -35,46 +27,6 @@ export const DefaultPublishButton: React.FC<{ label?: string }> = ({ label: labe
|
|||||||
})
|
})
|
||||||
}, [submit])
|
}, [submit])
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const fetchPublishAccess = async () => {
|
|
||||||
let docAccessURL: string
|
|
||||||
let operation = 'update'
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
locale: code || undefined,
|
|
||||||
}
|
|
||||||
if (globalSlug) {
|
|
||||||
docAccessURL = `/globals/${globalSlug}/access`
|
|
||||||
} else if (collectionSlug) {
|
|
||||||
if (!id) operation = 'create'
|
|
||||||
docAccessURL = `/${collectionSlug}/access${id ? `/${id}` : ''}`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (docAccessURL) {
|
|
||||||
const data = getData()
|
|
||||||
|
|
||||||
const res = await fetch(`${serverURL}${api}${docAccessURL}?${qs.stringify(params)}`, {
|
|
||||||
body: JSON.stringify({
|
|
||||||
...data,
|
|
||||||
_status: 'published',
|
|
||||||
}),
|
|
||||||
credentials: 'include',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
method: 'post',
|
|
||||||
})
|
|
||||||
const json = await res.json()
|
|
||||||
const result = Boolean(json?.[operation]?.permission)
|
|
||||||
setHasPublishPermission(result)
|
|
||||||
} else {
|
|
||||||
setHasPublishPermission(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void fetchPublishAccess()
|
|
||||||
}, [api, code, collectionSlug, getData, globalSlug, id, serverURL])
|
|
||||||
|
|
||||||
if (!hasPublishPermission) return null
|
if (!hasPublishPermission) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -96,6 +48,5 @@ type Props = {
|
|||||||
|
|
||||||
export const PublishButton: React.FC<Props> = ({ CustomComponent }) => {
|
export const PublishButton: React.FC<Props> = ({ CustomComponent }) => {
|
||||||
if (CustomComponent) return CustomComponent
|
if (CustomComponent) return CustomComponent
|
||||||
|
|
||||||
return <DefaultPublishButton />
|
return <DefaultPublishButton />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -488,6 +488,10 @@ export const Form: React.FC<FormProps> = (props) => {
|
|||||||
[getFieldStateBySchemaPath, dispatchFields],
|
[getFieldStateBySchemaPath, dispatchFields],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof disabledFromProps === 'boolean') setDisabled(disabledFromProps)
|
||||||
|
}, [disabledFromProps])
|
||||||
|
|
||||||
contextRef.current.submit = submit
|
contextRef.current.submit = submit
|
||||||
contextRef.current.getFields = getFields
|
contextRef.current.getFields = getFields
|
||||||
contextRef.current.getField = getField
|
contextRef.current.getField = getField
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { DocumentPermissions, DocumentPreferences, TypeWithID, Where } from
|
|||||||
|
|
||||||
import { notFound } from 'next/navigation.js'
|
import { notFound } from 'next/navigation.js'
|
||||||
import qs from 'qs'
|
import qs from 'qs'
|
||||||
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
import type { DocumentInfoContext, DocumentInfoProps } from './types.js'
|
import type { DocumentInfoContext, DocumentInfoProps } from './types.js'
|
||||||
|
|
||||||
@@ -32,16 +32,28 @@ export const DocumentInfoProvider: React.FC<
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
> = ({ children, ...props }) => {
|
> = ({ children, ...props }) => {
|
||||||
const { id, collectionSlug, globalSlug, onLoadError, onSave: onSaveFromProps } = props
|
const {
|
||||||
|
id,
|
||||||
|
collectionSlug,
|
||||||
|
docPermissions: docPermissionsFromProps,
|
||||||
|
globalSlug,
|
||||||
|
hasPublishPermission: hasPublishPermissionFromProps,
|
||||||
|
hasSavePermission: hasSavePermissionFromProps,
|
||||||
|
onLoadError,
|
||||||
|
onSave: onSaveFromProps,
|
||||||
|
} = props
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [isError, setIsError] = useState(false)
|
const [isError, setIsError] = useState(false)
|
||||||
const [documentTitle, setDocumentTitle] = useState('')
|
const [documentTitle, setDocumentTitle] = useState('')
|
||||||
const [initialData, setInitialData] = useState<Data>()
|
const [data, setData] = useState<Data>()
|
||||||
const [initialState, setInitialState] = useState<FormState>()
|
const [initialState, setInitialState] = useState<FormState>()
|
||||||
const [publishedDoc, setPublishedDoc] = useState<TypeWithID & TypeWithTimestamps>(null)
|
const [publishedDoc, setPublishedDoc] = useState<TypeWithID & TypeWithTimestamps>(null)
|
||||||
const [versions, setVersions] = useState<PaginatedDocs<TypeWithVersion<any>>>(null)
|
const [versions, setVersions] = useState<PaginatedDocs<TypeWithVersion<any>>>(null)
|
||||||
const [docPermissions, setDocPermissions] = useState<DocumentPermissions>(null)
|
const [docPermissions, setDocPermissions] = useState<DocumentPermissions>(null)
|
||||||
const [hasSavePermission, setHasSavePermission] = useState<boolean>(null)
|
const [hasSavePermission, setHasSavePermission] = useState<boolean>(null)
|
||||||
|
const [hasPublishPermission, setHasPublishPermission] = useState<boolean>(null)
|
||||||
|
const hasInitializedDocPermissions = useRef(false)
|
||||||
const [unpublishedVersions, setUnpublishedVersions] =
|
const [unpublishedVersions, setUnpublishedVersions] =
|
||||||
useState<PaginatedDocs<TypeWithVersion<any>>>(null)
|
useState<PaginatedDocs<TypeWithVersion<any>>>(null)
|
||||||
|
|
||||||
@@ -211,14 +223,17 @@ export const DocumentInfoProvider: React.FC<
|
|||||||
}
|
}
|
||||||
}, [i18n, globalSlug, collectionSlug, id, baseURL, locale, versionsConfig, shouldFetchVersions])
|
}, [i18n, globalSlug, collectionSlug, id, baseURL, locale, versionsConfig, shouldFetchVersions])
|
||||||
|
|
||||||
const getDocPermissions = React.useCallback(async () => {
|
const getDocPermissions = React.useCallback(
|
||||||
|
async (data: Data) => {
|
||||||
const params = {
|
const params = {
|
||||||
locale: locale || undefined,
|
locale: locale || undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEditing) {
|
const newIsEditing = getIsEditing({ id: data?.id, collectionSlug, globalSlug })
|
||||||
|
|
||||||
|
if (newIsEditing) {
|
||||||
const docAccessURL = collectionSlug
|
const docAccessURL = collectionSlug
|
||||||
? `/${collectionSlug}/access/${id}`
|
? `/${collectionSlug}/access/${data.id}`
|
||||||
: globalSlug
|
: globalSlug
|
||||||
? `/globals/${globalSlug}/access`
|
? `/globals/${globalSlug}/access`
|
||||||
: null
|
: null
|
||||||
@@ -232,12 +247,35 @@ export const DocumentInfoProvider: React.FC<
|
|||||||
})
|
})
|
||||||
|
|
||||||
const json: DocumentPermissions = await res.json()
|
const json: DocumentPermissions = await res.json()
|
||||||
|
const publishedAccessJSON = await fetch(
|
||||||
|
`${serverURL}${api}${docAccessURL}?${qs.stringify(params)}`,
|
||||||
|
{
|
||||||
|
body: JSON.stringify({
|
||||||
|
data: {
|
||||||
|
...(data || {}),
|
||||||
|
_status: 'published',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Accept-Language': i18n.language,
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
},
|
||||||
|
).then((res) => res.json())
|
||||||
|
|
||||||
setDocPermissions(json)
|
setDocPermissions(json)
|
||||||
|
|
||||||
setHasSavePermission(
|
setHasSavePermission(
|
||||||
getHasSavePermission({ collectionSlug, docPermissions: json, globalSlug, isEditing }),
|
getHasSavePermission({
|
||||||
|
collectionSlug,
|
||||||
|
docPermissions: json,
|
||||||
|
globalSlug,
|
||||||
|
isEditing: newIsEditing,
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
setHasPublishPermission(publishedAccessJSON?.update?.permission)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// when creating new documents, there is no permissions saved for this document yet
|
// when creating new documents, there is no permissions saved for this document yet
|
||||||
@@ -253,21 +291,13 @@ export const DocumentInfoProvider: React.FC<
|
|||||||
collectionSlug,
|
collectionSlug,
|
||||||
docPermissions: newDocPermissions,
|
docPermissions: newDocPermissions,
|
||||||
globalSlug,
|
globalSlug,
|
||||||
isEditing,
|
isEditing: newIsEditing,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, [
|
},
|
||||||
serverURL,
|
[serverURL, api, permissions, i18n.language, locale, collectionSlug, globalSlug, isEditing],
|
||||||
api,
|
)
|
||||||
id,
|
|
||||||
permissions,
|
|
||||||
i18n.language,
|
|
||||||
locale,
|
|
||||||
collectionSlug,
|
|
||||||
globalSlug,
|
|
||||||
isEditing,
|
|
||||||
])
|
|
||||||
|
|
||||||
const getDocPreferences = useCallback(() => {
|
const getDocPreferences = useCallback(() => {
|
||||||
return getPreference<DocumentPreferences>(preferencesKey)
|
return getPreference<DocumentPreferences>(preferencesKey)
|
||||||
@@ -320,8 +350,11 @@ export const DocumentInfoProvider: React.FC<
|
|||||||
serverURL,
|
serverURL,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const newData = json.doc
|
||||||
|
|
||||||
setInitialState(newState)
|
setInitialState(newState)
|
||||||
setInitialData(json.doc)
|
setData(newData)
|
||||||
|
await getDocPermissions(newData)
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
api,
|
api,
|
||||||
@@ -333,6 +366,7 @@ export const DocumentInfoProvider: React.FC<
|
|||||||
locale,
|
locale,
|
||||||
onSaveFromProps,
|
onSaveFromProps,
|
||||||
serverURL,
|
serverURL,
|
||||||
|
getDocPermissions,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -359,7 +393,7 @@ export const DocumentInfoProvider: React.FC<
|
|||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
})
|
})
|
||||||
|
|
||||||
setInitialData(reduceFieldsToValues(result, true))
|
setData(reduceFieldsToValues(result, true))
|
||||||
setInitialState(result)
|
setInitialState(result)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!abortController.signal.aborted) {
|
if (!abortController.signal.aborted) {
|
||||||
@@ -399,38 +433,49 @@ export const DocumentInfoProvider: React.FC<
|
|||||||
setDocumentTitle(
|
setDocumentTitle(
|
||||||
formatDocTitle({
|
formatDocTitle({
|
||||||
collectionConfig,
|
collectionConfig,
|
||||||
data: { ...initialData, id },
|
data: { ...data, id },
|
||||||
dateFormat,
|
dateFormat,
|
||||||
fallback: id?.toString(),
|
fallback: id?.toString(),
|
||||||
globalConfig,
|
globalConfig,
|
||||||
i18n,
|
i18n,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}, [collectionConfig, initialData, dateFormat, i18n, id, globalConfig])
|
}, [collectionConfig, data, dateFormat, i18n, id, globalConfig])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadDocPermissions = async () => {
|
const loadDocPermissions = async () => {
|
||||||
const docPermissions: DocumentPermissions = props.docPermissions
|
const docPermissions: DocumentPermissions = docPermissionsFromProps
|
||||||
const hasSavePermission: boolean = props.hasSavePermission
|
const hasSavePermission: boolean = hasSavePermissionFromProps
|
||||||
|
const hasPublishPermission: boolean = hasPublishPermissionFromProps
|
||||||
|
|
||||||
if (!docPermissions || hasSavePermission === undefined || hasSavePermission === null) {
|
if (
|
||||||
await getDocPermissions()
|
!docPermissions ||
|
||||||
|
hasSavePermission === undefined ||
|
||||||
|
hasSavePermission === null ||
|
||||||
|
hasPublishPermission === undefined ||
|
||||||
|
hasPublishPermission === null
|
||||||
|
) {
|
||||||
|
await getDocPermissions(data)
|
||||||
} else {
|
} else {
|
||||||
setDocPermissions(docPermissions)
|
setDocPermissions(docPermissions)
|
||||||
setHasSavePermission(hasSavePermission)
|
setHasSavePermission(hasSavePermission)
|
||||||
|
setHasPublishPermission(hasPublishPermission)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collectionSlug || globalSlug) {
|
if (!hasInitializedDocPermissions.current && data && (collectionSlug || globalSlug)) {
|
||||||
|
hasInitializedDocPermissions.current = true
|
||||||
void loadDocPermissions()
|
void loadDocPermissions()
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
getDocPermissions,
|
getDocPermissions,
|
||||||
props.docPermissions,
|
docPermissionsFromProps,
|
||||||
props.hasSavePermission,
|
hasSavePermissionFromProps,
|
||||||
|
hasPublishPermissionFromProps,
|
||||||
setDocPermissions,
|
setDocPermissions,
|
||||||
collectionSlug,
|
collectionSlug,
|
||||||
globalSlug,
|
globalSlug,
|
||||||
|
data,
|
||||||
])
|
])
|
||||||
|
|
||||||
if (isError) notFound()
|
if (isError) notFound()
|
||||||
@@ -446,8 +491,9 @@ export const DocumentInfoProvider: React.FC<
|
|||||||
getDocPermissions,
|
getDocPermissions,
|
||||||
getDocPreferences,
|
getDocPreferences,
|
||||||
getVersions,
|
getVersions,
|
||||||
|
hasPublishPermission,
|
||||||
hasSavePermission,
|
hasSavePermission,
|
||||||
initialData,
|
initialData: data,
|
||||||
initialState,
|
initialState,
|
||||||
onSave,
|
onSave,
|
||||||
publishedDoc,
|
publishedDoc,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export type DocumentInfoProps = {
|
|||||||
disableLeaveWithoutSaving?: boolean
|
disableLeaveWithoutSaving?: boolean
|
||||||
docPermissions?: DocumentPermissions
|
docPermissions?: DocumentPermissions
|
||||||
globalSlug?: SanitizedGlobalConfig['slug']
|
globalSlug?: SanitizedGlobalConfig['slug']
|
||||||
|
hasPublishPermission?: boolean
|
||||||
hasSavePermission?: boolean
|
hasSavePermission?: boolean
|
||||||
id: null | number | string
|
id: null | number | string
|
||||||
isEditing?: boolean
|
isEditing?: boolean
|
||||||
@@ -35,7 +36,7 @@ export type DocumentInfoProps = {
|
|||||||
|
|
||||||
export type DocumentInfoContext = DocumentInfoProps & {
|
export type DocumentInfoContext = DocumentInfoProps & {
|
||||||
docConfig?: ClientCollectionConfig | ClientGlobalConfig
|
docConfig?: ClientCollectionConfig | ClientGlobalConfig
|
||||||
getDocPermissions: () => Promise<void>
|
getDocPermissions: (data?: Data) => Promise<void>
|
||||||
getDocPreferences: () => Promise<DocumentPreferences>
|
getDocPreferences: () => Promise<DocumentPreferences>
|
||||||
getVersions: () => Promise<void>
|
getVersions: () => Promise<void>
|
||||||
initialData: Data
|
initialData: Data
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
|||||||
import { devUser } from '../credentials.js'
|
import { devUser } from '../credentials.js'
|
||||||
import { TestButton } from './TestButton.js'
|
import { TestButton } from './TestButton.js'
|
||||||
import {
|
import {
|
||||||
createNotUpdateSlug,
|
createNotUpdateCollectionSlug,
|
||||||
docLevelAccessSlug,
|
docLevelAccessSlug,
|
||||||
firstArrayText,
|
firstArrayText,
|
||||||
fullyRestrictedSlug,
|
fullyRestrictedSlug,
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
noAdminAccessEmail,
|
noAdminAccessEmail,
|
||||||
nonAdminUserEmail,
|
nonAdminUserEmail,
|
||||||
nonAdminUserSlug,
|
nonAdminUserSlug,
|
||||||
|
readNotUpdateGlobalSlug,
|
||||||
readOnlyGlobalSlug,
|
readOnlyGlobalSlug,
|
||||||
readOnlySlug,
|
readOnlySlug,
|
||||||
relyOnRequestHeadersSlug,
|
relyOnRequestHeadersSlug,
|
||||||
@@ -22,7 +23,8 @@ import {
|
|||||||
siblingDataSlug,
|
siblingDataSlug,
|
||||||
slug,
|
slug,
|
||||||
unrestrictedSlug,
|
unrestrictedSlug,
|
||||||
userRestrictedSlug,
|
userRestrictedCollectionSlug,
|
||||||
|
userRestrictedGlobalSlug,
|
||||||
} from './shared.js'
|
} from './shared.js'
|
||||||
|
|
||||||
const openAccess = {
|
const openAccess = {
|
||||||
@@ -90,6 +92,32 @@ export default buildConfigWithDefaults({
|
|||||||
update: () => false,
|
update: () => false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
slug: userRestrictedGlobalSlug,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
access: {
|
||||||
|
read: () => true,
|
||||||
|
update: ({ req, data }) => data?.name === req.user?.email,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: readNotUpdateGlobalSlug,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
access: {
|
||||||
|
read: () => true,
|
||||||
|
update: () => false,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
collections: [
|
collections: [
|
||||||
{
|
{
|
||||||
@@ -201,13 +229,13 @@ export default buildConfigWithDefaults({
|
|||||||
{
|
{
|
||||||
name: 'userRestrictedDocs',
|
name: 'userRestrictedDocs',
|
||||||
type: 'relationship',
|
type: 'relationship',
|
||||||
relationTo: userRestrictedSlug,
|
relationTo: userRestrictedCollectionSlug,
|
||||||
hasMany: true,
|
hasMany: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'createNotUpdateDocs',
|
name: 'createNotUpdateDocs',
|
||||||
type: 'relationship',
|
type: 'relationship',
|
||||||
relationTo: 'create-not-update',
|
relationTo: createNotUpdateCollectionSlug,
|
||||||
hasMany: true,
|
hasMany: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -243,7 +271,7 @@ export default buildConfigWithDefaults({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: userRestrictedSlug,
|
slug: userRestrictedCollectionSlug,
|
||||||
admin: {
|
admin: {
|
||||||
useAsTitle: 'name',
|
useAsTitle: 'name',
|
||||||
},
|
},
|
||||||
@@ -265,7 +293,7 @@ export default buildConfigWithDefaults({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: createNotUpdateSlug,
|
slug: createNotUpdateCollectionSlug,
|
||||||
admin: {
|
admin: {
|
||||||
useAsTitle: 'name',
|
useAsTitle: 'name',
|
||||||
},
|
},
|
||||||
@@ -563,5 +591,12 @@ export default buildConfigWithDefaults({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await payload.updateGlobal({
|
||||||
|
slug: userRestrictedGlobalSlug,
|
||||||
|
data: {
|
||||||
|
name: 'dev@payloadcms.com',
|
||||||
|
},
|
||||||
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import type { TypeWithID } from 'payload/types'
|
|||||||
import { expect, test } from '@playwright/test'
|
import { expect, test } from '@playwright/test'
|
||||||
import { devUser } from 'credentials.js'
|
import { devUser } from 'credentials.js'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { wait } from 'payload/utilities'
|
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
|
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
|
||||||
@@ -30,17 +29,20 @@ import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
|||||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||||
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||||
import {
|
import {
|
||||||
createNotUpdateSlug,
|
createNotUpdateCollectionSlug,
|
||||||
docLevelAccessSlug,
|
docLevelAccessSlug,
|
||||||
fullyRestrictedSlug,
|
fullyRestrictedSlug,
|
||||||
noAdminAccessEmail,
|
noAdminAccessEmail,
|
||||||
nonAdminUserEmail,
|
nonAdminUserEmail,
|
||||||
nonAdminUserSlug,
|
nonAdminUserSlug,
|
||||||
|
readNotUpdateGlobalSlug,
|
||||||
readOnlyGlobalSlug,
|
readOnlyGlobalSlug,
|
||||||
readOnlySlug,
|
readOnlySlug,
|
||||||
restrictedVersionsSlug,
|
restrictedVersionsSlug,
|
||||||
slug,
|
slug,
|
||||||
unrestrictedSlug,
|
unrestrictedSlug,
|
||||||
|
userRestrictedCollectionSlug,
|
||||||
|
userRestrictedGlobalSlug,
|
||||||
} from './shared.js'
|
} from './shared.js'
|
||||||
const filename = fileURLToPath(import.meta.url)
|
const filename = fileURLToPath(import.meta.url)
|
||||||
const dirname = path.dirname(filename)
|
const dirname = path.dirname(filename)
|
||||||
@@ -63,6 +65,8 @@ describe('access control', () => {
|
|||||||
let readOnlyCollectionUrl: AdminUrlUtil
|
let readOnlyCollectionUrl: AdminUrlUtil
|
||||||
let readOnlyGlobalUrl: AdminUrlUtil
|
let readOnlyGlobalUrl: AdminUrlUtil
|
||||||
let restrictedVersionsUrl: AdminUrlUtil
|
let restrictedVersionsUrl: AdminUrlUtil
|
||||||
|
let userRestrictedCollectionURL: AdminUrlUtil
|
||||||
|
let userRestrictedGlobalURL: AdminUrlUtil
|
||||||
let serverURL: string
|
let serverURL: string
|
||||||
let context: BrowserContext
|
let context: BrowserContext
|
||||||
let logoutURL: string
|
let logoutURL: string
|
||||||
@@ -77,6 +81,8 @@ describe('access control', () => {
|
|||||||
readOnlyCollectionUrl = new AdminUrlUtil(serverURL, readOnlySlug)
|
readOnlyCollectionUrl = new AdminUrlUtil(serverURL, readOnlySlug)
|
||||||
readOnlyGlobalUrl = new AdminUrlUtil(serverURL, readOnlySlug)
|
readOnlyGlobalUrl = new AdminUrlUtil(serverURL, readOnlySlug)
|
||||||
restrictedVersionsUrl = new AdminUrlUtil(serverURL, restrictedVersionsSlug)
|
restrictedVersionsUrl = new AdminUrlUtil(serverURL, restrictedVersionsSlug)
|
||||||
|
userRestrictedCollectionURL = new AdminUrlUtil(serverURL, userRestrictedCollectionSlug)
|
||||||
|
userRestrictedGlobalURL = new AdminUrlUtil(serverURL, userRestrictedGlobalSlug)
|
||||||
|
|
||||||
context = await browser.newContext()
|
context = await browser.newContext()
|
||||||
page = await context.newPage()
|
page = await context.newPage()
|
||||||
@@ -134,7 +140,7 @@ describe('access control', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('collection - fully restricted', () => {
|
describe('collection — fully restricted', () => {
|
||||||
let existingDoc: ReadOnlyCollection
|
let existingDoc: ReadOnlyCollection
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@@ -178,7 +184,7 @@ describe('access control', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('collection - read-only', () => {
|
describe('collection — read-only', () => {
|
||||||
let existingDoc: ReadOnlyCollection
|
let existingDoc: ReadOnlyCollection
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@@ -215,7 +221,7 @@ describe('access control', () => {
|
|||||||
await expect(page.locator(`#card-${readOnlySlug}`)).not.toHaveClass('card__actions')
|
await expect(page.locator(`#card-${readOnlySlug}`)).not.toHaveClass('card__actions')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('edit view should not have actions buttons', async () => {
|
test('should not display actions on edit view', async () => {
|
||||||
await page.goto(readOnlyCollectionUrl.edit(existingDoc.id))
|
await page.goto(readOnlyCollectionUrl.edit(existingDoc.id))
|
||||||
await expect(page.locator('.collection-edit__collection-actions li')).toHaveCount(0)
|
await expect(page.locator('.collection-edit__collection-actions li')).toHaveCount(0)
|
||||||
})
|
})
|
||||||
@@ -234,9 +240,9 @@ describe('access control', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('collection - create but not edit', () => {
|
describe('collection — create but not edit', () => {
|
||||||
test('should not show edit button', async () => {
|
test('should not show edit button', async () => {
|
||||||
const createNotUpdateURL = new AdminUrlUtil(serverURL, createNotUpdateSlug)
|
const createNotUpdateURL = new AdminUrlUtil(serverURL, createNotUpdateCollectionSlug)
|
||||||
await page.goto(createNotUpdateURL.create)
|
await page.goto(createNotUpdateURL.create)
|
||||||
await page.waitForURL(createNotUpdateURL.create)
|
await page.waitForURL(createNotUpdateURL.create)
|
||||||
await expect(page.locator('#field-name')).toBeVisible()
|
await expect(page.locator('#field-name')).toBeVisible()
|
||||||
@@ -265,7 +271,7 @@ describe('access control', () => {
|
|||||||
|
|
||||||
await expect(addDocButton).toBeVisible()
|
await expect(addDocButton).toBeVisible()
|
||||||
await addDocButton.click()
|
await addDocButton.click()
|
||||||
const documentDrawer = page.locator('[id^=doc-drawer_create-not-update_1_]')
|
const documentDrawer = page.locator(`[id^=doc-drawer_${createNotUpdateCollectionSlug}_1_]`)
|
||||||
await expect(documentDrawer).toBeVisible()
|
await expect(documentDrawer).toBeVisible()
|
||||||
await expect(documentDrawer.locator('#action-save')).toBeVisible()
|
await expect(documentDrawer.locator('#action-save')).toBeVisible()
|
||||||
await documentDrawer.locator('#field-name').fill('name')
|
await documentDrawer.locator('#field-name').fill('name')
|
||||||
@@ -277,7 +283,37 @@ describe('access control', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('collection - dynamic update access', () => {
|
describe('global — read but not update', () => {
|
||||||
|
test('should not show edit button', async () => {
|
||||||
|
const createNotUpdateURL = new AdminUrlUtil(serverURL, readNotUpdateGlobalSlug)
|
||||||
|
await page.goto(createNotUpdateURL.global(readNotUpdateGlobalSlug))
|
||||||
|
await page.waitForURL(createNotUpdateURL.global(readNotUpdateGlobalSlug))
|
||||||
|
await expect(page.locator('#field-name')).toBeVisible()
|
||||||
|
await expect(page.locator('#field-name')).toBeDisabled()
|
||||||
|
await expect(page.locator('#action-save')).toBeHidden()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('dynamic update access', () => {
|
||||||
|
describe('collection', () => {
|
||||||
|
test('should restrict update access based on document field', async () => {
|
||||||
|
await page.goto(userRestrictedCollectionURL.create)
|
||||||
|
await expect(page.locator('#field-name')).toBeVisible()
|
||||||
|
await page.locator('#field-name').fill('anonymous@email.com')
|
||||||
|
await page.locator('#action-save').click()
|
||||||
|
await expect(page.locator('.Toastify')).toContainText('successfully')
|
||||||
|
await expect(page.locator('#field-name')).toBeDisabled()
|
||||||
|
await expect(page.locator('#action-save')).toBeHidden()
|
||||||
|
|
||||||
|
await page.goto(userRestrictedCollectionURL.create)
|
||||||
|
await expect(page.locator('#field-name')).toBeVisible()
|
||||||
|
await page.locator('#field-name').fill(devUser.email)
|
||||||
|
await page.locator('#action-save').click()
|
||||||
|
await expect(page.locator('.Toastify')).toContainText('successfully')
|
||||||
|
await expect(page.locator('#field-name')).toBeEnabled()
|
||||||
|
await expect(page.locator('#action-save')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
test('maintain access control in document drawer', async () => {
|
test('maintain access control in document drawer', async () => {
|
||||||
const unrestrictedDoc = await payload.create({
|
const unrestrictedDoc = await payload.create({
|
||||||
collection: unrestrictedSlug,
|
collection: unrestrictedSlug,
|
||||||
@@ -293,7 +329,7 @@ describe('access control', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await addDocButton.click()
|
await addDocButton.click()
|
||||||
const documentDrawer = page.locator('[id^=doc-drawer_user-restricted_1_]')
|
const documentDrawer = page.locator('[id^=doc-drawer_user-restricted-collection_1_]')
|
||||||
await expect(documentDrawer).toBeVisible()
|
await expect(documentDrawer).toBeVisible()
|
||||||
await documentDrawer.locator('#field-name').fill('anonymous@email.com')
|
await documentDrawer.locator('#field-name').fill('anonymous@email.com')
|
||||||
await documentDrawer.locator('#action-save').click()
|
await documentDrawer.locator('#action-save').click()
|
||||||
@@ -302,7 +338,7 @@ describe('access control', () => {
|
|||||||
await documentDrawer.locator('button.doc-drawer__header-close').click()
|
await documentDrawer.locator('button.doc-drawer__header-close').click()
|
||||||
await expect(documentDrawer).toBeHidden()
|
await expect(documentDrawer).toBeHidden()
|
||||||
await addDocButton.click()
|
await addDocButton.click()
|
||||||
const documentDrawer2 = page.locator('[id^=doc-drawer_user-restricted_1_]')
|
const documentDrawer2 = page.locator('[id^=doc-drawer_user-restricted-collection_1_]')
|
||||||
await expect(documentDrawer2).toBeVisible()
|
await expect(documentDrawer2).toBeVisible()
|
||||||
await documentDrawer2.locator('#field-name').fill('dev@payloadcms.com')
|
await documentDrawer2.locator('#field-name').fill('dev@payloadcms.com')
|
||||||
await documentDrawer2.locator('#action-save').click()
|
await documentDrawer2.locator('#action-save').click()
|
||||||
@@ -311,7 +347,52 @@ describe('access control', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('collection - restricted versions', () => {
|
describe('global', () => {
|
||||||
|
test('should restrict update access based on document field', async () => {
|
||||||
|
await page.goto(userRestrictedGlobalURL.global(userRestrictedGlobalSlug))
|
||||||
|
await page.waitForURL(userRestrictedGlobalURL.global(userRestrictedGlobalSlug))
|
||||||
|
await expect(page.locator('#field-name')).toBeVisible()
|
||||||
|
await expect(page.locator('#field-name')).toHaveValue(devUser.email)
|
||||||
|
await expect(page.locator('#field-name')).toBeEnabled()
|
||||||
|
await page.locator('#field-name').fill('anonymous@email.com')
|
||||||
|
await page.locator('#action-save').click()
|
||||||
|
await expect(page.locator('.Toastify')).toContainText(
|
||||||
|
'You are not allowed to perform this action',
|
||||||
|
)
|
||||||
|
|
||||||
|
await payload.updateGlobal({
|
||||||
|
slug: userRestrictedGlobalSlug,
|
||||||
|
data: {
|
||||||
|
name: 'anonymous@payloadcms.com',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.goto(userRestrictedGlobalURL.global(userRestrictedGlobalSlug))
|
||||||
|
await page.waitForURL(userRestrictedGlobalURL.global(userRestrictedGlobalSlug))
|
||||||
|
await expect(page.locator('#field-name')).toBeDisabled()
|
||||||
|
await expect(page.locator('#action-save')).toBeHidden()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should restrict access based on user settings', async () => {
|
||||||
|
const url = `${serverURL}/admin/globals/settings`
|
||||||
|
await page.goto(url)
|
||||||
|
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain(url)
|
||||||
|
await openNav(page)
|
||||||
|
await expect(page.locator('#nav-global-settings')).toBeVisible()
|
||||||
|
await expect(page.locator('#nav-global-test')).toBeHidden()
|
||||||
|
await closeNav(page)
|
||||||
|
await page.locator('.checkbox-input:has(#field-test) input').check()
|
||||||
|
await saveDocAndAssert(page)
|
||||||
|
await openNav(page)
|
||||||
|
const globalTest = page.locator('#nav-global-test')
|
||||||
|
await expect(async () => await globalTest.isVisible()).toPass({
|
||||||
|
timeout: POLL_TOPASS_TIMEOUT,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('collection — restricted versions', () => {
|
||||||
let existingDoc: RestrictedVersion
|
let existingDoc: RestrictedVersion
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@@ -346,7 +427,7 @@ describe('access control', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('disable field based on document data', async () => {
|
test('should disable field based on document data', async () => {
|
||||||
await page.goto(docLevelAccessURL.edit(existingDoc.id))
|
await page.goto(docLevelAccessURL.edit(existingDoc.id))
|
||||||
|
|
||||||
// validate that the text input is disabled because the field is "locked"
|
// validate that the text input is disabled because the field is "locked"
|
||||||
@@ -354,7 +435,7 @@ describe('access control', () => {
|
|||||||
await expect(isDisabled).toBeDisabled()
|
await expect(isDisabled).toBeDisabled()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('disable operation based on document data', async () => {
|
test('should disable operation based on document data', async () => {
|
||||||
await page.goto(docLevelAccessURL.edit(existingDoc.id))
|
await page.goto(docLevelAccessURL.edit(existingDoc.id))
|
||||||
|
|
||||||
// validate that the delete action is not displayed
|
// validate that the delete action is not displayed
|
||||||
@@ -371,37 +452,6 @@ describe('access control', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Test flakes. In CI, test global does not appear in nav. Perhaps the checkbox setValue is not triggered BEFORE the document is saved, as the custom save button can be clicked even if the form has not been set to modified.
|
|
||||||
test('should show test global immediately after allowing access', async () => {
|
|
||||||
const url = `${serverURL}/admin/globals/settings`
|
|
||||||
await page.goto(url)
|
|
||||||
|
|
||||||
await expect.poll(() => page.url(), { timeout: POLL_TOPASS_TIMEOUT }).toContain(url)
|
|
||||||
|
|
||||||
await openNav(page)
|
|
||||||
|
|
||||||
// Ensure that we have loaded accesses by checking that settings collection
|
|
||||||
// at least is visible in the menu.
|
|
||||||
await expect(page.locator('#nav-global-settings')).toBeVisible()
|
|
||||||
|
|
||||||
// Test collection should be hidden at first.
|
|
||||||
await expect(page.locator('#nav-global-test')).toBeHidden()
|
|
||||||
|
|
||||||
await closeNav(page)
|
|
||||||
|
|
||||||
// Allow access to test global.
|
|
||||||
await page.locator('.checkbox-input:has(#field-test) input').check()
|
|
||||||
await saveDocAndAssert(page)
|
|
||||||
|
|
||||||
await openNav(page)
|
|
||||||
|
|
||||||
const globalTest = page.locator('#nav-global-test')
|
|
||||||
|
|
||||||
await expect(async () => await globalTest.isVisible()).toPass({
|
|
||||||
timeout: POLL_TOPASS_TIMEOUT,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('admin access', () => {
|
describe('admin access', () => {
|
||||||
test('should block admin access to admin user', async () => {
|
test('should block admin access to admin user', async () => {
|
||||||
const adminURL = `${serverURL}/admin`
|
const adminURL = `${serverURL}/admin`
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ export interface Config {
|
|||||||
unrestricted: Unrestricted;
|
unrestricted: Unrestricted;
|
||||||
'fully-restricted': FullyRestricted;
|
'fully-restricted': FullyRestricted;
|
||||||
'read-only-collection': ReadOnlyCollection;
|
'read-only-collection': ReadOnlyCollection;
|
||||||
'user-restricted': UserRestricted;
|
'user-restricted-collection': UserRestrictedCollection;
|
||||||
'create-not-update': CreateNotUpdate;
|
'create-not-update-collection': CreateNotUpdateCollection;
|
||||||
'restricted-versions': RestrictedVersion;
|
'restricted-versions': RestrictedVersion;
|
||||||
'sibling-data': SiblingDatum;
|
'sibling-data': SiblingDatum;
|
||||||
'rely-on-request-headers': RelyOnRequestHeader;
|
'rely-on-request-headers': RelyOnRequestHeader;
|
||||||
@@ -30,6 +30,8 @@ export interface Config {
|
|||||||
settings: Setting;
|
settings: Setting;
|
||||||
test: Test;
|
test: Test;
|
||||||
'read-only-global': ReadOnlyGlobal;
|
'read-only-global': ReadOnlyGlobal;
|
||||||
|
'user-restricted-global': UserRestrictedGlobal;
|
||||||
|
'read-not-update-global': ReadNotUpdateGlobal;
|
||||||
};
|
};
|
||||||
locale: null;
|
locale: null;
|
||||||
user:
|
user:
|
||||||
@@ -97,16 +99,16 @@ export interface Post {
|
|||||||
export interface Unrestricted {
|
export interface Unrestricted {
|
||||||
id: string;
|
id: string;
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
userRestrictedDocs?: (string | UserRestricted)[] | null;
|
userRestrictedDocs?: (string | UserRestrictedCollection)[] | null;
|
||||||
createNotUpdateDocs?: (string | CreateNotUpdate)[] | null;
|
createNotUpdateDocs?: (string | CreateNotUpdateCollection)[] | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "user-restricted".
|
* via the `definition` "user-restricted-collection".
|
||||||
*/
|
*/
|
||||||
export interface UserRestricted {
|
export interface UserRestrictedCollection {
|
||||||
id: string;
|
id: string;
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
@@ -114,9 +116,9 @@ export interface UserRestricted {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "create-not-update".
|
* via the `definition` "create-not-update-collection".
|
||||||
*/
|
*/
|
||||||
export interface CreateNotUpdate {
|
export interface CreateNotUpdateCollection {
|
||||||
id: string;
|
id: string;
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
@@ -303,6 +305,26 @@ export interface ReadOnlyGlobal {
|
|||||||
updatedAt?: string | null;
|
updatedAt?: string | null;
|
||||||
createdAt?: string | null;
|
createdAt?: string | null;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "user-restricted-global".
|
||||||
|
*/
|
||||||
|
export interface UserRestrictedGlobal {
|
||||||
|
id: string;
|
||||||
|
name?: string | null;
|
||||||
|
updatedAt?: string | null;
|
||||||
|
createdAt?: string | null;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "read-not-update-global".
|
||||||
|
*/
|
||||||
|
export interface ReadNotUpdateGlobal {
|
||||||
|
id: string;
|
||||||
|
name?: string | null;
|
||||||
|
updatedAt?: string | null;
|
||||||
|
createdAt?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
declare module 'payload' {
|
declare module 'payload' {
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ export const unrestrictedSlug = 'unrestricted'
|
|||||||
export const readOnlySlug = 'read-only-collection'
|
export const readOnlySlug = 'read-only-collection'
|
||||||
export const readOnlyGlobalSlug = 'read-only-global'
|
export const readOnlyGlobalSlug = 'read-only-global'
|
||||||
|
|
||||||
export const userRestrictedSlug = 'user-restricted'
|
export const userRestrictedCollectionSlug = 'user-restricted-collection'
|
||||||
export const fullyRestrictedSlug = 'fully-restricted'
|
export const fullyRestrictedSlug = 'fully-restricted'
|
||||||
export const createNotUpdateSlug = 'create-not-update'
|
export const createNotUpdateCollectionSlug = 'create-not-update-collection'
|
||||||
|
export const userRestrictedGlobalSlug = 'user-restricted-global'
|
||||||
|
export const readNotUpdateGlobalSlug = 'read-not-update-global'
|
||||||
export const restrictedVersionsSlug = 'restricted-versions'
|
export const restrictedVersionsSlug = 'restricted-versions'
|
||||||
export const siblingDataSlug = 'sibling-data'
|
export const siblingDataSlug = 'sibling-data'
|
||||||
export const relyOnRequestHeadersSlug = 'rely-on-request-headers'
|
export const relyOnRequestHeadersSlug = 'rely-on-request-headers'
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ describe('versions', () => {
|
|||||||
await expect(findTableCell(page, '_status', 'Draft Title')).toContainText('Draft')
|
await expect(findTableCell(page, '_status', 'Draft Title')).toContainText('Draft')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('bulk update - should publish changes', async () => {
|
test('bulk update — should publish changes', async () => {
|
||||||
const description = 'published document'
|
const description = 'published document'
|
||||||
await page.goto(url.list)
|
await page.goto(url.list)
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ describe('versions', () => {
|
|||||||
await expect(findTableCell(page, '_status', 'Draft Title')).toContainText('Published')
|
await expect(findTableCell(page, '_status', 'Draft Title')).toContainText('Published')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('bulk update - should draft changes', async () => {
|
test('bulk update — should draft changes', async () => {
|
||||||
const description = 'draft document'
|
const description = 'draft document'
|
||||||
await page.goto(url.list)
|
await page.goto(url.list)
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ describe('versions', () => {
|
|||||||
await expect(findTableCell(page, '_status', 'Draft Title')).toContainText('Draft')
|
await expect(findTableCell(page, '_status', 'Draft Title')).toContainText('Draft')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('collection - has versions tab', async () => {
|
test('collection — has versions tab', async () => {
|
||||||
await page.goto(url.list)
|
await page.goto(url.list)
|
||||||
await page.locator('tbody tr .cell-title a').first().click()
|
await page.locator('tbody tr .cell-title a').first().click()
|
||||||
|
|
||||||
@@ -276,7 +276,7 @@ describe('versions', () => {
|
|||||||
expect(href).toBe(`${pathname}/versions`)
|
expect(href).toBe(`${pathname}/versions`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('collection - tab displays proper number of versions', async () => {
|
test('collection — tab displays proper number of versions', async () => {
|
||||||
await page.goto(url.list)
|
await page.goto(url.list)
|
||||||
|
|
||||||
const linkToDoc = page
|
const linkToDoc = page
|
||||||
@@ -297,12 +297,10 @@ describe('versions', () => {
|
|||||||
expect(versionCount).toBe('11')
|
expect(versionCount).toBe('11')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('collection - has versions route', async () => {
|
test('collection — has versions route', async () => {
|
||||||
await page.goto(url.list)
|
await page.goto(url.list)
|
||||||
await page.locator('tbody tr .cell-title a').first().click()
|
await page.locator('tbody tr .cell-title a').first().click()
|
||||||
|
|
||||||
await page.waitForSelector('.doc-header__title', { state: 'visible' })
|
await page.waitForSelector('.doc-header__title', { state: 'visible' })
|
||||||
|
|
||||||
await page.goto(`${page.url()}/versions`)
|
await page.goto(`${page.url()}/versions`)
|
||||||
expect(page.url()).toMatch(/\/versions/)
|
expect(page.url()).toMatch(/\/versions/)
|
||||||
})
|
})
|
||||||
@@ -361,7 +359,7 @@ describe('versions', () => {
|
|||||||
|
|
||||||
// TODO: Check versions/:version-id view for collections / globals
|
// TODO: Check versions/:version-id view for collections / globals
|
||||||
|
|
||||||
test('global - has versions tab', async () => {
|
test('global — has versions tab', async () => {
|
||||||
const global = new AdminUrlUtil(serverURL, draftGlobalSlug)
|
const global = new AdminUrlUtil(serverURL, draftGlobalSlug)
|
||||||
await page.goto(global.global(draftGlobalSlug))
|
await page.goto(global.global(draftGlobalSlug))
|
||||||
|
|
||||||
@@ -378,7 +376,7 @@ describe('versions', () => {
|
|||||||
expect(href).toBe(`${pathname}/versions`)
|
expect(href).toBe(`${pathname}/versions`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('global - has versions route', async () => {
|
test('global — has versions route', async () => {
|
||||||
const global = new AdminUrlUtil(serverURL, globalSlug)
|
const global = new AdminUrlUtil(serverURL, globalSlug)
|
||||||
const versionsURL = `${global.global(globalSlug)}/versions`
|
const versionsURL = `${global.global(globalSlug)}/versions`
|
||||||
await page.goto(versionsURL)
|
await page.goto(versionsURL)
|
||||||
@@ -488,7 +486,7 @@ describe('versions', () => {
|
|||||||
await expect(page.locator('#field-title')).toHaveValue(spanishTitle)
|
await expect(page.locator('#field-title')).toHaveValue(spanishTitle)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('collection - autosave should only update the current document', async () => {
|
test('collection — autosave should only update the current document', async () => {
|
||||||
// create and save first doc
|
// create and save first doc
|
||||||
await page.goto(autosaveURL.create)
|
await page.goto(autosaveURL.create)
|
||||||
// Should redirect from /create to /[collectionslug]/[new id] due to auto-save
|
// Should redirect from /create to /[collectionslug]/[new id] due to auto-save
|
||||||
@@ -547,20 +545,18 @@ describe('versions', () => {
|
|||||||
await expect(page.locator('#field-title')).toHaveValue('title')
|
await expect(page.locator('#field-title')).toHaveValue('title')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should hide publish when access control prevents updating on globals', async () => {
|
test('globals — should hide publish button when access control prevents update', async () => {
|
||||||
const url = new AdminUrlUtil(serverURL, disablePublishGlobalSlug)
|
const url = new AdminUrlUtil(serverURL, disablePublishGlobalSlug)
|
||||||
await page.goto(url.global(disablePublishGlobalSlug))
|
await page.goto(url.global(disablePublishGlobalSlug))
|
||||||
|
|
||||||
await expect(page.locator('#action-save')).not.toBeAttached()
|
await expect(page.locator('#action-save')).not.toBeAttached()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should hide publish when access control prevents create operation', async () => {
|
test('collections — should hide publish button when access control prevents create', async () => {
|
||||||
await page.goto(disablePublishURL.create)
|
await page.goto(disablePublishURL.create)
|
||||||
|
|
||||||
await expect(page.locator('#action-save')).not.toBeAttached()
|
await expect(page.locator('#action-save')).not.toBeAttached()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should hide publish when access control prevents update operation', async () => {
|
test('collections — should hide publish button when access control prevents update', async () => {
|
||||||
const publishedDoc = await payload.create({
|
const publishedDoc = await payload.create({
|
||||||
collection: disablePublishSlug,
|
collection: disablePublishSlug,
|
||||||
data: {
|
data: {
|
||||||
@@ -571,7 +567,6 @@ describe('versions', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await page.goto(disablePublishURL.edit(String(publishedDoc.id)))
|
await page.goto(disablePublishURL.edit(String(publishedDoc.id)))
|
||||||
|
|
||||||
await expect(page.locator('#action-save')).not.toBeAttached()
|
await expect(page.locator('#action-save')).not.toBeAttached()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@payload-config": [
|
"@payload-config": [
|
||||||
"./test/_community/config.ts"
|
"./test/access-control/config.ts"
|
||||||
],
|
],
|
||||||
"@payloadcms/live-preview": [
|
"@payloadcms/live-preview": [
|
||||||
"./packages/live-preview/src"
|
"./packages/live-preview/src"
|
||||||
|
|||||||
Reference in New Issue
Block a user