@lenne.tech/nest-server 11.1.14 → 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.
Files changed (77) hide show
  1. package/dist/core/common/decorators/unified-field.decorator.d.ts +2 -0
  2. package/dist/core/common/decorators/unified-field.decorator.js +26 -9
  3. package/dist/core/common/decorators/unified-field.decorator.js.map +1 -1
  4. package/dist/core/common/helpers/gridfs.helper.d.ts +42 -0
  5. package/dist/core/common/helpers/gridfs.helper.js +107 -0
  6. package/dist/core/common/helpers/gridfs.helper.js.map +1 -0
  7. package/dist/core/common/models/core-persistence.model.js +2 -2
  8. package/dist/core/common/models/core-persistence.model.js.map +1 -1
  9. package/dist/core/common/services/brevo.service.d.ts +1 -0
  10. package/dist/core/common/services/brevo.service.js +19 -18
  11. package/dist/core/common/services/brevo.service.js.map +1 -1
  12. package/dist/core/common/services/crud.service.js +1 -1
  13. package/dist/core/common/services/crud.service.js.map +1 -1
  14. package/dist/core/modules/file/core-file-info.model.js +41 -23
  15. package/dist/core/modules/file/core-file-info.model.js.map +1 -1
  16. package/dist/core/modules/file/core-file.controller.d.ts +2 -1
  17. package/dist/core/modules/file/core-file.controller.js +1 -1
  18. package/dist/core/modules/file/core-file.controller.js.map +1 -1
  19. package/dist/core/modules/file/core-file.service.d.ts +7 -7
  20. package/dist/core/modules/file/core-file.service.js +28 -51
  21. package/dist/core/modules/file/core-file.service.js.map +1 -1
  22. package/dist/core/modules/file/interfaces/file-upload.interface.d.ts +1 -1
  23. package/dist/core/modules/user/core-user.model.js +95 -54
  24. package/dist/core/modules/user/core-user.model.js.map +1 -1
  25. package/dist/core/modules/user/core-user.service.js +2 -3
  26. package/dist/core/modules/user/core-user.service.js.map +1 -1
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +1 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/main.js +22 -0
  31. package/dist/main.js.map +1 -1
  32. package/dist/server/common/models/persistence.model.js +13 -11
  33. package/dist/server/common/models/persistence.model.js.map +1 -1
  34. package/dist/server/modules/auth/auth.model.js +6 -2
  35. package/dist/server/modules/auth/auth.model.js.map +1 -1
  36. package/dist/server/modules/file/file-info.model.d.ts +7 -3
  37. package/dist/server/modules/file/file.controller.d.ts +6 -4
  38. package/dist/server/modules/file/file.controller.js +15 -4
  39. package/dist/server/modules/file/file.controller.js.map +1 -1
  40. package/dist/server/modules/file/file.resolver.js +1 -1
  41. package/dist/server/modules/file/file.resolver.js.map +1 -1
  42. package/dist/server/modules/file/multer-config.service.d.ts +0 -2
  43. package/dist/server/modules/file/multer-config.service.js +3 -22
  44. package/dist/server/modules/file/multer-config.service.js.map +1 -1
  45. package/dist/server/modules/user/user.controller.d.ts +19 -0
  46. package/dist/server/modules/user/user.controller.js +256 -0
  47. package/dist/server/modules/user/user.controller.js.map +1 -0
  48. package/dist/server/modules/user/user.model.d.ts +7 -3
  49. package/dist/server/modules/user/user.model.js +37 -24
  50. package/dist/server/modules/user/user.model.js.map +1 -1
  51. package/dist/server/modules/user/user.module.js +2 -1
  52. package/dist/server/modules/user/user.module.js.map +1 -1
  53. package/dist/tsconfig.build.tsbuildinfo +1 -1
  54. package/dist/types/graphql-upload.d.ts +25 -0
  55. package/package.json +41 -44
  56. package/src/core/common/decorators/unified-field.decorator.ts +49 -10
  57. package/src/core/common/helpers/gridfs.helper.ts +227 -0
  58. package/src/core/common/models/core-persistence.model.ts +3 -3
  59. package/src/core/common/services/brevo.service.ts +20 -18
  60. package/src/core/common/services/crud.service.ts +3 -3
  61. package/src/core/modules/file/core-file-info.model.ts +40 -22
  62. package/src/core/modules/file/core-file.controller.ts +3 -2
  63. package/src/core/modules/file/core-file.service.ts +49 -60
  64. package/src/core/modules/file/interfaces/file-upload.interface.ts +2 -1
  65. package/src/core/modules/user/core-user.model.ts +120 -78
  66. package/src/core/modules/user/core-user.service.ts +3 -3
  67. package/src/index.ts +1 -0
  68. package/src/main.ts +25 -0
  69. package/src/server/common/models/persistence.model.ts +15 -13
  70. package/src/server/modules/auth/auth.model.ts +7 -3
  71. package/src/server/modules/file/file.controller.ts +25 -7
  72. package/src/server/modules/file/file.resolver.ts +1 -2
  73. package/src/server/modules/file/multer-config.service.ts +6 -21
  74. package/src/server/modules/user/user.controller.ts +242 -0
  75. package/src/server/modules/user/user.model.ts +39 -26
  76. package/src/server/modules/user/user.module.ts +2 -1
  77. package/src/types/graphql-upload.d.ts +25 -0
