@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.
Files changed (109) hide show
  1. package/dist/__utils__/snapshots.js +9 -0
  2. package/dist/app.js +2 -0
  3. package/dist/cli/index.js +3 -4
  4. package/dist/cli/load-extensions.d.ts +1 -0
  5. package/dist/cli/load-extensions.js +19 -0
  6. package/dist/controllers/extensions.js +28 -19
  7. package/dist/controllers/versions.d.ts +2 -0
  8. package/dist/controllers/versions.js +188 -0
  9. package/dist/database/migrations/20230823A-add-content-versioning.d.ts +3 -0
  10. package/dist/database/migrations/20230823A-add-content-versioning.js +36 -0
  11. package/dist/database/migrations/20230927A-themes.d.ts +3 -0
  12. package/dist/database/migrations/20230927A-themes.js +49 -0
  13. package/dist/database/migrations/20231009B-update-panel-options.d.ts +3 -0
  14. package/dist/database/migrations/20231009B-update-panel-options.js +77 -0
  15. package/dist/database/migrations/20231010A-add-extensions.d.ts +3 -0
  16. package/dist/database/migrations/20231010A-add-extensions.js +9 -0
  17. package/dist/database/run-ast.js +1 -1
  18. package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +5 -1
  19. package/dist/database/system-data/collections/collections.yaml +6 -0
  20. package/dist/database/system-data/fields/activity.yaml +4 -4
  21. package/dist/database/system-data/fields/collections.yaml +19 -0
  22. package/dist/database/system-data/fields/extensions.yaml +10 -0
  23. package/dist/database/system-data/fields/revisions.yaml +3 -0
  24. package/dist/database/system-data/fields/settings.yaml +73 -17
  25. package/dist/database/system-data/fields/users.yaml +50 -12
  26. package/dist/database/system-data/fields/versions.yaml +38 -0
  27. package/dist/database/system-data/fields/webhooks.yaml +9 -9
  28. package/dist/database/system-data/relations/relations.yaml +88 -20
  29. package/dist/emitter.d.ts +2 -2
  30. package/dist/env.js +4 -0
  31. package/dist/extensions/lib/get-extensions-settings.d.ts +7 -0
  32. package/dist/extensions/lib/get-extensions-settings.js +39 -0
  33. package/dist/extensions/lib/get-extensions.d.ts +1 -0
  34. package/dist/extensions/lib/get-extensions.js +11 -0
  35. package/dist/extensions/{get-shared-deps-mapping.js → lib/get-shared-deps-mapping.js} +3 -3
  36. package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +31 -0
  37. package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.js +80 -0
  38. package/dist/extensions/lib/sandbox/generate-host-function-reference.d.ts +11 -0
  39. package/dist/extensions/lib/sandbox/generate-host-function-reference.js +28 -0
  40. package/dist/extensions/lib/sandbox/register/action.d.ts +6 -0
  41. package/dist/extensions/lib/sandbox/register/action.js +18 -0
  42. package/dist/extensions/lib/sandbox/register/call-reference.d.ts +5 -0
  43. package/dist/extensions/lib/sandbox/register/call-reference.js +20 -0
  44. package/dist/extensions/lib/sandbox/register/filter.d.ts +6 -0
  45. package/dist/extensions/lib/sandbox/register/filter.js +21 -0
  46. package/dist/extensions/lib/sandbox/register/index.d.ts +5 -0
  47. package/dist/extensions/lib/sandbox/register/index.js +5 -0
  48. package/dist/extensions/lib/sandbox/register/operation.d.ts +6 -0
  49. package/dist/extensions/lib/sandbox/register/operation.js +19 -0
  50. package/dist/extensions/lib/sandbox/register/route.d.ts +17 -0
  51. package/dist/extensions/lib/sandbox/register/route.js +44 -0
  52. package/dist/extensions/lib/sandbox/sdk/generators/index.d.ts +3 -0
  53. package/dist/extensions/lib/sandbox/sdk/generators/index.js +3 -0
  54. package/dist/extensions/lib/sandbox/sdk/generators/log.d.ts +3 -0
  55. package/dist/extensions/lib/sandbox/sdk/generators/log.js +11 -0
  56. package/dist/extensions/lib/sandbox/sdk/generators/request.d.ts +12 -0
  57. package/dist/extensions/lib/sandbox/sdk/generators/request.js +49 -0
  58. package/dist/extensions/lib/sandbox/sdk/generators/sleep.d.ts +3 -0
  59. package/dist/extensions/lib/sandbox/sdk/generators/sleep.js +11 -0
  60. package/dist/extensions/lib/sandbox/sdk/index.d.ts +2 -0
  61. package/dist/extensions/lib/sandbox/sdk/index.js +2 -0
  62. package/dist/extensions/lib/sandbox/sdk/instantiate.d.ts +11 -0
  63. package/dist/extensions/lib/sandbox/sdk/instantiate.js +28 -0
  64. package/dist/extensions/lib/sandbox/sdk/sdk.d.ts +20 -0
  65. package/dist/extensions/lib/sandbox/sdk/sdk.js +11 -0
  66. package/dist/extensions/lib/sandbox/sdk/utils/index.d.ts +1 -0
  67. package/dist/extensions/lib/sandbox/sdk/utils/index.js +1 -0
  68. package/dist/extensions/lib/sandbox/sdk/utils/wrap.d.ts +11 -0
  69. package/dist/extensions/lib/sandbox/sdk/utils/wrap.js +17 -0
  70. package/dist/extensions/manager.d.ts +128 -14
  71. package/dist/extensions/manager.js +310 -136
  72. package/dist/extensions/types.d.ts +1 -5
  73. package/dist/flows.d.ts +1 -1
  74. package/dist/flows.js +6 -6
  75. package/dist/middleware/respond.js +12 -0
  76. package/dist/server.js +2 -1
  77. package/dist/services/assets.js +1 -1
  78. package/dist/services/extensions.d.ts +31 -0
  79. package/dist/services/extensions.js +136 -0
  80. package/dist/services/graphql/index.d.ts +1 -1
  81. package/dist/services/graphql/index.js +87 -24
  82. package/dist/services/index.d.ts +2 -0
  83. package/dist/services/index.js +2 -0
  84. package/dist/services/server.js +3 -1
  85. package/dist/services/users.js +2 -0
  86. package/dist/services/versions.d.ts +21 -0
  87. package/dist/services/versions.js +238 -0
  88. package/dist/types/collection.d.ts +1 -0
  89. package/dist/utils/apply-query.d.ts +1 -1
  90. package/dist/utils/apply-query.js +30 -2
  91. package/dist/utils/delete-from-require-cache.d.ts +1 -0
  92. package/dist/utils/delete-from-require-cache.js +5 -0
  93. package/dist/utils/get-service.js +3 -1
  94. package/dist/utils/import-file-url.d.ts +5 -0
  95. package/dist/utils/import-file-url.js +6 -0
  96. package/dist/utils/job-queue.d.ts +2 -3
  97. package/dist/utils/redact-object.d.ts +1 -1
  98. package/dist/utils/redact-object.js +37 -24
  99. package/dist/utils/sanitize-query.js +3 -0
  100. package/dist/utils/validate-query.js +1 -0
  101. package/dist/worker-pool.js +8 -0
  102. package/package.json +28 -27
  103. package/dist/extensions/get-extensions.d.ts +0 -47
  104. package/dist/extensions/get-extensions.js +0 -9
  105. package/dist/extensions/normalize-extension-info.d.ts +0 -5
  106. package/dist/extensions/normalize-extension-info.js +0 -30
  107. /package/dist/extensions/{get-shared-deps-mapping.d.ts → lib/get-shared-deps-mapping.d.ts} +0 -0
  108. /package/dist/extensions/{wrap-embeds.d.ts → lib/wrap-embeds.d.ts} +0 -0
  109. /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 * as pkg from '../utils/package.js';
