@directus/api 10.2.0 → 11.0.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.
Files changed (58) hide show
  1. package/dist/app.js +4 -3
  2. package/dist/cli/utils/create-env/env-stub.liquid +3 -0
  3. package/dist/constants.d.ts +0 -1
  4. package/dist/constants.js +0 -1
  5. package/dist/controllers/files.js +19 -1
  6. package/dist/controllers/permissions.js +7 -4
  7. package/dist/controllers/translations.d.ts +2 -0
  8. package/dist/controllers/translations.js +149 -0
  9. package/dist/controllers/users.js +1 -1
  10. package/dist/database/migrations/20230525A-add-preview-settings.d.ts +3 -0
  11. package/dist/database/migrations/20230525A-add-preview-settings.js +10 -0
  12. package/dist/database/migrations/20230526A-migrate-translation-strings.d.ts +3 -0
  13. package/dist/database/migrations/20230526A-migrate-translation-strings.js +54 -0
  14. package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +3 -0
  15. package/dist/database/system-data/collections/collections.yaml +23 -0
  16. package/dist/database/system-data/fields/collections.yaml +16 -0
  17. package/dist/database/system-data/fields/settings.yaml +0 -5
  18. package/dist/database/system-data/fields/translations.yaml +27 -0
  19. package/dist/env.js +14 -0
  20. package/dist/exceptions/content-too-large.d.ts +4 -0
  21. package/dist/exceptions/content-too-large.js +6 -0
  22. package/dist/extensions.js +13 -11
  23. package/dist/flows.d.ts +1 -1
  24. package/dist/flows.js +20 -19
  25. package/dist/logger.d.ts +1 -1
  26. package/dist/logger.js +6 -6
  27. package/dist/server.js +0 -11
  28. package/dist/services/collections.js +8 -7
  29. package/dist/services/fields.js +4 -4
  30. package/dist/services/files.d.ts +2 -2
  31. package/dist/services/files.js +4 -9
  32. package/dist/services/graphql/index.js +0 -46
  33. package/dist/services/index.d.ts +1 -0
  34. package/dist/services/index.js +1 -0
  35. package/dist/services/items.js +10 -9
  36. package/dist/services/revisions.d.ts +6 -1
  37. package/dist/services/revisions.js +24 -0
  38. package/dist/services/server.js +0 -18
  39. package/dist/services/specifications.d.ts +2 -2
  40. package/dist/services/specifications.js +6 -5
  41. package/dist/services/translations.d.ts +10 -0
  42. package/dist/services/translations.js +36 -0
  43. package/dist/synchronization.d.ts +7 -0
  44. package/dist/synchronization.js +120 -0
  45. package/dist/types/events.d.ts +2 -2
  46. package/dist/utils/apply-query.d.ts +9 -2
  47. package/dist/utils/apply-query.js +41 -14
  48. package/dist/utils/md.js +1 -1
  49. package/dist/utils/redact.d.ts +11 -0
  50. package/dist/utils/redact.js +75 -0
  51. package/dist/utils/schedule.d.ts +5 -0
  52. package/dist/utils/schedule.js +27 -0
  53. package/dist/utils/should-clear-cache.d.ts +10 -0
  54. package/dist/utils/should-clear-cache.js +18 -0
  55. package/dist/utils/should-skip-cache.js +18 -2
  56. package/package.json +49 -53
  57. package/dist/utils/get-os-info.d.ts +0 -9
  58. package/dist/utils/get-os-info.js +0 -40
package/dist/app.js CHANGED
@@ -32,6 +32,7 @@ import schemaRouter from './controllers/schema.js';
32
32
  import serverRouter from './controllers/server.js';
33
33
  import settingsRouter from './controllers/settings.js';
34
34
  import sharesRouter from './controllers/shares.js';
35
+ import translationsRouter from './controllers/translations.js';
35
36
  import usersRouter from './controllers/users.js';
36
37
  import utilsRouter from './controllers/utils.js';
