@lenne.tech/nest-server 8.5.0 → 8.6.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/helpers/db.helper.d.ts +1 -1
- package/dist/core/common/helpers/db.helper.js +29 -5
- package/dist/core/common/helpers/db.helper.js.map +1 -1
- package/dist/core/common/helpers/service.helper.d.ts +11 -0
- package/dist/core/common/helpers/service.helper.js +24 -1
- package/dist/core/common/helpers/service.helper.js.map +1 -1
- package/dist/core/common/interfaces/prepare-input-options.interface.d.ts +8 -0
- package/dist/core/common/interfaces/prepare-input-options.interface.js +3 -0
- package/dist/core/common/interfaces/prepare-input-options.interface.js.map +1 -0
- package/dist/core/common/interfaces/prepare-output-options.interface.d.ts +7 -0
- package/dist/core/common/interfaces/prepare-output-options.interface.js +3 -0
- package/dist/core/common/interfaces/prepare-output-options.interface.js.map +1 -0
- package/dist/core/common/interfaces/service-options.interface.d.ts +4 -14
- package/dist/core/common/services/module.service.d.ts +1 -1
- package/dist/core/modules/auth/inputs/core-auth-sign-in.input.d.ts +5 -0
- package/dist/core/modules/auth/inputs/core-auth-sign-in.input.js +34 -0
- package/dist/core/modules/auth/inputs/core-auth-sign-in.input.js.map +1 -0
- package/dist/core/modules/auth/inputs/core-auth-sign-up.input.d.ts +5 -0
- package/dist/core/modules/auth/inputs/core-auth-sign-up.input.js +34 -0
- package/dist/core/modules/auth/inputs/core-auth-sign-up.input.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/server/modules/auth/auth.model.js +2 -2
- package/dist/server/modules/auth/auth.model.js.map +1 -1
- package/dist/server/modules/auth/auth.module.js +7 -2
- package/dist/server/modules/auth/auth.module.js.map +1 -1
- package/dist/server/modules/auth/auth.resolver.d.ts +8 -3
- package/dist/server/modules/auth/auth.resolver.js +33 -10
- package/dist/server/modules/auth/auth.resolver.js.map +1 -1
- package/dist/server/modules/auth/auth.service.d.ts +15 -0
- package/dist/server/modules/auth/auth.service.js +71 -0
- package/dist/server/modules/auth/auth.service.js.map +1 -0
- package/dist/server/modules/auth/inputs/auth-sign-in.input.d.ts +3 -0
- package/dist/server/modules/auth/inputs/auth-sign-in.input.js +18 -0
- package/dist/server/modules/auth/inputs/auth-sign-in.input.js.map +1 -0
- package/dist/server/modules/auth/inputs/auth-sign-up.input.d.ts +5 -0
- package/dist/server/modules/auth/inputs/auth-sign-up.input.js +34 -0
- package/dist/server/modules/auth/inputs/auth-sign-up.input.js.map +1 -0
- package/dist/server/modules/user/user.resolver.js +12 -11
- package/dist/server/modules/user/user.resolver.js.map +1 -1
- package/dist/server/modules/user/user.service.js +0 -4
- package/dist/server/modules/user/user.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/core/common/helpers/db.helper.ts +27 -5
- package/src/core/common/helpers/service.helper.ts +55 -0
- package/src/core/common/interfaces/prepare-input-options.interface.ts +11 -0
- package/src/core/common/interfaces/prepare-output-options.interface.ts +10 -0
- package/src/core/common/interfaces/service-options.interface.ts +4 -14
- package/src/core/modules/auth/inputs/core-auth-sign-in.input.ts +18 -0
- package/src/core/modules/auth/inputs/core-auth-sign-up.input.ts +18 -0
- package/src/index.ts +2 -0
- package/src/server/modules/auth/auth.model.ts +5 -5
- package/src/server/modules/auth/auth.module.ts +13 -2
- package/src/server/modules/auth/auth.resolver.ts +30 -12
- package/src/server/modules/auth/auth.service.ts +84 -0
- package/src/server/modules/auth/inputs/auth-sign-in.input.ts +10 -0
- package/src/server/modules/auth/inputs/auth-sign-up.input.ts +18 -0
- package/src/server/modules/user/user.resolver.ts +12 -11
- package/src/server/modules/user/user.service.ts +0 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.6.0",
|
|
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",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"prettier": "2.6.2",
|
|
113
113
|
"pretty-quick": "3.1.3",
|
|
114
114
|
"supertest": "6.2.3",
|
|
115
|
-
"ts-jest": "28.0.
|
|
115
|
+
"ts-jest": "28.0.2",
|
|
116
116
|
"ts-morph": "14.0.0",
|
|
117
117
|
"ts-node": "10.7.0",
|
|
118
118
|
"tsconfig-paths": "4.0.0",
|
|
@@ -275,19 +275,41 @@ export function getElementsViaIds<T = any>(
|
|
|
275
275
|
/**
|
|
276
276
|
* Get populate options from GraphQL resolve info
|
|
277
277
|
*/
|
|
278
|
-
export function getPopulateOptions(info: GraphQLResolveInfo,
|
|
278
|
+
export function getPopulateOptions(info: GraphQLResolveInfo, dotSeparatedSelect?: string): PopulateOptions[] {
|
|
279
279
|
const result = [];
|
|
280
280
|
|
|
281
281
|
if (!info?.fieldNodes?.length) {
|
|
282
282
|
return result;
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
285
|
+
if (dotSeparatedSelect?.length) {
|
|
286
|
+
for (const fieldNode of info.fieldNodes) {
|
|
287
|
+
const selects = dotSeparatedSelect.split('.');
|
|
288
|
+
const fieldNodeName = selects.shift();
|
|
289
|
+
if (fieldNode?.name?.value === fieldNodeName && fieldNode?.selectionSet?.selections?.length) {
|
|
290
|
+
if (!selects.length) {
|
|
291
|
+
return getPopulatOptionsFromSelections(fieldNode.selectionSet.selections);
|
|
292
|
+
} else {
|
|
293
|
+
let selections = fieldNode.selectionSet.selections;
|
|
294
|
+
do {
|
|
295
|
+
if (!selections?.length) {
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
const select = selects.shift();
|
|
299
|
+
for (const selection of selections) {
|
|
300
|
+
if ((selection as FieldNode)?.name?.value === select) {
|
|
301
|
+
selections = (selection as FieldNode)?.selectionSet?.selections;
|
|
302
|
+
if (!selects.length) {
|
|
303
|
+
return getPopulatOptionsFromSelections(selections);
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} while (selects.length);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
288
311
|
}
|
|
289
312
|
}
|
|
290
|
-
|
|
291
313
|
return result;
|
|
292
314
|
}
|
|
293
315
|
|
|
@@ -3,6 +3,10 @@ import * as bcrypt from 'bcrypt';
|
|
|
3
3
|
import { plainToInstance } from 'class-transformer';
|
|
4
4
|
import * as _ from 'lodash';
|
|
5
5
|
import { RoleEnum } from '../enums/role.enum';
|
|
6
|
+
import { PrepareInputOptions } from '../interfaces/prepare-input-options.interface';
|
|
7
|
+
import { PrepareOutputOptions } from '../interfaces/prepare-output-options.interface';
|
|
8
|
+
import { ResolveSelector } from '../interfaces/resolve-selector.interface';
|
|
9
|
+
import { ServiceOptions } from '../interfaces/service-options.interface';
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* Helper class for services
|
|
@@ -213,3 +217,54 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
|
|
|
213
217
|
// Return prepared output
|
|
214
218
|
return output;
|
|
215
219
|
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Prepare service options
|
|
223
|
+
*/
|
|
224
|
+
export function prepareServiceOptions(
|
|
225
|
+
serviceOptions: ServiceOptions,
|
|
226
|
+
options?: {
|
|
227
|
+
clone?: boolean;
|
|
228
|
+
inputType?: any;
|
|
229
|
+
outputType?: any;
|
|
230
|
+
subFieldSelection?: string;
|
|
231
|
+
prepareInput?: PrepareInputOptions;
|
|
232
|
+
prepareOutput?: PrepareOutputOptions;
|
|
233
|
+
}
|
|
234
|
+
): ServiceOptions {
|
|
235
|
+
// Set default values
|
|
236
|
+
const config = {
|
|
237
|
+
clone: true,
|
|
238
|
+
...options,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Clone
|
|
242
|
+
if (serviceOptions && config.clone) {
|
|
243
|
+
serviceOptions = _.cloneDeep(serviceOptions);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Init if not exists
|
|
247
|
+
serviceOptions = serviceOptions || {};
|
|
248
|
+
|
|
249
|
+
// Set properties
|
|
250
|
+
serviceOptions.inputType = serviceOptions.inputType || options?.inputType;
|
|
251
|
+
serviceOptions.outputType = serviceOptions.outputType || options?.outputType;
|
|
252
|
+
|
|
253
|
+
// Set properties which can deactivate handling when falsy
|
|
254
|
+
if (!serviceOptions.prepareInput && 'prepareInput' in config) {
|
|
255
|
+
serviceOptions.prepareInput = config.prepareInput;
|
|
256
|
+
}
|
|
257
|
+
if (!serviceOptions.prepareOutput && 'prepareOutput' in config) {
|
|
258
|
+
serviceOptions.prepareOutput = config.prepareOutput;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Set subfield selection
|
|
262
|
+
if (config.subFieldSelection) {
|
|
263
|
+
if ((serviceOptions.fieldSelection as ResolveSelector)?.select) {
|
|
264
|
+
(serviceOptions.fieldSelection as ResolveSelector).select += '.' + config.subFieldSelection;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Return (cloned and) prepared service options
|
|
269
|
+
return serviceOptions;
|
|
270
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Model } from 'mongoose';
|
|
2
2
|
import { FieldSelection } from '../types/field-selection.type';
|
|
3
|
+
import { PrepareInputOptions } from './prepare-input-options.interface';
|
|
4
|
+
import { PrepareOutputOptions } from './prepare-output-options.interface';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* General service options
|
|
@@ -40,24 +42,12 @@ export interface ServiceOptions {
|
|
|
40
42
|
// Prepare input configuration:
|
|
41
43
|
// If {} or not set, then the prepareInput function will run with defaults
|
|
42
44
|
// If falsy, then the prepareInput function is not executed
|
|
43
|
-
prepareInput?:
|
|
44
|
-
[key: string]: any;
|
|
45
|
-
create?: boolean;
|
|
46
|
-
clone?: boolean;
|
|
47
|
-
getNewArray?: boolean;
|
|
48
|
-
removeUndefined?: boolean;
|
|
49
|
-
};
|
|
45
|
+
prepareInput?: PrepareInputOptions;
|
|
50
46
|
|
|
51
47
|
// Prepare output configuration:
|
|
52
48
|
// If {} or not set, then the prepareInput function will run with defaults
|
|
53
49
|
// If falsy, then the prepareInput function is not executed
|
|
54
|
-
prepareOutput?:
|
|
55
|
-
[key: string]: any;
|
|
56
|
-
clone?: boolean;
|
|
57
|
-
getNewArray?: boolean;
|
|
58
|
-
removeUndefined?: boolean;
|
|
59
|
-
targetModel?: new (...args: any[]) => any;
|
|
60
|
-
};
|
|
50
|
+
prepareOutput?: PrepareOutputOptions;
|
|
61
51
|
|
|
62
52
|
// Whether to publish action via GraphQL subscription
|
|
63
53
|
pubSub?: boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Field, InputType } from '@nestjs/graphql';
|
|
2
|
+
import { CoreInput } from '../../../common/inputs/core-input.input';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SignIn input
|
|
6
|
+
*/
|
|
7
|
+
@InputType({ description: 'Sign-in input' })
|
|
8
|
+
export class CoreAuthSignInInput extends CoreInput {
|
|
9
|
+
// ===================================================================================================================
|
|
10
|
+
// Properties
|
|
11
|
+
// ===================================================================================================================
|
|
12
|
+
|
|
13
|
+
@Field({ description: 'Email', nullable: false })
|
|
14
|
+
email: string = undefined;
|
|
15
|
+
|
|
16
|
+
@Field({ description: 'Password', nullable: false })
|
|
17
|
+
password: string = undefined;
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Field, InputType } from '@nestjs/graphql';
|
|
2
|
+
import { CoreInput } from '../../../common/inputs/core-input.input';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SignUp input
|
|
6
|
+
*/
|
|
7
|
+
@InputType({ description: 'Sign-up input' })
|
|
8
|
+
export class CoreAuthSignUpInput extends CoreInput {
|
|
9
|
+
// ===================================================================================================================
|
|
10
|
+
// Properties
|
|
11
|
+
// ===================================================================================================================
|
|
12
|
+
|
|
13
|
+
@Field({ description: 'Email', nullable: false })
|
|
14
|
+
email: string = undefined;
|
|
15
|
+
|
|
16
|
+
@Field({ description: 'Password', nullable: false })
|
|
17
|
+
password: string = undefined;
|
|
18
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -35,6 +35,8 @@ export * from './core/common/inputs/sort.input';
|
|
|
35
35
|
export * from './core/common/interceptors/check-response.interceptor';
|
|
36
36
|
export * from './core/common/interfaces/core-persistence-model.interface';
|
|
37
37
|
export * from './core/common/interfaces/mailjet-options.interface';
|
|
38
|
+
export * from './core/common/interfaces/prepare-input-options.interface';
|
|
39
|
+
export * from './core/common/interfaces/prepare-output-options.interface';
|
|
38
40
|
export * from './core/common/interfaces/resolve-selector.interface';
|
|
39
41
|
export * from './core/common/interfaces/server-options.interface';
|
|
40
42
|
export * from './core/common/interfaces/service-options.interface';
|
|
@@ -3,22 +3,22 @@ import { CoreAuthModel } from '../../../core/modules/auth/core-auth.model';
|
|
|
3
3
|
import { User } from '../user/user.model';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Authentication data
|
|
7
7
|
*/
|
|
8
|
-
@ObjectType({ description: '
|
|
8
|
+
@ObjectType({ description: 'Authentication data' })
|
|
9
9
|
export class Auth extends CoreAuthModel {
|
|
10
10
|
// ===================================================================================================================
|
|
11
11
|
// Properties
|
|
12
12
|
// ===================================================================================================================
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Signed
|
|
15
|
+
* Signed-in user
|
|
16
16
|
*/
|
|
17
|
-
@Field((
|
|
17
|
+
@Field(() => User, { description: 'User who signed in' })
|
|
18
18
|
user: User = undefined;
|
|
19
19
|
|
|
20
20
|
// ===================================================================================================================
|
|
21
|
-
//
|
|
21
|
+
// Methods
|
|
22
22
|
// ===================================================================================================================
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { DynamicModule, Module } from '@nestjs/common';
|
|
2
2
|
import { JwtModuleOptions } from '@nestjs/jwt';
|
|
3
|
+
import { EmailService } from '../../../core/common/services/email.service';
|
|
3
4
|
import { CoreAuthModule } from '../../../core/modules/auth/core-auth.module';
|
|
4
5
|
import { UserModule } from '../user/user.module';
|
|
5
6
|
import { UserService } from '../user/user.service';
|
|
6
7
|
import { AuthResolver } from './auth.resolver';
|
|
8
|
+
import { AuthService } from './auth.service';
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* CoreAuthModule to handle user authentication
|
|
@@ -17,8 +19,17 @@ export class AuthModule {
|
|
|
17
19
|
static forRoot(options: JwtModuleOptions): DynamicModule {
|
|
18
20
|
return {
|
|
19
21
|
module: AuthModule,
|
|
20
|
-
imports: [
|
|
21
|
-
|
|
22
|
+
imports: [
|
|
23
|
+
CoreAuthModule.forRoot(UserModule, UserService, {
|
|
24
|
+
...options,
|
|
25
|
+
...{
|
|
26
|
+
// imports: [], // Integrate additional Services here to resolve dependencies
|
|
27
|
+
// providers: [] // Integrate additional Providers here to resolve dependencies
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
EmailService,
|
|
31
|
+
],
|
|
32
|
+
providers: [AuthResolver, AuthService],
|
|
22
33
|
exports: [AuthResolver, CoreAuthModule],
|
|
23
34
|
};
|
|
24
35
|
}
|
|
@@ -1,22 +1,40 @@
|
|
|
1
|
-
import { Args, Info, Query, Resolver } from '@nestjs/graphql';
|
|
1
|
+
import { Args, Info, Mutation, Query, Resolver } from '@nestjs/graphql';
|
|
2
2
|
import { GraphQLResolveInfo } from 'graphql';
|
|
3
|
-
import { CoreAuthResolver } from '../../../core/modules/auth/core-auth.resolver';
|
|
4
3
|
import { Auth } from './auth.model';
|
|
4
|
+
import { AuthService } from './auth.service';
|
|
5
|
+
import { AuthSignInInput } from './inputs/auth-sign-in.input';
|
|
6
|
+
import { AuthSignUpInput } from './inputs/auth-sign-up.input';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Authentication resolver for the sign in
|
|
8
10
|
*/
|
|
9
|
-
@Resolver((
|
|
10
|
-
export class AuthResolver
|
|
11
|
+
@Resolver(() => Auth)
|
|
12
|
+
export class AuthResolver {
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
14
|
+
* Integrate services
|
|
13
15
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
)
|
|
20
|
-
|
|
16
|
+
constructor(private readonly authService: AuthService) {}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* SignIn for User
|
|
20
|
+
*/
|
|
21
|
+
@Query(() => Auth, { description: 'Sign in and get JWT token' })
|
|
22
|
+
async signIn(@Info() info: GraphQLResolveInfo, @Args('input') input: AuthSignInInput): Promise<Auth> {
|
|
23
|
+
return this.authService.signIn(input, {
|
|
24
|
+
fieldSelection: { info, select: 'signIn' },
|
|
25
|
+
inputType: AuthSignInInput,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Sign up for user
|
|
31
|
+
*/
|
|
32
|
+
@Mutation(() => Auth, {
|
|
33
|
+
description: 'Sign up user and get JWT token',
|
|
34
|
+
})
|
|
35
|
+
async signUp(@Info() info: GraphQLResolveInfo, @Args('input') input: AuthSignUpInput): Promise<Auth> {
|
|
36
|
+
return this.authService.signUp(input, {
|
|
37
|
+
fieldSelection: { info, select: 'signUp' },
|
|
38
|
+
});
|
|
21
39
|
}
|
|
22
40
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
2
|
+
import { JwtService } from '@nestjs/jwt';
|
|
3
|
+
import * as bcrypt from 'bcrypt';
|
|
4
|
+
import envConfig from '../../../config.env';
|
|
5
|
+
import { prepareServiceOptions } from '../../../core/common/helpers/service.helper';
|
|
6
|
+
import { ResolveSelector } from '../../../core/common/interfaces/resolve-selector.interface';
|
|
7
|
+
import { ServiceOptions } from '../../../core/common/interfaces/service-options.interface';
|
|
8
|
+
import { EmailService } from '../../../core/common/services/email.service';
|
|
9
|
+
import { JwtPayload } from '../../../core/modules/auth/interfaces/jwt-payload.interface';
|
|
10
|
+
import { UserService } from '../user/user.service';
|
|
11
|
+
import { Auth } from './auth.model';
|
|
12
|
+
import { AuthSignInInput } from './inputs/auth-sign-in.input';
|
|
13
|
+
import { AuthSignUpInput } from './inputs/auth-sign-up.input';
|
|
14
|
+
|
|
15
|
+
@Injectable()
|
|
16
|
+
export class AuthService {
|
|
17
|
+
constructor(
|
|
18
|
+
protected readonly jwtService: JwtService,
|
|
19
|
+
protected readonly emailService: EmailService,
|
|
20
|
+
protected readonly userService: UserService
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Sign in for user
|
|
25
|
+
*/
|
|
26
|
+
async signIn(input: AuthSignInInput, serviceOptions?: ServiceOptions): Promise<Auth> {
|
|
27
|
+
// Prepare service options
|
|
28
|
+
const serviceOptionsForUserService = prepareServiceOptions(serviceOptions, {
|
|
29
|
+
// We need password, so we can't use prepare output handling and have to deactivate it
|
|
30
|
+
prepareOutput: null,
|
|
31
|
+
|
|
32
|
+
// Select user field for automatic populate handling via user service
|
|
33
|
+
subFieldSelection: 'user',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Get and check user
|
|
37
|
+
const user = await this.userService.getViaEmail(input.email, serviceOptionsForUserService);
|
|
38
|
+
if (!user) {
|
|
39
|
+
throw new UnauthorizedException();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check password
|
|
43
|
+
if (!(await bcrypt.compare(input.password, user.password))) {
|
|
44
|
+
throw new UnauthorizedException();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Create JWT and return sign-in data
|
|
48
|
+
const payload: JwtPayload = { email: user.email };
|
|
49
|
+
return Auth.map({
|
|
50
|
+
token: this.jwtService.sign(payload),
|
|
51
|
+
user,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Register a new user Account
|
|
57
|
+
*/
|
|
58
|
+
async signUp(input: AuthSignUpInput, serviceOptions?: ServiceOptions): Promise<Auth> {
|
|
59
|
+
// Prepare service options
|
|
60
|
+
const serviceOptionsForUserService = prepareServiceOptions(serviceOptions, {
|
|
61
|
+
// Select user field for automatic populate handling via user service
|
|
62
|
+
subFieldSelection: 'user',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Get and check user
|
|
66
|
+
const user = await this.userService.create(input, serviceOptionsForUserService);
|
|
67
|
+
if (!user) {
|
|
68
|
+
throw Error('Email Address already in use');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Send email
|
|
72
|
+
await this.emailService.sendMail(user.email, 'Welcome', {
|
|
73
|
+
htmlTemplate: 'welcome',
|
|
74
|
+
templateData: { name: user.username, link: envConfig.email.verificationLink + '/' + user.verificationToken },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Create JWT and return sign-in data
|
|
78
|
+
const payload: JwtPayload = { email: user.email };
|
|
79
|
+
return Auth.map({
|
|
80
|
+
token: this.jwtService.sign(payload),
|
|
81
|
+
user: user,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { InputType } from '@nestjs/graphql';
|
|
2
|
+
import { CoreAuthSignInInput } from '../../../../core/modules/auth/inputs/core-auth-sign-in.input';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SignIn input
|
|
6
|
+
*/
|
|
7
|
+
@InputType({ description: 'Description for AuthSignInInput' })
|
|
8
|
+
export class AuthSignInInput extends CoreAuthSignInInput {
|
|
9
|
+
// Extend UserInput here
|
|
10
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Field, InputType } from '@nestjs/graphql';
|
|
2
|
+
import { CoreAuthSignUpInput } from '../../../../core/modules/auth/inputs/core-auth-sign-up.input';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SignUp input
|
|
6
|
+
*/
|
|
7
|
+
@InputType({ description: 'Description for AuthSignUpInput' })
|
|
8
|
+
export class AuthSignUpInput extends CoreAuthSignUpInput {
|
|
9
|
+
// ===================================================================================================================
|
|
10
|
+
// Properties
|
|
11
|
+
// ===================================================================================================================
|
|
12
|
+
|
|
13
|
+
@Field({ description: 'firstName', nullable: true })
|
|
14
|
+
firstName: string = undefined;
|
|
15
|
+
|
|
16
|
+
@Field({ description: 'lastName', nullable: true })
|
|
17
|
+
lastName: string = undefined;
|
|
18
|
+
}
|
|
@@ -14,7 +14,7 @@ import { UserService } from './user.service';
|
|
|
14
14
|
/**
|
|
15
15
|
* Resolver to process with user data
|
|
16
16
|
*/
|
|
17
|
-
@Resolver((
|
|
17
|
+
@Resolver(() => User)
|
|
18
18
|
export class UserResolver {
|
|
19
19
|
/**
|
|
20
20
|
* Import services
|
|
@@ -29,7 +29,7 @@ export class UserResolver {
|
|
|
29
29
|
* Get users (via filter)
|
|
30
30
|
*/
|
|
31
31
|
@Roles(RoleEnum.ADMIN)
|
|
32
|
-
@Query((
|
|
32
|
+
@Query(() => [User], { description: 'Find users (via filter)' })
|
|
33
33
|
async findUsers(@Info() info: GraphQLResolveInfo, @Args() args?: FilterArgs) {
|
|
34
34
|
return await this.userService.find(args, {
|
|
35
35
|
fieldSelection: { info, select: 'findUsers' },
|
|
@@ -41,7 +41,7 @@ export class UserResolver {
|
|
|
41
41
|
* Get user via ID
|
|
42
42
|
*/
|
|
43
43
|
@Roles(RoleEnum.S_USER)
|
|
44
|
-
@Query((
|
|
44
|
+
@Query(() => User, { description: 'Get user with specified ID' })
|
|
45
45
|
async getUser(@Args('id') id: string, @Info() info: GraphQLResolveInfo, @GraphQLUser() user: User): Promise<User> {
|
|
46
46
|
return await this.userService.get(id, {
|
|
47
47
|
currentUser: user,
|
|
@@ -53,7 +53,7 @@ export class UserResolver {
|
|
|
53
53
|
/**
|
|
54
54
|
* Get verified state of user with token
|
|
55
55
|
*/
|
|
56
|
-
@Query((
|
|
56
|
+
@Query(() => Boolean, { description: 'Get verified state of user with token' })
|
|
57
57
|
async getVerifiedState(@Args('token') token: string) {
|
|
58
58
|
return await this.userService.getVerifiedState(token);
|
|
59
59
|
}
|
|
@@ -61,7 +61,7 @@ export class UserResolver {
|
|
|
61
61
|
/**
|
|
62
62
|
* Request new password for user with email
|
|
63
63
|
*/
|
|
64
|
-
@Query((
|
|
64
|
+
@Query(() => Boolean, { description: 'Request new password for user with email' })
|
|
65
65
|
async requestPasswordResetMail(@Args('email') email: string): Promise<boolean> {
|
|
66
66
|
return !!(await this.userService.sendPasswordResetMail(email));
|
|
67
67
|
}
|
|
@@ -73,7 +73,8 @@ export class UserResolver {
|
|
|
73
73
|
/**
|
|
74
74
|
* Create new user
|
|
75
75
|
*/
|
|
76
|
-
@
|
|
76
|
+
@Roles(RoleEnum.ADMIN)
|
|
77
|
+
@Mutation(() => User, { description: 'Create a new user' })
|
|
77
78
|
async createUser(
|
|
78
79
|
@Args('input') input: UserCreateInput,
|
|
79
80
|
@GraphQLUser() user: User,
|
|
@@ -90,7 +91,7 @@ export class UserResolver {
|
|
|
90
91
|
* Delete existing user
|
|
91
92
|
*/
|
|
92
93
|
@Roles(RoleEnum.S_USER)
|
|
93
|
-
@Mutation((
|
|
94
|
+
@Mutation(() => User, { description: 'Delete existing user' })
|
|
94
95
|
async deleteUser(@Args('id') id: string, @Info() info: GraphQLResolveInfo, @GraphQLUser() user: User): Promise<User> {
|
|
95
96
|
return await this.userService.delete(id, {
|
|
96
97
|
currentUser: user,
|
|
@@ -102,7 +103,7 @@ export class UserResolver {
|
|
|
102
103
|
/**
|
|
103
104
|
* Set new password for user with token
|
|
104
105
|
*/
|
|
105
|
-
@Mutation((
|
|
106
|
+
@Mutation(() => Boolean, { description: 'Set new password for user with token' })
|
|
106
107
|
async resetPassword(@Args('token') token: string, @Args('password') password: string): Promise<boolean> {
|
|
107
108
|
return !!(await this.userService.resetPassword(token, password));
|
|
108
109
|
}
|
|
@@ -111,7 +112,7 @@ export class UserResolver {
|
|
|
111
112
|
* Update existing user
|
|
112
113
|
*/
|
|
113
114
|
@Roles(RoleEnum.S_USER)
|
|
114
|
-
@Mutation((
|
|
115
|
+
@Mutation(() => User, { description: 'Update existing user' })
|
|
115
116
|
async updateUser(
|
|
116
117
|
@Args('input') input: UserInput,
|
|
117
118
|
@Args('id') id: string,
|
|
@@ -130,7 +131,7 @@ export class UserResolver {
|
|
|
130
131
|
/**
|
|
131
132
|
* Verify user with email
|
|
132
133
|
*/
|
|
133
|
-
@Mutation((
|
|
134
|
+
@Mutation(() => Boolean, { description: 'Verify user with email' })
|
|
134
135
|
async verifyUser(@Args('token') token: string): Promise<boolean> {
|
|
135
136
|
return !!(await this.userService.verify(token));
|
|
136
137
|
}
|
|
@@ -142,7 +143,7 @@ export class UserResolver {
|
|
|
142
143
|
/**
|
|
143
144
|
* Subscription for created user
|
|
144
145
|
*/
|
|
145
|
-
@Subscription((
|
|
146
|
+
@Subscription(() => User, {
|
|
146
147
|
filter(this: UserResolver, payload, variables, context) {
|
|
147
148
|
return context.user.roles.include(RoleEnum.ADMIN);
|
|
148
149
|
},
|
|
@@ -58,20 +58,12 @@ export class UserService extends CoreUserService<User, UserInput, UserCreateInpu
|
|
|
58
58
|
await this.pubSub.publish('userCreated', User.map(user));
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
// Send email
|
|
62
|
-
await this.emailService.sendMail(user.email, 'Welcome', {
|
|
63
|
-
htmlTemplate: 'welcome',
|
|
64
|
-
templateData: { name: user.username, link: envConfig.email.verificationLink + '/' + user.verificationToken },
|
|
65
|
-
});
|
|
66
|
-
|
|
67
61
|
// Return created user
|
|
68
62
|
return user;
|
|
69
63
|
}
|
|
70
64
|
|
|
71
65
|
/**
|
|
72
66
|
* Request password reset mail
|
|
73
|
-
*
|
|
74
|
-
* @param email
|
|
75
67
|
*/
|
|
76
68
|
async sendPasswordResetMail(email: string, serviceOptions?: ServiceOptions): Promise<User> {
|
|
77
69
|
// Set password reset token
|