@directus/api 10.1.0 → 11.0.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 +4 -3
- package/dist/auth/drivers/oauth2.js +1 -1
- package/dist/auth/drivers/openid.js +1 -1
- package/dist/cli/utils/create-env/env-stub.liquid +7 -0
- package/dist/constants.d.ts +0 -1
- package/dist/constants.js +0 -1
- package/dist/controllers/assets.js +6 -10
- package/dist/controllers/files.js +19 -1
- package/dist/controllers/permissions.js +7 -4
- package/dist/controllers/translations.d.ts +2 -0
- package/dist/controllers/translations.js +149 -0
- package/dist/controllers/users.js +1 -1
- package/dist/database/migrations/20230525A-add-preview-settings.d.ts +3 -0
- package/dist/database/migrations/20230525A-add-preview-settings.js +10 -0
- package/dist/database/migrations/20230526A-migrate-translation-strings.d.ts +3 -0
- package/dist/database/migrations/20230526A-migrate-translation-strings.js +54 -0
- package/dist/database/run-ast.js +3 -3
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +3 -0
- package/dist/database/system-data/collections/collections.yaml +23 -0
- package/dist/database/system-data/fields/collections.yaml +16 -0
- package/dist/database/system-data/fields/settings.yaml +0 -5
- package/dist/database/system-data/fields/translations.yaml +27 -0
- package/dist/env.js +17 -0
- package/dist/exceptions/content-too-large.d.ts +4 -0
- package/dist/exceptions/content-too-large.js +6 -0
- package/dist/extensions.js +13 -11
- package/dist/flows.d.ts +1 -1
- package/dist/flows.js +20 -19
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +6 -6
- package/dist/server.js +0 -11
- package/dist/services/assets.d.ts +2 -2
- package/dist/services/collections.js +8 -7
- package/dist/services/fields.js +7 -5
- package/dist/services/files.d.ts +2 -2
- package/dist/services/files.js +4 -9
- package/dist/services/graphql/index.js +4 -41
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/items.js +10 -9
- package/dist/services/revisions.d.ts +6 -1
- package/dist/services/revisions.js +24 -0
- package/dist/services/server.js +3 -17
- package/dist/services/specifications.d.ts +2 -2
- package/dist/services/specifications.js +6 -5
- package/dist/services/translations.d.ts +10 -0
- package/dist/services/translations.js +36 -0
- package/dist/synchronization.d.ts +7 -0
- package/dist/synchronization.js +120 -0
- package/dist/types/assets.d.ts +6 -1
- package/dist/types/events.d.ts +2 -2
- package/dist/utils/apply-query.d.ts +9 -2
- package/dist/utils/apply-query.js +43 -16
- package/dist/utils/md.js +1 -1
- package/dist/utils/redact.d.ts +11 -0
- package/dist/utils/redact.js +75 -0
- package/dist/utils/sanitize-query.js +10 -1
- package/dist/utils/schedule.d.ts +5 -0
- package/dist/utils/schedule.js +27 -0
- package/dist/utils/should-clear-cache.d.ts +10 -0
- package/dist/utils/should-clear-cache.js +18 -0
- package/dist/utils/should-skip-cache.js +18 -2
- package/dist/utils/transformations.d.ts +2 -2
- package/dist/utils/transformations.js +27 -10
- package/dist/utils/validate-query.js +3 -1
- package/package.json +49 -53
- package/dist/utils/get-os-info.d.ts +0 -9
- package/dist/utils/get-os-info.js +0 -40
package/dist/env.js
CHANGED
|
@@ -25,6 +25,8 @@ const allowedEnvironmentVars = [
|
|
|
25
25
|
'GRAPHQL_INTROSPECTION',
|
|
26
26
|
'MAX_BATCH_MUTATION',
|
|
27
27
|
'LOGGER_.+',
|
|
28
|
+
'QUERY_LIMIT_MAX',
|
|
29
|
+
'QUERY_LIMIT_DEFAULT',
|
|
28
30
|
'ROBOTS_TXT',
|
|
29
31
|
// server
|
|
30
32
|
'SERVER_.+',
|
|
@@ -66,6 +68,7 @@ const allowedEnvironmentVars = [
|
|
|
66
68
|
'CACHE_TTL',
|
|
67
69
|
'CACHE_CONTROL_S_MAXAGE',
|
|
68
70
|
'CACHE_AUTO_PURGE',
|
|
71
|
+
'CACHE_AUTO_PURGE_IGNORE_LIST',
|
|
69
72
|
'CACHE_SYSTEM_TTL',
|
|
70
73
|
'CACHE_SCHEMA',
|
|
71
74
|
'CACHE_PERMISSIONS',
|
|
@@ -100,6 +103,9 @@ const allowedEnvironmentVars = [
|
|
|
100
103
|
'STORAGE_.+_HEALTHCHECK_THRESHOLD',
|
|
101
104
|
// metadata
|
|
102
105
|
'FILE_METADATA_ALLOW_LIST',
|
|
106
|
+
// files
|
|
107
|
+
'FILES_MAX_UPLOAD_SIZE',
|
|
108
|
+
'FILES_CONTENT_TYPE_ALLOW_LIST',
|
|
103
109
|
// assets
|
|
104
110
|
'ASSETS_CACHE_TTL',
|
|
105
111
|
'ASSETS_TRANSFORM_MAX_CONCURRENT',
|
|
@@ -155,6 +161,13 @@ const allowedEnvironmentVars = [
|
|
|
155
161
|
'MESSENGER_REDIS_HOST',
|
|
156
162
|
'MESSENGER_REDIS_PORT',
|
|
157
163
|
'MESSENGER_REDIS_PASSWORD',
|
|
164
|
+
// synchronization
|
|
165
|
+
'SYNCHRONIZATION_STORE',
|
|
166
|
+
'SYNCHRONIZATION_NAMESPACE',
|
|
167
|
+
'SYNCHRONIZATION_REDIS',
|
|
168
|
+
'SYNCHRONIZATION_REDIS_HOST',
|
|
169
|
+
'SYNCHRONIZATION_REDIS_PORT',
|
|
170
|
+
'SYNCHRONIZATION_REDIS_PASSWORD',
|
|
158
171
|
// emails
|
|
159
172
|
'EMAIL_FROM',
|
|
160
173
|
'EMAIL_TRANSPORT',
|
|
@@ -196,6 +209,7 @@ const defaults = {
|
|
|
196
209
|
PUBLIC_URL: '/',
|
|
197
210
|
MAX_PAYLOAD_SIZE: '1mb',
|
|
198
211
|
MAX_RELATIONAL_DEPTH: 10,
|
|
212
|
+
QUERY_LIMIT_DEFAULT: 100,
|
|
199
213
|
MAX_BATCH_MUTATION: Infinity,
|
|
200
214
|
ROBOTS_TXT: 'User-agent: *\nDisallow: /',
|
|
201
215
|
DB_EXCLUDE_TABLES: 'spatial_ref_sys,sysdiagrams',
|
|
@@ -230,6 +244,7 @@ const defaults = {
|
|
|
230
244
|
CACHE_TTL: '5m',
|
|
231
245
|
CACHE_NAMESPACE: 'system-cache',
|
|
232
246
|
CACHE_AUTO_PURGE: false,
|
|
247
|
+
CACHE_AUTO_PURGE_IGNORE_LIST: 'directus_activity,directus_presets',
|
|
233
248
|
CACHE_CONTROL_S_MAXAGE: '0',
|
|
234
249
|
CACHE_SCHEMA: true,
|
|
235
250
|
CACHE_PERMISSIONS: true,
|
|
@@ -269,6 +284,7 @@ const defaults = {
|
|
|
269
284
|
PRESSURE_LIMITER_MAX_MEMORY_RSS: false,
|
|
270
285
|
PRESSURE_LIMITER_MAX_MEMORY_HEAP_USED: false,
|
|
271
286
|
PRESSURE_LIMITER_RETRY_AFTER: false,
|
|
287
|
+
FILES_MIME_TYPE_ALLOW_LIST: '*/*',
|
|
272
288
|
};
|
|
273
289
|
// Allows us to force certain environment variable into a type, instead of relying
|
|
274
290
|
// on the auto-parsed type in processValues. ref #3705
|
|
@@ -282,6 +298,7 @@ const typeMap = {
|
|
|
282
298
|
DB_PORT: 'number',
|
|
283
299
|
DB_EXCLUDE_TABLES: 'array',
|
|
284
300
|
CACHE_SKIP_ALLOWED: 'boolean',
|
|
301
|
+
CACHE_AUTO_PURGE_IGNORE_LIST: 'array',
|
|
285
302
|
IMPORT_IP_DENY_LIST: 'array',
|
|
286
303
|
FILE_METADATA_ALLOW_LIST: 'array',
|
|
287
304
|
GRAPHQL_INTROSPECTION: 'boolean',
|
package/dist/extensions.js
CHANGED
|
@@ -8,7 +8,6 @@ import virtualDefault from '@rollup/plugin-virtual';
|
|
|
8
8
|
import chokidar, { FSWatcher } from 'chokidar';
|
|
9
9
|
import express, { Router } from 'express';
|
|
10
10
|
import { clone, escapeRegExp } from 'lodash-es';
|
|
11
|
-
import { schedule, validate } from 'node-cron';
|
|
12
11
|
import { readdir } from 'node:fs/promises';
|
|
13
12
|
import { createRequire } from 'node:module';
|
|
14
13
|
import { dirname } from 'node:path';
|
|
@@ -25,6 +24,7 @@ import * as services from './services/index.js';
|
|
|
25
24
|
import getModuleDefault from './utils/get-module-default.js';
|
|
26
25
|
import { getSchema } from './utils/get-schema.js';
|
|
27
26
|
import { JobQueue } from './utils/job-queue.js';
|
|
27
|
+
import { scheduleSynchronizedJob, validateCron } from './utils/schedule.js';
|
|
28
28
|
import { Url } from './utils/url.js';
|
|
29
29
|
// Workaround for https://github.com/rollup/plugins/issues/1329
|
|
30
30
|
const virtual = virtualDefault;
|
|
@@ -189,7 +189,7 @@ class ExtensionManager {
|
|
|
189
189
|
this.isLoaded = true;
|
|
190
190
|
}
|
|
191
191
|
async unload() {
|
|
192
|
-
this.unregisterApiExtensions();
|
|
192
|
+
await this.unregisterApiExtensions();
|
|
193
193
|
this.apiEmitter.offAll();
|
|
194
194
|
if (env['SERVE_APP']) {
|
|
195
195
|
this.appExtensions = null;
|
|
@@ -299,7 +299,7 @@ class ExtensionManager {
|
|
|
299
299
|
const hookPath = path.resolve(hook.path, hook.entrypoint);
|
|
300
300
|
const hookInstance = await import(`./${pathToRelativeUrl(hookPath, __dirname)}?t=${Date.now()}`);
|
|
301
301
|
const config = getModuleDefault(hookInstance);
|
|
302
|
-
this.registerHook(config);
|
|
302
|
+
this.registerHook(config, hook.name);
|
|
303
303
|
this.apiExtensions.push({ path: hookPath });
|
|
304
304
|
}
|
|
305
305
|
catch (error) {
|
|
@@ -353,8 +353,8 @@ class ExtensionManager {
|
|
|
353
353
|
const bundlePath = path.resolve(bundle.path, bundle.entrypoint.api);
|
|
354
354
|
const bundleInstances = await import(`./${pathToRelativeUrl(bundlePath, __dirname)}?t=${Date.now()}`);
|
|
355
355
|
const configs = getModuleDefault(bundleInstances);
|
|
356
|
-
for (const { config } of configs.hooks) {
|
|
357
|
-
this.registerHook(config);
|
|
356
|
+
for (const { config, name } of configs.hooks) {
|
|
357
|
+
this.registerHook(config, name);
|
|
358
358
|
}
|
|
359
359
|
for (const { config, name } of configs.endpoints) {
|
|
360
360
|
this.registerEndpoint(config, name);
|
|
@@ -370,7 +370,8 @@ class ExtensionManager {
|
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
372
|
}
|
|
373
|
-
registerHook(register) {
|
|
373
|
+
registerHook(register, name) {
|
|
374
|
+
let scheduleIndex = 0;
|
|
374
375
|
const registerFunctions = {
|
|
375
376
|
filter: (event, handler) => {
|
|
376
377
|
emitter.onFilter(event, handler);
|
|
@@ -397,8 +398,8 @@ class ExtensionManager {
|
|
|
397
398
|
});
|
|
398
399
|
},
|
|
399
400
|
schedule: (cron, handler) => {
|
|
400
|
-
if (
|
|
401
|
-
const
|
|
401
|
+
if (validateCron(cron)) {
|
|
402
|
+
const job = scheduleSynchronizedJob(`${name}:${scheduleIndex}`, cron, async () => {
|
|
402
403
|
if (this.options.schedule) {
|
|
403
404
|
try {
|
|
404
405
|
await handler();
|
|
@@ -408,9 +409,10 @@ class ExtensionManager {
|
|
|
408
409
|
}
|
|
409
410
|
}
|
|
410
411
|
});
|
|
412
|
+
scheduleIndex++;
|
|
411
413
|
this.hookEvents.push({
|
|
412
414
|
type: 'schedule',
|
|
413
|
-
|
|
415
|
+
job,
|
|
414
416
|
});
|
|
415
417
|
}
|
|
416
418
|
else {
|
|
@@ -460,7 +462,7 @@ class ExtensionManager {
|
|
|
460
462
|
const flowManager = getFlowManager();
|
|
461
463
|
flowManager.addOperation(config.id, config.handler);
|
|
462
464
|
}
|
|
463
|
-
unregisterApiExtensions() {
|
|
465
|
+
async unregisterApiExtensions() {
|
|
464
466
|
for (const event of this.hookEvents) {
|
|
465
467
|
switch (event.type) {
|
|
466
468
|
case 'filter':
|
|
@@ -473,7 +475,7 @@ class ExtensionManager {
|
|
|
473
475
|
emitter.offInit(event.name, event.handler);
|
|
474
476
|
break;
|
|
475
477
|
case 'schedule':
|
|
476
|
-
event.
|
|
478
|
+
await event.job.stop();
|
|
477
479
|
break;
|
|
478
480
|
}
|
|
479
481
|
}
|
package/dist/flows.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ declare class FlowManager {
|
|
|
15
15
|
runOperationFlow(id: string, data: unknown, context: Record<string, unknown>): Promise<unknown>;
|
|
16
16
|
runWebhookFlow(id: string, data: unknown, context: Record<string, unknown>): Promise<{
|
|
17
17
|
result: unknown;
|
|
18
|
-
cacheEnabled
|
|
18
|
+
cacheEnabled?: boolean;
|
|
19
19
|
}>;
|
|
20
20
|
private load;
|
|
21
21
|
private unload;
|
package/dist/flows.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
import { Action, REDACTED_TEXT } from '@directus/constants';
|
|
1
2
|
import * as sharedExceptions from '@directus/exceptions';
|
|
2
|
-
import { Action } from '@directus/constants';
|
|
3
3
|
import { applyOptionsData, isValidJSON, parseJSON, toArray } from '@directus/utils';
|
|
4
|
-
import fastRedact from 'fast-redact';
|
|
5
4
|
import { omit, pick } from 'lodash-es';
|
|
6
5
|
import { get } from 'micromustache';
|
|
7
|
-
import { schedule, validate } from 'node-cron';
|
|
8
6
|
import getDatabase from './database/index.js';
|
|
9
7
|
import emitter from './emitter.js';
|
|
10
8
|
import env from './env.js';
|
|
@@ -12,20 +10,17 @@ import * as exceptions from './exceptions/index.js';
|
|
|
12
10
|
import logger from './logger.js';
|
|
13
11
|
import { getMessenger } from './messenger.js';
|
|
14
12
|
import { ActivityService } from './services/activity.js';
|
|
15
|
-
import * as services from './services/index.js';
|
|
16
13
|
import { FlowsService } from './services/flows.js';
|
|
14
|
+
import * as services from './services/index.js';
|
|
17
15
|
import { RevisionsService } from './services/revisions.js';
|
|
18
16
|
import { constructFlowTree } from './utils/construct-flow-tree.js';
|
|
19
17
|
import { getSchema } from './utils/get-schema.js';
|
|
20
18
|
import { JobQueue } from './utils/job-queue.js';
|
|
21
19
|
import { mapValuesDeep } from './utils/map-values-deep.js';
|
|
20
|
+
import { redact } from './utils/redact.js';
|
|
22
21
|
import { sanitizeError } from './utils/sanitize-error.js';
|
|
22
|
+
import { scheduleSynchronizedJob, validateCron } from './utils/schedule.js';
|
|
23
23
|
let flowManager;
|
|
24
|
-
const redactLogs = fastRedact({
|
|
25
|
-
censor: '--redacted--',
|
|
26
|
-
paths: ['*.headers.authorization', '*.access_token', '*.headers.cookie'],
|
|
27
|
-
serialize: false,
|
|
28
|
-
});
|
|
29
24
|
export function getFlowManager() {
|
|
30
25
|
if (flowManager) {
|
|
31
26
|
return flowManager;
|
|
@@ -147,8 +142,8 @@ class FlowManager {
|
|
|
147
142
|
}
|
|
148
143
|
}
|
|
149
144
|
else if (flow.trigger === 'schedule') {
|
|
150
|
-
if (
|
|
151
|
-
const
|
|
145
|
+
if (validateCron(flow.options['cron'])) {
|
|
146
|
+
const job = scheduleSynchronizedJob(flow.id, flow.options['cron'], async () => {
|
|
152
147
|
try {
|
|
153
148
|
await this.executeFlow(flow);
|
|
154
149
|
}
|
|
@@ -156,7 +151,7 @@ class FlowManager {
|
|
|
156
151
|
logger.error(error);
|
|
157
152
|
}
|
|
158
153
|
});
|
|
159
|
-
this.triggerHandlers.push({ id: flow.id, events: [{ type: flow.trigger,
|
|
154
|
+
this.triggerHandlers.push({ id: flow.id, events: [{ type: flow.trigger, job }] });
|
|
160
155
|
}
|
|
161
156
|
else {
|
|
162
157
|
logger.warn(`Couldn't register cron trigger. Provided cron is invalid: ${flow.options['cron']}`);
|
|
@@ -186,7 +181,7 @@ class FlowManager {
|
|
|
186
181
|
this.webhookFlowHandlers[`${method}-${flow.id}`] = handler;
|
|
187
182
|
}
|
|
188
183
|
else if (flow.trigger === 'manual') {
|
|
189
|
-
const handler = (data, context) => {
|
|
184
|
+
const handler = async (data, context) => {
|
|
190
185
|
const enabledCollections = flow.options?.['collections'] ?? [];
|
|
191
186
|
const targetCollection = data?.['body'].collection;
|
|
192
187
|
if (!targetCollection) {
|
|
@@ -203,10 +198,10 @@ class FlowManager {
|
|
|
203
198
|
}
|
|
204
199
|
if (flow.options['async']) {
|
|
205
200
|
this.executeFlow(flow, data, context);
|
|
206
|
-
return undefined;
|
|
201
|
+
return { result: undefined };
|
|
207
202
|
}
|
|
208
203
|
else {
|
|
209
|
-
return this.executeFlow(flow, data, context);
|
|
204
|
+
return { result: await this.executeFlow(flow, data, context) };
|
|
210
205
|
}
|
|
211
206
|
};
|
|
212
207
|
// Default return to $last for manual
|
|
@@ -218,7 +213,7 @@ class FlowManager {
|
|
|
218
213
|
}
|
|
219
214
|
async unload() {
|
|
220
215
|
for (const trigger of this.triggerHandlers) {
|
|
221
|
-
trigger.events
|
|
216
|
+
for (const event of trigger.events) {
|
|
222
217
|
switch (event.type) {
|
|
223
218
|
case 'filter':
|
|
224
219
|
emitter.offFilter(event.name, event.handler);
|
|
@@ -227,10 +222,10 @@ class FlowManager {
|
|
|
227
222
|
emitter.offAction(event.name, event.handler);
|
|
228
223
|
break;
|
|
229
224
|
case 'schedule':
|
|
230
|
-
event.
|
|
225
|
+
await event.job.stop();
|
|
231
226
|
break;
|
|
232
227
|
}
|
|
233
|
-
}
|
|
228
|
+
}
|
|
234
229
|
}
|
|
235
230
|
this.triggerHandlers = [];
|
|
236
231
|
this.operationFlowHandlers = {};
|
|
@@ -283,7 +278,13 @@ class FlowManager {
|
|
|
283
278
|
item: flow.id,
|
|
284
279
|
data: {
|
|
285
280
|
steps: steps,
|
|
286
|
-
data:
|
|
281
|
+
data: redact(omit(keyedData, '$accountability.permissions'), // Permissions is a ton of data, and is just a copy of what's in the directus_permissions table
|
|
282
|
+
[
|
|
283
|
+
['**', 'headers', 'authorization'],
|
|
284
|
+
['**', 'headers', 'cookie'],
|
|
285
|
+
['**', 'query', 'access_token'],
|
|
286
|
+
['**', 'payload', 'password'],
|
|
287
|
+
], REDACTED_TEXT),
|
|
287
288
|
},
|
|
288
289
|
});
|
|
289
290
|
}
|
package/dist/logger.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="qs" />
|
|
2
|
-
import type { LoggerOptions } from 'pino';
|
|
3
2
|
import type { RequestHandler } from 'express';
|
|
3
|
+
import type { LoggerOptions } from 'pino';
|
|
4
4
|
export declare const httpLoggerOptions: LoggerOptions;
|
|
5
5
|
declare const logger: import("pino").Logger<LoggerOptions & Record<string, any>>;
|
|
6
6
|
export declare const expressLogger: RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
package/dist/logger.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
+
import { REDACTED_TEXT } from '@directus/constants';
|
|
1
2
|
import { toArray } from '@directus/utils';
|
|
2
3
|
import { merge } from 'lodash-es';
|
|
3
4
|
import { pino } from 'pino';
|
|
4
5
|
import { pinoHttp, stdSerializers } from 'pino-http';
|
|
5
6
|
import { URL } from 'url';
|
|
6
7
|
import env from './env.js';
|
|
7
|
-
import { REDACT_TEXT } from './constants.js';
|
|
8
8
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
9
9
|
const pinoOptions = {
|
|
10
10
|
level: env['LOG_LEVEL'] || 'info',
|
|
11
11
|
redact: {
|
|
12
12
|
paths: ['req.headers.authorization', 'req.headers.cookie'],
|
|
13
|
-
censor:
|
|
13
|
+
censor: REDACTED_TEXT,
|
|
14
14
|
},
|
|
15
15
|
};
|
|
16
16
|
export const httpLoggerOptions = {
|
|
17
17
|
level: env['LOG_LEVEL'] || 'info',
|
|
18
18
|
redact: {
|
|
19
19
|
paths: ['req.headers.authorization', 'req.headers.cookie'],
|
|
20
|
-
censor:
|
|
20
|
+
censor: REDACTED_TEXT,
|
|
21
21
|
},
|
|
22
22
|
};
|
|
23
23
|
if (env['LOG_STYLE'] !== 'raw') {
|
|
@@ -48,11 +48,11 @@ if (env['LOG_STYLE'] === 'raw') {
|
|
|
48
48
|
const path = pathParts.join('.');
|
|
49
49
|
if (path === 'res.headers') {
|
|
50
50
|
if ('set-cookie' in value) {
|
|
51
|
-
value['set-cookie'] =
|
|
51
|
+
value['set-cookie'] = REDACTED_TEXT;
|
|
52
52
|
}
|
|
53
53
|
return value;
|
|
54
54
|
}
|
|
55
|
-
return
|
|
55
|
+
return REDACTED_TEXT;
|
|
56
56
|
},
|
|
57
57
|
};
|
|
58
58
|
}
|
|
@@ -99,7 +99,7 @@ export default logger;
|
|
|
99
99
|
function redactQuery(originalPath) {
|
|
100
100
|
const url = new URL(originalPath, 'http://example.com/');
|
|
101
101
|
if (url.searchParams.has('access_token')) {
|
|
102
|
-
url.searchParams.set('access_token',
|
|
102
|
+
url.searchParams.set('access_token', REDACTED_TEXT);
|
|
103
103
|
}
|
|
104
104
|
return url.pathname + url.search;
|
|
105
105
|
}
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isUpToDate } from '@directus/update-check';
|
|
2
1
|
import { createTerminus } from '@godaddy/terminus';
|
|
3
2
|
import * as http from 'http';
|
|
4
3
|
import * as https from 'https';
|
|
@@ -11,7 +10,6 @@ import emitter from './emitter.js';
|
|
|
11
10
|
import env from './env.js';
|
|
12
11
|
import logger from './logger.js';
|
|
13
12
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
14
|
-
import * as pkg from './utils/package.js';
|
|
15
13
|
export let SERVER_ONLINE = true;
|
|
16
14
|
export async function createServer() {
|
|
17
15
|
const server = http.createServer(await createApp());
|
|
@@ -107,15 +105,6 @@ export async function startServer() {
|
|
|
107
105
|
const port = env['PORT'];
|
|
108
106
|
server
|
|
109
107
|
.listen(port, host, () => {
|
|
110
|
-
isUpToDate(pkg.name, pkg.version)
|
|
111
|
-
.then((update) => {
|
|
112
|
-
if (update) {
|
|
113
|
-
logger.warn(`Update available: ${pkg.version} -> ${update}`);
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
.catch(() => {
|
|
117
|
-
// No need to log/warn here. The update message is only an informative nice-to-have
|
|
118
|
-
});
|
|
119
108
|
logger.info(`Server started at http://${host}:${port}`);
|
|
120
109
|
emitter.emitAction('server.start', { server }, {
|
|
121
110
|
database: getDatabase(),
|
|
@@ -3,14 +3,14 @@ import type { Range, Stat } from '@directus/storage';
|
|
|
3
3
|
import type { Accountability } from '@directus/types';
|
|
4
4
|
import type { Knex } from 'knex';
|
|
5
5
|
import type { Readable } from 'node:stream';
|
|
6
|
-
import type { AbstractServiceOptions,
|
|
6
|
+
import type { AbstractServiceOptions, TransformationSet } from '../types/index.js';
|
|
7
7
|
import { AuthorizationService } from './authorization.js';
|
|
8
8
|
export declare class AssetsService {
|
|
9
9
|
knex: Knex;
|
|
10
10
|
accountability: Accountability | null;
|
|
11
11
|
authorizationService: AuthorizationService;
|
|
12
12
|
constructor(options: AbstractServiceOptions);
|
|
13
|
-
getAsset(id: string, transformation:
|
|
13
|
+
getAsset(id: string, transformation: TransformationSet, range?: Range): Promise<{
|
|
14
14
|
stream: Readable;
|
|
15
15
|
file: any;
|
|
16
16
|
stat: Stat;
|
|
@@ -12,6 +12,7 @@ import { ForbiddenException, InvalidPayloadException } from '../exceptions/index
|
|
|
12
12
|
import { FieldsService } from '../services/fields.js';
|
|
13
13
|
import { ItemsService } from '../services/items.js';
|
|
14
14
|
import { getSchema } from '../utils/get-schema.js';
|
|
15
|
+
import { shouldClearCache } from '../utils/should-clear-cache.js';
|
|
15
16
|
export class CollectionsService {
|
|
16
17
|
knex;
|
|
17
18
|
helpers;
|
|
@@ -130,7 +131,7 @@ export class CollectionsService {
|
|
|
130
131
|
return payload.collection;
|
|
131
132
|
}
|
|
132
133
|
finally {
|
|
133
|
-
if (this.cache
|
|
134
|
+
if (shouldClearCache(this.cache, opts)) {
|
|
134
135
|
await this.cache.clear();
|
|
135
136
|
}
|
|
136
137
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -171,7 +172,7 @@ export class CollectionsService {
|
|
|
171
172
|
return collections;
|
|
172
173
|
}
|
|
173
174
|
finally {
|
|
174
|
-
if (this.cache
|
|
175
|
+
if (shouldClearCache(this.cache, opts)) {
|
|
175
176
|
await this.cache.clear();
|
|
176
177
|
}
|
|
177
178
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -315,7 +316,7 @@ export class CollectionsService {
|
|
|
315
316
|
return collectionKey;
|
|
316
317
|
}
|
|
317
318
|
finally {
|
|
318
|
-
if (this.cache
|
|
319
|
+
if (shouldClearCache(this.cache, opts)) {
|
|
319
320
|
await this.cache.clear();
|
|
320
321
|
}
|
|
321
322
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -363,7 +364,7 @@ export class CollectionsService {
|
|
|
363
364
|
});
|
|
364
365
|
}
|
|
365
366
|
finally {
|
|
366
|
-
if (this.cache
|
|
367
|
+
if (shouldClearCache(this.cache, opts)) {
|
|
367
368
|
await this.cache.clear();
|
|
368
369
|
}
|
|
369
370
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -405,7 +406,7 @@ export class CollectionsService {
|
|
|
405
406
|
return collectionKeys;
|
|
406
407
|
}
|
|
407
408
|
finally {
|
|
408
|
-
if (this.cache
|
|
409
|
+
if (shouldClearCache(this.cache, opts)) {
|
|
409
410
|
await this.cache.clear();
|
|
410
411
|
}
|
|
411
412
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -510,7 +511,7 @@ export class CollectionsService {
|
|
|
510
511
|
return collectionKey;
|
|
511
512
|
}
|
|
512
513
|
finally {
|
|
513
|
-
if (this.cache
|
|
514
|
+
if (shouldClearCache(this.cache, opts)) {
|
|
514
515
|
await this.cache.clear();
|
|
515
516
|
}
|
|
516
517
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -551,7 +552,7 @@ export class CollectionsService {
|
|
|
551
552
|
return collectionKeys;
|
|
552
553
|
}
|
|
553
554
|
finally {
|
|
554
|
-
if (this.cache
|
|
555
|
+
if (shouldClearCache(this.cache, opts)) {
|
|
555
556
|
await this.cache.clear();
|
|
556
557
|
}
|
|
557
558
|
if (opts?.autoPurgeSystemCache !== false) {
|
package/dist/services/fields.js
CHANGED
|
@@ -8,7 +8,6 @@ import { getHelpers } from '../database/helpers/index.js';
|
|
|
8
8
|
import getDatabase, { getSchemaInspector } from '../database/index.js';
|
|
9
9
|
import { systemFieldRows } from '../database/system-data/fields/index.js';
|
|
10
10
|
import emitter from '../emitter.js';
|
|
11
|
-
import env from '../env.js';
|
|
12
11
|
import { translateDatabaseError } from '../exceptions/database/translate.js';
|
|
13
12
|
import { ForbiddenException, InvalidPayloadException } from '../exceptions/index.js';
|
|
14
13
|
import { ItemsService } from '../services/items.js';
|
|
@@ -17,6 +16,7 @@ import getDefaultValue from '../utils/get-default-value.js';
|
|
|
17
16
|
import getLocalType from '../utils/get-local-type.js';
|
|
18
17
|
import { getSchema } from '../utils/get-schema.js';
|
|
19
18
|
import { sanitizeColumn } from '../utils/sanitize-schema.js';
|
|
19
|
+
import { shouldClearCache } from '../utils/should-clear-cache.js';
|
|
20
20
|
import { RelationsService } from './relations.js';
|
|
21
21
|
export class FieldsService {
|
|
22
22
|
knex;
|
|
@@ -270,7 +270,7 @@ export class FieldsService {
|
|
|
270
270
|
if (runPostColumnChange) {
|
|
271
271
|
await this.helpers.schema.postColumnChange();
|
|
272
272
|
}
|
|
273
|
-
if (this.cache
|
|
273
|
+
if (shouldClearCache(this.cache, opts)) {
|
|
274
274
|
await this.cache.clear();
|
|
275
275
|
}
|
|
276
276
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -311,7 +311,9 @@ export class FieldsService {
|
|
|
311
311
|
}
|
|
312
312
|
if (hookAdjustedField.schema) {
|
|
313
313
|
const existingColumn = await this.schemaInspector.columnInfo(collection, hookAdjustedField.field);
|
|
314
|
-
|
|
314
|
+
// Sanitize column only when applying snapshot diff as opts is only passed from /utils/apply-diff.ts
|
|
315
|
+
const columnToCompare = opts?.bypassLimits && opts.autoPurgeSystemCache === false ? sanitizeColumn(existingColumn) : existingColumn;
|
|
316
|
+
if (!isEqual(columnToCompare, hookAdjustedField.schema)) {
|
|
315
317
|
try {
|
|
316
318
|
await this.knex.schema.alterTable(collection, (table) => {
|
|
317
319
|
if (!hookAdjustedField.schema)
|
|
@@ -365,7 +367,7 @@ export class FieldsService {
|
|
|
365
367
|
if (runPostColumnChange) {
|
|
366
368
|
await this.helpers.schema.postColumnChange();
|
|
367
369
|
}
|
|
368
|
-
if (this.cache
|
|
370
|
+
if (shouldClearCache(this.cache, opts)) {
|
|
369
371
|
await this.cache.clear();
|
|
370
372
|
}
|
|
371
373
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -495,7 +497,7 @@ export class FieldsService {
|
|
|
495
497
|
if (runPostColumnChange) {
|
|
496
498
|
await this.helpers.schema.postColumnChange();
|
|
497
499
|
}
|
|
498
|
-
if (this.cache
|
|
500
|
+
if (shouldClearCache(this.cache, opts)) {
|
|
499
501
|
await this.cache.clear();
|
|
500
502
|
}
|
|
501
503
|
if (opts?.autoPurgeSystemCache !== false) {
|
package/dist/services/files.d.ts
CHANGED
|
@@ -26,9 +26,9 @@ export declare class FilesService extends ItemsService {
|
|
|
26
26
|
/**
|
|
27
27
|
* Delete a file
|
|
28
28
|
*/
|
|
29
|
-
deleteOne(key: PrimaryKey
|
|
29
|
+
deleteOne(key: PrimaryKey): Promise<PrimaryKey>;
|
|
30
30
|
/**
|
|
31
31
|
* Delete multiple files
|
|
32
32
|
*/
|
|
33
|
-
deleteMany(keys: PrimaryKey[]
|
|
33
|
+
deleteMany(keys: PrimaryKey[]): Promise<PrimaryKey[]>;
|
|
34
34
|
}
|
package/dist/services/files.js
CHANGED
|
@@ -66,6 +66,7 @@ export class FilesService extends ItemsService {
|
|
|
66
66
|
catch (err) {
|
|
67
67
|
logger.warn(`Couldn't save file ${payload.filename_disk}`);
|
|
68
68
|
logger.warn(err);
|
|
69
|
+
await this.deleteOne(primaryKey);
|
|
69
70
|
throw new ServiceUnavailableException(`Couldn't save file ${payload.filename_disk}`, { service: 'files' });
|
|
70
71
|
}
|
|
71
72
|
const { size } = await storage.location(data.storage).stat(payload.filename_disk);
|
|
@@ -87,9 +88,6 @@ export class FilesService extends ItemsService {
|
|
|
87
88
|
schema: this.schema,
|
|
88
89
|
});
|
|
89
90
|
await sudoService.updateOne(primaryKey, payload, { emitEvents: false });
|
|
90
|
-
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
91
|
-
await this.cache.clear();
|
|
92
|
-
}
|
|
93
91
|
if (opts?.emitEvents !== false) {
|
|
94
92
|
emitter.emitAction('files.upload', {
|
|
95
93
|
payload,
|
|
@@ -244,14 +242,14 @@ export class FilesService extends ItemsService {
|
|
|
244
242
|
/**
|
|
245
243
|
* Delete a file
|
|
246
244
|
*/
|
|
247
|
-
async deleteOne(key
|
|
248
|
-
await this.deleteMany([key]
|
|
245
|
+
async deleteOne(key) {
|
|
246
|
+
await this.deleteMany([key]);
|
|
249
247
|
return key;
|
|
250
248
|
}
|
|
251
249
|
/**
|
|
252
250
|
* Delete multiple files
|
|
253
251
|
*/
|
|
254
|
-
async deleteMany(keys
|
|
252
|
+
async deleteMany(keys) {
|
|
255
253
|
const storage = await getStorage();
|
|
256
254
|
const files = await super.readMany(keys, { fields: ['id', 'storage'], limit: -1 });
|
|
257
255
|
if (!files) {
|
|
@@ -265,9 +263,6 @@ export class FilesService extends ItemsService {
|
|
|
265
263
|
await disk.delete(filepath);
|
|
266
264
|
}
|
|
267
265
|
}
|
|
268
|
-
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
269
|
-
await this.cache.clear();
|
|
270
|
-
}
|
|
271
266
|
return keys;
|
|
272
267
|
}
|
|
273
268
|
}
|
|
@@ -1539,49 +1539,12 @@ export class GraphQLService {
|
|
|
1539
1539
|
},
|
|
1540
1540
|
}),
|
|
1541
1541
|
},
|
|
1542
|
-
|
|
1543
|
-
}
|
|
1544
|
-
if (this.accountability?.admin === true) {
|
|
1545
|
-
ServerInfo.addFields({
|
|
1546
|
-
directus: {
|
|
1547
|
-
type: new GraphQLObjectType({
|
|
1548
|
-
name: 'server_info_directus',
|
|
1549
|
-
fields: {
|
|
1550
|
-
version: {
|
|
1551
|
-
type: GraphQLString,
|
|
1552
|
-
},
|
|
1553
|
-
},
|
|
1554
|
-
}),
|
|
1555
|
-
},
|
|
1556
|
-
node: {
|
|
1557
|
-
type: new GraphQLObjectType({
|
|
1558
|
-
name: 'server_info_node',
|
|
1559
|
-
fields: {
|
|
1560
|
-
version: {
|
|
1561
|
-
type: GraphQLString,
|
|
1562
|
-
},
|
|
1563
|
-
uptime: {
|
|
1564
|
-
type: GraphQLInt,
|
|
1565
|
-
},
|
|
1566
|
-
},
|
|
1567
|
-
}),
|
|
1568
|
-
},
|
|
1569
|
-
os: {
|
|
1542
|
+
queryLimit: {
|
|
1570
1543
|
type: new GraphQLObjectType({
|
|
1571
|
-
name: '
|
|
1544
|
+
name: 'server_info_query_limit',
|
|
1572
1545
|
fields: {
|
|
1573
|
-
type:
|
|
1574
|
-
|
|
1575
|
-
},
|
|
1576
|
-
version: {
|
|
1577
|
-
type: GraphQLString,
|
|
1578
|
-
},
|
|
1579
|
-
uptime: {
|
|
1580
|
-
type: GraphQLInt,
|
|
1581
|
-
},
|
|
1582
|
-
totalmem: {
|
|
1583
|
-
type: GraphQLInt,
|
|
1584
|
-
},
|
|
1546
|
+
default: { type: GraphQLInt },
|
|
1547
|
+
max: { type: GraphQLInt },
|
|
1585
1548
|
},
|
|
1586
1549
|
}),
|
|
1587
1550
|
},
|
package/dist/services/index.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export * from './settings.js';
|
|
|
28
28
|
export * from './shares.js';
|
|
29
29
|
export * from './specifications.js';
|
|
30
30
|
export * from './tfa.js';
|
|
31
|
+
export * from './translations.js';
|
|
31
32
|
export * from './users.js';
|
|
32
33
|
export * from './utils.js';
|
|
33
34
|
export * from './webhooks.js';
|
package/dist/services/index.js
CHANGED
|
@@ -28,6 +28,7 @@ export * from './settings.js';
|
|
|
28
28
|
export * from './shares.js';
|
|
29
29
|
export * from './specifications.js';
|
|
30
30
|
export * from './tfa.js';
|
|
31
|
+
export * from './translations.js';
|
|
31
32
|
export * from './users.js';
|
|
32
33
|
export * from './utils.js';
|
|
33
34
|
export * from './webhooks.js';
|