/* eslint-disable no-underscore-dangle */ /* eslint-disable no-param-reassign */ /* eslint-disable no-use-before-define */ export default function buildQueryPlugin(schema) { function apiQuery(rawParams, locale, cb) { const model = this; const params = paramParser(this, rawParams, locale); if (cb) { model .find(params.searchParams) .exec(cb); } return params.searchParams; } schema.statics.apiQuery = apiQuery; } export function paramParser(model, rawParams, locale) { let query = { searchParams: {}, sort: false, }; // Construct searchParams Object.keys(rawParams).forEach((key) => { const separatedParams = rawParams[key] .match(/{\w+}(.[^{}]*)/g); if (separatedParams === null) { query = parseParam(key, rawParams[key], model, query, locale); } else { separatedParams.forEach((param) => { query = parseParam(key, param, model, query, locale); }); } }); return query; } function convertToBoolean(str) { return str.toLowerCase() === 'true' || str.toLowerCase() === 't' || str.toLowerCase() === 'yes' || str.toLowerCase() === 'y' || str === '1'; } function addSearchParam(query, key, value) { if (typeof query.searchParams[key] !== 'undefined') { value.forEach((i) => { query.searchParams[key][i] = value[i]; }); } else { query.searchParams[key] = value; } return query; } function parseParam(key, val, model, query, locale) { const lcKey = key; let operator = val.match(/\{(.*)\}/); val = val.replace(/\{(.*)\}/, ''); if (operator) [, operator] = operator; if (val === '') { return {}; } if (lcKey === 'sort_by' || lcKey === 'order_by') { const parts = val.split(','); query.sort = {}; query.sort[parts[0]] = parts[1] === 'asc' || parts.length <= 1 ? 1 : parts[1]; } else if (lcKey === 'include') { if (val.match(',')) { const orArray = []; val.split(',').map(id => orArray.push({ _id: id })); query = addSearchParam(query, '$or', orArray); } else query.searchParams._id = val; } else if (lcKey === 'exclude') { if (val.match(',')) { const andArray = []; val.split(',').map(id => andArray.push({ _id: { $ne: id } })); query = addSearchParam(query, '$and', andArray); } else query.searchParams._id = { $ne: val }; } else if (lcKey === 'locale') { // Do nothing } else if (lcKey === 'depth') { query.maxDepth = val; } else { query = parseSchemaForKey(model.schema, query, '', lcKey, val, operator, locale); } return query; } function parseSchemaForKey(schema, query, keyPrefix, lcKey, val, operator, locale) { let paramType; let key = keyPrefix + lcKey; const matches = lcKey.match(/(.+)\.(.+)/); if (matches) { // Parse SubSchema if (schema.paths[matches[1]].constructor.name === 'DocumentArray' || schema.paths[matches[1]].constructor.name === 'Mixed') { parseSchemaForKey(schema.paths[matches[1]].schema, `${matches[1]}.`, matches[2], val, operator); } else if (schema.paths[matches[1]].constructor.name === 'SchemaType' || schema.paths[matches[1]].constructor.name === 'SingleNestedPath') { // This wasn't handled in the original package but seems to work paramType = schema.paths[matches[1]].schema.paths.name.instance; } } else if (schema.obj[lcKey] && typeof schema === 'object') { if (schema.obj[lcKey].localized) { key = `${key}.${locale}`; } paramType = schema.obj[lcKey].name || schema.obj[lcKey].type.name; } else if (typeof schema === 'undefined') { paramType = 'String'; } else if (typeof schema.paths[lcKey] === 'undefined') { // nada, not found } else if (schema.paths[lcKey].constructor.name === 'SchemaBoolean') { paramType = 'Boolean'; } else if (schema.paths[lcKey].constructor.name === 'SchemaString') { paramType = 'String'; } else if (schema.paths[lcKey].constructor.name === 'SchemaNumber') { paramType = 'Number'; } else if (schema.paths[lcKey].constructor.name === 'ObjectId') { paramType = 'ObjectId'; } else if (schema.paths[lcKey].constructor.name === 'SchemaArray') { paramType = 'Array'; } if (paramType === 'Boolean') { query = addSearchParam(query, key, convertToBoolean(val)); } else if (paramType === 'Number') { if (val.match(/([0-9]+,?)/) && val.match(',')) { if (operator === 'all') { query = addSearchParam(query, key, { $all: val.split(',') }); } else if (operator === 'nin') { query = addSearchParam(query, key, { $nin: val.split(',') }); } else if (operator === 'mod') { query = addSearchParam(query, key, { $mod: [val.split(',')[0], val.split(',')[1]] }); } else { query = addSearchParam(query, key, { $in: val.split(',') }); } } else if (val.match(/([0-9]+)/)) { if (operator === 'gt' || operator === 'gte' || operator === 'lt' || operator === 'lte' || operator === 'ne') { const newParam = {}; newParam[`$${operator}`] = val; query = addSearchParam(query, key, newParam); } else { query = addSearchParam(query, key, parseInt(val, 0)); } } } else if (paramType === 'String') { if (val.match(',')) { const options = val.split(',').map(str => new RegExp(str, 'i')); if (operator === 'all') { query = addSearchParam(query, key, { $all: options }); } else if (operator === 'nin') { query = addSearchParam(query, key, { $nin: options }); } else { query = addSearchParam(query, key, { $in: options }); } } else if (val.match(/([0-9]+)/)) { if (operator === 'gt' || operator === 'gte' || operator === 'lt' || operator === 'lte') { const newParam = {}; newParam[`$${operator}`] = val; query = addSearchParam(query, key, newParam); } else { query = addSearchParam(query, key, val); } } else if (operator === 'ne' || operator === 'not') { const neregex = new RegExp(val, 'i'); query = addSearchParam(query, key, { $not: neregex }); } else if (operator === 'like') { query = addSearchParam(query, key, { $regex: val, $options: '-i' }); } else { query = addSearchParam(query, key, val); } } else if (paramType === 'ObjectId') { query = addSearchParam(query, key, val); } else if (paramType === 'Array') { query = addSearchParam(query, key, val); } return query; }