@@ -1,8 +1,8 @@
1
- import { Field, ObjectType } from '@nestjs/graphql';
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
- @Field(() => String, { description: 'ID of the file' })
29
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Field(() => Number, {
35
+ @UnifiedField({
33
36
  description:
34
- 'The size of each chunk in bytes. GridFS divides the document into chunks of size chunkSize, '
35
- + 'except for the last, which is only as large as needed. The default size is 255 kilobytes (kB)',
36
- nullable: true,
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
- @Field(() => String, { description: 'Content type', nullable: true })
43
- @Prop({ required: false, type: String })
44
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Field(() => String, { description: 'Name of the file', nullable: true })
48
- @Prop({ required: false, type: String })
49
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Field(() => Number, { description: 'The size of the document in bytes', nullable: true })
53
- @Prop({ required: false, type: Number })
54
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Field(() => Date, { description: 'The date the file was first stored', nullable: true })
58
- @Prop({ required: false, type: Date })
59
- @Restricted(RoleEnum.S_EVERYONE)
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,4 +1,5 @@
1
1
  import { BadRequestException, Controller, Get, NotFoundException, Param, Res } from '@nestjs/common';
2
+ import { Response } from 'express';
2
3
 
3
4
  import { Roles } from '../../common/decorators/roles.decorator';
4
5
  import { RoleEnum } from '../../common/enums/role.enum';
@@ -20,7 +21,7 @@ export abstract class CoreFileController {
20
21
  */
21
22
  @Get(':filename')
22
23
  @Roles(RoleEnum.S_EVERYONE)
23
- async getFile(@Param('filename') filename: string, @Res() res) {
24
+ async getFile(@Param('filename') filename: string, @Res() res: Response) {
24
25
  if (!filename) {
25
26
  throw new BadRequestException('Missing filename for download');
26
27
  }
@@ -30,7 +31,7 @@ export abstract class CoreFileController {
30
31
  throw new NotFoundException('File not found');
31
32
  }
32
33
  const filestream = await this.fileService.getFileStream(file.id);
33
- res.header('Content-Type', file.contentType);
34
+ res.header('Content-Type', file.contentType || 'application/octet-stream');
34
35
  res.header('Content-Disposition', `attachment; filename=${file.filename}`);
35
36
  return filestream.pipe(res);
36
37
  }
@@ -1,11 +1,10 @@
1
- import { createBucket, MongoGridFSOptions, MongooseGridFS } from '@lenne.tech/mongoose-gridfs';
2
1
  import { NotFoundException } from '@nestjs/common';
3
- import { GridFSBucket, GridFSBucketReadStream, GridFSBucketReadStreamOptions } from 'mongodb';
4
- import mongoose, { Connection, Types } from 'mongoose';
2
+ import mongoose, { Connection, mongo, Types } from 'mongoose';
5
3
 
6
4
  import { FilterArgs } from '../../common/args/filter.args';
7
5
  import { getObjectIds, getStringIds } from '../../common/helpers/db.helper';
8
6
  import { convertFilterArgsToQuery } from '../../common/helpers/filter.helper';
7
+ import { GridFSHelper } from '../../common/helpers/gridfs.helper';
9
8
  import { check } from '../../common/helpers/input.helper';
10
9
  import { prepareOutput } from '../../common/helpers/service.helper';
11
10
  import { MaybePromise } from '../../common/types/maybe-promise.type';
@@ -22,7 +21,8 @@ export type FileInputCheckType = 'file' | 'filename' | 'files' | 'filterArgs' |
22
21
  * Abstract core file service
23
22
  */
24
23
  export abstract class CoreFileService {
25
- files: GridFSBucket & MongooseGridFS;
24
+ // Use the native MongoDB driver's types (accessed via Mongoose's exports) to avoid BSON version conflicts
25
+ files: mongo.GridFSBucket;
26
26
 
27
27
  /**
28
28
  * Include MongoDB connection and create File bucket
@@ -31,7 +31,8 @@ export abstract class CoreFileService {
31
31
  protected readonly connection: Connection,
32
32
  bucketName = 'fs',
33
33
  ) {
34
- this.files = createBucket({ bucketName, connection });
34
+ // Use the native MongoDB driver's GridFSBucket via Mongoose's mongo export to avoid BSON version conflicts
35
+ this.files = new mongo.GridFSBucket(connection.db, { bucketName });
35
36
  }
36
37
 
37
38
  /**
@@ -41,15 +42,13 @@ export abstract class CoreFileService {
41
42
  if (!(await this.checkRights(file, { ...serviceOptions, checkInputType: 'file' }))) {
42
43
  return null;
43
44
  }
44
- return await new Promise(async (resolve, reject) => {
45
- // eslint-disable-next-line unused-imports/no-unused-vars
46
- const { createReadStream, encoding, filename, mimetype } = await file;
47
- const readStream = createReadStream();
48
- const options: MongoGridFSOptions = { contentType: mimetype, filename };
49
- this.files.writeFile(options, readStream, (error, fileInfo) => {
50
- error ? reject(error) : resolve(this.prepareOutput(fileInfo, serviceOptions));
51
- });
45
+ const { createReadStream, filename, mimetype } = await file;
46
+ const readStream = createReadStream();
47
+ const fileInfo = await GridFSHelper.writeFileFromStream(this.files, readStream, {
48
+ contentType: mimetype,
49
+ filename,
52
50
  });
51
+ return this.prepareOutput(fileInfo as unknown as CoreFileInfo, serviceOptions);
53
52
  }
54
53
 
55
54
  /**
@@ -71,7 +70,11 @@ export abstract class CoreFileService {
71
70
  */
72
71
  async duplicateByName(name: string, newName: string): Promise<any> {
73
72
  return new Promise(async (resolve) => {
74
- resolve(this.files.openDownloadStreamByName(name).pipe(this.files.openUploadStream(newName)));
73
+ resolve(
74
+ GridFSHelper.openDownloadStreamByName(this.files, name).pipe(
75
+ GridFSHelper.openUploadStream(this.files, newName),
76
+ ),
77
+ );
75
78
  });
76
79
  }
77
80
 
@@ -82,10 +85,10 @@ export abstract class CoreFileService {
82
85
  const objectId = getObjectIds(id);
83
86
  const file = await this.getFileInfo(objectId);
84
87
  return new Promise((resolve, reject) => {
85
- const downloadStream = this.files.openDownloadStream(objectId);
88
+ const downloadStream = GridFSHelper.openDownloadStream(this.files, objectId);
86
89
 
87
90
  const newFileId = new mongoose.Types.ObjectId();
88
- const uploadStream = this.files.openUploadStreamWithId(newFileId, file.filename, {
91
+ const uploadStream = GridFSHelper.openUploadStreamWithId(this.files, newFileId, file.filename, {
89
92
  contentType: file.contentType,
90
93
  });
91
94
 
@@ -112,16 +115,9 @@ export abstract class CoreFileService {
112
115
  if (!(await this.checkRights(filterArgs, { ...serviceOptions, checkInputType: 'filterArgs' }))) {
113
116
  return null;
114
117
  }
115
- return await new Promise((resolve, reject) => {
116
- const filterQuery = convertFilterArgsToQuery(filterArgs);
117
- const cursor = this.files.find(filterQuery[0], filterQuery[1]);
118
- if (!cursor) {
119
- throw new NotFoundException('File collection not found');
120
- }
121
- cursor.toArray((error, docs) => {
122
- error ? reject(error) : resolve(this.prepareOutput(docs, serviceOptions));
123
- });
124
- });
118
+ const filterQuery = convertFilterArgsToQuery(filterArgs);
119
+ const docs = await GridFSHelper.findFiles(this.files, filterQuery[0], filterQuery[1]);
120
+ return this.prepareOutput(docs as unknown as CoreFileInfo[], serviceOptions);
125
121
  }
126
122
 
127
123
  /**
@@ -131,11 +127,8 @@ export abstract class CoreFileService {
131
127
  if (!(await this.checkRights(id, { ...serviceOptions, checkInputType: 'id' }))) {
132
128
  return null;
133
129
  }
134
- return await new Promise((resolve, reject) => {
135
- this.files.findById(getObjectIds(id), (error, fileInfo) => {
136
- error ? reject(error) : resolve(this.prepareOutput(fileInfo, serviceOptions));
137
- });
138
- });
130
+ const fileInfo = await GridFSHelper.findFileById(this.files, getObjectIds(id));
131
+ return this.prepareOutput(fileInfo as unknown as CoreFileInfo, serviceOptions);
139
132
  }
140
133
 
141
134
  /**
@@ -145,11 +138,8 @@ export abstract class CoreFileService {
145
138
  if (!(await this.checkRights(filename, { ...serviceOptions, checkInputType: 'filename' }))) {
146
139
  return null;
147
140
  }
148
- return await new Promise((resolve, reject) => {
149
- this.files.findOne({ filename }, (error, fileInfo) => {
150
- error ? reject(error) : resolve(this.prepareOutput(fileInfo, serviceOptions));
151
- });
152
- });
141
+ const fileInfo = await GridFSHelper.findFileByName(this.files, filename);
142
+ return this.prepareOutput(fileInfo as unknown as CoreFileInfo, serviceOptions);
153
143
  }
154
144
 
155
145
  /**
@@ -159,7 +149,7 @@ export abstract class CoreFileService {
159
149
  if (!(await this.checkRights(id, { ...serviceOptions, checkInputType: 'id' }))) {
160
150
  return null;
161
151
  }
162
- return this.files.openDownloadStream(getObjectIds(id)) as GridFSBucketReadStream;
152
+ return GridFSHelper.openDownloadStream(this.files, getObjectIds(id)) as mongo.GridFSBucketReadStream;
163
153
  }
164
154
 
165
155
  /**
@@ -168,11 +158,11 @@ export abstract class CoreFileService {
168
158
  async getFileStreamByName(
169
159
  filename: string,
170
160
  serviceOptions?: FileServiceOptions,
171
- ): Promise<GridFSBucketReadStreamOptions> {
161
+ ): Promise<mongo.GridFSBucketReadStream> {
172
162
  if (!(await this.checkRights(filename, { ...serviceOptions, checkInputType: 'filename' }))) {
173
163
  return null;
174
164
  }
175
- return this.files.readFile({ filename });
165
+ return GridFSHelper.openDownloadStreamByName(this.files, filename);
176
166
  }
177
167
 
178
168
  /**
@@ -182,39 +172,30 @@ export abstract class CoreFileService {
182
172
  if (!(await this.checkRights(id, { ...serviceOptions, checkInputType: 'id' }))) {
183
173
  return null;
184
174
  }
185
- return await new Promise((resolve, reject) => {
186
- this.files.readFile({ _id: getObjectIds(id) }, (error, buffer) => {
187
- error ? reject(error) : resolve(buffer);
188
- });
189
- });
175
+ return await GridFSHelper.readFileToBuffer(this.files, { _id: getObjectIds(id) });
190
176
  }
191
177
 
192
178
  /**
193
- * Get file buffer (for small files) via file ID
179
+ * Get file buffer (for small files) via filename
194
180
  */
195
181
  async getBufferByName(filename: string, serviceOptions?: FileServiceOptions): Promise<Buffer> {
196
182
  if (!(await this.checkRights(filename, { ...serviceOptions, checkInputType: 'filename' }))) {
197
183
  return null;
198
184
  }
199
- return await new Promise((resolve, reject) => {
200
- this.files.readFile({ filename }, (error, buffer) => {
201
- error ? reject(error) : resolve(buffer);
202
- });
203
- });
185
+ return await GridFSHelper.readFileToBuffer(this.files, { filename });
204
186
  }
205
187
 
206
188
  /**
207
- * Delete file reference of avatar
189
+ * Delete file
208
190
  */
209
191
  async deleteFile(id: string | Types.ObjectId, serviceOptions?: FileServiceOptions): Promise<CoreFileInfo> {
210
192
  if (!(await this.checkRights(id, { ...serviceOptions, checkInputType: 'id' }))) {
211
193
  return null;
212
194
  }
213
- return await new Promise((resolve, reject) => {
214
- return this.files.unlink(getObjectIds(id), (error, fileInfo) => {
215
- error ? reject(error) : resolve(this.prepareOutput(fileInfo, serviceOptions));
216
- });
217
- });
195
+ const objectId = getObjectIds(id);
196
+ const fileInfo = await this.getFileInfo(objectId, serviceOptions);
197
+ await GridFSHelper.deleteFile(this.files, objectId);
198
+ return fileInfo;
218
199
  }
219
200
 
220
201
  /**
@@ -247,15 +228,23 @@ export abstract class CoreFileService {
247
228
  }
248
229
 
249
230
  /**
250
- * Prepare output before return
231
+ * Prepare output before return - single file
232
+ * Accepts both GridFSFileInfo (from GridFS operations) and CoreFileInfo
233
+ * They are structurally compatible (duck typing), so we use type assertion
251
234
  */
252
- protected async prepareOutput(fileInfo: CoreFileInfo | CoreFileInfo[], options?: FileServiceOptions) {
235
+ protected async prepareOutput(fileInfo: CoreFileInfo, options?: FileServiceOptions): Promise<CoreFileInfo>;
236
+ protected async prepareOutput(fileInfo: null, options?: FileServiceOptions): Promise<null>;
237
+ protected async prepareOutput(fileInfo: CoreFileInfo[], options?: FileServiceOptions): Promise<CoreFileInfo[]>;
238
+ protected async prepareOutput(
239
+ fileInfo: CoreFileInfo | CoreFileInfo[] | null,
240
+ options?: FileServiceOptions,
241
+ ): Promise<CoreFileInfo | CoreFileInfo[] | null> {
253
242
  if (!fileInfo) {
254
243
  return fileInfo;
255
244
  }
256
245
  this.setId(fileInfo);
257
- fileInfo = await prepareOutput(fileInfo, { targetModel: CoreFileInfo });
258
- return check(fileInfo, options?.currentUser, { roles: options?.roles });
246
+ const prepared = await prepareOutput(fileInfo, { targetModel: CoreFileInfo });
247
+ return check(prepared, options?.currentUser, { roles: options?.roles });
259
248
  }
260
249
 
261
250
  /**
@@ -23,8 +23,9 @@ export interface FileUpload {
23
23
 
24
24
  /**
25
25
  * Stream transfer encoding of the file
26
+ * @deprecated This property is deprecated and may be removed in future versions
26
27
  */
27
- encoding: string;
28
+ encoding?: string;
28
29
 
29
30
  /**
30
31
  * Name of the file
@@ -1,10 +1,10 @@
1
- import { Field, ObjectType } from '@nestjs/graphql';
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
- @ApiProperty()
30
- @Field({ description: 'Email of the user', nullable: true })
31
- @Prop({ index: true, lowercase: true, trim: true })
32
- @Restricted(RoleEnum.S_EVERYONE)
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
- @ApiProperty()
39
- @Field({ description: 'First name of the user', nullable: true })
40
- @IsOptional()
41
- @Prop()
42
- @Restricted(RoleEnum.S_EVERYONE)
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
- @ApiProperty()
49
- @Field({ description: 'Last name of the user', nullable: true })
50
- @IsOptional()
51
- @Prop()
52
- @Restricted(RoleEnum.S_EVERYONE)
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
- @ApiProperty()
59
- @Prop()
60
- @Restricted(RoleEnum.S_NO_ONE)
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
- @ApiProperty()
67
- @Field(() => [String], { description: 'Roles of the user', nullable: true })
68
- @IsOptional()
69
- @Prop([String])
70
- @Restricted(RoleEnum.S_EVERYONE)
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
- @ApiProperty()
77
- @Field({ description: 'Username of the user', nullable: true })
78
- @IsOptional()
79
- @Prop()
80
- @Restricted(RoleEnum.S_EVERYONE)
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
- @ApiProperty()
87
- @IsOptional()
88
- @Prop()
89
- @Restricted(RoleEnum.S_NO_ONE)
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
- additionalProperties: {
110
+ additionalProperties: {
100
111
  properties: {
101
- deviceDescription: { description: 'Description of the device from which the token was generated', nullable: true, type: 'string' },
102
- deviceId: { description: 'ID of the device from which the token was generated', nullable: true, type: 'string' },
103
- tokenId: { description: 'Token ID to make sure that there is only one RefreshToken for each device', nullable: false, type: 'string' },
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
- '49b5c7d6-94ae-4efe-b377-9b50d1a9c2cb': {
110
- deviceDescription: null,
111
- deviceId: '49b5c7d6-94ae-4efe-b377-9b50d1a9c2cb',
112
- tokenId: '50937407-4282-480e-8679-14ecc113f9c7',
113
- },
114
- 'e9e60a3e-2004-479f-8e79-13a0d1981d76': {
115
- deviceDescription: null,
116
- deviceId: 'e9e60a3e-2004-479f-8e79-13a0d1981d76',
117
- tokenId: '0604aa59-4fc8-4848-9fe7-c12d9cdf6ec0',
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
- additionalProperties: {
155
+ additionalProperties: {
134
156
  properties: {
135
- createdAt: { description: 'Token Created At', example: 1740037703939, format: 'int64', nullable: true, type: 'number' },
136
- deviceId: { description: 'ID of the device from which the token was generated', nullable: true, type: 'string' },
137
- tokenId: { description: 'Token ID to make sure that there is only one RefreshToken for each device', nullable: false, type: 'string' },
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: { // 👈 Add explicit example keys
143
- '49b5c7d6-94ae-4efe-b377-9b50d1a9c2cb': {
144
- createdAt: 1740037703939,
145
- deviceId: '49b5c7d6-94ae-4efe-b377-9b50d1a9c2cb',
146
- tokenId: '50937407-4282-480e-8679-14ecc113f9c7',
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
- @ApiProperty()
165
- @IsOptional()
166
- @Prop()
167
- @Restricted(RoleEnum.S_NO_ONE)
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
- @ApiProperty()
174
- @Field(() => Boolean, { description: 'Verification state of the user', nullable: true })
175
- @Prop({ type: Boolean })
176
- @Restricted(RoleEnum.S_EVERYONE)
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
- @ApiProperty()
183
- @Field({ description: 'Verified date', nullable: true })
184
- @Prop()
185
- @Restricted(RoleEnum.S_EVERYONE)
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
  /**
@@ -115,9 +115,9 @@ export abstract class CoreUserService<
115
115
  return this.process(
116
116
  async () => {
117
117
  // Update and return user
118
- return await this.mainDbModel
119
- .updateOne({ _id: dbObject.id }, { verified: true, verifiedAt: new Date() }, { returnDocument: 'after' })
120
- .exec();
118
+ await this.mainDbModel.updateOne({ _id: dbObject.id }, { verified: true, verifiedAt: new Date() }).exec();
119
+ // Return the updated user
120
+ return await this.mainDbModel.findById(dbObject.id).exec();
121
121
  },
122
122
  { dbObject, serviceOptions },
123
123
  );