@lenne.tech/nest-server 8.6.8 → 8.6.11
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/decorators/graphql-user.decorator.js +2 -3
- package/dist/core/common/decorators/graphql-user.decorator.js.map +1 -1
- package/dist/core/common/decorators/restricted.decorator.js +14 -13
- package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
- package/dist/core/common/helpers/context.helper.js +4 -5
- package/dist/core/common/helpers/context.helper.js.map +1 -1
- package/dist/core/common/helpers/db.helper.js +32 -21
- package/dist/core/common/helpers/db.helper.js.map +1 -1
- package/dist/core/common/helpers/file.helper.js +5 -1
- package/dist/core/common/helpers/file.helper.js.map +1 -1
- package/dist/core/common/helpers/filter.helper.js +16 -9
- package/dist/core/common/helpers/filter.helper.js.map +1 -1
- package/dist/core/common/helpers/graphql.helper.js +11 -8
- package/dist/core/common/helpers/graphql.helper.js.map +1 -1
- package/dist/core/common/helpers/input.helper.js +17 -11
- package/dist/core/common/helpers/input.helper.js.map +1 -1
- package/dist/core/common/helpers/model.helper.d.ts +2 -0
- package/dist/core/common/helpers/model.helper.js +125 -3
- package/dist/core/common/helpers/model.helper.js.map +1 -1
- package/dist/core/common/helpers/service.helper.js +27 -13
- package/dist/core/common/helpers/service.helper.js.map +1 -1
- package/dist/core/common/inputs/core-input.input.js +6 -1
- package/dist/core/common/inputs/core-input.input.js.map +1 -1
- package/dist/core/common/inputs/single-filter.input.d.ts +1 -0
- package/dist/core/common/inputs/single-filter.input.js +8 -0
- package/dist/core/common/inputs/single-filter.input.js.map +1 -1
- package/dist/core/common/models/core-model.model.js +14 -2
- package/dist/core/common/models/core-model.model.js.map +1 -1
- package/dist/core/common/pipes/check-input.pipe.js +1 -1
- package/dist/core/common/pipes/check-input.pipe.js.map +1 -1
- package/dist/core/common/pipes/map-and-validate.pipe.js +2 -2
- package/dist/core/common/pipes/map-and-validate.pipe.js.map +1 -1
- package/dist/core/common/services/crud.service.js +3 -5
- package/dist/core/common/services/crud.service.js.map +1 -1
- package/dist/core/common/services/email.service.js +5 -1
- package/dist/core/common/services/email.service.js.map +1 -1
- package/dist/core/common/services/mailjet.service.js +8 -1
- package/dist/core/common/services/mailjet.service.js.map +1 -1
- package/dist/core/common/services/module.service.js +29 -7
- package/dist/core/common/services/module.service.js.map +1 -1
- package/dist/core/modules/auth/core-auth.model.d.ts +1 -0
- package/dist/core/modules/auth/core-auth.model.js +4 -0
- package/dist/core/modules/auth/core-auth.model.js.map +1 -1
- package/dist/core/modules/auth/core-auth.module.js +2 -2
- package/dist/core/modules/auth/core-auth.module.js.map +1 -1
- package/dist/core/modules/auth/guards/auth.guard.js +4 -5
- package/dist/core/modules/auth/guards/auth.guard.js.map +1 -1
- package/dist/core/modules/auth/guards/roles.guard.js +1 -2
- package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
- package/dist/core/modules/file/core-file.service.d.ts +26 -0
- package/dist/core/modules/file/core-file.service.js +116 -0
- package/dist/core/modules/file/core-file.service.js.map +1 -0
- package/dist/core/modules/file/file-info.output.d.ts +10 -0
- package/dist/core/modules/file/file-info.output.js +49 -0
- package/dist/core/modules/file/file-info.output.js.map +1 -0
- package/dist/core/modules/file/interfaces/file-service-options.interface.d.ts +7 -0
- package/dist/core/modules/file/interfaces/file-service-options.interface.js +3 -0
- package/dist/core/modules/file/interfaces/file-service-options.interface.js.map +1 -0
- package/dist/core/modules/file/interfaces/file-upload.interface.d.ts +13 -0
- package/dist/core/modules/file/interfaces/file-upload.interface.js +3 -0
- package/dist/core/modules/file/interfaces/file-upload.interface.js.map +1 -0
- package/dist/core/modules/user/core-user.model.d.ts +1 -0
- package/dist/core/modules/user/core-user.model.js +4 -0
- package/dist/core/modules/user/core-user.model.js.map +1 -1
- package/dist/core/modules/user/core-user.service.js +7 -3
- package/dist/core/modules/user/core-user.service.js.map +1 -1
- package/dist/core.module.js +32 -37
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/server/common/models/persistence.model.d.ts +3 -3
- package/dist/server/common/models/persistence.model.js +8 -4
- 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/auth/auth.module.js +4 -1
- package/dist/server/modules/auth/auth.module.js.map +1 -1
- package/dist/server/modules/file/file.controller.d.ts +6 -1
- package/dist/server/modules/file/file.controller.js +34 -4
- package/dist/server/modules/file/file.controller.js.map +1 -1
- package/dist/server/modules/file/file.resolver.d.ts +8 -2
- package/dist/server/modules/file/file.resolver.js +43 -6
- package/dist/server/modules/file/file.resolver.js.map +1 -1
- package/dist/server/modules/file/file.service.d.ts +6 -0
- package/dist/server/modules/file/file.service.js +32 -0
- package/dist/server/modules/file/file.service.js.map +1 -0
- 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 +4 -3
- package/dist/server/modules/user/user.model.js +9 -4
- package/dist/server/modules/user/user.model.js.map +1 -1
- package/dist/server/modules/user/user.resolver.js +1 -2
- package/dist/server/modules/user/user.resolver.js.map +1 -1
- package/dist/server/modules/user/user.service.js +2 -2
- package/dist/server/modules/user/user.service.js.map +1 -1
- package/dist/server/server.module.js +2 -1
- package/dist/server/server.module.js.map +1 -1
- package/dist/test/test.helper.d.ts +24 -1
- package/dist/test/test.helper.js +100 -13
- package/dist/test/test.helper.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/src/core/common/helpers/db.helper.ts +2 -0
- package/src/core/common/helpers/filter.helper.ts +7 -1
- package/src/core/common/helpers/model.helper.ts +152 -0
- package/src/core/common/inputs/single-filter.input.ts +10 -1
- package/src/core/common/services/module.service.ts +1 -1
- package/src/core/modules/auth/core-auth.model.ts +10 -1
- package/src/core/modules/file/core-file.service.ts +179 -0
- package/src/core/modules/file/file-info.output.ts +26 -0
- package/src/core/modules/file/interfaces/file-service-options.interface.ts +7 -0
- package/src/core/modules/file/interfaces/file-upload.interface.ts +38 -0
- package/src/core/modules/user/core-user.model.ts +9 -0
- package/src/core.module.ts +1 -2
- package/src/index.ts +9 -0
- package/src/server/common/models/persistence.model.ts +24 -11
- package/src/server/modules/auth/auth.model.ts +9 -0
- package/src/server/modules/file/file.controller.ts +44 -5
- package/src/server/modules/file/file.resolver.ts +57 -33
- package/src/server/modules/file/file.service.ts +11 -0
- package/src/server/modules/user/inputs/user-create.input.ts +0 -4
- package/src/server/modules/user/inputs/user.input.ts +0 -4
- package/src/server/modules/user/user.model.ts +17 -8
- package/src/server/server.module.ts +2 -1
- package/src/test/test.helper.ts +125 -7
|
@@ -1,18 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
BadRequestException,
|
|
3
|
+
Body,
|
|
4
|
+
Controller,
|
|
5
|
+
Get,
|
|
6
|
+
NotFoundException,
|
|
7
|
+
Param,
|
|
8
|
+
Post,
|
|
9
|
+
Res,
|
|
10
|
+
UploadedFiles,
|
|
11
|
+
UseInterceptors,
|
|
12
|
+
} from '@nestjs/common';
|
|
2
13
|
import { FilesInterceptor } from '@nestjs/platform-express';
|
|
3
14
|
import { diskStorage } from 'multer';
|
|
4
15
|
import envConfig from '../../../config.env';
|
|
16
|
+
import { RESTUser } from '../../../core/common/decorators/rest-user.decorator';
|
|
5
17
|
import { Roles } from '../../../core/common/decorators/roles.decorator';
|
|
6
18
|
import { RoleEnum } from '../../../core/common/enums/role.enum';
|
|
7
19
|
import { multerRandomFileName } from '../../../core/common/helpers/file.helper';
|
|
20
|
+
import { User } from '../user/user.model';
|
|
21
|
+
import { FileService } from './file.service';
|
|
8
22
|
|
|
9
23
|
/**
|
|
10
|
-
* File controller
|
|
24
|
+
* File controller
|
|
11
25
|
*/
|
|
12
26
|
@Controller('files')
|
|
13
27
|
export class FileController {
|
|
14
28
|
/**
|
|
15
|
-
*
|
|
29
|
+
* Include services
|
|
30
|
+
*/
|
|
31
|
+
constructor(protected fileService: FileService) {}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Upload files via REST as an alternative to uploading via GraphQL (see file.resolver.ts)
|
|
16
35
|
*/
|
|
17
36
|
@Roles(RoleEnum.ADMIN)
|
|
18
37
|
@Post('upload')
|
|
@@ -31,7 +50,27 @@ export class FileController {
|
|
|
31
50
|
}),
|
|
32
51
|
})
|
|
33
52
|
)
|
|
34
|
-
|
|
35
|
-
console.log(JSON.stringify({ files, fields }, null, 2));
|
|
53
|
+
uploadFiles(@UploadedFiles() files, @Body() fields: any) {
|
|
54
|
+
console.log('Saved file info', JSON.stringify({ files, fields }, null, 2));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Download file
|
|
59
|
+
*/
|
|
60
|
+
@Roles(RoleEnum.ADMIN)
|
|
61
|
+
@Get(':filename')
|
|
62
|
+
async getFile(@Param('filename') filename: string, @Res() res, @RESTUser() user: User) {
|
|
63
|
+
if (!filename) {
|
|
64
|
+
throw new BadRequestException('Missing filename for download');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const file = await this.fileService.getFileInfoByName(filename);
|
|
68
|
+
if (!file) {
|
|
69
|
+
throw new NotFoundException('File not found');
|
|
70
|
+
}
|
|
71
|
+
const filestream = await this.fileService.getFileStream(file.id);
|
|
72
|
+
res.header('Content-Type', file.contentType);
|
|
73
|
+
res.header('Content-Disposition', 'attachment; filename=' + file.filename);
|
|
74
|
+
return filestream.pipe(res);
|
|
36
75
|
}
|
|
37
76
|
}
|
|
@@ -1,56 +1,80 @@
|
|
|
1
|
-
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
|
1
|
+
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
|
2
2
|
import { createWriteStream } from 'fs';
|
|
3
3
|
import * as GraphQLUpload from 'graphql-upload/GraphQLUpload.js';
|
|
4
|
-
import
|
|
4
|
+
import { Roles } from '../../../core/common/decorators/roles.decorator';
|
|
5
|
+
import { RoleEnum } from '../../../core/common/enums/role.enum';
|
|
6
|
+
import { FileInfo } from '../../../core/modules/file/file-info.output';
|
|
7
|
+
import { FileUpload } from '../../../core/modules/file/interfaces/file-upload.interface';
|
|
8
|
+
import { FileService } from './file.service';
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* File resolver
|
|
8
12
|
*/
|
|
9
13
|
@Resolver()
|
|
10
14
|
export class FileResolver {
|
|
15
|
+
/**
|
|
16
|
+
* Integrate services
|
|
17
|
+
*/
|
|
18
|
+
constructor(protected readonly fileService: FileService) {}
|
|
19
|
+
|
|
20
|
+
// ===========================================================================
|
|
21
|
+
// Queries
|
|
22
|
+
// ===========================================================================
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get file info
|
|
26
|
+
*/
|
|
27
|
+
@Roles(RoleEnum.ADMIN)
|
|
28
|
+
@Query(() => FileInfo, { nullable: true })
|
|
29
|
+
async getFileInfo(@Args({ name: 'filename', type: () => String }) filename: string) {
|
|
30
|
+
return await this.fileService.getFileInfoByName(filename);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ===========================================================================
|
|
34
|
+
// Mutations
|
|
35
|
+
// ===========================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Delete file
|
|
39
|
+
*/
|
|
40
|
+
@Roles(RoleEnum.ADMIN)
|
|
41
|
+
@Mutation(() => FileInfo)
|
|
42
|
+
async deleteFile(@Args({ name: 'filename', type: () => String }) filename: string) {
|
|
43
|
+
return await this.fileService.deleteFileByName(filename);
|
|
44
|
+
}
|
|
45
|
+
|
|
11
46
|
/**
|
|
12
47
|
* Upload file
|
|
13
48
|
*/
|
|
14
|
-
@
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
file
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const {filename, mimetype, encoding, createReadStream} = file;
|
|
22
|
-
await new Promise((resolve, reject) =>
|
|
23
|
-
createReadStream()
|
|
24
|
-
.pipe(createWriteStream(`./uploads/${filename}`))
|
|
25
|
-
.on('finish', () => resolve(true))
|
|
26
|
-
.on('error', (error) => reject(error))
|
|
27
|
-
);
|
|
28
|
-
*/
|
|
29
|
-
return true;
|
|
49
|
+
@Roles(RoleEnum.ADMIN)
|
|
50
|
+
@Mutation(() => FileInfo)
|
|
51
|
+
async uploadFile(@Args({ name: 'file', type: () => GraphQLUpload }) file: FileUpload) {
|
|
52
|
+
const { filename, mimetype, encoding, createReadStream } = file;
|
|
53
|
+
|
|
54
|
+
// Save file in DB
|
|
55
|
+
return await this.fileService.createFile(file);
|
|
30
56
|
}
|
|
31
57
|
|
|
32
58
|
/**
|
|
33
59
|
* Upload files
|
|
34
60
|
*/
|
|
61
|
+
@Roles(RoleEnum.ADMIN)
|
|
35
62
|
@Mutation(() => Boolean)
|
|
36
|
-
async uploadFiles(
|
|
37
|
-
|
|
38
|
-
files: FileUpload[]
|
|
39
|
-
) {
|
|
63
|
+
async uploadFiles(@Args({ name: 'files', type: () => [GraphQLUpload] }) files: FileUpload[]) {
|
|
64
|
+
// Save files in filesystem
|
|
40
65
|
const promises: Promise<any>[] = [];
|
|
41
66
|
for (const file of files) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
)
|
|
51
|
-
*/
|
|
67
|
+
const { filename, mimetype, encoding, createReadStream } = await file;
|
|
68
|
+
promises.push(
|
|
69
|
+
new Promise((resolve, reject) =>
|
|
70
|
+
createReadStream()
|
|
71
|
+
.pipe(createWriteStream(`./uploads/${filename}`))
|
|
72
|
+
.on('finish', () => resolve(true))
|
|
73
|
+
.on('error', (error) => reject(error))
|
|
74
|
+
)
|
|
75
|
+
);
|
|
52
76
|
}
|
|
53
|
-
await Promise.
|
|
77
|
+
await Promise.allSettled(promises);
|
|
54
78
|
return true;
|
|
55
79
|
}
|
|
56
80
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { InjectConnection } from '@nestjs/mongoose';
|
|
3
|
+
import { Connection } from 'mongoose';
|
|
4
|
+
import { CoreFileService } from '../../../core/modules/file/core-file.service';
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class FileService extends CoreFileService {
|
|
8
|
+
constructor(@InjectConnection() protected readonly connection: Connection) {
|
|
9
|
+
super(connection, 'File');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -3,10 +3,6 @@ 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.
|
|
10
6
|
*/
|
|
11
7
|
@InputType({ description: 'User input to create a new user' })
|
|
12
8
|
export class UserCreateInput extends CoreUserCreateInput {
|
|
@@ -3,10 +3,6 @@ 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.
|
|
10
6
|
*/
|
|
11
7
|
@InputType({ description: 'User input' })
|
|
12
8
|
export class UserInput extends CoreUserInput {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Field, ObjectType } from '@nestjs/graphql';
|
|
2
2
|
import { Prop, Schema as MongooseSchema, SchemaFactory } from '@nestjs/mongoose';
|
|
3
|
-
import { Document, Schema } from 'mongoose';
|
|
3
|
+
import { Document, Schema, Types } from 'mongoose';
|
|
4
|
+
import { mapClasses } from '../../../core/common/helpers/model.helper';
|
|
4
5
|
import { CoreUserModel } from '../../../core/modules/user/core-user.model';
|
|
5
6
|
import { PersistenceModel } from '../../common/models/persistence.model';
|
|
6
7
|
|
|
7
8
|
export type UserDocument = User & Document;
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
* User
|
|
11
|
+
* User model
|
|
11
12
|
*/
|
|
12
13
|
@ObjectType({ description: 'User' })
|
|
13
14
|
@MongooseSchema({ timestamps: true })
|
|
@@ -24,28 +25,28 @@ export class User extends CoreUserModel implements PersistenceModel {
|
|
|
24
25
|
avatar: string = undefined;
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
|
-
*
|
|
28
|
+
* ID of the user who created the object
|
|
28
29
|
*
|
|
29
30
|
* Not set when created by system
|
|
30
31
|
*/
|
|
31
|
-
@Field((
|
|
32
|
+
@Field(() => String, {
|
|
32
33
|
description: 'ID of the user who created the object',
|
|
33
34
|
nullable: true,
|
|
34
35
|
})
|
|
35
36
|
@Prop({ type: Schema.Types.ObjectId, ref: 'User' })
|
|
36
|
-
createdBy:
|
|
37
|
+
createdBy: Types.ObjectId | string = undefined;
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
|
-
*
|
|
40
|
+
* ID of the user who updated the object
|
|
40
41
|
*
|
|
41
42
|
* Not set when updated by system
|
|
42
43
|
*/
|
|
43
|
-
@Field((
|
|
44
|
+
@Field(() => String, {
|
|
44
45
|
description: 'ID of the user who last updated the object',
|
|
45
46
|
nullable: true,
|
|
46
47
|
})
|
|
47
48
|
@Prop({ type: Schema.Types.ObjectId, ref: 'User' })
|
|
48
|
-
updatedBy:
|
|
49
|
+
updatedBy: Types.ObjectId | string = undefined;
|
|
49
50
|
|
|
50
51
|
// ===================================================================================================================
|
|
51
52
|
// Methods
|
|
@@ -59,6 +60,14 @@ export class User extends CoreUserModel implements PersistenceModel {
|
|
|
59
60
|
// Nothing more to initialize yet
|
|
60
61
|
return this;
|
|
61
62
|
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Map input
|
|
66
|
+
*/
|
|
67
|
+
map(input) {
|
|
68
|
+
super.map(input);
|
|
69
|
+
return mapClasses(input, { createdBy: User, updatedBy: User }, this);
|
|
70
|
+
}
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
export const UserSchema = SchemaFactory.createForClass(User);
|
|
@@ -5,6 +5,7 @@ import { CoreAuthService } from '../core/modules/auth/services/core-auth.service
|
|
|
5
5
|
import { AuthModule } from './modules/auth/auth.module';
|
|
6
6
|
import { FileController } from './modules/file/file.controller';
|
|
7
7
|
import { FileResolver } from './modules/file/file.resolver';
|
|
8
|
+
import { FileService } from './modules/file/file.service';
|
|
8
9
|
import { ServerController } from './server.controller';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -28,7 +29,7 @@ import { ServerController } from './server.controller';
|
|
|
28
29
|
controllers: [FileController, ServerController],
|
|
29
30
|
|
|
30
31
|
// Include resolvers, services and other providers
|
|
31
|
-
providers: [FileResolver],
|
|
32
|
+
providers: [FileService, FileResolver],
|
|
32
33
|
|
|
33
34
|
// Export modules for reuse in other modules
|
|
34
35
|
exports: [CoreModule, AuthModule],
|
package/src/test/test.helper.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { INestApplication } from '@nestjs/common';
|
|
2
|
+
import { Blob } from 'buffer';
|
|
3
|
+
import * as fs from 'fs';
|
|
2
4
|
import { createClient } from 'graphql-ws';
|
|
3
5
|
import { jsonToGraphQLQuery } from 'json-to-graphql-query';
|
|
4
6
|
import * as LightMyRequest from 'light-my-request';
|
|
7
|
+
import * as superagent from 'superagent';
|
|
5
8
|
import * as supertest from 'supertest';
|
|
6
9
|
import * as util from 'util';
|
|
7
10
|
import * as ws from 'ws';
|
|
@@ -53,6 +56,21 @@ export interface TestGraphQLConfig {
|
|
|
53
56
|
* https://graphql.org/learn/queries
|
|
54
57
|
*/
|
|
55
58
|
type?: TestGraphQLType;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* GraphQL variables with variable name as key and Type as value
|
|
62
|
+
*/
|
|
63
|
+
variables?: Record<string, string>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface TestGraphQLAttachment {
|
|
67
|
+
file: Blob | Buffer | fs.ReadStream | string | boolean | number;
|
|
68
|
+
options?: string | { filename?: string | undefined; contentType?: string | undefined };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface TestGraphQLVariable {
|
|
72
|
+
value: string | string[] | TestGraphQLAttachment | TestGraphQLAttachment[] | any;
|
|
73
|
+
type: 'field' | 'attachment';
|
|
56
74
|
}
|
|
57
75
|
|
|
58
76
|
/**
|
|
@@ -89,6 +107,11 @@ export interface TestGraphQLOptions {
|
|
|
89
107
|
* Token of user who is logged in
|
|
90
108
|
*/
|
|
91
109
|
token?: string;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* GraphQL variables
|
|
113
|
+
*/
|
|
114
|
+
variables?: Record<string, TestGraphQLVariable>;
|
|
92
115
|
}
|
|
93
116
|
|
|
94
117
|
/**
|
|
@@ -122,6 +145,36 @@ export class TestHelper {
|
|
|
122
145
|
this.subscriptionUrl = subscriptionUrl;
|
|
123
146
|
}
|
|
124
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Download file from URL
|
|
150
|
+
* @return Superagent response with additional data field containing the content of the file
|
|
151
|
+
*/
|
|
152
|
+
download(url: string, token?: string): Promise<superagent.Response & { data: string }> {
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
const request = supertest((this.app as INestApplication).getHttpServer()).get(url);
|
|
155
|
+
if (token) {
|
|
156
|
+
request.set('Authorization', 'bearer ' + token);
|
|
157
|
+
}
|
|
158
|
+
let data = '';
|
|
159
|
+
request
|
|
160
|
+
.buffer()
|
|
161
|
+
.parse((res: any, callback) => {
|
|
162
|
+
res.setEncoding('binary');
|
|
163
|
+
res.on('data', (chunk) => {
|
|
164
|
+
data += chunk;
|
|
165
|
+
});
|
|
166
|
+
res.on('error', reject);
|
|
167
|
+
res.on('end', (err) => {
|
|
168
|
+
err ? reject(err) : callback(null, null);
|
|
169
|
+
});
|
|
170
|
+
})
|
|
171
|
+
.end((err, res: superagent.Response) => {
|
|
172
|
+
(res as superagent.Response & { data: string }).data = Buffer.from(data, 'binary').toString();
|
|
173
|
+
err ? reject(err) : resolve(res as superagent.Response & { data: string });
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
125
178
|
/**
|
|
126
179
|
* GraphQL request
|
|
127
180
|
*/
|
|
@@ -138,7 +191,7 @@ export class TestHelper {
|
|
|
138
191
|
};
|
|
139
192
|
|
|
140
193
|
// Init vars
|
|
141
|
-
const { token, statusCode, log, logError } = config;
|
|
194
|
+
const { token, statusCode, log, logError, variables } = config;
|
|
142
195
|
|
|
143
196
|
// Init
|
|
144
197
|
let query = '';
|
|
@@ -170,6 +223,11 @@ export class TestHelper {
|
|
|
170
223
|
// Set request type
|
|
171
224
|
queryObj[graphql.type] = {};
|
|
172
225
|
|
|
226
|
+
// Set variables
|
|
227
|
+
if (graphql.variables) {
|
|
228
|
+
queryObj[graphql.type]['__variables'] = graphql.variables;
|
|
229
|
+
}
|
|
230
|
+
|
|
173
231
|
// Set request name and fields
|
|
174
232
|
queryObj[graphql.type][graphql.name] = this.prepareFields(graphql.fields) || {};
|
|
175
233
|
|
|
@@ -212,7 +270,7 @@ export class TestHelper {
|
|
|
212
270
|
}
|
|
213
271
|
|
|
214
272
|
// Get response
|
|
215
|
-
const response = await this.getResponse(token, requestConfig, statusCode, log, logError);
|
|
273
|
+
const response = await this.getResponse(token, requestConfig, statusCode, log, logError, variables);
|
|
216
274
|
|
|
217
275
|
// Response of fastify
|
|
218
276
|
// if ((this.app as FastifyInstance).inject) {
|
|
@@ -325,7 +383,8 @@ export class TestHelper {
|
|
|
325
383
|
requestConfig: LightMyRequest.InjectOptions,
|
|
326
384
|
statusCode: number,
|
|
327
385
|
log: boolean,
|
|
328
|
-
logError: boolean
|
|
386
|
+
logError: boolean,
|
|
387
|
+
variables?: Record<string, TestGraphQLVariable>
|
|
329
388
|
): Promise<any> {
|
|
330
389
|
// Token
|
|
331
390
|
if (token) {
|
|
@@ -356,15 +415,74 @@ export class TestHelper {
|
|
|
356
415
|
const method: string = requestConfig.method.toLowerCase();
|
|
357
416
|
let request = supertest((this.app as INestApplication).getHttpServer())[method](requestConfig.url as string);
|
|
358
417
|
if (token) {
|
|
359
|
-
request
|
|
418
|
+
request.set('Authorization', 'bearer ' + token);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Process variables
|
|
422
|
+
if (variables) {
|
|
423
|
+
request = this.processVariables(request, variables, (requestConfig.payload as any)?.query);
|
|
360
424
|
}
|
|
361
425
|
|
|
362
426
|
// Response
|
|
363
|
-
const response = await request.send(requestConfig.payload);
|
|
427
|
+
const response = await (variables ? request : request.send(requestConfig.payload));
|
|
428
|
+
return this.processResponse(response, statusCode, log, logError);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Process GraphQL variables
|
|
433
|
+
*/
|
|
434
|
+
processVariables(request: any, variables: Record<string, TestGraphQLVariable>, query: string | object) {
|
|
435
|
+
// Check and optimize parameters
|
|
436
|
+
if (!variables) {
|
|
437
|
+
return request;
|
|
438
|
+
}
|
|
439
|
+
if (typeof query === 'object') {
|
|
440
|
+
query = JSON.stringify(query).replace(/"/g, '');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Create map
|
|
444
|
+
const mapArray: { key: string; type: 'attachment' | 'field'; value: any; index?: number }[] = [];
|
|
445
|
+
for (const [key, item] of Object.entries(variables)) {
|
|
446
|
+
if (item.type === 'attachment' && Array.isArray(item.value)) {
|
|
447
|
+
item.value.forEach((element, index) => {
|
|
448
|
+
mapArray.push({ key, type: 'attachment', value: element, index });
|
|
449
|
+
});
|
|
450
|
+
} else {
|
|
451
|
+
mapArray.push({ key, type: item.type, value: item.value });
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const map = {};
|
|
455
|
+
mapArray.forEach((item, index) => {
|
|
456
|
+
map[index] = ['variables.' + item.key + ('index' in item ? '.' + item.index : '')];
|
|
457
|
+
});
|
|
364
458
|
|
|
459
|
+
// Add operations
|
|
460
|
+
request.field('operations', JSON.stringify({ query })).field('map', JSON.stringify(map));
|
|
461
|
+
|
|
462
|
+
// Add variables as attachment or field
|
|
463
|
+
mapArray.forEach((variable, i) => {
|
|
464
|
+
if (variable.type === 'attachment') {
|
|
465
|
+
if (typeof variable.value === 'object' && variable.value.file) {
|
|
466
|
+
request.attach(`${i}`, variable.value.file, variable.value.options);
|
|
467
|
+
} else {
|
|
468
|
+
request.attach(`${i}`, variable.value);
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
request.field(`${i}`, variable.value);
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Return processed request
|
|
476
|
+
return request;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Process GraphQL response
|
|
481
|
+
*/
|
|
482
|
+
processResponse(response, statusCode, log, logError) {
|
|
365
483
|
// Log response
|
|
366
484
|
if (log) {
|
|
367
|
-
console.log(JSON.stringify(response, null, 2));
|
|
485
|
+
console.log('Response', JSON.stringify(response, null, 2));
|
|
368
486
|
}
|
|
369
487
|
|
|
370
488
|
// Log error
|
|
@@ -372,7 +490,7 @@ export class TestHelper {
|
|
|
372
490
|
if (response && response.error && response.error.text) {
|
|
373
491
|
const errors = JSON.parse(response.error.text).errors;
|
|
374
492
|
for (const error of errors) {
|
|
375
|
-
console.
|
|
493
|
+
console.error(util.inspect(error, false, null, true));
|
|
376
494
|
}
|
|
377
495
|
}
|
|
378
496
|
}
|