mirror of
https://github.com/strapi/strapi.git
synced 2025-12-28 15:44:59 +00:00
Handle component update and delete with categories
This commit is contained in:
parent
cfc5cf07ad
commit
d405285fae
@ -40,7 +40,7 @@
|
||||
},
|
||||
"body": {
|
||||
"type": "dynamiczone",
|
||||
"components": ["closingperiod", "restaurantservice"]
|
||||
"components": ["default.closingperiod", "default.restaurantservice"]
|
||||
},
|
||||
"description": {
|
||||
"type": "richtext",
|
||||
@ -69,11 +69,6 @@
|
||||
},
|
||||
"short_description": {
|
||||
"type": "text"
|
||||
},
|
||||
"metas": {
|
||||
"type": "component",
|
||||
"component": "seo.meta",
|
||||
"repeatable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
{
|
||||
"info": {
|
||||
"name": "meta",
|
||||
"description": ""
|
||||
},
|
||||
"connection": "default",
|
||||
"collectionName": "seo_meta",
|
||||
"attributes": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -25,7 +25,7 @@ describe.each([
|
||||
});
|
||||
|
||||
await modelsUtils.createModelWithType('withcomponent', 'component', {
|
||||
component: 'somecomponent',
|
||||
component: 'default.somecomponent',
|
||||
repeatable: true,
|
||||
required: false,
|
||||
min: 1,
|
||||
|
||||
@ -25,7 +25,7 @@ describe.each([
|
||||
});
|
||||
|
||||
await modelsUtils.createModelWithType('withcomponent', 'component', {
|
||||
component: 'somecomponent',
|
||||
component: 'default.somecomponent',
|
||||
repeatable: true,
|
||||
required: false,
|
||||
});
|
||||
|
||||
@ -25,7 +25,7 @@ describe.each([
|
||||
});
|
||||
|
||||
await modelsUtils.createModelWithType('withcomponent', 'component', {
|
||||
component: 'somecomponent',
|
||||
component: 'default.somecomponent',
|
||||
repeatable: true,
|
||||
required: true,
|
||||
});
|
||||
|
||||
@ -25,7 +25,7 @@ describe.each([
|
||||
});
|
||||
|
||||
await modelsUtils.createModelWithType('withcomponent', 'component', {
|
||||
component: 'somecomponent',
|
||||
component: 'default.somecomponent',
|
||||
repeatable: false,
|
||||
required: false,
|
||||
});
|
||||
|
||||
@ -25,7 +25,7 @@ describe.each([
|
||||
});
|
||||
|
||||
await modelsUtils.createModelWithType('withcomponent', 'component', {
|
||||
component: 'somecomponent',
|
||||
component: 'default.somecomponent',
|
||||
repeatable: false,
|
||||
required: true,
|
||||
});
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
'use strict';
|
||||
const validateComponentInput = require('./validation/component');
|
||||
const _ = require('lodash');
|
||||
const {
|
||||
validateComponentInput,
|
||||
validateUpdateComponentInput,
|
||||
} = require('./validation/component');
|
||||
|
||||
/**
|
||||
* Components controller
|
||||
*/
|
||||
@ -59,7 +62,7 @@ module.exports = {
|
||||
|
||||
strapi.reload.isWatching = false;
|
||||
|
||||
const newComponent = await service.createComponent(uid, body);
|
||||
const newComponent = await service.createComponent({ uid, infos: body });
|
||||
|
||||
strapi.reload();
|
||||
|
||||
@ -82,24 +85,24 @@ module.exports = {
|
||||
return ctx.send({ error: 'component.notFound' }, 404);
|
||||
}
|
||||
|
||||
// convert zero length string on default attributes to undefined
|
||||
if (_.has(body, 'attributes')) {
|
||||
Object.keys(body.attributes).forEach(attribute => {
|
||||
if (body.attributes[attribute].default === '') {
|
||||
body.attributes[attribute].default = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await validateComponentInput(body);
|
||||
await validateUpdateComponentInput(body);
|
||||
} catch (error) {
|
||||
return ctx.send({ error }, 400);
|
||||
}
|
||||
|
||||
const newUID = service.createComponentUID(body);
|
||||
if (newUID !== uid && service.getComponent(newUID)) {
|
||||
return ctx.send({ error: 'new.component.alreadyExists' }, 400);
|
||||
}
|
||||
|
||||
strapi.reload.isWatching = false;
|
||||
|
||||
const updatedComponent = await service.updateComponent(component, body);
|
||||
const updatedComponent = await service.updateComponent({
|
||||
newUID,
|
||||
component,
|
||||
infos: body,
|
||||
});
|
||||
await service.updateComponentInModels(component.uid, updatedComponent.uid);
|
||||
|
||||
strapi.reload();
|
||||
|
||||
@ -8,7 +8,25 @@ const { isValidName, isValidKey } = require('./common');
|
||||
const getTypeValidator = require('./types');
|
||||
const getRelationValidator = require('./relations');
|
||||
|
||||
module.exports = data => {
|
||||
const validateComponentInput = data => {
|
||||
return componentSchema
|
||||
.validate(data, {
|
||||
strict: true,
|
||||
abortEarly: false,
|
||||
})
|
||||
.catch(error => Promise.reject(formatYupErrors(error)));
|
||||
};
|
||||
|
||||
const validateUpdateComponentInput = 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,
|
||||
@ -69,3 +87,8 @@ const componentSchema = yup
|
||||
}),
|
||||
})
|
||||
.noUnknown();
|
||||
|
||||
module.exports = {
|
||||
validateComponentInput,
|
||||
validateUpdateComponentInput,
|
||||
};
|
||||
|
||||
@ -107,11 +107,10 @@ const formatAttribute = (key, attribute, { component }) => {
|
||||
* @param {string} uid
|
||||
* @param {Object} infos
|
||||
*/
|
||||
async function createComponent(uid, infos) {
|
||||
const { name, category } = infos;
|
||||
const schema = createSchema(uid, infos);
|
||||
async function createComponent({ uid, infos }) {
|
||||
const schema = createSchema(infos);
|
||||
|
||||
await writeSchema({ name, schema, category });
|
||||
await writeSchema({ uid, schema });
|
||||
return { uid };
|
||||
}
|
||||
|
||||
@ -120,13 +119,14 @@ async function createComponent(uid, infos) {
|
||||
* @param {Object} component
|
||||
* @param {Object} infos
|
||||
*/
|
||||
async function updateComponent(component, infos) {
|
||||
async function updateComponent({ component, newUID, infos }) {
|
||||
const { uid, schema: oldSchema } = component;
|
||||
|
||||
// don't update collectionName if not provided
|
||||
const updatedSchema = {
|
||||
info: {
|
||||
name: infos.name || oldSchema.name,
|
||||
icon: infos.icon,
|
||||
name: infos.name,
|
||||
description: infos.description || oldSchema.description,
|
||||
},
|
||||
connection: infos.connection || oldSchema.connection,
|
||||
@ -134,7 +134,6 @@ async function updateComponent(component, infos) {
|
||||
attributes: convertAttributes(infos.attributes),
|
||||
};
|
||||
|
||||
const newUID = createComponentUID(infos.name);
|
||||
if (uid !== newUID) {
|
||||
await deleteSchema(uid);
|
||||
|
||||
@ -146,11 +145,23 @@ async function updateComponent(component, infos) {
|
||||
]).updateUID(uid, newUID);
|
||||
}
|
||||
|
||||
await writeSchema(newUID, updatedSchema);
|
||||
await writeSchema({
|
||||
uid: newUID,
|
||||
schema: updatedSchema,
|
||||
});
|
||||
|
||||
const [category] = uid.split('.');
|
||||
|
||||
const categoryDir = path.join(strapi.dir, 'components', category);
|
||||
const categoryCompos = await fse.readdir(categoryDir);
|
||||
if (categoryCompos.length === 0) {
|
||||
await fse.rmdir(categoryDir);
|
||||
}
|
||||
|
||||
return { uid: newUID };
|
||||
}
|
||||
|
||||
await writeSchema(uid, updatedSchema);
|
||||
await writeSchema({ uid, schema: updatedSchema });
|
||||
return { uid };
|
||||
}
|
||||
|
||||
@ -158,7 +169,7 @@ async function updateComponent(component, infos) {
|
||||
* Create a schema
|
||||
* @param {Object} infos
|
||||
*/
|
||||
const createSchema = (uid, infos) => {
|
||||
const createSchema = infos => {
|
||||
const {
|
||||
name,
|
||||
icon,
|
||||
@ -252,16 +263,15 @@ async function deleteComponent(component) {
|
||||
/**
|
||||
* Writes a component schema file
|
||||
*/
|
||||
async function writeSchema({ name, schema, category }) {
|
||||
async function writeSchema({ uid, schema }) {
|
||||
const [category, filename] = uid.split('.');
|
||||
const categoryDir = path.join(strapi.dir, 'components', category);
|
||||
|
||||
if (!(await fse.pathExists(categoryDir))) {
|
||||
await fse.mkdir(categoryDir);
|
||||
await fse.ensureDir(categoryDir);
|
||||
}
|
||||
|
||||
const filename = nameToSlug(name);
|
||||
const filepath = path.join(categoryDir, `${filename}.json`);
|
||||
|
||||
await fse.writeFile(filepath, JSON.stringify(schema, null, 2));
|
||||
}
|
||||
|
||||
@ -270,7 +280,8 @@ async function writeSchema({ name, schema, category }) {
|
||||
* @param {string} ui
|
||||
*/
|
||||
async function deleteSchema(uid) {
|
||||
await strapi.fs.removeAppFile(`components/${uid}.json`);
|
||||
const [category, filename] = uid.split('.');
|
||||
await strapi.fs.removeAppFile(`components/${category}/${filename}.json`);
|
||||
}
|
||||
|
||||
const updateComponentInModels = (oldUID, newUID) => {
|
||||
@ -295,6 +306,13 @@ const updateComponentInModels = (oldUID, newUID) => {
|
||||
[]
|
||||
);
|
||||
|
||||
const dynamicoznesToUpdate = Object.keys(model.attributes).filter(key => {
|
||||
return (
|
||||
model.attributes[key].type === 'dynamiczone' &&
|
||||
model.attributes[key].components.includes(oldUID)
|
||||
);
|
||||
}, []);
|
||||
|
||||
if (attributesToModify.length > 0) {
|
||||
const modelJSON = contentTypeService.readModel(modelKey, {
|
||||
plugin,
|
||||
@ -305,6 +323,15 @@ const updateComponentInModels = (oldUID, newUID) => {
|
||||
modelJSON.attributes[key].component = newUID;
|
||||
});
|
||||
|
||||
dynamicoznesToUpdate.forEach(key => {
|
||||
modelJSON.attributes[key] = {
|
||||
...modelJSON.attributes[key],
|
||||
components: modelJSON.attributes[key].components.map(val =>
|
||||
val !== oldUID ? val : newUID
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
contentTypeService.writeModel(modelKey, modelJSON, {
|
||||
plugin,
|
||||
api: model.apiName,
|
||||
@ -319,10 +346,35 @@ const updateComponentInModels = (oldUID, newUID) => {
|
||||
updateModels(strapi.plugins[pluginKey].models, { plugin: pluginKey });
|
||||
});
|
||||
|
||||
// add strapi.components or strapi.admin if necessary
|
||||
Object.keys(strapi.components).forEach(uid => {
|
||||
const component = strapi.components[uid];
|
||||
|
||||
const componentsToRemove = Object.keys(component.attributes).filter(key => {
|
||||
return (
|
||||
component.attributes[key].type === 'component' &&
|
||||
component.attributes[key].component === oldUID
|
||||
);
|
||||
}, []);
|
||||
|
||||
if (componentsToRemove.length > 0) {
|
||||
const newSchema = {
|
||||
info: component.info,
|
||||
connection: component.connection,
|
||||
collectionName: component.collectionName,
|
||||
attributes: component.attributes,
|
||||
};
|
||||
|
||||
componentsToRemove.forEach(key => {
|
||||
newSchema.attributes[key].component = newUID;
|
||||
});
|
||||
|
||||
writeSchema({ uid, schema: newSchema });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const deleteComponentInModels = componentUID => {
|
||||
const deleteComponentInModels = async componentUID => {
|
||||
const [category] = componentUID.split('.');
|
||||
const contentTypeService =
|
||||
strapi.plugins['content-type-builder'].services.contenttypebuilder;
|
||||
|
||||
@ -330,30 +382,39 @@ const deleteComponentInModels = componentUID => {
|
||||
Object.keys(models).forEach(modelKey => {
|
||||
const model = models[modelKey];
|
||||
|
||||
const attributesToDelete = Object.keys(model.attributes).reduce(
|
||||
(acc, key) => {
|
||||
if (
|
||||
model.attributes[key].type === 'component' &&
|
||||
model.attributes[key].component === componentUID
|
||||
) {
|
||||
acc.push(key);
|
||||
}
|
||||
const componentsToRemove = Object.keys(model.attributes).filter(key => {
|
||||
return (
|
||||
model.attributes[key].type === 'component' &&
|
||||
model.attributes[key].component === componentUID
|
||||
);
|
||||
}, []);
|
||||
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
const dynamicoznesToUpdate = Object.keys(model.attributes).filter(key => {
|
||||
return (
|
||||
model.attributes[key].type === 'dynamiczone' &&
|
||||
model.attributes[key].components.includes(componentUID)
|
||||
);
|
||||
}, []);
|
||||
|
||||
if (attributesToDelete.length > 0) {
|
||||
if (componentsToRemove.length > 0 || dynamicoznesToUpdate.length > 0) {
|
||||
const modelJSON = contentTypeService.readModel(modelKey, {
|
||||
plugin,
|
||||
api: model.apiName,
|
||||
});
|
||||
|
||||
attributesToDelete.forEach(key => {
|
||||
componentsToRemove.forEach(key => {
|
||||
delete modelJSON.attributes[key];
|
||||
});
|
||||
|
||||
dynamicoznesToUpdate.forEach(key => {
|
||||
modelJSON.attributes[key] = {
|
||||
...modelJSON.attributes[key],
|
||||
components: modelJSON.attributes[key].components.filter(
|
||||
val => val !== componentUID
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
contentTypeService.writeModel(modelKey, modelJSON, {
|
||||
plugin,
|
||||
api: model.apiName,
|
||||
@ -368,7 +429,37 @@ const deleteComponentInModels = componentUID => {
|
||||
updateModels(strapi.plugins[pluginKey].models, { plugin: pluginKey });
|
||||
});
|
||||
|
||||
// add strapi.components or strapi.admin if necessary
|
||||
Object.keys(strapi.components).forEach(uid => {
|
||||
const component = strapi.components[uid];
|
||||
|
||||
const componentsToRemove = Object.keys(component.attributes).filter(key => {
|
||||
return (
|
||||
component.attributes[key].type === 'component' &&
|
||||
component.attributes[key].component === componentUID
|
||||
);
|
||||
}, []);
|
||||
|
||||
if (componentsToRemove.length > 0) {
|
||||
const newSchema = {
|
||||
info: component.info,
|
||||
connection: component.connection,
|
||||
collectionName: component.collectionName,
|
||||
attributes: component.attributes,
|
||||
};
|
||||
|
||||
componentsToRemove.forEach(key => {
|
||||
delete newSchema.attributes[key];
|
||||
});
|
||||
|
||||
writeSchema({ uid, schema: newSchema });
|
||||
}
|
||||
});
|
||||
|
||||
const categoryDir = path.join(strapi.dir, 'components', category);
|
||||
const categoryCompos = await fse.readdir(categoryDir);
|
||||
if (categoryCompos.length === 0) {
|
||||
await fse.rmdir(categoryDir);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
||||
@ -4,6 +4,7 @@ describe('Component Service', () => {
|
||||
describe('createSchema', () => {
|
||||
test('Formats schema and create default values', () => {
|
||||
const input = {
|
||||
category: 'default',
|
||||
name: 'Some name',
|
||||
attributes: {},
|
||||
};
|
||||
@ -24,11 +25,11 @@ describe('Component Service', () => {
|
||||
description: '',
|
||||
},
|
||||
connection: 'default',
|
||||
collectionName: 'components_some_names',
|
||||
collectionName: 'components_default_some_names',
|
||||
attributes: {},
|
||||
};
|
||||
|
||||
expect(createSchema('some_name', input)).toEqual(expected);
|
||||
expect(createSchema(input)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('Accepts overrides', () => {
|
||||
@ -49,15 +50,23 @@ describe('Component Service', () => {
|
||||
attributes: {},
|
||||
};
|
||||
|
||||
expect(createSchema('some_name', input)).toEqual(expected);
|
||||
expect(createSchema(input)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createComponentUID', () => {
|
||||
test('Generats normalized uids', () => {
|
||||
expect(createComponentUID('some char')).toBe('some_char');
|
||||
expect(createComponentUID('some-char')).toBe('some_char');
|
||||
expect(createComponentUID('Some Char')).toBe('some_char');
|
||||
expect(
|
||||
createComponentUID({ category: 'default', name: 'some char' })
|
||||
).toBe('default.some_char');
|
||||
|
||||
expect(
|
||||
createComponentUID({ category: 'default', name: 'some-char' })
|
||||
).toBe('default.some_char');
|
||||
|
||||
expect(
|
||||
createComponentUID({ category: 'default', name: 'Some Char' })
|
||||
).toBe('default.some_char');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -20,6 +20,8 @@ describe.only('Content Type Builder - Components', () => {
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toEqual({
|
||||
error: {
|
||||
category: ['category.required'],
|
||||
icon: ['icon.required'],
|
||||
attributes: ['attributes.required'],
|
||||
name: ['name.required'],
|
||||
},
|
||||
@ -31,7 +33,9 @@ describe.only('Content Type Builder - Components', () => {
|
||||
method: 'POST',
|
||||
url: '/content-type-builder/components',
|
||||
body: {
|
||||
name: 'SomeComponent',
|
||||
category: 'default',
|
||||
icon: 'default',
|
||||
name: 'Some Component',
|
||||
attributes: {
|
||||
title: {
|
||||
type: 'string',
|
||||
@ -46,7 +50,7 @@ describe.only('Content Type Builder - Components', () => {
|
||||
expect(res.statusCode).toBe(201);
|
||||
expect(res.body).toEqual({
|
||||
data: {
|
||||
uid: 'some_component',
|
||||
uid: 'default.some_component',
|
||||
},
|
||||
});
|
||||
|
||||
@ -58,6 +62,8 @@ describe.only('Content Type Builder - Components', () => {
|
||||
method: 'POST',
|
||||
url: '/content-type-builder/components',
|
||||
body: {
|
||||
category: 'default',
|
||||
icon: 'default',
|
||||
name: 'someComponent',
|
||||
attributes: {},
|
||||
},
|
||||
@ -113,18 +119,20 @@ describe.only('Content Type Builder - Components', () => {
|
||||
test('Returns correct format', async () => {
|
||||
const res = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-type-builder/components/some_component',
|
||||
url: '/content-type-builder/components/default.some_component',
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toMatchObject({
|
||||
data: {
|
||||
uid: 'some_component',
|
||||
uid: 'default.some_component',
|
||||
category: 'default',
|
||||
schema: {
|
||||
name: 'SomeComponent',
|
||||
icon: 'default',
|
||||
name: 'Some Component',
|
||||
description: '',
|
||||
connection: 'default',
|
||||
collectionName: 'components_some_components',
|
||||
collectionName: 'components_default_some_components',
|
||||
attributes: {
|
||||
title: {
|
||||
type: 'string',
|
||||
@ -157,7 +165,7 @@ describe.only('Content Type Builder - Components', () => {
|
||||
test('Validates input and return 400 in case of invalid input', async () => {
|
||||
const res = await rq({
|
||||
method: 'PUT',
|
||||
url: '/content-type-builder/components/some_component',
|
||||
url: '/content-type-builder/components/default.some_component',
|
||||
body: {
|
||||
attributes: {},
|
||||
},
|
||||
@ -166,6 +174,8 @@ describe.only('Content Type Builder - Components', () => {
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toEqual({
|
||||
error: {
|
||||
category: ['category.required'],
|
||||
icon: ['icon.required'],
|
||||
name: ['name.required'],
|
||||
},
|
||||
});
|
||||
@ -174,8 +184,10 @@ describe.only('Content Type Builder - Components', () => {
|
||||
test('Updates a component properly', async () => {
|
||||
const res = await rq({
|
||||
method: 'PUT',
|
||||
url: '/content-type-builder/components/some_component',
|
||||
url: '/content-type-builder/components/default.some_component',
|
||||
body: {
|
||||
category: 'default',
|
||||
icon: 'default',
|
||||
name: 'NewComponent',
|
||||
attributes: {},
|
||||
},
|
||||
@ -184,7 +196,7 @@ describe.only('Content Type Builder - Components', () => {
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toEqual({
|
||||
data: {
|
||||
uid: 'new_component',
|
||||
uid: 'default.new_component',
|
||||
},
|
||||
});
|
||||
|
||||
@ -208,13 +220,13 @@ describe.only('Content Type Builder - Components', () => {
|
||||
test('Deletes a component correctly', async () => {
|
||||
const res = await rq({
|
||||
method: 'DELETE',
|
||||
url: '/content-type-builder/components/new_component',
|
||||
url: '/content-type-builder/components/default.new_component',
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toEqual({
|
||||
data: {
|
||||
uid: 'new_component',
|
||||
uid: 'default.new_component',
|
||||
},
|
||||
});
|
||||
|
||||
@ -222,7 +234,7 @@ describe.only('Content Type Builder - Components', () => {
|
||||
|
||||
const tryGet = await rq({
|
||||
method: 'GET',
|
||||
url: '/content-type-builder/components/new_component',
|
||||
url: '/content-type-builder/components/default.new_component',
|
||||
});
|
||||
|
||||
expect(tryGet.statusCode).toBe(404);
|
||||
|
||||
@ -116,10 +116,8 @@ function watchFileChanges({ dir, strapiInstance }) {
|
||||
/tmp/,
|
||||
'**/admin',
|
||||
'**/admin/**',
|
||||
'**/components',
|
||||
'**/components/**',
|
||||
'**/documentation',
|
||||
'**/documentation/**',
|
||||
'extensions/**/admin',
|
||||
'extensions/**/admin/**',
|
||||
'**/node_modules',
|
||||
'**/node_modules/**',
|
||||
'**/plugins.json',
|
||||
|
||||
@ -6,6 +6,8 @@ module.exports = ({ rq }) => {
|
||||
url: '/content-type-builder/components',
|
||||
method: 'POST',
|
||||
body: {
|
||||
category: 'default',
|
||||
icon: 'default',
|
||||
connection: 'default',
|
||||
...data,
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user