directus 9.10.0 → 9.12.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 (141) hide show
  1. package/dist/app.js +8 -1
  2. package/dist/auth/drivers/oauth2.d.ts +1 -1
  3. package/dist/auth/drivers/oauth2.js +14 -11
  4. package/dist/auth/drivers/openid.d.ts +1 -1
  5. package/dist/auth/drivers/openid.js +14 -11
  6. package/dist/cli/commands/schema/apply.js +4 -3
  7. package/dist/cli/utils/create-env/env-stub.liquid +266 -9
  8. package/dist/controllers/activity.js +1 -1
  9. package/dist/controllers/assets.js +8 -9
  10. package/dist/controllers/flows.d.ts +2 -0
  11. package/dist/controllers/flows.js +157 -0
  12. package/dist/controllers/folders.js +1 -1
  13. package/dist/controllers/notifications.js +1 -1
  14. package/dist/controllers/operations.d.ts +2 -0
  15. package/dist/controllers/operations.js +138 -0
  16. package/dist/database/helpers/date/dialects/sqlite.js +6 -2
  17. package/dist/database/index.js +15 -19
  18. package/dist/database/migrations/20210225A-add-relations-sort-field.js +2 -1
  19. package/dist/database/migrations/20210506A-rename-interfaces.js +2 -1
  20. package/dist/database/migrations/20210802A-replace-groups.js +2 -1
  21. package/dist/database/migrations/20210805A-update-groups.js +2 -1
  22. package/dist/database/migrations/20210805B-change-image-metadata-structure.js +3 -2
  23. package/dist/database/migrations/20211007A-update-presets.js +5 -4
  24. package/dist/database/migrations/20220429A-add-flows.d.ts +3 -0
  25. package/dist/database/migrations/20220429A-add-flows.js +83 -0
  26. package/dist/database/migrations/20220429B-add-color-to-insights-icon.d.ts +3 -0
  27. package/dist/database/migrations/20220429B-add-color-to-insights-icon.js +15 -0
  28. package/dist/database/migrations/20220429C-drop-non-null-from-ip-of-activity.d.ts +3 -0
  29. package/dist/database/migrations/20220429C-drop-non-null-from-ip-of-activity.js +15 -0
  30. package/dist/database/migrations/20220429D-drop-non-null-from-sender-of-notifications.d.ts +3 -0
  31. package/dist/database/migrations/20220429D-drop-non-null-from-sender-of-notifications.js +15 -0
  32. package/dist/database/run-ast.js +10 -14
  33. package/dist/database/seeds/05-activity.yaml +0 -1
  34. package/dist/database/system-data/collections/collections.yaml +4 -0
  35. package/dist/database/system-data/fields/activity.yaml +3 -0
  36. package/dist/database/system-data/fields/dashboards.yaml +3 -1
  37. package/dist/database/system-data/fields/flows.yaml +21 -0
  38. package/dist/database/system-data/fields/notifications.yaml +3 -1
  39. package/dist/database/system-data/fields/operations.yaml +19 -0
  40. package/dist/database/system-data/fields/panels.yaml +3 -1
  41. package/dist/database/system-data/fields/shares.yaml +3 -1
  42. package/dist/database/system-data/fields/users.yaml +2 -4
  43. package/dist/database/system-data/relations/relations.yaml +20 -0
  44. package/dist/env.d.ts +1 -1
  45. package/dist/env.js +167 -12
  46. package/dist/exceptions/index.d.ts +1 -0
  47. package/dist/exceptions/index.js +1 -0
  48. package/dist/exceptions/invalid-provider.d.ts +4 -0
  49. package/dist/exceptions/invalid-provider.js +10 -0
  50. package/dist/exceptions/range-not-satisfiable.d.ts +2 -2
  51. package/dist/exceptions/range-not-satisfiable.js +5 -1
  52. package/dist/extensions.d.ts +3 -0
  53. package/dist/extensions.js +73 -20
  54. package/dist/flows.d.ts +17 -0
  55. package/dist/flows.js +310 -0
  56. package/dist/messenger.d.ts +24 -0
  57. package/dist/messenger.js +64 -0
  58. package/dist/middleware/graphql.js +2 -1
  59. package/dist/operations/condition/index.d.ts +6 -0
  60. package/dist/operations/condition/index.js +15 -0
  61. package/dist/operations/item-create/index.d.ts +8 -0
  62. package/dist/operations/item-create/index.js +40 -0
  63. package/dist/operations/item-delete/index.d.ts +9 -0
  64. package/dist/operations/item-delete/index.js +45 -0
  65. package/dist/operations/item-read/index.d.ts +9 -0
  66. package/dist/operations/item-read/index.js +45 -0
  67. package/dist/operations/item-update/index.d.ts +10 -0
  68. package/dist/operations/item-update/index.js +50 -0
  69. package/dist/operations/log/index.d.ts +5 -0
  70. package/dist/operations/log/index.js +14 -0
  71. package/dist/operations/mail/index.d.ts +7 -0
  72. package/dist/operations/mail/index.js +16 -0
  73. package/dist/operations/notification/index.d.ts +8 -0
  74. package/dist/operations/notification/index.js +39 -0
  75. package/dist/operations/request/index.d.ts +9 -0
  76. package/dist/operations/request/index.js +14 -0
  77. package/dist/operations/sleep/index.d.ts +5 -0
  78. package/dist/operations/sleep/index.js +9 -0
  79. package/dist/operations/transform/index.d.ts +5 -0
  80. package/dist/operations/transform/index.js +10 -0
  81. package/dist/operations/trigger/index.d.ts +6 -0
  82. package/dist/operations/trigger/index.js +21 -0
  83. package/dist/services/activity.d.ts +1 -2
  84. package/dist/services/activity.js +10 -10
  85. package/dist/services/assets.js +27 -1
  86. package/dist/services/authentication.d.ts +2 -2
  87. package/dist/services/authentication.js +11 -8
  88. package/dist/services/authorization.js +12 -0
  89. package/dist/services/fields.js +15 -8
  90. package/dist/services/flows.d.ts +14 -0
  91. package/dist/services/flows.js +42 -0
  92. package/dist/services/graphql.js +56 -33
  93. package/dist/services/import-export.d.ts +1 -1
  94. package/dist/services/import-export.js +13 -12
  95. package/dist/services/index.d.ts +2 -0
  96. package/dist/services/index.js +2 -0
  97. package/dist/services/items.d.ts +3 -3
  98. package/dist/services/items.js +25 -2
  99. package/dist/services/mail/index.js +2 -1
  100. package/dist/services/notifications.d.ts +2 -1
  101. package/dist/services/notifications.js +4 -3
  102. package/dist/services/operations.d.ts +14 -0
  103. package/dist/services/operations.js +42 -0
  104. package/dist/services/payload.d.ts +2 -2
  105. package/dist/services/payload.js +8 -7
  106. package/dist/services/users.d.ts +4 -0
  107. package/dist/services/users.js +20 -0
  108. package/dist/services/webhooks.d.ts +2 -0
  109. package/dist/services/webhooks.js +8 -7
  110. package/dist/types/events.d.ts +18 -0
  111. package/dist/types/events.js +2 -0
  112. package/dist/types/index.d.ts +1 -1
  113. package/dist/types/index.js +1 -1
  114. package/dist/utils/apply-query.js +31 -4
  115. package/dist/utils/apply-snapshot.d.ts +3 -3
  116. package/dist/utils/apply-snapshot.js +64 -49
  117. package/dist/utils/construct-flow-tree.d.ts +2 -0
  118. package/dist/utils/construct-flow-tree.js +31 -0
  119. package/dist/utils/get-accountability-for-role.d.ts +7 -0
  120. package/dist/utils/get-accountability-for-role.js +36 -0
  121. package/dist/utils/get-ast-from-query.js +1 -7
  122. package/dist/utils/get-default-value.js +4 -3
  123. package/dist/utils/get-permissions.d.ts +1 -1
  124. package/dist/utils/get-permissions.js +9 -8
  125. package/dist/utils/get-schema.js +2 -1
  126. package/dist/utils/get-snapshot.js +22 -4
  127. package/dist/utils/operation-options.d.ts +3 -0
  128. package/dist/utils/operation-options.js +45 -0
  129. package/dist/utils/parse-json.d.ts +5 -0
  130. package/dist/utils/parse-json.js +19 -0
  131. package/dist/utils/sanitize-query.d.ts +1 -2
  132. package/dist/utils/sanitize-query.js +6 -5
  133. package/dist/utils/validate-keys.d.ts +6 -0
  134. package/dist/utils/validate-keys.js +28 -0
  135. package/dist/utils/validate-query.js +1 -1
  136. package/dist/webhooks.d.ts +2 -0
  137. package/dist/webhooks.js +17 -2
  138. package/package.json +18 -14
  139. package/dist/types/activity.d.ts +0 -9
  140. package/dist/types/activity.js +0 -13
  141. package/example.env +0 -202
