directus 9.10.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/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/database/helpers/date/dialects/sqlite.js +6 -2
- 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 +10 -14
- package/dist/env.js +2 -1
- 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/fields.js +15 -8
- package/dist/services/graphql.js +49 -32
- package/dist/services/import-export.d.ts +1 -1
- package/dist/services/import-export.js +13 -12
- package/dist/services/items.d.ts +3 -3
- package/dist/services/items.js +8 -1
- package/dist/services/payload.d.ts +2 -2
- package/dist/services/payload.js +8 -7
- package/dist/services/users.d.ts +4 -0
- package/dist/services/users.js +20 -0
- package/dist/utils/{apply-query.d.ts → apply-query/index.d.ts} +0 -0
- package/dist/utils/{apply-query.js → apply-query/index.js} +53 -125
- 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 +1 -7
- 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-schema.js +2 -1
- package/dist/utils/get-snapshot.js +22 -4
- package/dist/utils/parse-json.d.ts +5 -0
- package/dist/utils/parse-json.js +19 -0
- package/dist/utils/sanitize-query.d.ts +1 -2
- package/dist/utils/sanitize-query.js +6 -5
- package/package.json +12 -12
package/dist/services/fields.js
CHANGED
|
@@ -351,13 +351,6 @@ class FieldsService {
|
|
|
351
351
|
});
|
|
352
352
|
await this.knex.transaction(async (trx) => {
|
|
353
353
|
var _a, _b;
|
|
354
|
-
if (this.schema.collections[collection] &&
|
|
355
|
-
field in this.schema.collections[collection].fields &&
|
|
356
|
-
this.schema.collections[collection].fields[field].alias === false) {
|
|
357
|
-
await trx.schema.table(collection, (table) => {
|
|
358
|
-
table.dropColumn(field);
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
354
|
const relations = this.schema.relations.filter((relation) => {
|
|
362
355
|
var _a;
|
|
363
356
|
return ((relation.collection === collection && relation.field === field) ||
|
|
@@ -389,6 +382,14 @@ class FieldsService {
|
|
|
389
382
|
.where({ many_collection: relation.collection, many_field: relation.field });
|
|
390
383
|
}
|
|
391
384
|
}
|
|
385
|
+
// Delete field only after foreign key constraints are removed
|
|
386
|
+
if (this.schema.collections[collection] &&
|
|
387
|
+
field in this.schema.collections[collection].fields &&
|
|
388
|
+
this.schema.collections[collection].fields[field].alias === false) {
|
|
389
|
+
await trx.schema.table(collection, (table) => {
|
|
390
|
+
table.dropColumn(field);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
392
393
|
const collectionMeta = await trx
|
|
393
394
|
.select('archive_field', 'sort_field')
|
|
394
395
|
.from('directus_collections')
|
|
@@ -441,7 +442,13 @@ class FieldsService {
|
|
|
441
442
|
if (field.type === 'alias' || field.type === 'unknown')
|
|
442
443
|
return;
|
|
443
444
|
if ((_a = field.schema) === null || _a === void 0 ? void 0 : _a.has_auto_increment) {
|
|
444
|
-
|
|
445
|
+
if (field.type === 'bigInteger') {
|
|
446
|
+
// Create an auto-incremented big integer (MySQL, PostgreSQL) or an auto-incremented integer (other DBs)
|
|
447
|
+
column = table.bigIncrements(field.field);
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
column = table.increments(field.field);
|
|
451
|
+
}
|
|
445
452
|
}
|
|
446
453
|
else if (field.type === 'string') {
|
|
447
454
|
column = table.string(field.field, (_c = (_b = field.schema) === null || _b === void 0 ? void 0 : _b.max_length) !== null && _c !== void 0 ? _c : undefined);
|
package/dist/services/graphql.js
CHANGED
|
@@ -170,16 +170,7 @@ class GraphQLService {
|
|
|
170
170
|
acc[collectionName] = ReadCollectionTypes[collection.collection].getResolver(collection.collection);
|
|
171
171
|
if (this.schema.collections[collection.collection].singleton === false) {
|
|
172
172
|
acc[`${collectionName}_by_id`] = ReadCollectionTypes[collection.collection].getResolver(`${collection.collection}_by_id`);
|
|
173
|
-
|
|
174
|
-
const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type);
|
|
175
|
-
if (graphqlType === graphql_1.GraphQLInt || graphqlType === graphql_1.GraphQLFloat) {
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
return false;
|
|
179
|
-
});
|
|
180
|
-
if (hasAggregate) {
|
|
181
|
-
acc[`${collectionName}_aggregated`] = ReadCollectionTypes[collection.collection].getResolver(`${collection.collection}_aggregated`);
|
|
182
|
-
}
|
|
173
|
+
acc[`${collectionName}_aggregated`] = ReadCollectionTypes[collection.collection].getResolver(`${collection.collection}_aggregated`);
|
|
183
174
|
}
|
|
184
175
|
return acc;
|
|
185
176
|
}, {}));
|
|
@@ -424,7 +415,8 @@ class GraphQLService {
|
|
|
424
415
|
const { CollectionTypes: ReadCollectionTypes } = getTypes('read');
|
|
425
416
|
const ReadableCollectionFilterTypes = {};
|
|
426
417
|
const AggregatedFunctions = {};
|
|
427
|
-
const
|
|
418
|
+
const AggregatedFields = {};
|
|
419
|
+
const AggregateMethods = {};
|
|
428
420
|
const StringFilterOperators = schemaComposer.createInputTC({
|
|
429
421
|
name: 'string_filter_operators',
|
|
430
422
|
fields: {
|
|
@@ -680,7 +672,7 @@ class GraphQLService {
|
|
|
680
672
|
_and: [ReadableCollectionFilterTypes[collection.collection]],
|
|
681
673
|
_or: [ReadableCollectionFilterTypes[collection.collection]],
|
|
682
674
|
});
|
|
683
|
-
|
|
675
|
+
AggregatedFields[collection.collection] = schemaComposer.createObjectTC({
|
|
684
676
|
name: `${collection.collection}_aggregated_fields`,
|
|
685
677
|
fields: Object.values(collection.fields).reduce((acc, field) => {
|
|
686
678
|
const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type);
|
|
@@ -698,46 +690,71 @@ class GraphQLService {
|
|
|
698
690
|
return acc;
|
|
699
691
|
}, {}),
|
|
700
692
|
});
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
693
|
+
AggregateMethods[collection.collection] = {
|
|
694
|
+
group: {
|
|
695
|
+
name: 'group',
|
|
696
|
+
type: graphql_compose_1.GraphQLJSON,
|
|
697
|
+
},
|
|
698
|
+
countAll: {
|
|
699
|
+
name: 'countAll',
|
|
700
|
+
type: graphql_1.GraphQLInt,
|
|
701
|
+
},
|
|
702
|
+
count: {
|
|
703
|
+
name: 'count',
|
|
704
|
+
type: schemaComposer.createObjectTC({
|
|
705
|
+
name: `${collection.collection}_aggregated_count`,
|
|
706
|
+
fields: Object.values(collection.fields).reduce((acc, field) => {
|
|
707
|
+
acc[field.field] = {
|
|
708
|
+
type: graphql_1.GraphQLInt,
|
|
709
|
+
description: field.note,
|
|
710
|
+
};
|
|
711
|
+
return acc;
|
|
712
|
+
}, {}),
|
|
713
|
+
}),
|
|
714
|
+
},
|
|
715
|
+
};
|
|
716
|
+
const hasNumericAggregates = Object.values(collection.fields).some((field) => {
|
|
717
|
+
const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type);
|
|
718
|
+
if (graphqlType === graphql_1.GraphQLInt || graphqlType === graphql_1.GraphQLFloat) {
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
return false;
|
|
722
|
+
});
|
|
723
|
+
if (hasNumericAggregates) {
|
|
724
|
+
Object.assign(AggregateMethods[collection.collection], {
|
|
708
725
|
avg: {
|
|
709
726
|
name: 'avg',
|
|
710
|
-
type:
|
|
727
|
+
type: AggregatedFields[collection.collection],
|
|
711
728
|
},
|
|
712
729
|
sum: {
|
|
713
730
|
name: 'sum',
|
|
714
|
-
type:
|
|
715
|
-
},
|
|
716
|
-
count: {
|
|
717
|
-
name: 'count',
|
|
718
|
-
type: AggregatedFilters[collection.collection],
|
|
731
|
+
type: AggregatedFields[collection.collection],
|
|
719
732
|
},
|
|
720
733
|
countDistinct: {
|
|
721
734
|
name: 'countDistinct',
|
|
722
|
-
type:
|
|
735
|
+
type: AggregatedFields[collection.collection],
|
|
723
736
|
},
|
|
724
737
|
avgDistinct: {
|
|
725
738
|
name: 'avgDistinct',
|
|
726
|
-
type:
|
|
739
|
+
type: AggregatedFields[collection.collection],
|
|
727
740
|
},
|
|
728
741
|
sumDistinct: {
|
|
729
742
|
name: 'sumDistinct',
|
|
730
|
-
type:
|
|
743
|
+
type: AggregatedFields[collection.collection],
|
|
731
744
|
},
|
|
732
745
|
min: {
|
|
733
746
|
name: 'min',
|
|
734
|
-
type:
|
|
747
|
+
type: AggregatedFields[collection.collection],
|
|
735
748
|
},
|
|
736
749
|
max: {
|
|
737
750
|
name: 'max',
|
|
738
|
-
type:
|
|
751
|
+
type: AggregatedFields[collection.collection],
|
|
739
752
|
},
|
|
740
|
-
}
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
AggregatedFunctions[collection.collection] = schemaComposer.createObjectTC({
|
|
756
|
+
name: `${collection.collection}_aggregated`,
|
|
757
|
+
fields: AggregateMethods[collection.collection],
|
|
741
758
|
});
|
|
742
759
|
ReadCollectionTypes[collection.collection].addResolver({
|
|
743
760
|
name: collection.collection,
|
|
@@ -1237,7 +1254,7 @@ class GraphQLService {
|
|
|
1237
1254
|
if (!query.deep)
|
|
1238
1255
|
query.deep = {};
|
|
1239
1256
|
const args = this.parseArgs(selection.arguments, variableValues);
|
|
1240
|
-
(0, lodash_1.set)(query.deep, current, (0, lodash_1.merge)((0, lodash_1.get)(query.deep, current), (0, lodash_1.mapKeys)((0, sanitize_query_1.sanitizeQuery)(args, this.accountability), (value, key) => `_${key}`)));
|
|
1257
|
+
(0, lodash_1.set)(query.deep, currentAlias !== null && currentAlias !== void 0 ? currentAlias : current, (0, lodash_1.merge)((0, lodash_1.get)(query.deep, currentAlias !== null && currentAlias !== void 0 ? currentAlias : current), (0, lodash_1.mapKeys)((0, sanitize_query_1.sanitizeQuery)(args, this.accountability), (value, key) => `_${key}`)));
|
|
1241
1258
|
}
|
|
1242
1259
|
}
|
|
1243
1260
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
+
import { Accountability, Query, SchemaOverview } from '@directus/shared/types';
|
|
2
3
|
import { Knex } from 'knex';
|
|
3
4
|
import { AbstractServiceOptions, File } from '../types';
|
|
4
|
-
import { Accountability, Query, SchemaOverview } from '@directus/shared/types';
|
|
5
5
|
export declare class ImportService {
|
|
6
6
|
knex: Knex;
|
|
7
7
|
accountability: Accountability | null;
|
|
@@ -4,25 +4,26 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ExportService = exports.ImportService = void 0;
|
|
7
|
-
const
|
|
8
|
-
const exceptions_1 = require("../exceptions");
|
|
9
|
-
const StreamArray_1 = __importDefault(require("stream-json/streamers/StreamArray"));
|
|
10
|
-
const items_1 = require("./items");
|
|
7
|
+
const utils_1 = require("@directus/shared/utils");
|
|
11
8
|
const async_1 = require("async");
|
|
12
|
-
const destroy_1 = __importDefault(require("destroy"));
|
|
13
9
|
const csv_parser_1 = __importDefault(require("csv-parser"));
|
|
14
|
-
const
|
|
10
|
+
const destroy_1 = __importDefault(require("destroy"));
|
|
11
|
+
const fs_extra_1 = require("fs-extra");
|
|
15
12
|
const js2xmlparser_1 = require("js2xmlparser");
|
|
16
13
|
const json2csv_1 = require("json2csv");
|
|
17
|
-
const
|
|
14
|
+
const lodash_1 = require("lodash");
|
|
15
|
+
const StreamArray_1 = __importDefault(require("stream-json/streamers/StreamArray"));
|
|
16
|
+
const strip_bom_stream_1 = __importDefault(require("strip-bom-stream"));
|
|
18
17
|
const tmp_promise_1 = require("tmp-promise");
|
|
18
|
+
const database_1 = __importDefault(require("../database"));
|
|
19
19
|
const env_1 = __importDefault(require("../env"));
|
|
20
|
-
const
|
|
20
|
+
const exceptions_1 = require("../exceptions");
|
|
21
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
21
22
|
const get_date_formatted_1 = require("../utils/get-date-formatted");
|
|
22
|
-
const
|
|
23
|
+
const parse_json_1 = require("../utils/parse-json");
|
|
24
|
+
const files_1 = require("./files");
|
|
25
|
+
const items_1 = require("./items");
|
|
23
26
|
const notifications_1 = require("./notifications");
|
|
24
|
-
const logger_1 = __importDefault(require("../logger"));
|
|
25
|
-
const strip_bom_stream_1 = __importDefault(require("strip-bom-stream"));
|
|
26
27
|
class ImportService {
|
|
27
28
|
constructor(options) {
|
|
28
29
|
this.knex = options.knex || (0, database_1.default)();
|
|
@@ -101,7 +102,7 @@ class ImportService {
|
|
|
101
102
|
}
|
|
102
103
|
else {
|
|
103
104
|
try {
|
|
104
|
-
const parsedJson =
|
|
105
|
+
const parsedJson = (0, parse_json_1.parseJSON)(value);
|
|
105
106
|
(0, lodash_1.set)(result, key, parsedJson);
|
|
106
107
|
}
|
|
107
108
|
catch {
|
package/dist/services/items.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Accountability, PermissionsAction, Query, SchemaOverview } from '@directus/shared/types';
|
|
2
2
|
import Keyv from 'keyv';
|
|
3
|
-
import {
|
|
4
|
-
import { AbstractService, AbstractServiceOptions, Item as AnyItem,
|
|
3
|
+
import { Knex } from 'knex';
|
|
4
|
+
import { AbstractService, AbstractServiceOptions, Item as AnyItem, MutationOptions, PrimaryKey } from '../types';
|
|
5
5
|
export declare type QueryOptions = {
|
|
6
6
|
stripNonRequested?: boolean;
|
|
7
7
|
permissionsAction?: PermissionsAction;
|
package/dist/services/items.js
CHANGED
|
@@ -15,8 +15,8 @@ const translate_1 = require("../exceptions/database/translate");
|
|
|
15
15
|
const types_1 = require("../types");
|
|
16
16
|
const get_ast_from_query_1 = __importDefault(require("../utils/get-ast-from-query"));
|
|
17
17
|
const authorization_1 = require("./authorization");
|
|
18
|
-
const payload_1 = require("./payload");
|
|
19
18
|
const index_1 = require("./index");
|
|
19
|
+
const payload_1 = require("./payload");
|
|
20
20
|
class ItemsService {
|
|
21
21
|
constructor(collection, options) {
|
|
22
22
|
this.collection = collection;
|
|
@@ -260,6 +260,10 @@ class ItemsService {
|
|
|
260
260
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
261
261
|
const filterWithKey = { _and: [{ [primaryKeyField]: { _in: keys } }, (_a = query.filter) !== null && _a !== void 0 ? _a : {}] };
|
|
262
262
|
const queryWithKey = (0, lodash_1.assign)({}, query, { filter: filterWithKey });
|
|
263
|
+
// Set query limit as the number of keys
|
|
264
|
+
if (Array.isArray(keys) && keys.length > 0 && !queryWithKey.limit) {
|
|
265
|
+
queryWithKey.limit = keys.length;
|
|
266
|
+
}
|
|
263
267
|
const results = await this.readByQuery(queryWithKey, opts);
|
|
264
268
|
return results;
|
|
265
269
|
}
|
|
@@ -306,6 +310,8 @@ class ItemsService {
|
|
|
306
310
|
accountability: this.accountability,
|
|
307
311
|
})
|
|
308
312
|
: payload;
|
|
313
|
+
// Sort keys to ensure that the order is maintained
|
|
314
|
+
keys.sort();
|
|
309
315
|
if (this.accountability) {
|
|
310
316
|
await authorizationService.checkAccess('update', this.collection, keys);
|
|
311
317
|
}
|
|
@@ -467,6 +473,7 @@ class ItemsService {
|
|
|
467
473
|
const authorizationService = new authorization_1.AuthorizationService({
|
|
468
474
|
accountability: this.accountability,
|
|
469
475
|
schema: this.schema,
|
|
476
|
+
knex: this.knex,
|
|
470
477
|
});
|
|
471
478
|
await authorizationService.checkAccess('delete', this.collection, keys);
|
|
472
479
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Knex } from 'knex';
|
|
2
|
-
import { AbstractServiceOptions, Item, PrimaryKey } from '../types';
|
|
3
1
|
import { Accountability, SchemaOverview } from '@directus/shared/types';
|
|
2
|
+
import { Knex } from 'knex';
|
|
4
3
|
import { Helpers } from '../database/helpers';
|
|
4
|
+
import { AbstractServiceOptions, Item, PrimaryKey } from '../types';
|
|
5
5
|
declare type Action = 'create' | 'read' | 'update';
|
|
6
6
|
declare type Transformers = {
|
|
7
7
|
[type: string]: (context: {
|
package/dist/services/payload.js
CHANGED
|
@@ -4,18 +4,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.PayloadService = void 0;
|
|
7
|
+
const utils_1 = require("@directus/shared/utils");
|
|
7
8
|
const date_fns_1 = require("date-fns");
|
|
9
|
+
const flat_1 = require("flat");
|
|
8
10
|
const joi_1 = __importDefault(require("joi"));
|
|
9
11
|
const lodash_1 = require("lodash");
|
|
10
12
|
const uuid_1 = require("uuid");
|
|
13
|
+
const wellknown_1 = require("wellknown");
|
|
11
14
|
const database_1 = __importDefault(require("../database"));
|
|
12
|
-
const exceptions_1 = require("../exceptions");
|
|
13
|
-
const utils_1 = require("@directus/shared/utils");
|
|
14
|
-
const items_1 = require("./items");
|
|
15
|
-
const flat_1 = require("flat");
|
|
16
15
|
const helpers_1 = require("../database/helpers");
|
|
17
|
-
const
|
|
16
|
+
const exceptions_1 = require("../exceptions");
|
|
18
17
|
const generate_hash_1 = require("../utils/generate-hash");
|
|
18
|
+
const parse_json_1 = require("../utils/parse-json");
|
|
19
|
+
const items_1 = require("./items");
|
|
19
20
|
/**
|
|
20
21
|
* Process a given payload for a collection to ensure the special fields (hash, uuid, date etc) are
|
|
21
22
|
* handled correctly.
|
|
@@ -55,7 +56,7 @@ class PayloadService {
|
|
|
55
56
|
if (action === 'read') {
|
|
56
57
|
if (typeof value === 'string') {
|
|
57
58
|
try {
|
|
58
|
-
return
|
|
59
|
+
return (0, parse_json_1.parseJSON)(value);
|
|
59
60
|
}
|
|
60
61
|
catch {
|
|
61
62
|
return value;
|
|
@@ -196,7 +197,7 @@ class PayloadService {
|
|
|
196
197
|
processGeometries(payloads, action) {
|
|
197
198
|
const process = action == 'read'
|
|
198
199
|
? (value) => (typeof value === 'string' ? (0, wellknown_1.parse)(value) : value)
|
|
199
|
-
: (value) => this.helpers.st.fromGeoJSON(typeof value == 'string' ?
|
|
200
|
+
: (value) => this.helpers.st.fromGeoJSON(typeof value == 'string' ? (0, parse_json_1.parseJSON)(value) : value);
|
|
200
201
|
const fieldsInCollection = Object.entries(this.schema.collections[this.collection].fields);
|
|
201
202
|
const geometryColumns = fieldsInCollection.filter(([_, field]) => field.type.startsWith('geometry'));
|
|
202
203
|
for (const [name] of geometryColumns) {
|
package/dist/services/users.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export declare class UsersService extends ItemsService {
|
|
|
18
18
|
*/
|
|
19
19
|
private checkPasswordPolicy;
|
|
20
20
|
private checkRemainingAdminExistence;
|
|
21
|
+
/**
|
|
22
|
+
* Make sure there's at least one active admin user when updating user status
|
|
23
|
+
*/
|
|
24
|
+
private checkRemainingActiveAdmin;
|
|
21
25
|
/**
|
|
22
26
|
* Create a new user
|
|
23
27
|
*/
|
package/dist/services/users.js
CHANGED
|
@@ -101,6 +101,23 @@ class UsersService extends items_1.ItemsService {
|
|
|
101
101
|
throw new exceptions_2.UnprocessableEntityException(`You can't remove the last admin user from the role.`);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Make sure there's at least one active admin user when updating user status
|
|
106
|
+
*/
|
|
107
|
+
async checkRemainingActiveAdmin(excludeKeys) {
|
|
108
|
+
const otherAdminUsers = await this.knex
|
|
109
|
+
.count('*', { as: 'count' })
|
|
110
|
+
.from('directus_users')
|
|
111
|
+
.whereNotIn('directus_users.id', excludeKeys)
|
|
112
|
+
.andWhere({ 'directus_roles.admin_access': true })
|
|
113
|
+
.andWhere({ 'directus_users.status': 'active' })
|
|
114
|
+
.leftJoin('directus_roles', 'directus_users.role', 'directus_roles.id')
|
|
115
|
+
.first();
|
|
116
|
+
const otherAdminUsersCount = +((otherAdminUsers === null || otherAdminUsers === void 0 ? void 0 : otherAdminUsers.count) || 0);
|
|
117
|
+
if (otherAdminUsersCount === 0) {
|
|
118
|
+
throw new exceptions_2.UnprocessableEntityException(`You can't change the active status of the last admin user.`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
104
121
|
/**
|
|
105
122
|
* Create a new user
|
|
106
123
|
*/
|
|
@@ -149,6 +166,9 @@ class UsersService extends items_1.ItemsService {
|
|
|
149
166
|
await this.checkRemainingAdminExistence(keys);
|
|
150
167
|
}
|
|
151
168
|
}
|
|
169
|
+
if (data.status !== undefined && data.status !== 'active') {
|
|
170
|
+
await this.checkRemainingActiveAdmin(keys);
|
|
171
|
+
}
|
|
152
172
|
if (data.email) {
|
|
153
173
|
if (keys.length > 1) {
|
|
154
174
|
throw new record_not_unique_1.RecordNotUniqueException('email', {
|
|
File without changes
|
|
@@ -4,15 +4,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.applyAggregate = exports.applySearch = exports.applyFilter = exports.applySort = void 0;
|
|
7
|
-
const utils_1 = require("@directus/shared/utils");
|
|
8
7
|
const lodash_1 = require("lodash");
|
|
9
8
|
const nanoid_1 = require("nanoid");
|
|
10
9
|
const uuid_validate_1 = __importDefault(require("uuid-validate"));
|
|
11
|
-
const helpers_1 = require("
|
|
12
|
-
const
|
|
13
|
-
const get_column_1 = require("
|
|
14
|
-
const get_column_path_1 = require("
|
|
15
|
-
const get_relation_info_1 = require("
|
|
10
|
+
const helpers_1 = require("../../database/helpers");
|
|
11
|
+
const invalid_query_1 = require("../../exceptions/invalid-query");
|
|
12
|
+
const get_column_1 = require("../get-column");
|
|
13
|
+
const get_column_path_1 = require("../get-column-path");
|
|
14
|
+
const get_relation_info_1 = require("../get-relation-info");
|
|
15
|
+
const operators_1 = __importDefault(require("./operators"));
|
|
16
16
|
const generateAlias = (0, nanoid_1.customAlphabet)('abcdefghijklmnopqrstuvwxyz', 5);
|
|
17
17
|
/**
|
|
18
18
|
* Apply the Query to a given Knex query builder instance
|
|
@@ -65,7 +65,7 @@ function addJoin({ path, collection, aliasMap, rootQuery, subQuery, schema, rela
|
|
|
65
65
|
if (relationType === 'a2o') {
|
|
66
66
|
const pathScope = pathParts[0].split(':')[1];
|
|
67
67
|
if (!pathScope) {
|
|
68
|
-
throw new
|
|
68
|
+
throw new invalid_query_1.InvalidQueryException(`You have to provide a collection scope when sorting or filtering on a many-to-any item`);
|
|
69
69
|
}
|
|
70
70
|
rootQuery.leftJoin({ [alias]: pathScope }, (joinClause) => {
|
|
71
71
|
joinClause
|
|
@@ -92,7 +92,7 @@ function addJoin({ path, collection, aliasMap, rootQuery, subQuery, schema, rela
|
|
|
92
92
|
else if (relationType === 'a2o') {
|
|
93
93
|
const pathScope = pathParts[0].split(':')[1];
|
|
94
94
|
if (!pathScope) {
|
|
95
|
-
throw new
|
|
95
|
+
throw new invalid_query_1.InvalidQueryException(`You have to provide a collection scope when sorting or filtering on a many-to-any item`);
|
|
96
96
|
}
|
|
97
97
|
parent = pathScope;
|
|
98
98
|
}
|
|
@@ -155,6 +155,9 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
155
155
|
addJoins(rootQuery, rootFilter, collection);
|
|
156
156
|
addWhereClauses(knex, rootQuery, rootFilter, collection);
|
|
157
157
|
return rootQuery;
|
|
158
|
+
function isNegativeOperator(operator) {
|
|
159
|
+
return operator.indexOf('_n') === 0;
|
|
160
|
+
}
|
|
158
161
|
function addJoins(dbQuery, filter, collection) {
|
|
159
162
|
for (const [key, value] of Object.entries(filter)) {
|
|
160
163
|
if (key === '_or' || key === '_and') {
|
|
@@ -182,8 +185,33 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
187
|
}
|
|
188
|
+
function callbackSubqueryRelation(relation, value) {
|
|
189
|
+
return function (subQueryKnex) {
|
|
190
|
+
const field = relation.field;
|
|
191
|
+
const collection = relation.collection;
|
|
192
|
+
const column = `${collection}.${field}`;
|
|
193
|
+
subQueryKnex.from(collection).whereRaw(`${field} = ${column}`);
|
|
194
|
+
applyQuery(knex, relation.collection, subQueryKnex, {
|
|
195
|
+
filter: value,
|
|
196
|
+
}, schema, true);
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function inverseFilters(value) {
|
|
200
|
+
for (const field in value) {
|
|
201
|
+
for (const operator in value[field]) {
|
|
202
|
+
let inverseOperator = operator;
|
|
203
|
+
if (isNegativeOperator(operator)) {
|
|
204
|
+
inverseOperator = '_' + operator.substring(2);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
inverseOperator = '_n' + operator.substring(1);
|
|
208
|
+
}
|
|
209
|
+
value[field][inverseOperator] = value[field][operator];
|
|
210
|
+
delete value[field][operator];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
185
214
|
function addWhereClauses(knex, dbQuery, filter, collection, logical = 'and') {
|
|
186
|
-
var _a, _b;
|
|
187
215
|
for (const [key, value] of Object.entries(filter)) {
|
|
188
216
|
if (key === '_or' || key === '_and') {
|
|
189
217
|
// If the _or array contains an empty object (full permissions), we should short-circuit and ignore all other
|
|
@@ -224,24 +252,12 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
224
252
|
if (relationType === 'o2a') {
|
|
225
253
|
pkField = knex.raw(`CAST(?? AS CHAR(255))`, [pkField]);
|
|
226
254
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const column = `${collection}.${field}`;
|
|
231
|
-
subQueryKnex
|
|
232
|
-
.select({ [field]: column })
|
|
233
|
-
.from(collection)
|
|
234
|
-
.whereNotNull(column);
|
|
235
|
-
applyQuery(knex, relation.collection, subQueryKnex, { filter }, schema, true);
|
|
236
|
-
};
|
|
237
|
-
if (((_a = Object.keys(value)) === null || _a === void 0 ? void 0 : _a[0]) === '_none') {
|
|
238
|
-
dbQuery[logical].whereNotIn(pkField, subQueryBuilder(Object.values(value)[0]));
|
|
239
|
-
}
|
|
240
|
-
else if (((_b = Object.keys(value)) === null || _b === void 0 ? void 0 : _b[0]) === '_some') {
|
|
241
|
-
dbQuery[logical].whereIn(pkField, subQueryBuilder(Object.values(value)[0]));
|
|
255
|
+
if (isNegativeOperator(filterOperator)) {
|
|
256
|
+
inverseFilters(value);
|
|
257
|
+
dbQuery[logical].whereNotExists(callbackSubqueryRelation(relation, value));
|
|
242
258
|
}
|
|
243
259
|
else {
|
|
244
|
-
dbQuery[logical].
|
|
260
|
+
dbQuery[logical].whereExists(callbackSubqueryRelation(relation, value));
|
|
245
261
|
}
|
|
246
262
|
}
|
|
247
263
|
}
|
|
@@ -251,32 +267,6 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
251
267
|
const selectionRaw = (0, get_column_1.getColumn)(knex, table, column, false, schema);
|
|
252
268
|
// Knex supports "raw" in the columnName parameter, but isn't typed as such. Too bad..
|
|
253
269
|
// See https://github.com/knex/knex/issues/4518 @TODO remove as any once knex is updated
|
|
254
|
-
// These operators don't rely on a value, and can thus be used without one (eg `?filter[field][_null]`)
|
|
255
|
-
if (operator === '_null' || (operator === '_nnull' && compareValue === false)) {
|
|
256
|
-
dbQuery[logical].whereNull(selectionRaw);
|
|
257
|
-
}
|
|
258
|
-
if (operator === '_nnull' || (operator === '_null' && compareValue === false)) {
|
|
259
|
-
dbQuery[logical].whereNotNull(selectionRaw);
|
|
260
|
-
}
|
|
261
|
-
if (operator === '_empty' || (operator === '_nempty' && compareValue === false)) {
|
|
262
|
-
dbQuery[logical].andWhere((query) => {
|
|
263
|
-
query.where(key, '=', '');
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
if (operator === '_nempty' || (operator === '_empty' && compareValue === false)) {
|
|
267
|
-
dbQuery[logical].andWhere((query) => {
|
|
268
|
-
query.where(key, '!=', '');
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
// Cast filter value (compareValue) based on function used
|
|
272
|
-
if (column.includes('(') && column.includes(')')) {
|
|
273
|
-
const functionName = column.split('(')[0];
|
|
274
|
-
const type = (0, utils_1.getOutputTypeForFunction)(functionName);
|
|
275
|
-
if (['bigInteger', 'integer', 'float', 'decimal'].includes(type)) {
|
|
276
|
-
compareValue = Number(compareValue);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
// Cast filter value (compareValue) based on type of field being filtered against
|
|
280
270
|
const [collection, field] = key.split('.');
|
|
281
271
|
if (collection in schema.collections && field in schema.collections[collection].fields) {
|
|
282
272
|
const type = schema.collections[collection].fields[field].type;
|
|
@@ -308,81 +298,16 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
308
298
|
// We need to remove any undefined values, as they are useless
|
|
309
299
|
compareValue = compareValue.filter((val) => val !== undefined);
|
|
310
300
|
}
|
|
311
|
-
if (operator
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
dbQuery[logical].where(selectionRaw, 'like', `%${compareValue}%`);
|
|
319
|
-
}
|
|
320
|
-
if (operator === '_ncontains') {
|
|
321
|
-
dbQuery[logical].whereNot(selectionRaw, 'like', `%${compareValue}%`);
|
|
322
|
-
}
|
|
323
|
-
if (operator === '_starts_with') {
|
|
324
|
-
dbQuery[logical].where(key, 'like', `${compareValue}%`);
|
|
325
|
-
}
|
|
326
|
-
if (operator === '_nstarts_with') {
|
|
327
|
-
dbQuery[logical].whereNot(key, 'like', `${compareValue}%`);
|
|
328
|
-
}
|
|
329
|
-
if (operator === '_ends_with') {
|
|
330
|
-
dbQuery[logical].where(key, 'like', `%${compareValue}`);
|
|
331
|
-
}
|
|
332
|
-
if (operator === '_nends_with') {
|
|
333
|
-
dbQuery[logical].whereNot(key, 'like', `%${compareValue}`);
|
|
334
|
-
}
|
|
335
|
-
if (operator === '_gt') {
|
|
336
|
-
dbQuery[logical].where(selectionRaw, '>', compareValue);
|
|
337
|
-
}
|
|
338
|
-
if (operator === '_gte') {
|
|
339
|
-
dbQuery[logical].where(selectionRaw, '>=', compareValue);
|
|
340
|
-
}
|
|
341
|
-
if (operator === '_lt') {
|
|
342
|
-
dbQuery[logical].where(selectionRaw, '<', compareValue);
|
|
343
|
-
}
|
|
344
|
-
if (operator === '_lte') {
|
|
345
|
-
dbQuery[logical].where(selectionRaw, '<=', compareValue);
|
|
346
|
-
}
|
|
347
|
-
if (operator === '_in') {
|
|
348
|
-
let value = compareValue;
|
|
349
|
-
if (typeof value === 'string')
|
|
350
|
-
value = value.split(',');
|
|
351
|
-
dbQuery[logical].whereIn(selectionRaw, value);
|
|
352
|
-
}
|
|
353
|
-
if (operator === '_nin') {
|
|
354
|
-
let value = compareValue;
|
|
355
|
-
if (typeof value === 'string')
|
|
356
|
-
value = value.split(',');
|
|
357
|
-
dbQuery[logical].whereNotIn(selectionRaw, value);
|
|
358
|
-
}
|
|
359
|
-
if (operator === '_between') {
|
|
360
|
-
if (compareValue.length !== 2)
|
|
361
|
-
return;
|
|
362
|
-
let value = compareValue;
|
|
363
|
-
if (typeof value === 'string')
|
|
364
|
-
value = value.split(',');
|
|
365
|
-
dbQuery[logical].whereBetween(selectionRaw, value);
|
|
366
|
-
}
|
|
367
|
-
if (operator === '_nbetween') {
|
|
368
|
-
if (compareValue.length !== 2)
|
|
369
|
-
return;
|
|
370
|
-
let value = compareValue;
|
|
371
|
-
if (typeof value === 'string')
|
|
372
|
-
value = value.split(',');
|
|
373
|
-
dbQuery[logical].whereNotBetween(selectionRaw, value);
|
|
374
|
-
}
|
|
375
|
-
if (operator == '_intersects') {
|
|
376
|
-
dbQuery[logical].whereRaw(helpers.st.intersects(key, compareValue));
|
|
377
|
-
}
|
|
378
|
-
if (operator == '_nintersects') {
|
|
379
|
-
dbQuery[logical].whereRaw(helpers.st.nintersects(key, compareValue));
|
|
380
|
-
}
|
|
381
|
-
if (operator == '_intersects_bbox') {
|
|
382
|
-
dbQuery[logical].whereRaw(helpers.st.intersects_bbox(key, compareValue));
|
|
301
|
+
if (operator in operators_1.default) {
|
|
302
|
+
operators_1.default[operator].apply({
|
|
303
|
+
query: dbQuery[logical],
|
|
304
|
+
helpers,
|
|
305
|
+
selectionRaw,
|
|
306
|
+
compareValue,
|
|
307
|
+
});
|
|
383
308
|
}
|
|
384
|
-
|
|
385
|
-
|
|
309
|
+
else {
|
|
310
|
+
throw new Error(`Operator ${operator} not supported`);
|
|
386
311
|
}
|
|
387
312
|
}
|
|
388
313
|
}
|
|
@@ -418,6 +343,9 @@ function applyAggregate(dbQuery, aggregate, collection) {
|
|
|
418
343
|
if (operation === 'avgDistinct') {
|
|
419
344
|
dbQuery.avgDistinct(`${collection}.${field}`, { as: `avgDistinct->${field}` });
|
|
420
345
|
}
|
|
346
|
+
if (operation === 'countAll') {
|
|
347
|
+
dbQuery.count('*', { as: 'countAll' });
|
|
348
|
+
}
|
|
421
349
|
if (operation === 'count') {
|
|
422
350
|
if (field === '*') {
|
|
423
351
|
dbQuery.count('*', { as: 'count' });
|