37
38
  import webhooksRouter from './controllers/webhooks.js';
@@ -111,11 +112,10 @@ export default async function createApp() {
111
112
  // friendly. Ref #10806
112
113
  upgradeInsecureRequests: null,
113
114
  // These are required for MapLibre
114
- // https://cdn.directus.io is required for images/videos in the official docs
115
115
  workerSrc: ["'self'", 'blob:'],
116
116
  childSrc: ["'self'", 'blob:'],
117
- imgSrc: ["'self'", 'data:', 'blob:', 'https://cdn.directus.io'],
118
- mediaSrc: ["'self'", 'https://cdn.directus.io'],
117
+ imgSrc: ["'self'", 'data:', 'blob:'],
118
+ mediaSrc: ["'self'"],
119
119
  connectSrc: ["'self'", 'https://*'],
120
120
  },
121
121
  }, getConfigFromEnv('CONTENT_SECURITY_POLICY_'))));
@@ -213,6 +213,7 @@ export default async function createApp() {
213
213
  app.use('/panels', panelsRouter);
214
214
  app.use('/permissions', permissionsRouter);
215
215
  app.use('/presets', presetsRouter);
216
+ app.use('/translations', translationsRouter);
216
217
  app.use('/relations', relationsRouter);
217
218
  app.use('/revisions', revisionsRouter);
218
219
  app.use('/roles', rolesRouter);
@@ -153,6 +153,9 @@ CACHE_ENABLED=false
153
153
  # Automatically purge the cache on create, update, and delete actions. [false]
154
154
  # CACHE_AUTO_PURGE=true
155
155
 
156
+ # List of collections that prevent cache purging when `CACHE_AUTO_PURGE` is enabled. ["directus_activity,directus_presets"]
157
+ # CACHE_AUTO_PURGE_IGNORE_LIST="directus_activity,directus_presets"
158
+
156
159
  # memory | redis | memcache
157
160
  CACHE_STORE=memory
158
161
 
@@ -14,4 +14,3 @@ export declare const OAS_REQUIRED_SCHEMAS: string[];
14
14
  export declare const SUPPORTED_IMAGE_TRANSFORM_FORMATS: string[];
15
15
  /** Formats where metadata extraction is supported */
16
16
  export declare const SUPPORTED_IMAGE_METADATA_FORMATS: string[];
17
- export declare const REDACT_TEXT = "--redact--";
package/dist/constants.js CHANGED
@@ -67,4 +67,3 @@ export const SUPPORTED_IMAGE_METADATA_FORMATS = [
67
67
  'image/tiff',
68
68
  'image/avif',
69
69
  ];
70
- export const REDACT_TEXT = '--redact--';
@@ -1,10 +1,13 @@
1
1
  import formatTitle from '@directus/format-title';
2
2
  import { toArray } from '@directus/utils';
3
3
  import Busboy from 'busboy';
4
+ import bytes from 'bytes';
4
5
  import express from 'express';
5
6
  import Joi from 'joi';
7
+ import { minimatch } from 'minimatch';
6
8
  import path from 'path';
7
9
  import env from '../env.js';
10
+ import { ContentTooLargeException } from '../exceptions/content-too-large.js';
8
11
  import { ForbiddenException, InvalidPayloadException } from '../exceptions/index.js';
9
12
  import { respond } from '../middleware/respond.js';
10
13
  import useCollection from '../middleware/use-collection.js';
@@ -28,7 +31,13 @@ export const multipartHandler = (req, res, next) => {
28
31
  'content-type': 'application/octet-stream',
29
32
  };
30
33
  }
31
- const busboy = Busboy({ headers, defParamCharset: 'utf8' });
34
+ const busboy = Busboy({
35
+ headers,
36
+ defParamCharset: 'utf8',
37
+ limits: {
38
+ fileSize: env['FILES_MAX_UPLOAD_SIZE'] ? bytes(env['FILES_MAX_UPLOAD_SIZE']) : undefined,
39
+ },
40
+ });
32
41
  const savedFiles = [];
