@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
package/dist/flows.js
CHANGED
|
@@ -33,7 +33,7 @@ const LAST_KEY = '$last';
|
|
|
33
33
|
const ENV_KEY = '$env';
|
|
34
34
|
class FlowManager {
|
|
35
35
|
isLoaded = false;
|
|
36
|
-
operations =
|
|
36
|
+
operations = new Map();
|
|
37
37
|
triggerHandlers = [];
|
|
38
38
|
operationFlowHandlers = {};
|
|
39
39
|
webhookFlowHandlers = {};
|
|
@@ -67,10 +67,10 @@ class FlowManager {
|
|
|
67
67
|
messenger.publish('flows', { type: 'reload' });
|
|
68
68
|
}
|
|
69
69
|
addOperation(id, operation) {
|
|
70
|
-
this.operations
|
|
70
|
+
this.operations.set(id, operation);
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
this.operations
|
|
72
|
+
removeOperation(id) {
|
|
73
|
+
this.operations.delete(id);
|
|
74
74
|
}
|
|
75
75
|
async runOperationFlow(id, data, context) {
|
|
76
76
|
if (!(id in this.operationFlowHandlers)) {
|
|
@@ -305,11 +305,11 @@ class FlowManager {
|
|
|
305
305
|
return undefined;
|
|
306
306
|
}
|
|
307
307
|
async executeOperation(operation, keyedData, context = {}) {
|
|
308
|
-
if (!(operation.type
|
|
308
|
+
if (!this.operations.has(operation.type)) {
|
|
309
309
|
logger.warn(`Couldn't find operation ${operation.type}`);
|
|
310
310
|
return { successor: null, status: 'unknown', data: null, options: null };
|
|
311
311
|
}
|
|
312
|
-
const handler = this.operations
|
|
312
|
+
const handler = this.operations.get(operation.type);
|
|
313
313
|
const options = applyOptionsData(operation.options, keyedData);
|
|
314
314
|
try {
|
|
315
315
|
let result = await handler(options, {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { parse as parseBytesConfiguration } from 'bytes';
|
|
2
|
+
import { assign } from 'lodash-es';
|
|
2
3
|
import { getCache, setCacheValue } from '../cache.js';
|
|
3
4
|
import env from '../env.js';
|
|
4
5
|
import logger from '../logger.js';
|
|
5
6
|
import { ExportService } from '../services/import-export/index.js';
|
|
7
|
+
import { VersionsService } from '../services/versions.js';
|
|
6
8
|
import asyncHandler from '../utils/async-handler.js';
|
|
7
9
|
import { getCacheControlHeader } from '../utils/get-cache-headers.js';
|
|
8
10
|
import { getCacheKey } from '../utils/get-cache-key.js';
|
|
@@ -17,6 +19,16 @@ export const respond = asyncHandler(async (req, res) => {
|
|
|
17
19
|
const maxSize = parseBytesConfiguration(env['CACHE_VALUE_MAX_SIZE']);
|
|
18
20
|
exceedsMaxSize = valueSize > maxSize;
|
|
19
21
|
}
|
|
22
|
+
if (req.sanitizedQuery.version &&
|
|
23
|
+
req.collection &&
|
|
24
|
+
(req.singleton || req.params['pk']) &&
|
|
25
|
+
'data' in res.locals['payload']) {
|
|
26
|
+
const versionsService = new VersionsService({ accountability: req.accountability ?? null, schema: req.schema });
|
|
27
|
+
const saves = await versionsService.getVersionSaves(req.sanitizedQuery.version, req.collection, req.params['pk']);
|
|
28
|
+
if (saves) {
|
|
29
|
+
assign(res.locals['payload'].data, ...saves);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
20
32
|
if ((req.method.toLowerCase() === 'get' || req.originalUrl?.startsWith('/graphql')) &&
|
|
21
33
|
env['CACHE_ENABLED'] === true &&
|
|
22
34
|
cache &&
|
package/dist/server.js
CHANGED
|
@@ -10,9 +10,9 @@ import emitter from './emitter.js';
|
|
|
10
10
|
import env from './env.js';
|
|
11
11
|
import logger from './logger.js';
|
|
12
12
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
13
|
+
import { toBoolean } from './utils/to-boolean.js';
|
|
13
14
|
import { createSubscriptionController, createWebSocketController, getSubscriptionController, getWebSocketController, } from './websocket/controllers/index.js';
|
|
14
15
|
import { startWebSocketHandlers } from './websocket/handlers/index.js';
|
|
15
|
-
import { toBoolean } from './utils/to-boolean.js';
|
|
16
16
|
export let SERVER_ONLINE = true;
|
|
17
17
|
export async function createServer() {
|
|
18
18
|
const server = http.createServer(await createApp());
|
|
@@ -116,6 +116,7 @@ export async function startServer() {
|
|
|
116
116
|
server
|
|
117
117
|
.listen(port, host, () => {
|
|
118
118
|
logger.info(`Server started at http://${host}:${port}`);
|
|
119
|
+
process.send?.('ready');
|
|
119
120
|
emitter.emitAction('server.start', { server }, {
|
|
120
121
|
database: getDatabase(),
|
|
121
122
|
schema: null,
|
package/dist/services/assets.js
CHANGED
|
@@ -25,7 +25,7 @@ export class AssetsService {
|
|
|
25
25
|
async getAsset(id, transformation, range) {
|
|
26
26
|
const storage = await getStorage();
|
|
27
27
|
const publicSettings = await this.knex
|
|
28
|
-
.select('project_logo', 'public_background', 'public_foreground')
|
|
28
|
+
.select('project_logo', 'public_background', 'public_foreground', 'public_favicon')
|
|
29
29
|
.from('directus_settings')
|
|
30
30
|
.first();
|
|
31
31
|
const systemPublicKeys = Object.values(publicSettings || {});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ApiOutput, ExtensionSettings } from '@directus/extensions';
|
|
2
|
+
import type { SchemaInspector } from '@directus/schema';
|
|
3
|
+
import type { Accountability, DeepPartial, SchemaOverview } from '@directus/types';
|
|
4
|
+
import type Keyv from 'keyv';
|
|
5
|
+
import type { Knex } from 'knex';
|
|
6
|
+
import type { Helpers } from '../database/helpers/index.js';
|
|
7
|
+
import type { ExtensionManager } from '../extensions/manager.js';
|
|
8
|
+
import type { AbstractServiceOptions } from '../types/index.js';
|
|
9
|
+
import { ItemsService } from './items.js';
|
|
10
|
+
import { PermissionsService } from './permissions.js';
|
|
11
|
+
export declare class ExtensionsService {
|
|
12
|
+
knex: Knex;
|
|
13
|
+
permissionsService: PermissionsService;
|
|
14
|
+
schemaInspector: SchemaInspector;
|
|
15
|
+
accountability: Accountability | null;
|
|
16
|
+
schema: SchemaOverview;
|
|
17
|
+
extensionsItemService: ItemsService<ExtensionSettings>;
|
|
18
|
+
systemCache: Keyv<any>;
|
|
19
|
+
helpers: Helpers;
|
|
20
|
+
extensionsManager: ExtensionManager;
|
|
21
|
+
constructor(options: AbstractServiceOptions);
|
|
22
|
+
readAll(): Promise<ApiOutput[]>;
|
|
23
|
+
readOne(bundle: string | null, name: string): Promise<ApiOutput>;
|
|
24
|
+
updateOne(bundle: string | null, name: string, data: DeepPartial<ApiOutput>): Promise<void>;
|
|
25
|
+
private getKey;
|
|
26
|
+
/**
|
|
27
|
+
* Combine the settings stored in the database with the information available from the installed
|
|
28
|
+
* extensions into the standardized extensions api output
|
|
29
|
+
*/
|
|
30
|
+
private stitch;
|
|
31
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
2
|
+
import { createInspector } from '@directus/schema';
|
|
3
|
+
import Joi from 'joi';
|
|
4
|
+
import { omit, pick } from 'lodash-es';
|
|
5
|
+
import { getCache } from '../cache.js';
|
|
6
|
+
import { getHelpers } from '../database/helpers/index.js';
|
|
7
|
+
import getDatabase, { getSchemaInspector } from '../database/index.js';
|
|
8
|
+
import { getExtensionManager } from '../extensions/index.js';
|
|
9
|
+
import { ItemsService } from './items.js';
|
|
10
|
+
import { PermissionsService } from './permissions.js';
|
|
11
|
+
export class ExtensionsService {
|
|
12
|
+
knex;
|
|
13
|
+
permissionsService;
|
|
14
|
+
schemaInspector;
|
|
15
|
+
accountability;
|
|
16
|
+
schema;
|
|
17
|
+
extensionsItemService;
|
|
18
|
+
systemCache;
|
|
19
|
+
helpers;
|
|
20
|
+
extensionsManager;
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.knex = options.knex || getDatabase();
|
|
23
|
+
this.permissionsService = new PermissionsService(options);
|
|
24
|
+
this.schemaInspector = options.knex ? createInspector(options.knex) : getSchemaInspector();
|
|
25
|
+
this.schema = options.schema;
|
|
26
|
+
this.accountability = options.accountability || null;
|
|
27
|
+
this.extensionsManager = getExtensionManager();
|
|
28
|
+
this.extensionsItemService = new ItemsService('directus_extensions', {
|
|
29
|
+
knex: this.knex,
|
|
30
|
+
schema: this.schema,
|
|
31
|
+
// No accountability here, as every other method is hardcoded to be admin only
|
|
32
|
+
});
|
|
33
|
+
this.systemCache = getCache().systemCache;
|
|
34
|
+
this.helpers = getHelpers(this.knex);
|
|
35
|
+
}
|
|
36
|
+
async readAll() {
|
|
37
|
+
if (this.accountability?.admin !== true) {
|
|
38
|
+
throw new ForbiddenError();
|
|
39
|
+
}
|
|
40
|
+
const installedExtensions = this.extensionsManager.getExtensions();
|
|
41
|
+
const configuredExtensions = await this.extensionsItemService.readByQuery({ limit: -1 });
|
|
42
|
+
return this.stitch(installedExtensions, configuredExtensions);
|
|
43
|
+
}
|
|
44
|
+
async readOne(bundle, name) {
|
|
45
|
+
if (this.accountability?.admin !== true) {
|
|
46
|
+
throw new ForbiddenError();
|
|
47
|
+
}
|
|
48
|
+
const key = this.getKey(bundle, name);
|
|
49
|
+
const schema = this.extensionsManager.getExtensions().find((extension) => extension.name === bundle ?? name);
|
|
50
|
+
const meta = await this.extensionsItemService.readOne(key);
|
|
51
|
+
const stitched = this.stitch(schema ? [schema] : [], [meta])[0];
|
|
52
|
+
if (stitched)
|
|
53
|
+
return stitched;
|
|
54
|
+
throw new ForbiddenError();
|
|
55
|
+
}
|
|
56
|
+
async updateOne(bundle, name, data) {
|
|
57
|
+
if (this.accountability?.admin !== true) {
|
|
58
|
+
throw new ForbiddenError();
|
|
59
|
+
}
|
|
60
|
+
const key = this.getKey(bundle, name);
|
|
61
|
+
const updateExtensionSchema = Joi.object({
|
|
62
|
+
meta: Joi.object({
|
|
63
|
+
enabled: Joi.boolean(),
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
const { error } = updateExtensionSchema.validate(data);
|
|
67
|
+
if (error) {
|
|
68
|
+
throw new InvalidPayloadError({ reason: error.message });
|
|
69
|
+
}
|
|
70
|
+
if ('meta' in data && 'enabled' in data.meta) {
|
|
71
|
+
await this.knex('directus_extensions').update({ enabled: data.meta.enabled }).where({ name: key });
|
|
72
|
+
this.extensionsManager.reload();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
getKey(bundle, name) {
|
|
76
|
+
return bundle ? `${bundle}/${name}` : name;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Combine the settings stored in the database with the information available from the installed
|
|
80
|
+
* extensions into the standardized extensions api output
|
|
81
|
+
*/
|
|
82
|
+
stitch(installed, configured) {
|
|
83
|
+
/**
|
|
84
|
+
* On startup, the extensions manager will automatically create the rows for installed
|
|
85
|
+
* extensions that don't have configured settings yet, so there should always be equal or more
|
|
86
|
+
* settings rows than installed extensions.
|
|
87
|
+
*/
|
|
88
|
+
return configured.map((meta) => {
|
|
89
|
+
let bundleName = null;
|
|
90
|
+
let name = meta.name;
|
|
91
|
+
if (name.includes('/')) {
|
|
92
|
+
const parts = name.split('/');
|
|
93
|
+
// NPM packages can have an optional organization scope in the format
|
|
94
|
+
// `@<org>/<package>`. This is limited to a single `/`.
|
|
95
|
+
//
|
|
96
|
+
// `foo` -> extension
|
|
97
|
+
// `foo/bar` -> bundle
|
|
98
|
+
// `@rijk/foo` -> extension
|
|
99
|
+
// `@rijk/foo/bar -> bundle
|
|
100
|
+
const hasOrg = parts.at(0).startsWith('@');
|
|
101
|
+
if (hasOrg && parts.length > 2) {
|
|
102
|
+
name = parts.pop();
|
|
103
|
+
bundleName = parts.join('/');
|
|
104
|
+
}
|
|
105
|
+
else if (hasOrg === false) {
|
|
106
|
+
[bundleName, name] = parts;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
let schema;
|
|
110
|
+
if (bundleName) {
|
|
111
|
+
const bundle = installed.find((extension) => extension.name === bundleName);
|
|
112
|
+
if (bundle && 'entries' in bundle) {
|
|
113
|
+
const entry = bundle.entries.find((entry) => entry.name === name) ?? null;
|
|
114
|
+
if (entry) {
|
|
115
|
+
schema = {
|
|
116
|
+
type: entry.type,
|
|
117
|
+
local: bundle.local,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
schema = null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
schema = installed.find((extension) => extension.name === name) ?? null;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
name,
|
|
130
|
+
bundle: bundleName,
|
|
131
|
+
schema: schema ? pick(schema, 'type', 'local') : null,
|
|
132
|
+
meta: omit(meta, 'name'),
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type DirectusError } from '@directus/errors';
|
|
2
2
|
import type { Accountability, Filter, Query, SchemaOverview } from '@directus/types';
|
|
3
3
|
import type { ArgumentNode, FormattedExecutionResult, FragmentDefinitionNode, GraphQLResolveInfo, SelectionNode } from 'graphql';
|
|
4
4
|
import { GraphQLError, GraphQLSchema } from 'graphql';
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { Action, FUNCTIONS } from '@directus/constants';
|
|
2
|
-
import { isDirectusError } from '@directus/errors';
|
|
2
|
+
import { ErrorCode, ForbiddenError, InvalidPayloadError, isDirectusError } from '@directus/errors';
|
|
3
3
|
import { parseFilterFunctionPath } from '@directus/utils';
|
|
4
4
|
import argon2 from 'argon2';
|
|
5
5
|
import { GraphQLBoolean, GraphQLEnumType, GraphQLError, GraphQLFloat, GraphQLID, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, GraphQLUnionType, NoSchemaIntrospectionCustomRule, execute, specifiedRules, validate, } from 'graphql';
|
|
6
6
|
import { GraphQLJSON, InputTypeComposer, ObjectTypeComposer, SchemaComposer, toInputObjectType } from 'graphql-compose';
|
|
7
|
-
import { flatten, get, mapKeys, merge, omit, pick, set, transform, uniq } from 'lodash-es';
|
|
7
|
+
import { assign, flatten, get, mapKeys, merge, omit, pick, set, transform, uniq } from 'lodash-es';
|
|
8
8
|
import { clearSystemCache, getCache } from '../../cache.js';
|
|
9
9
|
import { DEFAULT_AUTH_PROVIDER, GENERATE_SPECIAL } from '../../constants.js';
|
|
10
10
|
import getDatabase from '../../database/index.js';
|
|
11
11
|
import env from '../../env.js';
|
|
12
|
-
import { ErrorCode, ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
13
|
-
import { getExtensionManager } from '../../extensions/index.js';
|
|
14
12
|
import { generateHash } from '../../utils/generate-hash.js';
|
|
15
13
|
import { getGraphQLType } from '../../utils/get-graphql-type.js';
|
|
16
14
|
import { getMilliseconds } from '../../utils/get-milliseconds.js';
|
|
@@ -22,6 +20,7 @@ import { validateQuery } from '../../utils/validate-query.js';
|
|
|
22
20
|
import { ActivityService } from '../activity.js';
|
|
23
21
|
import { AuthenticationService } from '../authentication.js';
|
|
24
22
|
import { CollectionsService } from '../collections.js';
|
|
23
|
+
import { ExtensionsService } from '../extensions.js';
|
|
25
24
|
import { FieldsService } from '../fields.js';
|
|
26
25
|
import { FilesService } from '../files.js';
|
|
27
26
|
import { RelationsService } from '../relations.js';
|
|
@@ -31,6 +30,7 @@ import { SpecificationService } from '../specifications.js';
|
|
|
31
30
|
import { TFAService } from '../tfa.js';
|
|
32
31
|
import { UsersService } from '../users.js';
|
|
33
32
|
import { UtilsService } from '../utils.js';
|
|
33
|
+
import { VersionsService } from '../versions.js';
|
|
34
34
|
import { GraphQLExecutionError, GraphQLValidationError } from './errors/index.js';
|
|
35
35
|
import { createSubscriptionGenerator } from './subscription.js';
|
|
36
36
|
import { GraphQLBigInt } from './types/bigint.js';
|
|
@@ -54,6 +54,7 @@ const SYSTEM_DENY_LIST = [
|
|
|
54
54
|
'directus_relations',
|
|
55
55
|
'directus_migrations',
|
|
56
56
|
'directus_sessions',
|
|
57
|
+
'directus_extensions',
|
|
57
58
|
];
|
|
58
59
|
const READ_ONLY = ['directus_activity', 'directus_revisions'];
|
|
59
60
|
export class GraphQLService {
|
|
@@ -842,6 +843,13 @@ export class GraphQLService {
|
|
|
842
843
|
},
|
|
843
844
|
};
|
|
844
845
|
}
|
|
846
|
+
else {
|
|
847
|
+
resolver.args = {
|
|
848
|
+
version: {
|
|
849
|
+
type: GraphQLString,
|
|
850
|
+
},
|
|
851
|
+
};
|
|
852
|
+
}
|
|
845
853
|
ReadCollectionTypes[collection.collection].addResolver(resolver);
|
|
846
854
|
ReadCollectionTypes[collection.collection].addResolver({
|
|
847
855
|
name: `${collection.collection}_aggregated`,
|
|
@@ -877,6 +885,7 @@ export class GraphQLService {
|
|
|
877
885
|
type: ReadCollectionTypes[collection.collection],
|
|
878
886
|
args: {
|
|
879
887
|
id: new GraphQLNonNull(GraphQLID),
|
|
888
|
+
version: GraphQLString,
|
|
880
889
|
},
|
|
881
890
|
resolve: async ({ info, context }) => {
|
|
882
891
|
const result = await self.resolveQuery(info);
|
|
@@ -1147,6 +1156,20 @@ export class GraphQLService {
|
|
|
1147
1156
|
}
|
|
1148
1157
|
}
|
|
1149
1158
|
const result = await this.read(collection, query);
|
|
1159
|
+
if (args['version']) {
|
|
1160
|
+
const versionsService = new VersionsService({ accountability: this.accountability, schema: this.schema });
|
|
1161
|
+
const saves = await versionsService.getVersionSaves(args['version'], collection, args['id']);
|
|
1162
|
+
if (saves) {
|
|
1163
|
+
if (this.schema.collections[collection].singleton) {
|
|
1164
|
+
return assign(result, ...saves);
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
if (result?.[0] === undefined)
|
|
1168
|
+
return null;
|
|
1169
|
+
return assign(result[0], ...saves);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1150
1173
|
if (args['id']) {
|
|
1151
1174
|
return result?.[0] || null;
|
|
1152
1175
|
}
|
|
@@ -1597,26 +1620,6 @@ export class GraphQLService {
|
|
|
1597
1620
|
}
|
|
1598
1621
|
/** Globally available query */
|
|
1599
1622
|
schemaComposer.Query.addFields({
|
|
1600
|
-
extensions: {
|
|
1601
|
-
type: schemaComposer.createObjectTC({
|
|
1602
|
-
name: 'extensions',
|
|
1603
|
-
fields: {
|
|
1604
|
-
interfaces: new GraphQLList(GraphQLString),
|
|
1605
|
-
displays: new GraphQLList(GraphQLString),
|
|
1606
|
-
layouts: new GraphQLList(GraphQLString),
|
|
1607
|
-
modules: new GraphQLList(GraphQLString),
|
|
1608
|
-
},
|
|
1609
|
-
}),
|
|
1610
|
-
resolve: async () => {
|
|
1611
|
-
const extensionManager = getExtensionManager();
|
|
1612
|
-
return {
|
|
1613
|
-
interfaces: extensionManager.getExtensionsList('interface'),
|
|
1614
|
-
displays: extensionManager.getExtensionsList('display'),
|
|
1615
|
-
layouts: extensionManager.getExtensionsList('layout'),
|
|
1616
|
-
modules: extensionManager.getExtensionsList('module'),
|
|
1617
|
-
};
|
|
1618
|
-
},
|
|
1619
|
-
},
|
|
1620
1623
|
server_specs_oas: {
|
|
1621
1624
|
type: GraphQLJSON,
|
|
1622
1625
|
resolve: async () => {
|
|
@@ -1678,6 +1681,9 @@ export class GraphQLService {
|
|
|
1678
1681
|
const Relation = schemaComposer.createObjectTC({
|
|
1679
1682
|
name: 'directus_relations',
|
|
1680
1683
|
});
|
|
1684
|
+
const Extension = schemaComposer.createObjectTC({
|
|
1685
|
+
name: 'directus_extensions',
|
|
1686
|
+
});
|
|
1681
1687
|
/**
|
|
1682
1688
|
* Globally available mutations
|
|
1683
1689
|
*/
|
|
@@ -2361,6 +2367,63 @@ export class GraphQLService {
|
|
|
2361
2367
|
},
|
|
2362
2368
|
},
|
|
2363
2369
|
});
|
|
2370
|
+
Extension.addFields({
|
|
2371
|
+
bundle: GraphQLString,
|
|
2372
|
+
name: new GraphQLNonNull(GraphQLString),
|
|
2373
|
+
schema: schemaComposer.createObjectTC({
|
|
2374
|
+
name: 'directus_extensions_schema',
|
|
2375
|
+
fields: {
|
|
2376
|
+
type: GraphQLString,
|
|
2377
|
+
local: GraphQLBoolean,
|
|
2378
|
+
},
|
|
2379
|
+
}),
|
|
2380
|
+
meta: schemaComposer.createObjectTC({
|
|
2381
|
+
name: 'directus_extensions_meta',
|
|
2382
|
+
fields: {
|
|
2383
|
+
enabled: GraphQLBoolean,
|
|
2384
|
+
},
|
|
2385
|
+
}),
|
|
2386
|
+
});
|
|
2387
|
+
schemaComposer.Query.addFields({
|
|
2388
|
+
extensions: {
|
|
2389
|
+
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Extension.getType()))),
|
|
2390
|
+
resolve: async () => {
|
|
2391
|
+
const service = new ExtensionsService({
|
|
2392
|
+
accountability: this.accountability,
|
|
2393
|
+
schema: this.schema,
|
|
2394
|
+
});
|
|
2395
|
+
return await service.readAll();
|
|
2396
|
+
},
|
|
2397
|
+
},
|
|
2398
|
+
});
|
|
2399
|
+
schemaComposer.Mutation.addFields({
|
|
2400
|
+
update_extensions_item: {
|
|
2401
|
+
type: Extension,
|
|
2402
|
+
args: {
|
|
2403
|
+
bundle: GraphQLString,
|
|
2404
|
+
name: new GraphQLNonNull(GraphQLString),
|
|
2405
|
+
data: toInputObjectType(schemaComposer.createObjectTC({
|
|
2406
|
+
name: 'update_directus_extensions_input',
|
|
2407
|
+
fields: {
|
|
2408
|
+
meta: schemaComposer.createObjectTC({
|
|
2409
|
+
name: 'update_directus_extensions_input_meta',
|
|
2410
|
+
fields: {
|
|
2411
|
+
enabled: GraphQLBoolean,
|
|
2412
|
+
},
|
|
2413
|
+
}),
|
|
2414
|
+
},
|
|
2415
|
+
})),
|
|
2416
|
+
},
|
|
2417
|
+
resolve: async (_, args) => {
|
|
2418
|
+
const extensionsService = new ExtensionsService({
|
|
2419
|
+
accountability: this.accountability,
|
|
2420
|
+
schema: this.schema,
|
|
2421
|
+
});
|
|
2422
|
+
await extensionsService.updateOne(args['bundle'], args['name'], args['data']);
|
|
2423
|
+
return await extensionsService.readOne(args['bundle'], args['name']);
|
|
2424
|
+
},
|
|
2425
|
+
},
|
|
2426
|
+
});
|
|
2364
2427
|
}
|
|
2365
2428
|
if ('directus_users' in schema.read.collections) {
|
|
2366
2429
|
schemaComposer.Query.addFields({
|
package/dist/services/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from './authentication.js';
|
|
|
4
4
|
export * from './authorization.js';
|
|
5
5
|
export * from './collections.js';
|
|
6
6
|
export * from './dashboards.js';
|
|
7
|
+
export * from './extensions.js';
|
|
7
8
|
export * from './fields.js';
|
|
8
9
|
export * from './files.js';
|
|
9
10
|
export * from './flows.js';
|
|
@@ -31,5 +32,6 @@ export * from './tfa.js';
|
|
|
31
32
|
export * from './translations.js';
|
|
32
33
|
export * from './users.js';
|
|
33
34
|
export * from './utils.js';
|
|
35
|
+
export * from './versions.js';
|
|
34
36
|
export * from './webhooks.js';
|
|
35
37
|
export * from './websocket.js';
|
package/dist/services/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * from './authentication.js';
|
|
|
4
4
|
export * from './authorization.js';
|
|
5
5
|
export * from './collections.js';
|
|
6
6
|
export * from './dashboards.js';
|
|
7
|
+
export * from './extensions.js';
|
|
7
8
|
export * from './fields.js';
|
|
8
9
|
export * from './files.js';
|
|
9
10
|
export * from './flows.js';
|
|
@@ -31,5 +32,6 @@ export * from './tfa.js';
|
|
|
31
32
|
export * from './translations.js';
|
|
32
33
|
export * from './users.js';
|
|
33
34
|
export * from './utils.js';
|
|
35
|
+
export * from './versions.js';
|
|
34
36
|
export * from './webhooks.js';
|
|
35
37
|
export * from './websocket.js';
|
package/dist/services/server.js
CHANGED
|
@@ -12,8 +12,8 @@ import { rateLimiter } from '../middleware/rate-limiter-ip.js';
|
|
|
12
12
|
import { SERVER_ONLINE } from '../server.js';
|
|
13
13
|
import { getStorage } from '../storage/index.js';
|
|
14
14
|
import { version } from '../utils/package.js';
|
|
15
|
-
import { SettingsService } from './settings.js';
|
|
16
15
|
import { toBoolean } from '../utils/to-boolean.js';
|
|
16
|
+
import { SettingsService } from './settings.js';
|
|
17
17
|
export class ServerService {
|
|
18
18
|
knex;
|
|
19
19
|
accountability;
|
|
@@ -33,9 +33,11 @@ export class ServerService {
|
|
|
33
33
|
'project_descriptor',
|
|
34
34
|
'project_logo',
|
|
35
35
|
'project_color',
|
|
36
|
+
'default_appearance',
|
|
36
37
|
'default_language',
|
|
37
38
|
'public_foreground',
|
|
38
39
|
'public_background',
|
|
40
|
+
'public_favicon',
|
|
39
41
|
'public_note',
|
|
40
42
|
'custom_css',
|
|
41
43
|
],
|
package/dist/services/users.js
CHANGED
|
@@ -274,7 +274,9 @@ export class UsersService extends ItemsService {
|
|
|
274
274
|
catch (err) {
|
|
275
275
|
(opts || (opts = {})).preMutationError = err;
|
|
276
276
|
}
|
|
277
|
+
// Manual constraint, see https://github.com/directus/directus/pull/19912
|
|
277
278
|
await this.knex('directus_notifications').update({ sender: null }).whereIn('sender', keys);
|
|
279
|
+
await this.knex('directus_versions').update({ user_updated: null }).whereIn('user_updated', keys);
|
|
278
280
|
await super.deleteMany(keys, opts);
|
|
279
281
|
return keys;
|
|
280
282
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Item, PrimaryKey, Query } from '@directus/types';
|
|
2
|
+
import type { AbstractServiceOptions, MutationOptions } from '../types/index.js';
|
|
3
|
+
import { AuthorizationService } from './authorization.js';
|
|
4
|
+
import { ItemsService } from './items.js';
|
|
5
|
+
export declare class VersionsService extends ItemsService {
|
|
6
|
+
authorizationService: AuthorizationService;
|
|
7
|
+
constructor(options: AbstractServiceOptions);
|
|
8
|
+
private validateCreateData;
|
|
9
|
+
getMainItem(collection: string, item: PrimaryKey, query?: Query): Promise<Item>;
|
|
10
|
+
verifyHash(collection: string, item: PrimaryKey, hash: string): Promise<{
|
|
11
|
+
outdated: boolean;
|
|
12
|
+
mainHash: string;
|
|
13
|
+
}>;
|
|
14
|
+
getVersionSavesById(id: PrimaryKey): Promise<Partial<Item>[]>;
|
|
15
|
+
getVersionSaves(key: string, collection: string, item: string | undefined): Promise<Partial<Item>[] | null>;
|
|
16
|
+
createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey>;
|
|
17
|
+
createMany(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
18
|
+
updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
19
|
+
save(key: PrimaryKey, data: Partial<Item>): Promise<Partial<Item>>;
|
|
20
|
+
promote(version: PrimaryKey, mainHash: string, fields?: string[]): Promise<import("../types/items.js").PrimaryKey>;
|
|
21
|
+
}
|