@lenne.tech/nest-server 3.1.2 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/common/args/filter.args.d.ts +5 -0
- package/dist/core/common/args/filter.args.js +10 -0
- package/dist/core/common/args/filter.args.js.map +1 -1
- package/dist/core/common/args/pagination.args.d.ts +8 -1
- package/dist/core/common/args/pagination.args.js +24 -6
- package/dist/core/common/args/pagination.args.js.map +1 -1
- package/dist/core/common/decorators/restricted.decorator.d.ts +3 -0
- package/dist/core/common/decorators/restricted.decorator.js +16 -5
- package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
- package/dist/core/common/helpers/context.helper.js +15 -5
- package/dist/core/common/helpers/context.helper.js.map +1 -1
- package/dist/core/common/helpers/filter.helper.d.ts +3 -3
- package/dist/core/common/helpers/filter.helper.js.map +1 -1
- package/dist/core/common/helpers/input.helper.js +13 -7
- package/dist/core/common/helpers/input.helper.js.map +1 -1
- package/dist/core/common/helpers/service.helper.d.ts +6 -8
- package/dist/core/common/helpers/service.helper.js +34 -12
- package/dist/core/common/helpers/service.helper.js.map +1 -1
- package/dist/core/common/inputs/combined-filter.input.d.ts +8 -2
- package/dist/core/common/inputs/combined-filter.input.js +16 -5
- package/dist/core/common/inputs/combined-filter.input.js.map +1 -1
- package/dist/core/common/inputs/core-input.input.d.ts +8 -0
- package/dist/core/common/inputs/core-input.input.js +15 -0
- package/dist/core/common/inputs/core-input.input.js.map +1 -0
- package/dist/core/common/inputs/filter.input.d.ts +7 -1
- package/dist/core/common/inputs/filter.input.js +14 -1
- package/dist/core/common/inputs/filter.input.js.map +1 -1
- package/dist/core/common/inputs/single-filter.input.d.ts +2 -1
- package/dist/core/common/inputs/single-filter.input.js +10 -1
- package/dist/core/common/inputs/single-filter.input.js.map +1 -1
- package/dist/core/common/inputs/sort.input.d.ts +2 -1
- package/dist/core/common/inputs/sort.input.js +7 -1
- package/dist/core/common/inputs/sort.input.js.map +1 -1
- package/dist/core/common/interceptors/check-response.interceptor.js +1 -1
- package/dist/core/common/interceptors/check-response.interceptor.js.map +1 -1
- package/dist/core/common/models/core-model.model.d.ts +6 -0
- package/dist/core/common/models/core-model.model.js +10 -3
- package/dist/core/common/models/core-model.model.js.map +1 -1
- package/dist/core/common/models/core-persistence.model.d.ts +5 -29
- package/dist/core/common/models/core-persistence.model.js +19 -41
- package/dist/core/common/models/core-persistence.model.js.map +1 -1
- package/dist/core/common/pipes/check-input.pipe.d.ts +2 -3
- package/dist/core/common/pipes/check-input.pipe.js +5 -20
- package/dist/core/common/pipes/check-input.pipe.js.map +1 -1
- package/dist/core/common/pipes/map-and-validate.pipe.d.ts +4 -0
- package/dist/core/common/pipes/map-and-validate.pipe.js +40 -0
- package/dist/core/common/pipes/map-and-validate.pipe.js.map +1 -0
- package/dist/core/common/types/plain-input.type.d.ts +3 -0
- package/dist/core/common/types/plain-input.type.js +3 -0
- package/dist/core/common/types/plain-input.type.js.map +1 -0
- package/dist/core/modules/auth/core-auth.model.d.ts +3 -1
- package/dist/core/modules/auth/core-auth.model.js +7 -1
- package/dist/core/modules/auth/core-auth.model.js.map +1 -1
- package/dist/core/modules/auth/core-auth.resolver.d.ts +1 -1
- package/dist/core/modules/user/core-user.model.d.ts +3 -0
- package/dist/core/modules/user/core-user.model.js +10 -4
- package/dist/core/modules/user/core-user.model.js.map +1 -1
- package/dist/core/modules/user/core-user.service.d.ts +9 -13
- package/dist/core/modules/user/core-user.service.js +38 -67
- package/dist/core/modules/user/core-user.service.js.map +1 -1
- package/dist/core/modules/user/inputs/core-user-create.input.js +4 -0
- package/dist/core/modules/user/inputs/core-user-create.input.js.map +1 -1
- package/dist/core/modules/user/inputs/core-user.input.d.ts +2 -1
- package/dist/core/modules/user/inputs/core-user.input.js +12 -2
- package/dist/core/modules/user/inputs/core-user.input.js.map +1 -1
- package/dist/core.module.js +2 -3
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/server/common/models/persistence.model.d.ts +1 -0
- package/dist/server/common/models/persistence.model.js +4 -0
- package/dist/server/common/models/persistence.model.js.map +1 -1
- package/dist/server/modules/auth/auth.model.d.ts +1 -0
- package/dist/server/modules/auth/auth.model.js +4 -0
- package/dist/server/modules/auth/auth.model.js.map +1 -1
- package/dist/server/modules/user/inputs/user-create.input.js.map +1 -1
- package/dist/server/modules/user/inputs/user.input.js.map +1 -1
- package/dist/server/modules/user/user.model.d.ts +3 -2
- package/dist/server/modules/user/user.model.js +9 -5
- package/dist/server/modules/user/user.model.js.map +1 -1
- package/dist/server/modules/user/user.resolver.d.ts +2 -2
- package/dist/server/modules/user/user.resolver.js +10 -12
- package/dist/server/modules/user/user.resolver.js.map +1 -1
- package/dist/server/modules/user/user.service.d.ts +7 -9
- package/dist/server/modules/user/user.service.js +12 -4
- package/dist/server/modules/user/user.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/core/common/args/filter.args.ts +22 -1
- package/src/core/common/args/pagination.args.ts +42 -7
- package/src/core/common/decorators/restricted.decorator.ts +24 -5
- package/src/core/common/helpers/context.helper.ts +14 -3
- package/src/core/common/helpers/filter.helper.ts +3 -3
- package/src/core/common/helpers/input.helper.ts +17 -11
- package/src/core/common/helpers/service.helper.ts +42 -19
- package/src/core/common/inputs/combined-filter.input.ts +30 -9
- package/src/core/common/inputs/core-input.input.ts +36 -0
- package/src/core/common/inputs/filter.input.ts +27 -3
- package/src/core/common/inputs/single-filter.input.ts +7 -6
- package/src/core/common/inputs/sort.input.ts +4 -3
- package/src/core/common/interceptors/check-response.interceptor.ts +2 -2
- package/src/core/common/models/core-model.model.ts +30 -1
- package/src/core/common/models/core-persistence.model.ts +33 -120
- package/src/core/common/pipes/check-input.pipe.ts +13 -33
- package/src/core/common/pipes/map-and-validate.pipe.ts +32 -0
- package/src/core/common/types/plain-input.type.ts +6 -0
- package/src/core/modules/auth/core-auth.model.ts +15 -1
- package/src/core/modules/auth/core-auth.resolver.ts +1 -1
- package/src/core/modules/user/core-user.model.ts +17 -4
- package/src/core/modules/user/core-user.service.ts +59 -115
- package/src/core/modules/user/inputs/core-user-create.input.ts +5 -1
- package/src/core/modules/user/inputs/core-user.input.ts +13 -8
- package/src/core.module.ts +11 -5
- package/src/index.ts +6 -2
- package/src/server/common/models/persistence.model.ts +13 -0
- package/src/server/modules/auth/auth.model.ts +13 -0
- package/src/server/modules/user/inputs/user-create.input.ts +4 -0
- package/src/server/modules/user/inputs/user.input.ts +4 -0
- package/src/server/modules/user/user.model.ts +18 -5
- package/src/server/modules/user/user.resolver.ts +15 -19
- package/src/server/modules/user/user.service.ts +22 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.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",
|
|
@@ -13,5 +13,26 @@ export class FilterArgs extends PaginationArgs {
|
|
|
13
13
|
nullable: true,
|
|
14
14
|
})
|
|
15
15
|
@IsOptional()
|
|
16
|
-
filter?: FilterInput;
|
|
16
|
+
filter?: FilterInput = undefined;
|
|
17
|
+
|
|
18
|
+
// ===================================================================================================================
|
|
19
|
+
// Methods
|
|
20
|
+
// ===================================================================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Mapping for Subtypes
|
|
24
|
+
*/
|
|
25
|
+
map(
|
|
26
|
+
data: Partial<this> | Record<string, any>,
|
|
27
|
+
options: {
|
|
28
|
+
cloneDeep?: boolean;
|
|
29
|
+
funcAllowed?: boolean;
|
|
30
|
+
mapId?: boolean;
|
|
31
|
+
} = {}
|
|
32
|
+
): this {
|
|
33
|
+
super.map(data, options);
|
|
34
|
+
this.filter = data.filter ? FilterInput.map(data.filter, options) : undefined;
|
|
35
|
+
Object.keys(this).forEach((key) => this[key] === undefined && delete this[key]);
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
17
38
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { IsOptional, Max } from 'class-validator';
|
|
2
2
|
import { ArgsType, Field, Int } from '@nestjs/graphql';
|
|
3
|
+
import { ModelHelper } from '../helpers/model.helper';
|
|
4
|
+
import { CoreInput } from '../inputs/core-input.input';
|
|
3
5
|
import { SortInput } from '../inputs/sort.input';
|
|
4
6
|
|
|
5
7
|
@ArgsType()
|
|
6
|
-
export class PaginationArgs {
|
|
8
|
+
export class PaginationArgs extends CoreInput {
|
|
7
9
|
/**
|
|
8
10
|
* Limit for pagination
|
|
9
11
|
*/
|
|
@@ -14,7 +16,7 @@ export class PaginationArgs {
|
|
|
14
16
|
})
|
|
15
17
|
@IsOptional()
|
|
16
18
|
@Max(100)
|
|
17
|
-
limit?: number =
|
|
19
|
+
limit?: number = undefined;
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Offset for pagination
|
|
@@ -25,7 +27,7 @@ export class PaginationArgs {
|
|
|
25
27
|
defaultValue: 0,
|
|
26
28
|
})
|
|
27
29
|
@IsOptional()
|
|
28
|
-
offset?: number =
|
|
30
|
+
offset?: number = undefined;
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
33
|
* Alias for offset
|
|
@@ -33,10 +35,10 @@ export class PaginationArgs {
|
|
|
33
35
|
@Field((type) => Int, {
|
|
34
36
|
description: 'Alias for offset',
|
|
35
37
|
nullable: true,
|
|
36
|
-
defaultValue:
|
|
38
|
+
defaultValue: undefined,
|
|
37
39
|
})
|
|
38
40
|
@IsOptional()
|
|
39
|
-
skip?: number =
|
|
41
|
+
skip?: number = undefined;
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
44
|
* Sorting for pagination
|
|
@@ -46,7 +48,7 @@ export class PaginationArgs {
|
|
|
46
48
|
nullable: true,
|
|
47
49
|
})
|
|
48
50
|
@IsOptional()
|
|
49
|
-
sort?: SortInput[];
|
|
51
|
+
sort?: SortInput[] = undefined;
|
|
50
52
|
|
|
51
53
|
/**
|
|
52
54
|
* Alias for limit
|
|
@@ -58,5 +60,38 @@ export class PaginationArgs {
|
|
|
58
60
|
})
|
|
59
61
|
@IsOptional()
|
|
60
62
|
@Max(100)
|
|
61
|
-
take?: number =
|
|
63
|
+
take?: number = undefined;
|
|
64
|
+
|
|
65
|
+
// ===================================================================================================================
|
|
66
|
+
// Methods
|
|
67
|
+
// ===================================================================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Initialize instance with default values instead of undefined
|
|
71
|
+
*/
|
|
72
|
+
init(): this {
|
|
73
|
+
super.init();
|
|
74
|
+
this.limit = this.limit === undefined ? 25 : this.limit;
|
|
75
|
+
this.offset = this.offset === undefined ? 0 : this.offset;
|
|
76
|
+
this.skip = this.skip === undefined ? 0 : this.skip;
|
|
77
|
+
this.take = this.take === undefined ? 0 : this.take;
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Mapping for Subtypes
|
|
83
|
+
*/
|
|
84
|
+
map(
|
|
85
|
+
data: Partial<this> | Record<string, any>,
|
|
86
|
+
options: {
|
|
87
|
+
cloneDeep?: boolean;
|
|
88
|
+
funcAllowed?: boolean;
|
|
89
|
+
mapId?: boolean;
|
|
90
|
+
} = {}
|
|
91
|
+
): this {
|
|
92
|
+
super.map(data, options);
|
|
93
|
+
this.sort = ModelHelper.maps(data.sort, SortInput, options.cloneDeep);
|
|
94
|
+
Object.keys(this).forEach((key) => this[key] === undefined && delete this[key]);
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
62
97
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
+
import { UnauthorizedException } from '@nestjs/common';
|
|
2
3
|
import { RoleEnum } from '../enums/role.enum';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -31,8 +32,15 @@ export const getRestricted = (object: unknown, propertyKey: string) => {
|
|
|
31
32
|
export const checkRestricted = (
|
|
32
33
|
data: any,
|
|
33
34
|
user: { id: any; hasRole: (roles: string[]) => boolean },
|
|
35
|
+
options: { ignoreUndefined?: boolean; throwError?: boolean } = {},
|
|
34
36
|
processedObjects: any[] = []
|
|
35
37
|
) => {
|
|
38
|
+
const config = {
|
|
39
|
+
ignoreUndefined: true,
|
|
40
|
+
throwError: true,
|
|
41
|
+
...options,
|
|
42
|
+
};
|
|
43
|
+
|
|
36
44
|
// Primitives
|
|
37
45
|
if (!data || typeof data !== 'object') {
|
|
38
46
|
return data;
|
|
@@ -47,11 +55,16 @@ export const checkRestricted = (
|
|
|
47
55
|
// Array
|
|
48
56
|
if (Array.isArray(data)) {
|
|
49
57
|
// Check array items
|
|
50
|
-
return data.map((item) => checkRestricted(item, user, processedObjects));
|
|
58
|
+
return data.map((item) => checkRestricted(item, user, options, processedObjects));
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
// Object
|
|
54
62
|
for (const propertyKey of Object.keys(data)) {
|
|
63
|
+
// Check undefined
|
|
64
|
+
if (data[propertyKey] === undefined && config.ignoreUndefined) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
55
68
|
// Get roles
|
|
56
69
|
const roles = getRestricted(data, propertyKey);
|
|
57
70
|
|
|
@@ -71,18 +84,24 @@ export const checkRestricted = (
|
|
|
71
84
|
data.ownerIds.some((item) => (item.id ? item.id.toString() === userId : item.toString() === userId)))
|
|
72
85
|
)
|
|
73
86
|
) {
|
|
74
|
-
//
|
|
75
|
-
|
|
87
|
+
// The user does not have the required rights and is not the owner
|
|
88
|
+
if (config.throwError) {
|
|
89
|
+
throw new UnauthorizedException('Current user is not allowed to set ' + propertyKey);
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
76
92
|
}
|
|
77
93
|
} else {
|
|
78
94
|
// The user does not have the required rights
|
|
79
|
-
|
|
95
|
+
if (config.throwError) {
|
|
96
|
+
throw new UnauthorizedException('Current user is not allowed to set ' + propertyKey);
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
80
99
|
}
|
|
81
100
|
}
|
|
82
101
|
}
|
|
83
102
|
|
|
84
103
|
// Check property data
|
|
85
|
-
data[propertyKey] = checkRestricted(data[propertyKey], user, processedObjects);
|
|
104
|
+
data[propertyKey] = checkRestricted(data[propertyKey], user, options, processedObjects);
|
|
86
105
|
}
|
|
87
106
|
|
|
88
107
|
// Return processed data
|
|
@@ -16,15 +16,26 @@ export class Context {
|
|
|
16
16
|
|
|
17
17
|
// Init data
|
|
18
18
|
let user: { [key: string]: any };
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
let ctx: any = null;
|
|
20
|
+
try {
|
|
21
|
+
ctx = GqlExecutionContext.create(context)?.getContext();
|
|
22
|
+
} catch (e) {
|
|
23
|
+
// console.log(e);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let args: any;
|
|
27
|
+
try {
|
|
28
|
+
args = GqlExecutionContext.create(context)?.getArgs();
|
|
29
|
+
} catch (e) {
|
|
30
|
+
// console.log(e);
|
|
31
|
+
}
|
|
21
32
|
|
|
22
33
|
// Get data
|
|
23
34
|
if (ctx) {
|
|
24
35
|
// User from GraphQL context
|
|
25
36
|
user = ctx?.user || ctx?.req?.user;
|
|
26
37
|
} else {
|
|
27
|
-
const request = context.switchToHttp()
|
|
38
|
+
const request = context?.switchToHttp ? context.switchToHttp()?.getRequest() : null;
|
|
28
39
|
if (request) {
|
|
29
40
|
args = request.body;
|
|
30
41
|
|
|
@@ -13,14 +13,14 @@ export class Filter {
|
|
|
13
13
|
* Convert filter arguments to a query array
|
|
14
14
|
* @param filterArgs
|
|
15
15
|
*/
|
|
16
|
-
public static convertFilterArgsToQuery<T = any>(filterArgs: FilterArgs): [FilterQuery<T>, QueryOptions] {
|
|
16
|
+
public static convertFilterArgsToQuery<T = any>(filterArgs: Partial<FilterArgs>): [FilterQuery<T>, QueryOptions] {
|
|
17
17
|
return [Filter.generateFilterQuery(filterArgs.filter), Filter.generateFindOptions(filterArgs)];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Generate filter query
|
|
22
22
|
*/
|
|
23
|
-
public static generateFilterQuery<T = any>(filter?: FilterInput): FilterQuery<T> | any {
|
|
23
|
+
public static generateFilterQuery<T = any>(filter?: Partial<FilterInput>): FilterQuery<T> | any {
|
|
24
24
|
// Check filter
|
|
25
25
|
if (!filter) {
|
|
26
26
|
return undefined;
|
|
@@ -98,7 +98,7 @@ export class Filter {
|
|
|
98
98
|
/**
|
|
99
99
|
* Generate find options
|
|
100
100
|
*/
|
|
101
|
-
public static generateFindOptions<T = any>(filterArgs: FilterArgs): QueryOptions {
|
|
101
|
+
public static generateFindOptions<T = any>(filterArgs: Partial<FilterArgs>): QueryOptions {
|
|
102
102
|
// Check filterArgs
|
|
103
103
|
if (!filterArgs) {
|
|
104
104
|
return {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BadRequestException } from '@nestjs/common';
|
|
2
|
-
import {
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
3
|
import { validate } from 'class-validator';
|
|
4
4
|
import * as _ from 'lodash';
|
|
5
5
|
import { checkRestricted } from '../decorators/restricted.decorator';
|
|
@@ -17,21 +17,27 @@ export class InputHelper {
|
|
|
17
17
|
metatype?
|
|
18
18
|
): Promise<any> {
|
|
19
19
|
// Return value if it is only a basic type
|
|
20
|
-
if (!metatype || this.isBasicType(metatype)) {
|
|
20
|
+
if (typeof value !== 'object' || !metatype || this.isBasicType(metatype)) {
|
|
21
21
|
return value;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const errors = await validate(object);
|
|
31
|
-
if (errors.length > 0) {
|
|
32
|
-
throw new BadRequestException('Validation failed');
|
|
24
|
+
// Convert to metatype
|
|
25
|
+
if (!(value instanceof metatype)) {
|
|
26
|
+
if ((metatype as any)?.map) {
|
|
27
|
+
value = (metatype as any)?.map(value);
|
|
28
|
+
} else {
|
|
29
|
+
value = plainToInstance(metatype, value);
|
|
33
30
|
}
|
|
34
31
|
}
|
|
32
|
+
|
|
33
|
+
// Validate
|
|
34
|
+
const errors = await validate(value);
|
|
35
|
+
if (errors.length > 0) {
|
|
36
|
+
throw new BadRequestException('Validation failed');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Remove restricted values if roles are missing
|
|
40
|
+
value = checkRestricted(value, user);
|
|
35
41
|
return value;
|
|
36
42
|
}
|
|
37
43
|
|
|
@@ -11,26 +11,35 @@ export class ServiceHelper {
|
|
|
11
11
|
* Prepare input before save
|
|
12
12
|
*/
|
|
13
13
|
static async prepareInput(
|
|
14
|
-
input:
|
|
14
|
+
input: Record<string, any>,
|
|
15
15
|
currentUser: { [key: string]: any; id: string },
|
|
16
|
-
options: { [key: string]: any; create?: boolean; clone?: boolean } = {}
|
|
17
|
-
...args: any[]
|
|
16
|
+
options: { [key: string]: any; create?: boolean; clone?: boolean; removeUndefined?: boolean } = {}
|
|
18
17
|
) {
|
|
19
18
|
// Configuration
|
|
20
19
|
const config = {
|
|
21
|
-
checkRoles:
|
|
20
|
+
checkRoles: false,
|
|
22
21
|
clone: false,
|
|
23
22
|
create: false,
|
|
23
|
+
removeUndefined: false,
|
|
24
24
|
...options,
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
// Clone
|
|
27
|
+
// Clone input
|
|
28
28
|
if (config.clone) {
|
|
29
|
-
input
|
|
29
|
+
if (input.mapDeep && typeof input.mapDeep === 'function') {
|
|
30
|
+
input = Object.getPrototypeOf(input).mapDeep(input);
|
|
31
|
+
} else {
|
|
32
|
+
input = _.cloneDeep(input);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Remove undefined properties to avoid unwanted overwrites
|
|
37
|
+
if (config.removeUndefined) {
|
|
38
|
+
Object.keys(input).forEach((key) => input[key] === undefined && delete input[key]);
|
|
30
39
|
}
|
|
31
40
|
|
|
32
41
|
// Process roles
|
|
33
|
-
if (
|
|
42
|
+
if (config.checkRoles && input.roles && (!currentUser?.hasRole || !currentUser.hasRole(RoleEnum.ADMIN))) {
|
|
34
43
|
if (!(currentUser as any)?.roles) {
|
|
35
44
|
throw new UnauthorizedException('Missing roles of current user');
|
|
36
45
|
} else {
|
|
@@ -67,37 +76,51 @@ export class ServiceHelper {
|
|
|
67
76
|
*/
|
|
68
77
|
static async prepareOutput<T = Record<string, any>>(
|
|
69
78
|
output: any,
|
|
70
|
-
|
|
71
|
-
userService: any,
|
|
72
|
-
options: { [key: string]: any; clone?: boolean; targetModel?: Partial<T> } = {},
|
|
73
|
-
...args: any[]
|
|
79
|
+
options: { [key: string]: any; clone?: boolean; removeUndefined?: boolean; targetModel?: Partial<T> } = {}
|
|
74
80
|
) {
|
|
75
81
|
// Configuration
|
|
76
82
|
const config = {
|
|
77
|
-
clone:
|
|
83
|
+
clone: false,
|
|
84
|
+
removeUndefined: false,
|
|
85
|
+
targetModel: undefined,
|
|
78
86
|
...options,
|
|
79
87
|
};
|
|
80
88
|
|
|
81
89
|
// Clone output
|
|
82
90
|
if (config.clone) {
|
|
83
|
-
output
|
|
91
|
+
if (output.cloneDeep && typeof output.cloneDeep === 'function') {
|
|
92
|
+
output = Object.getPrototypeOf(output).cloneDeep(output);
|
|
93
|
+
} else {
|
|
94
|
+
output = _.cloneDeep(output);
|
|
95
|
+
}
|
|
84
96
|
}
|
|
85
97
|
|
|
86
98
|
// Map output if target model exist
|
|
87
|
-
if (
|
|
88
|
-
(
|
|
99
|
+
if (config.targetModel) {
|
|
100
|
+
output = (config.targetModel as any).map(output);
|
|
89
101
|
}
|
|
90
102
|
|
|
91
103
|
// Remove password if exists
|
|
92
|
-
|
|
104
|
+
if (output.password) {
|
|
105
|
+
output.password = undefined;
|
|
106
|
+
}
|
|
93
107
|
|
|
94
108
|
// Remove verification token if exists
|
|
95
|
-
|
|
109
|
+
if (output.verificationToken) {
|
|
110
|
+
output.verificationToken = undefined;
|
|
111
|
+
}
|
|
96
112
|
|
|
97
113
|
// Remove password reset token if exists
|
|
98
|
-
|
|
114
|
+
if (output.passwordResetToken) {
|
|
115
|
+
output.passwordResetToken = undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Remove undefined properties to avoid unwanted overwrites
|
|
119
|
+
if (config.removeUndefined) {
|
|
120
|
+
Object.keys(output).forEach((key) => output[key] === undefined && delete output[key]);
|
|
121
|
+
}
|
|
99
122
|
|
|
100
|
-
// Return prepared
|
|
123
|
+
// Return prepared output
|
|
101
124
|
return output;
|
|
102
125
|
}
|
|
103
126
|
}
|
|
@@ -1,26 +1,47 @@
|
|
|
1
1
|
import { Field, InputType } from '@nestjs/graphql';
|
|
2
2
|
import { LogicalOperatorEnum } from '../enums/logical-operator.enum';
|
|
3
|
+
import { ModelHelper } from '../helpers/model.helper';
|
|
4
|
+
import { CoreInput } from './core-input.input';
|
|
3
5
|
import { FilterInput } from './filter.input';
|
|
4
6
|
|
|
5
7
|
@InputType({
|
|
6
8
|
description: 'Combination of multiple filters via logical operator',
|
|
7
9
|
})
|
|
8
|
-
export class CombinedFilterInput {
|
|
10
|
+
export class CombinedFilterInput extends CoreInput {
|
|
9
11
|
/**
|
|
10
|
-
* Logical Operator to combine filters
|
|
12
|
+
* Logical Operator to combine filters
|
|
11
13
|
*/
|
|
12
14
|
@Field((type) => LogicalOperatorEnum, {
|
|
13
|
-
description: 'Logical Operator to combine filters
|
|
14
|
-
nullable: true,
|
|
15
|
+
description: 'Logical Operator to combine filters',
|
|
15
16
|
})
|
|
16
|
-
logicalOperator
|
|
17
|
+
logicalOperator: LogicalOperatorEnum = undefined;
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
|
-
* Filters to combine via logical operator
|
|
20
|
+
* Filters to combine via logical operator
|
|
20
21
|
*/
|
|
21
22
|
@Field((type) => [FilterInput], {
|
|
22
|
-
description: 'Filters to combine via logical operator
|
|
23
|
-
nullable: true,
|
|
23
|
+
description: 'Filters to combine via logical operator',
|
|
24
24
|
})
|
|
25
|
-
filters: FilterInput[];
|
|
25
|
+
filters: FilterInput[] = undefined;
|
|
26
|
+
|
|
27
|
+
// ===================================================================================================================
|
|
28
|
+
// Methods
|
|
29
|
+
// ===================================================================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Mapping for Subtypes
|
|
33
|
+
*/
|
|
34
|
+
map(
|
|
35
|
+
data: Partial<this> | Record<string, any>,
|
|
36
|
+
options: {
|
|
37
|
+
cloneDeep?: boolean;
|
|
38
|
+
funcAllowed?: boolean;
|
|
39
|
+
mapId?: boolean;
|
|
40
|
+
} = {}
|
|
41
|
+
): this {
|
|
42
|
+
super.map(data, options);
|
|
43
|
+
this.filters = ModelHelper.maps(data.filters, FilterInput, options.cloneDeep);
|
|
44
|
+
Object.keys(this).forEach((key) => this[key] === undefined && delete this[key]);
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
26
47
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ModelHelper } from '../helpers/model.helper';
|
|
2
|
+
import { CoreModel } from '../models/core-model.model';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Core Input
|
|
6
|
+
*
|
|
7
|
+
* All properties (in this class and all classes that extend this class) must be initialized with undefined!
|
|
8
|
+
*
|
|
9
|
+
* In contrast to the core model, properties that are undefined are completely removed from the instance during mapping,
|
|
10
|
+
* so that no existing data is overwritten when a data set is updated, for example. However, re-mapping causes the
|
|
11
|
+
* removed properties to be ignored and no longer automatically integrated into the instance. They must then be
|
|
12
|
+
* reactivated by direct assignment `instance.property = ...`.
|
|
13
|
+
*/
|
|
14
|
+
export abstract class CoreInput extends CoreModel {
|
|
15
|
+
/**
|
|
16
|
+
* Map method
|
|
17
|
+
*/
|
|
18
|
+
public map(
|
|
19
|
+
data: Partial<this> | Record<string, any>,
|
|
20
|
+
options: {
|
|
21
|
+
cloneDeep?: boolean;
|
|
22
|
+
funcAllowed?: boolean;
|
|
23
|
+
mapId?: boolean;
|
|
24
|
+
} = {}
|
|
25
|
+
): this {
|
|
26
|
+
const config = {
|
|
27
|
+
cloneDeep: false,
|
|
28
|
+
funcAllowed: false,
|
|
29
|
+
mapId: false,
|
|
30
|
+
...options,
|
|
31
|
+
};
|
|
32
|
+
const coreInput = ModelHelper.map(data, this, config);
|
|
33
|
+
Object.keys(coreInput).forEach((key) => coreInput[key] === undefined && delete coreInput[key]);
|
|
34
|
+
return coreInput;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Field, InputType } from '@nestjs/graphql';
|
|
2
|
+
import { ModelHelper } from '../helpers/model.helper';
|
|
2
3
|
import { CombinedFilterInput } from './combined-filter.input';
|
|
4
|
+
import { CoreInput } from './core-input.input';
|
|
3
5
|
import { SingleFilterInput } from './single-filter.input';
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -8,7 +10,7 @@ import { SingleFilterInput } from './single-filter.input';
|
|
|
8
10
|
@InputType({
|
|
9
11
|
description: 'Input for filtering. The `singleFilter` will be ignored if the `combinedFilter` is set.',
|
|
10
12
|
})
|
|
11
|
-
export class FilterInput {
|
|
13
|
+
export class FilterInput extends CoreInput {
|
|
12
14
|
/**
|
|
13
15
|
* Combination of multiple filters via logical operator
|
|
14
16
|
*/
|
|
@@ -16,7 +18,7 @@ export class FilterInput {
|
|
|
16
18
|
description: 'Combination of multiple filters via logical operator',
|
|
17
19
|
nullable: true,
|
|
18
20
|
})
|
|
19
|
-
combinedFilter?: CombinedFilterInput;
|
|
21
|
+
combinedFilter?: CombinedFilterInput = undefined;
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Filter for a single property
|
|
@@ -25,5 +27,27 @@ export class FilterInput {
|
|
|
25
27
|
description: 'Filter for a single property',
|
|
26
28
|
nullable: true,
|
|
27
29
|
})
|
|
28
|
-
singleFilter?: SingleFilterInput;
|
|
30
|
+
singleFilter?: SingleFilterInput = undefined;
|
|
31
|
+
|
|
32
|
+
// ===================================================================================================================
|
|
33
|
+
// Methods
|
|
34
|
+
// ===================================================================================================================
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Mapping for Subtypes
|
|
38
|
+
*/
|
|
39
|
+
map(
|
|
40
|
+
data: Partial<this> | Record<string, any>,
|
|
41
|
+
options: {
|
|
42
|
+
cloneDeep?: boolean;
|
|
43
|
+
funcAllowed?: boolean;
|
|
44
|
+
mapId?: boolean;
|
|
45
|
+
} = {}
|
|
46
|
+
): this {
|
|
47
|
+
super.map(data, options);
|
|
48
|
+
this.combinedFilter = data.combinedFilter ? CombinedFilterInput.map(data.combinedFilter, options) : undefined;
|
|
49
|
+
this.singleFilter = data.singleFilter ? SingleFilterInput.map(data.singleFilter, options) : undefined;
|
|
50
|
+
Object.keys(this).forEach((key) => this[key] === undefined && delete this[key]);
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
29
53
|
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { Field, InputType } from '@nestjs/graphql';
|
|
2
2
|
import { ComparisonOperatorEnum } from '../enums/comparison-operator.enum';
|
|
3
3
|
import { JSON } from '../scalars/json.scalar';
|
|
4
|
+
import { CoreInput } from './core-input.input';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Input for a configuration of a filter
|
|
7
8
|
*/
|
|
8
9
|
@InputType({ description: 'Input for a configuration of a filter' })
|
|
9
|
-
export class SingleFilterInput {
|
|
10
|
+
export class SingleFilterInput extends CoreInput {
|
|
10
11
|
/**
|
|
11
12
|
* Name of the property to be used for the filter'
|
|
12
13
|
*/
|
|
13
14
|
@Field({ description: 'Name of the property to be used for the filter' })
|
|
14
|
-
field: string;
|
|
15
|
+
field: string = undefined;
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* [Negate operator](https://docs.mongodb.com/manual/reference/operator/query/not/)
|
|
@@ -20,7 +21,7 @@ export class SingleFilterInput {
|
|
|
20
21
|
description: '[Negate operator](https://docs.mongodb.com/manual/reference/operator/query/not/)',
|
|
21
22
|
nullable: true,
|
|
22
23
|
})
|
|
23
|
-
not?: boolean;
|
|
24
|
+
not?: boolean = undefined;
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* [Comparison operator](https://docs.mongodb.com/manual/reference/operator/query-comparison/)
|
|
@@ -28,7 +29,7 @@ export class SingleFilterInput {
|
|
|
28
29
|
@Field((type) => ComparisonOperatorEnum, {
|
|
29
30
|
description: '[Comparison operator](https://docs.mongodb.com/manual/reference/operator/query-comparison/)',
|
|
30
31
|
})
|
|
31
|
-
operator: ComparisonOperatorEnum;
|
|
32
|
+
operator: ComparisonOperatorEnum = undefined;
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* [Options](https://docs.mongodb.com/manual/reference/operator/query/regex/#op._S_options) for
|
|
@@ -40,8 +41,8 @@ export class SingleFilterInput {
|
|
|
40
41
|
'[REGEX](https://docs.mongodb.com/manual/reference/operator/query/regex/) operator',
|
|
41
42
|
nullable: true,
|
|
42
43
|
})
|
|
43
|
-
options?: string;
|
|
44
|
+
options?: string = undefined;
|
|
44
45
|
|
|
45
46
|
@Field((type) => JSON, { description: 'Value of the property' })
|
|
46
|
-
value: any;
|
|
47
|
+
value: any = undefined;
|
|
47
48
|
}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { Field, InputType } from '@nestjs/graphql';
|
|
2
2
|
import { SortOrderEnum } from '../enums/sort-order.emum';
|
|
3
|
+
import { CoreInput } from './core-input.input';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Sorting the returned elements
|
|
6
7
|
*/
|
|
7
8
|
@InputType({ description: 'Sorting the returned elements' })
|
|
8
|
-
export class SortInput {
|
|
9
|
+
export class SortInput extends CoreInput {
|
|
9
10
|
/**
|
|
10
11
|
* Field that is to be used for sorting
|
|
11
12
|
*/
|
|
12
13
|
@Field({ description: 'Field that is to be used for sorting' })
|
|
13
|
-
field: string;
|
|
14
|
+
field: string = undefined;
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* SortInput order of the field
|
|
17
18
|
*/
|
|
18
19
|
@Field((type) => SortOrderEnum, { description: 'SortInput order of the field' })
|
|
19
|
-
order: SortOrderEnum;
|
|
20
|
+
order: SortOrderEnum = undefined;
|
|
20
21
|
}
|
|
@@ -19,8 +19,8 @@ export class CheckResponseInterceptor implements NestInterceptor {
|
|
|
19
19
|
// Response interception
|
|
20
20
|
return next.handle().pipe(
|
|
21
21
|
map((data) => {
|
|
22
|
-
// Prepare data for current user
|
|
23
|
-
return checkRestricted(data, currentUser);
|
|
22
|
+
// Prepare response data for current user
|
|
23
|
+
return checkRestricted(data, currentUser, { throwError: false });
|
|
24
24
|
})
|
|
25
25
|
);
|
|
26
26
|
}
|