directus 9.14.5 → 9.16.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/__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 +2 -1
- package/dist/auth/drivers/ldap.js +18 -8
- package/dist/auth/drivers/oauth2.js +19 -9
- package/dist/auth/drivers/openid.js +19 -9
- package/dist/cache.d.ts +3 -0
- package/dist/cache.js +21 -2
- 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/constants.d.ts +1 -0
- package/dist/constants.js +2 -1
- package/dist/controllers/assets.js +27 -1
- package/dist/controllers/extensions.js +3 -2
- 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/helpers/schema/dialects/cockroachdb.d.ts +3 -14
- package/dist/database/helpers/schema/dialects/cockroachdb.js +2 -8
- package/dist/database/helpers/schema/dialects/oracle.d.ts +3 -10
- package/dist/database/helpers/schema/dialects/oracle.js +2 -5
- package/dist/database/helpers/schema/types.d.ts +8 -18
- package/dist/database/helpers/schema/types.js +7 -36
- package/dist/database/migrations/20201105B-change-webhook-url-type.js +3 -2
- package/dist/database/migrations/20210312A-webhooks-collections-text.js +3 -2
- package/dist/database/migrations/20210415A-make-filesize-nullable.js +2 -2
- package/dist/database/migrations/20210510A-restructure-relations.js +3 -3
- package/dist/database/migrations/20210903A-add-auth-provider.js +2 -2
- package/dist/database/migrations/20210907A-webhooks-collections-not-null.js +4 -2
- package/dist/database/migrations/20210920A-webhooks-url-not-null.js +2 -2
- package/dist/database/migrations/20220303A-remove-default-project-color.js +2 -2
- package/dist/database/migrations/20220325B-add-default-language.js +2 -2
- package/dist/database/migrations/20220402A-remove-default-value-panel-icon.js +2 -2
- package/dist/database/migrations/20220801A-update-notifications-timestamp-column.d.ts +3 -0
- package/dist/database/migrations/20220801A-update-notifications-timestamp-column.js +19 -0
- package/dist/database/migrations/20220802A-add-custom-aspect-ratios.d.ts +3 -0
- package/dist/database/migrations/20220802A-add-custom-aspect-ratios.js +15 -0
- package/dist/database/migrations/run.test.d.ts +1 -0
- package/dist/database/migrations/run.test.js +92 -0
- package/dist/database/system-data/fields/settings.yaml +33 -0
- package/dist/env.js +8 -0
- package/dist/env.test.d.ts +8 -0
- package/dist/env.test.js +39 -0
- package/dist/extensions.d.ts +2 -2
- package/dist/extensions.js +10 -9
- package/dist/flows.js +2 -1
- package/dist/logger.js +0 -1
- package/dist/middleware/authenticate.test.d.ts +1 -0
- package/dist/middleware/authenticate.test.js +174 -0
- package/dist/middleware/cache.js +3 -3
- package/dist/middleware/extract-token.test.d.ts +1 -0
- package/dist/middleware/extract-token.test.js +60 -0
- package/dist/middleware/respond.js +2 -2
- 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/item-create/index.js +1 -1
- package/dist/operations/item-delete/index.js +2 -2
- package/dist/operations/item-read/index.js +2 -2
- package/dist/operations/item-update/index.js +3 -3
- package/dist/operations/notification/index.js +9 -6
- package/dist/operations/request/index.js +22 -3
- package/dist/services/assets.d.ts +1 -1
- package/dist/services/assets.js +7 -2
- package/dist/services/authorization.js +2 -1
- 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 +59 -40
- package/dist/services/graphql/types/hash.d.ts +2 -0
- package/dist/services/graphql/types/hash.js +9 -0
- 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.js +1 -6
- 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 +15 -0
- 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/compress.d.ts +3 -0
- package/dist/utils/compress.js +17 -0
- package/dist/utils/filter-items.test.d.ts +1 -0
- package/dist/utils/filter-items.test.js +60 -0
- package/dist/utils/get-ast-from-query.js +1 -1
- 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 +7 -1
- package/dist/utils/get-os-info.d.ts +9 -0
- package/dist/utils/get-os-info.js +47 -0
- package/dist/utils/get-permissions.js +2 -2
- 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-schema.js +1 -2
- 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 +12 -11
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Accountability, SchemaOverview } from '@directus/shared/types';
|
|
2
2
|
import { Knex } from 'knex';
|
|
3
3
|
import { Helpers } from '../database/helpers';
|
|
4
|
-
import { AbstractServiceOptions, Item, PrimaryKey } from '../types';
|
|
4
|
+
import { AbstractServiceOptions, ActionEventParams, Item, MutationOptions, PrimaryKey } from '../types';
|
|
5
5
|
declare type Action = 'create' | 'read' | 'update';
|
|
6
6
|
declare type Transformers = {
|
|
7
7
|
[type: string]: (context: {
|
|
@@ -44,22 +44,25 @@ export declare class PayloadService {
|
|
|
44
44
|
/**
|
|
45
45
|
* Recursively save/update all nested related Any-to-One items
|
|
46
46
|
*/
|
|
47
|
-
processA2O(data: Partial<Item
|
|
47
|
+
processA2O(data: Partial<Item>, opts?: MutationOptions): Promise<{
|
|
48
48
|
payload: Partial<Item>;
|
|
49
49
|
revisions: PrimaryKey[];
|
|
50
|
+
nestedActionEvents: ActionEventParams[];
|
|
50
51
|
}>;
|
|
51
52
|
/**
|
|
52
53
|
* Save/update all nested related m2o items inside the payload
|
|
53
54
|
*/
|
|
54
|
-
processM2O(data: Partial<Item
|
|
55
|
+
processM2O(data: Partial<Item>, opts?: MutationOptions): Promise<{
|
|
55
56
|
payload: Partial<Item>;
|
|
56
57
|
revisions: PrimaryKey[];
|
|
58
|
+
nestedActionEvents: ActionEventParams[];
|
|
57
59
|
}>;
|
|
58
60
|
/**
|
|
59
61
|
* Recursively save/update all nested related o2m items
|
|
60
62
|
*/
|
|
61
|
-
processO2M(data: Partial<Item>, parent: PrimaryKey): Promise<{
|
|
63
|
+
processO2M(data: Partial<Item>, parent: PrimaryKey, opts?: MutationOptions): Promise<{
|
|
62
64
|
revisions: PrimaryKey[];
|
|
65
|
+
nestedActionEvents: ActionEventParams[];
|
|
63
66
|
}>;
|
|
64
67
|
/**
|
|
65
68
|
* Transforms the input partial payload to match the output structure, to have consistency
|
package/dist/services/payload.js
CHANGED
|
@@ -300,12 +300,13 @@ class PayloadService {
|
|
|
300
300
|
/**
|
|
301
301
|
* Recursively save/update all nested related Any-to-One items
|
|
302
302
|
*/
|
|
303
|
-
async processA2O(data) {
|
|
303
|
+
async processA2O(data, opts) {
|
|
304
304
|
var _a, _b;
|
|
305
305
|
const relations = this.schema.relations.filter((relation) => {
|
|
306
306
|
return relation.collection === this.collection;
|
|
307
307
|
});
|
|
308
308
|
const revisions = [];
|
|
309
|
+
const nestedActionEvents = [];
|
|
309
310
|
const payload = (0, lodash_1.cloneDeep)(data);
|
|
310
311
|
// Only process related records that are actually in the payload
|
|
311
312
|
const relationsToProcess = relations.filter((relation) => {
|
|
@@ -345,26 +346,31 @@ class PayloadService {
|
|
|
345
346
|
if (Object.keys(fieldsToUpdate).length > 0) {
|
|
346
347
|
await itemsService.updateOne(relatedPrimaryKey, relatedRecord, {
|
|
347
348
|
onRevisionCreate: (pk) => revisions.push(pk),
|
|
349
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
350
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
348
351
|
});
|
|
349
352
|
}
|
|
350
353
|
}
|
|
351
354
|
else {
|
|
352
355
|
relatedPrimaryKey = await itemsService.createOne(relatedRecord, {
|
|
353
356
|
onRevisionCreate: (pk) => revisions.push(pk),
|
|
357
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
358
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
354
359
|
});
|
|
355
360
|
}
|
|
356
361
|
// Overwrite the nested object with just the primary key, so the parent level can be saved correctly
|
|
357
362
|
payload[relation.field] = relatedPrimaryKey;
|
|
358
363
|
}
|
|
359
|
-
return { payload, revisions };
|
|
364
|
+
return { payload, revisions, nestedActionEvents };
|
|
360
365
|
}
|
|
361
366
|
/**
|
|
362
367
|
* Save/update all nested related m2o items inside the payload
|
|
363
368
|
*/
|
|
364
|
-
async processM2O(data) {
|
|
369
|
+
async processM2O(data, opts) {
|
|
365
370
|
const payload = (0, lodash_1.cloneDeep)(data);
|
|
366
371
|
// All the revisions saved on this level
|
|
367
372
|
const revisions = [];
|
|
373
|
+
const nestedActionEvents = [];
|
|
368
374
|
// Many to one relations that exist on the current collection
|
|
369
375
|
const relations = this.schema.relations.filter((relation) => {
|
|
370
376
|
return relation.collection === this.collection;
|
|
@@ -400,24 +406,29 @@ class PayloadService {
|
|
|
400
406
|
if (Object.keys(fieldsToUpdate).length > 0) {
|
|
401
407
|
await itemsService.updateOne(relatedPrimaryKey, relatedRecord, {
|
|
402
408
|
onRevisionCreate: (pk) => revisions.push(pk),
|
|
409
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
410
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
403
411
|
});
|
|
404
412
|
}
|
|
405
413
|
}
|
|
406
414
|
else {
|
|
407
415
|
relatedPrimaryKey = await itemsService.createOne(relatedRecord, {
|
|
408
416
|
onRevisionCreate: (pk) => revisions.push(pk),
|
|
417
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
418
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
409
419
|
});
|
|
410
420
|
}
|
|
411
421
|
// Overwrite the nested object with just the primary key, so the parent level can be saved correctly
|
|
412
422
|
payload[relation.field] = relatedPrimaryKey;
|
|
413
423
|
}
|
|
414
|
-
return { payload, revisions };
|
|
424
|
+
return { payload, revisions, nestedActionEvents };
|
|
415
425
|
}
|
|
416
426
|
/**
|
|
417
427
|
* Recursively save/update all nested related o2m items
|
|
418
428
|
*/
|
|
419
|
-
async processO2M(data, parent) {
|
|
429
|
+
async processO2M(data, parent, opts) {
|
|
420
430
|
const revisions = [];
|
|
431
|
+
const nestedActionEvents = [];
|
|
421
432
|
const relations = this.schema.relations.filter((relation) => {
|
|
422
433
|
return relation.related_collection === this.collection;
|
|
423
434
|
});
|
|
@@ -485,6 +496,8 @@ class PayloadService {
|
|
|
485
496
|
}
|
|
486
497
|
savedPrimaryKeys.push(...(await itemsService.upsertMany(recordsToUpsert, {
|
|
487
498
|
onRevisionCreate: (pk) => revisions.push(pk),
|
|
499
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
500
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
488
501
|
})));
|
|
489
502
|
const query = {
|
|
490
503
|
filter: {
|
|
@@ -505,11 +518,16 @@ class PayloadService {
|
|
|
505
518
|
// Nullify all related items that aren't included in the current payload
|
|
506
519
|
if (relation.meta.one_deselect_action === 'delete') {
|
|
507
520
|
// There's no revision for a deletion
|
|
508
|
-
await itemsService.deleteByQuery(query
|
|
521
|
+
await itemsService.deleteByQuery(query, {
|
|
522
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
523
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
524
|
+
});
|
|
509
525
|
}
|
|
510
526
|
else {
|
|
511
527
|
await itemsService.updateByQuery(query, { [relation.field]: null }, {
|
|
512
528
|
onRevisionCreate: (pk) => revisions.push(pk),
|
|
529
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
530
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
513
531
|
});
|
|
514
532
|
}
|
|
515
533
|
}
|
|
@@ -525,6 +543,8 @@ class PayloadService {
|
|
|
525
543
|
[relation.field]: parent || payload[currentPrimaryKeyField],
|
|
526
544
|
})), {
|
|
527
545
|
onRevisionCreate: (pk) => revisions.push(pk),
|
|
546
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
547
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
528
548
|
});
|
|
529
549
|
}
|
|
530
550
|
if (alterations.update) {
|
|
@@ -535,6 +555,8 @@ class PayloadService {
|
|
|
535
555
|
[relation.field]: parent || payload[currentPrimaryKeyField],
|
|
536
556
|
}, {
|
|
537
557
|
onRevisionCreate: (pk) => revisions.push(pk),
|
|
558
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
559
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
538
560
|
});
|
|
539
561
|
}
|
|
540
562
|
}
|
|
@@ -556,17 +578,22 @@ class PayloadService {
|
|
|
556
578
|
},
|
|
557
579
|
};
|
|
558
580
|
if (relation.meta.one_deselect_action === 'delete') {
|
|
559
|
-
await itemsService.deleteByQuery(query
|
|
581
|
+
await itemsService.deleteByQuery(query, {
|
|
582
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
583
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
584
|
+
});
|
|
560
585
|
}
|
|
561
586
|
else {
|
|
562
587
|
await itemsService.updateByQuery(query, { [relation.field]: null }, {
|
|
563
588
|
onRevisionCreate: (pk) => revisions.push(pk),
|
|
589
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
590
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
564
591
|
});
|
|
565
592
|
}
|
|
566
593
|
}
|
|
567
594
|
}
|
|
568
595
|
}
|
|
569
|
-
return { revisions };
|
|
596
|
+
return { revisions, nestedActionEvents };
|
|
570
597
|
}
|
|
571
598
|
/**
|
|
572
599
|
* Transforms the input partial payload to match the output structure, to have consistency
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const knex_1 = __importDefault(require("knex"));
|
|
7
|
+
const knex_mock_client_1 = require("knex-mock-client");
|
|
8
|
+
const services_1 = require("../../src/services");
|
|
9
|
+
const helpers_1 = require("../../src/database/helpers");
|
|
10
|
+
jest.mock('../../src/database/index', () => {
|
|
11
|
+
return { getDatabaseClient: jest.fn().mockReturnValue('postgres') };
|
|
12
|
+
});
|
|
13
|
+
jest.requireMock('../../src/database/index');
|
|
14
|
+
describe('Integration Tests', () => {
|
|
15
|
+
let db;
|
|
16
|
+
let tracker;
|
|
17
|
+
beforeAll(async () => {
|
|
18
|
+
db = (0, knex_1.default)({ client: knex_mock_client_1.MockClient });
|
|
19
|
+
tracker = (0, knex_mock_client_1.getTracker)();
|
|
20
|
+
});
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
tracker.reset();
|
|
23
|
+
});
|
|
24
|
+
describe('Services / PayloadService', () => {
|
|
25
|
+
describe('transformers', () => {
|
|
26
|
+
let service;
|
|
27
|
+
let helpers;
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
service = new services_1.PayloadService('test', {
|
|
30
|
+
knex: db,
|
|
31
|
+
schema: { collections: {}, relations: [] },
|
|
32
|
+
});
|
|
33
|
+
helpers = (0, helpers_1.getHelpers)(db);
|
|
34
|
+
});
|
|
35
|
+
describe('csv', () => {
|
|
36
|
+
it('Returns undefined for illegal values', async () => {
|
|
37
|
+
const result = await service.transformers['cast-csv']({
|
|
38
|
+
value: 123,
|
|
39
|
+
action: 'read',
|
|
40
|
+
payload: {},
|
|
41
|
+
accountability: { role: null },
|
|
42
|
+
specials: [],
|
|
43
|
+
helpers,
|
|
44
|
+
});
|
|
45
|
+
expect(result).toBe(undefined);
|
|
46
|
+
});
|
|
47
|
+
it('Returns [] for empty strings', async () => {
|
|
48
|
+
const result = await service.transformers['cast-csv']({
|
|
49
|
+
value: '',
|
|
50
|
+
action: 'read',
|
|
51
|
+
payload: {},
|
|
52
|
+
accountability: { role: null },
|
|
53
|
+
specials: [],
|
|
54
|
+
helpers,
|
|
55
|
+
});
|
|
56
|
+
expect(result).toMatchObject([]);
|
|
57
|
+
});
|
|
58
|
+
it('Splits the CSV string', async () => {
|
|
59
|
+
const result = await service.transformers['cast-csv']({
|
|
60
|
+
value: 'test,directus',
|
|
61
|
+
action: 'read',
|
|
62
|
+
payload: {},
|
|
63
|
+
accountability: { role: null },
|
|
64
|
+
specials: [],
|
|
65
|
+
helpers,
|
|
66
|
+
});
|
|
67
|
+
expect(result).toMatchObject(['test', 'directus']);
|
|
68
|
+
});
|
|
69
|
+
it('Saves array values as joined string', async () => {
|
|
70
|
+
const result = await service.transformers['cast-csv']({
|
|
71
|
+
value: ['test', 'directus'],
|
|
72
|
+
action: 'create',
|
|
73
|
+
payload: {},
|
|
74
|
+
accountability: { role: null },
|
|
75
|
+
specials: [],
|
|
76
|
+
helpers,
|
|
77
|
+
});
|
|
78
|
+
expect(result).toBe('test,directus');
|
|
79
|
+
});
|
|
80
|
+
it('Saves string values as is', async () => {
|
|
81
|
+
const result = await service.transformers['cast-csv']({
|
|
82
|
+
value: 'test,directus',
|
|
83
|
+
action: 'create',
|
|
84
|
+
payload: {},
|
|
85
|
+
accountability: { role: null },
|
|
86
|
+
specials: [],
|
|
87
|
+
helpers,
|
|
88
|
+
});
|
|
89
|
+
expect(result).toBe('test,directus');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
package/dist/services/server.js
CHANGED
|
@@ -28,7 +28,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
29
|
exports.ServerService = void 0;
|
|
30
30
|
const lodash_1 = require("lodash");
|
|
31
|
-
const macos_release_1 = __importDefault(require("macos-release"));
|
|
32
31
|
const nanoid_1 = require("nanoid");
|
|
33
32
|
const os_1 = __importDefault(require("os"));
|
|
34
33
|
const perf_hooks_1 = require("perf_hooks");
|
|
@@ -43,6 +42,7 @@ const storage_1 = __importDefault(require("../storage"));
|
|
|
43
42
|
const utils_1 = require("@directus/shared/utils");
|
|
44
43
|
const mailer_1 = __importDefault(require("../mailer"));
|
|
45
44
|
const settings_1 = require("./settings");
|
|
45
|
+
const get_os_info_1 = require("../utils/get-os-info");
|
|
46
46
|
class ServerService {
|
|
47
47
|
constructor(options) {
|
|
48
48
|
this.knex = options.knex || (0, database_1.default)();
|
|
@@ -77,10 +77,12 @@ class ServerService {
|
|
|
77
77
|
else {
|
|
78
78
|
info.rateLimit = false;
|
|
79
79
|
}
|
|
80
|
+
info.flows = {
|
|
81
|
+
execAllowedModules: env_1.default.FLOWS_EXEC_ALLOWED_MODULES ? (0, utils_1.toArray)(env_1.default.FLOWS_EXEC_ALLOWED_MODULES) : [],
|
|
82
|
+
};
|
|
80
83
|
}
|
|
81
84
|
if (((_b = this.accountability) === null || _b === void 0 ? void 0 : _b.admin) === true) {
|
|
82
|
-
const osType =
|
|
83
|
-
const osVersion = osType === 'macOS' ? `${(0, macos_release_1.default)().name} (${(0, macos_release_1.default)().version})` : os_1.default.release();
|
|
85
|
+
const { osType, osVersion } = (0, get_os_info_1.getOSInfo)();
|
|
84
86
|
info.directus = {
|
|
85
87
|
version: package_json_1.version,
|
|
86
88
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const knex_1 = __importDefault(require("knex"));
|
|
7
|
+
const knex_mock_client_1 = require("knex-mock-client");
|
|
8
|
+
const services_1 = require("../../src/services");
|
|
9
|
+
jest.mock('../../src/database/index', () => {
|
|
10
|
+
return { getDatabaseClient: jest.fn().mockReturnValue('postgres') };
|
|
11
|
+
});
|
|
12
|
+
jest.requireMock('../../src/database/index');
|
|
13
|
+
class Client_PG extends knex_mock_client_1.MockClient {
|
|
14
|
+
}
|
|
15
|
+
describe('Integration Tests', () => {
|
|
16
|
+
let db;
|
|
17
|
+
let tracker;
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
db = (0, knex_1.default)({ client: Client_PG });
|
|
20
|
+
tracker = (0, knex_mock_client_1.getTracker)();
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
tracker.reset();
|
|
24
|
+
jest.clearAllMocks();
|
|
25
|
+
});
|
|
26
|
+
describe('Services / Specifications', () => {
|
|
27
|
+
describe('oas', () => {
|
|
28
|
+
describe('generate', () => {
|
|
29
|
+
let service;
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
service = new services_1.SpecificationService({
|
|
32
|
+
knex: db,
|
|
33
|
+
schema: { collections: {}, relations: [] },
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
it('returns untyped schema for json fields', async () => {
|
|
37
|
+
var _a;
|
|
38
|
+
jest.spyOn(services_1.CollectionsService.prototype, 'readByQuery').mockImplementation(jest.fn().mockReturnValue([
|
|
39
|
+
{
|
|
40
|
+
collection: 'test_table',
|
|
41
|
+
meta: {
|
|
42
|
+
accountability: 'all',
|
|
43
|
+
collection: 'test_table',
|
|
44
|
+
group: null,
|
|
45
|
+
hidden: false,
|
|
46
|
+
icon: null,
|
|
47
|
+
item_duplication_fields: null,
|
|
48
|
+
note: null,
|
|
49
|
+
singleton: false,
|
|
50
|
+
translations: null,
|
|
51
|
+
},
|
|
52
|
+
schema: {
|
|
53
|
+
name: 'test_table',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
]));
|
|
57
|
+
jest.spyOn(services_1.FieldsService.prototype, 'readAll').mockImplementation(jest.fn().mockReturnValue([
|
|
58
|
+
{
|
|
59
|
+
collection: 'test_table',
|
|
60
|
+
field: 'id',
|
|
61
|
+
type: 'integer',
|
|
62
|
+
schema: {
|
|
63
|
+
is_nullable: false,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
collection: 'test_table',
|
|
68
|
+
field: 'blob',
|
|
69
|
+
type: 'json',
|
|
70
|
+
schema: {
|
|
71
|
+
is_nullable: true,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
]));
|
|
75
|
+
jest.spyOn(services_1.RelationsService.prototype, 'readAll').mockImplementation(jest.fn().mockReturnValue([]));
|
|
76
|
+
const spec = await service.oas.generate();
|
|
77
|
+
expect((_a = spec.components) === null || _a === void 0 ? void 0 : _a.schemas).toEqual({
|
|
78
|
+
ItemsTestTable: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
id: {
|
|
82
|
+
nullable: false,
|
|
83
|
+
type: 'integer',
|
|
84
|
+
},
|
|
85
|
+
blob: {
|
|
86
|
+
nullable: true,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
'x-collection': 'test_table',
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
package/dist/types/items.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* I know this looks a little silly, but it allows us to explicitly differentiate between when we're
|
|
3
3
|
* expecting an item vs any other generic object.
|
|
4
4
|
*/
|
|
5
|
+
import { EventContext } from '@directus/shared/types';
|
|
5
6
|
export declare type Item = Record<string, any>;
|
|
6
7
|
export declare type PrimaryKey = string | number;
|
|
7
8
|
export declare type Alterations = {
|
|
@@ -26,4 +27,14 @@ export declare type MutationOptions = {
|
|
|
26
27
|
* Allow disabling the emitting of hooks. Useful if a custom hook is fired (like files.upload)
|
|
27
28
|
*/
|
|
28
29
|
emitEvents?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* To bypass the emitting of action events if emitEvents is enabled
|
|
32
|
+
* Can be used to queue up the nested events from item service's create, update and delete
|
|
33
|
+
*/
|
|
34
|
+
bypassEmitAction?: (params: ActionEventParams) => void;
|
|
35
|
+
};
|
|
36
|
+
export declare type ActionEventParams = {
|
|
37
|
+
event: string | string[];
|
|
38
|
+
meta: Record<string, any>;
|
|
39
|
+
context: EventContext;
|
|
29
40
|
};
|
|
@@ -13,6 +13,7 @@ const get_column_1 = require("./get-column");
|
|
|
13
13
|
const get_column_path_1 = require("./get-column-path");
|
|
14
14
|
const get_relation_info_1 = require("./get-relation-info");
|
|
15
15
|
const utils_1 = require("@directus/shared/utils");
|
|
16
|
+
const strip_function_1 = require("./strip-function");
|
|
16
17
|
const generateAlias = (0, nanoid_1.customAlphabet)('abcdefghijklmnopqrstuvwxyz', 5);
|
|
17
18
|
/**
|
|
18
19
|
* Apply the Query to a given Knex query builder instance
|
|
@@ -211,9 +212,11 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
211
212
|
const { columnPath, targetCollection } = (0, get_column_path_1.getColumnPath)({ path: filterPath, collection, relations, aliasMap });
|
|
212
213
|
if (!columnPath)
|
|
213
214
|
continue;
|
|
215
|
+
validateFilterOperator(schema.collections[targetCollection].fields[(0, strip_function_1.stripFunction)(filterPath[filterPath.length - 1])].type, filterOperator, schema.collections[targetCollection].fields[(0, strip_function_1.stripFunction)(filterPath[filterPath.length - 1])].special);
|
|
214
216
|
applyFilterToQuery(columnPath, filterOperator, filterValue, logical, targetCollection);
|
|
215
217
|
}
|
|
216
218
|
else {
|
|
219
|
+
validateFilterOperator(schema.collections[collection].fields[(0, strip_function_1.stripFunction)(filterPath[0])].type, filterOperator, schema.collections[collection].fields[(0, strip_function_1.stripFunction)(filterPath[0])].special);
|
|
217
220
|
applyFilterToQuery(`${collection}.${filterPath[0]}`, filterOperator, filterValue, logical);
|
|
218
221
|
}
|
|
219
222
|
}
|
|
@@ -245,6 +248,18 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
245
248
|
}
|
|
246
249
|
}
|
|
247
250
|
}
|
|
251
|
+
function validateFilterOperator(type, filterOperator, special) {
|
|
252
|
+
if (filterOperator.startsWith('_')) {
|
|
253
|
+
filterOperator = filterOperator.slice(1);
|
|
254
|
+
}
|
|
255
|
+
if (!(0, utils_1.getFilterOperatorsForType)(type).includes(filterOperator)) {
|
|
256
|
+
throw new invalid_query_1.InvalidQueryException(`"${type}" field type does not contain the "_${filterOperator}" filter operator`);
|
|
257
|
+
}
|
|
258
|
+
if (special.includes('conceal') &&
|
|
259
|
+
!(0, utils_1.getFilterOperatorsForType)('hash').includes(filterOperator)) {
|
|
260
|
+
throw new invalid_query_1.InvalidQueryException(`Field with "conceal" special does not allow the "_${filterOperator}" filter operator`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
248
263
|
function applyFilterToQuery(key, operator, compareValue, logical = 'and', originalCollectionName) {
|
|
249
264
|
const [table, column] = key.split('.');
|
|
250
265
|
// Is processed through Knex.Raw, so should be safe to string-inject into these where queries
|
|
@@ -64,6 +64,21 @@ async function applySnapshot(snapshot, options) {
|
|
|
64
64
|
const deleteCollections = async (collections) => {
|
|
65
65
|
for (const { collection, diff } of collections) {
|
|
66
66
|
if ((diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'D') {
|
|
67
|
+
const relations = schema.relations.filter((r) => r.related_collection === collection || r.collection === collection);
|
|
68
|
+
if (relations.length > 0) {
|
|
69
|
+
const relationsService = new services_1.RelationsService({ knex: trx, schema });
|
|
70
|
+
for (const relation of relations) {
|
|
71
|
+
try {
|
|
72
|
+
await relationsService.deleteOne(relation.collection, relation.field);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
logger_1.default.error(`Failed to delete collection "${collection}" due to relation "${relation.collection}.${relation.field}"`);
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// clean up deleted relations from existing schema
|
|
80
|
+
schema.relations = schema.relations.filter((r) => r.related_collection !== collection && r.collection !== collection);
|
|
81
|
+
}
|
|
67
82
|
await deleteCollections(getNestedCollectionsToDelete(collection));
|
|
68
83
|
try {
|
|
69
84
|
await collectionsService.deleteOne(collection);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|