directus 9.5.2 → 9.7.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 (73) hide show
  1. package/dist/app.js +3 -1
  2. package/dist/auth/drivers/ldap.d.ts +0 -1
  3. package/dist/auth/drivers/ldap.js +54 -60
  4. package/dist/auth/drivers/oauth2.js +3 -0
  5. package/dist/auth/drivers/openid.js +3 -0
  6. package/dist/cache.d.ts +4 -1
  7. package/dist/cache.js +27 -5
  8. package/dist/cli/commands/schema/apply.d.ts +1 -0
  9. package/dist/cli/commands/schema/apply.js +9 -5
  10. package/dist/cli/index.js +1 -0
  11. package/dist/cli/utils/create-env/env-stub.liquid +1 -0
  12. package/dist/controllers/assets.js +9 -1
  13. package/dist/controllers/utils.js +18 -1
  14. package/dist/database/index.js +0 -3
  15. package/dist/database/migrations/20220303A-remove-default-project-color.d.ts +3 -0
  16. package/dist/database/migrations/20220303A-remove-default-project-color.js +22 -0
  17. package/dist/database/migrations/20220314A-add-translation-strings.d.ts +3 -0
  18. package/dist/database/migrations/20220314A-add-translation-strings.js +15 -0
  19. package/dist/database/migrations/20220322A-rename-field-typecast-flags.d.ts +3 -0
  20. package/dist/database/migrations/20220322A-rename-field-typecast-flags.js +73 -0
  21. package/dist/database/migrations/run.js +1 -1
  22. package/dist/database/run-ast.d.ts +1 -1
  23. package/dist/database/run-ast.js +48 -35
  24. package/dist/database/system-data/fields/collections.yaml +4 -4
  25. package/dist/database/system-data/fields/fields.yaml +8 -8
  26. package/dist/database/system-data/fields/files.yaml +2 -2
  27. package/dist/database/system-data/fields/panels.yaml +2 -2
  28. package/dist/database/system-data/fields/permissions.yaml +4 -4
  29. package/dist/database/system-data/fields/presets.yaml +3 -3
  30. package/dist/database/system-data/fields/relations.yaml +1 -1
  31. package/dist/database/system-data/fields/revisions.yaml +2 -2
  32. package/dist/database/system-data/fields/roles.yaml +4 -4
  33. package/dist/database/system-data/fields/settings.yaml +25 -6
  34. package/dist/database/system-data/fields/users.yaml +2 -2
  35. package/dist/database/system-data/fields/webhooks.yaml +4 -4
  36. package/dist/env.js +9 -2
  37. package/dist/exceptions/database/dialects/mysql.js +23 -17
  38. package/dist/extensions.js +0 -2
  39. package/dist/middleware/authenticate.d.ts +5 -3
  40. package/dist/middleware/authenticate.js +22 -39
  41. package/dist/middleware/respond.js +7 -28
  42. package/dist/server.js +5 -2
  43. package/dist/services/authorization.js +60 -1
  44. package/dist/services/collections.js +6 -6
  45. package/dist/services/fields.js +3 -3
  46. package/dist/services/files.js +69 -3
  47. package/dist/services/graphql.js +2 -2
  48. package/dist/services/import-export.d.ts +34 -0
  49. package/dist/services/import-export.js +270 -0
  50. package/dist/services/index.d.ts +2 -1
  51. package/dist/services/index.js +2 -1
  52. package/dist/services/items.js +1 -0
  53. package/dist/services/mail/templates/base.liquid +2 -2
  54. package/dist/services/payload.js +3 -3
  55. package/dist/services/permissions.js +10 -10
  56. package/dist/services/relations.js +7 -4
  57. package/dist/services/utils.js +10 -0
  58. package/dist/utils/apply-query.js +2 -24
  59. package/dist/utils/get-date-formatted.d.ts +1 -0
  60. package/dist/utils/get-date-formatted.js +14 -0
  61. package/dist/utils/get-local-type.js +2 -2
  62. package/dist/utils/get-permissions.js +1 -1
  63. package/dist/utils/get-schema.js +1 -1
  64. package/dist/utils/is-directus-jwt.js +4 -21
  65. package/dist/utils/jwt.d.ts +2 -0
  66. package/dist/utils/jwt.js +49 -0
  67. package/dist/utils/merge-permissions.js +18 -6
  68. package/example.env +1 -0
  69. package/package.json +17 -18
  70. package/dist/__mocks__/cache.d.ts +0 -6
  71. package/dist/__mocks__/cache.js +0 -8
  72. package/dist/services/import.d.ts +0 -13
  73. package/dist/services/import.js +0 -118
