directus 9.7.1 → 9.9.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 (103) hide show
  1. package/dist/__mocks__/cache.d.ts +5 -0
  2. package/dist/__mocks__/cache.js +7 -0
  3. package/dist/auth/drivers/ldap.js +10 -11
  4. package/dist/auth/drivers/oauth2.js +9 -4
  5. package/dist/auth/drivers/openid.js +7 -4
  6. package/dist/cache.js +2 -2
  7. package/dist/cli/commands/schema/apply.js +9 -3
  8. package/dist/controllers/assets.js +5 -0
  9. package/dist/controllers/files.d.ts +2 -0
  10. package/dist/controllers/files.js +13 -5
  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/mssql.d.ts +1 -9
  14. package/dist/database/helpers/date/dialects/mssql.js +4 -23
  15. package/dist/database/helpers/date/dialects/mysql.d.ts +2 -9
  16. package/dist/database/helpers/date/dialects/mysql.js +7 -22
  17. package/dist/database/helpers/date/dialects/oracle.d.ts +1 -9
  18. package/dist/database/helpers/date/dialects/oracle.js +7 -23
  19. package/dist/database/helpers/date/dialects/sqlite.d.ts +1 -9
  20. package/dist/database/helpers/date/dialects/sqlite.js +8 -24
  21. package/dist/database/helpers/date/index.d.ts +4 -4
  22. package/dist/database/helpers/date/index.js +9 -9
  23. package/dist/database/helpers/date/types.d.ts +3 -9
  24. package/dist/database/helpers/date/types.js +10 -0
  25. package/dist/database/helpers/fn/dialects/mssql.d.ts +13 -0
  26. package/dist/database/helpers/fn/dialects/mssql.js +42 -0
  27. package/dist/database/helpers/{date/dialects/postgres.d.ts → fn/dialects/mysql.d.ts} +3 -2
  28. package/dist/database/helpers/fn/dialects/mysql.js +42 -0
  29. package/dist/database/helpers/fn/dialects/oracle.d.ts +13 -0
  30. package/dist/database/helpers/fn/dialects/oracle.js +42 -0
  31. package/dist/database/helpers/fn/dialects/postgres.d.ts +13 -0
  32. package/dist/database/helpers/{date → fn}/dialects/postgres.js +18 -3
  33. package/dist/database/helpers/fn/dialects/sqlite.d.ts +13 -0
  34. package/dist/database/helpers/fn/dialects/sqlite.js +42 -0
  35. package/dist/database/helpers/fn/index.d.ts +7 -0
  36. package/dist/database/helpers/fn/index.js +17 -0
  37. package/dist/database/helpers/fn/types.d.ts +18 -0
  38. package/dist/database/helpers/fn/types.js +27 -0
  39. package/dist/database/helpers/index.d.ts +4 -1
  40. package/dist/database/helpers/index.js +7 -1
  41. package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.d.ts +3 -0
  42. package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.js +17 -0
  43. package/dist/database/migrations/20220322A-rename-field-typecast-flags.js +6 -2
  44. package/dist/database/migrations/20220323A-add-field-validation.d.ts +3 -0
  45. package/dist/database/migrations/20220323A-add-field-validation.js +17 -0
  46. package/dist/database/migrations/20220325A-fix-typecast-flags.d.ts +3 -0
  47. package/dist/database/migrations/20220325A-fix-typecast-flags.js +49 -0
  48. package/dist/database/migrations/20220325B-add-default-language.d.ts +3 -0
  49. package/dist/database/migrations/20220325B-add-default-language.js +28 -0
  50. package/dist/database/migrations/20220402A-remove-default-value-panel-icon.d.ts +3 -0
  51. package/dist/database/migrations/20220402A-remove-default-value-panel-icon.js +22 -0
  52. package/dist/database/run-ast.js +7 -5
  53. package/dist/database/system-data/fields/activity.yaml +4 -4
  54. package/dist/database/system-data/fields/collections.yaml +1 -1
  55. package/dist/database/system-data/fields/fields.yaml +9 -0
  56. package/dist/database/system-data/fields/presets.yaml +14 -0
  57. package/dist/database/system-data/fields/settings.yaml +12 -1
  58. package/dist/database/system-data/fields/users.yaml +3 -0
  59. package/dist/env.js +5 -3
  60. package/dist/exceptions/index.d.ts +1 -0
  61. package/dist/exceptions/index.js +1 -0
  62. package/dist/exceptions/token-expired.d.ts +4 -0
  63. package/dist/exceptions/token-expired.js +10 -0
  64. package/dist/logger.js +2 -1
  65. package/dist/middleware/cache.js +10 -0
  66. package/dist/services/activity.js +4 -1
  67. package/dist/services/authorization.d.ts +1 -1
  68. package/dist/services/authorization.js +174 -48
  69. package/dist/services/collections.d.ts +2 -0
  70. package/dist/services/collections.js +232 -198
  71. package/dist/services/fields.js +210 -174
  72. package/dist/services/files.d.ts +5 -1
  73. package/dist/services/files.js +59 -40
  74. package/dist/services/graphql.d.ts +2 -3
  75. package/dist/services/graphql.js +53 -10
  76. package/dist/services/items.js +5 -3
  77. package/dist/services/payload.d.ts +2 -1
  78. package/dist/services/payload.js +28 -21
  79. package/dist/services/relations.js +93 -81
  80. package/dist/services/server.js +1 -0
  81. package/dist/services/shares.js +2 -1
  82. package/dist/services/specifications.js +1 -3
  83. package/dist/services/users.js +7 -2
  84. package/dist/types/files.d.ts +8 -0
  85. package/dist/utils/apply-query.js +38 -10
  86. package/dist/utils/apply-snapshot.d.ts +3 -1
  87. package/dist/utils/apply-snapshot.js +34 -5
  88. package/dist/utils/get-ast-from-query.js +15 -3
  89. package/dist/utils/get-column.d.ts +6 -5
  90. package/dist/utils/get-column.js +16 -8
  91. package/dist/utils/get-graphql-type.js +1 -0
  92. package/dist/utils/get-local-type.js +5 -0
  93. package/dist/utils/get-schema.d.ts +1 -1
  94. package/dist/utils/get-schema.js +18 -10
  95. package/dist/utils/jwt.js +1 -1
  96. package/dist/utils/reduce-schema.js +20 -12
  97. package/dist/utils/track.js +3 -2
  98. package/dist/utils/url.d.ts +1 -1
  99. package/dist/utils/url.js +1 -1
  100. package/dist/utils/validate-query.js +19 -15
  101. package/dist/utils/validate-storage.js +3 -1
  102. package/example.env +4 -0
  103. package/package.json +14 -13
