@lenne.tech/nest-server 9.0.18 → 9.0.20

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 (33) hide show
  1. package/dist/config.env.js +18 -18
  2. package/dist/config.env.js.map +1 -1
  3. package/dist/core/common/pipes/map-and-validate.pipe.js +1 -1
  4. package/dist/core/common/pipes/map-and-validate.pipe.js.map +1 -1
  5. package/dist/server/modules/file/file.controller.d.ts +7 -4
  6. package/dist/server/modules/file/file.controller.js +60 -21
  7. package/dist/server/modules/file/file.controller.js.map +1 -1
  8. package/dist/server/modules/file/file.module.js +12 -6
  9. package/dist/server/modules/file/file.module.js.map +1 -1
  10. package/dist/server/modules/file/file.service.d.ts +1 -0
  11. package/dist/server/modules/file/file.service.js +6 -1
  12. package/dist/server/modules/file/file.service.js.map +1 -1
  13. package/dist/server/modules/file/multer-config.service.d.ts +6 -0
  14. package/dist/server/modules/file/multer-config.service.js +42 -0
  15. package/dist/server/modules/file/multer-config.service.js.map +1 -0
  16. package/dist/server/modules/user/user.module.js +0 -2
  17. package/dist/server/modules/user/user.module.js.map +1 -1
  18. package/dist/server/server.module.js +4 -1
  19. package/dist/server/server.module.js.map +1 -1
  20. package/dist/test/test.helper.d.ts +4 -3
  21. package/dist/test/test.helper.js +13 -5
  22. package/dist/test/test.helper.js.map +1 -1
  23. package/dist/tsconfig.build.tsbuildinfo +1 -1
  24. package/package.json +27 -21
  25. package/src/config.env.ts +18 -18
  26. package/src/core/common/pipes/map-and-validate.pipe.ts +1 -1
  27. package/src/server/modules/file/file.controller.ts +68 -32
  28. package/src/server/modules/file/file.module.ts +13 -7
  29. package/src/server/modules/file/file.service.ts +10 -1
  30. package/src/server/modules/file/multer-config.service.ts +30 -0
  31. package/src/server/modules/user/user.module.ts +0 -2
  32. package/src/server/server.module.ts +4 -1
  33. package/src/test/test.helper.ts +20 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "9.0.18",
3
+ "version": "9.0.20",
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",
@@ -60,18 +60,18 @@
60
60
  "node": ">= 16.13.0"
61
61
  },
62
62
  "dependencies": {
63
- "@apollo/gateway": "2.1.3",
64
- "@nestjs/apollo": "10.1.3",
65
- "@nestjs/common": "9.1.4",
66
- "@nestjs/core": "9.1.4",
67
- "@nestjs/graphql": "10.1.3",
63
+ "@apollo/gateway": "2.1.4",
64
+ "@nestjs/apollo": "10.1.4",
65
+ "@nestjs/common": "9.1.6",
66
+ "@nestjs/core": "9.1.6",
67
+ "@nestjs/graphql": "10.1.5",
68
68
  "@nestjs/jwt": "9.0.0",
69
- "@nestjs/mongoose": "9.2.0",
69
+ "@nestjs/mongoose": "9.2.1",
70
70
  "@nestjs/passport": "9.0.0",
71
- "@nestjs/platform-express": "9.1.4",
71
+ "@nestjs/platform-express": "9.1.6",
72
72
  "@nestjs/schedule": "2.1.0",
73
- "apollo-server-core": "3.10.3",
74
- "apollo-server-express": "3.10.3",
73
+ "apollo-server-core": "3.11.1",
74
+ "apollo-server-express": "3.11.1",
75
75
  "bcrypt": "5.1.0",
76
76
  "class-transformer": "0.5.1",
77
77
  "class-validator": "0.13.2",
@@ -83,9 +83,10 @@
83
83
  "json-to-graphql-query": "2.2.4",
84
84
  "light-my-request": "5.6.1",
85
85
  "lodash": "4.17.21",
86
- "mongodb": "4.10.0",
87
- "mongoose": "6.6.5",
86
+ "mongodb": "4.11.0",
87
+ "mongoose": "6.7.1",
88
88
  "mongoose-gridfs": "1.3.0",
89
+ "multer-gridfs-storage": "5.0.2",
89
90
  "multer": "1.4.5-lts.1",
90
91
  "node-mailjet": "5.1.1",
91
92
  "nodemailer": "6.8.0",
@@ -98,20 +99,20 @@
98
99
  "rxjs": "7.5.7"
99
100
  },
