From d44262e94b3fefc436336cda5320f08b736e3f22 Mon Sep 17 00:00:00 2001 From: Jim LAURIE Date: Thu, 5 Jul 2018 10:22:43 +0200 Subject: [PATCH 1/2] API search mongoose --- .../templates/mongoose/controller.template | 6 ++- .../templates/mongoose/service.template | 48 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/packages/strapi-generate-api/templates/mongoose/controller.template b/packages/strapi-generate-api/templates/mongoose/controller.template index 51eb319fb0..c53e7c8fb1 100755 --- a/packages/strapi-generate-api/templates/mongoose/controller.template +++ b/packages/strapi-generate-api/templates/mongoose/controller.template @@ -15,7 +15,11 @@ module.exports = { */ find: async (ctx) => { - return strapi.services.<%= id %>.fetchAll(ctx.query); + if (ctx.query._q) { + return strapi.services.<%= id %>.search(ctx.query); + } else { + return strapi.services.<%= id %>.fetchAll(ctx.query); + } }, /** diff --git a/packages/strapi-generate-api/templates/mongoose/service.template b/packages/strapi-generate-api/templates/mongoose/service.template index 419952dd5d..c7897e3356 100755 --- a/packages/strapi-generate-api/templates/mongoose/service.template +++ b/packages/strapi-generate-api/templates/mongoose/service.template @@ -142,5 +142,53 @@ module.exports = { ); return data; + }, + + /** + * Promise to search a/an <%= id %>. + * + * @return {Promise} + */ + + search: async function (params) { + // Convert `params` object to filters compatible with Mongo. + const filters = strapi.utils.models.convertParams('<%= globalID.toLowerCase() %>', params); + // Select field to populate. + const populate = <%= globalID %>.associations + .filter(ast => ast.autoPopulate !== false) + .map(ast => ast.alias) + .join(' '); + + const $or = Object.keys(<%= globalID %>.attributes).reduce((acc, curr) => { + switch (<%= globalID %>.attributes[curr].type) { + case 'integer': + case 'float': + case 'decimal': + if (!_.isNaN(_.toNumber(params._q))) { + return acc.concat({ [curr]: params._q }); + } + + return acc; + case 'string': + case 'text': + case 'password': + return acc.concat({ [curr]: { $regex: params._q, $options: 'i' } }); + case 'boolean': + if (params._q === 'true' || params._q === 'false') { + return acc.concat({ [curr]: params._q === 'true' }); + } + + return acc; + default: + return acc; + } + }, []); + + return <%= globalID %> + .find({ $or }) + .sort(filters.sort) + .skip(filters.start) + .limit(filters.limit) + .populate(populate); } }; From d09b0b155017413e5215c8d442418136a3c9bd13 Mon Sep 17 00:00:00 2001 From: Jim LAURIE Date: Thu, 5 Jul 2018 11:28:55 +0200 Subject: [PATCH 2/2] API search bookshelf --- .../templates/bookshelf/controller.template | 6 +- .../templates/bookshelf/service.template | 84 +++++++++++++++++++ .../templates/mongoose/service.template | 2 +- .../lib/helpers/template/index.js | 4 +- 4 files changed, 93 insertions(+), 3 deletions(-) diff --git a/packages/strapi-generate-api/templates/bookshelf/controller.template b/packages/strapi-generate-api/templates/bookshelf/controller.template index 60ce25ca15..48ed7c3a4f 100755 --- a/packages/strapi-generate-api/templates/bookshelf/controller.template +++ b/packages/strapi-generate-api/templates/bookshelf/controller.template @@ -15,7 +15,11 @@ module.exports = { */ find: async (ctx) => { - return strapi.services.<%= id %>.fetchAll(ctx.query); + if (ctx.query._q) { + return strapi.services.<%= id %>.search(ctx.query); + } else { + return strapi.services.<%= id %>.fetchAll(ctx.query); + } }, /** diff --git a/packages/strapi-generate-api/templates/bookshelf/service.template b/packages/strapi-generate-api/templates/bookshelf/service.template index 708018b5b5..0685e6362e 100755 --- a/packages/strapi-generate-api/templates/bookshelf/service.template +++ b/packages/strapi-generate-api/templates/bookshelf/service.template @@ -154,5 +154,89 @@ module.exports = { await <%= globalID %>.updateRelations(params); return <%= globalID %>.forge(params).destroy(); + }, + + /** + * Promise to search a/an <%= id %>. + * + * @return {Promise} + */ + + search: async (params) => { + // Convert `params` object to filters compatible with Bookshelf. + const filters = strapi.utils.models.convertParams('<%= globalID.toLowerCase() %>', params); + // Select field to populate. + const populate = <%= globalID %>.associations + .filter(ast => ast.autoPopulate !== false) + .map(ast => ast.alias); + + const associations = <%= globalID %>.associations.map(x => x.alias); + const searchText = Object.keys(<%= globalID %>._attributes) + .filter(attribute => attribute !== <%= globalID %>.primaryKey && !associations.includes(attribute)) + .filter(attribute => ['string', 'text'].includes(<%= globalID %>._attributes[attribute].type)); + + const searchNoText = Object.keys(<%= globalID %>._attributes) + .filter(attribute => attribute !== <%= globalID %>.primaryKey && !associations.includes(attribute)) + .filter(attribute => !['string', 'text', 'boolean', 'integer', 'decimal', 'float'].includes(<%= globalID %>._attributes[attribute].type)); + + const searchInt = Object.keys(<%= globalID %>._attributes) + .filter(attribute => attribute !== <%= globalID %>.primaryKey && !associations.includes(attribute)) + .filter(attribute => ['integer', 'decimal', 'float'].includes(<%= globalID %>._attributes[attribute].type)); + + const searchBool = Object.keys(<%= globalID %>._attributes) + .filter(attribute => attribute !== <%= globalID %>.primaryKey && !associations.includes(attribute)) + .filter(attribute => ['boolean'].includes(<%= globalID %>._attributes[attribute].type)); + + const query = (params._q || '').replace(/[^a-zA-Z0-9.-\s]+/g, ''); + + return <%= globalID %>.query(qb => { + // Search in columns which are not text value. + searchNoText.forEach(attribute => { + qb.orWhereRaw(`LOWER(${attribute}) LIKE '%${_.toLower(query)}%'`); + }); + + if (!_.isNaN(_.toNumber(query))) { + searchInt.forEach(attribute => { + qb.orWhereRaw(`${attribute} = ${_.toNumber(query)}`); + }); + } + + if (query === 'true' || query === 'false') { + searchBool.forEach(attribute => { + qb.orWhereRaw(`${attribute} = ${_.toNumber(query === 'true')}`); + }); + } + + // Search in columns with text using index. + switch (<%= globalID %>.client) { + case 'pg': { + const searchQuery = searchText.map(attribute => + _.toLower(attribute) === attribute + ? `to_tsvector(${attribute})` + : `to_tsvector('${attribute}')` + ); + + qb.orWhereRaw(`${searchQuery.join(' || ')} @@ to_tsquery(?)`, query); + break; + } + default: + qb.orWhereRaw(`MATCH(${searchText.join(',')}) AGAINST(? IN BOOLEAN MODE)`, `*${query}*`); + break; + } + + if (filters.sort) { + qb.orderBy(filters.sort.key, filters.sort.order); + } + + if (filters.skip) { + qb.offset(_.toNumber(filters.skip)); + } + + if (filters.limit) { + qb.limit(_.toNumber(filters.limit)); + } + }).fetchAll({ + width: populate + }); } }; diff --git a/packages/strapi-generate-api/templates/mongoose/service.template b/packages/strapi-generate-api/templates/mongoose/service.template index c7897e3356..324fdedd6c 100755 --- a/packages/strapi-generate-api/templates/mongoose/service.template +++ b/packages/strapi-generate-api/templates/mongoose/service.template @@ -150,7 +150,7 @@ module.exports = { * @return {Promise} */ - search: async function (params) { + search: async (params) => { // Convert `params` object to filters compatible with Mongo. const filters = strapi.utils.models.convertParams('<%= globalID.toLowerCase() %>', params); // Select field to populate. diff --git a/packages/strapi-generate/lib/helpers/template/index.js b/packages/strapi-generate/lib/helpers/template/index.js index 7ef0cef672..4ad8e6f2ed 100755 --- a/packages/strapi-generate/lib/helpers/template/index.js +++ b/packages/strapi-generate/lib/helpers/template/index.js @@ -48,7 +48,9 @@ module.exports = function (options, cb) { } try { - const compiled = _.template(contents); + const compiled = _.template(contents, { + interpolate: /<%=([\s\S]+?)%>/g + }); contents = compiled(options); // With Lodash templates, HTML entities are escaped by default.