@@ -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);
@@ -179,6 +180,7 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
179
180
  }
180
181
  }
181
182
  function addWhereClauses(knex, dbQuery, filter, collection, logical = 'and') {
183
+ var _a, _b;
182
184
  for (const [key, value] of Object.entries(filter)) {
183
185
  if (key === '_or' || key === '_and') {
184
186
  // If the _or array contains an empty object (full permissions), we should short-circuit and ignore all other
@@ -219,22 +221,31 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
219
221
  if (relationType === 'o2a') {
220
222
  pkField = knex.raw(`CAST(?? AS CHAR(255))`, [pkField]);
221
223
  }
222
- // Note: knex's types don't appreciate knex.raw in whereIn, even though it's officially supported
223
- dbQuery[logical].whereIn(pkField, (subQueryKnex) => {
224
+ const subQueryBuilder = (filter) => (subQueryKnex) => {
224
225
  const field = relation.field;
225
226
  const collection = relation.collection;
226
227
  const column = `${collection}.${field}`;
227
- subQueryKnex.select({ [field]: column }).from(collection);
228
- applyQuery(knex, relation.collection, subQueryKnex, {
229
- filter: value,
230
- }, schema, true);
231
- });
228
+ subQueryKnex
229
+ .select({ [field]: column })
230
+ .from(collection)
231
+ .whereNotNull(column);
232
+ applyQuery(knex, relation.collection, subQueryKnex, { filter }, schema, true);
233
+ };
234
+ if (((_a = Object.keys(value)) === null || _a === void 0 ? void 0 : _a[0]) === '_none') {
235
+ dbQuery[logical].whereNotIn(pkField, subQueryBuilder(Object.values(value)[0]));
236
+ }
237
+ else if (((_b = Object.keys(value)) === null || _b === void 0 ? void 0 : _b[0]) === '_some') {
238
+ dbQuery[logical].whereIn(pkField, subQueryBuilder(Object.values(value)[0]));
239
+ }
240
+ else {
241
+ dbQuery[logical].whereIn(pkField, subQueryBuilder(value));
242
+ }
232
243
  }
233
244
  }
234
245
  function applyFilterToQuery(key, operator, compareValue, logical = 'and') {
235
246
  const [table, column] = key.split('.');
236
247
  // 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);
248
+ const selectionRaw = (0, get_column_1.getColumn)(knex, table, column, false, schema);
238
249
  // Knex supports "raw" in the columnName parameter, but isn't typed as such. Too bad..
239
250
  // See https://github.com/knex/knex/issues/4518 @TODO remove as any once knex is updated
240
251
  // These operators don't rely on a value, and can thus be used without one (eg `?filter[field][_null]`)
@@ -254,6 +265,15 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
254
265
  query.where(key, '!=', '');
255
266
  });
