directus 9.5.1 → 9.7.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 (56) 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 +9 -6
  6. package/dist/cache.d.ts +4 -1
  7. package/dist/cache.js +27 -5
  8. package/dist/cli/commands/init/index.js +8 -0
  9. package/dist/cli/commands/schema/apply.d.ts +1 -0
  10. package/dist/cli/commands/schema/apply.js +9 -5
  11. package/dist/cli/index.js +1 -0
  12. package/dist/cli/utils/create-env/env-stub.liquid +1 -0
  13. package/dist/controllers/assets.js +9 -1
  14. package/dist/controllers/utils.js +18 -1
  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/run-ast.d.ts +1 -1
  18. package/dist/database/run-ast.js +48 -35
  19. package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +8 -2
  20. package/dist/database/system-data/fields/collections.yaml +2 -0
  21. package/dist/database/system-data/fields/settings.yaml +19 -3
  22. package/dist/env.js +8 -2
  23. package/dist/exceptions/database/dialects/mysql.js +23 -17
  24. package/dist/extensions.js +0 -2
  25. package/dist/middleware/authenticate.d.ts +5 -3
  26. package/dist/middleware/authenticate.js +22 -39
  27. package/dist/middleware/respond.js +7 -28
  28. package/dist/server.js +5 -2
  29. package/dist/services/authorization.js +2 -1
  30. package/dist/services/collections.js +9 -7
  31. package/dist/services/fields.js +8 -8
  32. package/dist/services/files.js +69 -3
  33. package/dist/services/graphql.js +2 -2
  34. package/dist/services/import-export.d.ts +34 -0
  35. package/dist/services/import-export.js +270 -0
  36. package/dist/services/index.d.ts +2 -1
  37. package/dist/services/index.js +2 -1
  38. package/dist/services/mail/templates/base.liquid +2 -2
  39. package/dist/services/permissions.js +10 -10
  40. package/dist/services/relations.js +11 -4
  41. package/dist/services/utils.js +10 -0
  42. package/dist/utils/apply-query.js +2 -24
  43. package/dist/utils/get-date-formatted.d.ts +1 -0
  44. package/dist/utils/get-date-formatted.js +14 -0
  45. package/dist/utils/get-permissions.js +1 -1
  46. package/dist/utils/get-schema.js +1 -1
  47. package/dist/utils/is-directus-jwt.js +4 -21
  48. package/dist/utils/jwt.d.ts +2 -0
  49. package/dist/utils/jwt.js +49 -0
  50. package/dist/utils/merge-permissions.js +18 -6
  51. package/example.env +1 -0
  52. package/package.json +19 -20
  53. package/dist/__mocks__/cache.d.ts +0 -6
  54. package/dist/__mocks__/cache.js +0 -8
  55. package/dist/services/import.d.ts +0 -13
  56. package/dist/services/import.js +0 -118
@@ -135,6 +135,10 @@ class RelationsService {
135
135
  if (relation.field in this.schema.collections[relation.collection].fields === false) {
136
136
  throw new exceptions_1.InvalidPayloadException(`Field "${relation.field}" doesn't exist in collection "${relation.collection}"`);
137
137
  }
138
+ // A primary key should not be a foreign key
139
+ if (this.schema.collections[relation.collection].primary === relation.field) {
140
+ throw new exceptions_1.InvalidPayloadException(`Field "${relation.field}" in collection "${relation.collection}" is a primary key`);
141
+ }
138
142
  if (relation.related_collection && relation.related_collection in this.schema.collections === false) {
139
143
  throw new exceptions_1.InvalidPayloadException(`Collection "${relation.related_collection}" doesn't exist`);
140
144
  }
@@ -171,7 +175,7 @@ class RelationsService {
171
175
  });
172
176
  await relationsItemService.createOne(metaRow);
173
177
  });
174
- await this.systemCache.clear();
178
+ await (0, cache_1.clearSystemCache)();
175
179
  }
