@lenne.tech/nest-server 3.1.2 → 3.3.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 (122) 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 +16 -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/input.helper.js +13 -7
  15. package/dist/core/common/helpers/input.helper.js.map +1 -1
  16. package/dist/core/common/helpers/service.helper.d.ts +6 -8
  17. package/dist/core/common/helpers/service.helper.js +34 -12
  18. package/dist/core/common/helpers/service.helper.js.map +1 -1
  19. package/dist/core/common/inputs/combined-filter.input.d.ts +8 -2
  20. package/dist/core/common/inputs/combined-filter.input.js +16 -5
  21. package/dist/core/common/inputs/combined-filter.input.js.map +1 -1
  22. package/dist/core/common/inputs/core-input.input.d.ts +8 -0
  23. package/dist/core/common/inputs/core-input.input.js +15 -0
  24. package/dist/core/common/inputs/core-input.input.js.map +1 -0
  25. package/dist/core/common/inputs/filter.input.d.ts +7 -1
  26. package/dist/core/common/inputs/filter.input.js +14 -1
  27. package/dist/core/common/inputs/filter.input.js.map +1 -1
  28. package/dist/core/common/inputs/single-filter.input.d.ts +2 -1
  29. package/dist/core/common/inputs/single-filter.input.js +10 -1
  30. package/dist/core/common/inputs/single-filter.input.js.map +1 -1
  31. package/dist/core/common/inputs/sort.input.d.ts +2 -1
  32. package/dist/core/common/inputs/sort.input.js +7 -1
  33. package/dist/core/common/inputs/sort.input.js.map +1 -1
  34. package/dist/core/common/interceptors/check-response.interceptor.js +1 -1
  35. package/dist/core/common/interceptors/check-response.interceptor.js.map +1 -1
  36. package/dist/core/common/models/core-model.model.d.ts +6 -0
  37. package/dist/core/common/models/core-model.model.js +10 -3
  38. package/dist/core/common/models/core-model.model.js.map +1 -1
  39. package/dist/core/common/models/core-persistence.model.d.ts +5 -29
  40. package/dist/core/common/models/core-persistence.model.js +19 -41
  41. package/dist/core/common/models/core-persistence.model.js.map +1 -1
  42. package/dist/core/common/pipes/check-input.pipe.d.ts +2 -3
  43. package/dist/core/common/pipes/check-input.pipe.js +5 -20
  44. package/dist/core/common/pipes/check-input.pipe.js.map +1 -1
  45. package/dist/core/common/pipes/map-and-validate.pipe.d.ts +4 -0
  46. package/dist/core/common/pipes/map-and-validate.pipe.js +40 -0
  47. package/dist/core/common/pipes/map-and-validate.pipe.js.map +1 -0
  48. package/dist/core/common/types/plain-input.type.d.ts +3 -0
  49. package/dist/core/common/types/plain-input.type.js +3 -0
  50. package/dist/core/common/types/plain-input.type.js.map +1 -0
  51. package/dist/core/modules/auth/core-auth.model.d.ts +3 -1
  52. package/dist/core/modules/auth/core-auth.model.js +7 -1
  53. package/dist/core/modules/auth/core-auth.model.js.map +1 -1
  54. package/dist/core/modules/auth/core-auth.resolver.d.ts +1 -1
  55. package/dist/core/modules/user/core-user.model.d.ts +3 -0
  56. package/dist/core/modules/user/core-user.model.js +10 -4
  57. package/dist/core/modules/user/core-user.model.js.map +1 -1
  58. package/dist/core/modules/user/core-user.service.d.ts +9 -13
  59. package/dist/core/modules/user/core-user.service.js +38 -67
  60. package/dist/core/modules/user/core-user.service.js.map +1 -1
  61. package/dist/core/modules/user/inputs/core-user-create.input.js +4 -0
  62. package/dist/core/modules/user/inputs/core-user-create.input.js.map +1 -1
  63. package/dist/core/modules/user/inputs/core-user.input.d.ts +2 -1
  64. package/dist/core/modules/user/inputs/core-user.input.js +12 -2
  65. package/dist/core/modules/user/inputs/core-user.input.js.map +1 -1
  66. package/dist/core.module.js +2 -3
  67. package/dist/core.module.js.map +1 -1
  68. package/dist/index.d.ts +6 -2
  69. package/dist/index.js +6 -2
  70. package/dist/index.js.map +1 -1
  71. package/dist/server/common/models/persistence.model.d.ts +1 -0
  72. package/dist/server/common/models/persistence.model.js +4 -0
  73. package/dist/server/common/models/persistence.model.js.map +1 -1
  74. package/dist/server/modules/auth/auth.model.d.ts +1 -0
  75. package/dist/server/modules/auth/auth.model.js +4 -0
  76. package/dist/server/modules/auth/auth.model.js.map +1 -1
  77. package/dist/server/modules/user/inputs/user-create.input.js.map +1 -1
  78. package/dist/server/modules/user/inputs/user.input.js.map +1 -1
  79. package/dist/server/modules/user/user.model.d.ts +3 -2
  80. package/dist/server/modules/user/user.model.js +9 -5
  81. package/dist/server/modules/user/user.model.js.map +1 -1
  82. package/dist/server/modules/user/user.resolver.d.ts +2 -2
  83. package/dist/server/modules/user/user.resolver.js +10 -12
  84. package/dist/server/modules/user/user.resolver.js.map +1 -1
  85. package/dist/server/modules/user/user.service.d.ts +7 -9
  86. package/dist/server/modules/user/user.service.js +12 -4
  87. package/dist/server/modules/user/user.service.js.map +1 -1
  88. package/dist/tsconfig.build.tsbuildinfo +1 -1
  89. package/package.json +1 -1
  90. package/src/core/common/args/filter.args.ts +22 -1
  91. package/src/core/common/args/pagination.args.ts +42 -7
  92. package/src/core/common/decorators/restricted.decorator.ts +24 -5
  93. package/src/core/common/helpers/context.helper.ts +14 -3
  94. package/src/core/common/helpers/filter.helper.ts +3 -3
  95. package/src/core/common/helpers/input.helper.ts +17 -11
  96. package/src/core/common/helpers/service.helper.ts +42 -19
  97. package/src/core/common/inputs/combined-filter.input.ts +30 -9
  98. package/src/core/common/inputs/core-input.input.ts +36 -0
  99. package/src/core/common/inputs/filter.input.ts +27 -3
  100. package/src/core/common/inputs/single-filter.input.ts +7 -6
  101. package/src/core/common/inputs/sort.input.ts +4 -3
  102. package/src/core/common/interceptors/check-response.interceptor.ts +2 -2
  103. package/src/core/common/models/core-model.model.ts +30 -1
  104. package/src/core/common/models/core-persistence.model.ts +33 -120
  105. package/src/core/common/pipes/check-input.pipe.ts +13 -33
  106. package/src/core/common/pipes/map-and-validate.pipe.ts +32 -0
  107. package/src/core/common/types/plain-input.type.ts +6 -0
  108. package/src/core/modules/auth/core-auth.model.ts +15 -1
  109. package/src/core/modules/auth/core-auth.resolver.ts +1 -1
  110. package/src/core/modules/user/core-user.model.ts +17 -4
  111. package/src/core/modules/user/core-user.service.ts +59 -115
  112. package/src/core/modules/user/inputs/core-user-create.input.ts +5 -1
  113. package/src/core/modules/user/inputs/core-user.input.ts +13 -8
  114. package/src/core.module.ts +11 -5
  115. package/src/index.ts +6 -2
  116. package/src/server/common/models/persistence.model.ts +13 -0
  117. package/src/server/modules/auth/auth.model.ts +13 -0
  118. package/src/server/modules/user/inputs/user-create.input.ts +4 -0
  119. package/src/server/modules/user/inputs/user.input.ts +4 -0
  120. package/src/server/modules/user/user.model.ts +18 -5
  121. package/src/server/modules/user/user.resolver.ts +15 -19
  122. package/src/server/modules/user/user.service.ts +22 -7
