@directus/api 27.1.0 → 28.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/auth/drivers/oauth2.js +4 -4
  2. package/dist/auth/drivers/openid.d.ts +2 -1
  3. package/dist/auth/drivers/openid.js +75 -42
  4. package/dist/database/errors/dialects/mssql.d.ts +2 -1
  5. package/dist/database/errors/dialects/mssql.js +124 -120
  6. package/dist/database/errors/dialects/mysql.d.ts +2 -1
  7. package/dist/database/errors/dialects/mysql.js +112 -108
  8. package/dist/database/errors/dialects/postgres.d.ts +2 -1
  9. package/dist/database/errors/dialects/postgres.js +75 -71
  10. package/dist/database/errors/dialects/sqlite.d.ts +2 -1
  11. package/dist/database/errors/dialects/sqlite.js +6 -5
  12. package/dist/database/errors/translate.d.ts +2 -1
  13. package/dist/database/errors/translate.js +5 -5
  14. package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +3 -1
  15. package/dist/database/get-ast-from-query/lib/convert-wildcards.js +18 -8
  16. package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +2 -1
  17. package/dist/database/get-ast-from-query/lib/parse-fields.js +3 -2
  18. package/dist/database/migrations/20250609A-license-banner.d.ts +3 -0
  19. package/dist/database/migrations/20250609A-license-banner.js +14 -0
  20. package/dist/database/migrations/20250613A-add-project-id.d.ts +3 -0
  21. package/dist/database/migrations/20250613A-add-project-id.js +26 -0
  22. package/dist/extensions/lib/get-extensions-settings.js +14 -8
  23. package/dist/flows.d.ts +5 -1
  24. package/dist/flows.js +61 -4
  25. package/dist/permissions/utils/get-permissions-for-share.js +2 -0
  26. package/dist/permissions/utils/merge-fields.d.ts +1 -0
  27. package/dist/permissions/utils/merge-fields.js +29 -0
  28. package/dist/permissions/utils/merge-permissions.js +3 -14
  29. package/dist/services/fields.js +3 -3
  30. package/dist/services/graphql/resolvers/mutation.js +1 -1
  31. package/dist/services/graphql/resolvers/query.js +1 -1
  32. package/dist/services/graphql/resolvers/system.js +4 -4
  33. package/dist/services/graphql/schema/parse-query.d.ts +1 -1
  34. package/dist/services/graphql/schema/parse-query.js +8 -1
  35. package/dist/services/graphql/schema/read.js +4 -4
  36. package/dist/services/graphql/subscription.js +1 -1
  37. package/dist/services/graphql/utils/filter-replace-m2a.d.ts +3 -0
  38. package/dist/services/graphql/utils/filter-replace-m2a.js +59 -0
  39. package/dist/services/items.js +2 -2
  40. package/dist/services/specifications.js +6 -2
  41. package/dist/services/users.js +3 -0
  42. package/dist/telemetry/lib/get-report.js +4 -1
  43. package/dist/telemetry/types/report.d.ts +4 -0
  44. package/dist/telemetry/utils/get-project-id.d.ts +2 -0
  45. package/dist/telemetry/utils/get-project-id.js +4 -0
  46. package/dist/utils/is-url-allowed.js +1 -1
  47. package/dist/utils/sanitize-query.js +6 -0
  48. package/dist/utils/validate-query.js +1 -0
  49. package/dist/websocket/utils/items.d.ts +1 -1
  50. package/package.json +22 -21
  51. package/dist/utils/map-values-deep.d.ts +0 -1
  52. package/dist/utils/map-values-deep.js +0 -25
@@ -9,126 +9,130 @@ var MySQLErrorCodes;
9
9
  MySQLErrorCodes["ER_INVALID_USE_OF_NULL"] = "ER_INVALID_USE_OF_NULL";
10
10
  MySQLErrorCodes["WARN_DATA_TRUNCATED"] = "WARN_DATA_TRUNCATED";
11
11
  })(MySQLErrorCodes || (MySQLErrorCodes = {}));
