directus 9.15.1 → 9.17.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 +8 -7
- package/dist/auth/drivers/ldap.js +1 -0
- package/dist/auth/drivers/local.js +6 -0
- package/dist/auth/drivers/oauth2.js +1 -0
- package/dist/auth/drivers/openid.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 +6 -2
- package/dist/controllers/activity.js +1 -0
- package/dist/controllers/assets.js +20 -16
- package/dist/controllers/auth.js +6 -9
- 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/20220826A-add-origin-to-accountability.d.ts +3 -0
- package/dist/database/migrations/20220826A-add-origin-to-accountability.js +21 -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/activity.yaml +6 -0
- package/dist/database/system-data/fields/sessions.yaml +2 -0
- package/dist/env.js +15 -0
- package/dist/env.test.d.ts +8 -0
- package/dist/env.test.js +39 -0
- package/dist/extensions.d.ts +1 -0
- package/dist/extensions.js +16 -3
- package/dist/flows.js +28 -17
- package/dist/mailer.js +1 -0
- package/dist/middleware/authenticate.d.ts +1 -1
- package/dist/middleware/authenticate.js +1 -0
- package/dist/middleware/authenticate.test.d.ts +1 -0
- package/dist/middleware/authenticate.test.js +214 -0
- package/dist/middleware/extract-token.test.d.ts +1 -0
- package/dist/middleware/extract-token.test.js +60 -0
- package/dist/middleware/validate-batch.d.ts +1 -2
- package/dist/middleware/validate-batch.js +10 -13
- package/dist/middleware/validate-batch.test.d.ts +1 -0
- package/dist/middleware/validate-batch.test.js +82 -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/operations/transform/index.d.ts +1 -1
- package/dist/operations/transform/index.js +1 -1
- package/dist/services/authentication.js +13 -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 +78 -75
- package/dist/services/items.js +98 -42
- 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 +63 -12
- package/dist/services/payload.test.d.ts +1 -0
- package/dist/services/payload.test.js +94 -0
- package/dist/services/server.js +10 -7
- package/dist/services/shares.js +2 -1
- 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 +7 -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/async-handler.d.ts +2 -6
- package/dist/utils/async-handler.js +1 -13
- package/dist/utils/async-handler.test.d.ts +1 -0
- package/dist/utils/async-handler.test.js +18 -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 +14 -12
|
@@ -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
|
}
|
|
@@ -520,11 +538,37 @@ class PayloadService {
|
|
|
520
538
|
if (error)
|
|
521
539
|
throw new exceptions_1.InvalidPayloadException(`Invalid one-to-many update structure: ${error.message}`);
|
|
522
540
|
if (alterations.create) {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
541
|
+
const sortField = relation.meta.sort_field;
|
|
542
|
+
let createPayload;
|
|
543
|
+
if (sortField !== null) {
|
|
544
|
+
const highestOrderNumber = await this.knex
|
|
545
|
+
.from(relation.collection)
|
|
546
|
+
.where({ [relation.field]: parent })
|
|
547
|
+
.whereNotNull(sortField)
|
|
548
|
+
.max(sortField)
|
|
549
|
+
.first();
|
|
550
|
+
createPayload = alterations.create.map((item, index) => {
|
|
551
|
+
const record = (0, lodash_1.cloneDeep)(item);
|
|
552
|
+
// add sort field value if it is not supplied in the item
|
|
553
|
+
if (parent !== null && record[sortField] === undefined) {
|
|
554
|
+
record[sortField] = (highestOrderNumber === null || highestOrderNumber === void 0 ? void 0 : highestOrderNumber.max) ? highestOrderNumber.max + index + 1 : index + 1;
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
...record,
|
|
558
|
+
[relation.field]: parent || payload[currentPrimaryKeyField],
|
|
559
|
+
};
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
createPayload = alterations.create.map((item) => ({
|
|
564
|
+
...item,
|
|
565
|
+
[relation.field]: parent || payload[currentPrimaryKeyField],
|
|
566
|
+
}));
|
|
567
|
+
}
|
|
568
|
+
await itemsService.createMany(createPayload, {
|
|
527
569
|
onRevisionCreate: (pk) => revisions.push(pk),
|
|
570
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
571
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
528
572
|
});
|
|
529
573
|
}
|
|
530
574
|
if (alterations.update) {
|
|
@@ -535,6 +579,8 @@ class PayloadService {
|
|
|
535
579
|
[relation.field]: parent || payload[currentPrimaryKeyField],
|
|
536
580
|
}, {
|
|
537
581
|
onRevisionCreate: (pk) => revisions.push(pk),
|
|
582
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
583
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
538
584
|
});
|
|
539
585
|
}
|
|
540
586
|
}
|
|
@@ -556,17 +602,22 @@ class PayloadService {
|
|
|
556
602
|
},
|
|
557
603
|
};
|
|
558
604
|
if (relation.meta.one_deselect_action === 'delete') {
|
|
559
|
-
await itemsService.deleteByQuery(query
|
|
605
|
+
await itemsService.deleteByQuery(query, {
|
|
606
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
607
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
608
|
+
});
|
|
560
609
|
}
|
|
561
610
|
else {
|
|
562
611
|
await itemsService.updateByQuery(query, { [relation.field]: null }, {
|
|
563
612
|
onRevisionCreate: (pk) => revisions.push(pk),
|
|
613
|
+
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
614
|
+
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
564
615
|
});
|
|
565
616
|
}
|
|
566
617
|
}
|
|
567
618
|
}
|
|
568
619
|
}
|
|
569
|
-
return { revisions };
|
|
620
|
+
return { revisions, nestedActionEvents };
|
|
570
621
|
}
|
|
571
622
|
/**
|
|
572
623
|
* 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
|
};
|
|
@@ -141,7 +143,7 @@ class ServerService {
|
|
|
141
143
|
componentType: 'datastore',
|
|
142
144
|
observedUnit: 'ms',
|
|
143
145
|
observedValue: 0,
|
|
144
|
-
threshold: 150,
|
|
146
|
+
threshold: env_1.default.DB_HEALTHCHECK_THRESHOLD ? +env_1.default.DB_HEALTHCHECK_THRESHOLD : 150,
|
|
145
147
|
},
|
|
146
148
|
];
|
|
147
149
|
const startTime = perf_hooks_1.performance.now();
|
|
@@ -186,7 +188,7 @@ class ServerService {
|
|
|
186
188
|
componentType: 'cache',
|
|
187
189
|
observedValue: 0,
|
|
188
190
|
observedUnit: 'ms',
|
|
189
|
-
threshold: 150,
|
|
191
|
+
threshold: env_1.default.CACHE_HEALTHCHECK_THRESHOLD ? +env_1.default.CACHE_HEALTHCHECK_THRESHOLD : 150,
|
|
190
192
|
},
|
|
191
193
|
],
|
|
192
194
|
};
|
|
@@ -220,7 +222,7 @@ class ServerService {
|
|
|
220
222
|
componentType: 'ratelimiter',
|
|
221
223
|
observedValue: 0,
|
|
222
224
|
observedUnit: 'ms',
|
|
223
|
-
threshold: 150,
|
|
225
|
+
threshold: env_1.default.RATE_LIMITER_HEALTHCHECK_THRESHOLD ? +env_1.default.RATE_LIMITER_HEALTHCHECK_THRESHOLD : 150,
|
|
224
226
|
},
|
|
225
227
|
],
|
|
226
228
|
};
|
|
@@ -247,13 +249,14 @@ class ServerService {
|
|
|
247
249
|
const checks = {};
|
|
248
250
|
for (const location of (0, utils_1.toArray)(env_1.default.STORAGE_LOCATIONS)) {
|
|
249
251
|
const disk = storage_1.default.disk(location);
|
|
252
|
+
const envThresholdKey = `STORAGE_${location}_HEALTHCHECK_THRESHOLD`.toUpperCase();
|
|
250
253
|
checks[`storage:${location}:responseTime`] = [
|
|
251
254
|
{
|
|
252
255
|
status: 'ok',
|
|
253
256
|
componentType: 'objectstore',
|
|
254
257
|
observedValue: 0,
|
|
255
258
|
observedUnit: 'ms',
|
|
256
|
-
threshold: 750,
|
|
259
|
+
threshold: env_1.default[envThresholdKey] ? +env_1.default[envThresholdKey] : 750,
|
|
257
260
|
},
|
|
258
261
|
];
|
|
259
262
|
const startTime = perf_hooks_1.performance.now();
|
package/dist/services/shares.js
CHANGED
|
@@ -31,7 +31,7 @@ class SharesService extends items_1.ItemsService {
|
|
|
31
31
|
return super.createOne(data, opts);
|
|
32
32
|
}
|
|
33
33
|
async login(payload) {
|
|
34
|
-
var _a, _b;
|
|
34
|
+
var _a, _b, _c;
|
|
35
35
|
const record = await this.knex
|
|
36
36
|
.select({
|
|
37
37
|
share_id: 'id',
|
|
@@ -86,6 +86,7 @@ class SharesService extends items_1.ItemsService {
|
|
|
86
86
|
expires: refreshTokenExpiration,
|
|
87
87
|
ip: (_a = this.accountability) === null || _a === void 0 ? void 0 : _a.ip,
|
|
88
88
|
user_agent: (_b = this.accountability) === null || _b === void 0 ? void 0 : _b.userAgent,
|
|
89
|
+
origin: (_c = this.accountability) === null || _c === void 0 ? void 0 : _c.origin,
|
|
89
90
|
share: record.share_id,
|
|
90
91
|
});
|
|
91
92
|
await this.knex('directus_sessions').delete().where('expires', '<', new Date());
|
|
@@ -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
|
};
|
|
@@ -212,11 +212,11 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
212
212
|
const { columnPath, targetCollection } = (0, get_column_path_1.getColumnPath)({ path: filterPath, collection, relations, aliasMap });
|
|
213
213
|
if (!columnPath)
|
|
214
214
|
continue;
|
|
215
|
-
validateFilterOperator(schema.collections[targetCollection].fields[(0, strip_function_1.stripFunction)(filterPath[filterPath.length - 1])].type, filterOperator);
|
|
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);
|
|
216
216
|
applyFilterToQuery(columnPath, filterOperator, filterValue, logical, targetCollection);
|
|
217
217
|
}
|
|
218
218
|
else {
|
|
219
|
-
validateFilterOperator(schema.collections[collection].fields[(0, strip_function_1.stripFunction)(filterPath[0])].type, filterOperator);
|
|
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);
|
|
220
220
|
applyFilterToQuery(`${collection}.${filterPath[0]}`, filterOperator, filterValue, logical);
|
|
221
221
|
}
|
|
222
222
|
}
|
|
@@ -248,13 +248,17 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
|
-
function validateFilterOperator(type, filterOperator) {
|
|
251
|
+
function validateFilterOperator(type, filterOperator, special) {
|
|
252
252
|
if (filterOperator.startsWith('_')) {
|
|
253
253
|
filterOperator = filterOperator.slice(1);
|
|
254
254
|
}
|
|
255
255
|
if (!(0, utils_1.getFilterOperatorsForType)(type).includes(filterOperator)) {
|
|
256
256
|
throw new invalid_query_1.InvalidQueryException(`"${type}" field type does not contain the "_${filterOperator}" filter operator`);
|
|
257
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
|
+
}
|
|
258
262
|
}
|
|
259
263
|
function applyFilterToQuery(key, operator, compareValue, logical = 'and', originalCollectionName) {
|
|
260
264
|
const [table, column] = key.split('.');
|
|
@@ -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 {};
|