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.
Files changed (117) hide show
  1. package/dist/auth/drivers/oauth2.d.ts +1 -1
  2. package/dist/auth/drivers/oauth2.js +14 -11
  3. package/dist/auth/drivers/openid.d.ts +1 -1
  4. package/dist/auth/drivers/openid.js +14 -11
  5. package/dist/cli/commands/schema/apply.js +4 -3
  6. package/dist/controllers/assets.js +8 -9
  7. package/dist/database/helpers/date/dialects/sqlite.js +6 -2
  8. package/dist/database/migrations/20210225A-add-relations-sort-field.js +2 -1
  9. package/dist/database/migrations/20210506A-rename-interfaces.js +2 -1
  10. package/dist/database/migrations/20210802A-replace-groups.js +2 -1
  11. package/dist/database/migrations/20210805A-update-groups.js +2 -1
  12. package/dist/database/migrations/20210805B-change-image-metadata-structure.js +3 -2
  13. package/dist/database/migrations/20211007A-update-presets.js +5 -4
  14. package/dist/database/run-ast.js +10 -14
  15. package/dist/env.js +2 -1
  16. package/dist/exceptions/index.d.ts +1 -0
  17. package/dist/exceptions/index.js +1 -0
  18. package/dist/exceptions/invalid-provider.d.ts +4 -0
  19. package/dist/exceptions/invalid-provider.js +10 -0
  20. package/dist/exceptions/range-not-satisfiable.d.ts +2 -2
  21. package/dist/exceptions/range-not-satisfiable.js +5 -1
  22. package/dist/middleware/graphql.js +2 -1
  23. package/dist/services/assets.js +27 -1
  24. package/dist/services/authentication.js +4 -1
  25. package/dist/services/fields.js +15 -8
  26. package/dist/services/graphql.js +49 -32
  27. package/dist/services/import-export.d.ts +1 -1
  28. package/dist/services/import-export.js +13 -12
  29. package/dist/services/items.d.ts +3 -3
  30. package/dist/services/items.js +8 -1
  31. package/dist/services/payload.d.ts +2 -2
  32. package/dist/services/payload.js +8 -7
  33. package/dist/services/users.d.ts +4 -0
  34. package/dist/services/users.js +20 -0
  35. package/dist/utils/{apply-query.d.ts → apply-query/index.d.ts} +0 -0
  36. package/dist/utils/{apply-query.js → apply-query/index.js} +53 -125
  37. package/dist/utils/apply-query/operators/between.operator.d.ts +2 -0
  38. package/dist/utils/apply-query/operators/between.operator.js +16 -0
  39. package/dist/utils/apply-query/operators/contains.operator.d.ts +2 -0
  40. package/dist/utils/apply-query/operators/contains.operator.js +9 -0
  41. package/dist/utils/apply-query/operators/ends-with.operator.d.ts +2 -0
  42. package/dist/utils/apply-query/operators/ends-with.operator.js +9 -0
  43. package/dist/utils/apply-query/operators/equals.operator.d.ts +2 -0
  44. package/dist/utils/apply-query/operators/equals.operator.js +9 -0
  45. package/dist/utils/apply-query/operators/greather-than-equals.operator.d.ts +2 -0
  46. package/dist/utils/apply-query/operators/greather-than-equals.operator.js +9 -0
  47. package/dist/utils/apply-query/operators/greather-than.operator.d.ts +2 -0
  48. package/dist/utils/apply-query/operators/greather-than.operator.js +9 -0
  49. package/dist/utils/apply-query/operators/in.operator.d.ts +2 -0
  50. package/dist/utils/apply-query/operators/in.operator.js +14 -0
  51. package/dist/utils/apply-query/operators/index.d.ts +3 -0
  52. package/dist/utils/apply-query/operators/index.js +72 -0
  53. package/dist/utils/apply-query/operators/insensitive-contains.operator.d.ts +2 -0
  54. package/dist/utils/apply-query/operators/insensitive-contains.operator.js +9 -0
  55. package/dist/utils/apply-query/operators/insensitive-ends-with.operator.d.ts +2 -0
  56. package/dist/utils/apply-query/operators/insensitive-ends-with.operator.js +9 -0
  57. package/dist/utils/apply-query/operators/insensitive-equals.operator.d.ts +2 -0
  58. package/dist/utils/apply-query/operators/insensitive-equals.operator.js +9 -0
  59. package/dist/utils/apply-query/operators/insensitive-not-contains.operator.d.ts +2 -0
  60. package/dist/utils/apply-query/operators/insensitive-not-contains.operator.js +9 -0
  61. package/dist/utils/apply-query/operators/insensitive-not-ends-with.operator.d.ts +2 -0
  62. package/dist/utils/apply-query/operators/insensitive-not-ends-with.operator.js +9 -0
  63. package/dist/utils/apply-query/operators/insensitive-not-equals.operator.d.ts +2 -0
  64. package/dist/utils/apply-query/operators/insensitive-not-equals.operator.js +9 -0
  65. package/dist/utils/apply-query/operators/insensitive-not-starts-with.operator.d.ts +2 -0
  66. package/dist/utils/apply-query/operators/insensitive-not-starts-with.operator.js +9 -0
  67. package/dist/utils/apply-query/operators/insensitive-starts-with.operator.d.ts +2 -0
  68. package/dist/utils/apply-query/operators/insensitive-starts-with.operator.js +9 -0
  69. package/dist/utils/apply-query/operators/intersects-bbox.operator.d.ts +2 -0
  70. package/dist/utils/apply-query/operators/intersects-bbox.operator.js +9 -0
  71. package/dist/utils/apply-query/operators/intersects.operator.d.ts +2 -0
  72. package/dist/utils/apply-query/operators/intersects.operator.js +9 -0
  73. package/dist/utils/apply-query/operators/is-empty.operator.d.ts +2 -0
  74. package/dist/utils/apply-query/operators/is-empty.operator.js +14 -0
  75. package/dist/utils/apply-query/operators/is-not-empty.operator.d.ts +2 -0
  76. package/dist/utils/apply-query/operators/is-not-empty.operator.js +14 -0
  77. package/dist/utils/apply-query/operators/is-not-null.operator.d.ts +2 -0
  78. package/dist/utils/apply-query/operators/is-not-null.operator.js +14 -0
  79. package/dist/utils/apply-query/operators/is-null.operator.d.ts +2 -0
  80. package/dist/utils/apply-query/operators/is-null.operator.js +14 -0
  81. package/dist/utils/apply-query/operators/less-than-equals.operator.d.ts +2 -0
  82. package/dist/utils/apply-query/operators/less-than-equals.operator.js +9 -0
  83. package/dist/utils/apply-query/operators/less-than.operator.d.ts +2 -0
  84. package/dist/utils/apply-query/operators/less-than.operator.js +9 -0
  85. package/dist/utils/apply-query/operators/not-between.operator.d.ts +2 -0
  86. package/dist/utils/apply-query/operators/not-between.operator.js +16 -0
  87. package/dist/utils/apply-query/operators/not-contains.operator.d.ts +2 -0
  88. package/dist/utils/apply-query/operators/not-contains.operator.js +9 -0
  89. package/dist/utils/apply-query/operators/not-ends-with.operator.d.ts +2 -0
  90. package/dist/utils/apply-query/operators/not-ends-with.operator.js +9 -0
  91. package/dist/utils/apply-query/operators/not-equals.operator.d.ts +2 -0
  92. package/dist/utils/apply-query/operators/not-equals.operator.js +9 -0
  93. package/dist/utils/apply-query/operators/not-in.operator.d.ts +2 -0
  94. package/dist/utils/apply-query/operators/not-in.operator.js +14 -0
  95. package/dist/utils/apply-query/operators/not-intersects-bbox.operator.d.ts +2 -0
  96. package/dist/utils/apply-query/operators/not-intersects-bbox.operator.js +9 -0
  97. package/dist/utils/apply-query/operators/not-intersects.operator.d.ts +2 -0
  98. package/dist/utils/apply-query/operators/not-intersects.operator.js +9 -0
  99. package/dist/utils/apply-query/operators/not-starts-with.operator.d.ts +2 -0
  100. package/dist/utils/apply-query/operators/not-starts-with.operator.js +9 -0
  101. package/dist/utils/apply-query/operators/operator-register.d.ts +13 -0
  102. package/dist/utils/apply-query/operators/operator-register.js +7 -0
  103. package/dist/utils/apply-query/operators/starts-with.operator.d.ts +2 -0
  104. package/dist/utils/apply-query/operators/starts-with.operator.js +9 -0
  105. package/dist/utils/apply-snapshot.d.ts +3 -3
  106. package/dist/utils/apply-snapshot.js +64 -49
  107. package/dist/utils/get-ast-from-query.js +1 -7
  108. package/dist/utils/get-default-value.js +4 -3
  109. package/dist/utils/get-permissions.d.ts +1 -1
  110. package/dist/utils/get-permissions.js +9 -8
  111. package/dist/utils/get-schema.js +2 -1
  112. package/dist/utils/get-snapshot.js +22 -4
  113. package/dist/utils/parse-json.d.ts +5 -0
  114. package/dist/utils/parse-json.js +19 -0
  115. package/dist/utils/sanitize-query.d.ts +1 -2
  116. package/dist/utils/sanitize-query.js +6 -5
  117. package/package.json +12 -12
