@directus/api 27.1.0 → 28.0.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.
- package/dist/auth/drivers/oauth2.js +4 -4
- package/dist/auth/drivers/openid.d.ts +2 -1
- package/dist/auth/drivers/openid.js +75 -42
- package/dist/database/errors/dialects/mssql.d.ts +2 -1
- package/dist/database/errors/dialects/mssql.js +124 -120
- package/dist/database/errors/dialects/mysql.d.ts +2 -1
- package/dist/database/errors/dialects/mysql.js +112 -108
- package/dist/database/errors/dialects/postgres.d.ts +2 -1
- package/dist/database/errors/dialects/postgres.js +75 -71
- package/dist/database/errors/dialects/sqlite.d.ts +2 -1
- package/dist/database/errors/dialects/sqlite.js +6 -5
- package/dist/database/errors/translate.d.ts +2 -1
- package/dist/database/errors/translate.js +5 -5
- package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +3 -1
- package/dist/database/get-ast-from-query/lib/convert-wildcards.js +18 -8
- package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +2 -1
- package/dist/database/get-ast-from-query/lib/parse-fields.js +3 -2
- package/dist/database/migrations/20250609A-license-banner.d.ts +3 -0
- package/dist/database/migrations/20250609A-license-banner.js +14 -0
- package/dist/database/migrations/20250613A-add-project-id.d.ts +3 -0
- package/dist/database/migrations/20250613A-add-project-id.js +26 -0
- package/dist/extensions/lib/get-extensions-settings.js +14 -8
- package/dist/flows.d.ts +5 -1
- package/dist/flows.js +61 -4
- package/dist/permissions/utils/get-permissions-for-share.js +2 -0
- package/dist/permissions/utils/merge-fields.d.ts +1 -0
- package/dist/permissions/utils/merge-fields.js +29 -0
- package/dist/permissions/utils/merge-permissions.js +3 -14
- package/dist/services/fields.js +3 -3
- package/dist/services/graphql/resolvers/mutation.js +1 -1
- package/dist/services/graphql/resolvers/query.js +1 -1
- package/dist/services/graphql/resolvers/system.js +4 -4
- package/dist/services/graphql/schema/parse-query.d.ts +1 -1
- package/dist/services/graphql/schema/parse-query.js +8 -1
- package/dist/services/graphql/schema/read.js +4 -4
- package/dist/services/graphql/subscription.js +1 -1
- package/dist/services/graphql/utils/filter-replace-m2a.d.ts +3 -0
- package/dist/services/graphql/utils/filter-replace-m2a.js +59 -0
- package/dist/services/items.js +2 -2
- package/dist/services/specifications.js +6 -2
- package/dist/services/users.js +3 -0
- package/dist/telemetry/lib/get-report.js +4 -1
- package/dist/telemetry/types/report.d.ts +4 -0
- package/dist/telemetry/utils/get-project-id.d.ts +2 -0
- package/dist/telemetry/utils/get-project-id.js +4 -0
- package/dist/utils/is-url-allowed.js +1 -1
- package/dist/utils/sanitize-query.js +6 -0
- package/dist/utils/validate-query.js +1 -0
- package/dist/websocket/utils/items.d.ts +1 -1
- package/package.json +22 -21
- package/dist/utils/map-values-deep.d.ts +0 -1
- 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(
|
|
15
|
+
return uniqueViolation();
|
|
16
16
|
case MySQLErrorCodes.NUMERIC_VALUE_OUT_OF_RANGE:
|
|
17
|
-
return numericValueOutOfRange(
|
|
17
|
+
return numericValueOutOfRange();
|
|
18
18
|
case MySQLErrorCodes.ER_DATA_TOO_LONG:
|
|
19
|
-
return valueLimitViolation(
|
|
19
|
+
return valueLimitViolation();
|
|
20
20
|
case MySQLErrorCodes.NOT_NULL_VIOLATION:
|
|
21
|
-
return notNullViolation(
|
|
21
|
+
return notNullViolation();
|
|
22
22
|
case MySQLErrorCodes.FOREIGN_KEY_VIOLATION:
|
|
23
|
-
return foreignKeyViolation(
|
|
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(
|
|
27
|
+
return containsNullValues();
|
|
28
28
|
}
|
|
29
29
|
return error;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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(
|
|
13
|
+
return uniqueViolation();
|
|
14
14
|
case PostgresErrorCodes.NUMERIC_VALUE_OUT_OF_RANGE:
|
|
15
|
-
return numericValueOutOfRange(
|
|
15
|
+
return numericValueOutOfRange();
|
|
16
16
|
case PostgresErrorCodes.VALUE_LIMIT_VIOLATION:
|
|
17
|
-
return valueLimitViolation(
|
|
17
|
+
return valueLimitViolation();
|
|
18
18
|
case PostgresErrorCodes.NOT_NULL_VIOLATION:
|
|
19
|
-
return notNullViolation(
|
|
19
|
+
return notNullViolation();
|
|
20
20
|
case PostgresErrorCodes.FOREIGN_KEY_VIOLATION:
|
|
21
|
-
return foreignKeyViolation(
|
|
21
|
+
return foreignKeyViolation();
|
|
22
22
|
default:
|
|
23
23
|
return error;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
function numericValueOutOfRange(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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,
|
|
12
|
-
if (!table || !
|
|
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
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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,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,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
|
+
}
|