Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/next/src/exports/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export { addDataAndFileToRequest } from '../utilities/addDataAndFileToRequest.js'
export { addLocalesToRequestFromData, sanitizeLocales } from '../utilities/addLocalesToRequest.js'
export { traverseFields } from '../utilities/buildFieldSchemaMap/traverseFields.js'
export { createPayloadRequest } from '../utilities/createPayloadRequest.js'
export { getNextRequestI18n } from '../utilities/getNextRequestI18n.js'
export { getPayloadHMR, reload } from '../utilities/getPayloadHMR.js'
Expand Down
220 changes: 15 additions & 205 deletions packages/next/src/routes/rest/buildFormState.ts
Original file line number Diff line number Diff line change
@@ -1,106 +1,29 @@
import type { BuildFormStateArgs } from '@payloadcms/ui/forms/buildStateFromSchema'
import type { DocumentPreferences, Field, PayloadRequestWithData, TypeWithID } from 'payload/types'
import type { PayloadRequestWithData } from 'payload/types'

import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema'
import { reduceFieldsToValues } from '@payloadcms/ui/utilities/reduceFieldsToValues'
import { buildFormState as buildFormStateFn } from '@payloadcms/ui/utilities/buildFormState'
import httpStatus from 'http-status'

import type { FieldSchemaMap } from '../../utilities/buildFieldSchemaMap/types.js'

import { buildFieldSchemaMap } from '../../utilities/buildFieldSchemaMap/index.js'
import { headersWithCors } from '../../utilities/headersWithCors.js'
import { routeError } from './routeError.js'

let cached = global._payload_fieldSchemaMap

if (!cached) {
// eslint-disable-next-line no-multi-assign
cached = global._payload_fieldSchemaMap = null
}

export const getFieldSchemaMap = (req: PayloadRequestWithData): FieldSchemaMap => {
if (cached && process.env.NODE_ENV !== 'development') {
return cached
}

cached = buildFieldSchemaMap(req)

return cached
}

