@lenne.tech/nest-server 3.2.0 → 3.3.2

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 (142) hide show
  1. package/dist/core/common/args/filter.args.d.ts +5 -0
  2. package/dist/core/common/args/filter.args.js +10 -0
  3. package/dist/core/common/args/filter.args.js.map +1 -1
  4. package/dist/core/common/args/pagination.args.d.ts +8 -1
  5. package/dist/core/common/args/pagination.args.js +24 -6
  6. package/dist/core/common/args/pagination.args.js.map +1 -1
  7. package/dist/core/common/decorators/restricted.decorator.d.ts +3 -0
  8. package/dist/core/common/decorators/restricted.decorator.js +14 -5
  9. package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
  10. package/dist/core/common/helpers/context.helper.js +15 -5
  11. package/dist/core/common/helpers/context.helper.js.map +1 -1
  12. package/dist/core/common/helpers/filter.helper.d.ts +3 -3
  13. package/dist/core/common/helpers/filter.helper.js.map +1 -1
  14. package/dist/core/common/helpers/graphql.helper.d.ts +4 -4
  15. package/dist/core/common/helpers/input.helper.js +13 -7
  16. package/dist/core/common/helpers/input.helper.js.map +1 -1
  17. package/dist/core/common/helpers/model.helper.js +0 -1
  18. package/dist/core/common/helpers/model.helper.js.map +1 -1
  19. package/dist/core/common/helpers/service.helper.d.ts +11 -8
  20. package/dist/core/common/helpers/service.helper.js +50 -12
  21. package/dist/core/common/helpers/service.helper.js.map +1 -1
  22. package/dist/core/common/inputs/combined-filter.input.d.ts +8 -2
  23. package/dist/core/common/inputs/combined-filter.input.js +16 -5
  24. package/dist/core/common/inputs/combined-filter.input.js.map +1 -1
  25. package/dist/core/common/inputs/core-input.input.d.ts +8 -0
  26. package/dist/core/common/inputs/core-input.input.js +15 -0
  27. package/dist/core/common/inputs/core-input.input.js.map +1 -0
  28. package/dist/core/common/inputs/filter.input.d.ts +7 -1
  29. package/dist/core/common/inputs/filter.input.js +14 -1
  30. package/dist/core/common/inputs/filter.input.js.map +1 -1
  31. package/dist/core/common/inputs/single-filter.input.d.ts +2 -1
  32. package/dist/core/common/inputs/single-filter.input.js +10 -1
  33. package/dist/core/common/inputs/single-filter.input.js.map +1 -1
  34. package/dist/core/common/inputs/sort.input.d.ts +2 -1
  35. package/dist/core/common/inputs/sort.input.js +7 -1
  36. package/dist/core/common/inputs/sort.input.js.map +1 -1
  37. package/dist/core/common/interceptors/check-response.interceptor.js +1 -1
  38. package/dist/core/common/interceptors/check-response.interceptor.js.map +1 -1
  39. package/dist/core/common/interfaces/core-persistence-model.interface.d.ts +1 -0
  40. package/dist/core/common/interfaces/server-options.interface.d.ts +3 -15
  41. package/dist/core/common/models/core-model.model.d.ts +6 -0
  42. package/dist/core/common/models/core-model.model.js +14 -5
  43. package/dist/core/common/models/core-model.model.js.map +1 -1
  44. package/dist/core/common/models/core-persistence.model.d.ts +5 -29
  45. package/dist/core/common/models/core-persistence.model.js +19 -41
  46. package/dist/core/common/models/core-persistence.model.js.map +1 -1
  47. package/dist/core/common/pipes/check-input.pipe.d.ts +2 -3
  48. package/dist/core/common/pipes/check-input.pipe.js +5 -20
  49. package/dist/core/common/pipes/check-input.pipe.js.map +1 -1
  50. package/dist/core/common/pipes/map-and-validate.pipe.d.ts +4 -0
  51. package/dist/core/common/pipes/map-and-validate.pipe.js +40 -0
  52. package/dist/core/common/pipes/map-and-validate.pipe.js.map +1 -0
  53. package/dist/core/common/types/plain-input.type.d.ts +3 -0
  54. package/dist/core/common/types/plain-input.type.js +3 -0
  55. package/dist/core/common/types/plain-input.type.js.map +1 -0
  56. package/dist/core/modules/auth/core-auth.model.d.ts +3 -1
  57. package/dist/core/modules/auth/core-auth.model.js +7 -1
  58. package/dist/core/modules/auth/core-auth.model.js.map +1 -1
  59. package/dist/core/modules/auth/core-auth.module.d.ts +5 -2
  60. package/dist/core/modules/auth/core-auth.module.js +25 -17
  61. package/dist/core/modules/auth/core-auth.module.js.map +1 -1
  62. package/dist/core/modules/auth/core-auth.resolver.d.ts +1 -1
  63. package/dist/core/modules/user/core-user.model.d.ts +3 -0
  64. package/dist/core/modules/user/core-user.model.js +9 -4
  65. package/dist/core/modules/user/core-user.model.js.map +1 -1
  66. package/dist/core/modules/user/core-user.service.d.ts +9 -13
  67. package/dist/core/modules/user/core-user.service.js +38 -67
  68. package/dist/core/modules/user/core-user.service.js.map +1 -1
  69. package/dist/core/modules/user/inputs/core-user-create.input.js.map +1 -1
  70. package/dist/core/modules/user/inputs/core-user.input.d.ts +2 -2
  71. package/dist/core/modules/user/inputs/core-user.input.js +3 -3
  72. package/dist/core/modules/user/inputs/core-user.input.js.map +1 -1
  73. package/dist/core.module.js +4 -4
  74. package/dist/core.module.js.map +1 -1
  75. package/dist/index.d.ts +6 -2
  76. package/dist/index.js +6 -2
  77. package/dist/index.js.map +1 -1
  78. package/dist/main.js +0 -2
  79. package/dist/main.js.map +1 -1
  80. package/dist/server/common/models/persistence.model.d.ts +4 -2
  81. package/dist/server/common/models/persistence.model.js +6 -2
  82. package/dist/server/common/models/persistence.model.js.map +1 -1
  83. package/dist/server/modules/auth/auth.model.d.ts +1 -0
  84. package/dist/server/modules/auth/auth.model.js +4 -0
  85. package/dist/server/modules/auth/auth.model.js.map +1 -1
  86. package/dist/server/modules/user/inputs/user-create.input.js.map +1 -1
  87. package/dist/server/modules/user/inputs/user.input.js.map +1 -1
  88. package/dist/server/modules/user/user.model.d.ts +3 -2
  89. package/dist/server/modules/user/user.model.js +9 -5
  90. package/dist/server/modules/user/user.model.js.map +1 -1
  91. package/dist/server/modules/user/user.resolver.d.ts +2 -2
  92. package/dist/server/modules/user/user.resolver.js +11 -13
  93. package/dist/server/modules/user/user.resolver.js.map +1 -1
  94. package/dist/server/modules/user/user.service.d.ts +6 -6
  95. package/dist/server/modules/user/user.service.js +12 -4
  96. package/dist/server/modules/user/user.service.js.map +1 -1
  97. package/dist/test/test.helper.d.ts +1 -1
  98. package/dist/tsconfig.build.tsbuildinfo +1 -1
  99. package/package.json +26 -26
  100. package/src/core/common/args/filter.args.ts +22 -1
  101. package/src/core/common/args/pagination.args.ts +42 -7
  102. package/src/core/common/decorators/restricted.decorator.ts +22 -5
  103. package/src/core/common/helpers/context.helper.ts +14 -3
  104. package/src/core/common/helpers/filter.helper.ts +3 -3
  105. package/src/core/common/helpers/input.helper.ts +17 -11
  106. package/src/core/common/helpers/model.helper.ts +0 -3
  107. package/src/core/common/helpers/service.helper.ts +95 -30
  108. package/src/core/common/inputs/combined-filter.input.ts +30 -9
  109. package/src/core/common/inputs/core-input.input.ts +36 -0
  110. package/src/core/common/inputs/filter.input.ts +27 -3
  111. package/src/core/common/inputs/single-filter.input.ts +7 -6
  112. package/src/core/common/inputs/sort.input.ts +4 -3
  113. package/src/core/common/interceptors/check-response.interceptor.ts +2 -2
  114. package/src/core/common/interfaces/core-persistence-model.interface.ts +1 -0
  115. package/src/core/common/interfaces/server-options.interface.ts +3 -33
  116. package/src/core/common/models/core-model.model.ts +42 -3
  117. package/src/core/common/models/core-persistence.model.ts +33 -120
  118. package/src/core/common/pipes/check-input.pipe.ts +13 -33
  119. package/src/core/common/pipes/map-and-validate.pipe.ts +32 -0
  120. package/src/core/common/types/plain-input.type.ts +6 -0
  121. package/src/core/modules/auth/core-auth.model.ts +15 -1
  122. package/src/core/modules/auth/core-auth.module.ts +38 -22
  123. package/src/core/modules/auth/core-auth.resolver.ts +1 -1
  124. package/src/core/modules/user/core-user.model.ts +16 -4
  125. package/src/core/modules/user/core-user.service.ts +59 -115
  126. package/src/core/modules/user/inputs/core-user-create.input.ts +4 -0
  127. package/src/core/modules/user/inputs/core-user.input.ts +7 -3
  128. package/src/core.module.ts +13 -6
  129. package/src/index.ts +6 -2
  130. package/src/main.ts +0 -4
  131. package/src/server/common/models/persistence.model.ts +16 -2
  132. package/src/server/modules/auth/auth.model.ts +13 -0
  133. package/src/server/modules/user/inputs/user-create.input.ts +4 -0
  134. package/src/server/modules/user/inputs/user.input.ts +4 -0
  135. package/src/server/modules/user/user.model.ts +18 -5
  136. package/src/server/modules/user/user.resolver.ts +15 -19
  137. package/src/server/modules/user/user.service.ts +22 -7
  138. package/src/test/test.helper.ts +1 -1
  139. package/dist/core/common/pipes/map.pipe.d.ts +0 -4
  140. package/dist/core/common/pipes/map.pipe.js +0 -25
  141. package/dist/core/common/pipes/map.pipe.js.map +0 -1
  142. package/src/core/common/pipes/map.pipe.ts +0 -16
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "3.2.0",
3
+ "version": "3.3.2",
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",
@@ -52,11 +52,11 @@
52
52
  "node": ">= 16.13.0"
