init edit and delete content type with schema manager

This commit is contained in:
Alexandre Bodin 2019-11-20 14:42:53 +01:00
parent 22ca3c476a
commit f255d0d700
7 changed files with 191 additions and 165 deletions

View File

@ -127,20 +127,24 @@ module.exports = (scope, cb) => {
// Get default connection // Get default connection
try { try {
scope.connection = scope.connection = scope.args.connection;
_.get(scope.args, 'connection') || if (!scope.args.connection) {
JSON.parse( try {
fs.readFileSync( scope.connection = JSON.parse(
path.resolve( fs.readFileSync(
scope.rootPath, path.resolve(
'config', scope.rootPath,
'environments', 'config',
environment, 'environments',
'database.json' environment,
'database.json'
)
) )
) ).defaultConnection;
).defaultConnection || } catch (err) {
''; scope.connection = 'default';
}
}
} catch (err) { } catch (err) {
return cb.invalid(err); return cb.invalid(err);
} }

View File

@ -67,6 +67,7 @@ module.exports = {
ctx.send({ data: { uid: component.uid } }, 201); ctx.send({ data: { uid: component.uid } }, 201);
} catch (error) { } catch (error) {
strapi.log.error(error);
ctx.send({ error: error.message }, 400); ctx.send({ error: error.message }, 400);
} }
}, },
@ -98,6 +99,7 @@ module.exports = {
ctx.send({ data: { uid: component.uid } }); ctx.send({ data: { uid: component.uid } });
} catch (error) { } catch (error) {
strapi.log.error(error);
ctx.send({ error: error.message }, 400); ctx.send({ error: error.message }, 400);
} }
}, },
@ -119,6 +121,7 @@ module.exports = {
ctx.send({ data: { uid: component.uid } }); ctx.send({ data: { uid: component.uid } });
} catch (error) { } catch (error) {
strapi.log.error(error);
ctx.send({ error: error.message }, 400); ctx.send({ error: error.message }, 400);
} }
}, },

View File

