@lenne.tech/nest-server 8.6.3 → 8.6.6

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 (72) hide show
  1. package/dist/config.env.js +4 -2
  2. package/dist/config.env.js.map +1 -1
  3. package/dist/core/common/decorators/restricted.decorator.js +4 -4
  4. package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
  5. package/dist/core/common/helpers/file.helper.js +4 -7
  6. package/dist/core/common/helpers/file.helper.js.map +1 -1
  7. package/dist/core/common/helpers/filter.helper.d.ts +7 -0
  8. package/dist/core/common/helpers/filter.helper.js +38 -1
  9. package/dist/core/common/helpers/filter.helper.js.map +1 -1
  10. package/dist/core/common/helpers/input.helper.d.ts +6 -0
  11. package/dist/core/common/helpers/input.helper.js +28 -6
  12. package/dist/core/common/helpers/input.helper.js.map +1 -1
  13. package/dist/core/common/helpers/service.helper.js +3 -5
  14. package/dist/core/common/helpers/service.helper.js.map +1 -1
  15. package/dist/core/common/inputs/filter.input.js.map +1 -1
  16. package/dist/core/common/interfaces/server-options.interface.d.ts +16 -13
  17. package/dist/core/common/models/core-model.model.js +4 -1
  18. package/dist/core/common/models/core-model.model.js.map +1 -1
  19. package/dist/core/common/services/crud.service.js +9 -4
  20. package/dist/core/common/services/crud.service.js.map +1 -1
  21. package/dist/core/common/services/module.service.js +1 -1
  22. package/dist/core/common/services/module.service.js.map +1 -1
  23. package/dist/core/modules/auth/guards/roles.guard.js +2 -1
  24. package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
  25. package/dist/core/modules/user/core-user.service.js +9 -13
  26. package/dist/core/modules/user/core-user.service.js.map +1 -1
  27. package/dist/core.module.d.ts +3 -2
  28. package/dist/core.module.js +26 -4
  29. package/dist/core.module.js.map +1 -1
  30. package/dist/main.js +13 -0
  31. package/dist/main.js.map +1 -1
  32. package/dist/server/modules/auth/auth.module.js +1 -2
  33. package/dist/server/modules/auth/auth.module.js.map +1 -1
  34. package/dist/server/modules/file/file.controller.js +1 -1
  35. package/dist/server/modules/file/file.controller.js.map +1 -1
  36. package/dist/server/modules/file/file.resolver.d.ts +5 -0
  37. package/dist/server/modules/file/file.resolver.js +51 -0
  38. package/dist/server/modules/file/file.resolver.js.map +1 -0
  39. package/dist/server/modules/user/user.model.d.ts +1 -1
  40. package/dist/server/modules/user/user.resolver.js +2 -1
  41. package/dist/server/modules/user/user.resolver.js.map +1 -1
  42. package/dist/server/modules/user/user.service.js +1 -1
  43. package/dist/server/modules/user/user.service.js.map +1 -1
  44. package/dist/server/server.module.js +3 -1
  45. package/dist/server/server.module.js.map +1 -1
  46. package/dist/test/test.helper.d.ts +4 -1
  47. package/dist/test/test.helper.js +51 -25
  48. package/dist/test/test.helper.js.map +1 -1
  49. package/dist/tsconfig.build.tsbuildinfo +1 -1
  50. package/package.json +33 -28
  51. package/src/config.env.ts +4 -2
  52. package/src/core/common/decorators/restricted.decorator.ts +1 -2
  53. package/src/core/common/helpers/file.helper.ts +9 -11
  54. package/src/core/common/helpers/filter.helper.ts +66 -1
  55. package/src/core/common/helpers/input.helper.ts +61 -2
  56. package/src/core/common/helpers/service.helper.ts +2 -6
  57. package/src/core/common/inputs/filter.input.ts +0 -1
  58. package/src/core/common/interfaces/server-options.interface.ts +68 -52
  59. package/src/core/common/models/core-model.model.ts +4 -1
  60. package/src/core/common/services/crud.service.ts +7 -4
  61. package/src/core/common/services/module.service.ts +0 -1
  62. package/src/core/modules/auth/guards/roles.guard.ts +1 -1
  63. package/src/core/modules/user/core-user.service.ts +13 -22
  64. package/src/core.module.ts +38 -6
  65. package/src/main.ts +16 -0
  66. package/src/server/modules/auth/auth.module.ts +1 -2
  67. package/src/server/modules/file/file.controller.ts +1 -1
  68. package/src/server/modules/file/file.resolver.ts +55 -0
  69. package/src/server/modules/user/user.resolver.ts +1 -1
  70. package/src/server/modules/user/user.service.ts +1 -1
  71. package/src/server/server.module.ts +5 -1
  72. package/src/test/test.helper.ts +92 -34
