directus 9.5.0 → 9.5.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.
@@ -20,7 +20,7 @@ const router = express_1.default.Router();
20
20
  router.use((0, use_collection_1.default)('directus_files'));
21
21
  const multipartHandler = (0, async_handler_1.default)(async (req, res, next) => {
22
22
  if (req.is('multipart/form-data') === false)
23
- throw new exceptions_1.UnsupportedMediaTypeException(`Unsupported Content-Type header`);
23
+ return next();
24
24
  let headers;
25
25
  if (req.headers['content-type']) {
26
26
  headers = req.headers;
@@ -93,6 +93,9 @@ const multipartHandler = (0, async_handler_1.default)(async (req, res, next) =>
93
93
  }
94
94
  });
95
95
  router.post('/', multipartHandler, (0, async_handler_1.default)(async (req, res, next) => {
96
+ if (req.is('multipart/form-data') === false) {
97
+ throw new exceptions_1.UnsupportedMediaTypeException(`Unsupported Content-Type header`);
98
+ }
96
99
  const service = new services_1.FilesService({
97
100
  accountability: req.accountability,
98
101
  schema: req.schema,
@@ -103,16 +103,14 @@ function getDatabase() {
103
103
  // act the same
104
104
  (0, lodash_1.merge)(knexConfig, { connection: { options: { useUTC: false } } });
105
105
  }
106
- if (env_1.default.DB_CLIENT === 'mysql' && !env_1.default.DB_CHARSET) {
107
- logger_1.default.warn(`DB_CHARSET hasn't been set. Please make sure DB_CHARSET matches your database's collation.`);
108
- }
109
106
  database = (0, knex_1.knex)(knexConfig);
107
+ validateDatabaseCharset(database);
110
108
  const times = {};
111
109
  database
112
110
  .on('query', (queryInfo) => {
113
111
  times[queryInfo.__knexUid] = perf_hooks_1.performance.now();
114
112
  })
115
- .on('query-response', (response, queryInfo) => {
113
+ .on('query-response', (_response, queryInfo) => {
116
114
  const delta = perf_hooks_1.performance.now() - times[queryInfo.__knexUid];
117
115
  logger_1.default.trace(`[${delta.toFixed(3)}ms] ${queryInfo.sql} [${queryInfo.bindings.join(', ')}]`);
118
116
  delete times[queryInfo.__knexUid];
@@ -234,3 +232,34 @@ async function validateDatabaseExtensions() {
234
232
  }
235
233
  }
236
234
  exports.validateDatabaseExtensions = validateDatabaseExtensions;
235
+ async function validateDatabaseCharset(database) {
236
+ database = database !== null && database !== void 0 ? database : getDatabase();
237
+ if (getDatabaseClient(database) === 'mysql') {
238
+ if (env_1.default.DB_CHARSET) {
239
+ logger_1.default.warn(`Using custom DB_CHARSET "${env_1.default.DB_CHARSET}". Using a charset different from the database's default can cause problems in relationships. Omitting DB_CHARSET is strongly recommended.`);
240
+ }
241
+ const { collation } = await database.select(database.raw(`@@collation_database as collation`)).first();
242
+ const tables = await database('information_schema.tables')
243
+ .select({ name: 'TABLE_NAME', collation: 'TABLE_COLLATION' })
244
+ .where({ TABLE_SCHEMA: env_1.default.DB_DATABASE });
245
+ const columns = await database('information_schema.columns')
246
+ .select({ table_name: 'TABLE_NAME', name: 'COLUMN_NAME', collation: 'COLLATION_NAME' })
247
+ .where({ TABLE_SCHEMA: env_1.default.DB_DATABASE })
248
+ .whereNot({ COLLATION_NAME: collation });
249
+ let inconsistencies = '';
250
+ for (const table of tables) {
251
+ const tableColumns = columns.filter((column) => column.table_name === table.name);
252
+ const tableHasInvalidCollation = table.collation !== collation;
253
+ if (tableHasInvalidCollation || tableColumns.length > 0) {
254
+ inconsistencies += `\t\t- Table "${table.name}": "${table.collation}"\n`;
255
+ for (const column of tableColumns) {
256
+ inconsistencies += `\t\t - Column "${column.name}": "${column.collation}"\n`;
257
+ }
258
+ }
259
+ }
260
+ if (inconsistencies) {
261
+ logger_1.default.warn(`Some tables and columns do not match your database's default collation (${collation}):\n${inconsistencies}`);
262
+ }
263
+ }
264
+ return;
265
+ }
@@ -15,7 +15,7 @@ const extractToken = (req, res, next) => {
15
15
  }
16
16
  if (req.headers && req.headers.authorization) {
17
17
  const parts = req.headers.authorization.split(' ');
18
- if (parts.length === 2 && parts[0] === 'Bearer') {
18
+ if (parts.length === 2 && parts[0].toLowerCase() === 'bearer') {
19
19
  token = parts[1];
20
20
  }
21
21
  }
@@ -66,24 +66,7 @@ class CollectionsService {
66
66
  // permission problems. This might not work reliably in MySQL, as it doesn't support DDL in
67
67
  // transactions.
68
68
  await this.knex.transaction(async (trx) => {
69
- if (payload.meta) {
70
- const collectionItemsService = new items_1.ItemsService('directus_collections', {
71
- knex: trx,
72
- accountability: this.accountability,
73
- schema: this.schema,
74
- });
75
- await collectionItemsService.createOne({
76
- ...payload.meta,
77
- collection: payload.collection,
78
- });
79
- }
80
69
  if (payload.schema) {
81
- const fieldsService = new fields_1.FieldsService({ knex: trx, schema: this.schema });
82
- const fieldItemsService = new items_1.ItemsService('directus_fields', {
83
- knex: trx,
84
- accountability: this.accountability,
85
- schema: this.schema,
86
- });
87
70
  // Directus heavily relies on the primary key of a collection, so we have to make sure that
88
71
  // every collection that is created has a primary key. If no primary key field is created
89
72
  // while making the collection, we default to an auto incremented id named `id`
@@ -114,18 +97,33 @@ class CollectionsService {
114
97
  }
115
98
  return field;
116
99
  });
117
- await trx.transaction(async (schemaTrx) => {
118
- await schemaTrx.schema.createTable(payload.collection, (table) => {
119
- for (const field of payload.fields) {
120
- if (field.type && constants_1.ALIAS_TYPES.includes(field.type) === false) {
121
- fieldsService.addColumnToTable(table, field);
122
- }
100
+ const fieldsService = new fields_1.FieldsService({ knex: trx, schema: this.schema });
101
+ await trx.schema.createTable(payload.collection, (table) => {
102
+ for (const field of payload.fields) {
103
+ if (field.type && constants_1.ALIAS_TYPES.includes(field.type) === false) {
104
+ fieldsService.addColumnToTable(table, field);
123
105
  }
124
- });
106
+ }
107
+ });
108
+ const fieldItemsService = new items_1.ItemsService('directus_fields', {
109
+ knex: trx,
110
+ accountability: this.accountability,
111
+ schema: this.schema,
125
112
  });
126
113
  const fieldPayloads = payload.fields.filter((field) => field.meta).map((field) => field.meta);
127
114
  await fieldItemsService.createMany(fieldPayloads);
128
115
  }
116
+ if (payload.meta) {
117
+ const collectionItemsService = new items_1.ItemsService('directus_collections', {
118
+ knex: trx,
119
+ accountability: this.accountability,
120
+ schema: this.schema,
121
+ });
122
+ await collectionItemsService.createOne({
123
+ ...payload.meta,
124
+ collection: payload.collection,
125
+ });
126
+ }
129
127
  return payload.collection;
130
128
  });
131
129
  if (this.cache && env_1.default.CACHE_AUTO_PURGE && (opts === null || opts === void 0 ? void 0 : opts.autoPurgeCache) !== false) {
@@ -318,6 +316,7 @@ class CollectionsService {
318
316
  }
319
317
  await this.knex.transaction(async (trx) => {
320
318
  var _a;
319
+ await trx.schema.dropTable(collectionKey);
321
320
  // Make sure this collection isn't used as a group in any other collections
322
321
  await trx('directus_collections').update({ group: null }).where({ group: collectionKey });
323
322
  if (collectionToBeDeleted.meta) {
@@ -373,9 +372,6 @@ class CollectionsService {
373
372
  .update({ one_allowed_collections: newAllowedCollections })
374
373
  .where({ id: relation.meta.id });
375
374
  }
376
- await trx.transaction(async (schemaTrx) => {
377
- await schemaTrx.schema.dropTable(collectionKey);
378
- });
379
375
  }
380
376
  });
381
377
  if (this.cache && env_1.default.CACHE_AUTO_PURGE && (opts === null || opts === void 0 ? void 0 : opts.autoPurgeCache) !== false) {
@@ -224,10 +224,8 @@ class FieldsService {
224
224
  this.addColumnToTable(table, hookAdjustedField);
225
225
  }
226
226
  else {
227
- await trx.transaction(async (schemaTrx) => {
228
- await schemaTrx.schema.alterTable(collection, (table) => {
229
- this.addColumnToTable(table, hookAdjustedField);
230
- });
227
+ await trx.schema.alterTable(collection, (table) => {
228
+ this.addColumnToTable(table, hookAdjustedField);
231
229
  });
232
230
  }
233
231
  }
@@ -327,6 +325,13 @@ class FieldsService {
327
325
  });
328
326
  await this.knex.transaction(async (trx) => {
329
327
  var _a, _b;
328
+ if (this.schema.collections[collection] &&
329
+ field in this.schema.collections[collection].fields &&
330
+ this.schema.collections[collection].fields[field].alias === false) {
331
+ await trx.schema.table(collection, (table) => {
332
+ table.dropColumn(field);
333
+ });
334
+ }
330
335
  const relations = this.schema.relations.filter((relation) => {
331
336
  var _a;
332
337
  return ((relation.collection === collection && relation.field === field) ||
@@ -386,15 +391,6 @@ class FieldsService {
386
391
  .where({ group: metaRow.field, collection: metaRow.collection });
387
392
  }
388
393
  await trx('directus_fields').delete().where({ collection, field });
389
- if (this.schema.collections[collection] &&
390
- field in this.schema.collections[collection].fields &&
391
- this.schema.collections[collection].fields[field].alias === false) {
392
- await trx.transaction(async (schemaTrx) => {
393
- await schemaTrx.schema.table(collection, (table) => {
394
- table.dropColumn(field);
395
- });
396
- });
397
- }
398
394
  });
399
395
  if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
400
396
  await this.cache.clear();
@@ -410,7 +406,7 @@ class FieldsService {
410
406
  });
411
407
  }
412
408
  addColumnToTable(table, field, alter = null) {
413
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
409
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
414
410
  let column;
415
411
  // Don't attempt to add a DB column for alias / corrupt fields
416
412
  if (field.type === 'alias' || field.type === 'unknown')
@@ -456,21 +452,25 @@ class FieldsService {
456
452
  column.defaultTo(field.schema.default_value);
457
453
  }
458
454
  }
459
- if (((_g = field.schema) === null || _g === void 0 ? void 0 : _g.is_nullable) !== undefined && field.schema.is_nullable === false) {
460
- column.notNullable();
455
+ if (((_g = field.schema) === null || _g === void 0 ? void 0 : _g.is_nullable) === false) {
456
+ if (!alter || alter.is_nullable === true) {
457
+ column.notNullable();
458
+ }
461
459
  }
462
- else {
463
- column.nullable();
460
+ else if (((_h = field.schema) === null || _h === void 0 ? void 0 : _h.is_nullable) === true) {
461
+ if (!alter || alter.is_nullable === false) {
462
+ column.nullable();
463
+ }
464
464
  }
465
- if ((_h = field.schema) === null || _h === void 0 ? void 0 : _h.is_primary_key) {
465
+ if ((_j = field.schema) === null || _j === void 0 ? void 0 : _j.is_primary_key) {
466
466
  column.primary().notNullable();
467
467
  }
468
- else if (((_j = field.schema) === null || _j === void 0 ? void 0 : _j.is_unique) === true) {
468
+ else if (((_k = field.schema) === null || _k === void 0 ? void 0 : _k.is_unique) === true) {
469
469
  if (!alter || alter.is_unique === false) {
470
470
  column.unique();
471
471
  }
472
472
  }
473
- else if (((_k = field.schema) === null || _k === void 0 ? void 0 : _k.is_unique) === false) {
473
+ else if (((_l = field.schema) === null || _l === void 0 ? void 0 : _l.is_unique) === false) {
474
474
  if (alter && alter.is_unique === true) {
475
475
  table.dropUnique([field.field]);
476
476
  }
@@ -100,12 +100,16 @@ class PayloadService {
100
100
  return value;
101
101
  },
102
102
  async csv({ action, value }) {
103
- if (!value)
103
+ if (Array.isArray(value) === false && typeof value !== 'string')
104
104
  return;
105
- if (action === 'read' && Array.isArray(value) === false)
105
+ if (action === 'read' && Array.isArray(value) === false) {
106
+ if (value === '')
107
+ return [];
106
108
  return value.split(',');
107
- if (Array.isArray(value))
109
+ }
110
+ if (Array.isArray(value)) {
108
111
  return value.join(',');
112
+ }
109
113
  return value;
110
114
  },
111
115
  };
@@ -145,7 +145,7 @@ function processPermissions(accountability, permissions, filterContext) {
145
145
  return permissions.map((permission) => {
146
146
  permission.permissions = (0, utils_1.parseFilter)(permission.permissions, accountability, filterContext);
147
147
  permission.validation = (0, utils_1.parseFilter)(permission.validation, accountability, filterContext);
148
- permission.presets = (0, utils_1.parseFilter)(permission.presets, accountability, filterContext);
148
+ permission.presets = (0, utils_1.parsePreset)(permission.presets, accountability, filterContext);
149
149
  return permission;
150
150
  });
151
151
  }
package/dist/utils/md.js CHANGED
@@ -10,6 +10,6 @@ const sanitize_html_1 = __importDefault(require("sanitize-html"));
10
10
  * Render and sanitize a markdown string
11
11
  */
12
12
  function md(str) {
13
- return (0, sanitize_html_1.default)((0, marked_1.parse)(str));
13
+ return (0, sanitize_html_1.default)((0, marked_1.marked)(str));
14
14
  }
15
15
  exports.md = md;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "directus",
3
- "version": "9.5.0",
3
+ "version": "9.5.1",
4
4
  "license": "GPL-3.0-only",
5
5
  "homepage": "https://github.com/directus/directus#readme",
6
6
  "description": "Directus is a real-time API and App dashboard for managing SQL database content.",
@@ -63,7 +63,8 @@
63
63
  "cleanup": "rimraf dist",
64
64
  "dev": "cross-env NODE_ENV=development SERVE_APP=false ts-node-dev --files --transpile-only --respawn --watch \".env\" --inspect=0 --exit-child -- src/start.ts",
65
65
  "cli": "cross-env NODE_ENV=development SERVE_APP=false ts-node --script-mode --transpile-only src/cli/run.ts",
66
- "test": "jest --coverage",
66
+ "test": "jest",
67
+ "test:coverage": "jest --coverage",
67
68
  "test:watch": "jest --watchAll"
68
69
  },
69
70
  "engines": {
@@ -77,16 +78,16 @@
77
78
  ],
78
79
  "dependencies": {
79
80
  "@aws-sdk/client-ses": "^3.40.0",
80
- "@directus/app": "9.5.0",
81
- "@directus/drive": "9.5.0",
82
- "@directus/drive-azure": "9.5.0",
83
- "@directus/drive-gcs": "9.5.0",
84
- "@directus/drive-s3": "9.5.0",
85
- "@directus/extensions-sdk": "9.5.0",
86
- "@directus/format-title": "9.5.0",
87
- "@directus/schema": "9.5.0",
88
- "@directus/shared": "9.5.0",
89
- "@directus/specs": "9.5.0",
81
+ "@directus/app": "9.5.1",
82
+ "@directus/drive": "9.5.1",
83
+ "@directus/drive-azure": "9.5.1",
84
+ "@directus/drive-gcs": "9.5.1",
85
+ "@directus/drive-s3": "9.5.1",
86
+ "@directus/extensions-sdk": "9.5.1",
87
+ "@directus/format-title": "9.5.1",
88
+ "@directus/schema": "9.5.1",
89
+ "@directus/shared": "9.5.1",
90
+ "@directus/specs": "9.5.1",
90
91
  "@godaddy/terminus": "^4.9.0",
91
92
  "@rollup/plugin-alias": "^3.1.2",
92
93
  "@rollup/plugin-virtual": "^2.0.3",
@@ -125,7 +126,7 @@
125
126
  "jsonwebtoken": "^8.5.1",
126
127
  "keyv": "^4.0.3",
127
128
  "knex": "^0.95.14",
128
- "knex-schema-inspector": "1.7.2",
129
+ "knex-schema-inspector": "1.7.3",
129
130
  "ldapjs": "^2.3.1",
130
131
  "liquidjs": "^9.25.0",
131
132
  "lodash": "^4.17.21",
@@ -172,7 +173,7 @@
172
173
  "sqlite3": "^5.0.2",
173
174
  "tedious": "^13.0.0"
174
175
  },
175
- "gitHead": "78a25fe9538dcd30452dc1a0889e720040c0596c",
176
+ "gitHead": "c0c412d30b116bd38a2690b62463f775d37db79c",
176
177
  "devDependencies": {
177
178
  "@types/async": "3.2.10",
178
179
  "@types/atob": "2.1.2",
@@ -195,12 +196,15 @@
195
196
  "@types/keyv": "3.1.3",
196
197
  "@types/ldapjs": "2.2.2",
197
198
  "@types/lodash": "4.14.177",
199
+ "@types/marked": "4.0.1",
198
200
  "@types/mime-types": "2.1.1",
199
201
  "@types/ms": "0.7.31",
200
202
  "@types/node": "16.11.9",
201
203
  "@types/node-cron": "2.0.5",
202
204
  "@types/nodemailer": "6.4.4",
203
205
  "@types/object-hash": "2.2.1",
206
+ "@types/pino": "6.3.12",
207
+ "@types/pino-http": "5.8.0",
204
208
  "@types/qs": "6.9.7",
205
209
  "@types/sanitize-html": "2.5.0",
206
210
  "@types/sharp": "0.29.4",