@lenne.tech/nest-server 8.6.5 → 8.6.8

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 (64) hide show
  1. package/dist/core/common/decorators/restricted.decorator.js +4 -4
  2. package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
  3. package/dist/core/common/helpers/db.helper.js +3 -2
  4. package/dist/core/common/helpers/db.helper.js.map +1 -1
  5. package/dist/core/common/helpers/file.helper.js +4 -7
  6. package/dist/core/common/helpers/file.helper.js.map +1 -1
  7. package/dist/core/common/helpers/filter.helper.d.ts +7 -0
  8. package/dist/core/common/helpers/filter.helper.js +38 -1
  9. package/dist/core/common/helpers/filter.helper.js.map +1 -1
  10. package/dist/core/common/helpers/input.helper.d.ts +6 -0
  11. package/dist/core/common/helpers/input.helper.js +28 -6
  12. package/dist/core/common/helpers/input.helper.js.map +1 -1
  13. package/dist/core/common/helpers/service.helper.d.ts +2 -0
  14. package/dist/core/common/helpers/service.helper.js +7 -9
  15. package/dist/core/common/helpers/service.helper.js.map +1 -1
  16. package/dist/core/common/interfaces/prepare-input-options.interface.d.ts +1 -0
  17. package/dist/core/common/interfaces/prepare-output-options.interface.d.ts +1 -0
  18. package/dist/core/common/interfaces/service-options.interface.d.ts +3 -1
  19. package/dist/core/common/models/core-model.model.js +4 -1
  20. package/dist/core/common/models/core-model.model.js.map +1 -1
  21. package/dist/core/common/services/crud.service.js +9 -4
  22. package/dist/core/common/services/crud.service.js.map +1 -1
  23. package/dist/core/common/services/mailjet.service.d.ts +1 -1
  24. package/dist/core/common/services/mailjet.service.js +2 -2
  25. package/dist/core/common/services/mailjet.service.js.map +1 -1
  26. package/dist/core/common/services/module.service.js +14 -2
  27. package/dist/core/common/services/module.service.js.map +1 -1
  28. package/dist/core/common/types/field-selection.type.d.ts +1 -1
  29. package/dist/core/modules/auth/guards/roles.guard.js +2 -1
  30. package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
  31. package/dist/core/modules/user/core-user.service.js +9 -13
  32. package/dist/core/modules/user/core-user.service.js.map +1 -1
  33. package/dist/core.module.js +2 -2
  34. package/dist/core.module.js.map +1 -1
  35. package/dist/server/modules/file/file.resolver.d.ts +1 -1
  36. package/dist/server/modules/file/file.resolver.js +4 -5
  37. package/dist/server/modules/file/file.resolver.js.map +1 -1
  38. package/dist/server/modules/user/user.model.d.ts +14 -1
  39. package/dist/server/modules/user/user.resolver.js +2 -1
  40. package/dist/server/modules/user/user.resolver.js.map +1 -1
  41. package/dist/test/test.helper.js +11 -25
  42. package/dist/test/test.helper.js.map +1 -1
  43. package/dist/tsconfig.build.tsbuildinfo +1 -1
  44. package/package.json +33 -32
  45. package/src/core/common/decorators/restricted.decorator.ts +1 -2
  46. package/src/core/common/helpers/db.helper.ts +7 -2
  47. package/src/core/common/helpers/file.helper.ts +9 -11
  48. package/src/core/common/helpers/filter.helper.ts +66 -1
  49. package/src/core/common/helpers/input.helper.ts +61 -2
  50. package/src/core/common/helpers/service.helper.ts +8 -9
  51. package/src/core/common/interfaces/prepare-input-options.interface.ts +1 -0
  52. package/src/core/common/interfaces/prepare-output-options.interface.ts +1 -0
  53. package/src/core/common/interfaces/service-options.interface.ts +8 -2
  54. package/src/core/common/models/core-model.model.ts +4 -1
  55. package/src/core/common/services/crud.service.ts +7 -4
  56. package/src/core/common/services/mailjet.service.ts +1 -1
  57. package/src/core/common/services/module.service.ts +17 -1
  58. package/src/core/common/types/field-selection.type.ts +1 -1
  59. package/src/core/modules/auth/guards/roles.guard.ts +1 -1
  60. package/src/core/modules/user/core-user.service.ts +13 -22
  61. package/src/core.module.ts +1 -1
  62. package/src/server/modules/file/file.resolver.ts +2 -1
  63. package/src/server/modules/user/user.resolver.ts +1 -1
  64. package/src/test/test.helper.ts +27 -34
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "8.6.5",
3
+ "version": "8.6.8",
4
4
  "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
