directus 9.16.0 → 9.17.1
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 +7 -7
- package/dist/auth/drivers/ldap.js +1 -0
- package/dist/auth/drivers/local.js +6 -0
- package/dist/auth/drivers/oauth2.js +1 -0
- package/dist/auth/drivers/openid.js +1 -0
- package/dist/cli/utils/create-env/env-stub.liquid +8 -1
- package/dist/controllers/activity.js +1 -0
- package/dist/controllers/auth.js +6 -9
- package/dist/database/helpers/schema/dialects/sqlite.d.ts +2 -2
- package/dist/database/helpers/schema/dialects/sqlite.js +2 -2
- package/dist/database/helpers/schema/types.d.ts +2 -2
- package/dist/database/helpers/schema/types.js +2 -2
- package/dist/database/migrations/20220826A-add-origin-to-accountability.d.ts +3 -0
- package/dist/database/migrations/20220826A-add-origin-to-accountability.js +21 -0
- package/dist/database/system-data/fields/activity.yaml +6 -0
- package/dist/database/system-data/fields/sessions.yaml +2 -0
- package/dist/env.js +8 -0
- package/dist/extensions.d.ts +1 -0
- package/dist/extensions.js +16 -3
- package/dist/flows.js +27 -17
- package/dist/mailer.js +7 -0
- package/dist/middleware/authenticate.d.ts +1 -1
- package/dist/middleware/authenticate.js +1 -0
- package/dist/middleware/authenticate.test.js +44 -4
- package/dist/middleware/validate-batch.d.ts +1 -2
- package/dist/middleware/validate-batch.js +10 -13
- package/dist/middleware/validate-batch.test.d.ts +1 -0
- package/dist/middleware/validate-batch.test.js +82 -0
- package/dist/operations/request/index.js +2 -2
- package/dist/operations/transform/index.d.ts +1 -1
- package/dist/operations/transform/index.js +1 -1
- package/dist/services/authentication.js +13 -3
- package/dist/services/fields.js +11 -3
- package/dist/services/files.js +2 -2
- package/dist/services/graphql/index.js +45 -37
- package/dist/services/items.js +15 -3
- package/dist/services/payload.js +28 -4
- package/dist/services/relations.d.ts +2 -0
- package/dist/services/relations.js +14 -0
- package/dist/services/server.js +5 -4
- package/dist/services/shares.js +2 -1
- package/dist/utils/async-handler.d.ts +2 -6
- package/dist/utils/async-handler.js +1 -13
- package/dist/utils/async-handler.test.d.ts +1 -0
- package/dist/utils/async-handler.test.js +18 -0
- package/package.json +14 -11
|
@@ -14,39 +14,36 @@ const validateBatch = (scope) => (0, async_handler_1.default)(async (req, res, n
|
|
|
14
14
|
req.body = {};
|
|
15
15
|
return next();
|
|
16
16
|
}
|
|
17
|
+
if (req.method.toLowerCase() !== 'search' && scope !== 'read' && req.singleton) {
|
|
18
|
+
return next();
|
|
19
|
+
}
|
|
17
20
|
if (!req.body)
|
|
18
21
|
throw new exceptions_1.InvalidPayloadException('Payload in body is required');
|
|
19
|
-
if (req.
|
|
22
|
+
if (['update', 'delete'].includes(scope) && Array.isArray(req.body)) {
|
|
20
23
|
return next();
|
|
24
|
+
}
|
|
25
|
+
// In reads, the query in the body should override the query params for searching
|
|
26
|
+
if (scope === 'read' && req.body.query) {
|
|
27
|
+
req.sanitizedQuery = (0, sanitize_query_1.sanitizeQuery)(req.body.query, req.accountability);
|
|
28
|
+
}
|
|
21
29
|
// Every cRUD action has either keys or query
|
|
22
30
|
let batchSchema = joi_1.default.object().keys({
|
|
23
31
|
keys: joi_1.default.array().items(joi_1.default.alternatives(joi_1.default.string(), joi_1.default.number())),
|
|
24
32
|
query: joi_1.default.object().unknown(),
|
|
25
33
|
});
|
|
26
|
-
|
|
27
|
-
if (scope !== 'read') {
|
|
34
|
+
if (['update', 'delete'].includes(scope)) {
|
|
28
35
|
batchSchema = batchSchema.xor('query', 'keys');
|
|
29
36
|
}
|
|
30
37
|
// In updates, we add a required `data` that holds the update payload if an array isn't used
|
|
31
38
|
if (scope === 'update') {
|
|
32
|
-
if (Array.isArray(req.body))
|
|
33
|
-
return next();
|
|
34
39
|
batchSchema = batchSchema.keys({
|
|
35
40
|
data: joi_1.default.object().unknown().required(),
|
|
36
41
|
});
|
|
37
42
|
}
|
|
38
|
-
// In deletes, we want to keep supporting an array of just primary keys
|
|
39
|
-
if (scope === 'delete' && Array.isArray(req.body)) {
|
|
40
|
-
return next();
|
|
41
|
-
}
|
|
42
43
|
const { error } = batchSchema.validate(req.body);
|
|
43
44
|
if (error) {
|
|
44
45
|
throw new exceptions_2.FailedValidationException(error.details[0]);
|
|
45
46
|
}
|
|
46
|
-
// In reads, the query in the body should override the query params for searching
|
|
47
|
-
if (scope === 'read' && req.body.query) {
|
|
48
|
-
req.sanitizedQuery = (0, sanitize_query_1.sanitizeQuery)(req.body.query, req.accountability);
|
|
49
|
-
}
|
|
50
47
|
return next();
|
|
51
48
|
});
|
|
52
49
|
exports.validateBatch = validateBatch;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '../../src/types/express.d.ts';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const validate_batch_1 = require("./validate-batch");
|
|
4
|
+
require("../../src/types/express.d.ts");
|
|
5
|
+
const exceptions_1 = require("../exceptions");
|
|
6
|
+
const exceptions_2 = require("@directus/shared/exceptions");
|
|
7
|
+
let mockRequest;
|
|
8
|
+
let mockResponse;
|
|
9
|
+
const nextFunction = jest.fn();
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
mockRequest = {};
|
|
12
|
+
mockResponse = {};
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
test('Sets body to empty, calls next on GET requests', async () => {
|
|
16
|
+
mockRequest.method = 'GET';
|
|
17
|
+
await (0, validate_batch_1.validateBatch)('read')(mockRequest, mockResponse, nextFunction);
|
|
18
|
+
expect(mockRequest.body).toEqual({});
|
|
19
|
+
expect(nextFunction).toHaveBeenCalledTimes(1);
|
|
20
|
+
});
|
|
21
|
+
test(`Short circuits on singletons that aren't queried through SEARCH`, async () => {
|
|
22
|
+
mockRequest.method = 'PATCH';
|
|
23
|
+
mockRequest.singleton = true;
|
|
24
|
+
mockRequest.body = { title: 'test' };
|
|
25
|
+
await (0, validate_batch_1.validateBatch)('update')(mockRequest, mockResponse, nextFunction);
|
|
26
|
+
expect(nextFunction).toHaveBeenCalledTimes(1);
|
|
27
|
+
});
|
|
28
|
+
test('Throws InvalidPayloadException on missing body', async () => {
|
|
29
|
+
mockRequest.method = 'SEARCH';
|
|
30
|
+
await (0, validate_batch_1.validateBatch)('read')(mockRequest, mockResponse, nextFunction);
|
|
31
|
+
expect(nextFunction).toHaveBeenCalledTimes(1);
|
|
32
|
+
expect(jest.mocked(nextFunction).mock.calls[0][0]).toBeInstanceOf(exceptions_1.InvalidPayloadException);
|
|
33
|
+
});
|
|
34
|
+
test(`Short circuits on Array body in update/delete use`, async () => {
|
|
35
|
+
mockRequest.method = 'PATCH';
|
|
36
|
+
mockRequest.body = [1, 2, 3];
|
|
37
|
+
await (0, validate_batch_1.validateBatch)('update')(mockRequest, mockResponse, nextFunction);
|
|
38
|
+
expect(mockRequest.sanitizedQuery).toBe(undefined);
|
|
39
|
+
expect(nextFunction).toHaveBeenCalled();
|
|
40
|
+
});
|
|
41
|
+
test(`Sets sanitizedQuery based on body.query in read operations`, async () => {
|
|
42
|
+
mockRequest.method = 'SEARCH';
|
|
43
|
+
mockRequest.body = {
|
|
44
|
+
query: {
|
|
45
|
+
sort: 'id',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
await (0, validate_batch_1.validateBatch)('read')(mockRequest, mockResponse, nextFunction);
|
|
49
|
+
expect(mockRequest.sanitizedQuery).toEqual({
|
|
50
|
+
sort: ['id'],
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
test(`Doesn't allow both query and keys in a batch delete`, async () => {
|
|
54
|
+
mockRequest.method = 'DELETE';
|
|
55
|
+
mockRequest.body = {
|
|
56
|
+
keys: [1, 2, 3],
|
|
57
|
+
query: { filter: {} },
|
|
58
|
+
};
|
|
59
|
+
await (0, validate_batch_1.validateBatch)('delete')(mockRequest, mockResponse, nextFunction);
|
|
60
|
+
expect(nextFunction).toHaveBeenCalledTimes(1);
|
|
61
|
+
expect(jest.mocked(nextFunction).mock.calls[0][0]).toBeInstanceOf(exceptions_2.FailedValidationException);
|
|
62
|
+
});
|
|
63
|
+
test(`Requires 'data' on batch update`, async () => {
|
|
64
|
+
mockRequest.method = 'PATCH';
|
|
65
|
+
mockRequest.body = {
|
|
66
|
+
keys: [1, 2, 3],
|
|
67
|
+
query: { filter: {} },
|
|
68
|
+
};
|
|
69
|
+
await (0, validate_batch_1.validateBatch)('update')(mockRequest, mockResponse, nextFunction);
|
|
70
|
+
expect(nextFunction).toHaveBeenCalledTimes(1);
|
|
71
|
+
expect(jest.mocked(nextFunction).mock.calls[0][0]).toBeInstanceOf(exceptions_2.FailedValidationException);
|
|
72
|
+
});
|
|
73
|
+
test(`Calls next when all is well`, async () => {
|
|
74
|
+
mockRequest.method = 'PATCH';
|
|
75
|
+
mockRequest.body = {
|
|
76
|
+
query: { filter: {} },
|
|
77
|
+
data: {},
|
|
78
|
+
};
|
|
79
|
+
await (0, validate_batch_1.validateBatch)('update')(mockRequest, mockResponse, nextFunction);
|
|
80
|
+
expect(nextFunction).toHaveBeenCalledTimes(1);
|
|
81
|
+
expect(jest.mocked(nextFunction).mock.calls[0][0]).toBeUndefined();
|
|
82
|
+
});
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const utils_1 = require("@directus/shared/utils");
|
|
7
7
|
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const encodeurl_1 = __importDefault(require("encodeurl"));
|
|
8
9
|
exports.default = (0, utils_1.defineOperationApi)({
|
|
9
10
|
id: 'request',
|
|
10
11
|
handler: async ({ url, method, body, headers }) => {
|
|
@@ -16,9 +17,8 @@ exports.default = (0, utils_1.defineOperationApi)({
|
|
|
16
17
|
if (!customHeaders['Content-Type'] && isValidJSON(body)) {
|
|
17
18
|
customHeaders['Content-Type'] = 'application/json';
|
|
18
19
|
}
|
|
19
|
-
const shouldEncode = decodeURI(url) === url;
|
|
20
20
|
const result = await (0, axios_1.default)({
|
|
21
|
-
url:
|
|
21
|
+
url: (0, encodeurl_1.default)(url),
|
|
22
22
|
method,
|
|
23
23
|
data: body,
|
|
24
24
|
headers: customHeaders,
|
|
@@ -36,15 +36,23 @@ class AuthenticationService {
|
|
|
36
36
|
* to handle password existence checks elsewhere
|
|
37
37
|
*/
|
|
38
38
|
async login(providerName = constants_1.DEFAULT_AUTH_PROVIDER, payload, otp) {
|
|
39
|
-
var _a, _b;
|
|
40
|
-
const STALL_TIME =
|
|
39
|
+
var _a, _b, _c;
|
|
40
|
+
const STALL_TIME = env_1.default.LOGIN_STALL_TIME;
|
|
41
41
|
const timeStart = perf_hooks_1.performance.now();
|
|
42
42
|
const provider = (0, auth_1.getAuthProvider)(providerName);
|
|
43
|
+
let userId;
|
|
44
|
+
try {
|
|
45
|
+
userId = await provider.getUserID((0, lodash_1.cloneDeep)(payload));
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
await (0, stall_1.stall)(STALL_TIME, timeStart);
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
43
51
|
const user = await this.knex
|
|
44
52
|
.select('u.id', 'u.first_name', 'u.last_name', 'u.email', 'u.password', 'u.status', 'u.role', 'r.admin_access', 'r.app_access', 'u.tfa_secret', 'u.provider', 'u.external_identifier', 'u.auth_data')
|
|
45
53
|
.from('directus_users as u')
|
|
46
54
|
.leftJoin('directus_roles as r', 'u.role', 'r.id')
|
|
47
|
-
.where('u.id',
|
|
55
|
+
.where('u.id', userId)
|
|
48
56
|
.first();
|
|
49
57
|
const updatedPayload = await emitter_1.default.emitFilter('auth.login', payload, {
|
|
50
58
|
status: 'pending',
|
|
@@ -151,6 +159,7 @@ class AuthenticationService {
|
|
|
151
159
|
expires: refreshTokenExpiration,
|
|
152
160
|
ip: (_a = this.accountability) === null || _a === void 0 ? void 0 : _a.ip,
|
|
153
161
|
user_agent: (_b = this.accountability) === null || _b === void 0 ? void 0 : _b.userAgent,
|
|
162
|
+
origin: (_c = this.accountability) === null || _c === void 0 ? void 0 : _c.origin,
|
|
154
163
|
});
|
|
155
164
|
await this.knex('directus_sessions').delete().where('expires', '<', new Date());
|
|
156
165
|
if (this.accountability) {
|
|
@@ -159,6 +168,7 @@ class AuthenticationService {
|
|
|
159
168
|
user: user.id,
|
|
160
169
|
ip: this.accountability.ip,
|
|
161
170
|
user_agent: this.accountability.userAgent,
|
|
171
|
+
origin: this.accountability.origin,
|
|
162
172
|
collection: 'directus_users',
|
|
163
173
|
item: user.id,
|
|
164
174
|
});
|
package/dist/services/fields.js
CHANGED
|
@@ -222,6 +222,7 @@ class FieldsService {
|
|
|
222
222
|
if (this.accountability && this.accountability.admin !== true) {
|
|
223
223
|
throw new exceptions_1.ForbiddenException();
|
|
224
224
|
}
|
|
225
|
+
const runPostColumnChange = await this.helpers.schema.preColumnChange();
|
|
225
226
|
try {
|
|
226
227
|
const exists = field.field in this.schema.collections[collection].fields ||
|
|
227
228
|
(0, lodash_1.isNil)(await this.knex.select('id').from('directus_fields').where({ collection, field: field.field }).first()) === false;
|
|
@@ -276,6 +277,9 @@ class FieldsService {
|
|
|
276
277
|
});
|
|
277
278
|
}
|
|
278
279
|
finally {
|
|
280
|
+
if (runPostColumnChange) {
|
|
281
|
+
await this.helpers.schema.postColumnChange();
|
|
282
|
+
}
|
|
279
283
|
if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
|
|
280
284
|
await this.cache.clear();
|
|
281
285
|
}
|
|
@@ -286,6 +290,7 @@ class FieldsService {
|
|
|
286
290
|
if (this.accountability && this.accountability.admin !== true) {
|
|
287
291
|
throw new exceptions_1.ForbiddenException();
|
|
288
292
|
}
|
|
293
|
+
const runPostColumnChange = await this.helpers.schema.preColumnChange();
|
|
289
294
|
try {
|
|
290
295
|
const hookAdjustedField = await emitter_1.default.emitFilter(`fields.update`, field, {
|
|
291
296
|
keys: [field.field],
|
|
@@ -341,6 +346,9 @@ class FieldsService {
|
|
|
341
346
|
return field.field;
|
|
342
347
|
}
|
|
343
348
|
finally {
|
|
349
|
+
if (runPostColumnChange) {
|
|
350
|
+
await this.helpers.schema.postColumnChange();
|
|
351
|
+
}
|
|
344
352
|
if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
|
|
345
353
|
await this.cache.clear();
|
|
346
354
|
}
|
|
@@ -351,7 +359,7 @@ class FieldsService {
|
|
|
351
359
|
if (this.accountability && this.accountability.admin !== true) {
|
|
352
360
|
throw new exceptions_1.ForbiddenException();
|
|
353
361
|
}
|
|
354
|
-
const
|
|
362
|
+
const runPostColumnChange = await this.helpers.schema.preColumnChange();
|
|
355
363
|
try {
|
|
356
364
|
await emitter_1.default.emitFilter('fields.delete', [field], {
|
|
357
365
|
collection: collection,
|
|
@@ -440,8 +448,8 @@ class FieldsService {
|
|
|
440
448
|
});
|
|
441
449
|
}
|
|
442
450
|
finally {
|
|
443
|
-
if (
|
|
444
|
-
await this.helpers.schema.
|
|
451
|
+
if (runPostColumnChange) {
|
|
452
|
+
await this.helpers.schema.postColumnChange();
|
|
445
453
|
}
|
|
446
454
|
if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
|
|
447
455
|
await this.cache.clear();
|
package/dist/services/files.js
CHANGED
|
@@ -46,6 +46,7 @@ const utils_1 = require("@directus/shared/utils");
|
|
|
46
46
|
const items_1 = require("./items");
|
|
47
47
|
const net_1 = __importDefault(require("net"));
|
|
48
48
|
const os_1 = __importDefault(require("os"));
|
|
49
|
+
const encodeurl_1 = __importDefault(require("encodeurl"));
|
|
49
50
|
const lookupDNS = (0, util_1.promisify)(dns_1.lookup);
|
|
50
51
|
class FilesService extends items_1.ItemsService {
|
|
51
52
|
constructor(options) {
|
|
@@ -229,8 +230,7 @@ class FilesService extends items_1.ItemsService {
|
|
|
229
230
|
}
|
|
230
231
|
let fileResponse;
|
|
231
232
|
try {
|
|
232
|
-
|
|
233
|
-
fileResponse = await axios_1.default.get(shouldEncode ? encodeURI(importURL) : importURL, {
|
|
233
|
+
fileResponse = await axios_1.default.get((0, encodeurl_1.default)(importURL), {
|
|
234
234
|
responseType: 'stream',
|
|
235
235
|
});
|
|
236
236
|
}
|
|
@@ -293,41 +293,43 @@ class GraphQLService {
|
|
|
293
293
|
return obj[field.field];
|
|
294
294
|
},
|
|
295
295
|
};
|
|
296
|
-
if (
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
296
|
+
if (action === 'read') {
|
|
297
|
+
if (field.type === 'date') {
|
|
298
|
+
acc[`${field.field}_func`] = {
|
|
299
|
+
type: DateFunctions,
|
|
300
|
+
resolve: (obj) => {
|
|
301
|
+
const funcFields = Object.keys(DateFunctions.getFields()).map((key) => `${field.field}_${key}`);
|
|
302
|
+
return (0, lodash_1.mapKeys)((0, lodash_1.pick)(obj, funcFields), (_value, key) => key.substring(field.field.length + 1));
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
if (field.type === 'time') {
|
|
307
|
+
acc[`${field.field}_func`] = {
|
|
308
|
+
type: TimeFunctions,
|
|
309
|
+
resolve: (obj) => {
|
|
310
|
+
const funcFields = Object.keys(TimeFunctions.getFields()).map((key) => `${field.field}_${key}`);
|
|
311
|
+
return (0, lodash_1.mapKeys)((0, lodash_1.pick)(obj, funcFields), (_value, key) => key.substring(field.field.length + 1));
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
if (field.type === 'dateTime' || field.type === 'timestamp') {
|
|
316
|
+
acc[`${field.field}_func`] = {
|
|
317
|
+
type: DateTimeFunctions,
|
|
318
|
+
resolve: (obj) => {
|
|
319
|
+
const funcFields = Object.keys(DateTimeFunctions.getFields()).map((key) => `${field.field}_${key}`);
|
|
320
|
+
return (0, lodash_1.mapKeys)((0, lodash_1.pick)(obj, funcFields), (_value, key) => key.substring(field.field.length + 1));
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (field.type === 'json' || field.type === 'alias') {
|
|
325
|
+
acc[`${field.field}_func`] = {
|
|
326
|
+
type: CountFunctions,
|
|
327
|
+
resolve: (obj) => {
|
|
328
|
+
const funcFields = Object.keys(CountFunctions.getFields()).map((key) => `${field.field}_${key}`);
|
|
329
|
+
return (0, lodash_1.mapKeys)((0, lodash_1.pick)(obj, funcFields), (_value, key) => key.substring(field.field.length + 1));
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
}
|
|
331
333
|
}
|
|
332
334
|
return acc;
|
|
333
335
|
}, {}),
|
|
@@ -1615,6 +1617,7 @@ class GraphQLService {
|
|
|
1615
1617
|
const accountability = {
|
|
1616
1618
|
ip: req === null || req === void 0 ? void 0 : req.ip,
|
|
1617
1619
|
userAgent: req === null || req === void 0 ? void 0 : req.get('user-agent'),
|
|
1620
|
+
origin: req === null || req === void 0 ? void 0 : req.get('origin'),
|
|
1618
1621
|
role: null,
|
|
1619
1622
|
};
|
|
1620
1623
|
const authenticationService = new authentication_1.AuthenticationService({
|
|
@@ -1649,6 +1652,7 @@ class GraphQLService {
|
|
|
1649
1652
|
const accountability = {
|
|
1650
1653
|
ip: req === null || req === void 0 ? void 0 : req.ip,
|
|
1651
1654
|
userAgent: req === null || req === void 0 ? void 0 : req.get('user-agent'),
|
|
1655
|
+
origin: req === null || req === void 0 ? void 0 : req.get('origin'),
|
|
1652
1656
|
role: null,
|
|
1653
1657
|
};
|
|
1654
1658
|
const authenticationService = new authentication_1.AuthenticationService({
|
|
@@ -1685,6 +1689,7 @@ class GraphQLService {
|
|
|
1685
1689
|
const accountability = {
|
|
1686
1690
|
ip: req === null || req === void 0 ? void 0 : req.ip,
|
|
1687
1691
|
userAgent: req === null || req === void 0 ? void 0 : req.get('user-agent'),
|
|
1692
|
+
origin: req === null || req === void 0 ? void 0 : req.get('origin'),
|
|
1688
1693
|
role: null,
|
|
1689
1694
|
};
|
|
1690
1695
|
const authenticationService = new authentication_1.AuthenticationService({
|
|
@@ -1709,6 +1714,7 @@ class GraphQLService {
|
|
|
1709
1714
|
const accountability = {
|
|
1710
1715
|
ip: req === null || req === void 0 ? void 0 : req.ip,
|
|
1711
1716
|
userAgent: req === null || req === void 0 ? void 0 : req.get('user-agent'),
|
|
1717
|
+
origin: req === null || req === void 0 ? void 0 : req.get('origin'),
|
|
1712
1718
|
role: null,
|
|
1713
1719
|
};
|
|
1714
1720
|
const service = new users_1.UsersService({ accountability, schema: this.schema });
|
|
@@ -1733,6 +1739,7 @@ class GraphQLService {
|
|
|
1733
1739
|
const accountability = {
|
|
1734
1740
|
ip: req === null || req === void 0 ? void 0 : req.ip,
|
|
1735
1741
|
userAgent: req === null || req === void 0 ? void 0 : req.get('user-agent'),
|
|
1742
|
+
origin: req === null || req === void 0 ? void 0 : req.get('origin'),
|
|
1736
1743
|
role: null,
|
|
1737
1744
|
};
|
|
1738
1745
|
const service = new users_1.UsersService({ accountability, schema: this.schema });
|
|
@@ -2303,7 +2310,7 @@ class GraphQLService {
|
|
|
2303
2310
|
comment: (0, graphql_1.GraphQLNonNull)(graphql_1.GraphQLString),
|
|
2304
2311
|
},
|
|
2305
2312
|
resolve: async (_, args, __, info) => {
|
|
2306
|
-
var _a, _b, _c, _d, _e;
|
|
2313
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2307
2314
|
const service = new activity_1.ActivityService({
|
|
2308
2315
|
accountability: this.accountability,
|
|
2309
2316
|
schema: this.schema,
|
|
@@ -2314,9 +2321,10 @@ class GraphQLService {
|
|
|
2314
2321
|
user: (_a = this.accountability) === null || _a === void 0 ? void 0 : _a.user,
|
|
2315
2322
|
ip: (_b = this.accountability) === null || _b === void 0 ? void 0 : _b.ip,
|
|
2316
2323
|
user_agent: (_c = this.accountability) === null || _c === void 0 ? void 0 : _c.userAgent,
|
|
2324
|
+
origin: (_d = this.accountability) === null || _d === void 0 ? void 0 : _d.origin,
|
|
2317
2325
|
});
|
|
2318
2326
|
if ('directus_activity' in ReadCollectionTypes) {
|
|
2319
|
-
const selections = this.replaceFragmentsInSelections((
|
|
2327
|
+
const selections = this.replaceFragmentsInSelections((_f = (_e = info.fieldNodes[0]) === null || _e === void 0 ? void 0 : _e.selectionSet) === null || _f === void 0 ? void 0 : _f.selections, info.fragments);
|
|
2320
2328
|
const query = this.getQuery(args, selections || [], info.variableValues);
|
|
2321
2329
|
return await service.readOne(primaryKey, query);
|
|
2322
2330
|
}
|
package/dist/services/items.js
CHANGED
|
@@ -130,6 +130,7 @@ class ItemsService {
|
|
|
130
130
|
collection: this.collection,
|
|
131
131
|
ip: this.accountability.ip,
|
|
132
132
|
user_agent: this.accountability.userAgent,
|
|
133
|
+
origin: this.accountability.origin,
|
|
133
134
|
item: primaryKey,
|
|
134
135
|
});
|
|
135
136
|
// If revisions are tracked, create revisions record
|
|
@@ -214,7 +215,16 @@ class ItemsService {
|
|
|
214
215
|
* Get items by query
|
|
215
216
|
*/
|
|
216
217
|
async readByQuery(query, opts) {
|
|
217
|
-
|
|
218
|
+
const updatedQuery = (opts === null || opts === void 0 ? void 0 : opts.emitEvents) !== false
|
|
219
|
+
? await emitter_1.default.emitFilter(`${this.eventScope}.query`, query, {
|
|
220
|
+
collection: this.collection,
|
|
221
|
+
}, {
|
|
222
|
+
database: this.knex,
|
|
223
|
+
schema: this.schema,
|
|
224
|
+
accountability: this.accountability,
|
|
225
|
+
})
|
|
226
|
+
: query;
|
|
227
|
+
let ast = await (0, get_ast_from_query_1.default)(this.collection, updatedQuery, this.schema, {
|
|
218
228
|
accountability: this.accountability,
|
|
219
229
|
// By setting the permissions action, you can read items using the permissions for another
|
|
220
230
|
// operation's permissions. This is used to dynamically check if you have update/delete
|
|
@@ -240,7 +250,7 @@ class ItemsService {
|
|
|
240
250
|
}
|
|
241
251
|
const filteredRecords = (opts === null || opts === void 0 ? void 0 : opts.emitEvents) !== false
|
|
242
252
|
? await emitter_1.default.emitFilter(this.eventScope === 'items' ? ['items.read', `${this.collection}.items.read`] : `${this.eventScope}.read`, records, {
|
|
243
|
-
query,
|
|
253
|
+
query: updatedQuery,
|
|
244
254
|
collection: this.collection,
|
|
245
255
|
}, {
|
|
246
256
|
database: this.knex,
|
|
@@ -251,7 +261,7 @@ class ItemsService {
|
|
|
251
261
|
if ((opts === null || opts === void 0 ? void 0 : opts.emitEvents) !== false) {
|
|
252
262
|
emitter_1.default.emitAction(this.eventScope === 'items' ? ['items.read', `${this.collection}.items.read`] : `${this.eventScope}.read`, {
|
|
253
263
|
payload: filteredRecords,
|
|
254
|
-
query,
|
|
264
|
+
query: updatedQuery,
|
|
255
265
|
collection: this.collection,
|
|
256
266
|
}, {
|
|
257
267
|
database: this.knex || (0, database_1.default)(),
|
|
@@ -406,6 +416,7 @@ class ItemsService {
|
|
|
406
416
|
collection: this.collection,
|
|
407
417
|
ip: this.accountability.ip,
|
|
408
418
|
user_agent: this.accountability.userAgent,
|
|
419
|
+
origin: this.accountability.origin,
|
|
409
420
|
item: key,
|
|
410
421
|
})));
|
|
411
422
|
if (this.schema.collections[this.collection].accountability === 'all') {
|
|
@@ -573,6 +584,7 @@ class ItemsService {
|
|
|
573
584
|
collection: this.collection,
|
|
574
585
|
ip: this.accountability.ip,
|
|
575
586
|
user_agent: this.accountability.userAgent,
|
|
587
|
+
origin: this.accountability.origin,
|
|
576
588
|
item: key,
|
|
577
589
|
})));
|
|
578
590
|
}
|
package/dist/services/payload.js
CHANGED
|
@@ -538,10 +538,34 @@ class PayloadService {
|
|
|
538
538
|
if (error)
|
|
539
539
|
throw new exceptions_1.InvalidPayloadException(`Invalid one-to-many update structure: ${error.message}`);
|
|
540
540
|
if (alterations.create) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
541
|
+
const sortField = relation.meta.sort_field;
|
|
542
|
+
let createPayload;
|
|
543
|
+
if (sortField !== null) {
|
|
544
|
+
const highestOrderNumber = await this.knex
|
|
545
|
+
.from(relation.collection)
|
|
546
|
+
.where({ [relation.field]: parent })
|
|
547
|
+
.whereNotNull(sortField)
|
|
548
|
+
.max(sortField)
|
|
549
|
+
.first();
|
|
550
|
+
createPayload = alterations.create.map((item, index) => {
|
|
551
|
+
const record = (0, lodash_1.cloneDeep)(item);
|
|
552
|
+
// add sort field value if it is not supplied in the item
|
|
553
|
+
if (parent !== null && record[sortField] === undefined) {
|
|
554
|
+
record[sortField] = (highestOrderNumber === null || highestOrderNumber === void 0 ? void 0 : highestOrderNumber.max) ? highestOrderNumber.max + index + 1 : index + 1;
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
...record,
|
|
558
|
+
[relation.field]: parent || payload[currentPrimaryKeyField],
|
|
559
|
+
};
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
createPayload = alterations.create.map((item) => ({
|
|
564
|
+
...item,
|
|
565
|
+
[relation.field]: parent || payload[currentPrimaryKeyField],
|
|
566
|
+
}));
|
|
567
|
+
}
|
|
568
|
+
await itemsService.createMany(createPayload, {
|
|
545
569
|
onRevisionCreate: (pk) => revisions.push(pk),
|
|
546
570
|
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
547
571
|
emitEvents: opts === null || opts === void 0 ? void 0 : opts.emitEvents,
|
|
@@ -5,6 +5,7 @@ import { PermissionsService } from './permissions';
|
|
|
5
5
|
import SchemaInspector from '@directus/schema';
|
|
6
6
|
import Keyv from 'keyv';
|
|
7
7
|
import { AbstractServiceOptions } from '../types';
|
|
8
|
+
import { Helpers } from '../database/helpers';
|
|
8
9
|
export declare class RelationsService {
|
|
9
10
|
knex: Knex;
|
|
10
11
|
permissionsService: PermissionsService;
|
|
@@ -13,6 +14,7 @@ export declare class RelationsService {
|
|
|
13
14
|
schema: SchemaOverview;
|
|
14
15
|
relationsItemService: ItemsService<RelationMeta>;
|
|
15
16
|
systemCache: Keyv<any>;
|
|
17
|
+
helpers: Helpers;
|
|
16
18
|
constructor(options: AbstractServiceOptions);
|
|
17
19
|
readAll(collection?: string, opts?: QueryOptions): Promise<Relation[]>;
|
|
18
20
|
readOne(collection: string, field: string): Promise<Relation>;
|
|
@@ -36,6 +36,7 @@ const schema_1 = __importDefault(require("@directus/schema"));
|
|
|
36
36
|
const database_1 = __importStar(require("../database"));
|
|
37
37
|
const get_default_index_name_1 = require("../utils/get-default-index-name");
|
|
38
38
|
const cache_1 = require("../cache");
|
|
39
|
+
const helpers_1 = require("../database/helpers");
|
|
39
40
|
class RelationsService {
|
|
40
41
|
constructor(options) {
|
|
41
42
|
this.knex = options.knex || (0, database_1.default)();
|
|
@@ -51,6 +52,7 @@ class RelationsService {
|
|
|
51
52
|
// happens in `filterForbidden` down below
|
|
52
53
|
});
|
|
53
54
|
this.systemCache = (0, cache_1.getCache)().systemCache;
|
|
55
|
+
this.helpers = (0, helpers_1.getHelpers)(this.knex);
|
|
54
56
|
}
|
|
55
57
|
async readAll(collection, opts) {
|
|
56
58
|
if (this.accountability && this.accountability.admin !== true && this.hasReadAccess === false) {
|
|
@@ -150,6 +152,7 @@ class RelationsService {
|
|
|
150
152
|
if (existingRelation) {
|
|
151
153
|
throw new exceptions_1.InvalidPayloadException(`Field "${relation.field}" in collection "${relation.collection}" already has an associated relationship`);
|
|
152
154
|
}
|
|
155
|
+
const runPostColumnChange = await this.helpers.schema.preColumnChange();
|
|
153
156
|
try {
|
|
154
157
|
const metaRow = {
|
|
155
158
|
...(relation.meta || {}),
|
|
@@ -182,6 +185,9 @@ class RelationsService {
|
|
|
182
185
|
});
|
|
183
186
|
}
|
|
184
187
|
finally {
|
|
188
|
+
if (runPostColumnChange) {
|
|
189
|
+
await this.helpers.schema.postColumnChange();
|
|
190
|
+
}
|
|
185
191
|
await (0, cache_1.clearSystemCache)();
|
|
186
192
|
}
|
|
187
193
|
}
|
|
@@ -204,6 +210,7 @@ class RelationsService {
|
|
|
204
210
|
if (!existingRelation) {
|
|
205
211
|
throw new exceptions_1.InvalidPayloadException(`Field "${field}" in collection "${collection}" doesn't have a relationship.`);
|
|
206
212
|
}
|
|
213
|
+
const runPostColumnChange = await this.helpers.schema.preColumnChange();
|
|
207
214
|
try {
|
|
208
215
|
await this.knex.transaction(async (trx) => {
|
|
209
216
|
if (existingRelation.related_collection) {
|
|
@@ -247,6 +254,9 @@ class RelationsService {
|
|
|
247
254
|
});
|
|
248
255
|
}
|
|
249
256
|
finally {
|
|
257
|
+
if (runPostColumnChange) {
|
|
258
|
+
await this.helpers.schema.postColumnChange();
|
|
259
|
+
}
|
|
250
260
|
await (0, cache_1.clearSystemCache)();
|
|
251
261
|
}
|
|
252
262
|
}
|
|
@@ -267,6 +277,7 @@ class RelationsService {
|
|
|
267
277
|
if (!existingRelation) {
|
|
268
278
|
throw new exceptions_1.InvalidPayloadException(`Field "${field}" in collection "${collection}" doesn't have a relationship.`);
|
|
269
279
|
}
|
|
280
|
+
const runPostColumnChange = await this.helpers.schema.preColumnChange();
|
|
270
281
|
try {
|
|
271
282
|
await this.knex.transaction(async (trx) => {
|
|
272
283
|
var _a;
|
|
@@ -284,6 +295,9 @@ class RelationsService {
|
|
|
284
295
|
});
|
|
285
296
|
}
|
|
286
297
|
finally {
|
|
298
|
+
if (runPostColumnChange) {
|
|
299
|
+
await this.helpers.schema.postColumnChange();
|
|
300
|
+
}
|
|
287
301
|
await (0, cache_1.clearSystemCache)();
|
|
288
302
|
}
|
|
289
303
|
}
|