@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.
- package/dist/core/common/args/filter.args.d.ts +5 -0
- package/dist/core/common/args/filter.args.js +10 -0
- package/dist/core/common/args/filter.args.js.map +1 -1
- package/dist/core/common/args/pagination.args.d.ts +8 -1
- package/dist/core/common/args/pagination.args.js +24 -6
- package/dist/core/common/args/pagination.args.js.map +1 -1
- package/dist/core/common/decorators/restricted.decorator.d.ts +3 -0
- package/dist/core/common/decorators/restricted.decorator.js +16 -5
- package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
- package/dist/core/common/helpers/context.helper.js +15 -5
- package/dist/core/common/helpers/context.helper.js.map +1 -1
- package/dist/core/common/helpers/filter.helper.d.ts +3 -3
- package/dist/core/common/helpers/filter.helper.js.map +1 -1
- package/dist/core/common/helpers/input.helper.js +13 -7
- package/dist/core/common/helpers/input.helper.js.map +1 -1
- package/dist/core/common/helpers/service.helper.d.ts +6 -8
- package/dist/core/common/helpers/service.helper.js +34 -12
- package/dist/core/common/helpers/service.helper.js.map +1 -1
- package/dist/core/common/inputs/combined-filter.input.d.ts +8 -2
- package/dist/core/common/inputs/combined-filter.input.js +16 -5
- package/dist/core/common/inputs/combined-filter.input.js.map +1 -1
- package/dist/core/common/inputs/core-input.input.d.ts +8 -0
- package/dist/core/common/inputs/core-input.input.js +15 -0
- package/dist/core/common/inputs/core-input.input.js.map +1 -0
- package/dist/core/common/inputs/filter.input.d.ts +7 -1
- package/dist/core/common/inputs/filter.input.js +14 -1
- package/dist/core/common/inputs/filter.input.js.map +1 -1
- package/dist/core/common/inputs/single-filter.input.d.ts +2 -1
- package/dist/core/common/inputs/single-filter.input.js +10 -1
- package/dist/core/common/inputs/single-filter.input.js.map +1 -1
- package/dist/core/common/inputs/sort.input.d.ts +2 -1
- package/dist/core/common/inputs/sort.input.js +7 -1
- package/dist/core/common/inputs/sort.input.js.map +1 -1
- package/dist/core/common/interceptors/check-response.interceptor.js +1 -1
- package/dist/core/common/interceptors/check-response.interceptor.js.map +1 -1
- package/dist/core/common/models/core-model.model.d.ts +6 -0
- package/dist/core/common/models/core-model.model.js +10 -3
- package/dist/core/common/models/core-model.model.js.map +1 -1
- package/dist/core/common/models/core-persistence.model.d.ts +5 -29
- package/dist/core/common/models/core-persistence.model.js +19 -41
- package/dist/core/common/models/core-persistence.model.js.map +1 -1
- package/dist/core/common/pipes/check-input.pipe.d.ts +2 -3
- package/dist/core/common/pipes/check-input.pipe.js +5 -20
- package/dist/core/common/pipes/check-input.pipe.js.map +1 -1
- package/dist/core/common/pipes/map-and-validate.pipe.d.ts +4 -0
- package/dist/core/common/pipes/map-and-validate.pipe.js +40 -0
- package/dist/core/common/pipes/map-and-validate.pipe.js.map +1 -0
- package/dist/core/common/types/plain-input.type.d.ts +3 -0
- package/dist/core/common/types/plain-input.type.js +3 -0
- package/dist/core/common/types/plain-input.type.js.map +1 -0
- package/dist/core/modules/auth/core-auth.model.d.ts +3 -1
- package/dist/core/modules/auth/core-auth.model.js +7 -1
- package/dist/core/modules/auth/core-auth.model.js.map +1 -1
- package/dist/core/modules/auth/core-auth.resolver.d.ts +1 -1
- package/dist/core/modules/user/core-user.model.d.ts +3 -0
- package/dist/core/modules/user/core-user.model.js +10 -4
- package/dist/core/modules/user/core-user.model.js.map +1 -1
- package/dist/core/modules/user/core-user.service.d.ts +9 -13
- package/dist/core/modules/user/core-user.service.js +38 -67
- package/dist/core/modules/user/core-user.service.js.map +1 -1
- package/dist/core/modules/user/inputs/core-user-create.input.js +4 -0
- package/dist/core/modules/user/inputs/core-user-create.input.js.map +1 -1
- package/dist/core/modules/user/inputs/core-user.input.d.ts +2 -1
- package/dist/core/modules/user/inputs/core-user.input.js +12 -2
- package/dist/core/modules/user/inputs/core-user.input.js.map +1 -1
- package/dist/core.module.js +2 -3
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/server/common/models/persistence.model.d.ts +1 -0
- package/dist/server/common/models/persistence.model.js +4 -0
- package/dist/server/common/models/persistence.model.js.map +1 -1
- package/dist/server/modules/auth/auth.model.d.ts +1 -0
- package/dist/server/modules/auth/auth.model.js +4 -0
- package/dist/server/modules/auth/auth.model.js.map +1 -1
- package/dist/server/modules/user/inputs/user-create.input.js.map +1 -1
- package/dist/server/modules/user/inputs/user.input.js.map +1 -1
- package/dist/server/modules/user/user.model.d.ts +3 -2
- package/dist/server/modules/user/user.model.js +9 -5
- package/dist/server/modules/user/user.model.js.map +1 -1
- package/dist/server/modules/user/user.resolver.d.ts +2 -2
- package/dist/server/modules/user/user.resolver.js +10 -12
- package/dist/server/modules/user/user.resolver.js.map +1 -1
- package/dist/server/modules/user/user.service.d.ts +7 -9
- package/dist/server/modules/user/user.service.js +12 -4
- package/dist/server/modules/user/user.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/core/common/args/filter.args.ts +22 -1
- package/src/core/common/args/pagination.args.ts +42 -7
- package/src/core/common/decorators/restricted.decorator.ts +24 -5
- package/src/core/common/helpers/context.helper.ts +14 -3
- package/src/core/common/helpers/filter.helper.ts +3 -3
- package/src/core/common/helpers/input.helper.ts +17 -11
- package/src/core/common/helpers/service.helper.ts +42 -19
- package/src/core/common/inputs/combined-filter.input.ts +30 -9
- package/src/core/common/inputs/core-input.input.ts +36 -0
- package/src/core/common/inputs/filter.input.ts +27 -3
- package/src/core/common/inputs/single-filter.input.ts +7 -6
- package/src/core/common/inputs/sort.input.ts +4 -3
- package/src/core/common/interceptors/check-response.interceptor.ts +2 -2
- package/src/core/common/models/core-model.model.ts +30 -1
- package/src/core/common/models/core-persistence.model.ts +33 -120
- package/src/core/common/pipes/check-input.pipe.ts +13 -33
- package/src/core/common/pipes/map-and-validate.pipe.ts +32 -0
- package/src/core/common/types/plain-input.type.ts +6 -0
- package/src/core/modules/auth/core-auth.model.ts +15 -1
- package/src/core/modules/auth/core-auth.resolver.ts +1 -1
- package/src/core/modules/user/core-user.model.ts +17 -4
- package/src/core/modules/user/core-user.service.ts +59 -115
- package/src/core/modules/user/inputs/core-user-create.input.ts +5 -1
- package/src/core/modules/user/inputs/core-user.input.ts +13 -8
- package/src/core.module.ts +11 -5
- package/src/index.ts +6 -2
- package/src/server/common/models/persistence.model.ts +13 -0
- package/src/server/modules/auth/auth.model.ts +13 -0
- package/src/server/modules/user/inputs/user-create.input.ts +4 -0
- package/src/server/modules/user/inputs/user.input.ts +4 -0
- package/src/server/modules/user/user.model.ts +18 -5
- package/src/server/modules/user/user.resolver.ts +15 -19
- 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<
|
|
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(
|
|
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:
|
|
63
|
+
pubSub.publish('userCreated', { userCreated: preparedUser });
|
|
70
64
|
|
|
71
65
|
// Return created user
|
|
72
|
-
return
|
|
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
|
-
|
|
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
|
|
82
|
+
await user.delete();
|
|
83
|
+
|
|
84
|
+
// Prepare output
|
|
85
|
+
const deletedUser = await this.prepareOutput(this.model.map(user), args[0]);
|
|
89
86
|
|
|
90
|
-
|
|
87
|
+
// Inform subscriber
|
|
88
|
+
pubSub.publish('userDeleted', { userDeleted: deletedUser });
|
|
91
89
|
|
|
92
90
|
// Return deleted user
|
|
93
|
-
return
|
|
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.
|
|
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<
|
|
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
|
-
|
|
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
|
-
|
|
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<
|
|
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
|
-
|
|
163
|
-
await
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
// Update user
|
|
167
|
+
await Object.assign(user, {
|
|
168
|
+
password: await bcrypt.hash(newPassword, 10),
|
|
169
|
+
passwordResetToken: null,
|
|
170
|
+
}).save();
|
|
166
171
|
|
|
167
|
-
|
|
172
|
+
// Return prepared user with changed password
|
|
173
|
+
return this.prepareOutput(this.model.map(user), args[0]);
|
|
168
174
|
}
|
|
169
175
|
|
|
170
176
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* @param email
|
|
177
|
+
* Set password rest token for email
|
|
174
178
|
*/
|
|
175
|
-
async
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
}
|
package/src/core.module.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { DynamicModule, Global, Module
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
98
|
+
return await this.usersService.update(id, input, user);
|
|
103
99
|
}
|
|
104
100
|
|
|
105
101
|
/**
|