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
@@ -45,6 +45,8 @@ const get_module_default_1 = __importDefault(require("./utils/get-module-default
45
45
  const lodash_1 = require("lodash");
46
46
  const chokidar_1 = __importDefault(require("chokidar"));
47
47
  const utils_1 = require("@directus/shared/utils");
48
+ const flows_1 = require("./flows");
49
+ const globby_1 = __importDefault(require("globby"));
48
50
  let extensionManager;
49
51
  function getExtensionManager() {
50
52
  if (extensionManager) {
@@ -63,7 +65,7 @@ class ExtensionManager {
63
65
  this.isLoaded = false;
64
66
  this.extensions = [];
65
67
  this.appExtensions = {};
66
- this.apiExtensions = { hooks: [], endpoints: [] };
68
+ this.apiExtensions = { hooks: [], endpoints: [], operations: [] };
67
69
  this.watcher = null;
68
70
  this.options = defaultOptions;
69
71
  this.apiEmitter = new emitter_1.Emitter();
@@ -131,6 +133,7 @@ class ExtensionManager {
131
133
  }
132
134
  this.registerHooks();
133
135
  this.registerEndpoints();
136
+ await this.registerOperations();
134
137
  if (env_1.default.SERVE_APP) {
135
138
  this.appExtensions = await this.generateExtensionBundles();
136
139
  }
@@ -139,6 +142,7 @@ class ExtensionManager {
139
142
  async unload() {
140
143
  this.unregisterHooks();
141
144
  this.unregisterEndpoints();
145
+ this.unregisterOperations();
142
146
  this.apiEmitter.offAll();
143
147
  if (env_1.default.SERVE_APP) {
144
148
  this.appExtensions = {};
@@ -148,8 +152,13 @@ class ExtensionManager {
148
152
  initializeWatcher() {
149
153
  if (this.options.watch && !this.watcher) {
150
154
  logger_1.default.info('Watching extensions for changes...');
151
- const localExtensionPaths = (env_1.default.SERVE_APP ? constants_1.EXTENSION_TYPES : constants_1.API_EXTENSION_TYPES).map((type) => path_1.default.posix.join(path_1.default.relative('.', env_1.default.EXTENSIONS_PATH).split(path_1.default.sep).join(path_1.default.posix.sep), (0, utils_1.pluralize)(type), '*', 'index.js'));
152
- this.watcher = chokidar_1.default.watch([path_1.default.resolve('.', 'package.json'), ...localExtensionPaths], {
155
+ const localExtensionPaths = (env_1.default.SERVE_APP ? constants_1.EXTENSION_TYPES : constants_1.API_EXTENSION_TYPES).flatMap((type) => {
156
+ const typeDir = path_1.default.posix.join(path_1.default.relative('.', env_1.default.EXTENSIONS_PATH).split(path_1.default.sep).join(path_1.default.posix.sep), (0, utils_1.pluralize)(type));
157
+ return (0, utils_1.isHybridExtension)(type)
158
+ ? [path_1.default.posix.join(typeDir, '*', 'app.js'), path_1.default.posix.join(typeDir, '*', 'api.js')]
159
+ : path_1.default.posix.join(typeDir, '*', 'index.js');
160
+ });
161
+ this.watcher = chokidar_1.default.watch([path_1.default.resolve('package.json'), ...localExtensionPaths], {
153
162
  ignoreInitial: true,
154
163
  });
155
164
  this.watcher
@@ -162,9 +171,14 @@ class ExtensionManager {
162
171
  if (this.watcher) {
163
172
  const toPackageExtensionPaths = (extensions) => extensions
164
173
  .filter((extension) => !extension.local)
165
- .map((extension) => extension.type !== 'pack'
166
- ? path_1.default.resolve(extension.path, extension.entrypoint || '')
167
- : path_1.default.resolve(extension.path, 'package.json'));
174
+ .flatMap((extension) => extension.type === constants_1.PACK_EXTENSION_TYPE
175
+ ? path_1.default.resolve(extension.path, 'package.json')
176
+ : (0, utils_1.isExtensionObject)(extension, constants_1.HYBRID_EXTENSION_TYPES)
177
+ ? [
178
+ path_1.default.resolve(extension.path, extension.entrypoint.app),
179
+ path_1.default.resolve(extension.path, extension.entrypoint.api),
180
+ ]
181
+ : path_1.default.resolve(extension.path, extension.entrypoint));
168
182
  const addedPackageExtensionPaths = toPackageExtensionPaths(added);
169
183
  const removedPackageExtensionPaths = toPackageExtensionPaths(removed);
170
184
  this.watcher.add(addedPackageExtensionPaths);
@@ -223,7 +237,10 @@ class ExtensionManager {
223
237
  const hooks = this.extensions.filter((extension) => extension.type === 'hook');
224
238
  for (const hook of hooks) {
225
239
  try {
226
- this.registerHook(hook);
240
+ const hookPath = path_1.default.resolve(hook.path, hook.entrypoint);
241
+ const hookInstance = require(hookPath);
242
+ const config = (0, get_module_default_1.default)(hookInstance);
243
+ this.registerHook(config, hookPath);
227
244
  }
228
245
  catch (error) {
229
246
  logger_1.default.warn(`Couldn't register hook "${hook.name}"`);
@@ -235,7 +252,10 @@ class ExtensionManager {
235
252
  const endpoints = this.extensions.filter((extension) => extension.type === 'endpoint');
236
253
  for (const endpoint of endpoints) {
237
254
  try {
238
- this.registerEndpoint(endpoint, this.endpointRouter);
255
+ const endpointPath = path_1.default.resolve(endpoint.path, endpoint.entrypoint);
256
+ const endpointInstance = require(endpointPath);
257
+ const config = (0, get_module_default_1.default)(endpointInstance);
258
+ this.registerEndpoint(config, endpointPath, this.endpointRouter);
239
259
  }
240
260
  catch (error) {
241
261
  logger_1.default.warn(`Couldn't register endpoint "${endpoint.name}"`);
@@ -243,12 +263,33 @@ class ExtensionManager {
243
263
  }
244
264
  }
245
265
  }
246
- registerHook(hook) {
247
- const hookPath = path_1.default.resolve(hook.path, hook.entrypoint || '');
248
- const hookInstance = require(hookPath);
249
- const register = (0, get_module_default_1.default)(hookInstance);
266
+ async registerOperations() {
267
+ const internalPaths = await (0, globby_1.default)(path_1.default.posix.join(path_1.default.relative('.', __dirname).split(path_1.default.sep).join(path_1.default.posix.sep), 'operations/*/index.(js|ts)'));
268
+ const internalOperations = internalPaths.map((internalPath) => {
269
+ const dirs = internalPath.split(path_1.default.sep);
270
+ return {
271
+ name: dirs[dirs.length - 2],
272
+ path: dirs.slice(0, -1).join(path_1.default.sep),
273
+ entrypoint: { api: dirs[dirs.length - 1] },
274
+ };
275
+ });
276
+ const operations = this.extensions.filter((extension) => extension.type === 'operation');
277
+ for (const operation of [...internalOperations, ...operations]) {
278
+ try {
279
+ const operationPath = path_1.default.resolve(operation.path, operation.entrypoint.api);
280
+ const operationInstance = require(operationPath);
281
+ const config = (0, get_module_default_1.default)(operationInstance);
282
+ this.registerOperation(config, operationPath);
283
+ }
284
+ catch (error) {
285
+ logger_1.default.warn(`Couldn't register operation "${operation.name}"`);
286
+ logger_1.default.warn(error);
287
+ }
288
+ }
289
+ }
290
+ registerHook(register, path) {
250
291
  const hookHandler = {
251
- path: hookPath,
292
+ path,
252
293
  events: [],
253
294
  };
254
295
  const registerFunctions = {
@@ -309,12 +350,9 @@ class ExtensionManager {
309
350
  });
310
351
  this.apiExtensions.hooks.push(hookHandler);
311
352
  }
312
- registerEndpoint(endpoint, router) {
313
- const endpointPath = path_1.default.resolve(endpoint.path, endpoint.entrypoint || '');
314
- const endpointInstance = require(endpointPath);
315
- const mod = (0, get_module_default_1.default)(endpointInstance);
316
- const register = typeof mod === 'function' ? mod : mod.handler;
317
- const routeName = typeof mod === 'function' ? endpoint.name : mod.id;
353
+ registerEndpoint(config, path, router) {
354
+ const register = typeof config === 'function' ? config : config.handler;
355
+ const routeName = typeof config === 'function' ? config.name : config.id;
318
356
  const scopedRouter = express_1.default.Router();
319
357
  router.use(`/${routeName}`, scopedRouter);
320
358
  register(scopedRouter, {
@@ -327,7 +365,14 @@ class ExtensionManager {
327
365
  getSchema: get_schema_1.getSchema,
328
366
  });
329
367
  this.apiExtensions.endpoints.push({
330
- path: endpointPath,
368
+ path,
369
+ });
370
+ }
371
+ registerOperation(config, path) {
372
+ const flowManager = (0, flows_1.getFlowManager)();
373
+ flowManager.addOperation(config.id, config.handler);
374
+ this.apiExtensions.operations.push({
375
+ path,
331
376
  });
332
377
  }
333
378
  unregisterHooks() {
@@ -359,4 +404,12 @@ class ExtensionManager {
359
404
  this.endpointRouter.stack = [];
360
405
  this.apiExtensions.endpoints = [];
361
406
  }
407
+ unregisterOperations() {
408
+ for (const operation of this.apiExtensions.operations) {
409
+ delete require.cache[require.resolve(operation.path)];
410
+ }
411
+ const flowManager = (0, flows_1.getFlowManager)();
412
+ flowManager.clearOperations();
413
+ this.apiExtensions.operations = [];
414
+ }
362
415
  }
@@ -0,0 +1,17 @@
1
+ import { OperationHandler } from '@directus/shared/types';
2
+ export declare function getFlowManager(): FlowManager;
3
+ declare class FlowManager {
4
+ private operations;
5
+ private triggerHandlers;
6
+ private operationFlowHandlers;
7
+ private webhookFlowHandlers;
8
+ initialize(): Promise<void>;
9
+ reload(): Promise<void>;
10
+ addOperation(id: string, operation: OperationHandler): void;
11
+ clearOperations(): void;
12
+ runOperationFlow(id: string, data: unknown, context: Record<string, unknown>): Promise<unknown>;
13
+ runWebhookFlow(id: string, data: unknown, context: Record<string, unknown>): Promise<unknown>;
14
+ private executeFlow;
15
+ private executeOperation;
16
+ }
17
+ export {};
package/dist/flows.js ADDED
@@ -0,0 +1,310 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __importDefault = (this && this.__importDefault) || function (mod) {
22
+ return (mod && mod.__esModule) ? mod : { "default": mod };
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.getFlowManager = void 0;
26
+ const sharedExceptions = __importStar(require("@directus/shared/exceptions"));
27
+ const types_1 = require("@directus/shared/types");
28
+ const micromustache_1 = require("micromustache");
29
+ const node_cron_1 = require("node-cron");
30
+ const database_1 = __importDefault(require("./database"));
31
+ const emitter_1 = __importDefault(require("./emitter"));
32
+ const env_1 = __importDefault(require("./env"));
33
+ const exceptions = __importStar(require("./exceptions"));
34
+ const logger_1 = __importDefault(require("./logger"));
35
+ const services = __importStar(require("./services"));
36
+ const services_1 = require("./services");
37
+ const construct_flow_tree_1 = require("./utils/construct-flow-tree");
38
+ const get_schema_1 = require("./utils/get-schema");
39
+ const activity_1 = require("./services/activity");
40
+ const revisions_1 = require("./services/revisions");
41
+ const lodash_1 = require("lodash");
42
+ const messenger_1 = require("./messenger");
43
+ const fast_redact_1 = __importDefault(require("fast-redact"));
44
+ const operation_options_1 = require("./utils/operation-options");
45
+ let flowManager;
46
+ const redactLogs = (0, fast_redact_1.default)({
47
+ censor: '--redacted--',
48
+ paths: ['*.headers.authorization', '*.access_token', '*.headers.cookie'],
49
+ serialize: false,
50
+ });
51
+ function getFlowManager() {
52
+ if (flowManager) {
53
+ return flowManager;
54
+ }
55
+ flowManager = new FlowManager();
56
+ return flowManager;
57
+ }
58
+ exports.getFlowManager = getFlowManager;
59
+ const TRIGGER_KEY = '$trigger';
60
+ const ACCOUNTABILITY_KEY = '$accountability';
61
+ const LAST_KEY = '$last';
62
+ class FlowManager {
63
+ constructor() {
64
+ this.operations = {};
65
+ this.triggerHandlers = [];
66
+ this.operationFlowHandlers = {};
67
+ this.webhookFlowHandlers = {};
68
+ }
69
+ async initialize() {
70
+ var _a, _b, _c, _d, _e, _f, _g;
71
+ const flowsService = new services_1.FlowsService({ knex: (0, database_1.default)(), schema: await (0, get_schema_1.getSchema)() });
72
+ const flows = await flowsService.readByQuery({
73
+ filter: { status: { _eq: 'active' } },
74
+ fields: ['*', 'operations.*'],
75
+ });
76
+ const flowTrees = flows.map((flow) => (0, construct_flow_tree_1.constructFlowTree)(flow));
77
+ for (const flow of flowTrees) {
78
+ if (flow.trigger === 'event') {
79
+ const events = (_d = (_c = (_b = (_a = flow.options) === null || _a === void 0 ? void 0 : _a.scope) === null || _b === void 0 ? void 0 : _b.map((scope) => {
80
+ var _a, _b, _c;
81
+ if (['items.create', 'items.update', 'items.delete'].includes(scope)) {
82
+ return ((_c = (_b = (_a = flow.options) === null || _a === void 0 ? void 0 : _a.collections) === null || _b === void 0 ? void 0 : _b.map((collection) => {
83
+ if (collection.startsWith('directus_')) {
84
+ const action = scope.split('.')[1];
85
+ return collection.substring(9) + '.' + action;
86
+ }
87
+ return `${collection}.${scope}`;
88
+ })) !== null && _c !== void 0 ? _c : []);
89
+ }
90
+ return scope;
91
+ })) === null || _c === void 0 ? void 0 : _c.flat()) !== null && _d !== void 0 ? _d : [];
92
+ if (flow.options.type === 'filter') {
93
+ const handler = (payload, meta, context) => this.executeFlow(flow, { payload, ...meta }, {
94
+ accountability: context.accountability,
95
+ database: context.database,
96
+ getSchema: context.schema ? () => context.schema : get_schema_1.getSchema,
97
+ });
98
+ events.forEach((event) => emitter_1.default.onFilter(event, handler));
99
+ this.triggerHandlers.push({
100
+ id: flow.id,
101
+ events: events.map((event) => ({ type: 'filter', name: event, handler })),
102
+ });
103
+ }
104
+ else if (flow.options.type === 'action') {
105
+ const handler = (meta, context) => this.executeFlow(flow, meta, {
106
+ accountability: context.accountability,
107
+ database: context.database,
108
+ getSchema: context.schema ? () => context.schema : get_schema_1.getSchema,
109
+ });
110
+ events.forEach((event) => emitter_1.default.onAction(event, handler));
111
+ this.triggerHandlers.push({
112
+ id: flow.id,
113
+ events: events.map((event) => ({ type: 'action', name: event, handler })),
114
+ });
115
+ }
116
+ }
117
+ else if (flow.trigger === 'schedule') {
118
+ if ((0, node_cron_1.validate)(flow.options.cron)) {
119
+ const task = (0, node_cron_1.schedule)(flow.options.cron, async () => {
120
+ try {
121
+ await this.executeFlow(flow);
122
+ }
123
+ catch (error) {
124
+ logger_1.default.error(error);
125
+ }
126
+ });
127
+ this.triggerHandlers.push({ id: flow.id, events: [{ type: flow.trigger, task }] });
128
+ }
129
+ else {
130
+ logger_1.default.warn(`Couldn't register cron trigger. Provided cron is invalid: ${flow.options.cron}`);
131
+ }
132
+ }
133
+ else if (flow.trigger === 'operation') {
134
+ const handler = (data, context) => this.executeFlow(flow, data, context);
135
+ this.operationFlowHandlers[flow.id] = handler;
136
+ }
137
+ else if (flow.trigger === 'webhook') {
138
+ const handler = (data, context) => {
139
+ if (flow.options.async) {
140
+ this.executeFlow(flow, data, context);
141
+ }
142
+ else {
143
+ return this.executeFlow(flow, data, context);
144
+ }
145
+ };
146
+ const method = (_f = (_e = flow.options) === null || _e === void 0 ? void 0 : _e.method) !== null && _f !== void 0 ? _f : 'GET';
147
+ // Default return to $last for webhooks
148
+ flow.options.return = (_g = flow.options.return) !== null && _g !== void 0 ? _g : '$last';
149
+ this.webhookFlowHandlers[`${method}-${flow.id}`] = handler;
150
+ }
151
+ else if (flow.trigger === 'manual') {
152
+ const handler = (data, context) => {
153
+ var _a, _b, _c;
154
+ const enabledCollections = (_b = (_a = flow.options) === null || _a === void 0 ? void 0 : _a.collections) !== null && _b !== void 0 ? _b : [];
155
+ const targetCollection = (_c = data) === null || _c === void 0 ? void 0 : _c.body.collection;
156
+ if (!targetCollection) {
157
+ logger_1.default.warn(`Manual trigger requires "collection" to be specified in the payload`);
158
+ throw new exceptions.ForbiddenException();
159
+ }
160
+ if (enabledCollections.length === 0) {
161
+ logger_1.default.warn(`There is no collections configured for this manual trigger`);
162
+ throw new exceptions.ForbiddenException();
163
+ }
164
+ if (!enabledCollections.includes(targetCollection)) {
165
+ logger_1.default.warn(`Specified collection must be one of: ${enabledCollections.join(', ')}.`);
166
+ throw new exceptions.ForbiddenException();
167
+ }
168
+ if (flow.options.async) {
169
+ this.executeFlow(flow, data, context);
170
+ }
171
+ else {
172
+ return this.executeFlow(flow, data, context);
173
+ }
174
+ };
175
+ // Default return to $last for manual
176
+ flow.options.return = '$last';
177
+ this.webhookFlowHandlers[`POST-${flow.id}`] = handler;
178
+ }
179
+ }
180
+ (0, messenger_1.getMessenger)().subscribe('flows', (event) => {
181
+ if (event.type === 'reload') {
182
+ this.reload();
183
+ }
184
+ });
185
+ }
186
+ async reload() {
187
+ for (const trigger of this.triggerHandlers) {
188
+ trigger.events.forEach((event) => {
189
+ switch (event.type) {
190
+ case 'filter':
191
+ emitter_1.default.offFilter(event.name, event.handler);
192
+ break;
193
+ case 'action':
194
+ emitter_1.default.offAction(event.name, event.handler);
195
+ break;
196
+ case 'schedule':
197
+ event.task.stop();
198
+ break;
199
+ }
200
+ });
201
+ }
202
+ this.triggerHandlers = [];
203
+ this.operationFlowHandlers = {};
204
+ this.webhookFlowHandlers = {};
205
+ await this.initialize();
206
+ }
207
+ addOperation(id, operation) {
208
+ this.operations[id] = operation;
209
+ }
210
+ clearOperations() {
211
+ this.operations = {};
212
+ }
213
+ async runOperationFlow(id, data, context) {
214
+ if (!(id in this.operationFlowHandlers)) {
215
+ logger_1.default.warn(`Couldn't find operation triggered flow with id "${id}"`);
216
+ return null;
217
+ }
218
+ const handler = this.operationFlowHandlers[id];
219
+ return handler(data, context);
220
+ }
221
+ async runWebhookFlow(id, data, context) {
222
+ if (!(id in this.webhookFlowHandlers)) {
223
+ logger_1.default.warn(`Couldn't find webhook or manual triggered flow with id "${id}"`);
224
+ throw new exceptions.ForbiddenException();
225
+ }
226
+ const handler = this.webhookFlowHandlers[id];
227
+ return handler(data, context);
228
+ }
229
+ async executeFlow(flow, data = null, context = {}) {
230
+ var _a, _b, _c, _d, _e, _f;
231
+ const database = (_a = context.database) !== null && _a !== void 0 ? _a : (0, database_1.default)();
232
+ const schema = (_b = context.schema) !== null && _b !== void 0 ? _b : (await (0, get_schema_1.getSchema)({ database }));
233
+ const keyedData = {
234
+ [TRIGGER_KEY]: data,
235
+ [LAST_KEY]: data,
236
+ [ACCOUNTABILITY_KEY]: (_c = context === null || context === void 0 ? void 0 : context.accountability) !== null && _c !== void 0 ? _c : null,
237
+ };
238
+ let nextOperation = flow.operation;
239
+ const steps = [];
240
+ while (nextOperation !== null) {
241
+ const { successor, data, status, options } = await this.executeOperation(nextOperation, keyedData, context);
242
+ keyedData[nextOperation.key] = data;
243
+ keyedData[LAST_KEY] = data;
244
+ steps.push({ operation: nextOperation.id, key: nextOperation.key, status, options });
245
+ nextOperation = successor;
246
+ }
247
+ if (flow.accountability !== null) {
248
+ const activityService = new activity_1.ActivityService({
249
+ knex: database,
250
+ schema: schema,
251
+ });
252
+ const accountability = context === null || context === void 0 ? void 0 : context.accountability;
253
+ const activity = await activityService.createOne({
254
+ action: types_1.Action.RUN,
255
+ user: (_d = accountability === null || accountability === void 0 ? void 0 : accountability.user) !== null && _d !== void 0 ? _d : null,
256
+ collection: 'directus_flows',
257
+ ip: (_e = accountability === null || accountability === void 0 ? void 0 : accountability.ip) !== null && _e !== void 0 ? _e : null,
258
+ user_agent: (_f = accountability === null || accountability === void 0 ? void 0 : accountability.userAgent) !== null && _f !== void 0 ? _f : null,
259
+ item: flow.id,
260
+ });
261
+ if (flow.accountability === 'all') {
262
+ const revisionsService = new revisions_1.RevisionsService({
263
+ knex: database,
264
+ schema: schema,
265
+ });
266
+ await revisionsService.createOne({
267
+ activity: activity,
268
+ collection: 'directus_flows',
269
+ item: flow.id,
270
+ data: {
271
+ steps: steps,
272
+ data: redactLogs((0, lodash_1.omit)(keyedData, '$accountability.permissions')), // Permissions is a ton of data, and is just a copy of what's in the directus_permissions table
273
+ },
274
+ });
275
+ }
276
+ }
277
+ if (flow.options.return === '$all') {
278
+ return keyedData;
279
+ }
280
+ else if (flow.options.return) {
281
+ return (0, micromustache_1.get)(keyedData, flow.options.return);
282
+ }
283
+ return undefined;
284
+ }
285
+ async executeOperation(operation, keyedData, context = {}) {
286
+ if (!(operation.type in this.operations)) {
287
+ logger_1.default.warn(`Couldn't find operation ${operation.type}`);
288
+ return { successor: null, status: 'unknown', data: null, options: null };
289
+ }
290
+ const handler = this.operations[operation.type];
291
+ const options = (0, operation_options_1.applyOperationOptions)(operation.options, keyedData);
292
+ try {
293
+ const result = await handler(options, {
294
+ services,
295
+ exceptions: { ...exceptions, ...sharedExceptions },
296
+ env: env_1.default,
297
+ database: (0, database_1.default)(),
298
+ logger: logger_1.default,
299
+ getSchema: get_schema_1.getSchema,
300
+ data: keyedData,
301
+ accountability: null,
302
+ ...context,
303
+ });
304
+ return { successor: operation.resolve, status: 'resolve', data: result !== null && result !== void 0 ? result : null, options };
305
+ }
306
+ catch (error) {
307
+ return { successor: operation.reject, status: 'reject', data: error !== null && error !== void 0 ? error : null, options };
308
+ }
309
+ }
310
+ }
@@ -0,0 +1,24 @@
1
+ import IORedis from 'ioredis';
2
+ export declare type MessengerSubscriptionCallback = (payload: Record<string, any>) => void;
3
+ export interface Messenger {
4
+ publish: (channel: string, payload: Record<string, any>) => void;
5
+ subscribe: (channel: string, callback: MessengerSubscriptionCallback) => void;
6
+ unsubscribe: (channel: string) => void;
7
+ }
8
+ export declare class MessengerMemory implements Messenger {
9
+ handlers: Record<string, MessengerSubscriptionCallback>;
10
+ constructor();
11
+ publish(channel: string, payload: Record<string, any>): void;
12
+ subscribe(channel: string, callback: MessengerSubscriptionCallback): void;
13
+ unsubscribe(channel: string): void;
14
+ }
15
+ export declare class MessengerRedis implements Messenger {
16
+ namespace: string;
17
+ pub: IORedis.Redis;
18
+ sub: IORedis.Redis;
19
+ constructor();
20
+ publish(channel: string, payload: Record<string, any>): void;
21
+ subscribe(channel: string, callback: MessengerSubscriptionCallback): void;
22
+ unsubscribe(channel: string): void;
23
+ }
24
+ export declare function getMessenger(): Messenger;
@@ -0,0 +1,64 @@
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
+ exports.getMessenger = exports.MessengerRedis = exports.MessengerMemory = void 0;
7
+ const ioredis_1 = __importDefault(require("ioredis"));
8
+ const env_1 = __importDefault(require("./env"));
9
+ const get_config_from_env_1 = require("./utils/get-config-from-env");
10
+ const parse_json_1 = require("./utils/parse-json");
11
+ class MessengerMemory {
12
+ constructor() {
13
+ this.handlers = {};
14
+ }
15
+ publish(channel, payload) {
16
+ var _a, _b;
17
+ (_b = (_a = this.handlers)[channel]) === null || _b === void 0 ? void 0 : _b.call(_a, payload);
18
+ }
19
+ subscribe(channel, callback) {
20
+ this.handlers[channel] = callback;
21
+ }
22
+ unsubscribe(channel) {
23
+ delete this.handlers[channel];
24
+ }
25
+ }
26
+ exports.MessengerMemory = MessengerMemory;
27
+ class MessengerRedis {
28
+ constructor() {
29
+ var _a, _b, _c;
30
+ const config = (0, get_config_from_env_1.getConfigFromEnv)('MESSENGER_REDIS');
31
+ this.pub = new ioredis_1.default((_a = env_1.default.MESSENGER_REDIS) !== null && _a !== void 0 ? _a : config);
32
+ this.sub = new ioredis_1.default((_b = env_1.default.MESSENGER_REDIS) !== null && _b !== void 0 ? _b : config);
33
+ this.namespace = (_c = env_1.default.MESSENGER_NAMESPACE) !== null && _c !== void 0 ? _c : 'directus';
34
+ }
35
+ publish(channel, payload) {
36
+ this.pub.publish(`${this.namespace}:${channel}`, JSON.stringify(payload));
37
+ }
38
+ subscribe(channel, callback) {
39
+ this.sub.subscribe(`${this.namespace}:${channel}`);
40
+ this.sub.on('message', (messageChannel, payloadString) => {
41
+ const payload = (0, parse_json_1.parseJSON)(payloadString);
42
+ if (messageChannel === `${this.namespace}:${channel}`) {
43
+ callback(payload);
44
+ }
45
+ });
46
+ }
47
+ unsubscribe(channel) {
48
+ this.sub.unsubscribe(`${this.namespace}:${channel}`);
49
+ }
50
+ }
51
+ exports.MessengerRedis = MessengerRedis;
52
+ let messenger;
53
+ function getMessenger() {
54
+ if (messenger)
55
+ return messenger;
56
+ if (env_1.default.MESSENGER_STORE === 'redis') {
57
+ messenger = new MessengerRedis();
58
+ }
59
+ else {
60
+ messenger = new MessengerMemory();
61
+ }
62
+ return messenger;
63
+ }
64
+ exports.getMessenger = getMessenger;
@@ -7,6 +7,7 @@ exports.parseGraphQL = void 0;
7
7
  const graphql_1 = require("graphql");
8
8
  const exceptions_1 = require("../exceptions");
9
9
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
10
+ const parse_json_1 = require("../utils/parse-json");
10
11
  exports.parseGraphQL = (0, async_handler_1.default)(async (req, res, next) => {
11
12
  if (req.method !== 'GET' && req.method !== 'POST') {
12
13
  throw new exceptions_1.MethodNotAllowedException('GraphQL only supports GET and POST requests.', { allow: ['GET', 'POST'] });
@@ -19,7 +20,7 @@ exports.parseGraphQL = (0, async_handler_1.default)(async (req, res, next) => {
19
20
  query = req.query.query || null;
20
21
  if (req.query.variables) {
21
22
  try {
22
- variables = JSON.parse(req.query.variables);
23
+ variables = (0, parse_json_1.parseJSON)(req.query.variables);
23
24
  }
24
25
  catch {
25
26
  throw new exceptions_1.InvalidQueryException(`Variables are invalid JSON.`);
@@ -0,0 +1,6 @@
1
+ import { Filter } from '@directus/shared/types';
2
+ declare type Options = {
3
+ filter: Filter;
4
+ };
5
+ declare const _default: import("@directus/shared/types").OperationApiConfig<Options>;
6
+ export default _default;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("@directus/shared/utils");
4
+ exports.default = (0, utils_1.defineOperationApi)({
5
+ id: 'condition',
6
+ handler: ({ filter }, { data }) => {
7
+ const errors = (0, utils_1.validatePayload)(filter, data);
8
+ if (errors.length > 0) {
9
+ throw errors;
10
+ }
11
+ else {
12
+ return null;
13
+ }
14
+ },
15
+ });
@@ -0,0 +1,8 @@
1
+ declare type Options = {
2
+ collection: string;
3
+ payload?: Record<string, any> | string | null;
4
+ emitEvents: boolean;
5
+ permissions: string;
6
+ };
7
+ declare const _default: import("@directus/shared/types").OperationApiConfig<Options>;
8
+ export default _default;