@ -66,127 +66,53 @@ module.exports = {
ctx.send({ data: { uid: component.uid } }, 201); ctx.send({ data: { uid: component.uid } }, 201);
} catch (error) { } catch (error) {
strapi.log.error(error);
strapi.emit('didNotCreateContentType', error); strapi.emit('didNotCreateContentType', error);
ctx.send({ error: error.message }, 400); ctx.send({ error: error.message }, 400);
} }
// strapi.reload.isWatching = false;
// try {
// const contentTypeSchema = contentTypeService.createContentTypeSchema(
// body.contentType
// );
// await contentTypeService.generateAPI(modelName, contentTypeSchema);
// await contentTypeService.generateReversedRelations({
// attributes: body.contentType.attributes,
// modelName,
// });
// if (_.isEmpty(strapi.api)) {
// strapi.emit('didCreateFirstContentType');
// } else {
// strapi.emit('didCreateContentType');
// }
// } catch (e) {
// strapi.log.error(e);
// strapi.emit('didNotCreateContentType', e);
// return ctx.badRequest(null, [
// { messages: [{ id: 'request.error.model.write' }] },
// ]);
// }
// setImmediate(() => strapi.reload());
// ctx.send(
// {
// data: {
// uid,
// },
// },
// 201
// );
}, },
async updateContentType(ctx) { async updateContentType(ctx) {
const { uid } = ctx.params; const { uid } = ctx.params;
const { body } = ctx.request; const { body } = ctx.request;
const contentType = strapi.contentTypes[uid];
if (!contentType) {
return ctx.send({ error: 'contentType.notFound' }, 404);
}
try { try {
await validateUpdateContentTypeInput(body); await validateUpdateContentTypeInput(body);
} catch (error) { } catch (error) {
return ctx.send({ error }, 400); return ctx.send({ error }, 400);
} }
strapi.reload.isWatching = false;
try { try {
const newSchema = contentTypeService.updateContentTypeSchema( strapi.reload.isWatching = false;
contentType.__schema__,
body.contentType
);
await contentTypeService.writeContentType({ uid, schema: newSchema }); const component = await contentTypeService.editContentType(uid, {
contentType: body.contentType,
// delete all relations directed to the updated ct except for oneWay and manyWay components: body.components,
await contentTypeService.deleteBidirectionalRelations(contentType);
await contentTypeService.generateReversedRelations({
attributes: body.contentType.attributes,
modelName: contentType.modelName,
plugin: contentType.plugin,
}); });
if (_.isEmpty(strapi.api)) { setImmediate(() => strapi.reload());
strapi.emit('didCreateFirstContentType');
} else { ctx.send({ data: { uid: component.uid } }, 201);
strapi.emit('didCreateContentType');
}
} catch (error) { } catch (error) {
strapi.emit('didNotCreateContentType', error); strapi.log.error(error);
throw error; ctx.send({ error: error.message }, 400);
} }
setImmediate(() => strapi.reload());
ctx.send({
data: {
uid,
},
});
}, },
async deleteContentType(ctx) { async deleteContentType(ctx) {
const { uid } = ctx.params; const { uid } = ctx.params;
const contentType = strapi.contentTypes[uid]; try {
strapi.reload.isWatching = false;
if (!contentType) { const component = await contentTypeService.deleteContentType(uid);
return ctx.send({ error: 'contentType.notFound' }, 404);
setImmediate(() => strapi.reload());
ctx.send({ data: { uid: component.uid } });
} catch (error) {
strapi.log.error(error);
ctx.send({ error: error.message }, 400);
} }
if (!_.has(contentType, 'apiName')) {
return ctx.send({ error: 'contentType.not.deletable' }, 400);
}
strapi.reload.isWatching = false;
await contentTypeService.deleteAllRelations(contentType);
await contentTypeService.removeContentType(contentType);
setImmediate(() => strapi.reload());
ctx.send({
data: {
uid,
},
});
}, },
}; };

View File

