@directus/api 26.0.1 → 27.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.
- package/dist/cli/utils/create-db-connection.js +3 -5
- package/dist/controllers/files.js +1 -1
- package/dist/database/get-ast-from-query/lib/convert-wildcards.js +2 -2
- package/dist/database/get-ast-from-query/lib/parse-fields.js +2 -2
- package/dist/database/get-ast-from-query/utils/get-related-collection.js +2 -2
- package/dist/database/helpers/fn/types.js +2 -1
- package/dist/database/helpers/schema/dialects/mssql.d.ts +2 -0
- package/dist/database/helpers/schema/dialects/mssql.js +6 -0
- package/dist/database/helpers/schema/dialects/mysql.d.ts +0 -1
- package/dist/database/helpers/schema/dialects/mysql.js +0 -11
- package/dist/database/helpers/schema/dialects/oracle.d.ts +2 -0
- package/dist/database/helpers/schema/dialects/oracle.js +6 -0
- package/dist/database/helpers/schema/types.d.ts +2 -1
- package/dist/database/helpers/schema/types.js +6 -4
- package/dist/database/index.d.ts +0 -6
- package/dist/database/index.js +12 -28
- package/dist/database/migrations/20210225A-add-relations-sort-field.js +3 -1
- package/dist/database/migrations/20210510A-restructure-relations.js +8 -8
- package/dist/database/migrations/20210924A-add-collection-organization.js +6 -1
- package/dist/database/migrations/20210927A-replace-fields-group.js +3 -1
- package/dist/database/migrations/20211118A-add-notifications.js +3 -1
- package/dist/database/migrations/20211211A-add-shares.js +7 -1
- package/dist/database/migrations/20230823A-add-content-versioning.js +3 -1
- package/dist/database/migrations/20240909A-separate-comments.js +3 -1
- package/dist/database/run-ast/lib/apply-query/add-join.d.ts +54 -0
- package/dist/database/run-ast/lib/apply-query/add-join.js +86 -0
- package/dist/database/run-ast/lib/apply-query/aggregate.d.ts +3 -0
- package/dist/database/run-ast/lib/apply-query/aggregate.js +24 -0
- package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +8 -0
- package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.js +20 -0
- package/dist/database/run-ast/lib/apply-query/filter/index.d.ts +8 -0
- package/dist/database/run-ast/lib/apply-query/filter/index.js +151 -0
- package/dist/database/run-ast/lib/apply-query/filter/operator.d.ts +3 -0
- package/dist/database/run-ast/lib/apply-query/filter/operator.js +175 -0
- package/dist/database/run-ast/lib/apply-query/filter/validate-operator.d.ts +2 -0
- package/dist/database/run-ast/lib/apply-query/filter/validate-operator.js +18 -0
- package/dist/database/run-ast/lib/apply-query/get-filter-path.d.ts +1 -0
- package/dist/database/run-ast/lib/apply-query/get-filter-path.js +13 -0
- package/dist/database/run-ast/lib/apply-query/get-operation.d.ts +7 -0
- package/dist/database/run-ast/lib/apply-query/get-operation.js +19 -0
- package/dist/database/run-ast/lib/apply-query/index.d.ts +20 -0
- package/dist/database/run-ast/lib/apply-query/index.js +92 -0
- package/dist/database/run-ast/lib/apply-query/join-filter-with-cases.d.ts +2 -0
- package/dist/database/run-ast/lib/apply-query/join-filter-with-cases.js +12 -0
- package/dist/database/run-ast/lib/apply-query/mock.d.ts +3 -0
- package/dist/database/run-ast/lib/apply-query/mock.js +4 -0
- package/dist/database/run-ast/lib/apply-query/pagination.d.ts +3 -0
- package/dist/database/run-ast/lib/apply-query/pagination.js +11 -0
- package/dist/database/run-ast/lib/apply-query/search.d.ts +4 -0
- package/dist/database/run-ast/lib/apply-query/search.js +78 -0
- package/dist/database/run-ast/lib/apply-query/sort.d.ts +19 -0
- package/dist/database/run-ast/lib/apply-query/sort.js +87 -0
- package/dist/database/run-ast/lib/get-db-query.js +7 -5
- package/dist/database/run-ast/run-ast.js +1 -1
- package/dist/database/run-ast/utils/apply-case-when.js +1 -1
- package/dist/database/run-ast/utils/get-column-pre-processor.js +2 -2
- package/dist/{utils → database/run-ast/utils}/get-column.js +1 -1
- package/dist/database/run-ast/utils/get-field-alias.js +1 -1
- package/dist/database/run-ast/utils/remove-temporary-fields.js +1 -1
- package/dist/database/seeds/01-collections.yaml +2 -2
- package/dist/database/seeds/04-fields.yaml +2 -2
- package/dist/database/seeds/05-activity.yaml +1 -1
- package/dist/database/seeds/08-permissions.yaml +1 -1
- package/dist/database/seeds/09-presets.yaml +1 -1
- package/dist/database/seeds/10-relations.yaml +8 -8
- package/dist/database/seeds/11-revisions.yaml +1 -1
- package/dist/database/seeds/run.js +8 -1
- package/dist/flows.js +8 -1
- package/dist/metrics/lib/create-metrics.js +13 -7
- package/dist/operations/condition/index.js +7 -4
- package/dist/operations/exec/index.js +2 -1
- package/dist/permissions/utils/default-permission.d.ts +7 -0
- package/dist/permissions/utils/default-permission.js +7 -0
- package/dist/services/collections.js +22 -18
- package/dist/services/fields.js +2 -5
- package/dist/services/payload.d.ts +6 -6
- package/dist/services/payload.js +47 -13
- package/dist/services/server.js +7 -2
- package/dist/services/specifications.js +2 -2
- package/dist/utils/get-relation-info.d.ts +1 -1
- package/dist/utils/get-relation-info.js +2 -4
- package/dist/websocket/handlers/items.js +5 -0
- package/package.json +23 -21
- package/dist/database/get-ast-from-query/utils/get-relation.d.ts +0 -2
- package/dist/database/get-ast-from-query/utils/get-relation.js +0 -7
- package/dist/utils/apply-query.d.ts +0 -46
- package/dist/utils/apply-query.js +0 -771
- /package/dist/{utils → database/run-ast/utils}/apply-function-to-column-name.d.ts +0 -0
- /package/dist/{utils → database/run-ast/utils}/apply-function-to-column-name.js +0 -0
- /package/dist/{utils → database/run-ast/utils}/get-column.d.ts +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { NUMERIC_TYPES } from '@directus/constants';
|
|
2
|
+
import { isIn } from '@directus/utils';
|
|
3
|
+
import { getCases } from '../../../../permissions/modules/process-ast/lib/get-cases.js';
|
|
4
|
+
import { isValidUuid } from '../../../../utils/is-valid-uuid.js';
|
|
5
|
+
import { parseNumericString } from '../../../../utils/parse-numeric-string.js';
|
|
6
|
+
import { getHelpers } from '../../../helpers/index.js';
|
|
7
|
+
import { applyFilter } from './filter/index.js';
|
|
8
|
+
export function applySearch(knex, schema, dbQuery, searchQuery, collection, aliasMap, permissions) {
|
|
9
|
+
const { number: numberHelper } = getHelpers(knex);
|
|
10
|
+
const allowedFields = new Set(permissions.filter((p) => p.collection === collection).flatMap((p) => p.fields ?? []));
|
|
11
|
+
let fields = Object.entries(schema.collections[collection].fields);
|
|
12
|
+
const { cases, caseMap } = getCases(collection, permissions, []);
|
|
13
|
+
// Add field restrictions if non-admin and "everything" is not allowed
|
|
14
|
+
if (cases.length !== 0 && !allowedFields.has('*')) {
|
|
15
|
+
fields = fields.filter((field) => allowedFields.has(field[0]));
|
|
16
|
+
}
|
|
17
|
+
dbQuery.andWhere(function (queryBuilder) {
|
|
18
|
+
let needsFallbackCondition = true;
|
|
19
|
+
fields.forEach(([name, field]) => {
|
|
20
|
+
// only account for when cases when full access is not given
|
|
21
|
+
const whenCases = allowedFields.has('*') ? [] : (caseMap[name] ?? []).map((caseIndex) => cases[caseIndex]);
|
|
22
|
+
const fieldType = getFieldType(field);
|
|
23
|
+
if (fieldType !== null) {
|
|
24
|
+
needsFallbackCondition = false;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (cases.length !== 0 && whenCases?.length !== 0) {
|
|
30
|
+
queryBuilder.orWhere((subQuery) => {
|
|
31
|
+
addSearchCondition(subQuery, name, fieldType, 'and');
|
|
32
|
+
applyFilter(knex, schema, subQuery, { _or: whenCases }, collection, aliasMap, cases, permissions);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
addSearchCondition(queryBuilder, name, fieldType, 'or');
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
if (needsFallbackCondition) {
|
|
40
|
+
queryBuilder.orWhereRaw('1 = 0');
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
function addSearchCondition(queryBuilder, name, fieldType, logical) {
|
|
44
|
+
if (fieldType === null) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (fieldType === 'string') {
|
|
48
|
+
queryBuilder[logical].whereRaw(`LOWER(??) LIKE ?`, [`${collection}.${name}`, `%${searchQuery.toLowerCase()}%`]);
|
|
49
|
+
}
|
|
50
|
+
else if (fieldType === 'numeric') {
|
|
51
|
+
numberHelper.addSearchCondition(queryBuilder, collection, name, parseNumericString(searchQuery), logical);
|
|
52
|
+
}
|
|
53
|
+
else if (fieldType === 'uuid') {
|
|
54
|
+
queryBuilder[logical].where({ [`${collection}.${name}`]: searchQuery });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function getFieldType(field) {
|
|
58
|
+
if (['text', 'string'].includes(field.type)) {
|
|
59
|
+
return 'string';
|
|
60
|
+
}
|
|
61
|
+
if (isNumericField(field)) {
|
|
62
|
+
const number = parseNumericString(searchQuery);
|
|
63
|
+
if (number === null) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (numberHelper.isNumberValid(number, field)) {
|
|
67
|
+
return 'numeric';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (field.type === 'uuid' && isValidUuid(searchQuery)) {
|
|
71
|
+
return 'uuid';
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function isNumericField(field) {
|
|
77
|
+
return isIn(field.type, NUMERIC_TYPES);
|
|
78
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Aggregate, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
import type { AliasMap } from '../../../../utils/get-column-path.js';
|
|
4
|
+
export type ColumnSortRecord = {
|
|
5
|
+
order: 'asc' | 'desc';
|
|
6
|
+
column: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function applySort(knex: Knex, schema: SchemaOverview, rootQuery: Knex.QueryBuilder, sort: string[], aggregate: Aggregate | null | undefined, collection: string, aliasMap: AliasMap, returnRecords?: boolean): {
|
|
9
|
+
sortRecords: {
|
|
10
|
+
order: "asc" | "desc";
|
|
11
|
+
column: any;
|
|
12
|
+
}[];
|
|
13
|
+
hasJoins: boolean;
|
|
14
|
+
hasMultiRelationalSort: boolean;
|
|
15
|
+
} | {
|
|
16
|
+
hasJoins: boolean;
|
|
17
|
+
hasMultiRelationalSort: boolean;
|
|
18
|
+
sortRecords?: never;
|
|
19
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { getColumnPath } from '../../../../utils/get-column-path.js';
|
|
2
|
+
import { getColumn } from '../../utils/get-column.js';
|
|
3
|
+
import { getRelationInfo } from '../../../../utils/get-relation-info.js';
|
|
4
|
+
import { addJoin } from './add-join.js';
|
|
5
|
+
export function applySort(knex, schema, rootQuery, sort, aggregate, collection, aliasMap, returnRecords = false) {
|
|
6
|
+
const relations = schema.relations;
|
|
7
|
+
let hasJoins = false;
|
|
8
|
+
let hasMultiRelationalSort = false;
|
|
9
|
+
const sortRecords = sort.map((sortField) => {
|
|
10
|
+
const column = sortField.split('.');
|
|
11
|
+
let order = 'asc';
|
|
12
|
+
if (sortField.startsWith('-')) {
|
|
13
|
+
order = 'desc';
|
|
14
|
+
}
|
|
15
|
+
if (column[0].startsWith('-')) {
|
|
16
|
+
column[0] = column[0].substring(1);
|
|
17
|
+
}
|
|
18
|
+
// Is the column name one of the aggregate functions used in the query if there is any?
|
|
19
|
+
if (Object.keys(aggregate ?? {}).includes(column[0])) {
|
|
20
|
+
// If so, return the column name without the order prefix
|
|
21
|
+
const operation = column[0];
|
|
22
|
+
// Get the field for the aggregate function
|
|
23
|
+
const field = column[1];
|
|
24
|
+
// If the operation is countAll there is no field.
|
|
25
|
+
if (operation === 'countAll') {
|
|
26
|
+
return {
|
|
27
|
+
order,
|
|
28
|
+
column: 'countAll',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// If the operation is a root count there is no field.
|
|
32
|
+
if (operation === 'count' && (field === '*' || !field)) {
|
|
33
|
+
return {
|
|
34
|
+
order,
|
|
35
|
+
column: 'count',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Return the column name with the operation and field name
|
|
39
|
+
return {
|
|
40
|
+
order,
|
|
41
|
+
column: returnRecords ? column[0] : `${operation}->${field}`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (column.length === 1) {
|
|
45
|
+
const pathRoot = column[0].split(':')[0];
|
|
46
|
+
const { relation, relationType } = getRelationInfo(relations, collection, pathRoot);
|
|
47
|
+
if (!relation || ['m2o', 'a2o'].includes(relationType ?? '')) {
|
|
48
|
+
return {
|
|
49
|
+
order,
|
|
50
|
+
column: returnRecords ? column[0] : getColumn(knex, collection, column[0], false, schema),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const { hasMultiRelational, isJoinAdded } = addJoin({
|
|
55
|
+
path: column,
|
|
56
|
+
collection,
|
|
57
|
+
aliasMap,
|
|
58
|
+
rootQuery,
|
|
59
|
+
schema,
|
|
60
|
+
knex,
|
|
61
|
+
});
|
|
62
|
+
const { columnPath } = getColumnPath({
|
|
63
|
+
path: column,
|
|
64
|
+
collection,
|
|
65
|
+
aliasMap,
|
|
66
|
+
relations,
|
|
67
|
+
schema,
|
|
68
|
+
});
|
|
69
|
+
const [alias, field] = columnPath.split('.');
|
|
70
|
+
if (!hasJoins) {
|
|
71
|
+
hasJoins = isJoinAdded;
|
|
72
|
+
}
|
|
73
|
+
if (!hasMultiRelationalSort) {
|
|
74
|
+
hasMultiRelationalSort = hasMultiRelational;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
order,
|
|
78
|
+
column: returnRecords ? columnPath : getColumn(knex, alias, field, false, schema),
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
if (returnRecords)
|
|
82
|
+
return { sortRecords, hasJoins, hasMultiRelationalSort };
|
|
83
|
+
// Clears the order if any, eg: from MSSQL offset
|
|
84
|
+
rootQuery.clear('order');
|
|
85
|
+
rootQuery.orderBy(sortRecords);
|
|
86
|
+
return { hasJoins, hasMultiRelationalSort };
|
|
87
|
+
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
2
|
import { cloneDeep } from 'lodash-es';
|
|
3
|
-
import
|
|
3
|
+
import { applySort } from './apply-query/sort.js';
|
|
4
4
|
import { getCollectionFromAlias } from '../../../utils/get-collection-from-alias.js';
|
|
5
|
-
import { getColumn } from '
|
|
5
|
+
import { getColumn } from '../utils/get-column.js';
|
|
6
6
|
import { getHelpers } from '../../helpers/index.js';
|
|
7
7
|
import { applyCaseWhen } from '../utils/apply-case-when.js';
|
|
8
8
|
import { getColumnPreprocessor } from '../utils/get-column-pre-processor.js';
|
|
9
9
|
import { getNodeAlias } from '../utils/get-field-alias.js';
|
|
10
10
|
import { getInnerQueryColumnPreProcessor } from '../utils/get-inner-query-column-pre-processor.js';
|
|
11
11
|
import { withPreprocessBindings } from '../utils/with-preprocess-bindings.js';
|
|
12
|
+
import applyQuery, { generateAlias } from './apply-query/index.js';
|
|
13
|
+
import { applyLimit } from './apply-query/pagination.js';
|
|
12
14
|
export function getDBQuery({ table, fieldNodes, o2mNodes, query, cases, permissions, permissionsOnly }, { knex, schema }) {
|
|
13
15
|
const aliasMap = Object.create(null);
|
|
14
16
|
const env = useEnv();
|
|
@@ -46,12 +48,12 @@ export function getDBQuery({ table, fieldNodes, o2mNodes, query, cases, permissi
|
|
|
46
48
|
return dbQuery;
|
|
47
49
|
}
|
|
48
50
|
const primaryKey = schema.collections[table].primary;
|
|
49
|
-
|
|
51
|
+
const dbQuery = knex.from(table);
|
|
50
52
|
let sortRecords;
|
|
51
53
|
const innerQuerySortRecords = [];
|
|
52
54
|
let hasMultiRelationalSort;
|
|
53
55
|
if (queryCopy.sort) {
|
|
54
|
-
const sortResult = applySort(knex, schema, dbQuery, queryCopy, table, aliasMap, true);
|
|
56
|
+
const sortResult = applySort(knex, schema, dbQuery, queryCopy.sort, queryCopy.aggregate, table, aliasMap, true);
|
|
55
57
|
if (sortResult) {
|
|
56
58
|
sortRecords = sortResult.sortRecords;
|
|
57
59
|
hasMultiRelationalSort = sortResult.hasMultiRelationalSort;
|
|
@@ -115,7 +117,7 @@ export function getDBQuery({ table, fieldNodes, o2mNodes, query, cases, permissi
|
|
|
115
117
|
innerQuerySortRecords.push({ alias: sortAlias, order: sortRecord.order, column: orderByColumn });
|
|
116
118
|
});
|
|
117
119
|
if (hasMultiRelationalSort) {
|
|
118
|
-
dbQuery
|
|
120
|
+
dbQuery.rowNumber(knex.ref('directus_row_number').toQuery(), knex.raw(`partition by ?? order by ${orderByString}`, [`${table}.${primaryKey}`, ...orderByFields]));
|
|
119
121
|
// Start order by with directus_row_number. The directus_row_number is derived from a window function that
|
|
120
122
|
// is ordered by the sort fields within every primary key partition. That ensures that the result with the
|
|
121
123
|
// row number = 1 is the top-most row of every partition, according to the selected sort fields.
|
|
@@ -49,7 +49,7 @@ export async function runAst(originalAST, schema, accountability, options) {
|
|
|
49
49
|
return null;
|
|
50
50
|
// Run the items through the special transforms
|
|
51
51
|
const payloadService = new PayloadService(collection, { knex, schema });
|
|
52
|
-
let items = await payloadService.processValues('read', rawItems, query.alias ?? {});
|
|
52
|
+
let items = await payloadService.processValues('read', rawItems, query.alias ?? {}, query.aggregate ?? {});
|
|
53
53
|
if (!items || (Array.isArray(items) && items.length === 0))
|
|
54
54
|
return items;
|
|
55
55
|
// Apply the `_in` filters to the nested collection batches
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyFilter } from '
|
|
1
|
+
import { applyFilter } from '../lib/apply-query/filter/index.js';
|
|
2
2
|
export function applyCaseWhen({ columnCases, table, aliasMap, cases, column, alias, permissions }, { knex, schema }) {
|
|
3
3
|
const caseQuery = knex.queryBuilder();
|
|
4
4
|
applyFilter(knex, schema, caseQuery, { _or: columnCases }, table, aliasMap, cases, permissions);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { joinFilterWithCases } from '
|
|
2
|
-
import { getColumn } from '
|
|
1
|
+
import { joinFilterWithCases } from '../lib/apply-query/join-filter-with-cases.js';
|
|
2
|
+
import { getColumn } from './get-column.js';
|
|
3
3
|
import { parseFilterKey } from '../../../utils/parse-filter-key.js';
|
|
4
4
|
import { getHelpers } from '../../helpers/index.js';
|
|
5
5
|
import { applyCaseWhen } from './apply-case-when.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { REGEX_BETWEEN_PARENS } from '@directus/constants';
|
|
2
2
|
import { getFunctionsForType } from '@directus/utils';
|
|
3
|
-
import { getFunctions } from '
|
|
3
|
+
import { getFunctions } from '../../helpers/index.js';
|
|
4
4
|
import { InvalidQueryError } from '@directus/errors';
|
|
5
5
|
import { applyFunctionToColumnName } from './apply-function-to-column-name.js';
|
|
6
6
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { toArray } from '@directus/utils';
|
|
2
2
|
import { cloneDeep, pick } from 'lodash-es';
|
|
3
|
-
import { applyFunctionToColumnName } from '
|
|
3
|
+
import { applyFunctionToColumnName } from './apply-function-to-column-name.js';
|
|
4
4
|
export function removeTemporaryFields(schema, rawItem, ast, primaryKeyField, parentItem) {
|
|
5
5
|
const rawItems = cloneDeep(toArray(rawItem));
|
|
6
6
|
const items = [];
|
|
@@ -3,7 +3,7 @@ table: directus_collections
|
|
|
3
3
|
columns:
|
|
4
4
|
collection:
|
|
5
5
|
type: string
|
|
6
|
-
length:
|
|
6
|
+
length: 'MAX_TABLE_NAME_LENGTH'
|
|
7
7
|
primary: true
|
|
8
8
|
nullable: false
|
|
9
9
|
icon:
|
|
@@ -39,4 +39,4 @@ columns:
|
|
|
39
39
|
length: 255
|
|
40
40
|
sort_field:
|
|
41
41
|
type: string
|
|
42
|
-
length:
|
|
42
|
+
length: 'MAX_COLUMN_NAME_LENGTH'
|
|
@@ -5,14 +5,14 @@ columns:
|
|
|
5
5
|
increments: true
|
|
6
6
|
collection:
|
|
7
7
|
type: string
|
|
8
|
-
length:
|
|
8
|
+
length: 'MAX_TABLE_NAME_LENGTH'
|
|
9
9
|
nullable: false
|
|
10
10
|
references:
|
|
11
11
|
table: directus_collections
|
|
12
12
|
column: collection
|
|
13
13
|
field:
|
|
14
14
|
type: string
|
|
15
|
-
length:
|
|
15
|
+
length: 'MAX_COLUMN_NAME_LENGTH'
|
|
16
16
|
nullable: false
|
|
17
17
|
special:
|
|
18
18
|
type: string
|
|
@@ -5,36 +5,36 @@ columns:
|
|
|
5
5
|
increments: true
|
|
6
6
|
many_collection:
|
|
7
7
|
type: string
|
|
8
|
-
length:
|
|
8
|
+
length: 'MAX_TABLE_NAME_LENGTH'
|
|
9
9
|
nullable: false
|
|
10
10
|
references:
|
|
11
11
|
table: directus_collections
|
|
12
12
|
column: collection
|
|
13
13
|
many_field:
|
|
14
14
|
type: string
|
|
15
|
-
length:
|
|
15
|
+
length: 'MAX_COLUMN_NAME_LENGTH'
|
|
16
16
|
nullable: false
|
|
17
17
|
many_primary:
|
|
18
18
|
type: string
|
|
19
|
-
length:
|
|
19
|
+
length: 'MAX_COLUMN_NAME_LENGTH'
|
|
20
20
|
nullable: false
|
|
21
21
|
one_collection:
|
|
22
22
|
type: string
|
|
23
|
-
length:
|
|
23
|
+
length: 'MAX_TABLE_NAME_LENGTH'
|
|
24
24
|
references:
|
|
25
25
|
table: directus_collections
|
|
26
26
|
column: collection
|
|
27
27
|
one_field:
|
|
28
28
|
type: string
|
|
29
|
-
length:
|
|
29
|
+
length: 'MAX_COLUMN_NAME_LENGTH'
|
|
30
30
|
one_primary:
|
|
31
31
|
type: string
|
|
32
|
-
length:
|
|
32
|
+
length: 'MAX_COLUMN_NAME_LENGTH'
|
|
33
33
|
one_collection_field:
|
|
34
34
|
type: string
|
|
35
|
-
length:
|
|
35
|
+
length: 'MAX_COLUMN_NAME_LENGTH'
|
|
36
36
|
one_allowed_collections:
|
|
37
37
|
type: text
|
|
38
38
|
junction_field:
|
|
39
39
|
type: string
|
|
40
|
-
length:
|
|
40
|
+
length: 'MAX_COLUMN_NAME_LENGTH'
|
|
@@ -24,7 +24,14 @@ export default async function runSeed(database) {
|
|
|
24
24
|
if (columnInfo.type === 'alias' || columnInfo.type === 'unknown')
|
|
25
25
|
return;
|
|
26
26
|
if (columnInfo.type === 'string') {
|
|
27
|
-
|
|
27
|
+
let length = columnInfo.length;
|
|
28
|
+
if (length === 'MAX_TABLE_NAME_LENGTH') {
|
|
29
|
+
length = helpers.schema.getTableNameMaxLength();
|
|
30
|
+
}
|
|
31
|
+
else if (length === 'MAX_COLUMN_NAME_LENGTH') {
|
|
32
|
+
length = helpers.schema.getColumnNameMaxLength();
|
|
33
|
+
}
|
|
34
|
+
column = tableBuilder.string(columnName, Number(length));
|
|
28
35
|
}
|
|
29
36
|
else if (columnInfo.increments) {
|
|
30
37
|
column = tableBuilder.increments();
|
package/dist/flows.js
CHANGED
|
@@ -297,6 +297,12 @@ class FlowManager {
|
|
|
297
297
|
});
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
|
+
if ((flow.trigger === 'manual' || flow.trigger === 'webhook') &&
|
|
301
|
+
flow.options['async'] !== true &&
|
|
302
|
+
flow.options['error_on_reject'] === true &&
|
|
303
|
+
lastOperationStatus === 'reject') {
|
|
304
|
+
throw keyedData[LAST_KEY];
|
|
305
|
+
}
|
|
300
306
|
if (flow.trigger === 'event' && flow.options['type'] === 'filter' && lastOperationStatus === 'reject') {
|
|
301
307
|
throw keyedData[LAST_KEY];
|
|
302
308
|
}
|
|
@@ -315,8 +321,9 @@ class FlowManager {
|
|
|
315
321
|
return { successor: null, status: 'unknown', data: null, options: null };
|
|
316
322
|
}
|
|
317
323
|
const handler = this.operations.get(operation.type);
|
|
318
|
-
|
|
324
|
+
let options = operation.options;
|
|
319
325
|
try {
|
|
326
|
+
options = applyOptionsData(options, keyedData);
|
|
320
327
|
let result = await handler(options, {
|
|
321
328
|
services,
|
|
322
329
|
env: useEnv(),
|
|
@@ -7,6 +7,7 @@ import pm2 from 'pm2';
|
|
|
7
7
|
import { AggregatorRegistry, Counter, Histogram, register } from 'prom-client';
|
|
8
8
|
import { getCache } from '../../cache.js';
|
|
9
9
|
import { hasDatabaseConnection } from '../../database/index.js';
|
|
10
|
+
import { useLogger } from '../../logger/index.js';
|
|
10
11
|
import { redisConfigAvailable, useRedis } from '../../redis/index.js';
|
|
11
12
|
import { getStorage } from '../../storage/index.js';
|
|
12
13
|
const isPM2 = 'PM2_HOME' in process.env;
|
|
@@ -15,6 +16,7 @@ const listApps = promisify(pm2.list.bind(pm2));
|
|
|
15
16
|
const sendDataToProcessId = promisify(pm2.sendDataToProcessId.bind(pm2));
|
|
16
17
|
export function createMetrics() {
|
|
17
18
|
const env = useEnv();
|
|
19
|
+
const logger = useLogger();
|
|
18
20
|
const services = env['METRICS_SERVICES'] ?? [];
|
|
19
21
|
const aggregates = new Map();
|
|
20
22
|
/**
|
|
@@ -54,8 +56,8 @@ export function createMetrics() {
|
|
|
54
56
|
}
|
|
55
57
|
await Promise.allSettled(syncs);
|
|
56
58
|
}
|
|
57
|
-
catch {
|
|
58
|
-
|
|
59
|
+
catch (error) {
|
|
60
|
+
logger.error(error);
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
}
|
|
@@ -215,11 +217,15 @@ export function createMetrics() {
|
|
|
215
217
|
try {
|
|
216
218
|
await disk.write(`metric-${checkId}`, Readable.from(['check']));
|
|
217
219
|
const fileStream = await disk.read(`metric-${checkId}`);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
fileStream.on('data', async () => {
|
|
221
|
+
try {
|
|
222
|
+
fileStream.destroy();
|
|
223
|
+
await disk.delete(`metric-${checkId}`);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
logger.error(error);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
223
229
|
}
|
|
224
230
|
catch {
|
|
225
231
|
metric.inc();
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { defineOperationApi } from '@directus/extensions';
|
|
2
|
-
import { validatePayload } from '@directus/utils';
|
|
2
|
+
import { parseFilter, validatePayload } from '@directus/utils';
|
|
3
3
|
import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
|
|
4
4
|
export default defineOperationApi({
|
|
5
5
|
id: 'condition',
|
|
6
|
-
handler: ({ filter }, { data }) => {
|
|
7
|
-
const
|
|
6
|
+
handler: ({ filter }, { data, accountability }) => {
|
|
7
|
+
const parsedFilter = parseFilter(filter, accountability);
|
|
8
|
+
if (!parsedFilter) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const errors = validatePayload(parsedFilter, data, { requireAll: true });
|
|
8
12
|
if (errors.length > 0) {
|
|
9
|
-
// sanitize and format errors
|
|
10
13
|
const validationErrors = errors
|
|
11
14
|
.map((error) => error.details.map((details) => new FailedValidationError(joiValidationErrorItemToErrorExtensions(details))))
|
|
12
15
|
.flat();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineOperationApi } from '@directus/extensions';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
+
import { sieveFunctions } from '@directus/utils';
|
|
3
4
|
const require = createRequire(import.meta.url);
|
|
4
5
|
const ivm = require('isolated-vm');
|
|
5
6
|
/**
|
|
@@ -31,7 +32,7 @@ export default defineOperationApi({
|
|
|
31
32
|
}, { copy: true });
|
|
32
33
|
// Run the operation once to define the module.exports function
|
|
33
34
|
await context.eval(code, { timeout: scriptTimeoutMs });
|
|
34
|
-
const inputData = new ivm.ExternalCopy({ data });
|
|
35
|
+
const inputData = new ivm.ExternalCopy({ data: sieveFunctions(data) });
|
|
35
36
|
const resultRef = await context.evalClosure(`return module.exports($0.data)`, [inputData.copyInto()], {
|
|
36
37
|
result: { reference: true, promise: true },
|
|
37
38
|
timeout: scriptTimeoutMs,
|
|
@@ -70,25 +70,29 @@ export class CollectionsService {
|
|
|
70
70
|
if ('fields' in payload && !Array.isArray(payload.fields)) {
|
|
71
71
|
throw new InvalidPayloadError({ reason: `"fields" must be an array` });
|
|
72
72
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Directus heavily relies on the primary key of a collection, so we have to make sure that
|
|
75
|
+
* every collection that is created has a primary key. If no primary key field is created
|
|
76
|
+
* while making the collection, we default to an auto incremented id named `id`
|
|
77
|
+
*/
|
|
78
|
+
const injectedPrimaryKeyField = {
|
|
79
|
+
field: 'id',
|
|
80
|
+
type: 'integer',
|
|
81
|
+
meta: {
|
|
82
|
+
hidden: true,
|
|
83
|
+
interface: 'numeric',
|
|
84
|
+
readonly: true,
|
|
85
|
+
},
|
|
86
|
+
schema: {
|
|
87
|
+
is_primary_key: true,
|
|
88
|
+
has_auto_increment: true,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
76
91
|
if (!payload.fields || payload.fields.length === 0) {
|
|
77
|
-
payload.fields = [
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
meta: {
|
|
82
|
-
hidden: true,
|
|
83
|
-
interface: 'numeric',
|
|
84
|
-
readonly: true,
|
|
85
|
-
},
|
|
86
|
-
schema: {
|
|
87
|
-
is_primary_key: true,
|
|
88
|
-
has_auto_increment: true,
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
];
|
|
92
|
+
payload.fields = [injectedPrimaryKeyField];
|
|
93
|
+
}
|
|
94
|
+
else if (!payload.fields.some((f) => f.schema?.is_primary_key === true || f.schema?.has_auto_increment === true)) {
|
|
95
|
+
payload.fields = [injectedPrimaryKeyField, ...payload.fields];
|
|
92
96
|
}
|
|
93
97
|
// Ensure that every field meta has the field/collection fields filled correctly
|
|
94
98
|
payload.fields = payload.fields.map((field) => {
|
package/dist/services/fields.js
CHANGED
|
@@ -2,7 +2,7 @@ import { DEFAULT_NUMERIC_PRECISION, DEFAULT_NUMERIC_SCALE, KNEX_TYPES, REGEX_BET
|
|
|
2
2
|
import { useEnv } from '@directus/env';
|
|
3
3
|
import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
4
4
|
import { createInspector } from '@directus/schema';
|
|
5
|
-
import { addFieldFlag, toArray } from '@directus/utils';
|
|
5
|
+
import { addFieldFlag, getRelations, toArray } from '@directus/utils';
|
|
6
6
|
import { isEqual, isNil, merge } from 'lodash-es';
|
|
7
7
|
import { clearSystemCache, getCache, getCacheValue, setCacheValue } from '../cache.js';
|
|
8
8
|
import { ALIAS_TYPES, ALLOWED_DB_DEFAULT_FUNCTIONS } from '../constants.js';
|
|
@@ -511,10 +511,7 @@ export class FieldsService {
|
|
|
511
511
|
});
|
|
512
512
|
}
|
|
513
513
|
await transaction(this.knex, async (trx) => {
|
|
514
|
-
const relations = this.schema.relations
|
|
515
|
-
return ((relation.collection === collection && relation.field === field) ||
|
|
516
|
-
(relation.related_collection === collection && relation.meta?.one_field === field));
|
|
517
|
-
});
|
|
514
|
+
const relations = getRelations(this.schema.relations, collection, field);
|
|
518
515
|
const relationsService = new RelationsService({
|
|
519
516
|
knex: trx,
|
|
520
517
|
accountability: this.accountability,
|