directus 9.15.1 → 9.17.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 (107) 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 +8 -7
  8. package/dist/auth/drivers/ldap.js +1 -0
  9. package/dist/auth/drivers/local.js +6 -0
  10. package/dist/auth/drivers/oauth2.js +1 -0
  11. package/dist/auth/drivers/openid.js +1 -0
  12. package/dist/cli/index.test.d.ts +1 -0
  13. package/dist/cli/index.test.js +58 -0
  14. package/dist/cli/utils/create-env/env-stub.liquid +6 -2
  15. package/dist/controllers/activity.js +1 -0
  16. package/dist/controllers/assets.js +20 -16
  17. package/dist/controllers/auth.js +6 -9
  18. package/dist/controllers/files.test.d.ts +1 -0
  19. package/dist/controllers/files.test.js +49 -0
  20. package/dist/controllers/server.js +0 -1
  21. package/dist/database/migrations/20220826A-add-origin-to-accountability.d.ts +3 -0
  22. package/dist/database/migrations/20220826A-add-origin-to-accountability.js +21 -0
  23. package/dist/database/migrations/run.test.d.ts +1 -0
  24. package/dist/database/migrations/run.test.js +92 -0
  25. package/dist/database/system-data/fields/activity.yaml +6 -0
  26. package/dist/database/system-data/fields/sessions.yaml +2 -0
  27. package/dist/env.js +15 -0
  28. package/dist/env.test.d.ts +8 -0
  29. package/dist/env.test.js +39 -0
  30. package/dist/extensions.d.ts +1 -0
  31. package/dist/extensions.js +16 -3
  32. package/dist/flows.js +28 -17
  33. package/dist/mailer.js +1 -0
  34. package/dist/middleware/authenticate.d.ts +1 -1
  35. package/dist/middleware/authenticate.js +1 -0
  36. package/dist/middleware/authenticate.test.d.ts +1 -0
  37. package/dist/middleware/authenticate.test.js +214 -0
  38. package/dist/middleware/extract-token.test.d.ts +1 -0
  39. package/dist/middleware/extract-token.test.js +60 -0
  40. package/dist/middleware/validate-batch.d.ts +1 -2
  41. package/dist/middleware/validate-batch.js +10 -13
  42. package/dist/middleware/validate-batch.test.d.ts +1 -0
  43. package/dist/middleware/validate-batch.test.js +82 -0
  44. package/dist/operations/exec/index.d.ts +5 -0
  45. package/dist/operations/exec/index.js +26 -0
  46. package/dist/operations/exec/index.test.d.ts +1 -0
  47. package/dist/operations/exec/index.test.js +95 -0
  48. package/dist/operations/notification/index.js +9 -6
  49. package/dist/operations/request/index.js +22 -3
  50. package/dist/operations/transform/index.d.ts +1 -1
  51. package/dist/operations/transform/index.js +1 -1
  52. package/dist/services/authentication.js +13 -3
  53. package/dist/services/files.js +3 -2
  54. package/dist/services/files.test.d.ts +1 -0
  55. package/dist/services/files.test.js +53 -0
  56. package/dist/services/flows.js +4 -0
  57. package/dist/services/graphql/index.d.ts +2 -2
  58. package/dist/services/graphql/index.js +78 -75
  59. package/dist/services/items.js +98 -42
  60. package/dist/services/items.test.d.ts +1 -0
  61. package/dist/services/items.test.js +765 -0
  62. package/dist/services/payload.d.ts +7 -4
  63. package/dist/services/payload.js +63 -12
  64. package/dist/services/payload.test.d.ts +1 -0
  65. package/dist/services/payload.test.js +94 -0
  66. package/dist/services/server.js +10 -7
  67. package/dist/services/shares.js +2 -1
  68. package/dist/services/specifications.test.d.ts +1 -0
  69. package/dist/services/specifications.test.js +96 -0
  70. package/dist/types/items.d.ts +11 -0
  71. package/dist/utils/apply-query.js +7 -3
  72. package/dist/utils/apply-snapshot.js +15 -0
  73. package/dist/utils/apply-snapshot.test.d.ts +1 -0
  74. package/dist/utils/apply-snapshot.test.js +305 -0
  75. package/dist/utils/async-handler.d.ts +2 -6
  76. package/dist/utils/async-handler.js +1 -13
  77. package/dist/utils/async-handler.test.d.ts +1 -0
  78. package/dist/utils/async-handler.test.js +18 -0
  79. package/dist/utils/calculate-field-depth.test.d.ts +1 -0
  80. package/dist/utils/calculate-field-depth.test.js +76 -0
  81. package/dist/utils/filter-items.test.d.ts +1 -0
  82. package/dist/utils/filter-items.test.js +60 -0
  83. package/dist/utils/get-cache-key.test.d.ts +1 -0
  84. package/dist/utils/get-cache-key.test.js +53 -0
  85. package/dist/utils/get-column-path.test.d.ts +1 -0
  86. package/dist/utils/get-column-path.test.js +136 -0
  87. package/dist/utils/get-config-from-env.test.d.ts +1 -0
  88. package/dist/utils/get-config-from-env.test.js +19 -0
  89. package/dist/utils/get-graphql-type.d.ts +1 -1
  90. package/dist/utils/get-graphql-type.js +4 -1
  91. package/dist/utils/get-os-info.d.ts +9 -0
  92. package/dist/utils/get-os-info.js +47 -0
  93. package/dist/utils/get-relation-info.test.d.ts +1 -0
  94. package/dist/utils/get-relation-info.test.js +88 -0
  95. package/dist/utils/get-relation-type.test.d.ts +1 -0
  96. package/dist/utils/get-relation-type.test.js +69 -0
  97. package/dist/utils/get-string-byte-size.test.d.ts +1 -0
  98. package/dist/utils/get-string-byte-size.test.js +8 -0
  99. package/dist/utils/is-directus-jwt.test.d.ts +1 -0
  100. package/dist/utils/is-directus-jwt.test.js +26 -0
  101. package/dist/utils/jwt.test.d.ts +1 -0
  102. package/dist/utils/jwt.test.js +36 -0
  103. package/dist/utils/merge-permissions.test.d.ts +1 -0
  104. package/dist/utils/merge-permissions.test.js +80 -0
  105. package/dist/utils/validate-keys.test.d.ts +1 -0
  106. package/dist/utils/validate-keys.test.js +97 -0
  107. package/package.json +14 -12