@@ -38,7 +38,7 @@ export class RolesGuard extends AuthGuard('jwt') {
38
38
  }
39
39
 
40
40
  // Check user and user roles
41
- if (!user || !user.hasRole(roles)) {
41
+ if (!user?.hasRole?.(roles)) {
42
42
  // Get args
43
43
  const args: any = GqlExecutionContext.create(context).getArgs();
44
44
 
@@ -3,6 +3,7 @@ import * as bcrypt from 'bcrypt';
3
3
  import * as crypto from 'crypto';
4
4
  import { Document, Model } from 'mongoose';
5
5
  import { merge } from '../../common/helpers/config.helper';
6
+ import { assignPlain } from '../../common/helpers/input.helper';
6
7
  import { ServiceOptions } from '../../common/interfaces/service-options.interface';
7
8
  import { CrudService } from '../../common/services/crud.service';
8
9
  import { EmailService } from '../../common/services/email.service';
@@ -39,9 +40,12 @@ export abstract class CoreUserService<
39
40
  return this.process(
40
41
  async (data) => {
41
42
  // Create user with verification token
43
+ const currentUserId = serviceOptions?.currentUser?._id;
42
44
  const createdUser = new this.mainDbModel({
43
45
  ...data.input,
44
46
  verificationToken: crypto.randomBytes(32).toString('hex'),
47
+ createdBy: currentUserId,
48
+ updatedBy: currentUserId,
45
49
  });
46
50
 
47
51
  // Distinguish between different error messages when saving
@@ -105,21 +109,15 @@ export abstract class CoreUserService<
105
109
  }
106
110
  return this.process(
107
111
  async () => {
108
- // Update user
109
- await Object.assign(dbObject, {
110
- verified: true,
111
- verificationToken: null,
112
- }).save();
113
-
114
- // Return prepared user
115
- return dbObject;
112
+ // Update and return user
113
+ return await assignPlain(dbObject, { verified: true, verificationToken: null }).save();
116
114
  },
117
115
  { dbObject, serviceOptions }
118
116
  );
119
117
  }
120
118
 
121
119
  /**
122
- * Set newpassword for user with token
120
+ * Set new password for user with token
123
121
  */
124
122
  async resetPassword(token: string, newPassword: string, serviceOptions?: ServiceOptions): Promise<TUser> {
125
123
  // Get user
@@ -130,14 +128,11 @@ export abstract class CoreUserService<
130
128
 
131
129
  return this.process(
132
130
  async () => {
133
- // Update user
134
- await Object.assign(dbObject, {
131
+ // Update and return user
132
+ return await assignPlain(dbObject, {
135
133
  password: await bcrypt.hash(newPassword, 10),
136
134
  passwordResetToken: null,
137
135
  }).save();
138
-
139
- // Return user
140
- return dbObject;
141
136
  },
142
137
  { dbObject, serviceOptions }
143
138
  );
@@ -154,13 +149,9 @@ export abstract class CoreUserService<
154
149
  }
155
150
  return this.process(
156
151
  async () => {
157
- // Set reset token
158
- const resetToken = crypto.randomBytes(32).toString('hex');
159
- dbObject.passwordResetToken = resetToken;
160
- await dbObject.save();
161
-
162
- // Return user
163
- return dbObject;
152
+ // Set reset token and return
153
+ dbObject.passwordResetToken = crypto.randomBytes(32).toString('hex');
154
+ return await dbObject.save();
164
155
  },
165
156
  { dbObject, serviceOptions }
166
157
  );
@@ -182,7 +173,7 @@ export abstract class CoreUserService<
182
173
 
183
174
  // Update and return user
184
175
  return this.process(
185
- async (data) => {
176
+ async () => {
186
177
  return await this.mainDbModel.findByIdAndUpdate(userId, { roles }).exec();
187
178
  },
188
179
  { serviceOptions }
@@ -1,15 +1,17 @@
1
- import { DynamicModule, Global, Module, UnauthorizedException } from '@nestjs/common';
1
+ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
2
+ import { DynamicModule, Global, MiddlewareConsumer, Module, NestModule, UnauthorizedException } from '@nestjs/common';
2
3
  import { APP_PIPE } from '@nestjs/core';
3
4
  import { GraphQLModule } from '@nestjs/graphql';
5
+ import { MongooseModule } from '@nestjs/mongoose';
6
+ import { Context } from 'apollo-server-core';
7
+ import { graphqlUploadExpress } from 'graphql-upload';
4
8
  import { merge } from './core/common/helpers/config.helper';
5
9
  import { IServerOptions } from './core/common/interfaces/server-options.interface';
6
10
  import { MapAndValidatePipe } from './core/common/pipes/map-and-validate.pipe';
7
11
  import { ConfigService } from './core/common/services/config.service';
8
12
  import { EmailService } from './core/common/services/email.service';
9
- import { TemplateService } from './core/common/services/template.service';
10
- import { MongooseModule } from '@nestjs/mongoose';
11
13
  import { MailjetService } from './core/common/services/mailjet.service';
12
- import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
14
+ import { TemplateService } from './core/common/services/template.service';
13
15
 
14
16
  /**
15
17
  * Core module (dynamic)
@@ -26,7 +28,13 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
26
28
  */
27
29
  @Global()
28
30
  @Module({})
29
- export class CoreModule {
31
+ export class CoreModule implements NestModule {
32
+ /**
33
+ * Integrate middleware, e.g. GraphQL upload handing for express
34
+ */
35
+ configure(consumer: MiddlewareConsumer) {
36
+ consumer.apply(graphqlUploadExpress()).forRoutes('graphql');
37
+ }
30
38
  /**
31
39
  * Dynamic module
32
40
  * see https://docs.nestjs.com/modules#dynamic-modules
@@ -66,6 +74,28 @@ export class CoreModule {
66
74
  }
67
75
  },
68
76
  },
77
+ 'graphql-ws': {
78
+ onConnect: async (context: Context<any>) => {
79
+ const { connectionParams, extra } = context;
80
+ if (config.graphQl.enableSubscriptionAuth) {
81
+ // get authToken from authorization header
82
+ const authToken: string =
83
+ 'Authorization' in connectionParams && connectionParams?.Authorization?.split(' ')[1];
84
+ if (authToken) {
85
+ // verify authToken/getJwtPayLoad
86
+ const payload = authService.decodeJwt(authToken);
87
+ const user = await authService.validateUser(payload);
88
+ // the user/jwtPayload object found will be available as context.currentUser/jwtPayload in your GraphQL resolvers
89
+ extra.user = user;
90
+ extra.header = connectionParams;
91
+ return extra;
92
+ }
93
+
94
+ throw new UnauthorizedException();
95
+ }
96
+ },
97
+ context: ({ extra }) => extra,
98
+ },
69
99
  },
70
100
  },
71
101
  options?.graphQl?.driver
@@ -113,7 +143,9 @@ export class CoreModule {
113
143
  module: CoreModule,
114
144
  imports: [
115
145
  MongooseModule.forRoot(config.mongoose.uri, config.mongoose.options),
116
- GraphQLModule.forRootAsync<ApolloDriverConfig>(Object.assign({ driver: ApolloDriver }, config.graphQl.driver)),
146
+ GraphQLModule.forRootAsync<ApolloDriverConfig>(
147
+ Object.assign({ driver: ApolloDriver }, config.graphQl.driver, config.graphQl.options)
148
+ ),
117
149
  ],
118
150
  providers,
119
151
  exports: [ConfigService, EmailService, TemplateService, MailjetService],
package/src/main.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { NestFactory } from '@nestjs/core';
2
2
  import { NestExpressApplication } from '@nestjs/platform-express';
3
+ import { exec } from 'child_process';
3
4
  import envConfig from './config.env';
4
5
  import { ServerModule } from './server/server.module';
5
6
 
@@ -25,6 +26,21 @@ async function bootstrap() {
25
26
 
26
27
  // Start server on configured port
27
28
  await server.listen(envConfig.port);
29
+
30
+ // Run command after server init
31
+ if (envConfig.execAfterInit) {
32
+ exec(envConfig.execAfterInit, (error, stdout, stderr) => {
33
+ if (error) {
34
+ console.error(`error: ${error.message}`);
35
+ return;
36
+ }
37
+
38
+ if (stderr) {
39
+ console.error(`stderr: ${stderr}`);
40
+ return;
41
+ }
42
+ });
43
+ }
28
44
  }
29
45
 
30
46
  // Start server
@@ -27,9 +27,8 @@ export class AuthModule {
27
27
  // providers: [] // Integrate additional Providers here to resolve dependencies
28
28
  },
29
29
  }),
30
- EmailService,
31
30
  ],
32
- providers: [AuthResolver, AuthService],
31
+ providers: [AuthResolver, AuthService, EmailService],
33
32
  exports: [AuthResolver, CoreAuthModule],
34
33
  };
35
34
  }
@@ -32,6 +32,6 @@ export class FileController {
32
32
  })
33
33
  )
34
34
  uploadFile(@UploadedFiles() files, @Body() fields: any) {
35
- console.log(files, fields);
35
+ console.log(JSON.stringify({ files, fields }, null, 2));
36
36
  }
37
37
  }
@@ -0,0 +1,55 @@
1
+ import { Args, Mutation, Resolver } from '@nestjs/graphql';
2
+ import { createWriteStream } from 'fs';
3
+ import { FileUpload, GraphQLUpload } from 'graphql-upload';
4
+
5
+ /**
6
+ * File resolver
7
+ */
8
+ @Resolver()
9
+ export class FileResolver {
10
+ /**
11
+ * Upload file
12
+ */
13
+ @Mutation(() => Boolean)
14
+ async uploadFile(
15
+ @Args({ name: 'file', type: () => GraphQLUpload })
16
+ file: FileUpload
17
+ ) {
18
+ console.log(JSON.stringify(file, null, 2));
19
+ /*
20
+ const {filename, mimetype, encoding, createReadStream} = file;
21
+ await new Promise((resolve, reject) =>
22
+ createReadStream()
23
+ .pipe(createWriteStream(`./uploads/${filename}`))
24
+ .on('finish', () => resolve(true))
25
+ .on('error', (error) => reject(error))
26
+ );
27
+ */
28
+ return true;
29
+ }
30
+
31
+ /**
32
+ * Upload files
33
+ */
34
+ @Mutation(() => Boolean)
35
+ async uploadFiles(
36
+ @Args({ name: 'files', type: () => [GraphQLUpload] })
37
+ files: FileUpload[]
38
+ ) {
39
+ const promises: Promise<any>[] = [];
40
+ for (const file of files) {
41
+ console.log(JSON.stringify(await file, null, 2));
42
+ /*
43
+ const {filename, mimetype, encoding, createReadStream} = await file
44
+ promises.push(new Promise((resolve, reject) =>
45
+ createReadStream()
46
+ .pipe(createWriteStream(`./uploads/${filename}`))
47
+ .on('finish', () => resolve(true))
48
+ .on('error', (error) => reject(error))
49
+ ));
50
+ */
51
+ }
52
+ await Promise.all(promises);
53
+ return true;
54
+ }
55
+ }
@@ -145,7 +145,7 @@ export class UserResolver {
145
145
  */
146
146
  @Subscription(() => User, {
147
147
  filter(this: UserResolver, payload, variables, context) {
148
- return context.user.roles.include(RoleEnum.ADMIN);
148
+ return context?.user?.hasRole?.(RoleEnum.ADMIN);
149
149
  },
150
150
  resolve: (user) => user,
151
151
  })
@@ -50,7 +50,7 @@ export class UserService extends CoreUserService<User, UserInput, UserCreateInpu
50
50
  // and could not exist as currentUser before
51
51
  if (!user.createdBy) {
52
52
  await this.mainDbModel.findByIdAndUpdate(user.id, { createdBy: user.id });
53
- user = await this.get(user.id, { ...serviceOptions, currentUser: serviceOptions.currentUser || user });
53
+ user = await this.get(user.id, { ...serviceOptions, currentUser: serviceOptions?.currentUser || user });
54
54
  }
55
55
 
56
56
  // Publish action
@@ -1,10 +1,11 @@
1
1
  import { Module } from '@nestjs/common';
2
2
  import envConfig from '../config.env';
3
3
  import { CoreModule } from '../core.module';
4
+ import { CoreAuthService } from '../core/modules/auth/services/core-auth.service';
4
5
  import { AuthModule } from './modules/auth/auth.module';
5
6
  import { FileController } from './modules/file/file.controller';
7
+ import { FileResolver } from './modules/file/file.resolver';
6
8
  import { ServerController } from './server.controller';
7
- import { CoreAuthService } from '../core/modules/auth/services/core-auth.service';
8
9
 
9
10
  /**
10
11
  * Server module (dynamic)
@@ -26,6 +27,9 @@ import { CoreAuthService } from '../core/modules/auth/services/core-auth.service
26
27
  // Include REST controllers
27
28
  controllers: [FileController, ServerController],
28
29
 
30
+ // Include resolvers, services and other providers
31
+ providers: [FileResolver],
32
+
29
33
  // Export modules for reuse in other modules
30
34
  exports: [CoreModule, AuthModule],
31
35
  })
@@ -1,9 +1,10 @@
1
1
  import { INestApplication } from '@nestjs/common';
2
- // import { FastifyInstance } from 'fastify';
2
+ import { createClient } from 'graphql-ws';
3
3
  import { jsonToGraphQLQuery } from 'json-to-graphql-query';
4
4
  import * as LightMyRequest from 'light-my-request';
5
5
  import * as supertest from 'supertest';
6
6
  import * as util from 'util';
7
+ import * as ws from 'ws';
7
8
 
8
9
  /**
9
10
  * GraphQL request type
@@ -64,6 +65,11 @@ export interface TestGraphQLOptions {
64
65
  */
65
66
  convertEnums?: boolean | string[];
66
67
 
68
+ /**
69
+ * Count of subscription messages, specifies how many messages are to be received on subscription
70
+ */
71
+ countOfSubscriptionMessages?: number;
72
+
67
73
  /**
68
74
  * Print console logs
69
75
  */
@@ -104,33 +110,35 @@ export class TestHelper {
104
110
  // app: FastifyInstance | INestApplication;
105
111
  app: INestApplication;
106
112
 
113
+ // URL with port and directory to subscription endpoint
114
+ // e.g.: ws://localhost:3030/graphql
115
+ subscriptionUrl: string;
116
+
107
117
  /**
108
118
  * Constructor
109
119
  */
110
- constructor(app: any) {
120
+ constructor(app: any, subscriptionUrl?: string) {
111
121
  this.app = app;
122
+ this.subscriptionUrl = subscriptionUrl;
112
123
  }
113
124
 
114
125
  /**
115
126
  * GraphQL request
116
- * @param graphql
117
- * @param options
118
127
  */
119
128
  async graphQl(graphql: string | TestGraphQLConfig, options: TestGraphQLOptions = {}): Promise<any> {
120
129
  // Default options
121
- options = Object.assign(
122
- {
123
- convertEnums: true,
124
- token: null,
125
- statusCode: 200,
126
- log: false,
127
- logError: false,
128
- },
129
- options
130
- );
130
+ const config = {
131
+ convertEnums: true,
132
+ countOfSubscriptionMessages: 1,
133
+ token: null,
134
+ statusCode: 200,
135
+ log: false,
136
+ logError: false,
137
+ ...options,
138
+ };
131
139
 
132
140
  // Init vars
133
- const { token, statusCode, log, logError } = options;
141
+ const { token, statusCode, log, logError } = config;
134
142
 
135
143
  // Init
136
144
  let query = '';
@@ -174,10 +182,14 @@ export class TestHelper {
174
182
  query = jsonToGraphQLQuery(queryObj, { pretty: true });
175
183
  }
176
184
 
185
+ if ((graphql as TestGraphQLConfig).type === TestGraphQLType.SUBSCRIPTION) {
186
+ return this.getSubscription(graphql as TestGraphQLConfig, query, config);
187
+ }
188
+
177
189
  // Convert uppercase strings in arguments of query to enums
178
- if (options.convertEnums) {
179
- if (Array.isArray(options.convertEnums)) {
180
- for (const key of Object.values(options.convertEnums)) {
190
+ if (config.convertEnums) {
191
+ if (Array.isArray(config.convertEnums)) {
192
+ for (const key of Object.values(config.convertEnums)) {
181
193
  const regExpStr = '(' + key + ': )\\"([_A-Z][_0-9A-Z]*)\\"';
182
194
  const regExp = new RegExp(regExpStr, 'g');
183
195
  query = query.replace(regExp, '$1$2');
@@ -225,28 +237,26 @@ export class TestHelper {
225
237
  */
226
238
  async rest(url: string, options: TestRestOptions = {}): Promise<any> {
227
239
  // Default options
228
- options = Object.assign(
229
- {
230
- token: null,
231
- statusCode: 200,
232
- log: false,
233
- logError: false,
234
- payload: null,
235
- method: 'GET',
236
- },
237
- options
238
- );
240
+ const config: TestRestOptions = {
241
+ token: null,
242
+ statusCode: 200,
243
+ log: false,
244
+ logError: false,
245
+ payload: null,
246
+ method: 'GET',
247
+ ...options,
248
+ };
239
249
 
240
250
  // Init vars
241
- const { token, statusCode, log, logError } = options;
251
+ const { token, statusCode, log, logError } = config;
242
252
 
243
253
  // Request configuration
244
254
  const requestConfig: LightMyRequest.InjectOptions = {
245
- method: options.method,
255
+ method: config.method,
246
256
  url,
247
257
  };
248
- if (options.payload) {
249
- requestConfig.payload = options.payload;
258
+ if (config.payload) {
259
+ requestConfig.payload = config.payload;
250
260
  }
251
261
 
252
262
  // Process response
@@ -354,7 +364,7 @@ export class TestHelper {
354
364
 
355
365
  // Log response
356
366
  if (log) {
357
- console.log(response);
367
+ console.log(JSON.stringify(response, null, 2));
358
368
  }
359
369
 
360
370
  // Log error
@@ -373,4 +383,52 @@ export class TestHelper {
373
383
  // Return response
374
384
  return response;
375
385
  }
386
+
387
+ /**
388
+ * Get subscription
389
+ */
390
+ async getSubscription(graphql: TestGraphQLConfig, query: string, options?: TestGraphQLOptions) {
391
+ // Check url
392
+ if (!this.subscriptionUrl) {
393
+ throw new Error("Missing subscriptionUrl in TestHelper: new TestHelper(app, 'ws://localhost:3030/graphql')");
394
+ }
395
+
396
+ // Prepare subscription
397
+ let connectionParams;
398
+ if (options?.token) {
399
+ connectionParams = { Authorization: `Bearer ${options?.token}` };
400
+ }
401
+
402
+ // Init client
403
+ if (options.log) {
404
+ console.log('Subscription query', JSON.stringify(query, null, 2));
405
+ }
406
+ const client = createClient({ url: this.subscriptionUrl, connectionParams, webSocketImpl: ws });
407
+ const messages: any[] = [];
408
+ let unsubscribe: () => void;
409
+ const onNext = (message) => {
410
+ if (options.log) {
411
+ console.log('Subscription message', JSON.stringify(message, null, 2));
412
+ }
413
+ messages.push(message?.data?.[graphql.name]);
414
+ if (messages.length <= options.countOfSubscriptionMessages) {
415
+ unsubscribe();
416
+ }
417
+ };
418
+
419
+ // Subscribe
420
+ await new Promise((resolve, reject) => {
421
+ unsubscribe = client.subscribe(
422
+ { query },
423
+ {
424
+ next: onNext,
425
+ error: reject,
426
+ complete: resolve as any,
427
+ }
428
+ );
429
+ });
430
+
431
+ // Return subscribed messages
432
+ return messages;
433
+ }
376
434
  }