12
- export function extractError(error) {
12
+ export function extractError(error, data) {
13
13
  switch (error.code) {
14
14
  case MySQLErrorCodes.UNIQUE_VIOLATION:
15
- return uniqueViolation(error);
15
+ return uniqueViolation();
16
16
  case MySQLErrorCodes.NUMERIC_VALUE_OUT_OF_RANGE:
17
- return numericValueOutOfRange(error);
17
+ return numericValueOutOfRange();
18
18
  case MySQLErrorCodes.ER_DATA_TOO_LONG:
19
- return valueLimitViolation(error);
19
+ return valueLimitViolation();
20
20
  case MySQLErrorCodes.NOT_NULL_VIOLATION:
21
- return notNullViolation(error);
21
+ return notNullViolation();
22
22
  case MySQLErrorCodes.FOREIGN_KEY_VIOLATION:
23
- return foreignKeyViolation(error);
23
+ return foreignKeyViolation();
24
24
  // Note: MariaDB throws data truncated for null value error
25
25
  case MySQLErrorCodes.ER_INVALID_USE_OF_NULL:
26
26
  case MySQLErrorCodes.WARN_DATA_TRUNCATED:
27
- return containsNullValues(error);
27
+ return containsNullValues();
28
28
  }
29
29
  return error;
30
- }
31
- function uniqueViolation(error) {
32
- const betweenQuotes = /'([^']+)'/g;
33
- const matches = error.sqlMessage.match(betweenQuotes);
34
- if (!matches)
35
- return error;
36
- /**
37
- * MySQL's error doesn't return the field name in the error. In case the field is created through
38
- * Directus (/ Knex), the key name will be `<collection>_<field>_unique` in which case we can pull
39
- * the field name from the key name.
40
- * If the field is the primary key it instead will be `<collection>_PRIMARY` for MySQL 8+
41
- * and `PRIMARY` for MySQL 5.7 and MariaDB.
42
- */
43
- let collection;
44
- let indexName;
45
- let field = null;
46
- if (matches[1].includes('.')) {
47
- // MySQL 8+ style error message
48
- // In case of primary key matches[1] is `'<collection>.PRIMARY'`
49
- // In case of other field matches[1] is `'<collection>.<collection>_<field>_unique'`
50
- [collection, indexName] = matches[1].slice(1, -1).split('.');
30
+ function uniqueViolation() {
31
+ const betweenQuotes = /'([^']+)'/g;
32
+ const matches = error.sqlMessage.match(betweenQuotes);
33
+ if (!matches)
34
+ return error;
35
+ /**
36
+ * MySQL's error doesn't return the field name in the error. In case the field is created through
37
+ * Directus (/ Knex), the key name will be `<collection>_<field>_unique` in which case we can pull
38
+ * the field name from the key name.
39
+ * If the field is the primary key it instead will be `<collection>_PRIMARY` for MySQL 8+
40
+ * and `PRIMARY` for MySQL 5.7 and MariaDB.
41
+ */
42
+ let collection;
43
+ let indexName;
44
+ let field = null;
45
+ if (matches[1].includes('.')) {
46
+ // MySQL 8+ style error message
47
+ // In case of primary key matches[1] is `'<collection>.PRIMARY'`
48
+ // In case of other field matches[1] is `'<collection>.<collection>_<field>_unique'`
49
+ [collection, indexName] = matches[1].slice(1, -1).split('.');
50
+ }
51
+ else {
52
+ // MySQL 5.7 and MariaDB style error message
53
+ // In case of primary key matches[1] is `'PRIMARY'`
54
+ // In case of other field matches[1] is `'<collection>_<field>_unique'`
55
+ indexName = matches[1].slice(1, -1);
56
+ collection = indexName.includes('_') ? indexName.split('_')[0] : null;
57
+ }
58
+ if (collection !== null && indexName.startsWith(`${collection}_`) && indexName.endsWith('_unique')) {
59
+ field = indexName?.slice(collection.length + 1, -7);
60
+ }
61
+ return new RecordNotUniqueError({
62
+ collection,
63
+ field,
64
+ value: field ? data[field] : null,
65
+ primaryKey: indexName === 'PRIMARY', // propagate information about primary key violation
66
+ });
51
67
  }
