@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
|
@@ -13,6 +13,7 @@ export const snapshotBeforeCreateCollection = {
|
|
|
13
13
|
item_duplication_fields: null,
|
|
14
14
|
note: null,
|
|
15
15
|
singleton: false,
|
|
16
|
+
versioning: false,
|
|
16
17
|
translations: {},
|
|
17
18
|
},
|
|
18
19
|
schema: {
|
|
@@ -86,6 +87,7 @@ export const snapshotCreateCollection = {
|
|
|
86
87
|
item_duplication_fields: null,
|
|
87
88
|
note: null,
|
|
88
89
|
singleton: false,
|
|
90
|
+
versioning: false,
|
|
89
91
|
translations: {},
|
|
90
92
|
},
|
|
91
93
|
schema: {
|
|
@@ -105,6 +107,7 @@ export const snapshotCreateCollection = {
|
|
|
105
107
|
item_duplication_fields: null,
|
|
106
108
|
note: null,
|
|
107
109
|
singleton: false,
|
|
110
|
+
versioning: false,
|
|
108
111
|
translations: {},
|
|
109
112
|
},
|
|
110
113
|
schema: {
|
|
@@ -124,6 +127,7 @@ export const snapshotCreateCollection = {
|
|
|
124
127
|
item_duplication_fields: null,
|
|
125
128
|
note: null,
|
|
126
129
|
singleton: false,
|
|
130
|
+
versioning: false,
|
|
127
131
|
translations: {},
|
|
128
132
|
},
|
|
129
133
|
schema: {
|
|
@@ -287,6 +291,7 @@ export const snapshotCreateCollectionNotNested = {
|
|
|
287
291
|
item_duplication_fields: null,
|
|
288
292
|
note: null,
|
|
289
293
|
singleton: false,
|
|
294
|
+
versioning: false,
|
|
290
295
|
translations: {},
|
|
291
296
|
},
|
|
292
297
|
schema: {
|
|
@@ -306,6 +311,7 @@ export const snapshotCreateCollectionNotNested = {
|
|
|
306
311
|
item_duplication_fields: null,
|
|
307
312
|
note: null,
|
|
308
313
|
singleton: false,
|
|
314
|
+
versioning: false,
|
|
309
315
|
translations: {},
|
|
310
316
|
},
|
|
311
317
|
schema: {
|
|
@@ -424,6 +430,7 @@ export const snapshotBeforeDeleteCollection = {
|
|
|
424
430
|
item_duplication_fields: null,
|
|
425
431
|
note: null,
|
|
426
432
|
singleton: false,
|
|
433
|
+
versioning: false,
|
|
427
434
|
translations: {},
|
|
428
435
|
},
|
|
429
436
|
schema: {
|
|
@@ -443,6 +450,7 @@ export const snapshotBeforeDeleteCollection = {
|
|
|
443
450
|
item_duplication_fields: null,
|
|
444
451
|
note: null,
|
|
445
452
|
singleton: false,
|
|
453
|
+
versioning: false,
|
|
446
454
|
translations: {},
|
|
447
455
|
},
|
|
448
456
|
schema: {
|
|
@@ -462,6 +470,7 @@ export const snapshotBeforeDeleteCollection = {
|
|
|
462
470
|
item_duplication_fields: null,
|
|
463
471
|
note: null,
|
|
464
472
|
singleton: false,
|
|
473
|
+
versioning: false,
|
|
465
474
|
translations: {},
|
|
466
475
|
},
|
|
467
476
|
schema: {
|
package/dist/app.js
CHANGED
|
@@ -35,6 +35,7 @@ import sharesRouter from './controllers/shares.js';
|
|
|
35
35
|
import translationsRouter from './controllers/translations.js';
|
|
36
36
|
import usersRouter from './controllers/users.js';
|
|
37
37
|
import utilsRouter from './controllers/utils.js';
|
|
38
|
+
import versionsRouter from './controllers/versions.js';
|
|
38
39
|
import webhooksRouter from './controllers/webhooks.js';
|
|
39
40
|
import { isInstalled, validateDatabaseConnection, validateDatabaseExtensions, validateMigrations, } from './database/index.js';
|
|
40
41
|
import emitter from './emitter.js';
|
|
@@ -222,6 +223,7 @@ export default async function createApp() {
|
|
|
222
223
|
app.use('/shares', sharesRouter);
|
|
223
224
|
app.use('/users', usersRouter);
|
|
224
225
|
app.use('/utils', utilsRouter);
|
|
226
|
+
app.use('/versions', versionsRouter);
|
|
225
227
|
app.use('/webhooks', webhooksRouter);
|
|
226
228
|
// Register custom endpoints
|
|
227
229
|
await emitter.emitInit('routes.custom.before', { app });
|
package/dist/cli/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command, Option } from 'commander';
|
|
2
2
|
import emitter from '../emitter.js';
|
|
3
|
-
import { getExtensionManager } from '../extensions/index.js';
|
|
4
3
|
import { startServer } from '../server.js';
|
|
4
|
+
import * as pkg from '../utils/package.js';
|
|
5
5
|
import bootstrap from './commands/bootstrap/index.js';
|
|
6
6
|
import count from './commands/count/index.js';
|
|
7
7
|
import dbInstall from './commands/database/install.js';
|
|
@@ -14,11 +14,10 @@ import keyGenerate from './commands/security/key.js';
|
|
|
14
14
|
import secretGenerate from './commands/security/secret.js';
|
|
15
15
|
import usersCreate from './commands/users/create.js';
|
|
16
16
|
import usersPasswd from './commands/users/passwd.js';
|
|
17
|
-
import
|
|
17
|
+
import { loadExtensions } from './load-extensions.js';
|
|
18
18
|
export async function createCli() {
|
|
19
19
|
const program = new Command();
|
|
20
|
-
|
|
21
|
-
await extensionManager.initialize({ schedule: false, watch: false });
|
|
20
|
+
await loadExtensions();
|
|
22
21
|
await emitter.emitInit('cli.before', { program });
|
|
23
22
|
program.name('directus').usage('[command] [options]');
|
|
24
23
|
program.version(pkg.version, '-v, --version');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const loadExtensions: () => Promise<void>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { isInstalled, validateMigrations } from '../database/index.js';
|
|
2
|
+
import { getEnv } from '../env.js';
|
|
3
|
+
import { getExtensionManager } from '../extensions/index.js';
|
|
4
|
+
import logger from '../logger.js';
|
|
5
|
+
export const loadExtensions = async () => {
|
|
6
|
+
const env = getEnv();
|
|
7
|
+
if (!('DB_CLIENT' in env))
|
|
8
|
+
return;
|
|
9
|
+
const installed = await isInstalled();
|
|
10
|
+
if (!installed)
|
|
11
|
+
return;
|
|
12
|
+
const migrationsValid = await validateMigrations();
|
|
13
|
+
if (!migrationsValid) {
|
|
14
|
+
logger.info('Skipping CLI extensions initialization due to outstanding migrations.');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const extensionManager = getExtensionManager();
|
|
18
|
+
await extensionManager.initialize({ schedule: false, watch: false });
|
|
19
|
+
};
|
|
@@ -1,28 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { Router } from 'express';
|
|
1
|
+
import { ForbiddenError, RouteNotFoundError } from '@directus/errors';
|
|
2
|
+
import express from 'express';
|
|
4
3
|
import env from '../env.js';
|
|
5
|
-
import { RouteNotFoundError } from '@directus/errors';
|
|
6
4
|
import { getExtensionManager } from '../extensions/index.js';
|
|
7
5
|
import { respond } from '../middleware/respond.js';
|
|
6
|
+
import useCollection from '../middleware/use-collection.js';
|
|
7
|
+
import { ExtensionsService } from '../services/extensions.js';
|
|
8
8
|
import asyncHandler from '../utils/async-handler.js';
|
|
9
9
|
import { getCacheControlHeader } from '../utils/get-cache-headers.js';
|
|
10
10
|
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
11
|
-
const router = Router();
|
|
12
|
-
router.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
const router = express.Router();
|
|
12
|
+
router.use(useCollection('directus_extensions'));
|
|
13
|
+
router.get('/', asyncHandler(async (req, res, next) => {
|
|
14
|
+
const service = new ExtensionsService({
|
|
15
|
+
accountability: req.accountability,
|
|
16
|
+
schema: req.schema,
|
|
17
|
+
});
|
|
18
|
+
const extensions = await service.readAll();
|
|
19
|
+
res.locals['payload'] = { data: extensions || null };
|
|
20
|
+
return next();
|
|
21
|
+
}), respond);
|
|
22
|
+
router.patch('/:bundleOrName/:name?', asyncHandler(async (req, res, next) => {
|
|
23
|
+
const service = new ExtensionsService({
|
|
24
|
+
accountability: req.accountability,
|
|
25
|
+
schema: req.schema,
|
|
26
|
+
});
|
|
27
|
+
const bundle = req.params['name'] ? req.params['bundleOrName'] : null;
|
|
28
|
+
const name = req.params['name'] ? req.params['name'] : req.params['bundleOrName'];
|
|
29
|
+
if (bundle === undefined || !name) {
|
|
30
|
+
throw new ForbiddenError();
|
|
20
31
|
}
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
res.locals['payload'] = {
|
|
24
|
-
data: extensions,
|
|
25
|
-
};
|
|
32
|
+
await service.updateOne(bundle, name, req.body);
|
|
33
|
+
const updated = await service.readOne(bundle, name);
|
|
34
|
+
res.locals['payload'] = { data: updated || null };
|
|
26
35
|
return next();
|
|
27
36
|
}), respond);
|
|
28
37
|
router.get('/sources/:chunk', asyncHandler(async (req, res) => {
|
|
@@ -30,7 +39,7 @@ router.get('/sources/:chunk', asyncHandler(async (req, res) => {
|
|
|
30
39
|
const extensionManager = getExtensionManager();
|
|
31
40
|
let source;
|
|
32
41
|
if (chunk === 'index.js') {
|
|
33
|
-
source = extensionManager.
|
|
42
|
+
source = extensionManager.getAppExtensionsBundle();
|
|
34
43
|
}
|
|
35
44
|
else {
|
|
36
45
|
source = extensionManager.getAppExtensionChunk(chunk);
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { ErrorCode, InvalidPayloadError, isDirectusError } from '@directus/errors';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import { assign } from 'lodash-es';
|
|
4
|
+
import { respond } from '../middleware/respond.js';
|
|
5
|
+
import useCollection from '../middleware/use-collection.js';
|
|
6
|
+
import { validateBatch } from '../middleware/validate-batch.js';
|
|
7
|
+
import { MetaService } from '../services/meta.js';
|
|
8
|
+
import { VersionsService } from '../services/versions.js';
|
|
9
|
+
import asyncHandler from '../utils/async-handler.js';
|
|
10
|
+
import { sanitizeQuery } from '../utils/sanitize-query.js';
|
|
11
|
+
const router = express.Router();
|
|
12
|
+
router.use(useCollection('directus_versions'));
|
|
13
|
+
router.post('/', asyncHandler(async (req, res, next) => {
|
|
14
|
+
const service = new VersionsService({
|
|
15
|
+
accountability: req.accountability,
|
|
16
|
+
schema: req.schema,
|
|
17
|
+
});
|
|
18
|
+
const savedKeys = [];
|
|
19
|
+
if (Array.isArray(req.body)) {
|
|
20
|
+
const keys = await service.createMany(req.body);
|
|
21
|
+
savedKeys.push(...keys);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const primaryKey = await service.createOne(req.body);
|
|
25
|
+
savedKeys.push(primaryKey);
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
if (Array.isArray(req.body)) {
|
|
29
|
+
const records = await service.readMany(savedKeys, req.sanitizedQuery);
|
|
30
|
+
res.locals['payload'] = { data: records };
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const record = await service.readOne(savedKeys[0], req.sanitizedQuery);
|
|
34
|
+
res.locals['payload'] = { data: record };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (isDirectusError(error, ErrorCode.Forbidden)) {
|
|
39
|
+
return next();
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
return next();
|
|
44
|
+
}), respond);
|
|
45
|
+
const readHandler = asyncHandler(async (req, res, next) => {
|
|
46
|
+
const service = new VersionsService({
|
|
47
|
+
accountability: req.accountability,
|
|
48
|
+
schema: req.schema,
|
|
49
|
+
});
|
|
50
|
+
const metaService = new MetaService({
|
|
51
|
+
accountability: req.accountability,
|
|
52
|
+
schema: req.schema,
|
|
53
|
+
});
|
|
54
|
+
let result;
|
|
55
|
+
if (req.singleton) {
|
|
56
|
+
result = await service.readSingleton(req.sanitizedQuery);
|
|
57
|
+
}
|
|
58
|
+
else if (req.body.keys) {
|
|
59
|
+
result = await service.readMany(req.body.keys, req.sanitizedQuery);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
result = await service.readByQuery(req.sanitizedQuery);
|
|
63
|
+
}
|
|
64
|
+
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
|
65
|
+
res.locals['payload'] = { data: result, meta };
|
|
66
|
+
return next();
|
|
67
|
+
});
|
|
68
|
+
router.get('/', validateBatch('read'), readHandler, respond);
|
|
69
|
+
router.search('/', validateBatch('read'), readHandler, respond);
|
|
70
|
+
router.get('/:pk', asyncHandler(async (req, res, next) => {
|
|
71
|
+
const service = new VersionsService({
|
|
72
|
+
accountability: req.accountability,
|
|
73
|
+
schema: req.schema,
|
|
74
|
+
});
|
|
75
|
+
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
|
|
76
|
+
res.locals['payload'] = { data: record || null };
|
|
77
|
+
return next();
|
|
78
|
+
}), respond);
|
|
79
|
+
router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) => {
|
|
80
|
+
const service = new VersionsService({
|
|
81
|
+
accountability: req.accountability,
|
|
82
|
+
schema: req.schema,
|
|
83
|
+
});
|
|
84
|
+
let keys = [];
|
|
85
|
+
if (Array.isArray(req.body)) {
|
|
86
|
+
keys = await service.updateBatch(req.body);
|
|
87
|
+
}
|
|
88
|
+
else if (req.body.keys) {
|
|
89
|
+
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
93
|
+
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const result = await service.readMany(keys, req.sanitizedQuery);
|
|
97
|
+
res.locals['payload'] = { data: result || null };
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (isDirectusError(error, ErrorCode.Forbidden)) {
|
|
101
|
+
return next();
|
|
102
|
+
}
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
return next();
|
|
106
|
+
}), respond);
|
|
107
|
+
router.patch('/:pk', asyncHandler(async (req, res, next) => {
|
|
108
|
+
const service = new VersionsService({
|
|
109
|
+
accountability: req.accountability,
|
|
110
|
+
schema: req.schema,
|
|
111
|
+
});
|
|
112
|
+
const primaryKey = await service.updateOne(req.params['pk'], req.body);
|
|
113
|
+
try {
|
|
114
|
+
const record = await service.readOne(primaryKey, req.sanitizedQuery);
|
|
115
|
+
res.locals['payload'] = { data: record || null };
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (isDirectusError(error, ErrorCode.Forbidden)) {
|
|
119
|
+
return next();
|
|
120
|
+
}
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
return next();
|
|
124
|
+
}), respond);
|
|
125
|
+
router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next) => {
|
|
126
|
+
const service = new VersionsService({
|
|
127
|
+
accountability: req.accountability,
|
|
128
|
+
schema: req.schema,
|
|
129
|
+
});
|
|
130
|
+
if (Array.isArray(req.body)) {
|
|
131
|
+
await service.deleteMany(req.body);
|
|
132
|
+
}
|
|
133
|
+
else if (req.body.keys) {
|
|
134
|
+
await service.deleteMany(req.body.keys);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
138
|
+
await service.deleteByQuery(sanitizedQuery);
|
|
139
|
+
}
|
|
140
|
+
return next();
|
|
141
|
+
}), respond);
|
|
142
|
+
router.delete('/:pk', asyncHandler(async (req, _res, next) => {
|
|
143
|
+
const service = new VersionsService({
|
|
144
|
+
accountability: req.accountability,
|
|
145
|
+
schema: req.schema,
|
|
146
|
+
});
|
|
147
|
+
await service.deleteOne(req.params['pk']);
|
|
148
|
+
return next();
|
|
149
|
+
}), respond);
|
|
150
|
+
router.get('/:pk/compare', asyncHandler(async (req, res, next) => {
|
|
151
|
+
const service = new VersionsService({
|
|
152
|
+
accountability: req.accountability,
|
|
153
|
+
schema: req.schema,
|
|
154
|
+
});
|
|
155
|
+
const version = await service.readOne(req.params['pk']);
|
|
156
|
+
const { outdated, mainHash } = await service.verifyHash(version['collection'], version['item'], version['hash']);
|
|
157
|
+
const saves = await service.getVersionSavesById(version['id']);
|
|
158
|
+
const current = assign({}, ...saves);
|
|
159
|
+
const main = await service.getMainItem(version['collection'], version['item']);
|
|
160
|
+
res.locals['payload'] = { data: { outdated, mainHash, current, main } };
|
|
161
|
+
return next();
|
|
162
|
+
}), respond);
|
|
163
|
+
router.post('/:pk/save', asyncHandler(async (req, res, next) => {
|
|
164
|
+
const service = new VersionsService({
|
|
165
|
+
accountability: req.accountability,
|
|
166
|
+
schema: req.schema,
|
|
167
|
+
});
|
|
168
|
+
const version = await service.readOne(req.params['pk']);
|
|
169
|
+
const mainItem = await service.getMainItem(version['collection'], version['item']);
|
|
170
|
+
await service.save(req.params['pk'], req.body);
|
|
171
|
+
const saves = await service.getVersionSavesById(req.params['pk']);
|
|
172
|
+
const result = assign(mainItem, ...saves);
|
|
173
|
+
res.locals['payload'] = { data: result || null };
|
|
174
|
+
return next();
|
|
175
|
+
}), respond);
|
|
176
|
+
router.post('/:pk/promote', asyncHandler(async (req, res, next) => {
|
|
177
|
+
if (typeof req.body.mainHash !== 'string') {
|
|
178
|
+
throw new InvalidPayloadError({ reason: `"mainHash" field is required` });
|
|
179
|
+
}
|
|
180
|
+
const service = new VersionsService({
|
|
181
|
+
accountability: req.accountability,
|
|
182
|
+
schema: req.schema,
|
|
183
|
+
});
|
|
184
|
+
const updatedItemKey = await service.promote(req.params['pk'], req.body.mainHash, req.body?.['fields']);
|
|
185
|
+
res.locals['payload'] = { data: updatedItemKey || null };
|
|
186
|
+
return next();
|
|
187
|
+
}), respond);
|
|
188
|
+
export default router;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export async function up(knex) {
|
|
2
|
+
await knex.schema.createTable('directus_versions', (table) => {
|
|
3
|
+
table.uuid('id').primary().notNullable();
|
|
4
|
+
table.string('key', 64).notNullable();
|
|
5
|
+
table.string('name');
|
|
6
|
+
table
|
|
7
|
+
.string('collection', 64)
|
|
8
|
+
.notNullable()
|
|
9
|
+
.references('collection')
|
|
10
|
+
.inTable('directus_collections')
|
|
11
|
+
.onDelete('CASCADE');
|
|
12
|
+
table.string('item').notNullable();
|
|
13
|
+
// Hash is managed on API side
|
|
14
|
+
table.string('hash');
|
|
15
|
+
table.timestamp('date_created').defaultTo(knex.fn.now());
|
|
16
|
+
table.timestamp('date_updated').defaultTo(knex.fn.now());
|
|
17
|
+
table.uuid('user_created').references('id').inTable('directus_users').onDelete('SET NULL');
|
|
18
|
+
// Cannot have two constraints from/to the same table, handled on API side
|
|
19
|
+
table.uuid('user_updated').references('id').inTable('directus_users');
|
|
20
|
+
});
|
|
21
|
+
await knex.schema.alterTable('directus_collections', (table) => {
|
|
22
|
+
table.boolean('versioning').notNullable().defaultTo(false);
|
|
23
|
+
});
|
|
24
|
+
await knex.schema.alterTable('directus_revisions', (table) => {
|
|
25
|
+
table.uuid('version').references('id').inTable('directus_versions').onDelete('CASCADE');
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export async function down(knex) {
|
|
29
|
+
await knex.schema.alterTable('directus_collections', (table) => {
|
|
30
|
+
table.dropColumn('versioning');
|
|
31
|
+
});
|
|
32
|
+
await knex.schema.alterTable('directus_revisions', (table) => {
|
|
33
|
+
table.dropColumn('version');
|
|
34
|
+
});
|
|
35
|
+
await knex.schema.dropTable('directus_versions');
|
|
36
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export async function up(knex) {
|
|
2
|
+
/**
|
|
3
|
+
* Knex doesn't support setting defaults to null (you'll end up with `NULL::character varying`),
|
|
4
|
+
* so we'll have to create a new column, copy over the relevant bits, and remove the old
|
|
5
|
+
*/
|
|
6
|
+
await knex.schema.alterTable('directus_users', (table) => {
|
|
7
|
+
table.string('appearance');
|
|
8
|
+
});
|
|
9
|
+
await knex('directus_users').update({ appearance: 'dark' }).where({ theme: 'dark' });
|
|
10
|
+
await knex('directus_users').update({ appearance: 'light' }).where({ theme: 'light' });
|
|
11
|
+
await knex.schema.alterTable('directus_users', (table) => {
|
|
12
|
+
table.dropColumn('theme');
|
|
13
|
+
table.string('theme_dark');
|
|
14
|
+
table.string('theme_light');
|
|
15
|
+
table.json('theme_light_overrides');
|
|
16
|
+
table.json('theme_dark_overrides');
|
|
17
|
+
});
|
|
18
|
+
await knex('directus_settings').update({ project_color: '#6644ff' }).whereNull('project_color');
|
|
19
|
+
await knex.schema.alterTable('directus_settings', (table) => {
|
|
20
|
+
table.string('project_color').defaultTo('#6644FF').notNullable().alter();
|
|
21
|
+
table.uuid('public_favicon').references('directus_files.id');
|
|
22
|
+
table.string('default_appearance').defaultTo('auto').notNullable();
|
|
23
|
+
table.string('default_theme_light');
|
|
24
|
+
table.json('theme_light_overrides');
|
|
25
|
+
table.string('default_theme_dark');
|
|
26
|
+
table.json('theme_dark_overrides');
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export async function down(knex) {
|
|
30
|
+
await knex.schema.alterTable('directus_users', (table) => {
|
|
31
|
+
table.renameColumn('appearance', 'theme');
|
|
32
|
+
});
|
|
33
|
+
await knex.schema.alterTable('directus_users', (table) => {
|
|
34
|
+
table.string('theme').defaultTo('auto').alter();
|
|
35
|
+
table.dropColumn('theme_dark');
|
|
36
|
+
table.dropColumn('theme_light');
|
|
37
|
+
table.dropColumn('theme_light_overrides');
|
|
38
|
+
table.dropColumn('theme_dark_overrides');
|
|
39
|
+
});
|
|
40
|
+
await knex.schema.alterTable('directus_settings', (table) => {
|
|
41
|
+
table.string('project_color').defaultTo(null).nullable().alter();
|
|
42
|
+
table.dropColumn('public_favicon');
|
|
43
|
+
table.dropColumn('default_appearance');
|
|
44
|
+
table.dropColumn('default_theme_light');
|
|
45
|
+
table.dropColumn('theme_light_overrides');
|
|
46
|
+
table.dropColumn('default_theme_dark');
|
|
47
|
+
table.dropColumn('theme_dark_overrides');
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export async function up(knex) {
|
|
2
|
+
const panels = await knex('directus_panels').where('type', '=', 'metric').select();
|
|
3
|
+
const updates = [];
|
|
4
|
+
for (const panel of panels) {
|
|
5
|
+
let options = panel.options;
|
|
6
|
+
// Check if the options are stringified and parse them
|
|
7
|
+
const wasStringified = typeof options === 'string';
|
|
8
|
+
if (wasStringified) {
|
|
9
|
+
options = JSON.parse(options);
|
|
10
|
+
}
|
|
11
|
+
// Not expected, just to be on the safe side
|
|
12
|
+
if (!options)
|
|
13
|
+
continue;
|
|
14
|
+
let needsUpdate = false;
|
|
15
|
+
// Check and update abbreviate -> notation
|
|
16
|
+
if (options.abbreviate === true) {
|
|
17
|
+
options.notation = 'compact';
|
|
18
|
+
delete options.abbreviate;
|
|
19
|
+
needsUpdate = true;
|
|
20
|
+
}
|
|
21
|
+
// Check and update decimals -> minimumFractionDigits and maximumFractionDigits
|
|
22
|
+
if (typeof options.decimals === 'number') {
|
|
23
|
+
options.minimumFractionDigits = options.decimals;
|
|
24
|
+
options.maximumFractionDigits = options.decimals;
|
|
25
|
+
delete options.decimals;
|
|
26
|
+
needsUpdate = true;
|
|
27
|
+
}
|
|
28
|
+
// Update the row with modified options if necessary
|
|
29
|
+
if (needsUpdate) {
|
|
30
|
+
// Convert the options back to string if they were stringified initially
|
|
31
|
+
if (wasStringified) {
|
|
32
|
+
options = JSON.stringify(options);
|
|
33
|
+
}
|
|
34
|
+
updates.push(knex('directus_panels').update({ options }).where('id', panel.id));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return Promise.all(updates);
|
|
38
|
+
}
|
|
39
|
+
export async function down(knex) {
|
|
40
|
+
const panels = await knex('directus_panels').where('type', '=', 'metric').select();
|
|
41
|
+
const updates = [];
|
|
42
|
+
for (const panel of panels) {
|
|
43
|
+
let options = panel.options;
|
|
44
|
+
// Check if the options are stringified and parse them
|
|
45
|
+
const wasStringified = typeof options === 'string';
|
|
46
|
+
if (wasStringified) {
|
|
47
|
+
options = JSON.parse(options);
|
|
48
|
+
}
|
|
49
|
+
// Not expected, just to be on the safe side
|
|
50
|
+
if (!options)
|
|
51
|
+
continue;
|
|
52
|
+
let needsUpdate = false;
|
|
53
|
+
// Revert notation -> abbreviate
|
|
54
|
+
if (options.notation === 'compact') {
|
|
55
|
+
options.abbreviate = true;
|
|
56
|
+
delete options.notation;
|
|
57
|
+
needsUpdate = true;
|
|
58
|
+
}
|
|
59
|
+
// Revert minimumFractionDigits and maximumFractionDigits -> decimals
|
|
60
|
+
if (typeof options.minimumFractionDigits === 'number' &&
|
|
61
|
+
options.minimumFractionDigits === options.maximumFractionDigits) {
|
|
62
|
+
options.decimals = options.minimumFractionDigits;
|
|
63
|
+
delete options.minimumFractionDigits;
|
|
64
|
+
delete options.maximumFractionDigits;
|
|
65
|
+
needsUpdate = true;
|
|
66
|
+
}
|
|
67
|
+
// Update the row with reverted options if necessary
|
|
68
|
+
if (needsUpdate) {
|
|
69
|
+
// Convert the options back to string if they were stringified initially
|
|
70
|
+
if (wasStringified) {
|
|
71
|
+
options = JSON.stringify(options);
|
|
72
|
+
}
|
|
73
|
+
updates.push(knex('directus_panels').update({ options }).where('id', panel.id));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return Promise.all(updates);
|
|
77
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export async function up(knex) {
|
|
2
|
+
await knex.schema.createTable('directus_extensions', (table) => {
|
|
3
|
+
table.string('name').primary().notNullable();
|
|
4
|
+
table.boolean('enabled').defaultTo(true).notNullable();
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
export async function down(knex) {
|
|
8
|
+
await knex.schema.dropTable('directus_extensions');
|
|
9
|
+
}
|
package/dist/database/run-ast.js
CHANGED
|
@@ -164,7 +164,7 @@ async function getDBQuery(schema, knex, table, fieldNodes, query) {
|
|
|
164
164
|
const innerQuerySortRecords = [];
|
|
165
165
|
let hasMultiRelationalSort;
|
|
166
166
|
if (queryCopy.sort) {
|
|
167
|
-
const sortResult = applySort(knex, schema, dbQuery, queryCopy
|
|
167
|
+
const sortResult = applySort(knex, schema, dbQuery, queryCopy, table, aliasMap, true);
|
|
168
168
|
if (sortResult) {
|
|
169
169
|
sortRecords = sortResult.sortRecords;
|
|
170
170
|
hasMultiRelationalSort = sortResult.hasMultiRelationalSort;
|
|
@@ -95,3 +95,9 @@ data:
|
|
|
95
95
|
|
|
96
96
|
- collection: directus_translations
|
|
97
97
|
note: $t:directus_collection.directus_translations
|
|
98
|
+
|
|
99
|
+
- collection: directus_versions
|
|
100
|
+
note: $t:directus_collection.directus_versions
|
|
101
|
+
|
|
102
|
+
- collection: directus_extensions
|
|
103
|
+
note: $t:directus_collection.directus_extensions
|
|
@@ -13,15 +13,15 @@ fields:
|
|
|
13
13
|
choices:
|
|
14
14
|
- text: $t:field_options.directus_activity.create
|
|
15
15
|
value: create
|
|
16
|
-
foreground: 'var(--primary)'
|
|
17
|
-
background: 'var(--primary-
|
|
16
|
+
foreground: 'var(--theme--primary)'
|
|
17
|
+
background: 'var(--theme--primary-subdued)'
|
|
18
18
|
- text: $t:field_options.directus_activity.update
|
|
19
19
|
value: update
|
|
20
20
|
foreground: 'var(--blue)'
|
|
21
21
|
background: 'var(--blue-25)'
|
|
22
22
|
- text: $t:field_options.directus_activity.delete
|
|
23
23
|
value: delete
|
|
24
|
-
foreground: 'var(--danger)'
|
|
24
|
+
foreground: 'var(--theme--danger)'
|
|
25
25
|
background: 'var(--danger-25)'
|
|
26
26
|
- text: $t:field_options.directus_activity.login
|
|
27
27
|
value: login
|
|
@@ -51,7 +51,7 @@ fields:
|
|
|
51
51
|
- field: comment
|
|
52
52
|
display: formatted-value
|
|
53
53
|
display_options:
|
|
54
|
-
color: 'var(--foreground-subdued)'
|
|
54
|
+
color: 'var(--theme--foreground-subdued)'
|
|
55
55
|
width: half
|
|
56
56
|
|
|
57
57
|
- field: user_agent
|