directus 9.13.0 → 9.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +9 -4
- package/dist/auth/drivers/openid.js +4 -1
- package/dist/cli/commands/security/key.d.ts +1 -0
- package/dist/cli/commands/security/key.js +8 -0
- package/dist/cli/commands/security/secret.d.ts +1 -0
- package/dist/cli/commands/security/secret.js +8 -0
- package/dist/cli/index.js +6 -0
- package/dist/controllers/extensions.js +9 -1
- package/dist/controllers/files.js +1 -1
- package/dist/database/helpers/fn/dialects/mssql.d.ts +2 -2
- package/dist/database/helpers/fn/dialects/mssql.js +2 -2
- package/dist/database/helpers/fn/dialects/mysql.d.ts +2 -2
- package/dist/database/helpers/fn/dialects/mysql.js +2 -2
- package/dist/database/helpers/fn/dialects/oracle.d.ts +1 -1
- package/dist/database/helpers/fn/dialects/oracle.js +2 -2
- package/dist/database/helpers/fn/dialects/postgres.d.ts +2 -2
- package/dist/database/helpers/fn/dialects/postgres.js +2 -2
- package/dist/database/helpers/fn/dialects/sqlite.d.ts +1 -1
- package/dist/database/helpers/fn/dialects/sqlite.js +2 -2
- package/dist/database/helpers/fn/types.d.ts +3 -2
- package/dist/database/helpers/fn/types.js +11 -8
- package/dist/database/helpers/index.d.ts +3 -3
- package/dist/database/helpers/schema/dialects/sqlite.d.ts +5 -0
- package/dist/database/helpers/schema/dialects/sqlite.js +17 -0
- package/dist/database/helpers/schema/index.d.ts +1 -1
- package/dist/database/helpers/schema/index.js +4 -4
- package/dist/database/helpers/schema/types.d.ts +2 -0
- package/dist/database/helpers/schema/types.js +6 -0
- package/dist/database/run-ast.js +8 -5
- package/dist/emitter.js +12 -7
- package/dist/logger.d.ts +0 -1
- package/dist/middleware/authenticate.d.ts +0 -1
- package/dist/middleware/cache.js +3 -3
- package/dist/middleware/graphql.js +9 -7
- package/dist/middleware/respond.js +5 -5
- package/dist/operations/item-read/index.d.ts +1 -0
- package/dist/operations/item-read/index.js +3 -3
- package/dist/operations/request/index.js +1 -1
- package/dist/services/authorization.js +15 -5
- package/dist/services/fields.js +4 -0
- package/dist/services/graphql/index.js +40 -15
- package/dist/services/import-export.js +6 -1
- package/dist/services/items.d.ts +1 -0
- package/dist/services/items.js +21 -17
- package/dist/services/notifications.js +22 -11
- package/dist/services/payload.js +11 -8
- package/dist/services/server.js +13 -2
- package/dist/types/ast.d.ts +11 -4
- package/dist/utils/apply-query.js +5 -13
- package/dist/utils/apply-snapshot.js +41 -17
- package/dist/utils/filter-items.js +3 -6
- package/dist/utils/get-ast-from-query.js +18 -0
- package/dist/utils/get-column-path.d.ts +5 -1
- package/dist/utils/get-column-path.js +3 -1
- package/dist/utils/get-column.d.ts +2 -2
- package/dist/utils/get-column.js +2 -2
- package/dist/utils/get-config-from-env.js +1 -1
- package/dist/utils/merge-permissions.d.ts +0 -1
- package/package.json +232 -226
- package/dist/utils/generate-joi.d.ts +0 -3
- package/dist/utils/generate-joi.js +0 -145
|
@@ -6,7 +6,7 @@ const get_accountability_for_role_1 = require("../../utils/get-accountability-fo
|
|
|
6
6
|
const sanitize_query_1 = require("../../utils/sanitize-query");
|
|
7
7
|
exports.default = (0, utils_1.defineOperationApi)({
|
|
8
8
|
id: 'item-read',
|
|
9
|
-
handler: async ({ collection, key, query, permissions }, { accountability, database, getSchema }) => {
|
|
9
|
+
handler: async ({ collection, key, query, emitEvents, permissions }, { accountability, database, getSchema }) => {
|
|
10
10
|
const schema = await getSchema({ database });
|
|
11
11
|
let customAccountability;
|
|
12
12
|
if (!permissions || permissions === '$trigger') {
|
|
@@ -35,10 +35,10 @@ exports.default = (0, utils_1.defineOperationApi)({
|
|
|
35
35
|
else {
|
|
36
36
|
const keys = (0, utils_1.toArray)(key);
|
|
37
37
|
if (keys.length === 1) {
|
|
38
|
-
result = await itemsService.readOne(keys[0], sanitizedQueryObject);
|
|
38
|
+
result = await itemsService.readOne(keys[0], sanitizedQueryObject, { emitEvents });
|
|
39
39
|
}
|
|
40
40
|
else {
|
|
41
|
-
result = await itemsService.readMany(keys, sanitizedQueryObject);
|
|
41
|
+
result = await itemsService.readMany(keys, sanitizedQueryObject, { emitEvents });
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
return result;
|
|
@@ -12,7 +12,7 @@ exports.default = (0, utils_1.defineOperationApi)({
|
|
|
12
12
|
acc[header] = value;
|
|
13
13
|
return acc;
|
|
14
14
|
}, {});
|
|
15
|
-
const result = await (0, axios_1.default)({ url, method, data: body, headers: customHeaders });
|
|
15
|
+
const result = await (0, axios_1.default)({ url: encodeURI(url), method, data: body, headers: customHeaders });
|
|
16
16
|
return { status: result.status, statusText: result.statusText, headers: result.headers, data: result.data };
|
|
17
17
|
},
|
|
18
18
|
});
|
|
@@ -48,7 +48,7 @@ class AuthorizationService {
|
|
|
48
48
|
collections.push(...ast.names.map((name) => ({ collection: name, field: ast.fieldKey })));
|
|
49
49
|
for (const children of Object.values(ast.children)) {
|
|
50
50
|
for (const nestedNode of children) {
|
|
51
|
-
if (nestedNode.type !== 'field') {
|
|
51
|
+
if (nestedNode.type !== 'field' && nestedNode.type !== 'functionField') {
|
|
52
52
|
collections.push(...getCollectionsFromAST(nestedNode));
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -60,7 +60,13 @@ class AuthorizationService {
|
|
|
60
60
|
field: ast.type === 'root' ? null : ast.fieldKey,
|
|
61
61
|
});
|
|
62
62
|
for (const nestedNode of ast.children) {
|
|
63
|
-
if (nestedNode.type
|
|
63
|
+
if (nestedNode.type === 'functionField') {
|
|
64
|
+
collections.push({
|
|
65
|
+
collection: nestedNode.relatedCollection,
|
|
66
|
+
field: null,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else if (nestedNode.type !== 'field') {
|
|
64
70
|
collections.push(...getCollectionsFromAST(nestedNode));
|
|
65
71
|
}
|
|
66
72
|
}
|
|
@@ -69,7 +75,7 @@ class AuthorizationService {
|
|
|
69
75
|
}
|
|
70
76
|
function validateFields(ast) {
|
|
71
77
|
var _a, _b, _c;
|
|
72
|
-
if (ast.type !== 'field') {
|
|
78
|
+
if (ast.type !== 'field' && ast.type !== 'functionField') {
|
|
73
79
|
if (ast.type === 'a2o') {
|
|
74
80
|
for (const [collection, children] of Object.entries(ast.children)) {
|
|
75
81
|
checkFields(collection, children, (_b = (_a = ast.query) === null || _a === void 0 ? void 0 : _a[collection]) === null || _b === void 0 ? void 0 : _b.aggregate);
|
|
@@ -112,7 +118,7 @@ class AuthorizationService {
|
|
|
112
118
|
function validateFilterPermissions(ast, schema, action, accountability) {
|
|
113
119
|
var _a, _b, _c, _d, _e;
|
|
114
120
|
let requiredFieldPermissions = {};
|
|
115
|
-
if (ast.type !== 'field') {
|
|
121
|
+
if (ast.type !== 'field' && ast.type !== 'functionField') {
|
|
116
122
|
if (ast.type === 'a2o') {
|
|
117
123
|
for (const collection of Object.keys(ast.children)) {
|
|
118
124
|
requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, extractRequiredFieldPermissions(collection, (_c = (_b = (_a = ast.query) === null || _a === void 0 ? void 0 : _a[collection]) === null || _b === void 0 ? void 0 : _b.filter) !== null && _c !== void 0 ? _c : {}));
|
|
@@ -290,7 +296,11 @@ class AuthorizationService {
|
|
|
290
296
|
}
|
|
291
297
|
}
|
|
292
298
|
function applyFilters(ast, accountability) {
|
|
293
|
-
if (ast.type
|
|
299
|
+
if (ast.type === 'functionField') {
|
|
300
|
+
const collection = ast.relatedCollection;
|
|
301
|
+
updateFilterQuery(collection, ast.query);
|
|
302
|
+
}
|
|
303
|
+
else if (ast.type !== 'field') {
|
|
294
304
|
if (ast.type === 'a2o') {
|
|
295
305
|
const collections = Object.keys(ast.children);
|
|
296
306
|
for (const collection of collections) {
|
package/dist/services/fields.js
CHANGED
|
@@ -351,6 +351,7 @@ class FieldsService {
|
|
|
351
351
|
if (this.accountability && this.accountability.admin !== true) {
|
|
352
352
|
throw new exceptions_1.ForbiddenException();
|
|
353
353
|
}
|
|
354
|
+
const runPostColumnDelete = await this.helpers.schema.preColumnDelete();
|
|
354
355
|
try {
|
|
355
356
|
await emitter_1.default.emitFilter('fields.delete', [field], {
|
|
356
357
|
collection: collection,
|
|
@@ -439,6 +440,9 @@ class FieldsService {
|
|
|
439
440
|
});
|
|
440
441
|
}
|
|
441
442
|
finally {
|
|
443
|
+
if (runPostColumnDelete) {
|
|
444
|
+
await this.helpers.schema.postColumnDelete();
|
|
445
|
+
}
|
|
442
446
|
if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
|
|
443
447
|
await this.cache.clear();
|
|
444
448
|
}
|
|
@@ -12,6 +12,7 @@ const lodash_1 = require("lodash");
|
|
|
12
12
|
const ms_1 = __importDefault(require("ms"));
|
|
13
13
|
const cache_1 = require("../../cache");
|
|
14
14
|
const constants_1 = require("../../constants");
|
|
15
|
+
const constants_2 = require("@directus/shared/constants");
|
|
15
16
|
const database_1 = __importDefault(require("../../database"));
|
|
16
17
|
const env_1 = __importDefault(require("../../env"));
|
|
17
18
|
const exceptions_1 = require("../../exceptions");
|
|
@@ -488,6 +489,12 @@ class GraphQLService {
|
|
|
488
489
|
_nnull: {
|
|
489
490
|
type: graphql_1.GraphQLBoolean,
|
|
490
491
|
},
|
|
492
|
+
_between: {
|
|
493
|
+
type: new graphql_1.GraphQLList(string_or_float_1.GraphQLStringOrFloat),
|
|
494
|
+
},
|
|
495
|
+
_nbetween: {
|
|
496
|
+
type: new graphql_1.GraphQLList(string_or_float_1.GraphQLStringOrFloat),
|
|
497
|
+
},
|
|
491
498
|
},
|
|
492
499
|
});
|
|
493
500
|
// Uses StringOrFloat rather than Float to support api dynamic variables (like `$NOW`)
|
|
@@ -524,6 +531,12 @@ class GraphQLService {
|
|
|
524
531
|
_nnull: {
|
|
525
532
|
type: graphql_1.GraphQLBoolean,
|
|
526
533
|
},
|
|
534
|
+
_between: {
|
|
535
|
+
type: new graphql_1.GraphQLList(string_or_float_1.GraphQLStringOrFloat),
|
|
536
|
+
},
|
|
537
|
+
_nbetween: {
|
|
538
|
+
type: new graphql_1.GraphQLList(string_or_float_1.GraphQLStringOrFloat),
|
|
539
|
+
},
|
|
527
540
|
},
|
|
528
541
|
});
|
|
529
542
|
const GeometryFilterOperators = schemaComposer.createInputTC({
|
|
@@ -671,6 +684,16 @@ class GraphQLService {
|
|
|
671
684
|
return acc;
|
|
672
685
|
}, {}),
|
|
673
686
|
});
|
|
687
|
+
const countType = schemaComposer.createObjectTC({
|
|
688
|
+
name: `${collection.collection}_aggregated_count`,
|
|
689
|
+
fields: Object.values(collection.fields).reduce((acc, field) => {
|
|
690
|
+
acc[field.field] = {
|
|
691
|
+
type: graphql_1.GraphQLInt,
|
|
692
|
+
description: field.note,
|
|
693
|
+
};
|
|
694
|
+
return acc;
|
|
695
|
+
}, {}),
|
|
696
|
+
});
|
|
674
697
|
AggregateMethods[collection.collection] = {
|
|
675
698
|
group: {
|
|
676
699
|
name: 'group',
|
|
@@ -682,16 +705,11 @@ class GraphQLService {
|
|
|
682
705
|
},
|
|
683
706
|
count: {
|
|
684
707
|
name: 'count',
|
|
685
|
-
type:
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
description: field.note,
|
|
691
|
-
};
|
|
692
|
-
return acc;
|
|
693
|
-
}, {}),
|
|
694
|
-
}),
|
|
708
|
+
type: countType,
|
|
709
|
+
},
|
|
710
|
+
countDistinct: {
|
|
711
|
+
name: 'countDistinct',
|
|
712
|
+
type: countType,
|
|
695
713
|
},
|
|
696
714
|
};
|
|
697
715
|
const hasNumericAggregates = Object.values(collection.fields).some((field) => {
|
|
@@ -711,10 +729,6 @@ class GraphQLService {
|
|
|
711
729
|
name: 'sum',
|
|
712
730
|
type: AggregatedFields[collection.collection],
|
|
713
731
|
},
|
|
714
|
-
countDistinct: {
|
|
715
|
-
name: 'countDistinct',
|
|
716
|
-
type: AggregatedFields[collection.collection],
|
|
717
|
-
},
|
|
718
732
|
avgDistinct: {
|
|
719
733
|
name: 'avgDistinct',
|
|
720
734
|
type: AggregatedFields[collection.collection],
|
|
@@ -1042,6 +1056,17 @@ class GraphQLService {
|
|
|
1042
1056
|
};
|
|
1043
1057
|
query.limit = 1;
|
|
1044
1058
|
}
|
|
1059
|
+
// Transform count(a.b.c) into a.b.count(c)
|
|
1060
|
+
for (let fieldIndex = 0; fieldIndex < query.fields.length; fieldIndex++) {
|
|
1061
|
+
if (query.fields[fieldIndex].includes('(') && query.fields[fieldIndex].includes(')')) {
|
|
1062
|
+
const functionName = query.fields[fieldIndex].split('(')[0];
|
|
1063
|
+
const columnNames = query.fields[fieldIndex].match(constants_2.REGEX_BETWEEN_PARENS)[1].split('.');
|
|
1064
|
+
if (columnNames.length > 1) {
|
|
1065
|
+
const column = columnNames.pop();
|
|
1066
|
+
query.fields[fieldIndex] = columnNames.join('.') + '.' + functionName + '(' + column + ')';
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1045
1070
|
const result = await this.read(collection, query);
|
|
1046
1071
|
if (args.id) {
|
|
1047
1072
|
return (result === null || result === void 0 ? void 0 : result[0]) || null;
|
|
@@ -1288,7 +1313,7 @@ class GraphQLService {
|
|
|
1288
1313
|
result[currentKey] = Object.values(value)[0];
|
|
1289
1314
|
}
|
|
1290
1315
|
else {
|
|
1291
|
-
result[currentKey] = (0
|
|
1316
|
+
result[currentKey] = (value === null || value === void 0 ? void 0 : value.constructor) === Object ? replaceFuncDeep(value) : value;
|
|
1292
1317
|
}
|
|
1293
1318
|
});
|
|
1294
1319
|
}
|
|
@@ -102,7 +102,12 @@ class ImportService {
|
|
|
102
102
|
else {
|
|
103
103
|
try {
|
|
104
104
|
const parsedJson = (0, utils_1.parseJSON)(value);
|
|
105
|
-
|
|
105
|
+
if (typeof parsedJson === 'number') {
|
|
106
|
+
(0, lodash_1.set)(result, key, value);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
(0, lodash_1.set)(result, key, parsedJson);
|
|
110
|
+
}
|
|
106
111
|
}
|
|
107
112
|
catch {
|
|
108
113
|
(0, lodash_1.set)(result, key, value);
|
package/dist/services/items.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { AbstractService, AbstractServiceOptions, Item as AnyItem, MutationOptio
|
|
|
5
5
|
export declare type QueryOptions = {
|
|
6
6
|
stripNonRequested?: boolean;
|
|
7
7
|
permissionsAction?: PermissionsAction;
|
|
8
|
+
emitEvents?: boolean;
|
|
8
9
|
};
|
|
9
10
|
export declare class ItemsService<Item extends AnyItem = AnyItem> implements AbstractService {
|
|
10
11
|
collection: string;
|
package/dist/services/items.js
CHANGED
|
@@ -221,23 +221,27 @@ class ItemsService {
|
|
|
221
221
|
if (records === null) {
|
|
222
222
|
throw new exceptions_1.ForbiddenException();
|
|
223
223
|
}
|
|
224
|
-
const filteredRecords =
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
collection: this.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
224
|
+
const filteredRecords = (opts === null || opts === void 0 ? void 0 : opts.emitEvents) !== false
|
|
225
|
+
? await emitter_1.default.emitFilter(this.eventScope === 'items' ? ['items.read', `${this.collection}.items.read`] : `${this.eventScope}.read`, records, {
|
|
226
|
+
query,
|
|
227
|
+
collection: this.collection,
|
|
228
|
+
}, {
|
|
229
|
+
database: this.knex,
|
|
230
|
+
schema: this.schema,
|
|
231
|
+
accountability: this.accountability,
|
|
232
|
+
})
|
|
233
|
+
: records;
|
|
234
|
+
if ((opts === null || opts === void 0 ? void 0 : opts.emitEvents) !== false) {
|
|
235
|
+
emitter_1.default.emitAction(this.eventScope === 'items' ? ['items.read', `${this.collection}.items.read`] : `${this.eventScope}.read`, {
|
|
236
|
+
payload: filteredRecords,
|
|
237
|
+
query,
|
|
238
|
+
collection: this.collection,
|
|
239
|
+
}, {
|
|
240
|
+
database: this.knex || (0, database_1.default)(),
|
|
241
|
+
schema: this.schema,
|
|
242
|
+
accountability: this.accountability,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
241
245
|
return filteredRecords;
|
|
242
246
|
}
|
|
243
247
|
/**
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.NotificationsService = void 0;
|
|
4
7
|
const items_1 = require("./items");
|
|
5
8
|
const md_1 = require("../utils/md");
|
|
6
9
|
const users_1 = require("./users");
|
|
7
10
|
const mail_1 = require("./mail");
|
|
11
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
8
12
|
class NotificationsService extends items_1.ItemsService {
|
|
9
13
|
constructor(options) {
|
|
10
14
|
super('directus_notifications', options);
|
|
@@ -12,29 +16,36 @@ class NotificationsService extends items_1.ItemsService {
|
|
|
12
16
|
this.mailService = new mail_1.MailService({ schema: this.schema, accountability: this.accountability });
|
|
13
17
|
}
|
|
14
18
|
async createOne(data, opts) {
|
|
19
|
+
const response = await super.createOne(data, opts);
|
|
15
20
|
await this.sendEmail(data);
|
|
16
|
-
return
|
|
21
|
+
return response;
|
|
17
22
|
}
|
|
18
23
|
async createMany(data, opts) {
|
|
24
|
+
const response = await super.createMany(data, opts);
|
|
19
25
|
for (const notification of data) {
|
|
20
26
|
await this.sendEmail(notification);
|
|
21
27
|
}
|
|
22
|
-
return
|
|
28
|
+
return response;
|
|
23
29
|
}
|
|
24
30
|
async sendEmail(data) {
|
|
25
31
|
if (data.recipient) {
|
|
26
32
|
const user = await this.usersService.readOne(data.recipient, { fields: ['email', 'email_notifications'] });
|
|
27
33
|
if (user.email && user.email_notifications === true) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
try {
|
|
35
|
+
await this.mailService.send({
|
|
36
|
+
template: {
|
|
37
|
+
name: 'base',
|
|
38
|
+
data: {
|
|
39
|
+
html: data.message ? (0, md_1.md)(data.message) : '',
|
|
40
|
+
},
|
|
33
41
|
},
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
42
|
+
to: user.email,
|
|
43
|
+
subject: data.subject,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
logger_1.default.error(error.message);
|
|
48
|
+
}
|
|
38
49
|
}
|
|
39
50
|
}
|
|
40
51
|
}
|
package/dist/services/payload.js
CHANGED
|
@@ -4,8 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.PayloadService = void 0;
|
|
7
|
-
const utils_1 = require("@directus/shared/utils");
|
|
8
7
|
const date_fns_1 = require("date-fns");
|
|
8
|
+
const utils_1 = require("@directus/shared/utils");
|
|
9
9
|
const flat_1 = require("flat");
|
|
10
10
|
const joi_1 = __importDefault(require("joi"));
|
|
11
11
|
const lodash_1 = require("lodash");
|
|
@@ -259,15 +259,18 @@ class PayloadService {
|
|
|
259
259
|
else {
|
|
260
260
|
if (value instanceof Date === false && typeof value === 'string') {
|
|
261
261
|
if (dateColumn.type === 'date') {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
const parsedDate = (0, date_fns_1.parseISO)(value);
|
|
263
|
+
if (!(0, date_fns_1.isValid)(parsedDate)) {
|
|
264
|
+
throw new exceptions_1.InvalidPayloadException(`Invalid Date format in field "${dateColumn.field}"`);
|
|
265
|
+
}
|
|
266
|
+
payload[name] = parsedDate;
|
|
265
267
|
}
|
|
266
268
|
if (dateColumn.type === 'dateTime') {
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
269
|
+
const parsedDate = (0, date_fns_1.parseISO)(value);
|
|
270
|
+
if (!(0, date_fns_1.isValid)(parsedDate)) {
|
|
271
|
+
throw new exceptions_1.InvalidPayloadException(`Invalid DateTime format in field "${dateColumn.field}"`);
|
|
272
|
+
}
|
|
273
|
+
payload[name] = parsedDate;
|
|
271
274
|
}
|
|
272
275
|
if (dateColumn.type === 'timestamp') {
|
|
273
276
|
const newValue = this.helpers.date.writeTimestamp(value);
|
package/dist/services/server.js
CHANGED
|
@@ -51,7 +51,7 @@ class ServerService {
|
|
|
51
51
|
this.settingsService = new settings_1.SettingsService({ knex: this.knex, schema: this.schema });
|
|
52
52
|
}
|
|
53
53
|
async serverInfo() {
|
|
54
|
-
var _a;
|
|
54
|
+
var _a, _b;
|
|
55
55
|
const info = {};
|
|
56
56
|
const projectInfo = await this.settingsService.readSingleton({
|
|
57
57
|
fields: [
|
|
@@ -67,7 +67,18 @@ class ServerService {
|
|
|
67
67
|
],
|
|
68
68
|
});
|
|
69
69
|
info.project = projectInfo;
|
|
70
|
-
if ((
|
|
70
|
+
if ((_a = this.accountability) === null || _a === void 0 ? void 0 : _a.user) {
|
|
71
|
+
if (env_1.default.RATE_LIMITER_ENABLED) {
|
|
72
|
+
info.rateLimit = {
|
|
73
|
+
points: env_1.default.RATE_LIMITER_POINTS,
|
|
74
|
+
duration: env_1.default.RATE_LIMITER_DURATION,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
info.rateLimit = false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (((_b = this.accountability) === null || _b === void 0 ? void 0 : _b.admin) === true) {
|
|
71
82
|
const osType = os_1.default.type() === 'Darwin' ? 'macOS' : os_1.default.type();
|
|
72
83
|
const osVersion = osType === 'macOS' ? `${(0, macos_release_1.default)().name} (${(0, macos_release_1.default)().version})` : os_1.default.release();
|
|
73
84
|
info.directus = {
|
package/dist/types/ast.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Query, Relation } from '@directus/shared/types';
|
|
|
2
2
|
export declare type M2ONode = {
|
|
3
3
|
type: 'm2o';
|
|
4
4
|
name: string;
|
|
5
|
-
children: (NestedCollectionNode | FieldNode)[];
|
|
5
|
+
children: (NestedCollectionNode | FieldNode | FunctionFieldNode)[];
|
|
6
6
|
query: Query;
|
|
7
7
|
fieldKey: string;
|
|
8
8
|
relation: Relation;
|
|
@@ -13,7 +13,7 @@ export declare type A2MNode = {
|
|
|
13
13
|
type: 'a2o';
|
|
14
14
|
names: string[];
|
|
15
15
|
children: {
|
|
16
|
-
[collection: string]: (NestedCollectionNode | FieldNode)[];
|
|
16
|
+
[collection: string]: (NestedCollectionNode | FieldNode | FunctionFieldNode)[];
|
|
17
17
|
};
|
|
18
18
|
query: {
|
|
19
19
|
[collection: string]: Query;
|
|
@@ -28,7 +28,7 @@ export declare type A2MNode = {
|
|
|
28
28
|
export declare type O2MNode = {
|
|
29
29
|
type: 'o2m';
|
|
30
30
|
name: string;
|
|
31
|
-
children: (NestedCollectionNode | FieldNode)[];
|
|
31
|
+
children: (NestedCollectionNode | FieldNode | FunctionFieldNode)[];
|
|
32
32
|
query: Query;
|
|
33
33
|
fieldKey: string;
|
|
34
34
|
relation: Relation;
|
|
@@ -41,9 +41,16 @@ export declare type FieldNode = {
|
|
|
41
41
|
name: string;
|
|
42
42
|
fieldKey: string;
|
|
43
43
|
};
|
|
44
|
+
export declare type FunctionFieldNode = {
|
|
45
|
+
type: 'functionField';
|
|
46
|
+
name: string;
|
|
47
|
+
fieldKey: string;
|
|
48
|
+
query: Query;
|
|
49
|
+
relatedCollection: string;
|
|
50
|
+
};
|
|
44
51
|
export declare type AST = {
|
|
45
52
|
type: 'root';
|
|
46
53
|
name: string;
|
|
47
|
-
children: (NestedCollectionNode | FieldNode)[];
|
|
54
|
+
children: (NestedCollectionNode | FieldNode | FunctionFieldNode)[];
|
|
48
55
|
query: Query;
|
|
49
56
|
};
|
|
@@ -129,8 +129,8 @@ function applySort(knex, schema, rootQuery, rootSort, collection, subQuery = fal
|
|
|
129
129
|
relations,
|
|
130
130
|
knex,
|
|
131
131
|
});
|
|
132
|
-
const
|
|
133
|
-
const [alias, field] =
|
|
132
|
+
const { columnPath } = (0, get_column_path_1.getColumnPath)({ path: column, collection, aliasMap, relations });
|
|
133
|
+
const [alias, field] = columnPath.split('.');
|
|
134
134
|
return {
|
|
135
135
|
order,
|
|
136
136
|
column: (0, get_column_1.getColumn)(knex, alias, field, false, schema),
|
|
@@ -208,18 +208,10 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
|
|
|
208
208
|
const { operator: filterOperator, value: filterValue } = getOperation(key, value);
|
|
209
209
|
if (relationType === 'm2o' || relationType === 'a2o' || relationType === null) {
|
|
210
210
|
if (filterPath.length > 1) {
|
|
211
|
-
const
|
|
212
|
-
if (!
|
|
211
|
+
const { columnPath, targetCollection } = (0, get_column_path_1.getColumnPath)({ path: filterPath, collection, relations, aliasMap });
|
|
212
|
+
if (!columnPath)
|
|
213
213
|
continue;
|
|
214
|
-
|
|
215
|
-
applyFilterToQuery(columnName, filterOperator, filterValue, logical, relation.related_collection); // m2o
|
|
216
|
-
}
|
|
217
|
-
else if (filterPath[0].includes(':')) {
|
|
218
|
-
applyFilterToQuery(columnName, filterOperator, filterValue, logical, filterPath[0].split(':')[1]); // a2o
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
applyFilterToQuery(columnName, filterOperator, filterValue, logical);
|
|
222
|
-
}
|
|
214
|
+
applyFilterToQuery(columnPath, filterOperator, filterValue, logical, targetCollection);
|
|
223
215
|
}
|
|
224
216
|
else {
|
|
225
217
|
applyFilterToQuery(`${collection}.${filterPath[0]}`, filterOperator, filterValue, logical);
|
|
@@ -32,23 +32,18 @@ async function applySnapshot(snapshot, options) {
|
|
|
32
32
|
.filter((fieldDiff) => fieldDiff.collection === collection)
|
|
33
33
|
.map((fieldDiff) => fieldDiff.diff[0].rhs)
|
|
34
34
|
.map((fieldDiff) => {
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
if (
|
|
39
|
-
fieldDiff.schema.
|
|
40
|
-
fieldDiff.schema
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return fieldDiff;
|
|
35
|
+
var _a, _b, _c, _d, _e;
|
|
36
|
+
// Casts field type to UUID when applying non-PostgreSQL schema onto PostgreSQL database.
|
|
37
|
+
// This is needed because they snapshots UUID fields as char with length 36.
|
|
38
|
+
if (((_a = fieldDiff.schema) === null || _a === void 0 ? void 0 : _a.data_type) === 'char' &&
|
|
39
|
+
((_b = fieldDiff.schema) === null || _b === void 0 ? void 0 : _b.max_length) === 36 &&
|
|
40
|
+
(((_c = fieldDiff.schema) === null || _c === void 0 ? void 0 : _c.is_primary_key) ||
|
|
41
|
+
(((_d = fieldDiff.schema) === null || _d === void 0 ? void 0 : _d.foreign_key_table) && ((_e = fieldDiff.schema) === null || _e === void 0 ? void 0 : _e.foreign_key_column)))) {
|
|
42
|
+
return (0, lodash_1.merge)(fieldDiff, { type: 'uuid', schema: { data_type: 'uuid', max_length: null } });
|
|
44
43
|
}
|
|
45
|
-
|
|
46
|
-
if (!matchingForeignKeyTable)
|
|
47
|
-
return fieldDiff;
|
|
48
|
-
const matchingForeignKeyField = matchingForeignKeyTable.fields[fieldDiff.schema.foreign_key_column];
|
|
49
|
-
if (!matchingForeignKeyField || matchingForeignKeyField.type !== 'uuid')
|
|
44
|
+
else {
|
|
50
45
|
return fieldDiff;
|
|
51
|
-
|
|
46
|
+
}
|
|
52
47
|
});
|
|
53
48
|
try {
|
|
54
49
|
await collectionsService.createOne({
|
|
@@ -80,8 +75,37 @@ async function applySnapshot(snapshot, options) {
|
|
|
80
75
|
}
|
|
81
76
|
}
|
|
82
77
|
};
|
|
83
|
-
//
|
|
84
|
-
|
|
78
|
+
// Finds all collections that need to be created
|
|
79
|
+
const filterCollectionsForCreation = ({ diff }) => {
|
|
80
|
+
var _a;
|
|
81
|
+
// Check new collections only
|
|
82
|
+
const isNewCollection = diff[0].kind === 'N';
|
|
83
|
+
if (!isNewCollection)
|
|
84
|
+
return false;
|
|
85
|
+
// Create now if no group
|
|
86
|
+
const groupName = (_a = diff[0].rhs.meta) === null || _a === void 0 ? void 0 : _a.group;
|
|
87
|
+
if (!groupName)
|
|
88
|
+
return true;
|
|
89
|
+
// Check if parent collection already exists in schema
|
|
90
|
+
const parentExists = current.collections.find((c) => c.collection === groupName) !== undefined;
|
|
91
|
+
// If this is a new collection and the parent collection doesn't exist in current schema ->
|
|
92
|
+
// Check if the parent collection will be created as part of applying this snapshot ->
|
|
93
|
+
// If yes -> this collection will be created recursively
|
|
94
|
+
// If not -> create now
|
|
95
|
+
// (ex.)
|
|
96
|
+
// TopLevelCollection - I exist in current schema
|
|
97
|
+
// NestedCollection - I exist in snapshotDiff as a new collection
|
|
98
|
+
// TheCurrentCollectionInIteration - I exist in snapshotDiff as a new collection but will be created as part of NestedCollection
|
|
99
|
+
const parentWillBeCreatedInThisApply = snapshotDiff.collections.filter(({ collection, diff }) => diff[0].kind === 'N' && collection === groupName)
|
|
100
|
+
.length > 0;
|
|
101
|
+
// Has group, but parent is not new, parent is also not being created in this snapshot apply
|
|
102
|
+
if (parentExists && !parentWillBeCreatedInThisApply)
|
|
103
|
+
return true;
|
|
104
|
+
return false;
|
|
105
|
+
};
|
|
106
|
+
// Create top level collections (no group, or highest level in existing group) first,
|
|
107
|
+
// then continue with nested collections recursively
|
|
108
|
+
await createCollections(snapshotDiff.collections.filter(filterCollectionsForCreation));
|
|
85
109
|
// delete top level collections (no group) first, then continue with nested collections recursively
|
|
86
110
|
await deleteCollections(snapshotDiff.collections.filter(({ diff }) => { var _a; return diff[0].kind === 'D' && ((_a = diff[0].lhs.meta) === null || _a === void 0 ? void 0 : _a.group) === null; }));
|
|
87
111
|
for (const { collection, diff } of snapshotDiff.collections) {
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.filterItems = void 0;
|
|
7
|
-
const
|
|
4
|
+
const utils_1 = require("@directus/shared/utils");
|
|
8
5
|
/*
|
|
9
6
|
Note: Filtering is normally done through SQL in run-ast. This function can be used in case an already
|
|
10
7
|
existing array of items has to be filtered using the same filter syntax as used in the ast-to-sql flow
|
|
@@ -16,7 +13,7 @@ function filterItems(items, filter) {
|
|
|
16
13
|
return passesFilter(item, filter);
|
|
17
14
|
});
|
|
18
15
|
function passesFilter(item, filter) {
|
|
19
|
-
if (!filter)
|
|
16
|
+
if (!filter || Object.keys(filter).length === 0)
|
|
20
17
|
return true;
|
|
21
18
|
if (Object.keys(filter)[0] === '_and') {
|
|
22
19
|
const subfilter = Object.values(filter)[0];
|
|
@@ -31,7 +28,7 @@ function filterItems(items, filter) {
|
|
|
31
28
|
});
|
|
32
29
|
}
|
|
33
30
|
else {
|
|
34
|
-
const schema = (0,
|
|
31
|
+
const schema = (0, utils_1.generateJoi)(filter);
|
|
35
32
|
const { error } = schema.validate(item);
|
|
36
33
|
return error === undefined;
|
|
37
34
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const lodash_1 = require("lodash");
|
|
7
7
|
const get_relation_type_1 = require("../utils/get-relation-type");
|
|
8
|
+
const constants_1 = require("@directus/shared/constants");
|
|
8
9
|
async function getASTFromQuery(collection, query, schema, options) {
|
|
9
10
|
var _a, _b, _c, _d, _e;
|
|
10
11
|
query = (0, lodash_1.cloneDeep)(query);
|
|
@@ -118,6 +119,23 @@ async function getASTFromQuery(collection, query, schema, options) {
|
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
else {
|
|
122
|
+
if (fieldKey.includes('(') && fieldKey.includes(')')) {
|
|
123
|
+
const columnName = fieldKey.match(constants_1.REGEX_BETWEEN_PARENS)[1];
|
|
124
|
+
const foundField = schema.collections[parentCollection].fields[columnName];
|
|
125
|
+
if (foundField && foundField.type === 'alias') {
|
|
126
|
+
const foundRelation = schema.relations.find((relation) => { var _a; return relation.related_collection === parentCollection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === columnName; });
|
|
127
|
+
if (foundRelation) {
|
|
128
|
+
children.push({
|
|
129
|
+
type: 'functionField',
|
|
130
|
+
name,
|
|
131
|
+
fieldKey,
|
|
132
|
+
query: {},
|
|
133
|
+
relatedCollection: foundRelation.collection,
|
|
134
|
+
});
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
121
139
|
children.push({ type: 'field', name, fieldKey });
|
|
122
140
|
}
|
|
123
141
|
}
|