@@ -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
  }
@@ -520,11 +538,37 @@ class PayloadService {
520
538
  if (error)
521
539
  throw new exceptions_1.InvalidPayloadException(`Invalid one-to-many update structure: ${error.message}`);
522
540
  if (alterations.create) {
523
- await itemsService.createMany(alterations.create.map((item) => ({
524
- ...item,
525
- [relation.field]: parent || payload[currentPrimaryKeyField],
526
- })), {
541
+ const sortField = relation.meta.sort_field;
542
+ let createPayload;
543
+ if (sortField !== null) {
544
+ const highestOrderNumber = await this.knex
545
+ .from(relation.collection)
546
+ .where({ [relation.field]: parent })
547
+ .whereNotNull(sortField)
548
+ .max(sortField)
549
+ .first();
550
+ createPayload = alterations.create.map((item, index) => {
551
+ const record = (0, lodash_1.cloneDeep)(item);
552
+ // add sort field value if it is not supplied in the item
553
+ if (parent !== null && record[sortField] === undefined) {
554
+ record[sortField] = (highestOrderNumber === null || highestOrderNumber === void 0 ? void 0 : highestOrderNumber.max) ? highestOrderNumber.max + index + 1 : index + 1;
555
+ }
556
+ return {
557
+ ...record,
558
+ [relation.field]: parent || payload[currentPrimaryKeyField],
559
+ };
560
+ });
561
+ }
562
+ else {
563
+ createPayload = alterations.create.map((item) => ({
564
+ ...item,
565
+ [relation.field]: parent || payload[currentPrimaryKeyField],
566
+ }));
567
+ }
568
+ await itemsService.createMany(createPayload, {
527
569
  onRevisionCreate: (pk) => revisions.push(pk),
570
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
571
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
528
572
  });
529
573
  }
530
574
  if (alterations.update) {
@@ -535,6 +579,8 @@ class PayloadService {
535
579
  [relation.field]: parent || payload[currentPrimaryKeyField],
536
580
  }, {
537
581
  onRevisionCreate: (pk) => revisions.push(pk),
582
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
583
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
538
584
  });
539
585
  }
540
586
  }
@@ -556,17 +602,22 @@ class PayloadService {
556
602
  },
557
603
  };
558
604
  if (relation.meta.one_deselect_action === 'delete') {
559
- await itemsService.deleteByQuery(query);
605
+ await itemsService.deleteByQuery(query, {
606
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
607
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
608
+ });
560
609
  }
561
610
  else {
562
611
  await itemsService.updateByQuery(query, { [relation.field]: null }, {
563
612
  onRevisionCreate: (pk) => revisions.push(pk),
613
+ bypassEmitAction: (params) => nestedActionEvents.push(params),
614
+ emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
564
615
  });
565
616
  }
566
617
  }
567
618
  }
568
619
  }
569
- return { revisions };
620
+ return { revisions, nestedActionEvents };
570
621
  }
