directus 9.8.0 → 9.10.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 (71) hide show
  1. package/README.md +1 -1
  2. package/dist/__mocks__/cache.d.ts +5 -0
  3. package/dist/__mocks__/cache.js +7 -0
  4. package/dist/app.js +3 -0
  5. package/dist/auth/drivers/ldap.js +10 -11
  6. package/dist/auth/drivers/oauth2.js +11 -6
  7. package/dist/auth/drivers/openid.js +9 -6
  8. package/dist/cli/commands/schema/apply.js +9 -3
  9. package/dist/controllers/assets.js +5 -0
  10. package/dist/controllers/files.d.ts +2 -0
  11. package/dist/controllers/files.js +13 -5
  12. package/dist/database/helpers/date/dialects/mssql.d.ts +4 -0
  13. package/dist/database/helpers/date/dialects/mssql.js +12 -0
  14. package/dist/database/helpers/date/dialects/mysql.d.ts +5 -0
  15. package/dist/database/helpers/date/dialects/mysql.js +16 -0
  16. package/dist/database/helpers/date/dialects/oracle.d.ts +4 -0
  17. package/dist/database/helpers/date/dialects/oracle.js +15 -0
  18. package/dist/database/helpers/date/dialects/sqlite.d.ts +1 -0
  19. package/dist/database/helpers/date/dialects/sqlite.js +8 -0
  20. package/dist/database/helpers/date/index.d.ts +3 -3
  21. package/dist/database/helpers/date/index.js +6 -6
  22. package/dist/database/helpers/date/types.d.ts +3 -0
  23. package/dist/database/helpers/date/types.js +10 -0
  24. package/dist/database/helpers/fn/dialects/postgres.js +5 -1
  25. package/dist/database/helpers/index.d.ts +1 -1
  26. package/dist/database/migrations/20220402A-remove-default-value-panel-icon.d.ts +3 -0
  27. package/dist/database/migrations/20220402A-remove-default-value-panel-icon.js +22 -0
  28. package/dist/database/run-ast.js +3 -3
  29. package/dist/database/system-data/fields/collections.yaml +1 -1
  30. package/dist/database/system-data/fields/settings.yaml +0 -1
  31. package/dist/database/system-data/fields/users.yaml +3 -0
  32. package/dist/env.js +9 -3
  33. package/dist/exceptions/index.d.ts +1 -0
  34. package/dist/exceptions/index.js +1 -0
  35. package/dist/exceptions/token-expired.d.ts +4 -0
  36. package/dist/exceptions/token-expired.js +10 -0
  37. package/dist/middleware/cache.js +10 -0
  38. package/dist/services/authorization.js +72 -30
  39. package/dist/services/collections.d.ts +2 -0
  40. package/dist/services/collections.js +10 -0
  41. package/dist/services/fields.js +26 -1
  42. package/dist/services/files.d.ts +5 -1
  43. package/dist/services/files.js +59 -40
  44. package/dist/services/graphql.d.ts +2 -3
  45. package/dist/services/graphql.js +65 -11
  46. package/dist/services/import-export.js +2 -0
  47. package/dist/services/items.js +12 -5
  48. package/dist/services/payload.d.ts +2 -1
  49. package/dist/services/payload.js +22 -17
  50. package/dist/services/specifications.js +1 -3
  51. package/dist/services/users.js +4 -1
  52. package/dist/types/files.d.ts +8 -0
  53. package/dist/utils/apply-query.d.ts +2 -1
  54. package/dist/utils/apply-query.js +134 -156
  55. package/dist/utils/apply-snapshot.d.ts +3 -1
  56. package/dist/utils/apply-snapshot.js +34 -5
  57. package/dist/utils/get-ast-from-query.js +15 -3
  58. package/dist/utils/get-column-path.d.ts +16 -0
  59. package/dist/utils/get-column-path.js +46 -0
  60. package/dist/utils/get-graphql-type.js +1 -0
  61. package/dist/utils/get-local-type.js +5 -0
  62. package/dist/utils/get-relation-info.d.ts +7 -0
  63. package/dist/utils/get-relation-info.js +45 -0
  64. package/dist/utils/get-relation-type.d.ts +1 -1
  65. package/dist/utils/get-schema.js +3 -0
  66. package/dist/utils/jwt.js +1 -1
  67. package/dist/utils/merge-permissions-for-share.js +1 -1
  68. package/dist/utils/reduce-schema.js +18 -11
  69. package/dist/utils/validate-query.js +19 -15
  70. package/example.env +4 -0
  71. package/package.json +18 -19
