@directus/api 13.1.1 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/dist/__utils__/snapshots.js +9 -0
  2. package/dist/app.js +6 -4
  3. package/dist/auth/drivers/ldap.js +3 -2
  4. package/dist/auth/drivers/local.js +1 -1
  5. package/dist/auth/drivers/oauth2.js +1 -1
  6. package/dist/auth/drivers/openid.js +1 -1
  7. package/dist/auth/drivers/saml.js +1 -1
  8. package/dist/auth.js +1 -1
  9. package/dist/cli/index.js +7 -4
  10. package/dist/controllers/activity.js +1 -1
  11. package/dist/controllers/assets.js +2 -2
  12. package/dist/controllers/auth.js +1 -1
  13. package/dist/controllers/collections.js +1 -1
  14. package/dist/controllers/dashboards.js +1 -1
  15. package/dist/controllers/extensions.js +29 -16
  16. package/dist/controllers/fields.js +1 -1
  17. package/dist/controllers/files.js +1 -1
  18. package/dist/controllers/flows.js +1 -1
  19. package/dist/controllers/folders.js +1 -1
  20. package/dist/controllers/items.js +1 -1
  21. package/dist/controllers/not-found.js +1 -1
  22. package/dist/controllers/notifications.js +1 -1
  23. package/dist/controllers/operations.js +1 -1
  24. package/dist/controllers/panels.js +1 -1
  25. package/dist/controllers/permissions.js +1 -1
  26. package/dist/controllers/presets.js +1 -1
  27. package/dist/controllers/relations.js +1 -1
  28. package/dist/controllers/roles.js +1 -1
  29. package/dist/controllers/schema.js +1 -1
  30. package/dist/controllers/server.js +1 -1
  31. package/dist/controllers/settings.js +1 -1
  32. package/dist/controllers/shares.js +1 -1
  33. package/dist/controllers/translations.js +1 -1
  34. package/dist/controllers/users.js +1 -1
  35. package/dist/controllers/utils.js +37 -18
  36. package/dist/controllers/versions.d.ts +2 -0
  37. package/dist/controllers/versions.js +188 -0
  38. package/dist/controllers/webhooks.js +1 -1
  39. package/dist/database/errors/dialects/mssql.js +1 -1
  40. package/dist/database/errors/dialects/mysql.js +1 -1
  41. package/dist/database/errors/dialects/oracle.js +1 -1
  42. package/dist/database/errors/dialects/postgres.js +1 -1
  43. package/dist/database/errors/dialects/sqlite.js +1 -1
  44. package/dist/database/helpers/schema/dialects/mysql.js +1 -1
  45. package/dist/database/helpers/sequence/dialects/postgres.d.ts +5 -2
  46. package/dist/database/helpers/sequence/dialects/postgres.js +6 -3
  47. package/dist/database/migrations/20230823A-add-content-versioning.d.ts +3 -0
  48. package/dist/database/migrations/20230823A-add-content-versioning.js +36 -0
  49. package/dist/database/migrations/20230927A-themes.d.ts +3 -0
  50. package/dist/database/migrations/20230927A-themes.js +49 -0
  51. package/dist/database/migrations/20231009A-update-csv-fields-to-text.d.ts +3 -0
  52. package/dist/database/migrations/20231009A-update-csv-fields-to-text.js +44 -0
  53. package/dist/database/migrations/20231009B-update-panel-options.d.ts +3 -0
  54. package/dist/database/migrations/20231009B-update-panel-options.js +77 -0
  55. package/dist/database/migrations/20231010A-add-extensions.d.ts +3 -0
  56. package/dist/database/migrations/20231010A-add-extensions.js +9 -0
  57. package/dist/database/run-ast.js +2 -2
  58. package/dist/database/seeds/run.js +1 -1
  59. package/dist/database/system-data/collections/collections.yaml +6 -0
  60. package/dist/database/system-data/fields/activity.yaml +4 -4
  61. package/dist/database/system-data/fields/collections.yaml +19 -0
  62. package/dist/database/system-data/fields/extensions.yaml +10 -0
  63. package/dist/database/system-data/fields/revisions.yaml +3 -0
  64. package/dist/database/system-data/fields/settings.yaml +73 -17
  65. package/dist/database/system-data/fields/users.yaml +48 -12
  66. package/dist/database/system-data/fields/versions.yaml +38 -0
  67. package/dist/database/system-data/fields/webhooks.yaml +9 -9
  68. package/dist/database/system-data/relations/relations.yaml +88 -20
  69. package/dist/env.js +4 -0
  70. package/dist/extensions/index.d.ts +2 -0
  71. package/dist/extensions/index.js +9 -0
  72. package/dist/extensions/lib/get-extensions-settings.d.ts +7 -0
  73. package/dist/extensions/lib/get-extensions-settings.js +39 -0
  74. package/dist/extensions/lib/get-extensions.d.ts +1 -0
  75. package/dist/extensions/lib/get-extensions.js +11 -0
  76. package/dist/extensions/lib/get-shared-deps-mapping.d.ts +1 -0
  77. package/dist/extensions/lib/get-shared-deps-mapping.js +26 -0
  78. package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +31 -0
  79. package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.js +80 -0
  80. package/dist/extensions/lib/sandbox/generate-host-function-reference.d.ts +11 -0
  81. package/dist/extensions/lib/sandbox/generate-host-function-reference.js +28 -0
  82. package/dist/extensions/lib/sandbox/register/action.d.ts +6 -0
  83. package/dist/extensions/lib/sandbox/register/action.js +18 -0
  84. package/dist/extensions/lib/sandbox/register/call-reference.d.ts +5 -0
  85. package/dist/extensions/lib/sandbox/register/call-reference.js +20 -0
  86. package/dist/extensions/lib/sandbox/register/filter.d.ts +6 -0
  87. package/dist/extensions/lib/sandbox/register/filter.js +21 -0
  88. package/dist/extensions/lib/sandbox/register/index.d.ts +5 -0
  89. package/dist/extensions/lib/sandbox/register/index.js +5 -0
  90. package/dist/extensions/lib/sandbox/register/operation.d.ts +6 -0
  91. package/dist/extensions/lib/sandbox/register/operation.js +19 -0
  92. package/dist/extensions/lib/sandbox/register/route.d.ts +17 -0
  93. package/dist/extensions/lib/sandbox/register/route.js +44 -0
  94. package/dist/extensions/lib/sandbox/sdk/generators/index.d.ts +3 -0
  95. package/dist/extensions/lib/sandbox/sdk/generators/index.js +3 -0
  96. package/dist/extensions/lib/sandbox/sdk/generators/log.d.ts +3 -0
  97. package/dist/extensions/lib/sandbox/sdk/generators/log.js +11 -0
  98. package/dist/extensions/lib/sandbox/sdk/generators/request.d.ts +12 -0
  99. package/dist/extensions/lib/sandbox/sdk/generators/request.js +49 -0
  100. package/dist/extensions/lib/sandbox/sdk/generators/sleep.d.ts +3 -0
  101. package/dist/extensions/lib/sandbox/sdk/generators/sleep.js +11 -0
  102. package/dist/extensions/lib/sandbox/sdk/index.d.ts +2 -0
  103. package/dist/extensions/lib/sandbox/sdk/index.js +2 -0
  104. package/dist/extensions/lib/sandbox/sdk/instantiate.d.ts +11 -0
  105. package/dist/extensions/lib/sandbox/sdk/instantiate.js +28 -0
  106. package/dist/extensions/lib/sandbox/sdk/sdk.d.ts +20 -0
  107. package/dist/extensions/lib/sandbox/sdk/sdk.js +11 -0
  108. package/dist/extensions/lib/sandbox/sdk/utils/index.d.ts +1 -0
  109. package/dist/extensions/lib/sandbox/sdk/utils/index.js +1 -0
  110. package/dist/extensions/lib/sandbox/sdk/utils/wrap.d.ts +11 -0
  111. package/dist/extensions/lib/sandbox/sdk/utils/wrap.js +17 -0
  112. package/dist/extensions/lib/wrap-embeds.d.ts +4 -0
  113. package/dist/extensions/lib/wrap-embeds.js +8 -0
  114. package/dist/extensions/manager.d.ts +158 -0
  115. package/dist/extensions/manager.js +604 -0
  116. package/dist/extensions/types.d.ts +19 -0
  117. package/dist/flows.d.ts +2 -2
  118. package/dist/flows.js +7 -7
  119. package/dist/middleware/check-ip.js +1 -1
  120. package/dist/middleware/collection-exists.js +1 -1
  121. package/dist/middleware/error-handler.js +1 -1
  122. package/dist/middleware/graphql.js +1 -1
  123. package/dist/middleware/rate-limiter-global.js +1 -1
  124. package/dist/middleware/rate-limiter-ip.js +1 -1
  125. package/dist/middleware/respond.js +13 -1
  126. package/dist/middleware/validate-batch.js +1 -1
  127. package/dist/operations/condition/index.d.ts +1 -1
  128. package/dist/operations/condition/index.js +2 -1
  129. package/dist/operations/exec/index.d.ts +1 -1
  130. package/dist/operations/exec/index.js +1 -1
  131. package/dist/operations/item-create/index.d.ts +1 -1
  132. package/dist/operations/item-create/index.js +2 -1
  133. package/dist/operations/item-delete/index.d.ts +1 -1
  134. package/dist/operations/item-delete/index.js +2 -1
  135. package/dist/operations/item-read/index.d.ts +1 -1
  136. package/dist/operations/item-read/index.js +2 -1
  137. package/dist/operations/item-update/index.d.ts +1 -1
  138. package/dist/operations/item-update/index.js +2 -1
  139. package/dist/operations/json-web-token/index.d.ts +1 -1
  140. package/dist/operations/json-web-token/index.js +2 -1
  141. package/dist/operations/log/index.d.ts +1 -1
  142. package/dist/operations/log/index.js +2 -1
  143. package/dist/operations/mail/index.d.ts +1 -1
  144. package/dist/operations/mail/index.js +1 -1
  145. package/dist/operations/notification/index.d.ts +1 -1
  146. package/dist/operations/notification/index.js +2 -1
  147. package/dist/operations/request/index.d.ts +1 -1
  148. package/dist/operations/request/index.js +3 -2
  149. package/dist/operations/sleep/index.d.ts +1 -1
  150. package/dist/operations/sleep/index.js +1 -1
  151. package/dist/operations/transform/index.d.ts +1 -1
  152. package/dist/operations/transform/index.js +2 -1
  153. package/dist/operations/trigger/index.d.ts +1 -1
  154. package/dist/operations/trigger/index.js +2 -1
  155. package/dist/services/activity.js +1 -1
  156. package/dist/services/assets.d.ts +1 -1
  157. package/dist/services/assets.js +3 -3
  158. package/dist/services/authentication.js +2 -2
  159. package/dist/services/authorization.js +1 -1
  160. package/dist/services/collections.js +3 -3
  161. package/dist/services/extensions.d.ts +31 -0
  162. package/dist/services/extensions.js +121 -0
  163. package/dist/services/fields.d.ts +2 -2
  164. package/dist/services/fields.js +4 -4
  165. package/dist/services/files.d.ts +4 -1
  166. package/dist/services/files.js +5 -5
  167. package/dist/services/graphql/index.d.ts +1 -1
  168. package/dist/services/graphql/index.js +87 -24
  169. package/dist/services/graphql/subscription.js +3 -3
  170. package/dist/services/import-export/import-worker.d.ts +9 -0
  171. package/dist/services/import-export/import-worker.js +9 -0
  172. package/dist/services/{import-export.d.ts → import-export/index.d.ts} +2 -2
  173. package/dist/services/{import-export.js → import-export/index.js} +51 -42
  174. package/dist/services/index.d.ts +3 -1
  175. package/dist/services/index.js +3 -1
  176. package/dist/services/items.js +2 -2
  177. package/dist/services/mail/index.js +1 -1
  178. package/dist/services/meta.js +1 -1
  179. package/dist/services/payload.js +1 -1
  180. package/dist/services/permissions.d.ts +2 -2
  181. package/dist/services/permissions.js +1 -1
  182. package/dist/services/relations.js +1 -1
  183. package/dist/services/revisions.js +1 -1
  184. package/dist/services/roles.js +1 -1
  185. package/dist/services/schema.js +1 -1
  186. package/dist/services/server.js +3 -1
  187. package/dist/services/shares.js +1 -1
  188. package/dist/services/tfa.js +1 -1
  189. package/dist/services/translations.js +1 -1
  190. package/dist/services/users.js +4 -2
  191. package/dist/services/utils.d.ts +1 -0
  192. package/dist/services/utils.js +8 -2
  193. package/dist/services/versions.d.ts +21 -0
  194. package/dist/services/versions.js +232 -0
  195. package/dist/services/websocket.js +11 -1
  196. package/dist/types/collection.d.ts +1 -0
  197. package/dist/types/index.d.ts +0 -1
  198. package/dist/types/index.js +0 -1
  199. package/dist/utils/apply-query.d.ts +1 -1
  200. package/dist/utils/apply-query.js +31 -3
  201. package/dist/utils/delete-from-require-cache.d.ts +1 -0
  202. package/dist/utils/delete-from-require-cache.js +5 -0
  203. package/dist/utils/get-accountability-for-token.js +1 -1
  204. package/dist/utils/get-ast-from-query.js +1 -1
  205. package/dist/utils/get-column-path.js +1 -1
  206. package/dist/utils/get-column.js +1 -1
  207. package/dist/utils/get-default-value.d.ts +1 -2
  208. package/dist/utils/get-permissions.js +1 -1
  209. package/dist/utils/get-service.js +3 -1
  210. package/dist/utils/import-file-url.d.ts +5 -0
  211. package/dist/utils/import-file-url.js +6 -0
  212. package/dist/utils/job-queue.d.ts +2 -3
  213. package/dist/utils/jwt.js +1 -1
  214. package/dist/utils/redact-object.js +9 -3
  215. package/dist/utils/sanitize-query.js +3 -0
  216. package/dist/utils/transformations.d.ts +2 -1
  217. package/dist/utils/validate-diff.js +1 -1
  218. package/dist/utils/validate-keys.js +1 -1
  219. package/dist/utils/validate-query.js +2 -1
  220. package/dist/utils/validate-snapshot.js +1 -1
  221. package/dist/websocket/controllers/base.js +1 -1
  222. package/dist/websocket/controllers/index.d.ts +1 -1
  223. package/dist/websocket/controllers/index.js +0 -7
  224. package/dist/websocket/handlers/heartbeat.js +6 -1
  225. package/dist/websocket/handlers/subscribe.js +11 -16
  226. package/dist/websocket/utils/items.d.ts +4 -14
  227. package/dist/websocket/utils/items.js +59 -64
  228. package/dist/worker-pool.d.ts +2 -0
  229. package/dist/worker-pool.js +11 -0
  230. package/package.json +34 -31
  231. package/dist/errors/codes.d.ts +0 -29
  232. package/dist/errors/codes.js +0 -30
  233. package/dist/errors/contains-null-values.d.ts +0 -7
  234. package/dist/errors/contains-null-values.js +0 -4
  235. package/dist/errors/content-too-large.d.ts +0 -1
  236. package/dist/errors/content-too-large.js +0 -3
  237. package/dist/errors/forbidden.d.ts +0 -1
  238. package/dist/errors/forbidden.js +0 -3
  239. package/dist/errors/hit-rate-limit.d.ts +0 -6
  240. package/dist/errors/hit-rate-limit.js +0 -8
  241. package/dist/errors/illegal-asset-transformation.d.ts +0 -4
  242. package/dist/errors/illegal-asset-transformation.js +0 -3
  243. package/dist/errors/index.d.ts +0 -28
  244. package/dist/errors/index.js +0 -28
  245. package/dist/errors/invalid-credentials.d.ts +0 -1
  246. package/dist/errors/invalid-credentials.js +0 -3
  247. package/dist/errors/invalid-foreign-key.d.ts +0 -6
  248. package/dist/errors/invalid-foreign-key.js +0 -14
  249. package/dist/errors/invalid-ip.d.ts +0 -1
  250. package/dist/errors/invalid-ip.js +0 -3
  251. package/dist/errors/invalid-otp.d.ts +0 -1
  252. package/dist/errors/invalid-otp.js +0 -3
  253. package/dist/errors/invalid-payload.d.ts +0 -5
  254. package/dist/errors/invalid-payload.js +0 -4
  255. package/dist/errors/invalid-provider-config.d.ts +0 -5
  256. package/dist/errors/invalid-provider-config.js +0 -3
  257. package/dist/errors/invalid-provider.d.ts +0 -1
  258. package/dist/errors/invalid-provider.js +0 -3
  259. package/dist/errors/invalid-query.d.ts +0 -5
  260. package/dist/errors/invalid-query.js +0 -4
  261. package/dist/errors/invalid-token.d.ts +0 -1
  262. package/dist/errors/invalid-token.js +0 -3
  263. package/dist/errors/method-not-allowed.d.ts +0 -6
  264. package/dist/errors/method-not-allowed.js +0 -6
  265. package/dist/errors/not-null-violation.d.ts +0 -6
  266. package/dist/errors/not-null-violation.js +0 -14
  267. package/dist/errors/range-not-satisfiable.d.ts +0 -7
  268. package/dist/errors/range-not-satisfiable.js +0 -7
  269. package/dist/errors/record-not-unique.d.ts +0 -6
  270. package/dist/errors/record-not-unique.js +0 -14
  271. package/dist/errors/route-not-found.d.ts +0 -5
  272. package/dist/errors/route-not-found.js +0 -4
  273. package/dist/errors/service-unavailable.d.ts +0 -7
  274. package/dist/errors/service-unavailable.js +0 -4
  275. package/dist/errors/token-expired.d.ts +0 -1
  276. package/dist/errors/token-expired.js +0 -3
  277. package/dist/errors/unexpected-response.d.ts +0 -1
  278. package/dist/errors/unexpected-response.js +0 -3
  279. package/dist/errors/unprocessable-content.d.ts +0 -5
  280. package/dist/errors/unprocessable-content.js +0 -4
  281. package/dist/errors/unsupported-media-type.d.ts +0 -6
  282. package/dist/errors/unsupported-media-type.js +0 -4
  283. package/dist/errors/user-suspended.d.ts +0 -1
  284. package/dist/errors/user-suspended.js +0 -3
  285. package/dist/errors/value-out-of-range.d.ts +0 -6
  286. package/dist/errors/value-out-of-range.js +0 -14
  287. package/dist/errors/value-too-long.d.ts +0 -6
  288. package/dist/errors/value-too-long.js +0 -14
  289. package/dist/extensions.d.ts +0 -51
  290. package/dist/extensions.js +0 -487
  291. package/dist/types/files.d.ts +0 -29
  292. /package/dist/{types/files.js → extensions/types.js} +0 -0
