Update component servie

This commit is contained in:
Alexandre Bodin 2019-11-08 13:25:15 +01:00
parent 05f1dd7b19
commit 1e28d8b9d9
6 changed files with 542 additions and 539 deletions

View File

@ -30,4 +30,4 @@
"model": "country"
}
}
}
}

View File

@ -1,9 +1,13 @@
'use strict';
const {
validateComponentInput,
validateUpdateComponentInput,
} = require('./validation/component');
const _ = require('lodash');
const componentService = require('../services/Components');
/**
* Components controller
*/
@ -15,8 +19,9 @@ module.exports = {
* @param {Object} ctx - koa context
*/
async getComponents(ctx) {
const service = strapi.plugins['content-type-builder'].services.components;
const data = service.getComponents();
const data = Object.keys(strapi.components).map(uid => {
return componentService.formatComponent(strapi.components[uid]);
});
ctx.send({ data });
},
@ -29,14 +34,13 @@ module.exports = {
async getComponent(ctx) {
const { uid } = ctx.params;
const service = strapi.plugins['content-type-builder'].services.components;
const component = service.getComponent(uid);
const component = strapi.components[uid];
if (!component) {
return ctx.send({ error: 'component.notFound' }, 404);
}
ctx.send({ data: component });
ctx.send({ data: componentService.formatComponent(component) });
},
/**
@ -53,16 +57,18 @@ module.exports = {
return ctx.send({ error }, 400);
}
const service = strapi.plugins['content-type-builder'].services.components;
const uid = service.createComponentUID(body);
const uid = componentService.createComponentUID(body);
if (service.getComponent(uid)) {
if (_.has(strapi.components, uid)) {
return ctx.send({ error: 'component.alreadyExists' }, 400);
}
strapi.reload.isWatching = false;
const newComponent = await service.createComponent({ uid, infos: body });
const newComponent = await componentService.createComponent({
uid,
infos: body,
});
strapi.reload();
@ -78,8 +84,7 @@ module.exports = {
const { uid } = ctx.params;
const { body } = ctx.request;
const service = strapi.plugins['content-type-builder'].services.components;
const component = service.getComponent(uid);
const component = strapi.components[uid];
if (!component) {
return ctx.send({ error: 'component.notFound' }, 404);
@ -91,23 +96,26 @@ module.exports = {
return ctx.send({ error }, 400);
}
const newUID = service.createComponentUID(body);
if (newUID !== uid && service.getComponent(newUID)) {
const newUID = componentService.editComponentUID(body);
if (newUID !== uid && _.has(strapi.components, newUID)) {
return ctx.send({ error: 'new.component.alreadyExists' }, 400);
}
strapi.reload.isWatching = false;
const updatedComponent = await service.updateComponent({
newUID,
component,
const updatedComponent = await componentService.updateComponent({
uid,
infos: body,
});
await service.updateComponentInModels(component.uid, updatedComponent.uid);
strapi.reload();
await componentService.updateComponentInModels(
component.uid,
updatedComponent.uid
);
ctx.send({ data: updatedComponent }, 200);
setImmediate(() => strapi.reload());
ctx.send({ data: updatedComponent });
},
/**
@ -118,8 +126,7 @@ module.exports = {
async deleteComponent(ctx) {
const { uid } = ctx.params;
const service = strapi.plugins['content-type-builder'].services.components;
const component = service.getComponent(uid);
const component = strapi.components[uid];
if (!component) {
return ctx.send({ error: 'component.notFound' }, 404);
@ -127,11 +134,11 @@ module.exports = {
strapi.reload.isWatching = false;
await service.deleteComponent(component);
await service.deleteComponentInModels(component.uid);
await componentService.deleteComponentInModels(component.uid);
await componentService.deleteComponent(component);
strapi.reload();
setImmediate(() => strapi.reload());
ctx.send({ data: { uid } }, 200);
ctx.send({ data: { uid } });
},
};

View File

@ -1,18 +1,15 @@
'use strict';
const _ = require('lodash');
const pluralize = require('pluralize');
const fse = require('fs-extra');
const path = require('path');
const generator = require('strapi-generate');
const { formatAttributes, convertAttributes } = require('../utils/attributes');
const { nameToSlug } = require('../utils/helpers');
const {
validateContentTypeInput,
validateUpdateContentTypeInput,
} = require('./validation/content-type');
const contentTypeService = require('../services/ContentTypes');
module.exports = {
getContentTypes(ctx) {
const contentTypes = Object.keys(strapi.contentTypes)
@ -22,7 +19,9 @@ module.exports = {
return true;
})
.map(uid => formatContentType(strapi.contentTypes[uid]));
.map(uid =>
contentTypeService.formatContentType(strapi.contentTypes[uid])
);
ctx.send({
data: contentTypes,
@ -38,7 +37,7 @@ module.exports = {
return ctx.send({ error: 'contentType.notFound' }, 404);
}
ctx.send({ data: formatContentType(contentType) });
ctx.send({ data: contentTypeService.formatContentType(contentType) });
},
async createContentType(ctx) {
@ -60,11 +59,11 @@ module.exports = {
strapi.reload.isWatching = false;
try {
const contentType = createContentTypeSchema(body);
const contentType = contentTypeService.createContentTypeSchema(body);
await generateAPI(modelName, contentType);
await contentTypeService.generateAPI(modelName, contentType);
await generateReversedRelations({
await contentTypeService.generateReversedRelations({
attributes: body.attributes,
modelName,
});
@ -84,11 +83,14 @@ module.exports = {
setImmediate(() => strapi.reload());
ctx.send({
data: {
uid,
ctx.send(
{
data: {
uid,
},
},
});
201
);
},
async updateContentType(ctx) {
@ -110,14 +112,17 @@ module.exports = {
strapi.reload.isWatching = false;
try {
const newSchema = updateContentTypeSchema(contentType.__schema__, body);
const newSchema = contentTypeService.updateContentTypeSchema(
contentType.__schema__,
body
);
await writeContentType({ uid, schema: newSchema });
await contentTypeService.writeContentType({ uid, schema: newSchema });
// delete all relations directed to the updated ct except for oneWay and manyWay
await deleteBidirectionalRelations(contentType);
await contentTypeService.deleteBidirectionalRelations(contentType);
await generateReversedRelations({
await contentTypeService.generateReversedRelations({
attributes: body.attributes,
modelName: contentType.modelName,
plugin: contentType.plugin,
@ -128,12 +133,9 @@ module.exports = {
} else {
strapi.emit('didCreateContentType');
}
} catch (e) {
strapi.log.error(e);
strapi.emit('didNotCreateContentType', e);
return ctx.badRequest(null, [
{ messages: [{ id: 'request.error.model.write' }] },
]);
} catch (error) {
strapi.emit('didNotCreateContentType', error);
throw error;
}
setImmediate(() => strapi.reload());
@ -160,8 +162,8 @@ module.exports = {
strapi.reload.isWatching = false;
await removeContentType(contentType);
await deleteAllRelations(contentType);
await contentTypeService.deleteAllRelations(contentType);
await contentTypeService.removeContentType(contentType);
setImmediate(() => strapi.reload());
@ -172,303 +174,3 @@ module.exports = {
});
},
};
const deleteAllRelations = ({ modelName, plugin }) => {
const contentTypeUpdates = 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;
if (samePluginOrNoPlugin && sameModel) {
return true;
}
return false;
});
if (keysToDelete.length > 0) {
const newchema = {
...__schema__,
attributes: _.omit(__schema__.attributes, keysToDelete),
};
return writeContentType({ uid, schema: newchema });
}
});
const componentUpdates = Object.keys(strapi.components).map(uid => {
const { __schema__ } = strapi.components[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;
if (samePluginOrNoPlugin && sameModel) {
return true;
}
return false;
});
if (keysToDelete.length > 0) {
const newchema = {
...__schema__,
attributes: _.omit(__schema__.attributes, keysToDelete),
};
return strapi.plugins[
'content-type-builder'
].services.components.writeComponent({ uid, schema: newchema });
}
});
return Promise.all([...contentTypeUpdates, ...componentUpdates]);
};
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 schema = _.merge({}, target.__schema__, {
attributes: {
[attr.targetAttribute]: buildReversedRelation({
key,
attr,
plugin,
modelName,
}),
},
});
return writeContentType({ uid: attr.target, schema });
});
return Promise.all(promises);
};
const removeContentType = async ({ uid }) => {
const { apiName, __filename__ } = strapi.contentTypes[uid];
const baseName = path.basename(__filename__, '.settings.json');
const apiFolder = path.join(strapi.dir, 'api', apiName);
const deleteFile = async filePath => {
const fileName = path.basename(filePath);
if (_.startsWith(_.toLower(fileName), _.toLower(baseName) + '.')) {
await fse.remove(filePath);
}
if (fileName === 'routes.json') {
const { routes } = await fse.readJSON(filePath);
const clearedRoutes = routes.filter(route => {
return !_.startsWith(
_.toLower(route.handler),
_.toLower(baseName) + '.'
);
});
if (clearedRoutes.length === 0) {
await fse.remove(filePath);
} else {
await fse.writeJSON(
filePath,
{
routes: clearedRoutes,
},
{
spaces: 2,
}
);
}
}
};
const recursiveRemoveFiles = async folder => {
const filesName = await fse.readdir(folder);
for (const fileName of filesName) {
const filePath = path.join(folder, fileName);
const stat = await fse.stat(filePath);
if (stat.isDirectory()) {
await recursiveRemoveFiles(filePath);
} else {
await deleteFile(filePath);
}
}
const files = await fse.readdir(folder);
if (files.length === 0) {
await fse.remove(folder);
}
};
await recursiveRemoveFiles(apiFolder);
};
const writeContentType = async ({ uid, schema }) => {
const { plugin, apiName, __filename__ } = strapi.contentTypes[uid];
let fileDir;
if (plugin) {
fileDir = `./extensions/${plugin}/models`;
} else {
fileDir = `./api/${apiName}/models`;
}
const filePath = path.join(strapi.dir, fileDir, __filename__);
await fse.ensureFile(filePath);
return fse.writeFile(filePath, JSON.stringify(schema, null, 2));
};
const formatContentType = contentType => {
const { uid, plugin, connection, collectionName, info } = contentType;
return {
uid,
plugin,
schema: {
name: _.get(info, 'name') || _.upperFirst(pluralize(uid)),
description: _.get(info, 'description', ''),
connection,
collectionName,
attributes: formatAttributes(contentType),
},
};
};
const createContentTypeSchema = infos => ({
connection:
infos.connection ||
_.get(
strapi,
['config', 'currentEnvironment', 'database', 'defaultConnection'],
'default'
),
collectionName:
infos.collectionName || `${_.snakeCase(pluralize(infos.name))}`,
info: {
name: infos.name,
description: infos.description,
},
attributes: convertAttributes(infos.attributes),
});
const updateContentTypeSchema = (old, infos) => ({
...old,
connection: infos.connection || old.connection,
collectionName: infos.collectionName || old.collectionName,
info: {
name: infos.name || old.info.name,
description: infos.description || old.info.description,
},
// TODO: keep old params like autoMigration, private, configurable
attributes: convertAttributes(infos.attributes),
});
const generateAPI = (name, contentType) => {
// create api
return new Promise((resolve, reject) => {
const scope = {
generatorType: 'api',
id: name,
name,
rootPath: strapi.dir,
args: {
displayName: contentType.info.name,
description: contentType.info.description,
connection: contentType.connection,
collectionName: contentType.collectionName,
attributes: contentType.attributes,
},
};
generator(scope, {
success: () => resolve(),
error: err => reject(err),
});
});
};

View File

@ -5,36 +5,17 @@ const _ = require('lodash');
const fse = require('fs-extra');
const pluralize = require('pluralize');
const contentTypeService = require('./ContentTypes');
const { formatAttributes, convertAttributes } = require('../utils/attributes');
const { nameToSlug } = require('../utils/helpers');
/**
* Returns a list of all available components with formatted attributes
*/
const getComponents = () => {
return Object.keys(strapi.components).map(uid => {
return formatComponent(uid, strapi.components[uid]);
});
};
/**
* Returns a component by uid
* @param {string} uid - component's UID
*/
const getComponent = uid => {
const component = strapi.components[uid];
if (!component) return null;
return formatComponent(uid, component);
};
/**
* Formats a component attributes
* @param {string} uid - string
* @param {Object} component - strapi component model
*/
const formatComponent = (uid, component) => {
const { connection, collectionName, info, category } = component;
const formatComponent = component => {
const { uid, connection, collectionName, info, category } = component;
return {
uid,
@ -58,7 +39,12 @@ const formatComponent = (uid, component) => {
async function createComponent({ uid, infos }) {
const schema = createSchema(infos);
await writeSchema({ uid, schema });
await writeSchema({
category: nameToSlug(infos.category),
name: nameToSlug(infos.name),
schema,
});
return { uid };
}
@ -67,49 +53,43 @@ async function createComponent({ uid, infos }) {
* @param {Object} component
* @param {Object} infos
*/
async function updateComponent({ component, newUID, infos }) {
const { uid, schema: oldSchema } = component;
async function updateComponent({ component, infos }) {
const { uid, __schema__: oldSchema } = component;
// don't update collectionName if not provided
const updatedSchema = {
info: {
icon: infos.icon,
name: infos.name,
description: infos.description || oldSchema.description,
},
...oldSchema,
connection: infos.connection || oldSchema.connection,
collectionName: infos.collectionName || oldSchema.collectionName,
info: {
name: infos.name || oldSchema.info.name,
icon: infos.icon || oldSchema.info.icon,
description: infos.description || oldSchema.info.description,
},
attributes: convertAttributes(infos.attributes),
};
if (uid !== newUID) {
await deleteSchema(uid);
await editSchema({ uid, schema: updatedSchema });
if (_.has(strapi.plugins, ['content-manager', 'services', 'components'])) {
await _.get(strapi.plugins, [
'content-manager',
'services',
'components',
]).updateUID(uid, newUID);
if (component.category !== infos.category) {
const oldDir = path.join(strapi.dir, 'components', component.category);
const newDir = path.join(strapi.dir, 'components', infos.category);
await fse.move(
path.join(oldDir, component.__filename__),
path.join(newDir, component.__filename__)
);
const list = await fse.readdir(oldDir);
if (list.length === 0) {
await fse.remove(oldDir);
}
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.remove(categoryDir);
}
return { uid: newUID };
return {
uid: `${infos.category}.${component.modelName}`,
};
}
await writeSchema({ uid, schema: updatedSchema });
return { uid };
}
@ -164,14 +144,27 @@ async function deleteComponent(component) {
/**
* Writes a component schema file
*/
async function writeSchema({ uid, schema }) {
const [category, filename] = uid.split('.');
const categoryDir = path.join(strapi.dir, 'components', category);
async function writeSchema({ category, name, schema }) {
const filePath = path.join(
strapi.dir,
'components',
category,
`${name}.json`
);
await fse.ensureDir(categoryDir);
await fse.ensureFile(filePath);
await fse.writeJSON(filePath, schema, { spaces: 2 });
}
const filepath = path.join(categoryDir, `${filename}.json`);
await fse.writeFile(filepath, JSON.stringify(schema, null, 2));
/**
* Edit a component schema file
*/
async function editSchema({ uid, schema }) {
const { category, __filename__ } = strapi.components[uid];
const filePath = path.join(strapi.dir, 'components', category, __filename__);
await fse.ensureFile(filePath);
await fse.writeJSON(filePath, schema, { spaces: 2 });
}
/**
@ -179,156 +172,131 @@ async function writeSchema({ uid, schema }) {
* @param {string} ui
*/
async function deleteSchema(uid) {
const [category, filename] = uid.split('.');
await strapi.fs.removeAppFile(`components/${category}/${filename}.json`);
const { category, __filename__ } = strapi.components[uid];
await strapi.fs.removeAppFile(`components/${category}/${__filename__}`);
}
const updateComponentInModels = (oldUID, newUID) => {
const contentTypeService =
strapi.plugins['content-type-builder'].services.contenttypebuilder;
const contentTypeUpdates = Object.keys(strapi.contentTypes).map(uid => {
const { __schema__: oldSchema } = strapi.contentTypes[uid];
const updateModels = (models, { plugin } = {}) => {
Object.keys(models).forEach(modelKey => {
const model = models[modelKey];
const componentsToUpdate = Object.keys(oldSchema.attributes).reduce(
(acc, key) => {
if (
oldSchema.attributes[key].type === 'component' &&
oldSchema.attributes[key].component === oldUID
) {
acc.push(key);
}
const attributesToModify = Object.keys(model.attributes).reduce(
(acc, key) => {
if (
model.attributes[key].type === 'component' &&
model.attributes[key].component === oldUID
) {
acc.push(key);
}
return acc;
},
[]
);
return acc;
},
[]
);
const dynamicoznesToUpdate = Object.keys(model.attributes).filter(key => {
const dynamiczonesToUpdate = Object.keys(oldSchema.attributes).filter(
key => {
return (
model.attributes[key].type === 'dynamiczone' &&
model.attributes[key].components.includes(oldUID)
oldSchema.attributes[key].type === 'dynamiczone' &&
oldSchema.attributes[key].components.includes(oldUID)
);
}, []);
},
[]
);
if (attributesToModify.length > 0) {
const modelJSON = contentTypeService.readModel(modelKey, {
plugin,
api: model.apiName,
});
if (componentsToUpdate.length > 0 || dynamiczonesToUpdate.length > 0) {
const newSchema = _.cloneDeep(oldSchema);
attributesToModify.forEach(key => {
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,
});
}
});
};
updateModels(strapi.models);
Object.keys(strapi.plugins).forEach(pluginKey => {
updateModels(strapi.plugins[pluginKey].models, { plugin: pluginKey });
});
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 => {
componentsToUpdate.forEach(key => {
newSchema.attributes[key].component = newUID;
});
writeSchema({ uid, schema: newSchema });
dynamiczonesToUpdate.forEach(key => {
newSchema.attributes[key].components = oldSchema.attributes[
key
].components.map(val => (val !== oldUID ? val : newUID));
});
return contentTypeService.writeContentType({ uid, schema: newSchema });
}
return Promise.resolve();
});
const componentUpdates = Object.keys(strapi.components).map(uid => {
const { __schema__: oldSchema } = strapi.components[uid];
const componentsToUpdate = Object.keys(oldSchema.attributes).filter(key => {
return (
oldSchema.attributes[key].type === 'component' &&
oldSchema.attributes[key].component === oldUID
);
}, []);
if (componentsToUpdate.length > 0) {
const newSchema = {
...oldSchema,
};
componentsToUpdate.forEach(key => {
newSchema.attributes[key].component = newUID;
});
return editSchema({ uid, schema: newSchema });
}
return Promise.resolve();
});
return Promise.all([...contentTypeUpdates, ...componentUpdates]);
};
const deleteComponentInModels = async componentUID => {
const [category] = componentUID.split('.');
const contentTypeService =
strapi.plugins['content-type-builder'].services.contenttypebuilder;
const component = strapi.components[componentUID];
const updateModels = (models, { plugin } = {}) => {
Object.keys(models).forEach(modelKey => {
const model = models[modelKey];
const contentTypeUpdates = Object.keys(strapi.contentTypes).map(uid => {
const { __schema__: oldSchema } = strapi.contentTypes[uid];
const componentsToRemove = Object.keys(model.attributes).filter(key => {
const componentsToRemove = Object.keys(oldSchema.attributes).filter(key => {
return (
oldSchema.attributes[key].type === 'component' &&
oldSchema.attributes[key].component === componentUID
);
}, []);
const dynamiczonesToUpdate = Object.keys(oldSchema.attributes).filter(
key => {
return (
model.attributes[key].type === 'component' &&
model.attributes[key].component === componentUID
oldSchema.attributes[key].type === 'dynamiczone' &&
oldSchema.attributes[key].components.includes(componentUID)
);
}, []);
},
[]
);
const dynamicoznesToUpdate = Object.keys(model.attributes).filter(key => {
return (
model.attributes[key].type === 'dynamiczone' &&
model.attributes[key].components.includes(componentUID)
);
}, []);
if (componentsToRemove.length > 0 || dynamiczonesToUpdate.length > 0) {
const newSchema = _.cloneDeep(oldSchema);
if (componentsToRemove.length > 0 || dynamicoznesToUpdate.length > 0) {
const modelJSON = contentTypeService.readModel(modelKey, {
plugin,
api: model.apiName,
});
componentsToRemove.forEach(key => {
delete newSchema.attributes[key];
});
componentsToRemove.forEach(key => {
delete modelJSON.attributes[key];
});
dynamiczonesToUpdate.forEach(key => {
newSchema.attributes[key] = {
...newSchema.attributes[key],
components: newSchema.attributes[key].components.filter(
val => val !== componentUID
),
};
});
dynamicoznesToUpdate.forEach(key => {
modelJSON.attributes[key] = {
...modelJSON.attributes[key],
components: modelJSON.attributes[key].components.filter(
val => val !== componentUID
),
};
});
return contentTypeService.writeContentType({ uid, schema: newSchema });
}
contentTypeService.writeModel(modelKey, modelJSON, {
plugin,
api: model.apiName,
});
}
});
};
updateModels(strapi.models);
Object.keys(strapi.plugins).forEach(pluginKey => {
updateModels(strapi.plugins[pluginKey].models, { plugin: pluginKey });
return Promise.resolve();
});
Object.keys(strapi.components).forEach(uid => {
const componentUpdates = Object.keys(strapi.components).map(uid => {
const component = strapi.components[uid];
const componentsToRemove = Object.keys(component.attributes).filter(key => {
@ -350,25 +318,28 @@ const deleteComponentInModels = async componentUID => {
delete newSchema.attributes[key];
});
writeSchema({ uid, schema: newSchema });
return editSchema({ uid, schema: newSchema });
}
return Promise.resolve();
});
const categoryDir = path.join(strapi.dir, 'components', category);
const categoryCompos = await fse.readdir(categoryDir);
if (categoryCompos.length === 0) {
await Promise.all([...contentTypeUpdates, ...componentUpdates]);
const categoryDir = path.join(strapi.dir, 'components', component.category);
const list = await fse.readdir(categoryDir);
if (list.length === 0) {
await fse.remove(categoryDir);
}
};
module.exports = {
getComponents,
getComponent,
createComponent,
createComponentUID,
updateComponent,
deleteComponent,
writeComponent: writeSchema,
editSchema,
formatComponent,
// export for testing only
createSchema,

View File

@ -0,0 +1,322 @@
'use strict';
const path = require('path');
const _ = require('lodash');
const pluralize = require('pluralize');
const fse = require('fs-extra');
const generator = require('strapi-generate');
const componentService = require('./Components');
const { formatAttributes, convertAttributes } = require('../utils/attributes');
const deleteAllRelations = ({ modelName, plugin }) => {
const contentTypeUpdates = 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;
if (samePluginOrNoPlugin && sameModel) {
return true;
}
return false;
});
if (keysToDelete.length > 0) {
const newchema = {
...__schema__,
attributes: _.omit(__schema__.attributes, keysToDelete),
};
return writeContentType({ uid, schema: newchema });
}
});
const componentUpdates = Object.keys(strapi.components).map(uid => {
const { __schema__ } = strapi.components[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;
if (samePluginOrNoPlugin && sameModel) {
return true;
}
return false;
});
if (keysToDelete.length > 0) {
const newchema = {
...__schema__,
attributes: _.omit(__schema__.attributes, keysToDelete),
};
return componentService.editSchema({ uid, schema: newchema });
}
});
return Promise.all([...contentTypeUpdates, ...componentUpdates]);
};
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 schema = _.merge({}, target.__schema__, {
attributes: {
[attr.targetAttribute]: buildReversedRelation({
key,
attr,
plugin,
modelName,
}),
},
});
return writeContentType({ uid: attr.target, schema });
});
return Promise.all(promises);
};
const removeContentType = async ({ uid }) => {
const { apiName, __filename__ } = strapi.contentTypes[uid];
const baseName = path.basename(__filename__, '.settings.json');
const apiFolder = path.join(strapi.dir, 'api', apiName);
const deleteFile = async filePath => {
const fileName = path.basename(filePath);
if (_.startsWith(_.toLower(fileName), _.toLower(baseName) + '.')) {
await fse.remove(filePath);
}
if (fileName === 'routes.json') {
const { routes } = await fse.readJSON(filePath);
const clearedRoutes = routes.filter(route => {
return !_.startsWith(
_.toLower(route.handler),
_.toLower(baseName) + '.'
);
});
if (clearedRoutes.length === 0) {
await fse.remove(filePath);
} else {
await fse.writeJSON(
filePath,
{
routes: clearedRoutes,
},
{
spaces: 2,
}
);
}
}
};
const recursiveRemoveFiles = async folder => {
const filesName = await fse.readdir(folder);
for (const fileName of filesName) {
const filePath = path.join(folder, fileName);
const stat = await fse.stat(filePath);
if (stat.isDirectory()) {
await recursiveRemoveFiles(filePath);
} else {
await deleteFile(filePath);
}
}
const files = await fse.readdir(folder);
if (files.length === 0) {
await fse.remove(folder);
}
};
await recursiveRemoveFiles(apiFolder);
};
const writeContentType = async ({ uid, schema }) => {
const { plugin, apiName, __filename__ } = strapi.contentTypes[uid];
let fileDir;
if (plugin) {
fileDir = `./extensions/${plugin}/models`;
} else {
fileDir = `./api/${apiName}/models`;
}
const filePath = path.join(strapi.dir, fileDir, __filename__);
await fse.ensureFile(filePath);
return fse.writeFile(filePath, JSON.stringify(schema, null, 2));
};
const formatContentType = contentType => {
const { uid, plugin, connection, collectionName, info } = contentType;
return {
uid,
plugin,
schema: {
name: _.get(info, 'name') || _.upperFirst(pluralize(uid)),
description: _.get(info, 'description', ''),
connection,
collectionName,
attributes: formatAttributes(contentType),
},
};
};
const createContentTypeSchema = infos => ({
connection:
infos.connection ||
_.get(
strapi,
['config', 'currentEnvironment', 'database', 'defaultConnection'],
'default'
),
collectionName:
infos.collectionName || `${_.snakeCase(pluralize(infos.name))}`,
info: {
name: infos.name,
description: infos.description,
},
attributes: convertAttributes(infos.attributes),
});
const updateContentTypeSchema = (old, infos) => ({
...old,
connection: infos.connection || old.connection,
collectionName: infos.collectionName || old.collectionName,
info: {
name: infos.name || old.info.name,
description: infos.description || old.info.description,
},
// TODO: keep old params like autoMigration, private, configurable
attributes: convertAttributes(infos.attributes),
});
const generateAPI = (name, contentType) => {
// create api
return new Promise((resolve, reject) => {
const scope = {
generatorType: 'api',
id: name,
name,
rootPath: strapi.dir,
args: {
displayName: contentType.info.name,
description: contentType.info.description,
connection: contentType.connection,
collectionName: contentType.collectionName,
attributes: contentType.attributes,
},
};
generator(scope, {
success: () => resolve(),
error: err => reject(err),
});
});
};
module.exports = {
generateAPI,
createContentTypeSchema,
updateContentTypeSchema,
deleteAllRelations,
deleteBidirectionalRelations,
generateReversedRelations,
formatContentType,
writeContentType,
removeContentType,
};

View File

@ -17,6 +17,7 @@ module.exports = async ({ dir }) => {
Object.keys(map[category]).forEach(key => {
acc[`${category}.${key}`] = Object.assign(map[category][key], {
category,
modelName: key,
});
});
return acc;