Compare commits

...

3 Commits

Author SHA1 Message Date
Jacob Fletcher
6be26f6dd5 Merge branch 'main' into chore/sanitized-config-types 2025-06-23 12:29:49 -04:00
Jacob Fletcher
9d5f801488 more 2025-06-18 14:07:03 -04:00
Jacob Fletcher
5f6fad69fb poc 2025-06-18 12:25:54 -04:00
7 changed files with 262 additions and 241 deletions

View File

@@ -27,27 +27,32 @@ import {
import { sanitizeCompoundIndexes } from './sanitizeCompoundIndexes.js'
import { validateUseAsTitle } from './useAsTitle.js'
export const sanitizeCollection = async (
config: Config,
collection: CollectionConfig,
export const sanitizeCollection = async ({
collectionConfig,
config,
richTextSanitizationPromises,
validRelationships: _validRelationships,
}: {
collectionConfig: CollectionConfig
config: Config
/**
* If this property is set, RichText fields won't be sanitized immediately. Instead, they will be added to this array as promises
* so that you can sanitize them together, after the config has been sanitized.
*/
richTextSanitizationPromises?: Array<(config: SanitizedConfig) => Promise<void>>,
_validRelationships?: string[],
): Promise<SanitizedCollectionConfig> => {
if (collection._sanitized) {
return collection as SanitizedCollectionConfig
richTextSanitizationPromises?: Array<(config: SanitizedConfig) => Promise<void>>
validRelationships?: string[]
}): Promise<SanitizedCollectionConfig> => {
if (collectionConfig._sanitized) {
return collectionConfig as SanitizedCollectionConfig
}
collection._sanitized = true
collectionConfig._sanitized = true
// /////////////////////////////////
// Make copy of collection config
// /////////////////////////////////
const sanitized: CollectionConfig = addDefaultsToCollectionConfig(collection)
const sanitized: CollectionConfig = addDefaultsToCollectionConfig(collectionConfig)
// /////////////////////////////////
// Sanitize fields
@@ -148,7 +153,7 @@ export const sanitizeCollection = async (
}
if (sanitized.timestamps === false) {
throw new TimestampsRequired(collection)
throw new TimestampsRequired(collectionConfig)
}
sanitized.versions.maxPerDoc =
@@ -231,15 +236,15 @@ export const sanitizeCollection = async (
sanitized.auth.loginWithUsername = false
}
if (!collection?.admin?.useAsTitle) {
if (!collectionConfig?.admin?.useAsTitle) {
sanitized.admin!.useAsTitle = sanitized.auth.loginWithUsername ? 'username' : 'email'
}
sanitized.fields = mergeBaseFields(sanitized.fields, getBaseAuthFields(sanitized.auth))
}
if (collection?.admin?.pagination?.limits?.length) {
sanitized.admin!.pagination!.limits = collection.admin.pagination.limits
if (collectionConfig?.admin?.pagination?.limits?.length) {
sanitized.admin!.pagination!.limits = collectionConfig.admin.pagination.limits
}
validateUseAsTitle(sanitized)

View File

@@ -29,14 +29,13 @@ describe('sanitize - collections -', () => {
},
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
await sanitizeCollection({
config: {
...config,
collections: [collectionConfig],
},
collectionConfig,
)
} as Config,
collection: collectionConfig,
})
}).rejects.toThrow(InvalidConfiguration)
})
@@ -48,14 +47,13 @@ describe('sanitize - collections -', () => {
},
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
await sanitizeCollection({
config: {
...config,
collections: [collectionConfig],
},
collectionConfig,
)
} as Config,
collection: collectionConfig,
})
}).not.toThrow()
})
@@ -83,14 +81,13 @@ describe('sanitize - collections -', () => {
],
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
await sanitizeCollection({
config: {
...config,
collections: [collectionConfig],
},
collectionConfig,
)
} as Config,
collection: collectionConfig,
})
}).not.toThrow()
})
@@ -114,14 +111,13 @@ describe('sanitize - collections -', () => {
],
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
await sanitizeCollection({
config: {
...config,
collections: [collectionConfig],
},
collectionConfig,
)
} as Config,
collection: collectionConfig,
})
}).not.toThrow()
})
@@ -133,14 +129,13 @@ describe('sanitize - collections -', () => {
},
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
await sanitizeCollection({
config: {
...config,
collections: [collectionConfig],
},
collectionConfig,
)
} as Config,
collection: collectionConfig,
})
}).rejects.toThrow(InvalidConfiguration)
})
@@ -152,14 +147,13 @@ describe('sanitize - collections -', () => {
},
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
await sanitizeCollection({
config: {
...config,
collections: [collectionConfig],
},
collectionConfig,
)
} as Config,
collection: collectionConfig,
})
}).not.toThrow()
})
@@ -172,14 +166,13 @@ describe('sanitize - collections -', () => {
},
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
await sanitizeCollection({
config: {
...config,
collections: [collectionConfig],
},
collectionConfig,
)
} as Config,
collection: collectionConfig,
})
}).not.toThrow()
})
it('should throw on default field: email if auth is not enabled', async () => {
@@ -190,14 +183,13 @@ describe('sanitize - collections -', () => {
},
}
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
await sanitizeCollection({
config: {
...config,
collections: [collectionConfig],
},
collectionConfig,
)
} as Config,
collection: collectionConfig,
})
}).rejects.toThrow(InvalidConfiguration)
})
})