53
53
  },
54
54
  "dependencies": {
55
- "@apollo/federation": "0.33.9",
56
- "@apollo/gateway": "0.42.3",
55
+ "@apollo/gateway": "0.48.1",
56
+ "@nestjs/apollo": "10.0.2",
57
57
  "@nestjs/common": "8.2.6",
58
58
  "@nestjs/core": "8.2.6",
59
- "@nestjs/graphql": "9.1.2",
59
+ "@nestjs/graphql": "10.0.2",
60
60
  "@nestjs/jwt": "8.0.0",
61
61
  "@nestjs/mongoose": "9.0.2",
62
62
  "@nestjs/passport": "8.1.0",
@@ -67,22 +67,24 @@
67
67
  "@types/jest": "27.4.0",
68
68
  "@types/lodash": "4.14.178",
69
69
  "@types/multer": "1.4.7",
70
- "@types/node": "16.11.21",
70
+ "@types/node": "17.0.17",
71
71
  "@types/node-mailjet": "3.3.8",
72
72
  "@types/nodemailer": "6.4.4",
73
73
  "@types/passport": "1.0.7",
74
74
  "@types/supertest": "2.0.11",
75
- "@typescript-eslint/eslint-plugin": "5.10.0",
76
- "@typescript-eslint/parser": "5.10.0",
77
- "apollo-server-core": "3.6.2",
78
- "apollo-server-express": "3.6.2",
75
+ "@typescript-eslint/eslint-plugin": "5.11.0",
76
+ "@typescript-eslint/parser": "5.11.0",
77
+ "apollo-server-core": "3.6.3",
78
+ "apollo-server-express": "3.6.3",
79
79
  "bcrypt": "5.0.1",
80
80
  "class-transformer": "0.5.1",
81
81
  "class-validator": "0.13.2",
82
82
  "coffeescript": "2.6.1",
83
83
  "ejs": "3.1.6",
84
- "fastify": "3.27.0",
85
- "graphql": "15.8.0",
84
+ "eslint": "8.9.0",
85
+ "eslint-config-prettier": "8.3.0",
86
+ "fastify": "3.27.1",
87
+ "graphql": "16.3.0",
86
88
  "graphql-subscriptions": "2.0.0",
87
89
  "grunt": "1.4.1",
88
90
  "grunt-bg-shell": "2.3.3",
@@ -90,35 +92,33 @@
90
92
  "grunt-contrib-watch": "1.1.0",
91
93
  "grunt-sync": "0.8.2",
92
94
  "husky": "7.0.4",
93
- "jest": "27.4.7",
94
- "json-to-graphql-query": "2.2.0",
95
- "light-my-request": "4.7.0",
95
+ "jest": "27.5.1",
96
+ "json-to-graphql-query": "2.2.2",
97
+ "light-my-request": "4.7.1",
96
98
  "lodash": "4.17.21",
97
- "mongodb": "4.3.0",
98
- "mongoose": "6.1.7",
99
+ "mongodb": "4.3.1",
100
+ "mongoose": "6.2.1",
99
101
  "multer": "1.4.4",
100
102
  "node-mailjet": "3.3.5",
101
103
  "nodemailer": "6.7.2",
102
104
  "nodemon": "2.0.15",
103
105
  "passport": "0.5.2",
104
106
  "passport-jwt": "4.0.0",
107
+ "prettier": "2.5.1",
108
+ "pretty-quick": "3.1.3",
105
109
  "reflect-metadata": "0.1.13",
106
110
  "rimraf": "3.0.2",
107
- "rxjs": "7.5.2",
111
+ "rxjs": "7.5.4",
108
112
  "supertest": "6.2.2",
113
+ "ts-jest": "27.1.3",
109
114
  "ts-morph": "13.0.3",
110
- "ts-node": "10.4.0",
111
- "tsconfig-paths": "3.12.0"
115
+ "ts-node": "10.5.0",
116
+ "tsconfig-paths": "3.12.0",
117
+ "typescript": "4.5.5"
112
118
  },