5
5
  "keywords": [
6
6
  "node",
@@ -26,8 +26,9 @@
26
26
  "reinit": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i && npm run test:e2e && npm run build",
27
27
  "reinit:force": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i --force && npm run test:e2e",
28
28
  "reinit:legacy": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i --legacy-peer-deps && npm run test:e2e",
29
- "start": "./node_modules/.bin/grunt",
29
+ "start": "npm run start:dev",
30
30
  "stop": "./node_modules/.bin/pm2 delete nest",
31
+ "start:pm2": "./node_modules/.bin/grunt",
31
32
  "start:prod": "./node_modules/.bin/grunt productive",
32
33
  "start:nodemon": "ts-node -r tsconfig-paths/register src/main.ts",
33
34
  "start:debug": "nodemon --config nodemon-debug.json",
@@ -56,54 +57,54 @@
56
57
  "node": ">= 16.13.0"
57
58
  },
58
59
  "dependencies": {
59
- "@apollo/gateway": "0.50.2",
60
- "@nestjs/apollo": "10.0.11",
61
- "@nestjs/common": "8.4.5",
62
- "@nestjs/core": "8.4.5",
63
- "@nestjs/graphql": "10.0.11",
64
- "@nestjs/jwt": "8.0.0",
65
- "@nestjs/mongoose": "9.0.3",
66
- "@nestjs/passport": "8.2.1",
67
- "@nestjs/platform-express": "8.4.5",
68
- "apollo-server-core": "3.7.0",
69
- "apollo-server-express": "3.7.0",
60
+ "@apollo/gateway": "2.0.5",
61
+ "@nestjs/apollo": "10.0.16",
62
+ "@nestjs/common": "8.4.7",
63
+ "@nestjs/core": "8.4.7",
64
+ "@nestjs/graphql": "10.0.16",
65
+ "@nestjs/jwt": "8.0.1",
66
+ "@nestjs/mongoose": "9.1.1",
67
+ "@nestjs/passport": "8.2.2",
68
+ "@nestjs/platform-express": "8.4.7",
69
+ "apollo-server-core": "3.9.0",
70
+ "apollo-server-express": "3.9.0",
70
71
  "bcrypt": "5.0.1",
71
72
  "class-transformer": "0.5.1",
72
73
  "class-validator": "0.13.2",
73
74
  "ejs": "3.1.8",
74
75
  "graphql": "16.5.0",
75
76
  "graphql-subscriptions": "2.0.0",
76
- "graphql-upload": "13.0.0",
77
+ "graphql-upload": "15.0.1",
77
78
  "json-to-graphql-query": "2.2.4",
78
- "light-my-request": "4.10.1",
79
+ "light-my-request": "5.0.0",
79
80
  "lodash": "4.17.21",
80
- "mongodb": "4.6.0",
81
- "mongoose": "6.3.3",
81
+ "mongodb": "4.7.0",
82
+ "mongoose": "6.3.8",
82
83
  "multer": "1.4.4",
83
84
  "node-mailjet": "3.4.1",
84
85
  "nodemailer": "6.7.5",
85
86
  "nodemon": "2.0.16",
86
- "passport": "0.5.2",
87
+ "passport": "0.6.0",
87
88
  "passport-jwt": "4.0.0",
88
89
  "reflect-metadata": "0.1.13",
89
90
  "rimraf": "3.0.2",
90
91
  "rxjs": "7.5.5"
91
92
  },
