directus 9.21.2 → 9.22.1
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/dist/app.js +9 -5
- package/dist/app.test.d.ts +1 -0
- package/dist/cli/commands/bootstrap/index.js +2 -2
- package/dist/cli/commands/security/secret.js +2 -2
- package/dist/cli/utils/create-env/env-stub.liquid +3 -3
- package/dist/cli/utils/create-env/index.js +5 -8
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -1
- package/dist/controllers/assets.js +9 -7
- package/dist/controllers/files.js +2 -1
- package/dist/controllers/utils.js +2 -2
- package/dist/database/helpers/fn/dialects/mssql.js +2 -1
- package/dist/database/helpers/fn/dialects/mysql.js +2 -1
- package/dist/database/helpers/fn/dialects/oracle.js +2 -1
- package/dist/database/helpers/fn/dialects/postgres.js +2 -1
- package/dist/database/helpers/fn/dialects/sqlite.js +2 -1
- package/dist/database/helpers/fn/types.d.ts +1 -0
- package/dist/database/helpers/fn/types.js +5 -4
- package/dist/database/helpers/index.d.ts +1 -1
- package/dist/database/helpers/schema/dialects/mssql.d.ts +6 -0
- package/dist/database/helpers/schema/dialects/mssql.js +14 -0
- package/dist/database/helpers/schema/dialects/mysql.d.ts +5 -0
- package/dist/database/helpers/schema/dialects/mysql.js +19 -0
- package/dist/database/helpers/schema/dialects/oracle.d.ts +1 -0
- package/dist/database/helpers/schema/dialects/oracle.js +3 -0
- package/dist/database/helpers/schema/index.d.ts +2 -2
- package/dist/database/helpers/schema/index.js +4 -4
- package/dist/database/helpers/schema/types.d.ts +5 -0
- package/dist/database/helpers/schema/types.js +13 -0
- package/dist/database/index.d.ts +6 -0
- package/dist/database/index.js +20 -1
- package/dist/database/migrations/20211007A-update-presets.js +2 -2
- package/dist/database/migrations/run.js +7 -31
- package/dist/database/run-ast.js +132 -6
- package/dist/database/system-data/fields/index.js +2 -1
- package/dist/env.js +3 -2
- package/dist/exceptions/range-not-satisfiable.d.ts +1 -1
- package/dist/extensions.d.ts +7 -1
- package/dist/extensions.js +41 -15
- package/dist/logger.js +27 -1
- package/dist/operations/request/index.d.ts +1 -2
- package/dist/operations/request/index.js +2 -2
- package/dist/services/assets.d.ts +4 -3
- package/dist/services/assets.js +13 -11
- package/dist/services/authentication.js +4 -3
- package/dist/services/authorization.js +1 -1
- package/dist/services/files.d.ts +4 -3
- package/dist/services/files.js +92 -68
- package/dist/services/flows.d.ts +0 -2
- package/dist/services/flows.js +0 -14
- package/dist/services/flows.test.d.ts +1 -0
- package/dist/services/graphql/index.d.ts +5 -1
- package/dist/services/graphql/index.js +29 -31
- package/dist/services/import-export.d.ts +4 -3
- package/dist/services/items.js +7 -1
- package/dist/services/meta.js +2 -2
- package/dist/services/operations.d.ts +0 -2
- package/dist/services/operations.js +0 -12
- package/dist/services/operations.test.d.ts +1 -0
- package/dist/services/permissions.d.ts +0 -5
- package/dist/services/permissions.js +0 -25
- package/dist/services/permissions.test.d.ts +1 -0
- package/dist/services/roles.js +0 -3
- package/dist/services/roles.test.d.ts +1 -0
- package/dist/services/server.js +8 -6
- package/dist/services/shares.js +2 -2
- package/dist/services/specifications.js +12 -1
- package/dist/services/webhooks.d.ts +0 -2
- package/dist/services/webhooks.js +0 -10
- package/dist/services/webhooks.test.d.ts +1 -0
- package/dist/storage/get-storage-driver.d.ts +3 -0
- package/dist/storage/get-storage-driver.js +20 -0
- package/dist/storage/get-storage-driver.test.d.ts +1 -0
- package/dist/storage/index.d.ts +5 -0
- package/dist/storage/index.js +20 -0
- package/dist/storage/index.test.d.ts +1 -0
- package/dist/storage/register-drivers.d.ts +2 -0
- package/dist/storage/register-drivers.js +22 -0
- package/dist/storage/register-drivers.test.d.ts +1 -0
- package/dist/storage/register-locations.d.ts +2 -0
- package/dist/storage/register-locations.js +17 -0
- package/dist/storage/register-locations.test.d.ts +1 -0
- package/dist/utils/apply-query.d.ts +27 -3
- package/dist/utils/apply-query.js +180 -127
- package/dist/utils/dynamic-import.d.ts +1 -0
- package/dist/utils/dynamic-import.js +7 -0
- package/dist/utils/get-collection-from-alias.d.ts +6 -0
- package/dist/utils/get-collection-from-alias.js +15 -0
- package/dist/utils/get-collection-from-alias.test.d.ts +1 -0
- package/dist/utils/get-column-path.d.ts +14 -8
- package/dist/utils/get-column-path.js +24 -7
- package/dist/utils/get-column.d.ts +8 -1
- package/dist/utils/get-column.js +10 -3
- package/dist/utils/get-config-from-env.js +3 -2
- package/dist/utils/get-default-value.d.ts +1 -1
- package/dist/utils/parse-image-metadata.d.ts +3 -0
- package/dist/utils/parse-image-metadata.js +73 -0
- package/dist/utils/track.js +2 -2
- package/dist/utils/validate-env.js +3 -2
- package/dist/webhooks.js +2 -2
- package/package.json +17 -11
- package/dist/storage.d.ts +0 -3
- package/dist/storage.js +0 -61
|
@@ -3,9 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.applyAggregate = exports.applySearch = exports.applyFilter = exports.applySort = void 0;
|
|
6
|
+
exports.applyAggregate = exports.applySearch = exports.applyFilter = exports.applyOffset = exports.applyLimit = exports.applySort = exports.generateAlias = void 0;
|
|
7
7
|
const lodash_1 = require("lodash");
|
|
8
|
-
const nanoid_1 = require("nanoid");
|
|
9
8
|
const uuid_validate_1 = __importDefault(require("uuid-validate"));
|
|
10
9
|
const helpers_1 = require("../database/helpers");
|
|
11
10
|
const invalid_query_1 = require("../exceptions/invalid-query");
|
|
@@ -14,19 +13,24 @@ const get_column_path_1 = require("./get-column-path");
|
|
|
14
13
|
const get_relation_info_1 = require("./get-relation-info");
|
|
15
14
|
const utils_1 = require("@directus/shared/utils");
|
|
16
15
|
const strip_function_1 = require("./strip-function");
|
|
17
|
-
|
|
16
|
+
// @ts-ignore
|
|
17
|
+
const non_secure_1 = require("nanoid/non-secure");
|
|
18
|
+
exports.generateAlias = (0, non_secure_1.customAlphabet)('abcdefghijklmnopqrstuvwxyz', 5);
|
|
18
19
|
/**
|
|
19
20
|
* Apply the Query to a given Knex query builder instance
|
|
20
21
|
*/
|
|
21
|
-
function applyQuery(knex, collection, dbQuery, query, schema,
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
function applyQuery(knex, collection, dbQuery, query, schema, options) {
|
|
23
|
+
var _a;
|
|
24
|
+
const aliasMap = (_a = options === null || options === void 0 ? void 0 : options.aliasMap) !== null && _a !== void 0 ? _a : Object.create(null);
|
|
25
|
+
let hasMultiRelationalFilter = false;
|
|
26
|
+
if (query.sort && !(options === null || options === void 0 ? void 0 : options.isInnerQuery) && !(options === null || options === void 0 ? void 0 : options.hasMultiRelationalSort)) {
|
|
27
|
+
applySort(knex, schema, dbQuery, query.sort, collection, aliasMap);
|
|
24
28
|
}
|
|
25
|
-
if (
|
|
26
|
-
dbQuery
|
|
29
|
+
if (!(options === null || options === void 0 ? void 0 : options.hasMultiRelationalSort)) {
|
|
30
|
+
applyLimit(dbQuery, query.limit);
|
|
27
31
|
}
|
|
28
32
|
if (query.offset) {
|
|
29
|
-
dbQuery
|
|
33
|
+
applyOffset(knex, dbQuery, query.offset);
|
|
30
34
|
}
|
|
31
35
|
if (query.page && query.limit && query.limit !== -1) {
|
|
32
36
|
dbQuery.offset(query.limit * (query.page - 1));
|
|
@@ -41,15 +45,18 @@ function applyQuery(knex, collection, dbQuery, query, schema, subQuery = false)
|
|
|
41
45
|
applyAggregate(dbQuery, query.aggregate, collection);
|
|
42
46
|
}
|
|
43
47
|
if (query.filter) {
|
|
44
|
-
applyFilter(knex, schema, dbQuery, query.filter, collection,
|
|
48
|
+
hasMultiRelationalFilter = applyFilter(knex, schema, dbQuery, query.filter, collection, aliasMap).hasMultiRelationalFilter;
|
|
45
49
|
}
|
|
46
|
-
return dbQuery;
|
|
50
|
+
return { query: dbQuery, hasMultiRelationalFilter };
|
|
47
51
|
}
|
|
48
52
|
exports.default = applyQuery;
|
|
49
|
-
function addJoin({ path, collection, aliasMap, rootQuery,
|
|
53
|
+
function addJoin({ path, collection, aliasMap, rootQuery, schema, relations, knex }) {
|
|
54
|
+
let hasMultiRelational = false;
|
|
50
55
|
path = (0, lodash_1.clone)(path);
|
|
51
56
|
followRelation(path);
|
|
52
|
-
|
|
57
|
+
return hasMultiRelational;
|
|
58
|
+
function followRelation(pathParts, parentCollection = collection, parentFields) {
|
|
59
|
+
var _a, _b, _c;
|
|
53
60
|
/**
|
|
54
61
|
* For A2M fields, the path can contain an optional collection scope <field>:<scope>
|
|
55
62
|
*/
|
|
@@ -58,104 +65,135 @@ function addJoin({ path, collection, aliasMap, rootQuery, subQuery, schema, rela
|
|
|
58
65
|
if (!relation) {
|
|
59
66
|
return;
|
|
60
67
|
}
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
throw new invalid_query_1.InvalidQueryException(`You have to provide a collection scope when sorting or filtering on a many-to-any item`);
|
|
70
|
-
}
|
|
71
|
-
rootQuery.leftJoin({ [alias]: pathScope }, (joinClause) => {
|
|
72
|
-
joinClause
|
|
73
|
-
.onVal(relation.meta.one_collection_field, '=', pathScope)
|
|
74
|
-
.andOn(`${parentAlias || parentCollection}.${relation.field}`, '=', knex.raw(`CAST(?? AS CHAR(255))`, `${alias}.${schema.collections[pathScope].primary}`));
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
if (relationType === 'o2a') {
|
|
78
|
-
rootQuery.leftJoin({ [alias]: relation.collection }, (joinClause) => {
|
|
79
|
-
joinClause
|
|
80
|
-
.onVal(relation.meta.one_collection_field, '=', parentCollection)
|
|
81
|
-
.andOn(`${alias}.${relation.field}`, '=', knex.raw(`CAST(?? AS CHAR(255))`, `${parentAlias || parentCollection}.${schema.collections[parentCollection].primary}`));
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
// Still join o2m relations when in subquery OR when the o2m relation is not at the root level
|
|
85
|
-
if (relationType === 'o2m' && (subQuery === true || parentAlias !== undefined)) {
|
|
86
|
-
rootQuery.leftJoin({ [alias]: relation.collection }, `${parentAlias || parentCollection}.${schema.collections[relation.related_collection].primary}`, `${alias}.${relation.field}`);
|
|
87
|
-
}
|
|
88
|
-
if (relationType === 'm2o' || subQuery === true || (relationType === 'o2m' && parentAlias !== undefined)) {
|
|
89
|
-
let parent;
|
|
68
|
+
const existingAlias = parentFields
|
|
69
|
+
? (_a = aliasMap[`${parentFields}.${pathParts[0]}`]) === null || _a === void 0 ? void 0 : _a.alias
|
|
70
|
+
: (_b = aliasMap[pathParts[0]]) === null || _b === void 0 ? void 0 : _b.alias;
|
|
71
|
+
if (!existingAlias) {
|
|
72
|
+
const alias = (0, exports.generateAlias)();
|
|
73
|
+
const aliasKey = parentFields ? `${parentFields}.${pathParts[0]}` : pathParts[0];
|
|
74
|
+
const aliasedParentCollection = ((_c = aliasMap[parentFields !== null && parentFields !== void 0 ? parentFields : '']) === null || _c === void 0 ? void 0 : _c.alias) || parentCollection;
|
|
75
|
+
aliasMap[aliasKey] = { alias, collection: '' };
|
|
90
76
|
if (relationType === 'm2o') {
|
|
91
|
-
|
|
77
|
+
rootQuery.leftJoin({ [alias]: relation.related_collection }, `${aliasedParentCollection}.${relation.field}`, `${alias}.${schema.collections[relation.related_collection].primary}`);
|
|
78
|
+
aliasMap[aliasKey].collection = relation.related_collection;
|
|
92
79
|
}
|
|
93
80
|
else if (relationType === 'a2o') {
|
|
94
81
|
const pathScope = pathParts[0].split(':')[1];
|
|
95
82
|
if (!pathScope) {
|
|
96
83
|
throw new invalid_query_1.InvalidQueryException(`You have to provide a collection scope when sorting or filtering on a many-to-any item`);
|
|
97
84
|
}
|
|
98
|
-
|
|
85
|
+
rootQuery.leftJoin({ [alias]: pathScope }, (joinClause) => {
|
|
86
|
+
joinClause
|
|
87
|
+
.onVal(relation.meta.one_collection_field, '=', pathScope)
|
|
88
|
+
.andOn(`${aliasedParentCollection}.${relation.field}`, '=', knex.raw((0, helpers_1.getHelpers)(knex).schema.castA2oPrimaryKey(), `${alias}.${schema.collections[pathScope].primary}`));
|
|
89
|
+
});
|
|
90
|
+
aliasMap[aliasKey].collection = pathScope;
|
|
99
91
|
}
|
|
100
|
-
else {
|
|
101
|
-
|
|
92
|
+
else if (relationType === 'o2a') {
|
|
93
|
+
rootQuery.leftJoin({ [alias]: relation.collection }, (joinClause) => {
|
|
94
|
+
joinClause
|
|
95
|
+
.onVal(relation.meta.one_collection_field, '=', parentCollection)
|
|
96
|
+
.andOn(`${alias}.${relation.field}`, '=', knex.raw((0, helpers_1.getHelpers)(knex).schema.castA2oPrimaryKey(), `${aliasedParentCollection}.${schema.collections[parentCollection].primary}`));
|
|
97
|
+
});
|
|
98
|
+
aliasMap[aliasKey].collection = relation.collection;
|
|
99
|
+
hasMultiRelational = true;
|
|
102
100
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
else if (relationType === 'o2m') {
|
|
102
|
+
rootQuery.leftJoin({ [alias]: relation.collection }, `${aliasedParentCollection}.${schema.collections[relation.related_collection].primary}`, `${alias}.${relation.field}`);
|
|
103
|
+
aliasMap[aliasKey].collection = relation.collection;
|
|
104
|
+
hasMultiRelational = true;
|
|
106
105
|
}
|
|
107
106
|
}
|
|
107
|
+
let parent;
|
|
108
|
+
if (relationType === 'm2o') {
|
|
109
|
+
parent = relation.related_collection;
|
|
110
|
+
}
|
|
111
|
+
else if (relationType === 'a2o') {
|
|
112
|
+
const pathScope = pathParts[0].split(':')[1];
|
|
113
|
+
if (!pathScope) {
|
|
114
|
+
throw new invalid_query_1.InvalidQueryException(`You have to provide a collection scope when sorting or filtering on a many-to-any item`);
|
|
115
|
+
}
|
|
116
|
+
parent = pathScope;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
parent = relation.collection;
|
|
120
|
+
}
|
|
121
|
+
if (pathParts.length > 1) {
|
|
122
|
+
followRelation(pathParts.slice(1), parent, `${parentFields ? parentFields + '.' : ''}${pathParts[0]}`);
|
|
123
|
+
}
|
|
108
124
|
}
|
|
109
125
|
}
|
|
110
|
-
function applySort(knex, schema, rootQuery, rootSort, collection,
|
|
126
|
+
function applySort(knex, schema, rootQuery, rootSort, collection, aliasMap, returnRecords = false) {
|
|
111
127
|
const relations = schema.relations;
|
|
112
|
-
|
|
113
|
-
|
|
128
|
+
let hasMultiRelationalSort = false;
|
|
129
|
+
const sortRecords = rootSort.map((sortField) => {
|
|
114
130
|
const column = sortField.split('.');
|
|
115
131
|
let order = 'asc';
|
|
116
|
-
if (column.length > 1) {
|
|
117
|
-
if (sortField.startsWith('-')) {
|
|
118
|
-
order = 'desc';
|
|
119
|
-
}
|
|
120
|
-
if (column[0].startsWith('-')) {
|
|
121
|
-
column[0] = column[0].substring(1);
|
|
122
|
-
}
|
|
123
|
-
addJoin({
|
|
124
|
-
path: column,
|
|
125
|
-
collection,
|
|
126
|
-
aliasMap,
|
|
127
|
-
rootQuery,
|
|
128
|
-
subQuery,
|
|
129
|
-
schema,
|
|
130
|
-
relations,
|
|
131
|
-
knex,
|
|
132
|
-
});
|
|
133
|
-
const { columnPath } = (0, get_column_path_1.getColumnPath)({ path: column, collection, aliasMap, relations });
|
|
134
|
-
const [alias, field] = columnPath.split('.');
|
|
135
|
-
return {
|
|
136
|
-
order,
|
|
137
|
-
column: (0, get_column_1.getColumn)(knex, alias, field, false, schema),
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
let col = column[0];
|
|
141
132
|
if (sortField.startsWith('-')) {
|
|
142
|
-
col = column[0].substring(1);
|
|
143
133
|
order = 'desc';
|
|
144
134
|
}
|
|
135
|
+
if (column[0].startsWith('-')) {
|
|
136
|
+
column[0] = column[0].substring(1);
|
|
137
|
+
}
|
|
138
|
+
if (column.length === 1) {
|
|
139
|
+
const pathRoot = column[0].split(':')[0];
|
|
140
|
+
const { relation, relationType } = (0, get_relation_info_1.getRelationInfo)(relations, collection, pathRoot);
|
|
141
|
+
if (!relation || ['m2o', 'a2o'].includes(relationType !== null && relationType !== void 0 ? relationType : '')) {
|
|
142
|
+
return {
|
|
143
|
+
order,
|
|
144
|
+
column: returnRecords ? column[0] : (0, get_column_1.getColumn)(knex, collection, column[0], false, schema),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const hasMultiRelational = addJoin({
|
|
149
|
+
path: column,
|
|
150
|
+
collection,
|
|
151
|
+
aliasMap,
|
|
152
|
+
rootQuery,
|
|
153
|
+
schema,
|
|
154
|
+
relations,
|
|
155
|
+
knex,
|
|
156
|
+
});
|
|
157
|
+
const { columnPath } = (0, get_column_path_1.getColumnPath)({
|
|
158
|
+
path: column,
|
|
159
|
+
collection,
|
|
160
|
+
aliasMap,
|
|
161
|
+
relations,
|
|
162
|
+
schema,
|
|
163
|
+
});
|
|
164
|
+
const [alias, field] = columnPath.split('.');
|
|
165
|
+
if (!hasMultiRelationalSort) {
|
|
166
|
+
hasMultiRelationalSort = hasMultiRelational;
|
|
167
|
+
}
|
|
145
168
|
return {
|
|
146
169
|
order,
|
|
147
|
-
column: (0, get_column_1.getColumn)(knex,
|
|
170
|
+
column: returnRecords ? columnPath : (0, get_column_1.getColumn)(knex, alias, field, false, schema),
|
|
148
171
|
};
|
|
149
|
-
})
|
|
172
|
+
});
|
|
173
|
+
if (returnRecords)
|
|
174
|
+
return { sortRecords, hasMultiRelationalSort };
|
|
175
|
+
rootQuery.orderBy(sortRecords);
|
|
150
176
|
}
|
|
151
177
|
exports.applySort = applySort;
|
|
152
|
-
function
|
|
178
|
+
function applyLimit(rootQuery, limit) {
|
|
179
|
+
if (typeof limit === 'number' && limit !== -1) {
|
|
180
|
+
rootQuery.limit(limit);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
exports.applyLimit = applyLimit;
|
|
184
|
+
function applyOffset(knex, rootQuery, offset) {
|
|
185
|
+
if (typeof offset === 'number') {
|
|
186
|
+
(0, helpers_1.getHelpers)(knex).schema.applyOffset(rootQuery, offset);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
exports.applyOffset = applyOffset;
|
|
190
|
+
function applyFilter(knex, schema, rootQuery, rootFilter, collection, aliasMap) {
|
|
153
191
|
const helpers = (0, helpers_1.getHelpers)(knex);
|
|
154
192
|
const relations = schema.relations;
|
|
155
|
-
|
|
193
|
+
let hasMultiRelationalFilter = false;
|
|
156
194
|
addJoins(rootQuery, rootFilter, collection);
|
|
157
195
|
addWhereClauses(knex, rootQuery, rootFilter, collection);
|
|
158
|
-
return rootQuery;
|
|
196
|
+
return { query: rootQuery, hasMultiRelationalFilter };
|
|
159
197
|
function addJoins(dbQuery, filter, collection) {
|
|
160
198
|
for (const [key, value] of Object.entries(filter)) {
|
|
161
199
|
if (key === '_or' || key === '_and') {
|
|
@@ -169,22 +207,25 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
169
207
|
continue;
|
|
170
208
|
}
|
|
171
209
|
const filterPath = getFilterPath(key, value);
|
|
172
|
-
if (filterPath.length > 1
|
|
173
|
-
|
|
210
|
+
if (filterPath.length > 1 ||
|
|
211
|
+
(!(key.includes('(') && key.includes(')')) && schema.collections[collection].fields[key].type === 'alias')) {
|
|
212
|
+
const hasMultiRelational = addJoin({
|
|
174
213
|
path: filterPath,
|
|
175
214
|
collection,
|
|
176
215
|
knex,
|
|
177
216
|
schema,
|
|
178
217
|
relations,
|
|
179
|
-
subQuery,
|
|
180
218
|
rootQuery,
|
|
181
219
|
aliasMap,
|
|
182
220
|
});
|
|
221
|
+
if (!hasMultiRelationalFilter) {
|
|
222
|
+
hasMultiRelationalFilter = hasMultiRelational;
|
|
223
|
+
}
|
|
183
224
|
}
|
|
184
225
|
}
|
|
185
226
|
}
|
|
186
227
|
function addWhereClauses(knex, dbQuery, filter, collection, logical = 'and') {
|
|
187
|
-
var _a
|
|
228
|
+
var _a;
|
|
188
229
|
for (const [key, value] of Object.entries(filter)) {
|
|
189
230
|
if (key === '_or' || key === '_and') {
|
|
190
231
|
// If the _or array contains an empty object (full permissions), we should short-circuit and ignore all other
|
|
@@ -207,47 +248,58 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
207
248
|
const pathRoot = filterPath[0].split(':')[0];
|
|
208
249
|
const { relation, relationType } = (0, get_relation_info_1.getRelationInfo)(relations, collection, pathRoot);
|
|
209
250
|
const { operator: filterOperator, value: filterValue } = getOperation(key, value);
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
const { columnPath, targetCollection } = (0, get_column_path_1.getColumnPath)({ path: filterPath, collection, relations, aliasMap });
|
|
213
|
-
if (!columnPath)
|
|
214
|
-
continue;
|
|
215
|
-
const { type, special } = validateFilterField(schema.collections[targetCollection].fields, (0, strip_function_1.stripFunction)(filterPath[filterPath.length - 1]), targetCollection);
|
|
216
|
-
validateFilterOperator(type, filterOperator, special);
|
|
217
|
-
applyFilterToQuery(columnPath, filterOperator, filterValue, logical, targetCollection);
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
const { type, special } = validateFilterField(schema.collections[collection].fields, (0, strip_function_1.stripFunction)(filterPath[0]), collection);
|
|
221
|
-
validateFilterOperator(type, filterOperator, special);
|
|
222
|
-
applyFilterToQuery(`${collection}.${filterPath[0]}`, filterOperator, filterValue, logical);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
else if (subQuery === false || filterPath.length > 1) {
|
|
251
|
+
if (filterPath.length > 1 ||
|
|
252
|
+
(!(key.includes('(') && key.includes(')')) && schema.collections[collection].fields[key].type === 'alias')) {
|
|
226
253
|
if (!relation)
|
|
227
254
|
continue;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
255
|
+
if (relationType === 'o2m' || relationType === 'o2a') {
|
|
256
|
+
let pkField = `${collection}.${schema.collections[relation.related_collection].primary}`;
|
|
257
|
+
if (relationType === 'o2a') {
|
|
258
|
+
pkField = knex.raw((0, helpers_1.getHelpers)(knex).schema.castA2oPrimaryKey(), [pkField]);
|
|
259
|
+
}
|
|
260
|
+
const subQueryBuilder = (filter) => (subQueryKnex) => {
|
|
261
|
+
const field = relation.field;
|
|
262
|
+
const collection = relation.collection;
|
|
263
|
+
const column = `${collection}.${field}`;
|
|
264
|
+
subQueryKnex
|
|
265
|
+
.select({ [field]: column })
|
|
266
|
+
.from(collection)
|
|
267
|
+
.whereNotNull(column);
|
|
268
|
+
applyQuery(knex, relation.collection, subQueryKnex, { filter }, schema);
|
|
269
|
+
};
|
|
270
|
+
const childKey = (_a = Object.keys(value)) === null || _a === void 0 ? void 0 : _a[0];
|
|
271
|
+
if (childKey === '_none') {
|
|
272
|
+
dbQuery[logical].whereNotIn(pkField, subQueryBuilder(Object.values(value)[0]));
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
else if (childKey === '_some') {
|
|
276
|
+
dbQuery[logical].whereIn(pkField, subQueryBuilder(Object.values(value)[0]));
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
244
279
|
}
|
|
245
|
-
|
|
246
|
-
|
|
280
|
+
if (filterPath.includes('_none') || filterPath.includes('_some')) {
|
|
281
|
+
throw new invalid_query_1.InvalidQueryException(`"${filterPath.includes('_none') ? '_none' : '_some'}" can only be used with top level relational alias field`);
|
|
247
282
|
}
|
|
248
|
-
|
|
249
|
-
|
|
283
|
+
const { columnPath, targetCollection, addNestedPkField } = (0, get_column_path_1.getColumnPath)({
|
|
284
|
+
path: filterPath,
|
|
285
|
+
collection,
|
|
286
|
+
relations,
|
|
287
|
+
aliasMap,
|
|
288
|
+
schema,
|
|
289
|
+
});
|
|
290
|
+
if (addNestedPkField) {
|
|
291
|
+
filterPath.push(addNestedPkField);
|
|
250
292
|
}
|
|
293
|
+
if (!columnPath)
|
|
294
|
+
continue;
|
|
295
|
+
const { type, special } = validateFilterField(schema.collections[targetCollection].fields, (0, strip_function_1.stripFunction)(filterPath[filterPath.length - 1]), targetCollection);
|
|
296
|
+
validateFilterOperator(type, filterOperator, special);
|
|
297
|
+
applyFilterToQuery(columnPath, filterOperator, filterValue, logical, targetCollection);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
const { type, special } = validateFilterField(schema.collections[collection].fields, (0, strip_function_1.stripFunction)(filterPath[0]), collection);
|
|
301
|
+
validateFilterOperator(type, filterOperator, special);
|
|
302
|
+
applyFilterToQuery(`${collection}.${filterPath[0]}`, filterOperator, filterValue, logical);
|
|
251
303
|
}
|
|
252
304
|
}
|
|
253
305
|
function validateFilterField(fields, key, collection = 'unknown') {
|
|
@@ -271,7 +323,7 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
271
323
|
function applyFilterToQuery(key, operator, compareValue, logical = 'and', originalCollectionName) {
|
|
272
324
|
const [table, column] = key.split('.');
|
|
273
325
|
// Is processed through Knex.Raw, so should be safe to string-inject into these where queries
|
|
274
|
-
const selectionRaw = (0, get_column_1.getColumn)(knex, table, column, false, schema);
|
|
326
|
+
const selectionRaw = (0, get_column_1.getColumn)(knex, table, column, false, schema, { originalCollectionName });
|
|
275
327
|
// Knex supports "raw" in the columnName parameter, but isn't typed as such. Too bad..
|
|
276
328
|
// See https://github.com/knex/knex/issues/4518 @TODO remove as any once knex is updated
|
|
277
329
|
// These operators don't rely on a value, and can thus be used without one (eg `?filter[field][_null]`)
|
|
@@ -498,16 +550,17 @@ function applyAggregate(dbQuery, aggregate, collection) {
|
|
|
498
550
|
exports.applyAggregate = applyAggregate;
|
|
499
551
|
function getFilterPath(key, value) {
|
|
500
552
|
const path = [key];
|
|
501
|
-
|
|
553
|
+
const childKey = Object.keys(value)[0];
|
|
554
|
+
if (typeof childKey === 'string' && childKey.startsWith('_') === true && !['_none', '_some'].includes(childKey)) {
|
|
502
555
|
return path;
|
|
503
556
|
}
|
|
504
557
|
if ((0, lodash_1.isPlainObject)(value)) {
|
|
505
|
-
path.push(...getFilterPath(
|
|
558
|
+
path.push(...getFilterPath(childKey, Object.values(value)[0]));
|
|
506
559
|
}
|
|
507
560
|
return path;
|
|
508
561
|
}
|
|
509
562
|
function getOperation(key, value) {
|
|
510
|
-
if (key.startsWith('_') &&
|
|
563
|
+
if (key.startsWith('_') && !['_and', '_or', '_none', '_some'].includes(key)) {
|
|
511
564
|
return { operator: key, value };
|
|
512
565
|
}
|
|
513
566
|
else if ((0, lodash_1.isPlainObject)(value) === false) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const dynamicImport: (mod: string) => Promise<any>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCollectionFromAlias = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Extract the collection of an alias within an aliasMap
|
|
6
|
+
* For example: 'ljnsv.name' -> 'authors'
|
|
7
|
+
*/
|
|
8
|
+
function getCollectionFromAlias(alias, aliasMap) {
|
|
9
|
+
for (const aliasValue of Object.values(aliasMap)) {
|
|
10
|
+
if (aliasValue.alias === alias) {
|
|
11
|
+
return aliasValue.collection;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.getCollectionFromAlias = getCollectionFromAlias;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
import { Relation } from '@directus/shared/types';
|
|
2
|
-
type AliasMap =
|
|
3
|
-
[key: string]:
|
|
1
|
+
import { Relation, SchemaOverview } from '@directus/shared/types';
|
|
2
|
+
export type AliasMap = {
|
|
3
|
+
[key: string]: {
|
|
4
|
+
alias: string;
|
|
5
|
+
collection: string;
|
|
6
|
+
};
|
|
4
7
|
};
|
|
5
8
|
export type ColPathProps = {
|
|
6
9
|
path: string[];
|
|
7
10
|
collection: string;
|
|
8
11
|
aliasMap: AliasMap;
|
|
9
12
|
relations: Relation[];
|
|
13
|
+
schema?: SchemaOverview;
|
|
14
|
+
};
|
|
15
|
+
export type ColPathResult = {
|
|
16
|
+
columnPath: string;
|
|
17
|
+
targetCollection: string;
|
|
18
|
+
addNestedPkField?: string;
|
|
10
19
|
};
|
|
11
20
|
/**
|
|
12
21
|
* Converts a Directus field list path to the correct SQL names based on the constructed alias map.
|
|
13
22
|
* For example: ['author', 'role', 'name'] -> 'ljnsv.name'
|
|
14
23
|
* Also returns the target collection of the column: 'directus_roles'
|
|
24
|
+
* If the last filter path is an alias field, a nested PK is appended to the path
|
|
15
25
|
*/
|
|
16
|
-
export declare function getColumnPath({ path, collection, aliasMap, relations }: ColPathProps):
|
|
17
|
-
columnPath: string;
|
|
18
|
-
targetCollection: string;
|
|
19
|
-
};
|
|
20
|
-
export {};
|
|
26
|
+
export declare function getColumnPath({ path, collection, aliasMap, relations, schema }: ColPathProps): ColPathResult;
|
|
@@ -3,15 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getColumnPath = void 0;
|
|
4
4
|
const get_relation_info_1 = require("./get-relation-info");
|
|
5
5
|
const exceptions_1 = require("../exceptions");
|
|
6
|
-
const lodash_1 = require("lodash");
|
|
7
6
|
/**
|
|
8
7
|
* Converts a Directus field list path to the correct SQL names based on the constructed alias map.
|
|
9
8
|
* For example: ['author', 'role', 'name'] -> 'ljnsv.name'
|
|
10
9
|
* Also returns the target collection of the column: 'directus_roles'
|
|
10
|
+
* If the last filter path is an alias field, a nested PK is appended to the path
|
|
11
11
|
*/
|
|
12
|
-
function getColumnPath({ path, collection, aliasMap, relations }) {
|
|
12
|
+
function getColumnPath({ path, collection, aliasMap, relations, schema }) {
|
|
13
13
|
return followRelation(path);
|
|
14
|
-
function followRelation(pathParts, parentCollection = collection,
|
|
14
|
+
function followRelation(pathParts, parentCollection = collection, parentFields, addNestedPkField) {
|
|
15
|
+
var _a, _b, _c, _d;
|
|
15
16
|
/**
|
|
16
17
|
* For A2M fields, the path can contain an optional collection scope <field>:<scope>
|
|
17
18
|
*/
|
|
@@ -20,7 +21,7 @@ function getColumnPath({ path, collection, aliasMap, relations }) {
|
|
|
20
21
|
if (!relation) {
|
|
21
22
|
throw new exceptions_1.InvalidQueryException(`"${parentCollection}.${pathRoot}" is not a relational field`);
|
|
22
23
|
}
|
|
23
|
-
const alias = (0
|
|
24
|
+
const alias = parentFields ? (_a = aliasMap[`${parentFields}.${pathParts[0]}`]) === null || _a === void 0 ? void 0 : _a.alias : (_b = aliasMap[pathParts[0]]) === null || _b === void 0 ? void 0 : _b.alias;
|
|
24
25
|
const remainingParts = pathParts.slice(1);
|
|
25
26
|
let parent;
|
|
26
27
|
if (relationType === 'a2o') {
|
|
@@ -36,13 +37,29 @@ function getColumnPath({ path, collection, aliasMap, relations }) {
|
|
|
36
37
|
else {
|
|
37
38
|
parent = relation.collection;
|
|
38
39
|
}
|
|
40
|
+
// Top level alias field
|
|
41
|
+
if (schema && !(((_c = remainingParts[0]) !== null && _c !== void 0 ? _c : parent).includes('(') && ((_d = remainingParts[0]) !== null && _d !== void 0 ? _d : parent).includes(')'))) {
|
|
42
|
+
if (remainingParts.length === 0) {
|
|
43
|
+
remainingParts.push(schema.collections[parent].primary);
|
|
44
|
+
addNestedPkField = schema.collections[parent].primary;
|
|
45
|
+
}
|
|
46
|
+
// Nested level alias field
|
|
47
|
+
else if (remainingParts.length === 1 && schema.collections[parent].fields[remainingParts[0]].type === 'alias') {
|
|
48
|
+
remainingParts.push(schema.collections[relation.related_collection].primary);
|
|
49
|
+
addNestedPkField = schema.collections[relation.related_collection].primary;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
39
52
|
if (remainingParts.length === 1) {
|
|
40
|
-
return {
|
|
53
|
+
return {
|
|
54
|
+
columnPath: `${alias || parent}.${remainingParts[0]}`,
|
|
55
|
+
targetCollection: parent,
|
|
56
|
+
addNestedPkField,
|
|
57
|
+
};
|
|
41
58
|
}
|
|
42
59
|
if (remainingParts.length) {
|
|
43
|
-
return followRelation(remainingParts, parent,
|
|
60
|
+
return followRelation(remainingParts, parent, `${parentFields ? parentFields + '.' : ''}${pathParts[0]}`, addNestedPkField);
|
|
44
61
|
}
|
|
45
|
-
return { columnPath: '', targetCollection: '' };
|
|
62
|
+
return { columnPath: '', targetCollection: '', addNestedPkField };
|
|
46
63
|
}
|
|
47
64
|
}
|
|
48
65
|
exports.getColumnPath = getColumnPath;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { Query, SchemaOverview } from '@directus/shared/types';
|
|
2
2
|
import { Knex } from 'knex';
|
|
3
|
+
type GetColumnOptions = {
|
|
4
|
+
query?: Query;
|
|
5
|
+
originalCollectionName?: string;
|
|
6
|
+
};
|
|
3
7
|
/**
|
|
4
8
|
* Return column prefixed by table. If column includes functions (like `year(date_created)`), the
|
|
5
9
|
* column is replaced with the appropriate SQL
|
|
@@ -8,6 +12,9 @@ import { Knex } from 'knex';
|
|
|
8
12
|
* @param table Collection or alias in which column resides
|
|
9
13
|
* @param column name of the column
|
|
10
14
|
* @param alias Whether or not to add a SQL AS statement
|
|
15
|
+
* @param schema For retrieval of the column type
|
|
16
|
+
* @param options Optional parameters
|
|
11
17
|
* @returns Knex raw instance
|
|
12
18
|
*/
|
|
13
|
-
export declare function getColumn(knex: Knex, table: string, column: string, alias: string | false | undefined, schema: SchemaOverview,
|
|
19
|
+
export declare function getColumn(knex: Knex, table: string, column: string, alias: string | false | undefined, schema: SchemaOverview, options?: GetColumnOptions): Knex.Raw;
|
|
20
|
+
export {};
|
package/dist/utils/get-column.js
CHANGED
|
@@ -14,21 +14,28 @@ const apply_function_to_column_name_1 = require("./apply-function-to-column-name
|
|
|
14
14
|
* @param table Collection or alias in which column resides
|
|
15
15
|
* @param column name of the column
|
|
16
16
|
* @param alias Whether or not to add a SQL AS statement
|
|
17
|
+
* @param schema For retrieval of the column type
|
|
18
|
+
* @param options Optional parameters
|
|
17
19
|
* @returns Knex raw instance
|
|
18
20
|
*/
|
|
19
|
-
function getColumn(knex, table, column, alias = (0, apply_function_to_column_name_1.applyFunctionToColumnName)(column), schema,
|
|
21
|
+
function getColumn(knex, table, column, alias = (0, apply_function_to_column_name_1.applyFunctionToColumnName)(column), schema, options) {
|
|
20
22
|
var _a, _b, _c, _d;
|
|
21
23
|
const fn = (0, helpers_1.getFunctions)(knex, schema);
|
|
22
24
|
if (column.includes('(') && column.includes(')')) {
|
|
23
25
|
const functionName = column.split('(')[0];
|
|
24
26
|
const columnName = column.match(constants_1.REGEX_BETWEEN_PARENS)[1];
|
|
25
27
|
if (functionName in fn) {
|
|
26
|
-
const
|
|
28
|
+
const collectionName = (options === null || options === void 0 ? void 0 : options.originalCollectionName) || table;
|
|
29
|
+
const type = (_d = (_c = (_b = (_a = schema === null || schema === void 0 ? void 0 : schema.collections[collectionName]) === null || _a === void 0 ? void 0 : _a.fields) === null || _b === void 0 ? void 0 : _b[columnName]) === null || _c === void 0 ? void 0 : _c.type) !== null && _d !== void 0 ? _d : 'unknown';
|
|
27
30
|
const allowedFunctions = (0, utils_1.getFunctionsForType)(type);
|
|
28
31
|
if (allowedFunctions.includes(functionName) === false) {
|
|
29
32
|
throw new exceptions_1.InvalidQueryException(`Invalid function specified "${functionName}"`);
|
|
30
33
|
}
|
|
31
|
-
const result = fn[functionName](table, columnName, {
|
|
34
|
+
const result = fn[functionName](table, columnName, {
|
|
35
|
+
type,
|
|
36
|
+
query: options === null || options === void 0 ? void 0 : options.query,
|
|
37
|
+
originalCollectionName: options === null || options === void 0 ? void 0 : options.originalCollectionName,
|
|
38
|
+
});
|
|
32
39
|
if (alias) {
|
|
33
40
|
return knex.raw(result + ' AS ??', [alias]);
|
|
34
41
|
}
|
|
@@ -6,10 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.getConfigFromEnv = void 0;
|
|
7
7
|
const camelcase_1 = __importDefault(require("camelcase"));
|
|
8
8
|
const lodash_1 = require("lodash");
|
|
9
|
-
const env_1 =
|
|
9
|
+
const env_1 = require("../env");
|
|
10
10
|
function getConfigFromEnv(prefix, omitPrefix, type = 'camelcase') {
|
|
11
|
+
const env = (0, env_1.getEnv)();
|
|
11
12
|
const config = {};
|
|
12
|
-
for (const [key, value] of Object.entries(
|
|
13
|
+
for (const [key, value] of Object.entries(env)) {
|
|
13
14
|
if (key.toLowerCase().startsWith(prefix.toLowerCase()) === false)
|
|
14
15
|
continue;
|
|
15
16
|
if (omitPrefix) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { SchemaOverview } from '@directus/schema/
|
|
1
|
+
import { SchemaOverview } from '@directus/schema/types/overview';
|
|
2
2
|
import { Column } from 'knex-schema-inspector/dist/types/column';
|
|
3
3
|
export default function getDefaultValue(column: SchemaOverview[string]['columns'][string] | Column): string | boolean | number | Record<string, any> | any[] | null;
|