directus 9.6.0 → 9.8.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.
Files changed (99) hide show
  1. package/dist/app.js +3 -1
  2. package/dist/auth/drivers/oauth2.js +3 -0
  3. package/dist/auth/drivers/openid.js +3 -0
  4. package/dist/cache.d.ts +4 -1
  5. package/dist/cache.js +29 -7
  6. package/dist/cli/commands/schema/apply.d.ts +1 -0
  7. package/dist/cli/commands/schema/apply.js +9 -5
  8. package/dist/cli/index.js +1 -0
  9. package/dist/controllers/assets.js +9 -1
  10. package/dist/controllers/utils.js +18 -1
  11. package/dist/database/helpers/date/dialects/default.d.ts +3 -0
  12. package/dist/database/helpers/date/dialects/default.js +7 -0
  13. package/dist/database/helpers/date/dialects/sqlite.d.ts +0 -9
  14. package/dist/database/helpers/date/dialects/sqlite.js +0 -24
  15. package/dist/database/helpers/date/index.d.ts +6 -6
  16. package/dist/database/helpers/date/index.js +13 -13
  17. package/dist/database/helpers/date/types.d.ts +0 -9
  18. package/dist/database/helpers/{date → fn}/dialects/mssql.d.ts +3 -2
  19. package/dist/database/helpers/{date → fn}/dialects/mssql.js +14 -3
  20. package/dist/database/helpers/{date → fn}/dialects/mysql.d.ts +3 -2
  21. package/dist/database/helpers/{date → fn}/dialects/mysql.js +14 -3
  22. package/dist/database/helpers/{date → fn}/dialects/oracle.d.ts +3 -2
  23. package/dist/database/helpers/{date → fn}/dialects/oracle.js +14 -3
  24. package/dist/database/helpers/{date → fn}/dialects/postgres.d.ts +3 -2
  25. package/dist/database/helpers/{date → fn}/dialects/postgres.js +14 -3
  26. package/dist/database/helpers/fn/dialects/sqlite.d.ts +13 -0
  27. package/dist/database/helpers/fn/dialects/sqlite.js +42 -0
  28. package/dist/database/helpers/fn/index.d.ts +7 -0
  29. package/dist/database/helpers/fn/index.js +17 -0
  30. package/dist/database/helpers/fn/types.d.ts +18 -0
  31. package/dist/database/helpers/fn/types.js +27 -0
  32. package/dist/database/helpers/index.d.ts +4 -1
  33. package/dist/database/helpers/index.js +7 -1
  34. package/dist/database/index.js +0 -3
  35. package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.d.ts +3 -0
  36. package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.js +17 -0
  37. package/dist/database/migrations/20220314A-add-translation-strings.d.ts +3 -0
  38. package/dist/database/migrations/20220314A-add-translation-strings.js +15 -0
  39. package/dist/database/migrations/20220322A-rename-field-typecast-flags.d.ts +3 -0
  40. package/dist/database/migrations/20220322A-rename-field-typecast-flags.js +77 -0
  41. package/dist/database/migrations/20220323A-add-field-validation.d.ts +3 -0
  42. package/dist/database/migrations/20220323A-add-field-validation.js +17 -0
  43. package/dist/database/migrations/20220325A-fix-typecast-flags.d.ts +3 -0
  44. package/dist/database/migrations/20220325A-fix-typecast-flags.js +49 -0
  45. package/dist/database/migrations/20220325B-add-default-language.d.ts +3 -0
  46. package/dist/database/migrations/20220325B-add-default-language.js +28 -0
  47. package/dist/database/migrations/run.js +1 -1
  48. package/dist/database/run-ast.js +11 -3
  49. package/dist/database/system-data/fields/activity.yaml +4 -4
  50. package/dist/database/system-data/fields/collections.yaml +4 -4
  51. package/dist/database/system-data/fields/fields.yaml +17 -8
  52. package/dist/database/system-data/fields/files.yaml +2 -2
  53. package/dist/database/system-data/fields/panels.yaml +2 -2
  54. package/dist/database/system-data/fields/permissions.yaml +4 -4
  55. package/dist/database/system-data/fields/presets.yaml +17 -3
  56. package/dist/database/system-data/fields/relations.yaml +1 -1
  57. package/dist/database/system-data/fields/revisions.yaml +2 -2
  58. package/dist/database/system-data/fields/roles.yaml +4 -4
  59. package/dist/database/system-data/fields/settings.yaml +19 -4
  60. package/dist/database/system-data/fields/users.yaml +2 -2
  61. package/dist/database/system-data/fields/webhooks.yaml +4 -4
  62. package/dist/env.js +6 -2
  63. package/dist/exceptions/database/dialects/mysql.js +23 -17
  64. package/dist/logger.js +2 -1
  65. package/dist/middleware/respond.js +7 -28
  66. package/dist/services/activity.js +4 -1
  67. package/dist/services/authorization.d.ts +1 -1
  68. package/dist/services/authorization.js +156 -14
  69. package/dist/services/collections.js +222 -198
  70. package/dist/services/fields.js +184 -173
  71. package/dist/services/files.js +69 -3
  72. package/dist/services/graphql.js +2 -2
  73. package/dist/services/import-export.d.ts +34 -0
  74. package/dist/services/import-export.js +270 -0
  75. package/dist/services/index.d.ts +2 -1
  76. package/dist/services/index.js +2 -1
  77. package/dist/services/items.js +1 -0
  78. package/dist/services/payload.js +11 -9
  79. package/dist/services/permissions.js +10 -10
  80. package/dist/services/relations.js +93 -78
  81. package/dist/services/server.js +1 -0
  82. package/dist/services/shares.js +2 -1
  83. package/dist/services/users.js +3 -1
  84. package/dist/utils/apply-query.js +21 -3
  85. package/dist/utils/get-column.d.ts +6 -5
  86. package/dist/utils/get-column.js +16 -8
  87. package/dist/utils/get-date-formatted.d.ts +1 -0
  88. package/dist/utils/get-date-formatted.js +14 -0
  89. package/dist/utils/get-local-type.js +2 -2
  90. package/dist/utils/get-permissions.js +1 -1
  91. package/dist/utils/get-schema.d.ts +1 -1
  92. package/dist/utils/get-schema.js +16 -11
  93. package/dist/utils/track.js +3 -2
  94. package/dist/utils/url.d.ts +1 -1
  95. package/dist/utils/url.js +1 -1
  96. package/dist/utils/validate-storage.js +3 -1
  97. package/package.json +13 -12
  98. package/dist/services/import.d.ts +0 -13
  99. package/dist/services/import.js +0 -118