256
267
  }
268
+ // Cast filter value (compareValue) based on function used
269
+ if (column.includes('(') && column.includes(')')) {
270
+ const functionName = column.split('(')[0];
271
+ const type = (0, utils_1.getOutputTypeForFunction)(functionName);
272
+ if (['bigInteger', 'integer', 'float', 'decimal'].includes(type)) {
273
+ compareValue = Number(compareValue);
274
+ }
275
+ }
276
+ // Cast filter value (compareValue) based on type of field being filtered against
257
277
  const [collection, field] = key.split('.');
258
278
  if (collection in schema.collections && field in schema.collections[collection].fields) {
259
279
  const type = schema.collections[collection].fields[field].type;
@@ -265,6 +285,14 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
265
285
  compareValue = helpers.date.parse(compareValue);
266
286
  }
267
287
  }
288
+ if (['bigInteger', 'integer', 'float', 'decimal'].includes(type)) {
289
+ if (Array.isArray(compareValue)) {
290
+ compareValue = compareValue.map((val) => Number(val));
291
+ }
292
+ else {
293
+ compareValue = Number(compareValue);
294
+ }
295
+ }
268
296
  }
269
297
  // The following fields however, require a value to be run. If no value is passed, we
270
298
  // ignore them. This allows easier use in GraphQL, where you wouldn't be able to
@@ -1,5 +1,6 @@
1
- import { Snapshot, SnapshotDiff } from '../types';
1
+ import { Snapshot, SnapshotDiff, SnapshotField } from '../types';
2
2
  import { Knex } from 'knex';
3
+ import { Diff } from 'deep-diff';
3
4
  import { SchemaOverview } from '@directus/shared/types';