113
119
  "devDependencies": {
114
- "eslint": "8.7.0",
115
- "eslint-config-prettier": "8.3.0",
116
120
  "find-file-up": "2.0.1",
117
- "pm2": "5.1.2",
118
- "prettier": "2.5.1",
119
- "pretty-quick": "3.1.3",
120
- "ts-jest": "27.1.3",
121
- "typescript": "4.5.5"
121
+ "pm2": "5.1.2"
122
122
  },
123
123
  "jest": {
124
124
  "collectCoverage": true,
@@ -13,5 +13,26 @@ export class FilterArgs extends PaginationArgs {
13
13
  nullable: true,
14
14
  })
15
15
  @IsOptional()
16
- filter?: FilterInput;
16
+ filter?: FilterInput = undefined;
17
+
18
+ // ===================================================================================================================
19
+ // Methods
20
+ // ===================================================================================================================
21
+
22
+ /**
23
+ * Mapping for Subtypes
24
+ */
25
+ map(
26
+ data: Partial<this> | Record<string, any>,
27
+ options: {
28
+ cloneDeep?: boolean;
29
+ funcAllowed?: boolean;
30
+ mapId?: boolean;
31
+ } = {}
32
+ ): this {
33
+ super.map(data, options);
34
+ this.filter = data.filter ? FilterInput.map(data.filter, options) : undefined;
35
+ Object.keys(this).forEach((key) => this[key] === undefined && delete this[key]);
36
+ return this;
37
+ }
17
38
  }