@@ -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;
@@ -1,6 +1,6 @@
1
1
  import { isDirectusError } from '@directus/errors';
2
2
  import express from 'express';
3
- import { ErrorCode } from '../errors/index.js';
3
+ import { ErrorCode } from '@directus/errors';
4
4
  import { respond } from '../middleware/respond.js';
5
5
  import useCollection from '../middleware/use-collection.js';
6
6
  import { validateBatch } from '../middleware/validate-batch.js';
@@ -1,4 +1,4 @@
1
- import { ContainsNullValuesError, InvalidForeignKeyError, NotNullViolationError, RecordNotUniqueError, ValueOutOfRangeError, ValueTooLongError, } from '../../../errors/index.js';
1
+ import { ContainsNullValuesError, InvalidForeignKeyError, NotNullViolationError, RecordNotUniqueError, ValueOutOfRangeError, ValueTooLongError, } from '@directus/errors';
2
2
  import getDatabase from '../../index.js';
3
3
  var MSSQLErrorCodes;
4
4
  (function (MSSQLErrorCodes) {
@@ -1,4 +1,4 @@
1
- import { ContainsNullValuesError, InvalidForeignKeyError, NotNullViolationError, RecordNotUniqueError, ValueOutOfRangeError, ValueTooLongError, } from '../../../errors/index.js';
1
+ import { ContainsNullValuesError, InvalidForeignKeyError, NotNullViolationError, RecordNotUniqueError, ValueOutOfRangeError, ValueTooLongError, } from '@directus/errors';
2
2
  var MySQLErrorCodes;
3
3
  (function (MySQLErrorCodes) {
4
4
  MySQLErrorCodes["UNIQUE_VIOLATION"] = "ER_DUP_ENTRY";
@@ -1,4 +1,4 @@
1
- import { ContainsNullValuesError } from '../../../errors/index.js';
1
+ import { ContainsNullValuesError } from '@directus/errors';
2
2
  var OracleErrorCodes;
3
3
  (function (OracleErrorCodes) {
4
4
  OracleErrorCodes[OracleErrorCodes["CONTAINS_NULL_VALUES"] = 2296] = "CONTAINS_NULL_VALUES";
@@ -1,4 +1,4 @@
1
- import { ContainsNullValuesError, InvalidForeignKeyError, NotNullViolationError, RecordNotUniqueError, ValueOutOfRangeError, ValueTooLongError, } from '../../../errors/index.js';
1
+ import { ContainsNullValuesError, InvalidForeignKeyError, NotNullViolationError, RecordNotUniqueError, ValueOutOfRangeError, ValueTooLongError, } from '@directus/errors';
2
2
  var PostgresErrorCodes;
3
3
  (function (PostgresErrorCodes) {
4
4
  PostgresErrorCodes["FOREIGN_KEY_VIOLATION"] = "23503";
@@ -1,4 +1,4 @@
1
- import { ContainsNullValuesError, InvalidForeignKeyError, NotNullViolationError, RecordNotUniqueError, } from '../../../errors/index.js';
1
+ import { ContainsNullValuesError, InvalidForeignKeyError, NotNullViolationError, RecordNotUniqueError, } from '@directus/errors';
2
2
  // NOTE:
3
3
  // - Sqlite doesn't have varchar with length support, so no ValueTooLongError
4
4
  // - Sqlite doesn't have a max range for numbers, so no ValueOutOfRangeError
@@ -1,4 +1,4 @@
1
- import { getDatabaseVersion } from '../../../../database/index.js';
1
+ import { getDatabaseVersion } from '../../../index.js';
2
2
  import { SchemaHelper } from '../types.js';
3
3
  export class SchemaHelperMySQL extends SchemaHelper {
4
4
  applyMultiRelationalSort(knex, dbQuery, table, primaryKey, orderByString, orderByFields) {
@@ -2,8 +2,11 @@ import type { Knex } from 'knex';
2
2
  import { AutoSequenceHelper } from '../types.js';
3
3
  export declare class AutoIncrementHelperPostgres extends AutoSequenceHelper {
4
4
  /**
5
- * Resets the auto increment sequence for a table based on the max value of the PK column.
6
- * The sequence name of determined using a sub query.
5
+ * Resets the auto increment sequence based on the max value of the PK column.
6
+ * The sequence name is determined using a sub query.
7
+ *
8
+ * The table name value for getting the sequence name needs to be escaped explicitly,
9
+ * otherwise PostgreSQL would throw an error for capitalized table names saying "relation x does not exist".
7
10
  */
8
11
  resetAutoIncrementSequence(table: string, column: string): Promise<Knex.Raw | void>;
9
12
  }
@@ -1,10 +1,13 @@
1
1
  import { AutoSequenceHelper } from '../types.js';
2
2
  export class AutoIncrementHelperPostgres extends AutoSequenceHelper {
3
3
  /**
4
- * Resets the auto increment sequence for a table based on the max value of the PK column.
5
- * The sequence name of determined using a sub query.
4
+ * Resets the auto increment sequence based on the max value of the PK column.
5
+ * The sequence name is determined using a sub query.
6
+ *
7
+ * The table name value for getting the sequence name needs to be escaped explicitly,
8
+ * otherwise PostgreSQL would throw an error for capitalized table names saying "relation x does not exist".
6
9
  */
7
10
  async resetAutoIncrementSequence(table, column) {
8
- return await this.knex.raw(`WITH sequence_infos AS (SELECT pg_get_serial_sequence('${table}', '${column}') AS seq_name, MAX(${column}) as max_val FROM ${table}) SELECT SETVAL(seq_name, max_val) FROM sequence_infos;`);
11
+ return await this.knex.raw(`WITH sequence_infos AS (SELECT pg_get_serial_sequence(?, ?) AS seq_name, MAX(??) as max_val FROM ??) SELECT SETVAL(seq_name, max_val) FROM sequence_infos;`, [`"${table}"`, column, column, table]);
9
12
  }
10
13
  }
@@ -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<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,44 @@
1
+ import { getHelpers } from '../helpers/index.js';
2
+ import { createInspector } from '@directus/schema';
3
+ export async function up(knex) {
4
+ const inspector = createInspector(knex);
5
+ const helper = getHelpers(knex).schema;
6
+ const csvFields = await knex.select('collection', 'field').from('directus_fields').where('special', '=', 'cast-csv');
7
+ const updates = [];
8
+ for (const { collection, field } of csvFields) {
9
+ updates.push(inspector.columnInfo(collection, field).then((column) => {
10
+ if (column.data_type === 'text')
11
+ return;
12
+ return helper.changeToType(collection, field, 'text', {
13
+ default: column.default_value,
14
+ nullable: column.is_nullable,
15
+ });
16
+ }));
17
+ }
18
+ return checkPromises(updates);
19
+ }
20
+ export async function down(knex) {
21
+ const inspector = createInspector(knex);
22
+ const helper = getHelpers(knex).schema;
23
+ const csvFields = await knex.select('collection', 'field').from('directus_fields').where('special', '=', 'cast-csv');
24
+ const updates = [];
25
+ for (const { collection, field } of csvFields) {
26
+ updates.push(inspector.columnInfo(collection, field).then((column) => {
27
+ return helper.changeToType(collection, field, 'string', {
28
+ default: column.default_value,
29
+ nullable: column.is_nullable,
30
+ });
31
+ }));
32
+ }
33
+ return checkPromises(updates);
34
+ }
35
+ async function checkPromises(promises) {
36
+ const result = await Promise.allSettled(promises);
37
+ const errors = result.filter(isRejectedPromise).map((promise) => promise.reason);
38
+ if (errors.length > 0) {
39
+ throw new Error(errors.toString());
40
+ }
41
+ }
42
+ function isRejectedPromise(promise) {
43
+ return promise.status === 'rejected';
44
+ }
@@ -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
+ }
@@ -1,6 +1,6 @@
1
1
  import { toArray } from '@directus/utils';
2
2
  import { clone, cloneDeep, isNil, merge, pick, uniq } from 'lodash-es';
3
- import { getHelpers } from '../database/helpers/index.js';
3
+ import { getHelpers } from './helpers/index.js';
4
4
  import env from '../env.js';
5
5
  import { PayloadService } from '../services/payload.js';
6
6
  import { applyFunctionToColumnName } from '../utils/apply-function-to-column-name.js';
@@ -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;
@@ -30,7 +30,7 @@ export default async function runSeed(database) {
30
30
  column = tableBuilder.increments();
31
31
  }
32
32
  else if (columnInfo.type === 'csv') {
33
- column = tableBuilder.string(columnName);
33
+ column = tableBuilder.text(columnName);
34
34
  }
35
35
  else if (columnInfo.type === 'hash') {
36
36
  column = tableBuilder.string(columnName, 255);
@@ -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
@@ -114,8 +114,27 @@ fields:
114
114
  interface: system-display-template
115
115
  options:
116
116
  collectionField: collection
117
+ injectVersionField: true
117
118
  width: full
118
119
 
120
+ - field: content_versioning_divider
121
+ special:
122
+ - alias
123
+ - no-data
124
+ interface: presentation-divider
125
+ options:
126
+ icon: published_with_changes
127
+ title: $t:field_options.directus_collections.content_versioning_divider
128
+ width: full
129
+
130
+ - field: versioning
131
+ interface: boolean
132
+ special:
133
+ - cast-boolean
134
+ options:
135
+ label: $t:field_options.directus_collections.enable_versioning
136
+ width: half
137
+
119
138
  - field: archive_divider
120
139
  special:
121
140
  - alias
@@ -0,0 +1,10 @@
1
+ table: directus_extensions
2
+
3
+ fields:
4
+ - collection: directus_extensions
5
+ field: name
6
+
7
+ - collection: directus_extensions
8
+ field: enabled
9
+ special:
10
+ - cast-boolean
@@ -25,3 +25,6 @@ fields:
25
25
 
26
26
  - field: parent
27
27
  width: half
28
+
29
+ - field: version
30
+ width: half