directus 9.8.0 → 9.9.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/__mocks__/cache.d.ts +5 -0
- package/dist/__mocks__/cache.js +7 -0
- package/dist/auth/drivers/ldap.js +10 -11
- package/dist/auth/drivers/oauth2.js +9 -4
- package/dist/auth/drivers/openid.js +7 -4
- package/dist/cli/commands/schema/apply.js +9 -3
- package/dist/controllers/assets.js +5 -0
- package/dist/controllers/files.d.ts +2 -0
- package/dist/controllers/files.js +10 -5
- package/dist/database/helpers/date/dialects/mssql.d.ts +4 -0
- package/dist/database/helpers/date/dialects/mssql.js +12 -0
- package/dist/database/helpers/date/dialects/mysql.d.ts +5 -0
- package/dist/database/helpers/date/dialects/mysql.js +16 -0
- package/dist/database/helpers/date/dialects/oracle.d.ts +4 -0
- package/dist/database/helpers/date/dialects/oracle.js +15 -0
- package/dist/database/helpers/date/dialects/sqlite.d.ts +1 -0
- package/dist/database/helpers/date/dialects/sqlite.js +8 -0
- package/dist/database/helpers/date/index.d.ts +3 -3
- package/dist/database/helpers/date/index.js +6 -6
- package/dist/database/helpers/date/types.d.ts +3 -0
- package/dist/database/helpers/date/types.js +10 -0
- package/dist/database/helpers/index.d.ts +1 -1
- package/dist/database/migrations/20220402A-remove-default-value-panel-icon.d.ts +3 -0
- package/dist/database/migrations/20220402A-remove-default-value-panel-icon.js +22 -0
- package/dist/database/system-data/fields/settings.yaml +0 -1
- package/dist/database/system-data/fields/users.yaml +3 -0
- package/dist/env.js +3 -1
- package/dist/exceptions/index.d.ts +1 -0
- package/dist/exceptions/index.js +1 -0
- package/dist/exceptions/token-expired.d.ts +4 -0
- package/dist/exceptions/token-expired.js +10 -0
- package/dist/middleware/cache.js +10 -0
- package/dist/services/authorization.js +72 -30
- package/dist/services/collections.d.ts +2 -0
- package/dist/services/collections.js +8 -0
- package/dist/services/fields.js +24 -1
- package/dist/services/files.d.ts +5 -1
- package/dist/services/files.js +59 -40
- package/dist/services/graphql.d.ts +2 -3
- package/dist/services/graphql.js +39 -9
- package/dist/services/payload.d.ts +1 -0
- package/dist/services/payload.js +19 -16
- package/dist/types/files.d.ts +8 -0
- package/dist/utils/apply-query.js +17 -7
- package/dist/utils/apply-snapshot.d.ts +3 -1
- package/dist/utils/apply-snapshot.js +34 -5
- package/dist/utils/get-graphql-type.js +1 -0
- package/dist/utils/get-local-type.js +5 -0
- package/dist/utils/jwt.js +1 -1
- package/dist/utils/validate-query.js +19 -15
- package/example.env +4 -0
- package/package.json +14 -13
|
@@ -35,7 +35,7 @@ class AuthorizationService {
|
|
|
35
35
|
throw new exceptions_2.ForbiddenException();
|
|
36
36
|
}
|
|
37
37
|
validateFields(ast);
|
|
38
|
-
validateFilterPermissions(ast, this.schema, this.accountability);
|
|
38
|
+
validateFilterPermissions(ast, this.schema, action, this.accountability);
|
|
39
39
|
applyFilters(ast, this.accountability);
|
|
40
40
|
return ast;
|
|
41
41
|
/**
|
|
@@ -87,6 +87,8 @@ class AuthorizationService {
|
|
|
87
87
|
if (!aliasMap)
|
|
88
88
|
continue;
|
|
89
89
|
for (const column of Object.values(aliasMap)) {
|
|
90
|
+
if (column === '*')
|
|
91
|
+
continue;
|
|
90
92
|
if (allowedFields.includes(column) === false)
|
|
91
93
|
throw new exceptions_2.ForbiddenException();
|
|
92
94
|
}
|
|
@@ -106,7 +108,7 @@ class AuthorizationService {
|
|
|
106
108
|
}
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
|
-
function validateFilterPermissions(ast, schema, accountability) {
|
|
111
|
+
function validateFilterPermissions(ast, schema, action, accountability) {
|
|
110
112
|
var _a, _b, _c, _d, _e;
|
|
111
113
|
let requiredFieldPermissions = {};
|
|
112
114
|
if (ast.type !== 'field') {
|
|
@@ -114,28 +116,34 @@ class AuthorizationService {
|
|
|
114
116
|
for (const collection of Object.keys(ast.children)) {
|
|
115
117
|
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 : {}));
|
|
116
118
|
for (const child of ast.children[collection]) {
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
|
|
119
|
+
const childPermissions = validateFilterPermissions(child, schema, action, accountability);
|
|
120
|
+
if (Object.keys(childPermissions).length > 0) {
|
|
121
|
+
//Only add relational field if deep child has a filter
|
|
122
|
+
if (child.type !== 'field') {
|
|
123
|
+
(requiredFieldPermissions[collection] || (requiredFieldPermissions[collection] = new Set())).add(child.fieldKey);
|
|
124
|
+
}
|
|
125
|
+
requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, childPermissions);
|
|
120
126
|
}
|
|
121
|
-
requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, validateFilterPermissions(child, schema, accountability));
|
|
122
127
|
}
|
|
123
128
|
}
|
|
124
129
|
}
|
|
125
130
|
else {
|
|
126
131
|
requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, extractRequiredFieldPermissions(ast.name, (_e = (_d = ast.query) === null || _d === void 0 ? void 0 : _d.filter) !== null && _e !== void 0 ? _e : {}));
|
|
127
132
|
for (const child of ast.children) {
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
|
|
133
|
+
const childPermissions = validateFilterPermissions(child, schema, action, accountability);
|
|
134
|
+
if (Object.keys(childPermissions).length > 0) {
|
|
135
|
+
// Only add relational field if deep child has a filter
|
|
136
|
+
if (child.type !== 'field') {
|
|
137
|
+
(requiredFieldPermissions[ast.name] || (requiredFieldPermissions[ast.name] = new Set())).add(child.fieldKey);
|
|
138
|
+
}
|
|
139
|
+
requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, childPermissions);
|
|
131
140
|
}
|
|
132
|
-
requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, validateFilterPermissions(child, schema, accountability));
|
|
133
141
|
}
|
|
134
142
|
}
|
|
135
143
|
}
|
|
136
144
|
if (ast.type === 'root') {
|
|
137
145
|
// Validate all required permissions once at the root level
|
|
138
|
-
checkFieldPermissions(requiredFieldPermissions);
|
|
146
|
+
checkFieldPermissions(ast.name, schema, action, requiredFieldPermissions);
|
|
139
147
|
}
|
|
140
148
|
return requiredFieldPermissions;
|
|
141
149
|
function extractRequiredFieldPermissions(collection, filter, parentCollection, parentField) {
|
|
@@ -153,6 +161,34 @@ class AuthorizationService {
|
|
|
153
161
|
// Filter value is not a filter, so we should skip it
|
|
154
162
|
return result;
|
|
155
163
|
}
|
|
164
|
+
// m2a filter in the form of `item:collection`
|
|
165
|
+
else if (filterKey.includes(':')) {
|
|
166
|
+
const [field, collectionScope] = filterKey.split(':');
|
|
167
|
+
if (collection) {
|
|
168
|
+
// Add the `item` field to the required permissions
|
|
169
|
+
(result[collection] || (result[collection] = new Set())).add(field);
|
|
170
|
+
// Add the `collection` field to the required permissions
|
|
171
|
+
result[collection].add('collection');
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
const relation = schema.relations.find((relation) => {
|
|
175
|
+
var _a;
|
|
176
|
+
return ((relation.collection === parentCollection && relation.field === parentField) ||
|
|
177
|
+
(relation.related_collection === parentCollection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === parentField));
|
|
178
|
+
});
|
|
179
|
+
// Filter key not found in parent collection
|
|
180
|
+
if (!relation)
|
|
181
|
+
throw new exceptions_2.ForbiddenException();
|
|
182
|
+
const relatedCollectionName = relation.related_collection === parentCollection ? relation.collection : relation.related_collection;
|
|
183
|
+
// Add the `item` field to the required permissions
|
|
184
|
+
(result[relatedCollectionName] || (result[relatedCollectionName] = new Set())).add(field);
|
|
185
|
+
// Add the `collection` field to the required permissions
|
|
186
|
+
result[relatedCollectionName].add('collection');
|
|
187
|
+
}
|
|
188
|
+
// Continue to parse the filter for nested `collection` afresh
|
|
189
|
+
const requiredPermissions = extractRequiredFieldPermissions(collectionScope, filterValue);
|
|
190
|
+
result = mergeRequiredFieldPermissions(result, requiredPermissions);
|
|
191
|
+
}
|
|
156
192
|
else {
|
|
157
193
|
if (collection) {
|
|
158
194
|
(result[collection] || (result[collection] = new Set())).add(filterKey);
|
|
@@ -163,20 +199,12 @@ class AuthorizationService {
|
|
|
163
199
|
return ((relation.collection === parentCollection && relation.field === parentField) ||
|
|
164
200
|
(relation.related_collection === parentCollection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === parentField));
|
|
165
201
|
});
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
(result[relation.collection] || (result[relation.collection] = new Set())).add(filterKey);
|
|
169
|
-
parentCollection = relation.collection;
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
(result[relation.related_collection] || (result[relation.related_collection] = new Set())).add(filterKey);
|
|
173
|
-
parentCollection = relation.related_collection;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
// Filter key not found in parent collection
|
|
202
|
+
// Filter key not found in parent collection
|
|
203
|
+
if (!relation)
|
|
178
204
|
throw new exceptions_2.ForbiddenException();
|
|
179
|
-
|
|
205
|
+
parentCollection =
|
|
206
|
+
relation.related_collection === parentCollection ? relation.collection : relation.related_collection;
|
|
207
|
+
(result[parentCollection] || (result[parentCollection] = new Set())).add(filterKey);
|
|
180
208
|
}
|
|
181
209
|
if (typeof filterValue === 'object') {
|
|
182
210
|
// Parent collection is undefined when we process the top level filter
|
|
@@ -214,20 +242,34 @@ class AuthorizationService {
|
|
|
214
242
|
}
|
|
215
243
|
return current;
|
|
216
244
|
}
|
|
217
|
-
function checkFieldPermissions(requiredPermissions) {
|
|
218
|
-
var _a;
|
|
245
|
+
function checkFieldPermissions(rootCollection, schema, action, requiredPermissions) {
|
|
246
|
+
var _a, _b;
|
|
219
247
|
if ((accountability === null || accountability === void 0 ? void 0 : accountability.admin) === true)
|
|
220
248
|
return;
|
|
221
249
|
for (const collection of Object.keys(requiredPermissions)) {
|
|
222
250
|
const permission = (_a = accountability === null || accountability === void 0 ? void 0 : accountability.permissions) === null || _a === void 0 ? void 0 : _a.find((permission) => permission.collection === collection && permission.action === 'read');
|
|
223
|
-
|
|
251
|
+
let allowedFields;
|
|
252
|
+
// Allow the filtering of top level ID for actions such as update and delete
|
|
253
|
+
if (action !== 'read' && collection === rootCollection) {
|
|
254
|
+
const actionPermission = (_b = accountability === null || accountability === void 0 ? void 0 : accountability.permissions) === null || _b === void 0 ? void 0 : _b.find((permission) => permission.collection === collection && permission.action === action);
|
|
255
|
+
if (!actionPermission || !actionPermission.fields) {
|
|
256
|
+
throw new exceptions_2.ForbiddenException();
|
|
257
|
+
}
|
|
258
|
+
allowedFields = (permission === null || permission === void 0 ? void 0 : permission.fields)
|
|
259
|
+
? [...permission.fields, schema.collections[collection].primary]
|
|
260
|
+
: [schema.collections[collection].primary];
|
|
261
|
+
}
|
|
262
|
+
else if (!permission || !permission.fields) {
|
|
224
263
|
throw new exceptions_2.ForbiddenException();
|
|
225
|
-
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
allowedFields = permission.fields;
|
|
267
|
+
}
|
|
226
268
|
if (allowedFields.includes('*'))
|
|
227
269
|
continue;
|
|
228
270
|
// Allow legacy permissions with an empty fields array, where id can be accessed
|
|
229
271
|
if (allowedFields.length === 0)
|
|
230
|
-
allowedFields.push(
|
|
272
|
+
allowedFields.push(schema.collections[collection].primary);
|
|
231
273
|
for (const field of requiredPermissions[collection]) {
|
|
232
274
|
if (!allowedFields.includes(field))
|
|
233
275
|
throw new exceptions_2.ForbiddenException();
|
|
@@ -342,7 +384,7 @@ class AuthorizationService {
|
|
|
342
384
|
}
|
|
343
385
|
}
|
|
344
386
|
if (hasFieldValidationRules) {
|
|
345
|
-
if (permission.validation) {
|
|
387
|
+
if (permission.validation && Object.keys(permission.validation).length > 0) {
|
|
346
388
|
permission.validation = { _and: [permission.validation, ...fieldValidationRules] };
|
|
347
389
|
}
|
|
348
390
|
else {
|
|
@@ -4,6 +4,7 @@ import Keyv from 'keyv';
|
|
|
4
4
|
import { AbstractServiceOptions, Collection, CollectionMeta, MutationOptions } from '../types';
|
|
5
5
|
import { Accountability, RawField, SchemaOverview } from '@directus/shared/types';
|
|
6
6
|
import { Table } from 'knex-schema-inspector/dist/types/table';
|
|
7
|
+
import { Helpers } from '../database/helpers';
|
|
7
8
|
export declare type RawCollection = {
|
|
8
9
|
collection: string;
|
|
9
10
|
fields?: RawField[];
|
|
@@ -12,6 +13,7 @@ export declare type RawCollection = {
|
|
|
12
13
|
};
|
|
13
14
|
export declare class CollectionsService {
|
|
14
15
|
knex: Knex;
|
|
16
|
+
helpers: Helpers;
|
|
15
17
|
accountability: Accountability | null;
|
|
16
18
|
schemaInspector: ReturnType<typeof SchemaInspector>;
|
|
17
19
|
schema: SchemaOverview;
|
|
@@ -32,9 +32,12 @@ const env_1 = __importDefault(require("../env"));
|
|
|
32
32
|
const exceptions_1 = require("../exceptions");
|
|
33
33
|
const fields_1 = require("../services/fields");
|
|
34
34
|
const items_1 = require("../services/items");
|
|
35
|
+
const utils_1 = require("@directus/shared/utils");
|
|
36
|
+
const helpers_1 = require("../database/helpers");
|
|
35
37
|
class CollectionsService {
|
|
36
38
|
constructor(options) {
|
|
37
39
|
this.knex = options.knex || (0, database_1.default)();
|
|
40
|
+
this.helpers = (0, helpers_1.getHelpers)(this.knex);
|
|
38
41
|
this.accountability = options.accountability || null;
|
|
39
42
|
this.schemaInspector = options.knex ? (0, schema_1.default)(options.knex) : (0, database_1.getSchemaInspector)();
|
|
40
43
|
this.schema = options.schema;
|
|
@@ -96,6 +99,11 @@ class CollectionsService {
|
|
|
96
99
|
collection: payload.collection,
|
|
97
100
|
};
|
|
98
101
|
}
|
|
102
|
+
// Add flag for specific database type overrides
|
|
103
|
+
const flagToAdd = this.helpers.date.fieldFlagForField(field.type);
|
|
104
|
+
if (flagToAdd) {
|
|
105
|
+
(0, utils_1.addFieldFlag)(field, flagToAdd);
|
|
106
|
+
}
|
|
99
107
|
return field;
|
|
100
108
|
});
|
|
101
109
|
const fieldsService = new fields_1.FieldsService({ knex: trx, schema: this.schema });
|
package/dist/services/fields.js
CHANGED
|
@@ -40,6 +40,7 @@ const utils_1 = require("@directus/shared/utils");
|
|
|
40
40
|
const lodash_1 = require("lodash");
|
|
41
41
|
const relations_1 = require("./relations");
|
|
42
42
|
const helpers_1 = require("../database/helpers");
|
|
43
|
+
const constants_2 = require("@directus/shared/constants");
|
|
43
44
|
class FieldsService {
|
|
44
45
|
constructor(options) {
|
|
45
46
|
this.knex = options.knex || (0, database_1.default)();
|
|
@@ -60,6 +61,7 @@ class FieldsService {
|
|
|
60
61
|
}));
|
|
61
62
|
}
|
|
62
63
|
async readAll(collection) {
|
|
64
|
+
var _a, _b, _c, _d;
|
|
63
65
|
let fields;
|
|
64
66
|
if (this.accountability && this.accountability.admin !== true && this.hasReadAccess === false) {
|
|
65
67
|
throw new exceptions_1.ForbiddenException();
|
|
@@ -151,6 +153,15 @@ class FieldsService {
|
|
|
151
153
|
return allowedFields.includes(field.field);
|
|
152
154
|
});
|
|
153
155
|
}
|
|
156
|
+
// Update specific database type overrides
|
|
157
|
+
for (const field of result) {
|
|
158
|
+
if ((_b = (_a = field.meta) === null || _a === void 0 ? void 0 : _a.special) === null || _b === void 0 ? void 0 : _b.includes('cast-timestamp')) {
|
|
159
|
+
field.type = 'timestamp';
|
|
160
|
+
}
|
|
161
|
+
else if ((_d = (_c = field.meta) === null || _c === void 0 ? void 0 : _c.special) === null || _d === void 0 ? void 0 : _d.includes('cast-datetime')) {
|
|
162
|
+
field.type = 'dateTime';
|
|
163
|
+
}
|
|
164
|
+
}
|
|
154
165
|
return result;
|
|
155
166
|
}
|
|
156
167
|
async readOne(collection, field) {
|
|
@@ -206,6 +217,11 @@ class FieldsService {
|
|
|
206
217
|
if (exists) {
|
|
207
218
|
throw new exceptions_1.InvalidPayloadException(`Field "${field.field}" already exists in collection "${collection}"`);
|
|
208
219
|
}
|
|
220
|
+
// Add flag for specific database type overrides
|
|
221
|
+
const flagToAdd = this.helpers.date.fieldFlagForField(field.type);
|
|
222
|
+
if (flagToAdd) {
|
|
223
|
+
(0, utils_1.addFieldFlag)(field, flagToAdd);
|
|
224
|
+
}
|
|
209
225
|
await this.knex.transaction(async (trx) => {
|
|
210
226
|
const itemsService = new items_1.ItemsService('directus_fields', {
|
|
211
227
|
knex: trx,
|
|
@@ -452,9 +468,16 @@ class FieldsService {
|
|
|
452
468
|
column = table[field.type](field.field);
|
|
453
469
|
}
|
|
454
470
|
if (((_f = field.schema) === null || _f === void 0 ? void 0 : _f.default_value) !== undefined) {
|
|
455
|
-
if (typeof field.schema.default_value === 'string' &&
|
|
471
|
+
if (typeof field.schema.default_value === 'string' &&
|
|
472
|
+
(field.schema.default_value.toLowerCase() === 'now()' || field.schema.default_value === 'CURRENT_TIMESTAMP')) {
|
|
456
473
|
column.defaultTo(this.knex.fn.now());
|
|
457
474
|
}
|
|
475
|
+
else if (typeof field.schema.default_value === 'string' &&
|
|
476
|
+
field.schema.default_value.includes('CURRENT_TIMESTAMP(') &&
|
|
477
|
+
field.schema.default_value.includes(')')) {
|
|
478
|
+
const precision = field.schema.default_value.match(constants_2.REGEX_BETWEEN_PARENS)[1];
|
|
479
|
+
column.defaultTo(this.knex.fn.now(Number(precision)));
|
|
480
|
+
}
|
|
458
481
|
else if (typeof field.schema.default_value === 'string' &&
|
|
459
482
|
['"null"', 'null'].includes(field.schema.default_value.toLowerCase())) {
|
|
460
483
|
column.defaultTo(null);
|
package/dist/services/files.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { AbstractServiceOptions, File, PrimaryKey, MutationOptions } from '../types';
|
|
2
|
+
import { AbstractServiceOptions, File, PrimaryKey, MutationOptions, Metadata } from '../types';
|
|
3
3
|
import { ItemsService } from './items';
|
|
4
4
|
export declare class FilesService extends ItemsService {
|
|
5
5
|
constructor(options: AbstractServiceOptions);
|
|
@@ -10,6 +10,10 @@ export declare class FilesService extends ItemsService {
|
|
|
10
10
|
filename_download: string;
|
|
11
11
|
storage: string;
|
|
12
12
|
}, primaryKey?: PrimaryKey, opts?: MutationOptions): Promise<PrimaryKey>;
|
|
13
|
+
/**
|
|
14
|
+
* Extract metadata from a buffer's content
|
|
15
|
+
*/
|
|
16
|
+
getMetadata(bufferContent: any, allowList?: any): Promise<Metadata>;
|
|
13
17
|
/**
|
|
14
18
|
* Import a single file from an external URL
|
|
15
19
|
*/
|
package/dist/services/files.js
CHANGED
|
@@ -88,46 +88,13 @@ class FilesService extends items_1.ItemsService {
|
|
|
88
88
|
payload.filesize = size;
|
|
89
89
|
if (['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/tiff'].includes(payload.type)) {
|
|
90
90
|
const buffer = await storage_1.default.disk(data.storage).getBuffer(payload.filename_disk);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
payload.width = meta.width;
|
|
99
|
-
payload.height = meta.height;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
catch (err) {
|
|
103
|
-
logger_1.default.warn(`Couldn't extract sharp metadata from file`);
|
|
104
|
-
logger_1.default.warn(err);
|
|
105
|
-
}
|
|
106
|
-
payload.metadata = {};
|
|
107
|
-
try {
|
|
108
|
-
payload.metadata = await exifr_1.default.parse(buffer.content, {
|
|
109
|
-
icc: false,
|
|
110
|
-
iptc: true,
|
|
111
|
-
ifd1: true,
|
|
112
|
-
interop: true,
|
|
113
|
-
translateValues: true,
|
|
114
|
-
reviveValues: true,
|
|
115
|
-
mergeOutput: false,
|
|
116
|
-
});
|
|
117
|
-
if ((_b = (_a = payload.metadata) === null || _a === void 0 ? void 0 : _a.iptc) === null || _b === void 0 ? void 0 : _b.Headline) {
|
|
118
|
-
payload.title = payload.metadata.iptc.Headline;
|
|
119
|
-
}
|
|
120
|
-
if (!payload.description && ((_d = (_c = payload.metadata) === null || _c === void 0 ? void 0 : _c.iptc) === null || _d === void 0 ? void 0 : _d.Caption)) {
|
|
121
|
-
payload.description = payload.metadata.iptc.Caption;
|
|
122
|
-
}
|
|
123
|
-
if ((_f = (_e = payload.metadata) === null || _e === void 0 ? void 0 : _e.iptc) === null || _f === void 0 ? void 0 : _f.Keywords) {
|
|
124
|
-
payload.tags = payload.metadata.iptc.Keywords;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
catch (err) {
|
|
128
|
-
logger_1.default.warn(`Couldn't extract EXIF metadata from file`);
|
|
129
|
-
logger_1.default.warn(err);
|
|
130
|
-
}
|
|
91
|
+
const { height, width, description, title, tags, metadata } = await this.getMetadata(buffer.content);
|
|
92
|
+
(_a = payload.height) !== null && _a !== void 0 ? _a : (payload.height = height);
|
|
93
|
+
(_b = payload.width) !== null && _b !== void 0 ? _b : (payload.width = width);
|
|
94
|
+
(_c = payload.description) !== null && _c !== void 0 ? _c : (payload.description = description);
|
|
95
|
+
(_d = payload.title) !== null && _d !== void 0 ? _d : (payload.title = title);
|
|
96
|
+
(_e = payload.tags) !== null && _e !== void 0 ? _e : (payload.tags = tags);
|
|
97
|
+
(_f = payload.metadata) !== null && _f !== void 0 ? _f : (payload.metadata = metadata);
|
|
131
98
|
}
|
|
132
99
|
// We do this in a service without accountability. Even if you don't have update permissions to the file,
|
|
133
100
|
// we still want to be able to set the extracted values from the file on create
|
|
@@ -152,6 +119,58 @@ class FilesService extends items_1.ItemsService {
|
|
|
152
119
|
}
|
|
153
120
|
return primaryKey;
|
|
154
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Extract metadata from a buffer's content
|
|
124
|
+
*/
|
|
125
|
+
async getMetadata(bufferContent, allowList = env_1.default.FILE_METADATA_ALLOW_LIST) {
|
|
126
|
+
const metadata = {};
|
|
127
|
+
try {
|
|
128
|
+
const sharpMetadata = await (0, sharp_1.default)(bufferContent, {}).metadata();
|
|
129
|
+
if (sharpMetadata.orientation && sharpMetadata.orientation >= 5) {
|
|
130
|
+
metadata.height = sharpMetadata.width;
|
|
131
|
+
metadata.width = sharpMetadata.height;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
metadata.width = sharpMetadata.width;
|
|
135
|
+
metadata.height = sharpMetadata.height;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
logger_1.default.warn(`Couldn't extract sharp metadata from file`);
|
|
140
|
+
logger_1.default.warn(err);
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const exifrMetadata = await exifr_1.default.parse(bufferContent, {
|
|
144
|
+
icc: false,
|
|
145
|
+
iptc: true,
|
|
146
|
+
ifd1: true,
|
|
147
|
+
interop: true,
|
|
148
|
+
translateValues: true,
|
|
149
|
+
reviveValues: true,
|
|
150
|
+
mergeOutput: false,
|
|
151
|
+
});
|
|
152
|
+
if (allowList === '*' || (allowList === null || allowList === void 0 ? void 0 : allowList[0]) === '*') {
|
|
153
|
+
metadata.metadata = exifrMetadata;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
metadata.metadata = (0, lodash_1.pick)(exifrMetadata, allowList);
|
|
157
|
+
}
|
|
158
|
+
if (!metadata.description && (exifrMetadata === null || exifrMetadata === void 0 ? void 0 : exifrMetadata.Caption)) {
|
|
159
|
+
metadata.description = exifrMetadata.Caption;
|
|
160
|
+
}
|
|
161
|
+
if (exifrMetadata === null || exifrMetadata === void 0 ? void 0 : exifrMetadata.Headline) {
|
|
162
|
+
metadata.title = exifrMetadata.Headline;
|
|
163
|
+
}
|
|
164
|
+
if (exifrMetadata === null || exifrMetadata === void 0 ? void 0 : exifrMetadata.Keywords) {
|
|
165
|
+
metadata.tags = exifrMetadata.Keywords;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
logger_1.default.warn(`Couldn't extract EXIF metadata from file`);
|
|
170
|
+
logger_1.default.warn(err);
|
|
171
|
+
}
|
|
172
|
+
return metadata;
|
|
173
|
+
}
|
|
155
174
|
/**
|
|
156
175
|
* Import a single file from an external URL
|
|
157
176
|
*/
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import { BaseException } from '@directus/shared/exceptions';
|
|
2
|
+
import { Accountability, Query, SchemaOverview } from '@directus/shared/types';
|
|
1
3
|
import { ArgumentNode, FormattedExecutionResult, FragmentDefinitionNode, GraphQLError, GraphQLResolveInfo, GraphQLScalarType, GraphQLSchema, ObjectFieldNode, SelectionNode } from 'graphql';
|
|
2
|
-
import { SchemaOverview } from '@directus/shared/types';
|
|
3
4
|
import { ObjectTypeComposer, SchemaComposer } from 'graphql-compose';
|
|
4
5
|
import { Knex } from 'knex';
|
|
5
|
-
import { BaseException } from '@directus/shared/exceptions';
|
|
6
|
-
import { Accountability, Query } from '@directus/shared/types';
|
|
7
6
|
import { AbstractServiceOptions, GraphQLParams, Item } from '../types';
|
|
8
7
|
import { ItemsService } from './items';
|
|
9
8
|
export declare const GraphQLGeoJSON: GraphQLScalarType;
|
package/dist/services/graphql.js
CHANGED
|
@@ -5,20 +5,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.GraphQLService = exports.GraphQLDate = exports.GraphQLGeoJSON = void 0;
|
|
7
7
|
const argon2_1 = __importDefault(require("argon2"));
|
|
8
|
-
const validate_query_1 = require("../utils/validate-query");
|
|
9
8
|
const graphql_1 = require("graphql");
|
|
10
9
|
const graphql_compose_1 = require("graphql-compose");
|
|
11
10
|
const lodash_1 = require("lodash");
|
|
12
11
|
const ms_1 = __importDefault(require("ms"));
|
|
13
12
|
const cache_1 = require("../cache");
|
|
13
|
+
const constants_1 = require("../constants");
|
|
14
14
|
const database_1 = __importDefault(require("../database"));
|
|
15
15
|
const env_1 = __importDefault(require("../env"));
|
|
16
16
|
const exceptions_1 = require("../exceptions");
|
|
17
17
|
const extensions_1 = require("../extensions");
|
|
18
18
|
const types_1 = require("../types");
|
|
19
|
+
const generate_hash_1 = require("../utils/generate-hash");
|
|
19
20
|
const get_graphql_type_1 = require("../utils/get-graphql-type");
|
|
20
21
|
const reduce_schema_1 = require("../utils/reduce-schema");
|
|
21
22
|
const sanitize_query_1 = require("../utils/sanitize-query");
|
|
23
|
+
const validate_query_1 = require("../utils/validate-query");
|
|
22
24
|
const activity_1 = require("./activity");
|
|
23
25
|
const authentication_1 = require("./authentication");
|
|
24
26
|
const collections_1 = require("./collections");
|
|
@@ -26,9 +28,9 @@ const fields_1 = require("./fields");
|
|
|
26
28
|
const files_1 = require("./files");
|
|
27
29
|
const folders_1 = require("./folders");
|
|
28
30
|
const items_1 = require("./items");
|
|
31
|
+
const notifications_1 = require("./notifications");
|
|
29
32
|
const permissions_1 = require("./permissions");
|
|
30
33
|
const presets_1 = require("./presets");
|
|
31
|
-
const notifications_1 = require("./notifications");
|
|
32
34
|
const relations_1 = require("./relations");
|
|
33
35
|
const revisions_1 = require("./revisions");
|
|
34
36
|
const roles_1 = require("./roles");
|
|
@@ -40,8 +42,6 @@ const tfa_1 = require("./tfa");
|
|
|
40
42
|
const users_1 = require("./users");
|
|
41
43
|
const utils_1 = require("./utils");
|
|
42
44
|
const webhooks_1 = require("./webhooks");
|
|
43
|
-
const generate_hash_1 = require("../utils/generate-hash");
|
|
44
|
-
const constants_1 = require("../constants");
|
|
45
45
|
const GraphQLVoid = new graphql_1.GraphQLScalarType({
|
|
46
46
|
name: 'Void',
|
|
47
47
|
description: 'Represents NULL values',
|
|
@@ -235,6 +235,14 @@ class GraphQLService {
|
|
|
235
235
|
function getTypes(action) {
|
|
236
236
|
var _a, _b, _c, _d, _e;
|
|
237
237
|
const CollectionTypes = {};
|
|
238
|
+
const CountFunctions = schemaComposer.createObjectTC({
|
|
239
|
+
name: 'count_functions',
|
|
240
|
+
fields: {
|
|
241
|
+
count: {
|
|
242
|
+
type: graphql_1.GraphQLInt,
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
});
|
|
238
246
|
const DateFunctions = schemaComposer.createObjectTC({
|
|
239
247
|
name: 'date_functions',
|
|
240
248
|
fields: {
|
|
@@ -328,6 +336,15 @@ class GraphQLService {
|
|
|
328
336
|
},
|
|
329
337
|
};
|
|
330
338
|
}
|
|
339
|
+
if (field.type === 'json' || field.type === 'alias') {
|
|
340
|
+
acc[`${field.field}_func`] = {
|
|
341
|
+
type: CountFunctions,
|
|
342
|
+
resolve: (obj) => {
|
|
343
|
+
const funcFields = Object.keys(CountFunctions.getFields()).map((key) => `${field.field}_${key}`);
|
|
344
|
+
return (0, lodash_1.mapKeys)((0, lodash_1.pick)(obj, funcFields), (_value, key) => key.substring(field.field.length + 1));
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
}
|
|
331
348
|
return acc;
|
|
332
349
|
}, {}),
|
|
333
350
|
});
|
|
@@ -550,6 +567,14 @@ class GraphQLService {
|
|
|
550
567
|
},
|
|
551
568
|
},
|
|
552
569
|
});
|
|
570
|
+
const CountFunctionFilterOperators = schemaComposer.createInputTC({
|
|
571
|
+
name: 'count_function_filter_operators',
|
|
572
|
+
fields: {
|
|
573
|
+
count: {
|
|
574
|
+
type: NumberFilterOperators,
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
});
|
|
553
578
|
const DateFunctionFilterOperators = schemaComposer.createInputTC({
|
|
554
579
|
name: 'date_function_filter_operators',
|
|
555
580
|
fields: {
|
|
@@ -634,6 +659,11 @@ class GraphQLService {
|
|
|
634
659
|
type: DateTimeFunctionFilterOperators,
|
|
635
660
|
};
|
|
636
661
|
}
|
|
662
|
+
if (field.type === 'json' || field.type === 'alias') {
|
|
663
|
+
acc[`${field.field}_func`] = {
|
|
664
|
+
type: CountFunctionFilterOperators,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
637
667
|
return acc;
|
|
638
668
|
}, {}),
|
|
639
669
|
});
|
|
@@ -1308,7 +1338,7 @@ class GraphQLService {
|
|
|
1308
1338
|
return result;
|
|
1309
1339
|
}
|
|
1310
1340
|
injectSystemResolvers(schemaComposer, { CreateCollectionTypes, ReadCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes, }, schema) {
|
|
1311
|
-
var _a, _b, _c, _d, _e;
|
|
1341
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1312
1342
|
const AuthTokens = schemaComposer.createObjectTC({
|
|
1313
1343
|
name: 'auth_tokens',
|
|
1314
1344
|
fields: {
|
|
@@ -2131,7 +2161,7 @@ class GraphQLService {
|
|
|
2131
2161
|
},
|
|
2132
2162
|
});
|
|
2133
2163
|
}
|
|
2134
|
-
if ('directus_users' in schema.update.collections) {
|
|
2164
|
+
if ('directus_users' in schema.update.collections && ((_c = this.accountability) === null || _c === void 0 ? void 0 : _c.user)) {
|
|
2135
2165
|
schemaComposer.Mutation.addFields({
|
|
2136
2166
|
update_users_me: {
|
|
2137
2167
|
type: ReadCollectionTypes['directus_users'],
|
|
@@ -2160,7 +2190,7 @@ class GraphQLService {
|
|
|
2160
2190
|
if ('directus_activity' in schema.create.collections) {
|
|
2161
2191
|
schemaComposer.Mutation.addFields({
|
|
2162
2192
|
create_comment: {
|
|
2163
|
-
type: (
|
|
2193
|
+
type: (_d = ReadCollectionTypes['directus_activity']) !== null && _d !== void 0 ? _d : graphql_1.GraphQLBoolean,
|
|
2164
2194
|
args: {
|
|
2165
2195
|
collection: (0, graphql_1.GraphQLNonNull)(graphql_1.GraphQLString),
|
|
2166
2196
|
item: (0, graphql_1.GraphQLNonNull)(graphql_1.GraphQLID),
|
|
@@ -2192,7 +2222,7 @@ class GraphQLService {
|
|
|
2192
2222
|
if ('directus_activity' in schema.update.collections) {
|
|
2193
2223
|
schemaComposer.Mutation.addFields({
|
|
2194
2224
|
update_comment: {
|
|
2195
|
-
type: (
|
|
2225
|
+
type: (_e = ReadCollectionTypes['directus_activity']) !== null && _e !== void 0 ? _e : graphql_1.GraphQLBoolean,
|
|
2196
2226
|
args: {
|
|
2197
2227
|
id: (0, graphql_1.GraphQLNonNull)(graphql_1.GraphQLID),
|
|
2198
2228
|
comment: (0, graphql_1.GraphQLNonNull)(graphql_1.GraphQLString),
|
|
@@ -2235,7 +2265,7 @@ class GraphQLService {
|
|
|
2235
2265
|
if ('directus_files' in schema.create.collections) {
|
|
2236
2266
|
schemaComposer.Mutation.addFields({
|
|
2237
2267
|
import_file: {
|
|
2238
|
-
type: (
|
|
2268
|
+
type: (_f = ReadCollectionTypes['directus_files']) !== null && _f !== void 0 ? _f : graphql_1.GraphQLBoolean,
|
|
2239
2269
|
args: {
|
|
2240
2270
|
url: (0, graphql_1.GraphQLNonNull)(graphql_1.GraphQLString),
|
|
2241
2271
|
data: (0, graphql_compose_1.toInputObjectType)(CreateCollectionTypes['directus_files']).setTypeName('create_directus_files_input'),
|