100
101
  "devDependencies": {
101
- "@nestjs/testing": "9.1.4",
102
+ "@nestjs/testing": "9.1.6",
102
103
  "@types/cron": "2.0.0",
103
104
  "@types/ejs": "3.1.1",
104
- "@types/jest": "29.2.0",
105
- "@types/lodash": "4.14.186",
105
+ "@types/jest": "29.2.2",
106
+ "@types/lodash": "4.14.188",
106
107
  "@types/multer": "1.4.7",
107
- "@types/node": "18.11.2",
108
+ "@types/node": "18.11.9",
108
109
  "@types/nodemailer": "6.4.6",
109
110
  "@types/passport": "1.0.11",
110
111
  "@types/supertest": "2.0.12",
111
- "@typescript-eslint/eslint-plugin": "5.40.1",
112
- "@typescript-eslint/parser": "5.40.1",
112
+ "@typescript-eslint/eslint-plugin": "5.42.0",
113
+ "@typescript-eslint/parser": "5.42.0",
113
114
  "coffeescript": "2.7.0",
114
- "eslint": "8.25.0",
115
+ "eslint": "8.27.0",
115
116
  "eslint-config-prettier": "8.5.0",
116
117
  "find-file-up": "2.0.1",
117
118
  "grunt": "1.5.3",
@@ -120,12 +121,12 @@
120
121
  "grunt-contrib-watch": "1.1.0",
121
122
  "grunt-sync": "0.8.2",
122
123
  "husky": "8.0.1",
123
- "jest": "29.2.1",
124
+ "jest": "29.2.2",
124
125
  "npm-watch": "0.11.0",
125
126
  "pm2": "5.2.2",
126
127
  "prettier": "2.7.1",
127
128
  "pretty-quick": "3.1.3",
128
- "supertest": "6.3.0",
129
+ "supertest": "6.3.1",
129
130
  "ts-jest": "29.0.3",
130
131
  "ts-morph": "16.0.0",
131
132
  "ts-node": "10.9.1",
@@ -133,6 +134,11 @@
133
134
  "typescript": "4.8.4",
134
135
  "yalc": "1.0.0-pre.53"
135
136
  },
