@lenne.tech/nest-server 8.6.3 → 8.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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/file.helper.js +4 -7
- package/dist/core/common/helpers/file.helper.js.map +1 -1
- package/dist/core/common/helpers/filter.helper.d.ts +7 -0
- package/dist/core/common/helpers/filter.helper.js +38 -1
- package/dist/core/common/helpers/filter.helper.js.map +1 -1
- package/dist/core/common/helpers/input.helper.d.ts +6 -0
- package/dist/core/common/helpers/input.helper.js +28 -6
- package/dist/core/common/helpers/input.helper.js.map +1 -1
- package/dist/core/common/helpers/service.helper.js +3 -5
- package/dist/core/common/helpers/service.helper.js.map +1 -1
- package/dist/core/common/inputs/filter.input.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +16 -13
- package/dist/core/common/models/core-model.model.js +4 -1
- package/dist/core/common/models/core-model.model.js.map +1 -1
- package/dist/core/common/services/crud.service.js +9 -4
- package/dist/core/common/services/crud.service.js.map +1 -1
- package/dist/core/common/services/module.service.js +1 -1
- package/dist/core/common/services/module.service.js.map +1 -1
- package/dist/core/modules/auth/guards/roles.guard.js +2 -1
- package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
- package/dist/core/modules/user/core-user.service.js +9 -13
- package/dist/core/modules/user/core-user.service.js.map +1 -1
- package/dist/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 +1 -1
- package/dist/server/modules/user/user.resolver.js +2 -1
- package/dist/server/modules/user/user.resolver.js.map +1 -1
- package/dist/server/modules/user/user.service.js +1 -1
- package/dist/server/modules/user/user.service.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 +33 -28
- package/src/config.env.ts +4 -2
- package/src/core/common/decorators/restricted.decorator.ts +1 -2
- package/src/core/common/helpers/file.helper.ts +9 -11
- package/src/core/common/helpers/filter.helper.ts +66 -1
- package/src/core/common/helpers/input.helper.ts +61 -2
- package/src/core/common/helpers/service.helper.ts +2 -6
- package/src/core/common/inputs/filter.input.ts +0 -1
- package/src/core/common/interfaces/server-options.interface.ts +68 -52
- package/src/core/common/models/core-model.model.ts +4 -1
- package/src/core/common/services/crud.service.ts +7 -4
- package/src/core/common/services/module.service.ts +0 -1
- package/src/core/modules/auth/guards/roles.guard.ts +1 -1
- package/src/core/modules/user/core-user.service.ts +13 -22
- package/src/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/modules/user/user.service.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.6",
|
|
4
4
|
"description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node",
|
|
@@ -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",
|
|
@@ -32,9 +35,10 @@
|
|
|
32
35
|
"test": "NODE_ENV=local jest",
|
|
33
36
|
"test:cov": "NODE_ENV=local jest --coverage",
|
|
34
37
|
"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:
|
|
38
|
+
"test:e2e": "NODE_ENV=local jest --config jest-e2e.json --forceExit",
|
|
39
|
+
"test:e2e-cov": "NODE_ENV=local jest --config jest-e2e.json --coverage --forceExit",
|
|
40
|
+
"test:e2e-doh": "NODE_ENV=local jest --config jest-e2e.json --forceExit --detectOpenHandles",
|
|
41
|
+
"test:ci": "NODE_ENV=local jest --config jest-e2e.json --ci --forceExit",
|
|
38
42
|
"test:watch": "NODE_ENV=local jest --watch",
|
|
39
43
|
"prepack": "npm run prestart:prod",
|
|
40
44
|
"prepare": "husky install",
|
|
@@ -53,60 +57,61 @@
|
|
|
53
57
|
},
|
|
54
58
|
"dependencies": {
|
|
55
59
|
"@apollo/gateway": "0.50.2",
|
|
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
|
+
"@nestjs/apollo": "10.0.12",
|
|
61
|
+
"@nestjs/common": "8.4.5",
|
|
62
|
+
"@nestjs/core": "8.4.5",
|
|
63
|
+
"@nestjs/graphql": "10.0.12",
|
|
64
|
+
"@nestjs/jwt": "8.0.1",
|
|
65
|
+
"@nestjs/mongoose": "9.1.0",
|
|
62
66
|
"@nestjs/passport": "8.2.1",
|
|
63
|
-
"@nestjs/platform-express": "8.4.
|
|
67
|
+
"@nestjs/platform-express": "8.4.5",
|
|
64
68
|
"apollo-server-core": "3.7.0",
|
|
65
69
|
"apollo-server-express": "3.7.0",
|
|
66
70
|
"bcrypt": "5.0.1",
|
|
67
71
|
"class-transformer": "0.5.1",
|
|
68
72
|
"class-validator": "0.13.2",
|
|
69
|
-
"ejs": "3.1.
|
|
70
|
-
"graphql": "16.
|
|
73
|
+
"ejs": "3.1.8",
|
|
74
|
+
"graphql": "16.5.0",
|
|
71
75
|
"graphql-subscriptions": "2.0.0",
|
|
76
|
+
"graphql-upload": "13.0.0",
|
|
72
77
|
"json-to-graphql-query": "2.2.4",
|
|
73
|
-
"light-my-request": "
|
|
78
|
+
"light-my-request": "5.0.0",
|
|
74
79
|
"lodash": "4.17.21",
|
|
75
|
-
"mongodb": "4.
|
|
76
|
-
"mongoose": "6.3.
|
|
80
|
+
"mongodb": "4.6.0",
|
|
81
|
+
"mongoose": "6.3.4",
|
|
77
82
|
"multer": "1.4.4",
|
|
78
83
|
"node-mailjet": "3.4.1",
|
|
79
84
|
"nodemailer": "6.7.5",
|
|
80
85
|
"nodemon": "2.0.16",
|
|
81
|
-
"passport": "0.5.
|
|
86
|
+
"passport": "0.5.3",
|
|
82
87
|
"passport-jwt": "4.0.0",
|
|
83
88
|
"reflect-metadata": "0.1.13",
|
|
84
89
|
"rimraf": "3.0.2",
|
|
85
90
|
"rxjs": "7.5.5"
|
|
86
91
|
},
|
|
87
92
|
"devDependencies": {
|
|
88
|
-
"@nestjs/testing": "8.4.
|
|
89
|
-
"@types/ejs": "3.1.
|
|
90
|
-
"@types/jest": "27.5.
|
|
93
|
+
"@nestjs/testing": "8.4.5",
|
|
94
|
+
"@types/ejs": "3.1.1",
|
|
95
|
+
"@types/jest": "27.5.1",
|
|
91
96
|
"@types/lodash": "4.14.182",
|
|
92
97
|
"@types/multer": "1.4.7",
|
|
93
|
-
"@types/node": "16.11.
|
|
94
|
-
"@types/node-mailjet": "3.3.
|
|
98
|
+
"@types/node": "16.11.36",
|
|
99
|
+
"@types/node-mailjet": "3.3.9",
|
|
95
100
|
"@types/nodemailer": "6.4.4",
|
|
96
101
|
"@types/passport": "1.0.7",
|
|
97
102
|
"@types/supertest": "2.0.12",
|
|
98
|
-
"@typescript-eslint/eslint-plugin": "5.
|
|
99
|
-
"@typescript-eslint/parser": "5.
|
|
103
|
+
"@typescript-eslint/eslint-plugin": "5.25.0",
|
|
104
|
+
"@typescript-eslint/parser": "5.25.0",
|
|
100
105
|
"coffeescript": "2.7.0",
|
|
101
|
-
"eslint": "8.
|
|
106
|
+
"eslint": "8.16.0",
|
|
102
107
|
"eslint-config-prettier": "8.5.0",
|
|
103
108
|
"find-file-up": "2.0.1",
|
|
104
|
-
"grunt": "1.5.
|
|
109
|
+
"grunt": "1.5.3",
|
|
105
110
|
"grunt-bg-shell": "2.3.3",
|
|
106
111
|
"grunt-contrib-clean": "2.0.1",
|
|
107
112
|
"grunt-contrib-watch": "1.1.0",
|
|
108
113
|
"grunt-sync": "0.8.2",
|
|
109
|
-
"husky": "
|
|
114
|
+
"husky": "8.0.1",
|
|
110
115
|
"jest": "28.1.0",
|
|
111
116
|
"pm2": "5.2.0",
|
|
112
117
|
"prettier": "2.6.2",
|
|
@@ -114,7 +119,7 @@
|
|
|
114
119
|
"supertest": "6.2.3",
|
|
115
120
|
"ts-jest": "28.0.2",
|
|
116
121
|
"ts-morph": "14.0.0",
|
|
117
|
-
"ts-node": "10.
|
|
122
|
+
"ts-node": "10.8.0",
|
|
118
123
|
"tsconfig-paths": "4.0.0",
|
|
119
124
|
"typescript": "4.6.4"
|
|
120
125
|
},
|
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
|
) {
|
|
@@ -75,23 +75,21 @@ export function multerOptionsForImageUpload(options: {
|
|
|
75
75
|
fileSize?: number;
|
|
76
76
|
fileTypeRegex?: RegExp;
|
|
77
77
|
}): MulterOptions {
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
options
|
|
85
|
-
);
|
|
78
|
+
// Set config
|
|
79
|
+
const config = {
|
|
80
|
+
fileSize: 1024 * 1024, // 1MB
|
|
81
|
+
fileTypeRegex: /jpeg|jpg|png/, // Images only
|
|
82
|
+
...options,
|
|
83
|
+
};
|
|
86
84
|
|
|
87
85
|
return {
|
|
88
86
|
// File filter
|
|
89
|
-
fileFilter:
|
|
87
|
+
fileFilter: config.fileTypeRegex ? multerFileFilter(config.fileTypeRegex) : undefined,
|
|
90
88
|
|
|
91
89
|
// Limits
|
|
92
90
|
limits: {
|
|
93
91
|
// Limit of file size
|
|
94
|
-
fileSize:
|
|
92
|
+
fileSize: config.fileSize ? config.fileSize : undefined,
|
|
95
93
|
},
|
|
96
94
|
|
|
97
95
|
// Automatic storage handling
|
|
@@ -100,7 +98,7 @@ export function multerOptionsForImageUpload(options: {
|
|
|
100
98
|
// Destination for uploaded file
|
|
101
99
|
// If destination is not set file will be buffered and can be processed
|
|
102
100
|
// in the method
|
|
103
|
-
destination:
|
|
101
|
+
destination: config.destination ? config.destination : undefined,
|
|
104
102
|
|
|
105
103
|
// Generated random file name
|
|
106
104
|
filename: multerRandomFileName(),
|
|
@@ -4,6 +4,8 @@ import { ComparisonOperatorEnum } from '../enums/comparison-operator.enum';
|
|
|
4
4
|
import { LogicalOperatorEnum } from '../enums/logical-operator.enum';
|
|
5
5
|
import { FilterInput } from '../inputs/filter.input';
|
|
6
6
|
import { SortInput } from '../inputs/sort.input';
|
|
7
|
+
import { getObjectIds } from './db.helper';
|
|
8
|
+
import { assignPlain } from './input.helper';
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Helper for filter handling
|
|
@@ -33,9 +35,72 @@ export class Filter {
|
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Helper function to create $and, $or or $nor filter
|
|
40
|
+
*/
|
|
41
|
+
export function findFilter(options?: {
|
|
42
|
+
conditions?: Record<string, any>[];
|
|
43
|
+
filterOptions?: FilterQuery<any>;
|
|
44
|
+
id?: any;
|
|
45
|
+
ids?: any[];
|
|
46
|
+
type?: '$and' | '$or' | '$nor';
|
|
47
|
+
}): FilterQuery<any> {
|
|
48
|
+
const config = {
|
|
49
|
+
type: '$and',
|
|
50
|
+
...options,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Init filter Option
|
|
54
|
+
let filterOptions: FilterQuery<any> = config?.filterOptions;
|
|
55
|
+
|
|
56
|
+
// Check where condition
|
|
57
|
+
if (!filterOptions) {
|
|
58
|
+
filterOptions = {};
|
|
59
|
+
filterOptions[config.type] = [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Convert where condition to array
|
|
63
|
+
if (!Array.isArray(filterOptions?.[config.type])) {
|
|
64
|
+
filterOptions = {};
|
|
65
|
+
filterOptions[config.type] = [config?.filterOptions];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ObjectId
|
|
69
|
+
if (config?.id) {
|
|
70
|
+
filterOptions[config.type].push({ _id: getObjectIds(config.id) });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ObjectIds
|
|
74
|
+
if (config?.ids) {
|
|
75
|
+
if (!Array.isArray(config.ids)) {
|
|
76
|
+
config.ids = [config.ids];
|
|
77
|
+
}
|
|
78
|
+
filterOptions[config.type].push({ _id: { $in: getObjectIds(config.ids) } });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Integrate conditions
|
|
82
|
+
if (config?.conditions) {
|
|
83
|
+
filterOptions[config.type] = [...filterOptions[config.type], ...config.conditions];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Filter falsy values
|
|
87
|
+
filterOptions[config.type] = filterOptions[config.type].filter((value) => value);
|
|
88
|
+
|
|
89
|
+
// Optimizations
|
|
90
|
+
if (!filterOptions[config.type].length) {
|
|
91
|
+
filterOptions = {};
|
|
92
|
+
} else if (filterOptions[config.type].length === 1) {
|
|
93
|
+
const additionalProperties = filterOptions[config.type][0];
|
|
94
|
+
delete filterOptions[config.type];
|
|
95
|
+
assignPlain(filterOptions, additionalProperties);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Return filter config
|
|
99
|
+
return filterOptions;
|
|
100
|
+
}
|
|
101
|
+
|
|
36
102
|
/**
|
|
37
103
|
* Convert filter arguments to a query array
|
|
38
|
-
* @param filterArgs
|
|
39
104
|
*/
|
|
40
105
|
export function convertFilterArgsToQuery<T = any>(filterArgs: Partial<FilterArgs>): [FilterQuery<T>, QueryOptions] {
|
|
41
106
|
return [generateFilterQuery(filterArgs?.filter), generateFindOptions(filterArgs)];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
|
2
2
|
import { plainToInstance } from 'class-transformer';
|
|
3
3
|
import { validate } from 'class-validator';
|
|
4
|
+
import { ValidatorOptions } from 'class-validator/types/validation/ValidatorOptions';
|
|
4
5
|
import * as _ from 'lodash';
|
|
5
6
|
import { checkRestricted } from '../decorators/restricted.decorator';
|
|
6
7
|
import { ProcessType } from '../enums/process-type.enum';
|
|
@@ -182,6 +183,24 @@ export default class InputHelper {
|
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Assign plain objects to the target object and ignores undefined
|
|
188
|
+
*/
|
|
189
|
+
export function assignPlain(target: Record<any, any>, ...args: Record<any, any>[]): any {
|
|
190
|
+
return Object.assign(
|
|
191
|
+
target,
|
|
192
|
+
...args.map(
|
|
193
|
+
// Prepare records
|
|
194
|
+
(item) =>
|
|
195
|
+
!item
|
|
196
|
+
? // Return item if not an object
|
|
197
|
+
item
|
|
198
|
+
: // Return cloned record with undefined properties removed
|
|
199
|
+
filterProperties(JSON.parse(JSON.stringify(item)), (prop) => prop !== undefined)
|
|
200
|
+
)
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
185
204
|
/**
|
|
186
205
|
* Check input
|
|
187
206
|
*/
|
|
@@ -194,11 +213,16 @@ export async function check(
|
|
|
194
213
|
processType?: ProcessType;
|
|
195
214
|
roles?: string | string[];
|
|
196
215
|
throwError?: boolean;
|
|
216
|
+
validatorOptions?: ValidatorOptions;
|
|
197
217
|
}
|
|
198
218
|
): Promise<any> {
|
|
199
219
|
const config = {
|
|
200
220
|
throwError: true,
|
|
201
221
|
...options,
|
|
222
|
+
validatorOptions: {
|
|
223
|
+
skipUndefinedProperties: true,
|
|
224
|
+
...options?.validatorOptions,
|
|
225
|
+
},
|
|
202
226
|
};
|
|
203
227
|
|
|
204
228
|
// Check roles
|
|
@@ -212,7 +236,7 @@ export async function check(
|
|
|
212
236
|
// check if user is logged in
|
|
213
237
|
(roles.includes(RoleEnum.S_USER) && user?.id) ||
|
|
214
238
|
// check if the user has at least one of the required roles
|
|
215
|
-
user
|
|
239
|
+
user?.hasRole?.(roles) ||
|
|
216
240
|
// check if the user is the creator
|
|
217
241
|
(roles.includes(RoleEnum.S_CREATOR) && equalIds(config.dbObject?.createdBy, user))
|
|
218
242
|
) {
|
|
@@ -254,7 +278,7 @@ export async function check(
|
|
|
254
278
|
}
|
|
255
279
|
|
|
256
280
|
// Validate
|
|
257
|
-
const errors = await validate(value);
|
|
281
|
+
const errors = await validate(value, config.validatorOptions);
|
|
258
282
|
if (errors.length > 0 && config.throwError) {
|
|
259
283
|
throw new BadRequestException('Validation failed');
|
|
260
284
|
}
|
|
@@ -264,6 +288,13 @@ export async function check(
|
|
|
264
288
|
return value;
|
|
265
289
|
}
|
|
266
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Combines objects to a new single plain object and ignores undefined
|
|
293
|
+
*/
|
|
294
|
+
export function combinePlain(...args: Record<any, any>[]): any {
|
|
295
|
+
return assignPlain({}, ...args);
|
|
296
|
+
}
|
|
297
|
+
|
|
267
298
|
/**
|
|
268
299
|
* Standard error function
|
|
269
300
|
*/
|
|
@@ -273,6 +304,18 @@ export function errorFunction(caller: (...params) => any, message = 'Required pa
|
|
|
273
304
|
throw err;
|
|
274
305
|
}
|
|
275
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Filter function for objects
|
|
309
|
+
*/
|
|
310
|
+
export function filterProperties<T = Record<string, any>>(
|
|
311
|
+
obj: T,
|
|
312
|
+
filterFunction: (value?: any, key?: string, obj?: T) => boolean
|
|
313
|
+
): Partial<T> {
|
|
314
|
+
return Object.keys(obj)
|
|
315
|
+
.filter((key) => filterFunction(obj[key], key, obj))
|
|
316
|
+
.reduce((res, key) => Object.assign(res, { [key]: obj[key] }), {});
|
|
317
|
+
}
|
|
318
|
+
|
|
276
319
|
/**
|
|
277
320
|
* Check if parameter is an array
|
|
278
321
|
*/
|
|
@@ -433,6 +476,22 @@ export function returnFalse(): boolean {
|
|
|
433
476
|
return false;
|
|
434
477
|
}
|
|
435
478
|
|
|
479
|
+
/**
|
|
480
|
+
* Match function to use instead of switch case
|
|
481
|
+
* Inspired by https://yusfuu.medium.com/dont-use-switch-or-if-else-in-javascript-instead-try-this-82f32616c269
|
|
482
|
+
*
|
|
483
|
+
* Example:
|
|
484
|
+
* const matched = match(expr, {
|
|
485
|
+
* Oranges: 'Oranges are $0.59 a pound.',
|
|
486
|
+
* Mangoes: 'Mangoes and papayas are $2.79 a pound.',
|
|
487
|
+
* Papayas: 'Mangoes and papayas are $2.79 a pound.',
|
|
488
|
+
* default: `Sorry, we are out of ${expr}.`,
|
|
489
|
+
* });
|
|
490
|
+
*/
|
|
491
|
+
export function match(expression: any, cases: Record<any, any>): any {
|
|
492
|
+
return cases[expression] || cases?.default;
|
|
493
|
+
}
|
|
494
|
+
|
|
436
495
|
/**
|
|
437
496
|
* Map values into specific type
|
|
438
497
|
*/
|
|
@@ -68,7 +68,7 @@ export async function prepareInput<T = any>(
|
|
|
68
68
|
clone: false,
|
|
69
69
|
create: false,
|
|
70
70
|
getNewArray: false,
|
|
71
|
-
removeUndefined:
|
|
71
|
+
removeUndefined: true,
|
|
72
72
|
...options,
|
|
73
73
|
};
|
|
74
74
|
|
|
@@ -107,11 +107,7 @@ export async function prepareInput<T = any>(
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
// Process roles
|
|
110
|
-
if (
|
|
111
|
-
config.checkRoles &&
|
|
112
|
-
(input as Record<string, any>).roles &&
|
|
113
|
-
(!currentUser?.hasRole || !currentUser.hasRole(RoleEnum.ADMIN))
|
|
114
|
-
) {
|
|
110
|
+
if (config.checkRoles && (input as Record<string, any>).roles && !currentUser?.hasRole?.(RoleEnum.ADMIN)) {
|
|
115
111
|
if (!(currentUser as any)?.roles) {
|
|
116
112
|
throw new UnauthorizedException('Missing roles of current user');
|
|
117
113
|
} else {
|
|
@@ -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';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ApolloDriverConfig } from '@nestjs/apollo';
|
|
2
|
+
import { GqlModuleAsyncOptions } from '@nestjs/graphql';
|
|
2
3
|
import { JwtModuleOptions } from '@nestjs/jwt';
|
|
3
4
|
import { MongooseModuleOptions } from '@nestjs/mongoose/dist/interfaces/mongoose-options.interface';
|
|
4
5
|
import { ServeStaticOptions } from '@nestjs/platform-express/interfaces/serve-static-options.interface';
|
|
@@ -9,12 +10,58 @@ import { MailjetOptions } from './mailjet-options.interface';
|
|
|
9
10
|
* Options for the server
|
|
10
11
|
*/
|
|
11
12
|
export interface IServerOptions {
|
|
13
|
+
/**
|
|
14
|
+
* SMTP and template configuration for sending emails
|
|
15
|
+
*/
|
|
16
|
+
email?: {
|
|
17
|
+
/**
|
|
18
|
+
* Data for default sender
|
|
19
|
+
*/
|
|
20
|
+
defaultSender?: {
|
|
21
|
+
/**
|
|
22
|
+
* Default email for sending emails
|
|
23
|
+
*/
|
|
24
|
+
email?: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Default name for sending emails
|
|
28
|
+
*/
|
|
29
|
+
name?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Options for Mailjet
|
|
34
|
+
*/
|
|
35
|
+
mailjet?: MailjetOptions;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Password reset link for email
|
|
39
|
+
*/
|
|
40
|
+
passwordResetLink?: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* SMTP configuration for nodemailer
|
|
44
|
+
*/
|
|
45
|
+
smtp?: SMTPTransport | SMTPTransport.Options | string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Verification link for email
|
|
49
|
+
*/
|
|
50
|
+
verificationLink?: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
12
53
|
/**
|
|
13
54
|
* Environment
|
|
14
55
|
* e.g. 'development'
|
|
15
56
|
*/
|
|
16
57
|
env?: string;
|
|
17
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Exec a command after server is initialized
|
|
61
|
+
* e.g. 'npm run docs:bootstrap'
|
|
62
|
+
*/
|
|
63
|
+
execAfterInit?: string;
|
|
64
|
+
|
|
18
65
|
/**
|
|
19
66
|
* Configuration of the GraphQL module
|
|
20
67
|
* see https://docs.nestjs.com/graphql/quick-start
|
|
@@ -30,6 +77,11 @@ export interface IServerOptions {
|
|
|
30
77
|
* Subscription authentication
|
|
31
78
|
*/
|
|
32
79
|
enableSubscriptionAuth?: boolean;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Module options (forRootAsync)
|
|
83
|
+
*/
|
|
84
|
+
options?: GqlModuleAsyncOptions;
|
|
33
85
|
};
|
|
34
86
|
|
|
35
87
|
/**
|
|
@@ -67,6 +119,11 @@ export interface IServerOptions {
|
|
|
67
119
|
secretOrPrivateKey?: string;
|
|
68
120
|
} & JwtModuleOptions;
|
|
69
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Configuration for Mongoose
|
|
124
|
+
*/
|
|
125
|
+
mongoose?: { uri: string; options?: MongooseModuleOptions };
|
|
126
|
+
|
|
70
127
|
/**
|
|
71
128
|
* Port number of the server
|
|
72
129
|
* e.g. 8080
|
|
@@ -74,77 +131,36 @@ export interface IServerOptions {
|
|
|
74
131
|
port?: number;
|
|
75
132
|
|
|
76
133
|
/**
|
|
77
|
-
*
|
|
134
|
+
* Configuration for useStaticAssets
|
|
78
135
|
*/
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* SMTP configuration for nodemailer
|
|
82
|
-
*/
|
|
83
|
-
smtp?: SMTPTransport | SMTPTransport.Options | string;
|
|
84
|
-
|
|
85
|
-
mailjet?: MailjetOptions;
|
|
86
|
-
/**
|
|
87
|
-
* Verification link for email
|
|
88
|
-
*/
|
|
89
|
-
verificationLink?: string;
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Password reset link for email
|
|
93
|
-
*/
|
|
94
|
-
passwordResetLink?: string;
|
|
95
|
-
|
|
136
|
+
staticAssets?: {
|
|
96
137
|
/**
|
|
97
|
-
*
|
|
138
|
+
* Additional options for useStaticAssets
|
|
139
|
+
* e.g. {prefix: '/public/'}
|
|
98
140
|
*/
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Default email for sending emails
|
|
102
|
-
*/
|
|
103
|
-
email?: string;
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Default name for sending emails
|
|
107
|
-
*/
|
|
108
|
-
name?: string;
|
|
109
|
-
};
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Configuration for Mongoose
|
|
114
|
-
*/
|
|
115
|
-
mongoose?: { uri: string; options?: MongooseModuleOptions };
|
|
141
|
+
options?: ServeStaticOptions;
|
|
116
142
|
|
|
117
|
-
/**
|
|
118
|
-
* Configuration for useStaticAssets
|
|
119
|
-
*/
|
|
120
|
-
staticAssets?: {
|
|
121
143
|
/**
|
|
122
144
|
* Root directory for static assets
|
|
123
145
|
* e.g. join(__dirname, '..', 'public')
|
|
124
146
|
*/
|
|
125
147
|
path?: string;
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Additional options for useStaticAssets
|
|
129
|
-
* e.g. {prefix: '/public/'}
|
|
130
|
-
*/
|
|
131
|
-
options?: ServeStaticOptions;
|
|
132
148
|
};
|
|
133
149
|
|
|
134
150
|
/**
|
|
135
151
|
* Templates
|
|
136
152
|
*/
|
|
137
153
|
templates?: {
|
|
138
|
-
/**
|
|
139
|
-
* Directory for templates
|
|
140
|
-
* e.g. join(__dirname, '..', 'templates')
|
|
141
|
-
*/
|
|
142
|
-
path?: string;
|
|
143
|
-
|
|
144
154
|
/**
|
|
145
155
|
* View engine
|
|
146
156
|
* e.g. 'ejs'
|
|
147
157
|
*/
|
|
148
158
|
engine?: string;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Directory for templates
|
|
162
|
+
* e.g. join(__dirname, '..', 'templates')
|
|
163
|
+
*/
|
|
164
|
+
path?: string;
|
|
149
165
|
};
|
|
150
166
|
}
|
|
@@ -92,7 +92,10 @@ export abstract class CoreModel {
|
|
|
92
92
|
mapId: false,
|
|
93
93
|
...options,
|
|
94
94
|
};
|
|
95
|
-
|
|
95
|
+
if (config.init) {
|
|
96
|
+
this.init(config.init);
|
|
97
|
+
}
|
|
98
|
+
return map(data, this, config);
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
/**
|
|
@@ -2,6 +2,7 @@ import { NotFoundException } from '@nestjs/common';
|
|
|
2
2
|
import { FilterArgs } from '../args/filter.args';
|
|
3
3
|
import { merge } from '../helpers/config.helper';
|
|
4
4
|
import { convertFilterArgsToQuery } from '../helpers/filter.helper';
|
|
5
|
+
import { assignPlain } from '../helpers/input.helper';
|
|
5
6
|
import { ServiceOptions } from '../interfaces/service-options.interface';
|
|
6
7
|
import { CoreModel } from '../models/core-model.model';
|
|
7
8
|
import { ModuleService } from './module.service';
|
|
@@ -14,7 +15,8 @@ export abstract class CrudService<T extends CoreModel = any> extends ModuleServi
|
|
|
14
15
|
merge({ prepareInput: { create: true } }, serviceOptions);
|
|
15
16
|
return this.process(
|
|
16
17
|
async (data) => {
|
|
17
|
-
|
|
18
|
+
const currentUserId = serviceOptions?.currentUser?.id;
|
|
19
|
+
return new this.mainDbModel({ ...data.input, createdBy: currentUserId, updatedBy: currentUserId }).save();
|
|
18
20
|
},
|
|
19
21
|
{ input, serviceOptions }
|
|
20
22
|
);
|
|
@@ -75,7 +77,8 @@ export abstract class CrudService<T extends CoreModel = any> extends ModuleServi
|
|
|
75
77
|
}
|
|
76
78
|
return this.process(
|
|
77
79
|
async (data) => {
|
|
78
|
-
|
|
80
|
+
const currentUserId = serviceOptions?.currentUser?.id;
|
|
81
|
+
return await assignPlain(dbObject, data.input, { updatedBy: currentUserId }).save();
|
|
79
82
|
},
|
|
80
83
|
{ dbObject, input, serviceOptions }
|
|
81
84
|
);
|
|
@@ -90,11 +93,11 @@ export abstract class CrudService<T extends CoreModel = any> extends ModuleServi
|
|
|
90
93
|
throw new NotFoundException(`No ${this.mainModelConstructor.name} found with ID: ${id}`);
|
|
91
94
|
}
|
|
92
95
|
return this.process(
|
|
93
|
-
async (
|
|
96
|
+
async () => {
|
|
94
97
|
await this.mainDbModel.findByIdAndDelete(id).exec();
|
|
95
98
|
return dbObject;
|
|
96
99
|
},
|
|
97
|
-
{ dbObject,
|
|
100
|
+
{ dbObject, serviceOptions }
|
|
98
101
|
);
|
|
99
102
|
}
|
|
100
103
|
}
|
|
@@ -150,7 +150,6 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
150
150
|
*/
|
|
151
151
|
async prepareInput(input: Record<string, any>, options: ServiceOptions = {}) {
|
|
152
152
|
const config = {
|
|
153
|
-
targetModel: this.mainModelConstructor,
|
|
154
153
|
...options?.prepareInput,
|
|
155
154
|
};
|
|
156
155
|
return prepareInput(input, options.currentUser, config);
|