33
42
  const service = new FilesService({ accountability: req.accountability, schema: req.schema });
34
43
  const existingPrimaryKey = req.params['pk'] || undefined;
@@ -57,6 +66,11 @@ export const multipartHandler = (req, res, next) => {
57
66
  if (!filename) {
58
67
  return busboy.emit('error', new InvalidPayloadException(`File is missing filename`));
59
68
  }
69
+ const allowedPatterns = toArray(env['FILES_MIME_TYPE_ALLOW_LIST']);
70
+ const mimeTypeAllowed = allowedPatterns.some((pattern) => minimatch(mimeType, pattern));
71
+ if (mimeTypeAllowed === false) {
72
+ return busboy.emit('error', new InvalidPayloadException(`File is of invalid content type`));
73
+ }
60
74
  fileCount++;
61
75
  if (!existingPrimaryKey) {
62
76
  if (!payload.title) {
@@ -71,6 +85,10 @@ export const multipartHandler = (req, res, next) => {
71
85
  };
72
86
  // Clear the payload for the next to-be-uploaded file
73
87
  payload = {};
88
+ fileStream.on('limit', () => {
89
+ const error = new ContentTooLargeException(`Uploaded file is too large`);
90
+ next(error);
91
+ });
74
92
  try {
75
93
  const primaryKey = await service.uploadOne(fileStream, payloadWithRequiredFields, existingPrimaryKey);
76
94
  savedFiles.push(primaryKey);
@@ -51,16 +51,19 @@ const readHandler = asyncHandler(async (req, res, next) => {
51
51
  schema: req.schema,
52
52
  });
53
53
  let result;
54
+ // TODO fix this at the service level
55
+ // temporary fix for missing permissions https://github.com/directus/directus/issues/18654
56
+ const temporaryQuery = { ...req.sanitizedQuery, limit: -1 };
54
57
  if (req.singleton) {
55
- result = await service.readSingleton(req.sanitizedQuery);
58
+ result = await service.readSingleton(temporaryQuery);
56
59
  }
57
60
  else if (req.body.keys) {
58
- result = await service.readMany(req.body.keys, req.sanitizedQuery);
61
+ result = await service.readMany(req.body.keys, temporaryQuery);
59
62
  }
60
63
  else {
61
- result = await service.readByQuery(req.sanitizedQuery);
64
+ result = await service.readByQuery(temporaryQuery);
62
65
  }
63
- const meta = await metaService.getMetaForQuery('directus_permissions', req.sanitizedQuery);
66
+ const meta = await metaService.getMetaForQuery('directus_permissions', temporaryQuery);
64
67
  res.locals['payload'] = { data: result, meta };
65
68
  return next();
66
69
  });
@@ -0,0 +1,2 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
@@ -0,0 +1,149 @@
1
+ import express from 'express';
2
+ import { ForbiddenException } from '../exceptions/index.js';
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 { TranslationsService } from '../services/translations.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_translations'));
12
+ router.post('/', asyncHandler(async (req, res, next) => {
13
+ const service = new TranslationsService({
14
+ accountability: req.accountability,
15
+ schema: req.schema,
16
+ });
17
+ const savedKeys = [];
18
+ if (Array.isArray(req.body)) {
19
+ const keys = await service.createMany(req.body);
20
+ savedKeys.push(...keys);
21
+ }
22
+ else {
23
+ const primaryKey = await service.createOne(req.body);
24
+ savedKeys.push(primaryKey);
25
+ }
26
+ try {
27
+ if (Array.isArray(req.body)) {
28
+ const records = await service.readMany(savedKeys, req.sanitizedQuery);
29
+ res.locals['payload'] = { data: records };
30
+ }
31
+ else {
32
+ const record = await service.readOne(savedKeys[0], req.sanitizedQuery);
33
+ res.locals['payload'] = { data: record };
34
+ }
35
+ }
36
+ catch (error) {
37
+ if (error instanceof ForbiddenException) {
38
+ return next();
39
+ }
40
+ throw error;
41
+ }
42
+ return next();
43
+ }), respond);
44
+ const readHandler = asyncHandler(async (req, res, next) => {
45
+ const service = new TranslationsService({
46
+ accountability: req.accountability,
47
+ schema: req.schema,
48
+ });
49
+ const metaService = new MetaService({
50
+ accountability: req.accountability,
51
+ schema: req.schema,
52
+ });
53
+ let result;
54
+ if (req.singleton) {
55
+ result = await service.readSingleton(req.sanitizedQuery);
56
+ }
57
+ else if (req.body.keys) {
58
+ result = await service.readMany(req.body.keys, req.sanitizedQuery);
59
+ }
60
+ else {
61
+ result = await service.readByQuery(req.sanitizedQuery);
62
+ }
63
+ const meta = await metaService.getMetaForQuery('directus_translations', req.sanitizedQuery);
64
+ res.locals['payload'] = { data: result, meta };
65
+ return next();
66
+ });
67
+ router.get('/', validateBatch('read'), readHandler, respond);
68
+ router.search('/', validateBatch('read'), readHandler, respond);
69
+ router.get('/:pk', asyncHandler(async (req, res, next) => {
70
+ const service = new TranslationsService({
71
+ accountability: req.accountability,
72
+ schema: req.schema,
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 TranslationsService({
80
+ accountability: req.accountability,
81
+ schema: req.schema,
82
+ });
83
+ let keys = [];
84
+ if (Array.isArray(req.body)) {
85
+ keys = await service.updateBatch(req.body);
86
+ }
87
+ else if (req.body.keys) {
88
+ keys = await service.updateMany(req.body.keys, req.body.data);
89
+ }
90
+ else {
91
+ const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
92
+ keys = await service.updateByQuery(sanitizedQuery, req.body.data);
93
+ }
94
+ try {
95
+ const result = await service.readMany(keys, req.sanitizedQuery);
96
+ res.locals['payload'] = { data: result || null };
97
+ }
98
+ catch (error) {
99
+ if (error instanceof ForbiddenException) {
100
+ return next();
101
+ }
102
+ throw error;
103
+ }
104
+ return next();
105
+ }), respond);
106
+ router.patch('/:pk', asyncHandler(async (req, res, next) => {
107
+ const service = new TranslationsService({
108
+ accountability: req.accountability,
109
+ schema: req.schema,
110
+ });
111
+ const primaryKey = await service.updateOne(req.params['pk'], req.body);
112
+ try {
113
+ const record = await service.readOne(primaryKey, req.sanitizedQuery);
114
+ res.locals['payload'] = { data: record || null };
115
+ }
116
+ catch (error) {
117
+ if (error instanceof ForbiddenException) {
118
+ return next();
119
+ }
120
+ throw error;
121
+ }
122
+ return next();
123
+ }), respond);
124
+ router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next) => {
125
+ const service = new TranslationsService({
126
+ accountability: req.accountability,
127
+ schema: req.schema,
128
+ });
129
+ if (Array.isArray(req.body)) {
130
+ await service.deleteMany(req.body);
131
+ }
132
+ else if (req.body.keys) {
133
+ await service.deleteMany(req.body.keys);
134
+ }
135
+ else {
136
+ const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
137
+ await service.deleteByQuery(sanitizedQuery);
138
+ }
139
+ return next();
140
+ }), respond);
141
+ router.delete('/:pk', asyncHandler(async (req, _res, next) => {
142
+ const service = new TranslationsService({
143
+ accountability: req.accountability,
144
+ schema: req.schema,
145
+ });
146
+ await service.deleteOne(req.params['pk']);
147
+ return next();
148
+ }), respond);
149
+ export default router;
@@ -126,7 +126,7 @@ router.patch('/me/track/page', asyncHandler(async (req, _res, next) => {
126
126
  throw new InvalidPayloadException(`"last_page" key is required.`);
127
127
  }
128
128
  const service = new UsersService({ schema: req.schema });
129
- await service.updateOne(req.accountability.user, { last_page: req.body.last_page });
129
+ await service.updateOne(req.accountability.user, { last_page: req.body.last_page }, { autoPurgeCache: false });
130
130
  return next();
131
131
  }), respond);
