mirror of
https://github.com/strapi/strapi.git
synced 2025-12-24 21:54:24 +00:00
Simplify schema builder
This commit is contained in:
parent
23ee9c9ebe
commit
cdbd6e4063
14
examples/getstarted/components/test-a-zdazd/test-edit-3.json
Normal file
14
examples/getstarted/components/test-a-zdazd/test-edit-3.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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();
|
||||
};
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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');
|
||||
@ -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())
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -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;
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user