@lenne.tech/nest-server 8.1.0 → 8.2.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/config.env.js +3 -2
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/decorators/restricted.decorator.d.ts +2 -0
- package/dist/core/common/decorators/restricted.decorator.js +10 -7
- package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
- package/dist/core/common/helpers/input.helper.d.ts +12 -2
- package/dist/core/common/helpers/input.helper.js +50 -11
- package/dist/core/common/helpers/input.helper.js.map +1 -1
- package/dist/core/common/interfaces/service-options.interface.d.ts +6 -0
- package/dist/core/common/models/core-model.model.d.ts +1 -1
- 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/services/crud.service.js +17 -19
- package/dist/core/common/services/crud.service.js.map +1 -1
- package/dist/core/common/services/module.service.d.ts +13 -1
- package/dist/core/common/services/module.service.js +39 -4
- package/dist/core/common/services/module.service.js.map +1 -1
- package/dist/core/common/types/ids.type.d.ts +8 -0
- package/dist/core/common/types/ids.type.js +3 -0
- package/dist/core/common/types/ids.type.js.map +1 -0
- package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
- package/dist/core/modules/user/core-user.service.js +38 -35
- package/dist/core/modules/user/core-user.service.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/server/modules/user/user.model.d.ts +1 -1
- package/dist/server/modules/user/user.resolver.d.ts +2 -2
- package/dist/server/modules/user/user.resolver.js +30 -14
- package/dist/server/modules/user/user.resolver.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +37 -37
- package/src/config.env.ts +3 -2
- package/src/core/common/decorators/restricted.decorator.ts +16 -10
- package/src/core/common/helpers/input.helper.ts +63 -14
- package/src/core/common/interfaces/service-options.interface.ts +19 -1
- package/src/core/common/models/core-model.model.ts +1 -1
- package/src/core/common/pipes/check-input.pipe.ts +1 -1
- package/src/core/common/services/crud.service.ts +17 -22
- package/src/core/common/services/module.service.ts +75 -9
- package/src/core/common/types/ids.type.ts +7 -0
- package/src/core/modules/auth/guards/roles.guard.ts +1 -1
- package/src/core/modules/user/core-user.service.ts +42 -44
- package/src/index.ts +1 -0
- package/src/server/modules/user/user.resolver.ts +26 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.2.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",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"format:staged": "pretty-quick --staged",
|
|
21
21
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
22
22
|
"prestart:prod": "npm run build",
|
|
23
|
-
"reinit": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i && npm run test:e2e",
|
|
23
|
+
"reinit": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i && npm run test:e2e && npm run build",
|
|
24
24
|
"reinit:force": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i --force && npm run test:e2e",
|
|
25
25
|
"reinit:legacy": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i --legacy-peer-deps && npm run test:e2e",
|
|
26
26
|
"start": "./node_modules/.bin/grunt",
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
"start:nodemon": "ts-node -r tsconfig-paths/register src/main.ts",
|
|
30
30
|
"start:debug": "nodemon --config nodemon-debug.json",
|
|
31
31
|
"start:dev": "nodemon",
|
|
32
|
-
"test": "jest",
|
|
33
|
-
"test:cov": "jest --coverage",
|
|
34
|
-
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
|
35
|
-
"test:e2e": "jest --config jest-e2e.json",
|
|
36
|
-
"test:e2e-cov": "jest --config jest-e2e.json --coverage",
|
|
37
|
-
"test:ci": "jest --config jest-e2e.json --ci",
|
|
38
|
-
"test:watch": "jest --watch",
|
|
32
|
+
"test": "NODE_ENV=local jest",
|
|
33
|
+
"test:cov": "NODE_ENV=local jest --coverage",
|
|
34
|
+
"test:debug": "NODE_ENV=local node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
|
35
|
+
"test:e2e": "NODE_ENV=local jest --config jest-e2e.json",
|
|
36
|
+
"test:e2e-cov": "NODE_ENV=local jest --config jest-e2e.json --coverage",
|
|
37
|
+
"test:ci": "NODE_ENV=local jest --config jest-e2e.json --ci",
|
|
38
|
+
"test:watch": "NODE_ENV=local jest --watch",
|
|
39
39
|
"prepack": "npm run prestart:prod",
|
|
40
40
|
"prepare": "husky install",
|
|
41
41
|
"prepublishOnly": "npm run format && npm run lint && npm run test:ci",
|
|
@@ -52,39 +52,32 @@
|
|
|
52
52
|
"node": ">= 16.13.0"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@apollo/gateway": "0.50.
|
|
56
|
-
"@nestjs/apollo": "10.0.
|
|
55
|
+
"@apollo/gateway": "0.50.2",
|
|
56
|
+
"@nestjs/apollo": "10.0.11",
|
|
57
57
|
"@nestjs/common": "8.4.4",
|
|
58
58
|
"@nestjs/core": "8.4.4",
|
|
59
|
-
"@nestjs/graphql": "10.0.
|
|
59
|
+
"@nestjs/graphql": "10.0.11",
|
|
60
60
|
"@nestjs/jwt": "8.0.0",
|
|
61
61
|
"@nestjs/mongoose": "9.0.3",
|
|
62
62
|
"@nestjs/passport": "8.2.1",
|
|
63
63
|
"@nestjs/platform-express": "8.4.4",
|
|
64
|
-
"@types/ejs": "3.1.0",
|
|
65
|
-
"@types/lodash": "4.14.182",
|
|
66
|
-
"@types/multer": "1.4.7",
|
|
67
|
-
"@types/node": "17.0.25",
|
|
68
|
-
"@types/node-mailjet": "3.3.8",
|
|
69
|
-
"@types/nodemailer": "6.4.4",
|
|
70
|
-
"@types/passport": "1.0.7",
|
|
71
64
|
"apollo-server-core": "3.6.7",
|
|
72
65
|
"apollo-server-express": "3.6.7",
|
|
73
66
|
"bcrypt": "5.0.1",
|
|
74
67
|
"class-transformer": "0.5.1",
|
|
75
68
|
"class-validator": "0.13.2",
|
|
76
|
-
"ejs": "3.
|
|
77
|
-
"graphql": "16.
|
|
69
|
+
"ejs": "3.1.7",
|
|
70
|
+
"graphql": "16.4.0",
|
|
78
71
|
"graphql-subscriptions": "2.0.0",
|
|
79
|
-
"json-to-graphql-query": "2.2.
|
|
80
|
-
"light-my-request": "4.
|
|
72
|
+
"json-to-graphql-query": "2.2.4",
|
|
73
|
+
"light-my-request": "4.10.1",
|
|
81
74
|
"lodash": "4.17.21",
|
|
82
75
|
"mongodb": "4.5.0",
|
|
83
|
-
"mongoose": "6.3.
|
|
76
|
+
"mongoose": "6.3.2",
|
|
84
77
|
"multer": "1.4.4",
|
|
85
|
-
"node-mailjet": "3.3.
|
|
86
|
-
"nodemailer": "6.7.
|
|
87
|
-
"nodemon": "2.0.
|
|
78
|
+
"node-mailjet": "3.3.13",
|
|
79
|
+
"nodemailer": "6.7.5",
|
|
80
|
+
"nodemon": "2.0.16",
|
|
88
81
|
"passport": "0.5.2",
|
|
89
82
|
"passport-jwt": "4.0.0",
|
|
90
83
|
"reflect-metadata": "0.1.13",
|
|
@@ -93,12 +86,19 @@
|
|
|
93
86
|
},
|
|
94
87
|
"devDependencies": {
|
|
95
88
|
"@nestjs/testing": "8.4.4",
|
|
96
|
-
"@types/
|
|
89
|
+
"@types/ejs": "3.1.0",
|
|
90
|
+
"@types/jest": "27.5.0",
|
|
91
|
+
"@types/lodash": "4.14.182",
|
|
92
|
+
"@types/multer": "1.4.7",
|
|
93
|
+
"@types/node": "16.11.33",
|
|
94
|
+
"@types/node-mailjet": "3.3.8",
|
|
95
|
+
"@types/nodemailer": "6.4.4",
|
|
96
|
+
"@types/passport": "1.0.7",
|
|
97
97
|
"@types/supertest": "2.0.12",
|
|
98
|
-
"@typescript-eslint/eslint-plugin": "5.
|
|
99
|
-
"@typescript-eslint/parser": "5.
|
|
100
|
-
"coffeescript": "2.
|
|
101
|
-
"eslint": "8.
|
|
98
|
+
"@typescript-eslint/eslint-plugin": "5.22.0",
|
|
99
|
+
"@typescript-eslint/parser": "5.22.0",
|
|
100
|
+
"coffeescript": "2.7.0",
|
|
101
|
+
"eslint": "8.14.0",
|
|
102
102
|
"eslint-config-prettier": "8.5.0",
|
|
103
103
|
"find-file-up": "2.0.1",
|
|
104
104
|
"grunt": "1.5.2",
|
|
@@ -107,16 +107,16 @@
|
|
|
107
107
|
"grunt-contrib-watch": "1.1.0",
|
|
108
108
|
"grunt-sync": "0.8.2",
|
|
109
109
|
"husky": "7.0.4",
|
|
110
|
-
"jest": "
|
|
110
|
+
"jest": "28.0.3",
|
|
111
111
|
"pm2": "5.2.0",
|
|
112
112
|
"prettier": "2.6.2",
|
|
113
113
|
"pretty-quick": "3.1.3",
|
|
114
|
-
"supertest": "6.2.
|
|
115
|
-
"ts-jest": "
|
|
114
|
+
"supertest": "6.2.3",
|
|
115
|
+
"ts-jest": "28.0.1",
|
|
116
116
|
"ts-morph": "14.0.0",
|
|
117
117
|
"ts-node": "10.7.0",
|
|
118
|
-
"tsconfig-paths": "
|
|
119
|
-
"typescript": "4.6.
|
|
118
|
+
"tsconfig-paths": "4.0.0",
|
|
119
|
+
"typescript": "4.6.4"
|
|
120
120
|
},
|
|
121
121
|
"jest": {
|
|
122
122
|
"collectCoverage": true,
|
package/src/config.env.ts
CHANGED
|
@@ -109,8 +109,9 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
109
109
|
*
|
|
110
110
|
* default: development
|
|
111
111
|
*/
|
|
112
|
-
const
|
|
113
|
-
|
|
112
|
+
const env = process.env['NODE' + '_ENV'] || 'development';
|
|
113
|
+
const envConfig = config[env] || config.development;
|
|
114
|
+
console.log('Configured for: ' + envConfig.env + (env !== envConfig.env ? ' (requested: ' + env + ')' : ''));
|
|
114
115
|
|
|
115
116
|
/**
|
|
116
117
|
* Export envConfig as default
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { UnauthorizedException } from '@nestjs/common';
|
|
3
3
|
import { RoleEnum } from '../enums/role.enum';
|
|
4
|
+
import { getStringIds } from '../helpers/db.helper';
|
|
5
|
+
import { IdsType } from '../types/ids.type';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Restricted meta key
|
|
@@ -28,11 +30,13 @@ export const getRestricted = (object: unknown, propertyKey: string) => {
|
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* Check data for restricted properties (properties with `Restricted` decorator)
|
|
33
|
+
*
|
|
34
|
+
* If restricted roles includes RoleEnum.OWNER, ownerId(s) from current data (in DB) must be set in options.
|
|
31
35
|
*/
|
|
32
36
|
export const checkRestricted = (
|
|
33
37
|
data: any,
|
|
34
38
|
user: { id: any; hasRole: (roles: string[]) => boolean },
|
|
35
|
-
options: { ignoreUndefined?: boolean; throwError?: boolean } = {},
|
|
39
|
+
options: { ignoreUndefined?: boolean; ownerIds?: IdsType; throwError?: boolean } = {},
|
|
36
40
|
processedObjects: any[] = []
|
|
37
41
|
) => {
|
|
38
42
|
const config = {
|
|
@@ -55,7 +59,7 @@ export const checkRestricted = (
|
|
|
55
59
|
// Array
|
|
56
60
|
if (Array.isArray(data)) {
|
|
57
61
|
// Check array items
|
|
58
|
-
return data.map((item) => checkRestricted(item, user,
|
|
62
|
+
return data.map((item) => checkRestricted(item, user, config, processedObjects));
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
// Object
|
|
@@ -74,18 +78,20 @@ export const checkRestricted = (
|
|
|
74
78
|
if (!user || !user.hasRole(roles)) {
|
|
75
79
|
// Check special role for owner
|
|
76
80
|
if (user && roles.includes(RoleEnum.OWNER)) {
|
|
77
|
-
const userId = user
|
|
81
|
+
const userId = getStringIds(user);
|
|
82
|
+
const ownerIds = config.ownerIds ? getStringIds(config.ownerIds) : null;
|
|
78
83
|
|
|
79
84
|
if (
|
|
80
|
-
|
|
81
|
-
!
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
data.ownerIds.some((item) => (item.id ? item.id.toString() === userId : item.toString() === userId)))
|
|
85
|
-
)
|
|
85
|
+
// No owner IDs
|
|
86
|
+
!ownerIds ||
|
|
87
|
+
// User is not the owner
|
|
88
|
+
!(ownerIds === userId || (Array.isArray(ownerIds) && ownerIds.includes(userId)))
|
|
86
89
|
) {
|
|
87
90
|
// The user does not have the required rights and is not the owner
|
|
88
91
|
if (config.throwError) {
|
|
92
|
+
if (!config.ownerIds) {
|
|
93
|
+
throw new UnauthorizedException('Lack of ownerIds to verify ownership of ' + propertyKey);
|
|
94
|
+
}
|
|
89
95
|
throw new UnauthorizedException('Current user is not allowed to set ' + propertyKey);
|
|
90
96
|
}
|
|
91
97
|
continue;
|
|
@@ -101,7 +107,7 @@ export const checkRestricted = (
|
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
// Check property data
|
|
104
|
-
data[propertyKey] = checkRestricted(data[propertyKey], user,
|
|
110
|
+
data[propertyKey] = checkRestricted(data[propertyKey], user, config, processedObjects);
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
// Return processed data
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { BadRequestException } from '@nestjs/common';
|
|
1
|
+
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
|
2
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';
|
|
6
|
+
import { RoleEnum } from '../enums/role.enum';
|
|
7
|
+
import { IdsType } from '../types/ids.type';
|
|
8
|
+
import { getStringIds } from './db.helper';
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Helper class for inputs
|
|
@@ -15,9 +18,9 @@ export default class InputHelper {
|
|
|
15
18
|
public static async check(
|
|
16
19
|
value: any,
|
|
17
20
|
user: { id: any; hasRole: (roles: string[]) => boolean },
|
|
18
|
-
metatype
|
|
21
|
+
options?: { metatype?: any; ownerIds?: IdsType; roles?: string | string[] }
|
|
19
22
|
): Promise<any> {
|
|
20
|
-
return check(value, user,
|
|
23
|
+
return check(value, user, options);
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
// Standard error function
|
|
@@ -179,34 +182,80 @@ export default class InputHelper {
|
|
|
179
182
|
export async function check(
|
|
180
183
|
value: any,
|
|
181
184
|
user: { id: any; hasRole: (roles: string[]) => boolean },
|
|
182
|
-
metatype
|
|
185
|
+
options?: { metatype?: any; ownerIds?: IdsType; roles?: string | string[]; throwError?: boolean }
|
|
183
186
|
): Promise<any> {
|
|
187
|
+
const config = {
|
|
188
|
+
throwError: true,
|
|
189
|
+
...options,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Check roles
|
|
193
|
+
if (config.roles?.length && config.throwError) {
|
|
194
|
+
let roles = config.roles;
|
|
195
|
+
if (!Array.isArray(roles)) {
|
|
196
|
+
roles = [roles];
|
|
197
|
+
}
|
|
198
|
+
let valid = false;
|
|
199
|
+
if (roles.includes(RoleEnum.USER) && user?.id) {
|
|
200
|
+
valid = true;
|
|
201
|
+
} else if (user.hasRole(roles)) {
|
|
202
|
+
valid = true;
|
|
203
|
+
} else if (roles.includes(RoleEnum.OWNER) && user?.id && config.ownerIds) {
|
|
204
|
+
let ownerIds: string | string[] = getStringIds(config.ownerIds);
|
|
205
|
+
if (!Array.isArray(ownerIds)) {
|
|
206
|
+
ownerIds = [ownerIds];
|
|
207
|
+
}
|
|
208
|
+
valid = ownerIds.includes(getStringIds(user.id));
|
|
209
|
+
}
|
|
210
|
+
if (!valid) {
|
|
211
|
+
throw new UnauthorizedException('Missing rights');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
184
215
|
// Return value if it is only a basic type
|
|
185
|
-
if (typeof value !== 'object'
|
|
216
|
+
if (typeof value !== 'object') {
|
|
186
217
|
return value;
|
|
187
218
|
}
|
|
188
219
|
|
|
189
|
-
//
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
value = (
|
|
193
|
-
}
|
|
194
|
-
|
|
220
|
+
// Check array
|
|
221
|
+
if (Array.isArray(value)) {
|
|
222
|
+
for (const [key, item] of Object.entries(value)) {
|
|
223
|
+
value[key] = await check(item, user, config);
|
|
224
|
+
}
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const metatype = config.metatype;
|
|
229
|
+
if (metatype) {
|
|
230
|
+
// Check metatype
|
|
231
|
+
if (isBasicType(metatype)) {
|
|
232
|
+
return value;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Convert to metatype
|
|
236
|
+
if (!(value instanceof metatype)) {
|
|
237
|
+
if ((metatype as any)?.map) {
|
|
238
|
+
value = (metatype as any)?.map(value);
|
|
239
|
+
} else {
|
|
240
|
+
value = plainToInstance(metatype, value);
|
|
241
|
+
}
|
|
195
242
|
}
|
|
196
243
|
}
|
|
197
244
|
|
|
198
245
|
// Validate
|
|
199
246
|
const errors = await validate(value);
|
|
200
|
-
if (errors.length > 0) {
|
|
247
|
+
if (errors.length > 0 && config.throwError) {
|
|
201
248
|
throw new BadRequestException('Validation failed');
|
|
202
249
|
}
|
|
203
250
|
|
|
204
251
|
// Remove restricted values if roles are missing
|
|
205
|
-
value = checkRestricted(value, user);
|
|
252
|
+
value = checkRestricted(value, user, config);
|
|
206
253
|
return value;
|
|
207
254
|
}
|
|
208
255
|
|
|
209
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Standard error function
|
|
258
|
+
*/
|
|
210
259
|
export function errorFunction(caller: (...params) => any, message = 'Required parameter is missing or invalid') {
|
|
211
260
|
const err = new Error(message);
|
|
212
261
|
Error.captureStackTrace(err, caller);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Model } from 'mongoose';
|
|
1
|
+
import { Model, Types } from 'mongoose';
|
|
2
2
|
import { FieldSelection } from '../types/field-selection.type';
|
|
3
|
+
import { IdsType } from '../types/ids.type';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* General service options
|
|
@@ -8,6 +9,11 @@ export interface ServiceOptions {
|
|
|
8
9
|
// All fields are allowed to be compatible as far as possible
|
|
9
10
|
[key: string]: any;
|
|
10
11
|
|
|
12
|
+
// Check rights for input data (see check function in InputHelper)
|
|
13
|
+
// If falsy: input data will not be checked
|
|
14
|
+
// If truly (default): input data will be checked
|
|
15
|
+
checkRights?: boolean;
|
|
16
|
+
|
|
11
17
|
// Current user to set ownership, check roles and other things
|
|
12
18
|
currentUser?: {
|
|
13
19
|
[key: string]: any;
|
|
@@ -18,6 +24,12 @@ export interface ServiceOptions {
|
|
|
18
24
|
// Field selection for results
|
|
19
25
|
fieldSelection?: FieldSelection;
|
|
20
26
|
|
|
27
|
+
// Overwrites type of input (array items)
|
|
28
|
+
inputType?: new (...params: any[]) => any;
|
|
29
|
+
|
|
30
|
+
// Owner IDs
|
|
31
|
+
ownerIds?: IdsType;
|
|
32
|
+
|
|
21
33
|
// Process field selection
|
|
22
34
|
// If {} or not set, then the field selection runs with defaults
|
|
23
35
|
// If falsy, then the field selection will not be automatically executed
|
|
@@ -50,4 +62,10 @@ export interface ServiceOptions {
|
|
|
50
62
|
|
|
51
63
|
// Whether to publish action via GraphQL subscription
|
|
52
64
|
pubSub?: boolean;
|
|
65
|
+
|
|
66
|
+
// Overwrites type of result (array items)
|
|
67
|
+
resultType?: new (...params: any[]) => any;
|
|
68
|
+
|
|
69
|
+
// Roles (as string) to check
|
|
70
|
+
roles?: string | string[];
|
|
53
71
|
}
|
|
@@ -78,7 +78,7 @@ export abstract class CoreModel {
|
|
|
78
78
|
* Initialize instance with default values instead of undefined
|
|
79
79
|
* Should be overwritten in child class to organize the defaults
|
|
80
80
|
*/
|
|
81
|
-
public init
|
|
81
|
+
public init(...args: any[]): this {
|
|
82
82
|
return this;
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -24,16 +24,11 @@ export abstract class CrudService<T extends CoreModel = any> extends ModuleServi
|
|
|
24
24
|
* Get item by ID
|
|
25
25
|
*/
|
|
26
26
|
async get(id: string, serviceOptions?: ServiceOptions): Promise<T> {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
return item;
|
|
34
|
-
},
|
|
35
|
-
{ input: id, serviceOptions }
|
|
36
|
-
);
|
|
27
|
+
const dbObject = await this.mainDbModel.findById(id).exec();
|
|
28
|
+
if (!dbObject) {
|
|
29
|
+
throw new NotFoundException(`No ${this.mainModelConstructor.name} found with ID: ${id}`);
|
|
30
|
+
}
|
|
31
|
+
return this.process(async () => dbObject, { dbObject, serviceOptions });
|
|
37
32
|
}
|
|
38
33
|
|
|
39
34
|
/**
|
|
@@ -74,15 +69,15 @@ export abstract class CrudService<T extends CoreModel = any> extends ModuleServi
|
|
|
74
69
|
* Update item via ID
|
|
75
70
|
*/
|
|
76
71
|
async update(id: string, input: any, serviceOptions?: ServiceOptions): Promise<T> {
|
|
72
|
+
const dbObject = await this.mainDbModel.findById(id).exec();
|
|
73
|
+
if (!dbObject) {
|
|
74
|
+
throw new NotFoundException(`No ${this.mainModelConstructor.name} found with ID: ${id}`);
|
|
75
|
+
}
|
|
77
76
|
return this.process(
|
|
78
77
|
async (data) => {
|
|
79
|
-
|
|
80
|
-
if (!item) {
|
|
81
|
-
throw new NotFoundException(`No ${this.mainModelConstructor.name} found with ID: ${id}`);
|
|
82
|
-
}
|
|
83
|
-
return await Object.assign(item, data.input).save();
|
|
78
|
+
return await Object.assign(dbObject, data.input).save();
|
|
84
79
|
},
|
|
85
|
-
{ input, serviceOptions }
|
|
80
|
+
{ dbObject, input, serviceOptions }
|
|
86
81
|
);
|
|
87
82
|
}
|
|
88
83
|
|
|
@@ -90,16 +85,16 @@ export abstract class CrudService<T extends CoreModel = any> extends ModuleServi
|
|
|
90
85
|
* Delete item via ID
|
|
91
86
|
*/
|
|
92
87
|
async delete(id: string, serviceOptions?: ServiceOptions): Promise<T> {
|
|
88
|
+
const dbObject = await this.mainDbModel.findById(id).exec();
|
|
89
|
+
if (!dbObject) {
|
|
90
|
+
throw new NotFoundException(`No ${this.mainModelConstructor.name} found with ID: ${id}`);
|
|
91
|
+
}
|
|
93
92
|
return this.process(
|
|
94
93
|
async (data) => {
|
|
95
|
-
const item = await this.mainDbModel.findById(id).exec();
|
|
96
|
-
if (!item) {
|
|
97
|
-
throw new NotFoundException(`No ${this.mainModelConstructor.name} found with ID: ${id}`);
|
|
98
|
-
}
|
|
99
94
|
await this.mainDbModel.findByIdAndDelete(id).exec();
|
|
100
|
-
return
|
|
95
|
+
return dbObject;
|
|
101
96
|
},
|
|
102
|
-
{ input: id, serviceOptions }
|
|
97
|
+
{ dbObject, input: id, serviceOptions }
|
|
103
98
|
);
|
|
104
99
|
}
|
|
105
100
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Document, Model } from 'mongoose';
|
|
2
|
-
import { popAndMap } from '../helpers/db.helper';
|
|
1
|
+
import { Document, Model, Types } from 'mongoose';
|
|
2
|
+
import { getStringIds, popAndMap } from '../helpers/db.helper';
|
|
3
|
+
import { check } from '../helpers/input.helper';
|
|
3
4
|
import { prepareInput, prepareOutput } from '../helpers/service.helper';
|
|
4
5
|
import { ServiceOptions } from '../interfaces/service-options.interface';
|
|
5
6
|
import { CoreModel } from '../models/core-model.model';
|
|
6
7
|
import { FieldSelection } from '../types/field-selection.type';
|
|
8
|
+
import { IdsType } from '../types/ids.type';
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Module service class to be extended by concrete module services
|
|
@@ -30,17 +32,48 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
30
32
|
this.mainModelConstructor = options?.mainModelConstructor;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Check rights of current user for input
|
|
37
|
+
*/
|
|
38
|
+
checkRights(
|
|
39
|
+
input: any,
|
|
40
|
+
currentUser: { id: any; hasRole: (roles: string[]) => boolean },
|
|
41
|
+
options?: {
|
|
42
|
+
metatype?: any;
|
|
43
|
+
ownerIds?: IdsType;
|
|
44
|
+
roles?: string | string[];
|
|
45
|
+
throwError?: boolean;
|
|
46
|
+
}
|
|
47
|
+
): Promise<any> {
|
|
48
|
+
const config = {
|
|
49
|
+
metatype: this.mainModelConstructor,
|
|
50
|
+
...options,
|
|
51
|
+
};
|
|
52
|
+
return check(input, currentUser, config);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get function to get Object via ID, necessary for checkInput
|
|
57
|
+
*/
|
|
58
|
+
abstract get(id: any, ...args: any[]): any;
|
|
59
|
+
|
|
33
60
|
/**
|
|
34
61
|
* Run service function with pre- and post-functions
|
|
35
62
|
*/
|
|
36
63
|
async process(
|
|
37
64
|
serviceFunc: (options?: { [key: string]: any; input?: any; serviceOptions?: ServiceOptions }) => any,
|
|
38
|
-
options?: {
|
|
65
|
+
options?: {
|
|
66
|
+
[key: string]: any;
|
|
67
|
+
dbObject?: string | Types.ObjectId | any;
|
|
68
|
+
input?: any;
|
|
69
|
+
serviceOptions?: ServiceOptions;
|
|
70
|
+
}
|
|
39
71
|
) {
|
|
40
72
|
// Configuration with default values
|
|
41
73
|
const config = {
|
|
42
|
-
|
|
43
|
-
|
|
74
|
+
checkRights: true,
|
|
75
|
+
dbObject: options?.dbObject,
|
|
76
|
+
input: options?.input,
|
|
44
77
|
processFieldSelection: {},
|
|
45
78
|
prepareInput: {},
|
|
46
79
|
prepareOutput: {},
|
|
@@ -50,11 +83,35 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
50
83
|
|
|
51
84
|
// Prepare input
|
|
52
85
|
if (config.prepareInput && this.prepareInput) {
|
|
53
|
-
await this.prepareInput(
|
|
86
|
+
await this.prepareInput(config.input, config.prepareInput);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Get owner IDs
|
|
90
|
+
let ownerIds = undefined;
|
|
91
|
+
if (config.checkRights && this.checkRights) {
|
|
92
|
+
ownerIds = getStringIds(config.ownerIds);
|
|
93
|
+
if (!ownerIds?.length) {
|
|
94
|
+
if (config.dbObject) {
|
|
95
|
+
if (typeof config.dbObject === 'string' || config.dbObject instanceof Types.ObjectId) {
|
|
96
|
+
ownerIds = (await this.get(getStringIds(config.dbObject)))?.ownerIds;
|
|
97
|
+
} else {
|
|
98
|
+
ownerIds = config.dbObject.ownerIds;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check rights for input
|
|
105
|
+
if (config.input && config.checkRights && this.checkRights) {
|
|
106
|
+
const opts: any = { ownerIds, roles: config.roles };
|
|
107
|
+
if (config.inputType) {
|
|
108
|
+
opts.metatype = config.resultType;
|
|
109
|
+
}
|
|
110
|
+
config.input = await this.checkRights(config.input, config.currentUser as any, opts);
|
|
54
111
|
}
|
|
55
112
|
|
|
56
113
|
// Run service function
|
|
57
|
-
|
|
114
|
+
let result = await serviceFunc(config);
|
|
58
115
|
|
|
59
116
|
// Pop and map main model
|
|
60
117
|
if (config.processFieldSelection && config.fieldSelection && this.processFieldSelection) {
|
|
@@ -67,10 +124,19 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
67
124
|
if (config.processFieldSelection && config.fieldSelection && this.processFieldSelection) {
|
|
68
125
|
config.prepareOutput.targetModel = null;
|
|
69
126
|
}
|
|
70
|
-
|
|
127
|
+
result = await this.prepareOutput(result, config.prepareOutput);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Check output rights
|
|
131
|
+
if (config.checkRights && this.checkRights) {
|
|
132
|
+
const opts: any = { ownerIds, roles: config.roles, throwError: false };
|
|
133
|
+
if (config.resultType) {
|
|
134
|
+
opts.metatype = config.resultType;
|
|
135
|
+
}
|
|
136
|
+
result = await this.checkRights(result, config.currentUser as any, opts);
|
|
71
137
|
}
|
|
72
138
|
|
|
73
|
-
// Return result
|
|
139
|
+
// Return (prepared) result
|
|
74
140
|
return result;
|
|
75
141
|
}
|
|
76
142
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
|
|
1
|
+
import { ExecutionContext, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
|
2
2
|
import { Reflector } from '@nestjs/core';
|
|
3
3
|
import { GqlExecutionContext } from '@nestjs/graphql';
|
|
4
4
|
import { RoleEnum } from '../../../common/enums/role.enum';
|