@ -1,8 +1,12 @@
'use strict'; 'use strict';
const _ = require('lodash'); const _ = require('lodash');
const pluralize = require('pluralize');
const generator = require('strapi-generate');
const { formatAttributes } = require('../utils/attributes');
const getSchemaManager = require('./schema-manager'); const getSchemaManager = require('./schema-manager');
const { nameToSlug } = require('../utils/helpers');
/** /**
* Creates a component and handle the nested components sent with it * Creates a component and handle the nested components sent with it
@ -10,7 +14,10 @@ const getSchemaManager = require('./schema-manager');
* @param {Object} params.component Main component to create * @param {Object} params.component Main component to create
* @param {Array<Object>} params.components List of nested components to created or edit * @param {Array<Object>} params.components List of nested components to created or edit
*/ */
const createContentType = ({ contentType, components = [] }) => { const createContentType = async ({ contentType, components = [] }) => {
// generate api squeleton
await generateAPI(contentType.name);
const componentsToCreate = components.filter(compo => !_.has(compo, 'uid')); const componentsToCreate = components.filter(compo => !_.has(compo, 'uid'));
const componentsToEdit = components.filter(compo => _.has(compo, 'uid')); const componentsToEdit = components.filter(compo => _.has(compo, 'uid'));
@ -24,8 +31,81 @@ const createContentType = ({ contentType, components = [] }) => {
}); });
}; };
/**
* Generate a squeleton API
* @param {*} name
* @param {*} contentType
*/
const generateAPI = name => {
return new Promise((resolve, reject) => {
const scope = {
generatorType: 'api',
id: nameToSlug(name),
name: nameToSlug(name),
rootPath: strapi.dir,
args: {
attributes: {},
},
};
generator(scope, {
success: () => resolve(),
error: err => reject(err),
});
});
};
/**
* Edits a contentType and handle the nested contentTypes sent with it
* @param {Object} params params object
* @param {Object} params.contentType Main contentType to create
* @param {Array<Object>} params.components List of nested components to created or edit
*/
const editContentType = (uid, { contentType, components = [] }) => {
const componentsToCreate = components.filter(compo => !_.has(compo, 'uid'));
const componentsToEdit = components.filter(compo => _.has(compo, 'uid'));
return getSchemaManager().edit(ctx => {
const updatedComponent = ctx.editContentType({
uid,
...contentType,
});
componentsToCreate.forEach(ctx.createComponent);
componentsToEdit.forEach(ctx.editComponent);
return updatedComponent;
});
};
const deleteContentType = uid => {
return getSchemaManager().edit(ctx => {
return ctx.deleteContentType(uid);
});
};
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),
},
};
};
module.exports = { module.exports = {
createContentType, createContentType,
editContentType,
deleteContentType,
formatContentType,
}; };
// const path = require('path'); // const path = require('path');
@ -267,51 +347,6 @@ module.exports = {
// return fse.writeFile(filePath, JSON.stringify(schema, null, 2)); // 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 || `${nameToCollectionName(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) => { // const generateAPI = (name, contentType) => {
// // create api // // create api
// return new Promise((resolve, reject) => { // return new Promise((resolve, reject) => {

View File

@ -15,7 +15,7 @@ const createSchemaHandler = require('./schema-handler');
const createComponentUID = ({ category, name }) => const createComponentUID = ({ category, name }) =>
`${nameToSlug(category)}.${nameToSlug(name)}`; `${nameToSlug(category)}.${nameToSlug(name)}`;
module.exports = function createComponentBuilder({ tmpComponents }) { module.exports = function createComponentBuilder() {
return { return {
/** /**
* create a component in the tmpComponent map * create a component in the tmpComponent map
@ -23,7 +23,7 @@ module.exports = function createComponentBuilder({ tmpComponents }) {
createComponent(infos) { createComponent(infos) {
const uid = createComponentUID(infos); const uid = createComponentUID(infos);
if (tmpComponents.has(uid)) { if (this.components.has(uid)) {
throw new Error('component.alreadyExists'); throw new Error('component.alreadyExists');
} }
@ -51,7 +51,7 @@ module.exports = function createComponentBuilder({ tmpComponents }) {
.set(['info', 'description'], infos.description) .set(['info', 'description'], infos.description)
.set('attributes', convertAttributes(infos.attributes)); .set('attributes', convertAttributes(infos.attributes));
tmpComponents.set(uid, handler); this.components.set(uid, handler);
return handler; return handler;
}, },
@ -62,18 +62,18 @@ module.exports = function createComponentBuilder({ tmpComponents }) {
editComponent(infos) { editComponent(infos) {
const { uid } = infos; const { uid } = infos;
if (!tmpComponents.has(uid)) { if (!this.components.has(uid)) {
throw new Error('component.notFound'); throw new Error('component.notFound');
} }
const handler = tmpComponents.get(uid); const handler = this.components.get(uid);
const [, nameUID] = uid.split('.'); const [, nameUID] = uid.split('.');
const newCategory = nameToSlug(infos.category); const newCategory = nameToSlug(infos.category);
const newUID = `${newCategory}.${nameUID}`; const newUID = `${newCategory}.${nameUID}`;
if (newUID !== uid && tmpComponents.has(newUID)) { if (newUID !== uid && this.components.has(newUID)) {
throw new Error('component.edit.alreadyExists'); throw new Error('component.edit.alreadyExists');
} }
@ -87,6 +87,7 @@ module.exports = function createComponentBuilder({ tmpComponents }) {
.set(['info', 'name'], infos.name) .set(['info', 'name'], infos.name)
.set(['info', 'icon'], infos.icon) .set(['info', 'icon'], infos.icon)
.set(['info', 'description'], infos.description) .set(['info', 'description'], infos.description)
// TODO: keep configurable args etc...
.set('attributes', convertAttributes(infos.attributes)); .set('attributes', convertAttributes(infos.attributes));
if (newUID !== uid) { if (newUID !== uid) {
@ -103,7 +104,7 @@ module.exports = function createComponentBuilder({ tmpComponents }) {
}, },
deleteComponent(uid) { deleteComponent(uid) {
if (!tmpComponents.has(uid)) { if (!this.components.has(uid)) {
throw new Error('component.notFound'); throw new Error('component.notFound');
} }
@ -115,7 +116,7 @@ module.exports = function createComponentBuilder({ tmpComponents }) {
ct.removeComponent(uid); ct.removeComponent(uid);
}); });
return tmpComponents.get(uid).delete(); return this.components.get(uid).delete();
}, },
}; };
}; };

View File

@ -15,7 +15,7 @@ const createSchemaHandler = require('./schema-handler');
const createContentTypeUID = ({ name }) => const createContentTypeUID = ({ name }) =>
`application::${nameToSlug(name)}.${nameToSlug(name)}`; `application::${nameToSlug(name)}.${nameToSlug(name)}`;
module.exports = function createComponentBuilder({ tmpContentTypes }) { module.exports = function createComponentBuilder() {
return { return {
/** /**
* create a component in the tmpComponent map * create a component in the tmpComponent map
@ -23,13 +23,13 @@ module.exports = function createComponentBuilder({ tmpContentTypes }) {
createContentType(infos) { createContentType(infos) {
const uid = createContentTypeUID(infos); const uid = createContentTypeUID(infos);
if (tmpContentTypes.has(uid)) { if (this.contentTypes.has(uid)) {
throw new Error('contentType.alreadyExists'); throw new Error('contentType.alreadyExists');
} }
const handler = createSchemaHandler({ const handler = createSchemaHandler({
dir: path.join(strapi.dir, 'api', nameToSlug(infos.name), 'models'), dir: path.join(strapi.dir, 'api', nameToSlug(infos.name), 'models'),
filename: `${nameToSlug(infos.name)}.json`, filename: `${nameToSlug(infos.name)}.settings.json`,
}); });
const defaultConnection = _.get( const defaultConnection = _.get(
@ -50,9 +50,51 @@ module.exports = function createComponentBuilder({ tmpContentTypes }) {
.set(['info', 'description'], infos.description) .set(['info', 'description'], infos.description)
.set('attributes', convertAttributes(infos.attributes)); .set('attributes', convertAttributes(infos.attributes));
tmpContentTypes.set(uid, handler); this.contentTypes.set(uid, handler);
// TODO: add reversed relations
return handler; return handler;
}, },
editContentType(infos) {
const { uid } = infos;
if (!this.contentTypes.has(uid)) {
throw new Error('contentType.notFound');
}
const handler = this.contentTypes.get(uid);
handler
.set('connection', infos.connection)
.set('collectionName', infos.collectionName)
.set(['info', 'name'], infos.name)
.set(['info', 'description'], infos.description)
// TODO: keep configurable args etc...
.set('attributes', convertAttributes(infos.attributes));
// TODO: clear old relations
// TODO: build new reversed relations
return handler;
},
deleteContentType(uid) {
if (!this.contentTypes.has(uid)) {
throw new Error('contentType.notFound');
}
this.components.forEach(compo => {
compo.removeContentType(uid);
});
this.contentTypes.forEach(ct => {
ct.removeContentType(uid);
});
// TODO: clear api when a contentType is deleted
return this.contentTypes.get(uid).delete();
},
}; };
}; };

View File

@ -78,6 +78,21 @@ module.exports = function createSchemaHandler(infos) {
return this; return this;
}, },
removeContentType(uid) {
const { attributes } = state.schema;
Object.keys(attributes).forEach(key => {
const attr = attributes[key];
const target = attr.model || attr.collection;
if (target === uid) {
this.unset(['attributes', key]);
}
});
return this;
},
// utils // utils
removeComponent(uid) { removeComponent(uid) {
const { attributes } = state.schema; const { attributes } = state.schema;