@@ -0,0 +1,34 @@
1
+ /// <reference types="node" />
2
+ import { Knex } from 'knex';
3
+ import { AbstractServiceOptions, File } from '../types';
4
+ import { Accountability, Query, SchemaOverview } from '@directus/shared/types';
5
+ export declare class ImportService {
6
+ knex: Knex;
7
+ accountability: Accountability | null;
8
+ schema: SchemaOverview;
9
+ constructor(options: AbstractServiceOptions);
10
+ import(collection: string, mimetype: string, stream: NodeJS.ReadableStream): Promise<void>;
11
+ importJSON(collection: string, stream: NodeJS.ReadableStream): Promise<void>;
12
+ importCSV(collection: string, stream: NodeJS.ReadableStream): Promise<void>;
13
+ }
14
+ export declare class ExportService {
15
+ knex: Knex;
16
+ accountability: Accountability | null;
17
+ schema: SchemaOverview;
18
+ constructor(options: AbstractServiceOptions);
19
+ /**
20
+ * Export the query results as a named file. Will query in batches, and keep appending a tmp file
21
+ * until all the data is retrieved. Uploads the result as a new file using the regular
22
+ * FilesService upload method.
23
+ */
24
+ exportToFile(collection: string, query: Partial<Query>, format: 'xml' | 'csv' | 'json', options?: {
25
+ file?: Partial<File>;
26
+ }): Promise<void>;
27
+ /**
28
+ * Transform a given input object / array to the given type
29
+ */
30
+ transform(input: Record<string, any>[], format: 'xml' | 'csv' | 'json', options?: {
31
+ includeHeader?: boolean;
32
+ includeFooter?: boolean;
33
+ }): string;
34
+ }
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ExportService = exports.ImportService = void 0;
7
+ const database_1 = __importDefault(require("../database"));
8
+ const exceptions_1 = require("../exceptions");
9
+ const StreamArray_1 = __importDefault(require("stream-json/streamers/StreamArray"));
10
+ const items_1 = require("./items");
11
+ const async_1 = require("async");
12
+ const destroy_1 = __importDefault(require("destroy"));
13
+ const csv_parser_1 = __importDefault(require("csv-parser"));
14
+ const lodash_1 = require("lodash");
15
+ const js2xmlparser_1 = require("js2xmlparser");
16
+ const json2csv_1 = require("json2csv");
17
+ const fs_extra_1 = require("fs-extra");
18
+ const tmp_promise_1 = require("tmp-promise");
19
+ const env_1 = __importDefault(require("../env"));
20
+ const files_1 = require("./files");
21
+ const get_date_formatted_1 = require("../utils/get-date-formatted");
22
+ const utils_1 = require("@directus/shared/utils");
23
+ const notifications_1 = require("./notifications");
24
+ const logger_1 = __importDefault(require("../logger"));
25
+ class ImportService {
26
+ constructor(options) {
27
+ this.knex = options.knex || (0, database_1.default)();
28
+ this.accountability = options.accountability || null;
29
+ this.schema = options.schema;
30
+ }
31
+ async import(collection, mimetype, stream) {
32
+ var _a, _b, _c, _d, _e;
33
+ if (collection.startsWith('directus_'))
34
+ throw new exceptions_1.ForbiddenException();
35
+ const createPermissions = (_b = (_a = this.accountability) === null || _a === void 0 ? void 0 : _a.permissions) === null || _b === void 0 ? void 0 : _b.find((permission) => permission.collection === collection && permission.action === 'create');
36
+ const updatePermissions = (_d = (_c = this.accountability) === null || _c === void 0 ? void 0 : _c.permissions) === null || _d === void 0 ? void 0 : _d.find((permission) => permission.collection === collection && permission.action === 'update');
37
+ if (((_e = this.accountability) === null || _e === void 0 ? void 0 : _e.admin) !== true && (!createPermissions || !updatePermissions)) {
38
+ throw new exceptions_1.ForbiddenException();
39
+ }
40
+ switch (mimetype) {
41
+ case 'application/json':
42
+ return await this.importJSON(collection, stream);
43
+ case 'text/csv':
44
+ case 'application/vnd.ms-excel':
45
+ return await this.importCSV(collection, stream);
46
+ default:
47
+ throw new exceptions_1.UnsupportedMediaTypeException(`Can't import files of type "${mimetype}"`);
48
+ }
49
+ }
50
+ importJSON(collection, stream) {
51
+ const extractJSON = StreamArray_1.default.withParser();
52
+ return this.knex.transaction((trx) => {
53
+ const service = new items_1.ItemsService(collection, {
54
+ knex: trx,
55
+ schema: this.schema,
56
+ accountability: this.accountability,
57
+ });
58
+ const saveQueue = (0, async_1.queue)(async (value) => {
59
+ return await service.upsertOne(value);
60
+ });
61
+ return new Promise((resolve, reject) => {
62
+ stream.pipe(extractJSON);
63
+ extractJSON.on('data', ({ value }) => {
64
+ saveQueue.push(value);
65
+ });
66
+ extractJSON.on('error', (err) => {
67
+ (0, destroy_1.default)(stream);
68
+ (0, destroy_1.default)(extractJSON);
69
+ reject(new exceptions_1.InvalidPayloadException(err.message));
70
+ });
71
+ saveQueue.error((err) => {
72
+ reject(err);
73
+ });
74
+ extractJSON.on('end', () => {
75
+ saveQueue.drain(() => {
76
+ return resolve();
77
+ });
78
+ });
79
+ });
80
+ });
81
+ }
82
+ importCSV(collection, stream) {
83
+ return this.knex.transaction((trx) => {
84
+ const service = new items_1.ItemsService(collection, {
85
+ knex: trx,
86
+ schema: this.schema,
87
+ accountability: this.accountability,
88
+ });
89
+ const saveQueue = (0, async_1.queue)(async (value) => {
90
+ return await service.upsertOne(value);
91
+ });
92
+ return new Promise((resolve, reject) => {
93
+ stream
94
+ .pipe((0, csv_parser_1.default)())
95
+ .on('data', (value) => {
96
+ const obj = (0, lodash_1.transform)(value, (result, value, key) => {
97
+ if (value.length === 0) {
98
+ delete result[key];
99
+ }
100
+ else {
101
+ try {
102
+ const parsedJson = JSON.parse(value);
103
+ (0, lodash_1.set)(result, key, parsedJson);
104
+ }
105
+ catch {
106
+ (0, lodash_1.set)(result, key, value);
107
+ }
108
+ }
109
+ });
110
+ saveQueue.push(obj);
111
+ })
112
+ .on('error', (err) => {
113
+ (0, destroy_1.default)(stream);
114
+ reject(new exceptions_1.InvalidPayloadException(err.message));
115
+ })
116
+ .on('end', () => {
117
+ saveQueue.drain(() => {
118
+ return resolve();
119
+ });
120
+ });
121
+ saveQueue.error((err) => {
122
+ reject(err);
123
+ });
124
+ });
125
+ });
126
+ }
127
+ }
128
+ exports.ImportService = ImportService;
129
+ class ExportService {
130
+ constructor(options) {
131
+ this.knex = options.knex || (0, database_1.default)();
132
+ this.accountability = options.accountability || null;
133
+ this.schema = options.schema;
134
+ }
135
+ /**
136
+ * Export the query results as a named file. Will query in batches, and keep appending a tmp file
137
+ * until all the data is retrieved. Uploads the result as a new file using the regular
138
+ * FilesService upload method.
139
+ */
140
+ async exportToFile(collection, query, format, options) {
141
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
142
+ try {
143
+ const mimeTypes = {
144
+ xml: 'text/xml',
145
+ csv: 'text/csv',
146
+ json: 'application/json',
147
+ };
148
+ const database = (0, database_1.default)();
149
+ const { path, cleanup } = await (0, tmp_promise_1.file)();
150
+ await database.transaction(async (trx) => {
151
+ var _a;
152
+ const service = new items_1.ItemsService(collection, {
153
+ accountability: this.accountability,
154
+ schema: this.schema,
155
+ knex: trx,
156
+ });
157
+ const totalCount = await service
158
+ .readByQuery({
159
+ ...query,
160
+ aggregate: {
161
+ count: ['*'],
162
+ },
163
+ })
164
+ .then((result) => { var _a, _b; return Number((_b = (_a = result === null || result === void 0 ? void 0 : result[0]) === null || _a === void 0 ? void 0 : _a.count) !== null && _b !== void 0 ? _b : 0); });
165
+ const count = query.limit ? Math.min(totalCount, query.limit) : totalCount;
166
+ const requestedLimit = (_a = query.limit) !== null && _a !== void 0 ? _a : -1;
167
+ const batchesRequired = Math.ceil(count / env_1.default.EXPORT_BATCH_SIZE);
168
+ let readCount = 0;
169
+ for (let batch = 0; batch <= batchesRequired; batch++) {
170
+ let limit = env_1.default.EXPORT_BATCH_SIZE;
171
+ if (requestedLimit > 0 && env_1.default.EXPORT_BATCH_SIZE > requestedLimit - readCount) {
172
+ limit = requestedLimit - readCount;
173
+ }
174
+ const result = await service.readByQuery({
175
+ ...query,
176
+ limit,
177
+ page: batch,
178
+ });
179
+ readCount += result.length;
180
+ if (result.length) {
181
+ await (0, fs_extra_1.appendFile)(path, this.transform(result, format, {
182
+ includeHeader: batch === 0,
183
+ includeFooter: batch + 1 === batchesRequired,
184
+ }));
185
+ }
186
+ }
187
+ });
188
+ const filesService = new files_1.FilesService({
189
+ accountability: this.accountability,
190
+ schema: this.schema,
191
+ });
192
+ const storage = (0, utils_1.toArray)(env_1.default.STORAGE_LOCATIONS)[0];
193
+ const title = `export-${collection}-${(0, get_date_formatted_1.getDateFormatted)()}`;
194
+ const filename = `${title}.${format}`;
195
+ const fileWithDefaults = {
196
+ ...((_a = options === null || options === void 0 ? void 0 : options.file) !== null && _a !== void 0 ? _a : {}),
197
+ title: (_c = (_b = options === null || options === void 0 ? void 0 : options.file) === null || _b === void 0 ? void 0 : _b.title) !== null && _c !== void 0 ? _c : title,
198
+ filename_download: (_e = (_d = options === null || options === void 0 ? void 0 : options.file) === null || _d === void 0 ? void 0 : _d.filename_download) !== null && _e !== void 0 ? _e : filename,
199
+ storage: (_g = (_f = options === null || options === void 0 ? void 0 : options.file) === null || _f === void 0 ? void 0 : _f.storage) !== null && _g !== void 0 ? _g : storage,
200
+ type: mimeTypes[format],
201
+ };
202
+ const savedFile = await filesService.uploadOne((0, fs_extra_1.createReadStream)(path), fileWithDefaults);
203
+ if ((_h = this.accountability) === null || _h === void 0 ? void 0 : _h.user) {
204
+ const notificationsService = new notifications_1.NotificationsService({
205
+ accountability: this.accountability,
206
+ schema: this.schema,
207
+ });
208
+ await notificationsService.createOne({
209
+ recipient: this.accountability.user,
210
+ sender: this.accountability.user,
211
+ subject: `Your export of ${collection} is ready`,
212
+ collection: `directus_files`,
213
+ item: savedFile,
214
+ });
215
+ }
216
+ await cleanup();
217
+ }
218
+ catch (err) {
219
+ logger_1.default.error(err, `Couldn't export ${collection}: ${err.message}`);
220
+ if ((_j = this.accountability) === null || _j === void 0 ? void 0 : _j.user) {
221
+ const notificationsService = new notifications_1.NotificationsService({
222
+ accountability: this.accountability,
223
+ schema: this.schema,
224
+ });
225
+ await notificationsService.createOne({
226
+ recipient: this.accountability.user,
227
+ sender: this.accountability.user,
228
+ subject: `Your export of ${collection} failed`,
229
+ message: `Please contact your system administrator for more information.`,
230
+ });
231
+ }
232
+ }
233
+ }
234
+ /**
235
+ * Transform a given input object / array to the given type
236
+ */
237
+ transform(input, format, options) {
238
+ if (format === 'json') {
239
+ let string = JSON.stringify(input || null, null, '\t');
240
+ if ((options === null || options === void 0 ? void 0 : options.includeHeader) === false)
241
+ string = string.split('\n').slice(1).join('\n');
242
+ if ((options === null || options === void 0 ? void 0 : options.includeFooter) === false) {
243
+ const lines = string.split('\n');
244
+ string = lines.slice(0, lines.length - 1).join('\n');
245
+ string += ',\n';
246
+ }
247
+ return string;
248
+ }
249
+ if (format === 'xml') {
250
+ let string = (0, js2xmlparser_1.parse)('data', input);
251
+ if ((options === null || options === void 0 ? void 0 : options.includeHeader) === false)
252
+ string = string.split('\n').slice(2).join('\n');
253
+ if ((options === null || options === void 0 ? void 0 : options.includeFooter) === false) {
254
+ const lines = string.split('\n');
255
+ string = lines.slice(0, lines.length - 1).join('\n');
256
+ string += '\n';
257
+ }
258
+ return string;
259
+ }
260
+ if (format === 'csv') {
261
+ const parser = new json2csv_1.Parser({
262
+ transforms: [json2csv_1.transforms.flatten({ separator: '.' })],
263
+ header: (options === null || options === void 0 ? void 0 : options.includeHeader) !== false,
264
+ });
265
+ return parser.parse(input);
266
+ }
267
+ throw new exceptions_1.ServiceUnavailableException(`Illegal export type used: "${format}"`, { service: 'export' });
268
+ }
269
+ }
270
+ exports.ExportService = ExportService;
@@ -2,13 +2,14 @@ export * from './items';
2
2
  export * from './activity';
