From 51474fa661ae24ab8fc0d13001fafc0f35216c1e Mon Sep 17 00:00:00 2001 From: James Mikrut Date: Tue, 16 Jul 2024 12:58:55 -0400 Subject: [PATCH] feat: allows mongoose schemaOptions to be configured (#7099) ## Description This PR adds the ability to configure Mongoose's `schemaOptions`, which exposes more control about how Mongoose operates internally. For example, you can now disable `strict` mode in Mongoose to be able to preserve / surface data in MongoDB that is not reflected in Payload schemas. - [x] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository. ## Type of change - [x] New feature (non-breaking change which adds functionality) ## Checklist: - [x] I have added tests that prove my fix is effective or that my feature works - [x] Existing test suite passes locally with my changes - [ ] I have made corresponding changes to the documentation --- .vscode/settings.json | 6 +- docs/database/mongodb.mdx | 27 ++ packages/db-mongodb/package.json | 2 +- packages/db-mongodb/src/index.ts | 47 ++- packages/db-mongodb/src/init.ts | 12 +- .../src/models/buildCollectionSchema.ts | 15 +- .../db-mongodb/src/models/buildGlobalModel.ts | 21 +- packages/db-mongodb/src/models/buildSchema.ts | 150 ++++---- packages/db-mongodb/src/updateGlobal.ts | 1 + .../fields/hooks/beforeValidate/promise.ts | 19 +- .../hooks/beforeValidate/traverseFields.ts | 15 + pnpm-lock.yaml | 75 ++++ test/auth/config.ts | 22 +- test/auth/int.spec.ts | 2 +- test/collections-graphql/media/test-image.jpg | Bin 0 -> 19572 bytes test/database/config.ts | 338 ++++++++++-------- test/database/int.spec.ts | 110 +++++- test/database/payload-types.ts | 71 +++- test/fields/int.spec.ts | 2 +- test/plugin-search/int.spec.ts | 2 +- .../webhooks/subscriptionCreatedOrUpdated.ts | 2 +- test/relationships/int.spec.ts | 33 +- test/uploads/focal-only/focal-1-400x300.png | Bin 0 -> 5788 bytes test/uploads/focal-only/focal-1-600x300.png | Bin 0 -> 6223 bytes test/uploads/focal-only/focal-1-900x300.png | Bin 0 -> 8457 bytes test/uploads/focal-only/focal-1.png | Bin 0 -> 51467 bytes test/uploads/focal-only/focal-400x300.png | Bin 0 -> 5788 bytes test/uploads/focal-only/focal-600x300.png | Bin 0 -> 6223 bytes test/uploads/focal-only/focal-900x300.png | Bin 0 -> 8457 bytes test/uploads/focal-only/focal.png | Bin 0 -> 51467 bytes 30 files changed, 702 insertions(+), 270 deletions(-) create mode 100644 test/collections-graphql/media/test-image.jpg create mode 100644 test/uploads/focal-only/focal-1-400x300.png create mode 100644 test/uploads/focal-only/focal-1-600x300.png create mode 100644 test/uploads/focal-only/focal-1-900x300.png create mode 100644 test/uploads/focal-only/focal-1.png create mode 100644 test/uploads/focal-only/focal-400x300.png create mode 100644 test/uploads/focal-only/focal-600x300.png create mode 100644 test/uploads/focal-only/focal-900x300.png create mode 100644 test/uploads/focal-only/focal.png diff --git a/.vscode/settings.json b/.vscode/settings.json index 64968d91007..a2d4b7f4ac4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -35,5 +35,9 @@ "eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }], "typescript.tsdk": "node_modules/typescript/lib", // Load .git-blame-ignore-revs file - "gitlens.advanced.blame.customArguments": ["--ignore-revs-file", ".git-blame-ignore-revs"] + "gitlens.advanced.blame.customArguments": ["--ignore-revs-file", ".git-blame-ignore-revs"], + "jestrunner.jestCommand": "pnpm exec cross-env NODE_OPTIONS=\"--experimental-vm-modules --no-deprecation\" node 'node_modules/jest/bin/jest.js'", + "jestrunner.debugOptions": { + "runtimeArgs": ["--experimental-vm-modules", "--no-deprecation"] + } } diff --git a/docs/database/mongodb.mdx b/docs/database/mongodb.mdx index 4682b99b500..28453d5fd44 100644 --- a/docs/database/mongodb.mdx +++ b/docs/database/mongodb.mdx @@ -33,6 +33,9 @@ export default buildConfig({ | Option | Description | |----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `autoPluralization` | Tell Mongoose to auto-pluralize any collection names if it encounters any singular words used as collection `slug`s. | +| `schemaOptions` | Customize schema options for all Mongoose schemas created internally. | +| `collections` | Options on a collection-by-collection basis. [More](#collections-options) | +| `globals` | Options for the Globals collection created by Payload. [More](#globals-options) | | `connectOptions` | Customize MongoDB connection options. Payload will connect to your MongoDB database using default options which you can override and extend to include all the [options](https://mongoosejs.com/docs/connections.html#options) available to mongoose. | | `disableIndexHints` | Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination, as it increases the speed of the count function used in that query. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false | | `migrationDir` | Customize the directory that migrations are stored. | @@ -48,3 +51,27 @@ You can access Mongoose models as follows: - Collection models - `payload.db.collections[myCollectionSlug]` - Globals model - `payload.db.globals` - Versions model (both collections and globals) - `payload.db.versions[myEntitySlug]` + +### Collections Options + +You can configure the way the MongoDB adapter works on a collection-by-collection basis, including customizing Mongoose `schemaOptions` for each collection schema created. + +Example: + +```ts +const db = mongooseAdapter({ + url: 'your-url-here', + collections: { + users: { + // + schemaOptions: { + strict: false, + } + } + } +}) +``` + +### Global Options + +Payload automatically creates a single `globals` collection that correspond with any Payload globals that you define. When you initialize the `mongooseAdapter`, you can specify settings here for your globals in a similar manner to how you can for collections above. Right now, the only property available is `schemaOptions` but more may be added in the future. \ No newline at end of file diff --git a/packages/db-mongodb/package.json b/packages/db-mongodb/package.json index ed5516ecd46..7318518e54c 100644 --- a/packages/db-mongodb/package.json +++ b/packages/db-mongodb/package.json @@ -27,11 +27,11 @@ "bson-objectid": "2.0.4", "deepmerge": "4.3.1", "get-port": "5.1.1", + "http-status": "1.6.2", "mongoose": "6.12.3", "mongoose-aggregate-paginate-v2": "1.0.6", "mongoose-paginate-v2": "1.7.22", "prompts": "2.4.2", - "http-status": "1.6.2", "uuid": "9.0.0" }, "devDependencies": { diff --git a/packages/db-mongodb/src/index.ts b/packages/db-mongodb/src/index.ts index cdb8cad26a8..cde1bcbd289 100644 --- a/packages/db-mongodb/src/index.ts +++ b/packages/db-mongodb/src/index.ts @@ -1,5 +1,5 @@ import type { TransactionOptions } from 'mongodb' -import type { ClientSession, ConnectOptions, Connection } from 'mongoose' +import type { ClientSession, ConnectOptions, Connection, SchemaOptions } from 'mongoose' import type { Payload } from 'payload' import type { BaseDatabaseAdapter } from 'payload/database' @@ -42,6 +42,15 @@ export type { MigrateDownArgs, MigrateUpArgs } from './types' export interface Args { /** Set to false to disable auto-pluralization of collection names, Defaults to true */ autoPluralization?: boolean + /** Define Mongoose options on a collection-by-collection basis. + */ + collections?: { + [slug: string]: { + /** Define Mongoose schema options for a given collection. + */ + schemaOptions?: SchemaOptions + } + } /** Extra configuration options */ connectOptions?: ConnectOptions & { /** Set false to disable $facet aggregation in non-supporting databases, Defaults to true */ @@ -49,7 +58,15 @@ export interface Args { } /** Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false */ disableIndexHints?: boolean + /** Define Mongoose options for the globals collection. + */ + globals?: { + schemaOptions?: SchemaOptions + } migrationDir?: string + /** Define default Mongoose schema options for all schemas created. + */ + schemaOptions?: SchemaOptions transactionOptions?: TransactionOptions | false /** The URL to connect to MongoDB or false to start payload and prevent connecting */ url: false | string @@ -57,12 +74,21 @@ export interface Args { export type MongooseAdapter = BaseDatabaseAdapter & Args & { + collectionOptions: { + [slug: string]: { + schemaOptions?: SchemaOptions + } + } collections: { [slug: string]: CollectionModel } connection: Connection globals: GlobalModel + globalsOptions: { + schemaOptions?: SchemaOptions + } mongoMemoryServer: any + schemaOptions?: SchemaOptions sessions: Record versions: { [slug: string]: CollectionModel @@ -74,13 +100,23 @@ type MongooseAdapterResult = (args: { payload: Payload }) => MongooseAdapter declare module 'payload' { export interface DatabaseAdapter extends Omit, - Omit { + Omit { + collectionOptions: { + [slug: string]: { + schemaOptions?: SchemaOptions + } + } collections: { [slug: string]: CollectionModel } connection: Connection globals: GlobalModel + globalsOptions: { + schemaOptions?: SchemaOptions + } mongoMemoryServer: any + schemaOptions?: SchemaOptions + sessions: Record transactionOptions: TransactionOptions versions: { @@ -91,9 +127,12 @@ declare module 'payload' { export function mongooseAdapter({ autoPluralization = true, + collections, connectOptions, disableIndexHints = false, + globals, migrationDir: migrationDirArg, + schemaOptions, transactionOptions = {}, url, }: Args): MongooseAdapterResult { @@ -106,17 +145,21 @@ export function mongooseAdapter({ // Mongoose-specific autoPluralization, + collectionOptions: collections || {}, collections: {}, connectOptions: connectOptions || {}, connection: undefined, count, disableIndexHints, globals: undefined, + globalsOptions: globals || {}, mongoMemoryServer: undefined, + schemaOptions: schemaOptions || {}, sessions: {}, transactionOptions: transactionOptions === false ? undefined : transactionOptions, url, versions: {}, + // DatabaseAdapter beginTransaction: transactionOptions ? beginTransaction : undefined, commitTransaction, diff --git a/packages/db-mongodb/src/init.ts b/packages/db-mongodb/src/init.ts index 68e09f4d3cc..4744e7e0e7d 100644 --- a/packages/db-mongodb/src/init.ts +++ b/packages/db-mongodb/src/init.ts @@ -19,20 +19,22 @@ import { getDBName } from './utilities/getDBName' export const init: Init = async function init(this: MongooseAdapter) { this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => { - const schema = buildCollectionSchema(collection, this.payload.config) + const schema = buildCollectionSchema(collection, this) if (collection.versions) { const versionModelName = getDBName({ config: collection, versions: true }) const versionCollectionFields = buildVersionCollectionFields(collection) - const versionSchema = buildSchema(this.payload.config, versionCollectionFields, { + const versionSchema = buildSchema(this, versionCollectionFields, { disableUnique: true, draftsEnabled: true, indexSortableFields: this.payload.config.indexSortableFields, options: { minimize: false, timestamps: false, + ...this.schemaOptions, + ...(this.collectionOptions[collection.slug]?.schemaOptions || {}), }, }) @@ -69,7 +71,7 @@ export const init: Init = async function init(this: MongooseAdapter) { } }) - const model = buildGlobalModel(this.payload.config) + const model = buildGlobalModel(this) this.globals = model this.payload.config.globals.forEach((global) => { @@ -78,13 +80,15 @@ export const init: Init = async function init(this: MongooseAdapter) { const versionGlobalFields = buildVersionGlobalFields(global) - const versionSchema = buildSchema(this.payload.config, versionGlobalFields, { + const versionSchema = buildSchema(this, versionGlobalFields, { disableUnique: true, draftsEnabled: true, indexSortableFields: this.payload.config.indexSortableFields, options: { minimize: false, timestamps: false, + ...this.schemaOptions, + ...(this.globalsOptions.schemaOptions || {}), }, }) diff --git a/packages/db-mongodb/src/models/buildCollectionSchema.ts b/packages/db-mongodb/src/models/buildCollectionSchema.ts index 44c95c8bc37..bad61422455 100644 --- a/packages/db-mongodb/src/models/buildCollectionSchema.ts +++ b/packages/db-mongodb/src/models/buildCollectionSchema.ts @@ -1,28 +1,29 @@ import type { PaginateOptions, Schema } from 'mongoose' -import type { SanitizedConfig } from 'payload/config' import type { SanitizedCollectionConfig } from 'payload/types' import paginate from 'mongoose-paginate-v2' +import type { MongooseAdapter } from '..' + import getBuildQueryPlugin from '../queries/buildQuery' import buildSchema from './buildSchema' const buildCollectionSchema = ( collection: SanitizedCollectionConfig, - config: SanitizedConfig, - schemaOptions = {}, + adapter: MongooseAdapter, ): Schema => { - const schema = buildSchema(config, collection.fields, { + const schema = buildSchema(adapter, collection.fields, { draftsEnabled: Boolean(typeof collection?.versions === 'object' && collection.versions.drafts), - indexSortableFields: config.indexSortableFields, + indexSortableFields: adapter.payload.config.indexSortableFields, options: { minimize: false, timestamps: collection.timestamps !== false, - ...schemaOptions, + ...adapter.schemaOptions, + ...(adapter.collectionOptions[collection.slug]?.schemaOptions || {}), }, }) - if (config.indexSortableFields && collection.timestamps !== false) { + if (adapter.payload.config.indexSortableFields && collection.timestamps !== false) { schema.index({ updatedAt: 1 }) schema.index({ createdAt: 1 }) } diff --git a/packages/db-mongodb/src/models/buildGlobalModel.ts b/packages/db-mongodb/src/models/buildGlobalModel.ts index e3c6f8e0dba..a167416e184 100644 --- a/packages/db-mongodb/src/models/buildGlobalModel.ts +++ b/packages/db-mongodb/src/models/buildGlobalModel.ts @@ -1,27 +1,34 @@ -import type { SanitizedConfig } from 'payload/config' - import mongoose from 'mongoose' +import type { MongooseAdapter } from '..' import type { GlobalModel } from '../types' import getBuildQueryPlugin from '../queries/buildQuery' import buildSchema from './buildSchema' -export const buildGlobalModel = (config: SanitizedConfig): GlobalModel | null => { - if (config.globals && config.globals.length > 0) { +export const buildGlobalModel = (adapter: MongooseAdapter): GlobalModel | null => { + if (adapter.payload.config.globals && adapter.payload.config.globals.length > 0) { const globalsSchema = new mongoose.Schema( {}, - { discriminatorKey: 'globalType', minimize: false, timestamps: true }, + { + discriminatorKey: 'globalType', + minimize: false, + ...adapter.schemaOptions, + ...(adapter.globalsOptions.schemaOptions || {}), + timestamps: true, + }, ) globalsSchema.plugin(getBuildQueryPlugin()) const Globals = mongoose.model('globals', globalsSchema, 'globals') as unknown as GlobalModel - Object.values(config.globals).forEach((globalConfig) => { - const globalSchema = buildSchema(config, globalConfig.fields, { + Object.values(adapter.payload.config.globals).forEach((globalConfig) => { + const globalSchema = buildSchema(adapter, globalConfig.fields, { options: { minimize: false, + ...adapter.schemaOptions, + ...(adapter.globalsOptions.schemaOptions || {}), }, }) Globals.discriminator(globalConfig.slug, globalSchema) diff --git a/packages/db-mongodb/src/models/buildSchema.ts b/packages/db-mongodb/src/models/buildSchema.ts index 23067f64d4c..89d56b6c262 100644 --- a/packages/db-mongodb/src/models/buildSchema.ts +++ b/packages/db-mongodb/src/models/buildSchema.ts @@ -40,6 +40,8 @@ import { tabHasName, } from 'payload/types' +import type { MongooseAdapter } from '..' + export type BuildSchemaOptions = { allowIDField?: boolean disableUnique?: boolean @@ -51,7 +53,7 @@ export type BuildSchemaOptions = { type FieldSchemaGenerator = ( field: Field, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ) => void @@ -90,10 +92,10 @@ const localizeSchema = ( if (fieldIsLocalized(entity) && localization && Array.isArray(localization.locales)) { return { type: localization.localeCodes.reduce( - (localeSchema, locale) => ({ - ...localeSchema, - [locale]: schema, - }), + (localeSchema, locale) => { + localeSchema[locale] = schema + return localeSchema + }, { _id: false, }, @@ -105,7 +107,7 @@ const localizeSchema = ( } const buildSchema = ( - config: SanitizedConfig, + adapter: MongooseAdapter, configFields: Field[], buildSchemaOptions: BuildSchemaOptions = {}, ): Schema => { @@ -133,7 +135,7 @@ const buildSchema = ( const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[field.type] if (addFieldSchema) { - addFieldSchema(field, schema, config, buildSchemaOptions) + addFieldSchema(field, schema, adapter, buildSchemaOptions) } } }) @@ -145,20 +147,22 @@ const fieldToSchemaMap: Record = { array: ( field: ArrayField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ) => { const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: [ - buildSchema(config, field.fields, { + buildSchema(adapter, field.fields, { allowIDField: true, disableUnique: buildSchemaOptions.disableUnique, draftsEnabled: buildSchemaOptions.draftsEnabled, options: { + minimize: false, + ...(buildSchemaOptions.options || {}), _id: false, id: false, - minimize: false, + timestamps: false, }, }), ], @@ -166,36 +170,54 @@ const fieldToSchemaMap: Record = { } schema.add({ - [field.name]: localizeSchema(field, baseSchema, config.localization), + [field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization), }) }, blocks: ( field: BlockField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const fieldSchema = { - type: [new Schema({}, { _id: false, discriminatorKey: 'blockType' })], + type: [ + new Schema( + {}, + { + _id: false, + discriminatorKey: 'blockType', + ...(buildSchemaOptions.options || {}), + timestamps: false, + }, + ), + ], default: undefined, } schema.add({ - [field.name]: localizeSchema(field, fieldSchema, config.localization), + [field.name]: localizeSchema(field, fieldSchema, adapter.payload.config.localization), }) field.blocks.forEach((blockItem: Block) => { - const blockSchema = new Schema({}, { _id: false, id: false }) + const blockSchema = new Schema( + {}, + { + ...(buildSchemaOptions.options || {}), + _id: false, + id: false, + timestamps: false, + }, + ) blockItem.fields.forEach((blockField) => { const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[blockField.type] if (addFieldSchema) { - addFieldSchema(blockField, blockSchema, config, buildSchemaOptions) + addFieldSchema(blockField, blockSchema, adapter, buildSchemaOptions) } }) - if (field.localized && config.localization) { - config.localization.localeCodes.forEach((localeCode) => { + if (field.localized && adapter.payload.config.localization) { + adapter.payload.config.localization.localeCodes.forEach((localeCode) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error Possible incorrect typing in mongoose types, this works schema.path(`${field.name}.${localeCode}`).discriminator(blockItem.slug, blockSchema) @@ -210,69 +232,69 @@ const fieldToSchemaMap: Record = { checkbox: ( field: CheckboxField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: Boolean } schema.add({ - [field.name]: localizeSchema(field, baseSchema, config.localization), + [field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization), }) }, code: ( field: CodeField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String } schema.add({ - [field.name]: localizeSchema(field, baseSchema, config.localization), + [field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization), }) }, collapsible: ( field: CollapsibleField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { field.fields.forEach((subField: Field) => { const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type] if (addFieldSchema) { - addFieldSchema(subField, schema, config, buildSchemaOptions) + addFieldSchema(subField, schema, adapter, buildSchemaOptions) } }) }, date: ( field: DateField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: Date } schema.add({ - [field.name]: localizeSchema(field, baseSchema, config.localization), + [field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization), }) }, email: ( field: EmailField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String } schema.add({ - [field.name]: localizeSchema(field, baseSchema, config.localization), + [field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization), }) }, group: ( field: GroupField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const formattedBaseSchema = formatBaseSchema(field, buildSchemaOptions) @@ -285,38 +307,40 @@ const fieldToSchemaMap: Record = { const baseSchema = { ...formattedBaseSchema, - type: buildSchema(config, field.fields, { + type: buildSchema(adapter, field.fields, { disableUnique: buildSchemaOptions.disableUnique, draftsEnabled: buildSchemaOptions.draftsEnabled, indexSortableFields, options: { + minimize: false, + ...(buildSchemaOptions.options || {}), _id: false, id: false, - minimize: false, + timestamps: false, }, }), } schema.add({ - [field.name]: localizeSchema(field, baseSchema, config.localization), + [field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization), }) }, json: ( field: JSONField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: Schema.Types.Mixed } schema.add({ - [field.name]: localizeSchema(field, baseSchema, config.localization), + [field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization), }) }, number: ( field: NumberField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const baseSchema = { @@ -325,13 +349,13 @@ const fieldToSchemaMap: Record = { } schema.add({ - [field.name]: localizeSchema(field, baseSchema, config.localization), + [field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization), }) }, point: ( field: PointField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const baseSchema: SchemaTypeOptions = { @@ -350,7 +374,7 @@ const fieldToSchemaMap: Record = { } schema.add({ - [field.name]: localizeSchema(field, baseSchema, config.localization), + [field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization), }) if (field.index === true || field.index === undefined) { @@ -359,8 +383,8 @@ const fieldToSchemaMap: Record = { indexOptions.sparse = true indexOptions.unique = true } - if (field.localized && config.localization) { - config.localization.locales.forEach((locale) => { + if (field.localized && adapter.payload.config.localization) { + adapter.payload.config.localization.locales.forEach((locale) => { schema.index({ [`${field.name}.${locale.code}`]: '2dsphere' }, indexOptions) }) } else { @@ -371,7 +395,7 @@ const fieldToSchemaMap: Record = { radio: ( field: RadioField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const baseSchema = { @@ -384,21 +408,21 @@ const fieldToSchemaMap: Record = { } schema.add({ - [field.name]: localizeSchema(field, baseSchema, config.localization), + [field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization), }) }, relationship: ( field: RelationshipField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ) => { const hasManyRelations = Array.isArray(field.relationTo) let schemaToReturn: { [key: string]: any } = {} - if (field.localized && config.localization) { + if (field.localized && adapter.payload.config.localization) { schemaToReturn = { - type: config.localization.localeCodes.reduce((locales, locale) => { + type: adapter.payload.config.localization.localeCodes.reduce((locales, locale) => { let localeSchema: { [key: string]: any } = {} if (hasManyRelations) { @@ -467,33 +491,33 @@ const fieldToSchemaMap: Record = { richText: ( field: RichTextField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: Schema.Types.Mixed } schema.add({ - [field.name]: localizeSchema(field, baseSchema, config.localization), + [field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization), }) }, row: ( field: RowField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { field.fields.forEach((subField: Field) => { const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type] if (addFieldSchema) { - addFieldSchema(subField, schema, config, buildSchemaOptions) + addFieldSchema(subField, schema, adapter, buildSchemaOptions) } }) }, select: ( field: SelectField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const baseSchema = { @@ -513,39 +537,41 @@ const fieldToSchemaMap: Record = { [field.name]: localizeSchema( field, field.hasMany ? [baseSchema] : baseSchema, - config.localization, + adapter.payload.config.localization, ), }) }, tabs: ( field: TabsField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { field.tabs.forEach((tab) => { if (tabHasName(tab)) { const baseSchema = { - type: buildSchema(config, tab.fields, { + type: buildSchema(adapter, tab.fields, { disableUnique: buildSchemaOptions.disableUnique, draftsEnabled: buildSchemaOptions.draftsEnabled, options: { + minimize: false, + ...(buildSchemaOptions.options || {}), _id: false, id: false, - minimize: false, + timestamps: false, }, }), } schema.add({ - [tab.name]: localizeSchema(tab, baseSchema, config.localization), + [tab.name]: localizeSchema(tab, baseSchema, adapter.payload.config.localization), }) } else { tab.fields.forEach((subField: Field) => { const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type] if (addFieldSchema) { - addFieldSchema(subField, schema, config, buildSchemaOptions) + addFieldSchema(subField, schema, adapter, buildSchemaOptions) } }) } @@ -554,7 +580,7 @@ const fieldToSchemaMap: Record = { text: ( field: TextField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const baseSchema = { @@ -563,25 +589,25 @@ const fieldToSchemaMap: Record = { } schema.add({ - [field.name]: localizeSchema(field, baseSchema, config.localization), + [field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization), }) }, textarea: ( field: TextareaField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String } schema.add({ - [field.name]: localizeSchema(field, baseSchema, config.localization), + [field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization), }) }, upload: ( field: UploadField, schema: Schema, - config: SanitizedConfig, + adapter: MongooseAdapter, buildSchemaOptions: BuildSchemaOptions, ): void => { const baseSchema = { @@ -591,7 +617,7 @@ const fieldToSchemaMap: Record = { } schema.add({ - [field.name]: localizeSchema(field, baseSchema, config.localization), + [field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization), }) }, } diff --git a/packages/db-mongodb/src/updateGlobal.ts b/packages/db-mongodb/src/updateGlobal.ts index e6e67dad1fb..d21d8d0d1d3 100644 --- a/packages/db-mongodb/src/updateGlobal.ts +++ b/packages/db-mongodb/src/updateGlobal.ts @@ -18,6 +18,7 @@ export const updateGlobal: UpdateGlobal = async function updateGlobal( } let result + result = await Model.findOneAndUpdate({ globalType: slug }, data, options) result = JSON.parse(JSON.stringify(result)) diff --git a/packages/payload/src/fields/hooks/beforeValidate/promise.ts b/packages/payload/src/fields/hooks/beforeValidate/promise.ts index 557064e90e4..8db1ad3b28c 100644 --- a/packages/payload/src/fields/hooks/beforeValidate/promise.ts +++ b/packages/payload/src/fields/hooks/beforeValidate/promise.ts @@ -23,6 +23,7 @@ type Args = { req: PayloadRequest siblingData: Record siblingDoc: Record + siblingDocKeys: Set } // This function is responsible for the following actions, in order: @@ -45,8 +46,18 @@ export const promise = async ({ req, siblingData, siblingDoc, + siblingDocKeys, }: Args): Promise => { if (fieldAffectsData(field)) { + // Remove the key from siblingDocKeys + // the goal is to keep any existing data present + // before updating, for users that want to maintain + // external data in the same collections as Payload manages, + // without having fields defined for them + if (siblingDocKeys.has(field.name)) { + siblingDocKeys.delete(field.name) + } + if (field.name === 'id') { if (field.type === 'number' && typeof siblingData[field.name] === 'string') { const value = siblingData[field.name] as string @@ -368,6 +379,7 @@ export const promise = async ({ req, siblingData, siblingDoc, + siblingDocKeys, }) break @@ -376,7 +388,10 @@ export const promise = async ({ case 'tab': { let tabSiblingData let tabSiblingDoc - if (tabHasName(field)) { + + const isNamedTab = tabHasName(field) + + if (isNamedTab) { if (typeof siblingData[field.name] !== 'object') siblingData[field.name] = {} if (typeof siblingDoc[field.name] !== 'object') siblingDoc[field.name] = {} @@ -400,6 +415,7 @@ export const promise = async ({ req, siblingData: tabSiblingData, siblingDoc: tabSiblingDoc, + siblingDocKeys: isNamedTab ? undefined : siblingDocKeys, }) break @@ -419,6 +435,7 @@ export const promise = async ({ req, siblingData, siblingDoc, + siblingDocKeys, }) break diff --git a/packages/payload/src/fields/hooks/beforeValidate/traverseFields.ts b/packages/payload/src/fields/hooks/beforeValidate/traverseFields.ts index 1e72bf9c1a4..c0dedd0d875 100644 --- a/packages/payload/src/fields/hooks/beforeValidate/traverseFields.ts +++ b/packages/payload/src/fields/hooks/beforeValidate/traverseFields.ts @@ -18,6 +18,7 @@ type Args = { req: PayloadRequest siblingData: Record siblingDoc: Record + siblingDocKeys?: Set } export const traverseFields = async ({ @@ -33,8 +34,11 @@ export const traverseFields = async ({ req, siblingData, siblingDoc, + siblingDocKeys: incomingSiblingDocKeys, }: Args): Promise => { const promises = [] + const siblingDocKeys = incomingSiblingDocKeys || new Set(Object.keys(siblingDoc)) + fields.forEach((field) => { promises.push( promise({ @@ -50,8 +54,19 @@ export const traverseFields = async ({ req, siblingData, siblingDoc, + siblingDocKeys, }), ) }) + await Promise.all(promises) + + // For any siblingDocKeys that have not been deleted, + // we will move the data to the siblingData object + // to preserve it + siblingDocKeys.forEach((key) => { + if (!['createdAt', 'globalType', 'id', 'updatedAt'].includes(key)) { + siblingData[key] = siblingDoc[key] + } + }) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a5c81e6587..75840812fba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1550,6 +1550,7 @@ packages: /@aws-crypto/ie11-detection@3.0.0: resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} + requiresBuild: true dependencies: tslib: 1.14.1 @@ -1566,6 +1567,7 @@ packages: /@aws-crypto/sha256-browser@3.0.0: resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==} + requiresBuild: true dependencies: '@aws-crypto/ie11-detection': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 @@ -1586,6 +1588,7 @@ packages: /@aws-crypto/sha256-js@3.0.0: resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} + requiresBuild: true dependencies: '@aws-crypto/util': 3.0.0 '@aws-sdk/types': 3.535.0 @@ -1593,6 +1596,7 @@ packages: /@aws-crypto/supports-web-crypto@3.0.0: resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==} + requiresBuild: true dependencies: tslib: 1.14.1 @@ -1606,6 +1610,7 @@ packages: /@aws-crypto/util@3.0.0: resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} + requiresBuild: true dependencies: '@aws-sdk/types': 3.535.0 '@aws-sdk/util-utf8-browser': 3.259.0 @@ -1614,6 +1619,7 @@ packages: /@aws-sdk/client-cognito-identity@3.563.0: resolution: {integrity: sha512-hBgxRHmy099lEEJVbAP807HiCZO2RHPldG1ppwyQdPSqGMisAkaw9sgHog/tCSaSty2pkG/YvZtiH9mrhcYwpA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 @@ -1725,6 +1731,7 @@ packages: /@aws-sdk/client-sso-oidc@3.556.0(@aws-sdk/credential-provider-node@3.563.0): resolution: {integrity: sha512-AXKd2TB6nNrksu+OfmHl8uI07PdgzOo4o8AxoRO8SHlwoMAGvcT9optDGVSYoVfgOKTymCoE7h8/UoUfPc11wQ==} engines: {node: '>=14.0.0'} + requiresBuild: true peerDependencies: '@aws-sdk/credential-provider-node': ^3.556.0 dependencies: @@ -1774,6 +1781,7 @@ packages: /@aws-sdk/client-sso@3.556.0: resolution: {integrity: sha512-unXdWS7uvHqCcOyC1de+Fr8m3F2vMg2m24GPea0bg7rVGTYmiyn9mhUX11VCt+ozydrw+F50FQwL6OqoqPocmw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 @@ -1819,6 +1827,7 @@ packages: /@aws-sdk/client-sts@3.556.0(@aws-sdk/credential-provider-node@3.563.0): resolution: {integrity: sha512-TsK3js7Suh9xEmC886aY+bv0KdLLYtzrcmVt6sJ/W6EnDXYQhBuKYFhp03NrN2+vSvMGpqJwR62DyfKe1G0QzQ==} engines: {node: '>=14.0.0'} + requiresBuild: true peerDependencies: '@aws-sdk/credential-provider-node': ^3.556.0 dependencies: @@ -1867,6 +1876,7 @@ packages: /@aws-sdk/core@3.556.0: resolution: {integrity: sha512-vJaSaHw2kPQlo11j/Rzuz0gk1tEaKdz+2ser0f0qZ5vwFlANjt08m/frU17ctnVKC1s58bxpctO/1P894fHLrA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/core': 1.4.2 '@smithy/protocol-http': 3.3.0 @@ -1879,6 +1889,7 @@ packages: /@aws-sdk/credential-provider-cognito-identity@3.563.0: resolution: {integrity: sha512-FnvqvH+tLfJKqMJrUjuV1irWb5d++ByeX/3XPj8o/QmajhwCOEJ9akiEEGrawKnssqJWVPX6RIm+8XsiIzmduw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/client-cognito-identity': 3.563.0 '@aws-sdk/types': 3.535.0 @@ -1891,6 +1902,7 @@ packages: /@aws-sdk/credential-provider-env@3.535.0: resolution: {integrity: sha512-XppwO8c0GCGSAvdzyJOhbtktSEaShg14VJKg8mpMa1XcgqzmcqqHQjtDWbx5rZheY1VdpXZhpEzJkB6LpQejpA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.535.0 '@smithy/property-provider': 2.2.0 @@ -1900,6 +1912,7 @@ packages: /@aws-sdk/credential-provider-http@3.552.0: resolution: {integrity: sha512-vsmu7Cz1i45pFEqzVb4JcFmAmVnWFNLsGheZc8SCptlqCO5voETrZZILHYIl4cjKkSDk3pblBOf0PhyjqWW6WQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.535.0 '@smithy/fetch-http-handler': 2.5.0 @@ -1914,6 +1927,7 @@ packages: /@aws-sdk/credential-provider-ini@3.556.0(@aws-sdk/credential-provider-node@3.563.0): resolution: {integrity: sha512-0Nz4ErOlXhe3muxWYMbPwRMgfKmVbBp36BAE2uv/z5wTbfdBkcgUwaflEvlKCLUTdHzuZsQk+BFS/gVyaUeOuA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/client-sts': 3.556.0(@aws-sdk/credential-provider-node@3.563.0) '@aws-sdk/credential-provider-env': 3.535.0 @@ -1933,6 +1947,7 @@ packages: /@aws-sdk/credential-provider-node@3.563.0: resolution: {integrity: sha512-cNGT93uuAzM2ZnWT0rHbT/bjnVRCIQDTi8ylnvNpsOj+zFCn2q2eQAh6fgACPjKhCA7Szc38AX0hH2PDXH6+yg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/credential-provider-env': 3.535.0 '@aws-sdk/credential-provider-http': 3.552.0 @@ -1952,6 +1967,7 @@ packages: /@aws-sdk/credential-provider-process@3.535.0: resolution: {integrity: sha512-9O1OaprGCnlb/kYl8RwmH7Mlg8JREZctB8r9sa1KhSsWFq/SWO0AuJTyowxD7zL5PkeS4eTvzFFHWCa3OO5epA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.535.0 '@smithy/property-provider': 2.2.0 @@ -1962,6 +1978,7 @@ packages: /@aws-sdk/credential-provider-sso@3.556.0(@aws-sdk/credential-provider-node@3.563.0): resolution: {integrity: sha512-ETuBgcnpfxqadEAqhQFWpKoV1C/NAgvs5CbBc5EJbelJ8f4prTdErIHjrRtVT8c02MXj92QwczsiNYd5IoOqyw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/client-sso': 3.556.0 '@aws-sdk/token-providers': 3.556.0(@aws-sdk/credential-provider-node@3.563.0) @@ -1977,6 +1994,7 @@ packages: /@aws-sdk/credential-provider-web-identity@3.556.0(@aws-sdk/credential-provider-node@3.563.0): resolution: {integrity: sha512-R/YAL8Uh8i+dzVjzMnbcWLIGeeRi2mioHVGnVF+minmaIkCiQMZg2HPrdlKm49El+RljT28Nl5YHRuiqzEIwMA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/client-sts': 3.556.0(@aws-sdk/credential-provider-node@3.563.0) '@aws-sdk/types': 3.535.0 @@ -1990,6 +2008,7 @@ packages: /@aws-sdk/credential-providers@3.563.0: resolution: {integrity: sha512-lXZeYWJw3AWbY8M5xxY3ehUyP7L+UL/snQgEgHTVhdDbLrSF+lG06sFI0UPNxtlc85pkVYaKFHPkiLdGbdIGRA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/client-cognito-identity': 3.563.0 '@aws-sdk/client-sso': 3.556.0 @@ -2062,6 +2081,7 @@ packages: /@aws-sdk/middleware-host-header@3.535.0: resolution: {integrity: sha512-0h6TWjBWtDaYwHMQJI9ulafeS4lLaw1vIxRjbpH0svFRt6Eve+Sy8NlVhECfTU2hNz/fLubvrUxsXoThaLBIew==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.535.0 '@smithy/protocol-http': 3.3.0 @@ -2079,6 +2099,7 @@ packages: /@aws-sdk/middleware-logger@3.535.0: resolution: {integrity: sha512-huNHpONOrEDrdRTvSQr1cJiRMNf0S52NDXtaPzdxiubTkP+vni2MohmZANMOai/qT0olmEVX01LhZ0ZAOgmg6A==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.535.0 '@smithy/types': 2.12.0 @@ -2087,6 +2108,7 @@ packages: /@aws-sdk/middleware-recursion-detection@3.535.0: resolution: {integrity: sha512-am2qgGs+gwqmR4wHLWpzlZ8PWhm4ktj5bYSgDrsOfjhdBlWNxvPoID9/pDAz5RWL48+oH7I6SQzMqxXsFDikrw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.535.0 '@smithy/protocol-http': 3.3.0 @@ -2130,6 +2152,7 @@ packages: /@aws-sdk/middleware-user-agent@3.540.0: resolution: {integrity: sha512-8Rd6wPeXDnOYzWj1XCmOKcx/Q87L0K1/EHqOBocGjLVbN3gmRxBvpmR1pRTjf7IsWfnnzN5btqtcAkfDPYQUMQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.535.0 '@aws-sdk/util-endpoints': 3.540.0 @@ -2140,6 +2163,7 @@ packages: /@aws-sdk/region-config-resolver@3.535.0: resolution: {integrity: sha512-IXOznDiaItBjsQy4Fil0kzX/J3HxIOknEphqHbOfUf+LpA5ugcsxuQQONrbEQusCBnfJyymrldBvBhFmtlU9Wg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.535.0 '@smithy/node-config-provider': 2.3.0 @@ -2162,6 +2186,7 @@ packages: /@aws-sdk/token-providers@3.556.0(@aws-sdk/credential-provider-node@3.563.0): resolution: {integrity: sha512-tvIiugNF0/+2wfuImMrpKjXMx4nCnFWQjQvouObny+wrif/PGqqQYrybwxPJDvzbd965bu1I+QuSv85/ug7xsg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/client-sso-oidc': 3.556.0(@aws-sdk/credential-provider-node@3.563.0) '@aws-sdk/types': 3.535.0 @@ -2176,6 +2201,7 @@ packages: /@aws-sdk/types@3.535.0: resolution: {integrity: sha512-aY4MYfduNj+sRR37U7XxYR8wemfbKP6lx00ze2M2uubn7mZotuVrWYAafbMSXrdEMSToE5JDhr28vArSOoLcSg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.12.0 tslib: 2.6.2 @@ -2189,6 +2215,7 @@ packages: /@aws-sdk/util-endpoints@3.540.0: resolution: {integrity: sha512-1kMyQFAWx6f8alaI6UT65/5YW/7pDWAKAdNwL6vuJLea03KrZRX3PMoONOSJpAS5m3Ot7HlWZvf3wZDNTLELZw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@aws-sdk/types': 3.535.0 '@smithy/types': 2.12.0 @@ -2198,11 +2225,13 @@ packages: /@aws-sdk/util-locate-window@3.535.0: resolution: {integrity: sha512-PHJ3SL6d2jpcgbqdgiPxkXpu7Drc2PYViwxSIqvvMKhDwzSB1W3mMvtpzwKM4IE7zLFodZo0GKjJ9AsoXndXhA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.2 /@aws-sdk/util-user-agent-browser@3.535.0: resolution: {integrity: sha512-RWMcF/xV5n+nhaA/Ff5P3yNP3Kur/I+VNZngog4TEs92oB/nwOdAg/2JL8bVAhUbMrjTjpwm7PItziYFQoqyig==} + requiresBuild: true dependencies: '@aws-sdk/types': 3.535.0 '@smithy/types': 2.12.0 @@ -2212,6 +2241,7 @@ packages: /@aws-sdk/util-user-agent-node@3.535.0: resolution: {integrity: sha512-dRek0zUuIT25wOWJlsRm97nTkUlh1NDcLsQZIN2Y8KxhwoXXWtJs5vaDPT+qAg+OpcNj80i1zLR/CirqlFg/TQ==} engines: {node: '>=14.0.0'} + requiresBuild: true peerDependencies: aws-crt: '>=1.0.0' peerDependenciesMeta: @@ -2225,6 +2255,7 @@ packages: /@aws-sdk/util-utf8-browser@3.259.0: resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} + requiresBuild: true dependencies: tslib: 2.6.2 @@ -4808,6 +4839,7 @@ packages: /@smithy/abort-controller@2.2.0: resolution: {integrity: sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.12.0 tslib: 2.6.2 @@ -4826,6 +4858,7 @@ packages: /@smithy/config-resolver@2.2.0: resolution: {integrity: sha512-fsiMgd8toyUba6n1WRmr+qACzXltpdDkPTAaDqc8QqPBUzO+/JKwL6bUBseHVi8tu9l+3JOK+tSf7cay+4B3LA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/node-config-provider': 2.3.0 '@smithy/types': 2.12.0 @@ -4836,6 +4869,7 @@ packages: /@smithy/core@1.4.2: resolution: {integrity: sha512-2fek3I0KZHWJlRLvRTqxTEri+qV0GRHrJIoLFuBMZB4EMg4WgeBGfF0X6abnrNYpq55KJ6R4D6x4f0vLnhzinA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/middleware-endpoint': 2.5.1 '@smithy/middleware-retry': 2.3.1 @@ -4849,6 +4883,7 @@ packages: /@smithy/credential-provider-imds@2.3.0: resolution: {integrity: sha512-BWB9mIukO1wjEOo1Ojgl6LrG4avcaC7T/ZP6ptmAaW4xluhSIPZhY+/PI5YKzlk+jsm+4sQZB45Bt1OfMeQa3w==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/node-config-provider': 2.3.0 '@smithy/property-provider': 2.2.0 @@ -4897,6 +4932,7 @@ packages: /@smithy/fetch-http-handler@2.5.0: resolution: {integrity: sha512-BOWEBeppWhLn/no/JxUL/ghTfANTjT7kg3Ww2rPqTUY9R4yHPXxJ9JhMe3Z03LN3aPwiwlpDIUcVw1xDyHqEhw==} + requiresBuild: true dependencies: '@smithy/protocol-http': 3.3.0 '@smithy/querystring-builder': 2.2.0 @@ -4915,6 +4951,7 @@ packages: /@smithy/hash-node@2.2.0: resolution: {integrity: sha512-zLWaC/5aWpMrHKpoDF6nqpNtBhlAYKF/7+9yMN7GpdR8CzohnWfGtMznPybnwSS8saaXBMxIGwJqR4HmRp6b3g==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.12.0 '@smithy/util-buffer-from': 2.2.0 @@ -4931,6 +4968,7 @@ packages: /@smithy/invalid-dependency@2.2.0: resolution: {integrity: sha512-nEDASdbKFKPXN2O6lOlTgrEEOO9NHIeO+HVvZnkqc8h5U9g3BIhWsvzFo+UcUbliMHvKNPD/zVxDrkP1Sbgp8Q==} + requiresBuild: true dependencies: '@smithy/types': 2.12.0 tslib: 2.6.2 @@ -4938,6 +4976,7 @@ packages: /@smithy/is-array-buffer@2.2.0: resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.2 @@ -4951,6 +4990,7 @@ packages: /@smithy/middleware-content-length@2.2.0: resolution: {integrity: sha512-5bl2LG1Ah/7E5cMSC+q+h3IpVHMeOkG0yLRyQT1p2aMJkSrZG7RlXHPuAgb7EyaFeidKEnnd/fNaLLaKlHGzDQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/protocol-http': 3.3.0 '@smithy/types': 2.12.0 @@ -4959,6 +4999,7 @@ packages: /@smithy/middleware-endpoint@2.5.1: resolution: {integrity: sha512-1/8kFp6Fl4OsSIVTWHnNjLnTL8IqpIb/D3sTSczrKFnrE9VMNWxnrRKNvpUHOJ6zpGD5f62TPm7+17ilTJpiCQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/middleware-serde': 2.3.0 '@smithy/node-config-provider': 2.3.0 @@ -4971,6 +5012,7 @@ packages: /@smithy/middleware-retry@2.3.1: resolution: {integrity: sha512-P2bGufFpFdYcWvqpyqqmalRtwFUNUA8vHjJR5iGqbfR6mp65qKOLcUd6lTr4S9Gn/enynSrSf3p3FVgVAf6bXA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/node-config-provider': 2.3.0 '@smithy/protocol-http': 3.3.0 @@ -4985,6 +5027,7 @@ packages: /@smithy/middleware-serde@2.3.0: resolution: {integrity: sha512-sIADe7ojwqTyvEQBe1nc/GXB9wdHhi9UwyX0lTyttmUWDJLP655ZYE1WngnNyXREme8I27KCaUhyhZWRXL0q7Q==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.12.0 tslib: 2.6.2 @@ -4992,6 +5035,7 @@ packages: /@smithy/middleware-stack@2.2.0: resolution: {integrity: sha512-Qntc3jrtwwrsAC+X8wms8zhrTr0sFXnyEGhZd9sLtsJ/6gGQKFzNB+wWbOcpJd7BR8ThNCoKt76BuQahfMvpeA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.12.0 tslib: 2.6.2 @@ -4999,6 +5043,7 @@ packages: /@smithy/node-config-provider@2.3.0: resolution: {integrity: sha512-0elK5/03a1JPWMDPaS726Iw6LpQg80gFut1tNpPfxFuChEEklo2yL823V94SpTZTxmKlXFtFgsP55uh3dErnIg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 @@ -5008,6 +5053,7 @@ packages: /@smithy/node-http-handler@2.5.0: resolution: {integrity: sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/abort-controller': 2.2.0 '@smithy/protocol-http': 3.3.0 @@ -5018,6 +5064,7 @@ packages: /@smithy/property-provider@2.2.0: resolution: {integrity: sha512-+xiil2lFhtTRzXkx8F053AV46QnIw6e7MV8od5Mi68E1ICOjCeCHw2XfLnDEUHnT9WGUIkwcqavXjfwuJbGlpg==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.12.0 tslib: 2.6.2 @@ -5025,6 +5072,7 @@ packages: /@smithy/protocol-http@3.3.0: resolution: {integrity: sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.12.0 tslib: 2.6.2 @@ -5032,6 +5080,7 @@ packages: /@smithy/querystring-builder@2.2.0: resolution: {integrity: sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.12.0 '@smithy/util-uri-escape': 2.2.0 @@ -5040,6 +5089,7 @@ packages: /@smithy/querystring-parser@2.2.0: resolution: {integrity: sha512-BvHCDrKfbG5Yhbpj4vsbuPV2GgcpHiAkLeIlcA1LtfpMz3jrqizP1+OguSNSj1MwBHEiN+jwNisXLGdajGDQJA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.12.0 tslib: 2.6.2 @@ -5047,12 +5097,14 @@ packages: /@smithy/service-error-classification@2.1.5: resolution: {integrity: sha512-uBDTIBBEdAQryvHdc5W8sS5YX7RQzF683XrHePVdFmAgKiMofU15FLSM0/HU03hKTnazdNRFa0YHS7+ArwoUSQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.12.0 /@smithy/shared-ini-file-loader@2.4.0: resolution: {integrity: sha512-WyujUJL8e1B6Z4PBfAqC/aGY1+C7T0w20Gih3yrvJSk97gpiVfB+y7c46T4Nunk+ZngLq0rOIdeVeIklk0R3OA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.12.0 tslib: 2.6.2 @@ -5060,6 +5112,7 @@ packages: /@smithy/signature-v4@2.3.0: resolution: {integrity: sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/is-array-buffer': 2.2.0 '@smithy/types': 2.12.0 @@ -5072,6 +5125,7 @@ packages: /@smithy/smithy-client@2.5.1: resolution: {integrity: sha512-jrbSQrYCho0yDaaf92qWgd+7nAeap5LtHTI51KXqmpIFCceKU3K9+vIVTUH72bOJngBMqa4kyu1VJhRcSrk/CQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/middleware-endpoint': 2.5.1 '@smithy/middleware-stack': 2.2.0 @@ -5083,11 +5137,13 @@ packages: /@smithy/types@2.12.0: resolution: {integrity: sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.2 /@smithy/url-parser@2.2.0: resolution: {integrity: sha512-hoA4zm61q1mNTpksiSWp2nEl1dt3j726HdRhiNgVJQMj7mLp7dprtF57mOB6JvEk/x9d2bsuL5hlqZbBuHQylQ==} + requiresBuild: true dependencies: '@smithy/querystring-parser': 2.2.0 '@smithy/types': 2.12.0 @@ -5096,6 +5152,7 @@ packages: /@smithy/util-base64@2.3.0: resolution: {integrity: sha512-s3+eVwNeJuXUwuMbusncZNViuhv2LjVJ1nMwTqSA0XAC7gjKhqqxRdJPhR8+YrkoZ9IiIbFk/yK6ACe/xlF+hw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/util-buffer-from': 2.2.0 '@smithy/util-utf8': 2.3.0 @@ -5103,18 +5160,21 @@ packages: /@smithy/util-body-length-browser@2.2.0: resolution: {integrity: sha512-dtpw9uQP7W+n3vOtx0CfBD5EWd7EPdIdsQnWTDoFf77e3VUf05uA7R7TGipIo8e4WL2kuPdnsr3hMQn9ziYj5w==} + requiresBuild: true dependencies: tslib: 2.6.2 /@smithy/util-body-length-node@2.3.0: resolution: {integrity: sha512-ITWT1Wqjubf2CJthb0BuT9+bpzBfXeMokH/AAa5EJQgbv9aPMVfnM76iFIZVFf50hYXGbtiV71BHAthNWd6+dw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.2 /@smithy/util-buffer-from@2.2.0: resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/is-array-buffer': 2.2.0 tslib: 2.6.2 @@ -5122,12 +5182,14 @@ packages: /@smithy/util-config-provider@2.3.0: resolution: {integrity: sha512-HZkzrRcuFN1k70RLqlNK4FnPXKOpkik1+4JaBoHNJn+RnJGYqaa3c5/+XtLOXhlKzlRgNvyaLieHTW2VwGN0VQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.2 /@smithy/util-defaults-mode-browser@2.2.1: resolution: {integrity: sha512-RtKW+8j8skk17SYowucwRUjeh4mCtnm5odCL0Lm2NtHQBsYKrNW0od9Rhopu9wF1gHMfHeWF7i90NwBz/U22Kw==} engines: {node: '>= 10.0.0'} + requiresBuild: true dependencies: '@smithy/property-provider': 2.2.0 '@smithy/smithy-client': 2.5.1 @@ -5138,6 +5200,7 @@ packages: /@smithy/util-defaults-mode-node@2.3.1: resolution: {integrity: sha512-vkMXHQ0BcLFysBMWgSBLSk3+leMpFSyyFj8zQtv5ZyUBx8/owVh1/pPEkzmW/DR/Gy/5c8vjLDD9gZjXNKbrpA==} engines: {node: '>= 10.0.0'} + requiresBuild: true dependencies: '@smithy/config-resolver': 2.2.0 '@smithy/credential-provider-imds': 2.3.0 @@ -5150,6 +5213,7 @@ packages: /@smithy/util-endpoints@1.2.0: resolution: {integrity: sha512-BuDHv8zRjsE5zXd3PxFXFknzBG3owCpjq8G3FcsXW3CykYXuEqM3nTSsmLzw5q+T12ZYuDlVUZKBdpNbhVtlrQ==} engines: {node: '>= 14.0.0'} + requiresBuild: true dependencies: '@smithy/node-config-provider': 2.3.0 '@smithy/types': 2.12.0 @@ -5158,12 +5222,14 @@ packages: /@smithy/util-hex-encoding@2.2.0: resolution: {integrity: sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.2 /@smithy/util-middleware@2.2.0: resolution: {integrity: sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/types': 2.12.0 tslib: 2.6.2 @@ -5171,6 +5237,7 @@ packages: /@smithy/util-retry@2.2.0: resolution: {integrity: sha512-q9+pAFPTfftHXRytmZ7GzLFFrEGavqapFc06XxzZFcSIGERXMerXxCitjOG1prVDR9QdjqotF40SWvbqcCpf8g==} engines: {node: '>= 14.0.0'} + requiresBuild: true dependencies: '@smithy/service-error-classification': 2.1.5 '@smithy/types': 2.12.0 @@ -5179,6 +5246,7 @@ packages: /@smithy/util-stream@2.2.0: resolution: {integrity: sha512-17faEXbYWIRst1aU9SvPZyMdWmqIrduZjVOqCPMIsWFNxs5yQQgFrJL6b2SdiCzyW9mJoDjFtgi53xx7EH+BXA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/fetch-http-handler': 2.5.0 '@smithy/node-http-handler': 2.5.0 @@ -5192,12 +5260,14 @@ packages: /@smithy/util-uri-escape@2.2.0: resolution: {integrity: sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: tslib: 2.6.2 /@smithy/util-utf8@2.3.0: resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} engines: {node: '>=14.0.0'} + requiresBuild: true dependencies: '@smithy/util-buffer-from': 2.2.0 tslib: 2.6.2 @@ -7351,6 +7421,7 @@ packages: /bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + requiresBuild: true /boxen@7.1.1: resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==} @@ -10172,6 +10243,7 @@ packages: /fast-xml-parser@4.2.5: resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} hasBin: true + requiresBuild: true dependencies: strnum: 1.0.5 @@ -16957,6 +17029,7 @@ packages: /strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + requiresBuild: true /strtok3@6.3.0: resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} @@ -17536,6 +17609,7 @@ packages: /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + requiresBuild: true /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -17922,6 +17996,7 @@ packages: /uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true + requiresBuild: true /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} diff --git a/test/auth/config.ts b/test/auth/config.ts index 3093f7892c7..d3862988708 100644 --- a/test/auth/config.ts +++ b/test/auth/config.ts @@ -212,14 +212,20 @@ export default buildConfigWithDefaults({ }, }) - await mapAsync([...Array(2)], async () => { - await payload.create({ - collection: 'api-keys', - data: { - apiKey: uuid(), - enableAPIKey: true, - }, - }) + await payload.create({ + collection: 'api-keys', + data: { + apiKey: uuid(), + enableAPIKey: true, + }, + }) + + await payload.create({ + collection: 'api-keys', + data: { + apiKey: uuid(), + enableAPIKey: true, + }, }) }, }) diff --git a/test/auth/int.spec.ts b/test/auth/int.spec.ts index aae4a35b078..274a02d6d99 100644 --- a/test/auth/int.spec.ts +++ b/test/auth/int.spec.ts @@ -5,7 +5,7 @@ import { v4 as uuid } from 'uuid' import type { User } from '../../packages/payload/src/auth' import payload from '../../packages/payload/src' -import configPromise from '../collections-graphql/config' +import configPromise from '../auth/config' import { devUser } from '../credentials' import { initPayloadTest } from '../helpers/configHelpers' import { apiKeysSlug, namedSaveToJWTValue, saveToJWTKey, slug } from './shared' diff --git a/test/collections-graphql/media/test-image.jpg b/test/collections-graphql/media/test-image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3a0303dcf8bf9cdfe2d715bcd2775f6de4527467 GIT binary patch literal 19572 zcmeI2Sx6O89LCR?nd`mY0v)%OmvXCXLEzH*a`9@WLWs6mPieb8X_*xSYI`qYw$p>Q zSVkaLw)oKI*|IK%P=R1E!Yt~*f z&-j?Q7{(>eV)cV*uUHi09O$usqXt4vb4ta7c6&0vfhhR0?jKQ76N z!OduLH&4=UsPJSGmt{$j4F*Ng@7}9lXNqd@@(+!4^G-}L24whzLm(x7XSxKpH8>8aY>E^BOglu->(*kGx5UFYq(O7l;Gr z3&pVjI|3jAI|B0nI|6Zl9iceD&PcEs^91Y&%meHQ!~u4M;s85i@&=nMv1=Ti1Yk#~ z5!wr)b`v&P*kn1^Gz4~l9p{{*w~>JzV8=Q006V~rbIt*FL_v@hp|>NLS-_4!9Q1aC z;vnsyw<9(P31(nNC>(k_q;vlJv|~vhR1-RiCg!7^j*_a{!olS>!7fb1Eb%pKZkooj z@(1cC=6k8ZfpmL_wY|yd6fE>izh7Bjvc|4K_`kdX;i<_8$CJyH2T((3BLg+$Is()H zHKaMHg&(M)0vAw2O(!!0YA9`xYKWCv?-e7}5ZVAW)N}%BsD)eqh6^nNN*tgDsfJ*! zppk`CLtz-GA>9Pj05zmJ$n7{#1Jsb_K&qk40n`9Bf&fQN9Ac&+)sSvNssU<9a}d)B Ty&9T7Kn>|8poT&X(^`H34rQyQ literal 0 HcmV?d00001 diff --git a/test/database/config.ts b/test/database/config.ts index e9f943e9809..2f438154ea7 100644 --- a/test/database/config.ts +++ b/test/database/config.ts @@ -1,171 +1,203 @@ +import path from 'path' + +import type { Config } from '../../packages/payload/src/config/types' + +import { mongooseAdapter } from '../../packages/db-mongodb/src' import { buildConfigWithDefaults } from '../buildConfigWithDefaults' import { devUser } from '../credentials' -export default buildConfigWithDefaults({ - collections: [ - { - slug: 'posts', - fields: [ - { - name: 'title', - type: 'text', - required: true, - }, - { - name: 'throwAfterChange', - type: 'checkbox', - defaultValue: false, - hooks: { - afterChange: [ - ({ value }) => { - if (value) { - throw new Error('throw after change') - } - }, - ], +const [testSuiteDir] = process.argv.slice(4) +const migrationDir = path.resolve( + (process.env.PAYLOAD_CONFIG_PATH + ? path.join(process.env.PAYLOAD_CONFIG_PATH, '..') + : testSuiteDir) || __dirname, + 'migrations', +) + +const createDatabaseTestConfig = async () => { + const config: Partial = { + collections: [ + { + slug: 'posts', + fields: [ + { + name: 'title', + type: 'text', + required: true, }, - }, - ], - }, - { - slug: 'relation-a', - fields: [ - { - name: 'relationship', - type: 'relationship', - relationTo: 'relation-b', - }, - { - name: 'richText', - type: 'richText', - }, - ], - labels: { - plural: 'Relation As', - singular: 'Relation A', + { + name: 'throwAfterChange', + type: 'checkbox', + defaultValue: false, + hooks: { + afterChange: [ + ({ value }) => { + if (value) { + throw new Error('throw after change') + } + }, + ], + }, + }, + ], }, - }, - { - slug: 'relation-b', - fields: [ - { - name: 'relationship', - type: 'relationship', - relationTo: 'relation-a', - }, - { - name: 'richText', - type: 'richText', + { + slug: 'relation-a', + fields: [ + { + name: 'relationship', + type: 'relationship', + relationTo: 'relation-b', + }, + { + name: 'richText', + type: 'richText', + }, + ], + labels: { + plural: 'Relation As', + singular: 'Relation A', }, - ], - labels: { - plural: 'Relation Bs', - singular: 'Relation B', }, - }, - { - slug: 'custom-schema', - dbName: 'customs', - fields: [ - { - name: 'text', - type: 'text', - }, - { - name: 'localizedText', - type: 'text', - localized: true, - }, - { - name: 'relationship', - type: 'relationship', - hasMany: true, - relationTo: 'relation-a', - }, - { - name: 'select', - type: 'select', - dbName: ({ tableName }) => `${tableName}_customSelect`, - enumName: 'selectEnum', - hasMany: true, - options: ['a', 'b', 'c'], - }, - { - name: 'radio', - type: 'select', - enumName: 'radioEnum', - options: ['a', 'b', 'c'], - }, - { - name: 'array', - type: 'array', - dbName: 'customArrays', - fields: [ - { - name: 'text', - type: 'text', - }, - { - name: 'localizedText', - type: 'text', - localized: true, - }, - ], + { + slug: 'relation-b', + fields: [ + { + name: 'relationship', + type: 'relationship', + relationTo: 'relation-a', + }, + { + name: 'richText', + type: 'richText', + }, + ], + labels: { + plural: 'Relation Bs', + singular: 'Relation B', }, - { - name: 'blocks', - type: 'blocks', - blocks: [ - { - slug: 'block', - dbName: 'customBlocks', - fields: [ - { - name: 'text', - type: 'text', - }, - { - name: 'localizedText', - type: 'text', - localized: true, - }, - ], - }, - ], + }, + { + slug: 'custom-schema', + dbName: 'customs', + fields: [ + { + name: 'text', + type: 'text', + }, + { + name: 'localizedText', + type: 'text', + localized: true, + }, + { + name: 'relationship', + type: 'relationship', + hasMany: true, + relationTo: 'relation-a', + }, + { + name: 'select', + type: 'select', + dbName: ({ tableName }) => `${tableName}_customSelect`, + enumName: 'selectEnum', + hasMany: true, + options: ['a', 'b', 'c'], + }, + { + name: 'radio', + type: 'select', + enumName: 'radioEnum', + options: ['a', 'b', 'c'], + }, + { + name: 'array', + type: 'array', + dbName: 'customArrays', + fields: [ + { + name: 'text', + type: 'text', + }, + { + name: 'localizedText', + type: 'text', + localized: true, + }, + ], + }, + { + name: 'blocks', + type: 'blocks', + blocks: [ + { + slug: 'block', + dbName: 'customBlocks', + fields: [ + { + name: 'text', + type: 'text', + }, + { + name: 'localizedText', + type: 'text', + localized: true, + }, + ], + }, + ], + }, + ], + versions: { + drafts: true, }, - ], - versions: { - drafts: true, }, + ], + globals: [ + { + slug: 'global', + dbName: 'customGlobal', + fields: [ + { + name: 'text', + type: 'text', + }, + ], + versions: true, + }, + ], + localization: { + defaultLocale: 'en', + locales: ['en', 'es'], }, - ], - globals: [ - { - slug: 'global', - dbName: 'customGlobal', - fields: [ - { - name: 'text', - type: 'text', + onInit: async (payload) => { + await payload.create({ + collection: 'users', + data: { + email: devUser.email, + password: devUser.password, }, - ], - versions: true, + }) }, - ], - localization: { - defaultLocale: 'en', - locales: ['en', 'es'], - }, - onInit: async (payload) => { - await payload.create({ - collection: 'users', - data: { - email: devUser.email, - password: devUser.password, + } + + const configWithDefaults = await buildConfigWithDefaults(config) + + if (process.env.PAYLOAD_DATABASE === 'mongoose' || !process.env.PAYLOAD_DATABASE) { + configWithDefaults.db = mongooseAdapter({ + migrationDir, + url: 'mongodb://127.0.0.1/payloadtests', + // Disable strict mode for Mongoose + schemaOptions: { + strict: false, }, }) - }, -}) + } + + return configWithDefaults +} + +export default createDatabaseTestConfig() export const postDoc = { title: 'test post', diff --git a/test/database/int.spec.ts b/test/database/int.spec.ts index 5938fc61226..d3437287cda 100644 --- a/test/database/int.spec.ts +++ b/test/database/int.spec.ts @@ -2,11 +2,13 @@ import { sql } from 'drizzle-orm' import fs from 'fs' import { GraphQLClient } from 'graphql-request' import path from 'path' +import { v4 as uuid } from 'uuid' -import type { PostgresAdapter } from '../../packages/db-postgres/src/types' +import type { MongooseAdapter } from '../../packages/db-mongodb/src' import type { PostgresAdapter } from '../../packages/db-postgres/src/types' import type { TypeWithID } from '../../packages/payload/src/collections/config/types' import type { PayloadRequest } from '../../packages/payload/src/express/types' +import type { CustomSchema } from './payload-types' import payload from '../../packages/payload/src' import { migrate } from '../../packages/payload/src/bin/migrate' @@ -15,7 +17,6 @@ import { initTransaction } from '../../packages/payload/src/utilities/initTransa import { devUser } from '../credentials' import { initPayloadTest } from '../helpers/configHelpers' import removeFiles from '../helpers/removeFiles' -import { MongooseAdapter } from '../../packages/db-mongodb/src' describe('database', () => { let serverURL @@ -344,4 +345,109 @@ describe('database', () => { }) }) }) + + describe('existing data', () => { + let existingDataDoc: CustomSchema + + beforeAll(async () => { + if (payload.db.name === 'mongoose') { + const Model = payload.db.collections['custom-schema'] + + const [doc] = await Model.create([ + { + text: 'hello', + localizedText: { + en: 'goodbye', + }, + noFieldDefined: 'hi', + array: [ + { + id: uuid(), + noFieldDefined: 'hi', + text: 'hello', + localizedText: { + en: 'goodbye', + }, + }, + ], + blocks: [ + { + id: uuid(), + blockType: 'block', + noFieldDefined: 'hi', + text: 'hello', + localizedText: { + en: 'goodbye', + }, + }, + ], + }, + ]) + + const result = JSON.parse(JSON.stringify(doc)) + result.id = result._id + existingDataDoc = result + } + }) + + it('should allow storage of existing data', async () => { + expect(payload.db).toBeDefined() + + if (payload.db.name === 'mongoose') { + expect(existingDataDoc.noFieldDefined).toStrictEqual('hi') + expect(existingDataDoc.array[0].noFieldDefined).toStrictEqual('hi') + expect(existingDataDoc.blocks[0].noFieldDefined).toStrictEqual('hi') + + const docWithExistingData = await payload.findByID({ + collection: 'custom-schema', + id: existingDataDoc.id, + }) + + expect(docWithExistingData.noFieldDefined).toStrictEqual('hi') + expect(docWithExistingData.array[0].noFieldDefined).toStrictEqual('hi') + expect(docWithExistingData.blocks[0].noFieldDefined).toStrictEqual('hi') + } + }) + + it('should maintain existing data while updating', async () => { + expect(payload.db).toBeDefined() + + if (payload.db.name === 'mongoose') { + const result = await payload.update({ + id: existingDataDoc.id, + collection: 'custom-schema', + data: { + text: 'hola', + localizedText: 'adios', + array: [ + { + id: existingDataDoc.array[0].id, + text: 'hola', + localizedText: 'adios', + }, + ], + blocks: [ + { + blockType: 'block', + id: existingDataDoc.blocks[0].id, + text: 'hola', + localizedText: 'adios', + }, + ], + }, + }) + + expect(result.text).toStrictEqual('hola') + expect(result.array[0].text).toStrictEqual('hola') + expect(result.blocks[0].text).toStrictEqual('hola') + expect(result.localizedText).toStrictEqual('adios') + expect(result.array[0].localizedText).toStrictEqual('adios') + expect(result.blocks[0].localizedText).toStrictEqual('adios') + + expect(result.noFieldDefined).toStrictEqual('hi') + expect(result.array[0].noFieldDefined).toStrictEqual('hi') + expect(result.blocks[0].noFieldDefined).toStrictEqual('hi') + } + }) + }) }) diff --git a/test/database/payload-types.ts b/test/database/payload-types.ts index d166628f796..d4c58189a02 100644 --- a/test/database/payload-types.ts +++ b/test/database/payload-types.ts @@ -11,18 +11,30 @@ export interface Config { posts: Post 'relation-a': RelationA 'relation-b': RelationB + 'custom-schema': CustomSchema users: User 'payload-preferences': PayloadPreference 'payload-migrations': PayloadMigration } - globals: {} + globals: { + global: Global + } } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "posts". + */ export interface Post { id: string title: string + throwAfterChange?: boolean | null updatedAt: string createdAt: string } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "relation-a". + */ export interface RelationA { id: string relationship?: (string | null) | RelationB @@ -34,6 +46,10 @@ export interface RelationA { updatedAt: string createdAt: string } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "relation-b". + */ export interface RelationB { id: string relationship?: (string | null) | RelationA @@ -45,6 +61,41 @@ export interface RelationB { updatedAt: string createdAt: string } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "custom-schema". + */ +export interface CustomSchema { + id: string + text?: string | null + localizedText?: string | null + relationship?: (string | RelationA)[] | null + select?: ('a' | 'b' | 'c')[] | null + radio?: ('a' | 'b' | 'c') | null + array?: + | { + text?: string | null + localizedText?: string | null + id?: string | null + }[] + | null + blocks?: + | { + text?: string | null + localizedText?: string | null + id?: string | null + blockName?: string | null + blockType: 'block' + }[] + | null + updatedAt: string + createdAt: string + _status?: ('draft' | 'published') | null +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "users". + */ export interface User { id: string updatedAt: string @@ -58,6 +109,10 @@ export interface User { lockUntil?: string | null password: string | null } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-preferences". + */ export interface PayloadPreference { id: string user: { @@ -77,6 +132,10 @@ export interface PayloadPreference { updatedAt: string createdAt: string } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-migrations". + */ export interface PayloadMigration { id: string name?: string | null @@ -84,6 +143,16 @@ export interface PayloadMigration { updatedAt: string createdAt: string } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "global". + */ +export interface Global { + id: string + text?: string | null + updatedAt?: string | null + createdAt?: string | null +} declare module 'payload' { export interface GeneratedTypes extends Config {} diff --git a/test/fields/int.spec.ts b/test/fields/int.spec.ts index b83e33376db..80cab1293e7 100644 --- a/test/fields/int.spec.ts +++ b/test/fields/int.spec.ts @@ -8,6 +8,7 @@ import type { PaginatedDocs } from '../../packages/payload/src/database/types' import type { GroupField, RichTextField } from './payload-types' import payload from '../../packages/payload/src' +import { NotFound } from '../../packages/payload/src/errors' import { devUser } from '../credentials' import { initPayloadTest } from '../helpers/configHelpers' import { isMongoose } from '../helpers/isMongoose' @@ -37,7 +38,6 @@ import { tabsFieldsSlug, textFieldsSlug, } from './slugs' -import { NotFound } from '../../packages/payload/src/errors' let client: RESTClient let graphQLClient: GraphQLClient diff --git a/test/plugin-search/int.spec.ts b/test/plugin-search/int.spec.ts index d9f28b821bb..2491443dab5 100644 --- a/test/plugin-search/int.spec.ts +++ b/test/plugin-search/int.spec.ts @@ -1,5 +1,5 @@ -import wait from '../../packages/payload/dist/utilities/wait' import payload from '../../packages/payload/src' +import wait from '../../packages/payload/src/utilities/wait' import { initPayloadTest } from '../helpers/configHelpers' describe('Search Plugin', () => { diff --git a/test/plugin-stripe/webhooks/subscriptionCreatedOrUpdated.ts b/test/plugin-stripe/webhooks/subscriptionCreatedOrUpdated.ts index f47329a5d1f..c689b8e9d74 100644 --- a/test/plugin-stripe/webhooks/subscriptionCreatedOrUpdated.ts +++ b/test/plugin-stripe/webhooks/subscriptionCreatedOrUpdated.ts @@ -1,4 +1,4 @@ -import { APIError } from '../../../packages/payload/errors' +import { APIError } from '../../../packages/payload/src/exports/errors' export const subscriptionCreatedOrUpdated = async (args) => { const { diff --git a/test/relationships/int.spec.ts b/test/relationships/int.spec.ts index 56939450b76..c68c6eb7900 100644 --- a/test/relationships/int.spec.ts +++ b/test/relationships/int.spec.ts @@ -236,23 +236,22 @@ describe('Relationships', () => { password: 'fwddsefe', }, }) - await Promise.all([ - payload.create({ - collection: 'movieReviews', - data: { - likes: [user3.id, user2.id, user.id, user4.id], - movieReviewer: user.id, - visibility: 'public', - }, - }), - payload.create({ - collection: 'movieReviews', - data: { - movieReviewer: user2.id, - visibility: 'public', - }, - }), - ]) + await payload.create({ + collection: 'movieReviews', + data: { + likes: [user3.id, user2.id, user.id, user4.id], + movieReviewer: user.id, + visibility: 'public', + }, + }) + + await payload.create({ + collection: 'movieReviews', + data: { + movieReviewer: user2.id, + visibility: 'public', + }, + }) const query = await payload.find({ collection: 'movieReviews', diff --git a/test/uploads/focal-only/focal-1-400x300.png b/test/uploads/focal-only/focal-1-400x300.png new file mode 100644 index 0000000000000000000000000000000000000000..749d3a83f63c4411358e1513338cabba2caf67d4 GIT binary patch literal 5788 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 Devn|3 literal 0 HcmV?d00001 diff --git a/test/uploads/focal-only/focal-1-600x300.png b/test/uploads/focal-only/focal-1-600x300.png new file mode 100644 index 0000000000000000000000000000000000000000..f215c9cca8cb760c2452e0f464f54bcefea37277 GIT binary patch literal 6223 zcmeI1X;_oTx4@B#pivNH(aNIWf)32nc9UBK84A zg|u}o2??NR5QI>OK|oUVpCJ2IiEIfHNem&$f8M;<~T4>$m%Xhi8T}XPYx~e)Dqv zpvRsii@sc>rlz)Julw%r)YR0y)zs$MXe|IUW-ib3!LNl;?mjVUYKUdf&pfqjH&h!_Ccm|%)wg3LW3Q(3vxsEP$~wbw@PfP@O|eT%31^PSE9qZ-h3~Y40Ah#+>T~$ z2TREQX7oLKM$GNFZq%0lY?vf@F7Yi#=`_1ctgxk(Cob^VDw53j#iO#1+NBy~GdfaU z`||6&0^TO?T0VGxUFmYyK&E-A$n0T0epP9X9c{khE-i|8@nRcD0bPpd*7mv}-W|C4 zEQ8<{IFA15D{&xs)5%nW|6lvrawTy_b^78{k|pTlB~MDbz96M;A*B+B;-oT(A38<6 z_4mn@uBptW>el&IUo@Z4NnF5}yFN-c7W7>c;vH%#!5Tl9!q)SCVp5;&f? zpr}p|hxiRl??=-Sd>b_LEW&19r$ zry5Axz4@u^9pmgB->V~><{PSoY1zD8>a9juJxfZ1@#X))AAwd}0WgaEEZRSl)1OhT zw55A&zTrtNn=|9i1w8)yt;kdj6LmyjRf2S1*!VJGKq%1$o~g$2NCA;0vfCA+b_wln zU(>$8ommX^*XyV?uYK{3v<>B`E6(Mxu6NN>w;d_|54b2xJs;(+G3=4pL%1Yo#q>=H zkF?CpXpwS<2M0!)LwCj!$gietB_8=uHE7rZjW5rW7v;h=a-p-*32j&%9^$f5-}}$i zrD_j*D~n5ihX07$M6G#jPA&f$URo+E3(OdFRXW@UoT%6{h70L_rl(wIsvy-?df`F{ za6op;6Se)(l=g4nWK3G(run#Ea<1RWFww%OSLRHC3 zw&#}dcN%!MCVSDT*9%WkPa}OI7o3BY)AJ8mdK{g~;C8IvH1v(vUHUXPC)!}MQak19 zolW=H54b(0+A%u*o~Gu!D(6VH?!;Ynq>t_?>TjlSZZ?xx?=z$kVdFkw<1%i?A@Z0@ zd`fbwpn{8vedpLXFf@jc-xXd_Xg;$TbzWY(!jft}9=s6BYT4lx-dvfWZdihfEJ>Ok zh`D4(Mctr{);HvWOh;;7AY0~QLi|HgG#Bm*WS&5k7WvOy-{S{*%~?a$(%Oq0-6PrnsY|EO+y zuYLI(gK)cvQ9iOnoMnm-QN{Z6QnV&W97iT64gtcD%VXqIk6QTxEzA%mKkicSd@mc! zf_zLEo#tYg*yzDE&Bgr48=0)Bi4l!`{reW$mg@91Yf6=&I@Qa{SZU((slDNexOmeG zk$&&4<>+JchA{8rF5OZo3Ze&$dQYs*tzddUv2O3XCR%j%RniP6Q(V?mO_U^ zQb!zShUi(ZEe}d}xc4EHDmq8rC$t(tx}Sh^(&W;$#{>xIB_rR`C+@`Kp4_h|g^;(< z%xlv1SHUh-*C)L%>+?tY+zp$q%WOLw%Pa1vCWy4Z1HBVU!wGG1Ne)QYlG@5++i>y% z@;)RM&&1qLFc>zuwKUaWe_m?Ho0a?{W{3z~9bVlBnF;Opmt}o#WF@w=->TE~EnqXa z`XpLVopg5t%}iGpm^ykUtoT6;CfwgGUUP#xyfX=z(jHdY&0_1HjX)5cpBi2U{SR5c~z{_JC(}I0qn(<#iej3$Q@4?-m4(~~&ZIHHNDiYQz zX#v{#vMl_6ARnG?a>{)-!X#A!4-CGdT0OV^)R+j!uA5Lw?d3_U?X6gtTS0XM;XWlu zK*<=;7B6O{>9jvPJ!M0cf7Nf-9b2dyM=hSM!Lch+&jf)AULQ6&n1;mW4i>+x)>HO4 zsMzD{lcpo32a<~%sQKo9tuLu~v{#&+4JNoRy5jZ2+C(x8Wg+ukwyf{m|~_NN5i^xPH6Y#>}`i}KL2nS-4(Fm z6z*zx2oY4ztXOr#*V9-TK3+p}y*sfuj+G%zEC>Y`r~TjLW!)APhqI*SIMd9*uB+-D zSKl4oZyzvV0J%ce60Fi#sKhkuULZ_AfD1tzQXFQNNi|sTE8S^M1x{)MgKe?_>M0xV zXn!}n<_6VCuFR?${A2Vf<~HP@)d13cp6V*t++|`!AX~JM8wIP*!xqU;A&dOx$CG7& z%mf38$(qe@-Eda$a}aGz3`ePC8tML>O38Mml47A%Gtzy_tT_>>|664^HiA{pVCp0f z0p+iWWD8@2*9~kz&rUtvkGB?|eh@0+6}>z4RJ%9c&!iaHmM<8^2|E5QY>Cgy{^SRPuF+E|P+L^yanzv}k9ryXYfzm+ zF=pLD9uutExsY;|J$RAZfiI~@X+f@hl^w~c)?^RyW*%`n!i-4iHaStaA9%+lm7enr zZI*gRlbXYXf;*I;uJks&OwPAC2qtHXvgOh939Jloq~!9_bPdFOud&b|lTp*d2Z!l@ zwsN}Jr_D5WQK7n1bj zIeI8VV7!2wj#W|~BR23Wy$2%MJ(89${Y zm0y~mtr#aghuz!(-~ox1I$t@@P_B!@edwPNVnY?RcO_4kD|2v3MxRKRvI(3Ahmd&c0hk|9lRfw|g*Ydohknm#8&-WeIQ68>W=DvW} z_H)8QN)lhupt~BR^K7~I4Y*}aXv~uQR{5r7##`Q#GAUp?ciPBqryHEwPsruC!5=$>1GbEf4!0F z-%=}&l1q3EtPGeV>GbJIA6!!6son9WK0a|OUMq{9dePp>X?Fbxjop=xzoPoDL#vpP zx)K{OY>cR}c`yqcPZNXctW*oe%&;pyg5W5VvnxmAz5u2vHDWfI=69556{H%M4Olft z|Ees=(v8%bWJ4^`#ju60Qta$Zt=SyKv)T_!GqiNo1G5XsFQN@3^n*}V2(q6jLEY(A zMK@%WD8KEG#&8#%3|q=o%iB&YVs4<+{OK$Gi+SVOh(5Nr3z$9uD}zpcNrmT;tV&Iq zfmZG-BUxw6{F%7aLdtD=$7!C`2<(=DFroT|FP>H*B62(Kb30-nbhJnFYMJ(p;4Ql99Ql<|Th>C0Q4oYvHu+wEDNp4eL?9eRw-8WA`05jc zGw20G%Jtyr3bBV*UP9iK$r=aMO>fG75>(eoeV3lyNca$1?h!pz$Y7=Anv0`m&`sc~F z3n^@p-{3tw_NafzY0bU6VWOGJlQP$uex9LqYHHE!xaYJD-yS;fPYgQ;fg(&tPCvIXk*2}&G zx=Ic$o{1PNq+Cynj!j0VB%CU|DG18wUHWy3=3YSKO5V+sR^({l+iLbe9^k=^?kRqU zUD?9|_&kYpKlZ6-(h_WzuPx6Xuo5C4Vxgv$3!0V+zxn;BN$-h=v4pd|iwS3iL3M(N zlLzp|hzQB1#fJ;9y1hFWsoGmlKWf^$G|tSUY@%~sSrmaRh>SmgfsljzgJ6{#+_$U+ zQN07+QKOBIFObKOSlt1@NVAx8a*5a_MRw4f1aI(k6(G3Pl`o98YWu~+U z0Hryo8MI|N%zCpagcU^u-Kz8POu)KYYpaMwPdUwJqA@?tLamv8({T_@is@u%4yc!# zjJ5}&X$uH{|HjI9S@?#ax*nIuY!7Y-E~K}TU_2MeO!zCHXzsrLAaVT(Cr=gGw4Ef4 zUR?e!Qh4#mmU4fWY_i$<>N{1`t;eB#P=5A+-WUck^xid2hKcUMg)EGykZd#8+3J3I zuv{W+h>op6T}zSQst|G8dz%vv+~r+fJ(qG9+%I1+?|A=7RF(EPRH= zAI*62G5>a&#gVx_#L)xfE6=uDkpF1<9}yHNLcc67p&+rG*+^>2=P4WutXLWCULtz& zwpq+5j|%8`rsva{bm~59PEH*&0ooHaNTy-jui)*^%j zCCazd*|I_lyJsaMd`fuA1c#R3_z<1vt$0+2^Pb zg1@8eNlT@kzYl2z2n^`yC z5y&+0_T*iWGo95a*Tj9ynH!|F#{50b8}+e@>6!KFh=>)J!cE86h#Bp_1j6Rz?9>c5{CDNjc<+l z!G}fHq^(~}zG)2jy^|z?p$)emz!Qz92#z*5(DV zUAMpy=30hUI}wmzk6?vrYzaE@IL>8EqJl>E0w`7Wq;c5Dg?vDYWqtLeQ57l5AaI-x zb%M3;L4gzy@kM{LDJzYL&sPl_pkUe(a#TZz(qo^5I;fT+Atrvp+pZcq0?v$;g?=15 z0L4K?ouBv=IB_-99+u66T)vp(59>WR1p>U7;`M0+L_hFU7|O{3qT%8TFfRTA9tHFc zk>nc~W^Y6J>qK4u1fl?7NxUtv5d&!DxbT~G>SPnPLXBtf@_W52wmRTH1ZsQTJa$uE H1JnKsZ$^N9 literal 0 HcmV?d00001 diff --git a/test/uploads/focal-only/focal-1-900x300.png b/test/uploads/focal-only/focal-1-900x300.png new file mode 100644 index 0000000000000000000000000000000000000000..4c8d2fe64211d66206ff1f969815dc2d87ba8e3d GIT binary patch literal 8457 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 Devn|3 literal 0 HcmV?d00001 diff --git a/test/uploads/focal-only/focal-600x300.png b/test/uploads/focal-only/focal-600x300.png new file mode 100644 index 0000000000000000000000000000000000000000..f215c9cca8cb760c2452e0f464f54bcefea37277 GIT binary patch literal 6223 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 H1JnKsZ$^N9 literal 0 HcmV?d00001 diff --git a/test/uploads/focal-only/focal-900x300.png b/test/uploads/focal-only/focal-900x300.png new file mode 100644 index 0000000000000000000000000000000000000000..4c8d2fe64211d66206ff1f969815dc2d87ba8e3d GIT binary patch literal 8457 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