@@ -1,9 +1,11 @@
1
1
  import { IsOptional, Max } from 'class-validator';
2
2
  import { ArgsType, Field, Int } from '@nestjs/graphql';
3
+ import { ModelHelper } from '../helpers/model.helper';
4
+ import { CoreInput } from '../inputs/core-input.input';
3
5
  import { SortInput } from '../inputs/sort.input';
4
6
 
5
7
  @ArgsType()
6
- export class PaginationArgs {
8
+ export class PaginationArgs extends CoreInput {
7
9
  /**
8
10
  * Limit for pagination
9
11
  */
@@ -14,7 +16,7 @@ export class PaginationArgs {
14
16
  })
15
17
  @IsOptional()
16
18
  @Max(100)
17
- limit?: number = 25;
19
+ limit?: number = undefined;
18
20
 
19
21
  /**
20
22
  * Offset for pagination
@@ -25,7 +27,7 @@ export class PaginationArgs {
25
27
  defaultValue: 0,
26
28
  })
27
29
  @IsOptional()
28
- offset?: number = 0;
30
+ offset?: number = undefined;
29
31
 
30
32
  /**
31
33
  * Alias for offset
@@ -33,10 +35,10 @@ export class PaginationArgs {
33
35
  @Field((type) => Int, {
34
36
  description: 'Alias for offset',
35
37
  nullable: true,
36
- defaultValue: 0,
38
+ defaultValue: undefined,
37
39
  })
38
40
  @IsOptional()
39
- skip?: number = 0;
41
+ skip?: number = undefined;
40
42
 
41
43
  /**
42
44
  * Sorting for pagination
@@ -46,7 +48,7 @@ export class PaginationArgs {
46
48
  nullable: true,
47
49
  })
48
50
  @IsOptional()
49
- sort?: SortInput[];
51
+ sort?: SortInput[] = undefined;
50
52
 
51
53
  /**
52
54
  * Alias for limit
@@ -58,5 +60,38 @@ export class PaginationArgs {
58
60
  })
59
61
  @IsOptional()
60
62
  @Max(100)
61
- take?: number = 25;
63
+ take?: number = undefined;
64
+
65
+ // ===================================================================================================================
66
+ // Methods
67
+ // ===================================================================================================================
68
+
69
+ /**
70
+ * Initialize instance with default values instead of undefined
71
+ */
72
+ init(): this {
73
+ super.init();
74
+ this.limit = this.limit === undefined ? 25 : this.limit;
75
+ this.offset = this.offset === undefined ? 0 : this.offset;
76
+ this.skip = this.skip === undefined ? 0 : this.skip;
77
+ this.take = this.take === undefined ? 0 : this.take;
78
+ return this;
79
+ }
80
+
81
+ /**
82
+ * Mapping for Subtypes
83
+ */
84
+ map(
85
+ data: Partial<this> | Record<string, any>,
86
+ options: {
87
+ cloneDeep?: boolean;
88
+ funcAllowed?: boolean;
89
+ mapId?: boolean;
90
+ } = {}
91
+ ): this {
92
+ super.map(data, options);
93
+ this.sort = ModelHelper.maps(data.sort, SortInput, options.cloneDeep);
94
+ Object.keys(this).forEach((key) => this[key] === undefined && delete this[key]);
95
+ return this;
96
+ }
62
97
  }