@@ -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
- column = table.increments(field.field);
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);
@@ -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
- const hasAggregate = Object.values(collection.fields).some((field) => {
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 AggregatedFilters = {};
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
- AggregatedFilters[collection.collection] = schemaComposer.createObjectTC({
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
- AggregatedFunctions[collection.collection] = schemaComposer.createObjectTC({
702
- name: `${collection.collection}_aggregated`,
703
- fields: {
704
- group: {
705
- name: 'group',
706
- type: graphql_compose_1.GraphQLJSON,
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: AggregatedFilters[collection.collection],
727
+ type: AggregatedFields[collection.collection],
711
728
  },
712
729
  sum: {
713
730
  name: 'sum',
714
- type: AggregatedFilters[collection.collection],
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: AggregatedFilters[collection.collection],
735
+ type: AggregatedFields[collection.collection],
723
736
  },
724
737
  avgDistinct: {
725
738
  name: 'avgDistinct',
726
- type: AggregatedFilters[collection.collection],
739
+ type: AggregatedFields[collection.collection],
727
740
  },
728
741
  sumDistinct: {
729
742
  name: 'sumDistinct',
730
- type: AggregatedFilters[collection.collection],
743
+ type: AggregatedFields[collection.collection],
731
744
  },
732
745
  min: {
733
746
  name: 'min',
734
- type: AggregatedFilters[collection.collection],
747
+ type: AggregatedFields[collection.collection],
735
748
  },
736
749
  max: {
737
750
  name: 'max',
738
- type: AggregatedFilters[collection.collection],
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 database_1 = __importDefault(require("../database"));
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 lodash_1 = require("lodash");
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 fs_extra_1 = require("fs-extra");
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 files_1 = require("./files");
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 utils_1 = require("@directus/shared/utils");
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 = JSON.parse(value);
105
+ const parsedJson = (0, parse_json_1.parseJSON)(value);
105
106
  (0, lodash_1.set)(result, key, parsedJson);
106
107
  }
107
108
  catch {
@@ -1,7 +1,7 @@
1
- import { Knex } from 'knex';
1
+ import { Accountability, PermissionsAction, Query, SchemaOverview } from '@directus/shared/types';
2
2
  import Keyv from 'keyv';
3
- import { Accountability, Query, PermissionsAction, SchemaOverview } from '@directus/shared/types';
4
- import { AbstractService, AbstractServiceOptions, Item as AnyItem, PrimaryKey, MutationOptions } from '../types';
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;
@@ -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: {
@@ -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 wellknown_1 = require("wellknown");
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 JSON.parse(value);
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' ? JSON.parse(value) : value);
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) {
@@ -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
  */
@@ -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', {
@@ -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("../database/helpers");
12
- const exceptions_1 = require("../exceptions");
13
- const get_column_1 = require("./get-column");
14
- const get_column_path_1 = require("./get-column-path");
15
- const get_relation_info_1 = require("./get-relation-info");
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 exceptions_1.InvalidQueryException(`You have to provide a collection scope when sorting or filtering on a many-to-any item`);
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 exceptions_1.InvalidQueryException(`You have to provide a collection scope when sorting or filtering on a many-to-any item`);
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
- const subQueryBuilder = (filter) => (subQueryKnex) => {
228
- const field = relation.field;
229
- const collection = relation.collection;
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].whereIn(pkField, subQueryBuilder(value));
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 === '_eq') {
312
- dbQuery[logical].where(selectionRaw, '=', compareValue);
313
- }
314
- if (operator === '_neq') {
315
- dbQuery[logical].whereNot(selectionRaw, compareValue);
316
- }
317
- if (operator === '_contains') {
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
- if (operator == '_nintersects_bbox') {
385
- dbQuery[logical].whereRaw(helpers.st.nintersects_bbox(key, compareValue));
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' });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("./operator-register").OperatorRegister;
2
+ export default _default;