176
180
  /**
177
181
  * Update an existing foreign key constraint
@@ -232,7 +236,7 @@ class RelationsService {
232
236
  }
233
237
  }
234
238
  });
235
- await this.systemCache.clear();
239
+ await (0, cache_1.clearSystemCache)();
236
240
  }
237
241
  /**
238
242
  * Delete an existing relationship
@@ -253,7 +257,10 @@ class RelationsService {
253
257
  }
254
258
  await this.knex.transaction(async (trx) => {
255
259
  var _a;
256
- 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)) {
257
264
  await trx.schema.alterTable(existingRelation.collection, (table) => {
258
265
  table.dropForeign(existingRelation.field, existingRelation.schema.constraint_name);
259
266
  });
@@ -262,7 +269,7 @@ class RelationsService {
262
269
  await trx('directus_relations').delete().where({ many_collection: collection, many_field: field });
263
270
  }
264
271
  });
265
- await this.systemCache.clear();
272
+ await (0, cache_1.clearSystemCache)();
266
273
  }
267
274
  /**
268
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;
@@ -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;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.verifyAccessJWT = void 0;
23
+ const jsonwebtoken_1 = __importStar(require("jsonwebtoken"));
24
+ const exceptions_1 = require("../exceptions");
25
+ function verifyAccessJWT(token, secret) {
26
+ let payload;
27
+ try {
28
+ payload = jsonwebtoken_1.default.verify(token, secret, {
29
+ issuer: 'directus',
30
+ });
31
+ }
32
+ catch (err) {
33
+ if (err instanceof jsonwebtoken_1.TokenExpiredError) {
34
+ throw new exceptions_1.InvalidTokenException('Token expired.');
35
+ }
36
+ else if (err instanceof jsonwebtoken_1.JsonWebTokenError) {
37
+ throw new exceptions_1.InvalidTokenException('Token invalid.');
38
+ }
39
+ else {
40
+ throw new exceptions_1.ServiceUnavailableException(`Couldn't verify token.`, { service: 'jwt' });
41
+ }
42
+ }
43
+ const { id, role, app_access, admin_access, share, share_scope } = payload;
44
+ if (role === undefined || app_access === undefined || admin_access === undefined) {
45
+ throw new exceptions_1.InvalidTokenException('Invalid token payload.');
46
+ }
47
+ return { id, role, app_access, admin_access, share, share_scope };
48
+ }
49
+ exports.verifyAccessJWT = verifyAccessJWT;
@@ -31,9 +31,15 @@ function mergePermission(strategy, currentPerm, newPerm) {
31
31
  };
32
32
  }
33
33
  else if (currentPerm.permissions) {
34
- permissions = {
35
- [logicalKey]: [currentPerm.permissions, newPerm.permissions],
36
- };
34
+ // Empty {} supersedes other permissions in _OR merge
35
+ if (strategy === 'or' && ((0, lodash_1.isEqual)(currentPerm.permissions, {}) || (0, lodash_1.isEqual)(newPerm.permissions, {}))) {
36
+ permissions = {};
37
+ }
38
+ else {
39
+ permissions = {
40
+ [logicalKey]: [currentPerm.permissions, newPerm.permissions],
41
+ };
42
+ }
37
43
  }
38
44
  else {
39
45
  permissions = {
@@ -51,9 +57,15 @@ function mergePermission(strategy, currentPerm, newPerm) {
51
57
  };
52
58
  }
53
59
  else if (currentPerm.validation) {
54
- validation = {
55
- [logicalKey]: [currentPerm.validation, newPerm.validation],
56
- };
60
+ // Empty {} supersedes other validations in _OR merge
61
+ if (strategy === 'or' && ((0, lodash_1.isEqual)(currentPerm.validation, {}) || (0, lodash_1.isEqual)(newPerm.validation, {}))) {
62
+ validation = {};
63
+ }
64
+ else {
65
+ validation = {
66
+ [logicalKey]: [currentPerm.validation, newPerm.validation],
67
+ };
68
+ }
57
69
  }
58
70
  else {
59
71
  validation = {
package/example.env CHANGED
@@ -1,6 +1,7 @@
1
1
  ####################################################################################################
2
2
  # General
3
3
 
4
+ HOST="0.0.0.0"
4
5
  PORT=8055
5
6
  PUBLIC_URL="http://localhost:8055"
6
7
  LOG_LEVEL="info"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "directus",
3
- "version": "9.5.1",
3
+ "version": "9.7.0",
4
4
  "license": "GPL-3.0-only",
5
5
  "homepage": "https://github.com/directus/directus#readme",
6
6
  "description": "Directus is a real-time API and App dashboard for managing SQL database content.",
@@ -65,7 +65,7 @@
65
65
  "cli": "cross-env NODE_ENV=development SERVE_APP=false ts-node --script-mode --transpile-only src/cli/run.ts",
66
66
  "test": "jest",
67
67
  "test:coverage": "jest --coverage",
68
- "test:watch": "jest --watchAll"
68
+ "test:watch": "jest --watch"
69
69
  },
70
70
  "engines": {
71
71
  "node": ">=12.20.0"
@@ -78,23 +78,22 @@
78
78
  ],
79
79
  "dependencies": {
80
80
  "@aws-sdk/client-ses": "^3.40.0",
81
- "@directus/app": "9.5.1",
82
- "@directus/drive": "9.5.1",
83
- "@directus/drive-azure": "9.5.1",
84
- "@directus/drive-gcs": "9.5.1",
85
- "@directus/drive-s3": "9.5.1",
86
- "@directus/extensions-sdk": "9.5.1",
87
- "@directus/format-title": "9.5.1",
88
- "@directus/schema": "9.5.1",
89
- "@directus/shared": "9.5.1",
90
- "@directus/specs": "9.5.1",
81
+ "@directus/app": "9.7.0",
82
+ "@directus/drive": "9.7.0",
83
+ "@directus/drive-azure": "9.7.0",
84
+ "@directus/drive-gcs": "9.7.0",
85
+ "@directus/drive-s3": "9.7.0",
86
+ "@directus/extensions-sdk": "9.7.0",
87
+ "@directus/format-title": "9.7.0",
88
+ "@directus/schema": "9.7.0",
89
+ "@directus/shared": "9.7.0",
90
+ "@directus/specs": "9.7.0",
91
91
  "@godaddy/terminus": "^4.9.0",
92
- "@rollup/plugin-alias": "^3.1.2",
92
+ "@rollup/plugin-alias": "^3.1.9",
93
93
  "@rollup/plugin-virtual": "^2.0.3",
94
94
  "argon2": "^0.28.2",
95
95
  "async": "^3.2.0",
96
96
  "async-mutex": "^0.3.1",
97
- "atob": "^2.1.2",
98
97
  "axios": "^0.24.0",
99
98
  "busboy": "^0.3.1",
100
99
  "camelcase": "^6.2.0",
@@ -149,11 +148,12 @@
149
148
  "qs": "^6.9.4",
150
149
  "rate-limiter-flexible": "^2.2.2",
151
150
  "resolve-cwd": "^3.0.0",
152
- "rollup": "^2.52.1",
151
+ "rollup": "^2.67.3",
153
152
  "sanitize-html": "^2.6.0",
154
153
  "sharp": "^0.29.0",
155
154
  "stream-json": "^1.7.1",
156
155
  "supertest": "^6.1.6",
156
+ "tmp-promise": "^3.0.3",
157
157
  "update-check": "^1.5.4",
158
158
  "uuid": "^8.3.2",
159
159
  "uuid-validate": "0.0.3",
@@ -173,10 +173,9 @@
173
173
  "sqlite3": "^5.0.2",
174
174
  "tedious": "^13.0.0"
175
175
  },
176
- "gitHead": "c0c412d30b116bd38a2690b62463f775d37db79c",
176
+ "gitHead": "c1da41d6719d4efdc5d0196019fb7b2c6672c575",
177
177
  "devDependencies": {
178
178
  "@types/async": "3.2.10",
179
- "@types/atob": "2.1.2",
180
179
  "@types/body-parser": "1.19.2",
181
180
  "@types/busboy": "0.3.1",
182
181
  "@types/cookie-parser": "1.4.2",
@@ -189,7 +188,7 @@
189
188
  "@types/flat": "5.0.2",
190
189
  "@types/fs-extra": "9.0.13",
191
190
  "@types/inquirer": "8.1.3",
192
- "@types/jest": "27.0.3",
191
+ "@types/jest": "27.4.1",
193
192
  "@types/js-yaml": "4.0.5",
194
193
  "@types/json2csv": "5.0.3",
195
194
  "@types/jsonwebtoken": "8.5.6",
@@ -215,9 +214,9 @@
215
214
  "@types/wellknown": "0.5.1",
216
215
  "copyfiles": "2.4.1",
217
216
  "cross-env": "7.0.3",
218
- "jest": "27.3.1",
217
+ "jest": "27.5.1",
219
218
  "knex-mock-client": "1.6.1",
220
- "ts-jest": "27.0.7",
219
+ "ts-jest": "27.1.3",
221
220
  "ts-node-dev": "1.1.8",
222
221
  "typescript": "4.5.2"
223
222
  }
@@ -1,6 +0,0 @@
1
- /// <reference types="jest" />
2
- export declare const cache: {
3
- get: jest.Mock<any, any>;
4
- set: jest.Mock<any, any>;
5
- };
6
- export declare const getCache: jest.Mock<any, any>;
@@ -1,8 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getCache = exports.cache = void 0;
4
- exports.cache = {
5
- get: jest.fn().mockResolvedValue(undefined),
6
- set: jest.fn().mockResolvedValue(true),
7
- };
8
- exports.getCache = jest.fn().mockReturnValue({ cache: exports.cache });
@@ -1,13 +0,0 @@
1
- /// <reference types="node" />
2
- import { Knex } from 'knex';
3
- import { AbstractServiceOptions } from '../types';
4
- import { Accountability, 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
- }
@@ -1,118 +0,0 @@
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.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
- class ImportService {
16
- constructor(options) {
17
- this.knex = options.knex || (0, database_1.default)();
18
- this.accountability = options.accountability || null;
19
- this.schema = options.schema;
20
- }
21
- async import(collection, mimetype, stream) {
22
- var _a, _b, _c, _d, _e;
23
- if (collection.startsWith('directus_'))
24
- throw new exceptions_1.ForbiddenException();
25
- 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');
26
- 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');
27
- if (((_e = this.accountability) === null || _e === void 0 ? void 0 : _e.admin) !== true && (!createPermissions || !updatePermissions)) {
28
- throw new exceptions_1.ForbiddenException();
29
- }
30
- switch (mimetype) {
31
- case 'application/json':
32
- return await this.importJSON(collection, stream);
33
- case 'text/csv':
34
- case 'application/vnd.ms-excel':
35
- return await this.importCSV(collection, stream);
36
- default:
37
- throw new exceptions_1.UnsupportedMediaTypeException(`Can't import files of type "${mimetype}"`);
38
- }
39
- }
40
- importJSON(collection, stream) {
41
- const extractJSON = StreamArray_1.default.withParser();
42
- return this.knex.transaction((trx) => {
43
- const service = new items_1.ItemsService(collection, {
44
- knex: trx,
45
- schema: this.schema,
46
- accountability: this.accountability,
47
- });
48
- const saveQueue = (0, async_1.queue)(async (value) => {
49
- return await service.upsertOne(value);
50
- });
51
- return new Promise((resolve, reject) => {
52
- stream.pipe(extractJSON);
53
- extractJSON.on('data', ({ value }) => {
54
- saveQueue.push(value);
55
- });
56
- extractJSON.on('error', (err) => {
57
- (0, destroy_1.default)(stream);
58
- (0, destroy_1.default)(extractJSON);
59
- reject(new exceptions_1.InvalidPayloadException(err.message));
60
- });
61
- saveQueue.error((err) => {
62
- reject(err);
63
- });
64
- extractJSON.on('end', () => {
65
- saveQueue.drain(() => {
66
- return resolve();
67
- });
68
- });
69
- });
70
- });
71
- }
72
- importCSV(collection, stream) {
73
- return this.knex.transaction((trx) => {
74
- const service = new items_1.ItemsService(collection, {
75
- knex: trx,
76
- schema: this.schema,
77
- accountability: this.accountability,
78
- });
79
- const saveQueue = (0, async_1.queue)(async (value) => {
80
- return await service.upsertOne(value);
81
- });
82
- return new Promise((resolve, reject) => {
83
- stream
84
- .pipe((0, csv_parser_1.default)())
85
- .on('data', (value) => {
86
- const obj = (0, lodash_1.transform)(value, (result, value, key) => {
87
- if (value.length === 0) {
88
- delete result[key];
89
- }
90
- else {
91
- try {
92
- const parsedJson = JSON.parse(value);
93
- (0, lodash_1.set)(result, key, parsedJson);
94
- }
95
- catch {
96
- (0, lodash_1.set)(result, key, value);
97
- }
98
- }
99
- });
100
- saveQueue.push(obj);
101
- })
102
- .on('error', (err) => {
103
- (0, destroy_1.default)(stream);
104
- reject(new exceptions_1.InvalidPayloadException(err.message));
105
- })
106
- .on('end', () => {
107
- saveQueue.drain(() => {
108
- return resolve();
109
- });
110
- });
111
- saveQueue.error((err) => {
112
- reject(err);
113
- });
114
- });
115
- });
116
- }
117
- }
118
- exports.ImportService = ImportService;