52
- else {
53
- // MySQL 5.7 and MariaDB style error message
54
- // In case of primary key matches[1] is `'PRIMARY'`
55
- // In case of other field matches[1] is `'<collection>_<field>_unique'`
56
- indexName = matches[1].slice(1, -1);
57
- collection = indexName.includes('_') ? indexName.split('_')[0] : null;
68
+ function numericValueOutOfRange() {
69
+ const betweenTicks = /`([^`]+)`/g;
70
+ const betweenQuotes = /'([^']+)'/g;
71
+ const tickMatches = error.sql.match(betweenTicks);
72
+ const quoteMatches = error.sqlMessage.match(betweenQuotes);
73
+ if (!tickMatches || !quoteMatches)
74
+ return error;
75
+ const collection = tickMatches[0]?.slice(1, -1);
76
+ const field = quoteMatches[0]?.slice(1, -1);
77
+ return new ValueOutOfRangeError({
78
+ collection,
79
+ field,
80
+ value: field ? data[field] : null,
81
+ });
58
82
  }
59
- if (collection !== null && indexName.startsWith(`${collection}_`) && indexName.endsWith('_unique')) {
60
- field = indexName?.slice(collection.length + 1, -7);
83
+ function valueLimitViolation() {
84
+ const betweenTicks = /`([^`]+)`/g;
85
+ const betweenQuotes = /'([^']+)'/g;
86
+ const tickMatches = error.sql.match(betweenTicks);
87
+ const quoteMatches = error.sqlMessage.match(betweenQuotes);
88
+ if (!tickMatches || !quoteMatches)
89
+ return error;
90
+ const collection = tickMatches[0]?.slice(1, -1);
91
+ const field = quoteMatches[0]?.slice(1, -1);
92
+ return new ValueTooLongError({
93
+ collection,
94
+ field,
95
+ value: field ? data[field] : null,
96
+ });
97
+ }
98
+ function notNullViolation() {
99
+ const betweenTicks = /`([^`]+)`/g;
100
+ const betweenQuotes = /'([^']+)'/g;
101
+ const tickMatches = error.sql.match(betweenTicks);
102
+ const quoteMatches = error.sqlMessage.match(betweenQuotes);
103
+ if (!tickMatches || !quoteMatches)
104
+ return error;
105
+ const collection = tickMatches[0]?.slice(1, -1);
106
+ const field = quoteMatches[0]?.slice(1, -1);
107
+ return new NotNullViolationError({
108
+ collection,
109
+ field,
110
+ });
111
+ }
112
+ function foreignKeyViolation() {
113
+ const betweenTicks = /`([^`]+)`/g;
114
+ const betweenParens = /\(([^)]+)\)/g;
115
+ const tickMatches = error.sqlMessage.match(betweenTicks);
116
+ const parenMatches = error.sql.match(betweenParens);
117
+ if (!tickMatches || !parenMatches)
118
+ return error;
119
+ const collection = tickMatches[1].slice(1, -1);
120
+ const field = tickMatches[3].slice(1, -1);
121
+ return new InvalidForeignKeyError({
122
+ collection,
123
+ field,
124
+ value: field ? data[field] : null,
125
+ });
126
+ }
127
+ function containsNullValues() {
128
+ const betweenTicks = /`([^`]+)`/g;
129
+ // Normally, we shouldn't read from the executed SQL. In this case, we're altering a single
130
+ // column, so we shouldn't have the problem where multiple columns are altered at the same time
131
+ const tickMatches = error.sql.match(betweenTicks);
132
+ if (!tickMatches)
133
+ return error;
134
+ const collection = tickMatches[0].slice(1, -1);
135
+ const field = tickMatches[1].slice(1, -1);
136
+ return new ContainsNullValuesError({ collection, field });
61
137
  }
62
- return new RecordNotUniqueError({
63
- collection,
64
- field,
65
- primaryKey: indexName === 'PRIMARY', // propagate information about primary key violation
66
- });
67
- }
68
- function numericValueOutOfRange(error) {
69
- const betweenTicks = /`([^`]+)`/g;
70
- const betweenQuotes = /'([^']+)'/g;
71
- const tickMatches = error.sql.match(betweenTicks);
72
- const quoteMatches = error.sqlMessage.match(betweenQuotes);
73
- if (!tickMatches || !quoteMatches)
74
- return error;
75
- const collection = tickMatches[0]?.slice(1, -1);
76
- const field = quoteMatches[0]?.slice(1, -1);
77
- return new ValueOutOfRangeError({
78
- collection,
79
- field,
80
- });
81
- }
82
- function valueLimitViolation(error) {
83
- const betweenTicks = /`([^`]+)`/g;
84
- const betweenQuotes = /'([^']+)'/g;
85
- const tickMatches = error.sql.match(betweenTicks);
86
- const quoteMatches = error.sqlMessage.match(betweenQuotes);
87
- if (!tickMatches || !quoteMatches)
88
- return error;
89
- const collection = tickMatches[0]?.slice(1, -1);
90
- const field = quoteMatches[0]?.slice(1, -1);
91
- return new ValueTooLongError({
92
- collection,
93
- field,
94
- });
95
- }
96
- function notNullViolation(error) {
97
- const betweenTicks = /`([^`]+)`/g;
98
- const betweenQuotes = /'([^']+)'/g;
99
- const tickMatches = error.sql.match(betweenTicks);
100
- const quoteMatches = error.sqlMessage.match(betweenQuotes);
101
- if (!tickMatches || !quoteMatches)
102
- return error;
103
- const collection = tickMatches[0]?.slice(1, -1);
104
- const field = quoteMatches[0]?.slice(1, -1);
105
- return new NotNullViolationError({
106
- collection,
107
- field,
108
- });
109
- }
110
- function foreignKeyViolation(error) {
111
- const betweenTicks = /`([^`]+)`/g;
112
- const betweenParens = /\(([^)]+)\)/g;
113
- const tickMatches = error.sqlMessage.match(betweenTicks);
114
- const parenMatches = error.sql.match(betweenParens);
115
- if (!tickMatches || !parenMatches)
116
- return error;
117
- const collection = tickMatches[1].slice(1, -1);
118
- const field = tickMatches[3].slice(1, -1);
119
- return new InvalidForeignKeyError({
120
- collection,
121
- field,
122
- });
123
- }
124
- function containsNullValues(error) {
125
- const betweenTicks = /`([^`]+)`/g;
126
- // Normally, we shouldn't read from the executed SQL. In this case, we're altering a single
127
- // column, so we shouldn't have the problem where multiple columns are altered at the same time
128
- const tickMatches = error.sql.match(betweenTicks);
129
- if (!tickMatches)
130
- return error;
131
- const collection = tickMatches[0].slice(1, -1);
132
- const field = tickMatches[1].slice(1, -1);
133
- return new ContainsNullValuesError({ collection, field });
134
138
  }
@@ -1,2 +1,3 @@
1
1
  import type { PostgresError } from './types.js';
2
- export declare function extractError(error: PostgresError): PostgresError | Error;
2
+ import type { Item } from '@directus/types';
3
+ export declare function extractError(error: PostgresError, data: Partial<Item>): PostgresError | Error;
@@ -7,85 +7,89 @@ var PostgresErrorCodes;
7
7
  PostgresErrorCodes["UNIQUE_VIOLATION"] = "23505";
8
8
  PostgresErrorCodes["VALUE_LIMIT_VIOLATION"] = "22001";
9
9
  })(PostgresErrorCodes || (PostgresErrorCodes = {}));
