Merge branch 'develop' of github.com:strapi/strapi into ctm/repeatable-edit-view

This commit is contained in:
soupette 2019-07-29 13:53:13 +02:00
commit fdb0f86fcc
17 changed files with 296 additions and 144 deletions

View File

@ -59,8 +59,8 @@
"collection": "tag"
},
"manyTags": {
"dominant": true,
"collection": "tag",
"dominant": true,
"via": "linkedArticles"
},
"fb_cta": {

View File

@ -24,6 +24,9 @@
},
"article": {
"model": "article"
},
"articles": {
"collection": "article"
}
}
}

View File

@ -623,7 +623,8 @@ module.exports = ({ models, target, plugin = false }, ctx) => {
} else {
options.withRelated = groupAttributes
.map(key => `${key}.slice`)
.map(addPolymorphicRelated);
.map(addPolymorphicRelated)
.reduce((acc, paths) => acc.concat(paths), []);
}
return _.isFunction(target[model.toLowerCase()]['beforeFetchAll'])

View File

@ -38,6 +38,13 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
});
};
const wrapTransaction = (fn, { transacting } = {}) => {
const db = strapi.connections[model.connection];
if (transacting) return fn(transacting);
return db.transaction(trx => fn(trx));
};
/**
* Find one entry based on params
*/
@ -60,12 +67,12 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
/**
* Find multiple entries based on params
*/
function find(params, populate) {
function find(params, populate, { transacting } = {}) {
const filters = convertRestQueryParams(params);
return model
.query(buildQuery({ model, filters }))
.fetchAll({ withRelated: populate || defaultPopulate })
.fetchAll({ withRelated: populate || defaultPopulate, transacting })
.then(results => results.toJSON());
}
@ -78,7 +85,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
return model.query(buildQuery({ model, filters: { where } })).count();
}
async function create(values) {
async function create(values, { transacting } = {}) {
const relations = pickRelations(values);
const data = selectAttributes(values);
@ -89,14 +96,16 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
return entry;
};
const db = strapi.connections[model.connection];
const entry = await db.transaction(trx => runCreate(trx));
const entry = await wrapTransaction(runCreate, { transacting });
return model.updateRelations({ id: entry.id, values: relations });
return model.updateRelations(
{ id: entry.id, values: relations },
{ transacting }
);
}
async function update(params, values) {
const entry = await model.forge(params).fetch();
async function update(params, values, { transacting } = {}) {
const entry = await model.forge(params).fetch({ transacting });
if (!entry) {
const err = new Error('entry.notFound');
@ -121,20 +130,20 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
return updatedEntry;
};
const db = strapi.connections[model.connection];
await db.transaction(trx => runUpdate(trx));
await wrapTransaction(runUpdate, { transacting });
if (Object.keys(relations).length > 0) {
return model.updateRelations(
Object.assign(params, { values: relations })
Object.assign(params, { values: relations }),
{ transacting }
);
}
return await model.forge(params).fetch();
return await model.forge(params).fetch({ transacting });
}
async function deleteOne(params) {
const entry = await model.forge(params).fetch();
async function deleteOne(params, { transacting } = {}) {
const entry = await model.forge(params).fetch({ transacting });
if (!entry) {
const err = new Error('entry.notFound');
@ -151,6 +160,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
case 'oneToManyMorph':
values[association.alias] = null;
break;
case 'manyWay':
case 'oneToMany':
case 'manyToMany':
case 'manyToManyMorph':
@ -160,25 +170,26 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
}
});
await model.updateRelations({ ...params, values });
await model.updateRelations({ ...params, values }, { transacting });
const runDelete = async trx => {
await deleteGroups(entry, { transacting: trx });
await model.forge(params).destroy({ transacting: trx });
await model.forge(params).destroy({ transacting: trx, require: false });
return entry;
};
const db = strapi.connections[model.connection];
return db.transaction(trx => runDelete(trx));
return wrapTransaction(runDelete, { transacting });
}
async function deleteMany(params) {
async function deleteMany(params, { transacting } = {}) {
const primaryKey = params[model.primaryKey] || params.id;
if (primaryKey) return deleteOne(params);
if (primaryKey) return deleteOne(params, { transacting });
const entries = await find(params);
return await Promise.all(entries.map(entry => deleteOne({ id: entry.id })));
const entries = await find(params, null, { transacting });
return await Promise.all(
entries.map(entry => deleteOne({ id: entry.id }, { transacting }))
);
}
function search(params, populate) {
@ -210,7 +221,11 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
}
function countSearch(params) {
return model.query(qb => buildSearchQuery(qb, model, params)).count();
return model
.query(qb => {
buildSearchQuery(qb, model, params);
})
.count();
}
async function createGroups(entry, values, { transacting }) {
@ -221,14 +236,14 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
for (let key of groupKeys) {
const attr = model.attributes[key];
const { group, required = false, repeatable = true } = attr;
const { group, required = false, repeatable = false } = attr;
const groupModel = strapi.groups[group];
const createGroupAndLink = async ({ value, order }) => {
return groupModel
.forge()
.save(value, { transacting })
return strapi
.query(groupModel.uid)
.create(value, { transacting })
.then(group => {
return joinModel.forge().save(
{
@ -278,7 +293,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
if (!_.has(values, key)) continue;
const attr = model.attributes[key];
const { group, repeatable = true } = attr;
const { group, repeatable = false } = attr;
const groupModel = strapi.groups[group];
@ -287,9 +302,15 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
const updateOrCreateGroupAndLink = async ({ value, order }) => {
// check if value has an id then update else create
if (_.has(value, groupModel.primaryKey)) {
return groupModel
.forge(value)
.save(value, { transacting, patch: true, require: false })
return strapi
.query(groupModel.uid)
.update(
{
[groupModel.primaryKey]: value[groupModel.primaryKey],
},
value,
{ transacting }
)
.then(group => {
return joinModel
.forge()
@ -310,9 +331,9 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
});
}
// create
return groupModel
.forge()
.save(value, { transacting })
return strapi
.query(groupModel.uid)
.create(value, { transacting })
.then(group => {
return joinModel.forge().save(
{
@ -367,7 +388,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
const idsToKeep = groupArr
.filter(el => _.has(el, groupModel.primaryKey))
.map(el => el[groupModel.primaryKey]);
.map(el => el[groupModel.primaryKey].toString());
const allIds = await joinModel
.forge()
@ -379,7 +400,7 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
// verify the provided ids are realted to this entity.
idsToKeep.forEach(id => {
if (!allIds.includes(id.toString())) {
if (!allIds.includes(id)) {
const err = new Error(
`Some of the provided groups in ${key} are not related to the entity`
);
@ -395,10 +416,12 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
.query(qb => qb.whereIn('slice_id', idsToDelete))
.destroy({ transacting, require: false });
await groupModel
.forge()
.query(qb => qb.whereIn(groupModel.primaryKey, idsToDelete))
.destroy({ transacting, require: false });
await strapi
.query(groupModel.uid)
.delete(
{ [`${groupModel.primaryKey}_in`]: idsToDelete },
{ transacting }
);
}
}
@ -426,10 +449,10 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
.fetchAll({ transacting })
.map(el => el.get('slice_id'));
await groupModel
.forge()
.query(qb => qb.whereIn(groupModel.primaryKey, ids))
.destroy({ transacting, require: false });
await strapi
.query(groupModel.uid)
.delete({ [`${groupModel.primaryKey}_in`]: ids }, { transacting });
await joinModel
.forge()
.query({
@ -497,13 +520,13 @@ const buildSearchQuery = (qb, model, params) => {
if (!_.isNaN(_.toNumber(query))) {
searchInt.forEach(attribute => {
qb.orWhereRaw(attribute, _.toNumber(query));
qb.orWhere(attribute, _.toNumber(query));
});
}
if (query === 'true' || query === 'false') {
searchBool.forEach(attribute => {
qb.orWhereRaw(attribute, _.toNumber(query === 'true'));
qb.orWhere(attribute, _.toNumber(query === 'true'));
});
}

View File

@ -46,10 +46,11 @@ const getModel = (model, plugin) => {
const removeUndefinedKeys = obj => _.pickBy(obj, _.negate(_.isUndefined));
module.exports = {
findOne: async function(params, populate) {
async findOne(params, populate, { transacting } = {}) {
const record = await this.forge({
[this.primaryKey]: getValuePrimaryKey(params, this.primaryKey),
}).fetch({
transacting,
withRelated: populate || this.associations.map(x => x.alias),
});
@ -70,7 +71,9 @@ module.exports = {
this.primaryKey
),
})
.fetchAll();
.fetchAll({
transacting,
});
});
const related = await Promise.all(arrayOfPromises);
@ -83,10 +86,12 @@ module.exports = {
return data;
},
update: async function(params) {
async update(params, { transacting } = {}) {
const relationUpdates = [];
const primaryKeyValue = getValuePrimaryKey(params, this.primaryKey);
const response = await module.exports.findOne.call(this, params);
const response = await module.exports.findOne.call(this, params, null, {
transacting,
});
// Only update fields which are on this document.
const values =
@ -129,7 +134,12 @@ module.exports = {
})
.save(
{ [details.via]: null },
{ method: 'update', patch: true, require: false }
{
method: 'update',
patch: true,
require: false,
transacting,
}
);
relationUpdates.push(updatePromise);
@ -140,14 +150,24 @@ module.exports = {
const updateLink = this.where({ [current]: property })
.save(
{ [current]: null },
{ method: 'update', patch: true, require: false }
{
method: 'update',
patch: true,
require: false,
transacting,
}
)
.then(() => {
return assocModel
.where({ [this.primaryKey]: property })
.save(
{ [details.via]: primaryKeyValue },
{ method: 'update', patch: true, require: false }
{
method: 'update',
patch: true,
require: false,
transacting,
}
);
});
@ -179,7 +199,12 @@ module.exports = {
)
.save(
{ [details.via]: null },
{ method: 'update', patch: true, require: false }
{
method: 'update',
patch: true,
require: false,
transacting,
}
)
.then(() => {
return assocModel
@ -190,7 +215,12 @@ module.exports = {
)
.save(
{ [details.via]: primaryKeyValue },
{ method: 'update', patch: true, require: false }
{
method: 'update',
patch: true,
require: false,
transacting,
}
);
});
@ -221,9 +251,10 @@ module.exports = {
const collection = this.forge({
[this.primaryKey]: primaryKeyValue,
})[association.alias]();
const updatePromise = collection
.detach(toRemove)
.then(() => collection.attach(toAdd));
.detach(toRemove, { transacting })
.then(() => collection.attach(toAdd, { transacting }));
relationUpdates.push(updatePromise);
return acc;
@ -242,31 +273,43 @@ module.exports = {
if (association.nature === 'manyMorphToOne') {
relationUpdates.push(
module.exports.removeRelationMorph
.call(this, {
alias: association.alias,
ref: model.collectionName,
refId: obj.refId,
field: obj.field,
})
.then(() =>
module.exports.addRelationMorph.call(this, {
id: response[this.primaryKey],
.call(
this,
{
alias: association.alias,
ref: model.collectionName,
refId: obj.refId,
field: obj.field,
})
},
{ transacting }
)
.then(() =>
module.exports.addRelationMorph.call(
this,
{
id: response[this.primaryKey],
alias: association.alias,
ref: model.collectionName,
refId: obj.refId,
field: obj.field,
},
{ transacting }
)
)
);
} else {
relationUpdates.push(
module.exports.addRelationMorph.call(this, {
id: response[this.primaryKey],
alias: association.alias,
ref: model.collectionName,
refId: obj.refId,
field: obj.field,
})
module.exports.addRelationMorph.call(
this,
{
id: response[this.primaryKey],
alias: association.alias,
ref: model.collectionName,
refId: obj.refId,
field: obj.field,
},
{ transacting }
)
);
}
});
@ -293,26 +336,34 @@ module.exports = {
toAdd.forEach(id => {
relationUpdates.push(
module.exports.addRelationMorph.call(model, {
id,
alias: association.via,
ref: this.collectionName,
refId: response.id,
field: association.alias,
})
module.exports.addRelationMorph.call(
model,
{
id,
alias: association.via,
ref: this.collectionName,
refId: response.id,
field: association.alias,
},
{ transacting }
)
);
});
// Update the relational array.
toRemove.forEach(id => {
relationUpdates.push(
module.exports.removeRelationMorph.call(model, {
id,
alias: association.via,
ref: this.collectionName,
refId: response.id,
field: association.alias,
})
module.exports.removeRelationMorph.call(
model,
{
id,
alias: association.via,
ref: this.collectionName,
refId: response.id,
field: association.alias,
},
{ transacting }
)
);
});
break;
@ -336,17 +387,19 @@ module.exports = {
[this.primaryKey]: getValuePrimaryKey(params, this.primaryKey),
}).save(values, {
patch: true,
transacting,
});
}
return await this.forge({
[this.primaryKey]: getValuePrimaryKey(params, this.primaryKey),
}).fetch({
transacting,
withRelated: this.associations.map(x => x.alias),
});
},
addRelationMorph: async function(params) {
async addRelationMorph(params, { transacting } = {}) {
const record = await this.morph
.forge()
.where({
@ -356,6 +409,7 @@ module.exports = {
field: params.field,
})
.fetch({
transacting,
withRelated: this.associations.map(x => x.alias),
});
@ -372,10 +426,10 @@ module.exports = {
[`${params.alias}_type`]: params.ref,
field: params.field,
})
.save();
.save(null, { transacting });
},
removeRelationMorph: async function(params) {
async removeRelationMorph(params, { transacting } = {}) {
return await this.morph
.forge()
.where(
@ -391,6 +445,7 @@ module.exports = {
)
.destroy({
require: false,
transacting,
});
},
};

View File

@ -38,7 +38,7 @@ module.exports = ({ model, modelKey, strapi }) => {
for (let key of groupKeys) {
const attr = model.attributes[key];
const { group, required = false, repeatable = true } = attr;
const { group, required = false, repeatable = false } = attr;
const groupModel = strapi.groups[group];
@ -55,23 +55,25 @@ module.exports = ({ model, modelKey, strapi }) => {
if (repeatable === true) {
validateRepeatableInput(groupValue, { key, ...attr });
const groups = await Promise.all(
groupValue.map(value => groupModel.create(value))
groupValue.map(value => {
return strapi.query(group).create(value);
})
);
const groupsArr = groups.map(group => ({
const groupsArr = groups.map(groupEntry => ({
kind: groupModel.globalId,
ref: group,
ref: groupEntry,
}));
entry[key] = groupsArr;
await entry.save();
} else {
validateNonRepeatableInput(groupValue, { key, ...attr });
const group = await groupModel.create(groupValue);
const groupEntry = await strapi.query(group).create(groupValue);
entry[key] = [
{
kind: groupModel.globalId,
ref: group,
ref: groupEntry,
},
];
await entry.save();
@ -87,7 +89,7 @@ module.exports = ({ model, modelKey, strapi }) => {
if (!_.has(values, key)) continue;
const attr = model.attributes[key];
const { group, repeatable = true } = attr;
const { group, repeatable = false } = attr;
const groupModel = strapi.groups[group];
const groupValue = values[key];
@ -95,15 +97,14 @@ module.exports = ({ model, modelKey, strapi }) => {
const updateOrCreateGroup = async value => {
// check if value has an id then update else create
if (hasPK(value)) {
return groupModel.findOneAndUpdate(
return strapi.query(group).update(
{
[model.primaryKey]: getPK(value),
},
value,
{ new: true }
value
);
}
return groupModel.create(value);
return strapi.query(group).create(value);
};
if (repeatable === true) {
@ -162,7 +163,9 @@ module.exports = ({ model, modelKey, strapi }) => {
}, []);
if (idsToDelete.length > 0) {
await groupModel.deleteMany({ [model.primaryKey]: { $in: idsToDelete } });
await strapi
.query(groupModel.uid)
.delete({ [`${model.primaryKey}_in`]: idsToDelete });
}
}
@ -175,9 +178,10 @@ module.exports = ({ model, modelKey, strapi }) => {
const groupModel = strapi.groups[group];
if (Array.isArray(entry[key]) && entry[key].length > 0) {
await groupModel.deleteMany({
[model.primaryKey]: { $in: entry[key].map(el => el.ref) },
});
const idsToDelete = entry[key].map(el => el.ref);
await strapi
.query(groupModel.uid)
.delete({ [`${model.primaryKey}_in`]: idsToDelete });
}
}
}

View File

@ -208,17 +208,9 @@ module.exports = {
case 'manyMorphToOne':
// Update the relational array.
acc[current] = property.map(obj => {
const globalId =
obj.source && obj.source !== 'content-manager'
? strapi.plugins[obj.source].models[_.toLower(obj.ref)]
.globalId
: strapi.models[_.toLower(obj.ref)].globalId;
// Define the object stored in database.
// The shape is this object is defined by the strapi-hook-mongoose connector.
return {
ref: obj.refId,
kind: globalId,
kind: obj.ref,
[association.filter]: obj.field,
};
});

View File

@ -11,7 +11,7 @@ module.exports = {
strapi.plugins['content-manager'].services['contentmanager'];
let entities = [];
if (!_.isEmpty(ctx.request.query._q)) {
if (_.has(ctx.request.query, '_q')) {
entities = await contentManagerService.search(
ctx.params,
ctx.request.query
@ -51,7 +51,7 @@ module.exports = {
strapi.plugins['content-manager'].services['contentmanager'];
let count;
if (!_.isEmpty(ctx.request.query._q)) {
if (_.has(ctx.request.query, '_q')) {
count = await contentManagerService.countSearch(
ctx.params,
ctx.request.query

View File

@ -127,23 +127,15 @@ module.exports = {
},
search(params, query) {
const { limit, skip, sort, source, _q, populate = [] } = query; // eslint-disable-line no-unused-vars
const filters = strapi.utils.models.convertParams(params.model, query);
const { model } = params;
const { source } = query;
// Find entries using `queries` system
return strapi.query(params.model, source).search(
{
limit: limit || filters.limit,
skip: skip || filters.start || 0,
sort: sort || filters.sort,
search: _q,
},
populate
);
return strapi.query(model, source).search(query);
},
countSearch(params, query) {
const { model } = params;
const { source, _q } = query;
return strapi.query(params.model, source).countSearch({ search: _q });
return strapi.query(model, source).countSearch({ _q });
},
};

View File

@ -105,4 +105,15 @@ module.exports = {
);
return getModel(uid);
},
async updateUID({ oldUID, newUID, source }) {
const oldKey = uidToStoreKey({ uid: oldUID, source });
const newKey = uidToStoreKey({ uid: newUID, source });
await storeUtils.setModelConfiguration(oldKey, {
uid: oldUID,
});
return storeUtils.moveKey(oldKey, newKey);
},
};

View File

@ -29,4 +29,16 @@ module.exports = {
const storeKey = uidToStoreKey(uid);
return storeUtils.deleteKey(storeKey);
},
async updateUID(oldUID, newUID) {
const oldKey = uidToStoreKey(oldUID);
const newKey = uidToStoreKey(newUID);
await storeUtils.setModelConfiguration(oldKey, {
uid: oldUID,
isGroup: true,
});
return storeUtils.moveKey(oldKey, newKey);
},
};

View File

@ -89,6 +89,16 @@ function findByKeyQuery({ model }, key) {
}
const findByKey = key => strapi.query('core_store').custom(findByKeyQuery)(key);
const moveKey = (oldKey, newKey) => {
return strapi.query('core_store').update(
{
key: `plugin_content_manager_configuration_${oldKey}`,
},
{
key: `plugin_content_manager_configuration_${newKey}`,
}
);
};
const getAllConfigurations = () =>
findByKey('plugin_content_manager_configuration');
@ -103,5 +113,6 @@ module.exports = {
setModelConfiguration,
deleteKey,
moveKey,
keys,
};

View File

@ -220,8 +220,8 @@ module.exports = {
])
);
modelJSON.connection = connection;
modelJSON.collectionName = collectionName;
modelJSON.connection = connection || modelJSON.connection;
modelJSON.collectionName = collectionName || modelJSON.collectionName;
modelJSON.info = {
name,
description: _description,
@ -256,6 +256,20 @@ module.exports = {
if (!_.isEmpty(removeModelErrors)) {
return ctx.badRequest(null, [{ messages: removeModelErrors }]);
}
if (
_.has(strapi.plugins, ['content-manager', 'services', 'contenttypes'])
) {
await _.get(strapi.plugins, [
'content-manager',
'services',
'contenttypes',
]).updateUID({
oldUID: model,
newUID: name.toLowerCase(),
source: plugin,
});
}
}
try {

View File

@ -116,15 +116,34 @@ async function createGroup(uid, infos) {
* @param {Object} infos
*/
async function updateGroup(group, infos) {
const { uid } = group;
const { uid, schema: oldSchema } = group;
const newUid = createGroupUID(infos.name);
if (uid !== newUid) {
// don't update collectionName if not provided
const updatedSchema = {
info: {
name: infos.name || oldSchema.name,
description: infos.description || oldSchema.description,
},
connection: infos.connection || oldSchema.connection,
collectionName: infos.collectionName || oldSchema.collectionName,
attributes: convertAttributes(infos.attributes),
};
const newUID = createGroupUID(infos.name);
if (uid !== newUID) {
await deleteSchema(uid);
return createGroup(newUid, infos);
}
const updatedSchema = { ...group.schema, ...createSchema(uid, infos) };
if (_.has(strapi.plugins, ['content-manager', 'services', 'groups'])) {
await _.get(strapi.plugins, [
'content-manager',
'services',
'groups',
]).updateUID(uid, newUID);
}
await writeSchema(newUID, updatedSchema);
return { uid: newUID };
}
await writeSchema(uid, updatedSchema);
return { uid };
@ -137,7 +156,11 @@ async function updateGroup(group, infos) {
const createSchema = (uid, infos) => {
const {
name,
connection = 'default',
connection = _.get(
strapi,
['config', 'currentEnvironment', 'database', 'defaultConnection'],
'default'
),
description = '',
collectionName,
attributes,
@ -149,7 +172,7 @@ const createSchema = (uid, infos) => {
description,
},
connection,
collectionName: collectionName || `groups_${pluralize(uid)}`,
collectionName: collectionName || `groups_${pluralize(uid).toLowerCase()}`,
attributes: convertAttributes(attributes),
};
};

View File

@ -8,6 +8,16 @@ describe('Group Service', () => {
attributes: {},
};
global.strapi = {
config: {
defaultEnvironment: {
database: {
defaultConnection: 'default',
},
},
},
};
const expected = {
info: {
name: 'Some name',

View File

@ -188,7 +188,7 @@ module.exports = {
.findOne({ id: roleID }, ['users', 'permissions']);
if (!role) {
throw new Error('Cannot found this role');
throw new Error('Cannot find this role');
}
// Group by `type`.

View File

@ -51,6 +51,7 @@ module.exports = function(strapi) {
throw new Error(`Group ${key} is missing a collectionName attribute`);
return Object.assign(group, {
uid: key,
modelType: 'group',
globalId: group.globalId || _.upperFirst(_.camelCase(`group_${key}`)),
});