@@ -1,4 +1,5 @@
1
1
  import 'reflect-metadata';
2
+ import { UnauthorizedException } from '@nestjs/common';
2
3
  import { RoleEnum } from '../enums/role.enum';
3
4
 
4
5
  /**
@@ -31,8 +32,15 @@ export const getRestricted = (object: unknown, propertyKey: string) => {
31
32
  export const checkRestricted = (
32
33
  data: any,
33
34
  user: { id: any; hasRole: (roles: string[]) => boolean },
35
+ options: { ignoreUndefined?: boolean; throwError?: boolean } = {},
34
36
  processedObjects: any[] = []
35
37
  ) => {
38
+ const config = {
39
+ ignoreUndefined: true,
40
+ throwError: true,
41
+ ...options,
42
+ };
43
+
36
44
  // Primitives
37
45
  if (!data || typeof data !== 'object') {
38
46
  return data;
@@ -47,11 +55,16 @@ export const checkRestricted = (
47
55
  // Array
48
56
  if (Array.isArray(data)) {
49
57
  // Check array items
50
- return data.map((item) => checkRestricted(item, user, processedObjects));
58
+ return data.map((item) => checkRestricted(item, user, options, processedObjects));
51
59
  }
52
60
 
53
61
  // Object
54
62
  for (const propertyKey of Object.keys(data)) {
63
+ // Check undefined
64
+ if (data[propertyKey] === undefined && config.ignoreUndefined) {
65
+ continue;
66
+ }
67
+
55
68
  // Get roles
56
69
  const roles = getRestricted(data, propertyKey);
57
70
 
@@ -71,20 +84,24 @@ export const checkRestricted = (
71
84
  data.ownerIds.some((item) => (item.id ? item.id.toString() === userId : item.toString() === userId)))
72
85
  )
73
86
  ) {
74
- // User is not the owner
75
- delete data[propertyKey];
87
+ // The user does not have the required rights and is not the owner
88
+ if (config.throwError) {
89
+ throw new UnauthorizedException('Current user is not allowed to set ' + propertyKey);
90
+ }
76
91
  continue;
77
92
  }
78
93
  } else {
79
94
  // The user does not have the required rights
80
- delete data[propertyKey];
95
+ if (config.throwError) {
96
+ throw new UnauthorizedException('Current user is not allowed to set ' + propertyKey);
97
+ }
81
98
  continue;
82
99
  }
83
100
  }
84
101
  }
85
102
 
86
103
  // Check property data
87
- data[propertyKey] = checkRestricted(data[propertyKey], user, processedObjects);
104
+ data[propertyKey] = checkRestricted(data[propertyKey], user, options, processedObjects);
88
105
  }
89
106
 
90
107
  // Return processed data
@@ -16,15 +16,26 @@ export class Context {
16
16
 
17
17
  // Init data
18
18
  let user: { [key: string]: any };
19
- const ctx: any = GqlExecutionContext.create(context).getContext();
20
- let args: any = GqlExecutionContext.create(context).getArgs();
19
+ let ctx: any = null;
20
+ try {
21
+ ctx = GqlExecutionContext.create(context)?.getContext();
22
+ } catch (e) {
23
+ // console.log(e);
24
+ }
25
+
26
+ let args: any;
27
+ try {
28
+ args = GqlExecutionContext.create(context)?.getArgs();
29
+ } catch (e) {
30
+ // console.log(e);
31
+ }
21
32
 
22
33
  // Get data
23
34
  if (ctx) {
24
35
  // User from GraphQL context
25
36
  user = ctx?.user || ctx?.req?.user;
26
37
  } else {
27
- const request = context.switchToHttp().getRequest();
38
+ const request = context?.switchToHttp ? context.switchToHttp()?.getRequest() : null;
28
39
  if (request) {
29
40
  args = request.body;
30
41
 
@@ -13,14 +13,14 @@ export class Filter {
13
13
  * Convert filter arguments to a query array
14
14
  * @param filterArgs
15
15
  */
