fix!: handle custom id logic in mongodb adapter (#9069)
### What? Moved the logic for copying the data.id to data._id to the mongoose adapter. ### Why? If you have any hooks that need to set the `id`, the value does not get sent to mongodb as you would expect since it was copied before the beforeValidate hooks. ### How? Now data._id is assigned only in the mongodb adapter's `create` function. BREAKING CHANGES: When using custom ID fields, if you have any collection hooks for beforeValidate, beforeChange then `data._id` will no longer be assigned as this happens now in the database adapter. Use `data.id` instead.
This commit is contained in:
@@ -356,7 +356,7 @@ For full details on Admin Options, see the [Field Admin Options](../admin/fields
|
|||||||
|
|
||||||
## Custom ID Fields
|
## Custom ID Fields
|
||||||
|
|
||||||
All [Collections](../configuration/collections) automatically generate their own ID field. If needed, you can override this behavior by providing an explicit ID field to your config. This will force users to provide a their own ID value when creating a record.
|
All [Collections](../configuration/collections) automatically generate their own ID field. If needed, you can override this behavior by providing an explicit ID field to your config. This field should either be required or have a hook to generate the ID dynamically.
|
||||||
|
|
||||||
To define a custom ID field, add a new field with the `name` property set to `id`:
|
To define a custom ID field, add a new field with the `name` property set to `id`:
|
||||||
|
|
||||||
@@ -368,6 +368,7 @@ export const MyCollection: CollectionConfig = {
|
|||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: 'id', // highlight-line
|
name: 'id', // highlight-line
|
||||||
|
required: true,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ export const create: Create = async function create(
|
|||||||
fields: this.payload.collections[collection].config.fields,
|
fields: this.payload.collections[collection].config.fields,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (this.payload.collections[collection].customIDType) {
|
||||||
|
sanitizedData._id = sanitizedData.id
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
;[doc] = await Model.create([sanitizedData], options)
|
;[doc] = await Model.create([sanitizedData], options)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -123,17 +123,6 @@ export const createOperation = async <
|
|||||||
await executeAccess({ data, req }, collectionConfig.access.create)
|
await executeAccess({ data, req }, collectionConfig.access.create)
|
||||||
}
|
}
|
||||||
|
|
||||||
// /////////////////////////////////////
|
|
||||||
// Custom id
|
|
||||||
// /////////////////////////////////////
|
|
||||||
|
|
||||||
if (payload.collections[collectionConfig.slug].customIDType) {
|
|
||||||
data = {
|
|
||||||
_id: data.id,
|
|
||||||
...data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// /////////////////////////////////////
|
// /////////////////////////////////////
|
||||||
// Generate data for all files and sizes
|
// Generate data for all files and sizes
|
||||||
// /////////////////////////////////////
|
// /////////////////////////////////////
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ const filename = fileURLToPath(import.meta.url)
|
|||||||
const dirname = path.dirname(filename)
|
const dirname = path.dirname(filename)
|
||||||
import type { TextField } from 'payload'
|
import type { TextField } from 'payload'
|
||||||
|
|
||||||
|
import { v4 as uuid } from 'uuid'
|
||||||
|
|
||||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||||
import { devUser } from '../credentials.js'
|
import { devUser } from '../credentials.js'
|
||||||
|
|
||||||
@@ -356,6 +358,29 @@ export default buildConfigWithDefaults({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
slug: 'custom-ids',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'text',
|
||||||
|
admin: {
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
beforeChange: [
|
||||||
|
({ value, operation }) => {
|
||||||
|
if (operation === 'create') {
|
||||||
|
return uuid()
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
versions: { drafts: true },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
globals: [
|
globals: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -75,10 +75,19 @@ describe('database', () => {
|
|||||||
|
|
||||||
expect(updated.id).toStrictEqual(created.doc.id)
|
expect(updated.id).toStrictEqual(created.doc.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should create with generated ID text from hook', async () => {
|
||||||
|
const doc = await payload.create({
|
||||||
|
collection: 'custom-ids',
|
||||||
|
data: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(doc.id).toBeDefined()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('timestamps', () => {
|
describe('timestamps', () => {
|
||||||
it('should have createdAt and updatedAt timetstamps to the millisecond', async () => {
|
it('should have createdAt and updatedAt timestamps to the millisecond', async () => {
|
||||||
const result = await payload.create({
|
const result = await payload.create({
|
||||||
collection: 'posts',
|
collection: 'posts',
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -19,21 +19,44 @@ export interface Config {
|
|||||||
'custom-schema': CustomSchema;
|
'custom-schema': CustomSchema;
|
||||||
places: Place;
|
places: Place;
|
||||||
'fields-persistance': FieldsPersistance;
|
'fields-persistance': FieldsPersistance;
|
||||||
|
'custom-ids': CustomId;
|
||||||
users: User;
|
users: User;
|
||||||
'payload-locked-documents': PayloadLockedDocument;
|
'payload-locked-documents': PayloadLockedDocument;
|
||||||
'payload-preferences': PayloadPreference;
|
'payload-preferences': PayloadPreference;
|
||||||
'payload-migrations': PayloadMigration;
|
'payload-migrations': PayloadMigration;
|
||||||
};
|
};
|
||||||
|
collectionsSelect: {
|
||||||
|
posts: PostsSelect<false> | PostsSelect<true>;
|
||||||
|
'default-values': DefaultValuesSelect<false> | DefaultValuesSelect<true>;
|
||||||
|
'relation-a': RelationASelect<false> | RelationASelect<true>;
|
||||||
|
'relation-b': RelationBSelect<false> | RelationBSelect<true>;
|
||||||
|
'pg-migrations': PgMigrationsSelect<false> | PgMigrationsSelect<true>;
|
||||||
|
'custom-schema': CustomSchemaSelect<false> | CustomSchemaSelect<true>;
|
||||||
|
places: PlacesSelect<false> | PlacesSelect<true>;
|
||||||
|
'fields-persistance': FieldsPersistanceSelect<false> | FieldsPersistanceSelect<true>;
|
||||||
|
'custom-ids': CustomIdsSelect<false> | CustomIdsSelect<true>;
|
||||||
|
users: UsersSelect<false> | UsersSelect<true>;
|
||||||
|
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||||
|
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||||
|
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||||
|
};
|
||||||
db: {
|
db: {
|
||||||
defaultIDType: string;
|
defaultIDType: string;
|
||||||
};
|
};
|
||||||
globals: {
|
globals: {
|
||||||
global: Global;
|
global: Global;
|
||||||
};
|
};
|
||||||
|
globalsSelect: {
|
||||||
|
global: GlobalSelect<false> | GlobalSelect<true>;
|
||||||
|
};
|
||||||
locale: 'en' | 'es';
|
locale: 'en' | 'es';
|
||||||
user: User & {
|
user: User & {
|
||||||
collection: 'users';
|
collection: 'users';
|
||||||
};
|
};
|
||||||
|
jobs?: {
|
||||||
|
tasks: unknown;
|
||||||
|
workflows?: unknown;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export interface UserAuthOperations {
|
export interface UserAuthOperations {
|
||||||
forgotPassword: {
|
forgotPassword: {
|
||||||
@@ -232,6 +255,15 @@ export interface FieldsPersistance {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "custom-ids".
|
||||||
|
*/
|
||||||
|
export interface CustomId {
|
||||||
|
id: string;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "users".
|
* via the `definition` "users".
|
||||||
@@ -288,6 +320,10 @@ export interface PayloadLockedDocument {
|
|||||||
relationTo: 'fields-persistance';
|
relationTo: 'fields-persistance';
|
||||||
value: string | FieldsPersistance;
|
value: string | FieldsPersistance;
|
||||||
} | null)
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'custom-ids';
|
||||||
|
value: string | CustomId;
|
||||||
|
} | null)
|
||||||
| ({
|
| ({
|
||||||
relationTo: 'users';
|
relationTo: 'users';
|
||||||
value: string | User;
|
value: string | User;
|
||||||
@@ -334,6 +370,215 @@ export interface PayloadMigration {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "posts_select".
|
||||||
|
*/
|
||||||
|
export interface PostsSelect<T extends boolean = true> {
|
||||||
|
title?: T;
|
||||||
|
hasTransaction?: T;
|
||||||
|
throwAfterChange?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "default-values_select".
|
||||||
|
*/
|
||||||
|
export interface DefaultValuesSelect<T extends boolean = true> {
|
||||||
|
title?: T;
|
||||||
|
defaultValue?: T;
|
||||||
|
array?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
defaultValue?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
group?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
defaultValue?: T;
|
||||||
|
};
|
||||||
|
select?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "relation-a_select".
|
||||||
|
*/
|
||||||
|
export interface RelationASelect<T extends boolean = true> {
|
||||||
|
title?: T;
|
||||||
|
richText?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "relation-b_select".
|
||||||
|
*/
|
||||||
|
export interface RelationBSelect<T extends boolean = true> {
|
||||||
|
title?: T;
|
||||||
|
relationship?: T;
|
||||||
|
richText?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "pg-migrations_select".
|
||||||
|
*/
|
||||||
|
export interface PgMigrationsSelect<T extends boolean = true> {
|
||||||
|
relation1?: T;
|
||||||
|
myArray?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
relation2?: T;
|
||||||
|
mySubArray?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
relation3?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
myGroup?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
relation4?: T;
|
||||||
|
};
|
||||||
|
myBlocks?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
myBlock?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
relation5?: T;
|
||||||
|
relation6?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "custom-schema_select".
|
||||||
|
*/
|
||||||
|
export interface CustomSchemaSelect<T extends boolean = true> {
|
||||||
|
text?: T;
|
||||||
|
localizedText?: T;
|
||||||
|
relationship?: T;
|
||||||
|
select?: T;
|
||||||
|
radio?: T;
|
||||||
|
array?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
text?: T;
|
||||||
|
localizedText?: T;
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
blocks?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
block?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
text?: T;
|
||||||
|
localizedText?: T;
|
||||||
|
id?: T;
|
||||||
|
blockName?: T;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
_status?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "places_select".
|
||||||
|
*/
|
||||||
|
export interface PlacesSelect<T extends boolean = true> {
|
||||||
|
country?: T;
|
||||||
|
city?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "fields-persistance_select".
|
||||||
|
*/
|
||||||
|
export interface FieldsPersistanceSelect<T extends boolean = true> {
|
||||||
|
text?: T;
|
||||||
|
textHooked?: T;
|
||||||
|
array?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
id?: T;
|
||||||
|
};
|
||||||
|
textWithinRow?: T;
|
||||||
|
textWithinCollapsible?: T;
|
||||||
|
textWithinTabs?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "custom-ids_select".
|
||||||
|
*/
|
||||||
|
export interface CustomIdsSelect<T extends boolean = true> {
|
||||||
|
id?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "users_select".
|
||||||
|
*/
|
||||||
|
export interface UsersSelect<T extends boolean = true> {
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
email?: T;
|
||||||
|
resetPasswordToken?: T;
|
||||||
|
resetPasswordExpiration?: T;
|
||||||
|
salt?: T;
|
||||||
|
hash?: T;
|
||||||
|
loginAttempts?: T;
|
||||||
|
lockUntil?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-locked-documents_select".
|
||||||
|
*/
|
||||||
|
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
|
||||||
|
document?: T;
|
||||||
|
globalSlug?: T;
|
||||||
|
user?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-preferences_select".
|
||||||
|
*/
|
||||||
|
export interface PayloadPreferencesSelect<T extends boolean = true> {
|
||||||
|
user?: T;
|
||||||
|
key?: T;
|
||||||
|
value?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-migrations_select".
|
||||||
|
*/
|
||||||
|
export interface PayloadMigrationsSelect<T extends boolean = true> {
|
||||||
|
name?: T;
|
||||||
|
batch?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "global".
|
* via the `definition` "global".
|
||||||
@@ -344,6 +589,16 @@ export interface Global {
|
|||||||
updatedAt?: string | null;
|
updatedAt?: string | null;
|
||||||
createdAt?: string | null;
|
createdAt?: string | null;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "global_select".
|
||||||
|
*/
|
||||||
|
export interface GlobalSelect<T extends boolean = true> {
|
||||||
|
text?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
globalType?: T;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "auth".
|
* via the `definition` "auth".
|
||||||
|
|||||||
Reference in New Issue
Block a user