4
5
  export declare function applySnapshot(snapshot: Snapshot, options?: {
5
6
  database?: Knex;
@@ -7,3 +8,4 @@ export declare function applySnapshot(snapshot: Snapshot, options?: {
7
8
  current?: Snapshot;
8
9
  diff?: SnapshotDiff;
9
10
  }): Promise<void>;
11
+ export declare function isNestedMetaUpdate(diff: Diff<SnapshotField | undefined>): boolean;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.applySnapshot = void 0;
6
+ exports.isNestedMetaUpdate = exports.applySnapshot = void 0;
7
7
  const get_snapshot_1 = require("./get-snapshot");
8
8
  const get_snapshot_diff_1 = require("./get-snapshot-diff");
9
9
  const database_1 = __importDefault(require("../database"));
@@ -34,7 +34,26 @@ async function applySnapshot(snapshot, options) {
34
34
  // creating a collection without a primary key
35
35
  const fields = snapshotDiff.fields
36
36
  .filter((fieldDiff) => fieldDiff.collection === collection)
37
- .map((fieldDiff) => fieldDiff.diff[0].rhs);
37
+ .map((fieldDiff) => fieldDiff.diff[0].rhs)
38
+ .map((fieldDiff) => {
39
+ // Casts field type to UUID when applying SQLite-based schema on other databases.
40
+ // This is needed because SQLite snapshots UUID fields as char with length 36, and
41
+ // it will fail when trying to create relation between char field to UUID field
42
+ if (!fieldDiff.schema ||
43
+ fieldDiff.schema.data_type !== 'char' ||
44
+ fieldDiff.schema.max_length !== 36 ||
45
+ !fieldDiff.schema.foreign_key_table ||
46
+ !fieldDiff.schema.foreign_key_column) {
47
+ return fieldDiff;
48
+ }
49
+ const matchingForeignKeyTable = schema.collections[fieldDiff.schema.foreign_key_table];
50
+ if (!matchingForeignKeyTable)
51
+ return fieldDiff;
52
+ const matchingForeignKeyField = matchingForeignKeyTable.fields[fieldDiff.schema.foreign_key_column];
53
+ if (!matchingForeignKeyField || matchingForeignKeyField.type !== 'uuid')
54
+ return fieldDiff;
55
+ return (0, lodash_1.merge)(fieldDiff, { type: 'uuid', schema: { data_type: 'uuid', max_length: null } });
56
+ });
38
57
  try {
39
58
  await collectionsService.createOne({
40
59
  ...diff[0].rhs,
@@ -66,7 +85,7 @@ async function applySnapshot(snapshot, options) {
66
85
  }
67
86
  const fieldsService = new services_1.FieldsService({ knex: trx, schema: await (0, get_schema_1.getSchema)({ database: trx }) });
68
87
  for (const { collection, field, diff } of snapshotDiff.fields) {
69
- if ((diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'N') {
88
+ if ((diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'N' && !isNestedMetaUpdate(diff === null || diff === void 0 ? void 0 : diff[0])) {
70
89
  try {
71
90
  await fieldsService.createField(collection, diff[0].rhs);
72
91
  }
@@ -75,7 +94,7 @@ async function applySnapshot(snapshot, options) {
75
94
  throw err;
76
95
  }
77
96
  }
78
- if ((diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'E' || (diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'A') {
97
+ if ((diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'E' || (diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'A' || isNestedMetaUpdate(diff === null || diff === void 0 ? void 0 : diff[0])) {
79
98
  const newValues = snapshot.fields.find((snapshotField) => {
80
99
  return snapshotField.collection === collection && snapshotField.field === field;
81
100
  });
@@ -91,7 +110,7 @@ async function applySnapshot(snapshot, options) {
91
110
  }
92
111
  }
93
112
  }
94
- if ((diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'D') {
113
+ if ((diff === null || diff === void 0 ? void 0 : diff[0].kind) === 'D' && !isNestedMetaUpdate(diff === null || diff === void 0 ? void 0 : diff[0])) {
95
114
  try {
96
115
  await fieldsService.deleteField(collection, field);
97
116
  }
@@ -146,3 +165,13 @@ async function applySnapshot(snapshot, options) {
146
165
  });
147
166
  }
148
167
  exports.applySnapshot = applySnapshot;
168
+ function isNestedMetaUpdate(diff) {
169
+ if (!diff)
170
+ return false;
171
+ if (diff.kind !== 'N' && diff.kind !== 'D')
172
+ return false;
173
+ if (!diff.path || diff.path.length < 2 || diff.path[0] !== 'meta')
174
+ return false;
175
+ return true;
176
+ }
177
+ exports.isNestedMetaUpdate = isNestedMetaUpdate;
@@ -75,9 +75,17 @@ async function getASTFromQuery(collection, query, schema, options) {
75
75
  const relationalStructure = {};
76
76
  for (const fieldKey of fields) {
77
77
  let name = fieldKey;
78
- const isAlias = (_a = (query.alias && name in query.alias)) !== null && _a !== void 0 ? _a : false;
79
- if (isAlias) {
80
- name = query.alias[fieldKey];
78
+ if (query.alias) {
79
+ // check for field alias (is is one of the key)
80
+ if (name in query.alias) {
81
+ name = query.alias[fieldKey];
82
+ }
83
+ // check for junction alias (it is one of the value instead of the key)
84
+ if (Object.values(query.alias).includes(name)) {
85
+ const aliasKey = Object.keys(query.alias).find((key) => { var _a; return ((_a = query.alias) === null || _a === void 0 ? void 0 : _a[key]) === name; });
86
+ if (aliasKey && fieldKey !== aliasKey)
87
+ name = aliasKey;
88
+ }
81
89
  }
82
90
  const isRelational = name.includes('.') ||
83
91
  // We'll always treat top level o2m fields as a related item. This is an alias field, otherwise it won't return
@@ -162,6 +170,10 @@ async function getASTFromQuery(collection, query, schema, options) {
162
170
  if (permissions && permissions.some((permission) => permission.collection === relatedCollection) === false) {
163
171
  continue;
164
172
  }
173
+ // update query alias for children parseFields
174
+ const deepAlias = (_a = getDeepQuery((deep === null || deep === void 0 ? void 0 : deep[fieldKey]) || {})) === null || _a === void 0 ? void 0 : _a.alias;
175
+ if (!(0, lodash_1.isEmpty)(deepAlias))
176
+ query.alias = deepAlias;
165
177
  child = {
166
178
  type: relationType,
167
179
  name: relatedCollection,
@@ -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) {
@@ -9,6 +9,7 @@ function getGraphQLType(localType) {
9
9
  case 'boolean':
10
10
  return graphql_1.GraphQLBoolean;
11
11
  case 'bigInteger':
12
+ return graphql_1.GraphQLString;
12
13
  case 'integer':
13
14
  return graphql_1.GraphQLInt;
14
15
  case 'decimal':
@@ -88,6 +88,7 @@ const localTypeMap = {
88
88
  'time without time zone': 'time',
89
89
  float4: 'float',
90
90
  float8: 'float',
91
+ citext: 'text',
91
92
  // Oracle
92
93
  number: 'integer',
93
94
  sdo_geometry: 'geometry',
@@ -109,6 +110,10 @@ function getLocalType(column, field) {
109
110
  return 'csv';
110
111
  if (special.includes('uuid') || special.includes('file'))
111
112
  return 'uuid';
113
+ if (special.includes('cast-timestamp'))
114
+ return 'timestamp';
115
+ if (special.includes('cast-datetime'))
116
+ return 'dateTime';
112
117
  if (type === null || type === void 0 ? void 0 : type.startsWith('geometry')) {
113
118
  return special[0] || 'geometry';
114
119
  }
@@ -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,18 @@ 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 constants_1 = require("../constants");
12
+ const database_1 = __importDefault(require("../database"));
9
13
  const collections_1 = require("../database/system-data/collections");
10
14
  const fields_1 = require("../database/system-data/fields");
15
+ const env_1 = __importDefault(require("../env"));
11
16
  const logger_1 = __importDefault(require("../logger"));
12
17
  const services_1 = require("../services");
13
- const utils_1 = require("@directus/shared/utils");
14
18
  const get_default_value_1 = __importDefault(require("./get-default-value"));
15
19
  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
20
  async function getSchema(options) {
20
21
  const database = (options === null || options === void 0 ? void 0 : options.database) || (0, database_1.default)();
21
22
  const schemaInspector = (0, schema_1.default)(database);
@@ -49,7 +50,7 @@ async function getSchema(options) {
49
50
  }
50
51
  exports.getSchema = getSchema;
51
52
  async function getDatabaseSchema(database, schemaInspector) {
52
- var _a, _b, _c, _d;
53
+ var _a, _b, _c, _d, _e, _f;
53
54
  const result = {
54
55
  collections: {},
55
56
  relations: [],
@@ -95,6 +96,7 @@ async function getDatabaseSchema(database, schemaInspector) {
95
96
  scale: column.numeric_scale || null,
96
97
  special: [],
97
98
  note: null,
99
+ validation: null,
98
100
  alias: false,
99
101
  };
100
102
  }),
@@ -102,7 +104,7 @@ async function getDatabaseSchema(database, schemaInspector) {
102
104
  }
103
105
  const fields = [
104
106
  ...(await database
105
- .select('id', 'collection', 'field', 'special', 'note')
107
+ .select('id', 'collection', 'field', 'special', 'note', 'validation')
106
108
  .from('directus_fields')),
107
109
  ...fields_1.systemFieldRows,
108
110
  ].filter((field) => (field.special ? (0, utils_1.toArray)(field.special) : []).includes('no-data') === false);
@@ -112,19 +114,25 @@ async function getDatabaseSchema(database, schemaInspector) {
112
114
  const existing = result.collections[field.collection].fields[field.field];
113
115
  const column = schemaOverview[field.collection].columns[field.field];
114
116
  const special = field.special ? (0, utils_1.toArray)(field.special) : [];
117
+ if (constants_1.ALIAS_TYPES.some((type) => special.includes(type)) === false && !existing)
118
+ continue;
115
119
  const type = (existing && (0, get_local_type_1.default)(column, { special })) || 'alias';
120
+ let validation = (_a = field.validation) !== null && _a !== void 0 ? _a : null;
121
+ if (validation && typeof validation === 'string')
122
+ validation = JSON.parse(validation);
116
123
  result.collections[field.collection].fields[field.field] = {
117
124
  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,
125
+ defaultValue: (_b = existing === null || existing === void 0 ? void 0 : existing.defaultValue) !== null && _b !== void 0 ? _b : null,
126
+ nullable: (_c = existing === null || existing === void 0 ? void 0 : existing.nullable) !== null && _c !== void 0 ? _c : true,
127
+ generated: (_d = existing === null || existing === void 0 ? void 0 : existing.generated) !== null && _d !== void 0 ? _d : false,
121
128
  type: type,
122
129
  dbType: (existing === null || existing === void 0 ? void 0 : existing.dbType) || null,
123
130
  precision: (existing === null || existing === void 0 ? void 0 : existing.precision) || null,
124
131
  scale: (existing === null || existing === void 0 ? void 0 : existing.scale) || null,
125
132
  special: special,
126
133
  note: field.note,
127
- alias: (_d = existing === null || existing === void 0 ? void 0 : existing.alias) !== null && _d !== void 0 ? _d : true,
134
+ alias: (_e = existing === null || existing === void 0 ? void 0 : existing.alias) !== null && _e !== void 0 ? _e : true,
135
+ validation: (_f = validation) !== null && _f !== void 0 ? _f : null,
128
136
  };
129
137
  }
130
138
  const relationsService = new services_1.RelationsService({ knex: database, schema: result });
package/dist/utils/jwt.js CHANGED
@@ -31,7 +31,7 @@ function verifyAccessJWT(token, secret) {
31
31
  }
32
32
  catch (err) {
33
33
  if (err instanceof jsonwebtoken_1.TokenExpiredError) {
34
- throw new exceptions_1.InvalidTokenException('Token expired.');
34
+ throw new exceptions_1.TokenExpiredException();
35
35
  }
36
36
  else if (err instanceof jsonwebtoken_1.JsonWebTokenError) {
37
37
  throw new exceptions_1.InvalidTokenException('Token invalid.');
@@ -10,7 +10,7 @@ const lodash_1 = require("lodash");
10
10
  * @returns Reduced schema
11
11
  */
12
12
  function reduceSchema(schema, permissions, actions = ['create', 'read', 'update', 'delete']) {
13
- var _a, _b, _c;
13
+ var _a, _b, _c, _d, _e;
14
14
  const reduced = {
15
15
  collections: {},
16
16
  relations: [],
@@ -25,19 +25,27 @@ function reduceSchema(schema, permissions, actions = ['create', 'read', 'update'
25
25
  return acc;
26
26
  }, {})) !== null && _a !== void 0 ? _a : {};
27
27
  for (const [collectionName, collection] of Object.entries(schema.collections)) {
28
- if (permissions === null || permissions === void 0 ? void 0 : permissions.some((permission) => permission.collection === collectionName && actions.includes(permission.action))) {
29
- const fields = {};
30
- for (const [fieldName, field] of Object.entries(schema.collections[collectionName].fields)) {
31
- if (((_b = allowedFieldsInCollection[collectionName]) === null || _b === void 0 ? void 0 : _b.includes('*')) ||
32
- ((_c = allowedFieldsInCollection[collectionName]) === null || _c === void 0 ? void 0 : _c.includes(fieldName))) {
33
- fields[fieldName] = field;
34
- }
28
+ if (!(permissions === null || permissions === void 0 ? void 0 : permissions.some((permission) => permission.collection === collectionName && actions.includes(permission.action)))) {
29
+ continue;
30
+ }
31
+ const fields = {};
32
+ for (const [fieldName, field] of Object.entries(schema.collections[collectionName].fields)) {
33
+ if (!((_b = allowedFieldsInCollection[collectionName]) === null || _b === void 0 ? void 0 : _b.includes('*')) &&
34
+ !((_c = allowedFieldsInCollection[collectionName]) === null || _c === void 0 ? void 0 : _c.includes(fieldName))) {
35
+ continue;
36
+ }
37
+ const relatedCollection = ((_d = schema.relations.find((relation) => relation.collection === collectionName && relation.field === fieldName)) === null || _d === void 0 ? void 0 : _d.related_collection) ||
38
+ ((_e = schema.relations.find((relation) => { var _a; return relation.related_collection === collectionName && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === fieldName; })) === null || _e === void 0 ? void 0 : _e.collection);
39
+ if (relatedCollection &&
40
+ !(permissions === null || permissions === void 0 ? void 0 : permissions.some((permission) => permission.collection === relatedCollection && actions.includes(permission.action)))) {
41
+ continue;
35
42
  }
36
- reduced.collections[collectionName] = {
37
- ...collection,
38
- fields,
39
- };
43
+ fields[fieldName] = field;
40
44
  }
45
+ reduced.collections[collectionName] = {
46
+ ...collection,
47
+ fields,
48
+ };
41
49
  }
42
50
  reduced.relations = schema.relations.filter((relation) => {
43
51
  var _a, _b, _c;
@@ -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();
@@ -47,21 +47,6 @@ function validateFilter(filter) {
47
47
  else if (key.startsWith('_')) {
48
48
  const value = nested;
49
49
  switch (key) {
50
- case '_eq':
51
- case '_neq':
52
- case '_contains':
53
- case '_ncontains':
54
- case '_starts_with':
55
- case '_nstarts_with':
56
- case '_ends_with':
57
- case '_nends_with':
58
- case '_gt':
59
- case '_gte':
60
- case '_lt':
61
- case '_lte':
62
- default:
63
- validateFilterPrimitive(value, key);
64
- break;
65
50
  case '_in':
66
51
  case '_nin':
67
52
  case '_between':
@@ -80,6 +65,25 @@ function validateFilter(filter) {
80
65
  case '_nintersects_bbox':
81
66
  validateGeometry(value, key);
82
67
  break;
68
+ case '_none':
69
+ case '_some':
70
+ validateFilter(nested);
71
+ break;
72
+ case '_eq':
73
+ case '_neq':
74
+ case '_contains':
75
+ case '_ncontains':
76
+ case '_starts_with':
77
+ case '_nstarts_with':
78
+ case '_ends_with':
79
+ case '_nends_with':
80
+ case '_gt':
81
+ case '_gte':
82
+ case '_lt':
83
+ case '_lte':
84
+ default:
85
+ validateFilterPrimitive(value, key);
86
+ break;
83
87
  }
84
88
  }
85
89
  else if ((0, lodash_1.isPlainObject)(nested)) {
@@ -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/example.env CHANGED
@@ -123,6 +123,10 @@ STORAGE_LOCAL_ROOT="./uploads"
123
123
  # STORAGE_GOOGLE_KEY_FILENAME="abcdef"
124
124
  # STORAGE_GOOGLE_BUCKET="my-files"
125
125
 
126
+
127
+ ## CSV of additional metadata keys
128
+ # FILE_METADATA_ALLOW_LIST=
129
+
126
130
  ####################################################################################################
127
131
  # Security
128
132