@@ -1,40 +1,41 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WebhooksService = void 0;
4
- const webhooks_1 = require("../webhooks");
5
4
  const items_1 = require("./items");
5
+ const messenger_1 = require("../messenger");
6
6
  class WebhooksService extends items_1.ItemsService {
7
7
  constructor(options) {
8
8
  super('directus_webhooks', options);
9
+ this.messenger = (0, messenger_1.getMessenger)();
9
10
  }
10
11
  async createOne(data, opts) {
11
12
  const result = await super.createOne(data, opts);
12
- await (0, webhooks_1.register)();
13
+ this.messenger.publish('webhooks', { type: 'reload' });
13
14
  return result;
14
15
  }
15
16
  async createMany(data, opts) {
16
17
  const result = await super.createMany(data, opts);
17
- await (0, webhooks_1.register)();
18
+ this.messenger.publish('webhooks', { type: 'reload' });
18
19
  return result;
19
20
  }
20
21
  async updateOne(key, data, opts) {
21
22
  const result = await super.updateOne(key, data, opts);
22
- await (0, webhooks_1.register)();
23
+ this.messenger.publish('webhooks', { type: 'reload' });
23
24
  return result;
24
25
  }
25
26
  async updateMany(keys, data, opts) {
26
27
  const result = await super.updateMany(keys, data, opts);
27
- await (0, webhooks_1.register)();
28
+ this.messenger.publish('webhooks', { type: 'reload' });
28
29
  return result;
29
30
  }
30
31
  async deleteOne(key, opts) {
31
32
  const result = await super.deleteOne(key, opts);
32
- await (0, webhooks_1.register)();
33
+ this.messenger.publish('webhooks', { type: 'reload' });
33
34
  return result;
34
35
  }
35
36
  async deleteMany(keys, opts) {
36
37
  const result = await super.deleteMany(keys, opts);
37
- await (0, webhooks_1.register)();
38
+ this.messenger.publish('webhooks', { type: 'reload' });
38
39
  return result;
39
40
  }
40
41
  }
