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.
- package/dist/__mocks__/cache.d.ts +5 -0
- package/dist/__mocks__/cache.js +7 -0
- package/dist/auth/drivers/ldap.js +10 -11
- package/dist/auth/drivers/oauth2.js +9 -4
- package/dist/auth/drivers/openid.js +7 -4
- package/dist/cache.js +2 -2
- package/dist/cli/commands/schema/apply.js +9 -3
- package/dist/controllers/assets.js +5 -0
- package/dist/controllers/files.d.ts +2 -0
- package/dist/controllers/files.js +13 -5
- package/dist/database/helpers/date/dialects/default.d.ts +3 -0
- package/dist/database/helpers/date/dialects/default.js +7 -0
- package/dist/database/helpers/date/dialects/mssql.d.ts +1 -9
- package/dist/database/helpers/date/dialects/mssql.js +4 -23
- package/dist/database/helpers/date/dialects/mysql.d.ts +2 -9
- package/dist/database/helpers/date/dialects/mysql.js +7 -22
- package/dist/database/helpers/date/dialects/oracle.d.ts +1 -9
- package/dist/database/helpers/date/dialects/oracle.js +7 -23
- package/dist/database/helpers/date/dialects/sqlite.d.ts +1 -9
- package/dist/database/helpers/date/dialects/sqlite.js +8 -24
- package/dist/database/helpers/date/index.d.ts +4 -4
- package/dist/database/helpers/date/index.js +9 -9
- package/dist/database/helpers/date/types.d.ts +3 -9
- package/dist/database/helpers/date/types.js +10 -0
- package/dist/database/helpers/fn/dialects/mssql.d.ts +13 -0
- package/dist/database/helpers/fn/dialects/mssql.js +42 -0
- package/dist/database/helpers/{date/dialects/postgres.d.ts → fn/dialects/mysql.d.ts} +3 -2
- package/dist/database/helpers/fn/dialects/mysql.js +42 -0
- package/dist/database/helpers/fn/dialects/oracle.d.ts +13 -0
- package/dist/database/helpers/fn/dialects/oracle.js +42 -0
- package/dist/database/helpers/fn/dialects/postgres.d.ts +13 -0
- package/dist/database/helpers/{date → fn}/dialects/postgres.js +18 -3
- package/dist/database/helpers/fn/dialects/sqlite.d.ts +13 -0
- package/dist/database/helpers/fn/dialects/sqlite.js +42 -0
- package/dist/database/helpers/fn/index.d.ts +7 -0
- package/dist/database/helpers/fn/index.js +17 -0
- package/dist/database/helpers/fn/types.d.ts +18 -0
- package/dist/database/helpers/fn/types.js +27 -0
- package/dist/database/helpers/index.d.ts +4 -1
- package/dist/database/helpers/index.js +7 -1
- package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.d.ts +3 -0
- package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.js +17 -0
- package/dist/database/migrations/20220322A-rename-field-typecast-flags.js +6 -2
- package/dist/database/migrations/20220323A-add-field-validation.d.ts +3 -0
- package/dist/database/migrations/20220323A-add-field-validation.js +17 -0
- package/dist/database/migrations/20220325A-fix-typecast-flags.d.ts +3 -0
- package/dist/database/migrations/20220325A-fix-typecast-flags.js +49 -0
- package/dist/database/migrations/20220325B-add-default-language.d.ts +3 -0
- package/dist/database/migrations/20220325B-add-default-language.js +28 -0
- package/dist/database/migrations/20220402A-remove-default-value-panel-icon.d.ts +3 -0
- package/dist/database/migrations/20220402A-remove-default-value-panel-icon.js +22 -0
- package/dist/database/run-ast.js +7 -5
- package/dist/database/system-data/fields/activity.yaml +4 -4
- package/dist/database/system-data/fields/collections.yaml +1 -1
- package/dist/database/system-data/fields/fields.yaml +9 -0
- package/dist/database/system-data/fields/presets.yaml +14 -0
- package/dist/database/system-data/fields/settings.yaml +12 -1
- package/dist/database/system-data/fields/users.yaml +3 -0
- package/dist/env.js +5 -3
- package/dist/exceptions/index.d.ts +1 -0
- package/dist/exceptions/index.js +1 -0
- package/dist/exceptions/token-expired.d.ts +4 -0
- package/dist/exceptions/token-expired.js +10 -0
- package/dist/logger.js +2 -1
- package/dist/middleware/cache.js +10 -0
- package/dist/services/activity.js +4 -1
- package/dist/services/authorization.d.ts +1 -1
- package/dist/services/authorization.js +174 -48
- package/dist/services/collections.d.ts +2 -0
- package/dist/services/collections.js +232 -198
- package/dist/services/fields.js +210 -174
- package/dist/services/files.d.ts +5 -1
- package/dist/services/files.js +59 -40
- package/dist/services/graphql.d.ts +2 -3
- package/dist/services/graphql.js +53 -10
- package/dist/services/items.js +5 -3
- package/dist/services/payload.d.ts +2 -1
- package/dist/services/payload.js +28 -21
- package/dist/services/relations.js +93 -81
- package/dist/services/server.js +1 -0
- package/dist/services/shares.js +2 -1
- package/dist/services/specifications.js +1 -3
- package/dist/services/users.js +7 -2
- package/dist/types/files.d.ts +8 -0
- package/dist/utils/apply-query.js +38 -10
- package/dist/utils/apply-snapshot.d.ts +3 -1
- package/dist/utils/apply-snapshot.js +34 -5
- package/dist/utils/get-ast-from-query.js +15 -3
- package/dist/utils/get-column.d.ts +6 -5
- package/dist/utils/get-column.js +16 -8
- package/dist/utils/get-graphql-type.js +1 -0
- package/dist/utils/get-local-type.js +5 -0
- package/dist/utils/get-schema.d.ts +1 -1
- package/dist/utils/get-schema.js +18 -10
- package/dist/utils/jwt.js +1 -1
- package/dist/utils/reduce-schema.js +20 -12
- package/dist/utils/track.js +3 -2
- package/dist/utils/url.d.ts +1 -1
- package/dist/utils/url.js +1 -1
- package/dist/utils/validate-query.js +19 -15
- package/dist/utils/validate-storage.js +3 -1
- package/example.env +4 -0
- 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
|
-
|
|
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
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
name
|
|
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)
|
|
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
|
|
8
|
-
* @param
|
|
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
|
|
13
|
+
export declare function getColumn(knex: Knex, table: string, column: string, alias: string | false | undefined, schema: SchemaOverview): Knex.Raw;
|
package/dist/utils/get-column.js
CHANGED
|
@@ -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)
|
|
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
|
|
13
|
-
* @param
|
|
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
|
-
|
|
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
|
|
38
|
+
throw new exceptions_1.InvalidQueryException(`Invalid function specified "${functionName}"`);
|
|
31
39
|
}
|
|
32
40
|
}
|
|
33
41
|
if (alias && column !== alias) {
|
|
@@ -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
|
}
|
package/dist/utils/get-schema.js
CHANGED
|
@@ -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: (
|
|
119
|
-
nullable: (
|
|
120
|
-
generated: (
|
|
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: (
|
|
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.
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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;
|
package/dist/utils/track.js
CHANGED
|
@@ -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
|
|
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
|
|
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) {
|
package/dist/utils/url.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|