@strapi/utils 4.0.0-next.6 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/build-query.js +1 -1
- package/lib/config.js +10 -6
- package/lib/content-types.js +23 -116
- package/lib/convert-query-params.js +252 -0
- package/lib/errors.js +82 -0
- package/lib/format-yup-error.js +20 -0
- package/lib/hooks.js +5 -3
- package/lib/index.js +18 -14
- package/lib/pagination.js +88 -0
- package/lib/parse-multipart.js +7 -3
- package/lib/pipe-async.js +11 -0
- package/lib/policy.js +75 -119
- package/lib/print-value.js +51 -0
- package/lib/sanitize/index.js +51 -0
- package/lib/sanitize/sanitizers.js +26 -0
- package/lib/sanitize/visitors/allowed-fields.js +92 -0
- package/lib/sanitize/visitors/index.js +9 -0
- package/lib/sanitize/visitors/remove-password.js +7 -0
- package/lib/sanitize/visitors/remove-private.js +11 -0
- package/lib/sanitize/visitors/remove-restricted-relations.js +67 -0
- package/lib/sanitize/visitors/restricted-fields.js +31 -0
- package/lib/string-formatting.js +4 -0
- package/lib/traverse-entity.js +100 -0
- package/lib/validators.js +70 -16
- package/package.json +21 -24
- package/lib/convert-rest-query-params.js +0 -243
- package/lib/finder.js +0 -32
- package/lib/sanitize-entity.js +0 -172
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isArray } = require('lodash/fp');
|
|
4
|
+
|
|
5
|
+
module.exports = (restrictedFields = null) => ({ key, path }, { remove }) => {
|
|
6
|
+
// Remove all fields
|
|
7
|
+
if (restrictedFields === null) {
|
|
8
|
+
remove(key);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Ignore invalid formats
|
|
13
|
+
if (!isArray(restrictedFields)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Remove if an exact match was found
|
|
18
|
+
if (restrictedFields.includes(path)) {
|
|
19
|
+
remove(key);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Remove nested matches
|
|
24
|
+
const isRestrictedNested = restrictedFields.some(allowedPath =>
|
|
25
|
+
path.startsWith(`${allowedPath}.`)
|
|
26
|
+
);
|
|
27
|
+
if (isRestrictedNested) {
|
|
28
|
+
remove(key);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
};
|
package/lib/string-formatting.js
CHANGED
|
@@ -33,6 +33,8 @@ const escapeQuery = (query, charsToEscape, escapeChar = '\\') => {
|
|
|
33
33
|
|
|
34
34
|
const stringIncludes = (arr, val) => arr.map(String).includes(String(val));
|
|
35
35
|
const stringEquals = (a, b) => String(a) === String(b);
|
|
36
|
+
const isCamelCase = value => /^[a-z][a-zA-Z0-9]+$/.test(value);
|
|
37
|
+
const isKebabCase = value => /^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/.test(value);
|
|
36
38
|
|
|
37
39
|
module.exports = {
|
|
38
40
|
nameToSlug,
|
|
@@ -42,4 +44,6 @@ module.exports = {
|
|
|
42
44
|
escapeQuery,
|
|
43
45
|
stringIncludes,
|
|
44
46
|
stringEquals,
|
|
47
|
+
isCamelCase,
|
|
48
|
+
isKebabCase,
|
|
45
49
|
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { cloneDeep, isObject, isArray, isNil, curry } = require('lodash/fp');
|
|
4
|
+
|
|
5
|
+
const traverseEntity = async (visitor, options, entity) => {
|
|
6
|
+
const { path = null, schema } = options;
|
|
7
|
+
|
|
8
|
+
// End recursion
|
|
9
|
+
if (!isObject(entity) || isNil(schema)) {
|
|
10
|
+
return entity;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Don't mutate the original entity object
|
|
14
|
+
const copy = cloneDeep(entity);
|
|
15
|
+
|
|
16
|
+
for (const key of Object.keys(copy)) {
|
|
17
|
+
// Retrieve the attribute definition associated to the key from the schema
|
|
18
|
+
const attribute = schema.attributes[key];
|
|
19
|
+
|
|
20
|
+
// If the attribute doesn't exist within the schema, ignore it
|
|
21
|
+
if (isNil(attribute)) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const newPath = path ? `${path}.${key}` : key;
|
|
26
|
+
|
|
27
|
+
// Visit the current attribute
|
|
28
|
+
const visitorOptions = { data: copy, schema, key, value: copy[key], attribute, path: newPath };
|
|
29
|
+
const visitorUtils = createVisitorUtils({ data: copy });
|
|
30
|
+
|
|
31
|
+
await visitor(visitorOptions, visitorUtils);
|
|
32
|
+
|
|
33
|
+
// Extract the value for the current key (after calling the visitor)
|
|
34
|
+
const value = copy[key];
|
|
35
|
+
|
|
36
|
+
// Ignore Nil values
|
|
37
|
+
if (isNil(value)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const isRelation = attribute.type === 'relation';
|
|
42
|
+
const isComponent = attribute.type === 'component';
|
|
43
|
+
const isDynamicZone = attribute.type === 'dynamiczone';
|
|
44
|
+
|
|
45
|
+
if (isRelation) {
|
|
46
|
+
const isMorphRelation = attribute.relation.toLowerCase().startsWith('morph');
|
|
47
|
+
|
|
48
|
+
const traverseTarget = entry => {
|
|
49
|
+
// Handle polymorphic relationships
|
|
50
|
+
const targetSchemaUID = isMorphRelation ? entry.__type : attribute.target;
|
|
51
|
+
const targetSchema = strapi.getModel(targetSchemaUID);
|
|
52
|
+
|
|
53
|
+
const traverseOptions = { schema: targetSchema, path: newPath };
|
|
54
|
+
|
|
55
|
+
return traverseEntity(visitor, traverseOptions, entry);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// need to update copy
|
|
59
|
+
copy[key] = isArray(value)
|
|
60
|
+
? await Promise.all(value.map(traverseTarget))
|
|
61
|
+
: await traverseTarget(value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (isComponent) {
|
|
65
|
+
const targetSchema = strapi.getModel(attribute.component);
|
|
66
|
+
const traverseOptions = { schema: targetSchema, path: newPath };
|
|
67
|
+
|
|
68
|
+
const traverseComponent = entry => traverseEntity(visitor, traverseOptions, entry);
|
|
69
|
+
|
|
70
|
+
copy[key] = isArray(value)
|
|
71
|
+
? await Promise.all(value.map(traverseComponent))
|
|
72
|
+
: await traverseComponent(value);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (isDynamicZone && isArray(value)) {
|
|
76
|
+
const visitDynamicZoneEntry = entry => {
|
|
77
|
+
const targetSchema = strapi.getModel(entry.__component);
|
|
78
|
+
const traverseOptions = { schema: targetSchema, path: newPath };
|
|
79
|
+
|
|
80
|
+
return traverseEntity(visitor, traverseOptions, entry);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
copy[key] = await Promise.all(value.map(visitDynamicZoneEntry));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return copy;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const createVisitorUtils = ({ data }) => ({
|
|
91
|
+
remove(key) {
|
|
92
|
+
delete data[key];
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
set(key, value) {
|
|
96
|
+
data[key] = value;
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
module.exports = curry(traverseEntity);
|
package/lib/validators.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
const yup = require('yup');
|
|
4
4
|
const _ = require('lodash');
|
|
5
|
+
const { defaults } = require('lodash/fp');
|
|
6
|
+
const utils = require('./string-formatting');
|
|
7
|
+
const { YupValidationError } = require('./errors');
|
|
8
|
+
const printValue = require('./print-value');
|
|
5
9
|
|
|
6
10
|
const MixedSchemaType = yup.mixed;
|
|
7
11
|
|
|
@@ -16,8 +20,32 @@ function isNotNull(msg = '${path} cannot be null.') {
|
|
|
16
20
|
return this.test('defined', msg, isNotNullTest);
|
|
17
21
|
}
|
|
18
22
|
|
|
23
|
+
function isFunction(message = '${path} is not a function') {
|
|
24
|
+
return this.test('is a function', message, value => _.isUndefined(value) || _.isFunction(value));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isCamelCase(message = '${path} is not in camel case (anExampleOfCamelCase)') {
|
|
28
|
+
return this.test('is in camelCase', message, value => utils.isCamelCase(value));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isKebabCase(message = '${path} is not in kebab case (an-example-of-kebab-case)') {
|
|
32
|
+
return this.test('is in kebab-case', message, value => utils.isKebabCase(value));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function onlyContainsFunctions(message = '${path} contains values that are not functions') {
|
|
36
|
+
return this.test(
|
|
37
|
+
'only contains functions',
|
|
38
|
+
message,
|
|
39
|
+
value => _.isUndefined(value) || (value && Object.values(value).every(_.isFunction))
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
19
43
|
yup.addMethod(yup.mixed, 'notNil', isNotNill);
|
|
20
44
|
yup.addMethod(yup.mixed, 'notNull', isNotNull);
|
|
45
|
+
yup.addMethod(yup.mixed, 'isFunction', isFunction);
|
|
46
|
+
yup.addMethod(yup.string, 'isCamelCase', isCamelCase);
|
|
47
|
+
yup.addMethod(yup.string, 'isKebabCase', isKebabCase);
|
|
48
|
+
yup.addMethod(yup.object, 'onlyContainsFunctions', onlyContainsFunctions);
|
|
21
49
|
|
|
22
50
|
class StrapiIDSchema extends MixedSchemaType {
|
|
23
51
|
constructor() {
|
|
@@ -31,27 +59,53 @@ class StrapiIDSchema extends MixedSchemaType {
|
|
|
31
59
|
|
|
32
60
|
yup.strapiID = () => new StrapiIDSchema();
|
|
33
61
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*/
|
|
38
|
-
const formatYupErrors = validationError => {
|
|
39
|
-
if (!validationError.inner) {
|
|
40
|
-
throw new Error('invalid.input');
|
|
41
|
-
}
|
|
62
|
+
const handleYupError = (error, errorMessage) => {
|
|
63
|
+
throw new YupValidationError(error, errorMessage);
|
|
64
|
+
};
|
|
42
65
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
66
|
+
const defaultValidationParam = { strict: true, abortEarly: false };
|
|
67
|
+
|
|
68
|
+
const validateYupSchema = (schema, options = {}) => async (body, errorMessage) => {
|
|
69
|
+
try {
|
|
70
|
+
const optionsWithDefaults = defaults(defaultValidationParam, options);
|
|
71
|
+
return await schema.validate(body, optionsWithDefaults);
|
|
72
|
+
} catch (e) {
|
|
73
|
+
handleYupError(e, errorMessage);
|
|
46
74
|
}
|
|
75
|
+
};
|
|
47
76
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
77
|
+
const validateYupSchemaSync = (schema, options = {}) => (body, errorMessage) => {
|
|
78
|
+
try {
|
|
79
|
+
const optionsWithDefaults = defaults(defaultValidationParam, options);
|
|
80
|
+
return schema.validateSync(body, optionsWithDefaults);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
handleYupError(e, errorMessage);
|
|
83
|
+
}
|
|
52
84
|
};
|
|
53
85
|
|
|
86
|
+
// Temporary fix of this issue : https://github.com/jquense/yup/issues/616
|
|
87
|
+
yup.setLocale({
|
|
88
|
+
mixed: {
|
|
89
|
+
notType({ path, type, value, originalValue }) {
|
|
90
|
+
let isCast = originalValue != null && originalValue !== value;
|
|
91
|
+
let msg =
|
|
92
|
+
`${path} must be a \`${type}\` type, ` +
|
|
93
|
+
`but the final value was: \`${printValue(value, true)}\`` +
|
|
94
|
+
(isCast ? ` (cast from the value \`${printValue(originalValue, true)}\`).` : '.');
|
|
95
|
+
|
|
96
|
+
/* Remove comment that is not supposed to be seen by the enduser
|
|
97
|
+
if (value === null) {
|
|
98
|
+
msg += `\n If "null" is intended as an empty value be sure to mark the schema as \`.nullable()\``;
|
|
99
|
+
}
|
|
100
|
+
*/
|
|
101
|
+
return msg;
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
54
106
|
module.exports = {
|
|
55
107
|
yup,
|
|
56
|
-
|
|
108
|
+
handleYupError,
|
|
109
|
+
validateYupSchema,
|
|
110
|
+
validateYupSchemaSync,
|
|
57
111
|
};
|
package/package.json
CHANGED
|
@@ -1,49 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/utils",
|
|
3
|
-
"version": "4.0.0
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Shared utilities for the Strapi packages",
|
|
5
|
-
"homepage": "https://strapi.io",
|
|
6
5
|
"keywords": [
|
|
7
6
|
"strapi",
|
|
8
|
-
"
|
|
9
|
-
"utils",
|
|
10
|
-
"winston"
|
|
7
|
+
"utils"
|
|
11
8
|
],
|
|
12
|
-
"
|
|
13
|
-
|
|
9
|
+
"homepage": "https://strapi.io",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/strapi/strapi/issues"
|
|
14
12
|
},
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
"date-fns": "^2.19.0",
|
|
19
|
-
"lodash": "4.17.21",
|
|
20
|
-
"pino": "^4.7.1",
|
|
21
|
-
"pluralize": "^8.0.0",
|
|
22
|
-
"yup": "^0.32.9"
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git://github.com/strapi/strapi.git"
|
|
23
16
|
},
|
|
17
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
24
18
|
"author": {
|
|
19
|
+
"name": "Strapi Solutions SAS",
|
|
25
20
|
"email": "hi@strapi.io",
|
|
26
|
-
"name": "Strapi team",
|
|
27
21
|
"url": "https://strapi.io"
|
|
28
22
|
},
|
|
29
23
|
"maintainers": [
|
|
30
24
|
{
|
|
31
|
-
"name": "Strapi
|
|
25
|
+
"name": "Strapi Solutions SAS",
|
|
32
26
|
"email": "hi@strapi.io",
|
|
33
27
|
"url": "https://strapi.io"
|
|
34
28
|
}
|
|
35
29
|
],
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
"
|
|
30
|
+
"main": "./lib",
|
|
31
|
+
"directories": {
|
|
32
|
+
"lib": "./lib"
|
|
39
33
|
},
|
|
40
|
-
"
|
|
41
|
-
"
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@sindresorhus/slugify": "1.1.0",
|
|
36
|
+
"date-fns": "2.24.0",
|
|
37
|
+
"http-errors": "1.8.0",
|
|
38
|
+
"lodash": "4.17.21",
|
|
39
|
+
"yup": "0.32.9"
|
|
42
40
|
},
|
|
43
41
|
"engines": {
|
|
44
42
|
"node": ">=12.x.x <=16.x.x",
|
|
45
43
|
"npm": ">=6.0.0"
|
|
46
44
|
},
|
|
47
|
-
"
|
|
48
|
-
"gitHead": "f528a865cb9c4234f2cda4f0df5b40e7c427ff9a"
|
|
45
|
+
"gitHead": "b181702f0202b2c6d645d42b195a831f25cd0b03"
|
|
49
46
|
}
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Converts the standard Strapi REST query params to a moe usable format for querying
|
|
5
|
-
* You can read more here: https://strapi.io/documentation/developer-docs/latest/developer-resources/content-api/content-api.html#filters
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const _ = require('lodash');
|
|
9
|
-
const {
|
|
10
|
-
constants: { DP_PUB_STATES },
|
|
11
|
-
} = require('./content-types');
|
|
12
|
-
|
|
13
|
-
const BOOLEAN_OPERATORS = ['or', 'and'];
|
|
14
|
-
const QUERY_OPERATORS = ['_where', '_or', '_and'];
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Global converter
|
|
18
|
-
* @param {Object} params
|
|
19
|
-
* @param defaults
|
|
20
|
-
*/
|
|
21
|
-
const convertRestQueryParams = (params = {}, defaults = {}) => {
|
|
22
|
-
if (typeof params !== 'object' || params === null) {
|
|
23
|
-
throw new Error(
|
|
24
|
-
`convertRestQueryParams expected an object got ${params === null ? 'null' : typeof params}`
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
let finalParams = Object.assign({ start: 0, limit: 100 }, defaults);
|
|
29
|
-
|
|
30
|
-
if (Object.keys(params).length === 0) {
|
|
31
|
-
return finalParams;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (_.has(params, '_sort')) {
|
|
35
|
-
finalParams.sort = convertSortQueryParams(params._sort);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (_.has(params, '_start')) {
|
|
39
|
-
finalParams.start = convertStartQueryParams(params._start);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (_.has(params, '_limit')) {
|
|
43
|
-
finalParams.limit = convertLimitQueryParams(params._limit);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (_.has(params, '_publicationState')) {
|
|
47
|
-
Object.assign(finalParams, convertPublicationStateParams(params._publicationState));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const whereParams = convertExtraRootParams(
|
|
51
|
-
_.omit(params, ['_sort', '_start', '_limit', '_where', '_publicationState'])
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
const whereClauses = [];
|
|
55
|
-
|
|
56
|
-
if (_.keys(whereParams).length > 0) {
|
|
57
|
-
whereClauses.push(...convertWhereParams(whereParams));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (_.has(params, '_where')) {
|
|
61
|
-
whereClauses.push(...convertWhereParams(params._where));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
Object.assign(finalParams, { where: whereClauses });
|
|
65
|
-
|
|
66
|
-
return finalParams;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Convert params prefixed with _ by removing the prefix after we have handle the internal params
|
|
71
|
-
* NOTE: This is only a temporary patch for v3 to handle extra params coming from plugins
|
|
72
|
-
* @param {object} params
|
|
73
|
-
* @returns {object}
|
|
74
|
-
*/
|
|
75
|
-
const convertExtraRootParams = params => {
|
|
76
|
-
return Object.entries(params).reduce((acc, [key, value]) => {
|
|
77
|
-
if (_.startsWith(key, '_') && !QUERY_OPERATORS.includes(key)) {
|
|
78
|
-
acc[key.slice(1)] = value;
|
|
79
|
-
} else {
|
|
80
|
-
acc[key] = value;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return acc;
|
|
84
|
-
}, {});
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Sort query parser
|
|
89
|
-
* @param {string} sortQuery - ex: id:asc,price:desc
|
|
90
|
-
*/
|
|
91
|
-
const convertSortQueryParams = sortQuery => {
|
|
92
|
-
if (typeof sortQuery !== 'string') {
|
|
93
|
-
throw new Error(`convertSortQueryParams expected a string, got ${typeof sortQuery}`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// TODO: handle array input
|
|
97
|
-
|
|
98
|
-
const sortKeys = [];
|
|
99
|
-
|
|
100
|
-
sortQuery.split(',').forEach(part => {
|
|
101
|
-
// split field and order param with default order to ascending
|
|
102
|
-
const [field, order = 'asc'] = part.split(':');
|
|
103
|
-
|
|
104
|
-
if (field.length === 0) {
|
|
105
|
-
throw new Error('Field cannot be empty');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (!['asc', 'desc'].includes(order.toLocaleLowerCase())) {
|
|
109
|
-
throw new Error('order can only be one of asc|desc|ASC|DESC');
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
sortKeys.push(_.set({}, field, order.toLowerCase()));
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
return sortKeys;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Start query parser
|
|
120
|
-
* @param {string} startQuery - ex: id:asc,price:desc
|
|
121
|
-
*/
|
|
122
|
-
const convertStartQueryParams = startQuery => {
|
|
123
|
-
const startAsANumber = _.toNumber(startQuery);
|
|
124
|
-
|
|
125
|
-
if (!_.isInteger(startAsANumber) || startAsANumber < 0) {
|
|
126
|
-
throw new Error(`convertStartQueryParams expected a positive integer got ${startAsANumber}`);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return startAsANumber;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Limit query parser
|
|
134
|
-
* @param {string} limitQuery - ex: id:asc,price:desc
|
|
135
|
-
*/
|
|
136
|
-
const convertLimitQueryParams = limitQuery => {
|
|
137
|
-
const limitAsANumber = _.toNumber(limitQuery);
|
|
138
|
-
|
|
139
|
-
if (!_.isInteger(limitAsANumber) || (limitAsANumber !== -1 && limitAsANumber < 0)) {
|
|
140
|
-
throw new Error(`convertLimitQueryParams expected a positive integer got ${limitAsANumber}`);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return limitAsANumber;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* publicationState query parser
|
|
148
|
-
* @param {string} publicationState - eg: 'live', 'preview'
|
|
149
|
-
*/
|
|
150
|
-
const convertPublicationStateParams = publicationState => {
|
|
151
|
-
if (!DP_PUB_STATES.includes(publicationState)) {
|
|
152
|
-
throw new Error(
|
|
153
|
-
`convertPublicationStateParams expected a value from: ${DP_PUB_STATES.join(
|
|
154
|
-
', '
|
|
155
|
-
)}. Got ${publicationState} instead`
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return { publicationState };
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
// List of all the possible filters
|
|
163
|
-
const VALID_REST_OPERATORS = [
|
|
164
|
-
'eq',
|
|
165
|
-
'ne',
|
|
166
|
-
'in',
|
|
167
|
-
'nin',
|
|
168
|
-
'contains',
|
|
169
|
-
'ncontains',
|
|
170
|
-
'containss',
|
|
171
|
-
'ncontainss',
|
|
172
|
-
'lt',
|
|
173
|
-
'lte',
|
|
174
|
-
'gt',
|
|
175
|
-
'gte',
|
|
176
|
-
'null',
|
|
177
|
-
];
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Parse where params
|
|
181
|
-
*/
|
|
182
|
-
const convertWhereParams = whereParams => {
|
|
183
|
-
let finalWhere = [];
|
|
184
|
-
|
|
185
|
-
if (Array.isArray(whereParams)) {
|
|
186
|
-
return whereParams.reduce((acc, whereParam) => {
|
|
187
|
-
return acc.concat(convertWhereParams(whereParam));
|
|
188
|
-
}, []);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
Object.keys(whereParams).forEach(whereClause => {
|
|
192
|
-
const { field, operator = 'eq', value } = convertWhereClause(
|
|
193
|
-
whereClause,
|
|
194
|
-
whereParams[whereClause]
|
|
195
|
-
);
|
|
196
|
-
|
|
197
|
-
finalWhere.push({
|
|
198
|
-
field,
|
|
199
|
-
operator,
|
|
200
|
-
value,
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
return finalWhere;
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Parse single where param
|
|
209
|
-
* @param {string} whereClause - Any possible where clause e.g: id_ne text_ncontains
|
|
210
|
-
* @param {string} value - the value of the where clause e.g id_ne=value
|
|
211
|
-
*/
|
|
212
|
-
const convertWhereClause = (whereClause, value) => {
|
|
213
|
-
const separatorIndex = whereClause.lastIndexOf('_');
|
|
214
|
-
|
|
215
|
-
// eq operator
|
|
216
|
-
if (separatorIndex === -1) {
|
|
217
|
-
return { field: whereClause, value };
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// split field and operator
|
|
221
|
-
const field = whereClause.substring(0, separatorIndex);
|
|
222
|
-
const operator = whereClause.slice(separatorIndex + 1);
|
|
223
|
-
|
|
224
|
-
if (BOOLEAN_OPERATORS.includes(operator) && field === '') {
|
|
225
|
-
return { field: null, operator, value: [].concat(value).map(convertWhereParams) };
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// the field as underscores
|
|
229
|
-
if (!VALID_REST_OPERATORS.includes(operator)) {
|
|
230
|
-
return { field: whereClause, value };
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return { field, operator, value };
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
module.exports = {
|
|
237
|
-
convertRestQueryParams,
|
|
238
|
-
convertSortQueryParams,
|
|
239
|
-
convertStartQueryParams,
|
|
240
|
-
convertLimitQueryParams,
|
|
241
|
-
VALID_REST_OPERATORS,
|
|
242
|
-
QUERY_OPERATORS,
|
|
243
|
-
};
|
package/lib/finder.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Module dependencies
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const _ = require('lodash');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Find controller's location
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
module.exports = (api, controller) => {
|
|
14
|
-
if (!_.isObject(api)) {
|
|
15
|
-
throw new Error('Should be an object');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (_.isObject(controller) && _.has(controller, 'identity')) {
|
|
19
|
-
controller = controller.identity.toLowerCase();
|
|
20
|
-
} else if (_.isString(controller)) {
|
|
21
|
-
controller = controller.toLowerCase();
|
|
22
|
-
} else {
|
|
23
|
-
throw new Error('Should be an object or a string');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const where = _.findKey(api, o => {
|
|
27
|
-
return _.get(o, `controllers.${controller}`);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// Return the API's name where the controller is located
|
|
31
|
-
return where;
|
|
32
|
-
};
|