directus 9.14.5 → 9.16.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 (129) hide show
  1. package/dist/__utils__/items-utils.d.ts +2 -0
  2. package/dist/__utils__/items-utils.js +36 -0
  3. package/dist/__utils__/schemas.d.ts +13 -0
  4. package/dist/__utils__/schemas.js +304 -0
  5. package/dist/__utils__/snapshots.d.ts +5 -0
  6. package/dist/__utils__/snapshots.js +897 -0
  7. package/dist/app.js +2 -1
  8. package/dist/auth/drivers/ldap.js +18 -8
  9. package/dist/auth/drivers/oauth2.js +19 -9
  10. package/dist/auth/drivers/openid.js +19 -9
  11. package/dist/cache.d.ts +3 -0
  12. package/dist/cache.js +21 -2
  13. package/dist/cli/index.test.d.ts +1 -0
  14. package/dist/cli/index.test.js +58 -0
  15. package/dist/cli/utils/create-env/env-stub.liquid +2 -2
  16. package/dist/constants.d.ts +1 -0
  17. package/dist/constants.js +2 -1
  18. package/dist/controllers/assets.js +27 -1
  19. package/dist/controllers/extensions.js +3 -2
  20. package/dist/controllers/files.test.d.ts +1 -0
  21. package/dist/controllers/files.test.js +49 -0
  22. package/dist/controllers/server.js +0 -1
  23. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +3 -14
  24. package/dist/database/helpers/schema/dialects/cockroachdb.js +2 -8
  25. package/dist/database/helpers/schema/dialects/oracle.d.ts +3 -10
  26. package/dist/database/helpers/schema/dialects/oracle.js +2 -5
  27. package/dist/database/helpers/schema/types.d.ts +8 -18
  28. package/dist/database/helpers/schema/types.js +7 -36
  29. package/dist/database/migrations/20201105B-change-webhook-url-type.js +3 -2
  30. package/dist/database/migrations/20210312A-webhooks-collections-text.js +3 -2
  31. package/dist/database/migrations/20210415A-make-filesize-nullable.js +2 -2
  32. package/dist/database/migrations/20210510A-restructure-relations.js +3 -3
  33. package/dist/database/migrations/20210903A-add-auth-provider.js +2 -2
  34. package/dist/database/migrations/20210907A-webhooks-collections-not-null.js +4 -2
  35. package/dist/database/migrations/20210920A-webhooks-url-not-null.js +2 -2
  36. package/dist/database/migrations/20220303A-remove-default-project-color.js +2 -2
  37. package/dist/database/migrations/20220325B-add-default-language.js +2 -2
  38. package/dist/database/migrations/20220402A-remove-default-value-panel-icon.js +2 -2
  39. package/dist/database/migrations/20220801A-update-notifications-timestamp-column.d.ts +3 -0
  40. package/dist/database/migrations/20220801A-update-notifications-timestamp-column.js +19 -0
  41. package/dist/database/migrations/20220802A-add-custom-aspect-ratios.d.ts +3 -0
  42. package/dist/database/migrations/20220802A-add-custom-aspect-ratios.js +15 -0
  43. package/dist/database/migrations/run.test.d.ts +1 -0
  44. package/dist/database/migrations/run.test.js +92 -0
  45. package/dist/database/system-data/fields/settings.yaml +33 -0
  46. package/dist/env.js +8 -0
  47. package/dist/env.test.d.ts +8 -0
  48. package/dist/env.test.js +39 -0
  49. package/dist/extensions.d.ts +2 -2
  50. package/dist/extensions.js +10 -9
  51. package/dist/flows.js +2 -1
  52. package/dist/logger.js +0 -1
  53. package/dist/middleware/authenticate.test.d.ts +1 -0
  54. package/dist/middleware/authenticate.test.js +174 -0
  55. package/dist/middleware/cache.js +3 -3
  56. package/dist/middleware/extract-token.test.d.ts +1 -0
  57. package/dist/middleware/extract-token.test.js +60 -0
  58. package/dist/middleware/respond.js +2 -2
  59. package/dist/operations/exec/index.d.ts +5 -0
  60. package/dist/operations/exec/index.js +26 -0
  61. package/dist/operations/exec/index.test.d.ts +1 -0
  62. package/dist/operations/exec/index.test.js +95 -0
  63. package/dist/operations/item-create/index.js +1 -1
  64. package/dist/operations/item-delete/index.js +2 -2
  65. package/dist/operations/item-read/index.js +2 -2
  66. package/dist/operations/item-update/index.js +3 -3
  67. package/dist/operations/notification/index.js +9 -6
  68. package/dist/operations/request/index.js +22 -3
  69. package/dist/services/assets.d.ts +1 -1
  70. package/dist/services/assets.js +7 -2
  71. package/dist/services/authorization.js +2 -1
  72. package/dist/services/files.js +3 -2
  73. package/dist/services/files.test.d.ts +1 -0
  74. package/dist/services/files.test.js +53 -0
  75. package/dist/services/flows.js +4 -0
  76. package/dist/services/graphql/index.d.ts +2 -2
  77. package/dist/services/graphql/index.js +59 -40
  78. package/dist/services/graphql/types/hash.d.ts +2 -0
  79. package/dist/services/graphql/types/hash.js +9 -0
  80. package/dist/services/items.js +83 -39
  81. package/dist/services/items.test.d.ts +1 -0
  82. package/dist/services/items.test.js +765 -0
  83. package/dist/services/payload.d.ts +7 -4
  84. package/dist/services/payload.js +35 -8
  85. package/dist/services/payload.test.d.ts +1 -0
  86. package/dist/services/payload.test.js +94 -0
  87. package/dist/services/server.js +5 -3
  88. package/dist/services/specifications.js +1 -6
  89. package/dist/services/specifications.test.d.ts +1 -0
  90. package/dist/services/specifications.test.js +96 -0
  91. package/dist/types/items.d.ts +11 -0
  92. package/dist/utils/apply-query.js +15 -0
  93. package/dist/utils/apply-snapshot.js +15 -0
  94. package/dist/utils/apply-snapshot.test.d.ts +1 -0
  95. package/dist/utils/apply-snapshot.test.js +305 -0
  96. package/dist/utils/calculate-field-depth.test.d.ts +1 -0
  97. package/dist/utils/calculate-field-depth.test.js +76 -0
  98. package/dist/utils/compress.d.ts +3 -0
  99. package/dist/utils/compress.js +17 -0
  100. package/dist/utils/filter-items.test.d.ts +1 -0
  101. package/dist/utils/filter-items.test.js +60 -0
  102. package/dist/utils/get-ast-from-query.js +1 -1
  103. package/dist/utils/get-cache-key.test.d.ts +1 -0
  104. package/dist/utils/get-cache-key.test.js +53 -0
  105. package/dist/utils/get-column-path.test.d.ts +1 -0
  106. package/dist/utils/get-column-path.test.js +136 -0
  107. package/dist/utils/get-config-from-env.test.d.ts +1 -0
  108. package/dist/utils/get-config-from-env.test.js +19 -0
  109. package/dist/utils/get-graphql-type.d.ts +1 -1
  110. package/dist/utils/get-graphql-type.js +7 -1
  111. package/dist/utils/get-os-info.d.ts +9 -0
  112. package/dist/utils/get-os-info.js +47 -0
  113. package/dist/utils/get-permissions.js +2 -2
  114. package/dist/utils/get-relation-info.test.d.ts +1 -0
  115. package/dist/utils/get-relation-info.test.js +88 -0
  116. package/dist/utils/get-relation-type.test.d.ts +1 -0
  117. package/dist/utils/get-relation-type.test.js +69 -0
  118. package/dist/utils/get-schema.js +1 -2
  119. package/dist/utils/get-string-byte-size.test.d.ts +1 -0
  120. package/dist/utils/get-string-byte-size.test.js +8 -0
  121. package/dist/utils/is-directus-jwt.test.d.ts +1 -0
  122. package/dist/utils/is-directus-jwt.test.js +26 -0
  123. package/dist/utils/jwt.test.d.ts +1 -0
  124. package/dist/utils/jwt.test.js +36 -0
  125. package/dist/utils/merge-permissions.test.d.ts +1 -0
  126. package/dist/utils/merge-permissions.test.js +80 -0
  127. package/dist/utils/validate-keys.test.d.ts +1 -0
  128. package/dist/utils/validate-keys.test.js +97 -0
  129. package/package.json +12 -11
