@directus/api 23.0.0 → 23.1.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 +2 -0
- package/dist/controllers/activity.js +30 -27
- package/dist/controllers/assets.js +1 -1
- package/dist/controllers/comments.d.ts +2 -0
- package/dist/controllers/comments.js +153 -0
- package/dist/controllers/permissions.js +1 -1
- package/dist/controllers/users.js +4 -8
- package/dist/controllers/versions.js +10 -5
- package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +1 -1
- package/dist/database/helpers/schema/dialects/cockroachdb.js +2 -2
- package/dist/database/helpers/schema/dialects/mssql.d.ts +1 -1
- package/dist/database/helpers/schema/dialects/mssql.js +1 -1
- package/dist/database/helpers/schema/dialects/mysql.d.ts +1 -1
- package/dist/database/helpers/schema/dialects/mysql.js +2 -2
- package/dist/database/helpers/schema/dialects/oracle.d.ts +1 -1
- package/dist/database/helpers/schema/dialects/oracle.js +1 -1
- package/dist/database/helpers/schema/dialects/postgres.d.ts +1 -1
- package/dist/database/helpers/schema/dialects/postgres.js +3 -3
- package/dist/database/helpers/schema/types.d.ts +1 -1
- package/dist/database/helpers/schema/types.js +1 -1
- package/dist/database/index.js +3 -0
- package/dist/database/migrations/20240806A-permissions-policies.d.ts +0 -3
- package/dist/database/migrations/20240806A-permissions-policies.js +8 -94
- package/dist/database/migrations/20240909A-separate-comments.d.ts +3 -0
- package/dist/database/migrations/20240909A-separate-comments.js +65 -0
- package/dist/database/migrations/20240909B-consolidate-content-versioning.d.ts +3 -0
- package/dist/database/migrations/20240909B-consolidate-content-versioning.js +10 -0
- package/dist/database/run-ast/lib/get-db-query.d.ts +12 -2
- package/dist/database/run-ast/lib/get-db-query.js +3 -3
- package/dist/database/run-ast/modules/fetch-permitted-ast-root-fields.d.ts +15 -0
- package/dist/database/run-ast/modules/fetch-permitted-ast-root-fields.js +29 -0
- package/dist/database/run-ast/run-ast.js +8 -1
- package/dist/database/run-ast/utils/apply-case-when.js +1 -1
- package/dist/database/run-ast/utils/get-column-pre-processor.d.ts +1 -1
- package/dist/database/run-ast/utils/get-column-pre-processor.js +10 -2
- package/dist/permissions/lib/fetch-permissions.d.ts +1 -1
- package/dist/permissions/lib/fetch-permissions.js +4 -1
- package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +2 -1
- package/dist/permissions/modules/validate-access/lib/validate-item-access.js +18 -13
- package/dist/permissions/modules/validate-access/validate-access.d.ts +1 -0
- package/dist/permissions/modules/validate-access/validate-access.js +14 -1
- package/dist/permissions/utils/fetch-share-info.d.ts +12 -0
- package/dist/permissions/utils/fetch-share-info.js +9 -0
- package/dist/permissions/utils/get-permissions-for-share.d.ts +4 -0
- package/dist/permissions/utils/get-permissions-for-share.js +182 -0
- package/dist/permissions/utils/merge-permissions.d.ts +9 -0
- package/dist/permissions/utils/merge-permissions.js +118 -0
- package/dist/services/activity.d.ts +1 -7
- package/dist/services/activity.js +0 -103
- package/dist/services/assets.js +5 -4
- package/dist/services/authentication.js +1 -10
- package/dist/services/collections.js +6 -4
- package/dist/services/comments.d.ts +31 -0
- package/dist/services/comments.js +378 -0
- package/dist/services/graphql/index.js +17 -16
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/items.js +3 -1
- package/dist/services/mail/index.d.ts +2 -1
- package/dist/services/mail/index.js +4 -1
- package/dist/services/payload.js +15 -14
- package/dist/services/shares.d.ts +2 -0
- package/dist/services/shares.js +11 -9
- package/dist/services/users.js +1 -0
- package/dist/services/versions.js +59 -44
- package/dist/types/auth.d.ts +0 -7
- package/dist/utils/apply-diff.js +5 -6
- package/dist/utils/get-accountability-for-token.js +0 -2
- package/dist/utils/get-service.js +3 -1
- package/dist/utils/sanitize-schema.d.ts +1 -1
- package/dist/utils/sanitize-schema.js +2 -0
- package/package.json +52 -52
package/dist/app.js
CHANGED
|
@@ -14,6 +14,7 @@ import accessRouter from './controllers/access.js';
|
|
|
14
14
|
import assetsRouter from './controllers/assets.js';
|
|
15
15
|
import authRouter from './controllers/auth.js';
|
|
16
16
|
import collectionsRouter from './controllers/collections.js';
|
|
17
|
+
import commentsRouter from './controllers/comments.js';
|
|
17
18
|
import dashboardsRouter from './controllers/dashboards.js';
|
|
18
19
|
import extensionsRouter from './controllers/extensions.js';
|
|
19
20
|
import fieldsRouter from './controllers/fields.js';
|
|
@@ -209,6 +210,7 @@ export default async function createApp() {
|
|
|
209
210
|
app.use('/access', accessRouter);
|
|
210
211
|
app.use('/assets', assetsRouter);
|
|
211
212
|
app.use('/collections', collectionsRouter);
|
|
213
|
+
app.use('/comments', commentsRouter);
|
|
212
214
|
app.use('/dashboards', dashboardsRouter);
|
|
213
215
|
app.use('/extensions', extensionsRouter);
|
|
214
216
|
app.use('/fields', fieldsRouter);
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isDirectusError } from '@directus/errors';
|
|
1
|
+
import { ErrorCode, InvalidPayloadError, isDirectusError } from '@directus/errors';
|
|
3
2
|
import express from 'express';
|
|
4
3
|
import Joi from 'joi';
|
|
5
|
-
import { ErrorCode, ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
6
4
|
import { respond } from '../middleware/respond.js';
|
|
7
5
|
import useCollection from '../middleware/use-collection.js';
|
|
8
6
|
import { validateBatch } from '../middleware/validate-batch.js';
|
|
9
7
|
import { ActivityService } from '../services/activity.js';
|
|
8
|
+
import { CommentsService } from '../services/comments.js';
|
|
10
9
|
import { MetaService } from '../services/meta.js';
|
|
11
10
|
import asyncHandler from '../utils/async-handler.js';
|
|
12
|
-
import { getIPFromReq } from '../utils/get-ip-from-req.js';
|
|
13
11
|
const router = express.Router();
|
|
14
12
|
router.use(useCollection('directus_activity'));
|
|
15
13
|
const readHandler = asyncHandler(async (req, res, next) => {
|
|
@@ -22,6 +20,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
|
|
|
22
20
|
schema: req.schema,
|
|
23
21
|
});
|
|
24
22
|
let result;
|
|
23
|
+
let isComment;
|
|
25
24
|
if (req.singleton) {
|
|
26
25
|
result = await service.readSingleton(req.sanitizedQuery);
|
|
27
26
|
}
|
|
@@ -29,9 +28,24 @@ const readHandler = asyncHandler(async (req, res, next) => {
|
|
|
29
28
|
result = await service.readMany(req.body.keys, req.sanitizedQuery);
|
|
30
29
|
}
|
|
31
30
|
else {
|
|
32
|
-
|
|
31
|
+
const sanitizedFilter = req.sanitizedQuery.filter;
|
|
32
|
+
if (sanitizedFilter &&
|
|
33
|
+
'_and' in sanitizedFilter &&
|
|
34
|
+
Array.isArray(sanitizedFilter['_and']) &&
|
|
35
|
+
sanitizedFilter['_and'].find((andItem) => 'action' in andItem && '_eq' in andItem['action'] && andItem['action']['_eq'] === 'comment')) {
|
|
36
|
+
const commentsService = new CommentsService({
|
|
37
|
+
accountability: req.accountability,
|
|
38
|
+
schema: req.schema,
|
|
39
|
+
serviceOrigin: 'activity',
|
|
40
|
+
});
|
|
41
|
+
result = await commentsService.readByQuery(req.sanitizedQuery);
|
|
42
|
+
isComment = true;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
result = await service.readByQuery(req.sanitizedQuery);
|
|
46
|
+
}
|
|
33
47
|
}
|
|
34
|
-
const meta = await metaService.getMetaForQuery('directus_activity', req.sanitizedQuery);
|
|
48
|
+
const meta = await metaService.getMetaForQuery(isComment ? 'directus_comments' : 'directus_activity', req.sanitizedQuery);
|
|
35
49
|
res.locals['payload'] = {
|
|
36
50
|
data: result,
|
|
37
51
|
meta,
|
|
@@ -57,22 +71,16 @@ const createCommentSchema = Joi.object({
|
|
|
57
71
|
item: [Joi.number().required(), Joi.string().required()],
|
|
58
72
|
});
|
|
59
73
|
router.post('/comment', asyncHandler(async (req, res, next) => {
|
|
60
|
-
const service = new
|
|
74
|
+
const service = new CommentsService({
|
|
61
75
|
accountability: req.accountability,
|
|
62
76
|
schema: req.schema,
|
|
77
|
+
serviceOrigin: 'activity',
|
|
63
78
|
});
|
|
64
79
|
const { error } = createCommentSchema.validate(req.body);
|
|
65
80
|
if (error) {
|
|
66
81
|
throw new InvalidPayloadError({ reason: error.message });
|
|
67
82
|
}
|
|
68
|
-
const primaryKey = await service.createOne(
|
|
69
|
-
...req.body,
|
|
70
|
-
action: Action.COMMENT,
|
|
71
|
-
user: req.accountability?.user,
|
|
72
|
-
ip: getIPFromReq(req),
|
|
73
|
-
user_agent: req.accountability?.userAgent,
|
|
74
|
-
origin: req.get('origin'),
|
|
75
|
-
});
|
|
83
|
+
const primaryKey = await service.createOne(req.body);
|
|
76
84
|
try {
|
|
77
85
|
const record = await service.readOne(primaryKey, req.sanitizedQuery);
|
|
78
86
|
res.locals['payload'] = {
|
|
@@ -91,17 +99,18 @@ const updateCommentSchema = Joi.object({
|
|
|
91
99
|
comment: Joi.string().required(),
|
|
92
100
|
});
|
|
93
101
|
router.patch('/comment/:pk', asyncHandler(async (req, res, next) => {
|
|
94
|
-
const
|
|
102
|
+
const commentsService = new CommentsService({
|
|
95
103
|
accountability: req.accountability,
|
|
96
104
|
schema: req.schema,
|
|
105
|
+
serviceOrigin: 'activity',
|
|
97
106
|
});
|
|
98
107
|
const { error } = updateCommentSchema.validate(req.body);
|
|
99
108
|
if (error) {
|
|
100
109
|
throw new InvalidPayloadError({ reason: error.message });
|
|
101
110
|
}
|
|
102
|
-
const primaryKey = await
|
|
111
|
+
const primaryKey = await commentsService.updateOne(req.params['pk'], req.body);
|
|
103
112
|
try {
|
|
104
|
-
const record = await
|
|
113
|
+
const record = await commentsService.readOne(primaryKey, req.sanitizedQuery);
|
|
105
114
|
res.locals['payload'] = {
|
|
106
115
|
data: record || null,
|
|
107
116
|
};
|
|
@@ -115,18 +124,12 @@ router.patch('/comment/:pk', asyncHandler(async (req, res, next) => {
|
|
|
115
124
|
return next();
|
|
116
125
|
}), respond);
|
|
117
126
|
router.delete('/comment/:pk', asyncHandler(async (req, _res, next) => {
|
|
118
|
-
const
|
|
127
|
+
const commentsService = new CommentsService({
|
|
119
128
|
accountability: req.accountability,
|
|
120
129
|
schema: req.schema,
|
|
130
|
+
serviceOrigin: 'activity',
|
|
121
131
|
});
|
|
122
|
-
|
|
123
|
-
schema: req.schema,
|
|
124
|
-
});
|
|
125
|
-
const item = await adminService.readOne(req.params['pk'], { fields: ['action'] });
|
|
126
|
-
if (!item || item['action'] !== Action.COMMENT) {
|
|
127
|
-
throw new ForbiddenError();
|
|
128
|
-
}
|
|
129
|
-
await service.deleteOne(req.params['pk']);
|
|
132
|
+
await commentsService.deleteOne(req.params['pk']);
|
|
130
133
|
return next();
|
|
131
134
|
}), respond);
|
|
132
135
|
export default router;
|
|
@@ -104,7 +104,7 @@ asyncHandler(async (req, res, next) => {
|
|
|
104
104
|
return helmet.contentSecurityPolicy(merge({
|
|
105
105
|
useDefaults: false,
|
|
106
106
|
directives: {
|
|
107
|
-
defaultSrc: ['none'],
|
|
107
|
+
defaultSrc: [`'none'`],
|
|
108
108
|
},
|
|
109
109
|
}, getConfigFromEnv('ASSETS_CONTENT_SECURITY_POLICY')))(req, res, next);
|
|
110
110
|
}),
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { ErrorCode, isDirectusError } from '@directus/errors';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import { respond } from '../middleware/respond.js';
|
|
4
|
+
import useCollection from '../middleware/use-collection.js';
|
|
5
|
+
import { validateBatch } from '../middleware/validate-batch.js';
|
|
6
|
+
import { CommentsService } from '../services/comments.js';
|
|
7
|
+
import { MetaService } from '../services/meta.js';
|
|
8
|
+
import asyncHandler from '../utils/async-handler.js';
|
|
9
|
+
import { sanitizeQuery } from '../utils/sanitize-query.js';
|
|
10
|
+
const router = express.Router();
|
|
11
|
+
router.use(useCollection('directus_comments'));
|
|
12
|
+
router.post('/', asyncHandler(async (req, res, next) => {
|
|
13
|
+
const service = new CommentsService({
|
|
14
|
+
accountability: req.accountability,
|
|
15
|
+
schema: req.schema,
|
|
16
|
+
serviceOrigin: 'comments',
|
|
17
|
+
});
|
|
18
|
+
const savedKeys = [];
|
|
19
|
+
if (Array.isArray(req.body)) {
|
|
20
|
+
const keys = await service.createMany(req.body);
|
|
21
|
+
savedKeys.push(...keys);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const key = await service.createOne(req.body);
|
|
25
|
+
savedKeys.push(key);
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
if (Array.isArray(req.body)) {
|
|
29
|
+
const records = await service.readMany(savedKeys, req.sanitizedQuery);
|
|
30
|
+
res.locals['payload'] = { data: records };
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const record = await service.readOne(savedKeys[0], req.sanitizedQuery);
|
|
34
|
+
res.locals['payload'] = { data: record };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (isDirectusError(error, ErrorCode.Forbidden)) {
|
|
39
|
+
return next();
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
return next();
|
|
44
|
+
}), respond);
|
|
45
|
+
const readHandler = asyncHandler(async (req, res, next) => {
|
|
46
|
+
const service = new CommentsService({
|
|
47
|
+
accountability: req.accountability,
|
|
48
|
+
schema: req.schema,
|
|
49
|
+
serviceOrigin: 'comments',
|
|
50
|
+
});
|
|
51
|
+
const metaService = new MetaService({
|
|
52
|
+
accountability: req.accountability,
|
|
53
|
+
schema: req.schema,
|
|
54
|
+
});
|
|
55
|
+
let result;
|
|
56
|
+
if (req.body.keys) {
|
|
57
|
+
result = await service.readMany(req.body.keys, req.sanitizedQuery);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
result = await service.readByQuery(req.sanitizedQuery);
|
|
61
|
+
}
|
|
62
|
+
const meta = await metaService.getMetaForQuery('directus_comments', req.sanitizedQuery);
|
|
63
|
+
res.locals['payload'] = { data: result, meta };
|
|
64
|
+
return next();
|
|
65
|
+
});
|
|
66
|
+
router.get('/', validateBatch('read'), readHandler, respond);
|
|
67
|
+
router.search('/', validateBatch('read'), readHandler, respond);
|
|
68
|
+
router.get('/:pk', asyncHandler(async (req, res, next) => {
|
|
69
|
+
const service = new CommentsService({
|
|
70
|
+
accountability: req.accountability,
|
|
71
|
+
schema: req.schema,
|
|
72
|
+
serviceOrigin: 'comments',
|
|
73
|
+
});
|
|
74
|
+
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
|
|
75
|
+
res.locals['payload'] = { data: record || null };
|
|
76
|
+
return next();
|
|
77
|
+
}), respond);
|
|
78
|
+
router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) => {
|
|
79
|
+
const service = new CommentsService({
|
|
80
|
+
accountability: req.accountability,
|
|
81
|
+
schema: req.schema,
|
|
82
|
+
serviceOrigin: 'comments',
|
|
83
|
+
});
|
|
84
|
+
let keys = [];
|
|
85
|
+
if (Array.isArray(req.body)) {
|
|
86
|
+
keys = await service.updateBatch(req.body);
|
|
87
|
+
}
|
|
88
|
+
else if (req.body.keys) {
|
|
89
|
+
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
93
|
+
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const result = await service.readMany(keys, req.sanitizedQuery);
|
|
97
|
+
res.locals['payload'] = { data: result };
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (isDirectusError(error, ErrorCode.Forbidden)) {
|
|
101
|
+
return next();
|
|
102
|
+
}
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
return next();
|
|
106
|
+
}), respond);
|
|
107
|
+
router.patch('/:pk', asyncHandler(async (req, res, next) => {
|
|
108
|
+
const service = new CommentsService({
|
|
109
|
+
accountability: req.accountability,
|
|
110
|
+
schema: req.schema,
|
|
111
|
+
serviceOrigin: 'comments',
|
|
112
|
+
});
|
|
113
|
+
const primaryKey = await service.updateOne(req.params['pk'], req.body);
|
|
114
|
+
try {
|
|
115
|
+
const record = await service.readOne(primaryKey, req.sanitizedQuery);
|
|
116
|
+
res.locals['payload'] = { data: record };
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (isDirectusError(error, ErrorCode.Forbidden)) {
|
|
120
|
+
return next();
|
|
121
|
+
}
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
return next();
|
|
125
|
+
}), respond);
|
|
126
|
+
router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next) => {
|
|
127
|
+
const service = new CommentsService({
|
|
128
|
+
accountability: req.accountability,
|
|
129
|
+
schema: req.schema,
|
|
130
|
+
serviceOrigin: 'comments',
|
|
131
|
+
});
|
|
132
|
+
if (Array.isArray(req.body)) {
|
|
133
|
+
await service.deleteMany(req.body);
|
|
134
|
+
}
|
|
135
|
+
else if (req.body.keys) {
|
|
136
|
+
await service.deleteMany(req.body.keys);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
140
|
+
await service.deleteByQuery(sanitizedQuery);
|
|
141
|
+
}
|
|
142
|
+
return next();
|
|
143
|
+
}), respond);
|
|
144
|
+
router.delete('/:pk', asyncHandler(async (req, _res, next) => {
|
|
145
|
+
const service = new CommentsService({
|
|
146
|
+
accountability: req.accountability,
|
|
147
|
+
schema: req.schema,
|
|
148
|
+
serviceOrigin: 'comments',
|
|
149
|
+
});
|
|
150
|
+
await service.deleteOne(req.params['pk']);
|
|
151
|
+
return next();
|
|
152
|
+
}), respond);
|
|
153
|
+
export default router;
|
|
@@ -72,7 +72,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
|
|
|
72
72
|
router.get('/', validateBatch('read'), readHandler, respond);
|
|
73
73
|
router.search('/', validateBatch('read'), readHandler, respond);
|
|
74
74
|
router.get('/me', asyncHandler(async (req, res, next) => {
|
|
75
|
-
if (!req.accountability?.user && !req.accountability?.role)
|
|
75
|
+
if (!req.accountability?.user && !req.accountability?.role && !req.accountability?.share)
|
|
76
76
|
throw new ForbiddenError();
|
|
77
77
|
const result = await fetchAccountabilityCollectionAccess(req.accountability, {
|
|
78
78
|
schema: req.schema,
|
|
@@ -62,16 +62,12 @@ const readHandler = asyncHandler(async (req, res, next) => {
|
|
|
62
62
|
router.get('/', validateBatch('read'), readHandler, respond);
|
|
63
63
|
router.search('/', validateBatch('read'), readHandler, respond);
|
|
64
64
|
router.get('/me', asyncHandler(async (req, res, next) => {
|
|
65
|
-
if (req.accountability?.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
id: req.accountability.role,
|
|
70
|
-
admin_access: false,
|
|
71
|
-
app_access: false,
|
|
65
|
+
if (req.accountability?.share) {
|
|
66
|
+
res.locals['payload'] = {
|
|
67
|
+
data: {
|
|
68
|
+
share: req.accountability?.share,
|
|
72
69
|
},
|
|
73
70
|
};
|
|
74
|
-
res.locals['payload'] = { data: user };
|
|
75
71
|
return next();
|
|
76
72
|
}
|
|
77
73
|
if (!req.accountability?.user) {
|
|
@@ -154,8 +154,14 @@ router.get('/:pk/compare', asyncHandler(async (req, res, next) => {
|
|
|
154
154
|
});
|
|
155
155
|
const version = await service.readOne(req.params['pk']);
|
|
156
156
|
const { outdated, mainHash } = await service.verifyHash(version['collection'], version['item'], version['hash']);
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
let current;
|
|
158
|
+
if (version['delta']) {
|
|
159
|
+
current = version['delta'];
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const saves = await service.getVersionSavesById(version['id']);
|
|
163
|
+
current = assign({}, ...saves);
|
|
164
|
+
}
|
|
159
165
|
const main = await service.getMainItem(version['collection'], version['item']);
|
|
160
166
|
res.locals['payload'] = { data: { outdated, mainHash, current, main } };
|
|
161
167
|
return next();
|
|
@@ -167,9 +173,8 @@ router.post('/:pk/save', asyncHandler(async (req, res, next) => {
|
|
|
167
173
|
});
|
|
168
174
|
const version = await service.readOne(req.params['pk']);
|
|
169
175
|
const mainItem = await service.getMainItem(version['collection'], version['item']);
|
|
170
|
-
await service.save(req.params['pk'], req.body);
|
|
171
|
-
const
|
|
172
|
-
const result = assign(mainItem, ...saves);
|
|
176
|
+
const updatedVersion = await service.save(req.params['pk'], req.body);
|
|
177
|
+
const result = assign(mainItem, updatedVersion);
|
|
173
178
|
res.locals['payload'] = { data: result || null };
|
|
174
179
|
return next();
|
|
175
180
|
}), respond);
|
|
@@ -7,5 +7,5 @@ export declare class SchemaHelperCockroachDb extends SchemaHelper {
|
|
|
7
7
|
constraintName(existingName: string): string;
|
|
8
8
|
getDatabaseSize(): Promise<number | null>;
|
|
9
9
|
preprocessBindings(queryParams: Sql): Sql;
|
|
10
|
-
addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[],
|
|
10
|
+
addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[], hasRelationalSort: boolean): void;
|
|
11
11
|
}
|
|
@@ -32,8 +32,8 @@ export class SchemaHelperCockroachDb extends SchemaHelper {
|
|
|
32
32
|
preprocessBindings(queryParams) {
|
|
33
33
|
return preprocessBindings(queryParams, { format: (index) => `$${index + 1}` });
|
|
34
34
|
}
|
|
35
|
-
addInnerSortFieldsToGroupBy(groupByFields, sortRecords,
|
|
36
|
-
if (
|
|
35
|
+
addInnerSortFieldsToGroupBy(groupByFields, sortRecords, hasRelationalSort) {
|
|
36
|
+
if (hasRelationalSort) {
|
|
37
37
|
/*
|
|
38
38
|
Cockroach allows aliases to be used in the GROUP BY clause and only needs columns in the GROUP BY clause that
|
|
39
39
|
are not functionally dependent on the primary key.
|
|
@@ -6,5 +6,5 @@ export declare class SchemaHelperMSSQL extends SchemaHelper {
|
|
|
6
6
|
formatUUID(uuid: string): string;
|
|
7
7
|
getDatabaseSize(): Promise<number | null>;
|
|
8
8
|
preprocessBindings(queryParams: Sql): Sql;
|
|
9
|
-
addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[],
|
|
9
|
+
addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[], _hasRelationalSort: boolean): void;
|
|
10
10
|
}
|
|
@@ -30,7 +30,7 @@ export class SchemaHelperMSSQL extends SchemaHelper {
|
|
|
30
30
|
preprocessBindings(queryParams) {
|
|
31
31
|
return preprocessBindings(queryParams, { format: (index) => `@p${index}` });
|
|
32
32
|
}
|
|
33
|
-
addInnerSortFieldsToGroupBy(groupByFields, sortRecords,
|
|
33
|
+
addInnerSortFieldsToGroupBy(groupByFields, sortRecords, _hasRelationalSort) {
|
|
34
34
|
/*
|
|
35
35
|
MSSQL requires all selected columns that are not aggregated over are to be present in the GROUP BY clause
|
|
36
36
|
|
|
@@ -3,5 +3,5 @@ import { SchemaHelper, type SortRecord } from '../types.js';
|
|
|
3
3
|
export declare class SchemaHelperMySQL extends SchemaHelper {
|
|
4
4
|
applyMultiRelationalSort(knex: Knex, dbQuery: Knex.QueryBuilder, table: string, primaryKey: string, orderByString: string, orderByFields: Knex.Raw[]): Knex.QueryBuilder;
|
|
5
5
|
getDatabaseSize(): Promise<number | null>;
|
|
6
|
-
addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[],
|
|
6
|
+
addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[], hasRelationalSort: boolean): void;
|
|
7
7
|
}
|
|
@@ -28,8 +28,8 @@ export class SchemaHelperMySQL extends SchemaHelper {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
addInnerSortFieldsToGroupBy(groupByFields, sortRecords,
|
|
32
|
-
if (
|
|
31
|
+
addInnerSortFieldsToGroupBy(groupByFields, sortRecords, hasRelationalSort) {
|
|
32
|
+
if (hasRelationalSort) {
|
|
33
33
|
/*
|
|
34
34
|
** MySQL **
|
|
35
35
|
|
|
@@ -10,5 +10,5 @@ export declare class SchemaHelperOracle extends SchemaHelper {
|
|
|
10
10
|
processFieldType(field: Field): Type;
|
|
11
11
|
getDatabaseSize(): Promise<number | null>;
|
|
12
12
|
preprocessBindings(queryParams: Sql): Sql;
|
|
13
|
-
addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[],
|
|
13
|
+
addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[], _hasRelationalSort: boolean): void;
|
|
14
14
|
}
|
|
@@ -42,7 +42,7 @@ export class SchemaHelperOracle extends SchemaHelper {
|
|
|
42
42
|
preprocessBindings(queryParams) {
|
|
43
43
|
return preprocessBindings(queryParams, { format: (index) => `:${index + 1}` });
|
|
44
44
|
}
|
|
45
|
-
addInnerSortFieldsToGroupBy(groupByFields, sortRecords,
|
|
45
|
+
addInnerSortFieldsToGroupBy(groupByFields, sortRecords, _hasRelationalSort) {
|
|
46
46
|
/*
|
|
47
47
|
Oracle requires all selected columns that are not aggregated over to be present in the GROUP BY clause
|
|
48
48
|
aliases can not be used before version 23c.
|
|
@@ -3,5 +3,5 @@ import { SchemaHelper, type SortRecord, type Sql } from '../types.js';
|
|
|
3
3
|
export declare class SchemaHelperPostgres extends SchemaHelper {
|
|
4
4
|
getDatabaseSize(): Promise<number | null>;
|
|
5
5
|
preprocessBindings(queryParams: Sql): Sql;
|
|
6
|
-
addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[],
|
|
6
|
+
addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[], hasRelationalSort: boolean): void;
|
|
7
7
|
}
|
|
@@ -15,12 +15,12 @@ export class SchemaHelperPostgres extends SchemaHelper {
|
|
|
15
15
|
preprocessBindings(queryParams) {
|
|
16
16
|
return preprocessBindings(queryParams, { format: (index) => `$${index + 1}` });
|
|
17
17
|
}
|
|
18
|
-
addInnerSortFieldsToGroupBy(groupByFields, sortRecords,
|
|
19
|
-
if (
|
|
18
|
+
addInnerSortFieldsToGroupBy(groupByFields, sortRecords, hasRelationalSort) {
|
|
19
|
+
if (hasRelationalSort) {
|
|
20
20
|
/*
|
|
21
21
|
Postgres only requires selected columns that are not functionally dependent on the primary key to be
|
|
22
22
|
included in the GROUP BY clause. Since the results are already grouped by the primary key, we don't need to
|
|
23
|
-
always include the sort columns in the GROUP BY but only if there is a
|
|
23
|
+
always include the sort columns in the GROUP BY but only if there is a relational sort involved, eg.
|
|
24
24
|
a sort column that comes from a related M2O relation.
|
|
25
25
|
|
|
26
26
|
> When GROUP BY is present, or any aggregate functions are present, it is not valid for the SELECT list
|
|
@@ -36,5 +36,5 @@ export declare abstract class SchemaHelper extends DatabaseHelper {
|
|
|
36
36
|
*/
|
|
37
37
|
getDatabaseSize(): Promise<number | null>;
|
|
38
38
|
preprocessBindings(queryParams: Sql): Sql;
|
|
39
|
-
addInnerSortFieldsToGroupBy(_groupByFields: (string | Knex.Raw)[], _sortRecords: SortRecord[],
|
|
39
|
+
addInnerSortFieldsToGroupBy(_groupByFields: (string | Knex.Raw)[], _sortRecords: SortRecord[], _hasRelationalSort: boolean): void;
|
|
40
40
|
}
|
|
@@ -97,7 +97,7 @@ export class SchemaHelper extends DatabaseHelper {
|
|
|
97
97
|
preprocessBindings(queryParams) {
|
|
98
98
|
return queryParams;
|
|
99
99
|
}
|
|
100
|
-
addInnerSortFieldsToGroupBy(_groupByFields, _sortRecords,
|
|
100
|
+
addInnerSortFieldsToGroupBy(_groupByFields, _sortRecords, _hasRelationalSort) {
|
|
101
101
|
// no-op by default
|
|
102
102
|
}
|
|
103
103
|
}
|
package/dist/database/index.js
CHANGED
|
@@ -140,6 +140,9 @@ export function getDatabase() {
|
|
|
140
140
|
times.delete(queryInfo.__knexUid);
|
|
141
141
|
}
|
|
142
142
|
logger.trace(`[${delta ? delta.toFixed(3) : '?'}ms] ${queryInfo.sql} [${(queryInfo.bindings ?? []).join(', ')}]`);
|
|
143
|
+
})
|
|
144
|
+
.on('query-error', (_, queryInfo) => {
|
|
145
|
+
times.delete(queryInfo.__knexUid);
|
|
143
146
|
});
|
|
144
147
|
return database;
|
|
145
148
|
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
1
|
import type { Knex } from 'knex';
|
|
2
|
-
import type { Permission } from '@directus/types';
|
|
3
|
-
export declare function mergePermissions(strategy: 'and' | 'or', ...permissions: Permission[][]): any[];
|
|
4
|
-
export declare function mergePermission(strategy: 'and' | 'or', currentPerm: Permission, newPerm: Permission): Omit<Permission, 'id' | 'system'>;
|
|
5
2
|
export declare function up(knex: Knex): Promise<void>;
|
|
6
3
|
export declare function down(knex: Knex): Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { processChunk, toBoolean } from '@directus/utils';
|
|
2
|
-
import {
|
|
2
|
+
import { omit } from 'lodash-es';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { useLogger } from '../../logger/index.js';
|
|
5
5
|
import { fetchPermissions } from '../../permissions/lib/fetch-permissions.js';
|
|
@@ -7,98 +7,7 @@ import { fetchPolicies } from '../../permissions/lib/fetch-policies.js';
|
|
|
7
7
|
import { fetchRolesTree } from '../../permissions/lib/fetch-roles-tree.js';
|
|
8
8
|
import { getSchema } from '../../utils/get-schema.js';
|
|
9
9
|
import { getSchemaInspector } from '../index.js';
|
|
10
|
-
|
|
11
|
-
export function mergePermissions(strategy, ...permissions) {
|
|
12
|
-
const allPermissions = flatten(permissions);
|
|
13
|
-
const mergedPermissions = allPermissions
|
|
14
|
-
.reduce((acc, val) => {
|
|
15
|
-
const key = `${val.collection}__${val.action}`;
|
|
16
|
-
const current = acc.get(key);
|
|
17
|
-
acc.set(key, current ? mergePermission(strategy, current, val) : val);
|
|
18
|
-
return acc;
|
|
19
|
-
}, new Map())
|
|
20
|
-
.values();
|
|
21
|
-
return Array.from(mergedPermissions);
|
|
22
|
-
}
|
|
23
|
-
export function mergePermission(strategy, currentPerm, newPerm) {
|
|
24
|
-
const logicalKey = `_${strategy}`;
|
|
25
|
-
let { permissions, validation, fields, presets } = currentPerm;
|
|
26
|
-
if (newPerm.permissions) {
|
|
27
|
-
if (currentPerm.permissions && Object.keys(currentPerm.permissions)[0] === logicalKey) {
|
|
28
|
-
permissions = {
|
|
29
|
-
[logicalKey]: [
|
|
30
|
-
...currentPerm.permissions[logicalKey],
|
|
31
|
-
newPerm.permissions,
|
|
32
|
-
],
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
else if (currentPerm.permissions) {
|
|
36
|
-
// Empty {} supersedes other permissions in _OR merge
|
|
37
|
-
if (strategy === 'or' && (isEqual(currentPerm.permissions, {}) || isEqual(newPerm.permissions, {}))) {
|
|
38
|
-
permissions = {};
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
permissions = {
|
|
42
|
-
[logicalKey]: [currentPerm.permissions, newPerm.permissions],
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
permissions = {
|
|
48
|
-
[logicalKey]: [newPerm.permissions],
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
if (newPerm.validation) {
|
|
53
|
-
if (currentPerm.validation && Object.keys(currentPerm.validation)[0] === logicalKey) {
|
|
54
|
-
validation = {
|
|
55
|
-
[logicalKey]: [
|
|
56
|
-
...currentPerm.validation[logicalKey],
|
|
57
|
-
newPerm.validation,
|
|
58
|
-
],
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
else if (currentPerm.validation) {
|
|
62
|
-
// Empty {} supersedes other validations in _OR merge
|
|
63
|
-
if (strategy === 'or' && (isEqual(currentPerm.validation, {}) || isEqual(newPerm.validation, {}))) {
|
|
64
|
-
validation = {};
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
validation = {
|
|
68
|
-
[logicalKey]: [currentPerm.validation, newPerm.validation],
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
validation = {
|
|
74
|
-
[logicalKey]: [newPerm.validation],
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (newPerm.fields) {
|
|
79
|
-
if (Array.isArray(currentPerm.fields) && strategy === 'or') {
|
|
80
|
-
fields = uniq([...currentPerm.fields, ...newPerm.fields]);
|
|
81
|
-
}
|
|
82
|
-
else if (Array.isArray(currentPerm.fields) && strategy === 'and') {
|
|
83
|
-
fields = intersection(currentPerm.fields, newPerm.fields);
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
fields = newPerm.fields;
|
|
87
|
-
}
|
|
88
|
-
if (fields.includes('*'))
|
|
89
|
-
fields = ['*'];
|
|
90
|
-
}
|
|
91
|
-
if (newPerm.presets) {
|
|
92
|
-
presets = merge({}, presets, newPerm.presets);
|
|
93
|
-
}
|
|
94
|
-
return omit({
|
|
95
|
-
...currentPerm,
|
|
96
|
-
permissions,
|
|
97
|
-
validation,
|
|
98
|
-
fields,
|
|
99
|
-
presets,
|
|
100
|
-
}, ['id', 'system']);
|
|
101
|
-
}
|
|
10
|
+
import { mergePermissions } from '../../permissions/utils/merge-permissions.js';
|
|
102
11
|
async function fetchRoleAccess(roles, context) {
|
|
103
12
|
const roleAccess = {
|
|
104
13
|
admin_access: false,
|
|
@@ -286,7 +195,12 @@ export async function down(knex) {
|
|
|
286
195
|
const policies = await fetchPolicies({ roles: roleTree, user: null, ip: null }, context);
|
|
287
196
|
// fetch all of the policies permissions
|
|
288
197
|
const rawPermissions = await fetchPermissions({
|
|
289
|
-
accountability: {
|
|
198
|
+
accountability: {
|
|
199
|
+
role: null,
|
|
200
|
+
roles: roleTree,
|
|
201
|
+
user: null,
|
|
202
|
+
app: roleAccess?.app_access || false,
|
|
203
|
+
},
|
|
290
204
|
policies,
|
|
291
205
|
bypassDynamicVariableProcessing: true,
|
|
292
206
|
}, context);
|