export const buildFormState = async ({ req }: { req: PayloadRequestWithData }) => {
const headers = headersWithCors({
headers: new Headers(),
req,
})

try {
const reqData: BuildFormStateArgs = req.data as BuildFormStateArgs
const { collectionSlug, formState, globalSlug, locale, operation, schemaPath } = reqData

const incomingUserSlug = req.user?.collection
const adminUserSlug = req.payload.config.admin.user

// If we have a user slug, test it against the functions
if (incomingUserSlug) {
const adminAccessFunction = req.payload.collections[incomingUserSlug].config.access?.admin

// Run the admin access function from the config if it exists
if (adminAccessFunction) {
const canAccessAdmin = await adminAccessFunction({ req })

if (!canAccessAdmin) {
return Response.json(null, {
headers,
status: httpStatus.UNAUTHORIZED,
})
}
// Match the user collection to the global admin config
} else if (adminUserSlug !== incomingUserSlug) {
return Response.json(null, {
headers,
status: httpStatus.UNAUTHORIZED,
})
}
} else {
const hasUsers = await req.payload.find({
collection: adminUserSlug,
depth: 0,
limit: 1,
pagination: false,
})
// If there are users, we should not allow access because of /create-first-user
if (hasUsers.docs.length) {
return Response.json(null, {
headers,
status: httpStatus.UNAUTHORIZED,
})
}
}

const fieldSchemaMap = getFieldSchemaMap(req)

const id = collectionSlug ? reqData.id : undefined
const schemaPathSegments = schemaPath.split('.')

let fieldSchema: Field[]

if (schemaPathSegments.length === 1) {
if (req.payload.collections[schemaPath]) {
fieldSchema = req.payload.collections[schemaPath].config.fields
} else {
fieldSchema = req.payload.config.globals.find(
(global) => global.slug === schemaPath,
)?.fields
}
} else if (fieldSchemaMap.has(schemaPath)) {
fieldSchema = fieldSchemaMap.get(schemaPath)
}
const result = await buildFormStateFn({ req })

if (!fieldSchema) {
return Response.json(result, {
headers,
status: httpStatus.OK,
})
} catch (err) {
if (err.message === 'Could not find field schema for given path') {
return Response.json(
{
message: 'Could not find field schema for given path',
message: err.message,
},
{
headers,
Expand All @@ -109,126 +32,13 @@ export const buildFormState = async ({ req }: { req: PayloadRequestWithData }) =
)
}

let docPreferences = reqData.docPreferences
let data = reqData.data

const promises: {
data?: Promise<void>
preferences?: Promise<void>
} = {}

// If the request does not include doc preferences,
// we should fetch them. This is useful for DocumentInfoProvider
// as it reduces the amount of client-side fetches necessary
// when we fetch data for the Edit view
if (!docPreferences) {
let preferencesKey

if (collectionSlug && id) {
preferencesKey = `collection-${collectionSlug}-${id}`
}

if (globalSlug) {
preferencesKey = `global-${globalSlug}`
}

if (preferencesKey) {
const fetchPreferences = async () => {
const preferencesResult = (await req.payload.find({
collection: 'payload-preferences',
depth: 0,
limit: 1,
where: {
key: {
equals: preferencesKey,
},
},
})) as unknown as { docs: { value: DocumentPreferences }[] }

if (preferencesResult?.docs?.[0]?.value) docPreferences = preferencesResult.docs[0].value
}

promises.preferences = fetchPreferences()
}
}

// If there is a form state,
// then we can deduce data from that form state
if (formState) data = reduceFieldsToValues(formState, true)

// If we do not have data at this point,
// we can fetch it. This is useful for DocumentInfoProvider
// to reduce the amount of fetches required
if (!data) {
const fetchData = async () => {
let resolvedData: TypeWithID

if (collectionSlug && id) {
resolvedData = await req.payload.findByID({
id,
collection: collectionSlug,
depth: 0,
draft: true,
fallbackLocale: null,
locale,
overrideAccess: false,
user: req.user,
})
}

if (globalSlug && schemaPath === globalSlug) {
resolvedData = await req.payload.findGlobal({
slug: globalSlug,
depth: 0,
draft: true,
fallbackLocale: null,
locale,
overrideAccess: false,
user: req.user,
})
}

data = resolvedData
}

promises.data = fetchData()
}

if (Object.keys(promises).length > 0) {
await Promise.all(Object.values(promises))
}

const result = await buildStateFromSchema({
id,
data,
fieldSchema,
operation,
preferences: docPreferences || { fields: {} },
req,
})

// Maintain form state of auth / upload fields
if (collectionSlug && formState) {
if (req.payload.collections[collectionSlug]?.config?.upload && formState.file) {
result.file = formState.file
}

if (
req.payload.collections[collectionSlug]?.config?.auth &&
!req.payload.collections[collectionSlug].config.auth.disableLocalStrategy
) {
if (formState.password) result.password = formState.password
if (formState['confirm-password'])
result['confirm-password'] = formState['confirm-password']
if (formState.email) result.email = formState.email
}
if (err.message === 'Unauthorized') {
return Response.json(null, {
headers,
status: httpStatus.UNAUTHORIZED,
})
}

return Response.json(result, {
headers,
status: httpStatus.OK,
})
} catch (err) {
req.payload.logger.error({ err, msg: `There was an error building form state` })

return routeError({
Expand Down
1 change: 0 additions & 1 deletion packages/payload/src/fields/getDefaultValue.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { User } from '../auth/index.js'
import type { PayloadRequestWithData } from '../types/index.js'

import { deepCopyObject } from '../utilities/deepCopyObject.js'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Config } from 'payload/config'
import type { Block, BlockField, Field } from 'payload/types'

import { traverseFields } from '@payloadcms/next/utilities'
import { traverseFields } from '@payloadcms/ui/utilities/buildFieldSchemaMap/traverseFields'
import { baseBlockFields, sanitizeFields } from 'payload/config'
import { fieldsToJSONSchema, formatLabels } from 'payload/utilities'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Config, SanitizedConfig } from 'payload/config'
import type { Field } from 'payload/types'

import { traverseFields } from '@payloadcms/next/utilities'
import { traverseFields } from '@payloadcms/ui/utilities/buildFieldSchemaMap/traverseFields'
import { sanitizeFields } from 'payload/config'
import { deepCopyObject } from 'payload/utilities'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Config } from 'payload/config'
import type { Field, FileData, FileSize, Payload, TypeWithID } from 'payload/types'

import { traverseFields } from '@payloadcms/next/utilities'
import { traverseFields } from '@payloadcms/ui/utilities/buildFieldSchemaMap/traverseFields'
import { sanitizeFields } from 'payload/config'

import type { FeatureProviderProviderServer } from '../types.js'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import type { PayloadRequestWithData } from 'payload/types'
import type { I18n } from '@payloadcms/translations'
import type { SanitizedConfig } from 'payload/types'

import type { FieldSchemaMap } from './types.js'

import { traverseFields } from './traverseFields.js'

export const buildFieldSchemaMap = ({
i18n,
payload: { config },
}: PayloadRequestWithData): FieldSchemaMap => {
export const buildFieldSchemaMap = (args: {
config: SanitizedConfig
i18n: I18n
}): FieldSchemaMap => {
const { config, i18n } = args

const result: FieldSchemaMap = new Map()

const validRelationships = config.collections.map((c) => c.slug) || []
Expand Down
Loading