View File

@@ -32,7 +32,7 @@ import {
lockedDocumentsCollectionSlug,
} from '../locked-documents/config.js'
import { getPreferencesCollection, preferencesCollectionSlug } from '../preferences/config.js'
import { getQueryPresetsConfig, queryPresetsCollectionSlug } from '../query-presets/config.js'
import { getQueryPresetsCollection, queryPresetsCollectionSlug } from '../query-presets/config.js'
import { getDefaultJobsCollection, jobsCollectionSlug } from '../queues/config/index.js'
import { flattenBlock } from '../utilities/flattenAllFields.js'
import { getSchedulePublishTask } from '../versions/schedule/job.js'
@@ -92,6 +92,7 @@ const sanitizeAdminConfig = (configToSanitize: Config): Partial<SanitizedConfig>
supportedTimezones: defaultTimezones,
}
}
// Timezones supported by the Intl API
const _internalSupportedTimezones = Intl.supportedValuesOf('timeZone')
@@ -110,30 +111,30 @@ const sanitizeAdminConfig = (configToSanitize: Config): Partial<SanitizedConfig>
export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedConfig> => {
const configWithDefaults = addDefaultsToConfig(incomingConfig)
const config: Partial<SanitizedConfig> = sanitizeAdminConfig(configWithDefaults)
const sanitizedConfig: Partial<SanitizedConfig> = sanitizeAdminConfig(configWithDefaults)
// Add orderable fields
setupOrderable(config as SanitizedConfig)
setupOrderable(sanitizedConfig as SanitizedConfig)
if (!config.endpoints) {
config.endpoints = []
if (!sanitizedConfig.endpoints) {
sanitizedConfig.endpoints = []
}
for (const endpoint of authRootEndpoints) {
config.endpoints.push(endpoint)
sanitizedConfig.endpoints.push(endpoint)
}
if (config.localization && config.localization.locales?.length > 0) {
// clone localization config so to not break everything
const firstLocale = config.localization.locales[0]
if (sanitizedConfig.localization && sanitizedConfig.localization.locales?.length > 0) {
// clone localization sanitizedConfig so to not break everything
const firstLocale = sanitizedConfig.localization.locales[0]
if (typeof firstLocale === 'string') {
config.localization.localeCodes = [
...(config.localization as unknown as LocalizationConfigWithNoLabels).locales,
sanitizedConfig.localization.localeCodes = [
...(sanitizedConfig.localization as unknown as LocalizationConfigWithNoLabels).locales,
]
// is string[], so convert to Locale[]
config.localization.locales = (
config.localization as unknown as LocalizationConfigWithNoLabels
sanitizedConfig.localization.locales = (
sanitizedConfig.localization as unknown as LocalizationConfigWithNoLabels
).locales.map((locale) => ({
code: locale,
label: locale,
@@ -142,10 +143,12 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
}))
} else {
// is Locale[], so convert to string[] for localeCodes
config.localization.localeCodes = config.localization.locales.map((locale) => locale.code)
sanitizedConfig.localization.localeCodes = sanitizedConfig.localization.locales.map(
(locale) => locale.code,
)
config.localization.locales = (
config.localization as LocalizationConfigWithLabels
sanitizedConfig.localization.locales = (
sanitizedConfig.localization as LocalizationConfigWithLabels
).locales.map((locale) => ({
...locale,
toString: () => locale.code,
@@ -153,7 +156,7 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
}
// Default fallback to true if not provided
config.localization.fallback = config.localization?.fallback ?? true
sanitizedConfig.localization.fallback = sanitizedConfig.localization?.fallback ?? true
}
const i18nConfig: SanitizedConfig['i18n'] = {
@@ -179,9 +182,10 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
i18nConfig.translations
}
config.i18n = i18nConfig
sanitizedConfig.i18n = i18nConfig
const richTextSanitizationPromises: Array<(config: SanitizedConfig) => Promise<void>> = []
const richTextSanitizationPromises: Array<(sanitizedConfig: SanitizedConfig) => Promise<void>> =
[]
const schedulePublishCollections: CollectionSlug[] = []
@@ -191,20 +195,20 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
const collectionSlugs = new Set<CollectionSlug>()
await addFolderCollections(config as unknown as Config)
await addFolderCollections(sanitizedConfig as unknown as Config)
const validRelationships = [
...(config.collections?.map((c) => c.slug) ?? []),
...(sanitizedConfig.collections?.map((c) => c.slug) ?? []),
jobsCollectionSlug,
lockedDocumentsCollectionSlug,
preferencesCollectionSlug,
]
/**
* Blocks sanitization needs to happen before collections, as collection/global join field sanitization needs config.blocks
* Blocks sanitization needs to happen before collections, as collection/global join field sanitization needs sanitizedConfig.blocks
* to be populated with the sanitized blocks
*/
config.blocks = []
sanitizedConfig.blocks = []
if (incomingConfig.blocks?.length) {
for (const block of incomingConfig.blocks) {
@@ -222,7 +226,7 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
: sanitizedBlock.labels
sanitizedBlock.fields = await sanitizeFields({
config: config as unknown as Config,
config: incomingConfig,
existingFieldNames: new Set(),
fields: sanitizedBlock.fields,
parentIsLocalized: false,
@@ -232,84 +236,84 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
const flattenedSanitizedBlock = flattenBlock({ block })
config.blocks.push(flattenedSanitizedBlock)
sanitizedConfig.blocks.push(flattenedSanitizedBlock)
}
}
for (let i = 0; i < config.collections!.length; i++) {
if (collectionSlugs.has(config.collections![i]!.slug)) {
throw new DuplicateCollection('slug', config.collections![i]!.slug)
for (let i = 0; i < sanitizedConfig.collections!.length; i++) {
if (collectionSlugs.has(sanitizedConfig.collections![i]!.slug)) {
throw new DuplicateCollection('slug', sanitizedConfig.collections![i]!.slug)
}
collectionSlugs.add(config.collections![i]!.slug)
collectionSlugs.add(sanitizedConfig.collections![i]!.slug)
const draftsConfig = config.collections![i]?.versions?.drafts
const draftsConfig = sanitizedConfig.collections![i]?.versions?.drafts
if (typeof draftsConfig === 'object' && draftsConfig.schedulePublish) {
schedulePublishCollections.push(config.collections![i]!.slug)
schedulePublishCollections.push(sanitizedConfig.collections![i]!.slug)
}
if (config.collections![i]!.enableQueryPresets) {
queryPresetsCollections.push(config.collections![i]!.slug)
if (sanitizedConfig.collections![i]!.enableQueryPresets) {
queryPresetsCollections.push(sanitizedConfig.collections![i]!.slug)
if (!validRelationships.includes(queryPresetsCollectionSlug)) {
validRelationships.push(queryPresetsCollectionSlug)
}
}
config.collections![i] = await sanitizeCollection(
config as unknown as Config,
config.collections![i]!,
sanitizedConfig.collections![i] = await sanitizeCollection({
collectionConfig: sanitizedConfig.collections![i]!,
config: incomingConfig,
richTextSanitizationPromises,
validRelationships,
)
})
}
if (config.globals!.length > 0) {
for (let i = 0; i < config.globals!.length; i++) {
const draftsConfig = config.globals![i]?.versions?.drafts
if (sanitizedConfig.globals!.length > 0) {
for (let i = 0; i < sanitizedConfig.globals!.length; i++) {
const draftsConfig = sanitizedConfig.globals![i]?.versions?.drafts
if (typeof draftsConfig === 'object' && draftsConfig.schedulePublish) {
schedulePublishGlobals.push(config.globals![i]!.slug)
schedulePublishGlobals.push(sanitizedConfig.globals![i]!.slug)
}
config.globals![i] = await sanitizeGlobal(
config as unknown as Config,
config.globals![i]!,
sanitizedConfig.globals![i] = await sanitizeGlobal({
config: incomingConfig,
globalConfig: sanitizedConfig.globals![i]!,
richTextSanitizationPromises,
validRelationships,
)
})
}
}
if (schedulePublishCollections.length || schedulePublishGlobals.length) {
;((config.jobs ??= {} as SanitizedJobsConfig).tasks ??= []).push(
;((sanitizedConfig.jobs ??= {} as SanitizedJobsConfig).tasks ??= []).push(
getSchedulePublishTask({
adminUserSlug: config.admin!.user,
adminUserSlug: sanitizedConfig.admin!.user,
collections: schedulePublishCollections,
globals: schedulePublishGlobals,
}),
)
}
;(config.jobs ??= {} as SanitizedJobsConfig).enabled = Boolean(
;(sanitizedConfig.jobs ??= {} as SanitizedJobsConfig).enabled = Boolean(
(Array.isArray(configWithDefaults.jobs?.tasks) && configWithDefaults.jobs?.tasks?.length) ||
(Array.isArray(configWithDefaults.jobs?.workflows) &&
configWithDefaults.jobs?.workflows?.length),
)
// Need to add default jobs collection before locked documents collections
if (config.jobs.enabled) {
let defaultJobsCollection = getDefaultJobsCollection(config as unknown as Config)
if (sanitizedConfig.jobs.enabled) {
let defaultJobsCollection = getDefaultJobsCollection(sanitizedConfig as unknown as Config)
if (typeof config.jobs.jobsCollectionOverrides === 'function') {
defaultJobsCollection = config.jobs.jobsCollectionOverrides({
if (typeof sanitizedConfig.jobs.jobsCollectionOverrides === 'function') {
defaultJobsCollection = sanitizedConfig.jobs.jobsCollectionOverrides({
defaultJobsCollection,
})
const hooks = defaultJobsCollection?.hooks
// @todo - delete this check in 4.0
if (hooks && config?.jobs?.runHooks !== true) {
if (hooks && sanitizedConfig?.jobs?.runHooks !== true) {
for (const [hookKey, hook] of Object.entries(hooks)) {
const defaultAmount = hookKey === 'afterRead' || hookKey === 'beforeChange' ? 1 : 0
if (hook.length > defaultAmount) {
@@ -322,101 +326,104 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
}
}
}
const sanitizedJobsCollection = await sanitizeCollection(
config as unknown as Config,
defaultJobsCollection,
const sanitizedJobsCollection = await sanitizeCollection({
collectionConfig: defaultJobsCollection,
config: incomingConfig,
richTextSanitizationPromises,
validRelationships,
)
})
configWithDefaults.collections!.push(sanitizedJobsCollection)
}
configWithDefaults.collections!.push(
await sanitizeCollection(
config as unknown as Config,
getLockedDocumentsCollection(config as unknown as Config),
await sanitizeCollection({
collectionConfig: getLockedDocumentsCollection(incomingConfig),
config: incomingConfig,
richTextSanitizationPromises,
validRelationships,
),
}),
)
configWithDefaults.collections!.push(
await sanitizeCollection(
config as unknown as Config,
getPreferencesCollection(config as unknown as Config),
await sanitizeCollection({
collectionConfig: getPreferencesCollection(incomingConfig),
config: incomingConfig,
richTextSanitizationPromises,
validRelationships,
),
}),
)
configWithDefaults.collections!.push(
await sanitizeCollection(
config as unknown as Config,
migrationsCollection,
await sanitizeCollection({
collectionConfig: migrationsCollection,
config: incomingConfig,
richTextSanitizationPromises,
validRelationships,
),
}),
)
if (queryPresetsCollections.length > 0) {
configWithDefaults.collections!.push(
await sanitizeCollection(
config as unknown as Config,
getQueryPresetsConfig(config as unknown as Config),
await sanitizeCollection({
collectionConfig: getQueryPresetsCollection(incomingConfig),
config: incomingConfig,
richTextSanitizationPromises,
validRelationships,
),
}),
)
}
if (config.serverURL !== '') {
config.csrf!.push(config.serverURL!)
if (sanitizedConfig.serverURL !== '') {
sanitizedConfig.csrf!.push(sanitizedConfig.serverURL!)
}
const uploadAdapters = new Set<string>()
// interact with all collections
for (const collection of config.collections!) {
for (const collection of sanitizedConfig.collections!) {
// deduped upload adapters
if (collection.upload?.adapter) {
uploadAdapters.add(collection.upload.adapter)
}
}
if (!config.upload) {
config.upload = { adapters: [] }
if (!sanitizedConfig.upload) {
sanitizedConfig.upload = { adapters: [] }
}
config.upload.adapters = Array.from(
new Set(config.collections!.map((c) => c.upload?.adapter).filter(Boolean) as string[]),
sanitizedConfig.upload.adapters = Array.from(
new Set(sanitizedConfig.collections!.map((c) => c.upload?.adapter).filter(Boolean) as string[]),
)
// Pass through the email config as is so adapters don't break
// Pass through the email sanitizedConfig as is so adapters don't break
if (incomingConfig.email) {
config.email = incomingConfig.email
sanitizedConfig.email = incomingConfig.email
}
/*
Execute richText sanitization
*/
if (typeof incomingConfig.editor === 'function') {
config.editor = await incomingConfig.editor({
config: config as SanitizedConfig,
sanitizedConfig.editor = await incomingConfig.editor({
config: sanitizedConfig as SanitizedConfig,
isRoot: true,
parentIsLocalized: false,
})
if (config.editor.i18n && Object.keys(config.editor.i18n).length >= 0) {
config.i18n.translations = deepMergeSimple(config.i18n.translations, config.editor.i18n)
if (sanitizedConfig.editor.i18n && Object.keys(sanitizedConfig.editor.i18n).length >= 0) {
sanitizedConfig.i18n.translations = deepMergeSimple(
sanitizedConfig.i18n.translations,
sanitizedConfig.editor.i18n,
)
}
}
const promises: Promise<void>[] = []
for (const sanitizeFunction of richTextSanitizationPromises) {
promises.push(sanitizeFunction(config as SanitizedConfig))
promises.push(sanitizeFunction(sanitizedConfig as SanitizedConfig))
}
await Promise.all(promises)
return config as SanitizedConfig
return sanitizedConfig as SanitizedConfig
}

View File

@@ -1,5 +1,5 @@
import type { Config } from '../../config/types.js'
import type { CollectionConfig, Field } from '../../index.js'
import type { CollectionConfig, Field, SanitizedCollectionConfig } from '../../index.js'
import { ReservedFieldName } from '../../errors/index.js'
import { sanitizeCollection } from '../../collections/config/sanitize.js'
@@ -27,9 +27,8 @@ describe('reservedFieldNames - collections -', () => {
]
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
await sanitizeCollection({
config: {
...config,
collections: [
{
@@ -37,12 +36,12 @@ describe('reservedFieldNames - collections -', () => {
fields,
},
],
},
{
} as Config,
collectionConfig: {
...collectionWithUploads,
fields,
},
)
})
}).rejects.toThrow(ReservedFieldName)
})
@@ -56,9 +55,8 @@ describe('reservedFieldNames - collections -', () => {
]
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
await sanitizeCollection({
config: {
...config,
collections: [
{
@@ -66,12 +64,12 @@ describe('reservedFieldNames - collections -', () => {
fields,
},
],
},
{
} as Config,
collectionConfig: {
...collectionWithUploads,
fields,
},
)
})
}).not.toThrow()
})
})
@@ -97,9 +95,8 @@ describe('reservedFieldNames - collections -', () => {
]
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
await sanitizeCollection({
config: {
...config,
collections: [
{
@@ -107,12 +104,12 @@ describe('reservedFieldNames - collections -', () => {
fields,
},
],
},
{
} as Config,
collectionConfig: {
...collectionWithAuth,
fields,
},
)
})
}).rejects.toThrow(ReservedFieldName)
})
@@ -126,9 +123,8 @@ describe('reservedFieldNames - collections -', () => {
]
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
await sanitizeCollection({
config: {
...config,
collections: [
{
@@ -136,12 +132,12 @@ describe('reservedFieldNames - collections -', () => {
fields,
},
],
},
{
} as Config,
collectionConfig: {
...collectionWithAuth,
fields,
},
)
})
}).rejects.toThrow(ReservedFieldName)
})
@@ -155,9 +151,8 @@ describe('reservedFieldNames - collections -', () => {
]
await expect(async () => {
await sanitizeCollection(
// @ts-expect-error
{
await sanitizeCollection({
config: {
...config,
collections: [
{
@@ -165,12 +160,12 @@ describe('reservedFieldNames - collections -', () => {
fields,
},
],
},
{
} as Config,
collectionConfig: {
...collectionWithAuth,
fields,
},
)
})
}).not.toThrow()
})
})

View File

@@ -179,6 +179,7 @@ export const sanitizeFields = async ({
console.warn(
`(payload): The "min" property is deprecated for the Relationship field "${field.name}" and will be removed in a future version. Please use "minRows" instead.`,
)
field.minRows = field.min
}
@@ -186,6 +187,7 @@ export const sanitizeFields = async ({
console.warn(
`(payload): The "max" property is deprecated for the Relationship field "${field.name}" and will be removed in a future version. Please use "maxRows" instead.`,
)
field.maxRows = field.max
}
}

View File

@@ -11,113 +11,128 @@ import { baseVersionFields } from '../../versions/baseFields.js'
import { versionDefaults } from '../../versions/defaults.js'
import { defaultGlobalEndpoints } from '../endpoints/index.js'
export const sanitizeGlobal = async (
config: Config,
global: GlobalConfig,
export const sanitizeGlobal = async ({
config,
globalConfig,
richTextSanitizationPromises,
validRelationships: _validRelationships,
}: {
config: Config
globalConfig: GlobalConfig
/**
* If this property is set, RichText fields won't be sanitized immediately. Instead, they will be added to this array as promises
* so that you can sanitize them together, after the config has been sanitized.
*/
richTextSanitizationPromises?: Array<(config: SanitizedConfig) => Promise<void>>,
_validRelationships?: string[],
): Promise<SanitizedGlobalConfig> => {
if (global._sanitized) {
return global as SanitizedGlobalConfig
richTextSanitizationPromises?: Array<(config: SanitizedConfig) => Promise<void>>
validRelationships?: string[]
}): Promise<SanitizedGlobalConfig> => {
if (globalConfig._sanitized) {
return globalConfig as SanitizedGlobalConfig
}
global._sanitized = true
global.label = global.label || toWords(global.slug)
globalConfig._sanitized = true
globalConfig.label = globalConfig.label || toWords(globalConfig.slug)
// /////////////////////////////////
// Ensure that collection has required object structure
// /////////////////////////////////
global.endpoints = global.endpoints ?? []
if (!global.hooks) {
global.hooks = {}
}
if (!global.access) {
global.access = {}
}
if (!global.admin) {
global.admin = {}
globalConfig.endpoints = globalConfig.endpoints ?? []
if (!globalConfig.hooks) {
globalConfig.hooks = {}
}
if (!global.access.read) {
global.access.read = defaultAccess
}
if (!global.access.update) {
global.access.update = defaultAccess
if (!globalConfig.access) {
globalConfig.access = {}
}
if (!global.hooks.beforeValidate) {
global.hooks.beforeValidate = []
if (!globalConfig.admin) {
globalConfig.admin = {}
}
if (!global.hooks.beforeChange) {
global.hooks.beforeChange = []
if (!globalConfig.access.read) {
globalConfig.access.read = defaultAccess
}
if (!global.hooks.afterChange) {
global.hooks.afterChange = []
if (!globalConfig.access.update) {
globalConfig.access.update = defaultAccess
}
if (!global.hooks.beforeRead) {
global.hooks.beforeRead = []
if (!globalConfig.hooks.beforeValidate) {
globalConfig.hooks.beforeValidate = []
}
if (!global.hooks.afterRead) {
global.hooks.afterRead = []
if (!globalConfig.hooks.beforeChange) {
globalConfig.hooks.beforeChange = []
}
if (!globalConfig.hooks.afterChange) {
globalConfig.hooks.afterChange = []
}
if (!globalConfig.hooks.beforeRead) {
globalConfig.hooks.beforeRead = []
}
if (!globalConfig.hooks.afterRead) {
globalConfig.hooks.afterRead = []
}
// Sanitize fields
const validRelationships = _validRelationships ?? config.collections?.map((c) => c.slug) ?? []
global.fields = await sanitizeFields({
globalConfig.fields = await sanitizeFields({
config,
fields: global.fields,
fields: globalConfig.fields,
parentIsLocalized: false,
richTextSanitizationPromises,
validRelationships,
})
if (global.endpoints !== false) {
if (!global.endpoints) {
global.endpoints = []
if (globalConfig.endpoints !== false) {
if (!globalConfig.endpoints) {
globalConfig.endpoints = []
}
for (const endpoint of defaultGlobalEndpoints) {
global.endpoints.push(endpoint)
globalConfig.endpoints.push(endpoint)
}
}
if (global.versions) {
if (global.versions === true) {
global.versions = { drafts: false, max: 100 }
if (globalConfig.versions) {
if (globalConfig.versions === true) {
globalConfig.versions = { drafts: false, max: 100 }
}
global.versions.max = typeof global.versions.max === 'number' ? global.versions.max : 100
globalConfig.versions.max =
typeof globalConfig.versions.max === 'number' ? globalConfig.versions.max : 100
if (global.versions.drafts) {
if (global.versions.drafts === true) {
global.versions.drafts = {
if (globalConfig.versions.drafts) {
if (globalConfig.versions.drafts === true) {
globalConfig.versions.drafts = {
autosave: false,
validate: false,
}
}
if (global.versions.drafts.autosave === true) {
global.versions.drafts.autosave = {
if (globalConfig.versions.drafts.autosave === true) {
globalConfig.versions.drafts.autosave = {
interval: versionDefaults.autosaveInterval,
}
}
if (global.versions.drafts.validate === undefined) {
global.versions.drafts.validate = false
if (globalConfig.versions.drafts.validate === undefined) {
globalConfig.versions.drafts.validate = false
}
global.fields = mergeBaseFields(global.fields, baseVersionFields)
globalConfig.fields = mergeBaseFields(globalConfig.fields, baseVersionFields)
}
}
if (!global.custom) {
global.custom = {}
if (!globalConfig.custom) {
globalConfig.custom = {}
}
// /////////////////////////////////
@@ -125,7 +140,8 @@ export const sanitizeGlobal = async (
// /////////////////////////////////
let hasUpdatedAt: boolean | null = null
let hasCreatedAt: boolean | null = null
global.fields.some((field) => {
globalConfig.fields.some((field) => {
if (fieldAffectsData(field)) {
if (field.name === 'updatedAt') {
hasUpdatedAt = true
@@ -136,8 +152,9 @@ export const sanitizeGlobal = async (
}
return hasCreatedAt && hasUpdatedAt
})
if (!hasUpdatedAt) {
global.fields.push({
globalConfig.fields.push({
name: 'updatedAt',
type: 'date',
admin: {
@@ -147,8 +164,9 @@ export const sanitizeGlobal = async (
label: ({ t }) => t('general:updatedAt'),
})
}
if (!hasCreatedAt) {
global.fields.push({
globalConfig.fields.push({
name: 'createdAt',
type: 'date',
admin: {
@@ -159,7 +177,9 @@ export const sanitizeGlobal = async (
})
}
;(global as SanitizedGlobalConfig).flattenedFields = flattenAllFields({ fields: global.fields })
;(globalConfig as SanitizedGlobalConfig).flattenedFields = flattenAllFields({
fields: globalConfig.fields,
})
return global as SanitizedGlobalConfig
return globalConfig as SanitizedGlobalConfig
}

View File

@@ -10,7 +10,7 @@ import { operations, type QueryPreset } from './types.js'
export const queryPresetsCollectionSlug = 'payload-query-presets'
export const getQueryPresetsConfig = (config: Config): CollectionConfig => ({
export const getQueryPresetsCollection = (config: Config): CollectionConfig => ({
slug: queryPresetsCollectionSlug,
access: getAccess(config),
admin: {