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.
- package/dist/app.js +8 -1
- package/dist/auth/drivers/oauth2.d.ts +1 -1
- package/dist/auth/drivers/oauth2.js +14 -11
- package/dist/auth/drivers/openid.d.ts +1 -1
- package/dist/auth/drivers/openid.js +14 -11
- package/dist/cli/commands/schema/apply.js +4 -3
- package/dist/cli/utils/create-env/env-stub.liquid +266 -9
- package/dist/controllers/activity.js +1 -1
- package/dist/controllers/assets.js +8 -9
- package/dist/controllers/flows.d.ts +2 -0
- package/dist/controllers/flows.js +157 -0
- package/dist/controllers/folders.js +1 -1
- package/dist/controllers/notifications.js +1 -1
- package/dist/controllers/operations.d.ts +2 -0
- package/dist/controllers/operations.js +138 -0
- package/dist/database/helpers/date/dialects/sqlite.js +6 -2
- package/dist/database/index.js +15 -19
- package/dist/database/migrations/20210225A-add-relations-sort-field.js +2 -1
- package/dist/database/migrations/20210506A-rename-interfaces.js +2 -1
- package/dist/database/migrations/20210802A-replace-groups.js +2 -1
- package/dist/database/migrations/20210805A-update-groups.js +2 -1
- package/dist/database/migrations/20210805B-change-image-metadata-structure.js +3 -2
- package/dist/database/migrations/20211007A-update-presets.js +5 -4
- package/dist/database/migrations/20220429A-add-flows.d.ts +3 -0
- package/dist/database/migrations/20220429A-add-flows.js +83 -0
- package/dist/database/migrations/20220429B-add-color-to-insights-icon.d.ts +3 -0
- package/dist/database/migrations/20220429B-add-color-to-insights-icon.js +15 -0
- package/dist/database/migrations/20220429C-drop-non-null-from-ip-of-activity.d.ts +3 -0
- package/dist/database/migrations/20220429C-drop-non-null-from-ip-of-activity.js +15 -0
- package/dist/database/migrations/20220429D-drop-non-null-from-sender-of-notifications.d.ts +3 -0
- package/dist/database/migrations/20220429D-drop-non-null-from-sender-of-notifications.js +15 -0
- package/dist/database/run-ast.js +10 -14
- package/dist/database/seeds/05-activity.yaml +0 -1
- package/dist/database/system-data/collections/collections.yaml +4 -0
- package/dist/database/system-data/fields/activity.yaml +3 -0
- package/dist/database/system-data/fields/dashboards.yaml +3 -1
- package/dist/database/system-data/fields/flows.yaml +21 -0
- package/dist/database/system-data/fields/notifications.yaml +3 -1
- package/dist/database/system-data/fields/operations.yaml +19 -0
- package/dist/database/system-data/fields/panels.yaml +3 -1
- package/dist/database/system-data/fields/shares.yaml +3 -1
- package/dist/database/system-data/fields/users.yaml +2 -4
- package/dist/database/system-data/relations/relations.yaml +20 -0
- package/dist/env.d.ts +1 -1
- package/dist/env.js +167 -12
- package/dist/exceptions/index.d.ts +1 -0
- package/dist/exceptions/index.js +1 -0
- package/dist/exceptions/invalid-provider.d.ts +4 -0
- package/dist/exceptions/invalid-provider.js +10 -0
- package/dist/exceptions/range-not-satisfiable.d.ts +2 -2
- package/dist/exceptions/range-not-satisfiable.js +5 -1
- package/dist/extensions.d.ts +3 -0
- package/dist/extensions.js +73 -20
- package/dist/flows.d.ts +17 -0
- package/dist/flows.js +310 -0
- package/dist/messenger.d.ts +24 -0
- package/dist/messenger.js +64 -0
- package/dist/middleware/graphql.js +2 -1
- package/dist/operations/condition/index.d.ts +6 -0
- package/dist/operations/condition/index.js +15 -0
- package/dist/operations/item-create/index.d.ts +8 -0
- package/dist/operations/item-create/index.js +40 -0
- package/dist/operations/item-delete/index.d.ts +9 -0
- package/dist/operations/item-delete/index.js +45 -0
- package/dist/operations/item-read/index.d.ts +9 -0
- package/dist/operations/item-read/index.js +45 -0
- package/dist/operations/item-update/index.d.ts +10 -0
- package/dist/operations/item-update/index.js +50 -0
- package/dist/operations/log/index.d.ts +5 -0
- package/dist/operations/log/index.js +14 -0
- package/dist/operations/mail/index.d.ts +7 -0
- package/dist/operations/mail/index.js +16 -0
- package/dist/operations/notification/index.d.ts +8 -0
- package/dist/operations/notification/index.js +39 -0
- package/dist/operations/request/index.d.ts +9 -0
- package/dist/operations/request/index.js +14 -0
- package/dist/operations/sleep/index.d.ts +5 -0
- package/dist/operations/sleep/index.js +9 -0
- package/dist/operations/transform/index.d.ts +5 -0
- package/dist/operations/transform/index.js +10 -0
- package/dist/operations/trigger/index.d.ts +6 -0
- package/dist/operations/trigger/index.js +21 -0
- package/dist/services/activity.d.ts +1 -2
- package/dist/services/activity.js +10 -10
- package/dist/services/assets.js +27 -1
- package/dist/services/authentication.d.ts +2 -2
- package/dist/services/authentication.js +11 -8
- package/dist/services/authorization.js +12 -0
- package/dist/services/fields.js +15 -8
- package/dist/services/flows.d.ts +14 -0
- package/dist/services/flows.js +42 -0
- package/dist/services/graphql.js +56 -33
- package/dist/services/import-export.d.ts +1 -1
- package/dist/services/import-export.js +13 -12
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +2 -0
- package/dist/services/items.d.ts +3 -3
- package/dist/services/items.js +25 -2
- package/dist/services/mail/index.js +2 -1
- package/dist/services/notifications.d.ts +2 -1
- package/dist/services/notifications.js +4 -3
- package/dist/services/operations.d.ts +14 -0
- package/dist/services/operations.js +42 -0
- package/dist/services/payload.d.ts +2 -2
- package/dist/services/payload.js +8 -7
- package/dist/services/users.d.ts +4 -0
- package/dist/services/users.js +20 -0
- package/dist/services/webhooks.d.ts +2 -0
- package/dist/services/webhooks.js +8 -7
- package/dist/types/events.d.ts +18 -0
- package/dist/types/events.js +2 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -1
- package/dist/utils/apply-query.js +31 -4
- package/dist/utils/apply-snapshot.d.ts +3 -3
- package/dist/utils/apply-snapshot.js +64 -49
- package/dist/utils/construct-flow-tree.d.ts +2 -0
- package/dist/utils/construct-flow-tree.js +31 -0
- package/dist/utils/get-accountability-for-role.d.ts +7 -0
- package/dist/utils/get-accountability-for-role.js +36 -0
- package/dist/utils/get-ast-from-query.js +1 -7
- package/dist/utils/get-default-value.js +4 -3
- package/dist/utils/get-permissions.d.ts +1 -1
- package/dist/utils/get-permissions.js +9 -8
- package/dist/utils/get-schema.js +2 -1
- package/dist/utils/get-snapshot.js +22 -4
- package/dist/utils/operation-options.d.ts +3 -0
- package/dist/utils/operation-options.js +45 -0
- package/dist/utils/parse-json.d.ts +5 -0
- package/dist/utils/parse-json.js +19 -0
- package/dist/utils/sanitize-query.d.ts +1 -2
- package/dist/utils/sanitize-query.js +6 -5
- package/dist/utils/validate-keys.d.ts +6 -0
- package/dist/utils/validate-keys.js +28 -0
- package/dist/utils/validate-query.js +1 -1
- package/dist/webhooks.d.ts +2 -0
- package/dist/webhooks.js +17 -2
- package/package.json +18 -14
- package/dist/types/activity.d.ts +0 -9
- package/dist/types/activity.js +0 -13
- package/example.env +0 -202
package/dist/extensions.js
CHANGED
|
@@ -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).
|
|
152
|
-
|
|
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
|
-
.
|
|
166
|
-
? path_1.default.resolve(extension.path,
|
|
167
|
-
:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
|
|
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
|
|
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(
|
|
313
|
-
const
|
|
314
|
-
const
|
|
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
|
|
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
|
}
|
package/dist/flows.d.ts
ADDED
|
@@ -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 =
|
|
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,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
|
+
});
|