@lenne.tech/nest-server 2.0.2 → 3.1.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 +18 -5
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/args/filter.args.js +3 -3
- package/dist/core/common/args/filter.args.js.map +1 -1
- package/dist/core/common/args/pagination.args.js +13 -13
- package/dist/core/common/args/pagination.args.js.map +1 -1
- package/dist/core/common/decorators/graphql-user.decorator.js +1 -1
- package/dist/core/common/decorators/graphql-user.decorator.js.map +1 -1
- package/dist/core/common/decorators/rest-user.decorator.js +1 -1
- package/dist/core/common/decorators/rest-user.decorator.js.map +1 -1
- package/dist/core/common/decorators/restricted.decorator.js +3 -3
- package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
- package/dist/core/common/decorators/roles.decorator.js +1 -1
- package/dist/core/common/decorators/roles.decorator.js.map +1 -1
- package/dist/core/common/enums/comparison-operator.enum.js +1 -1
- package/dist/core/common/enums/comparison-operator.enum.js.map +1 -1
- package/dist/core/common/enums/logical-operator.enum.js +1 -1
- package/dist/core/common/enums/logical-operator.enum.js.map +1 -1
- package/dist/core/common/enums/sort-order.emum.js +1 -1
- package/dist/core/common/enums/sort-order.emum.js.map +1 -1
- package/dist/core/common/helpers/file.helper.js +3 -3
- package/dist/core/common/helpers/file.helper.js.map +1 -1
- package/dist/core/common/helpers/input.helper.js +3 -3
- package/dist/core/common/helpers/input.helper.js.map +1 -1
- package/dist/core/common/helpers/service.helper.d.ts +1 -0
- package/dist/core/common/helpers/service.helper.js +20 -1
- package/dist/core/common/helpers/service.helper.js.map +1 -1
- package/dist/core/common/inputs/combined-filter.input.js +3 -3
- package/dist/core/common/inputs/combined-filter.input.js.map +1 -1
- package/dist/core/common/inputs/filter.input.js +3 -3
- package/dist/core/common/inputs/filter.input.js.map +1 -1
- package/dist/core/common/inputs/single-filter.input.js +6 -6
- package/dist/core/common/inputs/single-filter.input.js.map +1 -1
- package/dist/core/common/inputs/sort.input.js +3 -3
- package/dist/core/common/inputs/sort.input.js.map +1 -1
- package/dist/core/common/interceptors/check-response.interceptor.js +3 -3
- package/dist/core/common/interceptors/check-response.interceptor.js.map +1 -1
- package/dist/core/common/interfaces/mailjet-options.interface.d.ts +4 -0
- package/dist/core/common/interfaces/mailjet-options.interface.js +3 -0
- package/dist/core/common/interfaces/mailjet-options.interface.js.map +1 -0
- package/dist/core/common/interfaces/server-options.interface.d.ts +4 -0
- package/dist/core/common/models/core-persistence.model.js +14 -14
- package/dist/core/common/models/core-persistence.model.js.map +1 -1
- package/dist/core/common/pipes/check-input.pipe.js +5 -5
- package/dist/core/common/pipes/check-input.pipe.js.map +1 -1
- package/dist/core/common/scalars/any.scalar.js +1 -1
- package/dist/core/common/scalars/any.scalar.js.map +1 -1
- package/dist/core/common/scalars/date.scalar.js +1 -1
- package/dist/core/common/scalars/date.scalar.js.map +1 -1
- package/dist/core/common/scalars/json.scalar.js +1 -1
- package/dist/core/common/scalars/json.scalar.js.map +1 -1
- package/dist/core/common/services/config.service.d.ts +1 -1
- package/dist/core/common/services/config.service.js +3 -3
- package/dist/core/common/services/config.service.js.map +1 -1
- package/dist/core/common/services/email.service.js +1 -1
- package/dist/core/common/services/email.service.js.map +1 -1
- package/dist/core/common/services/mailjet.service.d.ts +15 -0
- package/dist/core/common/services/mailjet.service.js +64 -0
- package/dist/core/common/services/mailjet.service.js.map +1 -0
- package/dist/core/common/services/template.service.js +2 -2
- package/dist/core/common/services/template.service.js.map +1 -1
- package/dist/core/modules/auth/core-auth.model.js +2 -2
- package/dist/core/modules/auth/core-auth.model.js.map +1 -1
- package/dist/core/modules/auth/core-auth.module.js +1 -1
- package/dist/core/modules/auth/core-auth.module.js.map +1 -1
- package/dist/core/modules/auth/core-auth.resolver.js +4 -4
- package/dist/core/modules/auth/core-auth.resolver.js.map +1 -1
- package/dist/core/modules/auth/guards/auth.guard.js +3 -3
- package/dist/core/modules/auth/guards/auth.guard.js.map +1 -1
- package/dist/core/modules/auth/guards/roles.guard.js +2 -2
- package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
- package/dist/core/modules/auth/jwt.strategy.js +2 -2
- package/dist/core/modules/auth/jwt.strategy.js.map +1 -1
- package/dist/core/modules/auth/services/core-auth.service.js +1 -1
- package/dist/core/modules/auth/services/core-auth.service.js.map +1 -1
- package/dist/core/modules/user/core-basic-user.service.d.ts +1 -1
- package/dist/core/modules/user/core-user.model.d.ts +3 -0
- package/dist/core/modules/user/core-user.model.js +35 -18
- package/dist/core/modules/user/core-user.model.js.map +1 -1
- package/dist/core/modules/user/core-user.service.d.ts +4 -3
- package/dist/core/modules/user/core-user.service.js +28 -1
- package/dist/core/modules/user/core-user.service.js.map +1 -1
- package/dist/core/modules/user/inputs/core-user-create.input.js +3 -3
- package/dist/core/modules/user/inputs/core-user-create.input.js.map +1 -1
- package/dist/core/modules/user/inputs/core-user.input.js +15 -15
- package/dist/core/modules/user/inputs/core-user.input.js.map +1 -1
- package/dist/core.module.js +5 -3
- package/dist/core.module.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/common/models/persistence.model.js +5 -5
- package/dist/server/common/models/persistence.model.js.map +1 -1
- package/dist/server/modules/auth/auth.model.js +2 -2
- package/dist/server/modules/auth/auth.model.js.map +1 -1
- package/dist/server/modules/auth/auth.module.js +1 -1
- package/dist/server/modules/auth/auth.module.js.map +1 -1
- package/dist/server/modules/auth/auth.resolver.js +4 -4
- package/dist/server/modules/auth/auth.resolver.js.map +1 -1
- package/dist/server/modules/file/file.controller.js +7 -7
- package/dist/server/modules/file/file.controller.js.map +1 -1
- package/dist/server/modules/user/avatar.controller.js +6 -6
- package/dist/server/modules/user/avatar.controller.js.map +1 -1
- package/dist/server/modules/user/inputs/user-create.input.js +1 -1
- package/dist/server/modules/user/inputs/user-create.input.js.map +1 -1
- package/dist/server/modules/user/inputs/user.input.js +1 -1
- package/dist/server/modules/user/inputs/user.input.js.map +1 -1
- package/dist/server/modules/user/user.model.d.ts +1 -1
- package/dist/server/modules/user/user.model.js +8 -8
- package/dist/server/modules/user/user.model.js.map +1 -1
- package/dist/server/modules/user/user.module.js +1 -1
- package/dist/server/modules/user/user.module.js.map +1 -1
- package/dist/server/modules/user/user.resolver.d.ts +3 -0
- package/dist/server/modules/user/user.resolver.js +55 -24
- package/dist/server/modules/user/user.resolver.js.map +1 -1
- package/dist/server/modules/user/user.service.d.ts +3 -0
- package/dist/server/modules/user/user.service.js +47 -6
- package/dist/server/modules/user/user.service.js.map +1 -1
- package/dist/server/server.controller.js +5 -5
- package/dist/server/server.controller.js.map +1 -1
- package/dist/server/server.module.js +1 -1
- package/dist/server/server.module.js.map +1 -1
- package/dist/test/test.helper.js +2 -2
- package/dist/test/test.helper.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +48 -54
- package/src/config.env.ts +14 -1
- package/src/core/common/helpers/service.helper.ts +27 -2
- package/src/core/common/interfaces/mailjet-options.interface.ts +4 -0
- package/src/core/common/interfaces/server-options.interface.ts +12 -0
- package/src/core/common/models/core-persistence.model.ts +1 -1
- package/src/core/common/services/config.service.ts +7 -5
- package/src/core/common/services/mailjet.service.ts +84 -0
- package/src/core/modules/user/core-basic-user.service.ts +3 -3
- package/src/core/modules/user/core-user.model.ts +20 -0
- package/src/core/modules/user/core-user.service.ts +54 -8
- package/src/core.module.ts +3 -1
- package/src/index.ts +1 -0
- package/src/server/modules/user/user.resolver.ts +24 -0
- package/src/server/modules/user/user.service.ts +86 -4
- package/src/server/server.module.ts +0 -1
- package/src/templates/password-reset.ejs +3 -0
- package/src/templates/welcome.ejs +3 -2
- package/src/test/test.helper.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.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",
|
|
@@ -49,82 +49,76 @@
|
|
|
49
49
|
"url": "https://github.com/lenneTech/nest-server/issues"
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|
|
52
|
-
"node": ">=
|
|
53
|
-
},
|
|
54
|
-
"peerDependencies": {
|
|
55
|
-
"@nestjs/testing": "8.0.6",
|
|
56
|
-
"@nestjs/core": "8.0.6",
|
|
57
|
-
"@nestjs/common": "8.0.6",
|
|
58
|
-
"@nestjs/graphql": "9.0.2",
|
|
59
|
-
"@nestjs/jwt": "8.0.0",
|
|
60
|
-
"@nestjs/mongoose": "8.0.1",
|
|
61
|
-
"@nestjs/passport": "8.0.1",
|
|
62
|
-
"@nestjs/platform-express": "8.0.6"
|
|
52
|
+
"node": ">= 16.13.0"
|
|
63
53
|
},
|
|
64
54
|
"dependencies": {
|
|
65
|
-
"@apollo/federation": "0.
|
|
66
|
-
"@apollo/gateway": "0.
|
|
67
|
-
"@nestjs/
|
|
68
|
-
"@nestjs/core": "8.
|
|
69
|
-
"@nestjs/
|
|
70
|
-
"@nestjs/graphql": "9.0.2",
|
|
55
|
+
"@apollo/federation": "0.33.9",
|
|
56
|
+
"@apollo/gateway": "0.42.3",
|
|
57
|
+
"@nestjs/common": "8.2.6",
|
|
58
|
+
"@nestjs/core": "8.2.6",
|
|
59
|
+
"@nestjs/graphql": "9.1.2",
|
|
71
60
|
"@nestjs/jwt": "8.0.0",
|
|
72
|
-
"@nestjs/mongoose": "
|
|
73
|
-
"@nestjs/passport": "8.0
|
|
74
|
-
"@nestjs/platform-express": "8.
|
|
61
|
+
"@nestjs/mongoose": "9.0.2",
|
|
62
|
+
"@nestjs/passport": "8.1.0",
|
|
63
|
+
"@nestjs/platform-express": "8.2.6",
|
|
64
|
+
"@nestjs/testing": "8.2.6",
|
|
65
|
+
"@shelf/jest-mongodb": "2.2.0",
|
|
75
66
|
"@types/ejs": "3.1.0",
|
|
76
|
-
"@types/jest": "27.0
|
|
77
|
-
"@types/lodash": "4.14.
|
|
67
|
+
"@types/jest": "27.4.0",
|
|
68
|
+
"@types/lodash": "4.14.178",
|
|
78
69
|
"@types/multer": "1.4.7",
|
|
79
|
-
"@types/node": "
|
|
70
|
+
"@types/node": "16.11.21",
|
|
71
|
+
"@types/node-mailjet": "3.3.8",
|
|
80
72
|
"@types/nodemailer": "6.4.4",
|
|
81
73
|
"@types/passport": "1.0.7",
|
|
82
74
|
"@types/supertest": "2.0.11",
|
|
83
|
-
"@typescript-eslint/eslint-plugin": "
|
|
84
|
-
"@typescript-eslint/parser": "
|
|
85
|
-
"apollo-server-core": "3.
|
|
86
|
-
"apollo-server-express": "3.
|
|
75
|
+
"@typescript-eslint/eslint-plugin": "5.10.0",
|
|
76
|
+
"@typescript-eslint/parser": "5.10.0",
|
|
77
|
+
"apollo-server-core": "3.6.2",
|
|
78
|
+
"apollo-server-express": "3.6.2",
|
|
87
79
|
"bcrypt": "5.0.1",
|
|
88
|
-
"class-transformer": "0.
|
|
89
|
-
"class-validator": "0.13.
|
|
90
|
-
"coffeescript": "2.
|
|
80
|
+
"class-transformer": "0.5.1",
|
|
81
|
+
"class-validator": "0.13.2",
|
|
82
|
+
"coffeescript": "2.6.1",
|
|
91
83
|
"ejs": "3.1.6",
|
|
92
|
-
"fastify": "3.
|
|
93
|
-
"graphql": "15.
|
|
94
|
-
"graphql-subscriptions": "
|
|
84
|
+
"fastify": "3.27.0",
|
|
85
|
+
"graphql": "15.8.0",
|
|
86
|
+
"graphql-subscriptions": "2.0.0",
|
|
95
87
|
"grunt": "1.4.1",
|
|
96
88
|
"grunt-bg-shell": "2.3.3",
|
|
97
89
|
"grunt-contrib-clean": "2.0.0",
|
|
98
90
|
"grunt-contrib-watch": "1.1.0",
|
|
99
91
|
"grunt-sync": "0.8.2",
|
|
100
|
-
"husky": "7.0.
|
|
101
|
-
"jest": "27.
|
|
102
|
-
"json-to-graphql-query": "2.
|
|
103
|
-
"light-my-request": "4.
|
|
92
|
+
"husky": "7.0.4",
|
|
93
|
+
"jest": "27.4.7",
|
|
94
|
+
"json-to-graphql-query": "2.2.0",
|
|
95
|
+
"light-my-request": "4.7.0",
|
|
104
96
|
"lodash": "4.17.21",
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
97
|
+
"mongodb": "4.3.0",
|
|
98
|
+
"mongoose": "6.1.7",
|
|
99
|
+
"multer": "1.4.4",
|
|
100
|
+
"node-mailjet": "3.3.5",
|
|
101
|
+
"nodemailer": "6.7.2",
|
|
102
|
+
"nodemon": "2.0.15",
|
|
103
|
+
"passport": "0.5.2",
|
|
110
104
|
"passport-jwt": "4.0.0",
|
|
111
105
|
"reflect-metadata": "0.1.13",
|
|
112
106
|
"rimraf": "3.0.2",
|
|
113
|
-
"rxjs": "7.
|
|
114
|
-
"supertest": "6.
|
|
115
|
-
"ts-morph": "
|
|
116
|
-
"ts-node": "
|
|
117
|
-
"tsconfig-paths": "3.
|
|
107
|
+
"rxjs": "7.5.2",
|
|
108
|
+
"supertest": "6.2.2",
|
|
109
|
+
"ts-morph": "13.0.3",
|
|
110
|
+
"ts-node": "10.4.0",
|
|
111
|
+
"tsconfig-paths": "3.12.0"
|
|
118
112
|
},
|
|
119
113
|
"devDependencies": {
|
|
120
|
-
"eslint": "7.
|
|
114
|
+
"eslint": "8.7.0",
|
|
121
115
|
"eslint-config-prettier": "8.3.0",
|
|
122
116
|
"find-file-up": "2.0.1",
|
|
123
|
-
"pm2": "5.1.
|
|
124
|
-
"prettier": "2.
|
|
125
|
-
"pretty-quick": "3.1.
|
|
126
|
-
"ts-jest": "27.
|
|
127
|
-
"typescript": "4.
|
|
117
|
+
"pm2": "5.1.2",
|
|
118
|
+
"prettier": "2.5.1",
|
|
119
|
+
"pretty-quick": "3.1.3",
|
|
120
|
+
"ts-jest": "27.1.3",
|
|
121
|
+
"typescript": "4.5.5"
|
|
128
122
|
},
|
|
129
123
|
"jest": {
|
|
130
124
|
"collectCoverage": true,
|
package/src/config.env.ts
CHANGED
|
@@ -19,10 +19,16 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
19
19
|
port: 587,
|
|
20
20
|
secure: false,
|
|
21
21
|
},
|
|
22
|
+
mailjet: {
|
|
23
|
+
api_key_public: 'MAILJET_API_KEY_PUBLIC',
|
|
24
|
+
api_key_private: 'MAILJET_API_KEY_PRIVATE',
|
|
25
|
+
},
|
|
22
26
|
defaultSender: {
|
|
23
27
|
email: 'rebeca68@ethereal.email',
|
|
24
28
|
name: 'Rebeca Sixtyeight',
|
|
25
29
|
},
|
|
30
|
+
verificationLink: 'http://localhost:4200/user/verification',
|
|
31
|
+
passwordResetLink: 'http://localhost:4200/user/password-reset',
|
|
26
32
|
},
|
|
27
33
|
env: 'development',
|
|
28
34
|
graphQl: {
|
|
@@ -60,10 +66,16 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
60
66
|
port: 587,
|
|
61
67
|
secure: false,
|
|
62
68
|
},
|
|
69
|
+
mailjet: {
|
|
70
|
+
api_key_public: 'MAILJET_API_KEY_PUBLIC',
|
|
71
|
+
api_key_private: 'MAILJET_API_KEY_PRIVATE',
|
|
72
|
+
},
|
|
63
73
|
defaultSender: {
|
|
64
74
|
email: 'rebeca68@ethereal.email',
|
|
65
75
|
name: 'Rebeca Sixtyeight',
|
|
66
76
|
},
|
|
77
|
+
verificationLink: 'http://localhost:4200/user/verification',
|
|
78
|
+
passwordResetLink: 'http://localhost:4200/user/password-reset',
|
|
67
79
|
},
|
|
68
80
|
env: 'productive',
|
|
69
81
|
graphQl: {
|
|
@@ -93,7 +105,8 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
93
105
|
*
|
|
94
106
|
* default: development
|
|
95
107
|
*/
|
|
96
|
-
const envConfig = config[process.env
|
|
108
|
+
const envConfig = config[process.env['NODE' + '_ENV'] || 'development'] || config.development;
|
|
109
|
+
console.log('Server starts in mode: ', process.env['NODE' + '_ENV'] || 'development');
|
|
97
110
|
|
|
98
111
|
/**
|
|
99
112
|
* Export envConfig as default
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { UnauthorizedException } from '@nestjs/common';
|
|
1
2
|
import * as bcrypt from 'bcrypt';
|
|
3
|
+
import * as _ from 'lodash';
|
|
4
|
+
import { RoleEnum } from '../enums/role.enum';
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Helper class for services
|
|
@@ -9,13 +12,15 @@ export class ServiceHelper {
|
|
|
9
12
|
*/
|
|
10
13
|
static async prepareInput(
|
|
11
14
|
input: { [key: string]: any },
|
|
12
|
-
currentUser: { id: string },
|
|
15
|
+
currentUser: { [key: string]: any; id: string },
|
|
13
16
|
options: { [key: string]: any; create?: boolean; clone?: boolean } = {},
|
|
14
17
|
...args: any[]
|
|
15
18
|
) {
|
|
16
19
|
// Configuration
|
|
17
20
|
const config = {
|
|
21
|
+
checkRoles: true,
|
|
18
22
|
clone: false,
|
|
23
|
+
create: false,
|
|
19
24
|
...options,
|
|
20
25
|
};
|
|
21
26
|
|
|
@@ -24,7 +29,21 @@ export class ServiceHelper {
|
|
|
24
29
|
input = JSON.parse(JSON.stringify(input));
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
//
|
|
32
|
+
// Process roles
|
|
33
|
+
if (input.roles && config.checkRoles && (!currentUser?.hasRole || !currentUser.hasRole(RoleEnum.ADMIN))) {
|
|
34
|
+
if (!(currentUser as any)?.roles) {
|
|
35
|
+
throw new UnauthorizedException('Missing roles of current user');
|
|
36
|
+
} else {
|
|
37
|
+
const allowedRoles = _.intersection(input.roles, (currentUser as any).roles);
|
|
38
|
+
if (allowedRoles.length !== input.roles.length) {
|
|
39
|
+
const missingRoles = _.difference(input.roles, (currentUser as any).roles);
|
|
40
|
+
throw new UnauthorizedException('Current user not allowed setting roles: ' + missingRoles);
|
|
41
|
+
}
|
|
42
|
+
input.roles = allowedRoles;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Hash password
|
|
28
47
|
if (input.password) {
|
|
29
48
|
input.password = await bcrypt.hash((input as any).password, 10);
|
|
30
49
|
}
|
|
@@ -67,6 +86,12 @@ export class ServiceHelper {
|
|
|
67
86
|
// Remove password if exists
|
|
68
87
|
delete output.password;
|
|
69
88
|
|
|
89
|
+
// Remove verification token if exists
|
|
90
|
+
delete output.verificationToken;
|
|
91
|
+
|
|
92
|
+
// Remove password reset token if exists
|
|
93
|
+
delete output.passwordResetToken;
|
|
94
|
+
|
|
70
95
|
// Return prepared user
|
|
71
96
|
return output;
|
|
72
97
|
}
|
|
@@ -3,6 +3,7 @@ import { JwtModuleOptions } from '@nestjs/jwt';
|
|
|
3
3
|
import { ServeStaticOptions } from '@nestjs/platform-express/interfaces/serve-static-options.interface';
|
|
4
4
|
import * as SMTPTransport from 'nodemailer/lib/smtp-transport';
|
|
5
5
|
import { MongooseModuleOptions } from '@nestjs/mongoose/dist/interfaces/mongoose-options.interface';
|
|
6
|
+
import { MailjetOptions } from './mailjet-options.interface';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Options for the server
|
|
@@ -101,6 +102,17 @@ export interface IServerOptions {
|
|
|
101
102
|
*/
|
|
102
103
|
smtp?: SMTPTransport | SMTPTransport.Options | string;
|
|
103
104
|
|
|
105
|
+
mailjet?: MailjetOptions;
|
|
106
|
+
/**
|
|
107
|
+
* Verification link for email
|
|
108
|
+
*/
|
|
109
|
+
verificationLink?: string;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Password reset link for email
|
|
113
|
+
*/
|
|
114
|
+
passwordResetLink?: string;
|
|
115
|
+
|
|
104
116
|
/**
|
|
105
117
|
* Data for default sender
|
|
106
118
|
*/
|
|
@@ -19,7 +19,7 @@ export abstract class CorePersistenceModel {
|
|
|
19
19
|
// Getter
|
|
20
20
|
// ===========================================================================
|
|
21
21
|
get _id() {
|
|
22
|
-
return mongoose.Types.ObjectId(this.id);
|
|
22
|
+
return new mongoose.Types.ObjectId(this.id);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
// ===========================================================================
|
|
@@ -14,20 +14,22 @@ export class ConfigService {
|
|
|
14
14
|
* Create config service
|
|
15
15
|
*/
|
|
16
16
|
constructor(config: { [key: string]: any } & Partial<IServerOptions>) {
|
|
17
|
-
this._config = config;
|
|
17
|
+
this._config = config || {};
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Get config
|
|
21
|
+
* Get config (deep cloned to avoid unwanted side effects)
|
|
22
22
|
*/
|
|
23
23
|
get config() {
|
|
24
24
|
return _.cloneDeep(this._config);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Get data from config
|
|
28
|
+
* Get data from config (deep cloned to avoid unwanted side effects)
|
|
29
|
+
* @param key Property name of config object, which is to be returned
|
|
30
|
+
* @param defaultValue Default value which is to be returned if property doesn't exist
|
|
29
31
|
*/
|
|
30
|
-
get(key: string) {
|
|
31
|
-
return _.cloneDeep(_.get(this._config, key,
|
|
32
|
+
get(key: string, defaultValue: any = undefined) {
|
|
33
|
+
return _.cloneDeep(_.get(this._config, key, defaultValue));
|
|
32
34
|
}
|
|
33
35
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { ConfigService } from './config.service';
|
|
3
|
+
import * as mailjet from 'node-mailjet';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mailjet service
|
|
7
|
+
*/
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class MailjetService {
|
|
10
|
+
/**
|
|
11
|
+
* Inject services
|
|
12
|
+
*/
|
|
13
|
+
constructor(protected configService: ConfigService) {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Send a mail
|
|
17
|
+
*/
|
|
18
|
+
public async sendMail(
|
|
19
|
+
recipients: string | string[],
|
|
20
|
+
subject: string,
|
|
21
|
+
templateId: number,
|
|
22
|
+
config: {
|
|
23
|
+
senderEmail?: string;
|
|
24
|
+
senderName?: string;
|
|
25
|
+
attachments?: mailjet.Email.Attachment[];
|
|
26
|
+
templateData?: { [key: string]: any };
|
|
27
|
+
sandbox?: boolean;
|
|
28
|
+
}
|
|
29
|
+
): Promise<mailjet.Email.PostResponse> {
|
|
30
|
+
// Process config
|
|
31
|
+
const { senderName, senderEmail, templateData, attachments, sandbox } = {
|
|
32
|
+
senderEmail: this.configService.get('email.defaultSender.email'),
|
|
33
|
+
senderName: this.configService.get('email.defaultSender.name'),
|
|
34
|
+
sandbox: false,
|
|
35
|
+
attachments: null,
|
|
36
|
+
templateData: null,
|
|
37
|
+
...config,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Parse recipients
|
|
41
|
+
let to;
|
|
42
|
+
if (Array.isArray(recipients)) {
|
|
43
|
+
to = [];
|
|
44
|
+
for (const recipient of recipients) {
|
|
45
|
+
to.push({ Email: recipient });
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
to = [{ Email: recipients }];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Parse body for mailjet request
|
|
52
|
+
const body: mailjet.Email.SendParams = {
|
|
53
|
+
Messages: [
|
|
54
|
+
{
|
|
55
|
+
From: {
|
|
56
|
+
Email: senderEmail,
|
|
57
|
+
Name: senderName,
|
|
58
|
+
},
|
|
59
|
+
To: to,
|
|
60
|
+
TemplateID: templateId,
|
|
61
|
+
TemplateLanguage: true,
|
|
62
|
+
Variables: templateData,
|
|
63
|
+
Subject: subject,
|
|
64
|
+
Attachments: attachments,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
SandboxMode: sandbox,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let connection: mailjet.Email.Client;
|
|
71
|
+
try {
|
|
72
|
+
// Connect to mailjet
|
|
73
|
+
connection = await mailjet.connect(
|
|
74
|
+
this.configService.get('email.mailjet.api_key_public'),
|
|
75
|
+
this.configService.get('email.mailjet.api_key_private')
|
|
76
|
+
);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
throw new Error('Cannot connect to mailjet.');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Send mail with mailjet
|
|
82
|
+
return connection.post('send', { version: 'v3.1' }).request(body);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -16,9 +16,9 @@ const pubSub = new PubSub();
|
|
|
16
16
|
* User service
|
|
17
17
|
*/
|
|
18
18
|
export abstract class CoreBasicUserService<
|
|
19
|
-
TUser
|
|
20
|
-
TUserInput
|
|
21
|
-
TUserCreateInput
|
|
19
|
+
TUser extends CoreUserModel,
|
|
20
|
+
TUserInput extends CoreUserInput,
|
|
21
|
+
TUserCreateInput extends CoreUserCreateInput
|
|
22
22
|
> {
|
|
23
23
|
protected readonly model: ICorePersistenceModel;
|
|
24
24
|
|
|
@@ -59,6 +59,26 @@ export abstract class CoreUserModel extends CorePersistenceModel {
|
|
|
59
59
|
@Prop()
|
|
60
60
|
username: string = undefined;
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Password reset token of the user
|
|
64
|
+
*/
|
|
65
|
+
@IsOptional()
|
|
66
|
+
@Prop()
|
|
67
|
+
passwordResetToken: string = undefined;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Verification token of the user
|
|
71
|
+
*/
|
|
72
|
+
@IsOptional()
|
|
73
|
+
@Prop()
|
|
74
|
+
verificationToken: string = undefined;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Verification of the user
|
|
78
|
+
*/
|
|
79
|
+
@Prop({ type: Boolean })
|
|
80
|
+
verified = false;
|
|
81
|
+
|
|
62
82
|
// ===================================================================================================================
|
|
63
83
|
// Methods
|
|
64
84
|
// ===================================================================================================================
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
BadRequestException,
|
|
3
|
+
NotFoundException,
|
|
4
|
+
UnauthorizedException,
|
|
5
|
+
UnprocessableEntityException,
|
|
6
|
+
} from '@nestjs/common';
|
|
2
7
|
import * as bcrypt from 'bcrypt';
|
|
3
8
|
import { PubSub } from 'graphql-subscriptions';
|
|
4
9
|
import { FilterArgs } from '../../common/args/filter.args';
|
|
10
|
+
import { RoleEnum } from '../../common/enums/role.enum';
|
|
5
11
|
import { Filter } from '../../common/helpers/filter.helper';
|
|
6
12
|
import { CoreBasicUserService } from './core-basic-user.service';
|
|
7
13
|
import { CoreUserModel } from './core-user.model';
|
|
8
14
|
import { CoreUserCreateInput } from './inputs/core-user-create.input';
|
|
9
15
|
import { CoreUserInput } from './inputs/core-user.input';
|
|
10
16
|
import { Model } from 'mongoose';
|
|
17
|
+
import * as _ from 'lodash';
|
|
11
18
|
|
|
12
19
|
// Subscription
|
|
13
20
|
const pubSub = new PubSub();
|
|
@@ -16,9 +23,9 @@ const pubSub = new PubSub();
|
|
|
16
23
|
* User service
|
|
17
24
|
*/
|
|
18
25
|
export abstract class CoreUserService<
|
|
19
|
-
TUser
|
|
20
|
-
TUserInput
|
|
21
|
-
TUserCreateInput
|
|
26
|
+
TUser extends CoreUserModel,
|
|
27
|
+
TUserInput extends CoreUserInput,
|
|
28
|
+
TUserCreateInput extends CoreUserCreateInput
|
|
22
29
|
> extends CoreBasicUserService<TUser, TUserInput, TUserCreateInput> {
|
|
23
30
|
protected constructor(protected readonly userModel: Model<any>) {
|
|
24
31
|
super(userModel);
|
|
@@ -108,6 +115,24 @@ export abstract class CoreUserService<
|
|
|
108
115
|
);
|
|
109
116
|
}
|
|
110
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Set roles for specified user
|
|
120
|
+
*/
|
|
121
|
+
async setRoles(userId: string, roles: string[]): Promise<TUser> {
|
|
122
|
+
// Check roles
|
|
123
|
+
if (!Array.isArray(roles)) {
|
|
124
|
+
throw new BadRequestException('Missing roles');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check roles values
|
|
128
|
+
if (roles.some((role) => typeof role !== 'string')) {
|
|
129
|
+
throw new BadRequestException('roles contains invalid values');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Update and return user
|
|
133
|
+
return this.userModel.findByIdAndUpdate(userId, { roles }).exec();
|
|
134
|
+
}
|
|
135
|
+
|
|
111
136
|
/**
|
|
112
137
|
* Update user via ID
|
|
113
138
|
*/
|
|
@@ -144,22 +169,37 @@ export abstract class CoreUserService<
|
|
|
144
169
|
*/
|
|
145
170
|
protected async prepareInput(
|
|
146
171
|
input: { [key: string]: any },
|
|
147
|
-
currentUser
|
|
148
|
-
options: { [key: string]: any;
|
|
172
|
+
currentUser?: TUser,
|
|
173
|
+
options: { [key: string]: any; checkRoles?: boolean; clone?: boolean } = {},
|
|
149
174
|
...args: any[]
|
|
150
175
|
) {
|
|
151
176
|
// Configuration
|
|
152
177
|
const config = {
|
|
178
|
+
checkRoles: true,
|
|
153
179
|
clone: false,
|
|
154
180
|
...options,
|
|
155
181
|
};
|
|
156
182
|
|
|
157
|
-
// Clone
|
|
183
|
+
// Clone input
|
|
158
184
|
if (config.clone) {
|
|
159
185
|
input = JSON.parse(JSON.stringify(input));
|
|
160
186
|
}
|
|
161
187
|
|
|
162
|
-
//
|
|
188
|
+
// Process roles
|
|
189
|
+
if (input.roles && config.checkRoles && (!currentUser?.hasRole || !currentUser.hasRole(RoleEnum.ADMIN))) {
|
|
190
|
+
if (!(currentUser as any)?.roles) {
|
|
191
|
+
throw new UnauthorizedException('Missing roles of current user');
|
|
192
|
+
} else {
|
|
193
|
+
const allowedRoles = _.intersection(input.roles, (currentUser as any).roles);
|
|
194
|
+
if (allowedRoles.length !== input.roles.length) {
|
|
195
|
+
const missingRoles = _.difference(input.roles, (currentUser as any).roles);
|
|
196
|
+
throw new UnauthorizedException('Current user not allowed setting roles: ' + missingRoles);
|
|
197
|
+
}
|
|
198
|
+
input.roles = allowedRoles;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Hash password
|
|
163
203
|
if (input.password) {
|
|
164
204
|
input.password = await bcrypt.hash((input as any).password, 10);
|
|
165
205
|
}
|
|
@@ -190,6 +230,12 @@ export abstract class CoreUserService<
|
|
|
190
230
|
// Remove password if exists
|
|
191
231
|
delete (user as any).password;
|
|
192
232
|
|
|
233
|
+
// Remove verification token if exists
|
|
234
|
+
delete (user as any).verificationToken;
|
|
235
|
+
|
|
236
|
+
// Remove password reset token if exists
|
|
237
|
+
delete (user as any).passwordResetToken;
|
|
238
|
+
|
|
193
239
|
// Return prepared user
|
|
194
240
|
return user;
|
|
195
241
|
}
|
package/src/core.module.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { ConfigService } from './core/common/services/config.service';
|
|
|
9
9
|
import { EmailService } from './core/common/services/email.service';
|
|
10
10
|
import { TemplateService } from './core/common/services/template.service';
|
|
11
11
|
import { MongooseModule } from '@nestjs/mongoose';
|
|
12
|
+
import { MailjetService } from './core/common/services/mailjet.service';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Core module (dynamic)
|
|
@@ -91,6 +92,7 @@ export class CoreModule {
|
|
|
91
92
|
// Core Services
|
|
92
93
|
EmailService,
|
|
93
94
|
TemplateService,
|
|
95
|
+
MailjetService,
|
|
94
96
|
];
|
|
95
97
|
|
|
96
98
|
// Return dynamic module
|
|
@@ -101,7 +103,7 @@ export class CoreModule {
|
|
|
101
103
|
GraphQLModule.forRoot(config.graphQl),
|
|
102
104
|
],
|
|
103
105
|
providers,
|
|
104
|
-
exports: [ConfigService, EmailService, TemplateService],
|
|
106
|
+
exports: [ConfigService, EmailService, TemplateService, MailjetService],
|
|
105
107
|
};
|
|
106
108
|
}
|
|
107
109
|
}
|
package/src/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ export * from './core/common/scalars/json.scalar';
|
|
|
41
41
|
export * from './core/common/services/config.service';
|
|
42
42
|
export * from './core/common/services/email.service';
|
|
43
43
|
export * from './core/common/services/template.service';
|
|
44
|
+
export * from './core/common/services/mailjet.service';
|
|
44
45
|
|
|
45
46
|
// =====================================================================================================================
|
|
46
47
|
// Core - Modules - Auth
|
|
@@ -43,6 +43,30 @@ export class UserResolver {
|
|
|
43
43
|
return await this.usersService.find(args, info);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Verify user with email
|
|
48
|
+
*/
|
|
49
|
+
@Query((returns) => Boolean, { description: 'Verify user with email' })
|
|
50
|
+
async verifyUser(@Args('token') token: string) {
|
|
51
|
+
return await this.usersService.verify(token);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Request new password for user with email
|
|
56
|
+
*/
|
|
57
|
+
@Query((returns) => Boolean, { description: 'Request new password for user with email' })
|
|
58
|
+
async requestPasswordResetMail(@Args('email') email: string) {
|
|
59
|
+
return await this.usersService.requestPasswordResetMail(email);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Set new password for user with token
|
|
64
|
+
*/
|
|
65
|
+
@Query((returns) => Boolean, { description: 'Set new password for user with token' })
|
|
66
|
+
async resetPassword(@Args('token') token: string, @Args('password') password: string) {
|
|
67
|
+
return await this.usersService.resetPassword(token, password);
|
|
68
|
+
}
|
|
69
|
+
|
|
46
70
|
// ===========================================================================
|
|
47
71
|
// Mutations
|
|
48
72
|
// ===========================================================================
|