@lenne.tech/nest-server 8.6.5 → 8.6.8
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/db.helper.js +3 -2
- package/dist/core/common/helpers/db.helper.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.d.ts +2 -0
- package/dist/core/common/helpers/service.helper.js +7 -9
- package/dist/core/common/helpers/service.helper.js.map +1 -1
- package/dist/core/common/interfaces/prepare-input-options.interface.d.ts +1 -0
- package/dist/core/common/interfaces/prepare-output-options.interface.d.ts +1 -0
- package/dist/core/common/interfaces/service-options.interface.d.ts +3 -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/mailjet.service.d.ts +1 -1
- package/dist/core/common/services/mailjet.service.js +2 -2
- package/dist/core/common/services/mailjet.service.js.map +1 -1
- package/dist/core/common/services/module.service.js +14 -2
- package/dist/core/common/services/module.service.js.map +1 -1
- package/dist/core/common/types/field-selection.type.d.ts +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/core.module.js +2 -2
- package/dist/core.module.js.map +1 -1
- package/dist/server/modules/file/file.resolver.d.ts +1 -1
- package/dist/server/modules/file/file.resolver.js +4 -5
- package/dist/server/modules/file/file.resolver.js.map +1 -1
- package/dist/server/modules/user/user.model.d.ts +14 -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 +33 -32
- package/src/core/common/decorators/restricted.decorator.ts +1 -2
- package/src/core/common/helpers/db.helper.ts +7 -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 +8 -9
- package/src/core/common/interfaces/prepare-input-options.interface.ts +1 -0
- package/src/core/common/interfaces/prepare-output-options.interface.ts +1 -0
- package/src/core/common/interfaces/service-options.interface.ts +8 -2
- 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/mailjet.service.ts +1 -1
- package/src/core/common/services/module.service.ts +17 -1
- package/src/core/common/types/field-selection.type.ts +1 -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/core.module.ts +1 -1
- package/src/server/modules/file/file.resolver.ts +2 -1
- 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.8",
|
|
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",
|
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
"reinit": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i && npm run test:e2e && npm run build",
|
|
27
27
|
"reinit:force": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i --force && npm run test:e2e",
|
|
28
28
|
"reinit:legacy": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i --legacy-peer-deps && npm run test:e2e",
|
|
29
|
-
"start": "
|
|
29
|
+
"start": "npm run start:dev",
|
|
30
30
|
"stop": "./node_modules/.bin/pm2 delete nest",
|
|
31
|
+
"start:pm2": "./node_modules/.bin/grunt",
|
|
31
32
|
"start:prod": "./node_modules/.bin/grunt productive",
|
|
32
33
|
"start:nodemon": "ts-node -r tsconfig-paths/register src/main.ts",
|
|
33
34
|
"start:debug": "nodemon --config nodemon-debug.json",
|
|
@@ -56,54 +57,54 @@
|
|
|
56
57
|
"node": ">= 16.13.0"
|
|
57
58
|
},
|
|
58
59
|
"dependencies": {
|
|
59
|
-
"@apollo/gateway": "0.
|
|
60
|
-
"@nestjs/apollo": "10.0.
|
|
61
|
-
"@nestjs/common": "8.4.
|
|
62
|
-
"@nestjs/core": "8.4.
|
|
63
|
-
"@nestjs/graphql": "10.0.
|
|
64
|
-
"@nestjs/jwt": "8.0.
|
|
65
|
-
"@nestjs/mongoose": "9.
|
|
66
|
-
"@nestjs/passport": "8.2.
|
|
67
|
-
"@nestjs/platform-express": "8.4.
|
|
68
|
-
"apollo-server-core": "3.
|
|
69
|
-
"apollo-server-express": "3.
|
|
60
|
+
"@apollo/gateway": "2.0.5",
|
|
61
|
+
"@nestjs/apollo": "10.0.16",
|
|
62
|
+
"@nestjs/common": "8.4.7",
|
|
63
|
+
"@nestjs/core": "8.4.7",
|
|
64
|
+
"@nestjs/graphql": "10.0.16",
|
|
65
|
+
"@nestjs/jwt": "8.0.1",
|
|
66
|
+
"@nestjs/mongoose": "9.1.1",
|
|
67
|
+
"@nestjs/passport": "8.2.2",
|
|
68
|
+
"@nestjs/platform-express": "8.4.7",
|
|
69
|
+
"apollo-server-core": "3.9.0",
|
|
70
|
+
"apollo-server-express": "3.9.0",
|
|
70
71
|
"bcrypt": "5.0.1",
|
|
71
72
|
"class-transformer": "0.5.1",
|
|
72
73
|
"class-validator": "0.13.2",
|
|
73
74
|
"ejs": "3.1.8",
|
|
74
75
|
"graphql": "16.5.0",
|
|
75
76
|
"graphql-subscriptions": "2.0.0",
|
|
76
|
-
"graphql-upload": "
|
|
77
|
+
"graphql-upload": "15.0.1",
|
|
77
78
|
"json-to-graphql-query": "2.2.4",
|
|
78
|
-
"light-my-request": "
|
|
79
|
+
"light-my-request": "5.0.0",
|
|
79
80
|
"lodash": "4.17.21",
|
|
80
|
-
"mongodb": "4.
|
|
81
|
-
"mongoose": "6.3.
|
|
81
|
+
"mongodb": "4.7.0",
|
|
82
|
+
"mongoose": "6.3.8",
|
|
82
83
|
"multer": "1.4.4",
|
|
83
84
|
"node-mailjet": "3.4.1",
|
|
84
85
|
"nodemailer": "6.7.5",
|
|
85
86
|
"nodemon": "2.0.16",
|
|
86
|
-
"passport": "0.
|
|
87
|
+
"passport": "0.6.0",
|
|
87
88
|
"passport-jwt": "4.0.0",
|
|
88
89
|
"reflect-metadata": "0.1.13",
|
|
89
90
|
"rimraf": "3.0.2",
|
|
90
91
|
"rxjs": "7.5.5"
|
|
91
92
|
},
|
|
92
93
|
"devDependencies": {
|
|
93
|
-
"@nestjs/testing": "8.4.
|
|
94
|
+
"@nestjs/testing": "8.4.7",
|
|
94
95
|
"@types/ejs": "3.1.1",
|
|
95
|
-
"@types/jest": "
|
|
96
|
+
"@types/jest": "28.1.1",
|
|
96
97
|
"@types/lodash": "4.14.182",
|
|
97
98
|
"@types/multer": "1.4.7",
|
|
98
|
-
"@types/node": "
|
|
99
|
-
"@types/node-mailjet": "3.3.
|
|
99
|
+
"@types/node": "18.0.0",
|
|
100
|
+
"@types/node-mailjet": "3.3.9",
|
|
100
101
|
"@types/nodemailer": "6.4.4",
|
|
101
|
-
"@types/passport": "1.0.
|
|
102
|
+
"@types/passport": "1.0.9",
|
|
102
103
|
"@types/supertest": "2.0.12",
|
|
103
|
-
"@typescript-eslint/eslint-plugin": "5.
|
|
104
|
-
"@typescript-eslint/parser": "5.
|
|
104
|
+
"@typescript-eslint/eslint-plugin": "5.28.0",
|
|
105
|
+
"@typescript-eslint/parser": "5.28.0",
|
|
105
106
|
"coffeescript": "2.7.0",
|
|
106
|
-
"eslint": "8.
|
|
107
|
+
"eslint": "8.17.0",
|
|
107
108
|
"eslint-config-prettier": "8.5.0",
|
|
108
109
|
"find-file-up": "2.0.1",
|
|
109
110
|
"grunt": "1.5.3",
|
|
@@ -112,16 +113,16 @@
|
|
|
112
113
|
"grunt-contrib-watch": "1.1.0",
|
|
113
114
|
"grunt-sync": "0.8.2",
|
|
114
115
|
"husky": "8.0.1",
|
|
115
|
-
"jest": "28.1.
|
|
116
|
+
"jest": "28.1.1",
|
|
116
117
|
"pm2": "5.2.0",
|
|
117
|
-
"prettier": "2.
|
|
118
|
+
"prettier": "2.7.1",
|
|
118
119
|
"pretty-quick": "3.1.3",
|
|
119
120
|
"supertest": "6.2.3",
|
|
120
|
-
"ts-jest": "28.0.
|
|
121
|
-
"ts-morph": "
|
|
122
|
-
"ts-node": "10.
|
|
121
|
+
"ts-jest": "28.0.5",
|
|
122
|
+
"ts-morph": "15.1.0",
|
|
123
|
+
"ts-node": "10.8.1",
|
|
123
124
|
"tsconfig-paths": "4.0.0",
|
|
124
|
-
"typescript": "4.
|
|
125
|
+
"typescript": "4.7.3"
|
|
125
126
|
},
|
|
126
127
|
"jest": {
|
|
127
128
|
"collectCoverage": true,
|
|
@@ -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
|
) {
|
|
@@ -423,7 +423,9 @@ export function removeUnresolvedReferences<T = any>(
|
|
|
423
423
|
if (typeof populated === 'object') {
|
|
424
424
|
// populatedOptions is an array
|
|
425
425
|
if (Array.isArray(populatedOptions)) {
|
|
426
|
-
populatedOptions.forEach((po) =>
|
|
426
|
+
populatedOptions.forEach((po) =>
|
|
427
|
+
removeUnresolvedReferences(populated, ignoreFirst && typeof po === 'object' ? po.populate : po, false)
|
|
428
|
+
);
|
|
427
429
|
return populated;
|
|
428
430
|
}
|
|
429
431
|
|
|
@@ -462,7 +464,10 @@ export async function popAndMap<T extends CoreModel>(
|
|
|
462
464
|
let result;
|
|
463
465
|
let populateOptions: PopulateOptions[] = [];
|
|
464
466
|
if (populate) {
|
|
465
|
-
if (
|
|
467
|
+
if (
|
|
468
|
+
Array.isArray(populate) &&
|
|
469
|
+
(typeof (populate as string[])[0] === 'string' || typeof (populate as PopulateOptions[])[0]?.path === 'string')
|
|
470
|
+
) {
|
|
466
471
|
populateOptions = populate as PopulateOptions[];
|
|
467
472
|
} else if (Array.isArray(populate) && typeof (populate as SelectionNode[])[0]?.kind === 'string') {
|
|
468
473
|
populateOptions = getPopulatOptionsFromSelections(populate as SelectionNode[]);
|
|
@@ -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
|
*/
|
|
@@ -55,6 +55,7 @@ export async function prepareInput<T = any>(
|
|
|
55
55
|
currentUser: { [key: string]: any; id: string },
|
|
56
56
|
options: {
|
|
57
57
|
[key: string]: any;
|
|
58
|
+
checkRoles?: boolean;
|
|
58
59
|
create?: boolean;
|
|
59
60
|
clone?: boolean;
|
|
60
61
|
getNewArray?: boolean;
|
|
@@ -68,7 +69,7 @@ export async function prepareInput<T = any>(
|
|
|
68
69
|
clone: false,
|
|
69
70
|
create: false,
|
|
70
71
|
getNewArray: false,
|
|
71
|
-
removeUndefined:
|
|
72
|
+
removeUndefined: true,
|
|
72
73
|
...options,
|
|
73
74
|
};
|
|
74
75
|
|
|
@@ -107,11 +108,7 @@ export async function prepareInput<T = any>(
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
// Process roles
|
|
110
|
-
if (
|
|
111
|
-
config.checkRoles &&
|
|
112
|
-
(input as Record<string, any>).roles &&
|
|
113
|
-
(!currentUser?.hasRole || !currentUser.hasRole(RoleEnum.ADMIN))
|
|
114
|
-
) {
|
|
111
|
+
if (config.checkRoles && (input as Record<string, any>).roles && !currentUser?.hasRole?.(RoleEnum.ADMIN)) {
|
|
115
112
|
if (!(currentUser as any)?.roles) {
|
|
116
113
|
throw new UnauthorizedException('Missing roles of current user');
|
|
117
114
|
} else {
|
|
@@ -152,6 +149,7 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
|
|
|
152
149
|
[key: string]: any;
|
|
153
150
|
clone?: boolean;
|
|
154
151
|
getNewArray?: boolean;
|
|
152
|
+
removeSecrets?: boolean;
|
|
155
153
|
removeUndefined?: boolean;
|
|
156
154
|
targetModel?: new (...args: any[]) => T;
|
|
157
155
|
} = {}
|
|
@@ -160,6 +158,7 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
|
|
|
160
158
|
const config = {
|
|
161
159
|
clone: false,
|
|
162
160
|
getNewArray: false,
|
|
161
|
+
removeSecrets: true,
|
|
163
162
|
removeUndefined: false,
|
|
164
163
|
targetModel: undefined,
|
|
165
164
|
...options,
|
|
@@ -195,17 +194,17 @@ export async function prepareOutput<T = { [key: string]: any; map: (...args: any
|
|
|
195
194
|
}
|
|
196
195
|
|
|
197
196
|
// Remove password if exists
|
|
198
|
-
if (output.password) {
|
|
197
|
+
if (config.removeSecrets && output.password) {
|
|
199
198
|
output.password = undefined;
|
|
200
199
|
}
|
|
201
200
|
|
|
202
201
|
// Remove verification token if exists
|
|
203
|
-
if (output.verificationToken) {
|
|
202
|
+
if (config.removeSecrets && output.verificationToken) {
|
|
204
203
|
output.verificationToken = undefined;
|
|
205
204
|
}
|
|
206
205
|
|
|
207
206
|
// Remove password reset token if exists
|
|
208
|
-
if (output.passwordResetToken) {
|
|
207
|
+
if (config.removeSecrets && output.passwordResetToken) {
|
|
209
208
|
output.passwordResetToken = undefined;
|
|
210
209
|
}
|
|
211
210
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Model } from 'mongoose';
|
|
1
|
+
import { Model, PopulateOptions } from 'mongoose';
|
|
2
2
|
import { FieldSelection } from '../types/field-selection.type';
|
|
3
3
|
import { PrepareInputOptions } from './prepare-input-options.interface';
|
|
4
4
|
import { PrepareOutputOptions } from './prepare-output-options.interface';
|
|
@@ -22,15 +22,21 @@ export interface ServiceOptions {
|
|
|
22
22
|
roles?: string[];
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
// Field selection for results
|
|
25
|
+
// Field selection for results (will be overwritten by populate if populate was set)
|
|
26
26
|
fieldSelection?: FieldSelection;
|
|
27
27
|
|
|
28
|
+
// Determines whether all restrictions are ignored
|
|
29
|
+
force?: boolean;
|
|
30
|
+
|
|
28
31
|
// Overwrites type of input (array items)
|
|
29
32
|
inputType?: new (...params: any[]) => any;
|
|
30
33
|
|
|
31
34
|
// Overwrites type of output (array items)
|
|
32
35
|
outputType?: new (...params: any[]) => any;
|
|
33
36
|
|
|
37
|
+
// Alias for fieldSelection (if both are set fieldSelection is overwritten by populate)
|
|
38
|
+
populate?: PopulateOptions | (PopulateOptions | string)[];
|
|
39
|
+
|
|
34
40
|
// Process field selection
|
|
35
41
|
// If {} or not set, then the field selection runs with defaults
|
|
36
42
|
// If falsy, then the field selection will not be automatically executed
|
|
@@ -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
|
}
|
|
@@ -74,6 +74,7 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
74
74
|
const config = {
|
|
75
75
|
checkRights: true,
|
|
76
76
|
dbObject: options?.dbObject,
|
|
77
|
+
force: false,
|
|
77
78
|
input: options?.input,
|
|
78
79
|
processFieldSelection: {},
|
|
79
80
|
prepareInput: {},
|
|
@@ -82,6 +83,22 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
82
83
|
...options?.serviceOptions,
|
|
83
84
|
};
|
|
84
85
|
|
|
86
|
+
// Note force configuration
|
|
87
|
+
if (config.force) {
|
|
88
|
+
config.checkRights = false;
|
|
89
|
+
if (typeof config.prepareInput === 'object') {
|
|
90
|
+
config.prepareInput.checkRoles = false;
|
|
91
|
+
}
|
|
92
|
+
if (typeof config.prepareOutput === 'object') {
|
|
93
|
+
config.prepareOutput.removeSecrets = false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Note populate
|
|
98
|
+
if (config.populate) {
|
|
99
|
+
config.fieldSelection = config.populate;
|
|
100
|
+
}
|
|
101
|
+
|
|
85
102
|
// Prepare input
|
|
86
103
|
if (config.prepareInput && this.prepareInput) {
|
|
87
104
|
const opts = config.prepareInput;
|
|
@@ -150,7 +167,6 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
150
167
|
*/
|
|
151
168
|
async prepareInput(input: Record<string, any>, options: ServiceOptions = {}) {
|
|
152
169
|
const config = {
|
|
153
|
-
targetModel: this.mainModelConstructor,
|
|
154
170
|
...options?.prepareInput,
|
|
155
171
|
};
|
|
156
172
|
return prepareInput(input, options.currentUser, config);
|
|
@@ -5,4 +5,4 @@ import { ResolveSelector } from '../interfaces/resolve-selector.interface';
|
|
|
5
5
|
/**
|
|
6
6
|
* Field selection to set fields of (populated) result
|
|
7
7
|
*/
|
|
8
|
-
export type FieldSelection = PopulateOptions[] | SelectionNode[] | ResolveSelector;
|
|
8
|
+
export type FieldSelection = PopulateOptions | (PopulateOptions | string)[] | SelectionNode[] | ResolveSelector;
|