17
+ import { loadExtensions } from './load-extensions.js';
18
18
  export async function createCli() {
19
19
  const program = new Command();
20
- const extensionManager = getExtensionManager();
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 { EXTENSION_TYPES } from '@directus/extensions';
2
- import { depluralize, isIn } from '@directus/utils';
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.get('/:type?', asyncHandler(async (req, res, next) => {
13
- let type = undefined;
14
- if (req.params['type']) {
15
- const singularType = depluralize(req.params['type']);
16
- if (!isIn(singularType, EXTENSION_TYPES)) {
17
- throw new RouteNotFoundError({ path: req.path });
18
- }
19
- type = singularType;
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
- const extensionManager = getExtensionManager();
22
- const extensions = extensionManager.getExtensionsList(type);
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.getAppExtensions();
42
+ source = extensionManager.getAppExtensionsBundle();
34
43
  }
35
44
  else {
36
45
  source = extensionManager.getAppExtensionChunk(chunk);
@@ -0,0 +1,2 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
@@ -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,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -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,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -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,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<number[]>;
3
+ export declare function down(knex: Knex): Promise<number[]>;
@@ -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,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -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
+ }
@@ -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.sort, table, aliasMap, true);
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;
@@ -99,7 +99,11 @@
99
99
  - preferences_divider
100
100
  - avatar
101
101
  - language
102
- - theme
102
+ - appearance
103
+ - theme_light
104
+ - theme_dark
105
+ - theme_light_overrides
106
+ - theme_dark_overrides
103
107
  - tfa_secret
104
108
  - status
105
109
  - role
@@ -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-25)'
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