Skip to content

Commit 321e97f

Browse files
authored
feat: extracts buildFormState logic from endpoint for reuse (#6501)
1 parent 4e0dfd4 commit 321e97f

File tree

10 files changed

+238
-215
lines changed

10 files changed

+238
-215
lines changed

packages/next/src/exports/utilities.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export { addDataAndFileToRequest } from '../utilities/addDataAndFileToRequest.js'
22
export { addLocalesToRequestFromData, sanitizeLocales } from '../utilities/addLocalesToRequest.js'
3-
export { traverseFields } from '../utilities/buildFieldSchemaMap/traverseFields.js'
43
export { createPayloadRequest } from '../utilities/createPayloadRequest.js'
54
export { getNextRequestI18n } from '../utilities/getNextRequestI18n.js'
65
export { getPayloadHMR, reload } from '../utilities/getPayloadHMR.js'

packages/next/src/routes/rest/buildFormState.ts

Lines changed: 15 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,29 @@
1-
import type { BuildFormStateArgs } from '@payloadcms/ui/forms/buildStateFromSchema'
2-
import type { DocumentPreferences, Field, PayloadRequestWithData, TypeWithID } from 'payload/types'
1+
import type { PayloadRequestWithData } from 'payload/types'
32

4-
import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema'
5-
import { reduceFieldsToValues } from '@payloadcms/ui/utilities/reduceFieldsToValues'
3+
import { buildFormState as buildFormStateFn } from '@payloadcms/ui/utilities/buildFormState'
64
import httpStatus from 'http-status'
75

8-
import type { FieldSchemaMap } from '../../utilities/buildFieldSchemaMap/types.js'
9-
10-
import { buildFieldSchemaMap } from '../../utilities/buildFieldSchemaMap/index.js'
116
import { headersWithCors } from '../../utilities/headersWithCors.js'
127
import { routeError } from './routeError.js'
138

14-
let cached = global._payload_fieldSchemaMap
15-
16-
if (!cached) {
17-
// eslint-disable-next-line no-multi-assign
18-
cached = global._payload_fieldSchemaMap = null
19-
}
20-
21-
export const getFieldSchemaMap = (req: PayloadRequestWithData): FieldSchemaMap => {
22-
if (cached && process.env.NODE_ENV !== 'development') {
23-
return cached
24-
}
25-
26-
cached = buildFieldSchemaMap(req)
27-
28-
return cached
29-
}
30-
319
export const buildFormState = async ({ req }: { req: PayloadRequestWithData }) => {
3210
const headers = headersWithCors({
3311
headers: new Headers(),
3412
req,
3513
})
3614

3715
try {
38-
const reqData: BuildFormStateArgs = req.data as BuildFormStateArgs
39-
const { collectionSlug, formState, globalSlug, locale, operation, schemaPath } = reqData
40-
41-
const incomingUserSlug = req.user?.collection
42-
const adminUserSlug = req.payload.config.admin.user
43-
44-
// If we have a user slug, test it against the functions
45-
if (incomingUserSlug) {
46-
const adminAccessFunction = req.payload.collections[incomingUserSlug].config.access?.admin
47-
48-
// Run the admin access function from the config if it exists
49-
if (adminAccessFunction) {
50-
const canAccessAdmin = await adminAccessFunction({ req })
51-
52-
if (!canAccessAdmin) {
53-
return Response.json(null, {
54-
headers,
55-
status: httpStatus.UNAUTHORIZED,
56-
})
57-
}
58-
// Match the user collection to the global admin config
59-
} else if (adminUserSlug !== incomingUserSlug) {
60-
return Response.json(null, {
61-
headers,
62-
status: httpStatus.UNAUTHORIZED,
63-
})
64-
}
65-
} else {
66-
const hasUsers = await req.payload.find({
67-
collection: adminUserSlug,
68-
depth: 0,
69-
limit: 1,
70-
pagination: false,
71-
})
72-
// If there are users, we should not allow access because of /create-first-user
73-
if (hasUsers.docs.length) {
74-
return Response.json(null, {
75-
headers,
76-
status: httpStatus.UNAUTHORIZED,
77-
})
78-
}
79-
}
80-
81-
const fieldSchemaMap = getFieldSchemaMap(req)
82-
83-
const id = collectionSlug ? reqData.id : undefined
84-
const schemaPathSegments = schemaPath.split('.')
85-
86-
let fieldSchema: Field[]
87-
88-
if (schemaPathSegments.length === 1) {
89-
if (req.payload.collections[schemaPath]) {
90-
fieldSchema = req.payload.collections[schemaPath].config.fields
91-
} else {
92-
fieldSchema = req.payload.config.globals.find(
93-
(global) => global.slug === schemaPath,
94-
)?.fields
95-
}
96-
} else if (fieldSchemaMap.has(schemaPath)) {
97-
fieldSchema = fieldSchemaMap.get(schemaPath)
98-
}
16+
const result = await buildFormStateFn({ req })
9917

100-
if (!fieldSchema) {
18+
return Response.json(result, {
19+
headers,
20+
status: httpStatus.OK,
21+
})
22+
} catch (err) {
23+
if (err.message === 'Could not find field schema for given path') {
10124
return Response.json(
10225
{
103-
message: 'Could not find field schema for given path',
26+
message: err.message,
10427
},
10528
{
10629
headers,
@@ -109,126 +32,13 @@ export const buildFormState = async ({ req }: { req: PayloadRequestWithData }) =
10932
)
11033
}
11134

112-
let docPreferences = reqData.docPreferences
113-
let data = reqData.data
114-
115-
const promises: {
116-
data?: Promise<void>
117-
preferences?: Promise<void>
118-
} = {}
119-
120-
// If the request does not include doc preferences,
121-
// we should fetch them. This is useful for DocumentInfoProvider
122-
// as it reduces the amount of client-side fetches necessary
123-
// when we fetch data for the Edit view
124-
if (!docPreferences) {
125-
let preferencesKey
126-
127-
if (collectionSlug && id) {
128-
preferencesKey = `collection-${collectionSlug}-${id}`
129-
}
130-
131-
if (globalSlug) {
132-
preferencesKey = `global-${globalSlug}`
133-
}
134-
135-
if (preferencesKey) {
136-
const fetchPreferences = async () => {
137-
const preferencesResult = (await req.payload.find({
138-
collection: 'payload-preferences',
139-
depth: 0,
140-
limit: 1,
141-
where: {
142-
key: {
143-
equals: preferencesKey,
144-
},
145-
},
146-
})) as unknown as { docs: { value: DocumentPreferences }[] }
147-
148-
if (preferencesResult?.docs?.[0]?.value) docPreferences = preferencesResult.docs[0].value
149-
}
150-
151-
promises.preferences = fetchPreferences()
152-
}
153-
}
154-
155-
// If there is a form state,
156-
// then we can deduce data from that form state
157-
if (formState) data = reduceFieldsToValues(formState, true)
158-
159-
// If we do not have data at this point,
160-
// we can fetch it. This is useful for DocumentInfoProvider
161-
// to reduce the amount of fetches required
162-
if (!data) {
163-
const fetchData = async () => {
164-
let resolvedData: TypeWithID
165-
166-
if (collectionSlug && id) {
167-
resolvedData = await req.payload.findByID({
168-
id,
169-
collection: collectionSlug,
170-
depth: 0,
171-
draft: true,
172-
fallbackLocale: null,
173-
locale,
174-
overrideAccess: false,
175-
user: req.user,
176-
})
177-
}
178-
179-
if (globalSlug && schemaPath === globalSlug) {
180-
resolvedData = await req.payload.findGlobal({
181-
slug: globalSlug,
182-
depth: 0,
183-
draft: true,
184-
fallbackLocale: null,
185-
locale,
186-
overrideAccess: false,
187-
user: req.user,
188-
})
189-
}
190-
191-
data = resolvedData
192-
}
193-
194-
promises.data = fetchData()
195-
}
196-
197-
if (Object.keys(promises).length > 0) {
198-
await Promise.all(Object.values(promises))
199-
}
200-
201-
const result = await buildStateFromSchema({
202-
id,
203-
data,
204-
fieldSchema,
205-
operation,
206-
preferences: docPreferences || { fields: {} },
207-
req,
208-
})
209-
210-
// Maintain form state of auth / upload fields
211-
if (collectionSlug && formState) {
212-
if (req.payload.collections[collectionSlug]?.config?.upload && formState.file) {
213-
result.file = formState.file
214-
}
215-
216-
if (
217-
req.payload.collections[collectionSlug]?.config?.auth &&
218-
!req.payload.collections[collectionSlug].config.auth.disableLocalStrategy
219-
) {
220-
if (formState.password) result.password = formState.password
221-
if (formState['confirm-password'])
222-
result['confirm-password'] = formState['confirm-password']
223-
if (formState.email) result.email = formState.email
224-
}
35+
if (err.message === 'Unauthorized') {
36+
return Response.json(null, {
37+
headers,
38+
status: httpStatus.UNAUTHORIZED,
39+
})
22540
}
22641

227-
return Response.json(result, {
228-
headers,
229-
status: httpStatus.OK,
230-
})
231-
} catch (err) {
23242
req.payload.logger.error({ err, msg: `There was an error building form state` })
23343

23444
return routeError({

packages/payload/src/fields/getDefaultValue.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { User } from '../auth/index.js'
21
import type { PayloadRequestWithData } from '../types/index.js'
32

43
import { deepCopyObject } from '../utilities/deepCopyObject.js'

packages/richtext-lexical/src/field/features/blocks/feature.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Config } from 'payload/config'
22
import type { Block, BlockField, Field } from 'payload/types'
33

4-
import { traverseFields } from '@payloadcms/next/utilities'
4+
import { traverseFields } from '@payloadcms/ui/utilities/buildFieldSchemaMap/traverseFields'
55
import { baseBlockFields, sanitizeFields } from 'payload/config'
66
import { fieldsToJSONSchema, formatLabels } from 'payload/utilities'
77

packages/richtext-lexical/src/field/features/link/feature.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Config, SanitizedConfig } from 'payload/config'
22
import type { Field } from 'payload/types'
33

4-
import { traverseFields } from '@payloadcms/next/utilities'
4+
import { traverseFields } from '@payloadcms/ui/utilities/buildFieldSchemaMap/traverseFields'
55
import { sanitizeFields } from 'payload/config'
66
import { deepCopyObject } from 'payload/utilities'
77

packages/richtext-lexical/src/field/features/upload/feature.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Config } from 'payload/config'
22
import type { Field, FileData, FileSize, Payload, TypeWithID } from 'payload/types'
33

4-
import { traverseFields } from '@payloadcms/next/utilities'
4+
import { traverseFields } from '@payloadcms/ui/utilities/buildFieldSchemaMap/traverseFields'
55
import { sanitizeFields } from 'payload/config'
66

77
import type { FeatureProviderProviderServer } from '../types.js'

packages/next/src/utilities/buildFieldSchemaMap/index.ts renamed to packages/ui/src/utilities/buildFieldSchemaMap/index.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import type { PayloadRequestWithData } from 'payload/types'
1+
import type { I18n } from '@payloadcms/translations'
2+
import type { SanitizedConfig } from 'payload/types'
23

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

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

7-
export const buildFieldSchemaMap = ({
8-
i18n,
9-
payload: { config },
10-
}: PayloadRequestWithData): FieldSchemaMap => {
8+
export const buildFieldSchemaMap = (args: {
9+
config: SanitizedConfig
10+
i18n: I18n
11+
}): FieldSchemaMap => {
12+
const { config, i18n } = args
13+
1114
const result: FieldSchemaMap = new Map()
1215

1316
const validRelationships = config.collections.map((c) => c.slug) || []

0 commit comments

Comments
 (0)