mirror of
https://github.com/strapi/strapi.git
synced 2025-09-26 17:00:55 +00:00
Add kind property to content-type-builder
Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>
This commit is contained in:
parent
ff1b249d31
commit
57122c4acb
@ -5,10 +5,19 @@ const _ = require('lodash');
|
||||
const {
|
||||
validateContentTypeInput,
|
||||
validateUpdateContentTypeInput,
|
||||
validateKind,
|
||||
} = require('./validation/content-type');
|
||||
|
||||
module.exports = {
|
||||
getContentTypes(ctx) {
|
||||
async getContentTypes(ctx) {
|
||||
const { kind } = ctx.query;
|
||||
|
||||
try {
|
||||
await validateKind(kind);
|
||||
} catch (error) {
|
||||
return ctx.send({ error }, 400);
|
||||
}
|
||||
|
||||
const contentTypeService =
|
||||
strapi.plugins['content-type-builder'].services.contenttypes;
|
||||
|
||||
@ -17,6 +26,13 @@ module.exports = {
|
||||
if (uid.startsWith('strapi::')) return false;
|
||||
if (uid === 'plugins::upload.file') return false; // TODO: add a flag in the content type instead
|
||||
|
||||
if (
|
||||
kind &&
|
||||
_.get(strapi.contentTypes[uid], 'kind', 'collectionType') !== kind
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.map(uid =>
|
||||
|
@ -6,6 +6,7 @@ const yup = require('yup');
|
||||
const { isValidCategoryName, isValidIcon } = require('./common');
|
||||
const formatYupErrors = require('./yup-formatter');
|
||||
const createSchema = require('./model-schema');
|
||||
const removeEmptyDefaults = require('./remove-empty-defaults');
|
||||
const { modelTypes, DEFAULT_TYPES } = require('./constants');
|
||||
|
||||
const VALID_RELATIONS = ['oneWay', 'manyWay'];
|
||||
@ -63,26 +64,7 @@ const validateComponentInput = data => {
|
||||
};
|
||||
|
||||
const validateUpdateComponentInput = data => {
|
||||
// convert zero length string on default attributes to undefined
|
||||
if (_.has(data, ['component', 'attributes'])) {
|
||||
Object.keys(data.component.attributes).forEach(attribute => {
|
||||
if (data.component.attributes[attribute].default === '') {
|
||||
data.component.attributes[attribute].default = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (_.has(data, 'components') && Array.isArray(data.components)) {
|
||||
data.components.forEach(data => {
|
||||
if (_.has(data, 'attributes') && _.has(data, 'uid')) {
|
||||
Object.keys(data.attributes).forEach(attribute => {
|
||||
if (data.attributes[attribute].default === '') {
|
||||
data.attributes[attribute].default = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
removeEmptyDefaults(data);
|
||||
|
||||
return yup
|
||||
.object({
|
||||
|
@ -3,6 +3,9 @@
|
||||
const CONTENT_TYPE = 'CONTENT_TYPE';
|
||||
const COMPONENT = 'COMPONENT';
|
||||
|
||||
const SINGLE_TYPE = 'singleType';
|
||||
const COLLECTION_TYPE = 'collectionType';
|
||||
|
||||
const DEFAULT_TYPES = [
|
||||
// advanced types
|
||||
'media',
|
||||
@ -28,8 +31,15 @@ const DEFAULT_TYPES = [
|
||||
|
||||
const FORBIDDEN_ATTRIBUTE_NAMES = ['__component', '__contentType'];
|
||||
|
||||
const CONTENT_TYPE_KINDS = [SINGLE_TYPE, COLLECTION_TYPE];
|
||||
|
||||
module.exports = {
|
||||
DEFAULT_TYPES,
|
||||
CONTENT_TYPE_KINDS,
|
||||
typeKinds: {
|
||||
SINGLE_TYPE,
|
||||
COLLECTION_TYPE,
|
||||
},
|
||||
modelTypes: {
|
||||
CONTENT_TYPE,
|
||||
COMPONENT,
|
||||
|
@ -2,35 +2,66 @@
|
||||
|
||||
const _ = require('lodash');
|
||||
const yup = require('yup');
|
||||
|
||||
const formatYupErrors = require('./yup-formatter');
|
||||
|
||||
const createSchema = require('./model-schema');
|
||||
const removeEmptyDefaults = require('./remove-empty-defaults');
|
||||
const { nestedComponentSchema } = require('./component');
|
||||
const { modelTypes, DEFAULT_TYPES } = require('./constants');
|
||||
const {
|
||||
modelTypes,
|
||||
DEFAULT_TYPES,
|
||||
CONTENT_TYPE_KINDS,
|
||||
typeKinds,
|
||||
} = require('./constants');
|
||||
|
||||
const VALID_RELATIONS = [
|
||||
'oneWay',
|
||||
'manyWay',
|
||||
'oneToOne',
|
||||
'oneToMany',
|
||||
'manyToOne',
|
||||
'manyToMany',
|
||||
];
|
||||
/**
|
||||
* Allowed relation per type kind
|
||||
*/
|
||||
const VALID_RELATIONS = {
|
||||
[typeKinds.SINGLE_TYPE]: ['oneWay', 'manyWay'],
|
||||
[typeKinds.COLLECTION_TYPE]: [
|
||||
'oneWay',
|
||||
'manyWay',
|
||||
'oneToOne',
|
||||
'oneToMany',
|
||||
'manyToOne',
|
||||
'manyToMany',
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Allowed types
|
||||
*/
|
||||
const VALID_TYPES = [...DEFAULT_TYPES, 'component', 'dynamiczone'];
|
||||
|
||||
const contentTypeSchema = createSchema(VALID_TYPES, VALID_RELATIONS, {
|
||||
modelType: modelTypes.CONTENT_TYPE,
|
||||
});
|
||||
/**
|
||||
* Returns a yup schema to validate a content type payload
|
||||
* @param {Object} data payload
|
||||
*/
|
||||
const createContentTypeSchema = data => {
|
||||
const kind = _.get(data, 'kind', typeKinds.COLLECTION_TYPE);
|
||||
|
||||
const createContentTypeSchema = yup
|
||||
.object({
|
||||
contentType: contentTypeSchema.required().noUnknown(),
|
||||
components: nestedComponentSchema,
|
||||
})
|
||||
.noUnknown();
|
||||
const contentTypeSchema = createSchema(
|
||||
VALID_TYPES,
|
||||
VALID_RELATIONS[kind] || [],
|
||||
{
|
||||
modelType: modelTypes.CONTENT_TYPE,
|
||||
}
|
||||
);
|
||||
|
||||
return yup
|
||||
.object({
|
||||
contentType: contentTypeSchema.required().noUnknown(),
|
||||
components: nestedComponentSchema,
|
||||
})
|
||||
.noUnknown();
|
||||
};
|
||||
|
||||
/**
|
||||
* Validator for content type creation
|
||||
*/
|
||||
const validateContentTypeInput = data => {
|
||||
return createContentTypeSchema
|
||||
return createContentTypeSchema(data)
|
||||
.validate(data, {
|
||||
strict: true,
|
||||
abortEarly: false,
|
||||
@ -38,29 +69,13 @@ const validateContentTypeInput = data => {
|
||||
.catch(error => Promise.reject(formatYupErrors(error)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Validator for content type edition
|
||||
*/
|
||||
const validateUpdateContentTypeInput = data => {
|
||||
// convert zero length string on default attributes to undefined
|
||||
if (_.has(data, 'attributes')) {
|
||||
Object.keys(data.attributes).forEach(attribute => {
|
||||
if (data.attributes[attribute].default === '') {
|
||||
data.attributes[attribute].default = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
removeEmptyDefaults(data);
|
||||
|
||||
if (_.has(data, 'components') && Array.isArray(data.components)) {
|
||||
data.components.forEach(data => {
|
||||
if (_.has(data, 'attributes') && _.has(data, 'uid')) {
|
||||
Object.keys(data.attributes).forEach(attribute => {
|
||||
if (data.attributes[attribute].default === '') {
|
||||
data.attributes[attribute].default = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return createContentTypeSchema
|
||||
return createContentTypeSchema(data)
|
||||
.validate(data, {
|
||||
strict: true,
|
||||
abortEarly: false,
|
||||
@ -68,7 +83,20 @@ const validateUpdateContentTypeInput = data => {
|
||||
.catch(error => Promise.reject(formatYupErrors(error)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates type kind
|
||||
*/
|
||||
const validateKind = kind => {
|
||||
return yup
|
||||
.string()
|
||||
.oneOf(CONTENT_TYPE_KINDS)
|
||||
.nullable()
|
||||
.validate(kind)
|
||||
.catch(error => Promise.reject(formatYupErrors(error)));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
validateContentTypeInput,
|
||||
validateUpdateContentTypeInput,
|
||||
validateKind,
|
||||
};
|
||||
|
@ -3,72 +3,88 @@
|
||||
const _ = require('lodash');
|
||||
const yup = require('yup');
|
||||
|
||||
const { FORBIDDEN_ATTRIBUTE_NAMES } = require('./constants');
|
||||
const {
|
||||
modelTypes,
|
||||
FORBIDDEN_ATTRIBUTE_NAMES,
|
||||
CONTENT_TYPE_KINDS,
|
||||
} = require('./constants');
|
||||
const { isValidCollectionName, isValidKey } = require('./common');
|
||||
const { getTypeShape } = require('./types');
|
||||
const getRelationValidator = require('./relations');
|
||||
|
||||
const createSchema = (types, relations, { modelType } = {}) =>
|
||||
yup
|
||||
.object({
|
||||
name: yup
|
||||
.string()
|
||||
.min(1)
|
||||
.required('name.required'),
|
||||
description: yup.string(),
|
||||
connection: yup.string(),
|
||||
collectionName: yup
|
||||
.string()
|
||||
.nullable()
|
||||
.test(isValidCollectionName),
|
||||
attributes: yup.lazy(attributes => {
|
||||
return yup
|
||||
.object()
|
||||
.shape(
|
||||
_.mapValues(attributes, (attribute, key) => {
|
||||
if (FORBIDDEN_ATTRIBUTE_NAMES.includes(key)) {
|
||||
return yup.object().test({
|
||||
name: 'forbiddenKeys',
|
||||
message: `Attribute keys cannot be one of ${FORBIDDEN_ATTRIBUTE_NAMES.join(
|
||||
', '
|
||||
)}`,
|
||||
test: () => false,
|
||||
});
|
||||
}
|
||||
|
||||
if (_.has(attribute, 'type')) {
|
||||
const shape = {
|
||||
type: yup
|
||||
.string()
|
||||
.oneOf(types)
|
||||
.required(),
|
||||
configurable: yup.boolean().nullable(),
|
||||
private: yup.boolean().nullable(),
|
||||
...getTypeShape(attribute, { modelType }),
|
||||
};
|
||||
|
||||
return yup
|
||||
.object(shape)
|
||||
.test(isValidKey(key))
|
||||
.noUnknown();
|
||||
} else if (_.has(attribute, 'target')) {
|
||||
const shape = getRelationValidator(attribute, relations);
|
||||
|
||||
return yup
|
||||
.object(shape)
|
||||
.test(isValidKey(key))
|
||||
.noUnknown();
|
||||
}
|
||||
const createSchema = (types, relations, { modelType } = {}) => {
|
||||
const schema = yup.object({
|
||||
name: yup
|
||||
.string()
|
||||
.min(1)
|
||||
.required('name.required'),
|
||||
description: yup.string(),
|
||||
connection: yup.string(),
|
||||
collectionName: yup
|
||||
.string()
|
||||
.nullable()
|
||||
.test(isValidCollectionName),
|
||||
attributes: yup.lazy(attributes => {
|
||||
return yup
|
||||
.object()
|
||||
.shape(
|
||||
_.mapValues(attributes, (attribute, key) => {
|
||||
if (FORBIDDEN_ATTRIBUTE_NAMES.includes(key)) {
|
||||
return yup.object().test({
|
||||
name: 'mustHaveTypeOrTarget',
|
||||
message: 'Attribute must have either a type or a target',
|
||||
name: 'forbiddenKeys',
|
||||
message: `Attribute keys cannot be one of ${FORBIDDEN_ATTRIBUTE_NAMES.join(
|
||||
', '
|
||||
)}`,
|
||||
test: () => false,
|
||||
});
|
||||
})
|
||||
)
|
||||
.required('attributes.required');
|
||||
}),
|
||||
})
|
||||
.noUnknown();
|
||||
}
|
||||
|
||||
if (_.has(attribute, 'type')) {
|
||||
const shape = {
|
||||
type: yup
|
||||
.string()
|
||||
.oneOf(types)
|
||||
.required(),
|
||||
configurable: yup.boolean().nullable(),
|
||||
private: yup.boolean().nullable(),
|
||||
...getTypeShape(attribute, { modelType }),
|
||||
};
|
||||
|
||||
return yup
|
||||
.object(shape)
|
||||
.test(isValidKey(key))
|
||||
.noUnknown();
|
||||
} else if (_.has(attribute, 'target')) {
|
||||
const shape = getRelationValidator(attribute, relations);
|
||||
|
||||
return yup
|
||||
.object(shape)
|
||||
.test(isValidKey(key))
|
||||
.noUnknown();
|
||||
}
|
||||
return yup.object().test({
|
||||
name: 'mustHaveTypeOrTarget',
|
||||
message: 'Attribute must have either a type or a target',
|
||||
test: () => false,
|
||||
});
|
||||
})
|
||||
)
|
||||
.required('attributes.required');
|
||||
}),
|
||||
});
|
||||
|
||||
if (modelType === modelTypes.CONTENT_TYPE) {
|
||||
return schema
|
||||
.shape({
|
||||
kind: yup
|
||||
.string()
|
||||
.oneOf(CONTENT_TYPE_KINDS)
|
||||
.required('contentType.kind.required'),
|
||||
})
|
||||
.noUnknown();
|
||||
}
|
||||
|
||||
return schema.noUnknown();
|
||||
};
|
||||
|
||||
module.exports = createSchema;
|
||||
|
@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Convert zero length string on default attributes to undefined
|
||||
*/
|
||||
module.exports = data => {
|
||||
if (_.has(data, 'attributes')) {
|
||||
Object.keys(data.attributes).forEach(attribute => {
|
||||
if (data.attributes[attribute].default === '') {
|
||||
data.attributes[attribute].default = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (_.has(data, 'components') && Array.isArray(data.components)) {
|
||||
data.components.forEach(data => {
|
||||
if (_.has(data, 'attributes') && _.has(data, 'uid')) {
|
||||
Object.keys(data.attributes).forEach(attribute => {
|
||||
if (data.attributes[attribute].default === '') {
|
||||
data.attributes[attribute].default = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
@ -17,7 +17,7 @@ const { nameToSlug } = require('../utils/helpers');
|
||||
* @param {Object} contentType
|
||||
*/
|
||||
const formatContentType = contentType => {
|
||||
const { uid, plugin, connection, collectionName, info } = contentType;
|
||||
const { uid, kind, plugin, connection, collectionName, info } = contentType;
|
||||
|
||||
return {
|
||||
uid,
|
||||
@ -26,6 +26,7 @@ const formatContentType = contentType => {
|
||||
name: _.get(info, 'name') || _.upperFirst(pluralize(uid)),
|
||||
description: _.get(info, 'description', ''),
|
||||
connection,
|
||||
kind: kind || 'collectionType',
|
||||
collectionName,
|
||||
attributes: formatAttributes(contentType),
|
||||
},
|
||||
|
@ -80,6 +80,7 @@ module.exports = function createComponentBuilder() {
|
||||
contentType
|
||||
.setUID(uid)
|
||||
.set('connection', infos.connection || defaultConnection)
|
||||
.set('kind', infos.kind)
|
||||
.set('collectionName', infos.collectionName || defaultCollectionName)
|
||||
.set(['info', 'name'], infos.name)
|
||||
.set(['info', 'description'], infos.description)
|
||||
@ -197,9 +198,12 @@ module.exports = function createComponentBuilder() {
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: handle kind change => update routes.json file somehow
|
||||
|
||||
contentType
|
||||
.set('connection', infos.connection)
|
||||
.set('collectionName', infos.collectionName)
|
||||
.set('kind', infos.kind)
|
||||
.set(['info', 'name'], infos.name)
|
||||
.set(['info', 'description'], infos.description)
|
||||
.setAttributes(this.convertAttributes(newAttributes));
|
||||
|
@ -16,6 +16,7 @@ module.exports = function createSchemaHandler(infos) {
|
||||
dir,
|
||||
filename,
|
||||
schema: schema || {
|
||||
kind: undefined,
|
||||
info: {},
|
||||
options: {},
|
||||
attributes: {},
|
||||
|
Loading…
x
Reference in New Issue
Block a user