@@ -0,0 +1,18 @@
1
+ import { ActionHandler, FilterHandler, InitHandler } from '@directus/shared/types';
2
+ import { ScheduledTask } from 'node-cron';
3
+ export declare type EventHandler = {
4
+ type: 'filter';
5
+ name: string;
6
+ handler: FilterHandler;
7
+ } | {
8
+ type: 'action';
9
+ name: string;
10
+ handler: ActionHandler;
11
+ } | {
12
+ type: 'init';
13
+ name: string;
14
+ handler: InitHandler;
15
+ } | {
16
+ type: 'schedule';
17
+ task: ScheduledTask;
18
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,8 +1,8 @@
1
- export * from './activity';
2
1
  export * from './assets';
3
2
  export * from './ast';
4
3
  export * from './auth';
5
4
  export * from './collection';
5
+ export * from './events';
6
6
  export * from './files';
7
7
  export * from './graphql';
8
8
  export * from './items';
@@ -10,11 +10,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
10
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
- __exportStar(require("./activity"), exports);
14
13
  __exportStar(require("./assets"), exports);
15
14
  __exportStar(require("./ast"), exports);
16
15
  __exportStar(require("./auth"), exports);
17
16
  __exportStar(require("./collection"), exports);
17
+ __exportStar(require("./events"), exports);
18
18
  __exportStar(require("./files"), exports);
19
19
  __exportStar(require("./graphql"), exports);
20
20
  __exportStar(require("./items"), exports);
@@ -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
10
  const helpers_1 = require("../database/helpers");
12
- const exceptions_1 = require("../exceptions");
11
+ const invalid_query_1 = require("../exceptions/invalid-query");
13
12
  const get_column_1 = require("./get-column");
14
13
  const get_column_path_1 = require("./get-column-path");
15
14
  const get_relation_info_1 = require("./get-relation-info");
15
+ const utils_1 = require("@directus/shared/utils");
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
  }
@@ -314,24 +314,48 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
314
314
  if (operator === '_neq') {
315
315
  dbQuery[logical].whereNot(selectionRaw, compareValue);
316
316
  }
317
+ if (operator === '_ieq') {
318
+ dbQuery[logical].whereRaw(`LOWER(??) = ?`, [selectionRaw, `${compareValue.toLowerCase()}`]);
319
+ }
320
+ if (operator === '_nieq') {
321
+ dbQuery[logical].whereRaw(`LOWER(??) <> ?`, [selectionRaw, `${compareValue.toLowerCase()}`]);
322
+ }
317
323
  if (operator === '_contains') {
318
324
  dbQuery[logical].where(selectionRaw, 'like', `%${compareValue}%`);
319
325
  }
320
326
  if (operator === '_ncontains') {
321
327
  dbQuery[logical].whereNot(selectionRaw, 'like', `%${compareValue}%`);
322
328
  }
329
+ if (operator === '_icontains') {
330
+ dbQuery[logical].whereRaw(`LOWER(??) LIKE ?`, [selectionRaw, `%${compareValue.toLowerCase()}%`]);
331
+ }
332
+ if (operator === '_nicontains') {
333
+ dbQuery[logical].whereRaw(`LOWER(??) NOT LIKE ?`, [selectionRaw, `%${compareValue.toLowerCase()}%`]);
334
+ }
323
335
  if (operator === '_starts_with') {
324
336
  dbQuery[logical].where(key, 'like', `${compareValue}%`);
325
337
  }
326
338
  if (operator === '_nstarts_with') {
327
339
  dbQuery[logical].whereNot(key, 'like', `${compareValue}%`);
328
340
  }
341
+ if (operator === '_istarts_with') {
342
+ dbQuery[logical].whereRaw(`LOWER(??) LIKE ?`, [selectionRaw, `${compareValue.toLowerCase()}%`]);
343
+ }
344
+ if (operator === '_nistarts_with') {
345
+ dbQuery[logical].whereRaw(`LOWER(??) NOT LIKE ?`, [selectionRaw, `${compareValue.toLowerCase()}%`]);
346
+ }
329
347
  if (operator === '_ends_with') {
330
348
  dbQuery[logical].where(key, 'like', `%${compareValue}`);
331
349
  }
