@directus/api 22.1.0 → 22.2.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/app.js +1 -1
- package/dist/cache.d.ts +2 -2
- package/dist/cache.js +2 -2
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/database/get-ast-from-query/get-ast-from-query.js +2 -31
- 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 +21 -3
- package/dist/database/get-ast-from-query/utils/get-allowed-sort.d.ts +9 -0
- package/dist/database/get-ast-from-query/utils/get-allowed-sort.js +35 -0
- package/dist/database/helpers/fn/types.d.ts +6 -3
- package/dist/database/helpers/fn/types.js +2 -2
- package/dist/database/helpers/index.d.ts +2 -0
- package/dist/database/helpers/index.js +2 -0
- package/dist/database/helpers/nullable-update/dialects/default.d.ts +3 -0
- package/dist/database/helpers/nullable-update/dialects/default.js +3 -0
- package/dist/database/helpers/nullable-update/dialects/oracle.d.ts +12 -0
- package/dist/database/helpers/nullable-update/dialects/oracle.js +16 -0
- package/dist/database/helpers/nullable-update/index.d.ts +7 -0
- package/dist/database/helpers/nullable-update/index.js +7 -0
- package/dist/database/helpers/nullable-update/types.d.ts +7 -0
- package/dist/database/helpers/nullable-update/types.js +12 -0
- package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +3 -1
- package/dist/database/helpers/schema/dialects/cockroachdb.js +17 -0
- package/dist/database/helpers/schema/dialects/mssql.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/mssql.js +20 -0
- package/dist/database/helpers/schema/dialects/mysql.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/mysql.js +33 -0
- package/dist/database/helpers/schema/dialects/oracle.d.ts +3 -1
- package/dist/database/helpers/schema/dialects/oracle.js +21 -0
- package/dist/database/helpers/schema/dialects/postgres.d.ts +3 -1
- package/dist/database/helpers/schema/dialects/postgres.js +23 -0
- package/dist/database/helpers/schema/dialects/sqlite.d.ts +1 -0
- package/dist/database/helpers/schema/dialects/sqlite.js +3 -0
- package/dist/database/helpers/schema/types.d.ts +5 -0
- package/dist/database/helpers/schema/types.js +3 -0
- package/dist/database/helpers/schema/utils/preprocess-bindings.d.ts +5 -1
- package/dist/database/helpers/schema/utils/preprocess-bindings.js +23 -17
- package/dist/database/index.d.ts +1 -1
- package/dist/database/index.js +2 -2
- package/dist/database/migrations/20240806A-permissions-policies.js +3 -2
- package/dist/database/migrations/20240817A-update-icon-fields-length.d.ts +3 -0
- package/dist/database/migrations/20240817A-update-icon-fields-length.js +55 -0
- package/dist/database/run-ast/lib/get-db-query.d.ts +2 -2
- package/dist/database/run-ast/lib/get-db-query.js +23 -13
- package/dist/database/run-ast/run-ast.d.ts +2 -2
- package/dist/database/run-ast/run-ast.js +14 -7
- package/dist/database/run-ast/utils/apply-case-when.d.ts +3 -2
- package/dist/database/run-ast/utils/apply-case-when.js +2 -2
- package/dist/database/run-ast/utils/get-column-pre-processor.d.ts +2 -2
- package/dist/database/run-ast/utils/get-column-pre-processor.js +3 -1
- package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.d.ts +2 -2
- package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.js +2 -1
- package/dist/extensions/manager.js +2 -2
- package/dist/logger/index.d.ts +6 -0
- package/dist/logger/index.js +79 -28
- package/dist/logger/logs-stream.d.ts +11 -0
- package/dist/logger/logs-stream.js +41 -0
- package/dist/middleware/respond.js +1 -0
- package/dist/permissions/lib/fetch-permissions.d.ts +2 -3
- package/dist/permissions/lib/fetch-permissions.js +5 -39
- package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.d.ts +1 -2
- package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +1 -13
- package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.d.ts +1 -2
- package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +1 -6
- package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.d.ts +1 -2
- package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js +1 -7
- package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.d.ts +1 -2
- package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +2 -7
- package/dist/permissions/modules/process-ast/lib/get-cases.d.ts +6 -0
- package/dist/permissions/modules/process-ast/lib/get-cases.js +40 -0
- package/dist/permissions/modules/process-ast/lib/inject-cases.js +1 -40
- package/dist/permissions/modules/process-payload/process-payload.js +4 -5
- package/dist/permissions/modules/validate-access/lib/validate-item-access.js +7 -6
- package/dist/permissions/utils/fetch-dynamic-variable-context.d.ts +1 -2
- package/dist/permissions/utils/fetch-dynamic-variable-context.js +44 -24
- package/dist/permissions/utils/fetch-raw-permissions.d.ts +11 -0
- package/dist/permissions/utils/fetch-raw-permissions.js +39 -0
- package/dist/request/is-denied-ip.js +7 -1
- package/dist/server.js +4 -2
- package/dist/services/fields.d.ts +1 -1
- package/dist/services/fields.js +66 -25
- package/dist/services/import-export.js +2 -2
- package/dist/services/items.js +1 -1
- package/dist/services/mail/index.js +1 -5
- package/dist/services/meta.js +8 -7
- package/dist/services/notifications.d.ts +0 -4
- package/dist/services/notifications.js +8 -6
- package/dist/services/permissions.js +19 -19
- package/dist/services/server.js +8 -1
- package/dist/services/specifications.js +7 -7
- package/dist/services/users.js +4 -1
- package/dist/utils/apply-query.d.ts +3 -3
- package/dist/utils/apply-query.js +25 -20
- package/dist/utils/get-address.d.ts +1 -1
- package/dist/utils/get-address.js +6 -1
- package/dist/utils/get-allowed-log-levels.d.ts +3 -0
- package/dist/utils/get-allowed-log-levels.js +11 -0
- package/dist/utils/get-column.d.ts +8 -4
- package/dist/utils/get-column.js +10 -2
- package/dist/utils/get-schema.js +19 -24
- package/dist/utils/parse-filter-key.js +1 -5
- package/dist/utils/sanitize-query.js +1 -1
- package/dist/utils/sanitize-schema.d.ts +1 -1
- package/dist/websocket/controllers/base.d.ts +10 -10
- package/dist/websocket/controllers/base.js +22 -3
- package/dist/websocket/controllers/graphql.js +3 -1
- package/dist/websocket/controllers/index.d.ts +4 -0
- package/dist/websocket/controllers/index.js +12 -0
- package/dist/websocket/controllers/logs.d.ts +18 -0
- package/dist/websocket/controllers/logs.js +50 -0
- package/dist/websocket/controllers/rest.js +3 -1
- package/dist/websocket/handlers/index.d.ts +1 -0
- package/dist/websocket/handlers/index.js +21 -3
- package/dist/websocket/handlers/logs.d.ts +31 -0
- package/dist/websocket/handlers/logs.js +121 -0
- package/dist/websocket/messages.d.ts +26 -0
- package/dist/websocket/messages.js +9 -0
- package/dist/websocket/types.d.ts +7 -0
- package/package.json +27 -26
|
@@ -1,30 +1,36 @@
|
|
|
1
1
|
import { isString } from 'lodash-es';
|
|
2
|
+
/**
|
|
3
|
+
* Preprocess a SQL query, such that repeated binding values are bound to the same binding index.
|
|
4
|
+
**/
|
|
2
5
|
export function preprocessBindings(queryParams, options) {
|
|
3
6
|
const query = { bindings: [], ...(isString(queryParams) ? { sql: queryParams } : queryParams) };
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (prevIndex !== -1) {
|
|
9
|
-
bindingIndices[i] = prevIndex;
|
|
10
|
-
}
|
|
11
|
-
else {
|
|
12
|
-
bindingIndices[i] = i;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
7
|
+
// bindingIndices[i] is the index of the first occurrence of query.bindings[i]
|
|
8
|
+
const bindingIndices = new Map();
|
|
9
|
+
// The new, deduplicated bindings
|
|
10
|
+
const bindings = [];
|
|
15
11
|
let matchIndex = 0;
|
|
16
|
-
let
|
|
17
|
-
const sql = query.sql.replace(/(\\*)(\?)/g,
|
|
12
|
+
let nextBindingIndex = 0;
|
|
13
|
+
const sql = query.sql.replace(/(\\*)(\?)/g, (_, escapes) => {
|
|
18
14
|
if (escapes.length % 2) {
|
|
19
15
|
// Return an escaped question mark, so it stays escaped
|
|
20
16
|
return `${'\\'.repeat(escapes.length)}?`;
|
|
21
17
|
}
|
|
18
|
+
const binding = query.bindings[matchIndex];
|
|
19
|
+
let bindingIndex;
|
|
20
|
+
if (bindingIndices.has(binding)) {
|
|
21
|
+
// This index belongs to a binding that has been encountered before.
|
|
22
|
+
bindingIndex = bindingIndices.get(binding);
|
|
23
|
+
}
|
|
22
24
|
else {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
// The first time the value is encountered, set the index lookup to the current index
|
|
26
|
+
// Use the nextBindingIndex to get the next unused binding index that is used in the new, deduplicated bindings
|
|
27
|
+
bindingIndex = nextBindingIndex++;
|
|
28
|
+
bindingIndices.set(binding, bindingIndex);
|
|
29
|
+
bindings.push(binding);
|
|
26
30
|
}
|
|
31
|
+
// Increment the loop counter
|
|
32
|
+
matchIndex++;
|
|
33
|
+
return options.format(bindingIndex);
|
|
27
34
|
});
|
|
28
|
-
const bindings = query.bindings.filter((_, i) => bindingIndices[i] === i);
|
|
29
35
|
return { ...query, sql, bindings };
|
|
30
36
|
}
|
package/dist/database/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Knex } from 'knex';
|
|
|
3
3
|
import type { DatabaseClient } from '../types/index.js';
|
|
4
4
|
export default getDatabase;
|
|
5
5
|
export declare function getDatabase(): Knex;
|
|
6
|
-
export declare function getSchemaInspector(): SchemaInspector;
|
|
6
|
+
export declare function getSchemaInspector(database?: Knex): SchemaInspector;
|
|
7
7
|
/**
|
|
8
8
|
* Get database version. Value currently exists for MySQL only.
|
|
9
9
|
*
|
package/dist/database/index.js
CHANGED
|
@@ -143,11 +143,11 @@ export function getDatabase() {
|
|
|
143
143
|
});
|
|
144
144
|
return database;
|
|
145
145
|
}
|
|
146
|
-
export function getSchemaInspector() {
|
|
146
|
+
export function getSchemaInspector(database) {
|
|
147
147
|
if (inspector) {
|
|
148
148
|
return inspector;
|
|
149
149
|
}
|
|
150
|
-
|
|
150
|
+
database ??= getDatabase();
|
|
151
151
|
inspector = createInspector(database);
|
|
152
152
|
return inspector;
|
|
153
153
|
}
|
|
@@ -186,10 +186,10 @@ export async function up(knex) {
|
|
|
186
186
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
187
187
|
// Link permissions to policies instead of roles
|
|
188
188
|
await knex.schema.alterTable('directus_permissions', (table) => {
|
|
189
|
-
table.uuid('policy')
|
|
189
|
+
table.uuid('policy');
|
|
190
190
|
});
|
|
191
191
|
try {
|
|
192
|
-
const inspector = await getSchemaInspector();
|
|
192
|
+
const inspector = await getSchemaInspector(knex);
|
|
193
193
|
const foreignKeys = await inspector.foreignKeys('directus_permissions');
|
|
194
194
|
const foreignConstraint = foreignKeys.find((foreign) => foreign.foreign_key_table === 'directus_roles' && foreign.column === 'role')
|
|
195
195
|
?.constraint_name || undefined;
|
|
@@ -212,6 +212,7 @@ export async function up(knex) {
|
|
|
212
212
|
await knex.schema.alterTable('directus_permissions', (table) => {
|
|
213
213
|
table.dropColumns('role');
|
|
214
214
|
table.dropNullable('policy');
|
|
215
|
+
table.foreign('policy').references('directus_policies.id').onDelete('CASCADE');
|
|
215
216
|
});
|
|
216
217
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
217
218
|
// Setup junction table between roles/users and policies
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { getHelpers } from '../helpers/index.js';
|
|
2
|
+
export async function up(knex) {
|
|
3
|
+
const helper = getHelpers(knex).schema;
|
|
4
|
+
await helper.changeToType('directus_collections', 'icon', 'string', {
|
|
5
|
+
length: 64,
|
|
6
|
+
});
|
|
7
|
+
await helper.changeToType('directus_dashboards', 'icon', 'string', {
|
|
8
|
+
nullable: false,
|
|
9
|
+
default: 'dashboard',
|
|
10
|
+
length: 64,
|
|
11
|
+
});
|
|
12
|
+
await helper.changeToType('directus_flows', 'icon', 'string', {
|
|
13
|
+
length: 64,
|
|
14
|
+
});
|
|
15
|
+
await helper.changeToType('directus_panels', 'icon', 'string', {
|
|
16
|
+
default: null,
|
|
17
|
+
length: 64,
|
|
18
|
+
});
|
|
19
|
+
await helper.changeToType('directus_presets', 'icon', 'string', {
|
|
20
|
+
default: 'bookmark',
|
|
21
|
+
length: 64,
|
|
22
|
+
});
|
|
23
|
+
await helper.changeToType('directus_roles', 'icon', 'string', {
|
|
24
|
+
nullable: false,
|
|
25
|
+
default: 'supervised_user_circle',
|
|
26
|
+
length: 64,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export async function down(knex) {
|
|
30
|
+
const helper = getHelpers(knex).schema;
|
|
31
|
+
await helper.changeToType('directus_collections', 'icon', 'string', {
|
|
32
|
+
length: 30,
|
|
33
|
+
});
|
|
34
|
+
await helper.changeToType('directus_dashboards', 'icon', 'string', {
|
|
35
|
+
nullable: false,
|
|
36
|
+
default: 'dashboard',
|
|
37
|
+
length: 30,
|
|
38
|
+
});
|
|
39
|
+
await helper.changeToType('directus_flows', 'icon', 'string', {
|
|
40
|
+
length: 30,
|
|
41
|
+
});
|
|
42
|
+
await helper.changeToType('directus_panels', 'icon', 'string', {
|
|
43
|
+
default: null,
|
|
44
|
+
length: 30,
|
|
45
|
+
});
|
|
46
|
+
await helper.changeToType('directus_presets', 'icon', 'string', {
|
|
47
|
+
default: 'bookmark',
|
|
48
|
+
length: 30,
|
|
49
|
+
});
|
|
50
|
+
await helper.changeToType('directus_roles', 'icon', 'string', {
|
|
51
|
+
nullable: false,
|
|
52
|
+
default: 'supervised_user_circle',
|
|
53
|
+
length: 30,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Filter, Query, SchemaOverview } from '@directus/types';
|
|
1
|
+
import type { Filter, Permission, Query, SchemaOverview } from '@directus/types';
|
|
2
2
|
import type { Knex } from 'knex';
|
|
3
3
|
import type { FieldNode, FunctionFieldNode, O2MNode } from '../../../types/ast.js';
|
|
4
|
-
export declare function getDBQuery(schema: SchemaOverview, knex: Knex, table: string, fieldNodes: (FieldNode | FunctionFieldNode)[], o2mNodes: O2MNode[], query: Query, cases: Filter[]): Knex.QueryBuilder;
|
|
4
|
+
export declare function getDBQuery(schema: SchemaOverview, knex: Knex, table: string, fieldNodes: (FieldNode | FunctionFieldNode)[], o2mNodes: O2MNode[], query: Query, cases: Filter[], permissions: Permission[]): Knex.QueryBuilder;
|
|
@@ -9,10 +9,10 @@ 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
|
-
export function getDBQuery(schema, knex, table, fieldNodes, o2mNodes, query, cases) {
|
|
12
|
+
export function getDBQuery(schema, knex, table, fieldNodes, o2mNodes, query, cases, permissions) {
|
|
13
13
|
const aliasMap = Object.create(null);
|
|
14
14
|
const env = useEnv();
|
|
15
|
-
const preProcess = getColumnPreprocessor(knex, schema, table, cases, aliasMap);
|
|
15
|
+
const preProcess = getColumnPreprocessor(knex, schema, table, cases, permissions, aliasMap);
|
|
16
16
|
const queryCopy = cloneDeep(query);
|
|
17
17
|
const helpers = getHelpers(knex);
|
|
18
18
|
const hasCaseWhen = o2mNodes.some((node) => node.whenCase && node.whenCase.length > 0) ||
|
|
@@ -25,7 +25,10 @@ export function getDBQuery(schema, knex, table, fieldNodes, o2mNodes, query, cas
|
|
|
25
25
|
const groupWhenCases = hasCaseWhen
|
|
26
26
|
? queryCopy.group?.map((field) => fieldNodes.find(({ fieldKey }) => fieldKey === field)?.whenCase ?? [])
|
|
27
27
|
: undefined;
|
|
28
|
-
const dbQuery = applyQuery(knex, table, flatQuery, queryCopy, schema, cases,
|
|
28
|
+
const dbQuery = applyQuery(knex, table, flatQuery, queryCopy, schema, cases, permissions, {
|
|
29
|
+
aliasMap,
|
|
30
|
+
groupWhenCases,
|
|
31
|
+
}).query;
|
|
29
32
|
flatQuery.select(fieldNodes.map((node) => preProcess(node)));
|
|
30
33
|
withPreprocessBindings(knex, dbQuery);
|
|
31
34
|
return dbQuery;
|
|
@@ -42,7 +45,7 @@ export function getDBQuery(schema, knex, table, fieldNodes, o2mNodes, query, cas
|
|
|
42
45
|
hasMultiRelationalSort = sortResult.hasMultiRelationalSort;
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
|
-
const { hasMultiRelationalFilter } = applyQuery(knex, table, dbQuery, queryCopy, schema, cases, {
|
|
48
|
+
const { hasMultiRelationalFilter } = applyQuery(knex, table, dbQuery, queryCopy, schema, cases, permissions, {
|
|
46
49
|
aliasMap,
|
|
47
50
|
isInnerQuery: true,
|
|
48
51
|
hasMultiRelationalSort,
|
|
@@ -68,6 +71,7 @@ export function getDBQuery(schema, knex, table, fieldNodes, o2mNodes, query, cas
|
|
|
68
71
|
cases,
|
|
69
72
|
table,
|
|
70
73
|
alias: node.fieldKey,
|
|
74
|
+
permissions,
|
|
71
75
|
}, { knex, schema });
|
|
72
76
|
}));
|
|
73
77
|
}
|
|
@@ -82,19 +86,21 @@ export function getDBQuery(schema, knex, table, fieldNodes, o2mNodes, query, cas
|
|
|
82
86
|
orderByString += ', ';
|
|
83
87
|
}
|
|
84
88
|
const sortAlias = `sort_${generateAlias()}`;
|
|
89
|
+
let orderByColumn;
|
|
85
90
|
if (sortRecord.column.includes('.')) {
|
|
86
91
|
const [alias, field] = sortRecord.column.split('.');
|
|
87
92
|
const originalCollectionName = getCollectionFromAlias(alias, aliasMap);
|
|
88
93
|
dbQuery.select(getColumn(knex, alias, field, sortAlias, schema, { originalCollectionName }));
|
|
89
94
|
orderByString += `?? ${sortRecord.order}`;
|
|
90
|
-
|
|
95
|
+
orderByColumn = getColumn(knex, alias, field, false, schema, { originalCollectionName });
|
|
91
96
|
}
|
|
92
97
|
else {
|
|
93
98
|
dbQuery.select(getColumn(knex, table, sortRecord.column, sortAlias, schema));
|
|
94
99
|
orderByString += `?? ${sortRecord.order}`;
|
|
95
|
-
|
|
100
|
+
orderByColumn = getColumn(knex, table, sortRecord.column, false, schema);
|
|
96
101
|
}
|
|
97
|
-
|
|
102
|
+
orderByFields.push(orderByColumn);
|
|
103
|
+
innerQuerySortRecords.push({ alias: sortAlias, order: sortRecord.order, column: orderByColumn });
|
|
98
104
|
});
|
|
99
105
|
if (hasMultiRelationalSort) {
|
|
100
106
|
dbQuery = helpers.schema.applyMultiRelationalSort(knex, dbQuery, table, primaryKey, orderByString, orderByFields);
|
|
@@ -159,7 +165,7 @@ export function getDBQuery(schema, knex, table, fieldNodes, o2mNodes, query, cas
|
|
|
159
165
|
SELECT ...,
|
|
160
166
|
CASE WHEN `inner`.<random-prefix>_<alias> > 0 THEN <actual-column> END AS <alias>
|
|
161
167
|
*/
|
|
162
|
-
const innerPreprocess = getInnerQueryColumnPreProcessor(knex, schema, table, cases, aliasMap, innerCaseWhenAliasPrefix);
|
|
168
|
+
const innerPreprocess = getInnerQueryColumnPreProcessor(knex, schema, table, cases, permissions, aliasMap, innerCaseWhenAliasPrefix);
|
|
163
169
|
// To optimize the query we avoid having unnecessary columns in the inner query, that don't have a caseWhen, since
|
|
164
170
|
// they are selected in the outer query directly
|
|
165
171
|
dbQuery.select(fieldNodes.map(innerPreprocess).filter((x) => x !== null));
|
|
@@ -167,11 +173,15 @@ export function getDBQuery(schema, knex, table, fieldNodes, o2mNodes, query, cas
|
|
|
167
173
|
// based on the case/when of that field.
|
|
168
174
|
dbQuery.select(o2mNodes.map(innerPreprocess).filter((x) => x !== null));
|
|
169
175
|
const groupByFields = [knex.raw('??.??', [table, primaryKey])];
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
// For some DB vendors sort fields need to be included in the group by clause, otherwise this causes problems those DBs
|
|
177
|
+
// since sort fields are selected in the inner query, and they expect all selected columns to be in
|
|
178
|
+
// the group by clause or aggregated over.
|
|
179
|
+
// For some DBs the field needs to be the actual raw column expression, since aliases are not available in the
|
|
180
|
+
// group by clause.
|
|
181
|
+
// Since the fields are expected to be the same for a single primary key it is safe to include them in the
|
|
182
|
+
// group by without influencing the result.
|
|
183
|
+
// This inclusion depends on the DB vendor, as such it is handled in a dialect specific helper.
|
|
184
|
+
helpers.schema.addInnerSortFieldsToGroupBy(groupByFields, innerQuerySortRecords, hasMultiRelationalSort ?? false);
|
|
175
185
|
dbQuery.groupBy(groupByFields);
|
|
176
186
|
}
|
|
177
187
|
const wrapperQuery = knex
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Item, SchemaOverview } from '@directus/types';
|
|
1
|
+
import type { Accountability, Item, SchemaOverview } from '@directus/types';
|
|
2
2
|
import type { AST, NestedCollectionNode } from '../../types/ast.js';
|
|
3
3
|
import type { RunASTOptions } from './types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Execute a given AST using Knex. Returns array of items based on requested AST.
|
|
6
6
|
*/
|
|
7
|
-
export declare function runAst(originalAST: AST | NestedCollectionNode, schema: SchemaOverview, options?: RunASTOptions): Promise<null | Item | Item[]>;
|
|
7
|
+
export declare function runAst(originalAST: AST | NestedCollectionNode, schema: SchemaOverview, accountability: Accountability | null, options?: RunASTOptions): Promise<null | Item | Item[]>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
2
|
import { cloneDeep, merge } from 'lodash-es';
|
|
3
|
+
import { fetchPermissions } from '../../permissions/lib/fetch-permissions.js';
|
|
4
|
+
import { fetchPolicies } from '../../permissions/lib/fetch-policies.js';
|
|
3
5
|
import { PayloadService } from '../../services/payload.js';
|
|
4
6
|
import getDatabase from '../index.js';
|
|
5
7
|
import { getDBQuery } from './lib/get-db-query.js';
|
|
@@ -10,26 +12,31 @@ import { removeTemporaryFields } from './utils/remove-temporary-fields.js';
|
|
|
10
12
|
/**
|
|
11
13
|
* Execute a given AST using Knex. Returns array of items based on requested AST.
|
|
12
14
|
*/
|
|
13
|
-
export async function runAst(originalAST, schema, options) {
|
|
15
|
+
export async function runAst(originalAST, schema, accountability, options) {
|
|
14
16
|
const ast = cloneDeep(originalAST);
|
|
15
17
|
const knex = options?.knex || getDatabase();
|
|
16
18
|
if (ast.type === 'a2o') {
|
|
17
19
|
const results = {};
|
|
18
20
|
for (const collection of ast.names) {
|
|
19
|
-
results[collection] = await run(collection, ast.children[collection], ast.query[collection], ast.cases[collection] ?? []);
|
|
21
|
+
results[collection] = await run(collection, ast.children[collection], ast.query[collection], ast.cases[collection] ?? [], accountability);
|
|
20
22
|
}
|
|
21
23
|
return results;
|
|
22
24
|
}
|
|
23
25
|
else {
|
|
24
|
-
return await run(ast.name, ast.children, options?.query || ast.query, ast.cases);
|
|
26
|
+
return await run(ast.name, ast.children, options?.query || ast.query, ast.cases, accountability);
|
|
25
27
|
}
|
|
26
|
-
async function run(collection, children, query, cases) {
|
|
28
|
+
async function run(collection, children, query, cases, accountability) {
|
|
27
29
|
const env = useEnv();
|
|
28
30
|
// Retrieve the database columns to select in the current AST
|
|
29
31
|
const { fieldNodes, primaryKeyField, nestedCollectionNodes } = await parseCurrentLevel(schema, collection, children, query);
|
|
30
32
|
const o2mNodes = nestedCollectionNodes.filter((node) => node.type === 'o2m');
|
|
33
|
+
let permissions = [];
|
|
34
|
+
if (accountability && !accountability.admin) {
|
|
35
|
+
const policies = await fetchPolicies(accountability, { schema, knex });
|
|
36
|
+
permissions = await fetchPermissions({ action: 'read', accountability, policies }, { schema, knex });
|
|
37
|
+
}
|
|
31
38
|
// The actual knex query builder instance. This is a promise that resolves with the raw items from the db
|
|
32
|
-
const dbQuery = getDBQuery(schema, knex, collection, fieldNodes, o2mNodes, query, cases);
|
|
39
|
+
const dbQuery = getDBQuery(schema, knex, collection, fieldNodes, o2mNodes, query, cases, permissions);
|
|
33
40
|
const rawItems = await dbQuery;
|
|
34
41
|
if (!rawItems)
|
|
35
42
|
return null;
|
|
@@ -72,7 +79,7 @@ export async function runAst(originalAST, schema, options) {
|
|
|
72
79
|
page: null,
|
|
73
80
|
},
|
|
74
81
|
});
|
|
75
|
-
nestedItems = (await runAst(node, schema, { knex, nested: true }));
|
|
82
|
+
nestedItems = (await runAst(node, schema, accountability, { knex, nested: true }));
|
|
76
83
|
if (nestedItems) {
|
|
77
84
|
items = mergeWithParentItems(schema, nestedItems, items, nestedNode, fieldAllowed);
|
|
78
85
|
}
|
|
@@ -86,7 +93,7 @@ export async function runAst(originalAST, schema, options) {
|
|
|
86
93
|
const node = merge({}, nestedNode, {
|
|
87
94
|
query: { limit: -1 },
|
|
88
95
|
});
|
|
89
|
-
nestedItems = (await runAst(node, schema, { knex, nested: true }));
|
|
96
|
+
nestedItems = (await runAst(node, schema, accountability, { knex, nested: true }));
|
|
90
97
|
if (nestedItems) {
|
|
91
98
|
// Merge all fetched nested records with the parent items
|
|
92
99
|
items = mergeWithParentItems(schema, nestedItems, items, nestedNode, true);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Filter, SchemaOverview } from '@directus/types';
|
|
1
|
+
import type { Filter, Permission, SchemaOverview } from '@directus/types';
|
|
2
2
|
import type { Knex } from 'knex';
|
|
3
3
|
import type { AliasMap } from '../../../utils/get-column-path.js';
|
|
4
4
|
export interface ApplyCaseWhenOptions {
|
|
@@ -8,9 +8,10 @@ export interface ApplyCaseWhenOptions {
|
|
|
8
8
|
cases: Filter[];
|
|
9
9
|
aliasMap: AliasMap;
|
|
10
10
|
alias?: string;
|
|
11
|
+
permissions: Permission[];
|
|
11
12
|
}
|
|
12
13
|
export interface ApplyCaseWhenContext {
|
|
13
14
|
knex: Knex;
|
|
14
15
|
schema: SchemaOverview;
|
|
15
16
|
}
|
|
16
|
-
export declare function applyCaseWhen({ columnCases, table, aliasMap, cases, column, alias }: ApplyCaseWhenOptions, { knex, schema }: ApplyCaseWhenContext): Knex.Raw;
|
|
17
|
+
export declare function applyCaseWhen({ columnCases, table, aliasMap, cases, column, alias, permissions }: ApplyCaseWhenOptions, { knex, schema }: ApplyCaseWhenContext): Knex.Raw;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { applyFilter } from '../../../utils/apply-query.js';
|
|
2
|
-
export function applyCaseWhen({ columnCases, table, aliasMap, cases, column, alias }, { knex, schema }) {
|
|
2
|
+
export function applyCaseWhen({ columnCases, table, aliasMap, cases, column, alias, permissions }, { knex, schema }) {
|
|
3
3
|
const caseQuery = knex.queryBuilder();
|
|
4
|
-
applyFilter(knex, schema, caseQuery, { _or: columnCases }, table, aliasMap, cases);
|
|
4
|
+
applyFilter(knex, schema, caseQuery, { _or: columnCases }, table, aliasMap, cases, permissions);
|
|
5
5
|
const compiler = knex.client.queryCompiler(caseQuery);
|
|
6
6
|
const sqlParts = [];
|
|
7
7
|
// Only empty filters, so no where was generated, skip it
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Filter, SchemaOverview } from '@directus/types';
|
|
1
|
+
import type { Filter, Permission, SchemaOverview } from '@directus/types';
|
|
2
2
|
import type { Knex } from 'knex';
|
|
3
3
|
import type { FieldNode, FunctionFieldNode, M2ONode } from '../../../types/ast.js';
|
|
4
4
|
import type { AliasMap } from '../../../utils/get-column-path.js';
|
|
@@ -6,5 +6,5 @@ interface NodePreProcessOptions {
|
|
|
6
6
|
/** Don't assign an alias to the column but instead return the column as is */
|
|
7
7
|
noAlias?: boolean;
|
|
8
8
|
}
|
|
9
|
-
export declare function getColumnPreprocessor(knex: Knex, schema: SchemaOverview, table: string, cases: Filter[], aliasMap: AliasMap): (fieldNode: FieldNode | FunctionFieldNode | M2ONode, options?: NodePreProcessOptions) => Knex.Raw<string>;
|
|
9
|
+
export declare function getColumnPreprocessor(knex: Knex, schema: SchemaOverview, table: string, cases: Filter[], permissions: Permission[], aliasMap: AliasMap): (fieldNode: FieldNode | FunctionFieldNode | M2ONode, options?: NodePreProcessOptions) => Knex.Raw<string>;
|
|
10
10
|
export {};
|
|
@@ -4,7 +4,7 @@ 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';
|
|
6
6
|
import { getNodeAlias } from './get-field-alias.js';
|
|
7
|
-
export function getColumnPreprocessor(knex, schema, table, cases, aliasMap) {
|
|
7
|
+
export function getColumnPreprocessor(knex, schema, table, cases, permissions, aliasMap) {
|
|
8
8
|
const helpers = getHelpers(knex);
|
|
9
9
|
return function (fieldNode, options) {
|
|
10
10
|
// Don't assign an alias to the column expression if the field has a whenCase
|
|
@@ -32,6 +32,7 @@ export function getColumnPreprocessor(knex, schema, table, cases, aliasMap) {
|
|
|
32
32
|
...fieldNode.query,
|
|
33
33
|
filter: joinFilterWithCases(fieldNode.query.filter, fieldNode.cases),
|
|
34
34
|
},
|
|
35
|
+
permissions,
|
|
35
36
|
cases: fieldNode.cases,
|
|
36
37
|
});
|
|
37
38
|
}
|
|
@@ -50,6 +51,7 @@ export function getColumnPreprocessor(knex, schema, table, cases, aliasMap) {
|
|
|
50
51
|
cases,
|
|
51
52
|
table,
|
|
52
53
|
alias,
|
|
54
|
+
permissions,
|
|
53
55
|
}, { knex, schema });
|
|
54
56
|
}
|
|
55
57
|
return column;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Filter, SchemaOverview } from '@directus/types';
|
|
1
|
+
import type { Filter, Permission, SchemaOverview } from '@directus/types';
|
|
2
2
|
import type { Knex } from 'knex';
|
|
3
3
|
import type { FieldNode, FunctionFieldNode, M2ONode, O2MNode } from '../../../types/index.js';
|
|
4
4
|
import type { AliasMap } from '../../../utils/get-column-path.js';
|
|
5
|
-
export declare function getInnerQueryColumnPreProcessor(knex: Knex, schema: SchemaOverview, table: string, cases: Filter[], aliasMap: AliasMap, aliasPrefix: string): (fieldNode: FieldNode | FunctionFieldNode | M2ONode | O2MNode) => Knex.Raw<string> | null;
|
|
5
|
+
export declare function getInnerQueryColumnPreProcessor(knex: Knex, schema: SchemaOverview, table: string, cases: Filter[], permissions: Permission[], aliasMap: AliasMap, aliasPrefix: string): (fieldNode: FieldNode | FunctionFieldNode | M2ONode | O2MNode) => Knex.Raw<string> | null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { applyCaseWhen } from './apply-case-when.js';
|
|
2
2
|
import { getNodeAlias } from './get-field-alias.js';
|
|
3
|
-
export function getInnerQueryColumnPreProcessor(knex, schema, table, cases, aliasMap, aliasPrefix) {
|
|
3
|
+
export function getInnerQueryColumnPreProcessor(knex, schema, table, cases, permissions, aliasMap, aliasPrefix) {
|
|
4
4
|
return function (fieldNode) {
|
|
5
5
|
const alias = getNodeAlias(fieldNode);
|
|
6
6
|
if (fieldNode.whenCase && fieldNode.whenCase.length > 0) {
|
|
@@ -15,6 +15,7 @@ export function getInnerQueryColumnPreProcessor(knex, schema, table, cases, alia
|
|
|
15
15
|
aliasMap,
|
|
16
16
|
cases,
|
|
17
17
|
table,
|
|
18
|
+
permissions,
|
|
18
19
|
}, { knex, schema });
|
|
19
20
|
return knex.raw('COUNT(??) AS ??', [caseWhen, `${aliasPrefix}_${alias}`]);
|
|
20
21
|
}
|
|
@@ -156,7 +156,7 @@ export class ExtensionManager {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
if (this.options.watch && !wasWatcherInitialized) {
|
|
159
|
-
this.updateWatchedExtensions(
|
|
159
|
+
this.updateWatchedExtensions([...this.extensions]);
|
|
160
160
|
}
|
|
161
161
|
this.messenger.subscribe(this.reloadChannel, (payload) => {
|
|
162
162
|
// Ignore requests for reloading that were published by the current process
|
|
@@ -327,7 +327,7 @@ export class ExtensionManager {
|
|
|
327
327
|
const extensionDir = path.resolve(getExtensionsPath());
|
|
328
328
|
const registryDir = path.join(extensionDir, '.registry');
|
|
329
329
|
const toPackageExtensionPaths = (extensions) => extensions
|
|
330
|
-
.filter((extension) => extension.local &&
|
|
330
|
+
.filter((extension) => extension.local && !extension.path.startsWith(registryDir))
|
|
331
331
|
.flatMap((extension) => isTypeIn(extension, HYBRID_EXTENSION_TYPES) || extension.type === 'bundle'
|
|
332
332
|
? [
|
|
333
333
|
path.resolve(extension.path, extension.entrypoint.app),
|
package/dist/logger/index.d.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
/// <reference types="qs" />
|
|
2
2
|
import type { RequestHandler } from 'express';
|
|
3
3
|
import { type Logger } from 'pino';
|
|
4
|
+
import { LogsStream } from './logs-stream.js';
|
|
4
5
|
export declare const _cache: {
|
|
5
6
|
logger: Logger<never> | undefined;
|
|
7
|
+
logsStream: LogsStream | undefined;
|
|
8
|
+
httpLogsStream: LogsStream | undefined;
|
|
6
9
|
};
|
|
7
10
|
export declare const useLogger: () => Logger<never>;
|
|
11
|
+
export declare const getLogsStream: (pretty: boolean) => LogsStream;
|
|
12
|
+
export declare const getHttpLogsStream: (pretty: boolean) => LogsStream;
|
|
13
|
+
export declare const getLoggerLevelValue: (level: string) => number;
|
|
8
14
|
export declare const createLogger: () => Logger<never>;
|
|
9
15
|
export declare const createExpressLogger: () => RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
package/dist/logger/index.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
|
-
import { REDACTED_TEXT, toArray } from '@directus/utils';
|
|
2
|
+
import { REDACTED_TEXT, toArray, toBoolean } from '@directus/utils';
|
|
3
3
|
import { merge } from 'lodash-es';
|
|
4
4
|
import { URL } from 'node:url';
|
|
5
5
|
import { pino } from 'pino';
|
|
6
6
|
import { pinoHttp, stdSerializers } from 'pino-http';
|
|
7
|
+
import { httpPrintFactory } from 'pino-http-print';
|
|
8
|
+
import { build as pinoPretty } from 'pino-pretty';
|
|
7
9
|
import { getConfigFromEnv } from '../utils/get-config-from-env.js';
|
|
10
|
+
import { LogsStream } from './logs-stream.js';
|
|
8
11
|
import { redactQuery } from './redact-query.js';
|
|
9
|
-
export const _cache = { logger: undefined };
|
|
12
|
+
export const _cache = { logger: undefined, logsStream: undefined, httpLogsStream: undefined };
|
|
10
13
|
export const useLogger = () => {
|
|
11
14
|
if (_cache.logger) {
|
|
12
15
|
return _cache.logger;
|
|
@@ -14,6 +17,23 @@ export const useLogger = () => {
|
|
|
14
17
|
_cache.logger = createLogger();
|
|
15
18
|
return _cache.logger;
|
|
16
19
|
};
|
|
20
|
+
export const getLogsStream = (pretty) => {
|
|
21
|
+
if (_cache.logsStream) {
|
|
22
|
+
return _cache.logsStream;
|
|
23
|
+
}
|
|
24
|
+
_cache.logsStream = new LogsStream(pretty ? 'basic' : false);
|
|
25
|
+
return _cache.logsStream;
|
|
26
|
+
};
|
|
27
|
+
export const getHttpLogsStream = (pretty) => {
|
|
28
|
+
if (_cache.httpLogsStream) {
|
|
29
|
+
return _cache.httpLogsStream;
|
|
30
|
+
}
|
|
31
|
+
_cache.httpLogsStream = new LogsStream(pretty ? 'http' : false);
|
|
32
|
+
return _cache.httpLogsStream;
|
|
33
|
+
};
|
|
34
|
+
export const getLoggerLevelValue = (level) => {
|
|
35
|
+
return pino.levels.values[level] || pino.levels.values['info'];
|
|
36
|
+
};
|
|
17
37
|
export const createLogger = () => {
|
|
18
38
|
const env = useEnv();
|
|
19
39
|
const pinoOptions = {
|
|
@@ -23,15 +43,6 @@ export const createLogger = () => {
|
|
|
23
43
|
censor: REDACTED_TEXT,
|
|
24
44
|
},
|
|
25
45
|
};
|
|
26
|
-
if (env['LOG_STYLE'] !== 'raw') {
|
|
27
|
-
pinoOptions.transport = {
|
|
28
|
-
target: 'pino-pretty',
|
|
29
|
-
options: {
|
|
30
|
-
ignore: 'hostname,pid',
|
|
31
|
-
sync: true,
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
46
|
const loggerEnvConfig = getConfigFromEnv('LOGGER_', 'LOGGER_HTTP');
|
|
36
47
|
// Expose custom log levels into formatter function
|
|
37
48
|
if (loggerEnvConfig['levels']) {
|
|
@@ -50,7 +61,33 @@ export const createLogger = () => {
|
|
|
50
61
|
};
|
|
51
62
|
delete loggerEnvConfig['levels'];
|
|
52
63
|
}
|
|
53
|
-
|
|
64
|
+
const mergedOptions = merge(pinoOptions, loggerEnvConfig);
|
|
65
|
+
const streams = [];
|
|
66
|
+
// Console Logs
|
|
67
|
+
if (env['LOG_STYLE'] !== 'raw') {
|
|
68
|
+
streams.push({
|
|
69
|
+
level: mergedOptions.level,
|
|
70
|
+
stream: pinoPretty({
|
|
71
|
+
ignore: 'hostname,pid',
|
|
72
|
+
sync: true,
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
streams.push({ level: mergedOptions.level, stream: process.stdout });
|
|
78
|
+
}
|
|
79
|
+
// WebSocket Logs
|
|
80
|
+
if (toBoolean(env['WEBSOCKETS_LOGS_ENABLED'])) {
|
|
81
|
+
const wsLevel = env['WEBSOCKETS_LOGS_LEVEL'] || 'info';
|
|
82
|
+
if (getLoggerLevelValue(wsLevel) < getLoggerLevelValue(mergedOptions.level)) {
|
|
83
|
+
mergedOptions.level = wsLevel;
|
|
84
|
+
}
|
|
85
|
+
streams.push({
|
|
86
|
+
level: wsLevel,
|
|
87
|
+
stream: getLogsStream(env['WEBSOCKETS_LOGS_STYLE'] !== 'raw'),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return pino(mergedOptions, pino.multistream(streams));
|
|
54
91
|
};
|
|
55
92
|
export const createExpressLogger = () => {
|
|
56
93
|
const env = useEnv();
|
|
@@ -63,21 +100,7 @@ export const createExpressLogger = () => {
|
|
|
63
100
|
censor: REDACTED_TEXT,
|
|
64
101
|
},
|
|
65
102
|
};
|
|
66
|
-
if (env['LOG_STYLE']
|
|
67
|
-
httpLoggerOptions.transport = {
|
|
68
|
-
target: 'pino-http-print',
|
|
69
|
-
options: {
|
|
70
|
-
all: true,
|
|
71
|
-
translateTime: 'SYS:HH:MM:ss',
|
|
72
|
-
relativeUrl: true,
|
|
73
|
-
prettyOptions: {
|
|
74
|
-
ignore: 'hostname,pid',
|
|
75
|
-
sync: true,
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
if (env['LOG_STYLE'] === 'raw') {
|
|
103
|
+
if (env['LOG_STYLE'] === 'raw' || toBoolean(env['WEBSOCKETS_LOGS_ENABLED'])) {
|
|
81
104
|
httpLoggerOptions.redact = {
|
|
82
105
|
paths: ['req.headers.authorization', 'req.headers.cookie', 'res.headers', 'req.query.access_token'],
|
|
83
106
|
censor: (value, pathParts) => {
|
|
@@ -120,8 +143,36 @@ export const createExpressLogger = () => {
|
|
|
120
143
|
},
|
|
121
144
|
};
|
|
122
145
|
}
|
|
146
|
+
const mergedHttpOptions = merge(httpLoggerOptions, loggerEnvConfig);
|
|
147
|
+
const streams = [];
|
|
148
|
+
if (env['LOG_STYLE'] !== 'raw') {
|
|
149
|
+
const pinoHttpPretty = httpPrintFactory({
|
|
150
|
+
all: true,
|
|
151
|
+
translateTime: 'SYS:HH:MM:ss',
|
|
152
|
+
relativeUrl: true,
|
|
153
|
+
prettyOptions: {
|
|
154
|
+
ignore: 'hostname,pid',
|
|
155
|
+
sync: true,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
streams.push({ level: mergedHttpOptions.level, stream: pinoHttpPretty(process.stdout) });
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
streams.push({ level: mergedHttpOptions.level, stream: process.stdout });
|
|
162
|
+
}
|
|
163
|
+
// WebSocket Logs
|
|
164
|
+
if (toBoolean(env['WEBSOCKETS_LOGS_ENABLED'])) {
|
|
165
|
+
const wsLevel = env['WEBSOCKETS_LOGS_LEVEL'] || 'info';
|
|
166
|
+
if (getLoggerLevelValue(wsLevel) < getLoggerLevelValue(mergedHttpOptions.level)) {
|
|
167
|
+
mergedHttpOptions.level = wsLevel;
|
|
168
|
+
}
|
|
169
|
+
streams.push({
|
|
170
|
+
level: wsLevel,
|
|
171
|
+
stream: getHttpLogsStream(env['WEBSOCKETS_LOGS_STYLE'] !== 'raw'),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
123
174
|
return pinoHttp({
|
|
124
|
-
logger: pino(
|
|
175
|
+
logger: pino(mergedHttpOptions, pino.multistream(streams)),
|
|
125
176
|
...httpLoggerEnvConfig,
|
|
126
177
|
serializers: {
|
|
127
178
|
req(request) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import type { Bus } from '@directus/memory';
|
|
3
|
+
import { Writable } from 'stream';
|
|
4
|
+
type PrettyType = 'basic' | 'http' | false;
|
|
5
|
+
export declare class LogsStream extends Writable {
|
|
6
|
+
messenger: Bus;
|
|
7
|
+
pretty: PrettyType;
|
|
8
|
+
constructor(pretty: PrettyType);
|
|
9
|
+
_write(chunk: string, _encoding: string, callback: (error?: Error | null) => void): void;
|
|
10
|
+
}
|
|
11
|
+
export {};
|