directus 9.9.0 → 9.11.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/README.md +1 -1
- package/dist/app.js +3 -0
- package/dist/auth/drivers/oauth2.d.ts +1 -1
- package/dist/auth/drivers/oauth2.js +14 -11
- package/dist/auth/drivers/openid.d.ts +1 -1
- package/dist/auth/drivers/openid.js +14 -11
- package/dist/cli/commands/schema/apply.js +4 -3
- package/dist/controllers/assets.js +8 -9
- package/dist/controllers/files.js +3 -0
- package/dist/database/helpers/date/dialects/sqlite.js +6 -2
- package/dist/database/helpers/fn/dialects/postgres.js +5 -1
- package/dist/database/migrations/20210225A-add-relations-sort-field.js +2 -1
- package/dist/database/migrations/20210506A-rename-interfaces.js +2 -1
- package/dist/database/migrations/20210802A-replace-groups.js +2 -1
- package/dist/database/migrations/20210805A-update-groups.js +2 -1
- package/dist/database/migrations/20210805B-change-image-metadata-structure.js +3 -2
- package/dist/database/migrations/20211007A-update-presets.js +5 -4
- package/dist/database/run-ast.js +11 -15
- package/dist/database/system-data/fields/collections.yaml +1 -1
- package/dist/env.js +8 -3
- package/dist/exceptions/index.d.ts +1 -0
- package/dist/exceptions/index.js +1 -0
- package/dist/exceptions/invalid-provider.d.ts +4 -0
- package/dist/exceptions/invalid-provider.js +10 -0
- package/dist/exceptions/range-not-satisfiable.d.ts +2 -2
- package/dist/exceptions/range-not-satisfiable.js +5 -1
- package/dist/middleware/graphql.js +2 -1
- package/dist/services/assets.js +27 -1
- package/dist/services/authentication.js +4 -1
- package/dist/services/collections.js +2 -0
- package/dist/services/fields.js +17 -8
- package/dist/services/graphql.js +75 -34
- package/dist/services/import-export.d.ts +1 -1
- package/dist/services/import-export.js +14 -11
- package/dist/services/items.d.ts +3 -3
- package/dist/services/items.js +20 -6
- package/dist/services/payload.d.ts +3 -3
- package/dist/services/payload.js +11 -8
- package/dist/services/specifications.js +1 -3
- package/dist/services/users.d.ts +4 -0
- package/dist/services/users.js +24 -1
- package/dist/utils/{apply-query.d.ts → apply-query/index.d.ts} +2 -1
- package/dist/utils/apply-query/index.js +394 -0
- package/dist/utils/apply-query/operators/between.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/between.operator.js +16 -0
- package/dist/utils/apply-query/operators/contains.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/contains.operator.js +9 -0
- package/dist/utils/apply-query/operators/ends-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/ends-with.operator.js +9 -0
- package/dist/utils/apply-query/operators/equals.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/equals.operator.js +9 -0
- package/dist/utils/apply-query/operators/greather-than-equals.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/greather-than-equals.operator.js +9 -0
- package/dist/utils/apply-query/operators/greather-than.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/greather-than.operator.js +9 -0
- package/dist/utils/apply-query/operators/in.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/in.operator.js +14 -0
- package/dist/utils/apply-query/operators/index.d.ts +3 -0
- package/dist/utils/apply-query/operators/index.js +72 -0
- package/dist/utils/apply-query/operators/insensitive-contains.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-contains.operator.js +9 -0
- package/dist/utils/apply-query/operators/insensitive-ends-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-ends-with.operator.js +9 -0
- package/dist/utils/apply-query/operators/insensitive-equals.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-equals.operator.js +9 -0
- package/dist/utils/apply-query/operators/insensitive-not-contains.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-not-contains.operator.js +9 -0
- package/dist/utils/apply-query/operators/insensitive-not-ends-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-not-ends-with.operator.js +9 -0
- package/dist/utils/apply-query/operators/insensitive-not-equals.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-not-equals.operator.js +9 -0
- package/dist/utils/apply-query/operators/insensitive-not-starts-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-not-starts-with.operator.js +9 -0
- package/dist/utils/apply-query/operators/insensitive-starts-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/insensitive-starts-with.operator.js +9 -0
- package/dist/utils/apply-query/operators/intersects-bbox.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/intersects-bbox.operator.js +9 -0
- package/dist/utils/apply-query/operators/intersects.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/intersects.operator.js +9 -0
- package/dist/utils/apply-query/operators/is-empty.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/is-empty.operator.js +14 -0
- package/dist/utils/apply-query/operators/is-not-empty.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/is-not-empty.operator.js +14 -0
- package/dist/utils/apply-query/operators/is-not-null.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/is-not-null.operator.js +14 -0
- package/dist/utils/apply-query/operators/is-null.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/is-null.operator.js +14 -0
- package/dist/utils/apply-query/operators/less-than-equals.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/less-than-equals.operator.js +9 -0
- package/dist/utils/apply-query/operators/less-than.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/less-than.operator.js +9 -0
- package/dist/utils/apply-query/operators/not-between.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-between.operator.js +16 -0
- package/dist/utils/apply-query/operators/not-contains.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-contains.operator.js +9 -0
- package/dist/utils/apply-query/operators/not-ends-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-ends-with.operator.js +9 -0
- package/dist/utils/apply-query/operators/not-equals.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-equals.operator.js +9 -0
- package/dist/utils/apply-query/operators/not-in.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-in.operator.js +14 -0
- package/dist/utils/apply-query/operators/not-intersects-bbox.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-intersects-bbox.operator.js +9 -0
- package/dist/utils/apply-query/operators/not-intersects.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-intersects.operator.js +9 -0
- package/dist/utils/apply-query/operators/not-starts-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/not-starts-with.operator.js +9 -0
- package/dist/utils/apply-query/operators/operator-register.d.ts +13 -0
- package/dist/utils/apply-query/operators/operator-register.js +7 -0
- package/dist/utils/apply-query/operators/starts-with.operator.d.ts +2 -0
- package/dist/utils/apply-query/operators/starts-with.operator.js +9 -0
- package/dist/utils/apply-snapshot.d.ts +3 -3
- package/dist/utils/apply-snapshot.js +64 -49
- package/dist/utils/get-ast-from-query.js +10 -4
- package/dist/utils/get-column-path.d.ts +16 -0
- package/dist/utils/get-column-path.js +46 -0
- package/dist/utils/get-default-value.js +4 -3
- package/dist/utils/get-permissions.d.ts +1 -1
- package/dist/utils/get-permissions.js +9 -8
- package/dist/utils/get-relation-info.d.ts +7 -0
- package/dist/utils/get-relation-info.js +45 -0
- package/dist/utils/get-relation-type.d.ts +1 -1
- package/dist/utils/get-schema.js +5 -1
- package/dist/utils/get-snapshot.js +22 -4
- package/dist/utils/merge-permissions-for-share.js +1 -1
- package/dist/utils/parse-json.d.ts +5 -0
- package/dist/utils/parse-json.js +19 -0
- package/dist/utils/reduce-schema.js +18 -11
- package/dist/utils/sanitize-query.d.ts +1 -2
- package/dist/utils/sanitize-query.js +6 -5
- package/package.json +16 -18
- package/dist/utils/apply-query.js +0 -498
|
@@ -7,6 +7,7 @@ exports.parseGraphQL = void 0;
|
|
|
7
7
|
const graphql_1 = require("graphql");
|
|
8
8
|
const exceptions_1 = require("../exceptions");
|
|
9
9
|
const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
10
|
+
const parse_json_1 = require("../utils/parse-json");
|
|
10
11
|
exports.parseGraphQL = (0, async_handler_1.default)(async (req, res, next) => {
|
|
11
12
|
if (req.method !== 'GET' && req.method !== 'POST') {
|
|
12
13
|
throw new exceptions_1.MethodNotAllowedException('GraphQL only supports GET and POST requests.', { allow: ['GET', 'POST'] });
|
|
@@ -19,7 +20,7 @@ exports.parseGraphQL = (0, async_handler_1.default)(async (req, res, next) => {
|
|
|
19
20
|
query = req.query.query || null;
|
|
20
21
|
if (req.query.variables) {
|
|
21
22
|
try {
|
|
22
|
-
variables =
|
|
23
|
+
variables = (0, parse_json_1.parseJSON)(req.query.variables);
|
|
23
24
|
}
|
|
24
25
|
catch {
|
|
25
26
|
throw new exceptions_1.InvalidQueryException(`Variables are invalid JSON.`);
|
package/dist/services/assets.js
CHANGED
|
@@ -70,9 +70,35 @@ class AssetsService {
|
|
|
70
70
|
if (!exists)
|
|
71
71
|
throw new exceptions_1.ForbiddenException();
|
|
72
72
|
if (range) {
|
|
73
|
-
|
|
73
|
+
const missingRangeLimits = range.start === undefined && range.end === undefined;
|
|
74
|
+
const endBeforeStart = range.start !== undefined && range.end !== undefined && range.end <= range.start;
|
|
75
|
+
const startOverflow = range.start !== undefined && range.start >= file.filesize;
|
|
76
|
+
const endUnderflow = range.end !== undefined && range.end <= 0;
|
|
77
|
+
if (missingRangeLimits || endBeforeStart || startOverflow || endUnderflow) {
|
|
74
78
|
throw new exceptions_1.RangeNotSatisfiableException(range);
|
|
75
79
|
}
|
|
80
|
+
const lastByte = file.filesize - 1;
|
|
81
|
+
if (range.end) {
|
|
82
|
+
if (range.start === undefined) {
|
|
83
|
+
// fetch chunk from tail
|
|
84
|
+
range.start = file.filesize - range.end;
|
|
85
|
+
range.end = lastByte;
|
|
86
|
+
}
|
|
87
|
+
if (range.end >= file.filesize) {
|
|
88
|
+
// fetch entire file
|
|
89
|
+
range.end = lastByte;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (range.start) {
|
|
93
|
+
if (range.end === undefined) {
|
|
94
|
+
// fetch entire file
|
|
95
|
+
range.end = lastByte;
|
|
96
|
+
}
|
|
97
|
+
if (range.start < 0) {
|
|
98
|
+
// fetch file from head
|
|
99
|
+
range.start = 0;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
76
102
|
}
|
|
77
103
|
const type = file.type;
|
|
78
104
|
const transforms = TransformationUtils.resolvePreset(transformation, file);
|
|
@@ -45,7 +45,6 @@ class AuthenticationService {
|
|
|
45
45
|
.from('directus_users as u')
|
|
46
46
|
.leftJoin('directus_roles as r', 'u.role', 'r.id')
|
|
47
47
|
.where('u.id', await provider.getUserID((0, lodash_1.cloneDeep)(payload)))
|
|
48
|
-
.andWhere('u.provider', providerName)
|
|
49
48
|
.first();
|
|
50
49
|
const updatedPayload = await emitter_1.default.emitFilter('auth.login', payload, {
|
|
51
50
|
status: 'pending',
|
|
@@ -79,6 +78,10 @@ class AuthenticationService {
|
|
|
79
78
|
throw new exceptions_1.InvalidCredentialsException();
|
|
80
79
|
}
|
|
81
80
|
}
|
|
81
|
+
else if (user.provider !== providerName) {
|
|
82
|
+
await (0, stall_1.stall)(STALL_TIME, timeStart);
|
|
83
|
+
throw new exceptions_1.InvalidProviderException();
|
|
84
|
+
}
|
|
82
85
|
const settingsService = new settings_1.SettingsService({
|
|
83
86
|
knex: this.knex,
|
|
84
87
|
schema: this.schema,
|
package/dist/services/fields.js
CHANGED
|
@@ -195,6 +195,8 @@ class FieldsService {
|
|
|
195
195
|
catch {
|
|
196
196
|
// Do nothing
|
|
197
197
|
}
|
|
198
|
+
if (!column && !fieldInfo)
|
|
199
|
+
throw new exceptions_1.ForbiddenException();
|
|
198
200
|
const type = (0, get_local_type_1.default)(column, fieldInfo);
|
|
199
201
|
const data = {
|
|
200
202
|
collection,
|
|
@@ -349,13 +351,6 @@ class FieldsService {
|
|
|
349
351
|
});
|
|
350
352
|
await this.knex.transaction(async (trx) => {
|
|
351
353
|
var _a, _b;
|
|
352
|
-
if (this.schema.collections[collection] &&
|
|
353
|
-
field in this.schema.collections[collection].fields &&
|
|
354
|
-
this.schema.collections[collection].fields[field].alias === false) {
|
|
355
|
-
await trx.schema.table(collection, (table) => {
|
|
356
|
-
table.dropColumn(field);
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
354
|
const relations = this.schema.relations.filter((relation) => {
|
|
360
355
|
var _a;
|
|
361
356
|
return ((relation.collection === collection && relation.field === field) ||
|
|
@@ -387,6 +382,14 @@ class FieldsService {
|
|
|
387
382
|
.where({ many_collection: relation.collection, many_field: relation.field });
|
|
388
383
|
}
|
|
389
384
|
}
|
|
385
|
+
// Delete field only after foreign key constraints are removed
|
|
386
|
+
if (this.schema.collections[collection] &&
|
|
387
|
+
field in this.schema.collections[collection].fields &&
|
|
388
|
+
this.schema.collections[collection].fields[field].alias === false) {
|
|
389
|
+
await trx.schema.table(collection, (table) => {
|
|
390
|
+
table.dropColumn(field);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
390
393
|
const collectionMeta = await trx
|
|
391
394
|
.select('archive_field', 'sort_field')
|
|
392
395
|
.from('directus_collections')
|
|
@@ -439,7 +442,13 @@ class FieldsService {
|
|
|
439
442
|
if (field.type === 'alias' || field.type === 'unknown')
|
|
440
443
|
return;
|
|
441
444
|
if ((_a = field.schema) === null || _a === void 0 ? void 0 : _a.has_auto_increment) {
|
|
442
|
-
|
|
445
|
+
if (field.type === 'bigInteger') {
|
|
446
|
+
// Create an auto-incremented big integer (MySQL, PostgreSQL) or an auto-incremented integer (other DBs)
|
|
447
|
+
column = table.bigIncrements(field.field);
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
column = table.increments(field.field);
|
|
451
|
+
}
|
|
443
452
|
}
|
|
444
453
|
else if (field.type === 'string') {
|
|
445
454
|
column = table.string(field.field, (_c = (_b = field.schema) === null || _b === void 0 ? void 0 : _b.max_length) !== null && _c !== void 0 ? _c : undefined);
|
package/dist/services/graphql.js
CHANGED
|
@@ -89,7 +89,16 @@ class GraphQLService {
|
|
|
89
89
|
async execute({ document, variables, operationName, contextValue, }) {
|
|
90
90
|
var _a;
|
|
91
91
|
const schema = this.getSchema();
|
|
92
|
-
const validationErrors = (0, graphql_1.validate)(schema, document,
|
|
92
|
+
const validationErrors = (0, graphql_1.validate)(schema, document, [
|
|
93
|
+
...graphql_1.specifiedRules,
|
|
94
|
+
(context) => ({
|
|
95
|
+
Field(node) {
|
|
96
|
+
if (env_1.default.GRAPHQL_INTROSPECTION === false && (node.name.value === '__schema' || node.name.value === '__type')) {
|
|
97
|
+
context.reportError(new graphql_1.GraphQLError('GraphQL introspection is not allowed. The query contained __schema or __type.', [node]));
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
]);
|
|
93
102
|
if (validationErrors.length > 0) {
|
|
94
103
|
throw new exceptions_1.GraphQLValidationException({ graphqlErrors: validationErrors });
|
|
95
104
|
}
|
|
@@ -161,16 +170,7 @@ class GraphQLService {
|
|
|
161
170
|
acc[collectionName] = ReadCollectionTypes[collection.collection].getResolver(collection.collection);
|
|
162
171
|
if (this.schema.collections[collection.collection].singleton === false) {
|
|
163
172
|
acc[`${collectionName}_by_id`] = ReadCollectionTypes[collection.collection].getResolver(`${collection.collection}_by_id`);
|
|
164
|
-
|
|
165
|
-
const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type);
|
|
166
|
-
if (graphqlType === graphql_1.GraphQLInt || graphqlType === graphql_1.GraphQLFloat) {
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
return false;
|
|
170
|
-
});
|
|
171
|
-
if (hasAggregate) {
|
|
172
|
-
acc[`${collectionName}_aggregated`] = ReadCollectionTypes[collection.collection].getResolver(`${collection.collection}_aggregated`);
|
|
173
|
-
}
|
|
173
|
+
acc[`${collectionName}_aggregated`] = ReadCollectionTypes[collection.collection].getResolver(`${collection.collection}_aggregated`);
|
|
174
174
|
}
|
|
175
175
|
return acc;
|
|
176
176
|
}, {}));
|
|
@@ -415,7 +415,8 @@ class GraphQLService {
|
|
|
415
415
|
const { CollectionTypes: ReadCollectionTypes } = getTypes('read');
|
|
416
416
|
const ReadableCollectionFilterTypes = {};
|
|
417
417
|
const AggregatedFunctions = {};
|
|
418
|
-
const
|
|
418
|
+
const AggregatedFields = {};
|
|
419
|
+
const AggregateMethods = {};
|
|
419
420
|
const StringFilterOperators = schemaComposer.createInputTC({
|
|
420
421
|
name: 'string_filter_operators',
|
|
421
422
|
fields: {
|
|
@@ -671,7 +672,7 @@ class GraphQLService {
|
|
|
671
672
|
_and: [ReadableCollectionFilterTypes[collection.collection]],
|
|
672
673
|
_or: [ReadableCollectionFilterTypes[collection.collection]],
|
|
673
674
|
});
|
|
674
|
-
|
|
675
|
+
AggregatedFields[collection.collection] = schemaComposer.createObjectTC({
|
|
675
676
|
name: `${collection.collection}_aggregated_fields`,
|
|
676
677
|
fields: Object.values(collection.fields).reduce((acc, field) => {
|
|
677
678
|
const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type);
|
|
@@ -689,46 +690,71 @@ class GraphQLService {
|
|
|
689
690
|
return acc;
|
|
690
691
|
}, {}),
|
|
691
692
|
});
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
693
|
+
AggregateMethods[collection.collection] = {
|
|
694
|
+
group: {
|
|
695
|
+
name: 'group',
|
|
696
|
+
type: graphql_compose_1.GraphQLJSON,
|
|
697
|
+
},
|
|
698
|
+
countAll: {
|
|
699
|
+
name: 'countAll',
|
|
700
|
+
type: graphql_1.GraphQLInt,
|
|
701
|
+
},
|
|
702
|
+
count: {
|
|
703
|
+
name: 'count',
|
|
704
|
+
type: schemaComposer.createObjectTC({
|
|
705
|
+
name: `${collection.collection}_aggregated_count`,
|
|
706
|
+
fields: Object.values(collection.fields).reduce((acc, field) => {
|
|
707
|
+
acc[field.field] = {
|
|
708
|
+
type: graphql_1.GraphQLInt,
|
|
709
|
+
description: field.note,
|
|
710
|
+
};
|
|
711
|
+
return acc;
|
|
712
|
+
}, {}),
|
|
713
|
+
}),
|
|
714
|
+
},
|
|
715
|
+
};
|
|
716
|
+
const hasNumericAggregates = Object.values(collection.fields).some((field) => {
|
|
717
|
+
const graphqlType = (0, get_graphql_type_1.getGraphQLType)(field.type);
|
|
718
|
+
if (graphqlType === graphql_1.GraphQLInt || graphqlType === graphql_1.GraphQLFloat) {
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
return false;
|
|
722
|
+
});
|
|
723
|
+
if (hasNumericAggregates) {
|
|
724
|
+
Object.assign(AggregateMethods[collection.collection], {
|
|
699
725
|
avg: {
|
|
700
726
|
name: 'avg',
|
|
701
|
-
type:
|
|
727
|
+
type: AggregatedFields[collection.collection],
|
|
702
728
|
},
|
|
703
729
|
sum: {
|
|
704
730
|
name: 'sum',
|
|
705
|
-
type:
|
|
706
|
-
},
|
|
707
|
-
count: {
|
|
708
|
-
name: 'count',
|
|
709
|
-
type: AggregatedFilters[collection.collection],
|
|
731
|
+
type: AggregatedFields[collection.collection],
|
|
710
732
|
},
|
|
711
733
|
countDistinct: {
|
|
712
734
|
name: 'countDistinct',
|
|
713
|
-
type:
|
|
735
|
+
type: AggregatedFields[collection.collection],
|
|
714
736
|
},
|
|
715
737
|
avgDistinct: {
|
|
716
738
|
name: 'avgDistinct',
|
|
717
|
-
type:
|
|
739
|
+
type: AggregatedFields[collection.collection],
|
|
718
740
|
},
|
|
719
741
|
sumDistinct: {
|
|
720
742
|
name: 'sumDistinct',
|
|
721
|
-
type:
|
|
743
|
+
type: AggregatedFields[collection.collection],
|
|
722
744
|
},
|
|
723
745
|
min: {
|
|
724
746
|
name: 'min',
|
|
725
|
-
type:
|
|
747
|
+
type: AggregatedFields[collection.collection],
|
|
726
748
|
},
|
|
727
749
|
max: {
|
|
728
750
|
name: 'max',
|
|
729
|
-
type:
|
|
751
|
+
type: AggregatedFields[collection.collection],
|
|
730
752
|
},
|
|
731
|
-
}
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
AggregatedFunctions[collection.collection] = schemaComposer.createObjectTC({
|
|
756
|
+
name: `${collection.collection}_aggregated`,
|
|
757
|
+
fields: AggregateMethods[collection.collection],
|
|
732
758
|
});
|
|
733
759
|
ReadCollectionTypes[collection.collection].addResolver({
|
|
734
760
|
name: collection.collection,
|
|
@@ -1175,6 +1201,7 @@ class GraphQLService {
|
|
|
1175
1201
|
continue;
|
|
1176
1202
|
selection = selection;
|
|
1177
1203
|
let current;
|
|
1204
|
+
let currentAlias = null;
|
|
1178
1205
|
// Union type (Many-to-Any)
|
|
1179
1206
|
if (selection.kind === 'InlineFragment') {
|
|
1180
1207
|
if (selection.typeCondition.name.value.startsWith('__'))
|
|
@@ -1187,8 +1214,20 @@ class GraphQLService {
|
|
|
1187
1214
|
if (selection.name.value.startsWith('__'))
|
|
1188
1215
|
continue;
|
|
1189
1216
|
current = selection.name.value;
|
|
1217
|
+
if (selection.alias) {
|
|
1218
|
+
currentAlias = selection.alias.value;
|
|
1219
|
+
}
|
|
1190
1220
|
if (parent) {
|
|
1191
1221
|
current = `${parent}.${current}`;
|
|
1222
|
+
if (currentAlias) {
|
|
1223
|
+
currentAlias = `${parent}.${currentAlias}`;
|
|
1224
|
+
// add nested aliases into deep query
|
|
1225
|
+
if (selection.selectionSet) {
|
|
1226
|
+
if (!query.deep)
|
|
1227
|
+
query.deep = {};
|
|
1228
|
+
(0, lodash_1.set)(query.deep, parent, (0, lodash_1.merge)((0, lodash_1.get)(query.deep, parent), { _alias: { [selection.alias.value]: selection.name.value } }));
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1192
1231
|
}
|
|
1193
1232
|
}
|
|
1194
1233
|
if (selection.selectionSet) {
|
|
@@ -1203,7 +1242,7 @@ class GraphQLService {
|
|
|
1203
1242
|
}
|
|
1204
1243
|
}
|
|
1205
1244
|
else {
|
|
1206
|
-
children = parseFields(selection.selectionSet.selections, current);
|
|
1245
|
+
children = parseFields(selection.selectionSet.selections, currentAlias !== null && currentAlias !== void 0 ? currentAlias : current);
|
|
1207
1246
|
}
|
|
1208
1247
|
fields.push(...children);
|
|
1209
1248
|
}
|
|
@@ -1215,7 +1254,7 @@ class GraphQLService {
|
|
|
1215
1254
|
if (!query.deep)
|
|
1216
1255
|
query.deep = {};
|
|
1217
1256
|
const args = this.parseArgs(selection.arguments, variableValues);
|
|
1218
|
-
(0, lodash_1.set)(query.deep, current, (0, lodash_1.merge)((0, lodash_1.get)(query.deep, current), (0, lodash_1.mapKeys)((0, sanitize_query_1.sanitizeQuery)(args, this.accountability), (value, key) => `_${key}`)));
|
|
1257
|
+
(0, lodash_1.set)(query.deep, currentAlias !== null && currentAlias !== void 0 ? currentAlias : current, (0, lodash_1.merge)((0, lodash_1.get)(query.deep, currentAlias !== null && currentAlias !== void 0 ? currentAlias : current), (0, lodash_1.mapKeys)((0, sanitize_query_1.sanitizeQuery)(args, this.accountability), (value, key) => `_${key}`)));
|
|
1219
1258
|
}
|
|
1220
1259
|
}
|
|
1221
1260
|
}
|
|
@@ -1274,8 +1313,10 @@ class GraphQLService {
|
|
|
1274
1313
|
*/
|
|
1275
1314
|
formatError(error) {
|
|
1276
1315
|
if (Array.isArray(error)) {
|
|
1316
|
+
error[0].extensions.code = error[0].code;
|
|
1277
1317
|
return new graphql_1.GraphQLError(error[0].message, undefined, undefined, undefined, undefined, error[0]);
|
|
1278
1318
|
}
|
|
1319
|
+
error.extensions.code = error.code;
|
|
1279
1320
|
return new graphql_1.GraphQLError(error.message, undefined, undefined, undefined, undefined, error);
|
|
1280
1321
|
}
|
|
1281
1322
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
+
import { Accountability, Query, SchemaOverview } from '@directus/shared/types';
|
|
2
3
|
import { Knex } from 'knex';
|
|
3
4
|
import { AbstractServiceOptions, File } from '../types';
|
|
4
|
-
import { Accountability, Query, SchemaOverview } from '@directus/shared/types';
|
|
5
5
|
export declare class ImportService {
|
|
6
6
|
knex: Knex;
|
|
7
7
|
accountability: Accountability | null;
|
|
@@ -4,24 +4,26 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ExportService = exports.ImportService = void 0;
|
|
7
|
-
const
|
|
8
|
-
const exceptions_1 = require("../exceptions");
|
|
9
|
-
const StreamArray_1 = __importDefault(require("stream-json/streamers/StreamArray"));
|
|
10
|
-
const items_1 = require("./items");
|
|
7
|
+
const utils_1 = require("@directus/shared/utils");
|
|
11
8
|
const async_1 = require("async");
|
|
12
|
-
const destroy_1 = __importDefault(require("destroy"));
|
|
13
9
|
const csv_parser_1 = __importDefault(require("csv-parser"));
|
|
14
|
-
const
|
|
10
|
+
const destroy_1 = __importDefault(require("destroy"));
|
|
11
|
+
const fs_extra_1 = require("fs-extra");
|
|
15
12
|
const js2xmlparser_1 = require("js2xmlparser");
|
|
16
13
|
const json2csv_1 = require("json2csv");
|
|
17
|
-
const
|
|
14
|
+
const lodash_1 = require("lodash");
|
|
15
|
+
const StreamArray_1 = __importDefault(require("stream-json/streamers/StreamArray"));
|
|
16
|
+
const strip_bom_stream_1 = __importDefault(require("strip-bom-stream"));
|
|
18
17
|
const tmp_promise_1 = require("tmp-promise");
|
|
18
|
+
const database_1 = __importDefault(require("../database"));
|
|
19
19
|
const env_1 = __importDefault(require("../env"));
|
|
20
|
-
const
|
|
20
|
+
const exceptions_1 = require("../exceptions");
|
|
21
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
21
22
|
const get_date_formatted_1 = require("../utils/get-date-formatted");
|
|
22
|
-
const
|
|
23
|
+
const parse_json_1 = require("../utils/parse-json");
|
|
24
|
+
const files_1 = require("./files");
|
|
25
|
+
const items_1 = require("./items");
|
|
23
26
|
const notifications_1 = require("./notifications");
|
|
24
|
-
const logger_1 = __importDefault(require("../logger"));
|
|
25
27
|
class ImportService {
|
|
26
28
|
constructor(options) {
|
|
27
29
|
this.knex = options.knex || (0, database_1.default)();
|
|
@@ -91,6 +93,7 @@ class ImportService {
|
|
|
91
93
|
});
|
|
92
94
|
return new Promise((resolve, reject) => {
|
|
93
95
|
stream
|
|
96
|
+
.pipe((0, strip_bom_stream_1.default)())
|
|
94
97
|
.pipe((0, csv_parser_1.default)())
|
|
95
98
|
.on('data', (value) => {
|
|
96
99
|
const obj = (0, lodash_1.transform)(value, (result, value, key) => {
|
|
@@ -99,7 +102,7 @@ class ImportService {
|
|
|
99
102
|
}
|
|
100
103
|
else {
|
|
101
104
|
try {
|
|
102
|
-
const parsedJson =
|
|
105
|
+
const parsedJson = (0, parse_json_1.parseJSON)(value);
|
|
103
106
|
(0, lodash_1.set)(result, key, parsedJson);
|
|
104
107
|
}
|
|
105
108
|
catch {
|
package/dist/services/items.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Accountability, PermissionsAction, Query, SchemaOverview } from '@directus/shared/types';
|
|
2
2
|
import Keyv from 'keyv';
|
|
3
|
-
import {
|
|
4
|
-
import { AbstractService, AbstractServiceOptions, Item as AnyItem,
|
|
3
|
+
import { Knex } from 'knex';
|
|
4
|
+
import { AbstractService, AbstractServiceOptions, Item as AnyItem, MutationOptions, PrimaryKey } from '../types';
|
|
5
5
|
export declare type QueryOptions = {
|
|
6
6
|
stripNonRequested?: boolean;
|
|
7
7
|
permissionsAction?: PermissionsAction;
|
package/dist/services/items.js
CHANGED
|
@@ -15,8 +15,8 @@ const translate_1 = require("../exceptions/database/translate");
|
|
|
15
15
|
const types_1 = require("../types");
|
|
16
16
|
const get_ast_from_query_1 = __importDefault(require("../utils/get-ast-from-query"));
|
|
17
17
|
const authorization_1 = require("./authorization");
|
|
18
|
-
const payload_1 = require("./payload");
|
|
19
18
|
const index_1 = require("./index");
|
|
19
|
+
const payload_1 = require("./payload");
|
|
20
20
|
class ItemsService {
|
|
21
21
|
constructor(collection, options) {
|
|
22
22
|
this.collection = collection;
|
|
@@ -90,8 +90,13 @@ class ItemsService {
|
|
|
90
90
|
// In case of manual string / UUID primary keys, the PK already exists in the object we're saving.
|
|
91
91
|
let primaryKey = payloadWithTypeCasting[primaryKeyField];
|
|
92
92
|
try {
|
|
93
|
-
const result = await trx
|
|
94
|
-
|
|
93
|
+
const result = await trx
|
|
94
|
+
.insert(payloadWithoutAliases)
|
|
95
|
+
.into(this.collection)
|
|
96
|
+
.returning(primaryKeyField)
|
|
97
|
+
.then((result) => result[0]);
|
|
98
|
+
const returnedKey = typeof result === 'object' ? result[primaryKeyField] : result;
|
|
99
|
+
primaryKey = primaryKey !== null && primaryKey !== void 0 ? primaryKey : returnedKey;
|
|
95
100
|
}
|
|
96
101
|
catch (err) {
|
|
97
102
|
throw await (0, translate_1.translateDatabaseError)(err);
|
|
@@ -255,6 +260,10 @@ class ItemsService {
|
|
|
255
260
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
256
261
|
const filterWithKey = { _and: [{ [primaryKeyField]: { _in: keys } }, (_a = query.filter) !== null && _a !== void 0 ? _a : {}] };
|
|
257
262
|
const queryWithKey = (0, lodash_1.assign)({}, query, { filter: filterWithKey });
|
|
263
|
+
// Set query limit as the number of keys
|
|
264
|
+
if (Array.isArray(keys) && keys.length > 0 && !queryWithKey.limit) {
|
|
265
|
+
queryWithKey.limit = keys.length;
|
|
266
|
+
}
|
|
258
267
|
const results = await this.readByQuery(queryWithKey, opts);
|
|
259
268
|
return results;
|
|
260
269
|
}
|
|
@@ -301,6 +310,8 @@ class ItemsService {
|
|
|
301
310
|
accountability: this.accountability,
|
|
302
311
|
})
|
|
303
312
|
: payload;
|
|
313
|
+
// Sort keys to ensure that the order is maintained
|
|
314
|
+
keys.sort();
|
|
304
315
|
if (this.accountability) {
|
|
305
316
|
await authorizationService.checkAccess('update', this.collection, keys);
|
|
306
317
|
}
|
|
@@ -354,13 +365,14 @@ class ItemsService {
|
|
|
354
365
|
knex: trx,
|
|
355
366
|
schema: this.schema,
|
|
356
367
|
});
|
|
357
|
-
const
|
|
368
|
+
const revisions = (await Promise.all(activity.map(async (activity, index) => ({
|
|
358
369
|
activity: activity,
|
|
359
370
|
collection: this.collection,
|
|
360
371
|
item: keys[index],
|
|
361
372
|
data: snapshots && Array.isArray(snapshots) ? JSON.stringify(snapshots[index]) : JSON.stringify(snapshots),
|
|
362
373
|
delta: await payloadService.prepareDelta(payloadWithTypeCasting),
|
|
363
|
-
}))));
|
|
374
|
+
})))).filter((revision) => revision.delta);
|
|
375
|
+
const revisionIDs = await revisionsService.createMany(revisions);
|
|
364
376
|
for (let i = 0; i < revisionIDs.length; i++) {
|
|
365
377
|
const revisionID = revisionIDs[i];
|
|
366
378
|
if (opts === null || opts === void 0 ? void 0 : opts.onRevisionCreate) {
|
|
@@ -461,6 +473,7 @@ class ItemsService {
|
|
|
461
473
|
const authorizationService = new authorization_1.AuthorizationService({
|
|
462
474
|
accountability: this.accountability,
|
|
463
475
|
schema: this.schema,
|
|
476
|
+
knex: this.knex,
|
|
464
477
|
});
|
|
465
478
|
await authorizationService.checkAccess('delete', this.collection, keys);
|
|
466
479
|
}
|
|
@@ -529,7 +542,8 @@ class ItemsService {
|
|
|
529
542
|
defaults[name] = null;
|
|
530
543
|
continue;
|
|
531
544
|
}
|
|
532
|
-
|
|
545
|
+
if (field.defaultValue)
|
|
546
|
+
defaults[name] = field.defaultValue;
|
|
533
547
|
}
|
|
534
548
|
return defaults;
|
|
535
549
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Knex } from 'knex';
|
|
2
|
-
import { AbstractServiceOptions, Item, PrimaryKey } from '../types';
|
|
3
1
|
import { Accountability, SchemaOverview } from '@directus/shared/types';
|
|
2
|
+
import { Knex } from 'knex';
|
|
4
3
|
import { Helpers } from '../database/helpers';
|
|
4
|
+
import { AbstractServiceOptions, Item, PrimaryKey } from '../types';
|
|
5
5
|
declare type Action = 'create' | 'read' | 'update';
|
|
6
6
|
declare type Transformers = {
|
|
7
7
|
[type: string]: (context: {
|
|
@@ -65,6 +65,6 @@ export declare class PayloadService {
|
|
|
65
65
|
* Transforms the input partial payload to match the output structure, to have consistency
|
|
66
66
|
* between delta and data
|
|
67
67
|
*/
|
|
68
|
-
prepareDelta(data: Partial<Item>): Promise<string>;
|
|
68
|
+
prepareDelta(data: Partial<Item>): Promise<string | null>;
|
|
69
69
|
}
|
|
70
70
|
export {};
|
package/dist/services/payload.js
CHANGED
|
@@ -4,18 +4,19 @@ 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");
|
|
7
8
|
const date_fns_1 = require("date-fns");
|
|
9
|
+
const flat_1 = require("flat");
|
|
8
10
|
const joi_1 = __importDefault(require("joi"));
|
|
9
11
|
const lodash_1 = require("lodash");
|
|
10
12
|
const uuid_1 = require("uuid");
|
|
13
|
+
const wellknown_1 = require("wellknown");
|
|
11
14
|
const database_1 = __importDefault(require("../database"));
|
|
12
|
-
const exceptions_1 = require("../exceptions");
|
|
13
|
-
const utils_1 = require("@directus/shared/utils");
|
|
14
|
-
const items_1 = require("./items");
|
|
15
|
-
const flat_1 = require("flat");
|
|
16
15
|
const helpers_1 = require("../database/helpers");
|
|
17
|
-
const
|
|
16
|
+
const exceptions_1 = require("../exceptions");
|
|
18
17
|
const generate_hash_1 = require("../utils/generate-hash");
|
|
18
|
+
const parse_json_1 = require("../utils/parse-json");
|
|
19
|
+
const items_1 = require("./items");
|
|
19
20
|
/**
|
|
20
21
|
* Process a given payload for a collection to ensure the special fields (hash, uuid, date etc) are
|
|
21
22
|
* handled correctly.
|
|
@@ -55,7 +56,7 @@ class PayloadService {
|
|
|
55
56
|
if (action === 'read') {
|
|
56
57
|
if (typeof value === 'string') {
|
|
57
58
|
try {
|
|
58
|
-
return
|
|
59
|
+
return (0, parse_json_1.parseJSON)(value);
|
|
59
60
|
}
|
|
60
61
|
catch {
|
|
61
62
|
return value;
|
|
@@ -196,7 +197,7 @@ class PayloadService {
|
|
|
196
197
|
processGeometries(payloads, action) {
|
|
197
198
|
const process = action == 'read'
|
|
198
199
|
? (value) => (typeof value === 'string' ? (0, wellknown_1.parse)(value) : value)
|
|
199
|
-
: (value) => this.helpers.st.fromGeoJSON(typeof value == 'string' ?
|
|
200
|
+
: (value) => this.helpers.st.fromGeoJSON(typeof value == 'string' ? (0, parse_json_1.parseJSON)(value) : value);
|
|
200
201
|
const fieldsInCollection = Object.entries(this.schema.collections[this.collection].fields);
|
|
201
202
|
const geometryColumns = fieldsInCollection.filter(([_, field]) => field.type.startsWith('geometry'));
|
|
202
203
|
for (const [name] of geometryColumns) {
|
|
@@ -318,7 +319,7 @@ class PayloadService {
|
|
|
318
319
|
}
|
|
319
320
|
const allowedCollections = relation.meta.one_allowed_collections;
|
|
320
321
|
if (allowedCollections.includes(relatedCollection) === false) {
|
|
321
|
-
throw new exceptions_1.InvalidPayloadException(`"${relation.collection}.${relation.field}" can't be linked to collection "${relatedCollection}`);
|
|
322
|
+
throw new exceptions_1.InvalidPayloadException(`"${relation.collection}.${relation.field}" can't be linked to collection "${relatedCollection}"`);
|
|
322
323
|
}
|
|
323
324
|
const itemsService = new items_1.ItemsService(relatedCollection, {
|
|
324
325
|
accountability: this.accountability,
|
|
@@ -578,6 +579,8 @@ class PayloadService {
|
|
|
578
579
|
}
|
|
579
580
|
}
|
|
580
581
|
payload = await this.processValues('read', payload);
|
|
582
|
+
if (Object.keys(payload).length === 0)
|
|
583
|
+
return null;
|
|
581
584
|
return JSON.stringify(payload);
|
|
582
585
|
}
|
|
583
586
|
}
|
package/dist/services/users.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export declare class UsersService extends ItemsService {
|
|
|
18
18
|
*/
|
|
19
19
|
private checkPasswordPolicy;
|
|
20
20
|
private checkRemainingAdminExistence;
|
|
21
|
+
/**
|
|
22
|
+
* Make sure there's at least one active admin user when updating user status
|
|
23
|
+
*/
|
|
24
|
+
private checkRemainingActiveAdmin;
|
|
21
25
|
/**
|
|
22
26
|
* Create a new user
|
|
23
27
|
*/
|
package/dist/services/users.js
CHANGED
|
@@ -101,6 +101,23 @@ class UsersService extends items_1.ItemsService {
|
|
|
101
101
|
throw new exceptions_2.UnprocessableEntityException(`You can't remove the last admin user from the role.`);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Make sure there's at least one active admin user when updating user status
|
|
106
|
+
*/
|
|
107
|
+
async checkRemainingActiveAdmin(excludeKeys) {
|
|
108
|
+
const otherAdminUsers = await this.knex
|
|
109
|
+
.count('*', { as: 'count' })
|
|
110
|
+
.from('directus_users')
|
|
111
|
+
.whereNotIn('directus_users.id', excludeKeys)
|
|
112
|
+
.andWhere({ 'directus_roles.admin_access': true })
|
|
113
|
+
.andWhere({ 'directus_users.status': 'active' })
|
|
114
|
+
.leftJoin('directus_roles', 'directus_users.role', 'directus_roles.id')
|
|
115
|
+
.first();
|
|
116
|
+
const otherAdminUsersCount = +((otherAdminUsers === null || otherAdminUsers === void 0 ? void 0 : otherAdminUsers.count) || 0);
|
|
117
|
+
if (otherAdminUsersCount === 0) {
|
|
118
|
+
throw new exceptions_2.UnprocessableEntityException(`You can't change the active status of the last admin user.`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
104
121
|
/**
|
|
105
122
|
* Create a new user
|
|
106
123
|
*/
|
|
@@ -140,12 +157,18 @@ class UsersService extends items_1.ItemsService {
|
|
|
140
157
|
* Update many users by primary key
|
|
141
158
|
*/
|
|
142
159
|
async updateMany(keys, data, opts) {
|
|
160
|
+
var _a, _b;
|
|
143
161
|
if (data.role) {
|
|
144
|
-
|
|
162
|
+
// data.role will be an object with id with GraphQL mutations
|
|
163
|
+
const roleId = (_b = (_a = data.role) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : data.role;
|
|
164
|
+
const newRole = await this.knex.select('admin_access').from('directus_roles').where('id', roleId).first();
|
|
145
165
|
if (!(newRole === null || newRole === void 0 ? void 0 : newRole.admin_access)) {
|
|
146
166
|
await this.checkRemainingAdminExistence(keys);
|
|
147
167
|
}
|
|
148
168
|
}
|
|
169
|
+
if (data.status !== undefined && data.status !== 'active') {
|
|
170
|
+
await this.checkRemainingActiveAdmin(keys);
|
|
171
|
+
}
|
|
149
172
|
if (data.email) {
|
|
150
173
|
if (keys.length > 1) {
|
|
151
174
|
throw new record_not_unique_1.RecordNotUniqueException('email', {
|