@@ -16,6 +16,7 @@ const users_1 = require("./users");
16
16
  const mail_1 = require("./mail");
17
17
  const user_name_1 = require("../utils/user-name");
18
18
  const md_1 = require("../utils/md");
19
+ const url_1 = require("../utils/url");
19
20
  class SharesService extends items_1.ItemsService {
20
21
  constructor(options) {
21
22
  super('directus_shares', options);
@@ -116,7 +117,7 @@ Hello!
116
117
 
117
118
  ${(0, user_name_1.userName)(userInfo)} has invited you to view an item in ${share.collection}.
118
119
 
119
- [Open](${env_1.default.PUBLIC_URL}/admin/shared/${payload.share})
120
+ [Open](${new url_1.Url(env_1.default.PUBLIC_URL).addPath('admin', 'shared', payload.share).toString()})
120
121
  `;
121
122
  for (const email of payload.emails) {
122
123
  await mailService.send({
@@ -264,7 +264,9 @@ class UsersService extends items_1.ItemsService {
264
264
  });
265
265
  const payload = { email, scope: 'password-reset', hash: (0, utils_2.getSimpleHash)('' + user.password) };
266
266
  const token = jsonwebtoken_1.default.sign(payload, env_1.default.SECRET, { expiresIn: '1d', issuer: 'directus' });
267
- const acceptURL = url ? `${url}?token=${token}` : `${env_1.default.PUBLIC_URL}/admin/reset-password?token=${token}`;
267
+ const acceptURL = url
268
+ ? new url_1.Url(url).setQuery('token', token).toString()
269
+ : new url_1.Url(env_1.default.PUBLIC_URL).addPath('admin', 'reset-password').setQuery('token', token);
268
270
  const subjectLine = subject ? subject : 'Password Reset Request';
269
271
  await mailService.send({
270
272
  to: email,
@@ -11,6 +11,7 @@ const exceptions_1 = require("../exceptions");
11
11
  const get_column_1 = require("./get-column");
12
12
  const get_relation_type_1 = require("./get-relation-type");
13
13
  const helpers_1 = require("../database/helpers");
14
+ const utils_1 = require("@directus/shared/utils");
14
15
  const generateAlias = (0, nanoid_1.customAlphabet)('abcdefghijklmnopqrstuvwxyz', 5);
15
16
  /**
16
17
  * Apply the Query to a given Knex query builder instance
@@ -26,7 +27,7 @@ function applyQuery(knex, collection, dbQuery, query, schema, subQuery = false)
26
27
  }
27
28
  return {
28
29
  order,
29
- column: (0, get_column_1.getColumn)(knex, collection, column, false),
30
+ column: (0, get_column_1.getColumn)(knex, collection, column, false, schema),
30
31
  };
31
32
  }));
32
33
  }
@@ -43,7 +44,7 @@ function applyQuery(knex, collection, dbQuery, query, schema, subQuery = false)
43
44
  applySearch(schema, dbQuery, query.search, collection);
44
45
  }
45
46
  if (query.group) {
46
- dbQuery.groupBy(query.group.map((column) => (0, get_column_1.getColumn)(knex, collection, column, false)));
47
+ dbQuery.groupBy(query.group.map((column) => (0, get_column_1.getColumn)(knex, collection, column, false, schema)));
47
48
  }
48
49
  if (query.aggregate) {
49
50
  applyAggregate(dbQuery, query.aggregate, collection);
@@ -234,7 +235,7 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
234
235
  function applyFilterToQuery(key, operator, compareValue, logical = 'and') {
235
236
  const [table, column] = key.split('.');
236
237
  // Is processed through Knex.Raw, so should be safe to string-inject into these where queries
237
- const selectionRaw = (0, get_column_1.getColumn)(knex, table, column, false);
238
+ const selectionRaw = (0, get_column_1.getColumn)(knex, table, column, false, schema);
238
239
  // Knex supports "raw" in the columnName parameter, but isn't typed as such. Too bad..
239
240
  // See https://github.com/knex/knex/issues/4518 @TODO remove as any once knex is updated
240
241
  // These operators don't rely on a value, and can thus be used without one (eg `?filter[field][_null]`)
@@ -254,6 +255,15 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
254
255
  query.where(key, '!=', '');
255
256
  });
256
257
  }
258
+ // Cast filter value (compareValue) based on function used
259
+ if (column.includes('(') && column.includes(')')) {
260
+ const functionName = column.split('(')[0];
261
+ const type = (0, utils_1.getOutputTypeForFunction)(functionName);
262
+ if (['bigInteger', 'integer', 'float', 'decimal'].includes(type)) {
263
+ compareValue = Number(compareValue);
264
+ }
265
+ }
266
+ // Cast filter value (compareValue) based on type of field being filtered against
257
267
  const [collection, field] = key.split('.');
258
268
  if (collection in schema.collections && field in schema.collections[collection].fields) {
259
269
  const type = schema.collections[collection].fields[field].type;
@@ -265,6 +275,14 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
265
275
  compareValue = helpers.date.parse(compareValue);
266
276
  }
267
277
  }
278
+ if (['bigInteger', 'integer', 'float', 'decimal'].includes(type)) {
279
+ if (Array.isArray(compareValue)) {
280
+ compareValue = compareValue.map((val) => Number(val));
281
+ }
282
+ else {
283
+ compareValue = Number(compareValue);
284
+ }
285
+ }
268
286
  }
269
287
  // The following fields however, require a value to be run. If no value is passed, we
270
288
  // ignore them. This allows easier use in GraphQL, where you wouldn't be able to
@@ -1,12 +1,13 @@
1
+ import { SchemaOverview } from '@directus/shared/types';
1
2
  import { Knex } from 'knex';
2
3
  /**
3
- * Return column prefixed by table. If column includes functions (like `year(date_created)`, the
4
- * column is replaced with the appropriate SQL)
4
+ * Return column prefixed by table. If column includes functions (like `year(date_created)`), the
5
+ * column is replaced with the appropriate SQL
5
6
  *
6
7
  * @param knex Current knex / transaction instance
7
- * @param collection Collection or alias in which column resides
8
- * @param field name of the column
8
+ * @param table Collection or alias in which column resides
9
+ * @param column name of the column
9
10
  * @param alias Whether or not to add a SQL AS statement
10
11
  * @returns Knex raw instance
11
12
  */
12
- export declare function getColumn(knex: Knex, table: string, column: string, alias?: string | false): Knex.Raw;
13
+ export declare function getColumn(knex: Knex, table: string, column: string, alias: string | false | undefined, schema: SchemaOverview): Knex.Raw;
@@ -1,25 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getColumn = void 0;
4
- const helpers_1 = require("../database/helpers");
5
4
  const constants_1 = require("@directus/shared/constants");
5
+ const utils_1 = require("@directus/shared/utils");
6
+ const helpers_1 = require("../database/helpers");
7
+ const exceptions_1 = require("../exceptions");
6
8
  const apply_function_to_column_name_1 = require("./apply-function-to-column-name");
7
9
  /**
8
- * Return column prefixed by table. If column includes functions (like `year(date_created)`, the
9
- * column is replaced with the appropriate SQL)
10
+ * Return column prefixed by table. If column includes functions (like `year(date_created)`), the
11
+ * column is replaced with the appropriate SQL
10
12
  *
11
13
  * @param knex Current knex / transaction instance
12
- * @param collection Collection or alias in which column resides
13
- * @param field name of the column
14
+ * @param table Collection or alias in which column resides
15
+ * @param column name of the column
14
16
  * @param alias Whether or not to add a SQL AS statement
15
17
  * @returns Knex raw instance
16
18
  */
17
- function getColumn(knex, table, column, alias = (0, apply_function_to_column_name_1.applyFunctionToColumnName)(column)) {
18
- const { date: fn } = (0, helpers_1.getHelpers)(knex);
19
+ function getColumn(knex, table, column, alias = (0, apply_function_to_column_name_1.applyFunctionToColumnName)(column), schema) {
20
+ var _a, _b, _c, _d;
21
+ const fn = (0, helpers_1.getFunctions)(knex, schema);
19
22
  if (column.includes('(') && column.includes(')')) {
20
23
  const functionName = column.split('(')[0];
21
24
  const columnName = column.match(constants_1.REGEX_BETWEEN_PARENS)[1];
22
25
  if (functionName in fn) {
26
+ const type = (_d = (_c = (_b = (_a = schema === null || schema === void 0 ? void 0 : schema.collections[table]) === null || _a === void 0 ? void 0 : _a.fields) === null || _b === void 0 ? void 0 : _b[columnName]) === null || _c === void 0 ? void 0 : _c.type) !== null && _d !== void 0 ? _d : 'unknown';
27
+ const allowedFunctions = (0, utils_1.getFunctionsForType)(type);
28
+ if (allowedFunctions.includes(functionName) === false) {
29
+ throw new exceptions_1.InvalidQueryException(`Invalid function specified "${functionName}"`);
30
+ }
23
31
  const result = fn[functionName](table, columnName);
24
32
  if (alias) {
25
33
  return knex.raw(result + ' AS ??', [alias]);
@@ -27,7 +35,7 @@ function getColumn(knex, table, column, alias = (0, apply_function_to_column_nam
27
35
  return result;
28
36
  }
29
37
  else {
30
- throw new Error(`Invalid function specified "${functionName}"`);
38
+ throw new exceptions_1.InvalidQueryException(`Invalid function specified "${functionName}"`);
31
39
  }
32
40
  }
33
41
  if (alias && column !== alias) {
@@ -0,0 +1 @@
1
+ export declare function getDateFormatted(): string;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDateFormatted = void 0;
4
+ function getDateFormatted() {
5
+ const date = new Date();
6
+ let month = String(date.getMonth() + 1);
7
+ if (month.length === 1)
8
+ month = '0' + month;
9
+ let day = String(date.getDate());
10
+ if (day.length === 1)
11
+ day = '0' + day;
12
+ return `${date.getFullYear()}${month}${day}-${date.getHours()}${date.getMinutes()}${date.getSeconds()}`;
13
+ }
14
+ exports.getDateFormatted = getDateFormatted;
@@ -101,11 +101,11 @@ function getLocalType(column, field) {
101
101
  const type = localTypeMap[dataType.split('(')[0]];
102
102
  const special = field === null || field === void 0 ? void 0 : field.special;
103
103
  if (special) {
104
- if (special.includes('json'))
104
+ if (special.includes('cast-json'))
105
105
  return 'json';
106
106
  if (special.includes('hash'))
107
107
  return 'hash';
108
- if (special.includes('csv'))
108
+ if (special.includes('cast-csv'))
109
109
  return 'csv';
110
110
  if (special.includes('uuid') || special.includes('file'))
111
111
  return 'uuid';
@@ -65,7 +65,7 @@ async function getPermissions(accountability, schema) {
65
65
  ? await getFilterContext(schema, accountability, requiredPermissionData)
66
66
  : {};
67
67
  if (env_1.default.CACHE_PERMISSIONS !== false) {
68
- await systemCache.set(cacheKey, { permissions, containDynamicData });
68
+ await (0, cache_1.setSystemCache)(cacheKey, { permissions, containDynamicData });
69
69
  if (containDynamicData && env_1.default.CACHE_ENABLED !== false) {
70
70
  await (cache === null || cache === void 0 ? void 0 : cache.set(`filterContext-${(0, object_hash_1.default)({ user, role, permissions })}`, filterContext));
71
71
  }
@@ -1,5 +1,5 @@
1
- import { Knex } from 'knex';
2
1
  import { Accountability, SchemaOverview } from '@directus/shared/types';
2
+ import { Knex } from 'knex';
3
3
  export declare function getSchema(options?: {
4
4
  accountability?: Accountability;
5
5
  database?: Knex;
@@ -5,17 +5,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getSchema = void 0;
7
7
  const schema_1 = __importDefault(require("@directus/schema"));
8
+ const utils_1 = require("@directus/shared/utils");
8
9
  const lodash_1 = require("lodash");
10
+ const cache_1 = require("../cache");
11
+ const database_1 = __importDefault(require("../database"));
9
12
  const collections_1 = require("../database/system-data/collections");
10
13
  const fields_1 = require("../database/system-data/fields");
14
+ const env_1 = __importDefault(require("../env"));
11
15
  const logger_1 = __importDefault(require("../logger"));
12
16
  const services_1 = require("../services");
13
- const utils_1 = require("@directus/shared/utils");
14
17
  const get_default_value_1 = __importDefault(require("./get-default-value"));
15
18
  const get_local_type_1 = __importDefault(require("./get-local-type"));
16
- const database_1 = __importDefault(require("../database"));
17
- const cache_1 = require("../cache");
18
- const env_1 = __importDefault(require("../env"));
19
19
  async function getSchema(options) {
20
20
  const database = (options === null || options === void 0 ? void 0 : options.database) || (0, database_1.default)();
21
21
  const schemaInspector = (0, schema_1.default)(database);
@@ -35,7 +35,7 @@ async function getSchema(options) {
35
35
  else {
36
36
  result = await getDatabaseSchema(database, schemaInspector);
37
37
  try {
38
- await systemCache.set('schema', result);
38
+ await (0, cache_1.setSystemCache)('schema', result);
39
39
  }
40
40
  catch (err) {
41
41
  logger_1.default.warn(err, `[schema-cache] Couldn't save cache. ${err}`);
@@ -49,7 +49,7 @@ async function getSchema(options) {
49
49
  }
50
50
  exports.getSchema = getSchema;
51
51
  async function getDatabaseSchema(database, schemaInspector) {
52
- var _a, _b, _c, _d;
52
+ var _a, _b, _c, _d, _e, _f;
53
53
  const result = {
54
54
  collections: {},
55
55
  relations: [],
@@ -95,6 +95,7 @@ async function getDatabaseSchema(database, schemaInspector) {
95
95
  scale: column.numeric_scale || null,
96
96
  special: [],
97
97
  note: null,
98
+ validation: null,
98
99
  alias: false,
99
100
  };
100
101
  }),
@@ -102,7 +103,7 @@ async function getDatabaseSchema(database, schemaInspector) {
102
103
  }
103
104
  const fields = [
104
105
  ...(await database
105
- .select('id', 'collection', 'field', 'special', 'note')
106
+ .select('id', 'collection', 'field', 'special', 'note', 'validation')
106
107
  .from('directus_fields')),
107
108
  ...fields_1.systemFieldRows,
108
109
  ].filter((field) => (field.special ? (0, utils_1.toArray)(field.special) : []).includes('no-data') === false);
@@ -113,18 +114,22 @@ async function getDatabaseSchema(database, schemaInspector) {
113
114
  const column = schemaOverview[field.collection].columns[field.field];
114
115
  const special = field.special ? (0, utils_1.toArray)(field.special) : [];
115
116
  const type = (existing && (0, get_local_type_1.default)(column, { special })) || 'alias';
117
+ let validation = (_a = field.validation) !== null && _a !== void 0 ? _a : null;
118
+ if (validation && typeof validation === 'string')
119
+ validation = JSON.parse(validation);
116
120
  result.collections[field.collection].fields[field.field] = {
117
121
  field: field.field,
118
- defaultValue: (_a = existing === null || existing === void 0 ? void 0 : existing.defaultValue) !== null && _a !== void 0 ? _a : null,
119
- nullable: (_b = existing === null || existing === void 0 ? void 0 : existing.nullable) !== null && _b !== void 0 ? _b : true,
120
- generated: (_c = existing === null || existing === void 0 ? void 0 : existing.generated) !== null && _c !== void 0 ? _c : false,
122
+ defaultValue: (_b = existing === null || existing === void 0 ? void 0 : existing.defaultValue) !== null && _b !== void 0 ? _b : null,
123
+ nullable: (_c = existing === null || existing === void 0 ? void 0 : existing.nullable) !== null && _c !== void 0 ? _c : true,
124
+ generated: (_d = existing === null || existing === void 0 ? void 0 : existing.generated) !== null && _d !== void 0 ? _d : false,
121
125
  type: type,
122
126
  dbType: (existing === null || existing === void 0 ? void 0 : existing.dbType) || null,
123
127
  precision: (existing === null || existing === void 0 ? void 0 : existing.precision) || null,
124
128
  scale: (existing === null || existing === void 0 ? void 0 : existing.scale) || null,
125
129
  special: special,
126
130
  note: field.note,
127
- alias: (_d = existing === null || existing === void 0 ? void 0 : existing.alias) !== null && _d !== void 0 ? _d : true,
131
+ alias: (_e = existing === null || existing === void 0 ? void 0 : existing.alias) !== null && _e !== void 0 ? _e : true,
132
+ validation: (_f = validation) !== null && _f !== void 0 ? _f : null,
128
133
  };
129
134
  }
130
135
  const relationsService = new services_1.RelationsService({ knex: database, schema: result });
@@ -12,6 +12,7 @@ const os_1 = __importDefault(require("os"));
12
12
  const package_json_1 = require("../../package.json");
13
13
  const env_1 = __importDefault(require("../env"));
14
14
  const logger_1 = __importDefault(require("../logger"));
15
+ const utils_1 = require("@directus/shared/utils");
15
16
  async function track(event) {
16
17
  if (env_1.default.TELEMETRY !== false) {
17
18
  const info = await getEnvInfo(event);
@@ -60,7 +61,7 @@ async function getEnvInfo(event) {
60
61
  transport: env_1.default.EMAIL_TRANSPORT,
61
62
  },
62
63
  auth: {
63
- providers: env_1.default.AUTH_PROVIDERS.split(',')
64
+ providers: (0, utils_1.toArray)(env_1.default.AUTH_PROVIDERS)
64
65
  .map((v) => v.trim())
65
66
  .filter((v) => v),
66
67
  },
@@ -69,7 +70,7 @@ async function getEnvInfo(event) {
69
70
  }
70
71
  function getStorageDrivers() {
71
72
  const drivers = [];
72
- const locations = env_1.default.STORAGE_LOCATIONS.split(',')
73
+ const locations = (0, utils_1.toArray)(env_1.default.STORAGE_LOCATIONS)
73
74
  .map((v) => v.trim())
74
75
  .filter((v) => v);
75
76
  for (const location of locations) {
@@ -9,7 +9,7 @@ export declare class Url {
9
9
  isAbsolute(): boolean;
10
10
  isProtocolRelative(): boolean;
11
11
  isRootRelative(): boolean;
12
- addPath(...paths: string[]): Url;
12
+ addPath(...paths: (string | number)[]): Url;
13
13
  setQuery(key: string, value: string): Url;
14
14
  toString({ rootRelative }?: {
15
15
  rootRelative: boolean;
package/dist/utils/url.js CHANGED
@@ -28,7 +28,7 @@ class Url {
28
28
  return this.protocol === null && this.host === null;
29
29
  }
30
30
  addPath(...paths) {
31
- const pathToAdd = paths.flatMap((p) => p.split('/')).filter((p) => p !== '');
31
+ const pathToAdd = paths.flatMap((p) => String(p).split('/')).filter((p) => p !== '');
32
32
  for (const pathSegment of pathToAdd) {
33
33
  if (pathSegment === '..') {
34
34
  this.path.pop();
@@ -9,6 +9,7 @@ const logger_1 = __importDefault(require("../logger"));
9
9
  const fs_extra_1 = require("fs-extra");
10
10
  const fs_1 = require("fs");
11
11
  const path_1 = __importDefault(require("path"));
12
+ const utils_1 = require("@directus/shared/utils");
12
13
  async function validateStorage() {
13
14
  if (env_1.default.DB_CLIENT === 'sqlite3') {
14
15
  try {
@@ -18,7 +19,8 @@ async function validateStorage() {
18
19
  logger_1.default.warn(`Directory for SQLite database file (${path_1.default.resolve(path_1.default.dirname(env_1.default.DB_FILENAME))}) is not read/writeable!`);
19
20
  }
20
21
  }
21
- if (env_1.default.STORAGE_LOCATIONS.split(',').includes('local')) {
22
+ const usedStorageDrivers = (0, utils_1.toArray)(env_1.default.STORAGE_LOCATIONS).map((location) => env_1.default[`STORAGE_${location.toUpperCase()}_DRIVER`]);
23
+ if (usedStorageDrivers.includes('local')) {
22
24
  try {
23
25
  await (0, fs_extra_1.access)(env_1.default.STORAGE_LOCAL_ROOT, fs_1.constants.R_OK | fs_1.constants.W_OK);
24
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "directus",
3
- "version": "9.6.0",
3
+ "version": "9.8.0",
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.",
@@ -78,16 +78,16 @@
78
78
  ],
79
79
  "dependencies": {
80
80
  "@aws-sdk/client-ses": "^3.40.0",
81
- "@directus/app": "9.6.0",
82
- "@directus/drive": "9.6.0",
83
- "@directus/drive-azure": "9.6.0",
84
- "@directus/drive-gcs": "9.6.0",
85
- "@directus/drive-s3": "9.6.0",
86
- "@directus/extensions-sdk": "9.6.0",
87
- "@directus/format-title": "9.6.0",
88
- "@directus/schema": "9.6.0",
89
- "@directus/shared": "9.6.0",
90
- "@directus/specs": "9.6.0",
81
+ "@directus/app": "9.8.0",
82
+ "@directus/drive": "9.8.0",
83
+ "@directus/drive-azure": "9.8.0",
84
+ "@directus/drive-gcs": "9.8.0",
85
+ "@directus/drive-s3": "9.8.0",
86
+ "@directus/extensions-sdk": "9.8.0",
87
+ "@directus/format-title": "9.8.0",
88
+ "@directus/schema": "9.8.0",
89
+ "@directus/shared": "9.8.0",
90
+ "@directus/specs": "9.8.0",
91
91
  "@godaddy/terminus": "^4.9.0",
92
92
  "@rollup/plugin-alias": "^3.1.9",
93
93
  "@rollup/plugin-virtual": "^2.0.3",
@@ -153,6 +153,7 @@
153
153
  "sharp": "^0.29.0",
154
154
  "stream-json": "^1.7.1",
155
155
  "supertest": "^6.1.6",
156
+ "tmp-promise": "^3.0.3",
156
157
  "update-check": "^1.5.4",
157
158
  "uuid": "^8.3.2",
158
159
  "uuid-validate": "0.0.3",
@@ -172,7 +173,7 @@
172
173
  "sqlite3": "^5.0.2",
173
174
  "tedious": "^13.0.0"
174
175
  },
175
- "gitHead": "eba2576227d493f87160973238bddadbf23bbe01",
176
+ "gitHead": "2a6db01c42dd1d7524962e7153d1d7e1bd63fb2f",
176
177
  "devDependencies": {
177
178
  "@types/async": "3.2.10",
178
179
  "@types/body-parser": "1.19.2",
@@ -1,13 +0,0 @@
1
- /// <reference types="node" />
2
- import { Knex } from 'knex';
3
- import { AbstractServiceOptions } from '../types';
4
- import { Accountability, SchemaOverview } from '@directus/shared/types';
5
- export declare class ImportService {
6
- knex: Knex;
7
- accountability: Accountability | null;
8
- schema: SchemaOverview;
9
- constructor(options: AbstractServiceOptions);
10
- import(collection: string, mimetype: string, stream: NodeJS.ReadableStream): Promise<void>;
11
- importJSON(collection: string, stream: NodeJS.ReadableStream): Promise<void>;
12
- importCSV(collection: string, stream: NodeJS.ReadableStream): Promise<void>;
13
- }
@@ -1,118 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ImportService = void 0;
7
- const database_1 = __importDefault(require("../database"));
8
- const exceptions_1 = require("../exceptions");
9
- const StreamArray_1 = __importDefault(require("stream-json/streamers/StreamArray"));
10
- const items_1 = require("./items");
11
- const async_1 = require("async");
12
- const destroy_1 = __importDefault(require("destroy"));
13
- const csv_parser_1 = __importDefault(require("csv-parser"));
14
- const lodash_1 = require("lodash");
15
- class ImportService {
16
- constructor(options) {
17
- this.knex = options.knex || (0, database_1.default)();
18
- this.accountability = options.accountability || null;
19
- this.schema = options.schema;
20
- }
21
- async import(collection, mimetype, stream) {
22
- var _a, _b, _c, _d, _e;
23
- if (collection.startsWith('directus_'))
24
- throw new exceptions_1.ForbiddenException();
25
- const createPermissions = (_b = (_a = this.accountability) === null || _a === void 0 ? void 0 : _a.permissions) === null || _b === void 0 ? void 0 : _b.find((permission) => permission.collection === collection && permission.action === 'create');
26
- const updatePermissions = (_d = (_c = this.accountability) === null || _c === void 0 ? void 0 : _c.permissions) === null || _d === void 0 ? void 0 : _d.find((permission) => permission.collection === collection && permission.action === 'update');
27
- if (((_e = this.accountability) === null || _e === void 0 ? void 0 : _e.admin) !== true && (!createPermissions || !updatePermissions)) {
28
- throw new exceptions_1.ForbiddenException();
29
- }
30
- switch (mimetype) {
31
- case 'application/json':
32
- return await this.importJSON(collection, stream);
33
- case 'text/csv':
34
- case 'application/vnd.ms-excel':
35
- return await this.importCSV(collection, stream);
36
- default:
37
- throw new exceptions_1.UnsupportedMediaTypeException(`Can't import files of type "${mimetype}"`);
38
- }
39
- }
40
- importJSON(collection, stream) {
41
- const extractJSON = StreamArray_1.default.withParser();
42
- return this.knex.transaction((trx) => {
43
- const service = new items_1.ItemsService(collection, {
44
- knex: trx,
45
- schema: this.schema,
46
- accountability: this.accountability,
47
- });
48
- const saveQueue = (0, async_1.queue)(async (value) => {
49
- return await service.upsertOne(value);
50
- });
51
- return new Promise((resolve, reject) => {
52
- stream.pipe(extractJSON);
53
- extractJSON.on('data', ({ value }) => {
54
- saveQueue.push(value);
55
- });
56
- extractJSON.on('error', (err) => {
57
- (0, destroy_1.default)(stream);
58
- (0, destroy_1.default)(extractJSON);
59
- reject(new exceptions_1.InvalidPayloadException(err.message));
60
- });
61
- saveQueue.error((err) => {
62
- reject(err);
63
- });
64
- extractJSON.on('end', () => {
65
- saveQueue.drain(() => {
66
- return resolve();
67
- });
68
- });
69
- });
70
- });
71
- }
72
- importCSV(collection, stream) {
73
- return this.knex.transaction((trx) => {
74
- const service = new items_1.ItemsService(collection, {
75
- knex: trx,
76
- schema: this.schema,
77
- accountability: this.accountability,
78
- });
79
- const saveQueue = (0, async_1.queue)(async (value) => {
80
- return await service.upsertOne(value);
81
- });
82
- return new Promise((resolve, reject) => {
83
- stream
84
- .pipe((0, csv_parser_1.default)())
85
- .on('data', (value) => {
86
- const obj = (0, lodash_1.transform)(value, (result, value, key) => {
87
- if (value.length === 0) {
88
- delete result[key];
89
- }
90
- else {
91
- try {
92
- const parsedJson = JSON.parse(value);
93
- (0, lodash_1.set)(result, key, parsedJson);
94
- }
95
- catch {
96
- (0, lodash_1.set)(result, key, value);
97
- }
98
- }
99
- });
100
- saveQueue.push(obj);
101
- })
102
- .on('error', (err) => {
103
- (0, destroy_1.default)(stream);
104
- reject(new exceptions_1.InvalidPayloadException(err.message));
105
- })
106
- .on('end', () => {
107
- saveQueue.drain(() => {
108
- return resolve();
109
- });
110
- });
111
- saveQueue.error((err) => {
112
- reject(err);
113
- });
114
- });
115
- });
116
- }
117
- }
118
- exports.ImportService = ImportService;