directus 9.15.0 → 9.16.1
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/__utils__/items-utils.d.ts +2 -0
- package/dist/__utils__/items-utils.js +36 -0
- package/dist/__utils__/schemas.d.ts +13 -0
- package/dist/__utils__/schemas.js +304 -0
- package/dist/__utils__/snapshots.d.ts +5 -0
- package/dist/__utils__/snapshots.js +897 -0
- package/dist/app.js +1 -0
- package/dist/cli/index.test.d.ts +1 -0
- package/dist/cli/index.test.js +58 -0
- package/dist/cli/utils/create-env/env-stub.liquid +2 -2
- package/dist/controllers/assets.js +20 -16
- package/dist/controllers/files.test.d.ts +1 -0
- package/dist/controllers/files.test.js +49 -0
- package/dist/controllers/server.js +0 -1
- package/dist/database/migrations/run.test.d.ts +1 -0
- package/dist/database/migrations/run.test.js +92 -0
- package/dist/env.js +8 -0
- package/dist/env.test.d.ts +8 -0
- package/dist/env.test.js +39 -0
- package/dist/flows.js +2 -1
- package/dist/middleware/authenticate.test.d.ts +1 -0
- package/dist/middleware/authenticate.test.js +174 -0
- package/dist/middleware/extract-token.test.d.ts +1 -0
- package/dist/middleware/extract-token.test.js +60 -0
- package/dist/operations/exec/index.d.ts +5 -0
- package/dist/operations/exec/index.js +26 -0
- package/dist/operations/exec/index.test.d.ts +1 -0
- package/dist/operations/exec/index.test.js +95 -0
- package/dist/operations/notification/index.js +9 -6
- package/dist/operations/request/index.js +22 -3
- package/dist/services/files.js +3 -2
- package/dist/services/files.test.d.ts +1 -0
- package/dist/services/files.test.js +53 -0
- package/dist/services/flows.js +4 -0
- package/dist/services/graphql/index.d.ts +2 -2
- package/dist/services/graphql/index.js +33 -38
- package/dist/services/items.js +83 -39
- package/dist/services/items.test.d.ts +1 -0
- package/dist/services/items.test.js +765 -0
- package/dist/services/payload.d.ts +7 -4
- package/dist/services/payload.js +35 -8
- package/dist/services/payload.test.d.ts +1 -0
- package/dist/services/payload.test.js +94 -0
- package/dist/services/server.js +5 -3
- package/dist/services/specifications.test.d.ts +1 -0
- package/dist/services/specifications.test.js +96 -0
- package/dist/types/items.d.ts +11 -0
- package/dist/utils/apply-query.js +8 -3
- package/dist/utils/apply-snapshot.js +15 -0
- package/dist/utils/apply-snapshot.test.d.ts +1 -0
- package/dist/utils/apply-snapshot.test.js +305 -0
- package/dist/utils/calculate-field-depth.test.d.ts +1 -0
- package/dist/utils/calculate-field-depth.test.js +76 -0
- package/dist/utils/filter-items.test.d.ts +1 -0
- package/dist/utils/filter-items.test.js +60 -0
- package/dist/utils/get-cache-key.test.d.ts +1 -0
- package/dist/utils/get-cache-key.test.js +53 -0
- package/dist/utils/get-column-path.test.d.ts +1 -0
- package/dist/utils/get-column-path.test.js +136 -0
- package/dist/utils/get-config-from-env.test.d.ts +1 -0
- package/dist/utils/get-config-from-env.test.js +19 -0
- package/dist/utils/get-graphql-type.d.ts +1 -1
- package/dist/utils/get-graphql-type.js +4 -1
- package/dist/utils/get-os-info.d.ts +9 -0
- package/dist/utils/get-os-info.js +47 -0
- package/dist/utils/get-relation-info.test.d.ts +1 -0
- package/dist/utils/get-relation-info.test.js +88 -0
- package/dist/utils/get-relation-type.test.d.ts +1 -0
- package/dist/utils/get-relation-type.test.js +69 -0
- package/dist/utils/get-string-byte-size.test.d.ts +1 -0
- package/dist/utils/get-string-byte-size.test.js +8 -0
- package/dist/utils/is-directus-jwt.test.d.ts +1 -0
- package/dist/utils/is-directus-jwt.test.js +26 -0
- package/dist/utils/jwt.test.d.ts +1 -0
- package/dist/utils/jwt.test.js +36 -0
- package/dist/utils/merge-permissions.test.d.ts +1 -0
- package/dist/utils/merge-permissions.test.js +80 -0
- package/dist/utils/validate-keys.test.d.ts +1 -0
- package/dist/utils/validate-keys.test.js +97 -0
- package/package.json +11 -11
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const vm2_1 = require("vm2");
|
|
7
|
+
const index_1 = __importDefault(require("./index"));
|
|
8
|
+
test('Rejects when modules are used without modules being allowed', async () => {
|
|
9
|
+
const testCode = `
|
|
10
|
+
const test = require('test');
|
|
11
|
+
`;
|
|
12
|
+
await expect(index_1.default.handler({ code: testCode }, {
|
|
13
|
+
data: {},
|
|
14
|
+
env: {
|
|
15
|
+
FLOWS_EXEC_ALLOWED_MODULES: '',
|
|
16
|
+
},
|
|
17
|
+
})).rejects.toEqual(new vm2_1.VMError("Cannot find module 'test'"));
|
|
18
|
+
});
|
|
19
|
+
test('Rejects when code contains syntax errors', async () => {
|
|
20
|
+
const testCode = `
|
|
21
|
+
~~
|
|
22
|
+
`;
|
|
23
|
+
await expect(index_1.default.handler({ code: testCode }, {
|
|
24
|
+
data: {},
|
|
25
|
+
env: {
|
|
26
|
+
FLOWS_EXEC_ALLOWED_MODULES: '',
|
|
27
|
+
},
|
|
28
|
+
})).rejects.toEqual(new Error("Couldn't compile code: Unexpected end of input"));
|
|
29
|
+
});
|
|
30
|
+
test('Rejects when returned function does something illegal', async () => {
|
|
31
|
+
const testCode = `
|
|
32
|
+
module.exports = function() {
|
|
33
|
+
return a + b;
|
|
34
|
+
};
|
|
35
|
+
`;
|
|
36
|
+
await expect(index_1.default.handler({ code: testCode }, {
|
|
37
|
+
data: {},
|
|
38
|
+
env: {
|
|
39
|
+
FLOWS_EXEC_ALLOWED_MODULES: '',
|
|
40
|
+
},
|
|
41
|
+
})).rejects.toEqual(new ReferenceError('a is not defined'));
|
|
42
|
+
});
|
|
43
|
+
test("Rejects when code doesn't return valid function", async () => {
|
|
44
|
+
const testCode = `
|
|
45
|
+
module.exports = false;
|
|
46
|
+
`;
|
|
47
|
+
await expect(index_1.default.handler({ code: testCode }, {
|
|
48
|
+
data: {},
|
|
49
|
+
env: {
|
|
50
|
+
FLOWS_EXEC_ALLOWED_MODULES: '',
|
|
51
|
+
},
|
|
52
|
+
})).rejects.toEqual(new TypeError('fn is not a function'));
|
|
53
|
+
});
|
|
54
|
+
test('Rejects returned function throws errors', async () => {
|
|
55
|
+
const testCode = `
|
|
56
|
+
module.exports = function () {
|
|
57
|
+
throw new Error('test');
|
|
58
|
+
};
|
|
59
|
+
`;
|
|
60
|
+
await expect(index_1.default.handler({ code: testCode }, {
|
|
61
|
+
data: {},
|
|
62
|
+
env: {
|
|
63
|
+
FLOWS_EXEC_ALLOWED_MODULES: '',
|
|
64
|
+
},
|
|
65
|
+
})).rejects.toEqual(new Error('test'));
|
|
66
|
+
});
|
|
67
|
+
test('Executes function when valid', () => {
|
|
68
|
+
const testCode = `
|
|
69
|
+
module.exports = function (data) {
|
|
70
|
+
return { result: data.input + ' test' };
|
|
71
|
+
};
|
|
72
|
+
`;
|
|
73
|
+
expect(index_1.default.handler({ code: testCode }, {
|
|
74
|
+
data: {
|
|
75
|
+
input: 'start',
|
|
76
|
+
},
|
|
77
|
+
env: {
|
|
78
|
+
FLOWS_EXEC_ALLOWED_MODULES: '',
|
|
79
|
+
},
|
|
80
|
+
})).resolves.toEqual({ result: 'start test' });
|
|
81
|
+
});
|
|
82
|
+
test('Allows modules that are whitelisted', () => {
|
|
83
|
+
const testCode = `
|
|
84
|
+
const bytes = require('bytes');
|
|
85
|
+
|
|
86
|
+
module.exports = function (data) {
|
|
87
|
+
return { result: bytes(1000) };
|
|
88
|
+
};
|
|
89
|
+
`;
|
|
90
|
+
expect(index_1.default.handler({ code: testCode }, {
|
|
91
|
+
env: {
|
|
92
|
+
FLOWS_EXEC_ALLOWED_MODULES: 'bytes',
|
|
93
|
+
},
|
|
94
|
+
})).resolves.toEqual({ result: '1000B' });
|
|
95
|
+
});
|
|
@@ -6,7 +6,6 @@ const get_accountability_for_role_1 = require("../../utils/get-accountability-fo
|
|
|
6
6
|
exports.default = (0, utils_1.defineOperationApi)({
|
|
7
7
|
id: 'notification',
|
|
8
8
|
handler: async ({ recipient, subject, message, permissions }, { accountability, database, getSchema }) => {
|
|
9
|
-
var _a;
|
|
10
9
|
const schema = await getSchema({ database });
|
|
11
10
|
let customAccountability;
|
|
12
11
|
if (!permissions || permissions === '$trigger') {
|
|
@@ -27,12 +26,16 @@ exports.default = (0, utils_1.defineOperationApi)({
|
|
|
27
26
|
knex: database,
|
|
28
27
|
});
|
|
29
28
|
const messageString = message ? (0, utils_1.optionToString)(message) : null;
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
const payload = (0, utils_1.toArray)(recipient).map((userId) => {
|
|
30
|
+
var _a;
|
|
31
|
+
return {
|
|
32
|
+
recipient: userId,
|
|
33
|
+
sender: (_a = customAccountability === null || customAccountability === void 0 ? void 0 : customAccountability.user) !== null && _a !== void 0 ? _a : null,
|
|
34
|
+
subject,
|
|
35
|
+
message: messageString,
|
|
36
|
+
};
|
|
35
37
|
});
|
|
38
|
+
const result = await notificationsService.createMany(payload);
|
|
36
39
|
return result;
|
|
37
40
|
},
|
|
38
41
|
});
|
|
@@ -8,11 +8,30 @@ const axios_1 = __importDefault(require("axios"));
|
|
|
8
8
|
exports.default = (0, utils_1.defineOperationApi)({
|
|
9
9
|
id: 'request',
|
|
10
10
|
handler: async ({ url, method, body, headers }) => {
|
|
11
|
-
|
|
11
|
+
var _a;
|
|
12
|
+
const customHeaders = (_a = headers === null || headers === void 0 ? void 0 : headers.reduce((acc, { header, value }) => {
|
|
12
13
|
acc[header] = value;
|
|
13
14
|
return acc;
|
|
14
|
-
}, {});
|
|
15
|
-
|
|
15
|
+
}, {})) !== null && _a !== void 0 ? _a : {};
|
|
16
|
+
if (!customHeaders['Content-Type'] && isValidJSON(body)) {
|
|
17
|
+
customHeaders['Content-Type'] = 'application/json';
|
|
18
|
+
}
|
|
19
|
+
const shouldEncode = decodeURI(url) === url;
|
|
20
|
+
const result = await (0, axios_1.default)({
|
|
21
|
+
url: shouldEncode ? encodeURI(url) : url,
|
|
22
|
+
method,
|
|
23
|
+
data: body,
|
|
24
|
+
headers: customHeaders,
|
|
25
|
+
});
|
|
16
26
|
return { status: result.status, statusText: result.statusText, headers: result.headers, data: result.data };
|
|
27
|
+
function isValidJSON(value) {
|
|
28
|
+
try {
|
|
29
|
+
(0, utils_1.parseJSON)(value);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
17
36
|
},
|
|
18
37
|
});
|
package/dist/services/files.js
CHANGED
|
@@ -229,7 +229,8 @@ class FilesService extends items_1.ItemsService {
|
|
|
229
229
|
}
|
|
230
230
|
let fileResponse;
|
|
231
231
|
try {
|
|
232
|
-
|
|
232
|
+
const shouldEncode = decodeURI(importURL) === importURL;
|
|
233
|
+
fileResponse = await axios_1.default.get(shouldEncode ? encodeURI(importURL) : importURL, {
|
|
233
234
|
responseType: 'stream',
|
|
234
235
|
});
|
|
235
236
|
}
|
|
@@ -240,7 +241,7 @@ class FilesService extends items_1.ItemsService {
|
|
|
240
241
|
});
|
|
241
242
|
}
|
|
242
243
|
const parsedURL = url_1.default.parse(fileResponse.request.res.responseUrl);
|
|
243
|
-
const filename = path_1.default.basename(parsedURL.pathname);
|
|
244
|
+
const filename = decodeURI(path_1.default.basename(parsedURL.pathname));
|
|
244
245
|
const payload = {
|
|
245
246
|
filename_download: filename,
|
|
246
247
|
storage: (0, utils_1.toArray)(env_1.default.STORAGE_LOCATIONS)[0],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const exifr_1 = __importDefault(require("exifr"));
|
|
7
|
+
const knex_1 = __importDefault(require("knex"));
|
|
8
|
+
const knex_mock_client_1 = require("knex-mock-client");
|
|
9
|
+
const _1 = require(".");
|
|
10
|
+
jest.mock('exifr');
|
|
11
|
+
jest.mock('../../src/database/index', () => {
|
|
12
|
+
return { getDatabaseClient: jest.fn().mockReturnValue('postgres') };
|
|
13
|
+
});
|
|
14
|
+
jest.requireMock('../../src/database/index');
|
|
15
|
+
describe('Integration Tests', () => {
|
|
16
|
+
let db;
|
|
17
|
+
let tracker;
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
db = (0, knex_1.default)({ client: knex_mock_client_1.MockClient });
|
|
20
|
+
tracker = (0, knex_mock_client_1.getTracker)();
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
tracker.reset();
|
|
24
|
+
});
|
|
25
|
+
describe('Services / Files', () => {
|
|
26
|
+
describe('getMetadata', () => {
|
|
27
|
+
let service;
|
|
28
|
+
let exifrParseSpy;
|
|
29
|
+
const sampleMetadata = {
|
|
30
|
+
CustomTagA: 'value a',
|
|
31
|
+
CustomTagB: 'value b',
|
|
32
|
+
CustomTagC: 'value c',
|
|
33
|
+
};
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
exifrParseSpy = jest.spyOn(exifr_1.default, 'parse');
|
|
36
|
+
service = new _1.FilesService({
|
|
37
|
+
knex: db,
|
|
38
|
+
schema: { collections: {}, relations: [] },
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
it('accepts allowlist metadata tags', async () => {
|
|
42
|
+
exifrParseSpy.mockReturnValue(Promise.resolve({ ...sampleMetadata }));
|
|
43
|
+
const bufferContent = 'file buffer content';
|
|
44
|
+
const allowList = ['CustomTagB', 'CustomTagA'];
|
|
45
|
+
const metadata = await service.getMetadata(bufferContent, allowList);
|
|
46
|
+
expect(exifrParseSpy).toHaveBeenCalled();
|
|
47
|
+
expect(metadata.metadata.CustomTagA).toStrictEqual(sampleMetadata.CustomTagA);
|
|
48
|
+
expect(metadata.metadata.CustomTagB).toStrictEqual(sampleMetadata.CustomTagB);
|
|
49
|
+
expect(metadata.metadata.CustomTagC).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
package/dist/services/flows.js
CHANGED
|
@@ -39,12 +39,16 @@ class FlowsService extends items_1.ItemsService {
|
|
|
39
39
|
}
|
|
40
40
|
async deleteOne(key, opts) {
|
|
41
41
|
const flowManager = (0, flows_1.getFlowManager)();
|
|
42
|
+
// this is to prevent foreign key constraint error on directus_operations resolve/reject during cascade deletion
|
|
43
|
+
await this.knex('directus_operations').update({ resolve: null, reject: null }).where('flow', key);
|
|
42
44
|
const result = await super.deleteOne(key, opts);
|
|
43
45
|
await flowManager.reload();
|
|
44
46
|
return result;
|
|
45
47
|
}
|
|
46
48
|
async deleteMany(keys, opts) {
|
|
47
49
|
const flowManager = (0, flows_1.getFlowManager)();
|
|
50
|
+
// this is to prevent foreign key constraint error on directus_operations resolve/reject during cascade deletion
|
|
51
|
+
await this.knex('directus_operations').update({ resolve: null, reject: null }).whereIn('flow', keys);
|
|
48
52
|
const result = await super.deleteMany(keys, opts);
|
|
49
53
|
await flowManager.reload();
|
|
50
54
|
return result;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BaseException } from '@directus/shared/exceptions';
|
|
2
2
|
import { Accountability, Query, SchemaOverview } from '@directus/shared/types';
|
|
3
|
-
import { ArgumentNode, FormattedExecutionResult, FragmentDefinitionNode, GraphQLError, GraphQLResolveInfo, GraphQLSchema,
|
|
3
|
+
import { ArgumentNode, FormattedExecutionResult, FragmentDefinitionNode, GraphQLError, GraphQLResolveInfo, GraphQLSchema, SelectionNode } from 'graphql';
|
|
4
4
|
import { ObjectTypeComposer, SchemaComposer } from 'graphql-compose';
|
|
5
5
|
import { Knex } from 'knex';
|
|
6
6
|
import { AbstractServiceOptions, GraphQLParams, Item } from '../../types';
|
|
@@ -44,7 +44,7 @@ export declare class GraphQLService {
|
|
|
44
44
|
* In order to do that, we'll parse over all ArgumentNodes and ObjectFieldNodes to manually recreate an object structure
|
|
45
45
|
* of arguments
|
|
46
46
|
*/
|
|
47
|
-
parseArgs(args: readonly ArgumentNode[]
|
|
47
|
+
parseArgs(args: readonly ArgumentNode[], variableValues: GraphQLResolveInfo['variableValues']): Record<string, any>;
|
|
48
48
|
/**
|
|
49
49
|
* Get a Directus Query object from the parsed arguments (rawQuery) and GraphQL AST selectionSet. Converts SelectionSet into
|
|
50
50
|
* Directus' `fields` query for use in the resolver. Also applies variables where appropriate.
|
|
@@ -274,7 +274,7 @@ class GraphQLService {
|
|
|
274
274
|
CollectionTypes[collection.collection] = schemaComposer.createObjectTC({
|
|
275
275
|
name: action === 'read' ? collection.collection : `${action}_${collection.collection}`,
|
|
276
276
|
fields: Object.values(collection.fields).reduce((acc, field) => {
|
|
277
|
-
let type = (0, get_graphql_type_1.getGraphQLType)(field.type);
|
|
277
|
+
let type = (0, get_graphql_type_1.getGraphQLType)(field.type, field.special);
|
|
278
278
|
// GraphQL doesn't differentiate between not-null and has-to-be-submitted. We
|
|
279
279
|
// can't non-null in update, as that would require every not-nullable field to be
|
|
280
280
|
// submitted on updates
|
|
@@ -639,7 +639,7 @@ class GraphQLService {
|
|
|
639
639
|
ReadableCollectionFilterTypes[collection.collection] = schemaComposer.createInputTC({
|
|
640
640
|
name: `${collection.collection}_filter`,
|
|
641
641
|
fields: Object.values(collection.fields).reduce((acc, field) => {
|
|
642
|
-
const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type);
|
|
642
|
+
const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type, field.special);
|
|
643
643
|
let filterOperatorType;
|
|
644
644
|
switch (graphqlType) {
|
|
645
645
|
case graphql_1.GraphQLBoolean:
|
|
@@ -692,7 +692,7 @@ class GraphQLService {
|
|
|
692
692
|
AggregatedFields[collection.collection] = schemaComposer.createObjectTC({
|
|
693
693
|
name: `${collection.collection}_aggregated_fields`,
|
|
694
694
|
fields: Object.values(collection.fields).reduce((acc, field) => {
|
|
695
|
-
const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type);
|
|
695
|
+
const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type, field.special);
|
|
696
696
|
switch (graphqlType) {
|
|
697
697
|
case graphql_1.GraphQLInt:
|
|
698
698
|
case graphql_1.GraphQLFloat:
|
|
@@ -736,7 +736,7 @@ class GraphQLService {
|
|
|
736
736
|
},
|
|
737
737
|
};
|
|
738
738
|
const hasNumericAggregates = Object.values(collection.fields).some((field) => {
|
|
739
|
-
const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type);
|
|
739
|
+
const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type, field.special);
|
|
740
740
|
if (graphqlType === graphql_1.GraphQLInt || graphqlType === graphql_1.GraphQLFloat) {
|
|
741
741
|
return true;
|
|
742
742
|
}
|
|
@@ -1207,38 +1207,29 @@ class GraphQLService {
|
|
|
1207
1207
|
parseArgs(args, variableValues) {
|
|
1208
1208
|
if (!args || args.length === 0)
|
|
1209
1209
|
return {};
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1210
|
+
const parse = (node) => {
|
|
1211
|
+
switch (node.kind) {
|
|
1212
|
+
case 'Variable':
|
|
1213
|
+
return variableValues[node.name.value];
|
|
1214
|
+
case 'ListValue':
|
|
1215
|
+
return node.values.map(parse);
|
|
1216
|
+
case 'ObjectValue':
|
|
1217
|
+
return Object.fromEntries(node.fields.map((node) => [node.name.value, parse(node.value)]));
|
|
1218
|
+
case 'NullValue':
|
|
1219
|
+
return null;
|
|
1220
|
+
case 'StringValue':
|
|
1221
|
+
return String(node.value);
|
|
1222
|
+
case 'IntValue':
|
|
1223
|
+
case 'FloatValue':
|
|
1224
|
+
return Number(node.value);
|
|
1225
|
+
case 'BooleanValue':
|
|
1226
|
+
return Boolean(node.value);
|
|
1227
|
+
case 'EnumValue':
|
|
1228
|
+
default:
|
|
1229
|
+
return node.value;
|
|
1220
1230
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
for (const valueNode of argument.value.values) {
|
|
1224
|
-
if (valueNode.kind === 'ObjectValue') {
|
|
1225
|
-
values.push(this.parseArgs(valueNode.fields, variableValues));
|
|
1226
|
-
}
|
|
1227
|
-
else {
|
|
1228
|
-
if (valueNode.kind === 'Variable') {
|
|
1229
|
-
values.push(variableValues[valueNode.name.value]);
|
|
1230
|
-
}
|
|
1231
|
-
else {
|
|
1232
|
-
values.push(valueNode.value);
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
argsObject[argument.name.value] = values;
|
|
1237
|
-
}
|
|
1238
|
-
else {
|
|
1239
|
-
argsObject[argument.name.value] = argument.value.value;
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1231
|
+
};
|
|
1232
|
+
const argsObject = Object.fromEntries(args.map((arg) => [arg.name.value, parse(arg.value)]));
|
|
1242
1233
|
return argsObject;
|
|
1243
1234
|
}
|
|
1244
1235
|
/**
|
|
@@ -1902,7 +1893,9 @@ class GraphQLService {
|
|
|
1902
1893
|
name: 'directus_collections_meta',
|
|
1903
1894
|
fields: Object.values(schema.read.collections['directus_collections'].fields).reduce((acc, field) => {
|
|
1904
1895
|
acc[field.field] = {
|
|
1905
|
-
type: field.nullable
|
|
1896
|
+
type: field.nullable
|
|
1897
|
+
? (0, get_graphql_type_1.getGraphQLType)(field.type, field.special)
|
|
1898
|
+
: (0, graphql_1.GraphQLNonNull)((0, get_graphql_type_1.getGraphQLType)(field.type, field.special)),
|
|
1906
1899
|
description: field.note,
|
|
1907
1900
|
};
|
|
1908
1901
|
return acc;
|
|
@@ -1951,7 +1944,9 @@ class GraphQLService {
|
|
|
1951
1944
|
name: 'directus_fields_meta',
|
|
1952
1945
|
fields: Object.values(schema.read.collections['directus_fields'].fields).reduce((acc, field) => {
|
|
1953
1946
|
acc[field.field] = {
|
|
1954
|
-
type: field.nullable
|
|
1947
|
+
type: field.nullable
|
|
1948
|
+
? (0, get_graphql_type_1.getGraphQLType)(field.type, field.special)
|
|
1949
|
+
: (0, graphql_1.GraphQLNonNull)((0, get_graphql_type_1.getGraphQLType)(field.type, field.special)),
|
|
1955
1950
|
description: field.note,
|
|
1956
1951
|
};
|
|
1957
1952
|
return acc;
|
|
@@ -2038,7 +2033,7 @@ class GraphQLService {
|
|
|
2038
2033
|
name: 'directus_relations_meta',
|
|
2039
2034
|
fields: Object.values(schema.read.collections['directus_relations'].fields).reduce((acc, field) => {
|
|
2040
2035
|
acc[field.field] = {
|
|
2041
|
-
type: (0, get_graphql_type_1.getGraphQLType)(field.type),
|
|
2036
|
+
type: (0, get_graphql_type_1.getGraphQLType)(field.type, field.special),
|
|
2042
2037
|
description: field.note,
|
|
2043
2038
|
};
|
|
2044
2039
|
return acc;
|
package/dist/services/items.js
CHANGED
|
@@ -52,6 +52,7 @@ class ItemsService {
|
|
|
52
52
|
.filter((field) => field.alias === true)
|
|
53
53
|
.map((field) => field.field);
|
|
54
54
|
const payload = (0, lodash_1.cloneDeep)(data);
|
|
55
|
+
const nestedActionEvents = [];
|
|
55
56
|
// By wrapping the logic in a transaction, we make sure we automatically roll back all the
|
|
56
57
|
// changes in the DB if any of the parts contained within throws an error. This also means
|
|
57
58
|
// that any errors thrown in any nested relational changes will bubble up and cancel the whole
|
|
@@ -84,8 +85,8 @@ class ItemsService {
|
|
|
84
85
|
const payloadWithPresets = this.accountability
|
|
85
86
|
? await authorizationService.validatePayload('create', this.collection, payloadAfterHooks)
|
|
86
87
|
: payloadAfterHooks;
|
|
87
|
-
const { payload: payloadWithM2O, revisions: revisionsM2O } = await payloadService.processM2O(payloadWithPresets);
|
|
88
|
-
const { payload: payloadWithA2O, revisions: revisionsA2O } = await payloadService.processA2O(payloadWithM2O);
|
|
88
|
+
const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
|
|
89
|
+
const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
|
|
89
90
|
const payloadWithoutAliases = (0, lodash_1.pick)(payloadWithA2O, (0, lodash_1.without)(fields, ...aliases));
|
|
90
91
|
const payloadWithTypeCasting = await payloadService.processValues('create', payloadWithoutAliases);
|
|
91
92
|
// In case of manual string / UUID primary keys, the PK already exists in the object we're saving.
|
|
@@ -113,7 +114,10 @@ class ItemsService {
|
|
|
113
114
|
// to read from it
|
|
114
115
|
payload[primaryKeyField] = primaryKey;
|
|
115
116
|
}
|
|
116
|
-
const { revisions: revisionsO2M } = await payloadService.processO2M(payload, primaryKey);
|
|
117
|
+
const { revisions: revisionsO2M, nestedActionEvents: nestedActionEventsO2M } = await payloadService.processO2M(payload, primaryKey, opts);
|
|
118
|
+
nestedActionEvents.push(...nestedActionEventsM2O);
|
|
119
|
+
nestedActionEvents.push(...nestedActionEventsA2O);
|
|
120
|
+
nestedActionEvents.push(...nestedActionEventsO2M);
|
|
117
121
|
// If this is an authenticated action, and accountability tracking is enabled, save activity row
|
|
118
122
|
if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
|
|
119
123
|
const activityService = new index_1.ActivityService({
|
|
@@ -154,17 +158,30 @@ class ItemsService {
|
|
|
154
158
|
return primaryKey;
|
|
155
159
|
});
|
|
156
160
|
if ((opts === null || opts === void 0 ? void 0 : opts.emitEvents) !== false) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
const actionEvent = {
|
|
162
|
+
event: this.eventScope === 'items'
|
|
163
|
+
? ['items.create', `${this.collection}.items.create`]
|
|
164
|
+
: `${this.eventScope}.create`,
|
|
165
|
+
meta: {
|
|
166
|
+
payload,
|
|
167
|
+
key: primaryKey,
|
|
168
|
+
collection: this.collection,
|
|
169
|
+
},
|
|
170
|
+
context: {
|
|
171
|
+
database: (0, database_1.default)(),
|
|
172
|
+
schema: this.schema,
|
|
173
|
+
accountability: this.accountability,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
if (!(opts === null || opts === void 0 ? void 0 : opts.bypassEmitAction)) {
|
|
177
|
+
emitter_1.default.emitAction(actionEvent.event, actionEvent.meta, actionEvent.context);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
opts.bypassEmitAction(actionEvent);
|
|
181
|
+
}
|
|
182
|
+
for (const nestedActionEvent of nestedActionEvents) {
|
|
183
|
+
emitter_1.default.emitAction(nestedActionEvent.event, nestedActionEvent.meta, nestedActionEvent.context);
|
|
184
|
+
}
|
|
168
185
|
}
|
|
169
186
|
if (this.cache && env_1.default.CACHE_AUTO_PURGE && (opts === null || opts === void 0 ? void 0 : opts.autoPurgeCache) !== false) {
|
|
170
187
|
await this.cache.clear();
|
|
@@ -323,6 +340,7 @@ class ItemsService {
|
|
|
323
340
|
.filter((field) => field.alias === true)
|
|
324
341
|
.map((field) => field.field);
|
|
325
342
|
const payload = (0, lodash_1.cloneDeep)(data);
|
|
343
|
+
const nestedActionEvents = [];
|
|
326
344
|
const authorizationService = new authorization_1.AuthorizationService({
|
|
327
345
|
accountability: this.accountability,
|
|
328
346
|
knex: this.knex,
|
|
@@ -356,8 +374,8 @@ class ItemsService {
|
|
|
356
374
|
knex: trx,
|
|
357
375
|
schema: this.schema,
|
|
358
376
|
});
|
|
359
|
-
const { payload: payloadWithM2O, revisions: revisionsM2O } = await payloadService.processM2O(payloadWithPresets);
|
|
360
|
-
const { payload: payloadWithA2O, revisions: revisionsA2O } = await payloadService.processA2O(payloadWithM2O);
|
|
377
|
+
const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
|
|
378
|
+
const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
|
|
361
379
|
const payloadWithoutAliasAndPK = (0, lodash_1.pick)(payloadWithA2O, (0, lodash_1.without)(fields, primaryKeyField, ...aliases));
|
|
362
380
|
const payloadWithTypeCasting = await payloadService.processValues('update', payloadWithoutAliasAndPK);
|
|
363
381
|
if (Object.keys(payloadWithTypeCasting).length > 0) {
|
|
@@ -369,9 +387,12 @@ class ItemsService {
|
|
|
369
387
|
}
|
|
370
388
|
}
|
|
371
389
|
const childrenRevisions = [...revisionsM2O, ...revisionsA2O];
|
|
390
|
+
nestedActionEvents.push(...nestedActionEventsM2O);
|
|
391
|
+
nestedActionEvents.push(...nestedActionEventsA2O);
|
|
372
392
|
for (const key of keys) {
|
|
373
|
-
const { revisions } = await payloadService.processO2M(payload, key);
|
|
393
|
+
const { revisions, nestedActionEvents: nestedActionEventsO2M } = await payloadService.processO2M(payload, key, opts);
|
|
374
394
|
childrenRevisions.push(...revisions);
|
|
395
|
+
nestedActionEvents.push(...nestedActionEventsO2M);
|
|
375
396
|
}
|
|
376
397
|
// If this is an authenticated action, and accountability tracking is enabled, save activity row
|
|
377
398
|
if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
|
|
@@ -427,17 +448,30 @@ class ItemsService {
|
|
|
427
448
|
await this.cache.clear();
|
|
428
449
|
}
|
|
429
450
|
if ((opts === null || opts === void 0 ? void 0 : opts.emitEvents) !== false) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
451
|
+
const actionEvent = {
|
|
452
|
+
event: this.eventScope === 'items'
|
|
453
|
+
? ['items.update', `${this.collection}.items.update`]
|
|
454
|
+
: `${this.eventScope}.update`,
|
|
455
|
+
meta: {
|
|
456
|
+
payload,
|
|
457
|
+
keys,
|
|
458
|
+
collection: this.collection,
|
|
459
|
+
},
|
|
460
|
+
context: {
|
|
461
|
+
database: (0, database_1.default)(),
|
|
462
|
+
schema: this.schema,
|
|
463
|
+
accountability: this.accountability,
|
|
464
|
+
},
|
|
465
|
+
};
|
|
466
|
+
if (!(opts === null || opts === void 0 ? void 0 : opts.bypassEmitAction)) {
|
|
467
|
+
emitter_1.default.emitAction(actionEvent.event, actionEvent.meta, actionEvent.context);
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
opts.bypassEmitAction(actionEvent);
|
|
471
|
+
}
|
|
472
|
+
for (const nestedActionEvent of nestedActionEvents) {
|
|
473
|
+
emitter_1.default.emitAction(nestedActionEvent.event, nestedActionEvent.meta, nestedActionEvent.context);
|
|
474
|
+
}
|
|
441
475
|
}
|
|
442
476
|
return keys;
|
|
443
477
|
}
|
|
@@ -547,17 +581,27 @@ class ItemsService {
|
|
|
547
581
|
await this.cache.clear();
|
|
548
582
|
}
|
|
549
583
|
if ((opts === null || opts === void 0 ? void 0 : opts.emitEvents) !== false) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
584
|
+
const actionEvent = {
|
|
585
|
+
event: this.eventScope === 'items'
|
|
586
|
+
? ['items.delete', `${this.collection}.items.delete`]
|
|
587
|
+
: `${this.eventScope}.delete`,
|
|
588
|
+
meta: {
|
|
589
|
+
payload: keys,
|
|
590
|
+
keys: keys,
|
|
591
|
+
collection: this.collection,
|
|
592
|
+
},
|
|
593
|
+
context: {
|
|
594
|
+
database: (0, database_1.default)(),
|
|
595
|
+
schema: this.schema,
|
|
596
|
+
accountability: this.accountability,
|
|
597
|
+
},
|
|
598
|
+
};
|
|
599
|
+
if (!(opts === null || opts === void 0 ? void 0 : opts.bypassEmitAction)) {
|
|
600
|
+
emitter_1.default.emitAction(actionEvent.event, actionEvent.meta, actionEvent.context);
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
opts.bypassEmitAction(actionEvent);
|
|
604
|
+
}
|
|
561
605
|
}
|
|
562
606
|
return keys;
|
|
563
607
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|