Merge pull request #313 from payloadcms/feat/custom-id

Feat/custom-id
This commit is contained in:
Dan Ribbens
2021-09-09 15:56:59 -04:00
committed by GitHub
23 changed files with 506 additions and 36 deletions

View File

@@ -0,0 +1,22 @@
import { CollectionConfig } from '../../src/collections/config/types';
const CustomID: CollectionConfig = {
slug: 'custom-id',
labels: {
singular: 'CustomID',
plural: 'CustomIDs',
},
fields: [
{
name: 'id',
type: 'number',
},
{
name: 'name',
type: 'text',
required: true,
},
],
};
export default CustomID;

View File

@@ -29,7 +29,7 @@ const RelationshipA: CollectionConfig = {
name: 'postLocalizedMultiple',
label: 'Localized Post Multiple',
type: 'relationship',
relationTo: ['localized-posts', 'all-fields'],
relationTo: ['localized-posts', 'all-fields', 'custom-id'],
hasMany: true,
localized: true,
},
@@ -49,6 +49,14 @@ const RelationshipA: CollectionConfig = {
relationTo: 'relationship-b',
hasMany: false,
},
{
name: 'customID',
label: 'CustomID Relation',
type: 'relationship',
relationTo: 'custom-id',
hasMany: true,
localized: true,
},
],
timestamps: true,
};

View File

