@directus/api 22.2.0 → 23.1.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/app.js +2 -0
- package/dist/auth/drivers/ldap.js +14 -3
- package/dist/auth/drivers/oauth2.js +13 -2
- package/dist/auth/drivers/openid.js +13 -2
- package/dist/cache.js +4 -4
- package/dist/cli/commands/init/questions.d.ts +5 -5
- package/dist/cli/commands/schema/apply.d.ts +1 -0
- package/dist/cli/commands/schema/apply.js +20 -1
- package/dist/cli/index.js +1 -0
- package/dist/cli/utils/create-env/env-stub.liquid +1 -4
- 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/versions.js +10 -5
- package/dist/database/index.js +3 -0
- package/dist/database/migrations/20210518A-add-foreign-key-constraints.js +1 -1
- package/dist/database/migrations/20240806A-permissions-policies.js +1 -1
- 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 +2 -2
- 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/get-column-pre-processor.d.ts +1 -1
- package/dist/database/run-ast/utils/get-column-pre-processor.js +10 -2
- package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +0 -3
- package/dist/extensions/lib/sandbox/register/route.d.ts +1 -2
- package/dist/logger/index.d.ts +2 -3
- package/dist/logger/logs-stream.d.ts +0 -1
- package/dist/mailer.js +0 -6
- package/dist/middleware/authenticate.d.ts +1 -3
- package/dist/middleware/error-handler.d.ts +0 -1
- package/dist/middleware/validate-batch.d.ts +1 -4
- package/dist/permissions/lib/fetch-permissions.d.ts +11 -1
- package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +2 -2
- 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/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +1 -2
- package/dist/permissions/utils/fetch-dynamic-variable-context.js +14 -6
- package/dist/permissions/utils/process-permissions.d.ts +11 -1
- package/dist/permissions/utils/process-permissions.js +6 -4
- package/dist/request/agent-with-ip-validation.d.ts +0 -1
- package/dist/server.d.ts +0 -3
- package/dist/services/activity.d.ts +1 -7
- package/dist/services/activity.js +0 -103
- package/dist/services/assets.d.ts +0 -1
- package/dist/services/assets.js +5 -4
- package/dist/services/collections.js +6 -4
- package/dist/services/comments.d.ts +31 -0
- package/dist/services/comments.js +374 -0
- package/dist/services/fields.js +0 -6
- package/dist/services/files/utils/get-metadata.d.ts +0 -1
- package/dist/services/files/utils/parse-image-metadata.d.ts +0 -1
- package/dist/services/files.d.ts +0 -1
- package/dist/services/graphql/index.js +17 -16
- package/dist/services/import-export.d.ts +0 -1
- 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/tus/data-store.d.ts +0 -1
- package/dist/services/users.js +3 -2
- package/dist/services/versions.js +59 -44
- package/dist/types/graphql.d.ts +0 -1
- package/dist/utils/apply-diff.js +5 -6
- package/dist/utils/apply-query.d.ts +1 -1
- package/dist/utils/compress.d.ts +0 -1
- package/dist/utils/delete-from-require-cache.js +1 -1
- package/dist/utils/fetch-user-count/fetch-user-count.d.ts +1 -2
- package/dist/utils/generate-hash.js +2 -2
- package/dist/utils/get-address.d.ts +0 -3
- package/dist/utils/get-cache-headers.d.ts +0 -1
- package/dist/utils/get-cache-key.d.ts +0 -1
- package/dist/utils/get-column.d.ts +1 -1
- package/dist/utils/get-graphql-query-and-variables.d.ts +0 -1
- package/dist/utils/get-ip-from-req.d.ts +0 -1
- package/dist/utils/get-service.js +3 -1
- package/dist/utils/get-snapshot.js +1 -1
- package/dist/utils/sanitize-query.js +1 -1
- package/dist/utils/sanitize-schema.d.ts +1 -1
- package/dist/utils/sanitize-schema.js +2 -0
- package/dist/utils/should-skip-cache.d.ts +0 -1
- package/dist/websocket/authenticate.js +1 -1
- package/dist/websocket/controllers/base.d.ts +1 -10
- package/dist/websocket/controllers/base.js +15 -21
- package/dist/websocket/controllers/graphql.d.ts +0 -3
- package/dist/websocket/controllers/index.d.ts +0 -3
- package/dist/websocket/controllers/logs.d.ts +4 -5
- package/dist/websocket/controllers/logs.js +7 -3
- package/dist/websocket/controllers/rest.d.ts +0 -3
- package/dist/websocket/controllers/rest.js +1 -1
- package/dist/websocket/types.d.ts +0 -6
- package/package.json +70 -71
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);
|
|
@@ -172,7 +172,7 @@ export class LDAPAuthDriver extends AuthDriver {
|
|
|
172
172
|
}
|
|
173
173
|
const logger = useLogger();
|
|
174
174
|
await this.validateBindClient();
|
|
175
|
-
const { userDn, userScope, userAttribute, groupDn, groupScope, groupAttribute, defaultRoleId } = this.config;
|
|
175
|
+
const { userDn, userScope, userAttribute, groupDn, groupScope, groupAttribute, defaultRoleId, syncUserInfo } = this.config;
|
|
176
176
|
const userInfo = await this.fetchUserInfo(userDn, new ldap.EqualityFilter({
|
|
177
177
|
attribute: userAttribute ?? 'cn',
|
|
178
178
|
value: payload['identifier'],
|
|
@@ -201,11 +201,22 @@ export class LDAPAuthDriver extends AuthDriver {
|
|
|
201
201
|
if (userId) {
|
|
202
202
|
// Run hook so the end user has the chance to augment the
|
|
203
203
|
// user that is about to be updated
|
|
204
|
-
let
|
|
204
|
+
let emitPayload = {};
|
|
205
205
|
// Only sync roles if the AD groups are configured
|
|
206
206
|
if (groupDn) {
|
|
207
|
-
|
|
207
|
+
emitPayload = {
|
|
208
|
+
role: userRole?.id ?? defaultRoleId ?? null,
|
|
209
|
+
};
|
|
208
210
|
}
|
|
211
|
+
if (syncUserInfo) {
|
|
212
|
+
emitPayload = {
|
|
213
|
+
...emitPayload,
|
|
214
|
+
first_name: userInfo.firstName,
|
|
215
|
+
last_name: userInfo.lastName,
|
|
216
|
+
email: userInfo.email,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const updatedUserPayload = await emitter.emitFilter(`auth.update`, emitPayload, { identifier: userInfo.dn, provider: this.config['provider'], providerPayload: { userInfo, userRole } }, { database: getDatabase(), schema: this.schema, accountability: null });
|
|
209
220
|
// Update user to update properties that might have changed
|
|
210
221
|
await this.usersService.updateOne(userId, updatedUserPayload);
|
|
211
222
|
return userId;
|
|
@@ -106,7 +106,7 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
106
106
|
}
|
|
107
107
|
// Flatten response to support dot indexes
|
|
108
108
|
userInfo = flatten(userInfo);
|
|
109
|
-
const { provider, emailKey, identifierKey, allowPublicRegistration } = this.config;
|
|
109
|
+
const { provider, emailKey, identifierKey, allowPublicRegistration, syncUserInfo } = this.config;
|
|
110
110
|
const email = userInfo[emailKey ?? 'email'] ? String(userInfo[emailKey ?? 'email']) : undefined;
|
|
111
111
|
// Fallback to email if explicit identifier not found
|
|
112
112
|
const identifier = userInfo[identifierKey] ? String(userInfo[identifierKey]) : email;
|
|
@@ -127,7 +127,18 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
127
127
|
if (userId) {
|
|
128
128
|
// Run hook so the end user has the chance to augment the
|
|
129
129
|
// user that is about to be updated
|
|
130
|
-
|
|
130
|
+
let emitPayload = {
|
|
131
|
+
auth_data: userPayload.auth_data,
|
|
132
|
+
};
|
|
133
|
+
if (syncUserInfo) {
|
|
134
|
+
emitPayload = {
|
|
135
|
+
...emitPayload,
|
|
136
|
+
first_name: userPayload.first_name,
|
|
137
|
+
last_name: userPayload.last_name,
|
|
138
|
+
email: userPayload.email,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const updatedUserPayload = await emitter.emitFilter(`auth.update`, emitPayload, {
|
|
131
142
|
identifier,
|
|
132
143
|
provider: this.config['provider'],
|
|
133
144
|
providerPayload: { accessToken: tokenSet.access_token, userInfo },
|
|
@@ -125,7 +125,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
|
|
125
125
|
}
|
|
126
126
|
// Flatten response to support dot indexes
|
|
127
127
|
userInfo = flatten(userInfo);
|
|
128
|
-
const { provider, identifierKey, allowPublicRegistration, requireVerifiedEmail } = this.config;
|
|
128
|
+
const { provider, identifierKey, allowPublicRegistration, requireVerifiedEmail, syncUserInfo } = this.config;
|
|
129
129
|
const email = userInfo['email'] ? String(userInfo['email']) : undefined;
|
|
130
130
|
// Fallback to email if explicit identifier not found
|
|
131
131
|
const identifier = userInfo[identifierKey ?? 'sub'] ? String(userInfo[identifierKey ?? 'sub']) : email;
|
|
@@ -146,7 +146,18 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
|
|
146
146
|
if (userId) {
|
|
147
147
|
// Run hook so the end user has the chance to augment the
|
|
148
148
|
// user that is about to be updated
|
|
149
|
-
|
|
149
|
+
let emitPayload = {
|
|
150
|
+
auth_data: userPayload.auth_data,
|
|
151
|
+
};
|
|
152
|
+
if (syncUserInfo) {
|
|
153
|
+
emitPayload = {
|
|
154
|
+
...emitPayload,
|
|
155
|
+
first_name: userPayload.first_name,
|
|
156
|
+
last_name: userPayload.last_name,
|
|
157
|
+
email: userPayload.email,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const updatedUserPayload = await emitter.emitFilter(`auth.update`, emitPayload, {
|
|
150
161
|
identifier,
|
|
151
162
|
provider: this.config['provider'],
|
|
152
163
|
providerPayload: { accessToken: tokenSet.access_token, userInfo },
|
package/dist/cache.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
|
-
import Keyv from 'keyv';
|
|
2
|
+
import Keyv, {} from 'keyv';
|
|
3
3
|
import { useBus } from './bus/index.js';
|
|
4
4
|
import { useLogger } from './logger/index.js';
|
|
5
|
+
import { clearCache as clearPermissionCache } from './permissions/cache.js';
|
|
5
6
|
import { redisConfigAvailable } from './redis/index.js';
|
|
6
7
|
import { compress, decompress } from './utils/compress.js';
|
|
7
8
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
8
9
|
import { getMilliseconds } from './utils/get-milliseconds.js';
|
|
9
10
|
import { validateEnv } from './utils/validate-env.js';
|
|
10
|
-
import { clearCache as clearPermissionCache } from './permissions/cache.js';
|
|
11
11
|
import { createRequire } from 'node:module';
|
|
12
12
|
const logger = useLogger();
|
|
13
13
|
const env = useEnv();
|
|
@@ -106,10 +106,10 @@ function getKeyvInstance(store, ttl, namespaceSuffix) {
|
|
|
106
106
|
function getConfig(store = 'memory', ttl, namespaceSuffix = '') {
|
|
107
107
|
const config = {
|
|
108
108
|
namespace: `${env['CACHE_NAMESPACE']}${namespaceSuffix}`,
|
|
109
|
-
ttl,
|
|
109
|
+
...(ttl && { ttl }),
|
|
110
110
|
};
|
|
111
111
|
if (store === 'redis') {
|
|
112
|
-
const KeyvRedis = require('@keyv/redis');
|
|
112
|
+
const { default: KeyvRedis } = require('@keyv/redis');
|
|
113
113
|
config.store = new KeyvRedis(env['REDIS'] || getConfigFromEnv('REDIS'), { useRedisSets: false });
|
|
114
114
|
}
|
|
115
115
|
return config;
|
|
@@ -4,18 +4,18 @@ export declare const databaseQuestions: {
|
|
|
4
4
|
filepath: string;
|
|
5
5
|
}) => Record<string, string>)[];
|
|
6
6
|
mysql2: (({ client }: {
|
|
7
|
-
client: Exclude<Driver,
|
|
7
|
+
client: Exclude<Driver, "sqlite3">;
|
|
8
8
|
}) => Record<string, any>)[];
|
|
9
9
|
pg: (({ client }: {
|
|
10
|
-
client: Exclude<Driver,
|
|
10
|
+
client: Exclude<Driver, "sqlite3">;
|
|
11
11
|
}) => Record<string, any>)[];
|
|
12
12
|
cockroachdb: (({ client }: {
|
|
13
|
-
client: Exclude<Driver,
|
|
13
|
+
client: Exclude<Driver, "sqlite3">;
|
|
14
14
|
}) => Record<string, any>)[];
|
|
15
15
|
oracledb: (({ client }: {
|
|
16
|
-
client: Exclude<Driver,
|
|
16
|
+
client: Exclude<Driver, "sqlite3">;
|
|
17
17
|
}) => Record<string, any>)[];
|
|
18
18
|
mssql: (({ client }: {
|
|
19
|
-
client: Exclude<Driver,
|
|
19
|
+
client: Exclude<Driver, "sqlite3">;
|
|
20
20
|
}) => Record<string, any>)[];
|
|
21
21
|
};
|
|
@@ -11,6 +11,22 @@ import { isNestedMetaUpdate } from '../../../utils/apply-diff.js';
|
|
|
11
11
|
import { applySnapshot } from '../../../utils/apply-snapshot.js';
|
|
12
12
|
import { getSnapshotDiff } from '../../../utils/get-snapshot-diff.js';
|
|
13
13
|
import { getSnapshot } from '../../../utils/get-snapshot.js';
|
|
14
|
+
function filterSnapshotDiff(snapshot, filters) {
|
|
15
|
+
const filterSet = new Set(filters);
|
|
16
|
+
function shouldKeep(item) {
|
|
17
|
+
if (filterSet.has(item.collection))
|
|
18
|
+
return false;
|
|
19
|
+
if (item.field && filterSet.has(`${item.collection}.${item.field}`))
|
|
20
|
+
return false;
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
const filteredDiff = {
|
|
24
|
+
collections: snapshot.collections.filter((item) => shouldKeep(item)),
|
|
25
|
+
fields: snapshot.fields.filter((item) => shouldKeep(item)),
|
|
26
|
+
relations: snapshot.relations.filter((item) => shouldKeep(item)),
|
|
27
|
+
};
|
|
28
|
+
return filteredDiff;
|
|
29
|
+
}
|
|
14
30
|
export async function apply(snapshotPath, options) {
|
|
15
31
|
const logger = useLogger();
|
|
16
32
|
const filename = path.resolve(process.cwd(), snapshotPath);
|
|
@@ -31,7 +47,10 @@ export async function apply(snapshotPath, options) {
|
|
|
31
47
|
snapshot = parseJSON(fileContents);
|
|
32
48
|
}
|
|
33
49
|
const currentSnapshot = await getSnapshot({ database });
|
|
34
|
-
|
|
50
|
+
let snapshotDiff = getSnapshotDiff(currentSnapshot, snapshot);
|
|
51
|
+
if (options?.ignoreRules) {
|
|
52
|
+
snapshotDiff = filterSnapshotDiff(snapshotDiff, options.ignoreRules.split(','));
|
|
53
|
+
}
|
|
35
54
|
if (snapshotDiff.collections.length === 0 &&
|
|
36
55
|
snapshotDiff.fields.length === 0 &&
|
|
37
56
|
snapshotDiff.relations.length === 0) {
|
package/dist/cli/index.js
CHANGED
|
@@ -81,6 +81,7 @@ export async function createCli() {
|
|
|
81
81
|
.description('Apply a snapshot file to the current database')
|
|
82
82
|
.option('-y, --yes', `Assume "yes" as answer to all prompts and run non-interactively`)
|
|
83
83
|
.option('-d, --dry-run', 'Plan and log changes to be applied', false)
|
|
84
|
+
.option('--ignoreRules <value>', `Comma-separated list of collections and or fields to ignore. Format: "products.title,reviews" this will ignore applying changes to the title field in the products collection and the entire reviews collection`)
|
|
84
85
|
.argument('<path>', 'Path to snapshot file')
|
|
85
86
|
.action(apply);
|
|
86
87
|
await emitter.emitInit('cli.after', { program });
|
|
@@ -313,7 +313,7 @@ EXTENSIONS_AUTO_RELOAD=false
|
|
|
313
313
|
EMAIL_FROM="no-reply@example.com"
|
|
314
314
|
|
|
315
315
|
# What to use to send emails. One of
|
|
316
|
-
# sendmail, smtp, mailgun,
|
|
316
|
+
# sendmail, smtp, mailgun, ses.
|
|
317
317
|
EMAIL_TRANSPORT="sendmail"
|
|
318
318
|
EMAIL_SENDMAIL_NEW_LINE="unix"
|
|
319
319
|
EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail"
|
|
@@ -340,6 +340,3 @@ EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail"
|
|
|
340
340
|
## Email (Mailgun Transport)
|
|
341
341
|
# EMAIL_MAILGUN_API_KEY="key-1234123412341234"
|
|
342
342
|
# EMAIL_MAILGUN_DOMAIN="a domain name from https://app.mailgun.com/app/sending/domains"
|
|
343
|
-
|
|
344
|
-
## Email (SendGrid Transport)
|
|
345
|
-
# EMAIL_SENDGRID_API_KEY="key-1234123412341234"
|
|
@@ -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;
|
|
@@ -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);
|
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
|
}
|
|
@@ -43,7 +43,7 @@ export async function up(knex) {
|
|
|
43
43
|
.update({ [constraint.many_field]: null })
|
|
44
44
|
.whereIn(currentPrimaryKeyField, ids);
|
|
45
45
|
}
|
|
46
|
-
catch
|
|
46
|
+
catch {
|
|
47
47
|
logger.error(`${constraint.many_collection}.${constraint.many_field} contains illegal foreign keys which couldn't be set to NULL. Please fix these references and rerun this migration to complete the upgrade.`);
|
|
48
48
|
if (ids.length < 25) {
|
|
49
49
|
logger.error(`Items with illegal foreign keys: ${ids.join(', ')}`);
|
|
@@ -198,7 +198,7 @@ export async function up(knex) {
|
|
|
198
198
|
table.dropForeign('role', foreignConstraint);
|
|
199
199
|
});
|
|
200
200
|
}
|
|
201
|
-
catch
|
|
201
|
+
catch {
|
|
202
202
|
logger.warn('Failed to drop foreign key constraint on `role` column in `directus_permissions` table');
|
|
203
203
|
}
|
|
204
204
|
await knex('directus_permissions')
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Action } from '@directus/constants';
|
|
2
|
+
export async function up(knex) {
|
|
3
|
+
await knex.schema.createTable('directus_comments', (table) => {
|
|
4
|
+
table.uuid('id').primary().notNullable();
|
|
5
|
+
table
|
|
6
|
+
.string('collection', 64)
|
|
7
|
+
.notNullable()
|
|
8
|
+
.references('collection')
|
|
9
|
+
.inTable('directus_collections')
|
|
10
|
+
.onDelete('CASCADE');
|
|
11
|
+
table.string('item').notNullable();
|
|
12
|
+
table.text('comment').notNullable();
|
|
13
|
+
table.timestamp('date_created').defaultTo(knex.fn.now());
|
|
14
|
+
table.timestamp('date_updated').defaultTo(knex.fn.now());
|
|
15
|
+
table.uuid('user_created').references('id').inTable('directus_users').onDelete('SET NULL');
|
|
16
|
+
// Cannot have two constraints from/to the same table, handled on API side
|
|
17
|
+
table.uuid('user_updated').references('id').inTable('directus_users');
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export async function down(knex) {
|
|
21
|
+
const rowsLimit = 50;
|
|
22
|
+
let hasMore = true;
|
|
23
|
+
while (hasMore) {
|
|
24
|
+
const comments = await knex
|
|
25
|
+
.select('id', 'collection', 'item', 'comment', 'date_created', 'user_created')
|
|
26
|
+
.from('directus_comments')
|
|
27
|
+
.limit(rowsLimit);
|
|
28
|
+
if (comments.length === 0) {
|
|
29
|
+
hasMore = false;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
await knex.transaction(async (trx) => {
|
|
33
|
+
for (const comment of comments) {
|
|
34
|
+
const migratedRecords = await trx('directus_activity')
|
|
35
|
+
.select('id')
|
|
36
|
+
.where('collection', '=', 'directus_comments')
|
|
37
|
+
.andWhere('item', '=', comment.id)
|
|
38
|
+
.andWhere('action', '=', Action.CREATE)
|
|
39
|
+
.limit(1);
|
|
40
|
+
if (migratedRecords[0]) {
|
|
41
|
+
await trx('directus_activity')
|
|
42
|
+
.update({
|
|
43
|
+
action: Action.COMMENT,
|
|
44
|
+
collection: comment.collection,
|
|
45
|
+
item: comment.item,
|
|
46
|
+
comment: comment.comment,
|
|
47
|
+
})
|
|
48
|
+
.where('id', '=', migratedRecords[0].id);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
await trx('directus_activity').insert({
|
|
52
|
+
action: Action.COMMENT,
|
|
53
|
+
collection: comment.collection,
|
|
54
|
+
item: comment.item,
|
|
55
|
+
comment: comment.comment,
|
|
56
|
+
user: comment.user_created,
|
|
57
|
+
timestamp: comment.date_created,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
await trx('directus_comments').where('id', '=', comment.id).delete();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
await knex.schema.dropTable('directus_comments');
|
|
65
|
+
}
|