Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(next,ui): fixes global doc permissions and optimizes publish access data loading #6451

Merged
merged 13 commits into from
May 22, 2024
Merged
28 changes: 21 additions & 7 deletions packages/next/src/routes/rest/auth/access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,29 @@ import type { BaseRouteHandler } from '../types.js'
import { headersWithCors } from '../../../utilities/headersWithCors.js'

export const access: BaseRouteHandler = async ({ req }) => {
const results = await accessOperation({
const headers = headersWithCors({
headers: new Headers(),
req,
})

return Response.json(results, {
headers: headersWithCors({
headers: new Headers(),
try {
const results = await accessOperation({
req,
}),
status: httpStatus.OK,
})
})

return Response.json(results, {
headers,
status: httpStatus.OK,
})
} catch (e: unknown) {
return Response.json(
{
error: e,
},
{
headers,
status: httpStatus.INTERNAL_SERVER_ERROR,
},
)
}
}
25 changes: 19 additions & 6 deletions packages/next/src/views/Account/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,21 @@ import { FormQueryParamsProvider } from '@payloadcms/ui/providers/FormQueryParam
import { notFound } from 'next/navigation.js'
import React from 'react'

import { getDocumentPermissions } from '../Document/getDocumentPermissions.js'
import { EditView } from '../Edit/index.js'
import { Settings } from './Settings/index.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 {
locale,
permissions,
req,
req: {
i18n,
payload,
Expand All @@ -32,11 +38,17 @@ export const Account: React.FC<AdminViewProps> = ({ initPageResult, params, sear
serverURL,
} = config

const collectionPermissions = permissions?.collections?.[userSlug]

const collectionConfig = config.collections.find((collection) => collection.slug === userSlug)

if (collectionConfig) {
const { docPermissions, hasPublishPermission, hasSavePermission } =
await getDocumentPermissions({
id: user.id,
collectionConfig,
data: user,
req,
})

const viewComponentProps: ServerSideEditViewProps = {
initPageResult,
params,
Expand All @@ -50,9 +62,10 @@ export const Account: React.FC<AdminViewProps> = ({ initPageResult, params, sear
action={`${serverURL}${api}/${userSlug}${user?.id ? `/${user.id}` : ''}`}
apiURL={`${serverURL}${api}/${userSlug}${user?.id ? `/${user.id}` : ''}`}
collectionSlug={userSlug}
docPermissions={collectionPermissions}
hasSavePermission={collectionPermissions?.update?.permission}
id={user?.id}
docPermissions={docPermissions}
hasPublishPermission={hasPublishPermission}
hasSavePermission={hasSavePermission}
id={user?.id.toString()}
isEditing
>
<DocumentHeader
Expand Down
41 changes: 41 additions & 0 deletions packages/next/src/views/Document/getDocumentData.tsx
Original file line number Diff line number Diff line change
@@ -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 changes: 105 additions & 0 deletions packages/next/src/views/Document/getDocumentPermissions.tsx
Original file line number Diff line number Diff line change
@@ -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,
}
}
42 changes: 20 additions & 22 deletions packages/next/src/views/Document/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { EditViewComponent } from 'payload/config'
import type { AdminViewComponent, ServerSideEditViewProps } from 'payload/types'
import type { DocumentPermissions } from 'payload/types'
import type { AdminViewProps } from 'payload/types'

import { DocumentHeader } from '@payloadcms/ui/elements/DocumentHeader'
Expand All @@ -9,15 +8,15 @@ import { RenderCustomComponent } from '@payloadcms/ui/elements/RenderCustomCompo
import { DocumentInfoProvider } from '@payloadcms/ui/providers/DocumentInfo'
import { EditDepthProvider } from '@payloadcms/ui/providers/EditDepth'
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 { notFound, redirect } from 'next/navigation.js'
import { docAccessOperation } from 'payload/operations'
import React from 'react'

import type { GenerateEditViewMetadata } from './getMetaBySegment.js'

import { NotFoundView } from '../NotFound/index.js'
import { getDocumentData } from './getDocumentData.js'
import { getDocumentPermissions } from './getDocumentPermissions.js'
import { getMetaBySegment } from './getMetaBySegment.js'
import { getViewsFromConfig } from './getViewsFromConfig.js'

Expand Down Expand Up @@ -61,32 +60,33 @@ export const Document: React.FC<AdminViewProps> = async ({
let DefaultView: EditViewComponent
let ErrorView: AdminViewComponent

let docPermissions: DocumentPermissions
let hasSavePermission: boolean
let apiURL: 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 (!visibleEntities?.collections?.find((visibleSlug) => visibleSlug === collectionSlug)) {
notFound()
}

try {
docPermissions = await docAccessOperation({
id,
collection: {
config: collectionConfig,
},
req,
})
} catch (error) {
notFound()
}

action = `${serverURL}${apiRoute}/${collectionSlug}${isEditing ? `/${id}` : ''}`

hasSavePermission = getHasSavePermission({ collectionSlug, docPermissions, isEditing })

apiURL = `${serverURL}${apiRoute}/${collectionSlug}/${id}?locale=${locale.code}${
collectionConfig.versions?.drafts ? '&draft=true' : ''
}`
Expand Down Expand Up @@ -117,9 +117,6 @@ export const Document: React.FC<AdminViewProps> = async ({
notFound()
}

docPermissions = permissions?.globals?.[globalSlug]
hasSavePermission = getHasSavePermission({ docPermissions, globalSlug, isEditing })

action = `${serverURL}${apiRoute}/globals/${globalSlug}`

apiURL = `${serverURL}${apiRoute}/${globalSlug}?locale=${locale.code}${
Expand Down Expand Up @@ -191,6 +188,7 @@ export const Document: React.FC<AdminViewProps> = async ({
disableActions={false}
docPermissions={docPermissions}
globalSlug={globalConfig?.slug}
hasPublishPermission={hasPublishPermission}
hasSavePermission={hasSavePermission}
id={id}
isEditing={isEditing}
Expand Down
5 changes: 2 additions & 3 deletions packages/next/src/views/Edit/Default/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ export const DefaultEditView: React.FC = () => {
disableActions,
disableLeaveWithoutSaving,
docPermissions,
getDocPermissions,
getDocPreferences,
getVersions,
globalSlug,
hasPublishPermission,
hasSavePermission,
initialData: data,
initialState,
Expand Down Expand Up @@ -115,7 +115,6 @@ export const DefaultEditView: React.FC = () => {
}

void getVersions()
void getDocPermissions()

if (typeof onSaveFromContext === 'function') {
void onSaveFromContext({
Expand Down Expand Up @@ -147,7 +146,6 @@ export const DefaultEditView: React.FC = () => {
depth,
collectionSlug,
getVersions,
getDocPermissions,
isEditing,
refreshCookieAsync,
adminRoute,
Expand Down Expand Up @@ -221,6 +219,7 @@ export const DefaultEditView: React.FC = () => {
apiURL={apiURL}
data={data}
disableActions={disableActions}
hasPublishPermission={hasPublishPermission}
hasSavePermission={hasSavePermission}
id={id}
isEditing={isEditing}
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/views/LivePreview/index.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const PreviewView: React.FC<Props> = ({
docPermissions,
getDocPreferences,
globalSlug,
hasPublishPermission,
hasSavePermission,
initialData,
initialState,
Expand Down Expand Up @@ -160,6 +161,7 @@ const PreviewView: React.FC<Props> = ({
apiURL={apiURL}
data={initialData}
disableActions={disableActions}
hasPublishPermission={hasPublishPermission}
hasSavePermission={hasSavePermission}
id={id}
isEditing={isEditing}
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/elements/DocumentControls/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const DocumentControls: React.FC<{
apiURL: string
data?: any
disableActions?: boolean
hasPublishPermission?: boolean
hasSavePermission?: boolean
id?: number | string
isAccountView?: boolean
Expand Down
Loading