531 lines
16 KiB
JavaScript
Raw Normal View History

2018-06-27 19:00:19 +02:00
const _ = require('lodash');
const pluralize = require('pluralize');
const {
getApis,
getApisKeys,
getApisUploadRelations,
getEditDisplayAvailableFieldsPath,
2019-02-04 17:03:51 +01:00
getEditDisplayDisplayedField,
getEditDisplayFieldsPath
} = require('./utils/getters');
const splitted = str => str.split('.');
const pickData = model =>
_.pick(model, [
'info',
'connection',
'collectionName',
'attributes',
'identity',
'globalId',
'globalName',
'orm',
'options',
'loadedModel',
'primaryKey',
'associations',
]);
2018-06-27 19:00:19 +02:00
module.exports = async cb => {
// Retrieve all layout files from the plugins config folder
2018-07-26 13:33:57 +02:00
const pluginsLayout = Object.keys(strapi.plugins).reduce((acc, current) => {
const models = _.get(strapi.plugins, [current, 'config', 'layout'], {});
Object.keys(models).forEach(model => {
const layout = _.get(
strapi.plugins,
[current, 'config', 'layout', model],
{}
);
acc[model] = layout;
});
return acc;
}, {});
// Remove the core_store layout since it is not needed
// And create a temporay one
2018-07-26 13:33:57 +02:00
const tempLayout = Object.keys(strapi.models)
.filter(m => m !== 'core_store')
.reduce((acc, current) => {
acc[current] = { attributes: {} };
return acc;
}, pluginsLayout);
2018-06-27 19:00:19 +02:00
const models = _.mapValues(strapi.models, pickData);
delete models['core_store'];
const pluginsModel = Object.keys(strapi.plugins).reduce((acc, current) => {
acc[current] = {
models: _.mapValues(strapi.plugins[current].models, pickData),
};
2018-07-23 15:12:33 +02:00
2018-06-27 19:00:19 +02:00
return acc;
}, {});
// Reference all current models
2018-11-16 18:32:26 +01:00
const appModels = Object.keys(pluginsModel).reduce((acc, curr) => {
const models = Object.keys(_.get(pluginsModel, [curr, 'models'], {}));
return acc.concat(models);
}, Object.keys(strapi.models).filter(m => m !== 'core_store'));
2018-06-27 19:00:19 +02:00
// Init schema
2018-06-28 17:54:24 +02:00
const schema = {
generalSettings: {
search: true,
filters: true,
bulkActions: true,
pageEntries: 10,
},
models: {
plugins: {},
},
layout: {},
2018-06-28 17:54:24 +02:00
};
2018-07-23 15:12:33 +02:00
// Populate the schema object
2018-06-27 19:00:19 +02:00
const buildSchema = (model, name, plugin = false) => {
// Model data
const schemaModel = Object.assign(
{
label: _.upperFirst(name),
labelPlural: _.upperFirst(pluralize(name)),
orm: model.orm || 'mongoose',
search: true,
filters: true,
bulkActions: true,
pageEntries: 10,
defaultSort: model.primaryKey,
sort: 'ASC',
options: model.options,
editDisplay: {
availableFields: {},
displayedField: model.primaryKey,
fields: [],
relations: [],
},
},
model
);
const fieldsToRemove = [];
2018-06-27 19:00:19 +02:00
// Fields (non relation)
const fields = _.mapValues(
_.pickBy(
model.attributes,
attribute => !attribute.model && !attribute.collection
),
(value, attribute) => {
const fieldClassName = _.get(
tempLayout,
[name, 'attributes', attribute, 'className'],
''
);
if (fieldClassName === 'd-none') {
fieldsToRemove.push(attribute);
}
return {
label: _.upperFirst(attribute),
description: '',
type: value.type || 'string',
disabled: false,
};
}
);
// Don't display fields that are hidden by default like the resetPasswordToken for the model user
fieldsToRemove.forEach(field => {
_.unset(fields, field);
});
schemaModel.attributes = _.omit(schemaModel.attributes, fieldsToRemove);
schemaModel.fields = fields;
schemaModel.editDisplay.availableFields = fields;
2018-07-23 15:12:33 +02:00
2018-06-27 19:00:19 +02:00
// Select fields displayed in list view
schemaModel.listDisplay = Object.keys(schemaModel.fields)
// Construct Array of attr ex { type: 'string', label: 'Foo', name: 'Foo', description: '' }
2018-06-28 17:54:24 +02:00
.map(attr => {
const attrType = schemaModel.fields[attr].type;
const sortable = attrType !== 'json' && attrType !== 'array';
return Object.assign(schemaModel.fields[attr], {
name: attr,
sortable,
searchable: sortable,
});
2018-06-28 17:54:24 +02:00
})
2018-06-27 19:00:19 +02:00
// Retrieve only the fourth first items
.slice(0, 4);
2018-07-23 15:12:33 +02:00
2018-06-28 12:12:54 +02:00
schemaModel.listDisplay.splice(0, 0, {
name: model.primaryKey || 'id',
label: 'Id',
type: 'string',
2018-06-28 17:54:24 +02:00
sortable: true,
searchable: true,
2018-06-28 12:12:54 +02:00
});
// This object will be used to customise the label and description and so on of an input.
// TODO: maybe add the customBootstrapClass in it;
schemaModel.editDisplay.availableFields = Object.keys(
schemaModel.fields
).reduce((acc, current) => {
acc[current] = Object.assign(
_.pick(_.get(schemaModel, ['fields', current], {}), [
'label',
'type',
'description',
'name',
]),
{
editable:
['updatedAt', 'createdAt', 'updated_at', 'created_at'].indexOf(
current
) === -1,
placeholder: '',
}
);
return acc;
}, {});
2018-07-23 15:12:33 +02:00
2018-06-27 19:00:19 +02:00
if (model.associations) {
// Model relations
schemaModel.relations = model.associations.reduce((acc, current) => {
const label = _.upperFirst(current.alias);
const displayedAttribute = current.plugin // Value to modified to custom what's displayed in the react-select
? _.get(pluginsModel, [
current.plugin,
'models',
current.model || current.collection,
'info',
'mainField',
]) ||
_.findKey(
_.get(pluginsModel, [
current.plugin,
'models',
current.model || current.collection,
'attributes',
]),
{ type: 'string' }
) ||
'id'
: _.get(models, [
current.model || current.collection,
'info',
'mainField',
]) ||
_.findKey(
_.get(models, [
current.model || current.collection,
'attributes',
]),
{ type: 'string' }
) ||
'id';
2018-07-23 15:12:33 +02:00
2018-06-27 19:00:19 +02:00
acc[current.alias] = {
...current,
description: '',
label,
2018-06-27 19:00:19 +02:00
displayedAttribute,
};
2018-07-23 15:12:33 +02:00
2018-06-27 19:00:19 +02:00
return acc;
}, {});
const relationsArray = Object.keys(schemaModel.relations).filter(
relation => {
const isUploadRelation =
_.get(schemaModel, ['relations', relation, 'plugin'], '') ===
'upload';
const isMorphSide =
_.get(schemaModel, ['relations', relation, 'nature'], '')
.toLowerCase()
.includes('morp') &&
_.get(schemaModel, ['relations', relation, relation]) !== undefined;
return !isUploadRelation && !isMorphSide;
}
);
const uploadRelations = Object.keys(schemaModel.relations).reduce(
(acc, current) => {
if (
_.get(schemaModel, ['relations', current, 'plugin']) === 'upload'
) {
const model = _.get(schemaModel, ['relations', current]);
acc[current] = {
description: '',
editable: true,
label: _.upperFirst(current),
multiple: _.has(model, 'collection'),
name: current,
placeholder: '',
type: 'file',
disabled: false,
};
}
return acc;
},
{}
);
schemaModel.editDisplay.availableFields = _.merge(
schemaModel.editDisplay.availableFields,
uploadRelations
);
schemaModel.editDisplay.relations = relationsArray;
2018-06-27 19:00:19 +02:00
}
schemaModel.editDisplay.fields = Object.keys(
schemaModel.editDisplay.availableFields
);
2018-07-23 15:12:33 +02:00
2018-06-27 19:00:19 +02:00
if (plugin) {
2018-06-28 17:54:24 +02:00
return _.set(schema.models.plugins, `${plugin}.${name}`, schemaModel);
2018-06-27 19:00:19 +02:00
}
2018-07-23 15:12:33 +02:00
2018-06-27 19:00:19 +02:00
// Set the formatted model to the schema
2018-06-28 17:54:24 +02:00
schema.models[name] = schemaModel;
2018-06-27 19:00:19 +02:00
};
2018-07-23 15:12:33 +02:00
// For each plugin's apis populate the schema object with the needed infos
2018-06-27 19:00:19 +02:00
_.forEach(pluginsModel, (plugin, pluginName) => {
_.forEach(plugin.models, (model, name) => {
buildSchema(model, name, pluginName);
});
});
2018-07-23 15:12:33 +02:00
2018-06-27 19:00:19 +02:00
// Generate schema for models.
_.forEach(models, (model, name) => {
buildSchema(model, name);
});
2018-07-23 15:12:33 +02:00
2018-06-27 19:00:19 +02:00
const pluginStore = strapi.store({
2018-06-28 12:12:54 +02:00
environment: '',
2018-06-27 19:00:19 +02:00
type: 'plugin',
name: 'content-manager',
2018-06-27 19:00:19 +02:00
});
try {
// Retrieve the previous schema from the db
2018-06-27 19:00:19 +02:00
const prevSchema = await pluginStore.get({ key: 'schema' });
// If no schema stored
2018-06-27 19:00:19 +02:00
if (!prevSchema) {
_.set(schema, 'layout', tempLayout);
2018-07-23 15:12:33 +02:00
2018-06-27 19:00:19 +02:00
pluginStore.set({ key: 'schema', value: schema });
2018-07-23 15:12:33 +02:00
return cb();
2018-11-16 18:32:26 +01:00
} else {
const modelsLayout = Object.keys(_.get(prevSchema, 'layout', {}));
2018-11-16 18:32:26 +01:00
// Remove previous model from the schema.layout
// Usually needed when renaming a model
modelsLayout.forEach(model => {
2018-11-16 18:32:26 +01:00
if (!appModels.includes(model)) {
_.unset(prevSchema, ['layout', model]);
}
});
2018-06-27 19:00:19 +02:00
}
// Here we do the difference between the previous schema from the database and the new one
// Retrieve all the api path, it generates an array
const prevSchemaApis = getApis(prevSchema.models);
const schemaApis = getApis(schema.models);
// Array of apis to add
const apisToAdd = schemaApis
.filter(api => prevSchemaApis.indexOf(api) === -1)
.map(splitted);
// Array of apis to remove
const apisToRemove = prevSchemaApis
.filter(api => schemaApis.indexOf(api) === -1)
.map(splitted);
// Retrieve the same apis by name
const sameApis = schemaApis
.filter(api => prevSchemaApis.indexOf(api) !== -1)
.map(splitted);
// Retrieve all the field's path of the current unchanged api name
const schemaSameApisKeys = _.flattenDeep(getApisKeys(schema, sameApis));
// Retrieve all the field's path of the previous unchanged api name
const prevSchemaSameApisKeys = _.flattenDeep(
getApisKeys(prevSchema, sameApis)
);
// Determine for the same api if we need to add some fields
const sameApisAttrToAdd = schemaSameApisKeys
.filter(attr => prevSchemaSameApisKeys.indexOf(attr) === -1)
.map(splitted);
// Special case for the relations
const prevSchemaSameApisUploadRelations = _.flattenDeep(
getApisUploadRelations(prevSchema, sameApis)
);
const schemaSameApisUploadRelations = _.flattenDeep(
getApisUploadRelations(schema, sameApis)
);
const sameApisUploadRelationsToAdd = schemaSameApisUploadRelations
.filter(attr => prevSchemaSameApisUploadRelations.indexOf(attr) === -1)
.map(splitted);
// Determine the fields to remove for the unchanged api name
const sameApisAttrToRemove = prevSchemaSameApisKeys
.filter(attr => schemaSameApisKeys.indexOf(attr) === -1)
.map(splitted);
// Remove api
apisToRemove.map(apiPath => {
_.unset(prevSchema.models, apiPath);
});
2018-07-23 15:12:33 +02:00
// Remove API attribute
sameApisAttrToRemove.map(attrPath => {
2018-07-27 17:24:58 +02:00
const editDisplayPath = getEditDisplayAvailableFieldsPath(attrPath);
// Remove the field from the available fields in the editDisplayObject
_.unset(prevSchema.models, editDisplayPath);
// Check default sort and change it if needed
_.unset(prevSchema.models, attrPath);
// Retrieve the api path in the schema Object
const apiPath =
attrPath.length > 3 ? _.take(attrPath, 3) : _.take(attrPath, 1);
// Retrieve the listDisplay path in the schema Object
const listDisplayPath = apiPath.concat('listDisplay');
const prevListDisplay = _.get(prevSchema.models, listDisplayPath);
const defaultSortPath = apiPath.concat('defaultSort');
const currentAttr = attrPath.slice(-1);
const defaultSort = _.get(prevSchema.models, defaultSortPath);
2019-02-04 17:03:51 +01:00
const displayedFieldPath = getEditDisplayDisplayedField(attrPath);
const displayedField = _.get(prevSchema.models, displayedFieldPath, null);
const primaryKey = _.get(prevSchema.models, [...apiPath, 'primaryKey'], null);
// If the user has deleted the default sort attribute in the content type builder
// Replace it by new generated one from the current schema
if (_.includes(currentAttr, defaultSort)) {
_.set(
prevSchema.models,
defaultSortPath,
_.get(schema.models, defaultSortPath)
);
}
2019-02-04 17:03:51 +01:00
// If the user has deleted the edit view displayed field (name in the header)
// Replace it by the model's primary key.
if (_.includes(currentAttr, displayedField)) {
_.set(prevSchema.models, displayedFieldPath, primaryKey);
}
// Update the displayed fields
const updatedListDisplay = prevListDisplay.filter(
obj => obj.name !== currentAttr.join()
);
// Retrieve the model's displayed fields for the `EditPage`
const fieldsPath = getEditDisplayFieldsPath(attrPath);
// Retrieve the previous settings
const prevEditDisplayFields = _.get(prevSchema.models, fieldsPath);
// Update the fields
const updatedEditDisplayFields = prevEditDisplayFields.filter(
field => field !== currentAttr.join()
);
2018-11-08 11:18:51 +01:00
// Set the new layout
_.set(prevSchema.models, fieldsPath, updatedEditDisplayFields);
if (updatedListDisplay.length === 0) {
// Update it with the one from the generated schema
_.set(
prevSchema.models,
listDisplayPath,
_.get(schema.models, listDisplayPath, [])
);
} else {
_.set(prevSchema.models, listDisplayPath, updatedListDisplay);
}
});
// Add API
// Here we just need to add the data from the current schema Object
apisToAdd.map(apiPath => {
const api = _.get(schema.models, apiPath);
const { search, filters, bulkActions, pageEntries, options } = _.get(
prevSchema,
'generalSettings'
);
2018-07-06 13:21:50 +02:00
2018-12-28 14:17:12 +05:30
_.set(api, 'options', options);
2018-07-06 13:21:50 +02:00
_.set(api, 'filters', filters);
_.set(api, 'search', search);
_.set(api, 'bulkActions', bulkActions);
_.set(api, 'pageEntries', pageEntries);
_.set(prevSchema.models, apiPath, api);
});
2018-07-23 15:12:33 +02:00
// Add attribute to an existing API
sameApisAttrToAdd.map(attrPath => {
const attr = _.get(schema.models, attrPath);
_.set(prevSchema.models, attrPath, attr);
2018-07-27 17:24:58 +02:00
// Add the field in the editDisplay object
const path = getEditDisplayAvailableFieldsPath(attrPath);
const availableAttrToAdd = _.get(schema.models, path);
_.set(prevSchema.models, path, availableAttrToAdd);
// Push the attr into the list
const fieldsPath = getEditDisplayFieldsPath(attrPath);
const currentFields = _.get(prevSchema.models, fieldsPath, []);
currentFields.push(availableAttrToAdd.name);
_.set(prevSchema.models, fieldsPath, currentFields);
});
// Update other keys
sameApis.forEach(apiPath => {
// This doesn't keep the prevSettings for the relations, the user will have to reset it.
// We might have to improve this if we want the order of the relations to be kept
[
'relations',
'loadedModel',
'associations',
'attributes',
['editDisplay', 'relations'],
]
.map(key => apiPath.concat(key))
.forEach(keyPath => {
const newValue = _.get(schema.models, keyPath);
2018-07-23 15:12:33 +02:00
_.set(prevSchema.models, keyPath, newValue);
});
});
2018-07-31 14:54:52 +02:00
// Special handler for the upload relations
sameApisUploadRelationsToAdd.forEach(attrPath => {
const attr = _.get(schema.models, attrPath);
_.set(prevSchema.models, attrPath, attr);
const fieldsPath = [..._.take(attrPath, attrPath.length - 2), 'fields'];
2018-07-31 14:54:52 +02:00
const currentFields = _.get(prevSchema.models, fieldsPath, []);
currentFields.push(attr.name);
_.set(prevSchema.models, fieldsPath, currentFields);
});
schemaApis.map(model => {
2019-01-23 15:19:34 +01:00
const isPlugin = model.includes('plugins.');
_.set(
prevSchema.models[model],
'info',
_.get(!isPlugin ? strapi.models[model] : strapi[model], 'info')
);
_.set(
prevSchema.models[model],
'options',
_.get(!isPlugin ? strapi.models[model] : strapi[model], 'options')
);
2019-01-23 15:19:34 +01:00
});
2018-07-05 17:57:30 +02:00
await pluginStore.set({ key: 'schema', value: prevSchema });
} catch (err) {
console.log('error', err); // eslint-disable-line no-console
2018-07-23 15:12:33 +02:00
}
2018-06-27 19:00:19 +02:00
cb();
};