3
3
  export * from './assets';
4
4
  export * from './authentication';
5
+ export * from './authorization';
5
6
  export * from './collections';
6
7
  export * from './dashboards';
7
8
  export * from './fields';
8
9
  export * from './files';
9
10
  export * from './folders';
10
11
  export * from './graphql';
11
- export * from './import';
12
+ export * from './import-export';
12
13
  export * from './mail';
13
14
  export * from './meta';
14
15
  export * from './notifications';
@@ -15,13 +15,14 @@ __exportStar(require("./items"), exports);
15
15
  __exportStar(require("./activity"), exports);
16
16
  __exportStar(require("./assets"), exports);
17
17
  __exportStar(require("./authentication"), exports);
18
+ __exportStar(require("./authorization"), exports);
18
19
  __exportStar(require("./collections"), exports);
19
20
  __exportStar(require("./dashboards"), exports);
20
21
  __exportStar(require("./fields"), exports);
21
22
  __exportStar(require("./files"), exports);
22
23
  __exportStar(require("./folders"), exports);
23
24
  __exportStar(require("./graphql"), exports);
24
- __exportStar(require("./import"), exports);
25
+ __exportStar(require("./import-export"), exports);
25
26
  __exportStar(require("./mail"), exports);
26
27
  __exportStar(require("./meta"), exports);
