@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.
- 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/helpers/gridfs.helper.d.ts +42 -0
- package/dist/core/common/helpers/gridfs.helper.js +107 -0
- package/dist/core/common/helpers/gridfs.helper.js.map +1 -0
- 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/common/services/brevo.service.d.ts +1 -0
- package/dist/core/common/services/brevo.service.js +19 -18
- package/dist/core/common/services/brevo.service.js.map +1 -1
- package/dist/core/common/services/crud.service.js +1 -1
- package/dist/core/common/services/crud.service.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/file/core-file.controller.d.ts +2 -1
- package/dist/core/modules/file/core-file.controller.js +1 -1
- package/dist/core/modules/file/core-file.controller.js.map +1 -1
- package/dist/core/modules/file/core-file.service.d.ts +7 -7
- package/dist/core/modules/file/core-file.service.js +28 -51
- package/dist/core/modules/file/core-file.service.js.map +1 -1
- package/dist/core/modules/file/interfaces/file-upload.interface.d.ts +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/core/modules/user/core-user.service.js +2 -3
- package/dist/core/modules/user/core-user.service.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.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/file/file-info.model.d.ts +7 -3
- package/dist/server/modules/file/file.controller.d.ts +6 -4
- package/dist/server/modules/file/file.controller.js +15 -4
- package/dist/server/modules/file/file.controller.js.map +1 -1
- package/dist/server/modules/file/file.resolver.js +1 -1
- package/dist/server/modules/file/file.resolver.js.map +1 -1
- package/dist/server/modules/file/multer-config.service.d.ts +0 -2
- package/dist/server/modules/file/multer-config.service.js +3 -22
- package/dist/server/modules/file/multer-config.service.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.d.ts +7 -3
- 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/dist/types/graphql-upload.d.ts +25 -0
- package/package.json +41 -44
- package/src/core/common/decorators/unified-field.decorator.ts +49 -10
- package/src/core/common/helpers/gridfs.helper.ts +227 -0
- package/src/core/common/models/core-persistence.model.ts +3 -3
- package/src/core/common/services/brevo.service.ts +20 -18
- package/src/core/common/services/crud.service.ts +3 -3
- package/src/core/modules/file/core-file-info.model.ts +40 -22
- package/src/core/modules/file/core-file.controller.ts +3 -2
- package/src/core/modules/file/core-file.service.ts +49 -60
- package/src/core/modules/file/interfaces/file-upload.interface.ts +2 -1
- package/src/core/modules/user/core-user.model.ts +120 -78
- package/src/core/modules/user/core-user.service.ts +3 -3
- package/src/index.ts +1 -0
- 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/file/file.controller.ts +25 -7
- package/src/server/modules/file/file.resolver.ts +1 -2
- package/src/server/modules/file/multer-config.service.ts +6 -21
- 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/src/types/graphql-upload.d.ts +25 -0
|
@@ -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,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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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(
|
|
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
|
|
88
|
+
const downloadStream = GridFSHelper.openDownloadStream(this.files, objectId);
|
|
86
89
|
|
|
87
90
|
const newFileId = new mongoose.Types.ObjectId();
|
|
88
|
-
const uploadStream = this.files
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
135
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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
|
|
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<
|
|
161
|
+
): Promise<mongo.GridFSBucketReadStream> {
|
|
172
162
|
if (!(await this.checkRights(filename, { ...serviceOptions, checkInputType: 'filename' }))) {
|
|
173
163
|
return null;
|
|
174
164
|
}
|
|
175
|
-
return this.files
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
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
|
-
|
|
258
|
-
return check(
|
|
246
|
+
const prepared = await prepareOutput(fileInfo, { targetModel: CoreFileInfo });
|
|
247
|
+
return check(prepared, options?.currentUser, { roles: options?.roles });
|
|
259
248
|
}
|
|
260
249
|
|
|
261
250
|
/**
|
|
@@ -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
|
/**
|
|
@@ -115,9 +115,9 @@ export abstract class CoreUserService<
|
|
|
115
115
|
return this.process(
|
|
116
116
|
async () => {
|
|
117
117
|
// Update and return user
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
);
|