132
132
  router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) => {
@@ -0,0 +1,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,10 @@
1
+ export async function up(knex) {
2
+ await knex.schema.alterTable('directus_collections', (table) => {
3
+ table.string('preview_url').nullable();
4
+ });
5
+ }
6
+ export async function down(knex) {
7
+ await knex.schema.alterTable('directus_collections', (table) => {
8
+ table.dropColumn('preview_url');
9
+ });
10
+ }
@@ -0,0 +1,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,54 @@
1
+ import { set } from 'lodash-es';
2
+ import { v4 as uuid } from 'uuid';
3
+ function transformStringsNewFormat(oldStrings) {
4
+ return oldStrings.reduce((result, item) => {
5
+ if (!item.key || !item.translations)
6
+ return result;
7
+ for (const [language, value] of Object.entries(item.translations)) {
8
+ result.push({ id: uuid(), key: item.key, language, value });
9
+ }
10
+ return result;
11
+ }, []);
12
+ }
13
+ function transformStringsOldFormat(newStrings) {
14
+ const keyCache = {};
15
+ for (const { key, language, value } of newStrings) {
16
+ set(keyCache, [key, language], value);
17
+ }
18
+ return Object.entries(keyCache).map(([key, translations]) => ({ key, translations }));
19
+ }
20
+ export async function up(knex) {
21
+ await knex.schema.createTable('directus_translations', (table) => {
22
+ table.uuid('id').primary().notNullable();
23
+ table.string('language').notNullable();
24
+ table.string('key').notNullable();
25
+ table.text('value').notNullable();
26
+ });
27
+ const data = await knex.select('translation_strings', 'id').from('directus_settings').first();
28
+ if (data?.translation_strings && data?.id) {
29
+ const parsedTranslationStrings = typeof data.translation_strings === 'string' ? JSON.parse(data.translation_strings) : data.translation_strings;
30
+ const newTranslationStrings = transformStringsNewFormat(parsedTranslationStrings);
31
+ for (const item of newTranslationStrings) {
32
+ await knex('directus_translations').insert(item);
33
+ }
34
+ }
35
+ await knex.schema.alterTable('directus_settings', (table) => {
36
+ table.dropColumn('translation_strings');
37
+ });
38
+ }
39
+ export async function down(knex) {
40
+ const data = await knex.select('language', 'key', 'value').from('directus_translations');
41
+ const settingsId = await knex.select('id').from('directus_settings').first();
42
+ await knex.schema.alterTable('directus_settings', (table) => {
43
+ table.json('translation_strings');
44
+ });
45
+ if (settingsId?.id && data) {
46
+ const oldTranslationStrings = transformStringsOldFormat(data);
47
+ await knex('directus_settings')
48
+ .where({ id: settingsId.id })
49
+ .update({
50
+ translation_strings: JSON.stringify(oldTranslationStrings),
51
+ });
52
+ }
53
+ await knex.schema.dropTable('directus_translations');
54
+ }
@@ -57,6 +57,9 @@
57
57
  - collection: directus_settings
58
58
  action: read
59
59
 
60
+ - collection: directus_translations
61
+ action: read
62
+
60
63
  - collection: directus_notifications
61
64
  action: read
62
65
  permissions:
@@ -14,42 +14,55 @@ data:
14
14
  - collection: directus_activity
15
15
  note: $t:directus_collection.directus_activity
16
16
  accountability: null
17
+
17
18
  - collection: directus_collections
18
19
  icon: list_alt
19
20
  note: $t:directus_collection.directus_collections
21
+
20
22
  - collection: directus_fields
21
23
  icon: input
22
24
  note: $t:directus_collection.directus_fields
25
+
23
26
  - collection: directus_files
24
27
  icon: folder
25
28
  note: $t:directus_collection.directus_files
26
29
  display_template: '{{ $thumbnail }} {{ title }}'
30
+
27
31
  - collection: directus_folders
28
32
  note: $t:directus_collection.directus_folders
29
33
  display_template: '{{ name }}'
34
+
30
35
  - collection: directus_migrations
31
36
  note: $t:directus_collection.directus_migrations
37
+
32
38
  - collection: directus_permissions
33
39
  icon: admin_panel_settings
34
40
  note: $t:directus_collection.directus_permissions
41
+
35
42
  - collection: directus_presets
36
43
  icon: bookmark
37
44
  note: $t:directus_collection.directus_presets
38
45
  accountability: null
46
+
39
47
  - collection: directus_relations
40
48
  icon: merge_type
41
49
  note: $t:directus_collection.directus_relations
50
+
42
51
  - collection: directus_revisions
43
52
  note: $t:directus_collection.directus_revisions
44
53
  accountability: null
54
+
45
55
  - collection: directus_roles
46
56
  icon: supervised_user_circle
47
57
  note: $t:directus_collection.directus_roles
58
+
48
59
  - collection: directus_sessions
49
60
  note: $t:directus_collection.directus_sessions
61
+
50
62
  - collection: directus_settings
51
63
  singleton: true
52
64
  note: $t:directus_collection.directus_settings
65
+
53
66
  - collection: directus_users
54
67
  archive_field: status
55
68
  archive_value: archived
@@ -57,18 +70,28 @@ data:
57
70
  icon: people_alt
58
71
  note: $t:directus_collection.directus_users
59
72
  display_template: '{{ first_name }} {{ last_name }}'
73
+
60
74
  - collection: directus_webhooks
61
75
  note: $t:directus_collection.directus_webhooks
76
+
62
77
  - collection: directus_dashboards
63
78
  note: $t:directus_collection.directus_dashboards
79
+
64
80
  - collection: directus_panels
65
81
  note: $t:directus_collection.directus_panels
82
+
66
83
  - collection: directus_notifications
67
84
  note: $t:directus_collection.directus_notifications
85
+
68
86
  - collection: directus_shares
69
87
  icon: share
70
88
  note: $t:directus_collection.directus_shares
89
+
71
90
  - collection: directus_flows
72
91
  note: $t:directus_collection.directus_flows
92
+
73
93
  - collection: directus_operations
74
94
  note: $t:directus_collection.directus_operations
95
+
96
+ - collection: directus_translations
97
+ note: $t:directus_collection.directus_translations
@@ -100,6 +100,22 @@ fields:
100
100
  placeholder: $t:field_options.directus_collections.translation_placeholder
101
101
  width: full
102
102
 
103
+ - field: preview_divider
104
+ special:
105
+ - alias
106
+ - no-data
107
+ interface: presentation-divider
108
+ options:
109
+ icon: preview
110
+ title: $t:field_options.directus_collections.preview_divider
111
+ width: full
112
+
113
+ - field: preview_url
114
+ interface: system-display-template
115
+ options:
116
+ collectionField: collection
117
+ width: full
118
+
103
119
  - field: archive_divider
104
120
  special:
105
121
  - alias
@@ -375,11 +375,6 @@ fields:
375
375
  options:
376
376
  placeholder: $t:fields.directus_settings.attribution_placeholder
377
377
 
378
- - field: translation_strings
379
- special:
380
- - cast-json
381
- hidden: true
382
-
383
378
  - field: image_editor
384
379
  interface: presentation-divider
385
380
  options:
@@ -0,0 +1,27 @@
1
+ table: directus_translations
2
+
3
+ fields:
4
+ - field: id
5
+ hidden: true
6
+ sort: 1
7
+ special:
8
+ - uuid
9
+ - field: key
10
+ width: half
11
+ sort: 2
12
+ required: true
13
+ interface: input
14
+ options:
15
+ font: monospace
16
+ placeholder: '$t:translation_key_placeholder'
17
+ - field: language
18
+ interface: system-language
19
+ width: half
20
+ sort: 3
21
+ required: true
22
+ - field: value
23
+ interface: input-multiline
24
+ sort: 4
25
+ required: true
26
+ options:
27
+ placeholder: '$t:enter_a_value'
package/dist/env.js CHANGED
@@ -68,6 +68,7 @@ const allowedEnvironmentVars = [
68
68
  'CACHE_TTL',
69
69
  'CACHE_CONTROL_S_MAXAGE',
70
70
  'CACHE_AUTO_PURGE',
71
+ 'CACHE_AUTO_PURGE_IGNORE_LIST',
71
72
  'CACHE_SYSTEM_TTL',
72
73
  'CACHE_SCHEMA',
73
74
  'CACHE_PERMISSIONS',
@@ -102,6 +103,9 @@ const allowedEnvironmentVars = [
102
103
  'STORAGE_.+_HEALTHCHECK_THRESHOLD',
103
104
  // metadata
104
105
  'FILE_METADATA_ALLOW_LIST',
106
+ // files
107
+ 'FILES_MAX_UPLOAD_SIZE',
108
+ 'FILES_CONTENT_TYPE_ALLOW_LIST',
105
109
  // assets
106
110
  'ASSETS_CACHE_TTL',
107
111
  'ASSETS_TRANSFORM_MAX_CONCURRENT',
@@ -157,6 +161,13 @@ const allowedEnvironmentVars = [
157
161
  'MESSENGER_REDIS_HOST',
158
162
  'MESSENGER_REDIS_PORT',
159
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',
160
171
  // emails
161
172
  'EMAIL_FROM',
162
173
  'EMAIL_TRANSPORT',
@@ -233,6 +244,7 @@ const defaults = {
233
244
  CACHE_TTL: '5m',
234
245
  CACHE_NAMESPACE: 'system-cache',
235
246
  CACHE_AUTO_PURGE: false,
247
+ CACHE_AUTO_PURGE_IGNORE_LIST: 'directus_activity,directus_presets',
236
248
  CACHE_CONTROL_S_MAXAGE: '0',
237
249
  CACHE_SCHEMA: true,
238
250
  CACHE_PERMISSIONS: true,
@@ -272,6 +284,7 @@ const defaults = {
272
284
  PRESSURE_LIMITER_MAX_MEMORY_RSS: false,
273
285
  PRESSURE_LIMITER_MAX_MEMORY_HEAP_USED: false,
274
286
  PRESSURE_LIMITER_RETRY_AFTER: false,
287
+ FILES_MIME_TYPE_ALLOW_LIST: '*/*',
275
288
  };
276
289
  // Allows us to force certain environment variable into a type, instead of relying
277
290
  // on the auto-parsed type in processValues. ref #3705
@@ -285,6 +298,7 @@ const typeMap = {
285
298
  DB_PORT: 'number',
286
299
  DB_EXCLUDE_TABLES: 'array',
287
300
  CACHE_SKIP_ALLOWED: 'boolean',
301
+ CACHE_AUTO_PURGE_IGNORE_LIST: 'array',
288
302
  IMPORT_IP_DENY_LIST: 'array',
289
303
  FILE_METADATA_ALLOW_LIST: 'array',
290
304
  GRAPHQL_INTROSPECTION: 'boolean',
@@ -0,0 +1,4 @@
1
+ import { BaseException } from '@directus/exceptions';
2
+ export declare class ContentTooLargeException extends BaseException {
3
+ constructor(message: string);
4
+ }
@@ -0,0 +1,6 @@
1
+ import { BaseException } from '@directus/exceptions';
2
+ export class ContentTooLargeException extends BaseException {
3
+ constructor(message) {
4
+ super(message, 413, 'CONTENT_TOO_LARGE');
5
+ }
6
+ }