@@ -9,6 +9,7 @@ import Conditions from './collections/Conditions';
import CustomComponents from './collections/CustomComponents';
import File from './collections/File';
import Blocks from './collections/Blocks';
import CustomID from './collections/CustomID';
import DefaultValues from './collections/DefaultValues';
import HiddenFields from './collections/HiddenFields';
import Hooks from './collections/Hooks';
@@ -64,6 +65,7 @@ export default buildConfig({
Code,
Conditions,
CustomComponents,
CustomID,
File,
DefaultValues,
Blocks,

View File

@@ -84,6 +84,24 @@ Example:
}
```
### Customizable ID
Collections ID fields are generated automatically by default. An explicit `id` field can be declared in the `fields` array to override this behavior.
Users are then required to provide a custom ID value when creating a record through the Admin UI or API.
Valid ID types are `number` and `text`.
Example:
```js
{
fields: [
{
name: 'id',
type: 'number',
},
],
}
```
### Admin config
In addition to each field's base configuration, you can define specific traits and properties for fields that only have effect on how they are rendered in the Admin panel. The following properties are available for all fields within the `admin` property:

View File

@@ -8,6 +8,7 @@ import usePayloadAPI from '../../../../../hooks/usePayloadAPI';
import ListControls from '../../../../elements/ListControls';
import Paginator from '../../../../elements/Paginator';
import UploadGallery from '../../../../elements/UploadGallery';
import { Field } from '../../../../../../fields/config/types';
import { Props } from './types';
import './index.scss';
@@ -45,7 +46,7 @@ const SelectExistingUploadModal: React.FC<Props> = (props) => {
const [{ data }, { setParams }] = usePayloadAPI(apiURL, {});
useEffect(() => {
setFields(formatFields(collection));
setFields(formatFields(collection) as Field[]);
}, [collection]);
useEffect(() => {

View File

@@ -0,0 +1,8 @@
import { SanitizedCollectionConfig } from '../../../../../collections/config/types';
import { Field } from '../../../../../fields/config/types';
const formatFields = (collection: SanitizedCollectionConfig, isEditing: boolean): Field[] => (isEditing
? collection.fields.filter(({ name }) => name !== 'id')
: collection.fields);
export default formatFields;

View File

@@ -7,6 +7,7 @@ import usePayloadAPI from '../../../../hooks/usePayloadAPI';
import RenderCustomComponent from '../../../utilities/RenderCustomComponent';
import { DocumentInfoProvider } from '../../../utilities/DocumentInfo';
import DefaultEdit from './Default';
import formatFields from './formatFields';
import buildStateFromSchema from '../../../forms/Form/buildStateFromSchema';
import { NegativeFieldGutterProvider } from '../../../forms/FieldTypeGutter/context';
import { useLocale } from '../../../utilities/Locale';
@@ -29,8 +30,8 @@ const EditView: React.FC<IndexProps> = (props) => {
} = {},
} = {},
} = {},
fields,
} = collection;
const [fields] = useState(() => formatFields(collection, isEditing));
const locale = useLocale();
const { serverURL, routes: { admin, api } } = useConfig();
@@ -110,7 +111,7 @@ const EditView: React.FC<IndexProps> = (props) => {
componentProps={{
isLoading,
data: dataToRender,
collection,
collection: { ...collection, fields },
permissions: collectionPermissions,
isEditing,
onSave,

View File

@@ -1,4 +1,8 @@
const formatFields = (config) => {
import { SanitizedCollectionConfig } from '../../../../../collections/config/types';
import { Field } from '../../../../../fields/config/types';
const formatFields = (config: SanitizedCollectionConfig): (Field | { name: string, label: string, type: string })[] => {
const hasID = config.fields.findIndex(({ name }) => name === 'id') > -1;
let fields = config.fields.reduce((formatted, field) => {
if (field.hidden === true || field?.admin?.disabled === true) {
return formatted;
@@ -8,7 +12,7 @@ const formatFields = (config) => {
...formatted,
field,
];
}, [{ name: 'id', label: 'ID', type: 'text' }]);
}, hasID ? [] : [{ name: 'id', label: 'ID', type: 'text' }]);
if (config.timestamps) {
fields = fields.concat([

View File

@@ -1,9 +1,16 @@
import { DateTimeResolver } from 'graphql-scalars';
import { GraphQLString, GraphQLObjectType, GraphQLBoolean, GraphQLNonNull, GraphQLInt } from 'graphql';
import {
GraphQLString,
GraphQLObjectType,
GraphQLBoolean,
GraphQLNonNull,
GraphQLInt,
} from 'graphql';
import formatName from '../../graphql/utilities/formatName';
import buildPaginatedListType from '../../graphql/schema/buildPaginatedListType';
import { BaseFields } from './types';
import { getCollectionIDType } from '../../graphql/schema/buildMutationInputType';
function registerCollections(): void {
const {
@@ -22,13 +29,11 @@ function registerCollections(): void {
singular,
plural,
},
fields: initialFields,
fields,
timestamps,
},
} = collection;
const fields = [...initialFields];
const singularLabel = formatName(singular);
let pluralLabel = formatName(plural);
@@ -43,16 +48,23 @@ function registerCollections(): void {
collection.graphQL = {};
const baseFields: BaseFields = {
id: {
type: new GraphQLNonNull(GraphQLString),
},
};
const idField = fields.find(({ name }) => name === 'id');
const idType = getCollectionIDType(collection.config);
const baseFields: BaseFields = {};
const whereInputFields = [
...fields,
];
if (!idField) {
baseFields.id = { type: idType };
whereInputFields.push({
name: 'id',
type: 'text',
});
}
if (timestamps) {
baseFields.createdAt = {
type: new GraphQLNonNull(DateTimeResolver),
@@ -105,7 +117,7 @@ function registerCollections(): void {
collection.graphQL.updateMutationInputType = new GraphQLNonNull(this.buildMutationInputType(
`${singularLabel}Update`,
fields,
fields.filter((field) => field.name !== 'id'),
`${singularLabel}Update`,
true,
));
@@ -113,7 +125,7 @@ function registerCollections(): void {
this.Query.fields[singularLabel] = {
type: collection.graphQL.type,
args: {
id: { type: GraphQLString },
id: { type: idType },
...(this.config.localization ? {
locale: { type: this.types.localeInputType },
fallbackLocale: { type: this.types.fallbackLocaleInputType },
@@ -148,7 +160,7 @@ function registerCollections(): void {
this.Mutation.fields[`update${singularLabel}`] = {
type: collection.graphQL.type,
args: {
id: { type: new GraphQLNonNull(GraphQLString) },
id: { type: new GraphQLNonNull(idType) },
data: { type: collection.graphQL.updateMutationInputType },
},
resolve: update(collection),
@@ -157,7 +169,7 @@ function registerCollections(): void {
this.Mutation.fields[`delete${singularLabel}`] = {
type: collection.graphQL.type,
args: {
id: { type: new GraphQLNonNull(GraphQLString) },
id: { type: new GraphQLNonNull(idType) },
},
resolve: deleteResolver(collection),
};

View File

@@ -279,4 +279,139 @@ describe('GrahpQL Resolvers', () => {
expect(typeof error.response.errors[0].message).toBe('string');
});
});
describe('Custom ID', () => {
it('should create', async () => {
const id = 10;
const query = `mutation {
createCustomID(data: {
id: ${id},
name: "custom"
}) {
id,
name
}
}`;
const response = await client.request(query);
const data = response.createCustomID;
expect(data.id).toStrictEqual(id);
});
it('should update', async () => {
const id = 11;
const name = 'custom name';
const query = `
mutation {
createCustomID(data: {
id: ${id},
name: "${name}"
}) {
id
name
}
}`;
await client.request(query);
const updatedName = 'updated name';
const update = `
mutation {
updateCustomID(id: ${id} data: {name: "${updatedName}"}) {
name
}
}`;
const response = await client.request(update);
const data = response.updateCustomID;
expect(data.name).toStrictEqual(updatedName);
expect(data.name).not.toStrictEqual(name);
});
it('should query on id', async () => {
const id = 15;
const name = 'custom name';
const create = `mutation {
createCustomID(data: {
id: ${id},
name: "${name}"
}) {
id
name
}
}`;
await client.request(create);
const query = `
query {
CustomIDs(where: { id: { equals: ${id} } }) {
docs {
id
name
}
}
}`;
const response = await client.request(query);
const [doc] = response.CustomIDs.docs;
expect(doc.id).toStrictEqual(id);
expect(doc.name).toStrictEqual(name);
});
it('should delete', async () => {
const id = 12;
const query = `mutation {
createCustomID(data: {
id: ${id},
name: "delete me"
}) {
id
name
}
}`;
await client.request(query);
const deleteMutation = `mutation {
deleteCustomID(id: ${id}) {
id
}
}`;
const deleteResponse = await client.request(deleteMutation);
const deletedId = deleteResponse.deleteCustomID.id;
expect(deletedId).toStrictEqual(id);
});
it('should allow relationships', async () => {
const id = 13;
const query = `mutation {
createCustomID(data: {
id: ${id},
name: "relate me"
}) {
id
name
}
}`;
await client.request(query);
const relation = `mutation {
createRelationshipA(data: {
customID: [ ${id} ]
}) {
customID {
id
}
}
}`;
const relationResponse = await client.request(relation);
const { customID } = relationResponse.createRelationshipA;
expect(customID).toHaveLength(1);
expect(customID).toHaveLength(1);
});
});
});

View File

@@ -70,6 +70,18 @@ async function create(this: Payload, incomingArgs: Arguments): Promise<Document>
await executeAccess({ req }, collectionConfig.access.create);
}
// /////////////////////////////////////
// Custom id
// /////////////////////////////////////
const hasIdField = collectionConfig.fields.findIndex(({ name }) => name === 'id') > -1;
if (hasIdField) {
data = {
_id: data.id,
...data,
};
}
// /////////////////////////////////////
// Upload and resize potential files
// /////////////////////////////////////
@@ -218,6 +230,8 @@ async function create(this: Payload, incomingArgs: Arguments): Promise<Document>
let result: Document = doc.toJSON({ virtuals: true });
const verificationToken = result._verificationToken;
// custom id type reset
result.id = result._id;
result = JSON.stringify(result);
result = JSON.parse(result);
result = sanitizeInternalFields(result);

View File

@@ -135,6 +135,8 @@ async function deleteQuery(incomingArgs: Arguments): Promise<Document> {
let result: Document = doc.toJSON({ virtuals: true });
// custom id type reset
result.id = result._id;
result = JSON.stringify(result);
result = JSON.parse(result);
result = sanitizeInternalFields(result);

View File

@@ -100,6 +100,8 @@ async function find(incomingArgs: Arguments): Promise<PaginatedDocs> {
} else {
sort = '-_id';
}
} else if (sort === 'id' || sort === '-id') {
sort = sort.replace('id', '_id');
}
const optionsToExecute = {

View File

@@ -265,6 +265,9 @@ async function update(incomingArgs: Arguments): Promise<Document> {
}
result = result.toJSON({ virtuals: true });
// custom id type reset
result.id = result._id;
result = JSON.stringify(result);
result = JSON.parse(result);
result = sanitizeInternalFields(result);

View File

@@ -548,4 +548,92 @@ describe('Collections - REST', () => {
expect(failedResponse.status).toStrictEqual(500);
});
});
describe('Custom ID', () => {
const document = {
id: 1,
name: 'name',
};
let data;
beforeAll(async (done) => {
// create document
const create = await fetch(`${url}/api/custom-id`, {
body: JSON.stringify(document),
headers,
method: 'post',
});
data = await create.json();
done();
});
it('should create collections with custom ID', async () => {
expect(data.doc.id).toBe(document.id);
});
it('should read collections by custom ID', async () => {
const response = await fetch(`${url}/api/custom-id/${document.id}`, {
headers,
method: 'get',
});
const result = await response.json();
expect(result.id).toStrictEqual(document.id);
expect(result.name).toStrictEqual(document.name);
});
it('should update collection by custom ID', async () => {
const updatedDoc = { id: 'cannot-update-id', name: 'updated' };
const response = await fetch(`${url}/api/custom-id/${document.id}`, {
headers,
body: JSON.stringify(updatedDoc),
method: 'put',
});
const result = await response.json();
expect(result.doc.id).not.toStrictEqual(updatedDoc.id);
expect(result.doc.name).not.toStrictEqual(document.name);
expect(result.doc.name).toStrictEqual(updatedDoc.name);
});
it('should delete collection by custom ID', async () => {
const doc = {
id: 2,
name: 'delete me',
};
const createResponse = await fetch(`${url}/api/custom-id`, {
body: JSON.stringify(doc),
headers,
method: 'post',
});
const result = await createResponse.json();
const response = await fetch(`${url}/api/custom-id/${result.doc.id}`, {
headers,
method: 'delete',
});
expect(response.status).toBe(200);
const deleteData = await response.json();
expect(deleteData.id).toBe(doc.id);
});
it('should allow querying by custom ID', async () => {
const response = await fetch(`${url}/api/custom-id?where[id][equals]=${document.id}`, {
headers,
method: 'get',
});
const emptyResponse = await fetch(`${url}/api/custom-id?where[id][equals]=900`, {
headers,
method: 'get',
});
const result = await response.json();
const emptyResult = await emptyResponse.json();
expect(result.docs).toHaveLength(1);
expect(emptyResult.docs).toHaveLength(0);
});
});
});

View File

@@ -103,5 +103,59 @@ describe('Collections - REST', () => {
expect(doc.postMaxDepth).toBe(documentB.id);
expect(doc.postMaxDepth).not.toHaveProperty('post');
});
it('should allow a custom id relation', async () => {
const customID = {
id: 30,
name: 'custom',
};
const newCustomID = await fetch(`${url}/api/custom-id`, {
headers,
body: JSON.stringify(customID),
method: 'post',
});
const custom = await newCustomID.json();
const response = await fetch(`${url}/api/relationship-a/${documentA.id}`, {
headers,
body: JSON.stringify({
...documentA,
post: documentB.id,
customID: [custom.doc.id],
}),
method: 'put',
});
const { doc } = await response.json();
expect(doc.customID[0].id).toBe(customID.id);
});
it('should allow a custom id relation and parse the id type', async () => {
const customID = {
id: '40',
name: 'custom',
};
const newCustomID = await fetch(`${url}/api/custom-id`, {
headers,
body: JSON.stringify(customID),
method: 'post',
});
const custom = await newCustomID.json();
const response = await fetch(`${url}/api/relationship-a/${documentA.id}`, {
headers,
body: JSON.stringify({
...documentA,
post: documentB.id,
customID: [custom.doc.id],
}),
method: 'put',
});
const { doc } = await response.json();
expect(custom.doc.id).toBe(parseFloat(customID.id));
expect(doc.customID[0].id).toBe(parseFloat(customID.id));
});
});
});

View File

@@ -1,9 +1,10 @@
import { ValidationResult } from 'joi';
import schema from './schema';
import collectionSchema from '../collections/config/schema';
import Logger from '../utilities/logger';
import { SanitizedConfig } from './types';
import { SanitizedCollectionConfig } from '../collections/config/types';
import fieldSchema from '../fields/config/schema';
import fieldSchema, { idField } from '../fields/config/schema';
import { SanitizedGlobalConfig } from '../globals/config/types';
import globalSchema from '../globals/config/schema';
@@ -12,7 +13,17 @@ const logger = Logger();
const validateFields = (context: string, entity: SanitizedCollectionConfig | SanitizedGlobalConfig): string[] => {
const errors: string[] = [];
entity.fields.forEach((field) => {
let idResult: Partial<ValidationResult> = { error: null };
if (field.name === 'id') {
idResult = idField.validate(field, { abortEarly: false });
}
const result = fieldSchema.validate(field, { abortEarly: false });
if (idResult.error) {
idResult.error.details.forEach(({ message }) => {
errors.push(`${context} "${entity.slug}" > Field "${field.name}" > ${message}`);
});
}
if (result.error) {
result.error.details.forEach(({ message }) => {
errors.push(`${context} "${entity.slug}" > Field "${field.name}" > ${message}`);

View File

@@ -47,6 +47,13 @@ export const baseField = joi.object().keys({
admin: baseAdminFields.default(),
}).default();
export const idField = baseField.keys({
name: joi.string().valid('id'),
type: joi.string().valid('text', 'number'),
required: joi.not(false, 0).default(true),
localized: joi.invalid(true),
});
export const text = baseField.keys({
type: joi.string().valid('text').required(),
name: joi.string().required(),

View File

@@ -87,10 +87,14 @@ const traverseFields = (args: Arguments): void => {
if ((field.type === 'upload' || field.type === 'relationship')
&& (data[field.name] === '' || data[field.name] === 'none' || data[field.name] === 'null')) {
dataCopy[field.name] = null;
if (field.type === 'relationship' && field.hasMany === true) {
dataCopy[field.name] = [];
} else {
dataCopy[field.name] = null;
}
}
if (field.type === 'relationship' && field.hasMany && (data[field.name]?.[0] === '' || data[field.name]?.[0] === 'none' || data[field.name]?.[0] === 'null')) {
if (field.type === 'relationship' && field.hasMany && (data[field.name] === '' || data[field.name] === 'none' || data[field.name] === 'null')) {
dataCopy[field.name] = [];
}
@@ -98,6 +102,15 @@ const traverseFields = (args: Arguments): void => {
dataCopy[field.name] = parseFloat(data[field.name]);
}
if (field.name === 'id') {
if (field.type === 'number' && typeof data[field.name] === 'string') {
dataCopy[field.name] = parseFloat(data[field.name]);
}
if (field.type === 'text' && typeof data[field.name]?.toString === 'function' && typeof data[field.name] !== 'string') {
dataCopy[field.name] = dataCopy[field.name].toString();
}
}
if (field.type === 'checkbox') {
if (data[field.name] === 'true') dataCopy[field.name] = true;
if (data[field.name] === 'false') dataCopy[field.name] = false;
@@ -267,6 +280,44 @@ const traverseFields = (args: Arguments): void => {
updatedData[field.name] = field.defaultValue;
}
if (field.type === 'relationship' || field.type === 'upload') {
if (Array.isArray(field.relationTo)) {
if (Array.isArray(dataCopy[field.name])) {
dataCopy[field.name].forEach((relatedDoc: {value: unknown, relationTo: string}, i) => {
const relatedCollection = payload.config.collections.find((collection) => collection.slug === relatedDoc.relationTo);
const relationshipIDField = relatedCollection.fields.find((collectionField) => collectionField.name === 'id');
if (relationshipIDField?.type === 'number') {
dataCopy[field.name][i] = { ...relatedDoc, value: parseFloat(relatedDoc.value as string) };
}
});
}
if (field.type === 'relationship' && field.hasMany !== true && dataCopy[field.name]?.relationTo) {
const relatedCollection = payload.config.collections.find((collection) => collection.slug === dataCopy[field.name].relationTo);
const relationshipIDField = relatedCollection.fields.find((collectionField) => collectionField.name === 'id');
if (relationshipIDField?.type === 'number') {
dataCopy[field.name] = { ...dataCopy[field.name], value: parseFloat(dataCopy[field.name].value as string) };
}
}
} else {
if (Array.isArray(dataCopy[field.name])) {
dataCopy[field.name].forEach((relatedDoc: unknown, i) => {
const relatedCollection = payload.config.collections.find((collection) => collection.slug === field.relationTo);
const relationshipIDField = relatedCollection.fields.find((collectionField) => collectionField.name === 'id');
if (relationshipIDField?.type === 'number') {
dataCopy[field.name][i] = parseFloat(relatedDoc as string);
}
});
}
if (field.type === 'relationship' && field.hasMany !== true && dataCopy[field.name]) {
const relatedCollection = payload.config.collections.find((collection) => collection.slug === field.relationTo);
const relationshipIDField = relatedCollection.fields.find((collectionField) => collectionField.name === 'id');
if (relationshipIDField?.type === 'number') {
dataCopy[field.name] = parseFloat(dataCopy[field.name]);
}
}
}
}
if (field.type === 'point' && data[field.name]) {
transformActions.push(() => {
if (Array.isArray(data[field.name]) && data[field.name][0] !== null && data[field.name][1] !== null) {

View File

@@ -4,6 +4,7 @@ import {
GraphQLEnumType,
GraphQLFloat,
GraphQLInputObjectType,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
GraphQLScalarType,
@@ -16,10 +17,26 @@ import formatName from '../utilities/formatName';
import combineParentName from '../utilities/combineParentName';
import { ArrayField, Field, FieldWithSubFields, GroupField, RelationshipField, RowField, SelectField } from '../../fields/config/types';
import { toWords } from '../../utilities/formatLabels';
import payload from '../../index';
import { SanitizedCollectionConfig } from '../../collections/config/types';
export const getCollectionIDType = (config: SanitizedCollectionConfig): GraphQLScalarType => {
const idField = config.fields.find(({ name }) => name === 'id');
if (!idField) return GraphQLString;
switch (idField.type) {
case 'number':
return GraphQLInt;
default:
return GraphQLString;
}
};
function buildMutationInputType(name: string, fields: Field[], parentName: string, forceNullable = false): GraphQLInputObjectType {
const fieldToSchemaMap = {
number: (field: Field) => ({ type: withNullableType(field, GraphQLFloat, forceNullable) }),
number: (field: Field) => {
const type = field.name === 'id' ? GraphQLInt : GraphQLFloat;
return { type: withNullableType(field, type, forceNullable) };
},
text: (field: Field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
email: (field: Field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
textarea: (field: Field) => ({ type: withNullableType(field, GraphQLString, forceNullable) }),
@@ -67,7 +84,7 @@ function buildMutationInputType(name: string, fields: Field[], parentName: strin
relationship: (field: RelationshipField) => {
const { relationTo } = field;
type PayloadGraphQLRelationshipType = GraphQLScalarType | GraphQLList<GraphQLScalarType> | GraphQLInputObjectType;
let type: PayloadGraphQLRelationshipType = GraphQLString;
let type: PayloadGraphQLRelationshipType;
if (Array.isArray(relationTo)) {
const fullName = `${combineParentName(parentName, field.label === false ? toWords(field.name, true) : field.label)}RelationshipInput`;
@@ -85,9 +102,11 @@ function buildMutationInputType(name: string, fields: Field[], parentName: strin
}), {}),
}),
},
value: { type: GraphQLString },
value: { type: GraphQLJSON },
},
});
} else {
type = getCollectionIDType(payload.collections[relationTo].config);
}
return { type: field.hasMany ? new GraphQLList(type) : type };

View File

@@ -344,7 +344,7 @@ const buildWhereInputType = (name: string, fields: Field[], parentName: string):
fieldTypes.id = {
type: withOperators(
{ name: 'id' } as Field,
GraphQLString,
GraphQLJSON,
parentName,
[...operators.equality, ...operators.contains],
),

View File

@@ -125,9 +125,6 @@ class ParamParser {
let localizedKey = this.getLocalizedKey(sanitizedKey, schemaObject);
if (key === '_id' || key === 'id') {
localizedKey = '_id';
if (!mongoose.Types.ObjectId.isValid(val)) {
return null;
}
}
if (key.includes('.') || key.includes('__')) {
const paths = key.split('.');

View File

@@ -49,9 +49,18 @@ const formatBaseSchema = (field: Field) => ({
const buildSchema = (config: SanitizedConfig, configFields: Field[], options = {}): Schema => {
let fields = {};
let schemaFields = configFields;
const indexFields = [];
configFields.forEach((field) => {
const idField = schemaFields.find(({ name }) => name === 'id');
if (idField) {
fields = {
_id: idField.type === 'number' ? Number : String,
};
schemaFields = schemaFields.filter(({ name }) => name !== 'id');
}
schemaFields.forEach((field) => {
const fieldSchema: FieldSchemaGenerator = fieldToSchemaMap[field.type];
if (fieldSchema) {
@@ -67,7 +76,9 @@ const buildSchema = (config: SanitizedConfig, configFields: Field[], options = {
indexFields.forEach((index) => {
schema.index(index);
});
indexFields.forEach((index) => {
schema.index(index);
});
setBlockDiscriminators(configFields, schema, config);
return schema;
@@ -315,7 +326,7 @@ const fieldToSchemaMap = {
upload: (field: UploadField, fields: SchemaDefinition, config: SanitizedConfig): SchemaDefinition => {
const baseSchema = {
...formatBaseSchema(field),
type: Schema.Types.ObjectId,
type: Schema.Types.Mixed,
ref: field.relationTo,
};
@@ -350,14 +361,14 @@ const fieldToSchemaMap = {
if (hasManyRelations) {
localeSchema._id = false;
localeSchema.value = {
type: Schema.Types.ObjectId,
type: Schema.Types.Mixed,
refPath: `${field.name}.${locale}.relationTo`,
};
localeSchema.relationTo = { type: String, enum: field.relationTo };
} else {
localeSchema = {
...formatBaseSchema(field),
type: Schema.Types.ObjectId,
type: Schema.Types.Mixed,
ref: field.relationTo,
};
}
@@ -372,7 +383,7 @@ const fieldToSchemaMap = {
} else if (hasManyRelations) {
schemaToReturn._id = false;
schemaToReturn.value = {
type: Schema.Types.ObjectId,
type: Schema.Types.Mixed,
refPath: `${field.name}.relationTo`,
};
schemaToReturn.relationTo = { type: String, enum: field.relationTo };
@@ -381,7 +392,7 @@ const fieldToSchemaMap = {
} else {
schemaToReturn = {
...formatBaseSchema(field),
type: Schema.Types.ObjectId,
type: Schema.Types.Mixed,
ref: field.relationTo,
};