@@ -3,33 +3,23 @@ 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.applyAggregate = exports.applySearch = exports.applyFilter = void 0;
6
+ exports.applyAggregate = exports.applySearch = exports.applyFilter = exports.applySort = void 0;
7
+ const utils_1 = require("@directus/shared/utils");
7
8
  const lodash_1 = require("lodash");
8
9
  const nanoid_1 = require("nanoid");
9
10
  const uuid_validate_1 = __importDefault(require("uuid-validate"));
11
+ const helpers_1 = require("../database/helpers");
10
12
  const exceptions_1 = require("../exceptions");
11
13
  const get_column_1 = require("./get-column");
12
- const get_relation_type_1 = require("./get-relation-type");
13
- const helpers_1 = require("../database/helpers");
14
- const utils_1 = require("@directus/shared/utils");
14
+ const get_column_path_1 = require("./get-column-path");
15
+ const get_relation_info_1 = require("./get-relation-info");
15
16
  const generateAlias = (0, nanoid_1.customAlphabet)('abcdefghijklmnopqrstuvwxyz', 5);
16
17
  /**
17
18
  * Apply the Query to a given Knex query builder instance
18
19
  */
19
20
  function applyQuery(knex, collection, dbQuery, query, schema, subQuery = false) {
20
21
  if (query.sort) {
21
- dbQuery.orderBy(query.sort.map((sortField) => {
22
- let column = sortField;
23
- let order = 'asc';
24
- if (sortField.startsWith('-')) {
25
- column = column.substring(1);
26
- order = 'desc';
27
- }
28
- return {
29
- order,
30
- column: (0, get_column_1.getColumn)(knex, collection, column, false, schema),
31
- };
32
- }));
22
+ applySort(knex, schema, dbQuery, query.sort, collection, subQuery);
33
23
  }
34
24
  if (typeof query.limit === 'number' && query.limit !== -1) {
35
25
  dbQuery.limit(query.limit);
@@ -55,44 +45,109 @@ function applyQuery(knex, collection, dbQuery, query, schema, subQuery = false)
55
45
  return dbQuery;
56
46
  }
57
47
  exports.default = applyQuery;
58
- function getRelationInfo(relations, collection, field) {
59
- var _a, _b;
60
- const implicitRelation = (_a = field.match(/^\$FOLLOW\((.*?),(.*?)(?:,(.*?))?\)$/)) === null || _a === void 0 ? void 0 : _a.slice(1);
61
- if (implicitRelation) {
62
- if (implicitRelation[2] === undefined) {
63
- const [m2oCollection, m2oField] = implicitRelation;
64
- const relation = {
65
- collection: m2oCollection,
66
- field: m2oField,
67
- related_collection: collection,
68
- schema: null,
69
- meta: null,
70
- };
71
- return { relation, relationType: 'o2m' };
48
+ function addJoin({ path, collection, aliasMap, rootQuery, subQuery, schema, relations, knex }) {
49
+ path = (0, lodash_1.clone)(path);
50
+ followRelation(path);
51
+ function followRelation(pathParts, parentCollection = collection, parentAlias) {
52
+ /**
53
+ * For A2M fields, the path can contain an optional collection scope <field>:<scope>
54
+ */
55
+ const pathRoot = pathParts[0].split(':')[0];
56
+ const { relation, relationType } = (0, get_relation_info_1.getRelationInfo)(relations, parentCollection, pathRoot);
57
+ if (!relation) {
58
+ return;
72
59
  }
73
- else {
74
- const [a2oCollection, a2oItemField, a2oCollectionField] = implicitRelation;
75
- const relation = {
76
- collection: a2oCollection,
77
- field: a2oItemField,
78
- related_collection: collection,
79
- schema: null,
80
- meta: {
81
- one_collection_field: a2oCollectionField,
82
- one_field: field,
83
- },
84
- };
85
- return { relation, relationType: 'o2a' };
60
+ const alias = generateAlias();
61
+ (0, lodash_1.set)(aliasMap, parentAlias ? [parentAlias, ...pathParts] : pathParts, alias);
62
+ if (relationType === 'm2o') {
63
+ rootQuery.leftJoin({ [alias]: relation.related_collection }, `${parentAlias || parentCollection}.${relation.field}`, `${alias}.${schema.collections[relation.related_collection].primary}`);
64
+ }
65
+ if (relationType === 'a2o') {
66
+ const pathScope = pathParts[0].split(':')[1];
67
+ if (!pathScope) {
68
+ throw new exceptions_1.InvalidQueryException(`You have to provide a collection scope when sorting or filtering on a many-to-any item`);
69
+ }
70
+ rootQuery.leftJoin({ [alias]: pathScope }, (joinClause) => {
71
+ joinClause
72
+ .onVal(relation.meta.one_collection_field, '=', pathScope)
73
+ .andOn(`${parentAlias || parentCollection}.${relation.field}`, '=', knex.raw(`CAST(?? AS CHAR(255))`, `${alias}.${schema.collections[pathScope].primary}`));
74
+ });
75
+ }
76
+ if (relationType === 'o2a') {
77
+ rootQuery.leftJoin({ [alias]: relation.collection }, (joinClause) => {
78
+ joinClause
79
+ .onVal(relation.meta.one_collection_field, '=', parentCollection)
80
+ .andOn(`${alias}.${relation.field}`, '=', knex.raw(`CAST(?? AS CHAR(255))`, `${parentAlias || parentCollection}.${schema.collections[parentCollection].primary}`));
81
+ });
82
+ }
83
+ // Still join o2m relations when in subquery OR when the o2m relation is not at the root level
84
+ if (relationType === 'o2m' && (subQuery === true || parentAlias !== undefined)) {
85
+ rootQuery.leftJoin({ [alias]: relation.collection }, `${parentAlias || parentCollection}.${schema.collections[relation.related_collection].primary}`, `${alias}.${relation.field}`);
86
+ }
87
+ if (relationType === 'm2o' || subQuery === true || (relationType === 'o2m' && parentAlias !== undefined)) {
88
+ let parent;
89
+ if (relationType === 'm2o') {
90
+ parent = relation.related_collection;
91
+ }
92
+ else if (relationType === 'a2o') {
93
+ const pathScope = pathParts[0].split(':')[1];
94
+ if (!pathScope) {
95
+ throw new exceptions_1.InvalidQueryException(`You have to provide a collection scope when sorting or filtering on a many-to-any item`);
96
+ }
97
+ parent = pathScope;
98
+ }
99
+ else {
100
+ parent = relation.collection;
101
+ }
102
+ pathParts.shift();
103
+ if (pathParts.length) {
104
+ followRelation(pathParts, parent, alias);
105
+ }
86
106
  }
87
107
  }
88
- const relation = (_b = relations.find((relation) => {
89
- var _a;
90
- return ((relation.collection === collection && relation.field === field) ||
91
- (relation.related_collection === collection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === field));
92
- })) !== null && _b !== void 0 ? _b : null;
93
- const relationType = relation ? (0, get_relation_type_1.getRelationType)({ relation, collection, field }) : null;
94
- return { relation, relationType };
95
108
  }
109
+ function applySort(knex, schema, rootQuery, rootSort, collection, subQuery = false) {
110
+ const relations = schema.relations;
111
+ const aliasMap = {};
112
+ rootQuery.orderBy(rootSort.map((sortField) => {
113
+ const column = sortField.split('.');
114
+ let order = 'asc';
115
+ if (column.length > 1) {
116
+ if (sortField.startsWith('-')) {
117
+ order = 'desc';
118
+ }
119
+ if (column[0].startsWith('-')) {
120
+ column[0] = column[0].substring(1);
121
+ }
122
+ addJoin({
123
+ path: column,
124
+ collection,
125
+ aliasMap,
126
+ rootQuery,
127
+ subQuery,
128
+ schema,
129
+ relations,
130
+ knex,
131
+ });
132
+ const colPath = (0, get_column_path_1.getColumnPath)({ path: column, collection, aliasMap, relations }) || '';
133
+ const [alias, field] = colPath.split('.');
134
+ return {
135
+ order,
136
+ column: (0, get_column_1.getColumn)(knex, alias, field, false, schema),
137
+ };
138
+ }
139
+ let col = column[0];
140
+ if (sortField.startsWith('-')) {
141
+ col = column[0].substring(1);
142
+ order = 'desc';
143
+ }
144
+ return {
145
+ order,
146
+ column: (0, get_column_1.getColumn)(knex, collection, col, false, schema),
147
+ };
148
+ }));
149
+ }
150
+ exports.applySort = applySort;
96
151
  function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery = false) {
97
152
  const helpers = (0, helpers_1.getHelpers)(knex);
98
153
  const relations = schema.relations;
@@ -114,72 +169,21 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
114
169
  }
115
170
  const filterPath = getFilterPath(key, value);
116
171
  if (filterPath.length > 1) {
117
- addJoin(filterPath, collection);
118
- }
119
- }
120
- function addJoin(path, collection) {
121
- path = (0, lodash_1.clone)(path);
122
- followRelation(path);
123
- function followRelation(pathParts, parentCollection = collection, parentAlias) {
124
- /**
125
- * For A2M fields, the path can contain an optional collection scope <field>:<scope>
126
- */
127
- const pathRoot = pathParts[0].split(':')[0];
128
- const { relation, relationType } = getRelationInfo(relations, parentCollection, pathRoot);
129
- if (!relation) {
130
- return;
131
- }
132
- const alias = generateAlias();
133
- (0, lodash_1.set)(aliasMap, parentAlias ? [parentAlias, ...pathParts] : pathParts, alias);
134
- if (relationType === 'm2o') {
135
- dbQuery.leftJoin({ [alias]: relation.related_collection }, `${parentAlias || parentCollection}.${relation.field}`, `${alias}.${schema.collections[relation.related_collection].primary}`);
136
- }
137
- if (relationType === 'a2o') {
138
- const pathScope = pathParts[0].split(':')[1];
139
- if (!pathScope) {
140
- throw new exceptions_1.InvalidQueryException(`You have to provide a collection scope when filtering on a many-to-any item`);
141
- }
142
- dbQuery.leftJoin({ [alias]: pathScope }, (joinClause) => {
143
- joinClause
144
- .onVal(relation.meta.one_collection_field, '=', pathScope)
145
- .andOn(`${parentAlias || parentCollection}.${relation.field}`, '=', knex.raw(`CAST(?? AS CHAR(255))`, `${alias}.${schema.collections[pathScope].primary}`));
146
- });
147
- }
148
- if (relationType === 'o2a') {
149
- dbQuery.leftJoin({ [alias]: relation.collection }, (joinClause) => {
150
- joinClause
151
- .onVal(relation.meta.one_collection_field, '=', parentCollection)
152
- .andOn(`${alias}.${relation.field}`, '=', knex.raw(`CAST(?? AS CHAR(255))`, `${parentAlias || parentCollection}.${schema.collections[parentCollection].primary}`));
153
- });
154
- }
155
- // Still join o2m relations when in subquery OR when the o2m relation is not at the root level
156
- if (relationType === 'o2m' && (subQuery === true || parentAlias !== undefined)) {
157
- dbQuery.leftJoin({ [alias]: relation.collection }, `${parentAlias || parentCollection}.${schema.collections[relation.related_collection].primary}`, `${alias}.${relation.field}`);
158
- }
159
- if (relationType === 'm2o' || subQuery === true || (relationType === 'o2m' && parentAlias !== undefined)) {
160
- let parent;
161
- if (relationType === 'm2o') {
162
- parent = relation.related_collection;
163
- }
164
- else if (relationType === 'a2o') {
165
- const pathScope = pathParts[0].split(':')[1];
166
- if (!pathScope) {
167
- throw new exceptions_1.InvalidQueryException(`You have to provide a collection scope when filtering on a many-to-any item`);
168
- }
169
- parent = pathScope;
170
- }
171
- else {
172
- parent = relation.collection;
173
- }
174
- pathParts.shift();
175
- if (pathParts.length) {
176
- followRelation(pathParts, parent, alias);
177
- }
178
- }
172
+ addJoin({
173
+ path: filterPath,
174
+ collection,
175
+ knex,
176
+ schema,
177
+ relations,
178
+ subQuery,
179
+ rootQuery,
180
+ aliasMap,
181
+ });
179
182
  }
180
183
  }
181
184
  }
182
185
  function addWhereClauses(knex, dbQuery, filter, collection, logical = 'and') {
186
+ var _a, _b;
183
187
  for (const [key, value] of Object.entries(filter)) {
184
188
  if (key === '_or' || key === '_and') {
185
189
  // If the _or array contains an empty object (full permissions), we should short-circuit and ignore all other
@@ -200,11 +204,11 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
200
204
  * For A2M fields, the path can contain an optional collection scope <field>:<scope>
201
205
  */
202
206
  const pathRoot = filterPath[0].split(':')[0];
203
- const { relation, relationType } = getRelationInfo(relations, collection, pathRoot);
207
+ const { relation, relationType } = (0, get_relation_info_1.getRelationInfo)(relations, collection, pathRoot);
204
208
  const { operator: filterOperator, value: filterValue } = getOperation(key, value);
205
209
  if (relationType === 'm2o' || relationType === 'a2o' || relationType === null) {
206
210
  if (filterPath.length > 1) {
207
- const columnName = getWhereColumn(filterPath, collection);
211
+ const columnName = (0, get_column_path_1.getColumnPath)({ path: filterPath, collection, relations, aliasMap });
208
212
  if (!columnName)
209
213
  continue;
210
214
  applyFilterToQuery(columnName, filterOperator, filterValue, logical);
@@ -220,16 +224,25 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
220
224
  if (relationType === 'o2a') {
221
225
  pkField = knex.raw(`CAST(?? AS CHAR(255))`, [pkField]);
222
226
  }
223
- // Note: knex's types don't appreciate knex.raw in whereIn, even though it's officially supported
224
- dbQuery[logical].whereIn(pkField, (subQueryKnex) => {
227
+ const subQueryBuilder = (filter) => (subQueryKnex) => {
225
228
  const field = relation.field;
226
229
  const collection = relation.collection;
227
230
  const column = `${collection}.${field}`;
228
- subQueryKnex.select({ [field]: column }).from(collection);
229
- applyQuery(knex, relation.collection, subQueryKnex, {
230
- filter: value,
231
- }, schema, true);
232
- });
231
+ subQueryKnex
232
+ .select({ [field]: column })
233
+ .from(collection)
234
+ .whereNotNull(column);
235
+ applyQuery(knex, relation.collection, subQueryKnex, { filter }, schema, true);
236
+ };
237
+ if (((_a = Object.keys(value)) === null || _a === void 0 ? void 0 : _a[0]) === '_none') {
238
+ dbQuery[logical].whereNotIn(pkField, subQueryBuilder(Object.values(value)[0]));
239
+ }
240
+ else if (((_b = Object.keys(value)) === null || _b === void 0 ? void 0 : _b[0]) === '_some') {
241
+ dbQuery[logical].whereIn(pkField, subQueryBuilder(Object.values(value)[0]));
242
+ }
243
+ else {
244
+ dbQuery[logical].whereIn(pkField, subQueryBuilder(value));
245
+ }
233
246
  }
234
247
  }
235
248
  function applyFilterToQuery(key, operator, compareValue, logical = 'and') {
@@ -372,41 +385,6 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
372
385
  dbQuery[logical].whereRaw(helpers.st.nintersects_bbox(key, compareValue));
373
386
  }
374
387
  }
375
- function getWhereColumn(path, collection) {
376
- return followRelation(path);
377
- function followRelation(pathParts, parentCollection = collection, parentAlias) {
378
- /**
379
- * For A2M fields, the path can contain an optional collection scope <field>:<scope>
380
- */
381
- const pathRoot = pathParts[0].split(':')[0];
382
- const { relation, relationType } = getRelationInfo(relations, parentCollection, pathRoot);
383
- if (!relation) {
384
- throw new exceptions_1.InvalidQueryException(`"${parentCollection}.${pathRoot}" is not a relational field`);
385
- }
386
- const alias = (0, lodash_1.get)(aliasMap, parentAlias ? [parentAlias, ...pathParts] : pathParts);
387
- const remainingParts = pathParts.slice(1);
388
- let parent;
389
- if (relationType === 'a2o') {
390
- const pathScope = pathParts[0].split(':')[1];
391
- if (!pathScope) {
392
- throw new exceptions_1.InvalidQueryException(`You have to provide a collection scope when filtering on a many-to-any item`);
393
- }
394
- parent = pathScope;
395
- }
396
- else if (relationType === 'm2o') {
397
- parent = relation.related_collection;
398
- }
399
- else {
400
- parent = relation.collection;
401
- }
402
- if (remainingParts.length === 1) {
403
- return `${alias || parent}.${remainingParts[0]}`;
404
- }
405
- if (remainingParts.length) {
406
- return followRelation(remainingParts, parent, alias);
407
- }
408
- }
409
- }
410
388
  }
411
389
  }
412
390
  exports.applyFilter = applyFilter;
@@ -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,
@@ -0,0 +1,16 @@
1
+ import { Relation } from '@directus/shared/types';
2
+ declare type AliasMap = string | {
3
+ [key: string]: AliasMap;
4
+ };
5
+ export declare type ColPathProps = {
6
+ path: string[];
7
+ collection: string;
8
+ aliasMap: AliasMap;
9
+ relations: Relation[];
10
+ };
11
+ /**
12
+ * Converts a Directus field list path to the correct SQL names based on the constructed alias map.
13
+ * For example: ['author', 'role', 'name'] -> 'ljnsv.name'
14
+ */
15
+ export declare function getColumnPath({ path, collection, aliasMap, relations }: ColPathProps): string | void;
16
+ export {};
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getColumnPath = void 0;
4
+ const get_relation_info_1 = require("./get-relation-info");
5
+ const exceptions_1 = require("../exceptions");
6
+ const lodash_1 = require("lodash");
7
+ /**
8
+ * Converts a Directus field list path to the correct SQL names based on the constructed alias map.
9
+ * For example: ['author', 'role', 'name'] -> 'ljnsv.name'
10
+ */
11
+ function getColumnPath({ path, collection, aliasMap, relations }) {
12
+ return followRelation(path);
13
+ function followRelation(pathParts, parentCollection = collection, parentAlias) {
14
+ /**
15
+ * For A2M fields, the path can contain an optional collection scope <field>:<scope>
16
+ */
17
+ const pathRoot = pathParts[0].split(':')[0];
18
+ const { relation, relationType } = (0, get_relation_info_1.getRelationInfo)(relations, parentCollection, pathRoot);
19
+ if (!relation) {
20
+ throw new exceptions_1.InvalidQueryException(`"${parentCollection}.${pathRoot}" is not a relational field`);
21
+ }
22
+ const alias = (0, lodash_1.get)(aliasMap, parentAlias ? [parentAlias, ...pathParts] : pathParts);
23
+ const remainingParts = pathParts.slice(1);
24
+ let parent;
25
+ if (relationType === 'a2o') {
26
+ const pathScope = pathParts[0].split(':')[1];
27
+ if (!pathScope) {
28
+ throw new exceptions_1.InvalidQueryException(`You have to provide a collection scope when sorting on a many-to-any item`);
29
+ }
30
+ parent = pathScope;
31
+ }
32
+ else if (relationType === 'm2o') {
33
+ parent = relation.related_collection;
34
+ }
35
+ else {
36
+ parent = relation.collection;
37
+ }
38
+ if (remainingParts.length === 1) {
39
+ return `${alias || parent}.${remainingParts[0]}`;
40
+ }
41
+ if (remainingParts.length) {
42
+ return followRelation(remainingParts, parent, alias);
43
+ }
44
+ }
45
+ }
46
+ exports.getColumnPath = getColumnPath;
@@ -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
  }
@@ -0,0 +1,7 @@
1
+ import { Relation } from '@directus/shared/types';
2
+ declare type RelationInfo = {
3
+ relation: Relation | null;
4
+ relationType: string | null;
5
+ };
6
+ export declare function getRelationInfo(relations: Relation[], collection: string, field: string): RelationInfo;
7
+ export {};
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getRelationInfo = void 0;
4
+ const get_relation_type_1 = require("./get-relation-type");
5
+ function getRelationInfo(relations, collection, field) {
6
+ var _a, _b;
7
+ if (field.startsWith('$FOLLOW') && field.length > 500) {
8
+ throw new Error(`Implicit $FOLLOW statement is too big to parse. Got: "${field.substring(500)}..."`);
9
+ }
10
+ const implicitRelation = (_a = field.match(/^\$FOLLOW\((.*?),(.*?)(?:,(.*?))?\)$/)) === null || _a === void 0 ? void 0 : _a.slice(1);
11
+ if (implicitRelation) {
12
+ if (implicitRelation[2] === undefined) {
13
+ const [m2oCollection, m2oField] = implicitRelation;
14
+ const relation = {
15
+ collection: m2oCollection.trim(),
16
+ field: m2oField.trim(),
17
+ related_collection: collection,
18
+ schema: null,
19
+ meta: null,
20
+ };
21
+ return { relation, relationType: 'o2m' };
22
+ }
23
+ else {
24
+ const [a2oCollection, a2oItemField, a2oCollectionField] = implicitRelation;
25
+ const relation = {
26
+ collection: a2oCollection.trim(),
27
+ field: a2oItemField.trim(),
28
+ related_collection: collection,
29
+ schema: null,
30
+ meta: {
31
+ one_collection_field: a2oCollectionField.trim(),
32
+ },
33
+ };
34
+ return { relation, relationType: 'o2a' };
35
+ }
36
+ }
37
+ const relation = (_b = relations.find((relation) => {
38
+ var _a;
39
+ return ((relation.collection === collection && relation.field === field) ||
40
+ (relation.related_collection === collection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === field));
41
+ })) !== null && _b !== void 0 ? _b : null;
42
+ const relationType = relation ? (0, get_relation_type_1.getRelationType)({ relation, collection, field }) : null;
43
+ return { relation, relationType };
44
+ }
45
+ exports.getRelationInfo = getRelationInfo;
@@ -1,6 +1,6 @@
1
1
  import { Relation } from '@directus/shared/types';
2
2
  export declare function getRelationType(getRelationOptions: {
3
- relation: Relation;
3
+ relation?: Relation | null;
4
4
  collection: string | null;
5
5
  field: string;
6
6
  }): 'm2o' | 'o2m' | 'a2o' | null;
@@ -8,6 +8,7 @@ const schema_1 = __importDefault(require("@directus/schema"));
8
8
  const utils_1 = require("@directus/shared/utils");
9
9
  const lodash_1 = require("lodash");
10
10
  const cache_1 = require("../cache");
11
+ const constants_1 = require("../constants");
11
12
  const database_1 = __importDefault(require("../database"));
12
13
  const collections_1 = require("../database/system-data/collections");
13
14
  const fields_1 = require("../database/system-data/fields");
@@ -113,6 +114,8 @@ async function getDatabaseSchema(database, schemaInspector) {
113
114
  const existing = result.collections[field.collection].fields[field.field];
114
115
  const column = schemaOverview[field.collection].columns[field.field];
115
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;
116
119
  const type = (existing && (0, get_local_type_1.default)(column, { special })) || 'alias';
117
120
  let validation = (_a = field.validation) !== null && _a !== void 0 ? _a : null;
118
121
  if (validation && typeof validation === 'string')