Simplify schema builder

This commit is contained in:
Alexandre Bodin 2019-11-25 12:25:17 +01:00
parent 23ee9c9ebe
commit cdbd6e4063
12 changed files with 248 additions and 230 deletions

View File

@ -0,0 +1,14 @@
{
"connection": "default",
"collectionName": "components_test_a_zdazd_test_edit_3_s",
"info": {
"name": "Test edit 3",
"icon": "test",
"description": "IONADZI\nazdinazodin"
},
"attributes": {
"key": {
"type": "string"
}
}
}

View File

@ -47,15 +47,20 @@ const componentSchema = createSchema(VALID_TYPES, VALID_RELATIONS, {
.required('category.required'),
});
const nestedComponentSchema = yup.array().of(
componentSchema
.shape({
uid: yup.string(),
tmpUID: yup.string(),
})
.required()
);
const createComponentSchema = () => {
return yup
.object({
component: componentSchema.required().noUnknown(),
components: yup.array().of(
componentSchema.shape({
uid: yup.string(),
})
),
components: nestedComponentSchema,
})
.noUnknown();
};
@ -103,4 +108,5 @@ module.exports = {
validateComponentInput,
validateUpdateComponentInput,
componentSchema,
nestedComponentSchema,
};

View File

@ -5,7 +5,7 @@ const yup = require('yup');
const formatYupErrors = require('./yup-formatter');
const createSchema = require('./model-schema');
const { componentSchema } = require('./component');
const { nestedComponentSchema } = require('./component');
const { modelTypes } = require('./constants');
const VALID_RELATIONS = [
@ -48,11 +48,7 @@ const createContentTypeSchema = () => {
return yup
.object({
contentType: contentTypeSchema.required().noUnknown(),
components: yup.array().of(
componentSchema.shape({
uid: yup.string(),
})
),
components: nestedComponentSchema,
})
.noUnknown();
};

View File

@ -148,7 +148,7 @@ const getTypeShape = (attribute, { modelType } = {}) => {
repeatable: yup.boolean(),
component: yup
.string()
.oneOf(Object.keys(strapi.components))
// .oneOf(Object.keys(strapi.components))
.test({
name: 'Check max component nesting is 1 lvl',
test: function(compoUID) {

View File

@ -4,7 +4,7 @@ const _ = require('lodash');
const pluralize = require('pluralize');
const { formatAttributes } = require('../utils/attributes');
const getSchemaManager = require('./schema-manager');
const createBuilder = require('./schema-builder');
/**
* Formats a component attributes
@ -34,18 +34,19 @@ const formatComponent = component => {
* @param {Object} params.component Main component to create
* @param {Array<Object>} params.components List of nested components to created or edit
*/
const createComponent = ({ component, components = [] }) => {
const createComponent = async ({ component, components = [] }) => {
const componentsToCreate = components.filter(compo => !_.has(compo, 'uid'));
const componentsToEdit = components.filter(compo => _.has(compo, 'uid'));
return getSchemaManager().edit(ctx => {
const newComponent = ctx.createComponent(component);
const builder = createBuilder();
componentsToCreate.forEach(component => ctx.createComponent(component));
componentsToEdit.forEach(component => ctx.editComponent(component));
const newComponent = builder.createComponent(component);
return newComponent;
});
componentsToCreate.forEach(component => builder.createComponent(component));
componentsToEdit.forEach(component => builder.editComponent(component));
await builder.writeFiles();
return newComponent;
};
/**
@ -54,27 +55,31 @@ const createComponent = ({ component, components = [] }) => {
* @param {Object} params.component Main component to create
* @param {Array<Object>} params.components List of nested components to created or edit
*/
const editComponent = (uid, { component, components = [] }) => {
const editComponent = async (uid, { component, components = [] }) => {
const componentsToCreate = components.filter(compo => !_.has(compo, 'uid'));
const componentsToEdit = components.filter(compo => _.has(compo, 'uid'));
return getSchemaManager().edit(ctx => {
const updatedComponent = ctx.editComponent({
uid,
...component,
});
const builder = createBuilder();
componentsToCreate.forEach(component => ctx.createComponent(component));
componentsToEdit.forEach(component => ctx.editComponent(component));
return updatedComponent;
const updatedComponent = builder.editComponent({
...component,
uid,
});
componentsToCreate.forEach(component => builder.createComponent(component));
componentsToEdit.forEach(component => builder.editComponent(component));
await builder.writeFiles();
return updatedComponent;
};
const deleteComponent = uid => {
return getSchemaManager().edit(ctx => {
return ctx.deleteComponent(uid);
});
const deleteComponent = async uid => {
const builder = createBuilder();
const deletedComponent = builder.deleteComponent(uid);
await builder.writeFiles();
return deletedComponent;
};
module.exports = {

View File

@ -4,7 +4,7 @@ const _ = require('lodash');
const pluralize = require('pluralize');
const generator = require('strapi-generate');
const getSchemaManager = require('./schema-manager');
const createBuilder = require('./schema-builder');
const apiCleaner = require('./clear-api');
const { formatAttributes } = require('../utils/attributes');
const { nameToSlug } = require('../utils/helpers');
@ -29,6 +29,25 @@ const formatContentType = contentType => {
};
};
const applyComponentUIDMap = map => ct => {
return {
...ct,
attributes: Object.keys(ct.attributes).reduce((acc, key) => {
const attr = ct.attributes[key];
if (attr.type === 'component' && _.has(map, attr.component)) {
acc[key] = {
...attr,
component: map[attr.component],
};
} else {
acc[key] = attr;
}
return acc;
}, {}),
};
};
/**
* Creates a component and handle the nested components sent with it
* @param {Object} params params object
@ -39,17 +58,32 @@ const createContentType = async ({ contentType, components = [] }) => {
const componentsToCreate = components.filter(compo => !_.has(compo, 'uid'));
const componentsToEdit = components.filter(compo => _.has(compo, 'uid'));
return getSchemaManager().edit(async ctx => {
const newContentType = ctx.createContentType(contentType);
const builder = createBuilder();
componentsToCreate.forEach(component => ctx.createComponent(component));
componentsToEdit.forEach(component => ctx.editComponent(component));
const uidMap = componentsToCreate.reduce((uidMap, component) => {
uidMap[component.tmpUID] = builder.createComponentUID(component);
return uidMap;
}, {});
// generate api squeleton
await generateAPI(contentType.name);
const updateAttributes = applyComponentUIDMap(uidMap);
return newContentType;
});
const newContentType = builder.createContentType(
updateAttributes(contentType)
);
componentsToCreate.forEach(component =>
builder.createComponent(updateAttributes(component))
);
componentsToEdit.forEach(component =>
builder.editComponent(updateAttributes(component))
);
// generate api squeleton
await generateAPI(contentType.name);
await builder.writeFiles();
return newContentType;
};
/**
@ -81,21 +115,21 @@ const generateAPI = name => {
* @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 editContentType = async (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(component => ctx.createComponent(component));
componentsToEdit.forEach(component => ctx.editComponent(component));
return updatedComponent;
const builder = createBuilder();
const updatedComponent = builder.editContentType({
uid,
...contentType,
});
componentsToCreate.forEach(component => builder.createComponent(component));
componentsToEdit.forEach(component => builder.editComponent(component));
await builder.writeFiles();
return updatedComponent;
};
/**
@ -103,26 +137,21 @@ const editContentType = (uid, { contentType, components = [] }) => {
* @param {string} uid content type uid
*/
const deleteContentType = async uid => {
const builder = createBuilder();
// make a backup
await apiCleaner.backup(uid);
return getSchemaManager().edit(async ctx => {
const component = ctx.deleteContentType(uid);
const component = builder.deleteContentType(uid);
try {
await ctx.flush();
await apiCleaner.clear(uid);
} catch (error) {
await ctx.rollback();
await apiCleaner.rollback(uid);
try {
await builder.writeFiles();
await apiCleaner.clear(uid);
} catch (error) {
await apiCleaner.rollback(uid);
}
throw new Error(
`Error delete ContentType: ${error.message}. Changes were rollbacked`
);
}
return component;
});
return component;
};
module.exports = {

View File

@ -8,20 +8,21 @@ const { convertAttributes } = require('../../utils/attributes');
const { nameToSlug, nameToCollectionName } = require('../../utils/helpers');
const createSchemaHandler = require('./schema-handler');
/**
* Returns a uid from a string
* @param {string} str - string to slugify
*/
const createComponentUID = ({ category, name }) =>
`${nameToSlug(category)}.${nameToSlug(name)}`;
module.exports = function createComponentBuilder() {
return {
/**
* Returns a uid from a string
* @param {string} str - string to slugify
*/
createComponentUID({ category, name }) {
return `${nameToSlug(category)}.${nameToSlug(name)}`;
},
/**
* create a component in the tmpComponent map
*/
createComponent(infos) {
const uid = createComponentUID(infos);
const uid = this.createComponentUID(infos);
if (this.components.has(uid)) {
throw new Error('component.alreadyExists');

View File

@ -0,0 +1,120 @@
'use strict';
const path = require('path');
const createSchemaHandler = require('./schema-handler');
const createComponentBuilder = require('./component-builder');
const createContentTypeBuilder = require('./content-type-builder');
module.exports = function createBuilder() {
const components = Object.keys(strapi.components).map(key => {
const compo = strapi.components[key];
return {
modelName: compo.modelName,
plugin: compo.modelName,
uid: compo.uid,
filename: compo.__filename__,
dir: path.join(strapi.dir, 'components', compo.category),
schema: compo.__schema__,
};
});
const contentTypes = Object.keys(strapi.contentTypes).map(key => {
const contentType = strapi.contentTypes[key];
let dir;
if (contentType.plugin) {
dir = `./extensions/${contentType.plugin}/models`;
} else {
dir = `./api/${contentType.apiName}/models`;
}
return {
modelName: contentType.modelName,
plugin: contentType.plugin,
uid: contentType.uid,
filename: contentType.__filename__,
dir: path.join(strapi.dir, dir),
schema: contentType.__schema__,
};
});
return createSchemaBuilder({
components,
contentTypes,
});
};
/**
* Schema builder
*/
function createSchemaBuilder({ components, contentTypes }) {
const tmpComponents = new Map();
const tmpContentTypes = new Map();
// init temporary ContentTypes
Object.keys(contentTypes).forEach(key => {
tmpContentTypes.set(
contentTypes[key].uid,
createSchemaHandler(contentTypes[key])
);
});
// init temporary components
Object.keys(components).forEach(key => {
tmpComponents.set(
components[key].uid,
createSchemaHandler(components[key])
);
});
return {
get components() {
return tmpComponents;
},
get contentTypes() {
return tmpContentTypes;
},
...createComponentBuilder({ tmpComponents, tmpContentTypes }),
...createContentTypeBuilder({ tmpComponents, tmpContentTypes }),
/**
* Write all type to files
*/
writeFiles() {
return Promise.all(
[
...Array.from(tmpComponents.values()),
...Array.from(tmpContentTypes.values()),
].map(schema => schema.flush())
)
.catch(error => {
strapi.log.error('Error writing schema files');
strapi.log.error(error);
return this.rollback();
})
.catch(error => {
strapi.log.error(
'Error rolling back schema files. You might need to fix your files manually'
);
strapi.log.error(error);
throw new Error('Invalid schema edition');
});
},
/**
* rollback all files
*/
rollback() {
return Promise.all(
[
...Array.from(tmpComponents.values()),
...Array.from(tmpContentTypes.values()),
].map(schema => schema.rollback())
);
},
};
}

View File

@ -1,82 +0,0 @@
'use strict';
const path = require('path');
const createSchemaBuilder = require('./schema-builder');
/**
* Singleton schemaManager
*/
let schemaManager;
module.exports = function getManager() {
if (schemaManager === undefined) return createSchemaManager();
return schemaManager;
};
function createSchemaManager() {
const components = Object.keys(strapi.components).map(key => {
const compo = strapi.components[key];
return {
modelName: compo.modelName,
plugin: compo.modelName,
uid: compo.uid,
filename: compo.__filename__,
dir: path.join(strapi.dir, 'components', compo.category),
schema: compo.__schema__,
};
});
const contentTypes = Object.keys(strapi.contentTypes).map(key => {
const contentType = strapi.contentTypes[key];
let dir;
if (contentType.plugin) {
dir = `./extensions/${contentType.plugin}/models`;
} else {
dir = `./api/${contentType.apiName}/models`;
}
return {
modelName: contentType.modelName,
plugin: contentType.plugin,
uid: contentType.uid,
filename: contentType.__filename__,
dir: path.join(strapi.dir, dir),
schema: contentType.__schema__,
};
});
return {
async edit(editorFn) {
const builder = createSchemaBuilder({
components,
contentTypes,
});
const result = await Promise.resolve()
.then(() => editorFn(builder))
.catch(error => {
strapi.log.error(error);
throw error;
});
await builder
.flush()
.catch(error => {
strapi.log.error('Error writing schema files');
strapi.log.error(error);
return builder.rollback();
})
.catch(error => {
strapi.log.error(
'Error rolling back schema files. You might need to fix your files manually'
);
strapi.log.error(error);
throw new Error('Invalid schema edition');
});
return result;
},
};
}

View File

@ -1,71 +0,0 @@
'use strict';
const createSchemaHandler = require('./schema-handler');
const createComponentBuilder = require('./component-builder');
const createContentTypeBuilder = require('./content-type-builder');
module.exports = function createSchemaBuilder({ components, contentTypes }) {
const tmpComponents = new Map();
const tmpContentTypes = new Map();
let flushed = false;
let rollbacked = false;
// init temporary ContentTypes
Object.keys(contentTypes).forEach(key => {
tmpContentTypes.set(
contentTypes[key].uid,
createSchemaHandler(contentTypes[key])
);
});
// init temporary components
Object.keys(components).forEach(key => {
tmpComponents.set(
components[key].uid,
createSchemaHandler(components[key])
);
});
const ctx = {
get components() {
return tmpComponents;
},
get contentTypes() {
return tmpContentTypes;
},
...createComponentBuilder({ tmpComponents, tmpContentTypes }),
...createContentTypeBuilder({ tmpComponents, tmpContentTypes }),
flush() {
if (!flushed) {
flushed = true;
return Promise.all(
[
...Array.from(tmpComponents.values()),
...Array.from(tmpContentTypes.values()),
].map(schema => schema.flush())
);
}
return Promise.resolve();
},
rollback() {
if (!rollbacked) {
rollbacked = true;
return Promise.all(
[
...Array.from(tmpComponents.values()),
...Array.from(tmpContentTypes.values()),
].map(schema => schema.rollback())
);
}
return Promise.resolve();
},
};
return ctx;
};