@lenne.tech/nest-server 11.2.0 → 11.3.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/core/common/decorators/unified-field.decorator.d.ts +2 -0
- package/dist/core/common/decorators/unified-field.decorator.js +26 -9
- package/dist/core/common/decorators/unified-field.decorator.js.map +1 -1
- package/dist/core/common/models/core-persistence.model.js +2 -2
- package/dist/core/common/models/core-persistence.model.js.map +1 -1
- package/dist/core/modules/file/core-file-info.model.js +41 -23
- package/dist/core/modules/file/core-file-info.model.js.map +1 -1
- package/dist/core/modules/user/core-user.model.js +95 -54
- package/dist/core/modules/user/core-user.model.js.map +1 -1
- package/dist/main.js +22 -0
- package/dist/main.js.map +1 -1
- package/dist/server/common/models/persistence.model.js +13 -11
- package/dist/server/common/models/persistence.model.js.map +1 -1
- package/dist/server/modules/auth/auth.model.js +6 -2
- package/dist/server/modules/auth/auth.model.js.map +1 -1
- package/dist/server/modules/user/user.controller.d.ts +19 -0
- package/dist/server/modules/user/user.controller.js +256 -0
- package/dist/server/modules/user/user.controller.js.map +1 -0
- package/dist/server/modules/user/user.model.js +37 -24
- package/dist/server/modules/user/user.model.js.map +1 -1
- package/dist/server/modules/user/user.module.js +2 -1
- package/dist/server/modules/user/user.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +27 -28
- package/src/core/common/decorators/unified-field.decorator.ts +49 -10
- package/src/core/common/models/core-persistence.model.ts +3 -3
- package/src/core/modules/file/core-file-info.model.ts +40 -22
- package/src/core/modules/user/core-user.model.ts +120 -78
- package/src/main.ts +25 -0
- package/src/server/common/models/persistence.model.ts +15 -13
- package/src/server/modules/auth/auth.model.ts +7 -3
- package/src/server/modules/user/user.controller.ts +242 -0
- package/src/server/modules/user/user.model.ts +39 -26
- package/src/server/modules/user/user.module.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.3.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",
|
|
@@ -67,18 +67,18 @@
|
|
|
67
67
|
"node": ">= 20"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@apollo/gateway": "2.
|
|
70
|
+
"@apollo/gateway": "2.12.0",
|
|
71
71
|
"@getbrevo/brevo": "3.0.1",
|
|
72
72
|
"@nestjs/apollo": "13.1.0",
|
|
73
|
-
"@nestjs/common": "11.1.
|
|
74
|
-
"@nestjs/core": "11.1.
|
|
73
|
+
"@nestjs/common": "11.1.8",
|
|
74
|
+
"@nestjs/core": "11.1.8",
|
|
75
75
|
"@nestjs/graphql": "13.1.0",
|
|
76
76
|
"@nestjs/jwt": "11.0.1",
|
|
77
77
|
"@nestjs/mongoose": "11.0.3",
|
|
78
78
|
"@nestjs/passport": "11.0.5",
|
|
79
|
-
"@nestjs/platform-express": "11.1.
|
|
79
|
+
"@nestjs/platform-express": "11.1.8",
|
|
80
80
|
"@nestjs/schedule": "6.0.1",
|
|
81
|
-
"@nestjs/swagger": "11.2.
|
|
81
|
+
"@nestjs/swagger": "11.2.1",
|
|
82
82
|
"@nestjs/terminus": "11.0.0",
|
|
83
83
|
"apollo-server-core": "3.13.0",
|
|
84
84
|
"apollo-server-express": "3.13.0",
|
|
@@ -89,55 +89,52 @@
|
|
|
89
89
|
"cookie-parser": "1.4.7",
|
|
90
90
|
"dotenv": "17.2.3",
|
|
91
91
|
"ejs": "3.1.10",
|
|
92
|
-
"graphql": "16.
|
|
92
|
+
"graphql": "16.12.0",
|
|
93
93
|
"graphql-query-complexity": "1.1.0",
|
|
94
94
|
"graphql-subscriptions": "3.0.0",
|
|
95
95
|
"graphql-upload": "15.0.2",
|
|
96
96
|
"js-sha256": "0.11.1",
|
|
97
97
|
"json-to-graphql-query": "2.3.0",
|
|
98
|
-
"light-my-request": "6.6.0",
|
|
99
98
|
"lodash": "4.17.21",
|
|
100
|
-
"mongodb": "
|
|
101
|
-
"mongoose": "8.19.
|
|
99
|
+
"mongodb": "7.0.0",
|
|
100
|
+
"mongoose": "8.19.3",
|
|
102
101
|
"multer": "2.0.2",
|
|
103
|
-
"node-mailjet": "6.0.
|
|
104
|
-
"nodemailer": "7.0.
|
|
105
|
-
"nodemon": "3.1.10",
|
|
102
|
+
"node-mailjet": "6.0.11",
|
|
103
|
+
"nodemailer": "7.0.10",
|
|
106
104
|
"passport": "0.7.0",
|
|
107
105
|
"passport-jwt": "4.0.1",
|
|
108
106
|
"reflect-metadata": "0.2.2",
|
|
109
107
|
"rfdc": "1.4.1",
|
|
110
|
-
"rimraf": "6.0.1",
|
|
111
108
|
"rxjs": "7.8.2",
|
|
112
109
|
"yuml-diagram": "1.2.0"
|
|
113
110
|
},
|
|
114
111
|
"devDependencies": {
|
|
115
112
|
"@babel/plugin-proposal-private-methods": "7.18.6",
|
|
116
|
-
"@compodoc/compodoc": "1.1.
|
|
117
|
-
"@lenne.tech/eslint-config-ts": "2.1.
|
|
113
|
+
"@compodoc/compodoc": "1.1.32",
|
|
114
|
+
"@lenne.tech/eslint-config-ts": "2.1.4",
|
|
118
115
|
"@nestjs/cli": "11.0.10",
|
|
119
116
|
"@nestjs/schematics": "11.0.9",
|
|
120
|
-
"@nestjs/testing": "11.1.
|
|
121
|
-
"@swc/cli": "0.7.
|
|
122
|
-
"@swc/core": "1.
|
|
117
|
+
"@nestjs/testing": "11.1.8",
|
|
118
|
+
"@swc/cli": "0.7.9",
|
|
119
|
+
"@swc/core": "1.15.0",
|
|
123
120
|
"@swc/jest": "0.2.39",
|
|
124
121
|
"@types/compression": "1.8.1",
|
|
125
|
-
"@types/cookie-parser": "1.4.
|
|
122
|
+
"@types/cookie-parser": "1.4.10",
|
|
126
123
|
"@types/ejs": "3.1.5",
|
|
127
124
|
"@types/express": "4.17.21",
|
|
128
125
|
"@types/jest": "30.0.0",
|
|
129
126
|
"@types/lodash": "4.17.20",
|
|
130
127
|
"@types/multer": "2.0.0",
|
|
131
|
-
"@types/node": "24.
|
|
132
|
-
"@types/nodemailer": "7.0.
|
|
128
|
+
"@types/node": "24.10.0",
|
|
129
|
+
"@types/nodemailer": "7.0.3",
|
|
133
130
|
"@types/passport": "1.0.17",
|
|
134
131
|
"@types/supertest": "6.0.3",
|
|
135
|
-
"@typescript-eslint/eslint-plugin": "8.46.
|
|
136
|
-
"@typescript-eslint/parser": "8.46.
|
|
132
|
+
"@typescript-eslint/eslint-plugin": "8.46.3",
|
|
133
|
+
"@typescript-eslint/parser": "8.46.3",
|
|
137
134
|
"coffeescript": "2.7.0",
|
|
138
|
-
"eslint": "9.
|
|
135
|
+
"eslint": "9.39.1",
|
|
139
136
|
"eslint-config-prettier": "10.1.8",
|
|
140
|
-
"eslint-plugin-unused-imports": "4.
|
|
137
|
+
"eslint-plugin-unused-imports": "4.3.0",
|
|
141
138
|
"find-file-up": "2.0.1",
|
|
142
139
|
"grunt": "1.6.1",
|
|
143
140
|
"grunt-bg-shell": "2.3.3",
|
|
@@ -146,14 +143,16 @@
|
|
|
146
143
|
"grunt-sync": "0.8.2",
|
|
147
144
|
"husky": "9.1.7",
|
|
148
145
|
"jest": "30.2.0",
|
|
146
|
+
"nodemon": "3.1.10",
|
|
149
147
|
"npm-watch": "0.13.0",
|
|
150
148
|
"pm2": "6.0.13",
|
|
151
149
|
"prettier": "3.6.2",
|
|
152
150
|
"pretty-quick": "4.2.2",
|
|
151
|
+
"rimraf": "6.1.0",
|
|
153
152
|
"supertest": "7.1.4",
|
|
154
153
|
"ts-jest": "29.4.5",
|
|
155
154
|
"ts-loader": "9.5.4",
|
|
156
|
-
"ts-morph": "27.0.
|
|
155
|
+
"ts-morph": "27.0.2",
|
|
157
156
|
"ts-node": "10.9.2",
|
|
158
157
|
"tsconfig-paths": "4.2.0",
|
|
159
158
|
"typescript": "5.9.3",
|
|
@@ -164,7 +163,7 @@
|
|
|
164
163
|
"flat": "5.0.2",
|
|
165
164
|
"mime": "2.6.0"
|
|
166
165
|
},
|
|
167
|
-
"ts-morph": "27.0.
|
|
166
|
+
"ts-morph": "27.0.2"
|
|
168
167
|
},
|
|
169
168
|
"jest": {
|
|
170
169
|
"collectCoverage": true,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Field, FieldOptions } from '@nestjs/graphql';
|
|
2
|
+
import { Prop, PropOptions } from '@nestjs/mongoose';
|
|
2
3
|
import { ApiProperty, ApiPropertyOptions } from '@nestjs/swagger';
|
|
3
4
|
import { EnumAllowedTypes } from '@nestjs/swagger/dist/interfaces/schema-object-metadata.interface';
|
|
4
5
|
import { Type } from 'class-transformer';
|
|
@@ -38,6 +39,8 @@ export interface UnifiedFieldOptions {
|
|
|
38
39
|
isArray?: boolean;
|
|
39
40
|
/** Default: false */
|
|
40
41
|
isOptional?: boolean;
|
|
42
|
+
/** Whether to apply Mongoose @Prop decorator. Optional, used for database models. Default: false */
|
|
43
|
+
mongoose?: boolean | PropOptions;
|
|
41
44
|
/** Restricted roles */
|
|
42
45
|
roles?: RestrictedType | RoleEnum | RoleEnum[];
|
|
43
46
|
/** Options for swagger api documentation */
|
|
@@ -48,8 +51,12 @@ export interface UnifiedFieldOptions {
|
|
|
48
51
|
*
|
|
49
52
|
* Enums should be defined via the enum option.
|
|
50
53
|
*
|
|
54
|
+
* For array fields, you can use either:
|
|
55
|
+
* - `type: () => ItemType` (recommended, decorator adds array wrapping automatically)
|
|
56
|
+
* - `type: () => [ItemType]` (also supported, decorator extracts ItemType to avoid double-nesting)
|
|
57
|
+
*
|
|
51
58
|
* Supports:
|
|
52
|
-
* - A factory function that returns the type: `() => MyType`
|
|
59
|
+
* - A factory function that returns the type: `() => MyType` or `() => [MyType]`
|
|
53
60
|
* - A GraphQL scalar: `GraphQLScalarType`
|
|
54
61
|
* - A class constructor: `MyClass`
|
|
55
62
|
* */
|
|
@@ -96,7 +103,16 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
|
|
|
96
103
|
return metadataType;
|
|
97
104
|
};
|
|
98
105
|
|
|
99
|
-
|
|
106
|
+
// Resolve the type and extract item type if user provided array syntax
|
|
107
|
+
const resolvedType = resolvedTypeFn();
|
|
108
|
+
|
|
109
|
+
// If this is an array field and the user provided type: () => [ItemType],
|
|
110
|
+
// extract the ItemType to avoid double-nesting (e.g., [[ItemType]])
|
|
111
|
+
// We check: isArrayField (should be array) && Array.isArray (is actually an array) && length === 1 (GraphQL array syntax)
|
|
112
|
+
const baseType =
|
|
113
|
+
isArrayField && Array.isArray(resolvedType) && resolvedType.length === 1
|
|
114
|
+
? resolvedType[0] // Extract item type from [ItemType]
|
|
115
|
+
: resolvedType; // Use as-is
|
|
100
116
|
|
|
101
117
|
// Prepare merged options
|
|
102
118
|
const gqlOpts: FieldOptions = { ...opts.gqlOptions };
|
|
@@ -161,8 +177,14 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
|
|
|
161
177
|
}
|
|
162
178
|
|
|
163
179
|
// Type function for gql
|
|
180
|
+
// We need to keep the factory pattern (calling resolvedTypeFn inside the arrow function)
|
|
181
|
+
// to support circular references. But we also need to extract array item types to avoid double-nesting.
|
|
164
182
|
const gqlTypeFn = isArrayField
|
|
165
|
-
? () =>
|
|
183
|
+
? () => {
|
|
184
|
+
const resolved = opts.enum?.enum || opts.gqlType || resolvedTypeFn();
|
|
185
|
+
// Extract item type if user provided [ItemType] syntax to avoid [[ItemType]]
|
|
186
|
+
return [Array.isArray(resolved) && resolved.length === 1 ? resolved[0] : resolved];
|
|
187
|
+
}
|
|
166
188
|
: () => opts.enum?.enum || opts.gqlType || resolvedTypeFn();
|
|
167
189
|
|
|
168
190
|
// Gql decorator
|
|
@@ -178,7 +200,7 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
|
|
|
178
200
|
|
|
179
201
|
// Completely skip validation if its any
|
|
180
202
|
if (opts.validator) {
|
|
181
|
-
opts.validator(valOpts).forEach(d => d(target, propertyKey));
|
|
203
|
+
opts.validator(valOpts).forEach((d) => d(target, propertyKey));
|
|
182
204
|
} else if (!opts.isAny) {
|
|
183
205
|
const validator = getBuiltInValidator(baseType, valOpts, isArrayField, target);
|
|
184
206
|
if (validator) {
|
|
@@ -199,6 +221,23 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
|
|
|
199
221
|
const rolesArr = Array.isArray(opts.roles) ? opts.roles : [opts.roles];
|
|
200
222
|
Restricted(...rolesArr)(target, propertyKey);
|
|
201
223
|
}
|
|
224
|
+
|
|
225
|
+
// Mongoose @Prop decorator (optional)
|
|
226
|
+
if (opts.mongoose) {
|
|
227
|
+
const propOptions: any = typeof opts.mongoose === 'object' ? opts.mongoose : {};
|
|
228
|
+
|
|
229
|
+
// Set type for Prop if not already defined in propOptions
|
|
230
|
+
if (typeof propOptions === 'object' && !Array.isArray(propOptions) && !propOptions.type && baseType) {
|
|
231
|
+
propOptions.type = baseType;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Apply array type if needed
|
|
235
|
+
if (typeof propOptions === 'object' && !Array.isArray(propOptions) && isArrayField && !propOptions.type) {
|
|
236
|
+
propOptions.type = [baseType];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
Prop(propOptions)(target, propertyKey);
|
|
240
|
+
}
|
|
202
241
|
};
|
|
203
242
|
}
|
|
204
243
|
|
|
@@ -228,12 +267,12 @@ function getBuiltInValidator(
|
|
|
228
267
|
function isGraphQLScalar(type: any): boolean {
|
|
229
268
|
// CustomScalar check (The CustomScalar interface implements these functions below)
|
|
230
269
|
return (
|
|
231
|
-
(type
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
270
|
+
(type &&
|
|
271
|
+
typeof type === 'function' &&
|
|
272
|
+
typeof type.prototype?.serialize === 'function' &&
|
|
273
|
+
typeof type.prototype?.parseValue === 'function' &&
|
|
274
|
+
typeof type.prototype?.parseLiteral === 'function') ||
|
|
275
|
+
type instanceof GraphQLScalarType
|
|
237
276
|
);
|
|
238
277
|
}
|
|
239
278
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ObjectType } from '@nestjs/graphql';
|
|
2
|
-
import {
|
|
2
|
+
import { Schema } from '@nestjs/mongoose';
|
|
3
3
|
import { Types } from 'mongoose';
|
|
4
4
|
|
|
5
5
|
import { Restricted } from '../decorators/restricted.decorator';
|
|
@@ -85,10 +85,10 @@ export abstract class CorePersistenceModel extends CoreModel {
|
|
|
85
85
|
/**
|
|
86
86
|
* Created date, is set automatically by mongoose
|
|
87
87
|
*/
|
|
88
|
-
@Prop()
|
|
89
88
|
@UnifiedField({
|
|
90
89
|
description: 'Created date',
|
|
91
90
|
isOptional: true,
|
|
91
|
+
mongoose: true,
|
|
92
92
|
roles: RoleEnum.S_EVERYONE,
|
|
93
93
|
swaggerApiOptions: { example: 1740037703939, format: 'int64', type: Date },
|
|
94
94
|
type: Date,
|
|
@@ -98,10 +98,10 @@ export abstract class CorePersistenceModel extends CoreModel {
|
|
|
98
98
|
/**
|
|
99
99
|
* Updated date is set automatically by mongoose
|
|
100
100
|
*/
|
|
101
|
-
@Prop()
|
|
102
101
|
@UnifiedField({
|
|
103
102
|
description: 'Updated date',
|
|
104
103
|
isOptional: true,
|
|
104
|
+
mongoose: true,
|
|
105
105
|
roles: RoleEnum.S_EVERYONE,
|
|
106
106
|
swaggerApiOptions: { example: 1740037703939, format: 'int64', type: Date },
|
|
107
107
|
type: Date,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Prop } from '@nestjs/mongoose';
|
|
1
|
+
import { ObjectType } from '@nestjs/graphql';
|
|
3
2
|
import { Types } from 'mongoose';
|
|
4
3
|
|
|
5
4
|
import { Restricted } from '../../common/decorators/restricted.decorator';
|
|
5
|
+
import { UnifiedField } from '../../common/decorators/unified-field.decorator';
|
|
6
6
|
import { RoleEnum } from '../../common/enums/role.enum';
|
|
7
7
|
import { CoreModel } from '../../common/models/core-model.model';
|
|
8
8
|
|
|
@@ -25,37 +25,55 @@ export class CoreFileInfo extends CoreModel {
|
|
|
25
25
|
// Properties
|
|
26
26
|
// ===========================================================================
|
|
27
27
|
|
|
28
|
-
@
|
|
29
|
-
|
|
28
|
+
@UnifiedField({
|
|
29
|
+
description: 'ID of the file',
|
|
30
|
+
roles: RoleEnum.S_EVERYONE,
|
|
31
|
+
type: () => String,
|
|
32
|
+
})
|
|
30
33
|
id: string = undefined;
|
|
31
34
|
|
|
32
|
-
@
|
|
35
|
+
@UnifiedField({
|
|
33
36
|
description:
|
|
34
|
-
'The size of each chunk in bytes. GridFS divides the document into chunks of size chunkSize, '
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
'The size of each chunk in bytes. GridFS divides the document into chunks of size chunkSize, ' +
|
|
38
|
+
'except for the last, which is only as large as needed. The default size is 255 kilobytes (kB)',
|
|
39
|
+
isOptional: true,
|
|
40
|
+
mongoose: { required: false, type: Number },
|
|
41
|
+
roles: RoleEnum.S_EVERYONE,
|
|
42
|
+
type: () => Number,
|
|
37
43
|
})
|
|
38
|
-
@Prop({ required: false, type: Number })
|
|
39
|
-
@Restricted(RoleEnum.S_EVERYONE)
|
|
40
44
|
chunkSize: number = undefined;
|
|
41
45
|
|
|
42
|
-
@
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
@UnifiedField({
|
|
47
|
+
description: 'Content type',
|
|
48
|
+
isOptional: true,
|
|
49
|
+
mongoose: { required: false, type: String },
|
|
50
|
+
roles: RoleEnum.S_EVERYONE,
|
|
51
|
+
})
|
|
45
52
|
contentType?: string = undefined;
|
|
46
53
|
|
|
47
|
-
@
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
@UnifiedField({
|
|
55
|
+
description: 'Name of the file',
|
|
56
|
+
isOptional: true,
|
|
57
|
+
mongoose: { required: false, type: String },
|
|
58
|
+
roles: RoleEnum.S_EVERYONE,
|
|
59
|
+
})
|
|
50
60
|
filename?: string = undefined;
|
|
51
61
|
|
|
52
|
-
@
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
@UnifiedField({
|
|
63
|
+
description: 'The size of the document in bytes',
|
|
64
|
+
isOptional: true,
|
|
65
|
+
mongoose: { required: false, type: Number },
|
|
66
|
+
roles: RoleEnum.S_EVERYONE,
|
|
67
|
+
type: () => Number,
|
|
68
|
+
})
|
|
55
69
|
length: number = undefined;
|
|
56
70
|
|
|
57
|
-
@
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
@UnifiedField({
|
|
72
|
+
description: 'The date the file was first stored',
|
|
73
|
+
isOptional: true,
|
|
74
|
+
mongoose: { required: false, type: Date },
|
|
75
|
+
roles: RoleEnum.S_EVERYONE,
|
|
76
|
+
type: () => Date,
|
|
77
|
+
})
|
|
60
78
|
uploadDate: Date = undefined;
|
|
61
79
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ObjectType } from '@nestjs/graphql';
|
|
2
2
|
import { Schema as MongooseSchema, Prop, raw } from '@nestjs/mongoose';
|
|
3
3
|
import { ApiExtraModels, ApiProperty } from '@nestjs/swagger';
|
|
4
|
-
import { IsOptional } from 'class-validator';
|
|
5
4
|
import { Document } from 'mongoose';
|
|
6
5
|
|
|
7
6
|
import { Restricted } from '../../common/decorators/restricted.decorator';
|
|
7
|
+
import { UnifiedField } from '../../common/decorators/unified-field.decorator';
|
|
8
8
|
import { RoleEnum } from '../../common/enums/role.enum';
|
|
9
9
|
import { CorePersistenceModel } from '../../common/models/core-persistence.model';
|
|
10
10
|
import { CoreTokenData } from '../auth/interfaces/core-token-data.interface';
|
|
@@ -26,67 +26,78 @@ export abstract class CoreUserModel extends CorePersistenceModel {
|
|
|
26
26
|
/**
|
|
27
27
|
* E-Mail address of the user
|
|
28
28
|
*/
|
|
29
|
-
@
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
@UnifiedField({
|
|
30
|
+
description: 'Email of the user',
|
|
31
|
+
isOptional: true,
|
|
32
|
+
mongoose: { index: true, lowercase: true, trim: true },
|
|
33
|
+
roles: RoleEnum.S_EVERYONE,
|
|
34
|
+
})
|
|
33
35
|
email: string = undefined;
|
|
34
36
|
|
|
35
37
|
/**
|
|
36
38
|
* First name of the user
|
|
37
39
|
*/
|
|
38
|
-
@
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
@UnifiedField({
|
|
41
|
+
description: 'First name of the user',
|
|
42
|
+
isOptional: true,
|
|
43
|
+
mongoose: true,
|
|
44
|
+
roles: RoleEnum.S_EVERYONE,
|
|
45
|
+
})
|
|
43
46
|
firstName: string = undefined;
|
|
44
47
|
|
|
45
48
|
/**
|
|
46
49
|
* Last name of the user
|
|
47
50
|
*/
|
|
48
|
-
@
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
@UnifiedField({
|
|
52
|
+
description: 'Last name of the user',
|
|
53
|
+
isOptional: true,
|
|
54
|
+
mongoose: true,
|
|
55
|
+
roles: RoleEnum.S_EVERYONE,
|
|
56
|
+
})
|
|
53
57
|
lastName: string = undefined;
|
|
54
58
|
|
|
55
59
|
/**
|
|
56
60
|
* Password of the user
|
|
57
61
|
*/
|
|
58
|
-
@
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
@UnifiedField({
|
|
63
|
+
isOptional: true,
|
|
64
|
+
mongoose: true,
|
|
65
|
+
roles: RoleEnum.S_NO_ONE,
|
|
66
|
+
})
|
|
61
67
|
password: string = undefined;
|
|
62
68
|
|
|
63
69
|
/**
|
|
64
70
|
* Roles of the user
|
|
65
71
|
*/
|
|
66
|
-
@
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
@UnifiedField({
|
|
73
|
+
description: 'Roles of the user',
|
|
74
|
+
isArray: true,
|
|
75
|
+
isOptional: true,
|
|
76
|
+
mongoose: [String],
|
|
77
|
+
roles: RoleEnum.S_EVERYONE,
|
|
78
|
+
type: () => String,
|
|
79
|
+
})
|
|
71
80
|
roles: string[] = undefined;
|
|
72
81
|
|
|
73
82
|
/**
|
|
74
83
|
* Username of the user
|
|
75
84
|
*/
|
|
76
|
-
@
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
@UnifiedField({
|
|
86
|
+
description: 'Username of the user',
|
|
87
|
+
isOptional: true,
|
|
88
|
+
mongoose: true,
|
|
89
|
+
roles: RoleEnum.S_EVERYONE,
|
|
90
|
+
})
|
|
81
91
|
username: string = undefined;
|
|
82
92
|
|
|
83
93
|
/**
|
|
84
94
|
* Password reset token of the user
|
|
85
95
|
*/
|
|
86
|
-
@
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
96
|
+
@UnifiedField({
|
|
97
|
+
isOptional: true,
|
|
98
|
+
mongoose: true,
|
|
99
|
+
roles: RoleEnum.S_NO_ONE,
|
|
100
|
+
})
|
|
90
101
|
passwordResetToken: string = undefined;
|
|
91
102
|
|
|
92
103
|
/**
|
|
@@ -96,30 +107,41 @@ export abstract class CoreUserModel extends CorePersistenceModel {
|
|
|
96
107
|
*/
|
|
97
108
|
@ApiProperty({ isArray: true })
|
|
98
109
|
@ApiProperty({
|
|
99
|
-
|
|
110
|
+
additionalProperties: {
|
|
100
111
|
properties: {
|
|
101
|
-
deviceDescription: {
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
deviceDescription: {
|
|
113
|
+
description: 'Description of the device from which the token was generated',
|
|
114
|
+
nullable: true,
|
|
115
|
+
type: 'string',
|
|
116
|
+
},
|
|
117
|
+
deviceId: {
|
|
118
|
+
description: 'ID of the device from which the token was generated',
|
|
119
|
+
nullable: true,
|
|
120
|
+
type: 'string',
|
|
121
|
+
},
|
|
122
|
+
tokenId: {
|
|
123
|
+
description: 'Token ID to make sure that there is only one RefreshToken for each device',
|
|
124
|
+
nullable: false,
|
|
125
|
+
type: 'string',
|
|
126
|
+
},
|
|
104
127
|
},
|
|
105
128
|
type: 'object',
|
|
106
129
|
},
|
|
107
130
|
description: 'Refresh tokens for devices (key: Token, value: TokenData)',
|
|
108
131
|
example: {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
},
|
|
132
|
+
'49b5c7d6-94ae-4efe-b377-9b50d1a9c2cb': {
|
|
133
|
+
deviceDescription: null,
|
|
134
|
+
deviceId: '49b5c7d6-94ae-4efe-b377-9b50d1a9c2cb',
|
|
135
|
+
tokenId: '50937407-4282-480e-8679-14ecc113f9c7',
|
|
136
|
+
},
|
|
137
|
+
'e9e60a3e-2004-479f-8e79-13a0d1981d76': {
|
|
138
|
+
deviceDescription: null,
|
|
139
|
+
deviceId: 'e9e60a3e-2004-479f-8e79-13a0d1981d76',
|
|
140
|
+
tokenId: '0604aa59-4fc8-4848-9fe7-c12d9cdf6ec0',
|
|
119
141
|
},
|
|
142
|
+
},
|
|
120
143
|
type: 'object',
|
|
121
144
|
})
|
|
122
|
-
@IsOptional()
|
|
123
145
|
@Prop(raw({}))
|
|
124
146
|
@Restricted(RoleEnum.S_NO_ONE)
|
|
125
147
|
refreshTokens: Record<string, CoreTokenData> = undefined;
|
|
@@ -130,30 +152,44 @@ export abstract class CoreUserModel extends CorePersistenceModel {
|
|
|
130
152
|
*/
|
|
131
153
|
@ApiProperty()
|
|
132
154
|
@ApiProperty({
|
|
133
|
-
|
|
155
|
+
additionalProperties: {
|
|
134
156
|
properties: {
|
|
135
|
-
createdAt: {
|
|
136
|
-
|
|
137
|
-
|
|
157
|
+
createdAt: {
|
|
158
|
+
description: 'Token Created At',
|
|
159
|
+
example: 1740037703939,
|
|
160
|
+
format: 'int64',
|
|
161
|
+
nullable: true,
|
|
162
|
+
type: 'number',
|
|
163
|
+
},
|
|
164
|
+
deviceId: {
|
|
165
|
+
description: 'ID of the device from which the token was generated',
|
|
166
|
+
nullable: true,
|
|
167
|
+
type: 'string',
|
|
168
|
+
},
|
|
169
|
+
tokenId: {
|
|
170
|
+
description: 'Token ID to make sure that there is only one RefreshToken for each device',
|
|
171
|
+
nullable: false,
|
|
172
|
+
type: 'string',
|
|
173
|
+
},
|
|
138
174
|
},
|
|
139
175
|
type: 'object',
|
|
140
176
|
},
|
|
141
177
|
description: 'Temporary token for parallel requests during the token refresh process',
|
|
142
|
-
example: {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
'f83ae5f6-90bf-4b4e-b318-651e0eaa67ae': {
|
|
149
|
-
createdAt: 1740037703940,
|
|
150
|
-
deviceId: 'f83ae5f6-90bf-4b4e-b318-651e0eaa67ae',
|
|
151
|
-
tokenId: '4f0dc3c5-e74e-41f4-9bd9-642869462c1e',
|
|
152
|
-
},
|
|
178
|
+
example: {
|
|
179
|
+
// 👈 Add explicit example keys
|
|
180
|
+
'49b5c7d6-94ae-4efe-b377-9b50d1a9c2cb': {
|
|
181
|
+
createdAt: 1740037703939,
|
|
182
|
+
deviceId: '49b5c7d6-94ae-4efe-b377-9b50d1a9c2cb',
|
|
183
|
+
tokenId: '50937407-4282-480e-8679-14ecc113f9c7',
|
|
153
184
|
},
|
|
185
|
+
'f83ae5f6-90bf-4b4e-b318-651e0eaa67ae': {
|
|
186
|
+
createdAt: 1740037703940,
|
|
187
|
+
deviceId: 'f83ae5f6-90bf-4b4e-b318-651e0eaa67ae',
|
|
188
|
+
tokenId: '4f0dc3c5-e74e-41f4-9bd9-642869462c1e',
|
|
189
|
+
},
|
|
190
|
+
},
|
|
154
191
|
type: 'object',
|
|
155
192
|
})
|
|
156
|
-
@IsOptional()
|
|
157
193
|
@Prop(raw({}))
|
|
158
194
|
@Restricted(RoleEnum.S_NO_ONE)
|
|
159
195
|
tempTokens: Record<string, { createdAt: number; deviceId: string; tokenId: string }> = undefined;
|
|
@@ -161,28 +197,34 @@ export abstract class CoreUserModel extends CorePersistenceModel {
|
|
|
161
197
|
/**
|
|
162
198
|
* Verification token of the user
|
|
163
199
|
*/
|
|
164
|
-
@
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
200
|
+
@UnifiedField({
|
|
201
|
+
isOptional: true,
|
|
202
|
+
mongoose: true,
|
|
203
|
+
roles: RoleEnum.S_NO_ONE,
|
|
204
|
+
})
|
|
168
205
|
verificationToken: string = undefined;
|
|
169
206
|
|
|
170
207
|
/**
|
|
171
208
|
* Verification of the user
|
|
172
209
|
*/
|
|
173
|
-
@
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
210
|
+
@UnifiedField({
|
|
211
|
+
description: 'Verification state of the user',
|
|
212
|
+
isOptional: true,
|
|
213
|
+
mongoose: { type: Boolean },
|
|
214
|
+
roles: RoleEnum.S_EVERYONE,
|
|
215
|
+
type: () => Boolean,
|
|
216
|
+
})
|
|
177
217
|
verified: boolean = undefined;
|
|
178
218
|
|
|
179
219
|
/**
|
|
180
220
|
* Verification date
|
|
181
221
|
*/
|
|
182
|
-
@
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
222
|
+
@UnifiedField({
|
|
223
|
+
description: 'Verified date',
|
|
224
|
+
isOptional: true,
|
|
225
|
+
mongoose: true,
|
|
226
|
+
roles: RoleEnum.S_EVERYONE,
|
|
227
|
+
})
|
|
186
228
|
verifiedAt: Date = undefined;
|
|
187
229
|
|
|
188
230
|
// ===================================================================================================================
|
|
@@ -199,7 +241,7 @@ export abstract class CoreUserModel extends CorePersistenceModel {
|
|
|
199
241
|
if (!this.roles || this.roles.length < 1) {
|
|
200
242
|
return false;
|
|
201
243
|
}
|
|
202
|
-
return !roles || roles.length < 1 ? true : this.roles.some(role => roles.includes(role));
|
|
244
|
+
return !roles || roles.length < 1 ? true : this.roles.some((role) => roles.includes(role));
|
|
203
245
|
}
|
|
204
246
|
|
|
205
247
|
/**
|
|
@@ -212,7 +254,7 @@ export abstract class CoreUserModel extends CorePersistenceModel {
|
|
|
212
254
|
if (!this.roles || this.roles.length < 1) {
|
|
213
255
|
return false;
|
|
214
256
|
}
|
|
215
|
-
return !roles ? true : roles.every(role => this.roles.includes(role));
|
|
257
|
+
return !roles ? true : roles.every((role) => this.roles.includes(role));
|
|
216
258
|
}
|
|
217
259
|
|
|
218
260
|
/**
|