@@ -1,20 +1,14 @@
1
- import {
2
- BadRequestException,
3
- NotFoundException,
4
- UnauthorizedException,
5
- UnprocessableEntityException,
6
- } from '@nestjs/common';
1
+ import { BadRequestException, NotFoundException, UnprocessableEntityException } from '@nestjs/common';
7
2
  import * as bcrypt from 'bcrypt';
8
3
  import { PubSub } from 'graphql-subscriptions';
9
4
  import { FilterArgs } from '../../common/args/filter.args';
10
- import { RoleEnum } from '../../common/enums/role.enum';
11
5
  import { Filter } from '../../common/helpers/filter.helper';
6
+ import { ServiceHelper } from '../../common/helpers/service.helper';
12
7
  import { CoreBasicUserService } from './core-basic-user.service';
13
8
  import { CoreUserModel } from './core-user.model';
14
9
  import { CoreUserCreateInput } from './inputs/core-user-create.input';
15
10
  import { CoreUserInput } from './inputs/core-user.input';
16
- import { Model } from 'mongoose';
17
- import * as _ from 'lodash';
11
+ import { Model, Document } from 'mongoose';
18
12
  import * as crypto from 'crypto';
19
13
  import envConfig from '../../../config.env';
20
14
  import { EmailService } from '../../common/services/email.service';
