mirror of
https://github.com/strapi/strapi.git
synced 2025-08-26 09:42:09 +00:00
Update component servie
This commit is contained in:
parent
05f1dd7b19
commit
1e28d8b9d9
@ -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 } });
|
||||
},
|
||||
};
|
||||
|
@ -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),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
};
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user