directus 9.9.0 → 9.11.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/README.md +1 -1
- package/dist/app.js +3 -0
- package/dist/auth/drivers/oauth2.d.ts +1 -1
- package/dist/auth/drivers/oauth2.js +14 -11
- package/dist/auth/drivers/openid.d.ts +1 -1
- package/dist/auth/drivers/openid.js +14 -11
- package/dist/cli/commands/schema/apply.js +4 -3
- package/dist/controllers/assets.js +8 -9
- package/dist/controllers/files.js +3 -0
- package/dist/database/helpers/date/dialects/sqlite.js +6 -2
- package/dist/database/helpers/fn/dialects/postgres.js +5 -1
- package/dist/database/migrations/20210225A-add-relations-sort-field.js +2 -1
- package/dist/database/migrations/20210506A-rename-interfaces.js +2 -1
- package/dist/database/migrations/20210802A-replace-groups.js +2 -1
- package/dist/database/migrations/20210805A-update-groups.js +2 -1
- package/dist/database/migrations/20210805B-change-image-metadata-structure.js +3 -2
- package/dist/database/migrations/20211007A-update-presets.js +5 -4
- package/dist/database/run-ast.js +11 -15
- package/dist/database/system-data/fields/collections.yaml +1 -1
- package/dist/env.js +8 -3
- package/dist/exceptions/index.d.ts +1 -0
- package/dist/exceptions/index.js +1 -0
- package/dist/exceptions/invalid-provider.d.ts +4 -0
- package/dist/exceptions/invalid-provider.js +10 -0
- package/dist/exceptions/range-not-satisfiable.d.ts +2 -2
- package/dist/exceptions/range-not-satisfiable.js +5 -1
- package/dist/middleware/graphql.js +2 -1
- package/dist/services/assets.js +27 -1
- package/dist/services/authentication.js +4 -1
- package/dist/services/collections.js +2 -0
- package/dist/services/fields.js +17 -8
- package/dist/services/graphql.js +75 -34
- package/dist/services/import-export.d.ts +1 -1
- package/dist/services/import-export.js +14 -11
- package/dist/services/items.d.ts +3 -3
- package/dist/services/items.js +20 -6
- package/dist/services/payload.d.ts +3 -3
- package/dist/services/payload.js +11 -8
- package/dist/services/specifications.js +1 -3
- package/dist/services/users.d.ts +4 -0
- package/dist/services/users.js +24 -1
- package/dist/utils/{apply-query.d.ts → apply-query/index.d.ts} +2 -1
- package/dist/utils/apply-query/index.js +394 -0
- package/dist/utils/apply-query/operators/between.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/between.operator.js +16 -0
- package/dist/utils/apply-query/operators/contains.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/contains.operator.js +9 -0
- package/dist/utils/apply-query/operators/ends-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/ends-with.operator.js +9 -0
- package/dist/utils/apply-query/operators/equals.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/equals.operator.js +9 -0
- package/dist/utils/apply-query/operators/greather-than-equals.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/greather-than-equals.operator.js +9 -0
- package/dist/utils/apply-query/operators/greather-than.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/greather-than.operator.js +9 -0
- package/dist/utils/apply-query/operators/in.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/in.operator.js +14 -0
- package/dist/utils/apply-query/operators/index.d.ts +3 -0
- package/dist/utils/apply-query/operators/index.js +72 -0
- package/dist/utils/apply-query/operators/insensitive-contains.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-contains.operator.js +9 -0
- package/dist/utils/apply-query/operators/insensitive-ends-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-ends-with.operator.js +9 -0
- package/dist/utils/apply-query/operators/insensitive-equals.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-equals.operator.js +9 -0
- package/dist/utils/apply-query/operators/insensitive-not-contains.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-not-contains.operator.js +9 -0
- package/dist/utils/apply-query/operators/insensitive-not-ends-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-not-ends-with.operator.js +9 -0
- package/dist/utils/apply-query/operators/insensitive-not-equals.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-not-equals.operator.js +9 -0
- package/dist/utils/apply-query/operators/insensitive-not-starts-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-not-starts-with.operator.js +9 -0
- package/dist/utils/apply-query/operators/insensitive-starts-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-starts-with.operator.js +9 -0
- package/dist/utils/apply-query/operators/intersects-bbox.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/intersects-bbox.operator.js +9 -0
- package/dist/utils/apply-query/operators/intersects.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/intersects.operator.js +9 -0
- package/dist/utils/apply-query/operators/is-empty.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/is-empty.operator.js +14 -0
- package/dist/utils/apply-query/operators/is-not-empty.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/is-not-empty.operator.js +14 -0
- package/dist/utils/apply-query/operators/is-not-null.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/is-not-null.operator.js +14 -0
- package/dist/utils/apply-query/operators/is-null.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/is-null.operator.js +14 -0
- package/dist/utils/apply-query/operators/less-than-equals.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/less-than-equals.operator.js +9 -0
- package/dist/utils/apply-query/operators/less-than.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/less-than.operator.js +9 -0
- package/dist/utils/apply-query/operators/not-between.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-between.operator.js +16 -0
- package/dist/utils/apply-query/operators/not-contains.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-contains.operator.js +9 -0
- package/dist/utils/apply-query/operators/not-ends-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-ends-with.operator.js +9 -0
- package/dist/utils/apply-query/operators/not-equals.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-equals.operator.js +9 -0
- package/dist/utils/apply-query/operators/not-in.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-in.operator.js +14 -0
- package/dist/utils/apply-query/operators/not-intersects-bbox.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-intersects-bbox.operator.js +9 -0
- package/dist/utils/apply-query/operators/not-intersects.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-intersects.operator.js +9 -0
- package/dist/utils/apply-query/operators/not-starts-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-starts-with.operator.js +9 -0
- package/dist/utils/apply-query/operators/operator-register.d.ts +13 -0
- package/dist/utils/apply-query/operators/operator-register.js +7 -0
- package/dist/utils/apply-query/operators/starts-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/starts-with.operator.js +9 -0
- package/dist/utils/apply-snapshot.d.ts +3 -3
- package/dist/utils/apply-snapshot.js +64 -49
- package/dist/utils/get-ast-from-query.js +10 -4
- package/dist/utils/get-column-path.d.ts +16 -0
- package/dist/utils/get-column-path.js +46 -0
- package/dist/utils/get-default-value.js +4 -3
- package/dist/utils/get-permissions.d.ts +1 -1
- package/dist/utils/get-permissions.js +9 -8
- package/dist/utils/get-relation-info.d.ts +7 -0
- package/dist/utils/get-relation-info.js +45 -0
- package/dist/utils/get-relation-type.d.ts +1 -1
- package/dist/utils/get-schema.js +5 -1
- package/dist/utils/get-snapshot.js +22 -4
- package/dist/utils/merge-permissions-for-share.js +1 -1
- package/dist/utils/parse-json.d.ts +5 -0
- package/dist/utils/parse-json.js +19 -0
- package/dist/utils/reduce-schema.js +18 -11
- package/dist/utils/sanitize-query.d.ts +1 -2
- package/dist/utils/sanitize-query.js +6 -5
- package/package.json +16 -18
- package/dist/utils/apply-query.js +0 -498
|
@@ -1,498 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.applyAggregate = exports.applySearch = exports.applyFilter = void 0;
|
|
7
|
-
const lodash_1 = require("lodash");
|
|
8
|
-
const nanoid_1 = require("nanoid");
|
|
9
|
-
const uuid_validate_1 = __importDefault(require("uuid-validate"));
|
|
10
|
-
const exceptions_1 = require("../exceptions");
|
|
11
|
-
const get_column_1 = require("./get-column");
|
|
12
|
-
const get_relation_type_1 = require("./get-relation-type");
|
|
13
|
-
const helpers_1 = require("../database/helpers");
|
|
14
|
-
const utils_1 = require("@directus/shared/utils");
|
|
15
|
-
const generateAlias = (0, nanoid_1.customAlphabet)('abcdefghijklmnopqrstuvwxyz', 5);
|
|
16
|
-
/**
|
|
17
|
-
* Apply the Query to a given Knex query builder instance
|
|
18
|
-
*/
|
|
19
|
-
function applyQuery(knex, collection, dbQuery, query, schema, subQuery = false) {
|
|
20
|
-
if (query.sort) {
|
|
21
|
-
dbQuery.orderBy(query.sort.map((sortField) => {
|
|
22
|
-
let column = sortField;
|
|
23
|
-
let order = 'asc';
|
|
24
|
-
if (sortField.startsWith('-')) {
|
|
25
|
-
column = column.substring(1);
|
|
26
|
-
order = 'desc';
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
order,
|
|
30
|
-
column: (0, get_column_1.getColumn)(knex, collection, column, false, schema),
|
|
31
|
-
};
|
|
32
|
-
}));
|
|
33
|
-
}
|
|
34
|
-
if (typeof query.limit === 'number' && query.limit !== -1) {
|
|
35
|
-
dbQuery.limit(query.limit);
|
|
36
|
-
}
|
|
37
|
-
if (query.offset) {
|
|
38
|
-
dbQuery.offset(query.offset);
|
|
39
|
-
}
|
|
40
|
-
if (query.page && query.limit && query.limit !== -1) {
|
|
41
|
-
dbQuery.offset(query.limit * (query.page - 1));
|
|
42
|
-
}
|
|
43
|
-
if (query.search) {
|
|
44
|
-
applySearch(schema, dbQuery, query.search, collection);
|
|
45
|
-
}
|
|
46
|
-
if (query.group) {
|
|
47
|
-
dbQuery.groupBy(query.group.map((column) => (0, get_column_1.getColumn)(knex, collection, column, false, schema)));
|
|
48
|
-
}
|
|
49
|
-
if (query.aggregate) {
|
|
50
|
-
applyAggregate(dbQuery, query.aggregate, collection);
|
|
51
|
-
}
|
|
52
|
-
if (query.filter) {
|
|
53
|
-
applyFilter(knex, schema, dbQuery, query.filter, collection, subQuery);
|
|
54
|
-
}
|
|
55
|
-
return dbQuery;
|
|
56
|
-
}
|
|
57
|
-
exports.default = applyQuery;
|
|
58
|
-
function getRelationInfo(relations, collection, field) {
|
|
59
|
-
var _a, _b;
|
|
60
|
-
const implicitRelation = (_a = field.match(/^\$FOLLOW\((.*?),(.*?)(?:,(.*?))?\)$/)) === null || _a === void 0 ? void 0 : _a.slice(1);
|
|
61
|
-
if (implicitRelation) {
|
|
62
|
-
if (implicitRelation[2] === undefined) {
|
|
63
|
-
const [m2oCollection, m2oField] = implicitRelation;
|
|
64
|
-
const relation = {
|
|
65
|
-
collection: m2oCollection,
|
|
66
|
-
field: m2oField,
|
|
67
|
-
related_collection: collection,
|
|
68
|
-
schema: null,
|
|
69
|
-
meta: null,
|
|
70
|
-
};
|
|
71
|
-
return { relation, relationType: 'o2m' };
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
const [a2oCollection, a2oItemField, a2oCollectionField] = implicitRelation;
|
|
75
|
-
const relation = {
|
|
76
|
-
collection: a2oCollection,
|
|
77
|
-
field: a2oItemField,
|
|
78
|
-
related_collection: collection,
|
|
79
|
-
schema: null,
|
|
80
|
-
meta: {
|
|
81
|
-
one_collection_field: a2oCollectionField,
|
|
82
|
-
one_field: field,
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
return { relation, relationType: 'o2a' };
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
const relation = (_b = relations.find((relation) => {
|
|
89
|
-
var _a;
|
|
90
|
-
return ((relation.collection === collection && relation.field === field) ||
|
|
91
|
-
(relation.related_collection === collection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === field));
|
|
92
|
-
})) !== null && _b !== void 0 ? _b : null;
|
|
93
|
-
const relationType = relation ? (0, get_relation_type_1.getRelationType)({ relation, collection, field }) : null;
|
|
94
|
-
return { relation, relationType };
|
|
95
|
-
}
|
|
96
|
-
function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery = false) {
|
|
97
|
-
const helpers = (0, helpers_1.getHelpers)(knex);
|
|
98
|
-
const relations = schema.relations;
|
|
99
|
-
const aliasMap = {};
|
|
100
|
-
addJoins(rootQuery, rootFilter, collection);
|
|
101
|
-
addWhereClauses(knex, rootQuery, rootFilter, collection);
|
|
102
|
-
return rootQuery;
|
|
103
|
-
function addJoins(dbQuery, filter, collection) {
|
|
104
|
-
for (const [key, value] of Object.entries(filter)) {
|
|
105
|
-
if (key === '_or' || key === '_and') {
|
|
106
|
-
// If the _or array contains an empty object (full permissions), we should short-circuit and ignore all other
|
|
107
|
-
// permission checks, as {} already matches full permissions.
|
|
108
|
-
if (key === '_or' && value.some((subFilter) => Object.keys(subFilter).length === 0))
|
|
109
|
-
continue;
|
|
110
|
-
value.forEach((subFilter) => {
|
|
111
|
-
addJoins(dbQuery, subFilter, collection);
|
|
112
|
-
});
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
const filterPath = getFilterPath(key, value);
|
|
116
|
-
if (filterPath.length > 1) {
|
|
117
|
-
addJoin(filterPath, collection);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
function addJoin(path, collection) {
|
|
121
|
-
path = (0, lodash_1.clone)(path);
|
|
122
|
-
followRelation(path);
|
|
123
|
-
function followRelation(pathParts, parentCollection = collection, parentAlias) {
|
|
124
|
-
/**
|
|
125
|
-
* For A2M fields, the path can contain an optional collection scope <field>:<scope>
|
|
126
|
-
*/
|
|
127
|
-
const pathRoot = pathParts[0].split(':')[0];
|
|
128
|
-
const { relation, relationType } = getRelationInfo(relations, parentCollection, pathRoot);
|
|
129
|
-
if (!relation) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const alias = generateAlias();
|
|
133
|
-
(0, lodash_1.set)(aliasMap, parentAlias ? [parentAlias, ...pathParts] : pathParts, alias);
|
|
134
|
-
if (relationType === 'm2o') {
|
|
135
|
-
dbQuery.leftJoin({ [alias]: relation.related_collection }, `${parentAlias || parentCollection}.${relation.field}`, `${alias}.${schema.collections[relation.related_collection].primary}`);
|
|
136
|
-
}
|
|
137
|
-
if (relationType === 'a2o') {
|
|
138
|
-
const pathScope = pathParts[0].split(':')[1];
|
|
139
|
-
if (!pathScope) {
|
|
140
|
-
throw new exceptions_1.InvalidQueryException(`You have to provide a collection scope when filtering on a many-to-any item`);
|
|
141
|
-
}
|
|
142
|
-
dbQuery.leftJoin({ [alias]: pathScope }, (joinClause) => {
|
|
143
|
-
joinClause
|
|
144
|
-
.onVal(relation.meta.one_collection_field, '=', pathScope)
|
|
145
|
-
.andOn(`${parentAlias || parentCollection}.${relation.field}`, '=', knex.raw(`CAST(?? AS CHAR(255))`, `${alias}.${schema.collections[pathScope].primary}`));
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
if (relationType === 'o2a') {
|
|
149
|
-
dbQuery.leftJoin({ [alias]: relation.collection }, (joinClause) => {
|
|
150
|
-
joinClause
|
|
151
|
-
.onVal(relation.meta.one_collection_field, '=', parentCollection)
|
|
152
|
-
.andOn(`${alias}.${relation.field}`, '=', knex.raw(`CAST(?? AS CHAR(255))`, `${parentAlias || parentCollection}.${schema.collections[parentCollection].primary}`));
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
// Still join o2m relations when in subquery OR when the o2m relation is not at the root level
|
|
156
|
-
if (relationType === 'o2m' && (subQuery === true || parentAlias !== undefined)) {
|
|
157
|
-
dbQuery.leftJoin({ [alias]: relation.collection }, `${parentAlias || parentCollection}.${schema.collections[relation.related_collection].primary}`, `${alias}.${relation.field}`);
|
|
158
|
-
}
|
|
159
|
-
if (relationType === 'm2o' || subQuery === true || (relationType === 'o2m' && parentAlias !== undefined)) {
|
|
160
|
-
let parent;
|
|
161
|
-
if (relationType === 'm2o') {
|
|
162
|
-
parent = relation.related_collection;
|
|
163
|
-
}
|
|
164
|
-
else if (relationType === 'a2o') {
|
|
165
|
-
const pathScope = pathParts[0].split(':')[1];
|
|
166
|
-
if (!pathScope) {
|
|
167
|
-
throw new exceptions_1.InvalidQueryException(`You have to provide a collection scope when filtering on a many-to-any item`);
|
|
168
|
-
}
|
|
169
|
-
parent = pathScope;
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
parent = relation.collection;
|
|
173
|
-
}
|
|
174
|
-
pathParts.shift();
|
|
175
|
-
if (pathParts.length) {
|
|
176
|
-
followRelation(pathParts, parent, alias);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
function addWhereClauses(knex, dbQuery, filter, collection, logical = 'and') {
|
|
183
|
-
var _a, _b;
|
|
184
|
-
for (const [key, value] of Object.entries(filter)) {
|
|
185
|
-
if (key === '_or' || key === '_and') {
|
|
186
|
-
// If the _or array contains an empty object (full permissions), we should short-circuit and ignore all other
|
|
187
|
-
// permission checks, as {} already matches full permissions.
|
|
188
|
-
if (key === '_or' && value.some((subFilter) => Object.keys(subFilter).length === 0)) {
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
/** @NOTE this callback function isn't called until Knex runs the query */
|
|
192
|
-
dbQuery[logical].where((subQuery) => {
|
|
193
|
-
value.forEach((subFilter) => {
|
|
194
|
-
addWhereClauses(knex, subQuery, subFilter, collection, key === '_and' ? 'and' : 'or');
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
const filterPath = getFilterPath(key, value);
|
|
200
|
-
/**
|
|
201
|
-
* For A2M fields, the path can contain an optional collection scope <field>:<scope>
|
|
202
|
-
*/
|
|
203
|
-
const pathRoot = filterPath[0].split(':')[0];
|
|
204
|
-
const { relation, relationType } = getRelationInfo(relations, collection, pathRoot);
|
|
205
|
-
const { operator: filterOperator, value: filterValue } = getOperation(key, value);
|
|
206
|
-
if (relationType === 'm2o' || relationType === 'a2o' || relationType === null) {
|
|
207
|
-
if (filterPath.length > 1) {
|
|
208
|
-
const columnName = getWhereColumn(filterPath, collection);
|
|
209
|
-
if (!columnName)
|
|
210
|
-
continue;
|
|
211
|
-
applyFilterToQuery(columnName, filterOperator, filterValue, logical);
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
applyFilterToQuery(`${collection}.${filterPath[0]}`, filterOperator, filterValue, logical);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
else if (subQuery === false || filterPath.length > 1) {
|
|
218
|
-
if (!relation)
|
|
219
|
-
continue;
|
|
220
|
-
let pkField = `${collection}.${schema.collections[relation.related_collection].primary}`;
|
|
221
|
-
if (relationType === 'o2a') {
|
|
222
|
-
pkField = knex.raw(`CAST(?? AS CHAR(255))`, [pkField]);
|
|
223
|
-
}
|
|
224
|
-
const subQueryBuilder = (filter) => (subQueryKnex) => {
|
|
225
|
-
const field = relation.field;
|
|
226
|
-
const collection = relation.collection;
|
|
227
|
-
const column = `${collection}.${field}`;
|
|
228
|
-
subQueryKnex
|
|
229
|
-
.select({ [field]: column })
|
|
230
|
-
.from(collection)
|
|
231
|
-
.whereNotNull(field);
|
|
232
|
-
applyQuery(knex, relation.collection, subQueryKnex, { filter }, schema, true);
|
|
233
|
-
};
|
|
234
|
-
if (((_a = Object.keys(value)) === null || _a === void 0 ? void 0 : _a[0]) === '_none') {
|
|
235
|
-
dbQuery[logical].whereNotIn(pkField, subQueryBuilder(Object.values(value)[0]));
|
|
236
|
-
}
|
|
237
|
-
else if (((_b = Object.keys(value)) === null || _b === void 0 ? void 0 : _b[0]) === '_some') {
|
|
238
|
-
dbQuery[logical].whereIn(pkField, subQueryBuilder(Object.values(value)[0]));
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
dbQuery[logical].whereIn(pkField, subQueryBuilder(value));
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
function applyFilterToQuery(key, operator, compareValue, logical = 'and') {
|
|
246
|
-
const [table, column] = key.split('.');
|
|
247
|
-
// Is processed through Knex.Raw, so should be safe to string-inject into these where queries
|
|
248
|
-
const selectionRaw = (0, get_column_1.getColumn)(knex, table, column, false, schema);
|
|
249
|
-
// Knex supports "raw" in the columnName parameter, but isn't typed as such. Too bad..
|
|
250
|
-
// See https://github.com/knex/knex/issues/4518 @TODO remove as any once knex is updated
|
|
251
|
-
// These operators don't rely on a value, and can thus be used without one (eg `?filter[field][_null]`)
|
|
252
|
-
if (operator === '_null' || (operator === '_nnull' && compareValue === false)) {
|
|
253
|
-
dbQuery[logical].whereNull(selectionRaw);
|
|
254
|
-
}
|
|
255
|
-
if (operator === '_nnull' || (operator === '_null' && compareValue === false)) {
|
|
256
|
-
dbQuery[logical].whereNotNull(selectionRaw);
|
|
257
|
-
}
|
|
258
|
-
if (operator === '_empty' || (operator === '_nempty' && compareValue === false)) {
|
|
259
|
-
dbQuery[logical].andWhere((query) => {
|
|
260
|
-
query.where(key, '=', '');
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
if (operator === '_nempty' || (operator === '_empty' && compareValue === false)) {
|
|
264
|
-
dbQuery[logical].andWhere((query) => {
|
|
265
|
-
query.where(key, '!=', '');
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
// Cast filter value (compareValue) based on function used
|
|
269
|
-
if (column.includes('(') && column.includes(')')) {
|
|
270
|
-
const functionName = column.split('(')[0];
|
|
271
|
-
const type = (0, utils_1.getOutputTypeForFunction)(functionName);
|
|
272
|
-
if (['bigInteger', 'integer', 'float', 'decimal'].includes(type)) {
|
|
273
|
-
compareValue = Number(compareValue);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
// Cast filter value (compareValue) based on type of field being filtered against
|
|
277
|
-
const [collection, field] = key.split('.');
|
|
278
|
-
if (collection in schema.collections && field in schema.collections[collection].fields) {
|
|
279
|
-
const type = schema.collections[collection].fields[field].type;
|
|
280
|
-
if (['date', 'dateTime', 'time', 'timestamp'].includes(type)) {
|
|
281
|
-
if (Array.isArray(compareValue)) {
|
|
282
|
-
compareValue = compareValue.map((val) => helpers.date.parse(val));
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
compareValue = helpers.date.parse(compareValue);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (['bigInteger', 'integer', 'float', 'decimal'].includes(type)) {
|
|
289
|
-
if (Array.isArray(compareValue)) {
|
|
290
|
-
compareValue = compareValue.map((val) => Number(val));
|
|
291
|
-
}
|
|
292
|
-
else {
|
|
293
|
-
compareValue = Number(compareValue);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
// The following fields however, require a value to be run. If no value is passed, we
|
|
298
|
-
// ignore them. This allows easier use in GraphQL, where you wouldn't be able to
|
|
299
|
-
// conditionally build out your filter structure (#4471)
|
|
300
|
-
if (compareValue === undefined)
|
|
301
|
-
return;
|
|
302
|
-
if (Array.isArray(compareValue)) {
|
|
303
|
-
// Tip: when using a `[Type]` type in GraphQL, but don't provide the variable, it'll be
|
|
304
|
-
// reported as [undefined].
|
|
305
|
-
// We need to remove any undefined values, as they are useless
|
|
306
|
-
compareValue = compareValue.filter((val) => val !== undefined);
|
|
307
|
-
}
|
|
308
|
-
if (operator === '_eq') {
|
|
309
|
-
dbQuery[logical].where(selectionRaw, '=', compareValue);
|
|
310
|
-
}
|
|
311
|
-
if (operator === '_neq') {
|
|
312
|
-
dbQuery[logical].whereNot(selectionRaw, compareValue);
|
|
313
|
-
}
|
|
314
|
-
if (operator === '_contains') {
|
|
315
|
-
dbQuery[logical].where(selectionRaw, 'like', `%${compareValue}%`);
|
|
316
|
-
}
|
|
317
|
-
if (operator === '_ncontains') {
|
|
318
|
-
dbQuery[logical].whereNot(selectionRaw, 'like', `%${compareValue}%`);
|
|
319
|
-
}
|
|
320
|
-
if (operator === '_starts_with') {
|
|
321
|
-
dbQuery[logical].where(key, 'like', `${compareValue}%`);
|
|
322
|
-
}
|
|
323
|
-
if (operator === '_nstarts_with') {
|
|
324
|
-
dbQuery[logical].whereNot(key, 'like', `${compareValue}%`);
|
|
325
|
-
}
|
|
326
|
-
if (operator === '_ends_with') {
|
|
327
|
-
dbQuery[logical].where(key, 'like', `%${compareValue}`);
|
|
328
|
-
}
|
|
329
|
-
if (operator === '_nends_with') {
|
|
330
|
-
dbQuery[logical].whereNot(key, 'like', `%${compareValue}`);
|
|
331
|
-
}
|
|
332
|
-
if (operator === '_gt') {
|
|
333
|
-
dbQuery[logical].where(selectionRaw, '>', compareValue);
|
|
334
|
-
}
|
|
335
|
-
if (operator === '_gte') {
|
|
336
|
-
dbQuery[logical].where(selectionRaw, '>=', compareValue);
|
|
337
|
-
}
|
|
338
|
-
if (operator === '_lt') {
|
|
339
|
-
dbQuery[logical].where(selectionRaw, '<', compareValue);
|
|
340
|
-
}
|
|
341
|
-
if (operator === '_lte') {
|
|
342
|
-
dbQuery[logical].where(selectionRaw, '<=', compareValue);
|
|
343
|
-
}
|
|
344
|
-
if (operator === '_in') {
|
|
345
|
-
let value = compareValue;
|
|
346
|
-
if (typeof value === 'string')
|
|
347
|
-
value = value.split(',');
|
|
348
|
-
dbQuery[logical].whereIn(selectionRaw, value);
|
|
349
|
-
}
|
|
350
|
-
if (operator === '_nin') {
|
|
351
|
-
let value = compareValue;
|
|
352
|
-
if (typeof value === 'string')
|
|
353
|
-
value = value.split(',');
|
|
354
|
-
dbQuery[logical].whereNotIn(selectionRaw, value);
|
|
355
|
-
}
|
|
356
|
-
if (operator === '_between') {
|
|
357
|
-
if (compareValue.length !== 2)
|
|
358
|
-
return;
|
|
359
|
-
let value = compareValue;
|
|
360
|
-
if (typeof value === 'string')
|
|
361
|
-
value = value.split(',');
|
|
362
|
-
dbQuery[logical].whereBetween(selectionRaw, value);
|
|
363
|
-
}
|
|
364
|
-
if (operator === '_nbetween') {
|
|
365
|
-
if (compareValue.length !== 2)
|
|
366
|
-
return;
|
|
367
|
-
let value = compareValue;
|
|
368
|
-
if (typeof value === 'string')
|
|
369
|
-
value = value.split(',');
|
|
370
|
-
dbQuery[logical].whereNotBetween(selectionRaw, value);
|
|
371
|
-
}
|
|
372
|
-
if (operator == '_intersects') {
|
|
373
|
-
dbQuery[logical].whereRaw(helpers.st.intersects(key, compareValue));
|
|
374
|
-
}
|
|
375
|
-
if (operator == '_nintersects') {
|
|
376
|
-
dbQuery[logical].whereRaw(helpers.st.nintersects(key, compareValue));
|
|
377
|
-
}
|
|
378
|
-
if (operator == '_intersects_bbox') {
|
|
379
|
-
dbQuery[logical].whereRaw(helpers.st.intersects_bbox(key, compareValue));
|
|
380
|
-
}
|
|
381
|
-
if (operator == '_nintersects_bbox') {
|
|
382
|
-
dbQuery[logical].whereRaw(helpers.st.nintersects_bbox(key, compareValue));
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
function getWhereColumn(path, collection) {
|
|
386
|
-
return followRelation(path);
|
|
387
|
-
function followRelation(pathParts, parentCollection = collection, parentAlias) {
|
|
388
|
-
/**
|
|
389
|
-
* For A2M fields, the path can contain an optional collection scope <field>:<scope>
|
|
390
|
-
*/
|
|
391
|
-
const pathRoot = pathParts[0].split(':')[0];
|
|
392
|
-
const { relation, relationType } = getRelationInfo(relations, parentCollection, pathRoot);
|
|
393
|
-
if (!relation) {
|
|
394
|
-
throw new exceptions_1.InvalidQueryException(`"${parentCollection}.${pathRoot}" is not a relational field`);
|
|
395
|
-
}
|
|
396
|
-
const alias = (0, lodash_1.get)(aliasMap, parentAlias ? [parentAlias, ...pathParts] : pathParts);
|
|
397
|
-
const remainingParts = pathParts.slice(1);
|
|
398
|
-
let parent;
|
|
399
|
-
if (relationType === 'a2o') {
|
|
400
|
-
const pathScope = pathParts[0].split(':')[1];
|
|
401
|
-
if (!pathScope) {
|
|
402
|
-
throw new exceptions_1.InvalidQueryException(`You have to provide a collection scope when filtering on a many-to-any item`);
|
|
403
|
-
}
|
|
404
|
-
parent = pathScope;
|
|
405
|
-
}
|
|
406
|
-
else if (relationType === 'm2o') {
|
|
407
|
-
parent = relation.related_collection;
|
|
408
|
-
}
|
|
409
|
-
else {
|
|
410
|
-
parent = relation.collection;
|
|
411
|
-
}
|
|
412
|
-
if (remainingParts.length === 1) {
|
|
413
|
-
return `${alias || parent}.${remainingParts[0]}`;
|
|
414
|
-
}
|
|
415
|
-
if (remainingParts.length) {
|
|
416
|
-
return followRelation(remainingParts, parent, alias);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
exports.applyFilter = applyFilter;
|
|
423
|
-
async function applySearch(schema, dbQuery, searchQuery, collection) {
|
|
424
|
-
const fields = Object.entries(schema.collections[collection].fields);
|
|
425
|
-
dbQuery.andWhere(function () {
|
|
426
|
-
fields.forEach(([name, field]) => {
|
|
427
|
-
if (['text', 'string'].includes(field.type)) {
|
|
428
|
-
this.orWhereRaw(`LOWER(??) LIKE ?`, [`${collection}.${name}`, `%${searchQuery.toLowerCase()}%`]);
|
|
429
|
-
}
|
|
430
|
-
else if (['bigInteger', 'integer', 'decimal', 'float'].includes(field.type)) {
|
|
431
|
-
const number = Number(searchQuery);
|
|
432
|
-
if (!isNaN(number))
|
|
433
|
-
this.orWhere({ [`${collection}.${name}`]: number });
|
|
434
|
-
}
|
|
435
|
-
else if (field.type === 'uuid' && (0, uuid_validate_1.default)(searchQuery)) {
|
|
436
|
-
this.orWhere({ [`${collection}.${name}`]: searchQuery });
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
exports.applySearch = applySearch;
|
|
442
|
-
function applyAggregate(dbQuery, aggregate, collection) {
|
|
443
|
-
for (const [operation, fields] of Object.entries(aggregate)) {
|
|
444
|
-
if (!fields)
|
|
445
|
-
continue;
|
|
446
|
-
for (const field of fields) {
|
|
447
|
-
if (operation === 'avg') {
|
|
448
|
-
dbQuery.avg(`${collection}.${field}`, { as: `avg->${field}` });
|
|
449
|
-
}
|
|
450
|
-
if (operation === 'avgDistinct') {
|
|
451
|
-
dbQuery.avgDistinct(`${collection}.${field}`, { as: `avgDistinct->${field}` });
|
|
452
|
-
}
|
|
453
|
-
if (operation === 'count') {
|
|
454
|
-
if (field === '*') {
|
|
455
|
-
dbQuery.count('*', { as: 'count' });
|
|
456
|
-
}
|
|
457
|
-
else {
|
|
458
|
-
dbQuery.count(`${collection}.${field}`, { as: `count->${field}` });
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
if (operation === 'countDistinct') {
|
|
462
|
-
dbQuery.countDistinct(`${collection}.${field}`, { as: `countDistinct->${field}` });
|
|
463
|
-
}
|
|
464
|
-
if (operation === 'sum') {
|
|
465
|
-
dbQuery.sum(`${collection}.${field}`, { as: `sum->${field}` });
|
|
466
|
-
}
|
|
467
|
-
if (operation === 'sumDistinct') {
|
|
468
|
-
dbQuery.sumDistinct(`${collection}.${field}`, { as: `sumDistinct->${field}` });
|
|
469
|
-
}
|
|
470
|
-
if (operation === 'min') {
|
|
471
|
-
dbQuery.min(`${collection}.${field}`, { as: `min->${field}` });
|
|
472
|
-
}
|
|
473
|
-
if (operation === 'max') {
|
|
474
|
-
dbQuery.max(`${collection}.${field}`, { as: `max->${field}` });
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
exports.applyAggregate = applyAggregate;
|
|
480
|
-
function getFilterPath(key, value) {
|
|
481
|
-
const path = [key];
|
|
482
|
-
if (typeof Object.keys(value)[0] === 'string' && Object.keys(value)[0].startsWith('_') === true) {
|
|
483
|
-
return path;
|
|
484
|
-
}
|
|
485
|
-
if ((0, lodash_1.isPlainObject)(value)) {
|
|
486
|
-
path.push(...getFilterPath(Object.keys(value)[0], Object.values(value)[0]));
|
|
487
|
-
}
|
|
488
|
-
return path;
|
|
489
|
-
}
|
|
490
|
-
function getOperation(key, value) {
|
|
491
|
-
if (key.startsWith('_') && key !== '_and' && key !== '_or') {
|
|
492
|
-
return { operator: key, value };
|
|
493
|
-
}
|
|
494
|
-
else if ((0, lodash_1.isPlainObject)(value) === false) {
|
|
495
|
-
return { operator: '_eq', value };
|
|
496
|
-
}
|
|
497
|
-
return getOperation(Object.keys(value)[0], Object.values(value)[0]);
|
|
498
|
-
}
|