mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 23:24:03 +00:00
Edit CT schema
This commit is contained in:
parent
c2df4bc404
commit
8d025a970d
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const validateComponentCategory = require('./validation/componentCategory');
|
||||
const validateComponentCategory = require('./validation/component-category');
|
||||
|
||||
module.exports = {
|
||||
async editCategory(ctx) {
|
||||
|
||||
@ -11,7 +11,7 @@ const { nameToSlug } = require('../utils/helpers');
|
||||
const {
|
||||
validateContentTypeInput,
|
||||
validateUpdateContentTypeInput,
|
||||
} = require('./validation/contentType');
|
||||
} = require('./validation/content-type');
|
||||
|
||||
module.exports = {
|
||||
getContentTypes(ctx) {
|
||||
@ -50,8 +50,8 @@ module.exports = {
|
||||
return ctx.send({ error }, 400);
|
||||
}
|
||||
|
||||
const slug = nameToSlug(body.name);
|
||||
const uid = `application::${slug}.${slug}`;
|
||||
const modelName = nameToSlug(body.name);
|
||||
const uid = `application::${modelName}.${modelName}`;
|
||||
|
||||
if (_.has(strapi.contentTypes, uid)) {
|
||||
return ctx.send({ error: 'contentType.alreadyExists' }, 400);
|
||||
@ -62,9 +62,12 @@ module.exports = {
|
||||
try {
|
||||
const contentType = createContentTypeSchema(body);
|
||||
|
||||
await generateAPI(slug, contentType);
|
||||
await generateAPI(modelName, contentType);
|
||||
|
||||
await generateReversedRelations({ attributes: body.attributes, slug });
|
||||
await generateReversedRelations({
|
||||
attributes: body.attributes,
|
||||
modelName,
|
||||
});
|
||||
|
||||
if (_.isEmpty(strapi.api)) {
|
||||
strapi.emit('didCreateFirstContentType');
|
||||
@ -111,46 +114,15 @@ module.exports = {
|
||||
|
||||
await writeContentType({ uid, schema: newSchema });
|
||||
|
||||
const updates = Object.keys(strapi.contentTypes).map(ctUID => {
|
||||
const contentType = strapi.contentTypes[ctUID];
|
||||
// delete all relations directed to the updated ct except for oneWay and manyWay
|
||||
await deleteBidirectionalRelations(contentType);
|
||||
|
||||
const keysToDelete = Object.keys(
|
||||
contentType.__schema__.attributes
|
||||
).filter(key => {
|
||||
const attr = contentType.__schema__.attributes[key];
|
||||
if (
|
||||
(attr.model === uid || attr.collection === uid) &&
|
||||
attr.via &&
|
||||
!Object.keys(newSchema.attributes).includes(attr.via)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const keysToUpdate = [];
|
||||
|
||||
if (keysToDelete.length > 0 || keysToUpdate.length > 0) {
|
||||
const newAttributes = _.omit(contentType.__schema__.attributes, [
|
||||
keysToDelete,
|
||||
]);
|
||||
|
||||
const newCTSchema = {
|
||||
...contentType.__schema__,
|
||||
attributes: newAttributes,
|
||||
};
|
||||
|
||||
return writeContentType({ uid: ctUID, schema: newCTSchema });
|
||||
}
|
||||
await generateReversedRelations({
|
||||
attributes: body.attributes,
|
||||
modelName: contentType.modelName,
|
||||
plugin: contentType.plugin,
|
||||
});
|
||||
|
||||
await Promise.all(updates);
|
||||
|
||||
// TODO: clear relations to delete (diff old vs new attributes and delete the ones with via)
|
||||
|
||||
// TODO: update relations that changed
|
||||
// TODO: add new relations (diff old vs new and add new attributes)
|
||||
|
||||
if (_.isEmpty(strapi.api)) {
|
||||
strapi.emit('didCreateFirstContentType');
|
||||
} else {
|
||||
@ -174,45 +146,87 @@ module.exports = {
|
||||
},
|
||||
};
|
||||
|
||||
const generateReversedRelations = ({ attributes, slug, plugin }) => {
|
||||
const deleteBidirectionalRelations = ({ modelName, plugin }) => {
|
||||
const updates = Object.keys(strapi.contentTypes).map(uid => {
|
||||
const { __schema__ } = strapi.contentTypes[uid];
|
||||
|
||||
const keysToDelete = Object.keys(__schema__.attributes).filter(key => {
|
||||
const attr = __schema__.attributes[key];
|
||||
const target = attr.model || attr.collection;
|
||||
|
||||
const sameModel = target === modelName;
|
||||
const samePluginOrNoPlugin =
|
||||
(attr.plugin && attr.plugin === plugin) || !attr.plugin;
|
||||
|
||||
const isBiDirectionnal = _.has(attr, 'via');
|
||||
|
||||
if (samePluginOrNoPlugin && sameModel && isBiDirectionnal) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (keysToDelete.length > 0) {
|
||||
const newchema = {
|
||||
...__schema__,
|
||||
attributes: _.omit(__schema__.attributes, keysToDelete),
|
||||
};
|
||||
|
||||
return writeContentType({ uid, schema: newchema });
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(updates);
|
||||
};
|
||||
|
||||
const buildReversedRelation = ({ key, attr, plugin, modelName }) => {
|
||||
const targetAttributeOptions = {
|
||||
via: key,
|
||||
columnName: attr.targetColumnName,
|
||||
plugin,
|
||||
};
|
||||
|
||||
switch (attr.nature) {
|
||||
case 'manyWay':
|
||||
case 'oneWay':
|
||||
return;
|
||||
case 'oneToOne':
|
||||
case 'oneToMany':
|
||||
targetAttributeOptions.model = modelName;
|
||||
break;
|
||||
case 'manyToOne':
|
||||
targetAttributeOptions.collection = modelName;
|
||||
break;
|
||||
case 'manyToMany': {
|
||||
targetAttributeOptions.collection = modelName;
|
||||
|
||||
if (!targetAttributeOptions.dominant) {
|
||||
targetAttributeOptions.dominant = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
return targetAttributeOptions;
|
||||
};
|
||||
|
||||
const generateReversedRelations = ({ attributes, modelName, plugin }) => {
|
||||
const promises = Object.keys(attributes)
|
||||
.filter(key => _.has(attributes[key], 'target'))
|
||||
.map(key => {
|
||||
const attr = attributes[key];
|
||||
|
||||
const target = strapi.contentTypes[attr.target];
|
||||
|
||||
const targetAttributeOptions = {
|
||||
via: key,
|
||||
columnName: attr.targetColumnName,
|
||||
plugin,
|
||||
};
|
||||
|
||||
switch (attr.nature) {
|
||||
case 'manyWay':
|
||||
case 'oneWay':
|
||||
return;
|
||||
case 'oneToOne':
|
||||
case 'oneToMany':
|
||||
targetAttributeOptions.model = slug;
|
||||
break;
|
||||
case 'manyToOne':
|
||||
targetAttributeOptions.collection = slug;
|
||||
break;
|
||||
case 'manyToMany': {
|
||||
targetAttributeOptions.collection = slug;
|
||||
|
||||
if (!targetAttributeOptions.dominant) {
|
||||
targetAttributeOptions.dominant = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
const schema = _.merge({}, target.__schema__, {
|
||||
attributes: {
|
||||
[attr.targetAttribute]: targetAttributeOptions,
|
||||
[attr.targetAttribute]: buildReversedRelation({
|
||||
key,
|
||||
attr,
|
||||
plugin,
|
||||
modelName,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const yup = require('yup');
|
||||
const _ = require('lodash');
|
||||
const formatYupErrors = require('./yup-formatter');
|
||||
|
||||
const { isValidName, isValidKey } = require('./common');
|
||||
const { getTypeShape } = require('./types');
|
||||
const getRelationValidator = require('./relations');
|
||||
const createSchema = require('./model-schema');
|
||||
|
||||
const VALID_COMPONENT_RELATIONS = ['oneWay', 'manyWay'];
|
||||
const VALID_COMPONENT_TYPES = [
|
||||
const VALID_RELATIONS = ['oneWay', 'manyWay'];
|
||||
const VALID_TYPES = [
|
||||
// advanced types
|
||||
'media',
|
||||
|
||||
@ -33,7 +30,7 @@ const VALID_COMPONENT_TYPES = [
|
||||
];
|
||||
|
||||
const validateComponentInput = data => {
|
||||
return componentSchema
|
||||
return createSchema(VALID_TYPES, VALID_RELATIONS)
|
||||
.validate(data, {
|
||||
strict: true,
|
||||
abortEarly: false,
|
||||
@ -51,7 +48,7 @@ const validateUpdateComponentInput = data => {
|
||||
});
|
||||
}
|
||||
|
||||
return componentSchema
|
||||
return createSchema(VALID_TYPES, VALID_RELATIONS)
|
||||
.validate(data, {
|
||||
strict: true,
|
||||
abortEarly: false,
|
||||
@ -59,65 +56,6 @@ const validateUpdateComponentInput = data => {
|
||||
.catch(error => Promise.reject(formatYupErrors(error)));
|
||||
};
|
||||
|
||||
const componentSchema = yup
|
||||
.object({
|
||||
name: yup
|
||||
.string()
|
||||
.min(1)
|
||||
.required('name.required'),
|
||||
icon: yup
|
||||
.string()
|
||||
.test(isValidName)
|
||||
.required('icon.required'),
|
||||
category: yup
|
||||
.string()
|
||||
.min(3)
|
||||
.test(isValidName)
|
||||
.required('category.required'),
|
||||
description: yup.string(),
|
||||
connection: yup.string(),
|
||||
collectionName: yup
|
||||
.string()
|
||||
.nullable()
|
||||
.test(isValidName),
|
||||
attributes: yup.lazy(obj => {
|
||||
return yup
|
||||
.object()
|
||||
.shape(
|
||||
_.mapValues(obj, (value, key) => {
|
||||
return yup.lazy(obj => {
|
||||
let shape;
|
||||
if (_.has(obj, 'type')) {
|
||||
shape = {
|
||||
type: yup
|
||||
.string()
|
||||
.oneOf(VALID_COMPONENT_TYPES)
|
||||
.required(),
|
||||
...getTypeShape(obj),
|
||||
};
|
||||
} else if (_.has(obj, 'target')) {
|
||||
shape = getRelationValidator(obj, VALID_COMPONENT_RELATIONS);
|
||||
} else {
|
||||
return yup.object().test({
|
||||
name: 'mustHaveTypeOrTarget',
|
||||
message: 'Attribute must have either a type or a target',
|
||||
test: () => false,
|
||||
});
|
||||
}
|
||||
|
||||
return yup
|
||||
.object()
|
||||
.shape(shape)
|
||||
.test(isValidKey(key))
|
||||
.noUnknown();
|
||||
});
|
||||
})
|
||||
)
|
||||
.required('attributes.required');
|
||||
}),
|
||||
})
|
||||
.noUnknown();
|
||||
|
||||
module.exports = {
|
||||
validateComponentInput,
|
||||
validateUpdateComponentInput,
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const formatYupErrors = require('./yup-formatter');
|
||||
|
||||
const createSchema = require('./model-schema');
|
||||
|
||||
const VALID_RELATIONS = [
|
||||
'oneWay',
|
||||
'manyWay',
|
||||
'oneToOne',
|
||||
'oneToMany',
|
||||
'manyToOne',
|
||||
'manyToMany',
|
||||
];
|
||||
const VALID_TYPES = [
|
||||
// advanced types
|
||||
'media',
|
||||
|
||||
// scalar types
|
||||
'string',
|
||||
'text',
|
||||
'richtext',
|
||||
'json',
|
||||
'enumeration',
|
||||
'password',
|
||||
'email',
|
||||
'integer',
|
||||
'biginteger',
|
||||
'float',
|
||||
'decimal',
|
||||
'date',
|
||||
'boolean',
|
||||
|
||||
// nested component
|
||||
'component',
|
||||
'dynamiczone',
|
||||
];
|
||||
|
||||
const validateContentTypeInput = data => {
|
||||
return createSchema(VALID_TYPES, VALID_RELATIONS)
|
||||
.validate(data, {
|
||||
strict: true,
|
||||
abortEarly: false,
|
||||
})
|
||||
.catch(error => Promise.reject(formatYupErrors(error)));
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return createSchema(VALID_TYPES, VALID_RELATIONS)
|
||||
.validate(data, {
|
||||
strict: true,
|
||||
abortEarly: false,
|
||||
})
|
||||
.catch(error => Promise.reject(formatYupErrors(error)));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
validateContentTypeInput,
|
||||
validateUpdateContentTypeInput,
|
||||
};
|
||||
@ -1,123 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const yup = require('yup');
|
||||
const _ = require('lodash');
|
||||
const formatYupErrors = require('./yup-formatter');
|
||||
|
||||
const { isValidName, isValidKey } = require('./common');
|
||||
const { getTypeShape } = require('./types');
|
||||
const getRelationValidator = require('./relations');
|
||||
|
||||
const VALID_COMPONENT_RELATIONS = [
|
||||
'oneWay',
|
||||
'manyWay',
|
||||
'oneToOne',
|
||||
'oneToMany',
|
||||
'manyToOne',
|
||||
'manyToMany',
|
||||
];
|
||||
const VALID_COMPONENT_TYPES = [
|
||||
// advanced types
|
||||
'media',
|
||||
|
||||
// scalar types
|
||||
'string',
|
||||
'text',
|
||||
'richtext',
|
||||
'json',
|
||||
'enumeration',
|
||||
'password',
|
||||
'email',
|
||||
'integer',
|
||||
'biginteger',
|
||||
'float',
|
||||
'decimal',
|
||||
'date',
|
||||
'boolean',
|
||||
|
||||
// nested component
|
||||
'component',
|
||||
'dynamiczone',
|
||||
];
|
||||
|
||||
const validateContentTypeInput = data => {
|
||||
return componentSchema
|
||||
.validate(data, {
|
||||
strict: true,
|
||||
abortEarly: false,
|
||||
})
|
||||
.catch(error => Promise.reject(formatYupErrors(error)));
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return componentSchema
|
||||
.validate(data, {
|
||||
strict: true,
|
||||
abortEarly: false,
|
||||
})
|
||||
.catch(error => Promise.reject(formatYupErrors(error)));
|
||||
};
|
||||
|
||||
const componentSchema = yup
|
||||
.object({
|
||||
name: yup
|
||||
.string()
|
||||
.min(1)
|
||||
.required('name.required'),
|
||||
description: yup.string(),
|
||||
connection: yup.string(),
|
||||
collectionName: yup
|
||||
.string()
|
||||
.nullable()
|
||||
.test(isValidName),
|
||||
attributes: yup.lazy(obj => {
|
||||
return yup
|
||||
.object()
|
||||
.shape(
|
||||
_.mapValues(obj, (value, key) => {
|
||||
return yup.lazy(obj => {
|
||||
let shape;
|
||||
if (_.has(obj, 'type')) {
|
||||
shape = {
|
||||
type: yup
|
||||
.string()
|
||||
.oneOf(VALID_COMPONENT_TYPES)
|
||||
.required(),
|
||||
...getTypeShape(obj),
|
||||
};
|
||||
} else if (_.has(obj, 'target')) {
|
||||
shape = getRelationValidator(obj, VALID_COMPONENT_RELATIONS);
|
||||
} else {
|
||||
return yup.object().test({
|
||||
name: 'mustHaveTypeOrTarget',
|
||||
message: 'Attribute must have either a type or a target',
|
||||
test: () => false,
|
||||
});
|
||||
}
|
||||
|
||||
return yup
|
||||
.object()
|
||||
.shape(shape)
|
||||
.test(isValidKey(key))
|
||||
.noUnknown();
|
||||
});
|
||||
})
|
||||
)
|
||||
.required('attributes.required');
|
||||
}),
|
||||
})
|
||||
.noUnknown();
|
||||
|
||||
module.exports = {
|
||||
validateContentTypeInput,
|
||||
validateUpdateContentTypeInput,
|
||||
};
|
||||
@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const yup = require('yup');
|
||||
|
||||
const { isValidName, isValidKey } = require('./common');
|
||||
const { getTypeShape } = require('./types');
|
||||
const getRelationValidator = require('./relations');
|
||||
|
||||
const createSchema = (types, relations) =>
|
||||
yup
|
||||
.object({
|
||||
name: yup
|
||||
.string()
|
||||
.min(1)
|
||||
.required('name.required'),
|
||||
description: yup.string(),
|
||||
connection: yup.string(),
|
||||
collectionName: yup
|
||||
.string()
|
||||
.nullable()
|
||||
.test(isValidName),
|
||||
attributes: yup.lazy(attributes => {
|
||||
return yup
|
||||
.object()
|
||||
.shape(
|
||||
_.mapValues(attributes, (attribute, key) => {
|
||||
if (_.has(attribute, 'type')) {
|
||||
const shape = {
|
||||
type: yup
|
||||
.string()
|
||||
.oneOf(types)
|
||||
.required(),
|
||||
...getTypeShape(attribute),
|
||||
};
|
||||
|
||||
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');
|
||||
}),
|
||||
})
|
||||
.noUnknown();
|
||||
|
||||
module.exports = createSchema;
|
||||
@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const yup = require('yup');
|
||||
const { validators, isValidName } = require('./common');
|
||||
|
||||
@ -25,15 +24,6 @@ module.exports = (obj, validNatures) => {
|
||||
? yup
|
||||
.string()
|
||||
.test(isValidName)
|
||||
.test({
|
||||
name: 'checkAvailableAttribute',
|
||||
message: `The attribute '${obj.targetAttribute}' already exists in the target`,
|
||||
test: value => {
|
||||
const targetContentType = strapi.contentTypes[obj.target];
|
||||
if (_.has(targetContentType.attributes, value)) return false;
|
||||
return true;
|
||||
},
|
||||
})
|
||||
.required()
|
||||
: yup.string().test(isValidName),
|
||||
targetColumnName: yup.string(),
|
||||
|
||||
19
packages/strapi/lib/core/bootstrap.js
vendored
19
packages/strapi/lib/core/bootstrap.js
vendored
@ -5,6 +5,17 @@ const _ = require('lodash');
|
||||
const { createController, createService } = require('../core-api');
|
||||
const getURLFromSegments = require('../utils/url-from-segments');
|
||||
|
||||
const pickSchema = obj =>
|
||||
_.cloneDeep(
|
||||
_.pick(obj, [
|
||||
'connection',
|
||||
'collectionName',
|
||||
'info',
|
||||
'options',
|
||||
'attributes',
|
||||
])
|
||||
);
|
||||
|
||||
module.exports = function(strapi) {
|
||||
// Retrieve Strapi version.
|
||||
strapi.config.uuid = _.get(strapi.config.info, 'strapi.uuid', '');
|
||||
@ -49,7 +60,7 @@ module.exports = function(strapi) {
|
||||
throw new Error(`Component ${key} is missing a collectionName attribute`);
|
||||
|
||||
Object.assign(component, {
|
||||
__schema__: _.cloneDeep(component),
|
||||
__schema__: pickSchema(component),
|
||||
uid: key,
|
||||
modelType: 'component',
|
||||
globalId:
|
||||
@ -63,7 +74,7 @@ module.exports = function(strapi) {
|
||||
let model = strapi.api[apiName].models[modelName];
|
||||
|
||||
Object.assign(model, {
|
||||
__schema__: _.cloneDeep(model),
|
||||
__schema__: pickSchema(model),
|
||||
modelType: 'contentType',
|
||||
uid: `application::${apiName}.${modelName}`,
|
||||
apiName,
|
||||
@ -144,7 +155,7 @@ module.exports = function(strapi) {
|
||||
let model = strapi.admin.models[key];
|
||||
|
||||
Object.assign(model, {
|
||||
__schema__: _.cloneDeep(model),
|
||||
__schema__: pickSchema(model),
|
||||
modelType: 'contentType',
|
||||
uid: `strapi::${key}`,
|
||||
modelName: key,
|
||||
@ -178,7 +189,7 @@ module.exports = function(strapi) {
|
||||
let model = plugin.models[key];
|
||||
|
||||
Object.assign(model, {
|
||||
__schema__: _.cloneDeep(model),
|
||||
__schema__: pickSchema(model),
|
||||
modelType: 'contentType',
|
||||
modelName: key,
|
||||
uid: `plugins::${pluginName}.${key}`,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user