27
28
  __exportStar(require("./notifications"), exports);
@@ -496,6 +496,7 @@ class ItemsService {
496
496
  if ((opts === null || opts === void 0 ? void 0 : opts.emitEvents) !== false) {
497
497
  emitter_1.default.emitAction(this.eventScope === 'items' ? ['items.delete', `${this.collection}.items.delete`] : `${this.eventScope}.delete`, {
498
498
  payload: keys,
499
+ keys: keys,
499
500
  collection: this.collection,
500
501
  }, {
501
502
  // This hook is called async. If we would pass the transaction here, the hook can be
@@ -35,7 +35,7 @@ a[x-apple-data-detectors] {
35
35
  line-height: inherit !important;
36
36
  }
37
37
  body a {
38
- color: #00C897;
38
+ color: #6644ff;
39
39
  text-decoration: none;
40
40
  }
41
41
  hr {
@@ -74,7 +74,7 @@ hr {
74
74
  color: #FFFFFF !important;
75
75
  }
76
76
  .link {
77
- color: #00c897 !important;
77
+ color: #6644ff !important;
78
78
  }
79
79
  .button {
80
80
  background-color:#0BA582 !important;
@@ -37,7 +37,7 @@ class PayloadService {
37
37
  }
38
38
  return value;
39
39
  },
40
- async boolean({ action, value }) {
40
+ async 'cast-boolean'({ action, value }) {
41
41
  if (action === 'read') {
42
42
  if (value === true || value === 1 || value === '1') {
43
43
  return true;
@@ -51,7 +51,7 @@ class PayloadService {
51
51
  }
52
52
  return value;
53
53
  },
54
- async json({ action, value }) {
54
+ async 'cast-json'({ action, value }) {
55
55
  if (action === 'read') {
56
56
  if (typeof value === 'string') {
57
57
  try {
@@ -99,7 +99,7 @@ class PayloadService {
99
99
  return new Date();
100
100
  return value;
101
101
  },
102
- async csv({ action, value }) {
102
+ async 'cast-csv'({ action, value }) {
103
103
  if (Array.isArray(value) === false && typeof value !== 'string')
104
104
  return;
105
105
  if (action === 'read' && Array.isArray(value) === false) {
@@ -52,52 +52,52 @@ class PermissionsService extends items_1.ItemsService {
52
52
  }
53
53
  async createOne(data, opts) {
54
54
  const res = await super.createOne(data, opts);
55
- await this.systemCache.clear();
55
+ await (0, cache_1.clearSystemCache)();
56
56
  return res;
57
57
  }
58
58
  async createMany(data, opts) {
59
59
  const res = await super.createMany(data, opts);
60
- await this.systemCache.clear();
60
+ await (0, cache_1.clearSystemCache)();
61
61
  return res;
62
62
  }
63
63
  async updateByQuery(query, data, opts) {
64
64
  const res = await super.updateByQuery(query, data, opts);
65
- await this.systemCache.clear();
65
+ await (0, cache_1.clearSystemCache)();
66
66
  return res;
67
67
  }
68
68
  async updateOne(key, data, opts) {
69
69
  const res = await super.updateOne(key, data, opts);
70
- await this.systemCache.clear();
70
+ await (0, cache_1.clearSystemCache)();
71
71
  return res;
72
72
  }
73
73
  async updateMany(keys, data, opts) {
74
74
  const res = await super.updateMany(keys, data, opts);
75
- await this.systemCache.clear();
75
+ await (0, cache_1.clearSystemCache)();
76
76
  return res;
77
77
  }
78
78
  async upsertOne(payload, opts) {
79
79
  const res = await super.upsertOne(payload, opts);
80
- await this.systemCache.clear();
80
+ await (0, cache_1.clearSystemCache)();
81
81
  return res;
82
82
  }
83
83
  async upsertMany(payloads, opts) {
84
84
  const res = await super.upsertMany(payloads, opts);
85
- await this.systemCache.clear();
85
+ await (0, cache_1.clearSystemCache)();
86
86
  return res;
87
87
  }
88
88
  async deleteByQuery(query, opts) {
89
89
  const res = await super.deleteByQuery(query, opts);
90
- await this.systemCache.clear();
90
+ await (0, cache_1.clearSystemCache)();
91
91
  return res;
92
92
  }
93
93
  async deleteOne(key, opts) {
94
94
  const res = await super.deleteOne(key, opts);
95
- await this.systemCache.clear();
95
+ await (0, cache_1.clearSystemCache)();
96
96
  return res;
97
97
  }
98
98
  async deleteMany(keys, opts) {
99
99
  const res = await super.deleteMany(keys, opts);
100
- await this.systemCache.clear();
100
+ await (0, cache_1.clearSystemCache)();
101
101
  return res;
102
102
  }
103
103
  }
@@ -175,7 +175,7 @@ class RelationsService {
175
175
  });
176
176
  await relationsItemService.createOne(metaRow);
177
177
  });
178
- await this.systemCache.clear();
178
+ await (0, cache_1.clearSystemCache)();
179
179
  }
180
180
  /**
181
181
  * Update an existing foreign key constraint
@@ -236,7 +236,7 @@ class RelationsService {
236
236
  }
237
237
  }
238
238
  });
239
- await this.systemCache.clear();
239
+ await (0, cache_1.clearSystemCache)();
240
240
  }
241
241
  /**
242
242
  * Delete an existing relationship
@@ -257,7 +257,10 @@ class RelationsService {
257
257
  }
258
258
  await this.knex.transaction(async (trx) => {
259
259
  var _a;
260
- if ((_a = existingRelation.schema) === null || _a === void 0 ? void 0 : _a.constraint_name) {
260
+ const existingConstraints = await this.schemaInspector.foreignKeys();
261
+ const constraintNames = existingConstraints.map((key) => key.constraint_name);
262
+ if (((_a = existingRelation.schema) === null || _a === void 0 ? void 0 : _a.constraint_name) &&
263
+ constraintNames.includes(existingRelation.schema.constraint_name)) {
261
264
  await trx.schema.alterTable(existingRelation.collection, (table) => {
262
265
  table.dropForeign(existingRelation.field, existingRelation.schema.constraint_name);
263
266
  });
@@ -266,7 +269,7 @@ class RelationsService {
266
269
  await trx('directus_relations').delete().where({ many_collection: collection, many_field: field });
267
270
  }
268
271
  });
269
- await this.systemCache.clear();
272
+ await (0, cache_1.clearSystemCache)();
270
273
  }
271
274
  /**
272
275
  * Whether or not the current user has read access to relations
@@ -7,6 +7,7 @@ exports.UtilsService = void 0;
7
7
  const database_1 = __importDefault(require("../database"));
8
8
  const collections_1 = require("../database/system-data/collections");
9
9
  const exceptions_1 = require("../exceptions");
10
+ const emitter_1 = __importDefault(require("../emitter"));
10
11
  class UtilsService {
11
12
  constructor(options) {
12
13
  this.knex = options.knex || (0, database_1.default)();
@@ -98,6 +99,15 @@ class UtilsService {
98
99
  .andWhere(sortField, '<=', sourceSortValue)
99
100
  .andWhereNot({ [primaryKeyField]: item });
100
101
  }
102
+ emitter_1.default.emitAction(['items.sort', `${collection}.items.sort`], {
103
+ collection,
104
+ item,
105
+ to,
106
+ }, {
107
+ database: this.knex,
108
+ schema: this.schema,
109
+ accountability: this.accountability,
110
+ });
101
111
  }
102
112
  }
103
113
  exports.UtilsService = UtilsService;
@@ -48,29 +48,7 @@ function applyQuery(knex, collection, dbQuery, query, schema, subQuery = false)
48
48
  if (query.aggregate) {
49
49
  applyAggregate(dbQuery, query.aggregate, collection);
50
50
  }
51
- if (query.union && query.union[1].length > 0) {
52
- const [field, keys] = query.union;
53
- const queries = keys.map((key) => {
54
- const unionFilter = { [field]: { _eq: key } };
55
- let filter = (0, lodash_1.cloneDeep)(query.filter);
56
- if (filter) {
57
- if ('_and' in filter) {
58
- filter._and.push(unionFilter);
59
- }
60
- else {
61
- filter = {
62
- _and: [filter, unionFilter],
63
- };
64
- }
65
- }
66
- else {
67
- filter = unionFilter;
68
- }
69
- return knex.select('*').from(applyFilter(knex, schema, dbQuery.clone(), filter, collection, subQuery).as('foo'));
70
- });
71
- dbQuery = knex.unionAll(queries);
72
- }
73
- else if (query.filter) {
51
+ if (query.filter) {
74
52
  applyFilter(knex, schema, dbQuery, query.filter, collection, subQuery);
75
53
  }
76
54
  return dbQuery;
@@ -234,7 +212,7 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
234
212
  applyFilterToQuery(`${collection}.${filterPath[0]}`, filterOperator, filterValue, logical);
235
213
  }
236
214
  }
237
- else if (subQuery === false) {
215
+ else if (subQuery === false || filterPath.length > 1) {
238
216
  if (!relation)
239
217
  continue;
240
218
  let pkField = `${collection}.${schema.collections[relation.related_collection].primary}`;
@@ -0,0 +1 @@
1
+ export declare function getDateFormatted(): string;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDateFormatted = void 0;
4
+ function getDateFormatted() {
5
+ const date = new Date();
6
+ let month = String(date.getMonth() + 1);
7
+ if (month.length === 1)
8
+ month = '0' + month;
9
+ let day = String(date.getDate());
10
+ if (day.length === 1)
11
+ day = '0' + day;
12
+ return `${date.getFullYear()}${month}${day}-${date.getHours()}${date.getMinutes()}${date.getSeconds()}`;
13
+ }
14
+ exports.getDateFormatted = getDateFormatted;
@@ -101,11 +101,11 @@ function getLocalType(column, field) {
101
101
  const type = localTypeMap[dataType.split('(')[0]];
102
102
  const special = field === null || field === void 0 ? void 0 : field.special;
103
103
  if (special) {
104
- if (special.includes('json'))
104
+ if (special.includes('cast-json'))
105
105
  return 'json';
106
106
  if (special.includes('hash'))
107
107
  return 'hash';
108
- if (special.includes('csv'))
108
+ if (special.includes('cast-csv'))
109
109
  return 'csv';
110
110
  if (special.includes('uuid') || special.includes('file'))
111
111
  return 'uuid';
@@ -65,7 +65,7 @@ async function getPermissions(accountability, schema) {
65
65
  ? await getFilterContext(schema, accountability, requiredPermissionData)
66
66
  : {};
67
67
  if (env_1.default.CACHE_PERMISSIONS !== false) {
68
- await systemCache.set(cacheKey, { permissions, containDynamicData });
68
+ await (0, cache_1.setSystemCache)(cacheKey, { permissions, containDynamicData });
69
69
  if (containDynamicData && env_1.default.CACHE_ENABLED !== false) {
70
70
  await (cache === null || cache === void 0 ? void 0 : cache.set(`filterContext-${(0, object_hash_1.default)({ user, role, permissions })}`, filterContext));
71
71
  }
@@ -35,7 +35,7 @@ async function getSchema(options) {
35
35
  else {
36
36
  result = await getDatabaseSchema(database, schemaInspector);
37
37
  try {
38
- await systemCache.set('schema', result);
38
+ await (0, cache_1.setSystemCache)('schema', result);
39
39
  }
40
40
  catch (err) {
41
41
  logger_1.default.warn(err, `[schema-cache] Couldn't save cache. ${err}`);
@@ -3,37 +3,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const atob_1 = __importDefault(require("atob"));
7
- const logger_1 = __importDefault(require("../logger"));
6
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
8
7
  /**
9
8
  * Check if a given string conforms to the structure of a JWT
10
9
  * and whether it is issued by Directus.
11
10
  */
12
11
  function isDirectusJWT(string) {
13
- const parts = string.split('.');
14
- // JWTs have the structure header.payload.signature
15
- if (parts.length !== 3)
16
- return false;
17
- // Check if all segments are base64 encoded
18
- try {
19
- (0, atob_1.default)(parts[0]);
20
- (0, atob_1.default)(parts[1]);
21
- (0, atob_1.default)(parts[2]);
22
- }
23
- catch (err) {
24
- logger_1.default.error(err);
25
- return false;
26
- }
27
- // Check if the header and payload are valid JSON
28
12
  try {
29
- JSON.parse((0, atob_1.default)(parts[0]));
30
- const payload = JSON.parse((0, atob_1.default)(parts[1]));
31
- if (payload.iss !== 'directus')
13
+ const payload = jsonwebtoken_1.default.decode(string, { json: true });
14
+ if ((payload === null || payload === void 0 ? void 0 : payload.iss) !== 'directus')
32
15
  return false;
16
+ return true;
33
17
  }
34
18
  catch {
35
19
  return false;
36
20
  }
37
- return true;
38
21
  }
39
22
  exports.default = isDirectusJWT;
@@ -0,0 +1,2 @@
1
+ import { DirectusTokenPayload } from '../types';
2
+ export declare function verifyAccessJWT(token: string, secret: string): DirectusTokenPayload;