571
622
  /**
572
623
  * 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
  };
@@ -141,7 +143,7 @@ class ServerService {
141
143
  componentType: 'datastore',
142
144
  observedUnit: 'ms',
143
145
  observedValue: 0,
144
- threshold: 150,
146
+ threshold: env_1.default.DB_HEALTHCHECK_THRESHOLD ? +env_1.default.DB_HEALTHCHECK_THRESHOLD : 150,
145
147
  },
146
148
  ];
147
149
  const startTime = perf_hooks_1.performance.now();
@@ -186,7 +188,7 @@ class ServerService {
186
188
  componentType: 'cache',
187
189
  observedValue: 0,
188
190
  observedUnit: 'ms',
189
- threshold: 150,
191
+ threshold: env_1.default.CACHE_HEALTHCHECK_THRESHOLD ? +env_1.default.CACHE_HEALTHCHECK_THRESHOLD : 150,
190
192
  },
191
193
  ],
192
194
  };
@@ -220,7 +222,7 @@ class ServerService {
220
222
  componentType: 'ratelimiter',
221
223
  observedValue: 0,
222
224
  observedUnit: 'ms',
223
- threshold: 150,
225
+ threshold: env_1.default.RATE_LIMITER_HEALTHCHECK_THRESHOLD ? +env_1.default.RATE_LIMITER_HEALTHCHECK_THRESHOLD : 150,
224
226
  },
225
227
  ],
226
228
  };
@@ -247,13 +249,14 @@ class ServerService {
247
249
  const checks = {};
248
250
  for (const location of (0, utils_1.toArray)(env_1.default.STORAGE_LOCATIONS)) {
249
251
  const disk = storage_1.default.disk(location);
252
+ const envThresholdKey = `STORAGE_${location}_HEALTHCHECK_THRESHOLD`.toUpperCase();
250
253
  checks[`storage:${location}:responseTime`] = [
251
254
  {
252
255
  status: 'ok',
253
256
  componentType: 'objectstore',
254
257
  observedValue: 0,
255
258
  observedUnit: 'ms',
256
- threshold: 750,
259
+ threshold: env_1.default[envThresholdKey] ? +env_1.default[envThresholdKey] : 750,
257
260
  },
258
261
  ];
259
262
  const startTime = perf_hooks_1.performance.now();
@@ -31,7 +31,7 @@ class SharesService extends items_1.ItemsService {
31
31
  return super.createOne(data, opts);
32
32
  }
33
33
  async login(payload) {
34
- var _a, _b;
34
+ var _a, _b, _c;
35
35
  const record = await this.knex
36
36
  .select({
37
37
  share_id: 'id',
@@ -86,6 +86,7 @@ class SharesService extends items_1.ItemsService {
86
86
  expires: refreshTokenExpiration,
87
87
  ip: (_a = this.accountability) === null || _a === void 0 ? void 0 : _a.ip,
88
88
  user_agent: (_b = this.accountability) === null || _b === void 0 ? void 0 : _b.userAgent,
89
+ origin: (_c = this.accountability) === null || _c === void 0 ? void 0 : _c.origin,
89
90
  share: record.share_id,
90
91
  });
91
92
  await this.knex('directus_sessions').delete().where('expires', '<', new Date());
@@ -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
  };
@@ -212,11 +212,11 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
212
212
  const { columnPath, targetCollection } = (0, get_column_path_1.getColumnPath)({ path: filterPath, collection, relations, aliasMap });
213
213
  if (!columnPath)
214
214
  continue;
215
- validateFilterOperator(schema.collections[targetCollection].fields[(0, strip_function_1.stripFunction)(filterPath[filterPath.length - 1])].type, filterOperator);
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);
216
216
  applyFilterToQuery(columnPath, filterOperator, filterValue, logical, targetCollection);
217
217
  }
218
218
  else {
219
- validateFilterOperator(schema.collections[collection].fields[(0, strip_function_1.stripFunction)(filterPath[0])].type, filterOperator);
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);
220
220
  applyFilterToQuery(`${collection}.${filterPath[0]}`, filterOperator, filterValue, logical);
221
221
  }
222
222
  }
@@ -248,13 +248,17 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
248
248
  }
249
249
  }
250
250
  }
251
- function validateFilterOperator(type, filterOperator) {
251
+ function validateFilterOperator(type, filterOperator, special) {
252
252
  if (filterOperator.startsWith('_')) {
253
253
  filterOperator = filterOperator.slice(1);
254
254
  }
255
255
  if (!(0, utils_1.getFilterOperatorsForType)(type).includes(filterOperator)) {
256
256
  throw new invalid_query_1.InvalidQueryException(`"${type}" field type does not contain the "_${filterOperator}" filter operator`);
257
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
+ }
258
262
  }
259
263
  function applyFilterToQuery(key, operator, compareValue, logical = 'and', originalCollectionName) {
260
264
  const [table, column] = key.split('.');
@@ -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 {};