const _ = require('lodash'); const parseParamsToFindOptions = (params) => { const result = {}; // Query if (params.query) { result.query = params.query } // Where if (params.params) { result.where = params.params } // Paginate if (params.paginate) { result.offset = params.paginate.limit * (params.paginate.page - 1); result.limit = result.offset + params.paginate.limit; } // Order if (params.sort) { result.order = []; Object.keys(params.sort).forEach(key => { let dir = params.sort[key] ? 'ASC' : 'DESC'; result.order.push([key, dir]) }); } // Attributes if (params.fields) { if (params.fields.validFields) { result.attributes = params.fields.validFields } if (params.fields.invalidFields && Array.isArray(params.fields.invalidFields)) { result.attributes = { ...result.attributes, exclude: params.fields.invalidFields } } } return result; } const defaultOptions = {}; const generateService = (model, extraMethods = {}, options = defaultOptions) => { const defaultService = { fetchAll: async (params, context) => { const findOptions = parseParamsToFindOptions(params); return await model.findAndCountAll(findOptions); }, fetchOne: async (params, context) => { return await model.findOne({ where: { 'id': params.id } }); }, count: async (params, context) => { // Convert `params` object to filters compatible with Bookshelf. //const filters = strapi.utils.models.convertParams('post', params); return await model.count(params); }, create: async (values, context) => { return await model.create(values); }, update: async (params, values, context) => { // Extract values related to relational data. const relations = _.pick(values, Post.associations.map(ast => ast.alias)); const data = _.omit(values, Post.associations.map(ast => ast.alias)); // Create entry with no-relational data. const entry = await Post.forge(params).save(data); // Create relational data and return the entry. return Post.updateRelations(Object.assign(params, { values: relations })); }, delete: async (params, context) => { const numAffectedRows = await model.destroy({ where: { 'id': params.id } }); return (numAffectedRows > 0); /*params.values = {}; Post.associations.map(association => { switch (association.nature) { case 'oneWay': case 'oneToOne': case 'manyToOne': case 'oneToManyMorph': params.values[association.alias] = null; break; case 'oneToMany': case 'manyToMany': case 'manyToManyMorph': params.values[association.alias] = []; break; default: } }); await Post.updateRelations(params); return Post.forge(params).destroy();*/ }, search: async (params, context) => { // Convert `params` object to filters compatible with Bookshelf. const filters = strapi.utils.models.convertParams('post', params); // Select field to populate. const populate = Post.associations .filter(ast => ast.autoPopulate !== false) .map(ast => ast.alias); const associations = Post.associations.map(x => x.alias); const searchText = Object.keys(Post._attributes) .filter(attribute => attribute !== Post.primaryKey && !associations.includes(attribute)) .filter(attribute => ['string', 'text'].includes(Post._attributes[attribute].type)); const searchNoText = Object.keys(Post._attributes) .filter(attribute => attribute !== Post.primaryKey && !associations.includes(attribute)) .filter(attribute => !['string', 'text', 'boolean', 'integer', 'decimal', 'float'].includes(Post._attributes[attribute].type)); const searchInt = Object.keys(Post._attributes) .filter(attribute => attribute !== Post.primaryKey && !associations.includes(attribute)) .filter(attribute => ['integer', 'decimal', 'float'].includes(Post._attributes[attribute].type)); const searchBool = Object.keys(Post._attributes) .filter(attribute => attribute !== Post.primaryKey && !associations.includes(attribute)) .filter(attribute => ['boolean'].includes(Post._attributes[attribute].type)); const query = (params._q || '').replace(/[^a-zA-Z0-9.-\s]+/g, ''); return Post.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 (Post.client) { case 'mysql': qb.orWhereRaw(`MATCH(${searchText.join(',')}) AGAINST(? IN BOOLEAN MODE)`, `*${query}*`); break; case 'pg': { const searchQuery = searchText.map(attribute => _.toLower(attribute) === attribute ? `to_tsvector(${attribute})` : `to_tsvector('${attribute}')` ); qb.orWhereRaw(`${searchQuery.join(' || ')} @@ to_tsquery(?)`, 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({ withRelated: populate }); } } return { ...defaultService, //...associationControllers ...extraMethods } } module.exports = { generateService, parseParamsToFindOptions, defaultOptions }