From 09dcea27f1092c20f4f4edc87df09375ce973769 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Tue, 16 Jul 2019 21:24:09 +0200 Subject: [PATCH] refactor mongoose queries to match api --- packages/strapi-hook-mongoose/lib/queries.js | 295 ++++++++++-------- .../config/queries/mongoose.js | 170 ++++++++++ .../config/queries/mongoose.js | 122 ++++++++ 3 files changed, 452 insertions(+), 135 deletions(-) create mode 100644 packages/strapi-plugin-content-manager/config/queries/mongoose.js create mode 100644 packages/strapi-plugin-users-permissions/config/queries/mongoose.js diff --git a/packages/strapi-hook-mongoose/lib/queries.js b/packages/strapi-hook-mongoose/lib/queries.js index f506b4db2c..b6977b2fce 100644 --- a/packages/strapi-hook-mongoose/lib/queries.js +++ b/packages/strapi-hook-mongoose/lib/queries.js @@ -183,142 +183,167 @@ module.exports = ({ model, modelKey, strapi }) => { } // public api + + function find(params, populate) { + const populateOpt = populate || defaultPopulate; + + const filters = convertRestQueryParams(params); + + return buildQuery({ + model, + filters, + populate: populateOpt, + }); + } + + function findOne(params, populate) { + const primaryKey = getPK(params); + + if (primaryKey) { + params = { + [model.primaryKey]: primaryKey, + }; + } + + return model + .findOne(params) + .populate(populate || defaultPopulate) + .lean(); + } + + function count(params) { + const filters = convertRestQueryParams(params); + + return buildQuery({ + model, + filters: { where: filters.where }, + }).count(); + } + + async function create(values) { + // Extract values related to relational data. + 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({ + [model.primaryKey]: getPK(entry), + values: relations, + }); + } + + async function update(params, values) { + const primaryKey = getPK(params); + + if (primaryKey) { + params = { + [model.primaryKey]: primaryKey, + }; + } + + const entry = await model.findOne(params); + + if (!entry) { + const err = new Error('entry.notFound'); + err.status = 404; + throw err; + } + + // Extract values related to relational data. + const relations = pickRelations(values); + const data = omitExernalValues(values); + + // Update entry with no-relational data. + await entry.updateOne(data); + await updateGroups(entry, values); + + // Update relational data and return the entry. + return model.updateRelations(Object.assign(params, { values: relations })); + } + + async function deleteMany(params) {} + + async function deleteOne(params) { + const entry = await model + .findOneAndRemove({ [model.primaryKey]: getPK(params) }) + .populate(defaultPopulate); + + if (!entry) { + const err = new Error('entry.notFound'); + err.status = 404; + throw err; + } + + await deleteGroups(entry); + + await Promise.all( + model.associations.map(async association => { + if ( + !association.via || + !entry[model.primaryKey] || + association.dominant + ) { + return true; + } + + const search = + _.endsWith(association.nature, 'One') || + association.nature === 'oneToMany' + ? { [association.via]: entry[model.primaryKey] } + : { [association.via]: { $in: [entry[model.primaryKey]] } }; + const update = + _.endsWith(association.nature, 'One') || + association.nature === 'oneToMany' + ? { [association.via]: null } + : { $pull: { [association.via]: entry[model.primaryKey] } }; + + // Retrieve model. + const assocModel = association.plugin + ? strapi.plugins[association.plugin].models[ + association.model || association.collection + ] + : strapi.models[association.model || association.collection]; + + return assocModel.update(search, update, { multi: true }); + }) + ); + + return entry; + } + + function search(params, populate) { + // Convert `params` object to filters compatible with Mongo. + const filters = modelUtils.convertParams(modelKey, params); + + const $or = buildSearchOr(model, params._q); + + return model + .find({ $or }) + .sort(filters.sort) + .skip(filters.start) + .limit(filters.limit) + .populate(populate || defaultPopulate); + } + + function countSearch(params) { + const $or = buildSearchOr(model, params._q); + return model.find({ $or }).countDocuments(); + } + return { - find(params, populate) { - const populateOpt = populate || defaultPopulate; - - const filters = convertRestQueryParams(params); - - return buildQuery({ - model, - filters, - populate: populateOpt, - }); - }, - - findOne(params, populate) { - const populateOpt = populate || defaultPopulate; - - return model - .findOne({ [model.primaryKey]: getPK(params) }) - .populate(populateOpt); - }, - - count(params) { - const filters = convertRestQueryParams(params); - - return buildQuery({ - model, - filters: { where: filters.where }, - }).count(); - }, - - async create(values) { - // Extract values related to relational data. - 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({ - [model.primaryKey]: getPK(entry), - values: relations, - }); - }, - - async update(params, values) { - const entry = await model.findOne({ [model.primaryKey]: getPK(params) }); - - if (!entry) { - const err = new Error('entry.notFound'); - err.status = 404; - throw err; - } - - // Extract values related to relational data. - const relations = pickRelations(values); - const data = omitExernalValues(values); - - // Update entry with no-relational data. - await entry.updateOne(data); - await updateGroups(entry, values); - - // Update relational data and return the entry. - return model.updateRelations( - Object.assign(params, { values: relations }) - ); - }, - - async delete(params) { - const entry = await model - .findOneAndRemove({ [model.primaryKey]: getPK(params) }) - .populate(defaultPopulate); - - if (!entry) { - const err = new Error('entry.notFound'); - err.status = 404; - throw err; - } - - await deleteGroups(entry); - - await Promise.all( - model.associations.map(async association => { - if ( - !association.via || - !entry[model.primaryKey] || - association.dominant - ) { - return true; - } - - const search = - _.endsWith(association.nature, 'One') || - association.nature === 'oneToMany' - ? { [association.via]: entry[model.primaryKey] } - : { [association.via]: { $in: [entry[model.primaryKey]] } }; - const update = - _.endsWith(association.nature, 'One') || - association.nature === 'oneToMany' - ? { [association.via]: null } - : { $pull: { [association.via]: entry[model.primaryKey] } }; - - // Retrieve model. - const assocModel = association.plugin - ? strapi.plugins[association.plugin].models[ - association.model || association.collection - ] - : strapi.models[association.model || association.collection]; - - return assocModel.update(search, update, { multi: true }); - }) - ); - - return entry; - }, - - search(params, populate) { - // Convert `params` object to filters compatible with Mongo. - const filters = modelUtils.convertParams(modelKey, params); - - const $or = buildSearchOr(model, params._q); - - return model - .find({ $or }) - .sort(filters.sort) - .skip(filters.start) - .limit(filters.limit) - .populate(populate || defaultPopulate); - }, - - countSearch(params) { - const $or = buildSearchOr(model, params._q); - return model.find({ $or }).countDocuments(); - }, + findOne, + find, + create, + update, + delete: deleteMany, + count, + search, + countSearch, }; }; diff --git a/packages/strapi-plugin-content-manager/config/queries/mongoose.js b/packages/strapi-plugin-content-manager/config/queries/mongoose.js new file mode 100644 index 0000000000..2497907771 --- /dev/null +++ b/packages/strapi-plugin-content-manager/config/queries/mongoose.js @@ -0,0 +1,170 @@ +const _ = require('lodash'); +const { convertRestQueryParams, buildQuery } = require('strapi-utils'); + +module.exports = ({ model }) => ({ + find(params, populate, raw = false) { + const filters = convertRestQueryParams(params); + + const query = buildQuery({ + model, + filters, + populate: populate || model.associations.map(x => x.alias), + }); + + return raw ? query.lean() : query; + }, + + count(params) { + const filters = convertRestQueryParams(params); + + return buildQuery({ + model, + filters: { where: filters.where }, + }).count(); + }, + + search(params, populate) { + // eslint-disable-line no-unused-vars + const $or = Object.keys(model.attributes).reduce((acc, curr) => { + switch (model.attributes[curr].type) { + case 'integer': + case 'biginteger': + case 'float': + case 'decimal': + if (!_.isNaN(_.toNumber(params.search))) { + return acc.concat({ [curr]: params.search }); + } + + return acc; + case 'string': + case 'text': + case 'password': + return acc.concat({ + [curr]: { $regex: params.search, $options: 'i' }, + }); + case 'boolean': + if (params.search === 'true' || params.search === 'false') { + return acc.concat({ [curr]: params.search === 'true' }); + } + + return acc; + default: + return acc; + } + }, []); + + return model + .find({ $or }) + .limit(Number(params.limit)) + .sort(params.sort) + .skip(Number(params.skip)) + .populate(populate || model.associations.map(x => x.alias).join(' ')) + .lean(); + }, + + countSearch(params = {}) { + // eslint-disable-line no-unused-vars + const $or = Object.keys(model.attributes).reduce((acc, curr) => { + switch (model.attributes[curr].type) { + case 'integer': + case 'biginteger': + case 'float': + case 'decimal': + if (!_.isNaN(_.toNumber(params.search))) { + return acc.concat({ [curr]: params.search }); + } + + return acc; + case 'string': + case 'text': + case 'password': + return acc.concat({ + [curr]: { $regex: params.search, $options: 'i' }, + }); + case 'boolean': + if (params.search === 'true' || params.search === 'false') { + return acc.concat({ [curr]: params.search === 'true' }); + } + + return acc; + default: + return acc; + } + }, []); + + return model.find({ $or }).countDocuments(); + }, + + findOne(params, populate, raw = true) { + const query = model + .findOne({ + [model.primaryKey]: params[model.primaryKey] || params.id, + }) + .populate(populate || model.associations.map(x => x.alias).join(' ')); + + return raw ? query.lean() : query; + }, + + async create(params) { + // Exclude relationships. + const values = Object.keys(params.values).reduce((acc, current) => { + if (model._attributes[current] && model._attributes[current].type) { + acc[current] = params.values[current]; + } + + return acc; + }, {}); + + const request = await model.create(values).catch(err => { + if (err.message) { + const message = err.message.split('index:'); + const field = _.words(_.last(message).split('_')[0]); + err = { message: `This ${field} is already taken`, field }; + } + throw err; + }); + + // Transform to JSON object. + const entry = request.toJSON ? request.toJSON() : request; + + // Extract relations. + const relations = model.associations.reduce((acc, association) => { + if (params.values[association.alias]) { + acc[association.alias] = params.values[association.alias]; + } + + return acc; + }, {}); + + return this.update({ + [model.primaryKey]: entry[model.primaryKey], + values: _.assign( + { + id: entry[model.primaryKey], + }, + relations + ), + }); + }, + + update(params) { + // Call the business logic located in the hook. + // This function updates no-relational and relational data. + return model.updateRelations(params); + }, + + delete(params) { + // Delete entry. + return model.findOneAndDelete({ + [model.primaryKey]: params.id, + }); + }, + + deleteMany(params) { + return model.deleteMany({ + [model.primaryKey]: { + $in: params[model.primaryKey] || params.id, + }, + }); + }, +}); diff --git a/packages/strapi-plugin-users-permissions/config/queries/mongoose.js b/packages/strapi-plugin-users-permissions/config/queries/mongoose.js new file mode 100644 index 0000000000..4eb13cd659 --- /dev/null +++ b/packages/strapi-plugin-users-permissions/config/queries/mongoose.js @@ -0,0 +1,122 @@ +const _ = require('lodash'); +const { convertRestQueryParams, buildQuery } = require('strapi-utils'); + +module.exports = ({ model }) => ({ + find(params, populate) { + const filters = convertRestQueryParams(params); + + return buildQuery({ + model, + filters, + populate: populate || model.associations.map(x => x.alias), + }).lean(); + }, + + count(params) { + const filters = convertRestQueryParams(params); + + return buildQuery({ + model, + filters: { where: filters.where }, + }).count(); + }, + + findOne(params, populate) { + const primaryKey = params[model.primaryKey] || params.id; + + if (primaryKey) { + params = { + [model.primaryKey]: primaryKey, + }; + } + + return model + .findOne(params) + .populate(populate || model.associations.map(x => x.alias).join(' ')) + .lean(); + }, + + create(params) { + return model + .create( + Object.keys(params).reduce((acc, current) => { + if ( + _.get(model._attributes, [current, 'type']) || + _.get(model._attributes, [current, 'model']) + ) { + acc[current] = params[current]; + } + + return acc; + }, {}) + ) + .catch(err => { + if (err.message.indexOf('index:') !== -1) { + const message = err.message.split('index:'); + const field = _.words(_.last(message).split('_')[0]); + const error = { message: `This ${field} is already taken`, field }; + + throw error; + } + + throw err; + }); + }, + + update(search, params = {}) { + if (_.isEmpty(params)) { + params = search; + } + + const primaryKey = search[model.primaryKey] || search.id; + + if (primaryKey) { + search = { + [model.primaryKey]: primaryKey, + }; + } + + return model + .updateOne(search, params, { + strict: false, + }) + .catch(error => { + const field = _.last(_.words(error.message.split('_')[0])); + const err = { message: `This ${field} is already taken`, field }; + + throw err; + }); + }, + + delete(params) { + // Delete entry. + return model.deleteOne({ + [model.primaryKey]: params[model.primaryKey] || params.id, + }); + }, + + deleteMany(params) { + // Delete entry. + return model.deleteMany({ + [model.primaryKey]: { + $in: params[model.primaryKey] || params.id, + }, + }); + }, + + search(params) { + const re = new RegExp(params.id); + + return model.find({ + $or: [{ username: re }, { email: re }], + }); + }, + + addPermission(params) { + return model.create(params); + }, + + removePermission(params) { + return model.remove(params); + }, +});