10
- export function extractError(error) {
10
+ export function extractError(error, data) {
11
11
  switch (error.code) {
12
12
  case PostgresErrorCodes.UNIQUE_VIOLATION:
13
- return uniqueViolation(error);
13
+ return uniqueViolation();
14
14
  case PostgresErrorCodes.NUMERIC_VALUE_OUT_OF_RANGE:
15
- return numericValueOutOfRange(error);
15
+ return numericValueOutOfRange();
16
16
  case PostgresErrorCodes.VALUE_LIMIT_VIOLATION:
17
- return valueLimitViolation(error);
17
+ return valueLimitViolation();
18
18
  case PostgresErrorCodes.NOT_NULL_VIOLATION:
19
- return notNullViolation(error);
19
+ return notNullViolation();
20
20
  case PostgresErrorCodes.FOREIGN_KEY_VIOLATION:
21
- return foreignKeyViolation(error);
21
+ return foreignKeyViolation();
22
22
  default:
23
23
  return error;
24
24
  }
25
- }
26
- function uniqueViolation(error) {
27
- const { table, detail } = error;
28
- const betweenParens = /\(([^)]+)\)/g;
29
- const matches = detail.match(betweenParens);
30
- if (!matches)
31
- return error;
32
- const collection = table;
33
- const field = matches[0].slice(1, -1);
34
- return new RecordNotUniqueError({
35
- collection,
36
- field,
37
- });
38
- }
39
- function numericValueOutOfRange(error) {
40
- const regex = /"(.*?)"/g;
41
- const matches = error.message.match(regex);
42
- if (!matches)
43
- return error;
44
- const collection = matches[0].slice(1, -1);
45
- const field = null;
46
- return new ValueOutOfRangeError({
47
- collection,
48
- field,
49
- });
50
- }
51
- function valueLimitViolation(error) {
52
- /**
53
- * NOTE:
54
- * Postgres doesn't return the offending column
55
- */
56
- const regex = /"(.*?)"/g;
57
- const matches = error.message.match(regex);
58
- if (!matches)
59
- return error;
60
- const collection = matches[0].slice(1, -1);
61
- const field = null;
62
- return new ValueTooLongError({
63
- collection,
64
- field,
65
- });
66
- }
67
- function notNullViolation(error) {
68
- const { table, column } = error;
69
- if (!column)
70
- return error;
71
- if (error.message.endsWith('contains null values')) {
72
- return new ContainsNullValuesError({ collection: table, field: column });
25
+ function uniqueViolation() {
26
+ const { table, detail } = error;
27
+ const betweenParens = /\(([^)]+)\)/g;
28
+ const matches = detail.match(betweenParens);
29
+ if (!matches)
30
+ return error;
31
+ const collection = table;
32
+ const field = matches[0].slice(1, -1);
33
+ return new RecordNotUniqueError({
34
+ collection,
35
+ field,
36
+ value: field ? data[field] : null,
37
+ });
38
+ }
39
+ function numericValueOutOfRange() {
40
+ const regex = /"(.*?)"/g;
41
+ const matches = error.message.match(regex);
42
+ if (!matches)
43
+ return error;
44
+ const collection = matches[0].slice(1, -1);
45
+ const field = matches[1]?.slice(1, -1) ?? null;
46
+ return new ValueOutOfRangeError({
47
+ collection,
48
+ field,
49
+ value: field ? data[field] : null,
50
+ });
51
+ }
52
+ function valueLimitViolation() {
53
+ /**
54
+ * NOTE:
55
+ * Postgres doesn't return the offending column
56
+ */
57
+ const regex = /"(.*?)"/g;
58
+ const matches = error.message.match(regex);
59
+ if (!matches)
60
+ return error;
61
+ const collection = matches[0].slice(1, -1);
62
+ const field = matches[1]?.slice(1, -1) ?? null;
63
+ return new ValueTooLongError({
64
+ collection,
65
+ field,
66
+ value: field ? data[field] : null,
67
+ });
68
+ }
69
+ function notNullViolation() {
70
+ const { table, column } = error;
71
+ if (!column)
72
+ return error;
73
+ if (error.message.endsWith('contains null values')) {
74
+ return new ContainsNullValuesError({ collection: table, field: column });
75
+ }
76
+ return new NotNullViolationError({
77
+ collection: table,
78
+ field: column,
79
+ });
80
+ }
81
+ function foreignKeyViolation() {
82
+ const { table, detail } = error;
83
+ const betweenParens = /\(([^)]+)\)/g;
84
+ const matches = detail.match(betweenParens);
85
+ if (!matches)
86
+ return error;
87
+ const collection = table;
88
+ const field = matches[0].slice(1, -1);
89
+ return new InvalidForeignKeyError({
90
+ collection,
91
+ field,
92
+ value: field ? data[field] : null,
93
+ });
73
94
  }
