@directus/api 13.2.0 → 14.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/__utils__/snapshots.js +9 -0
- package/dist/app.js +2 -0
- package/dist/cli/index.js +3 -4
- package/dist/cli/load-extensions.d.ts +1 -0
- package/dist/cli/load-extensions.js +19 -0
- package/dist/controllers/extensions.js +28 -19
- package/dist/controllers/versions.d.ts +2 -0
- package/dist/controllers/versions.js +188 -0
- package/dist/database/migrations/20230823A-add-content-versioning.d.ts +3 -0
- package/dist/database/migrations/20230823A-add-content-versioning.js +36 -0
- package/dist/database/migrations/20230927A-themes.d.ts +3 -0
- package/dist/database/migrations/20230927A-themes.js +49 -0
- package/dist/database/migrations/20231009B-update-panel-options.d.ts +3 -0
- package/dist/database/migrations/20231009B-update-panel-options.js +77 -0
- package/dist/database/migrations/20231010A-add-extensions.d.ts +3 -0
- package/dist/database/migrations/20231010A-add-extensions.js +9 -0
- package/dist/database/run-ast.js +1 -1
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +5 -1
- package/dist/database/system-data/collections/collections.yaml +6 -0
- package/dist/database/system-data/fields/activity.yaml +4 -4
- package/dist/database/system-data/fields/collections.yaml +19 -0
- package/dist/database/system-data/fields/extensions.yaml +10 -0
- package/dist/database/system-data/fields/revisions.yaml +3 -0
- package/dist/database/system-data/fields/settings.yaml +73 -17
- package/dist/database/system-data/fields/users.yaml +50 -12
- package/dist/database/system-data/fields/versions.yaml +38 -0
- package/dist/database/system-data/fields/webhooks.yaml +9 -9
- package/dist/database/system-data/relations/relations.yaml +88 -20
- package/dist/emitter.d.ts +2 -2
- package/dist/env.js +4 -0
- package/dist/extensions/lib/get-extensions-settings.d.ts +7 -0
- package/dist/extensions/lib/get-extensions-settings.js +39 -0
- package/dist/extensions/lib/get-extensions.d.ts +1 -0
- package/dist/extensions/lib/get-extensions.js +11 -0
- package/dist/extensions/{get-shared-deps-mapping.js → lib/get-shared-deps-mapping.js} +3 -3
- package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +31 -0
- package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.js +80 -0
- package/dist/extensions/lib/sandbox/generate-host-function-reference.d.ts +11 -0
- package/dist/extensions/lib/sandbox/generate-host-function-reference.js +28 -0
- package/dist/extensions/lib/sandbox/register/action.d.ts +6 -0
- package/dist/extensions/lib/sandbox/register/action.js +18 -0
- package/dist/extensions/lib/sandbox/register/call-reference.d.ts +5 -0
- package/dist/extensions/lib/sandbox/register/call-reference.js +20 -0
- package/dist/extensions/lib/sandbox/register/filter.d.ts +6 -0
- package/dist/extensions/lib/sandbox/register/filter.js +21 -0
- package/dist/extensions/lib/sandbox/register/index.d.ts +5 -0
- package/dist/extensions/lib/sandbox/register/index.js +5 -0
- package/dist/extensions/lib/sandbox/register/operation.d.ts +6 -0
- package/dist/extensions/lib/sandbox/register/operation.js +19 -0
- package/dist/extensions/lib/sandbox/register/route.d.ts +17 -0
- package/dist/extensions/lib/sandbox/register/route.js +44 -0
- package/dist/extensions/lib/sandbox/sdk/generators/index.d.ts +3 -0
- package/dist/extensions/lib/sandbox/sdk/generators/index.js +3 -0
- package/dist/extensions/lib/sandbox/sdk/generators/log.d.ts +3 -0
- package/dist/extensions/lib/sandbox/sdk/generators/log.js +11 -0
- package/dist/extensions/lib/sandbox/sdk/generators/request.d.ts +12 -0
- package/dist/extensions/lib/sandbox/sdk/generators/request.js +49 -0
- package/dist/extensions/lib/sandbox/sdk/generators/sleep.d.ts +3 -0
- package/dist/extensions/lib/sandbox/sdk/generators/sleep.js +11 -0
- package/dist/extensions/lib/sandbox/sdk/index.d.ts +2 -0
- package/dist/extensions/lib/sandbox/sdk/index.js +2 -0
- package/dist/extensions/lib/sandbox/sdk/instantiate.d.ts +11 -0
- package/dist/extensions/lib/sandbox/sdk/instantiate.js +28 -0
- package/dist/extensions/lib/sandbox/sdk/sdk.d.ts +20 -0
- package/dist/extensions/lib/sandbox/sdk/sdk.js +11 -0
- package/dist/extensions/lib/sandbox/sdk/utils/index.d.ts +1 -0
- package/dist/extensions/lib/sandbox/sdk/utils/index.js +1 -0
- package/dist/extensions/lib/sandbox/sdk/utils/wrap.d.ts +11 -0
- package/dist/extensions/lib/sandbox/sdk/utils/wrap.js +17 -0
- package/dist/extensions/manager.d.ts +128 -14
- package/dist/extensions/manager.js +310 -136
- package/dist/extensions/types.d.ts +1 -5
- package/dist/flows.d.ts +1 -1
- package/dist/flows.js +6 -6
- package/dist/middleware/respond.js +12 -0
- package/dist/server.js +2 -1
- package/dist/services/assets.js +1 -1
- package/dist/services/extensions.d.ts +31 -0
- package/dist/services/extensions.js +136 -0
- package/dist/services/graphql/index.d.ts +1 -1
- package/dist/services/graphql/index.js +87 -24
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +2 -0
- package/dist/services/server.js +3 -1
- package/dist/services/users.js +2 -0
- package/dist/services/versions.d.ts +21 -0
- package/dist/services/versions.js +238 -0
- package/dist/types/collection.d.ts +1 -0
- package/dist/utils/apply-query.d.ts +1 -1
- package/dist/utils/apply-query.js +30 -2
- package/dist/utils/delete-from-require-cache.d.ts +1 -0
- package/dist/utils/delete-from-require-cache.js +5 -0
- package/dist/utils/get-service.js +3 -1
- package/dist/utils/import-file-url.d.ts +5 -0
- package/dist/utils/import-file-url.js +6 -0
- package/dist/utils/job-queue.d.ts +2 -3
- package/dist/utils/redact-object.d.ts +1 -1
- package/dist/utils/redact-object.js +37 -24
- package/dist/utils/sanitize-query.js +3 -0
- package/dist/utils/validate-query.js +1 -0
- package/dist/worker-pool.js +8 -0
- package/package.json +28 -27
- package/dist/extensions/get-extensions.d.ts +0 -47
- package/dist/extensions/get-extensions.js +0 -9
- package/dist/extensions/normalize-extension-info.d.ts +0 -5
- package/dist/extensions/normalize-extension-info.js +0 -30
- /package/dist/extensions/{get-shared-deps-mapping.d.ts → lib/get-shared-deps-mapping.d.ts} +0 -0
- /package/dist/extensions/{wrap-embeds.d.ts → lib/wrap-embeds.d.ts} +0 -0
- /package/dist/extensions/{wrap-embeds.js → lib/wrap-embeds.js} +0 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { Action } from '@directus/constants';
|
|
2
|
+
import { InvalidPayloadError, UnprocessableContentError } from '@directus/errors';
|
|
3
|
+
import Joi from 'joi';
|
|
4
|
+
import { assign, pick } from 'lodash-es';
|
|
5
|
+
import objectHash from 'object-hash';
|
|
6
|
+
import { getCache } from '../cache.js';
|
|
7
|
+
import getDatabase from '../database/index.js';
|
|
8
|
+
import emitter from '../emitter.js';
|
|
9
|
+
import { shouldClearCache } from '../utils/should-clear-cache.js';
|
|
10
|
+
import { ActivityService } from './activity.js';
|
|
11
|
+
import { AuthorizationService } from './authorization.js';
|
|
12
|
+
import { ItemsService } from './items.js';
|
|
13
|
+
import { PayloadService } from './payload.js';
|
|
14
|
+
import { RevisionsService } from './revisions.js';
|
|
15
|
+
export class VersionsService extends ItemsService {
|
|
16
|
+
authorizationService;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
super('directus_versions', options);
|
|
19
|
+
this.authorizationService = new AuthorizationService({
|
|
20
|
+
accountability: this.accountability,
|
|
21
|
+
knex: this.knex,
|
|
22
|
+
schema: this.schema,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async validateCreateData(data) {
|
|
26
|
+
if (!data['key'])
|
|
27
|
+
throw new InvalidPayloadError({ reason: `"key" is required` });
|
|
28
|
+
// Reserves the "main" version key for the version query parameter
|
|
29
|
+
if (data['key'] === 'main')
|
|
30
|
+
throw new InvalidPayloadError({ reason: `"main" is a reserved version key` });
|
|
31
|
+
if (!data['collection']) {
|
|
32
|
+
throw new InvalidPayloadError({ reason: `"collection" is required` });
|
|
33
|
+
}
|
|
34
|
+
if (!data['item'])
|
|
35
|
+
throw new InvalidPayloadError({ reason: `"item" is required` });
|
|
36
|
+
const { CollectionsService } = await import('./collections.js');
|
|
37
|
+
const collectionsService = new CollectionsService({
|
|
38
|
+
accountability: null,
|
|
39
|
+
knex: this.knex,
|
|
40
|
+
schema: this.schema,
|
|
41
|
+
});
|
|
42
|
+
const existingCollection = await collectionsService.readOne(data['collection']);
|
|
43
|
+
if (!existingCollection.meta?.versioning) {
|
|
44
|
+
throw new UnprocessableContentError({
|
|
45
|
+
reason: `Content Versioning is not enabled for collection "${data['collection']}"`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const existingVersions = await super.readByQuery({
|
|
49
|
+
aggregate: { count: ['*'] },
|
|
50
|
+
filter: { key: { _eq: data['key'] }, collection: { _eq: data['collection'] }, item: { _eq: data['item'] } },
|
|
51
|
+
});
|
|
52
|
+
if (existingVersions[0]['count'] > 0) {
|
|
53
|
+
throw new UnprocessableContentError({
|
|
54
|
+
reason: `Version "${data['key']}" already exists for item "${data['item']}" in collection "${data['collection']}"`,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// will throw an error if the accountability does not have permission to read the item
|
|
58
|
+
await this.authorizationService.checkAccess('read', data['collection'], data['item']);
|
|
59
|
+
}
|
|
60
|
+
async getMainItem(collection, item, query) {
|
|
61
|
+
// will throw an error if the accountability does not have permission to read the item
|
|
62
|
+
await this.authorizationService.checkAccess('read', collection, item);
|
|
63
|
+
const itemsService = new ItemsService(collection, {
|
|
64
|
+
knex: this.knex,
|
|
65
|
+
accountability: this.accountability,
|
|
66
|
+
schema: this.schema,
|
|
67
|
+
});
|
|
68
|
+
return await itemsService.readOne(item, query);
|
|
69
|
+
}
|
|
70
|
+
async verifyHash(collection, item, hash) {
|
|
71
|
+
const mainItem = await this.getMainItem(collection, item);
|
|
72
|
+
const mainHash = objectHash(mainItem);
|
|
73
|
+
return { outdated: hash !== mainHash, mainHash };
|
|
74
|
+
}
|
|
75
|
+
async getVersionSavesById(id) {
|
|
76
|
+
const revisionsService = new RevisionsService({
|
|
77
|
+
knex: this.knex,
|
|
78
|
+
schema: this.schema,
|
|
79
|
+
});
|
|
80
|
+
const result = await revisionsService.readByQuery({
|
|
81
|
+
filter: { version: { _eq: id } },
|
|
82
|
+
});
|
|
83
|
+
return result.map((revision) => revision['delta']);
|
|
84
|
+
}
|
|
85
|
+
async getVersionSaves(key, collection, item) {
|
|
86
|
+
const filter = {
|
|
87
|
+
key: { _eq: key },
|
|
88
|
+
collection: { _eq: collection },
|
|
89
|
+
};
|
|
90
|
+
if (item) {
|
|
91
|
+
filter['item'] = { _eq: item };
|
|
92
|
+
}
|
|
93
|
+
const versions = await this.readByQuery({ filter });
|
|
94
|
+
if (!versions?.[0])
|
|
95
|
+
return null;
|
|
96
|
+
const saves = await this.getVersionSavesById(versions[0]['id']);
|
|
97
|
+
return saves;
|
|
98
|
+
}
|
|
99
|
+
async createOne(data, opts) {
|
|
100
|
+
await this.validateCreateData(data);
|
|
101
|
+
const mainItem = await this.getMainItem(data['collection'], data['item']);
|
|
102
|
+
data['hash'] = objectHash(mainItem);
|
|
103
|
+
return super.createOne(data, opts);
|
|
104
|
+
}
|
|
105
|
+
async createMany(data, opts) {
|
|
106
|
+
if (!Array.isArray(data)) {
|
|
107
|
+
throw new InvalidPayloadError({ reason: 'Input should be an array of items' });
|
|
108
|
+
}
|
|
109
|
+
const keyCombos = new Set();
|
|
110
|
+
for (const item of data) {
|
|
111
|
+
await this.validateCreateData(item);
|
|
112
|
+
const keyCombo = `${item['key']}-${item['collection']}-${item['item']}`;
|
|
113
|
+
if (keyCombos.has(keyCombo)) {
|
|
114
|
+
throw new UnprocessableContentError({
|
|
115
|
+
reason: `Cannot create multiple versions on "${item['item']}" in collection "${item['collection']}" with the same key "${item['key']}"`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
keyCombos.add(keyCombo);
|
|
119
|
+
const mainItem = await this.getMainItem(item['collection'], item['item']);
|
|
120
|
+
item['hash'] = objectHash(mainItem);
|
|
121
|
+
}
|
|
122
|
+
return super.createMany(data, opts);
|
|
123
|
+
}
|
|
124
|
+
async updateMany(keys, data, opts) {
|
|
125
|
+
// Only allow updates on "key" and "name" fields
|
|
126
|
+
const versionUpdateSchema = Joi.object({
|
|
127
|
+
key: Joi.string(),
|
|
128
|
+
name: Joi.string().allow(null).optional(),
|
|
129
|
+
});
|
|
130
|
+
const { error } = versionUpdateSchema.validate(data);
|
|
131
|
+
if (error)
|
|
132
|
+
throw new InvalidPayloadError({ reason: error.message });
|
|
133
|
+
if ('key' in data) {
|
|
134
|
+
// Reserves the "main" version key for the version query parameter
|
|
135
|
+
if (data['key'] === 'main')
|
|
136
|
+
throw new InvalidPayloadError({ reason: `"main" is a reserved version key` });
|
|
137
|
+
const keyCombos = new Set();
|
|
138
|
+
for (const pk of keys) {
|
|
139
|
+
const { collection, item } = await this.readOne(pk, { fields: ['collection', 'item'] });
|
|
140
|
+
const keyCombo = `${data['key']}-${collection}-${item}`;
|
|
141
|
+
if (keyCombos.has(keyCombo)) {
|
|
142
|
+
throw new UnprocessableContentError({
|
|
143
|
+
reason: `Cannot update multiple versions on "${item}" in collection "${collection}" to the same key "${data['key']}"`,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
keyCombos.add(keyCombo);
|
|
147
|
+
const existingVersions = await super.readByQuery({
|
|
148
|
+
aggregate: { count: ['*'] },
|
|
149
|
+
filter: { id: { _neq: pk }, key: { _eq: data['key'] }, collection: { _eq: collection }, item: { _eq: item } },
|
|
150
|
+
});
|
|
151
|
+
if (existingVersions[0]['count'] > 0) {
|
|
152
|
+
throw new UnprocessableContentError({
|
|
153
|
+
reason: `Version "${data['key']}" already exists for item "${item}" in collection "${collection}"`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return super.updateMany(keys, data, opts);
|
|
159
|
+
}
|
|
160
|
+
async save(key, data) {
|
|
161
|
+
const version = await super.readOne(key);
|
|
162
|
+
const payloadService = new PayloadService(this.collection, {
|
|
163
|
+
accountability: this.accountability,
|
|
164
|
+
knex: this.knex,
|
|
165
|
+
schema: this.schema,
|
|
166
|
+
});
|
|
167
|
+
const activityService = new ActivityService({
|
|
168
|
+
knex: this.knex,
|
|
169
|
+
schema: this.schema,
|
|
170
|
+
});
|
|
171
|
+
const revisionsService = new RevisionsService({
|
|
172
|
+
knex: this.knex,
|
|
173
|
+
schema: this.schema,
|
|
174
|
+
});
|
|
175
|
+
const activity = await activityService.createOne({
|
|
176
|
+
action: Action.VERSION_SAVE,
|
|
177
|
+
user: this.accountability?.user ?? null,
|
|
178
|
+
collection: version['collection'],
|
|
179
|
+
ip: this.accountability?.ip ?? null,
|
|
180
|
+
user_agent: this.accountability?.userAgent ?? null,
|
|
181
|
+
origin: this.accountability?.origin ?? null,
|
|
182
|
+
item: version['item'],
|
|
183
|
+
});
|
|
184
|
+
const revisionDelta = await payloadService.prepareDelta(data);
|
|
185
|
+
await revisionsService.createOne({
|
|
186
|
+
activity,
|
|
187
|
+
version: key,
|
|
188
|
+
collection: version['collection'],
|
|
189
|
+
item: version['item'],
|
|
190
|
+
data: revisionDelta,
|
|
191
|
+
delta: revisionDelta,
|
|
192
|
+
});
|
|
193
|
+
const { cache } = getCache();
|
|
194
|
+
if (shouldClearCache(cache, undefined, version['collection'])) {
|
|
195
|
+
cache.clear();
|
|
196
|
+
}
|
|
197
|
+
return data;
|
|
198
|
+
}
|
|
199
|
+
async promote(version, mainHash, fields) {
|
|
200
|
+
const { id, collection, item } = (await this.readOne(version));
|
|
201
|
+
// will throw an error if the accountability does not have permission to update the item
|
|
202
|
+
await this.authorizationService.checkAccess('update', collection, item);
|
|
203
|
+
const { outdated } = await this.verifyHash(collection, item, mainHash);
|
|
204
|
+
if (outdated) {
|
|
205
|
+
throw new UnprocessableContentError({
|
|
206
|
+
reason: `Main item has changed since this version was last updated`,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const saves = await this.getVersionSavesById(id);
|
|
210
|
+
const versionResult = assign({}, ...saves);
|
|
211
|
+
const payloadToUpdate = fields ? pick(versionResult, fields) : versionResult;
|
|
212
|
+
const itemsService = new ItemsService(collection, {
|
|
213
|
+
accountability: this.accountability,
|
|
214
|
+
schema: this.schema,
|
|
215
|
+
});
|
|
216
|
+
const payloadAfterHooks = await emitter.emitFilter(['items.promote', `${collection}.items.promote`], payloadToUpdate, {
|
|
217
|
+
collection,
|
|
218
|
+
item,
|
|
219
|
+
version,
|
|
220
|
+
}, {
|
|
221
|
+
database: getDatabase(),
|
|
222
|
+
schema: this.schema,
|
|
223
|
+
accountability: this.accountability,
|
|
224
|
+
});
|
|
225
|
+
const updatedItemKey = await itemsService.updateOne(item, payloadAfterHooks);
|
|
226
|
+
emitter.emitAction(['items.promote', `${collection}.items.promote`], {
|
|
227
|
+
payload: payloadAfterHooks,
|
|
228
|
+
collection,
|
|
229
|
+
item: updatedItemKey,
|
|
230
|
+
version,
|
|
231
|
+
}, {
|
|
232
|
+
database: getDatabase(),
|
|
233
|
+
schema: this.schema,
|
|
234
|
+
accountability: this.accountability,
|
|
235
|
+
});
|
|
236
|
+
return updatedItemKey;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -18,7 +18,7 @@ export type ColumnSortRecord = {
|
|
|
18
18
|
order: 'asc' | 'desc';
|
|
19
19
|
column: string;
|
|
20
20
|
};
|
|
21
|
-
export declare function applySort(knex: Knex, schema: SchemaOverview, rootQuery: Knex.QueryBuilder,
|
|
21
|
+
export declare function applySort(knex: Knex, schema: SchemaOverview, rootQuery: Knex.QueryBuilder, query: Query, collection: string, aliasMap: AliasMap, returnRecords?: boolean): {
|
|
22
22
|
sortRecords: {
|
|
23
23
|
order: "asc" | "desc";
|
|
24
24
|
column: any;
|
|
@@ -24,7 +24,7 @@ export default function applyQuery(knex, collection, dbQuery, query, schema, opt
|
|
|
24
24
|
applyOffset(knex, dbQuery, query.limit * (query.page - 1));
|
|
25
25
|
}
|
|
26
26
|
if (query.sort && !options?.isInnerQuery && !options?.hasMultiRelationalSort) {
|
|
27
|
-
const sortResult = applySort(knex, schema, dbQuery, query
|
|
27
|
+
const sortResult = applySort(knex, schema, dbQuery, query, collection, aliasMap);
|
|
28
28
|
if (!hasJoins) {
|
|
29
29
|
hasJoins = sortResult.hasJoins;
|
|
30
30
|
}
|
|
@@ -128,7 +128,9 @@ function addJoin({ path, collection, aliasMap, rootQuery, schema, relations, kne
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
-
export function applySort(knex, schema, rootQuery,
|
|
131
|
+
export function applySort(knex, schema, rootQuery, query, collection, aliasMap, returnRecords = false) {
|
|
132
|
+
const rootSort = query.sort;
|
|
133
|
+
const aggregate = query?.aggregate;
|
|
132
134
|
const relations = schema.relations;
|
|
133
135
|
let hasJoins = false;
|
|
134
136
|
let hasMultiRelationalSort = false;
|
|
@@ -141,6 +143,32 @@ export function applySort(knex, schema, rootQuery, rootSort, collection, aliasMa
|
|
|
141
143
|
if (column[0].startsWith('-')) {
|
|
142
144
|
column[0] = column[0].substring(1);
|
|
143
145
|
}
|
|
146
|
+
// Is the column name one of the aggregate functions used in the query if there is any?
|
|
147
|
+
if (Object.keys(aggregate ?? {}).includes(column[0])) {
|
|
148
|
+
// If so, return the column name without the order prefix
|
|
149
|
+
const operation = column[0];
|
|
150
|
+
// Get the field for the aggregate function
|
|
151
|
+
const field = column[1];
|
|
152
|
+
// If the operation is countAll there is no field.
|
|
153
|
+
if (operation === 'countAll') {
|
|
154
|
+
return {
|
|
155
|
+
order,
|
|
156
|
+
column: 'countAll',
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// If the operation is a root count there is no field.
|
|
160
|
+
if (operation === 'count' && (field === '*' || !field)) {
|
|
161
|
+
return {
|
|
162
|
+
order,
|
|
163
|
+
column: 'count',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
// Return the column name with the operation and field name
|
|
167
|
+
return {
|
|
168
|
+
order,
|
|
169
|
+
column: returnRecords ? column[0] : `${operation}->${field}`,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
144
172
|
if (column.length === 1) {
|
|
145
173
|
const pathRoot = column[0].split(':')[0];
|
|
146
174
|
const { relation, relationType } = getRelationInfo(relations, collection, pathRoot);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function deleteFromRequireCache(modulePath: string): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActivityService, DashboardsService, FilesService, FlowsService, FoldersService, ItemsService, NotificationsService, OperationsService, PanelsService, PermissionsService, PresetsService, RevisionsService, RolesService, SettingsService, SharesService, UsersService, WebhooksService, } from '../services/index.js';
|
|
1
|
+
import { ActivityService, DashboardsService, FilesService, FlowsService, FoldersService, ItemsService, NotificationsService, OperationsService, PanelsService, PermissionsService, PresetsService, RevisionsService, RolesService, SettingsService, SharesService, UsersService, VersionsService, WebhooksService, } from '../services/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Select the correct service for the given collection. This allows the individual services to run
|
|
4
4
|
* their custom checks (f.e. it allows UsersService to prevent updating TFA secret from outside)
|
|
@@ -43,6 +43,8 @@ export function getService(collection, opts) {
|
|
|
43
43
|
return new UsersService(opts);
|
|
44
44
|
case 'directus_webhooks':
|
|
45
45
|
return new WebhooksService(opts);
|
|
46
|
+
case 'directus_versions':
|
|
47
|
+
return new VersionsService(opts);
|
|
46
48
|
default:
|
|
47
49
|
return new ItemsService(collection, opts);
|
|
48
50
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { dirname } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { pathToRelativeUrl } from '@directus/utils/node';
|
|
4
|
+
export function importFileUrl(url, root, options = {}) {
|
|
5
|
+
return import(`./${pathToRelativeUrl(url, dirname(fileURLToPath(root)))}${options.fresh ? `?t=${Date.now()}` : ''}`);
|
|
6
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
type
|
|
1
|
+
import type { PromiseCallback } from '@directus/types';
|
|
2
2
|
export declare class JobQueue {
|
|
3
3
|
private running;
|
|
4
4
|
private jobs;
|
|
5
5
|
constructor();
|
|
6
|
-
enqueue(job:
|
|
6
|
+
enqueue(job: PromiseCallback): void;
|
|
7
7
|
private run;
|
|
8
8
|
}
|
|
9
|
-
export {};
|
|
@@ -19,5 +19,5 @@ export declare function redactObject(input: UnknownObject, redact: {
|
|
|
19
19
|
/**
|
|
20
20
|
* Replace values and extract Error objects for use with JSON.stringify()
|
|
21
21
|
*/
|
|
22
|
-
export declare function getReplacer(replacement: Replacement, values?: Values): (_key: string, value: unknown) =>
|
|
22
|
+
export declare function getReplacer(replacement: Replacement, values?: Values): (_key: string, value: unknown) => any;
|
|
23
23
|
export {};
|
|
@@ -84,31 +84,44 @@ export function getReplacer(replacement, values) {
|
|
|
84
84
|
const filteredValues = values
|
|
85
85
|
? Object.entries(values).filter(([_k, v]) => typeof v === 'string' && v.length > 0)
|
|
86
86
|
: [];
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
const replacer = (seen) => {
|
|
88
|
+
return function (_key, value) {
|
|
89
|
+
if (value instanceof Error) {
|
|
90
|
+
return {
|
|
91
|
+
name: value.name,
|
|
92
|
+
message: value.message,
|
|
93
|
+
stack: value.stack,
|
|
94
|
+
cause: value.cause,
|
|
95
|
+
};
|
|
93
96
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
97
|
+
if (value !== null && typeof value === 'object') {
|
|
98
|
+
if (seen.has(value)) {
|
|
99
|
+
return '[Circular]';
|
|
100
|
+
}
|
|
101
|
+
seen.add(value);
|
|
102
|
+
const newValue = Array.isArray(value) ? [] : {};
|
|
103
|
+
for (const [key2, value2] of Object.entries(value)) {
|
|
104
|
+
if (typeof value2 === 'string') {
|
|
105
|
+
newValue[key2] = value2;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
newValue[key2] = replacer(seen)(key2, value2);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
seen.delete(value);
|
|
112
|
+
return newValue;
|
|
110
113
|
}
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
if (!values || filteredValues.length === 0 || typeof value !== 'string')
|
|
115
|
+
return value;
|
|
116
|
+
let finalValue = value;
|
|
117
|
+
for (const [redactKey, valueToRedact] of filteredValues) {
|
|
118
|
+
if (finalValue.includes(valueToRedact)) {
|
|
119
|
+
finalValue = finalValue.replace(new RegExp(valueToRedact, 'g'), replacement(redactKey));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return finalValue;
|
|
123
|
+
};
|
|
113
124
|
};
|
|
125
|
+
const seen = new WeakSet();
|
|
126
|
+
return replacer(seen);
|
|
114
127
|
}
|
|
@@ -46,6 +46,9 @@ export function sanitizeQuery(rawQuery, accountability) {
|
|
|
46
46
|
if (rawQuery['search'] && typeof rawQuery['search'] === 'string') {
|
|
47
47
|
query.search = rawQuery['search'];
|
|
48
48
|
}
|
|
49
|
+
if (rawQuery['version']) {
|
|
50
|
+
query.version = rawQuery['version'];
|
|
51
|
+
}
|
|
49
52
|
if (rawQuery['export']) {
|
|
50
53
|
query.export = rawQuery['export'];
|
|
51
54
|
}
|
|
@@ -17,6 +17,7 @@ const querySchema = Joi.object({
|
|
|
17
17
|
meta: Joi.array().items(Joi.string().valid('total_count', 'filter_count')),
|
|
18
18
|
search: Joi.string(),
|
|
19
19
|
export: Joi.string().valid('csv', 'json', 'xml', 'yaml'),
|
|
20
|
+
version: Joi.string(),
|
|
20
21
|
aggregate: Joi.object(),
|
|
21
22
|
deep: Joi.object(),
|
|
22
23
|
alias: Joi.object(),
|
package/dist/worker-pool.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
1
2
|
import Tinypool from 'tinypool';
|
|
2
3
|
let workerPool;
|
|
3
4
|
export function getWorkerPool() {
|
|
@@ -6,6 +7,13 @@ export function getWorkerPool() {
|
|
|
6
7
|
minThreads: 0,
|
|
7
8
|
maxQueue: 'auto',
|
|
8
9
|
});
|
|
10
|
+
// TODO Workaround currently required for failing CPU count on ARM in Tinypool,
|
|
11
|
+
// remove again once fixed upstream
|
|
12
|
+
if (workerPool.options.maxThreads === 0) {
|
|
13
|
+
const availableParallelism = os.availableParallelism();
|
|
14
|
+
workerPool.options.maxThreads = availableParallelism;
|
|
15
|
+
workerPool.options.maxQueue = availableParallelism ** 2;
|
|
16
|
+
}
|
|
9
17
|
}
|
|
10
18
|
return workerPool;
|
|
11
19
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@directus/api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.0.1",
|
|
4
4
|
"description": "Directus is a real-time API and App dashboard for managing SQL database content",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"directus",
|
|
@@ -60,12 +60,12 @@
|
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@authenio/samlify-node-xmllint": "2.0.0",
|
|
62
62
|
"@aws-sdk/client-ses": "3.332.0",
|
|
63
|
-
"@directus/format-title": "10.
|
|
63
|
+
"@directus/format-title": "10.1.0",
|
|
64
64
|
"@godaddy/terminus": "4.12.0",
|
|
65
65
|
"@rollup/plugin-alias": "5.0.0",
|
|
66
66
|
"@rollup/plugin-node-resolve": "15.0.2",
|
|
67
67
|
"@rollup/plugin-virtual": "3.0.1",
|
|
68
|
-
"argon2": "0.
|
|
68
|
+
"argon2": "0.31.1",
|
|
69
69
|
"async": "3.2.4",
|
|
70
70
|
"axios": "1.4.0",
|
|
71
71
|
"busboy": "1.6.0",
|
|
@@ -89,9 +89,10 @@
|
|
|
89
89
|
"express": "4.18.2",
|
|
90
90
|
"flat": "5.0.2",
|
|
91
91
|
"fs-extra": "11.1.1",
|
|
92
|
-
"
|
|
92
|
+
"glob-to-regexp": "0.4.1",
|
|
93
|
+
"graphql": "16.8.1",
|
|
93
94
|
"graphql-compose": "9.0.10",
|
|
94
|
-
"graphql-ws": "5.
|
|
95
|
+
"graphql-ws": "5.14.1",
|
|
95
96
|
"helmet": "7.0.0",
|
|
96
97
|
"icc": "3.0.0",
|
|
97
98
|
"inquirer": "9.2.4",
|
|
@@ -131,7 +132,7 @@
|
|
|
131
132
|
"rollup": "3.22.0",
|
|
132
133
|
"samlify": "2.8.10",
|
|
133
134
|
"sanitize-html": "2.10.0",
|
|
134
|
-
"sharp": "0.32.
|
|
135
|
+
"sharp": "0.32.6",
|
|
135
136
|
"snappy": "7.2.2",
|
|
136
137
|
"stream-json": "1.7.5",
|
|
137
138
|
"strip-bom-stream": "5.0.0",
|
|
@@ -140,26 +141,26 @@
|
|
|
140
141
|
"uuid": "9.0.0",
|
|
141
142
|
"uuid-validate": "0.0.3",
|
|
142
143
|
"wellknown": "0.5.0",
|
|
143
|
-
"ws": "8.
|
|
144
|
-
"zod": "3.
|
|
144
|
+
"ws": "8.14.2",
|
|
145
|
+
"zod": "3.22.4",
|
|
145
146
|
"zod-validation-error": "1.0.1",
|
|
146
|
-
"@directus/app": "10.
|
|
147
|
-
"@directus/constants": "11.0.
|
|
148
|
-
"@directus/errors": "0.
|
|
149
|
-
"@directus/extensions": "0.
|
|
150
|
-
"@directus/extensions-sdk": "10.1.
|
|
151
|
-
"@directus/pressure": "1.0.
|
|
147
|
+
"@directus/app": "10.11.0",
|
|
148
|
+
"@directus/constants": "11.0.1",
|
|
149
|
+
"@directus/errors": "0.2.0",
|
|
150
|
+
"@directus/extensions": "0.1.1",
|
|
151
|
+
"@directus/extensions-sdk": "10.1.14",
|
|
152
|
+
"@directus/pressure": "1.0.12",
|
|
152
153
|
"@directus/schema": "11.0.0",
|
|
153
|
-
"@directus/specs": "10.2.
|
|
154
|
-
"@directus/storage": "10.0.
|
|
155
|
-
"@directus/storage-driver-azure": "10.0.
|
|
156
|
-
"@directus/storage-driver-cloudinary": "10.0.
|
|
157
|
-
"@directus/storage-driver-gcs": "10.0.
|
|
158
|
-
"@directus/storage-driver-local": "10.0.
|
|
159
|
-
"@directus/storage-driver-s3": "10.0.
|
|
160
|
-
"@directus/storage-driver-supabase": "1.0.
|
|
161
|
-
"@directus/utils": "11.0.
|
|
162
|
-
"@directus/validation": "0.0.
|
|
154
|
+
"@directus/specs": "10.2.1",
|
|
155
|
+
"@directus/storage": "10.0.7",
|
|
156
|
+
"@directus/storage-driver-azure": "10.0.13",
|
|
157
|
+
"@directus/storage-driver-cloudinary": "10.0.13",
|
|
158
|
+
"@directus/storage-driver-gcs": "10.0.13",
|
|
159
|
+
"@directus/storage-driver-local": "10.0.13",
|
|
160
|
+
"@directus/storage-driver-s3": "10.0.13",
|
|
161
|
+
"@directus/storage-driver-supabase": "1.0.5",
|
|
162
|
+
"@directus/utils": "11.0.1",
|
|
163
|
+
"@directus/validation": "0.0.8"
|
|
163
164
|
},
|
|
164
165
|
"devDependencies": {
|
|
165
166
|
"@ngneat/falso": "6.4.0",
|
|
@@ -177,11 +178,11 @@
|
|
|
177
178
|
"@types/express-serve-static-core": "4.17.35",
|
|
178
179
|
"@types/flat": "5.0.2",
|
|
179
180
|
"@types/fs-extra": "11.0.1",
|
|
181
|
+
"@types/glob-to-regexp": "0.4.3",
|
|
180
182
|
"@types/inquirer": "9.0.3",
|
|
181
183
|
"@types/js-yaml": "4.0.5",
|
|
182
184
|
"@types/json2csv": "5.0.3",
|
|
183
185
|
"@types/jsonwebtoken": "9.0.2",
|
|
184
|
-
"@types/keyv": "4.2.0",
|
|
185
186
|
"@types/ldapjs": "2.2.5",
|
|
186
187
|
"@types/lodash-es": "4.17.7",
|
|
187
188
|
"@types/marked": "4.3.0",
|
|
@@ -199,7 +200,7 @@
|
|
|
199
200
|
"@types/uuid": "9.0.1",
|
|
200
201
|
"@types/uuid-validate": "0.0.1",
|
|
201
202
|
"@types/wellknown": "0.5.4",
|
|
202
|
-
"@types/ws": "8.5.
|
|
203
|
+
"@types/ws": "8.5.8",
|
|
203
204
|
"@vitest/coverage-c8": "0.31.1",
|
|
204
205
|
"copyfiles": "2.4.1",
|
|
205
206
|
"form-data": "4.0.0",
|
|
@@ -209,7 +210,7 @@
|
|
|
209
210
|
"vitest": "0.31.1",
|
|
210
211
|
"@directus/random": "0.2.3",
|
|
211
212
|
"@directus/tsconfig": "1.0.1",
|
|
212
|
-
"@directus/types": "11.0.
|
|
213
|
+
"@directus/types": "11.0.1"
|
|
213
214
|
},
|
|
214
215
|
"optionalDependencies": {
|
|
215
216
|
"@keyv/redis": "2.5.8",
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
export declare const getExtensions: () => Promise<(({
|
|
2
|
-
path: string;
|
|
3
|
-
name: string;
|
|
4
|
-
version?: string;
|
|
5
|
-
host?: string;
|
|
6
|
-
local: boolean;
|
|
7
|
-
} & {
|
|
8
|
-
type: "interface" | "display" | "layout" | "module" | "panel";
|
|
9
|
-
entrypoint: string;
|
|
10
|
-
}) | ({
|
|
11
|
-
path: string;
|
|
12
|
-
name: string;
|
|
13
|
-
version?: string;
|
|
14
|
-
host?: string;
|
|
15
|
-
local: boolean;
|
|
16
|
-
} & {
|
|
17
|
-
type: "endpoint" | "hook";
|
|
18
|
-
entrypoint: string;
|
|
19
|
-
}) | ({
|
|
20
|
-
path: string;
|
|
21
|
-
name: string;
|
|
22
|
-
version?: string;
|
|
23
|
-
host?: string;
|
|
24
|
-
local: boolean;
|
|
25
|
-
} & {
|
|
26
|
-
type: "operation";
|
|
27
|
-
entrypoint: {
|
|
28
|
-
app: string;
|
|
29
|
-
api: string;
|
|
30
|
-
};
|
|
31
|
-
}) | ({
|
|
32
|
-
path: string;
|
|
33
|
-
name: string;
|
|
34
|
-
version?: string;
|
|
35
|
-
host?: string;
|
|
36
|
-
local: boolean;
|
|
37
|
-
} & {
|
|
38
|
-
type: "bundle";
|
|
39
|
-
entrypoint: {
|
|
40
|
-
app: string;
|
|
41
|
-
api: string;
|
|
42
|
-
};
|
|
43
|
-
entries: {
|
|
44
|
-
type: "operation" | "interface" | "display" | "endpoint" | "layout" | "module" | "panel" | "hook";
|
|
45
|
-
name: string;
|
|
46
|
-
}[];
|
|
47
|
-
}))[]>;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { APP_EXTENSION_TYPES } from '@directus/extensions';
|
|
2
|
-
import { getLocalExtensions, getPackageExtensions, resolvePackageExtensions } from '@directus/extensions/node';
|
|
3
|
-
import env from '../env.js';
|
|
4
|
-
export const getExtensions = async () => {
|
|
5
|
-
const packageExtensions = await getPackageExtensions(env['PACKAGE_FILE_LOCATION']);
|
|
6
|
-
const localPackageExtensions = await resolvePackageExtensions(env['EXTENSIONS_PATH']);
|
|
7
|
-
const localExtensions = await getLocalExtensions(env['EXTENSIONS_PATH']);
|
|
8
|
-
return [...packageExtensions, ...localPackageExtensions, ...localExtensions].filter((extension) => env['SERVE_APP'] || APP_EXTENSION_TYPES.includes(extension.type) === false);
|
|
9
|
-
};
|