92
93
  "devDependencies": {
93
- "@nestjs/testing": "8.4.5",
94
+ "@nestjs/testing": "8.4.7",
94
95
  "@types/ejs": "3.1.1",
95
- "@types/jest": "27.5.1",
96
+ "@types/jest": "28.1.1",
96
97
  "@types/lodash": "4.14.182",
97
98
  "@types/multer": "1.4.7",
98
- "@types/node": "16.11.35",
99
- "@types/node-mailjet": "3.3.8",
99
+ "@types/node": "18.0.0",
100
+ "@types/node-mailjet": "3.3.9",
100
101
  "@types/nodemailer": "6.4.4",
101
- "@types/passport": "1.0.7",
102
+ "@types/passport": "1.0.9",
102
103
  "@types/supertest": "2.0.12",
103
- "@typescript-eslint/eslint-plugin": "5.23.0",
104
- "@typescript-eslint/parser": "5.23.0",
104
+ "@typescript-eslint/eslint-plugin": "5.28.0",
105
+ "@typescript-eslint/parser": "5.28.0",
105
106
  "coffeescript": "2.7.0",
106
- "eslint": "8.15.0",
107
+ "eslint": "8.17.0",
107
108
  "eslint-config-prettier": "8.5.0",
108
109
  "find-file-up": "2.0.1",
109
110
  "grunt": "1.5.3",
@@ -112,16 +113,16 @@
112
113
  "grunt-contrib-watch": "1.1.0",
113
114
  "grunt-sync": "0.8.2",
114
115
  "husky": "8.0.1",
115
- "jest": "28.1.0",
116
+ "jest": "28.1.1",
116
117
  "pm2": "5.2.0",
117
- "prettier": "2.6.2",
118
+ "prettier": "2.7.1",
118
119
  "pretty-quick": "3.1.3",
119
120
  "supertest": "6.2.3",
120
- "ts-jest": "28.0.2",
121
- "ts-morph": "14.0.0",
122
- "ts-node": "10.7.0",
121
+ "ts-jest": "28.0.5",
122
+ "ts-morph": "15.1.0",
123
+ "ts-node": "10.8.1",
123
124
  "tsconfig-paths": "4.0.0",
124
- "typescript": "4.6.4"
125
+ "typescript": "4.7.3"
125
126
  },
