@lenne.tech/nest-server 8.1.0 → 8.3.1
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 +3 -0
- package/dist/core/common/decorators/restricted.decorator.js +14 -8
- package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
- package/dist/core/common/enums/role.enum.d.ts +3 -2
- package/dist/core/common/enums/role.enum.js +3 -2
- package/dist/core/common/enums/role.enum.js.map +1 -1
- package/dist/core/common/helpers/db.helper.d.ts +2 -0
- package/dist/core/common/helpers/db.helper.js +13 -2
- package/dist/core/common/helpers/db.helper.js.map +1 -1
- package/dist/core/common/helpers/input.helper.d.ts +14 -2
- package/dist/core/common/helpers/input.helper.js +53 -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 +14 -1
- package/dist/core/common/services/module.service.js +43 -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 +1 -2
- package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
- package/dist/core/modules/user/core-user.service.js +34 -36
- 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/avatar.controller.js +1 -1
- package/dist/server/modules/user/avatar.controller.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/server/modules/user/user.service.js +5 -1
- package/dist/server/modules/user/user.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +39 -39
- package/src/config.env.ts +3 -2
- package/src/core/common/decorators/restricted.decorator.ts +24 -12
- package/src/core/common/enums/role.enum.ts +24 -6
- package/src/core/common/helpers/db.helper.ts +16 -1
- package/src/core/common/helpers/input.helper.ts +65 -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 +83 -9
- package/src/core/common/types/ids.type.ts +7 -0
- package/src/core/modules/auth/guards/roles.guard.ts +5 -7
- package/src/core/modules/user/core-user.service.ts +38 -45
- package/src/index.ts +1 -0
- package/src/server/modules/user/avatar.controller.ts +1 -1
- package/src/server/modules/user/user.resolver.ts +26 -20
- package/src/server/modules/user/user.service.ts +8 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "8.1
|
|
3
|
+
"version": "8.3.1",
|
|
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
|
-
"
|
|
65
|
-
"
|
|
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
|
-
"apollo-server-core": "3.6.7",
|
|
72
|
-
"apollo-server-express": "3.6.7",
|
|
64
|
+
"apollo-server-core": "3.7.0",
|
|
65
|
+
"apollo-server-express": "3.7.0",
|
|
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.
|
|
86
|
-
"nodemailer": "6.7.
|
|
87
|
-
"nodemon": "2.0.
|
|
78
|
+
"node-mailjet": "3.4.1",
|
|
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 { equalIds, getStringIds } from '../helpers/db.helper';
|
|
5
|
+
import { IdsType } from '../types/ids.type';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Restricted meta key
|
|
@@ -28,11 +30,14 @@ 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.S_CREATOR, `creator` (createdBy) from current (DB) data must be set in options
|
|
35
|
+
* If restricted roles includes RoleEnum.S_OWNER, `ownerIds` from current (DB) data must be set in options
|
|
31
36
|
*/
|
|
32
37
|
export const checkRestricted = (
|
|
33
38
|
data: any,
|
|
34
39
|
user: { id: any; hasRole: (roles: string[]) => boolean },
|
|
35
|
-
options: { ignoreUndefined?: boolean; throwError?: boolean } = {},
|
|
40
|
+
options: { creator?: IdsType; ignoreUndefined?: boolean; ownerIds?: IdsType; throwError?: boolean } = {},
|
|
36
41
|
processedObjects: any[] = []
|
|
37
42
|
) => {
|
|
38
43
|
const config = {
|
|
@@ -55,7 +60,7 @@ export const checkRestricted = (
|
|
|
55
60
|
// Array
|
|
56
61
|
if (Array.isArray(data)) {
|
|
57
62
|
// Check array items
|
|
58
|
-
return data.map((item) => checkRestricted(item, user,
|
|
63
|
+
return data.map((item) => checkRestricted(item, user, config, processedObjects));
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
// Object
|
|
@@ -72,20 +77,27 @@ export const checkRestricted = (
|
|
|
72
77
|
if (roles && roles.some((value) => !!value)) {
|
|
73
78
|
// Check user and user roles
|
|
74
79
|
if (!user || !user.hasRole(roles)) {
|
|
75
|
-
// Check special role
|
|
76
|
-
if (user && roles.includes(RoleEnum.
|
|
77
|
-
|
|
80
|
+
// Check special creator role
|
|
81
|
+
if (user?.id && roles.includes(RoleEnum.S_CREATOR) && equalIds(user.id, config.creator)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check special owner role
|
|
86
|
+
else if (user && roles.includes(RoleEnum.S_OWNER)) {
|
|
87
|
+
const userId = getStringIds(user);
|
|
88
|
+
const ownerIds = config.ownerIds ? getStringIds(config.ownerIds) : null;
|
|
78
89
|
|
|
79
90
|
if (
|
|
80
|
-
|
|
81
|
-
!
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
data.ownerIds.some((item) => (item.id ? item.id.toString() === userId : item.toString() === userId)))
|
|
85
|
-
)
|
|
91
|
+
// No owner IDs
|
|
92
|
+
!ownerIds ||
|
|
93
|
+
// User is not the owner
|
|
94
|
+
!(ownerIds === userId || (Array.isArray(ownerIds) && ownerIds.includes(userId)))
|
|
86
95
|
) {
|
|
87
96
|
// The user does not have the required rights and is not the owner
|
|
88
97
|
if (config.throwError) {
|
|
98
|
+
if (!config.ownerIds) {
|
|
99
|
+
throw new UnauthorizedException('Lack of ownerIds to verify ownership of ' + propertyKey);
|
|
100
|
+
}
|
|
89
101
|
throw new UnauthorizedException('Current user is not allowed to set ' + propertyKey);
|
|
90
102
|
}
|
|
91
103
|
continue;
|
|
@@ -101,7 +113,7 @@ export const checkRestricted = (
|
|
|
101
113
|
}
|
|
102
114
|
|
|
103
115
|
// Check property data
|
|
104
|
-
data[propertyKey] = checkRestricted(data[propertyKey], user,
|
|
116
|
+
data[propertyKey] = checkRestricted(data[propertyKey], user, config, processedObjects);
|
|
105
117
|
}
|
|
106
118
|
|
|
107
119
|
// Return processed data
|
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Enums for
|
|
2
|
+
* Enums for Resolver @Role and Model @Restricted decorator and for roles property in ServiceOptions
|
|
3
3
|
*/
|
|
4
4
|
export enum RoleEnum {
|
|
5
|
-
//
|
|
5
|
+
// ===================================================================================================================
|
|
6
|
+
// Real roles (integrated into user.roles), which can be used via @Restricted for Models (properties),
|
|
7
|
+
// via @Roles for Resolvers (methods) and via ServiceOptions for Resolver methods.
|
|
8
|
+
// ===================================================================================================================
|
|
9
|
+
|
|
10
|
+
// User must be an administrator (see roles of user)
|
|
6
11
|
ADMIN = 'admin',
|
|
7
12
|
|
|
8
|
-
//
|
|
9
|
-
|
|
13
|
+
// ===================================================================================================================
|
|
14
|
+
// Special system roles, which can be used via @Restricted for Models (properties), via @Roles for Resolvers (methods)
|
|
15
|
+
// and via ServiceOptions for Resolver methods. This roles should not be integrated into user.roles!
|
|
16
|
+
// ===================================================================================================================
|
|
17
|
+
|
|
18
|
+
// User must be signed in (see context user, e.g. @GraphQLUser)
|
|
19
|
+
S_USER = 's_user',
|
|
20
|
+
|
|
21
|
+
// ===================================================================================================================
|
|
22
|
+
// Special system roles that check rights for DB objects and can be used via @Restricted for Models (properties)
|
|
23
|
+
// and via ServiceOptions for Resolver methods. These roles should not be integrated in user.roles!
|
|
24
|
+
// ===================================================================================================================
|
|
25
|
+
|
|
26
|
+
// User must be the creator of the processed object(s) (see createdBy property of object(s))
|
|
27
|
+
S_CREATOR = 's_creator',
|
|
10
28
|
|
|
11
|
-
// User must be
|
|
12
|
-
|
|
29
|
+
// User must be an owner of the processed object(s) (see owners property of object(s))
|
|
30
|
+
S_OWNER = 's_owner',
|
|
13
31
|
}
|
|
@@ -4,6 +4,7 @@ import { Document, Model, PopulateOptions, Query, SchemaType, Types } from 'mong
|
|
|
4
4
|
import { ResolveSelector } from '../interfaces/resolve-selector.interface';
|
|
5
5
|
import { CoreModel } from '../models/core-model.model';
|
|
6
6
|
import { FieldSelection } from '../types/field-selection.type';
|
|
7
|
+
import { IdsType } from '../types/ids.type';
|
|
7
8
|
import { StringOrObjectId } from '../types/string-or-object-id.type';
|
|
8
9
|
|
|
9
10
|
// =====================================================================================================================
|
|
@@ -78,6 +79,20 @@ export function addIds(
|
|
|
78
79
|
return result;
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Checks if all IDs are equal
|
|
84
|
+
*/
|
|
85
|
+
export function equalIds(...ids: IdsType[]): boolean {
|
|
86
|
+
if (!ids) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
const compare = getStringIds(ids[0]);
|
|
90
|
+
if (!compare) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return ids.every((id) => getStringIds(id) === compare);
|
|
94
|
+
}
|
|
95
|
+
|
|
81
96
|
/**
|
|
82
97
|
* Get indexes of IDs in an array
|
|
83
98
|
*/
|
|
@@ -97,7 +112,7 @@ export function getIndexesViaIds(ids: any | any[], array: any[]): number[] {
|
|
|
97
112
|
const indexes: number[] = [];
|
|
98
113
|
ids.forEach((id) => {
|
|
99
114
|
array.forEach((element, index) => {
|
|
100
|
-
if (
|
|
115
|
+
if (equalIds(id, element)) {
|
|
101
116
|
indexes.push(index);
|
|
102
117
|
}
|
|
103
118
|
});
|
|
@@ -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 { equalIds, 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?: { creator?: IdsType; 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,82 @@ 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?: { creator?: IdsType; 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.S_USER) && user?.id) {
|
|
200
|
+
valid = true;
|
|
201
|
+
} else if (user.hasRole(roles)) {
|
|
202
|
+
valid = true;
|
|
203
|
+
} else if (roles.includes(RoleEnum.S_CREATOR) && user?.id && equalIds(user.id, config.creator)) {
|
|
204
|
+
valid = true;
|
|
205
|
+
} else if (roles.includes(RoleEnum.S_OWNER) && user?.id && config.ownerIds) {
|
|
206
|
+
let ownerIds: string | string[] = getStringIds(config.ownerIds);
|
|
207
|
+
if (!Array.isArray(ownerIds)) {
|
|
208
|
+
ownerIds = [ownerIds];
|
|
209
|
+
}
|
|
210
|
+
valid = ownerIds.includes(getStringIds(user.id));
|
|
211
|
+
}
|
|
212
|
+
if (!valid) {
|
|
213
|
+
throw new UnauthorizedException('Missing rights');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
184
217
|
// Return value if it is only a basic type
|
|
185
|
-
if (typeof value !== 'object'
|
|
218
|
+
if (typeof value !== 'object') {
|
|
186
219
|
return value;
|
|
187
220
|
}
|
|
188
221
|
|
|
189
|
-
//
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
value = (
|
|
193
|
-
}
|
|
194
|
-
|
|
222
|
+
// Check array
|
|
223
|
+
if (Array.isArray(value)) {
|
|
224
|
+
for (const [key, item] of Object.entries(value)) {
|
|
225
|
+
value[key] = await check(item, user, config);
|
|
226
|
+
}
|
|
227
|
+
return value;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const metatype = config.metatype;
|
|
231
|
+
if (metatype) {
|
|
232
|
+
// Check metatype
|
|
233
|
+
if (isBasicType(metatype)) {
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Convert to metatype
|
|
238
|
+
if (!(value instanceof metatype)) {
|
|
239
|
+
if ((metatype as any)?.map) {
|
|
240
|
+
value = (metatype as any)?.map(value);
|
|
241
|
+
} else {
|
|
242
|
+
value = plainToInstance(metatype, value);
|
|
243
|
+
}
|
|
195
244
|
}
|
|
196
245
|
}
|
|
197
246
|
|
|
198
247
|
// Validate
|
|
199
248
|
const errors = await validate(value);
|
|
200
|
-
if (errors.length > 0) {
|
|
249
|
+
if (errors.length > 0 && config.throwError) {
|
|
201
250
|
throw new BadRequestException('Validation failed');
|
|
202
251
|
}
|
|
203
252
|
|
|
204
253
|
// Remove restricted values if roles are missing
|
|
205
|
-
value = checkRestricted(value, user);
|
|
254
|
+
value = checkRestricted(value, user, config);
|
|
206
255
|
return value;
|
|
207
256
|
}
|
|
208
257
|
|
|
209
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Standard error function
|
|
260
|
+
*/
|
|
210
261
|
export function errorFunction(caller: (...params) => any, message = 'Required parameter is missing or invalid') {
|
|
211
262
|
const err = new Error(message);
|
|
212
263
|
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
|
}
|