@@ -1,7 +1,7 @@
1
1
  import { Accountability, SchemaOverview } from '@directus/shared/types';
2
2
  import { Knex } from 'knex';
3
3
  import { Helpers } from '../database/helpers';
4
- import { AbstractServiceOptions, Item, PrimaryKey } from '../types';
4
+ import { AbstractServiceOptions, ActionEventParams, Item, MutationOptions, PrimaryKey } from '../types';
5
5
  declare type Action = 'create' | 'read' | 'update';
6
6
  declare type Transformers = {
7
7
  [type: string]: (context: {
@@ -44,22 +44,25 @@ export declare class PayloadService {
44
44
  /**
45
45
  * Recursively save/update all nested related Any-to-One items
46
46
  */
47
- processA2O(data: Partial<Item>): Promise<{
47
+ processA2O(data: Partial<Item>, opts?: MutationOptions): Promise<{
48
48
  payload: Partial<Item>;
49
49
  revisions: PrimaryKey[];
50
+ nestedActionEvents: ActionEventParams[];
50
51
  }>;
51
52
  /**
52
53
  * Save/update all nested related m2o items inside the payload
53
54
  */
54
- processM2O(data: Partial<Item>): Promise<{
55
+ processM2O(data: Partial<Item>, opts?: MutationOptions): Promise<{
55
56
  payload: Partial<Item>;
56
57
  revisions: PrimaryKey[];
58
+ nestedActionEvents: ActionEventParams[];
57
59
  }>;
58
60
  /**
59
61
  * Recursively save/update all nested related o2m items
60
62
  */
61
- processO2M(data: Partial<Item>, parent: PrimaryKey): Promise<{
63
+ processO2M(data: Partial<Item>, parent: PrimaryKey, opts?: MutationOptions): Promise<{
62
64
  revisions: PrimaryKey[];
65
+ nestedActionEvents: ActionEventParams[];
63
66
  }>;
64
67
  /**
65
68
  * Transforms the input partial payload to match the output structure, to have consistency
@@ -300,12 +300,13 @@ class PayloadService {
300
300
  /**
301
301
  * Recursively save/update all nested related Any-to-One items
302
302
  */
303
- async processA2O(data) {
303
+ async processA2O(data, opts) {
304
304
  var _a, _b;
305
305
  const relations = this.schema.relations.filter((relation) => {
306
306
  return relation.collection === this.collection;
307
307
  });
308
308
  const revisions = [];
309
+ const nestedActionEvents = [];
309
310
  const payload = (0, lodash_1.cloneDeep)(data);
310
311
  // Only process related records that are actually in the payload
311
312
  const relationsToProcess = relations.filter((relation) => {
@@ -345,26 +346,31 @@ class PayloadService {
345
346
  if (Object.keys(fieldsToUpdate).length > 0) {
346
347
  await itemsService.updateOne(relatedPrimaryKey, relatedRecord, {
347
348
  onRevisionCreate: (pk) => revisions.push(pk),
349
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
350
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
348
351
  });
349
352
  }
350
353
  }
351
354
  else {
352
355
  relatedPrimaryKey = await itemsService.createOne(relatedRecord, {
353
356
  onRevisionCreate: (pk) => revisions.push(pk),
357
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
358
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
354
359
  });
355
360
  }
356
361
  // Overwrite the nested object with just the primary key, so the parent level can be saved correctly
357
362
  payload[relation.field] = relatedPrimaryKey;
358
363
  }
359
- return { payload, revisions };
364
+ return { payload, revisions, nestedActionEvents };
360
365
  }
361
366
  /**
362
367
  * Save/update all nested related m2o items inside the payload
363
368
  */
364
- async processM2O(data) {
369
+ async processM2O(data, opts) {
365
370
  const payload = (0, lodash_1.cloneDeep)(data);
366
371
  // All the revisions saved on this level
367
372
  const revisions = [];
373
+ const nestedActionEvents = [];
368
374
  // Many to one relations that exist on the current collection
369
375
  const relations = this.schema.relations.filter((relation) => {
370
376
  return relation.collection === this.collection;
@@ -400,24 +406,29 @@ class PayloadService {
400
406
  if (Object.keys(fieldsToUpdate).length > 0) {
401
407
  await itemsService.updateOne(relatedPrimaryKey, relatedRecord, {
402
408
  onRevisionCreate: (pk) => revisions.push(pk),
409
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
410
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
403
411
  });
404
412
  }
405
413
  }
406
414
  else {
407
415
  relatedPrimaryKey = await itemsService.createOne(relatedRecord, {
408
416
  onRevisionCreate: (pk) => revisions.push(pk),
417
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
418
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
409
419
  });
410
420
  }
411
421
  // Overwrite the nested object with just the primary key, so the parent level can be saved correctly
412
422
  payload[relation.field] = relatedPrimaryKey;
413
423
  }
414
- return { payload, revisions };
424
+ return { payload, revisions, nestedActionEvents };
415
425
  }
416
426
  /**
417
427
  * Recursively save/update all nested related o2m items
418
428
  */
419
- async processO2M(data, parent) {
429
+ async processO2M(data, parent, opts) {
420
430
  const revisions = [];
431
+ const nestedActionEvents = [];
421
432
  const relations = this.schema.relations.filter((relation) => {
422
433
  return relation.related_collection === this.collection;
423
434
  });
@@ -485,6 +496,8 @@ class PayloadService {
485
496
  }
486
497
  savedPrimaryKeys.push(...(await itemsService.upsertMany(recordsToUpsert, {
487
498
  onRevisionCreate: (pk) => revisions.push(pk),
499
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
500
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
488
501
  })));
489
502
  const query = {
490
503
  filter: {
@@ -505,11 +518,16 @@ class PayloadService {
505
518
  // Nullify all related items that aren't included in the current payload
506
519
  if (relation.meta.one_deselect_action === 'delete') {
507
520
  // There's no revision for a deletion
508
- await itemsService.deleteByQuery(query);
521
+ await itemsService.deleteByQuery(query, {
522
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
523
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
524
+ });
509
525
  }
510
526
  else {
511
527
  await itemsService.updateByQuery(query, { [relation.field]: null }, {
512
528
  onRevisionCreate: (pk) => revisions.push(pk),
529
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
530
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
513
531
  });
514
532
  }
515
533
  }
@@ -525,6 +543,8 @@ class PayloadService {
525
543
  [relation.field]: parent || payload[currentPrimaryKeyField],
526
544
  })), {
527
545
  onRevisionCreate: (pk) => revisions.push(pk),
546
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
547
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
528
548
  });
529
549
  }
530
550
  if (alterations.update) {
@@ -535,6 +555,8 @@ class PayloadService {
535
555
  [relation.field]: parent || payload[currentPrimaryKeyField],
536
556
  }, {
537
557
  onRevisionCreate: (pk) => revisions.push(pk),
558
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
559
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
538
560
  });
539
561
  }
540
562
  }
@@ -556,17 +578,22 @@ class PayloadService {
556
578
  },
557
579
  };
558
580
  if (relation.meta.one_deselect_action === 'delete') {
559
- await itemsService.deleteByQuery(query);
581
+ await itemsService.deleteByQuery(query, {
582
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
583
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
584
+ });
560
585
  }
561
586
  else {
562
587
  await itemsService.updateByQuery(query, { [relation.field]: null }, {
563
588
  onRevisionCreate: (pk) => revisions.push(pk),
589
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
590
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
564
591
  });
565
592
  }
566
593
  }
567
594
  }
568
595
  }
569
- return { revisions };
596
+ return { revisions, nestedActionEvents };
570
597
  }
571
598
  /**
572
599
  * Transforms the input partial payload to match the output structure, to have consistency
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const knex_1 = __importDefault(require("knex"));
7
+ const knex_mock_client_1 = require("knex-mock-client");
8
+ const services_1 = require("../../src/services");
9
+ const helpers_1 = require("../../src/database/helpers");
10
+ jest.mock('../../src/database/index', () => {
11
+ return { getDatabaseClient: jest.fn().mockReturnValue('postgres') };
12
+ });
13
+ jest.requireMock('../../src/database/index');
14
+ describe('Integration Tests', () => {
15
+ let db;
16
+ let tracker;
17
+ beforeAll(async () => {
18
+ db = (0, knex_1.default)({ client: knex_mock_client_1.MockClient });
19
+ tracker = (0, knex_mock_client_1.getTracker)();
20
+ });
21
+ afterEach(() => {
22
+ tracker.reset();
23
+ });
24
+ describe('Services / PayloadService', () => {
25
+ describe('transformers', () => {
26
+ let service;
27
+ let helpers;
28
+ beforeEach(() => {
29
+ service = new services_1.PayloadService('test', {
30
+ knex: db,
31
+ schema: { collections: {}, relations: [] },
32
+ });
33
+ helpers = (0, helpers_1.getHelpers)(db);
34
+ });
35
+ describe('csv', () => {
36
+ it('Returns undefined for illegal values', async () => {
37
+ const result = await service.transformers['cast-csv']({
38
+ value: 123,
39
+ action: 'read',
40
+ payload: {},
41
+ accountability: { role: null },
42
+ specials: [],
43
+ helpers,
44
+ });
45
+ expect(result).toBe(undefined);
46
+ });
47
+ it('Returns [] for empty strings', async () => {
48
+ const result = await service.transformers['cast-csv']({
49
+ value: '',
50
+ action: 'read',
51
+ payload: {},
52
+ accountability: { role: null },
53
+ specials: [],
54
+ helpers,
55
+ });
56
+ expect(result).toMatchObject([]);
57
+ });
58
+ it('Splits the CSV string', async () => {
59
+ const result = await service.transformers['cast-csv']({
60
+ value: 'test,directus',
61
+ action: 'read',
62
+ payload: {},
63
+ accountability: { role: null },
64
+ specials: [],
65
+ helpers,
66
+ });
67
+ expect(result).toMatchObject(['test', 'directus']);
68
+ });
69
+ it('Saves array values as joined string', async () => {
70
+ const result = await service.transformers['cast-csv']({
71
+ value: ['test', 'directus'],
72
+ action: 'create',
73
+ payload: {},
74
+ accountability: { role: null },
75
+ specials: [],
76
+ helpers,
77
+ });
78
+ expect(result).toBe('test,directus');
79
+ });
80
+ it('Saves string values as is', async () => {
81
+ const result = await service.transformers['cast-csv']({
82
+ value: 'test,directus',
83
+ action: 'create',
84
+ payload: {},
85
+ accountability: { role: null },
86
+ specials: [],
87
+ helpers,
88
+ });
89
+ expect(result).toBe('test,directus');
90
+ });
91
+ });
92
+ });
93
+ });
94
+ });
@@ -28,7 +28,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.ServerService = void 0;
30
30
  const lodash_1 = require("lodash");
31
- const macos_release_1 = __importDefault(require("macos-release"));
32
31
  const nanoid_1 = require("nanoid");
33
32
  const os_1 = __importDefault(require("os"));
34
33
  const perf_hooks_1 = require("perf_hooks");
@@ -43,6 +42,7 @@ const storage_1 = __importDefault(require("../storage"));
43
42
  const utils_1 = require("@directus/shared/utils");
44
43
  const mailer_1 = __importDefault(require("../mailer"));
45
44
  const settings_1 = require("./settings");
45
+ const get_os_info_1 = require("../utils/get-os-info");
46
46
  class ServerService {
47
47
  constructor(options) {
48
48
  this.knex = options.knex || (0, database_1.default)();
@@ -77,10 +77,12 @@ class ServerService {
77
77
  else {
78
78
  info.rateLimit = false;
79
79
  }
80
+ info.flows = {
81
+ execAllowedModules: env_1.default.FLOWS_EXEC_ALLOWED_MODULES ? (0, utils_1.toArray)(env_1.default.FLOWS_EXEC_ALLOWED_MODULES) : [],
82
+ };
80
83
  }
81
84
  if (((_b = this.accountability) === null || _b === void 0 ? void 0 : _b.admin) === true) {
82
- const osType = os_1.default.type() === 'Darwin' ? 'macOS' : os_1.default.type();
83
- const osVersion = osType === 'macOS' ? `${(0, macos_release_1.default)().name} (${(0, macos_release_1.default)().version})` : os_1.default.release();
85
+ const { osType, osVersion } = (0, get_os_info_1.getOSInfo)();
84
86
  info.directus = {
85
87
  version: package_json_1.version,
86
88
  };
@@ -77,12 +77,7 @@ class OASSpecsService {
77
77
  integer: {
78
78
  type: 'integer',
79
79
  },
80
- json: {
81
- type: 'array',
82
- items: {
83
- type: 'string',
84
- },
85
- },
80
+ json: {},
86
81
  string: {
87
82
  type: 'string',
88
83
  },
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const knex_1 = __importDefault(require("knex"));
7
+ const knex_mock_client_1 = require("knex-mock-client");
8
+ const services_1 = require("../../src/services");
9
+ jest.mock('../../src/database/index', () => {
10
+ return { getDatabaseClient: jest.fn().mockReturnValue('postgres') };
11
+ });
12
+ jest.requireMock('../../src/database/index');
13
+ class Client_PG extends knex_mock_client_1.MockClient {
14
+ }
15
+ describe('Integration Tests', () => {
16
+ let db;
17
+ let tracker;
18
+ beforeAll(async () => {
19
+ db = (0, knex_1.default)({ client: Client_PG });
20
+ tracker = (0, knex_mock_client_1.getTracker)();
21
+ });
22
+ afterEach(() => {
23
+ tracker.reset();
24
+ jest.clearAllMocks();
25
+ });
26
+ describe('Services / Specifications', () => {
27
+ describe('oas', () => {
28
+ describe('generate', () => {
29
+ let service;
30
+ beforeEach(() => {
31
+ service = new services_1.SpecificationService({
32
+ knex: db,
33
+ schema: { collections: {}, relations: [] },
34
+ });
35
+ });
36
+ it('returns untyped schema for json fields', async () => {
37
+ var _a;
38
+ jest.spyOn(services_1.CollectionsService.prototype, 'readByQuery').mockImplementation(jest.fn().mockReturnValue([
39
+ {
40
+ collection: 'test_table',
41
+ meta: {
42
+ accountability: 'all',
43
+ collection: 'test_table',
44
+ group: null,
45
+ hidden: false,
46
+ icon: null,
47
+ item_duplication_fields: null,
48
+ note: null,
49
+ singleton: false,
50
+ translations: null,
51
+ },
52
+ schema: {
53
+ name: 'test_table',
54
+ },
55
+ },
56
+ ]));
57
+ jest.spyOn(services_1.FieldsService.prototype, 'readAll').mockImplementation(jest.fn().mockReturnValue([
58
+ {
59
+ collection: 'test_table',
60
+ field: 'id',
61
+ type: 'integer',
62
+ schema: {
63
+ is_nullable: false,
64
+ },
65
+ },
66
+ {
67
+ collection: 'test_table',
68
+ field: 'blob',
69
+ type: 'json',
70
+ schema: {
71
+ is_nullable: true,
72
+ },
73
+ },
74
+ ]));
75
+ jest.spyOn(services_1.RelationsService.prototype, 'readAll').mockImplementation(jest.fn().mockReturnValue([]));
76
+ const spec = await service.oas.generate();
77
+ expect((_a = spec.components) === null || _a === void 0 ? void 0 : _a.schemas).toEqual({
78
+ ItemsTestTable: {
79
+ type: 'object',
80
+ properties: {
81
+ id: {
82
+ nullable: false,
83
+ type: 'integer',
84
+ },
85
+ blob: {
86
+ nullable: true,
87
+ },
88
+ },
89
+ 'x-collection': 'test_table',
90
+ },
91
+ });
92
+ });
93
+ });
94
+ });
95
+ });
96
+ });
@@ -2,6 +2,7 @@
2
2
  * I know this looks a little silly, but it allows us to explicitly differentiate between when we're
3
3
  * expecting an item vs any other generic object.
4
4
  */
5
+ import { EventContext } from '@directus/shared/types';
5
6
  export declare type Item = Record<string, any>;
6
7
  export declare type PrimaryKey = string | number;
7
8
  export declare type Alterations = {
@@ -26,4 +27,14 @@ export declare type MutationOptions = {
26
27
  * Allow disabling the emitting of hooks. Useful if a custom hook is fired (like files.upload)
27
28
  */
28
29
  emitEvents?: boolean;
30
+ /**
31
+ * To bypass the emitting of action events if emitEvents is enabled
32
+ * Can be used to queue up the nested events from item service's create, update and delete
33
+ */
34
+ bypassEmitAction?: (params: ActionEventParams) => void;
35
+ };
36
+ export declare type ActionEventParams = {
37
+ event: string | string[];
38
+ meta: Record<string, any>;
39
+ context: EventContext;
29
40
  };
@@ -13,6 +13,7 @@ const get_column_1 = require("./get-column");
13
13
  const get_column_path_1 = require("./get-column-path");
14
14
  const get_relation_info_1 = require("./get-relation-info");
15
15
  const utils_1 = require("@directus/shared/utils");
16
+ const strip_function_1 = require("./strip-function");
16
17
  const generateAlias = (0, nanoid_1.customAlphabet)('abcdefghijklmnopqrstuvwxyz', 5);
17
18
  /**
18
19
  * Apply the Query to a given Knex query builder instance
@@ -211,9 +212,11 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
211
212
  const { columnPath, targetCollection } = (0, get_column_path_1.getColumnPath)({ path: filterPath, collection, relations, aliasMap });
212
213
  if (!columnPath)
213
214
  continue;
215
+ validateFilterOperator(schema.collections[targetCollection].fields[(0, strip_function_1.stripFunction)(filterPath[filterPath.length - 1])].type, filterOperator, schema.collections[targetCollection].fields[(0, strip_function_1.stripFunction)(filterPath[filterPath.length - 1])].special);
214
216
  applyFilterToQuery(columnPath, filterOperator, filterValue, logical, targetCollection);
215
217
  }
216
218
  else {
219
+ validateFilterOperator(schema.collections[collection].fields[(0, strip_function_1.stripFunction)(filterPath[0])].type, filterOperator, schema.collections[collection].fields[(0, strip_function_1.stripFunction)(filterPath[0])].special);
217
220
  applyFilterToQuery(`${collection}.${filterPath[0]}`, filterOperator, filterValue, logical);
218
221
  }
219
222
  }
@@ -245,6 +248,18 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
245
248
  }
246
249
  }
247
250
  }
251
+ function validateFilterOperator(type, filterOperator, special) {
252
+ if (filterOperator.startsWith('_')) {
253
+ filterOperator = filterOperator.slice(1);
254
+ }
255
+ if (!(0, utils_1.getFilterOperatorsForType)(type).includes(filterOperator)) {
256
+ throw new invalid_query_1.InvalidQueryException(`"${type}" field type does not contain the "_${filterOperator}" filter operator`);
257
+ }
258
+ if (special.includes('conceal') &&
259
+ !(0, utils_1.getFilterOperatorsForType)('hash').includes(filterOperator)) {
260
+ throw new invalid_query_1.InvalidQueryException(`Field with "conceal" special does not allow the "_${filterOperator}" filter operator`);
261
+ }
262
+ }
248
263
  function applyFilterToQuery(key, operator, compareValue, logical = 'and', originalCollectionName) {
249
264
  const [table, column] = key.split('.');
250
265
  // Is processed through Knex.Raw, so should be safe to string-inject into these where queries
@@ -64,6 +64,21 @@ async function applySnapshot(snapshot, options) {
64
64
  const deleteCollections = async (collections) => {
65
65
  for (const { collection, diff } of collections) {
66
66
  if ((diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'D') {
67
+ const relations = schema.relations.filter((r) => r.related_collection === collection || r.collection === collection);
68
+ if (relations.length > 0) {
69
+ const relationsService = new services_1.RelationsService({ knex: trx, schema });
70
+ for (const relation of relations) {
71
+ try {
72
+ await relationsService.deleteOne(relation.collection, relation.field);
73
+ }
74
+ catch (err) {
75
+ logger_1.default.error(`Failed to delete collection "${collection}" due to relation "${relation.collection}.${relation.field}"`);
76
+ throw err;
77
+ }
78
+ }
79
+ // clean up deleted relations from existing schema
80
+ schema.relations = schema.relations.filter((r) => r.related_collection !== collection && r.collection !== collection);
81
+ }
67
82
  await deleteCollections(getNestedCollectionsToDelete(collection));
68
83
  try {
69
84
  await collectionsService.deleteOne(collection);
@@ -0,0 +1 @@
1
+ export {};