137
+ "overrides": {
138
+ "multer-gridfs-storage": {
139
+ "multer": "$multer"
140
+ }
141
+ },
136
142
  "jest": {
137
143
  "collectCoverage": true,
138
144
  "coverageDirectory": "../coverage",
package/src/config.env.ts CHANGED
@@ -24,11 +24,11 @@ const config: { [env: string]: IServerOptions } = {
24
24
  email: {
25
25
  smtp: {
26
26
  auth: {
27
- user: 'cade72@ethereal.email',
28
- pass: 'jpvTwGYeSajEqDvRKT',
27
+ user: 'oren.satterfield@ethereal.email',
28
+ pass: 'K4DvD8U31VKseT7vQC',
29
29
  },
30
- host: 'smtp.ethereal.email',
31
- port: 587,
30
+ host: 'mailhog.lenne.tech',
31
+ port: 1025,
32
32
  secure: false,
33
33
  },
34
34
  mailjet: {
@@ -36,8 +36,8 @@ const config: { [env: string]: IServerOptions } = {
36
36
  api_key_private: 'MAILJET_API_KEY_PRIVATE',
37
37
  },
38
38
  defaultSender: {
39
- email: 'cade72@ethereal.email',
40
- name: 'Sandra Klein',
39
+ email: 'oren.satterfield@ethereal.email',
40
+ name: 'Nest Server Local',
41
41
  },
42
42
  verificationLink: 'http://localhost:4200/user/verification',
43
43
  passwordResetLink: 'http://localhost:4200/user/password-reset',
@@ -83,11 +83,11 @@ const config: { [env: string]: IServerOptions } = {
83
83
  email: {
84
84
  smtp: {
85
85
  auth: {
86
- user: 'cade72@ethereal.email',
87
- pass: 'jpvTwGYeSajEqDvRKT',
86
+ user: 'oren.satterfield@ethereal.email',
87
+ pass: 'K4DvD8U31VKseT7vQC',
88
88
  },
89
- host: 'smtp.ethereal.email',
90
- port: 587,
89
+ host: 'mailhog.lenne.tech',
90
+ port: 1025,
91
91
  secure: false,
92
92
  },
93
93
  mailjet: {
@@ -95,8 +95,8 @@ const config: { [env: string]: IServerOptions } = {
95
95
  api_key_private: 'MAILJET_API_KEY_PRIVATE',
96
96
  },
97
97
  defaultSender: {
98
- email: 'cade72@ethereal.email',
99
- name: 'Sandra Klein',
98
+ email: 'oren.satterfield@ethereal.email',
99
+ name: 'Nest Server Development',
100
100
  },
101
101
  verificationLink: 'http://localhost:4200/user/verification',
102
102
  passwordResetLink: 'http://localhost:4200/user/password-reset',
@@ -142,11 +142,11 @@ const config: { [env: string]: IServerOptions } = {
142
142
  email: {
143
143
  smtp: {
144
144
  auth: {
145
- user: 'cade72@ethereal.email',
146
- pass: 'jpvTwGYeSajEqDvRKT',
145
+ user: 'oren.satterfield@ethereal.email',
146
+ pass: 'K4DvD8U31VKseT7vQC',
147
147
  },
148
- host: 'smtp.ethereal.email',
149
- port: 587,
148
+ host: 'mailhog.lenne.tech',
149
+ port: 1025,
150
150
  secure: false,
151
151
  },
152
152
  mailjet: {
@@ -154,8 +154,8 @@ const config: { [env: string]: IServerOptions } = {
154
154
  api_key_private: 'MAILJET_API_KEY_PRIVATE',
155
155
  },
156
156
  defaultSender: {
157
- email: 'cade72@ethereal.email',
158
- name: 'Sandra Klein',
157
+ email: 'oren.satterfield@ethereal.email',
158
+ name: 'Nest Server Production',
159
159
  },
160
160
  verificationLink: 'http://localhost:4200/user/verification',
161
161
  passwordResetLink: 'http://localhost:4200/user/password-reset',
@@ -24,7 +24,7 @@ export class MapAndValidatePipe implements PipeTransform {
24
24
  // Validate
25
25
  const errors = await validate(value);
26
26
  if (errors.length > 0) {
27
- throw new BadRequestException('Input validation failed');
27
+ throw new BadRequestException('Input validation failed:' + errors.join('; '));
28
28
  }
29
29
 
30
30
  return value;
@@ -1,47 +1,83 @@
1
- import { Body, Controller, Post, UploadedFiles, UseInterceptors } from '@nestjs/common';
2
- import { FilesInterceptor } from '@nestjs/platform-express';
3
- import { diskStorage } from 'multer';
4
- import envConfig from '../../../config.env';
1
+ import {
2
+ Controller,
3
+ Delete,
4
+ Get,
5
+ NotFoundException,
6
+ Param,
7
+ Post,
8
+ Res,
9
+ UnprocessableEntityException,
10
+ UploadedFile,
11
+ UseInterceptors,
12
+ } from '@nestjs/common';
13
+ import { FileInterceptor } from '@nestjs/platform-express';
5
14
  import { Roles } from '../../../core/common/decorators/roles.decorator';
6
15
  import { RoleEnum } from '../../../core/common/enums/role.enum';
7
- import { multerRandomFileName } from '../../../core/common/helpers/file.helper';
8
- import { CoreFileController } from '../../../core/modules/file/core-file.controller';
9
16
  import { FileService } from './file.service';
10
17
 
11
18
  /**
12
- * File controller
19
+ * File controller for
13
20
  */
14
- @Roles(RoleEnum.S_USER)
21
+ @Roles(RoleEnum.ADMIN)
15
22
  @Controller('files')
16
- export class FileController extends CoreFileController {
23
+ export class FileController {
17
24
  /**
18
- * Include services
25
+ * Import services
19
26
  */
20
- constructor(protected fileService: FileService) {
21
- super(fileService);
22
- }
27
+ constructor(private readonly fileService: FileService) {}
23
28
 
24
29
  /**
25
- * Upload files via REST as an alternative to uploading via GraphQL (see file.resolver.ts)
30
+ * Upload file
26
31
  */
27
- @Roles(RoleEnum.ADMIN)
28
32
  @Post('upload')
29
- @UseInterceptors(
30
- FilesInterceptor('files', null, {
31
- // Automatic storage handling
32
- // For configuration see https://github.com/expressjs/multer#storage
33
- storage: diskStorage({
34
- // Destination for uploaded file
35
- // If destination is not set file will be buffered and can be processed
36
- // in the method
37
- destination: envConfig.staticAssets.path,
38
-
39
- // Generated random file name
40
- filename: multerRandomFileName(),
41
- }),
42
- })
43
- )
44
- uploadFiles(@UploadedFiles() files, @Body() fields: any) {
45
- console.info('Saved file info', JSON.stringify({ files, fields }, null, 2));
33
+ @UseInterceptors(FileInterceptor('file'))
34
+ uploadFile(@UploadedFile() file: Express.Multer.File): any {
35
+ return file;
36
+ }
37
+
38
+ /**
39
+ * Download file
40
+ */
41
+ @Get(':id')
42
+ async getFile(@Param('id') id: string, @Res() res) {
43
+ if (!id) {
44
+ throw new UnprocessableEntityException();
45
+ }
46
+
47
+ let file;
48
+ try {
49
+ file = await this.fileService.getFileInfo(id);
50
+ } catch (e) {
51
+ console.error(e);
52
+ }
53
+
54
+ if (!file) {
55
+ throw new NotFoundException();
56
+ }
57
+ const filestream = await this.fileService.getFileStream(id);
58
+ res.header('Content-Type', file.contentType);
59
+ res.header('Content-Disposition', 'attachment; filename=' + file.filename);
60
+ res.header('Cache-Control', 'max-age=31536000');
61
+ return filestream.pipe(res);
62
+ }
63
+
64
+ /**
65
+ * Get file information
66
+ */
67
+ @Get('info/:id')
68
+ async getFileInfo(@Param('id') id: string) {
69
+ return await this.fileService.getFileInfo(id);
70
+ }
71
+
72
+ /**
73
+ * Delete file
74
+ */
75
+ @Delete(':id')
76
+ async deleteFile(@Param('id') id: string) {
77
+ if (!id) {
78
+ throw new UnprocessableEntityException();
79
+ }
80
+
81
+ return await this.fileService.deleteFile(id);
46
82
  }
47
83
  }
@@ -1,17 +1,23 @@
1
- import { Module } from '@nestjs/common';
2
- import { MongooseModule } from '@nestjs/mongoose';
3
- import { FileInfo, FileInfoSchema } from './file-info.model';
4
- import { FileController } from './file.controller';
1
+ import { forwardRef, Module } from '@nestjs/common';
5
2
  import { FileResolver } from './file.resolver';
6
3
  import { FileService } from './file.service';
4
+ import { FileController } from './file.controller';
5
+ import { GridFsMulterConfigService } from './multer-config.service';
6
+ import { MulterModule } from '@nestjs/platform-express';
7
+ import { UserModule } from '../user/user.module';
7
8
 
8
9
  /**
9
10
  * File module
10
11
  */
11
12
  @Module({
12
- imports: [MongooseModule.forFeature([{ name: FileInfo.name, schema: FileInfoSchema }])],
13
+ imports: [
14
+ MulterModule.registerAsync({
15
+ useClass: GridFsMulterConfigService,
16
+ } as any),
17
+ forwardRef(() => UserModule),
18
+ ],
13
19
  controllers: [FileController],
14
- providers: [FileService, FileResolver],
15
- exports: [MongooseModule, FileService],
20
+ providers: [GridFsMulterConfigService, FileService, FileResolver],
21
+ exports: [FileService],
16
22
  })
17
23
  export class FileModule {}
@@ -9,6 +9,15 @@ import { CoreFileService } from '../../../core/modules/file/core-file.service';
9
9
  @Injectable()
10
10
  export class FileService extends CoreFileService {
11
11
  constructor(@InjectConnection() protected readonly connection: Connection) {
12
- super(connection, 'fs');
12
+ super(connection);
13
+ }
14
+
15
+ /**
16
+ * Duplicate file by name
17
+ */
18
+ async duplicate(fileName: string, newName: string): Promise<any> {
19
+ return new Promise(async (resolve, reject) => {
20
+ resolve(this.files.openDownloadStreamByName(fileName).pipe(this.files.openUploadStream(newName)));
21
+ });
13
22
  }
14
23
  }
@@ -0,0 +1,30 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { MulterModuleOptions, MulterOptionsFactory } from '@nestjs/platform-express';
3
+ import { GridFsStorage } from 'multer-gridfs-storage';
4
+ import envConfig from '../../../config.env';
5
+
6
+ @Injectable()
7
+ export class GridFsMulterConfigService implements MulterOptionsFactory {
8
+ gridFsStorage: any;
9
+
10
+ constructor() {
11
+ this.gridFsStorage = new GridFsStorage({
12
+ url: envConfig.mongoose.uri,
13
+ file: (req, file) => {
14
+ return new Promise((resolve) => {
15
+ const filename = file.originalname.trim();
16
+ const fileInfo = {
17
+ filename: filename,
18
+ };
19
+ resolve(fileInfo);
20
+ });
21
+ },
22
+ });
23
+ }
24
+
25
+ createMulterOptions(): MulterModuleOptions {
26
+ return {
27
+ storage: this.gridFsStorage,
28
+ };
29
+ }
30
+ }
@@ -1,7 +1,6 @@
1
1
  import { Module } from '@nestjs/common';
2
2
  import { MongooseModule } from '@nestjs/mongoose';
3
3
  import { PubSub } from 'graphql-subscriptions';
4
- import { JSON } from '../../../core/common/scalars/json.scalar';
5
4
  import { ConfigService } from '../../../core/common/services/config.service';
6
5
  import { AvatarController } from './avatar.controller';
7
6
  import { User, UserSchema } from './user.model';
@@ -15,7 +14,6 @@ import { UserService } from './user.service';
15
14
  imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])],
16
15
  controllers: [AvatarController],
17
16
  providers: [
18
- JSON,
19
17
  UserResolver,
20
18
  ConfigService,
21
19
  UserService,
@@ -2,6 +2,9 @@ import { Module } from '@nestjs/common';
2
2
  import { ScheduleModule } from '@nestjs/schedule';
3
3
  import envConfig from '../config.env';
4
4
  import { CoreModule } from '../core.module';
5
+ import { Any } from '../core/common/scalars/any.scalar';
6
+ import { DateScalar } from '../core/common/scalars/date.scalar';
7
+ import { JSON } from '../core/common/scalars/json.scalar';
5
8
  import { CoreAuthService } from '../core/modules/auth/services/core-auth.service';
6
9
  import { CronJobs } from './common/services/cron-jobs.service';
7
10
  import { AuthModule } from './modules/auth/auth.module';
@@ -31,7 +34,7 @@ import { ServerController } from './server.controller';
31
34
  FileModule,
32
35
  ],
33
36
 
34
- providers: [CronJobs],
37
+ providers: [Any, CronJobs, DateScalar, JSON],
35
38
 
36
39
  // Include REST controllers
37
40
  controllers: [ServerController],
@@ -125,12 +125,13 @@ export interface TestGraphQLOptions {
125
125
  * Options for rest requests
126
126
  */
127
127
  export interface TestRestOptions {
128
+ attachments?: Record<string, string>;
128
129
  log?: boolean;
129
130
  logError?: boolean;
131
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
132
+ payload?: any;
130
133
  statusCode?: number;
131
134
  token?: string;
132
- payload?: any;
133
- method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
134
135
  }
135
136
 
136
137
  /**
@@ -311,12 +312,12 @@ export class TestHelper {
311
312
  log: false,
312
313
  logError: false,
313
314
  payload: null,
314
- method: 'GET',
315
+ method: options?.attachments ? 'POST' : 'GET',
315
316
  ...options,
316
317
  };
317
318
 
318
319
  // Init vars
319
- const { token, statusCode, log, logError } = config;
320
+ const { token, statusCode, log, logError, attachments } = config;
320
321
 
321
322
  // Request configuration
322
323
  const requestConfig: LightMyRequest.InjectOptions = {
@@ -328,8 +329,11 @@ export class TestHelper {
328
329
  }
329
330
 
330
331
  // Process response
331
- const response = await this.getResponse(token, requestConfig, statusCode, log, logError);
332
+ const response = await this.getResponse(token, requestConfig, statusCode, log, logError, null, attachments);
332
333
  let result = response.text;
334
+ if (response.text === '') {
335
+ return null;
336
+ }
333
337
  try {
334
338
  result = JSON.parse(response.text);
335
339
  } catch (e) {
@@ -427,7 +431,8 @@ export class TestHelper {
427
431
  statusCode: number,
428
432
  log: boolean,
429
433
  logError: boolean,
430
- variables?: Record<string, TestGraphQLVariable>
434
+ variables?: Record<string, TestGraphQLVariable>,
435
+ attachments?: Record<string, string>
431
436
  ): Promise<any> {
432
437
  // Token
433
438
  if (token) {
@@ -461,16 +466,23 @@ export class TestHelper {
461
466
  request.set('Authorization', 'bearer ' + token);
462
467
  }
463
468
 
464
- // Process variables
469
+ // Process variables (incl. attachments for GraphQL)
465
470
  if (variables) {
466
471
  request = this.processVariables(request, variables, (requestConfig.payload as any)?.query);
467
472
  }
468
473
 
474
+ // Process REST attachments
475
+ if (attachments) {
476
+ for (const [key, value] of Object.entries(attachments)) {
477
+ request.attach(key, value);
478
+ }
479
+ }
480
+
469
481
  // Response
470
482
  if (log) {
471
483
  console.info(requestConfig);
472
484
  }
473
- const response = await (variables ? request : request.send(requestConfig.payload));
485
+ const response = await (variables || attachments ? request : request.send(requestConfig.payload));
474
486
  return this.processResponse(response, statusCode, log, logError);
475
487
  }
476
488