@@ -30,7 +24,7 @@ export abstract class CoreUserService<
30
24
  TUserInput extends CoreUserInput,
31
25
  TUserCreateInput extends CoreUserCreateInput
32
26
  > extends CoreBasicUserService<TUser, TUserInput, TUserCreateInput> {
33
- protected constructor(protected readonly userModel: Model<any>, protected emailService: EmailService) {
27
+ protected constructor(protected readonly userModel: Model<TUser & Document>, protected emailService: EmailService) {
34
28
  super(userModel);
35
29
  }
36
30
 
@@ -45,11 +39,11 @@ export abstract class CoreUserService<
45
39
  // Prepare input
46
40
  await this.prepareInput(input, currentUser, { create: true });
47
41
 
48
- // Generate verification token
49
- const newUser = { ...input, ...{ verificationToken: crypto.randomBytes(32).toString('hex') } };
50
-
51
42
  // Create new user
52
- const createdUser = new this.userModel(this.model.map(newUser));
43
+ const createdUser = new this.userModel({
44
+ ...input,
45
+ verificationToken: crypto.randomBytes(32).toString('hex'),
46
+ });
53
47
 
54
48
  try {
55
49
  // Save created user
@@ -63,13 +57,13 @@ export abstract class CoreUserService<
63
57
  }
64
58
 
65
59
  // Prepare output
66
- await this.prepareOutput(createdUser, args[0]);
60
+ const preparedUser = await this.prepareOutput(this.model.map(createdUser), args[0]);
67
61
 
68
62
  // Inform subscriber
69
- pubSub.publish('userCreated', { userCreated: createdUser });
63
+ pubSub.publish('userCreated', { userCreated: preparedUser });
70
64
 
71
65
  // Return created user
72
- return createdUser;
66
+ return preparedUser;
73
67
  }
74
68
 
75
69
  /**
@@ -77,7 +71,7 @@ export abstract class CoreUserService<
77
71
  */
78
72
  async delete(id: string, ...args: any[]): Promise<TUser> {
79
73
  // Search user
80
- let user = await this.userModel.findOne({ _id: id }).exec();
74
+ const user = await this.userModel.findById(id).exec();
81
75
 
82
76
  // Check user
83
77
  if (!user) {
@@ -85,19 +79,23 @@ export abstract class CoreUserService<
85
79
  }
86
80
 
87
81
  // Delete user
88
- await this.userModel.deleteOne({ _id: id }).exec();
82
+ await user.delete();
83
+
84
+ // Prepare output
85
+ const deletedUser = await this.prepareOutput(this.model.map(user), args[0]);
89
86
 
90
- user = this.model.map(user);
87
+ // Inform subscriber
88
+ pubSub.publish('userDeleted', { userDeleted: deletedUser });
91
89
 
92
90
  // Return deleted user
93
- return await this.prepareOutput(user, args[0]);
91
+ return deletedUser;
94
92
  }
95
93
 
96
94
  /**
97
95
  * Get user via ID
98
96
  */
99
97
  async get(id: string, ...args: any[]): Promise<TUser> {
100
- const user = await this.userModel.findOne({ _id: id }).exec();
98
+ const user = await this.userModel.findById(id).exec();
101
99
 
102
100
  if (!user) {
103
101
  throw new NotFoundException();
@@ -116,17 +114,15 @@ export abstract class CoreUserService<
116
114
  (
117
115
  await this.userModel.find(filterQuery[0], null, filterQuery[1]).exec()
118
116
  ).map((user) => {
119
- return this.prepareOutput(user, args[0]);
117
+ return this.prepareOutput(this.model.map(user), args[0]);
120
118
  })
121
119
  );
122
120
  }
123
121
 
124
122
  /**
125
123
  * Verify user with token
126
- *
127
- * @param token
128
124
  */
129
- async verify(token: string): Promise<boolean> {
125
+ async verify(token: string, ...args: any[]): Promise<TUser> {
130
126
  const user = await this.userModel.findOne({ verificationToken: token }).exec();
131
127
 
132
128
  if (!user) {
@@ -141,59 +137,65 @@ export abstract class CoreUserService<
141
137
  throw new Error('User already verified');
142
138
  }
143
139
 
144
- await this.userModel.findByIdAndUpdate(user.id, { $set: { verified: true, verificationToken: null } }).exec();
140
+ // Update user
141
+ await Object.assign(user, {
142
+ verified: true,
143
+ verificationToken: null,
144
+ }).save();
145
+
146
+ // Prepare verified user
147
+ const verifiedUser = this.prepareOutput(this.model.map(user), args[0]);
148
+
149
+ // Inform subscriber
150
+ pubSub.publish('userVerified', { userVerified: verifiedUser });
145
151
 
146
- return true;
152
+ // Return prepared verified user
153
+ return verifiedUser;
147
154
  }
148
155
 
149
156
  /**
150
157
  * Set newpassword for user with token
151
- *
152
- * @param token
153
- * @param newPassword
154
158
  */
155
- async resetPassword(token: string, newPassword: string): Promise<boolean> {
159
+ async resetPassword(token: string, newPassword: string, ...args: any[]): Promise<TUser> {
156
160
  const user = await this.userModel.findOne({ passwordResetToken: token }).exec();
157
161
 
158
162
  if (!user) {
159
163
  throw new NotFoundException();
160
164
  }
161
165
 
162
- const cryptedPassword = await bcrypt.hash(newPassword, 10);
163
- await this.userModel
164
- .findByIdAndUpdate(user.id, { $set: { password: cryptedPassword, passwordResetToken: null } })
165
- .exec();
166
+ // Update user
167
+ await Object.assign(user, {
168
+ password: await bcrypt.hash(newPassword, 10),
169
+ passwordResetToken: null,
170
+ }).save();
166
171
 
167
- return true;
172
+ // Return prepared user with changed password
173
+ return this.prepareOutput(this.model.map(user), args[0]);
168
174
  }
169
175
 
170
176
  /**
171
- * Request email with password reset link
172
- *
173
- * @param email
177
+ * Set password rest token for email
174
178
  */
175
- async requestPasswordResetMail(email: string): Promise<boolean> {
179
+ async setPasswordResetTokenForEmail(email: string, ...args: any[]): Promise<TUser> {
176
180
  const user = await this.userModel.findOne({ email }).exec();
177
181
 
178
182
  if (!user) {
179
183
  throw new NotFoundException();
180
184
  }
181
185
 
186
+ // Set reset token
182
187
  const resetToken = crypto.randomBytes(32).toString('hex');
183
- await this.userModel.findByIdAndUpdate(user.id, { $set: { passwordResetToken: resetToken } }).exec();
184
-
185
- await this.emailService.sendMail(user.email, 'Password reset', {
186
- htmlTemplate: 'password-reset',
187
- templateData: { name: user.username, link: envConfig.email.passwordResetLink + '/' + resetToken },
188
- });
188
+ user.passwordResetToken = resetToken;
189
+ await user.save();
189
190
 
190
- return true;
191
+ // Return user who want to reset the password
192
+ return this.prepareOutput(this.model.map(user), args[0]);
191
193
  }
192
194
 
193
195
  /**
194
196
  * Set roles for specified user
195
197
  */
196
- async setRoles(userId: string, roles: string[]): Promise<TUser> {
198
+ async setRoles(userId: string, roles: string[], ...args: any[]): Promise<TUser> {
197
199
  // Check roles
198
200
  if (!Array.isArray(roles)) {
199
201
  throw new BadRequestException('Missing roles');
@@ -205,7 +207,8 @@ export abstract class CoreUserService<
205
207
  }
206
208
 
207
209
  // Update and return user
208
- return this.userModel.findByIdAndUpdate(userId, { roles }).exec();
210
+ const user = await this.userModel.findByIdAndUpdate(userId, { roles }).exec();
211
+ return this.prepareOutput(this.model.map(user), args[0]);
209
212
  }
210
213
 
211
214
  /**
@@ -213,7 +216,7 @@ export abstract class CoreUserService<
213
216
  */
214
217
  async update(id: string, input: TUserInput, currentUser: TUser, ...args: any[]): Promise<TUser> {
215
218
  // Check if user exists
216
- let user = await this.userModel.findOne({ _id: id }).exec();
219
+ const user = await this.userModel.findById(id).exec();
217
220
 
218
221
  if (!user) {
219
222
  throw new NotFoundException(`User not found with ID: ${id}`);
@@ -223,16 +226,10 @@ export abstract class CoreUserService<
223
226
  await this.prepareInput(input, currentUser);
224
227
 
225
228
  // Update
226
- user.set(input);
227
-
228
- // Save
229
- await user.save();
230
-
231
- // Map for response
232
- user = this.model.map(user);
229
+ await Object.assign(user, input).save();
233
230
 
234
231
  // Return user
235
- return await this.prepareOutput(user as TUser, args[0]);
232
+ return await this.prepareOutput(this.model.map(user), args[0]);
236
233
  }
237
234
 
238
235
  // ===================================================================================================================
@@ -243,44 +240,12 @@ export abstract class CoreUserService<
243
240
  * Prepare input before save
244
241
  */
245
242
  protected async prepareInput(
246
- input: { [key: string]: any },
243
+ input: Record<string, any>,
247
244
  currentUser?: TUser,
248
245
  options: { [key: string]: any; checkRoles?: boolean; clone?: boolean } = {},
249
246
  ...args: any[]
250
247
  ) {
251
- // Configuration
252
- const config = {
253
- checkRoles: true,
254
- clone: false,
255
- ...options,
256
- };
257
-
258
- // Clone input
259
- if (config.clone) {
260
- input = JSON.parse(JSON.stringify(input));
261
- }
262
-
263
- // Process roles
264
- if (input.roles && config.checkRoles && (!currentUser?.hasRole || !currentUser.hasRole(RoleEnum.ADMIN))) {
265
- if (!(currentUser as any)?.roles) {
266
- throw new UnauthorizedException('Missing roles of current user');
267
- } else {
268
- const allowedRoles = _.intersection(input.roles, (currentUser as any).roles);
269
- if (allowedRoles.length !== input.roles.length) {
270
- const missingRoles = _.difference(input.roles, (currentUser as any).roles);
271
- throw new UnauthorizedException('Current user not allowed setting roles: ' + missingRoles);
272
- }
273
- input.roles = allowedRoles;
274
- }
275
- }
276
-
277
- // Hash password
278
- if (input.password) {
279
- input.password = await bcrypt.hash((input as any).password, 10);
280
- }
281
-
282
- // Return prepared input
283
- return input;
248
+ return ServiceHelper.prepareInput(input, currentUser, options);
284
249
  }
285
250
 
286
251
  /**
@@ -291,27 +256,6 @@ export abstract class CoreUserService<
291
256
  options: { [key: string]: any; clone?: boolean } = {},
292
257
  ...args: any[]
293
258
  ): Promise<TUser> {
294
- // Configuration
295
- const config = {
296
- clone: true,
297
- ...options,
298
- };
299
-
300
- // Clone user
301
- if (config.clone) {
302
- user = JSON.parse(JSON.stringify(user));
303
- }
304
-
305
- // Remove password if exists
306
- delete (user as any).password;
307
-
308
- // Remove verification token if exists
309
- delete (user as any).verificationToken;
310
-
311
- // Remove password reset token if exists
312
- delete (user as any).passwordResetToken;
313
-
314
- // Return prepared user
315
- return user;
259
+ return ServiceHelper.prepareOutput(user, options);
316
260
  }
317
261
  }
@@ -4,10 +4,14 @@ import { CoreUserInput } from './core-user.input';
4
4
 
5
5
  /**
6
6
  * User input to create a new user
7
+ *
8
+ * HINT: All properties (in this class and all classes that extend this class) must be initialized with undefined,
9
+ * otherwise the property will not be recognized via Object.keys (this is necessary for mapping) or will be initialized
10
+ * with a default value that may overwrite an existing value in the DB.
7
11
  */
8
12
  @InputType({ description: 'User input to create a new user', isAbstract: true })
9
13
  export abstract class CoreUserCreateInput extends CoreUserInput {
10
14
  @Field({ description: 'Email of the user', nullable: false })
11
15
  @IsEmail()
12
- email: string;
16
+ email: string = undefined;
13
17
  }
@@ -2,53 +2,58 @@ import { IsEmail, IsOptional } from 'class-validator';
2
2
  import { Field, InputType } from '@nestjs/graphql';
3
3
  import { Restricted } from '../../../common/decorators/restricted.decorator';
4
4
  import { RoleEnum } from '../../../common/enums/role.enum';
5
+ import { CoreInput } from '../../../common/inputs/core-input.input';
5
6
 
6
7
  /**
7
8
  * User input to update a user
9
+ *
10
+ * HINT: All properties (in this class and all classes that extend this class) must be initialized with undefined,
11
+ * otherwise the property will not be recognized via Object.keys (this is necessary for mapping) or will be initialized
12
+ * with a default value that may overwrite an existing value in the DB.
8
13
  */
9
14
  @InputType({ description: 'User input', isAbstract: true })
10
- export abstract class CoreUserInput {
15
+ export abstract class CoreUserInput extends CoreInput {
11
16
  /**
12
17
  * Email of the user
13
18
  */
14
19
  @Field({ description: 'Email of the user', nullable: true })
15
20
  @IsOptional()
16
21
  @IsEmail()
17
- email?: string;
22
+ email?: string = undefined;
18
23
 
19
24
  /**
20
25
  * First name of the user
21
26
  */
22
27
  @Field({ description: 'First name of the user', nullable: true })
23
28
  @IsOptional()
24
- firstName?: string;
29
+ firstName?: string = undefined;
25
30
 
26
31
  /**
27
32
  * Last name of the user
28
33
  */
29
34
  @Field({ description: 'Last name of the user', nullable: true })
30
35
  @IsOptional()
31
- lastName?: string;
36
+ lastName?: string = undefined;
32
37
 
33
38
  /**
34
39
  * Roles of the user
35
40
  */
36
- @Restricted(RoleEnum.ADMIN, RoleEnum.OWNER)
41
+ @Restricted(RoleEnum.ADMIN)
37
42
  @Field((type) => [String], { description: 'Roles of the user', nullable: true })
38
43
  @IsOptional()
39
- roles?: string[];
44
+ roles?: string[] = undefined;
40
45
 
41
46
  /**
42
47
  * Username / alias of the user
43
48
  */
44
49
  @Field({ description: 'Username / alias of the user', nullable: true })
45
50
  @IsOptional()
46
- username?: string;
51
+ username?: string = undefined;
47
52
 
48
53
  /**
49
54
  * Password of the user
50
55
  */
51
56
  @Field({ description: 'Password of the user', nullable: true })
52
57
  @IsOptional()
53
- password?: string;
58
+ password?: string = undefined;
54
59
  }
@@ -1,10 +1,10 @@
1
- import { DynamicModule, Global, Module, Scope } from '@nestjs/common';
1
+ import { DynamicModule, Global, Module } from '@nestjs/common';
2
2
  import { APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
3
3
  import { GraphQLModule } from '@nestjs/graphql';
4
4
  import { Config } from './core/common/helpers/config.helper';
5
5
  import { CheckResponseInterceptor } from './core/common/interceptors/check-response.interceptor';
6
6
  import { IServerOptions } from './core/common/interfaces/server-options.interface';
7
- import { CheckInputPipe } from './core/common/pipes/check-input.pipe';
7
+ import { MapAndValidatePipe } from './core/common/pipes/map-and-validate.pipe';
8
8
  import { ConfigService } from './core/common/services/config.service';
9
9
  import { EmailService } from './core/common/services/email.service';
10
10
  import { TemplateService } from './core/common/services/template.service';
@@ -82,11 +82,17 @@ export class CoreModule {
82
82
  },
83
83
 
84
84
  // [Global] The CheckInputPipe checks the permissibility of individual properties of inputs for the resolvers
85
- // in relation to the current user
85
+ // in relation to the current user, replaces MapAndValidatePipe
86
+ // Does not work yet, because context is missing: https://github.com/nestjs/graphql/issues/325
87
+ // {
88
+ // provide: APP_PIPE,
89
+ // useClass: CheckInputPipe,
90
+ // },
91
+
92
+ // [Global] Map plain objects to metatype and validate
86
93
  {
87
94
  provide: APP_PIPE,
88
- scope: Scope.REQUEST,
89
- useClass: CheckInputPipe,
95
+ useClass: MapAndValidatePipe,
90
96
  },
91
97
 
92
98
  // Core Services
package/src/index.ts CHANGED
@@ -26,22 +26,26 @@ export * from './core/common/helpers/input.helper';
26
26
  export * from './core/common/helpers/model.helper';
27
27
  export * from './core/common/helpers/service.helper';
28
28
  export * from './core/common/inputs/combined-filter.input';
29
+ export * from './core/common/inputs/core-input.input';
29
30
  export * from './core/common/inputs/filter.input';
30
31
  export * from './core/common/inputs/single-filter.input';
31
32
  export * from './core/common/inputs/sort.input';
32
33
  export * from './core/common/interceptors/check-response.interceptor';
33
34
  export * from './core/common/interfaces/core-persistence-model.interface';
35
+ export * from './core/common/interfaces/mailjet-options.interface';
34
36
  export * from './core/common/interfaces/server-options.interface';
35
37
  export * from './core/common/models/core-model.model';
36
38
  export * from './core/common/models/core-persistence.model';
37
39
  export * from './core/common/pipes/check-input.pipe';
40
+ export * from './core/common/pipes/map-and-validate.pipe';
38
41
  export * from './core/common/scalars/any.scalar';
39
42
  export * from './core/common/scalars/date.scalar';
40
43
  export * from './core/common/scalars/json.scalar';
41
44
  export * from './core/common/services/config.service';
42
45
  export * from './core/common/services/email.service';
43
- export * from './core/common/services/template.service';
44
46
  export * from './core/common/services/mailjet.service';
47
+ export * from './core/common/services/template.service';
48
+ export * from './core/common/types/plain-input.type';
45
49
 
46
50
  // =====================================================================================================================
47
51
  // Core - Modules - Auth
@@ -64,8 +68,8 @@ export * from './core/modules/auth/jwt.strategy';
64
68
 
65
69
  export * from './core/modules/user/inputs/core-user.input';
66
70
  export * from './core/modules/user/inputs/core-user-create.input';
67
- export * from './core/modules/user/core-user.model';
68
71
  export * from './core/modules/user/core-basic-user.service';
72
+ export * from './core/modules/user/core-user.model';
69
73
  export * from './core/modules/user/core-user.service';
70
74
 
71
75
  // =====================================================================================================================
@@ -37,4 +37,17 @@ export abstract class PersistenceModel extends CorePersistenceModel {
37
37
  })
38
38
  @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' })
39
39
  updatedBy?: User = undefined;
40
+
41
+ // ===========================================================================
42
+ // Properties
43
+ // ===========================================================================
44
+
45
+ /**
46
+ * Initialize instance with default values instead of undefined
47
+ */
48
+ init() {
49
+ super.init();
50
+ // Nothing more to initialize yet
51
+ return this;
52
+ }
40
53
  }
@@ -16,4 +16,17 @@ export class Auth extends CoreAuthModel {
16
16
  */
17
17
  @Field((type) => User, { description: 'User who signed in' })
18
18
  user: User = undefined;
19
+
20
+ // ===================================================================================================================
21
+ // Properties
22
+ // ===================================================================================================================
23
+
24
+ /**
25
+ * Initialize instance with default values instead of undefined
26
+ */
27
+ init() {
28
+ super.init();
29
+ // Nothing more to initialize yet
30
+ return this;
31
+ }
19
32
  }
@@ -3,6 +3,10 @@ import { CoreUserCreateInput } from '../../../../core/modules/user/inputs/core-u
3
3
 
4
4
  /**
5
5
  * User input to create a new user
6
+ *
7
+ * HINT: All properties (in this class and all classes that extend this class) must be initialized with undefined,
8
+ * otherwise the property will not be recognized via Object.keys (this is necessary for mapping) or will be initialized
9
+ * with a default value that may overwrite an existing value in the DB.
6
10
  */
7
11
  @InputType({ description: 'User input to create a new user' })
8
12
  export class UserCreateInput extends CoreUserCreateInput {
@@ -3,6 +3,10 @@ import { CoreUserInput } from '../../../../core/modules/user/inputs/core-user.in
3
3
 
4
4
  /**
5
5
  * User input to update a user
6
+ *
7
+ * HINT: All properties (in this class and all classes that extend this class) must be initialized with undefined,
8
+ * otherwise the property will not be recognized via Object.keys (this is necessary for mapping) or will be initialized
9
+ * with a default value that may overwrite an existing value in the DB.
6
10
  */
7
11
  @InputType({ description: 'User input' })
8
12
  export class UserInput extends CoreUserInput {
@@ -1,16 +1,16 @@
1
1
  import { Field, ObjectType } from '@nestjs/graphql';
2
2
  import { CoreUserModel } from '../../../core/modules/user/core-user.model';
3
3
  import { PersistenceModel } from '../../common/models/persistence.model';
4
- import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
5
- import * as mongoose from 'mongoose';
4
+ import { Prop, Schema as MongooseSchema, SchemaFactory } from '@nestjs/mongoose';
5
+ import { Schema, Document } from 'mongoose';
6
6
 
7
7
  export type UserDocument = User & Document;
8
8
 
9
9
  /**
10
10
  * User schema
11
11
  */
12
- @Schema()
13
12
  @ObjectType({ description: 'User' })
13
+ @MongooseSchema({ timestamps: true })
14
14
  export class User extends CoreUserModel implements PersistenceModel {
15
15
  // ===================================================================================================================
16
16
  // Properties
@@ -32,7 +32,7 @@ export class User extends CoreUserModel implements PersistenceModel {
32
32
  description: 'ID of the user who created the object',
33
33
  nullable: true,
34
34
  })
35
- @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' })
35
+ @Prop({ type: Schema.Types.ObjectId, ref: 'User' })
36
36
  createdBy: User = undefined;
37
37
 
38
38
  /**
@@ -44,8 +44,21 @@ export class User extends CoreUserModel implements PersistenceModel {
44
44
  description: 'ID of the user who last updated the object',
45
45
  nullable: true,
46
46
  })
47
- @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' })
47
+ @Prop({ type: Schema.Types.ObjectId, ref: 'User' })
48
48
  updatedBy: User = undefined;
49
+
50
+ // ===================================================================================================================
51
+ // Methods
52
+ // ===================================================================================================================
53
+
54
+ /**
55
+ * Initialize instance with default values instead of undefined
56
+ */
57
+ init() {
58
+ super.init();
59
+ // Nothing more to initialize yet
60
+ return this;
61
+ }
49
62
  }
50
63
 
51
64
  export const UserSchema = SchemaFactory.createForClass(User);
@@ -47,8 +47,8 @@ export class UserResolver {
47
47
  * Request new password for user with email
48
48
  */
49
49
  @Query((returns) => Boolean, { description: 'Request new password for user with email' })
50
- async requestPasswordResetMail(@Args('email') email: string) {
51
- return await this.usersService.requestPasswordResetMail(email);
50
+ async requestPasswordResetMail(@Args('email') email: string): Promise<boolean> {
51
+ return !!(await this.usersService.sendPasswordResetMail(email));
52
52
  }
53
53
 
54
54
  // ===========================================================================
@@ -58,28 +58,29 @@ export class UserResolver {
58
58
  * Verify user with email
59
59
  */
60
60
  @Mutation((returns) => Boolean, { description: 'Verify user with email' })
61
- async verifyUser(@Args('token') token: string) {
62
- return await this.usersService.verify(token);
61
+ async verifyUser(@Args('token') token: string): Promise<boolean> {
62
+ return !!(await this.usersService.verify(token));
63
63
  }
64
64
 
65
65
  /**
66
66
  * Set new password for user with token
67
67
  */
68
68
  @Mutation((returns) => Boolean, { description: 'Set new password for user with token' })
69
- async resetPassword(@Args('token') token: string, @Args('password') password: string) {
70
- return await this.usersService.resetPassword(token, password);
69
+ async resetPassword(@Args('token') token: string, @Args('password') password: string): Promise<boolean> {
70
+ return !!(await this.usersService.resetPassword(token, password));
71
71
  }
72
72
 
73
73
  /**
74
74
  * Create new user
75
75
  */
76
76
  @Mutation((returns) => User, { description: 'Create a new user' })
77
- async createUser(
78
- @Args('input') input: UserCreateInput,
79
- @GraphQLUser() user: User,
80
- @Info() info: GraphQLResolveInfo
81
- ): Promise<User> {
82
- return await this.usersService.create(input, user, info);
77
+ async createUser(@Args('input') input: UserCreateInput, @GraphQLUser() user: User): Promise<User> {
78
+ // Check input
79
+ // Hint: necessary as long as global CheckInputPipe can't access context for current user
80
+ // (see https://github.com/nestjs/graphql/issues/325)
81
+ input = await InputHelper.check(input, user, UserCreateInput);
82
+
83
+ return await this.usersService.create(input, user);
83
84
  }
84
85
 
85
86
  /**
@@ -87,19 +88,14 @@ export class UserResolver {
87
88
  */
88
89
  @Roles(RoleEnum.ADMIN, RoleEnum.OWNER)
89
90
  @Mutation((returns) => User, { description: 'Update existing user' })
90
- async updateUser(
91
- @Args('input') input: UserInput,
92
- @Args('id') id: string,
93
- @GraphQLUser() user: User,
94
- @Info() info: GraphQLResolveInfo
95
- ): Promise<User> {
91
+ async updateUser(@Args('input') input: UserInput, @Args('id') id: string, @GraphQLUser() user: User): Promise<User> {
96
92
  // Check input
97
93
  // Hint: necessary as long as global CheckInputPipe can't access context for current user
98
94
  // (see https://github.com/nestjs/graphql/issues/325)
99
95
  input = await InputHelper.check(input, user, UserInput);
100
96
 
101
97
  // Update user
102
- return await this.usersService.update(id, input, user, info);
98
+ return await this.usersService.update(id, input, user);
103
99
  }
104
100
 
105
101
  /**