@lenne.tech/nest-server 8.6.4 → 8.6.7
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 +4 -2
- package/dist/config.env.js.map +1 -1
- 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/inputs/filter.input.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/server-options.interface.d.ts +16 -13
- 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/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.d.ts +3 -2
- package/dist/core.module.js +26 -4
- package/dist/core.module.js.map +1 -1
- package/dist/main.js +13 -0
- package/dist/main.js.map +1 -1
- package/dist/server/modules/auth/auth.module.js +1 -2
- package/dist/server/modules/auth/auth.module.js.map +1 -1
- package/dist/server/modules/file/file.controller.js +1 -1
- package/dist/server/modules/file/file.controller.js.map +1 -1
- package/dist/server/modules/file/file.resolver.d.ts +5 -0
- package/dist/server/modules/file/file.resolver.js +51 -0
- package/dist/server/modules/file/file.resolver.js.map +1 -0
- 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/server/server.module.js +3 -1
- package/dist/server/server.module.js.map +1 -1
- package/dist/test/test.helper.d.ts +4 -1
- package/dist/test/test.helper.js +51 -25
- package/dist/test/test.helper.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +43 -37
- package/src/config.env.ts +4 -2
- 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/inputs/filter.input.ts +0 -1
- 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/server-options.interface.ts +68 -52
- 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/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 +38 -6
- package/src/main.ts +16 -0
- package/src/server/modules/auth/auth.module.ts +1 -2
- package/src/server/modules/file/file.controller.ts +1 -1
- package/src/server/modules/file/file.resolver.ts +55 -0
- package/src/server/modules/user/user.resolver.ts +1 -1
- package/src/server/server.module.ts +5 -1
- package/src/test/test.helper.ts +92 -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.7",
|
|
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",
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "rimraf dist && tsc -p tsconfig.build.json",
|
|
18
18
|
"build:pack": "npm pack && echo 'use file:/ROOT_PATH_TO_TGZ_FILE to integrate the package'",
|
|
19
|
+
"docs": "npm run docs:ci && open ./public/index.html",
|
|
20
|
+
"docs:bootstrap": "node extras/update-spectaql-version.mjs && npx -y spectaql ./spectaql.yml",
|
|
21
|
+
"docs:ci": "ts-node ./scripts/init-server.ts && npm run docs:boostrap",
|
|
19
22
|
"format": "prettier --write 'src/**/*.ts'",
|
|
20
23
|
"format:staged": "pretty-quick --staged",
|
|
21
24
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
@@ -23,8 +26,9 @@
|
|
|
23
26
|
"reinit": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i && npm run test:e2e && npm run build",
|
|
24
27
|
"reinit:force": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i --force && npm run test:e2e",
|
|
25
28
|
"reinit:legacy": "rimraf package-lock.json && rimraf node_modules && npm cache clean --force && npm i --legacy-peer-deps && npm run test:e2e",
|
|
26
|
-
"start": "
|
|
29
|
+
"start": "npm run start:dev",
|
|
27
30
|
"stop": "./node_modules/.bin/pm2 delete nest",
|
|
31
|
+
"start:pm2": "./node_modules/.bin/grunt",
|
|
28
32
|
"start:prod": "./node_modules/.bin/grunt productive",
|
|
29
33
|
"start:nodemon": "ts-node -r tsconfig-paths/register src/main.ts",
|
|
30
34
|
"start:debug": "nodemon --config nodemon-debug.json",
|
|
@@ -32,9 +36,10 @@
|
|
|
32
36
|
"test": "NODE_ENV=local jest",
|
|
33
37
|
"test:cov": "NODE_ENV=local jest --coverage",
|
|
34
38
|
"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 --forceExit
|
|
36
|
-
"test:e2e-cov": "NODE_ENV=local jest --config jest-e2e.json --coverage --forceExit
|
|
37
|
-
"test:
|
|
39
|
+
"test:e2e": "NODE_ENV=local jest --config jest-e2e.json --forceExit",
|
|
40
|
+
"test:e2e-cov": "NODE_ENV=local jest --config jest-e2e.json --coverage --forceExit",
|
|
41
|
+
"test:e2e-doh": "NODE_ENV=local jest --config jest-e2e.json --forceExit --detectOpenHandles",
|
|
42
|
+
"test:ci": "NODE_ENV=local jest --config jest-e2e.json --ci --forceExit",
|
|
38
43
|
"test:watch": "NODE_ENV=local jest --watch",
|
|
39
44
|
"prepack": "npm run prestart:prod",
|
|
40
45
|
"prepare": "husky install",
|
|
@@ -52,71 +57,72 @@
|
|
|
52
57
|
"node": ">= 16.13.0"
|
|
53
58
|
},
|
|
54
59
|
"dependencies": {
|
|
55
|
-
"@apollo/gateway": "0.
|
|
56
|
-
"@nestjs/apollo": "10.0.
|
|
57
|
-
"@nestjs/common": "8.4.
|
|
58
|
-
"@nestjs/core": "8.4.
|
|
59
|
-
"@nestjs/graphql": "10.0.
|
|
60
|
-
"@nestjs/jwt": "8.0.
|
|
61
|
-
"@nestjs/mongoose": "9.0
|
|
60
|
+
"@apollo/gateway": "2.0.5",
|
|
61
|
+
"@nestjs/apollo": "10.0.14",
|
|
62
|
+
"@nestjs/common": "8.4.6",
|
|
63
|
+
"@nestjs/core": "8.4.6",
|
|
64
|
+
"@nestjs/graphql": "10.0.15",
|
|
65
|
+
"@nestjs/jwt": "8.0.1",
|
|
66
|
+
"@nestjs/mongoose": "9.1.0",
|
|
62
67
|
"@nestjs/passport": "8.2.1",
|
|
63
|
-
"@nestjs/platform-express": "8.4.
|
|
64
|
-
"apollo-server-core": "3.
|
|
65
|
-
"apollo-server-express": "3.
|
|
68
|
+
"@nestjs/platform-express": "8.4.6",
|
|
69
|
+
"apollo-server-core": "3.8.2",
|
|
70
|
+
"apollo-server-express": "3.8.2",
|
|
66
71
|
"bcrypt": "5.0.1",
|
|
67
72
|
"class-transformer": "0.5.1",
|
|
68
73
|
"class-validator": "0.13.2",
|
|
69
|
-
"ejs": "3.1.
|
|
70
|
-
"graphql": "16.
|
|
74
|
+
"ejs": "3.1.8",
|
|
75
|
+
"graphql": "16.5.0",
|
|
71
76
|
"graphql-subscriptions": "2.0.0",
|
|
77
|
+
"graphql-upload": "13.0.0",
|
|
72
78
|
"json-to-graphql-query": "2.2.4",
|
|
73
|
-
"light-my-request": "
|
|
79
|
+
"light-my-request": "5.0.0",
|
|
74
80
|
"lodash": "4.17.21",
|
|
75
|
-
"mongodb": "4.
|
|
76
|
-
"mongoose": "6.3.
|
|
81
|
+
"mongodb": "4.7.0",
|
|
82
|
+
"mongoose": "6.3.6",
|
|
77
83
|
"multer": "1.4.4",
|
|
78
84
|
"node-mailjet": "3.4.1",
|
|
79
85
|
"nodemailer": "6.7.5",
|
|
80
86
|
"nodemon": "2.0.16",
|
|
81
|
-
"passport": "0.5.
|
|
87
|
+
"passport": "0.5.3",
|
|
82
88
|
"passport-jwt": "4.0.0",
|
|
83
89
|
"reflect-metadata": "0.1.13",
|
|
84
90
|
"rimraf": "3.0.2",
|
|
85
91
|
"rxjs": "7.5.5"
|
|
86
92
|
},
|
|
87
93
|
"devDependencies": {
|
|
88
|
-
"@nestjs/testing": "8.4.
|
|
89
|
-
"@types/ejs": "3.1.
|
|
90
|
-
"@types/jest": "
|
|
94
|
+
"@nestjs/testing": "8.4.6",
|
|
95
|
+
"@types/ejs": "3.1.1",
|
|
96
|
+
"@types/jest": "28.1.1",
|
|
91
97
|
"@types/lodash": "4.14.182",
|
|
92
98
|
"@types/multer": "1.4.7",
|
|
93
|
-
"@types/node": "16.11.
|
|
94
|
-
"@types/node-mailjet": "3.3.
|
|
99
|
+
"@types/node": "16.11.39",
|
|
100
|
+
"@types/node-mailjet": "3.3.9",
|
|
95
101
|
"@types/nodemailer": "6.4.4",
|
|
96
|
-
"@types/passport": "1.0.
|
|
102
|
+
"@types/passport": "1.0.8",
|
|
97
103
|
"@types/supertest": "2.0.12",
|
|
98
|
-
"@typescript-eslint/eslint-plugin": "5.
|
|
99
|
-
"@typescript-eslint/parser": "5.
|
|
104
|
+
"@typescript-eslint/eslint-plugin": "5.27.1",
|
|
105
|
+
"@typescript-eslint/parser": "5.27.1",
|
|
100
106
|
"coffeescript": "2.7.0",
|
|
101
|
-
"eslint": "8.
|
|
107
|
+
"eslint": "8.17.0",
|
|
102
108
|
"eslint-config-prettier": "8.5.0",
|
|
103
109
|
"find-file-up": "2.0.1",
|
|
104
|
-
"grunt": "1.5.
|
|
110
|
+
"grunt": "1.5.3",
|
|
105
111
|
"grunt-bg-shell": "2.3.3",
|
|
106
112
|
"grunt-contrib-clean": "2.0.1",
|
|
107
113
|
"grunt-contrib-watch": "1.1.0",
|
|
108
114
|
"grunt-sync": "0.8.2",
|
|
109
|
-
"husky": "
|
|
110
|
-
"jest": "28.1.
|
|
115
|
+
"husky": "8.0.1",
|
|
116
|
+
"jest": "28.1.1",
|
|
111
117
|
"pm2": "5.2.0",
|
|
112
118
|
"prettier": "2.6.2",
|
|
113
119
|
"pretty-quick": "3.1.3",
|
|
114
120
|
"supertest": "6.2.3",
|
|
115
|
-
"ts-jest": "28.0.
|
|
116
|
-
"ts-morph": "
|
|
117
|
-
"ts-node": "10.
|
|
121
|
+
"ts-jest": "28.0.4",
|
|
122
|
+
"ts-morph": "15.1.0",
|
|
123
|
+
"ts-node": "10.8.1",
|
|
118
124
|
"tsconfig-paths": "4.0.0",
|
|
119
|
-
"typescript": "4.
|
|
125
|
+
"typescript": "4.7.3"
|
|
120
126
|
},
|
|
121
127
|
"jest": {
|
|
122
128
|
"collectCoverage": true,
|
package/src/config.env.ts
CHANGED
|
@@ -31,6 +31,7 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
31
31
|
passwordResetLink: 'http://localhost:4200/user/password-reset',
|
|
32
32
|
},
|
|
33
33
|
env: 'development',
|
|
34
|
+
execAfterInit: 'npm run docs:bootstrap',
|
|
34
35
|
graphQl: {
|
|
35
36
|
driver: {
|
|
36
37
|
debug: true,
|
|
@@ -46,7 +47,7 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
46
47
|
port: 3000,
|
|
47
48
|
staticAssets: {
|
|
48
49
|
path: join(__dirname, '..', 'public'),
|
|
49
|
-
options: { prefix: '
|
|
50
|
+
options: { prefix: '' },
|
|
50
51
|
},
|
|
51
52
|
templates: {
|
|
52
53
|
path: join(__dirname, 'templates'),
|
|
@@ -80,6 +81,7 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
80
81
|
passwordResetLink: 'http://localhost:4200/user/password-reset',
|
|
81
82
|
},
|
|
82
83
|
env: 'productive',
|
|
84
|
+
execAfterInit: 'npm run docs:bootstrap',
|
|
83
85
|
graphQl: {
|
|
84
86
|
driver: {
|
|
85
87
|
debug: false,
|
|
@@ -95,7 +97,7 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
95
97
|
port: 3000,
|
|
96
98
|
staticAssets: {
|
|
97
99
|
path: join(__dirname, '..', 'public'),
|
|
98
|
-
options: { prefix: '
|
|
100
|
+
options: { prefix: '' },
|
|
99
101
|
},
|
|
100
102
|
templates: {
|
|
101
103
|
path: join(__dirname, 'templates'),
|
|
@@ -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,5 +1,4 @@
|
|
|
1
1
|
import { Field, InputType } from '@nestjs/graphql';
|
|
2
|
-
import { ModelHelper } from '../helpers/model.helper';
|
|
3
2
|
import { CombinedFilterInput } from './combined-filter.input';
|
|
4
3
|
import { CoreInput } from './core-input.input';
|
|
5
4
|
import { SingleFilterInput } from './single-filter.input';
|