16
- public static convertFilterArgsToQuery<T = any>(filterArgs: FilterArgs): [FilterQuery<T>, QueryOptions] {
16
+ public static convertFilterArgsToQuery<T = any>(filterArgs: Partial<FilterArgs>): [FilterQuery<T>, QueryOptions] {
17
17
  return [Filter.generateFilterQuery(filterArgs.filter), Filter.generateFindOptions(filterArgs)];
18
18
  }
19
19
 
20
20
  /**
21
21
  * Generate filter query
22
22
  */
23
- public static generateFilterQuery<T = any>(filter?: FilterInput): FilterQuery<T> | any {
23
+ public static generateFilterQuery<T = any>(filter?: Partial<FilterInput>): FilterQuery<T> | any {
24
24
  // Check filter
25
25
  if (!filter) {
26
26
  return undefined;
@@ -98,7 +98,7 @@ export class Filter {
98
98
  /**
99
99
  * Generate find options
100
100
  */
101
- public static generateFindOptions<T = any>(filterArgs: FilterArgs): QueryOptions {
101
+ public static generateFindOptions<T = any>(filterArgs: Partial<FilterArgs>): QueryOptions {
102
102
  // Check filterArgs
103
103
  if (!filterArgs) {
104
104
  return {};
@@ -1,5 +1,5 @@
1
1
  import { BadRequestException } from '@nestjs/common';
2
- import { plainToClass } from 'class-transformer';
2
+ import { plainToInstance } from 'class-transformer';
3
3
  import { validate } from 'class-validator';
4
4
  import * as _ from 'lodash';
5
5
  import { checkRestricted } from '../decorators/restricted.decorator';
@@ -17,21 +17,27 @@ export class InputHelper {
17
17
  metatype?
18
18
  ): Promise<any> {
19
19
  // Return value if it is only a basic type
20
- if (!metatype || this.isBasicType(metatype)) {
20
+ if (typeof value !== 'object' || !metatype || this.isBasicType(metatype)) {
21
21
  return value;
22
22
  }
23
23
 
24
- // Remove restricted values if roles are missing
25
- value = checkRestricted(value, user);
26
-
27
- // Check values
28
- if (metatype) {
29
- const object = plainToClass(metatype, value);
30
- const errors = await validate(object);
31
- if (errors.length > 0) {
32
- throw new BadRequestException('Validation failed');
24
+ // Convert to metatype
25
+ if (!(value instanceof metatype)) {
26
+ if ((metatype as any)?.map) {
27
+ value = (metatype as any)?.map(value);
28
+ } else {
29
+ value = plainToInstance(metatype, value);
33
30
  }
34
31
  }
32
+
33
+ // Validate
34
+ const errors = await validate(value);
35
+ if (errors.length > 0) {
36
+ throw new BadRequestException('Validation failed');
37
+ }
38
+
39
+ // Remove restricted values if roles are missing
40
+ value = checkRestricted(value, user);
35
41
  return value;
36
42
  }
37
43
 
@@ -77,9 +77,6 @@ export class ModelHelper {
77
77
  // Merge target with prepared source
78
78
  Object.assign(target, preparedSource);
79
79
 
80
- // Remove all props with undefined
81
- Object.keys(target).forEach((key) => target[key] === undefined && delete target[key]);
82
-
83
80
  // Return target
84
81
  return target;
85
82
  }
@@ -10,52 +10,85 @@ export class ServiceHelper {
10
10
  /**
11
11
  * Prepare input before save
12
12
  */
13
- static async prepareInput(
14
- input: { [key: string]: any },
13
+ static async prepareInput<T = any>(
14
+ input: T,
15
15
  currentUser: { [key: string]: any; id: string },
16
- options: { [key: string]: any; create?: boolean; clone?: boolean } = {},
17
- ...args: any[]
18
- ) {
16
+ options: {
17
+ [key: string]: any;
18
+ create?: boolean;
19
+ clone?: boolean;
20
+ getNewArray?: boolean;
21
+ removeUndefined?: boolean;
22
+ } = {}
23
+ ): Promise<T> {
19
24
  // Configuration
20
25
  const config = {
21
26
  checkRoles: false,
22
27
  clone: false,
23
28
  create: false,
29
+ getNewArray: false,
30
+ removeUndefined: false,
24
31
  ...options,
25
32
  };
26
33
 
27
- // Clone output
34
+ // Check input
35
+ if (typeof input !== 'object') {
36
+ return input;
37
+ }
38
+
39
+ // Process array
40
+ if (Array.isArray(input)) {
41
+ const processedArray = input.map(
42
+ async (item) => await ServiceHelper.prepareInput(item, currentUser, options)
43
+ ) as any;
44
+ return config.getNewArray ? processedArray : input;
45
+ }
46
+
47
+ // Clone input
28
48
  if (config.clone) {
29
- input = JSON.parse(JSON.stringify(input));
49
+ if ((input as Record<string, any>).mapDeep && typeof (input as any).mapDeep === 'function') {
50
+ input = await Object.getPrototypeOf(input).mapDeep(input);
51
+ } else {
52
+ input = _.cloneDeep(input);
53
+ }
54
+ }
55
+
56
+ // Remove undefined properties to avoid unwanted overwrites
57
+ if (config.removeUndefined) {
58
+ Object.keys(input).forEach((key) => input[key] === undefined && delete input[key]);
30
59
  }
31
60
 
32
61
  // Process roles
33
- if (input.roles && config.checkRoles && (!currentUser?.hasRole || !currentUser.hasRole(RoleEnum.ADMIN))) {
62
+ if (
63
+ config.checkRoles &&
64
+ (input as Record<string, any>).roles &&
65
+ (!currentUser?.hasRole || !currentUser.hasRole(RoleEnum.ADMIN))
66
+ ) {
34
67
  if (!(currentUser as any)?.roles) {
35
68
  throw new UnauthorizedException('Missing roles of current user');
36
69
  } else {
37
- const allowedRoles = _.intersection(input.roles, (currentUser as any).roles);
38
- if (allowedRoles.length !== input.roles.length) {
39
- const missingRoles = _.difference(input.roles, (currentUser as any).roles);
70
+ const allowedRoles = _.intersection((input as Record<string, any>).roles, (currentUser as any).roles);
71
+ if (allowedRoles.length !== (input as Record<string, any>).roles.length) {
72
+ const missingRoles = _.difference((input as Record<string, any>).roles, (currentUser as any).roles);
40
73
  throw new UnauthorizedException('Current user not allowed setting roles: ' + missingRoles);
41
74
  }
42
- input.roles = allowedRoles;
75
+ (input as Record<string, any>).roles = allowedRoles;
43
76
  }
44
77
  }
45
78
 
46
79
  // Hash password
47
- if (input.password) {
48
- input.password = await bcrypt.hash((input as any).password, 10);
80
+ if ((input as Record<string, any>).password) {
81
+ (input as Record<string, any>).password = await bcrypt.hash((input as any).password, 10);
49
82
  }
50
83
 
51
84
  // Set creator
52
85
  if (config.create && currentUser) {
53
- input.createdBy = currentUser.id;
86
+ (input as Record<string, any>).createdBy = currentUser.id;
54
87
  }
55
88
 
56
89
  // Set updater
57
90
  if (currentUser) {
58
- input.updatedBy = currentUser.id;
91
+ (input as Record<string, any>).updatedBy = currentUser.id;
59
92
  }
60
93
 
61
94
  // Return prepared input
@@ -65,39 +98,71 @@ export class ServiceHelper {
65
98
  /**
66
99
  * Prepare output before return
67
100
  */
68
- static async prepareOutput<T = Record<string, any>>(
101
+ static async prepareOutput<T = { [key: string]: any; map: (...args: any[]) => any }>(
69
102
  output: any,
70
- userModel: new () => any,
71
- userService: any,
72
- options: { [key: string]: any; clone?: boolean; targetModel?: Partial<T> } = {},
73
- ...args: any[]
74
- ) {
103
+ options: {
104
+ [key: string]: any;
105
+ clone?: boolean;
106
+ getNewArray?: boolean;
107
+ removeUndefined?: boolean;
108
+ targetModel?: new (...args: any[]) => T;
109
+ } = {}
110
+ ): Promise<T | T[] | any> {
75
111
  // Configuration
76
112
  const config = {
77
- clone: true,
113
+ clone: false,
114
+ getNewArray: false,
115
+ removeUndefined: false,
116
+ targetModel: undefined,
78
117
  ...options,
79
118
  };
80
119
 
120
+ // Check output
121
+ if (typeof output !== 'object') {
122
+ return output;
123
+ }
124
+
125
+ // Process array
126
+ if (Array.isArray(output)) {
127
+ const processedArray = output.map(async (item) => await ServiceHelper.prepareOutput(item, options)) as any;
128
+ return config.getNewArray ? processedArray : output;
129
+ }
130
+
81
131
  // Clone output
82
132
  if (config.clone) {
83
- output = JSON.parse(JSON.stringify(output));
133
+ if (output.mapDeep && typeof output.mapDeep === 'function') {
134
+ output = await Object.getPrototypeOf(output).mapDeep(output);
135
+ } else {
136
+ output = _.cloneDeep(output);
137
+ }
84
138
  }
85
139
 
86
140
  // Map output if target model exist
87
- if (options.targetModel) {
88
- (options.targetModel as any).map(output);
141
+ if (config.targetModel) {
142
+ output = await (config.targetModel as any).map(output);
89
143
  }
90
144
 
91
145
  // Remove password if exists
92
- delete output.password;
146
+ if (output.password) {
147
+ output.password = undefined;
148
+ }
93
149
 
94
150
  // Remove verification token if exists
95
- delete output.verificationToken;
151
+ if (output.verificationToken) {
152
+ output.verificationToken = undefined;
153
+ }
96
154
 
97
155
  // Remove password reset token if exists
98
- delete output.passwordResetToken;
156
+ if (output.passwordResetToken) {
157
+ output.passwordResetToken = undefined;
158
+ }
159
+
160
+ // Remove undefined properties to avoid unwanted overwrites
161
+ if (config.removeUndefined) {
162
+ Object.keys(output).forEach((key) => output[key] === undefined && delete output[key]);
163
+ }
99
164
 
100
- // Return prepared user
165
+ // Return prepared output
101
166
  return output;
102
167
  }
103
168
  }
@@ -1,26 +1,47 @@
1
1
  import { Field, InputType } from '@nestjs/graphql';
2
2
  import { LogicalOperatorEnum } from '../enums/logical-operator.enum';
3
+ import { ModelHelper } from '../helpers/model.helper';
4
+ import { CoreInput } from './core-input.input';
3
5
  import { FilterInput } from './filter.input';
4
6
 
5
7
  @InputType({
6
8
  description: 'Combination of multiple filters via logical operator',
7
9
  })
8
- export class CombinedFilterInput {
10
+ export class CombinedFilterInput extends CoreInput {
9
11
  /**
10
- * Logical Operator to combine filters. If set the `filters` must be also set.
12
+ * Logical Operator to combine filters
11
13
  */
12
14
  @Field((type) => LogicalOperatorEnum, {
13
- description: 'Logical Operator to combine filters. If set the `filters` must be also set.',
14
- nullable: true,
15
+ description: 'Logical Operator to combine filters',
15
16
  })
16
- logicalOperator?: LogicalOperatorEnum;
17
+ logicalOperator: LogicalOperatorEnum = undefined;
17
18
 
18
19
  /**
19
- * Filters to combine via logical operator. If set `logicalOperator` must be also set.
20
+ * Filters to combine via logical operator
20
21
  */
21
22
  @Field((type) => [FilterInput], {
22
- description: 'Filters to combine via logical operator. If set `logicalOperator` must be also set.',
23
- nullable: true,
23
+ description: 'Filters to combine via logical operator',
24
24
  })
25
- filters: FilterInput[];
25
+ filters: FilterInput[] = undefined;
26
+
27
+ // ===================================================================================================================
28
+ // Methods
29
+ // ===================================================================================================================
30
+
31
+ /**
32
+ * Mapping for Subtypes
33
+ */
34
+ map(
35
+ data: Partial<this> | Record<string, any>,
36
+ options: {
37
+ cloneDeep?: boolean;
38
+ funcAllowed?: boolean;
39
+ mapId?: boolean;
40
+ } = {}
41
+ ): this {
42
+ super.map(data, options);
43
+ this.filters = ModelHelper.maps(data.filters, FilterInput, options.cloneDeep);
44
+ Object.keys(this).forEach((key) => this[key] === undefined && delete this[key]);
45
+ return this;
46
+ }
26
47
  }