332
350
  if (operator === '_nends_with') {
333
351
  dbQuery[logical].whereNot(key, 'like', `%${compareValue}`);
334
352
  }
353
+ if (operator === '_iends_with') {
354
+ dbQuery[logical].whereRaw(`LOWER(??) LIKE ?`, [selectionRaw, `%${compareValue.toLowerCase()}`]);
355
+ }
356
+ if (operator === '_niends_with') {
357
+ dbQuery[logical].whereRaw(`LOWER(??) NOT LIKE ?`, [selectionRaw, `%${compareValue.toLowerCase()}`]);
358
+ }
335
359
  if (operator === '_gt') {
336
360
  dbQuery[logical].where(selectionRaw, '>', compareValue);
337
361
  }
@@ -418,6 +442,9 @@ function applyAggregate(dbQuery, aggregate, collection) {
418
442
  if (operation === 'avgDistinct') {
419
443
  dbQuery.avgDistinct(`${collection}.${field}`, { as: `avgDistinct->${field}` });
420
444
  }
445
+ if (operation === 'countAll') {
446
+ dbQuery.count('*', { as: 'countAll' });
447
+ }
421
448
  if (operation === 'count') {
422
449
  if (field === '*') {
423
450
  dbQuery.count('*', { as: 'count' });
@@ -1,7 +1,7 @@
1
- import { Snapshot, SnapshotDiff, SnapshotField } from '../types';
2
- import { Knex } from 'knex';
3
- import { Diff } from 'deep-diff';
4
1
  import { SchemaOverview } from '@directus/shared/types';
2
+ import { Diff } from 'deep-diff';
3
+ import { Knex } from 'knex';
4
+ import { Snapshot, SnapshotDiff, SnapshotField } from '../types';
5
5
  export declare function applySnapshot(snapshot: Snapshot, options?: {
6
6
  database?: Knex;
7
7
  schema?: SchemaOverview;
@@ -4,13 +4,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.isNestedMetaUpdate = exports.applySnapshot = void 0;
7
- const get_snapshot_1 = require("./get-snapshot");
8
- const get_snapshot_diff_1 = require("./get-snapshot-diff");
9
- const database_1 = __importDefault(require("../database"));
10
- const get_schema_1 = require("./get-schema");
11
- const services_1 = require("../services");
12
7
  const lodash_1 = require("lodash");
8
+ const database_1 = __importDefault(require("../database"));
13
9
  const logger_1 = __importDefault(require("../logger"));
10
+ const services_1 = require("../services");
11
+ const get_schema_1 = require("./get-schema");
12
+ const get_snapshot_1 = require("./get-snapshot");
13
+ const get_snapshot_diff_1 = require("./get-snapshot-diff");
14
14
  async function applySnapshot(snapshot, options) {
15
15
  var _a, _b, _c, _d;
16
16
  const database = (_a = options === null || options === void 0 ? void 0 : options.database) !== null && _a !== void 0 ? _a : (0, database_1.default)();
@@ -19,55 +19,70 @@ async function applySnapshot(snapshot, options) {
19
19
  const snapshotDiff = (_d = options === null || options === void 0 ? void 0 : options.diff) !== null && _d !== void 0 ? _d : (0, get_snapshot_diff_1.getSnapshotDiff)(current, snapshot);
20
20
  await database.transaction(async (trx) => {
21
21
  const collectionsService = new services_1.CollectionsService({ knex: trx, schema });
22
- for (const { collection, diff } of snapshotDiff.collections) {
23
- if ((diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'D') {
24
- try {
25
- await collectionsService.deleteOne(collection);
26
- }
27
- catch (err) {
28
- logger_1.default.error(`Failed to delete collection "${collection}"`);
29
- throw err;
22
+ const getNestedCollectionsToCreate = (currentLevelCollection) => snapshotDiff.collections.filter(({ diff }) => { var _a, _b; return ((_b = (_a = diff[0].rhs) === null || _a === void 0 ? void 0 : _a.meta) === null || _b === void 0 ? void 0 : _b.group) === currentLevelCollection; });
23
+ const getNestedCollectionsToDelete = (currentLevelCollection) => snapshotDiff.collections.filter(({ diff }) => { var _a, _b; return ((_b = (_a = diff[0].lhs) === null || _a === void 0 ? void 0 : _a.meta) === null || _b === void 0 ? void 0 : _b.group) === currentLevelCollection; });
24
+ const createCollections = async (collections) => {
25
+ for (const { collection, diff } of collections) {
26
+ if ((diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'N' && diff[0].rhs) {
27
+ // We'll nest the to-be-created fields in the same collection creation, to prevent
28
+ // creating a collection without a primary key
29
+ const fields = snapshotDiff.fields
30
+ .filter((fieldDiff) => fieldDiff.collection === collection)
31
+ .map((fieldDiff) => fieldDiff.diff[0].rhs)
32
+ .map((fieldDiff) => {
33
+ // Casts field type to UUID when applying SQLite-based schema on other databases.
34
+ // This is needed because SQLite snapshots UUID fields as char with length 36, and
35
+ // it will fail when trying to create relation between char field to UUID field
36
+ if (!fieldDiff.schema ||
37
+ fieldDiff.schema.data_type !== 'char' ||
38
+ fieldDiff.schema.max_length !== 36 ||
39
+ !fieldDiff.schema.foreign_key_table ||
40
+ !fieldDiff.schema.foreign_key_column) {
41
+ return fieldDiff;
42
+ }
43
+ const matchingForeignKeyTable = schema.collections[fieldDiff.schema.foreign_key_table];
44
+ if (!matchingForeignKeyTable)
45
+ return fieldDiff;
46
+ const matchingForeignKeyField = matchingForeignKeyTable.fields[fieldDiff.schema.foreign_key_column];
47
+ if (!matchingForeignKeyField || matchingForeignKeyField.type !== 'uuid')
48
+ return fieldDiff;
49
+ return (0, lodash_1.merge)(fieldDiff, { type: 'uuid', schema: { data_type: 'uuid', max_length: null } });
50
+ });
51
+ try {
52
+ await collectionsService.createOne({
53
+ ...diff[0].rhs,
54
+ fields,
55
+ });
56
+ }
57
+ catch (err) {
58
+ logger_1.default.error(`Failed to create collection "${collection}"`);
59
+ throw err;
60
+ }
61
+ // Now that the fields are in for this collection, we can strip them from the field edits
62
+ snapshotDiff.fields = snapshotDiff.fields.filter((fieldDiff) => fieldDiff.collection !== collection);
63
+ await createCollections(getNestedCollectionsToCreate(collection));
30
64
  }
31
65
  }
32
- if ((diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'N' && diff[0].rhs) {
33
- // We'll nest the to-be-created fields in the same collection creation, to prevent
34
- // creating a collection without a primary key
35
- const fields = snapshotDiff.fields
36
- .filter((fieldDiff) => fieldDiff.collection === collection)
37
- .map((fieldDiff) => fieldDiff.diff[0].rhs)
38
- .map((fieldDiff) => {
39
- // Casts field type to UUID when applying SQLite-based schema on other databases.
40
- // This is needed because SQLite snapshots UUID fields as char with length 36, and
41
- // it will fail when trying to create relation between char field to UUID field
42
- if (!fieldDiff.schema ||
43
- fieldDiff.schema.data_type !== 'char' ||
44
- fieldDiff.schema.max_length !== 36 ||
45
- !fieldDiff.schema.foreign_key_table ||
46
- !fieldDiff.schema.foreign_key_column) {
47
- return fieldDiff;
66
+ };
67
+ const deleteCollections = async (collections) => {
68
+ for (const { collection, diff } of collections) {
69
+ if ((diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'D') {
70
+ await deleteCollections(getNestedCollectionsToDelete(collection));
71
+ try {
72
+ await collectionsService.deleteOne(collection);
73
+ }
74
+ catch (err) {
75
+ logger_1.default.error(`Failed to delete collection "${collection}"`);
76
+ throw err;
48
77
  }
49
- const matchingForeignKeyTable = schema.collections[fieldDiff.schema.foreign_key_table];
50
- if (!matchingForeignKeyTable)
51
- return fieldDiff;
52
- const matchingForeignKeyField = matchingForeignKeyTable.fields[fieldDiff.schema.foreign_key_column];
53
- if (!matchingForeignKeyField || matchingForeignKeyField.type !== 'uuid')
54
- return fieldDiff;
55
- return (0, lodash_1.merge)(fieldDiff, { type: 'uuid', schema: { data_type: 'uuid', max_length: null } });
56
- });
57
- try {
58
- await collectionsService.createOne({
59
- ...diff[0].rhs,
60
- fields,
61
- });
62
- }
63
- catch (err) {
64
- logger_1.default.error(`Failed to create collection "${collection}"`);
65
- throw err;
66
78
  }
67
- // Now that the fields are in for this collection, we can strip them from the field
68
- // edits
69
- snapshotDiff.fields = snapshotDiff.fields.filter((fieldDiff) => fieldDiff.collection !== collection);
70
79
  }
80
+ };
81
+ // create top level collections (no group) first, then continue with nested collections recursively
82
+ await createCollections(snapshotDiff.collections.filter(({ diff }) => { var _a; return diff[0].kind === 'N' && ((_a = diff[0].rhs.meta) === null || _a === void 0 ? void 0 : _a.group) === null; }));
83
+ // delete top level collections (no group) first, then continue with nested collections recursively
84
+ await deleteCollections(snapshotDiff.collections.filter(({ diff }) => { var _a; return diff[0].kind === 'D' && ((_a = diff[0].lhs.meta) === null || _a === void 0 ? void 0 : _a.group) === null; }));
85
+ for (const { collection, diff } of snapshotDiff.collections) {
71
86
  if ((diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'E' || (diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'A') {
72
87
  const newValues = snapshot.collections.find((field) => {
73
88
  return field.collection === collection;
@@ -0,0 +1,2 @@
1
+ import { Flow, FlowRaw } from '@directus/shared/types';
2
+ export declare function constructFlowTree(flow: FlowRaw): Flow;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.constructFlowTree = void 0;
4
+ const lodash_1 = require("lodash");
5
+ function constructFlowTree(flow) {
6
+ var _a;
7
+ const rootOperation = (_a = flow.operations.find((operation) => operation.id === flow.operation)) !== null && _a !== void 0 ? _a : null;
8
+ const operationTree = constructOperationTree(rootOperation, flow.operations);
9
+ const flowTree = {
10
+ ...(0, lodash_1.omit)(flow, 'operations'),
11
+ operation: operationTree,
12
+ };
13
+ return flowTree;
14
+ }
15
+ exports.constructFlowTree = constructFlowTree;
16
+ function constructOperationTree(root, operations) {
17
+ if (root === null) {
18
+ return null;
19
+ }
20
+ const resolveOperation = root.resolve !== null ? operations.find((operation) => operation.id === root.resolve) : null;
21
+ const rejectOperation = root.reject !== null ? operations.find((operation) => operation.id === root.reject) : null;
22
+ if (resolveOperation === undefined || rejectOperation === undefined) {
23
+ throw new Error('Undefined reference in operations');
24
+ }
25
+ const operationTree = {
26
+ ...(0, lodash_1.omit)(root, 'flow'),
27
+ resolve: constructOperationTree(resolveOperation, operations),
28
+ reject: constructOperationTree(rejectOperation, operations),
29
+ };
30
+ return operationTree;
31
+ }
@@ -0,0 +1,7 @@
1
+ import { Accountability, SchemaOverview } from '@directus/shared/types';
2
+ import { Knex } from 'knex';
3
+ export declare function getAccountabilityForRole(role: null | string, context: {
4
+ accountability: null | Accountability;
5
+ schema: SchemaOverview;
6
+ database: Knex;
7
+ }): Promise<Accountability>;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAccountabilityForRole = void 0;
4
+ const get_permissions_1 = require("./get-permissions");
5
+ const exceptions_1 = require("../exceptions");
6
+ async function getAccountabilityForRole(role, context) {
7
+ let generatedAccountability = context.accountability;
8
+ if (role === null) {
9
+ generatedAccountability = {
10
+ role: null,
11
+ user: null,
12
+ admin: false,
13
+ app: false,
14
+ };
15
+ generatedAccountability.permissions = await (0, get_permissions_1.getPermissions)(generatedAccountability, context.schema);
16
+ }
17
+ else {
18
+ const roleInfo = await context.database
19
+ .select(['app_access', 'admin_access'])
20
+ .from('directus_roles')
21
+ .where({ id: role })
22
+ .first();
23
+ if (!roleInfo) {
24
+ throw new exceptions_1.InvalidConfigException(`Configured role "${role}" isn't a valid role ID or doesn't exist.`);
25
+ }
26
+ generatedAccountability = {
27
+ role,
28
+ user: null,
29
+ admin: roleInfo.admin_access === 1 || roleInfo.admin_access === '1' || roleInfo.admin_access === true,
30
+ app: roleInfo.app_access === 1 || roleInfo.app_access === '1' || roleInfo.app_access === true,
31
+ };
32
+ generatedAccountability.permissions = await (0, get_permissions_1.getPermissions)(generatedAccountability, context.schema);
33
+ }
34
+ return generatedAccountability;
35
+ }
36
+ exports.getAccountabilityForRole = getAccountabilityForRole;
@@ -80,12 +80,6 @@ async function getASTFromQuery(collection, query, schema, options) {
80
80
  if (name in query.alias) {
81
81
  name = query.alias[fieldKey];
82
82
  }
83
- // check for junction alias (it is one of the value instead of the key)
84
- if (Object.values(query.alias).includes(name)) {
85
- const aliasKey = Object.keys(query.alias).find((key) => { var _a; return ((_a = query.alias) === null || _a === void 0 ? void 0 : _a[key]) === name; });
86
- if (aliasKey && fieldKey !== aliasKey)
87
- name = aliasKey;
88
- }
89
83
  }
90
84
  const isRelational = name.includes('.') ||
91
85
  // We'll always treat top level o2m fields as a related item. This is an alias field, otherwise it won't return
@@ -93,7 +87,7 @@ async function getASTFromQuery(collection, query, schema, options) {
93
87
  !!schema.relations.find((relation) => { var _a; return relation.related_collection === parentCollection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === name; });
94
88
  if (isRelational) {
95
89
  // field is relational
96
- const parts = name.split('.');
90
+ const parts = fieldKey.split('.');
97
91
  let rootField = parts[0];
98
92
  let collectionScope = null;
99
93
  // a2o related collection scoped field selector `fields=sections.section_id:headings.title`
@@ -3,9 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const get_local_type_1 = __importDefault(require("./get-local-type"));
7
- const logger_1 = __importDefault(require("../logger"));
8
6
  const env_1 = __importDefault(require("../env"));
7
+ const logger_1 = __importDefault(require("../logger"));
8
+ const get_local_type_1 = __importDefault(require("./get-local-type"));
9
+ const parse_json_1 = require("./parse-json");
9
10
  function getDefaultValue(column) {
10
11
  var _a;
11
12
  const type = (0, get_local_type_1.default)(column);
@@ -59,7 +60,7 @@ function castToObject(value) {
59
60
  return value;
60
61
  if (typeof value === 'string') {
61
62
  try {
62
- return JSON.parse(value);
63
+ return (0, parse_json_1.parseJSON)(value);
63
64
  }
64
65
  catch (err) {
65
66
  if (env_1.default.NODE_ENV === 'development') {
@@ -1,2 +1,2 @@
1
- import { Permission, Accountability, SchemaOverview } from '@directus/shared/types';
1
+ import { Accountability, Permission, SchemaOverview } from '@directus/shared/types';
2
2
  export declare function getPermissions(accountability: Accountability, schema: SchemaOverview): Promise<Permission[]>;
@@ -6,15 +6,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getPermissions = void 0;
7
7
  const utils_1 = require("@directus/shared/utils");
8
8
  const lodash_1 = require("lodash");
9
+ const object_hash_1 = __importDefault(require("object-hash"));
10
+ const cache_1 = require("../cache");
9
11
  const database_1 = __importDefault(require("../database"));
10
12
  const app_access_permissions_1 = require("../database/system-data/app-access-permissions");
13
+ const env_1 = __importDefault(require("../env"));
14
+ const roles_1 = require("../services/roles");
15
+ const users_1 = require("../services/users");
11
16
  const merge_permissions_1 = require("../utils/merge-permissions");
12
17
  const merge_permissions_for_share_1 = require("./merge-permissions-for-share");
13
- const users_1 = require("../services/users");
14
- const roles_1 = require("../services/roles");
15
- const cache_1 = require("../cache");
16
- const object_hash_1 = __importDefault(require("object-hash"));
17
- const env_1 = __importDefault(require("../env"));
18
+ const parse_json_1 = require("./parse-json");
18
19
  async function getPermissions(accountability, schema) {
19
20
  const database = (0, database_1.default)();
20
21
  const { systemCache, cache } = (0, cache_1.getCache)();
@@ -84,19 +85,19 @@ function parsePermissions(permissions) {
84
85
  permissions = permissions.map((permissionRaw) => {
85
86
  const permission = (0, lodash_1.cloneDeep)(permissionRaw);
86
87
  if (permission.permissions && typeof permission.permissions === 'string') {
87
- permission.permissions = JSON.parse(permission.permissions);
88
+ permission.permissions = (0, parse_json_1.parseJSON)(permission.permissions);
88
89
  }
89
90
  else if (permission.permissions === null) {
90
91
  permission.permissions = {};
91
92
  }
92
93
  if (permission.validation && typeof permission.validation === 'string') {
93
- permission.validation = JSON.parse(permission.validation);
94
+ permission.validation = (0, parse_json_1.parseJSON)(permission.validation);
94
95
  }
95
96
  else if (permission.validation === null) {
96
97
  permission.validation = {};
97
98
  }
98
99
  if (permission.presets && typeof permission.presets === 'string') {
99
- permission.presets = JSON.parse(permission.presets);
100
+ permission.presets = (0, parse_json_1.parseJSON)(permission.presets);
100
101
  }
101
102
  else if (permission.presets === null) {
102
103
  permission.presets = {};
@@ -17,6 +17,7 @@ const logger_1 = __importDefault(require("../logger"));
17
17
  const services_1 = require("../services");
18
18
  const get_default_value_1 = __importDefault(require("./get-default-value"));
19
19
  const get_local_type_1 = __importDefault(require("./get-local-type"));
20
+ const parse_json_1 = require("./parse-json");
20
21
  async function getSchema(options) {
21
22
  const database = (options === null || options === void 0 ? void 0 : options.database) || (0, database_1.default)();
22
23
  const schemaInspector = (0, schema_1.default)(database);
@@ -119,7 +120,7 @@ async function getDatabaseSchema(database, schemaInspector) {
119
120
  const type = (existing && (0, get_local_type_1.default)(column, { special })) || 'alias';
120
121
  let validation = (_a = field.validation) !== null && _a !== void 0 ? _a : null;
121
122
  if (validation && typeof validation === 'string')
122
- validation = JSON.parse(validation);
123
+ validation = (0, parse_json_1.parseJSON)(validation);
123
124
  result.collections[field.collection].fields[field.field] = {
124
125
  field: field.field,
125
126
  defaultValue: (_b = existing === null || existing === void 0 ? void 0 : existing.defaultValue) !== null && _b !== void 0 ? _b : null,
@@ -16,17 +16,23 @@ async function getSnapshot(options) {
16
16
  const collectionsService = new services_1.CollectionsService({ knex: database, schema });
17
17
  const fieldsService = new services_1.FieldsService({ knex: database, schema });
18
18
  const relationsService = new services_1.RelationsService({ knex: database, schema });
19
- const [collections, fields, relations] = await Promise.all([
19
+ const [collectionsRaw, fieldsRaw, relationsRaw] = await Promise.all([
20
20
  collectionsService.readByQuery(),
21
21
  fieldsService.readAll(),
22
22
  relationsService.readAll(),
23
23
  ]);
24
+ const collectionsFiltered = collectionsRaw.filter((item) => excludeSystem(item));
25
+ const fieldsFiltered = fieldsRaw.filter((item) => excludeSystem(item)).map(omitID);
26
+ const relationsFiltered = relationsRaw.filter((item) => excludeSystem(item)).map(omitID);
27
+ const collectionsSorted = (0, lodash_1.sortBy)((0, lodash_1.mapValues)(collectionsFiltered, sortDeep), ['collection']);
28
+ const fieldsSorted = (0, lodash_1.sortBy)((0, lodash_1.mapValues)(fieldsFiltered, sortDeep), ['collection', 'field']);
29
+ const relationsSorted = (0, lodash_1.sortBy)((0, lodash_1.mapValues)(relationsFiltered, sortDeep), ['collection', 'field']);
24
30
  return {
25
31
  version: 1,
26
32
  directus: package_json_1.version,
27
- collections: collections.filter((item) => excludeSystem(item)),
28
- fields: fields.filter((item) => excludeSystem(item)).map(omitID),
29
- relations: relations.filter((item) => excludeSystem(item)).map(omitID),
33
+ collections: collectionsSorted,
34
+ fields: fieldsSorted,
35
+ relations: relationsSorted,
30
36
  };
31
37
  }
32
38
  exports.getSnapshot = getSnapshot;
@@ -39,3 +45,15 @@ function excludeSystem(item) {
39
45
  function omitID(item) {
40
46
  return (0, lodash_1.omit)(item, 'meta.id');
41
47
  }
48
+ function sortDeep(raw) {
49
+ if ((0, lodash_1.isPlainObject)(raw)) {
50
+ const mapped = (0, lodash_1.mapValues)(raw, sortDeep);
51
+ const pairs = (0, lodash_1.toPairs)(mapped);
52
+ const sorted = (0, lodash_1.sortBy)(pairs);
53
+ return (0, lodash_1.fromPairs)(sorted);
54
+ }
55
+ if ((0, lodash_1.isArray)(raw)) {
56
+ return (0, lodash_1.sortBy)(raw);
57
+ }
58
+ return raw;
59
+ }
@@ -0,0 +1,3 @@
1
+ export declare function applyOperationOptions(options: Record<string, any>, data: Record<string, any>): Record<string, any>;
2
+ export declare function optionToObject<T>(option: T): Exclude<T, string>;
3
+ export declare function optionToString(option: unknown): string;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.optionToString = exports.optionToObject = exports.applyOperationOptions = void 0;
4
+ const micromustache_1 = require("micromustache");
5
+ const parse_json_1 = require("./parse-json");
6
+ function resolveFn(path, scope) {
7
+ if (!scope)
8
+ return undefined;
9
+ const value = (0, micromustache_1.get)(scope, path);
10
+ return typeof value === 'object' ? JSON.stringify(value) : value;
11
+ }
12
+ function renderMustache(item, scope) {
13
+ if (typeof item === 'string') {
14
+ return (0, micromustache_1.renderFn)(item, resolveFn, scope, { explicit: true });
15
+ }
16
+ else if (Array.isArray(item)) {
17
+ return item.map((element) => renderMustache(element, scope));
18
+ }
19
+ else if (typeof item === 'object' && item !== null) {
20
+ return Object.fromEntries(Object.entries(item).map(([key, value]) => [key, renderMustache(value, scope)]));
21
+ }
22
+ else {
23
+ return item;
24
+ }
25
+ }
26
+ function applyOperationOptions(options, data) {
27
+ return Object.fromEntries(Object.entries(options).map(([key, value]) => {
28
+ if (typeof value === 'string') {
29
+ const single = value.match(/^\{\{\s*([^}\s]+)\s*\}\}$/);
30
+ if (single !== null) {
31
+ return [key, (0, micromustache_1.get)(data, single[1])];
32
+ }
33
+ }
34
+ return [key, renderMustache(value, data)];
35
+ }));
36
+ }
37
+ exports.applyOperationOptions = applyOperationOptions;
38
+ function optionToObject(option) {
39
+ return typeof option === 'string' ? (0, parse_json_1.parseJSON)(option) : option;
40
+ }
41
+ exports.optionToObject = optionToObject;
42
+ function optionToString(option) {
43
+ return typeof option === 'object' ? JSON.stringify(option) : String(option);
44
+ }
45
+ exports.optionToString = optionToString;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Run JSON.parse, but ignore `__proto__` properties. This prevents prototype pollution attacks
3
+ */
4
+ export declare function parseJSON(input: string): any;
5
+ export declare function noproto<T>(key: string, value: T): T | void;