mirror of
				https://github.com/strapi/strapi.git
				synced 2025-10-31 09:56:44 +00:00 
			
		
		
		
	Implement create and updates for groups in a CT
This commit is contained in:
		
							parent
							
								
									dc8045778b
								
							
						
					
					
						commit
						78380aa220
					
				| @ -17,6 +17,7 @@ module.exports = ({ models, target, plugin = false }, ctx) => { | ||||
|     definition.associations = []; | ||||
|     definition.globalName = _.upperFirst(_.camelCase(definition.globalId)); | ||||
|     definition.loadedModel = {}; | ||||
|     definition.ObjectId = mongoose.Types.ObjectId; | ||||
|     // Set the default values to model settings.
 | ||||
|     _.defaults(definition, { | ||||
|       primaryKey: '_id', | ||||
|  | ||||
| @ -8,9 +8,16 @@ module.exports = ({ model, modelKey }) => { | ||||
|   }); | ||||
|   const excludedKeys = assocKeys.concat(groupKeys); | ||||
| 
 | ||||
|   function excludeExernalValues(values) { | ||||
|   const defaultPopulate = model.associations | ||||
|     .filter(ast => ast.autoPopulate !== false) | ||||
|     .map(ast => ast.alias); | ||||
| 
 | ||||
|   const pickRelations = values => { | ||||
|     return _.pick(values, assocKeys); | ||||
|   }; | ||||
|   const omitExernalValues = values => { | ||||
|     return _.omit(values, excludedKeys); | ||||
|   } | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * | ||||
| @ -91,7 +98,7 @@ module.exports = ({ model, modelKey }) => { | ||||
| 
 | ||||
|       const groupValue = values[key]; | ||||
| 
 | ||||
|       const updateOreateGroupAndLink = async ({ value, order }) => { | ||||
|       const updateOrCreateGroupAndLink = async ({ value, order }) => { | ||||
|         // check if value has an id then update else create
 | ||||
|         if (_.has(value, groupModel.primaryKey)) { | ||||
|           return groupModel | ||||
| @ -115,23 +122,23 @@ module.exports = ({ model, modelKey }) => { | ||||
|                   { transacting, patch: true, require: false } | ||||
|                 ); | ||||
|             }); | ||||
|         } else { | ||||
|           return groupModel | ||||
|             .forge() | ||||
|             .save(value, { transacting }) | ||||
|             .then(group => { | ||||
|               return joinModel.forge().save( | ||||
|                 { | ||||
|                   [foreignKey]: entry.id, | ||||
|                   slice_type: groupModel.collectionName, | ||||
|                   slice_id: group.id, | ||||
|                   field: key, | ||||
|                   order, | ||||
|                 }, | ||||
|                 { transacting } | ||||
|               ); | ||||
|             }); | ||||
|         } | ||||
|         // create
 | ||||
|         return groupModel | ||||
|           .forge() | ||||
|           .save(value, { transacting }) | ||||
|           .then(group => { | ||||
|             return joinModel.forge().save( | ||||
|               { | ||||
|                 [foreignKey]: entry.id, | ||||
|                 slice_type: groupModel.collectionName, | ||||
|                 slice_id: group.id, | ||||
|                 field: key, | ||||
|                 order, | ||||
|               }, | ||||
|               { transacting } | ||||
|             ); | ||||
|           }); | ||||
|       }; | ||||
| 
 | ||||
|       if (repeatable === true) { | ||||
| @ -146,7 +153,7 @@ module.exports = ({ model, modelKey }) => { | ||||
| 
 | ||||
|         await Promise.all( | ||||
|           groupValue.map((value, idx) => { | ||||
|             return updateOreateGroupAndLink({ value, order: idx + 1 }); | ||||
|             return updateOrCreateGroupAndLink({ value, order: idx + 1 }); | ||||
|           }) | ||||
|         ); | ||||
|       } else { | ||||
| @ -159,7 +166,7 @@ module.exports = ({ model, modelKey }) => { | ||||
|           transacting, | ||||
|         }); | ||||
| 
 | ||||
|         await updateOreateGroupAndLink({ value: groupValue, order: 1 }); | ||||
|         await updateOrCreateGroupAndLink({ value: groupValue, order: 1 }); | ||||
|       } | ||||
|     } | ||||
|     return; | ||||
| @ -196,7 +203,7 @@ module.exports = ({ model, modelKey }) => { | ||||
|     }); | ||||
| 
 | ||||
|     const idsToDelete = _.difference(allIds, idsToKeep); | ||||
|     if (idsToDelete > 0) { | ||||
|     if (idsToDelete.length > 0) { | ||||
|       await joinModel | ||||
|         .forge() | ||||
|         .query(qb => qb.whereIn('slice_id', idsToDelete)) | ||||
| @ -252,11 +259,7 @@ module.exports = ({ model, modelKey }) => { | ||||
| 
 | ||||
|   return { | ||||
|     find(params, populate) { | ||||
|       const withRelated = | ||||
|         populate || | ||||
|         model.associations | ||||
|           .filter(ast => ast.autoPopulate !== false) | ||||
|           .map(ast => ast.alias); | ||||
|       const withRelated = populate || defaultPopulate; | ||||
| 
 | ||||
|       const filters = convertRestQueryParams(params); | ||||
| 
 | ||||
| @ -266,11 +269,7 @@ module.exports = ({ model, modelKey }) => { | ||||
|     }, | ||||
| 
 | ||||
|     findOne(params, populate) { | ||||
|       const withRelated = | ||||
|         populate || | ||||
|         model.associations | ||||
|           .filter(ast => ast.autoPopulate !== false) | ||||
|           .map(ast => ast.alias); | ||||
|       const withRelated = populate || defaultPopulate; | ||||
| 
 | ||||
|       return model | ||||
|         .forge({ | ||||
| @ -288,12 +287,8 @@ module.exports = ({ model, modelKey }) => { | ||||
|     }, | ||||
| 
 | ||||
|     async create(values) { | ||||
|       const relations = _.pick( | ||||
|         values, | ||||
|         model.associations.map(ast => ast.alias) | ||||
|       ); | ||||
| 
 | ||||
|       const data = excludeExernalValues(values); | ||||
|       const relations = pickRelations(values); | ||||
|       const data = omitExernalValues(values); | ||||
| 
 | ||||
|       const runCreate = async trx => { | ||||
|         // Create entry with no-relational data.
 | ||||
| @ -318,12 +313,8 @@ module.exports = ({ model, modelKey }) => { | ||||
|       } | ||||
| 
 | ||||
|       // Extract values related to relational data.
 | ||||
|       const relations = _.pick( | ||||
|         values, | ||||
|         model.associations.map(ast => ast.alias) | ||||
|       ); | ||||
| 
 | ||||
|       const data = excludeExernalValues(values); | ||||
|       const relations = pickRelations(values); | ||||
|       const data = omitExernalValues(values); | ||||
| 
 | ||||
|       const runUpdate = async trx => { | ||||
|         const entry = await model | ||||
| @ -386,11 +377,7 @@ module.exports = ({ model, modelKey }) => { | ||||
|       const filters = strapi.utils.models.convertParams(modelKey, params); | ||||
| 
 | ||||
|       // Select field to populate.
 | ||||
|       const withRelated = | ||||
|         populate || | ||||
|         model.associations | ||||
|           .filter(ast => ast.autoPopulate !== false) | ||||
|           .map(ast => ast.alias); | ||||
|       const withRelated = populate || defaultPopulate; | ||||
| 
 | ||||
|       return model | ||||
|         .query(qb => { | ||||
|  | ||||
| @ -2,12 +2,162 @@ const _ = require('lodash'); | ||||
| const { convertRestQueryParams, buildQuery } = require('strapi-utils'); | ||||
| 
 | ||||
| module.exports = ({ model, modelKey, strapi }) => { | ||||
|   const assocs = model.associations.map(ast => ast.alias); | ||||
|   const hasPK = obj => _.has(obj, model.primaryKey) || _.has(obj, 'id'); | ||||
|   const getPK = obj => | ||||
|     _.has(obj, model.primaryKey) ? obj[model.primaryKey] : obj.id; | ||||
| 
 | ||||
|   const assocKeys = model.associations.map(ast => ast.alias); | ||||
|   const groupKeys = Object.keys(model.attributes).filter(key => { | ||||
|     return model.attributes[key].type === 'group'; | ||||
|   }); | ||||
|   const excludedKeys = assocKeys.concat(groupKeys); | ||||
| 
 | ||||
|   const defaultPopulate = model.associations | ||||
|     .filter(ast => ast.autoPopulate !== false) | ||||
|     .map(ast => ast.alias); | ||||
| 
 | ||||
|   const pickRelations = values => { | ||||
|     return _.pick(values, assocKeys); | ||||
|   }; | ||||
| 
 | ||||
|   const omitExernalValues = values => { | ||||
|     return _.omit(values, excludedKeys); | ||||
|   }; | ||||
| 
 | ||||
|   async function createGroups(entry, values) { | ||||
|     if (groupKeys.length === 0) return; | ||||
| 
 | ||||
|     for (let key of groupKeys) { | ||||
|       const attr = model.attributes[key]; | ||||
|       const { group, required = true, repeatable = true } = attr; | ||||
| 
 | ||||
|       const groupModel = strapi.groups[group]; | ||||
| 
 | ||||
|       if (required === true && !_.has(values, key)) { | ||||
|         const err = new Error(`Group ${key} is required`); | ||||
|         err.status = 400; | ||||
|         throw err; | ||||
|       } | ||||
| 
 | ||||
|       if (!_.has(values, key)) continue; | ||||
| 
 | ||||
|       const groupValue = values[key]; | ||||
| 
 | ||||
|       if (repeatable === true) { | ||||
|         validateRepeatableInput(groupValue, { key, ...attr }); | ||||
|         const groups = await Promise.all( | ||||
|           groupValue.map(value => groupModel.create(value)) | ||||
|         ); | ||||
| 
 | ||||
|         const groupsArr = groups.map(group => ({ | ||||
|           kind: groupModel.globalId, | ||||
|           ref: group, | ||||
|         })); | ||||
| 
 | ||||
|         entry[key] = groupsArr; | ||||
|         await entry.save(); | ||||
|       } else { | ||||
|         validateNonRepeatableInput(groupValue, { key, ...attr }); | ||||
|         const group = await groupModel.create(groupValue); | ||||
|         entry[key] = [ | ||||
|           { | ||||
|             kind: groupModel.globalId, | ||||
|             ref: group, | ||||
|           }, | ||||
|         ]; | ||||
|         await entry.save(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async function updateGroups(entry, values) { | ||||
|     if (groupKeys.length === 0) return; | ||||
| 
 | ||||
|     for (let key of groupKeys) { | ||||
|       // if key isn't present then don't change the current group data
 | ||||
|       if (!_.has(values, key)) continue; | ||||
| 
 | ||||
|       const attr = model.attributes[key]; | ||||
|       const { group, repeatable = true } = attr; | ||||
| 
 | ||||
|       const groupModel = strapi.groups[group]; | ||||
|       const groupValue = values[key]; | ||||
| 
 | ||||
|       const updateOCreateGroup = async value => { | ||||
|         // check if value has an id then update else create
 | ||||
|         if (hasPK(value)) { | ||||
|           return groupModel.findOneAndUpdate( | ||||
|             { | ||||
|               [model.primaryKey]: getPK(value), | ||||
|             }, | ||||
|             value, | ||||
|             { new: true } | ||||
|           ); | ||||
|         } | ||||
|         return groupModel.create(value); | ||||
|       }; | ||||
| 
 | ||||
|       if (repeatable === true) { | ||||
|         validateRepeatableInput(groupValue, { key, ...attr }); | ||||
| 
 | ||||
|         await deleteOldGroups(entry, groupValue, { key, groupModel }); | ||||
| 
 | ||||
|         const groups = await Promise.all(groupValue.map(updateOCreateGroup)); | ||||
|         const groupsArr = groups.map(group => ({ | ||||
|           kind: groupModel.globalId, | ||||
|           ref: group, | ||||
|         })); | ||||
| 
 | ||||
|         entry[key] = groupsArr; | ||||
|         await entry.save(); | ||||
|       } else { | ||||
|         validateNonRepeatableInput(groupValue, { key, ...attr }); | ||||
| 
 | ||||
|         await deleteOldGroups(entry, groupValue, { key, groupModel }); | ||||
| 
 | ||||
|         const group = await updateOCreateGroup(groupValue); | ||||
|         entry[key] = [ | ||||
|           { | ||||
|             kind: groupModel.globalId, | ||||
|             ref: group, | ||||
|           }, | ||||
|         ]; | ||||
|         await entry.save(); | ||||
|       } | ||||
|     } | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   async function deleteOldGroups(entry, groupValue, { key, groupModel }) { | ||||
|     const groupArr = Array.isArray(groupValue) ? groupValue : [groupValue]; | ||||
| 
 | ||||
|     const idsToKeep = groupArr.filter(hasPK).map(getPK); | ||||
|     const allIds = await (entry[key] || []) | ||||
|       .filter(el => el.ref) | ||||
|       .map(el => el.ref._id); | ||||
| 
 | ||||
|     // verify the provided ids are realted to this entity.
 | ||||
|     idsToKeep.forEach(id => { | ||||
|       if (allIds.findIndex(currentId => currentId.toString() === id) === -1) { | ||||
|         const err = new Error( | ||||
|           `Some of the provided groups in ${key} are not related to the entity` | ||||
|         ); | ||||
|         err.status = 400; | ||||
|         throw err; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     const idsToDelete = allIds.reduce((acc, id) => { | ||||
|       if (idsToKeep.includes(id.toString())) return acc; | ||||
|       return acc.concat(id); | ||||
|     }, []); | ||||
| 
 | ||||
|     if (idsToDelete.length > 0) { | ||||
|       await groupModel.deleteMany({ [model.primaryKey]: { $in: idsToDelete } }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // public api
 | ||||
|   return { | ||||
|     find(params, populate) { | ||||
|       const populateOpt = populate || defaultPopulate; | ||||
| @ -25,9 +175,7 @@ module.exports = ({ model, modelKey, strapi }) => { | ||||
|       const populateOpt = populate || defaultPopulate; | ||||
| 
 | ||||
|       return model | ||||
|         .findOne({ | ||||
|           [model.primaryKey]: params[model.primaryKey] || params.id, | ||||
|         }) | ||||
|         .findOne({ [model.primaryKey]: getPK(params) }) | ||||
|         .populate(populateOpt); | ||||
|     }, | ||||
| 
 | ||||
| @ -42,23 +190,34 @@ module.exports = ({ model, modelKey, strapi }) => { | ||||
| 
 | ||||
|     async create(values) { | ||||
|       // Extract values related to relational data.
 | ||||
|       const relations = _.pick(values, assocs); | ||||
|       const data = _.omit(values, assocs); | ||||
|       const relations = pickRelations(values); | ||||
|       const data = omitExernalValues(values); | ||||
| 
 | ||||
|       // Create entry with no-relational data.
 | ||||
|       const entry = await model.create(data); | ||||
| 
 | ||||
|       await createGroups(entry, values); | ||||
| 
 | ||||
|       // Create relational data and return the entry.
 | ||||
|       return model.updateRelations({ _id: entry.id, values: relations }); | ||||
|     }, | ||||
| 
 | ||||
|     async update(params, values) { | ||||
|       const entry = await model.findOne({ _id: params.id }); | ||||
| 
 | ||||
|       if (!entry) { | ||||
|         const err = new Error('entry.notFound'); | ||||
|         err.status = 404; | ||||
|         throw err; | ||||
|       } | ||||
| 
 | ||||
|       // Extract values related to relational data.
 | ||||
|       const relations = _.pick(values, assocs); | ||||
|       const data = _.omit(values, assocs); | ||||
|       const relations = pickRelations(values); | ||||
|       const data = omitExernalValues(values); | ||||
| 
 | ||||
|       // Update entry with no-relational data.
 | ||||
|       await model.updateOne(params, data, { multi: true }); | ||||
|       await entry.updateOne(params, data); | ||||
|       await updateGroups(entry, values); | ||||
| 
 | ||||
|       // Update relational data and return the entry.
 | ||||
|       return model.updateRelations( | ||||
| @ -153,3 +312,36 @@ const buildSearchOr = (model, query) => { | ||||
|     } | ||||
|   }, []); | ||||
| }; | ||||
| 
 | ||||
| function validateRepeatableInput(value, { key, min, max }) { | ||||
|   if (!Array.isArray(value)) { | ||||
|     const err = new Error(`Group ${key} is repetable. Expected an array`); | ||||
|     err.status = 400; | ||||
|     throw err; | ||||
|   } | ||||
| 
 | ||||
|   if (min && value.length < min) { | ||||
|     const err = new Error(`Group ${key} must contain at least ${min} items`); | ||||
|     err.status = 400; | ||||
|     throw err; | ||||
|   } | ||||
|   if (max && value.length > max) { | ||||
|     const err = new Error(`Group ${key} must contain at most ${max} items`); | ||||
|     err.status = 400; | ||||
|     throw err; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function validateNonRepeatableInput(value, { key, required }) { | ||||
|   if (typeof value !== 'object') { | ||||
|     const err = new Error(`Group ${key} should be an object`); | ||||
|     err.status = 400; | ||||
|     throw err; | ||||
|   } | ||||
| 
 | ||||
|   if (required === true && value === null) { | ||||
|     const err = new Error(`Group ${key} is required`); | ||||
|     err.status = 400; | ||||
|     throw err; | ||||
|   } | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Alexandre Bodin
						Alexandre Bodin