/* This middleware is to be used in the controller beforeAction hook. You can search text, int and multi-column. Example use in a controller: query = ['search']; beforeAction = [ search(MyModel, { searchText: ['description'], searchMultiCol: { 'fullName': ['firstName', 'suffix', 'lastName'] } }) ]; Example query: /my-model?search[fullName]=query&search[description]=word Where: - MyModel is a reference to the model which is to be searched - options contains an object with one or more of searchText, searchInt and searchMultiCol + searchText is an array with the column names to be searched. String search is substring based and case insensitive. + searchInt is an array with the column names to be searched. Integer search is meant for numbers and is left to right. + searchMultiCol is an object with as the key a name for the multicol search parameter and as a value an array of the columns to be concatted and then searched in the same manner as normal text search. FilterIds is a small function which intersects the found id's with the id's already present in the filter array (if any). */ import snakeCase from 'lodash.snakecase'; import filterIds from 'app/utils/filter-ids'; export default function(model, options){ const { searchText = [], searchInt = [], searchMultiCol = {}, forActions = [] } = options; return async (request, response) => { const { action, method, params: { search } } = request; if(method === 'GET' && (action === 'index' || forActions.includes(action)) && search){ const keys = Object.keys(search); let _searchText = new Map(); searchText.forEach((key) => { if(keys.includes(key)){ _searchText.set(snakeCase(key), search[key]); } }); let _searchInt = new Map(); searchInt.forEach((key) => { if(keys.includes(key)){ _searchInt.set(snakeCase(key), search[key]); } }); let _searchMultiCol = new Map(); Object.keys(searchMultiCol).forEach((key) => { if(keys.includes(key)){ _searchMultiCol.set(key, search[key]); } }); if(_searchText.size || _searchInt.size || _searchMultiCol.size){ let queryArr = []; let queryValues = []; _searchText.forEach((value, key) => { queryArr.push(`${key} ILIKE ?`); queryValues.push(`%${value}%`); }); _searchInt.forEach((value, key) => { queryArr.push(`CAST(${key} AS TEXT) LIKE ?`); queryValues.push(`${value}%`); }); _searchMultiCol.forEach((value, key) => { let cols = searchMultiCol[key].map((value) => `coalesce(${snakeCase(value)}, '')`); queryArr.push(cols.join(` || `) + ' ILIKE ?'); // remove whitespaces from search string queryValues.push(`%${value.replace(/\s/g, '')}%`); }); let queryString = queryArr.join(' AND '); const filterIdsArr = await model.table() .whereRaw(queryString, queryValues) .reduce((idsRes, row) => { idsRes.push(row.id); return idsRes; }, []); request.params.filter = filterIds(request, filterIdsArr); } } } }