126
127
  "jest": {
127
128
  "collectCoverage": true,
@@ -118,9 +118,8 @@ export const checkRestricted = (
118
118
 
119
119
  // Check roles
120
120
  if (roles.length) {
121
- // Check roles
122
121
  if (
123
- user?.hasRole(roles) ||
122
+ user?.hasRole?.(roles) ||
124
123
  (user?.id && roles.includes(RoleEnum.S_USER)) ||
125
124
  (roles.includes(RoleEnum.S_CREATOR) && getIncludedIds(config.dbObject?.createdBy, user))
126
125
  ) {
@@ -423,7 +423,9 @@ export function removeUnresolvedReferences<T = any>(
423
423
  if (typeof populated === 'object') {
424
424
  // populatedOptions is an array
425
425
  if (Array.isArray(populatedOptions)) {
426
- populatedOptions.forEach((po) => removeUnresolvedReferences(populated, ignoreFirst ? po.populate : po, false));
426
+ populatedOptions.forEach((po) =>
427
+ removeUnresolvedReferences(populated, ignoreFirst && typeof po === 'object' ? po.populate : po, false)
428
+ );
427
429
  return populated;
428
430
  }
429
431
 
@@ -462,7 +464,10 @@ export async function popAndMap<T extends CoreModel>(
462
464
  let result;
463
465
  let populateOptions: PopulateOptions[] = [];
464
466
  if (populate) {
465
- if (Array.isArray(populate) && typeof (populate as PopulateOptions[])[0]?.path === 'string') {
467
+ if (
468
+ Array.isArray(populate) &&
469
+ (typeof (populate as string[])[0] === 'string' || typeof (populate as PopulateOptions[])[0]?.path === 'string')
470
+ ) {
466
471
  populateOptions = populate as PopulateOptions[];
467
472
  } else if (Array.isArray(populate) && typeof (populate as SelectionNode[])[0]?.kind === 'string') {
468
473
  populateOptions = getPopulatOptionsFromSelections(populate as SelectionNode[]);
@@ -75,23 +75,21 @@ export function multerOptionsForImageUpload(options: {
75
75
  fileSize?: number;
76
76
  fileTypeRegex?: RegExp;
77
77
  }): MulterOptions {
78
- // Default options
79
- options = Object.assign(
80
- {
81
- fileSize: 1024 * 1024, // 1MB
82
- fileTypeRegex: /jpeg|jpg|png/, // Images only
83
- },
84
- options
85
- );
78
+ // Set config
79
+ const config = {
80
+ fileSize: 1024 * 1024, // 1MB
81
+ fileTypeRegex: /jpeg|jpg|png/, // Images only
82
+ ...options,
83
+ };
86
84
 
87
85
  return {
88
86
  // File filter
89
- fileFilter: options.fileTypeRegex ? multerFileFilter(options.fileTypeRegex) : undefined,
87
+ fileFilter: config.fileTypeRegex ? multerFileFilter(config.fileTypeRegex) : undefined,
90
88
 
91
89
  // Limits
92
90
  limits: {
93
91
  // Limit of file size
94
- fileSize: options.fileSize ? options.fileSize : undefined,
92
+ fileSize: config.fileSize ? config.fileSize : undefined,
95
93
  },
96
94
 
97
95
  // Automatic storage handling
@@ -100,7 +98,7 @@ export function multerOptionsForImageUpload(options: {
100
98
  // Destination for uploaded file
101
99
  // If destination is not set file will be buffered and can be processed
102
100
  // in the method
103
- destination: options.destination ? options.destination : undefined,
101
+ destination: config.destination ? config.destination : undefined,
104
102
 
105
103
  // Generated random file name
106
104
  filename: multerRandomFileName(),
@@ -4,6 +4,8 @@ import { ComparisonOperatorEnum } from '../enums/comparison-operator.enum';
4
4
  import { LogicalOperatorEnum } from '../enums/logical-operator.enum';
5
5
  import { FilterInput } from '../inputs/filter.input';
6
6
  import { SortInput } from '../inputs/sort.input';
7
+ import { getObjectIds } from './db.helper';
8
+ import { assignPlain } from './input.helper';
7
9
 
8
10
  /**
9
11
  * Helper for filter handling
@@ -33,9 +35,72 @@ export class Filter {
33
35
  }
34
36
  }
35
37
 
38
+ /**
39
+ * Helper function to create $and, $or or $nor filter
40
+ */
41
+ export function findFilter(options?: {
42
+ conditions?: Record<string, any>[];
43
+ filterOptions?: FilterQuery<any>;
44
+ id?: any;
45
+ ids?: any[];
46
+ type?: '$and' | '$or' | '$nor';
47
+ }): FilterQuery<any> {
48
+ const config = {
49
+ type: '$and',
50
+ ...options,
51
+ };
52
+
53
+ // Init filter Option
54
+ let filterOptions: FilterQuery<any> = config?.filterOptions;
55
+
56
+ // Check where condition
57
+ if (!filterOptions) {
58
+ filterOptions = {};
59
+ filterOptions[config.type] = [];
60
+ }
61
+
62
+ // Convert where condition to array
63
+ if (!Array.isArray(filterOptions?.[config.type])) {
64
+ filterOptions = {};
65
+ filterOptions[config.type] = [config?.filterOptions];
66
+ }
67
+
68
+ // ObjectId
69
+ if (config?.id) {
70
+ filterOptions[config.type].push({ _id: getObjectIds(config.id) });
71
+ }
72
+
73
+ // ObjectIds
74
+ if (config?.ids) {
75
+ if (!Array.isArray(config.ids)) {
76
+ config.ids = [config.ids];
77
+ }
78
+ filterOptions[config.type].push({ _id: { $in: getObjectIds(config.ids) } });
79
+ }
80
+
81
+ // Integrate conditions
82
+ if (config?.conditions) {
83
+ filterOptions[config.type] = [...filterOptions[config.type], ...config.conditions];
84
+ }
85
+
86
+ // Filter falsy values
87
+ filterOptions[config.type] = filterOptions[config.type].filter((value) => value);
88
+
89
+ // Optimizations
90
+ if (!filterOptions[config.type].length) {
91
+ filterOptions = {};
92
+ } else if (filterOptions[config.type].length === 1) {
93
+ const additionalProperties = filterOptions[config.type][0];
94
+ delete filterOptions[config.type];
95
+ assignPlain(filterOptions, additionalProperties);
96
+ }
97
+
98
+ // Return filter config
99
+ return filterOptions;
100
+ }
101
+
36
102
  /**
37
103
  * Convert filter arguments to a query array
38
- * @param filterArgs
39
104
  */
40
105
  export function convertFilterArgsToQuery<T = any>(filterArgs: Partial<FilterArgs>): [FilterQuery<T>, QueryOptions] {
41
106
  return [generateFilterQuery(filterArgs?.filter), generateFindOptions(filterArgs)];
@@ -1,6 +1,7 @@
1
1
  import { BadRequestException, UnauthorizedException } from '@nestjs/common';
2
2
  import { plainToInstance } from 'class-transformer';
3
3
  import { validate } from 'class-validator';
4
+ import { ValidatorOptions } from 'class-validator/types/validation/ValidatorOptions';
4
5
  import * as _ from 'lodash';
5
6
  import { checkRestricted } from '../decorators/restricted.decorator';
6
7
  import { ProcessType } from '../enums/process-type.enum';
@@ -182,6 +183,24 @@ export default class InputHelper {
182
183
  }
183
184
  }
184
185
 
186
+ /**
187
+ * Assign plain objects to the target object and ignores undefined
188
+ */
189
+ export function assignPlain(target: Record<any, any>, ...args: Record<any, any>[]): any {
190
+ return Object.assign(
191
+ target,
192
+ ...args.map(
193
+ // Prepare records
194
+ (item) =>
195
+ !item
196
+ ? // Return item if not an object
197
+ item
198
+ : // Return cloned record with undefined properties removed
199
+ filterProperties(JSON.parse(JSON.stringify(item)), (prop) => prop !== undefined)
200
+ )
201
+ );
202
+ }
203
+
185
204
  /**
186
205
  * Check input
187
206
  */
@@ -194,11 +213,16 @@ export async function check(
194
213
  processType?: ProcessType;
195
214
  roles?: string | string[];
196
215
  throwError?: boolean;
216
+ validatorOptions?: ValidatorOptions;
197
217
  }
198
218
  ): Promise<any> {
199
219
  const config = {
200
220
  throwError: true,
201
221
  ...options,
222
+ validatorOptions: {
223
+ skipUndefinedProperties: true,
224
+ ...options?.validatorOptions,
225
+ },
202
226
  };
203
227
 
204
228
  // Check roles
@@ -212,7 +236,7 @@ export async function check(
212
236
  // check if user is logged in
213
237
  (roles.includes(RoleEnum.S_USER) && user?.id) ||
214
238
  // check if the user has at least one of the required roles
215
- user.hasRole(roles) ||
239
+ user?.hasRole?.(roles) ||
216
240
  // check if the user is the creator
217
241
  (roles.includes(RoleEnum.S_CREATOR) && equalIds(config.dbObject?.createdBy, user))
218
242
  ) {
@@ -254,7 +278,7 @@ export async function check(
254
278
  }
255
279
 
256
280
  // Validate
257
- const errors = await validate(value);
281
+ const errors = await validate(value, config.validatorOptions);
258
282
  if (errors.length > 0 && config.throwError) {
259
283
  throw new BadRequestException('Validation failed');
260
284
  }
@@ -264,6 +288,13 @@ export async function check(
264
288
  return value;
265
289
  }
266
290
 
291
+ /**
292
+ * Combines objects to a new single plain object and ignores undefined
293
+ */
294
+ export function combinePlain(...args: Record<any, any>[]): any {
295
+ return assignPlain({}, ...args);
296
+ }
297
+
267
298
  /**
268
299
  * Standard error function
269
300
  */
@@ -273,6 +304,18 @@ export function errorFunction(caller: (...params) => any, message = 'Required pa
273
304
  throw err;
274
305
  }
275
306
 
307
+ /**
308
+ * Filter function for objects
309
+ */
310
+ export function filterProperties<T = Record<string, any>>(
311
+ obj: T,
312
+ filterFunction: (value?: any, key?: string, obj?: T) => boolean
313
+ ): Partial<T> {
314
+ return Object.keys(obj)
315
+ .filter((key) => filterFunction(obj[key], key, obj))
316
+ .reduce((res, key) => Object.assign(res, { [key]: obj[key] }), {});
317
+ }
318
+
276
319
  /**
277
320
  * Check if parameter is an array
278
321
  */
@@ -433,6 +476,22 @@ export function returnFalse(): boolean {
433
476
  return false;
434
477
  }
435
478
 
479
+ /**
480
+ * Match function to use instead of switch case
481
+ * Inspired by https://yusfuu.medium.com/dont-use-switch-or-if-else-in-javascript-instead-try-this-82f32616c269
482
+ *
483
+ * Example:
484
+ * const matched = match(expr, {
485
+ * Oranges: 'Oranges are $0.59 a pound.',
486
+ * Mangoes: 'Mangoes and papayas are $2.79 a pound.',
487
+ * Papayas: 'Mangoes and papayas are $2.79 a pound.',
488
+ * default: `Sorry, we are out of ${expr}.`,
489
+ * });
490
+ */
491
+ export function match(expression: any, cases: Record<any, any>): any {
492
+ return cases[expression] || cases?.default;
493
+ }
494
+
436
495
  /**
437
496
  * Map values into specific type
438
497
  */
@@ -55,6 +55,7 @@ export async function prepareInput<T = any>(
55
55
  currentUser: { [key: string]: any; id: string },
56
56
  options: {
57
57
  [key: string]: any;
58
+ checkRoles?: boolean;
58
59
  create?: boolean;
59
60
  clone?: boolean;
60
61
  getNewArray?: boolean;
@@ -68,7 +69,7 @@ export async function prepareInput<T = any>(
68
69
  clone: false,
69
70
  create: false,
70
71
  getNewArray: false,
71
- removeUndefined: false,
72
+ removeUndefined: true,
72
73
  ...options,
73
74
  };
74
75
 
@@ -107,11 +108,7 @@ export async function prepareInput<T = any>(
107
108
  }
108
109
 
109
110
  // Process roles
110
- if (
111
- config.checkRoles &&
112
- (input as Record<string, any>).roles &&
113
- (!currentUser?.hasRole || !currentUser.hasRole(RoleEnum.ADMIN))
114
- ) {
111
+ if (config.checkRoles && (input as Record<string, any>).roles && !currentUser?.hasRole?.(RoleEnum.ADMIN)) {
115
112
  if (!(currentUser as any)?.roles) {
116
113
  throw new UnauthorizedException('Missing roles of current user');
117
114
  } else {
@@ -152,6 +149,7 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
152
149
  [key: string]: any;
153
150
  clone?: boolean;
154
151
  getNewArray?: boolean;
152
+ removeSecrets?: boolean;
155
153
  removeUndefined?: boolean;
156
154
  targetModel?: new (...args: any[]) => T;
157
155
  } = {}
@@ -160,6 +158,7 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
160
158
  const config = {
161
159
  clone: false,
162
160
  getNewArray: false,
161
+ removeSecrets: true,
163
162
  removeUndefined: false,
164
163
  targetModel: undefined,
165
164
  ...options,
@@ -195,17 +194,17 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
195
194
  }
196
195
 
197
196
  // Remove password if exists
198
- if (output.password) {
197
+ if (config.removeSecrets && output.password) {
199
198
  output.password = undefined;
200
199
  }
201
200
 
202
201
  // Remove verification token if exists
203
- if (output.verificationToken) {
202
+ if (config.removeSecrets && output.verificationToken) {
204
203
  output.verificationToken = undefined;
205
204
  }
206
205
 
207
206
  // Remove password reset token if exists
208
- if (output.passwordResetToken) {
207
+ if (config.removeSecrets && output.passwordResetToken) {
209
208
  output.passwordResetToken = undefined;
210
209
  }
211
210
 
@@ -3,6 +3,7 @@
3
3
  */
4
4
  export interface PrepareInputOptions {
5
5
  [key: string]: any;
6
+ checkRoles?: boolean;
6
7
  create?: boolean;
7
8
  clone?: boolean;
8
9
  getNewArray?: boolean;
@@ -5,6 +5,7 @@ export interface PrepareOutputOptions {
5
5
  [key: string]: any;
6
6
  clone?: boolean;
7
7
  getNewArray?: boolean;
8
+ removeSecrets?: boolean;
8
9
  removeUndefined?: boolean;
9
10
  targetModel?: new (...args: any[]) => any;
10
11
  }
@@ -1,4 +1,4 @@
1
- import { Model } from 'mongoose';
1
+ import { Model, PopulateOptions } from 'mongoose';
2
2
  import { FieldSelection } from '../types/field-selection.type';
3
3
  import { PrepareInputOptions } from './prepare-input-options.interface';
4
4
  import { PrepareOutputOptions } from './prepare-output-options.interface';
@@ -22,15 +22,21 @@ export interface ServiceOptions {
22
22
  roles?: string[];
23
23
  };
24
24
 
25
- // Field selection for results
25
+ // Field selection for results (will be overwritten by populate if populate was set)
26
26
  fieldSelection?: FieldSelection;
27
27
 
28
+ // Determines whether all restrictions are ignored
29
+ force?: boolean;
30
+
28
31
  // Overwrites type of input (array items)
29
32
  inputType?: new (...params: any[]) => any;
30
33
 
31
34
  // Overwrites type of output (array items)
32
35
  outputType?: new (...params: any[]) => any;
33
36
 
37
+ // Alias for fieldSelection (if both are set fieldSelection is overwritten by populate)
38
+ populate?: PopulateOptions | (PopulateOptions | string)[];
39
+
34
40
  // Process field selection
35
41
  // If {} or not set, then the field selection runs with defaults
36
42
  // If falsy, then the field selection will not be automatically executed
@@ -92,7 +92,10 @@ export abstract class CoreModel {
92
92
  mapId: false,
93
93
  ...options,
94
94
  };
95
- return config.init ? map(data, this, config).init(config.init) : map(data, this, config);
95
+ if (config.init) {
96
+ this.init(config.init);
97
+ }
98
+ return map(data, this, config);
96
99
  }
97
100
 
98
101
  /**
@@ -2,6 +2,7 @@ import { NotFoundException } from '@nestjs/common';
2
2
  import { FilterArgs } from '../args/filter.args';
3
3
  import { merge } from '../helpers/config.helper';
4
4
  import { convertFilterArgsToQuery } from '../helpers/filter.helper';
5
+ import { assignPlain } from '../helpers/input.helper';
5
6
  import { ServiceOptions } from '../interfaces/service-options.interface';
6
7
  import { CoreModel } from '../models/core-model.model';
7
8
  import { ModuleService } from './module.service';
@@ -14,7 +15,8 @@ export abstract class CrudService<T extends CoreModel = any> extends ModuleServi
14
15
  merge({ prepareInput: { create: true } }, serviceOptions);
15
16
  return this.process(
16
17
  async (data) => {
17
- return new this.mainDbModel({ ...data.input }).save();
18
+ const currentUserId = serviceOptions?.currentUser?.id;
19
+ return new this.mainDbModel({ ...data.input, createdBy: currentUserId, updatedBy: currentUserId }).save();
18
20
  },
19
21
  { input, serviceOptions }
20
22
  );
@@ -75,7 +77,8 @@ export abstract class CrudService<T extends CoreModel = any> extends ModuleServi
75
77
  }
76
78
  return this.process(
77
79
  async (data) => {
78
- return await Object.assign(dbObject, data.input).save();
80
+ const currentUserId = serviceOptions?.currentUser?.id;
81
+ return await assignPlain(dbObject, data.input, { updatedBy: currentUserId }).save();
79
82
  },
80
83
  { dbObject, input, serviceOptions }
81
84
  );
@@ -90,11 +93,11 @@ export abstract class CrudService<T extends CoreModel = any> extends ModuleServi
90
93
  throw new NotFoundException(`No ${this.mainModelConstructor.name} found with ID: ${id}`);
91
94
  }
92
95
  return this.process(
93
- async (data) => {
96
+ async () => {
94
97
  await this.mainDbModel.findByIdAndDelete(id).exec();
95
98
  return dbObject;
96
99
  },
97
- { dbObject, input: id, serviceOptions }
100
+ { dbObject, serviceOptions }
98
101
  );
99
102
  }
100
103
  }
@@ -1,6 +1,6 @@
1
1
  import { Injectable } from '@nestjs/common';
2
2
  import { ConfigService } from './config.service';
3
- import * as mailjet from 'node-mailjet';
3
+ import mailjet from 'node-mailjet';
4
4
 
5
5
  /**
6
6
  * Mailjet service
@@ -74,6 +74,7 @@ export abstract class ModuleService<T extends CoreModel = any> {
74
74
  const config = {
75
75
  checkRights: true,
76
76
  dbObject: options?.dbObject,
77
+ force: false,
77
78
  input: options?.input,
78
79
  processFieldSelection: {},
79
80
  prepareInput: {},
@@ -82,6 +83,22 @@ export abstract class ModuleService<T extends CoreModel = any> {
82
83
  ...options?.serviceOptions,
83
84
  };
84
85
 
86
+ // Note force configuration
87
+ if (config.force) {
88
+ config.checkRights = false;
89
+ if (typeof config.prepareInput === 'object') {
90
+ config.prepareInput.checkRoles = false;
91
+ }
92
+ if (typeof config.prepareOutput === 'object') {
93
+ config.prepareOutput.removeSecrets = false;
94
+ }
95
+ }
96
+
97
+ // Note populate
98
+ if (config.populate) {
99
+ config.fieldSelection = config.populate;
100
+ }
101
+
85
102
  // Prepare input
86
103
  if (config.prepareInput && this.prepareInput) {
87
104
  const opts = config.prepareInput;
@@ -150,7 +167,6 @@ export abstract class ModuleService<T extends CoreModel = any> {
150
167
  */
151
168
  async prepareInput(input: Record<string, any>, options: ServiceOptions = {}) {
152
169
  const config = {
153
- targetModel: this.mainModelConstructor,
154
170
  ...options?.prepareInput,
155
171
  };
156
172
  return prepareInput(input, options.currentUser, config);
@@ -5,4 +5,4 @@ import { ResolveSelector } from '../interfaces/resolve-selector.interface';
5
5
  /**
6
6
  * Field selection to set fields of (populated) result
7
7
  */
8
- export type FieldSelection = PopulateOptions[] | SelectionNode[] | ResolveSelector;
8
+ export type FieldSelection = PopulateOptions | (PopulateOptions | string)[] | SelectionNode[] | ResolveSelector;
@@ -38,7 +38,7 @@ export class RolesGuard extends AuthGuard('jwt') {
38
38
  }
39
39
 
40
40
  // Check user and user roles
41
- if (!user || !user.hasRole(roles)) {
41
+ if (!user?.hasRole?.(roles)) {
42
42
  // Get args
43
43
  const args: any = GqlExecutionContext.create(context).getArgs();
44
44