74
- return new NotNullViolationError({
75
- collection: table,
76
- field: column,
77
- });
78
- }
79
- function foreignKeyViolation(error) {
80
- const { table, detail } = error;
81
- const betweenParens = /\(([^)]+)\)/g;
82
- const matches = detail.match(betweenParens);
83
- if (!matches)
84
- return error;
85
- const collection = table;
86
- const field = matches[0].slice(1, -1);
87
- return new InvalidForeignKeyError({
88
- collection,
89
- field,
90
- });
91
95
  }
@@ -1,2 +1,3 @@
1
1
  import type { SQLiteError } from './types.js';
2
- export declare function extractError(error: SQLiteError): SQLiteError | Error;
2
+ import type { Item } from '@directus/types';
3
+ export declare function extractError(error: SQLiteError, data: Partial<Item>): SQLiteError | Error;
@@ -2,18 +2,19 @@ import { ContainsNullValuesError, InvalidForeignKeyError, NotNullViolationError,
2
2
  // NOTE:
3
3
  // - Sqlite doesn't have varchar with length support, so no ValueTooLongError
4
4
  // - Sqlite doesn't have a max range for numbers, so no ValueOutOfRangeError
5
- export function extractError(error) {
5
+ export function extractError(error, data) {
6
6
  if (error.message.includes('SQLITE_CONSTRAINT: NOT NULL')) {
7
7
  return notNullConstraint(error);
8
8
  }
9
9
  if (error.message.includes('SQLITE_CONSTRAINT: UNIQUE')) {
10
10
  const errorParts = error.message.split(' ');
11
- const [table, column] = errorParts[errorParts.length - 1].split('.');
12
- if (!table || !column)
11
+ const [table, field] = errorParts[errorParts.length - 1].split('.');
12
+ if (!table || !field)
13
13
  return error;
14
14
  return new RecordNotUniqueError({
15
15
  collection: table,
16
- field: column,
16
+ field,
17
+ value: field ? data[field] : null,
17
18
  });
18
19
  }
19
20
  if (error.message.includes('SQLITE_CONSTRAINT: FOREIGN KEY')) {
@@ -22,7 +23,7 @@ export function extractError(error) {
22
23
  * SQLite doesn't return any useful information in it's foreign key constraint failed error, so
23
24
  * we can't extract the table/column/value accurately
24
25
  */
25
- return new InvalidForeignKeyError({ collection: null, field: null });
26
+ return new InvalidForeignKeyError({ collection: null, field: null, value: null });
26
27
  }
27
28
  return error;
28
29
  }
@@ -1,4 +1,5 @@
1
1
  import type { SQLError } from './dialects/types.js';
2
+ import type { Item } from '@directus/types';
2
3
  /**
3
4
  * Translates an error thrown by any of the databases into a pre-defined Exception. Currently
4
5
  * supports:
@@ -8,4 +9,4 @@ import type { SQLError } from './dialects/types.js';
8
9
  * - Value Out of Range
9
10
  * - Value Too Long
10
11
  */
11
- export declare function translateDatabaseError(error: SQLError): Promise<any>;
12
+ export declare function translateDatabaseError(error: SQLError, data: Partial<Item>): Promise<any>;
@@ -14,25 +14,25 @@ import { extractError as sqlite } from './dialects/sqlite.js';
14
14
  * - Value Out of Range
15
15
  * - Value Too Long
16
16
  */
17
- export async function translateDatabaseError(error) {
17
+ export async function translateDatabaseError(error, data) {
18
18
  const client = getDatabaseClient();
19
19
  let defaultError;
20
20
  switch (client) {
21
21
  case 'mysql':
22
- defaultError = mysql(error);
22
+ defaultError = mysql(error, data);
23
23
  break;
24
24
  case 'cockroachdb':
25
25
  case 'postgres':
26
- defaultError = postgres(error);
26
+ defaultError = postgres(error, data);
27
27
  break;
28
28
  case 'sqlite':
29
- defaultError = sqlite(error);
29
+ defaultError = sqlite(error, data);
30
30
  break;
31
31
  case 'oracle':
32
32
  defaultError = oracle(error);
33
33
  break;
34
34
  case 'mssql':
35
- defaultError = await mssql(error);
35
+ defaultError = await mssql(error, data);
36
36
  break;
37
37
  }
38
38
  const hookError = await emitter.emitFilter('database.error', defaultError, { client }, {
@@ -1,13 +1,15 @@
1
- import type { Accountability, Query, SchemaOverview } from '@directus/types';
1
+ import type { Accountability, Query, Relation, SchemaOverview } from '@directus/types';
2
2
  import type { Knex } from 'knex';
3
3
  export interface ConvertWildcardsOptions {
4
4
  collection: string;
5
5
  fields: string[];
6
6
  alias: Query['alias'];
7
7
  accountability: Accountability | null;
8
+ backlink: boolean | undefined;
8
9
  }
9
10
  export interface ConvertWildCardsContext {
10
11
  schema: SchemaOverview;
11
12
  knex: Knex;
13
+ parentRelation?: Relation;
12
14
  }
13
15
  export declare function convertWildcards(options: ConvertWildcardsOptions, context: ConvertWildCardsContext): Promise<string[]>;
@@ -40,14 +40,24 @@ export async function convertWildcards(options, context) {
40
40
  // Swap *.* case for *,<relational-field>.*,<another-relational>.*
41
41
  if (fieldKey.includes('.') && fieldKey.split('.')[0] === '*') {
42
42
  const parts = fieldKey.split('.');
43
- const relationalFields = allowedFields.includes('*')
44
- ? context.schema.relations
45
- .filter((relation) => relation.collection === options.collection || relation.related_collection === options.collection)
46
- .map((relation) => {
47
- const isMany = relation.collection === options.collection;
48
- return isMany ? relation.field : relation.meta?.one_field;
49
- })
50
- : allowedFields.filter((fieldKey) => !!getRelation(context.schema.relations, options.collection, fieldKey));
43
+ let relationalFields = [];
44
+ if (allowedFields.includes('*')) {
45
+ relationalFields = context.schema.relations.reduce((acc, relation) => {
46
+ if (relation.collection === options.collection && !acc.includes(relation.field)) {
47
+ acc.push(relation.field);
48
+ }
49
+ if (relation.related_collection === options.collection && !acc.includes(relation.meta.one_field)) {
50
+ acc.push(relation.meta.one_field);
51
+ }
52
+ return acc;
53
+ }, []);
54
+ }
55
+ else {
56
+ relationalFields = allowedFields.filter((fieldKey) => getRelation(context.schema.relations, options.collection, fieldKey) !== undefined);
57
+ }
58
+ if (options.backlink === false) {
59
+ relationalFields = relationalFields.filter((relationField) => getRelation(context.schema.relations, options.collection, relationField) !== context.parentRelation);
60
+ }
51
61
  const nonRelationalFields = allowedFields.filter((fieldKey) => relationalFields.includes(fieldKey) === false);
52
62
  const aliasFields = Object.keys(options.alias ?? {}).map((fieldKey) => {
53
63
  const name = options.alias[fieldKey];
@@ -1,4 +1,4 @@
1
- import type { Accountability, Query, SchemaOverview } from '@directus/types';
1
+ import type { Accountability, Query, Relation, SchemaOverview } from '@directus/types';
2
2
  import type { Knex } from 'knex';
3
3
  import type { FieldNode, FunctionFieldNode, NestedCollectionNode, O2MNode } from '../../../types/index.js';
4
4
  export interface ParseFieldsOptions {
@@ -11,6 +11,7 @@ export interface ParseFieldsOptions {
11
11
  export interface ParseFieldsContext {
12
12
  schema: SchemaOverview;
13
13
  knex: Knex;
14
+ parentRelation?: Relation;
14
15
  }
15
16
  export declare function parseFields(options: ParseFieldsOptions, context: ParseFieldsContext): Promise<[] | (NestedCollectionNode | FieldNode | FunctionFieldNode)[]>;
16
17
  export declare function isO2MNode(node: NestedCollectionNode | null): node is O2MNode;
@@ -17,6 +17,7 @@ export async function parseFields(options, context) {
17
17
  collection: options.parentCollection,
18
18
  alias: options.query.alias,
19
19
  accountability: options.accountability,
20
+ backlink: options.query.backlink,
20
21
  }, context);
21
22
  if (!fields || !Array.isArray(fields))
22
23
  return [];
@@ -151,7 +152,7 @@ export async function parseFields(options, context) {
151
152
  query: options.query,
152
153
  deep: options.deep?.[`${fieldKey}:${relatedCollection}`],
153
154
  accountability: options.accountability,
154
- }, context);
155
+ }, { ...context, parentRelation: relation });
155
156
  child.query[relatedCollection] = getDeepQuery(options.deep?.[`${fieldKey}:${relatedCollection}`] || {});
156
157
  child.relatedKey[relatedCollection] = context.schema.collections[relatedCollection].primary;
157
158
  }
@@ -188,7 +189,7 @@ export async function parseFields(options, context) {
188
189
  query: childQuery,
189
190
  deep: options.deep?.[fieldKey] || {},
190
191
  accountability: options.accountability,
191
- }, context),
192
+ }, { ...context, parentRelation: relation }),
192
193
  cases: [],
193
194
  whenCase: [],
194
195
  };
@@ -0,0 +1,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,14 @@
1
+ import { useEnv } from '@directus/env';
2
+ import { toBoolean } from '@directus/utils';
3
+ export async function up(knex) {
4
+ const env = useEnv();
5
+ const acceptedTerms = toBoolean(env['ACCEPT_TERMS']);
6
+ await knex.schema.alterTable('directus_settings', (table) => {
7
+ table.boolean('accepted_terms').defaultTo(acceptedTerms);
8
+ });
9
+ }
10
+ export async function down(knex) {
11
+ await knex.schema.alterTable('directus_settings', (table) => {
12
+ table.dropColumn('accepted_terms');
13
+ });
14
+ }
@@ -0,0 +1,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,26 @@
1
+ import { v7 as uuid } from 'uuid';
2
+ export async function up(knex) {
3
+ await knex.schema.alterTable('directus_settings', (table) => {
4
+ table.uuid('project_id');
5
+ });
6
+ const existing = await knex('directus_settings').select('id').first();
7
+ const timestamp = await knex('directus_migrations').select('timestamp').first();
8
+ const msecs = timestamp ? new Date(timestamp.timestamp).getTime() : Date.now();
9
+ if (existing) {
10
+ await knex('directus_settings').update({
11
+ project_id: uuid({
12
+ msecs,
13
+ }),
14
+ });
15
+ }
16
+ else {
17
+ await knex('directus_settings').insert({
18
+ project_id: uuid(),
19
+ });
20
+ }
21
+ }
22
+ export async function down(knex) {
23
+ await knex.schema.alterTable('directus_settings', (table) => {
24
+ table.dropColumn('project_id');
25
+ });
26
+ }