Fix ability to update unique constraints in mongoose (#5612)

* Sync indexes on model mount with mongoose

Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu>

* Use Mongoose.Model.syncIndexes callback arg to subscribe to Model's indexes messages. Get rid off errors-pron-promises.

Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu>
This commit is contained in:
Jean-Sébastien Herbaux 2020-03-27 11:39:53 +01:00 committed by GitHub
parent 9137233bea
commit 4a8de29100
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -15,8 +15,7 @@ const isPolymorphicAssoc = assoc => {
module.exports = ({ models, target }, ctx) => {
const { instance } = ctx;
// Parse every authenticated model.
Object.keys(models).map(model => {
function mountModel(model) {
const definition = models[model];
definition.orm = 'mongoose';
definition.associations = [];
@ -34,20 +33,13 @@ module.exports = ({ models, target }, ctx) => {
const scalarAttributes = Object.keys(definition.attributes).filter(key => {
const { type } = definition.attributes[key];
return (
type !== undefined &&
type !== null &&
type !== 'component' &&
type !== 'dynamiczone'
);
return type !== undefined && type !== null && type !== 'component' && type !== 'dynamiczone';
});
const relationalAttributes = Object.keys(definition.attributes).filter(
key => {
const { type } = definition.attributes[key];
return type === undefined;
}
);
const relationalAttributes = Object.keys(definition.attributes).filter(key => {
const { type } = definition.attributes[key];
return type === undefined;
});
// handle component and dynamic zone attrs
if (componentAttributes.length > 0) {
@ -103,12 +95,7 @@ module.exports = ({ models, target }, ctx) => {
save: 'beforeSave',
};
const findLifecycles = [
'find',
'findOne',
'findOneAndUpdate',
'findOneAndRemove',
];
const findLifecycles = ['find', 'findOne', 'findOneAndUpdate', 'findOneAndRemove'];
/*
Override populate path for polymorphic association.
@ -116,9 +103,7 @@ module.exports = ({ models, target }, ctx) => {
instead of Upload.find().populate('related.item')
*/
const morphAssociations = definition.associations.filter(
isPolymorphicAssoc
);
const morphAssociations = definition.associations.filter(isPolymorphicAssoc);
const populateFn = createOnFetchPopulateFn({
componentAttributes,
@ -189,10 +174,7 @@ module.exports = ({ models, target }, ctx) => {
// Use provided timestamps if the elemnets in the array are string else use default.
const timestampsOption = _.get(definition, 'options.timestamps', true);
if (_.isArray(timestampsOption)) {
const [
createAtCol = 'createdAt',
updatedAtCol = 'updatedAt',
] = timestampsOption;
const [createAtCol = 'createdAt', updatedAtCol = 'updatedAt'] = timestampsOption;
schema.set('timestamps', {
createdAt: createAtCol,
@ -217,16 +199,12 @@ module.exports = ({ models, target }, ctx) => {
type: 'timestamp',
};
}
schema.set(
'minimize',
_.get(definition, 'options.minimize', false) === true
);
schema.set('minimize', _.get(definition, 'options.minimize', false) === true);
const refToStrapiRef = obj => {
const ref = obj.ref;
let plainData =
ref && typeof ref.toJSON === 'function' ? ref.toJSON() : ref;
let plainData = ref && typeof ref.toJSON === 'function' ? ref.toJSON() : ref;
if (typeof plainData !== 'object') return ref;
@ -255,17 +233,15 @@ module.exports = ({ models, target }, ctx) => {
// Reformat data by bypassing the many-to-many relationship.
switch (association.nature) {
case 'oneMorphToOne':
returned[association.alias] = refToStrapiRef(
returned[association.alias][0]
);
returned[association.alias] = refToStrapiRef(returned[association.alias][0]);
break;
case 'manyMorphToMany':
case 'manyMorphToOne':
returned[association.alias] = returned[
association.alias
].map(obj => refToStrapiRef(obj));
returned[association.alias] = returned[association.alias].map(obj =>
refToStrapiRef(obj)
);
break;
default:
}
@ -281,9 +257,7 @@ module.exports = ({ models, target }, ctx) => {
const components = returned[name].map(el => el.ref);
// Reformat data by bypassing the many-to-many relationship.
returned[name] =
attribute.repeatable === true
? components
: _.first(components) || null;
attribute.repeatable === true ? components : _.first(components) || null;
}
}
@ -302,24 +276,21 @@ module.exports = ({ models, target }, ctx) => {
};
// Instantiate model.
const Model = instance.model(
definition.globalId,
schema,
definition.collectionName
);
const Model = instance.model(definition.globalId, schema, definition.collectionName);
Model.on('index', error => {
if (error) {
if (error.code === 11000) {
strapi.log.error(
`Unique constraint fails, make sure to update your data and restart to apply the unique constraint.\n\t- ${error.message}`
);
} else {
strapi.log.error(
`An index error happened, it wasn't applied.\n\t- ${error.message}`
);
// Ensure indexes are synced with the model, prevent duplicate index errors
Model.syncIndexes(null, () => {
Model.on('index', error => {
if (error) {
if (error.code === 11000) {
strapi.log.error(
`Unique constraint fails, make sure to update your data and restart to apply the unique constraint.\n\t- ${error.message}`
);
} else {
strapi.log.error(`An index error happened, it wasn't applied.\n\t- ${error.message}`);
}
}
}
});
});
// Expose ORM functions through the `target` object.
@ -328,14 +299,13 @@ module.exports = ({ models, target }, ctx) => {
// Push attributes to be aware of model schema.
target[model]._attributes = definition.attributes;
target[model].updateRelations = relations.update;
});
}
// Parse every authenticated model.
Object.keys(models).map(mountModel);
};
const createOnFetchPopulateFn = ({
morphAssociations,
componentAttributes,
definition,
}) => {
const createOnFetchPopulateFn = ({ morphAssociations, componentAttributes, definition }) => {
return function() {
const populatedPaths = this.getPopulatedPaths();
@ -378,16 +348,10 @@ const createOnFetchPopulateFn = ({
const buildRelation = ({ definition, model, instance, attribute, name }) => {
const { nature, verbose } =
utilsModels.getNature(attribute, name, undefined, model.toLowerCase()) ||
{};
utilsModels.getNature(attribute, name, undefined, model.toLowerCase()) || {};
// Build associations key
utilsModels.defineAssociations(
model.toLowerCase(),
definition,
attribute,
name
);
utilsModels.defineAssociations(model.toLowerCase(), definition, attribute, name);
switch (verbose) {
case 'hasOne': {