@lenne.tech/nest-server 8.6.5 → 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.
- package/dist/core/common/decorators/restricted.decorator.js +4 -4
- package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
- package/dist/core/common/helpers/file.helper.js +4 -7
- package/dist/core/common/helpers/file.helper.js.map +1 -1
- package/dist/core/common/helpers/filter.helper.d.ts +7 -0
- package/dist/core/common/helpers/filter.helper.js +38 -1
- package/dist/core/common/helpers/filter.helper.js.map +1 -1
- package/dist/core/common/helpers/input.helper.d.ts +6 -0
- package/dist/core/common/helpers/input.helper.js +28 -6
- package/dist/core/common/helpers/input.helper.js.map +1 -1
- package/dist/core/common/helpers/service.helper.js +3 -5
- package/dist/core/common/helpers/service.helper.js.map +1 -1
- package/dist/core/common/models/core-model.model.js +4 -1
- package/dist/core/common/models/core-model.model.js.map +1 -1
- package/dist/core/common/services/crud.service.js +9 -4
- package/dist/core/common/services/crud.service.js.map +1 -1
- package/dist/core/common/services/module.service.js +1 -1
- package/dist/core/common/services/module.service.js.map +1 -1
- package/dist/core/modules/auth/guards/roles.guard.js +2 -1
- package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
- package/dist/core/modules/user/core-user.service.js +9 -13
- package/dist/core/modules/user/core-user.service.js.map +1 -1
- package/dist/server/modules/user/user.model.d.ts +1 -1
- package/dist/server/modules/user/user.resolver.js +2 -1
- package/dist/server/modules/user/user.resolver.js.map +1 -1
- package/dist/test/test.helper.js +11 -25
- package/dist/test/test.helper.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +14 -14
- package/src/core/common/decorators/restricted.decorator.ts +1 -2
- package/src/core/common/helpers/file.helper.ts +9 -11
- package/src/core/common/helpers/filter.helper.ts +66 -1
- package/src/core/common/helpers/input.helper.ts +61 -2
- package/src/core/common/helpers/service.helper.ts +2 -6
- package/src/core/common/models/core-model.model.ts +4 -1
- package/src/core/common/services/crud.service.ts +7 -4
- package/src/core/common/services/module.service.ts +0 -1
- package/src/core/modules/auth/guards/roles.guard.ts +1 -1
- package/src/core/modules/user/core-user.service.ts +13 -22
- package/src/server/modules/user/user.resolver.ts +1 -1
- package/src/test/test.helper.ts +27 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "8.6.
|
|
3
|
+
"version": "8.6.6",
|
|
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",
|
|
@@ -57,12 +57,12 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@apollo/gateway": "0.50.2",
|
|
60
|
-
"@nestjs/apollo": "10.0.
|
|
60
|
+
"@nestjs/apollo": "10.0.12",
|
|
61
61
|
"@nestjs/common": "8.4.5",
|
|
62
62
|
"@nestjs/core": "8.4.5",
|
|
63
|
-
"@nestjs/graphql": "10.0.
|
|
64
|
-
"@nestjs/jwt": "8.0.
|
|
65
|
-
"@nestjs/mongoose": "9.0
|
|
63
|
+
"@nestjs/graphql": "10.0.12",
|
|
64
|
+
"@nestjs/jwt": "8.0.1",
|
|
65
|
+
"@nestjs/mongoose": "9.1.0",
|
|
66
66
|
"@nestjs/passport": "8.2.1",
|
|
67
67
|
"@nestjs/platform-express": "8.4.5",
|
|
68
68
|
"apollo-server-core": "3.7.0",
|
|
@@ -75,15 +75,15 @@
|
|
|
75
75
|
"graphql-subscriptions": "2.0.0",
|
|
76
76
|
"graphql-upload": "13.0.0",
|
|
77
77
|
"json-to-graphql-query": "2.2.4",
|
|
78
|
-
"light-my-request": "
|
|
78
|
+
"light-my-request": "5.0.0",
|
|
79
79
|
"lodash": "4.17.21",
|
|
80
80
|
"mongodb": "4.6.0",
|
|
81
|
-
"mongoose": "6.3.
|
|
81
|
+
"mongoose": "6.3.4",
|
|
82
82
|
"multer": "1.4.4",
|
|
83
83
|
"node-mailjet": "3.4.1",
|
|
84
84
|
"nodemailer": "6.7.5",
|
|
85
85
|
"nodemon": "2.0.16",
|
|
86
|
-
"passport": "0.5.
|
|
86
|
+
"passport": "0.5.3",
|
|
87
87
|
"passport-jwt": "4.0.0",
|
|
88
88
|
"reflect-metadata": "0.1.13",
|
|
89
89
|
"rimraf": "3.0.2",
|
|
@@ -95,15 +95,15 @@
|
|
|
95
95
|
"@types/jest": "27.5.1",
|
|
96
96
|
"@types/lodash": "4.14.182",
|
|
97
97
|
"@types/multer": "1.4.7",
|
|
98
|
-
"@types/node": "16.11.
|
|
99
|
-
"@types/node-mailjet": "3.3.
|
|
98
|
+
"@types/node": "16.11.36",
|
|
99
|
+
"@types/node-mailjet": "3.3.9",
|
|
100
100
|
"@types/nodemailer": "6.4.4",
|
|
101
101
|
"@types/passport": "1.0.7",
|
|
102
102
|
"@types/supertest": "2.0.12",
|
|
103
|
-
"@typescript-eslint/eslint-plugin": "5.
|
|
104
|
-
"@typescript-eslint/parser": "5.
|
|
103
|
+
"@typescript-eslint/eslint-plugin": "5.25.0",
|
|
104
|
+
"@typescript-eslint/parser": "5.25.0",
|
|
105
105
|
"coffeescript": "2.7.0",
|
|
106
|
-
"eslint": "8.
|
|
106
|
+
"eslint": "8.16.0",
|
|
107
107
|
"eslint-config-prettier": "8.5.0",
|
|
108
108
|
"find-file-up": "2.0.1",
|
|
109
109
|
"grunt": "1.5.3",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"supertest": "6.2.3",
|
|
120
120
|
"ts-jest": "28.0.2",
|
|
121
121
|
"ts-morph": "14.0.0",
|
|
122
|
-
"ts-node": "10.
|
|
122
|
+
"ts-node": "10.8.0",
|
|
123
123
|
"tsconfig-paths": "4.0.0",
|
|
124
124
|
"typescript": "4.6.4"
|
|
125
125
|
},
|
|
@@ -118,9 +118,8 @@ export const checkRestricted = (
|
|
|
118
118
|
|
|
119
119
|
// Check roles
|
|
120
120
|
if (roles.length) {
|
|
121
|
-
// Check roles
|
|
122
121
|
if (
|
|
123
|
-
user?.hasRole(roles) ||
|
|
122
|
+
user?.hasRole?.(roles) ||
|
|
124
123
|
(user?.id && roles.includes(RoleEnum.S_USER)) ||
|
|
125
124
|
(roles.includes(RoleEnum.S_CREATOR) && getIncludedIds(config.dbObject?.createdBy, user))
|
|
126
125
|
) {
|
|
@@ -75,23 +75,21 @@ export function multerOptionsForImageUpload(options: {
|
|
|
75
75
|
fileSize?: number;
|
|
76
76
|
fileTypeRegex?: RegExp;
|
|
77
77
|
}): MulterOptions {
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
options
|
|
85
|
-
);
|
|
78
|
+
// Set config
|
|
79
|
+
const config = {
|
|
80
|
+
fileSize: 1024 * 1024, // 1MB
|
|
81
|
+
fileTypeRegex: /jpeg|jpg|png/, // Images only
|
|
82
|
+
...options,
|
|
83
|
+
};
|
|
86
84
|
|
|
87
85
|
return {
|
|
88
86
|
// File filter
|
|
89
|
-
fileFilter:
|
|
87
|
+
fileFilter: config.fileTypeRegex ? multerFileFilter(config.fileTypeRegex) : undefined,
|
|
90
88
|
|
|
91
89
|
// Limits
|
|
92
90
|
limits: {
|
|
93
91
|
// Limit of file size
|
|
94
|
-
fileSize:
|
|
92
|
+
fileSize: config.fileSize ? config.fileSize : undefined,
|
|
95
93
|
},
|
|
96
94
|
|
|
97
95
|
// Automatic storage handling
|
|
@@ -100,7 +98,7 @@ export function multerOptionsForImageUpload(options: {
|
|
|
100
98
|
// Destination for uploaded file
|
|
101
99
|
// If destination is not set file will be buffered and can be processed
|
|
102
100
|
// in the method
|
|
103
|
-
destination:
|
|
101
|
+
destination: config.destination ? config.destination : undefined,
|
|
104
102
|
|
|
105
103
|
// Generated random file name
|
|
106
104
|
filename: multerRandomFileName(),
|
|
@@ -4,6 +4,8 @@ import { ComparisonOperatorEnum } from '../enums/comparison-operator.enum';
|
|
|
4
4
|
import { LogicalOperatorEnum } from '../enums/logical-operator.enum';
|
|
5
5
|
import { FilterInput } from '../inputs/filter.input';
|
|
6
6
|
import { SortInput } from '../inputs/sort.input';
|
|
7
|
+
import { getObjectIds } from './db.helper';
|
|
8
|
+
import { assignPlain } from './input.helper';
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Helper for filter handling
|
|
@@ -33,9 +35,72 @@ export class Filter {
|
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Helper function to create $and, $or or $nor filter
|
|
40
|
+
*/
|
|
41
|
+
export function findFilter(options?: {
|
|
42
|
+
conditions?: Record<string, any>[];
|
|
43
|
+
filterOptions?: FilterQuery<any>;
|
|
44
|
+
id?: any;
|
|
45
|
+
ids?: any[];
|
|
46
|
+
type?: '$and' | '$or' | '$nor';
|
|
47
|
+
}): FilterQuery<any> {
|
|
48
|
+
const config = {
|
|
49
|
+
type: '$and',
|
|
50
|
+
...options,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Init filter Option
|
|
54
|
+
let filterOptions: FilterQuery<any> = config?.filterOptions;
|
|
55
|
+
|
|
56
|
+
// Check where condition
|
|
57
|
+
if (!filterOptions) {
|
|
58
|
+
filterOptions = {};
|
|
59
|
+
filterOptions[config.type] = [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Convert where condition to array
|
|
63
|
+
if (!Array.isArray(filterOptions?.[config.type])) {
|
|
64
|
+
filterOptions = {};
|
|
65
|
+
filterOptions[config.type] = [config?.filterOptions];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ObjectId
|
|
69
|
+
if (config?.id) {
|
|
70
|
+
filterOptions[config.type].push({ _id: getObjectIds(config.id) });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ObjectIds
|
|
74
|
+
if (config?.ids) {
|
|
75
|
+
if (!Array.isArray(config.ids)) {
|
|
76
|
+
config.ids = [config.ids];
|
|
77
|
+
}
|
|
78
|
+
filterOptions[config.type].push({ _id: { $in: getObjectIds(config.ids) } });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Integrate conditions
|
|
82
|
+
if (config?.conditions) {
|
|
83
|
+
filterOptions[config.type] = [...filterOptions[config.type], ...config.conditions];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Filter falsy values
|
|
87
|
+
filterOptions[config.type] = filterOptions[config.type].filter((value) => value);
|
|
88
|
+
|
|
89
|
+
// Optimizations
|
|
90
|
+
if (!filterOptions[config.type].length) {
|
|
91
|
+
filterOptions = {};
|
|
92
|
+
} else if (filterOptions[config.type].length === 1) {
|
|
93
|
+
const additionalProperties = filterOptions[config.type][0];
|
|
94
|
+
delete filterOptions[config.type];
|
|
95
|
+
assignPlain(filterOptions, additionalProperties);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Return filter config
|
|
99
|
+
return filterOptions;
|
|
100
|
+
}
|
|
101
|
+
|
|
36
102
|
/**
|
|
37
103
|
* Convert filter arguments to a query array
|
|
38
|
-
* @param filterArgs
|
|
39
104
|
*/
|
|
40
105
|
export function convertFilterArgsToQuery<T = any>(filterArgs: Partial<FilterArgs>): [FilterQuery<T>, QueryOptions] {
|
|
41
106
|
return [generateFilterQuery(filterArgs?.filter), generateFindOptions(filterArgs)];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
|
2
2
|
import { plainToInstance } from 'class-transformer';
|
|
3
3
|
import { validate } from 'class-validator';
|
|
4
|
+
import { ValidatorOptions } from 'class-validator/types/validation/ValidatorOptions';
|
|
4
5
|
import * as _ from 'lodash';
|
|
5
6
|
import { checkRestricted } from '../decorators/restricted.decorator';
|
|
6
7
|
import { ProcessType } from '../enums/process-type.enum';
|
|
@@ -182,6 +183,24 @@ export default class InputHelper {
|
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Assign plain objects to the target object and ignores undefined
|
|
188
|
+
*/
|
|
189
|
+
export function assignPlain(target: Record<any, any>, ...args: Record<any, any>[]): any {
|
|
190
|
+
return Object.assign(
|
|
191
|
+
target,
|
|
192
|
+
...args.map(
|
|
193
|
+
// Prepare records
|
|
194
|
+
(item) =>
|
|
195
|
+
!item
|
|
196
|
+
? // Return item if not an object
|
|
197
|
+
item
|
|
198
|
+
: // Return cloned record with undefined properties removed
|
|
199
|
+
filterProperties(JSON.parse(JSON.stringify(item)), (prop) => prop !== undefined)
|
|
200
|
+
)
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
185
204
|
/**
|
|
186
205
|
* Check input
|
|
187
206
|
*/
|
|
@@ -194,11 +213,16 @@ export async function check(
|
|
|
194
213
|
processType?: ProcessType;
|
|
195
214
|
roles?: string | string[];
|
|
196
215
|
throwError?: boolean;
|
|
216
|
+
validatorOptions?: ValidatorOptions;
|
|
197
217
|
}
|
|
198
218
|
): Promise<any> {
|
|
199
219
|
const config = {
|
|
200
220
|
throwError: true,
|
|
201
221
|
...options,
|
|
222
|
+
validatorOptions: {
|
|
223
|
+
skipUndefinedProperties: true,
|
|
224
|
+
...options?.validatorOptions,
|
|
225
|
+
},
|
|
202
226
|
};
|
|
203
227
|
|
|
204
228
|
// Check roles
|
|
@@ -212,7 +236,7 @@ export async function check(
|
|
|
212
236
|
// check if user is logged in
|
|
213
237
|
(roles.includes(RoleEnum.S_USER) && user?.id) ||
|
|
214
238
|
// check if the user has at least one of the required roles
|
|
215
|
-
user
|
|
239
|
+
user?.hasRole?.(roles) ||
|
|
216
240
|
// check if the user is the creator
|
|
217
241
|
(roles.includes(RoleEnum.S_CREATOR) && equalIds(config.dbObject?.createdBy, user))
|
|
218
242
|
) {
|
|
@@ -254,7 +278,7 @@ export async function check(
|
|
|
254
278
|
}
|
|
255
279
|
|
|
256
280
|
// Validate
|
|
257
|
-
const errors = await validate(value);
|
|
281
|
+
const errors = await validate(value, config.validatorOptions);
|
|
258
282
|
if (errors.length > 0 && config.throwError) {
|
|
259
283
|
throw new BadRequestException('Validation failed');
|
|
260
284
|
}
|
|
@@ -264,6 +288,13 @@ export async function check(
|
|
|
264
288
|
return value;
|
|
265
289
|
}
|
|
266
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Combines objects to a new single plain object and ignores undefined
|
|
293
|
+
*/
|
|
294
|
+
export function combinePlain(...args: Record<any, any>[]): any {
|
|
295
|
+
return assignPlain({}, ...args);
|
|
296
|
+
}
|
|
297
|
+
|
|
267
298
|
/**
|
|
268
299
|
* Standard error function
|
|
269
300
|
*/
|
|
@@ -273,6 +304,18 @@ export function errorFunction(caller: (...params) => any, message = 'Required pa
|
|
|
273
304
|
throw err;
|
|
274
305
|
}
|
|
275
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Filter function for objects
|
|
309
|
+
*/
|
|
310
|
+
export function filterProperties<T = Record<string, any>>(
|
|
311
|
+
obj: T,
|
|
312
|
+
filterFunction: (value?: any, key?: string, obj?: T) => boolean
|
|
313
|
+
): Partial<T> {
|
|
314
|
+
return Object.keys(obj)
|
|
315
|
+
.filter((key) => filterFunction(obj[key], key, obj))
|
|
316
|
+
.reduce((res, key) => Object.assign(res, { [key]: obj[key] }), {});
|
|
317
|
+
}
|
|
318
|
+
|
|
276
319
|
/**
|
|
277
320
|
* Check if parameter is an array
|
|
278
321
|
*/
|
|
@@ -433,6 +476,22 @@ export function returnFalse(): boolean {
|
|
|
433
476
|
return false;
|
|
434
477
|
}
|
|
435
478
|
|
|
479
|
+
/**
|
|
480
|
+
* Match function to use instead of switch case
|
|
481
|
+
* Inspired by https://yusfuu.medium.com/dont-use-switch-or-if-else-in-javascript-instead-try-this-82f32616c269
|
|
482
|
+
*
|
|
483
|
+
* Example:
|
|
484
|
+
* const matched = match(expr, {
|
|
485
|
+
* Oranges: 'Oranges are $0.59 a pound.',
|
|
486
|
+
* Mangoes: 'Mangoes and papayas are $2.79 a pound.',
|
|
487
|
+
* Papayas: 'Mangoes and papayas are $2.79 a pound.',
|
|
488
|
+
* default: `Sorry, we are out of ${expr}.`,
|
|
489
|
+
* });
|
|
490
|
+
*/
|
|
491
|
+
export function match(expression: any, cases: Record<any, any>): any {
|
|
492
|
+
return cases[expression] || cases?.default;
|
|
493
|
+
}
|
|
494
|
+
|
|
436
495
|
/**
|
|
437
496
|
* Map values into specific type
|
|
438
497
|
*/
|
|
@@ -68,7 +68,7 @@ export async function prepareInput<T = any>(
|
|
|
68
68
|
clone: false,
|
|
69
69
|
create: false,
|
|
70
70
|
getNewArray: false,
|
|
71
|
-
removeUndefined:
|
|
71
|
+
removeUndefined: true,
|
|
72
72
|
...options,
|
|
73
73
|
};
|
|
74
74
|
|
|
@@ -107,11 +107,7 @@ export async function prepareInput<T = any>(
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
// Process roles
|
|
110
|
-
if (
|
|
111
|
-
config.checkRoles &&
|
|
112
|
-
(input as Record<string, any>).roles &&
|
|
113
|
-
(!currentUser?.hasRole || !currentUser.hasRole(RoleEnum.ADMIN))
|
|
114
|
-
) {
|
|
110
|
+
if (config.checkRoles && (input as Record<string, any>).roles && !currentUser?.hasRole?.(RoleEnum.ADMIN)) {
|
|
115
111
|
if (!(currentUser as any)?.roles) {
|
|
116
112
|
throw new UnauthorizedException('Missing roles of current user');
|
|
117
113
|
} else {
|
|
@@ -92,7 +92,10 @@ export abstract class CoreModel {
|
|
|
92
92
|
mapId: false,
|
|
93
93
|
...options,
|
|
94
94
|
};
|
|
95
|
-
|
|
95
|
+
if (config.init) {
|
|
96
|
+
this.init(config.init);
|
|
97
|
+
}
|
|
98
|
+
return map(data, this, config);
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
/**
|
|
@@ -2,6 +2,7 @@ import { NotFoundException } from '@nestjs/common';
|
|
|
2
2
|
import { FilterArgs } from '../args/filter.args';
|
|
3
3
|
import { merge } from '../helpers/config.helper';
|
|
4
4
|
import { convertFilterArgsToQuery } from '../helpers/filter.helper';
|
|
5
|
+
import { assignPlain } from '../helpers/input.helper';
|
|
5
6
|
import { ServiceOptions } from '../interfaces/service-options.interface';
|
|
6
7
|
import { CoreModel } from '../models/core-model.model';
|
|
7
8
|
import { ModuleService } from './module.service';
|
|
@@ -14,7 +15,8 @@ export abstract class CrudService<T extends CoreModel = any> extends ModuleServi
|
|
|
14
15
|
merge({ prepareInput: { create: true } }, serviceOptions);
|
|
15
16
|
return this.process(
|
|
16
17
|
async (data) => {
|
|
17
|
-
|
|
18
|
+
const currentUserId = serviceOptions?.currentUser?.id;
|
|
19
|
+
return new this.mainDbModel({ ...data.input, createdBy: currentUserId, updatedBy: currentUserId }).save();
|
|
18
20
|
},
|
|
19
21
|
{ input, serviceOptions }
|
|
20
22
|
);
|
|
@@ -75,7 +77,8 @@ export abstract class CrudService<T extends CoreModel = any> extends ModuleServi
|
|
|
75
77
|
}
|
|
76
78
|
return this.process(
|
|
77
79
|
async (data) => {
|
|
78
|
-
|
|
80
|
+
const currentUserId = serviceOptions?.currentUser?.id;
|
|
81
|
+
return await assignPlain(dbObject, data.input, { updatedBy: currentUserId }).save();
|
|
79
82
|
},
|
|
80
83
|
{ dbObject, input, serviceOptions }
|
|
81
84
|
);
|
|
@@ -90,11 +93,11 @@ export abstract class CrudService<T extends CoreModel = any> extends ModuleServi
|
|
|
90
93
|
throw new NotFoundException(`No ${this.mainModelConstructor.name} found with ID: ${id}`);
|
|
91
94
|
}
|
|
92
95
|
return this.process(
|
|
93
|
-
async (
|
|
96
|
+
async () => {
|
|
94
97
|
await this.mainDbModel.findByIdAndDelete(id).exec();
|
|
95
98
|
return dbObject;
|
|
96
99
|
},
|
|
97
|
-
{ dbObject,
|
|
100
|
+
{ dbObject, serviceOptions }
|
|
98
101
|
);
|
|
99
102
|
}
|
|
100
103
|
}
|
|
@@ -150,7 +150,6 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
150
150
|
*/
|
|
151
151
|
async prepareInput(input: Record<string, any>, options: ServiceOptions = {}) {
|
|
152
152
|
const config = {
|
|
153
|
-
targetModel: this.mainModelConstructor,
|
|
154
153
|
...options?.prepareInput,
|
|
155
154
|
};
|
|
156
155
|
return prepareInput(input, options.currentUser, config);
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
159
|
-
dbObject.
|
|
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 (
|
|
176
|
+
async () => {
|
|
186
177
|
return await this.mainDbModel.findByIdAndUpdate(userId, { roles }).exec();
|
|
187
178
|
},
|
|
188
179
|
{ serviceOptions }
|
|
@@ -145,7 +145,7 @@ export class UserResolver {
|
|
|
145
145
|
*/
|
|
146
146
|
@Subscription(() => User, {
|
|
147
147
|
filter(this: UserResolver, payload, variables, context) {
|
|
148
|
-
return context
|
|
148
|
+
return context?.user?.hasRole?.(RoleEnum.ADMIN);
|
|
149
149
|
},
|
|
150
150
|
resolve: (user) => user,
|
|
151
151
|
})
|
package/src/test/test.helper.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { INestApplication } from '@nestjs/common';
|
|
2
2
|
import { createClient } from 'graphql-ws';
|
|
3
|
-
// import { FastifyInstance } from 'fastify';
|
|
4
3
|
import { jsonToGraphQLQuery } from 'json-to-graphql-query';
|
|
5
4
|
import * as LightMyRequest from 'light-my-request';
|
|
6
5
|
import * as supertest from 'supertest';
|
|
@@ -125,25 +124,21 @@ export class TestHelper {
|
|
|
125
124
|
|
|
126
125
|
/**
|
|
127
126
|
* GraphQL request
|
|
128
|
-
* @param graphql
|
|
129
|
-
* @param options
|
|
130
127
|
*/
|
|
131
128
|
async graphQl(graphql: string | TestGraphQLConfig, options: TestGraphQLOptions = {}): Promise<any> {
|
|
132
129
|
// Default options
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
options
|
|
143
|
-
);
|
|
130
|
+
const config = {
|
|
131
|
+
convertEnums: true,
|
|
132
|
+
countOfSubscriptionMessages: 1,
|
|
133
|
+
token: null,
|
|
134
|
+
statusCode: 200,
|
|
135
|
+
log: false,
|
|
136
|
+
logError: false,
|
|
137
|
+
...options,
|
|
138
|
+
};
|
|
144
139
|
|
|
145
140
|
// Init vars
|
|
146
|
-
const { token, statusCode, log, logError } =
|
|
141
|
+
const { token, statusCode, log, logError } = config;
|
|
147
142
|
|
|
148
143
|
// Init
|
|
149
144
|
let query = '';
|
|
@@ -188,13 +183,13 @@ export class TestHelper {
|
|
|
188
183
|
}
|
|
189
184
|
|
|
190
185
|
if ((graphql as TestGraphQLConfig).type === TestGraphQLType.SUBSCRIPTION) {
|
|
191
|
-
return this.getSubscription(graphql as TestGraphQLConfig, query,
|
|
186
|
+
return this.getSubscription(graphql as TestGraphQLConfig, query, config);
|
|
192
187
|
}
|
|
193
188
|
|
|
194
189
|
// Convert uppercase strings in arguments of query to enums
|
|
195
|
-
if (
|
|
196
|
-
if (Array.isArray(
|
|
197
|
-
for (const key of Object.values(
|
|
190
|
+
if (config.convertEnums) {
|
|
191
|
+
if (Array.isArray(config.convertEnums)) {
|
|
192
|
+
for (const key of Object.values(config.convertEnums)) {
|
|
198
193
|
const regExpStr = '(' + key + ': )\\"([_A-Z][_0-9A-Z]*)\\"';
|
|
199
194
|
const regExp = new RegExp(regExpStr, 'g');
|
|
200
195
|
query = query.replace(regExp, '$1$2');
|
|
@@ -242,28 +237,26 @@ export class TestHelper {
|
|
|
242
237
|
*/
|
|
243
238
|
async rest(url: string, options: TestRestOptions = {}): Promise<any> {
|
|
244
239
|
// Default options
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
options
|
|
255
|
-
);
|
|
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
|
+
};
|
|
256
249
|
|
|
257
250
|
// Init vars
|
|
258
|
-
const { token, statusCode, log, logError } =
|
|
251
|
+
const { token, statusCode, log, logError } = config;
|
|
259
252
|
|
|
260
253
|
// Request configuration
|
|
261
254
|
const requestConfig: LightMyRequest.InjectOptions = {
|
|
262
|
-
method:
|
|
255
|
+
method: config.method,
|
|
263
256
|
url,
|
|
264
257
|
};
|
|
265
|
-
if (
|
|
266
|
-
requestConfig.payload =
|
|
258
|
+
if (config.payload) {
|
|
259
|
+
requestConfig.payload = config.payload;
|
|
267
260
|
}
|
|
268
261
|
|
|
269
262
|
// Process response
|