@underpostnet/underpost 2.97.0 → 2.97.5
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/README.md +2 -2
- package/baremetal/commission-workflows.json +33 -3
- package/bin/deploy.js +1 -1
- package/cli.md +7 -2
- package/conf.js +3 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +1 -1
- package/packer/scripts/fuse-tar-root +3 -3
- package/scripts/disk-clean.sh +23 -23
- package/scripts/gpu-diag.sh +2 -2
- package/scripts/ip-info.sh +11 -11
- package/scripts/maas-upload-boot-resource.sh +1 -1
- package/scripts/nvim.sh +1 -1
- package/scripts/packer-setup.sh +13 -13
- package/scripts/rocky-setup.sh +2 -2
- package/scripts/rpmfusion-ffmpeg-setup.sh +4 -4
- package/scripts/ssl.sh +7 -7
- package/src/api/core/core.service.js +0 -5
- package/src/api/default/default.service.js +7 -5
- package/src/api/document/document.model.js +30 -1
- package/src/api/document/document.router.js +6 -0
- package/src/api/document/document.service.js +423 -51
- package/src/api/file/file.model.js +112 -4
- package/src/api/file/file.ref.json +42 -0
- package/src/api/file/file.service.js +380 -32
- package/src/api/user/user.model.js +38 -1
- package/src/api/user/user.router.js +96 -63
- package/src/api/user/user.service.js +81 -48
- package/src/cli/baremetal.js +689 -329
- package/src/cli/cluster.js +50 -52
- package/src/cli/db.js +424 -166
- package/src/cli/deploy.js +1 -1
- package/src/cli/index.js +12 -1
- package/src/cli/lxd.js +3 -3
- package/src/cli/repository.js +1 -1
- package/src/cli/run.js +2 -1
- package/src/cli/ssh.js +10 -10
- package/src/client/components/core/Account.js +327 -36
- package/src/client/components/core/AgGrid.js +3 -0
- package/src/client/components/core/Auth.js +9 -3
- package/src/client/components/core/Chat.js +2 -2
- package/src/client/components/core/Content.js +159 -78
- package/src/client/components/core/Css.js +16 -2
- package/src/client/components/core/CssCore.js +16 -12
- package/src/client/components/core/FileExplorer.js +115 -8
- package/src/client/components/core/Input.js +204 -11
- package/src/client/components/core/LogIn.js +42 -20
- package/src/client/components/core/Modal.js +257 -177
- package/src/client/components/core/Panel.js +324 -27
- package/src/client/components/core/PanelForm.js +280 -73
- package/src/client/components/core/PublicProfile.js +888 -0
- package/src/client/components/core/Router.js +117 -15
- package/src/client/components/core/SearchBox.js +1117 -0
- package/src/client/components/core/SignUp.js +26 -7
- package/src/client/components/core/SocketIo.js +6 -3
- package/src/client/components/core/Translate.js +98 -0
- package/src/client/components/core/Validator.js +15 -0
- package/src/client/components/core/windowGetDimensions.js +6 -6
- package/src/client/components/default/MenuDefault.js +59 -12
- package/src/client/components/default/RoutesDefault.js +1 -0
- package/src/client/services/core/core.service.js +163 -1
- package/src/client/services/default/default.management.js +451 -64
- package/src/client/services/default/default.service.js +13 -6
- package/src/client/services/document/document.service.js +23 -0
- package/src/client/services/file/file.service.js +43 -16
- package/src/client/services/user/user.service.js +13 -9
- package/src/db/DataBaseProvider.js +1 -1
- package/src/db/mongo/MongooseDB.js +1 -1
- package/src/index.js +1 -1
- package/src/mailer/MailerProvider.js +4 -4
- package/src/runtime/express/Express.js +2 -1
- package/src/runtime/lampp/Lampp.js +2 -2
- package/src/server/auth.js +3 -6
- package/src/server/data-query.js +449 -0
- package/src/server/dns.js +4 -4
- package/src/server/object-layer.js +0 -3
- package/src/ws/IoInterface.js +2 -2
|
@@ -20,7 +20,28 @@ const UserSchema = new Schema(
|
|
|
20
20
|
lastLoginDate: { type: Date },
|
|
21
21
|
failedLoginAttempts: { type: Number, default: 0 },
|
|
22
22
|
password: { type: String, trim: true, required: 'Password is required' },
|
|
23
|
-
username: {
|
|
23
|
+
username: {
|
|
24
|
+
type: String,
|
|
25
|
+
trim: true,
|
|
26
|
+
unique: true,
|
|
27
|
+
required: 'Username is required',
|
|
28
|
+
validate: [
|
|
29
|
+
{
|
|
30
|
+
validator: function (username) {
|
|
31
|
+
// Allow only alphanumeric characters, hyphens, and underscores (URI-safe)
|
|
32
|
+
return /^[a-zA-Z0-9_-]+$/.test(username);
|
|
33
|
+
},
|
|
34
|
+
message: 'Username can only contain letters, numbers, hyphens, and underscores',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
validator: function (username) {
|
|
38
|
+
// Length validation
|
|
39
|
+
return username && username.length >= 2 && username.length <= 20;
|
|
40
|
+
},
|
|
41
|
+
message: 'Username must be between 2 and 20 characters',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
24
45
|
role: { type: String, enum: userRoleEnum, default: 'guest' },
|
|
25
46
|
activeSessions: {
|
|
26
47
|
type: [
|
|
@@ -60,6 +81,8 @@ const UserSchema = new Schema(
|
|
|
60
81
|
context: [{ type: String, enum: ['client', 'supplier', 'employee', 'owner'] }],
|
|
61
82
|
},
|
|
62
83
|
],
|
|
84
|
+
publicProfile: { type: Boolean, default: false },
|
|
85
|
+
briefDescription: { type: String, default: 'Uploader' },
|
|
63
86
|
},
|
|
64
87
|
{
|
|
65
88
|
timestamps: true,
|
|
@@ -80,6 +103,8 @@ const UserDto = {
|
|
|
80
103
|
role: 1,
|
|
81
104
|
emailConfirmed: 1,
|
|
82
105
|
profileImageId: 1,
|
|
106
|
+
publicProfile: 1,
|
|
107
|
+
briefDescription: 1,
|
|
83
108
|
createdAt: 1,
|
|
84
109
|
updatedAt: 1,
|
|
85
110
|
};
|
|
@@ -88,6 +113,18 @@ const UserDto = {
|
|
|
88
113
|
return { _id: 1 };
|
|
89
114
|
},
|
|
90
115
|
},
|
|
116
|
+
public: {
|
|
117
|
+
get: () => {
|
|
118
|
+
return {
|
|
119
|
+
_id: 1,
|
|
120
|
+
username: 1,
|
|
121
|
+
profileImageId: 1,
|
|
122
|
+
publicProfile: 1,
|
|
123
|
+
briefDescription: 1,
|
|
124
|
+
createdAt: 1,
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
},
|
|
91
128
|
auth: {
|
|
92
129
|
payload: (user, jwtid, ip, userAgent, host, path) => {
|
|
93
130
|
const tokenPayload = {
|
|
@@ -4,8 +4,6 @@ import { loggerFactory } from '../../server/logger.js';
|
|
|
4
4
|
import { UserController } from './user.controller.js';
|
|
5
5
|
import express from 'express';
|
|
6
6
|
import { DataBaseProvider } from '../../db/DataBaseProvider.js';
|
|
7
|
-
import { FileFactory } from '../file/file.service.js';
|
|
8
|
-
import { s4 } from '../../client/components/core/CommonJs.js';
|
|
9
7
|
|
|
10
8
|
const logger = loggerFactory(import.meta);
|
|
11
9
|
|
|
@@ -40,12 +38,13 @@ const UserRouter = (options) => {
|
|
|
40
38
|
console.log(error);
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
//
|
|
41
|
+
// Cache mailer images
|
|
44
42
|
options.png = {
|
|
45
43
|
buffer: {
|
|
46
44
|
'invalid-token': fs.readFileSync(`./src/client/public/default/assets/mailer/api-user-invalid-token.png`),
|
|
47
45
|
recover: fs.readFileSync(`./src/client/public/default/assets/mailer/api-user-recover.png`),
|
|
48
46
|
check: fs.readFileSync(`./src/client/public/default/assets/mailer/api-user-check.png`),
|
|
47
|
+
avatar: fs.readFileSync(`./src/client/public/default/assets/mailer/api-user-default-avatar.png`),
|
|
49
48
|
},
|
|
50
49
|
header: (res) => {
|
|
51
50
|
res.set('Cross-Origin-Resource-Policy', 'cross-origin');
|
|
@@ -54,52 +53,38 @@ const UserRouter = (options) => {
|
|
|
54
53
|
res.set('Content-Type', 'image/png');
|
|
55
54
|
},
|
|
56
55
|
};
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const models = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models;
|
|
60
|
-
const name = 'api-user-default-avatar.png';
|
|
61
|
-
const imageFile = await models.File.findOne({ name });
|
|
62
|
-
let _id;
|
|
63
|
-
if (imageFile) {
|
|
64
|
-
_id = imageFile._id;
|
|
65
|
-
} else {
|
|
66
|
-
const file = await new models.File(
|
|
67
|
-
FileFactory.create(fs.readFileSync(`./src/client/public/default/assets/mailer/${name}`), name),
|
|
68
|
-
).save();
|
|
69
|
-
_id = file._id;
|
|
70
|
-
}
|
|
71
|
-
options.getDefaultProfileImageId = async () => {
|
|
72
|
-
return _id;
|
|
73
|
-
};
|
|
74
|
-
} catch (error) {
|
|
75
|
-
logger.error('Error checking/creating default profile image');
|
|
76
|
-
console.log(error);
|
|
77
|
-
}
|
|
78
56
|
})();
|
|
79
57
|
|
|
80
58
|
router.post(`/mailer/:id`, authMiddleware, async (req, res) => {
|
|
81
|
-
/*
|
|
59
|
+
/*
|
|
82
60
|
#swagger.ignore = true
|
|
83
61
|
*/
|
|
84
62
|
return await UserController.post(req, res, options);
|
|
85
63
|
});
|
|
86
64
|
|
|
65
|
+
router.get(`/assets/:id`, async (req, res) => {
|
|
66
|
+
/*
|
|
67
|
+
#swagger.ignore = true
|
|
68
|
+
*/
|
|
69
|
+
return await UserController.get(req, res, options);
|
|
70
|
+
});
|
|
71
|
+
|
|
87
72
|
router.get(`/mailer/:id`, async (req, res) => {
|
|
88
|
-
/*
|
|
73
|
+
/*
|
|
89
74
|
#swagger.ignore = true
|
|
90
75
|
*/
|
|
91
76
|
return await UserController.get(req, res, options);
|
|
92
77
|
});
|
|
93
78
|
|
|
94
79
|
router.get(`/email/:email`, authMiddleware, async (req, res) => {
|
|
95
|
-
/*
|
|
80
|
+
/*
|
|
96
81
|
#swagger.ignore = true
|
|
97
82
|
*/
|
|
98
83
|
return await UserController.get(req, res, options);
|
|
99
84
|
});
|
|
100
85
|
|
|
101
86
|
router.post(`/:id`, async (req, res) => {
|
|
102
|
-
/*
|
|
87
|
+
/*
|
|
103
88
|
#swagger.ignore = true
|
|
104
89
|
*/
|
|
105
90
|
return await UserController.post(req, res, options);
|
|
@@ -125,11 +110,11 @@ const UserRouter = (options) => {
|
|
|
125
110
|
'application/json': {
|
|
126
111
|
schema: {
|
|
127
112
|
$ref: '#/components/schemas/userLogInRequest'
|
|
128
|
-
}
|
|
113
|
+
}
|
|
129
114
|
}
|
|
130
115
|
}
|
|
131
116
|
}
|
|
132
|
-
|
|
117
|
+
|
|
133
118
|
#swagger.responses[200] = {
|
|
134
119
|
description: 'User created successfully',
|
|
135
120
|
content: {
|
|
@@ -137,9 +122,9 @@ const UserRouter = (options) => {
|
|
|
137
122
|
schema: {
|
|
138
123
|
$ref: '#/components/schemas/userResponse'
|
|
139
124
|
}
|
|
140
|
-
}
|
|
125
|
+
}
|
|
141
126
|
}
|
|
142
|
-
}
|
|
127
|
+
}
|
|
143
128
|
|
|
144
129
|
#swagger.responses[400] = {
|
|
145
130
|
description: 'Bad request. Please check the input data',
|
|
@@ -148,9 +133,9 @@ const UserRouter = (options) => {
|
|
|
148
133
|
schema: {
|
|
149
134
|
$ref: '#/components/schemas/userBadRequestResponse'
|
|
150
135
|
}
|
|
151
|
-
}
|
|
136
|
+
}
|
|
152
137
|
}
|
|
153
|
-
}
|
|
138
|
+
}
|
|
154
139
|
*/
|
|
155
140
|
|
|
156
141
|
// #swagger.end
|
|
@@ -174,11 +159,11 @@ const UserRouter = (options) => {
|
|
|
174
159
|
'application/json': {
|
|
175
160
|
schema: {
|
|
176
161
|
$ref: '#/components/schemas/userRequest'
|
|
177
|
-
}
|
|
162
|
+
}
|
|
178
163
|
}
|
|
179
164
|
}
|
|
180
165
|
}
|
|
181
|
-
|
|
166
|
+
|
|
182
167
|
#swagger.responses[200] = {
|
|
183
168
|
description: 'User created successfully',
|
|
184
169
|
content: {
|
|
@@ -186,9 +171,9 @@ const UserRouter = (options) => {
|
|
|
186
171
|
schema: {
|
|
187
172
|
$ref: '#/components/schemas/userResponse'
|
|
188
173
|
}
|
|
189
|
-
}
|
|
174
|
+
}
|
|
190
175
|
}
|
|
191
|
-
}
|
|
176
|
+
}
|
|
192
177
|
|
|
193
178
|
#swagger.responses[400] = {
|
|
194
179
|
description: 'Bad request. Please check the input data',
|
|
@@ -197,20 +182,63 @@ const UserRouter = (options) => {
|
|
|
197
182
|
schema: {
|
|
198
183
|
$ref: '#/components/schemas/userBadRequestResponse'
|
|
199
184
|
}
|
|
200
|
-
}
|
|
185
|
+
}
|
|
201
186
|
}
|
|
202
|
-
}
|
|
187
|
+
}
|
|
203
188
|
*/
|
|
204
189
|
return await UserController.post(req, res, options);
|
|
205
190
|
});
|
|
206
191
|
|
|
207
192
|
router.get(`/recover/:id`, async (req, res) => {
|
|
208
|
-
/*
|
|
193
|
+
/*
|
|
209
194
|
#swagger.ignore = true
|
|
210
195
|
*/
|
|
211
196
|
return await UserController.get(req, res, options);
|
|
212
197
|
});
|
|
213
198
|
|
|
199
|
+
router.get(`/u/:username`, async (req, res) => {
|
|
200
|
+
/*
|
|
201
|
+
#swagger.auto = false
|
|
202
|
+
#swagger.tags = ['user']
|
|
203
|
+
#swagger.summary = 'Get public user profile'
|
|
204
|
+
#swagger.description = 'This endpoint gets public user profile data by username (no auth required)'
|
|
205
|
+
#swagger.path = '/user/u/{username}'
|
|
206
|
+
#swagger.method = 'get'
|
|
207
|
+
#swagger.produces = ['application/json']
|
|
208
|
+
#swagger.consumes = ['application/json']
|
|
209
|
+
|
|
210
|
+
#swagger.parameters['username'] = {
|
|
211
|
+
in: 'path',
|
|
212
|
+
description: 'User username',
|
|
213
|
+
required: true,
|
|
214
|
+
type: 'string'
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#swagger.responses[200] = {
|
|
218
|
+
description: 'get public user profile successfully',
|
|
219
|
+
content: {
|
|
220
|
+
'application/json': {
|
|
221
|
+
schema: {
|
|
222
|
+
$ref: '#/components/schemas/userPublicResponse'
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#swagger.responses[400] = {
|
|
229
|
+
description: 'Bad request. Please check the input data',
|
|
230
|
+
content: {
|
|
231
|
+
'application/json': {
|
|
232
|
+
schema: {
|
|
233
|
+
$ref: '#/components/schemas/userBadRequestResponse'
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
*/
|
|
239
|
+
return await UserController.get(req, res, options);
|
|
240
|
+
});
|
|
241
|
+
|
|
214
242
|
router.get(`/:id`, authMiddleware, async (req, res) => {
|
|
215
243
|
/*
|
|
216
244
|
#swagger.auto = false
|
|
@@ -223,7 +251,7 @@ const UserRouter = (options) => {
|
|
|
223
251
|
#swagger.consumes = ['application/json']
|
|
224
252
|
#swagger.security = [{
|
|
225
253
|
'bearerAuth': []
|
|
226
|
-
}]
|
|
254
|
+
}]
|
|
227
255
|
|
|
228
256
|
#swagger.parameters['id'] = {
|
|
229
257
|
in: 'path',
|
|
@@ -239,9 +267,9 @@ const UserRouter = (options) => {
|
|
|
239
267
|
schema: {
|
|
240
268
|
$ref: '#/components/schemas/userGetResponse'
|
|
241
269
|
}
|
|
242
|
-
}
|
|
270
|
+
}
|
|
243
271
|
}
|
|
244
|
-
}
|
|
272
|
+
}
|
|
245
273
|
|
|
246
274
|
#swagger.responses[400] = {
|
|
247
275
|
description: 'Bad request. Please check the input data',
|
|
@@ -250,26 +278,26 @@ const UserRouter = (options) => {
|
|
|
250
278
|
schema: {
|
|
251
279
|
$ref: '#/components/schemas/userBadRequestResponse'
|
|
252
280
|
}
|
|
253
|
-
}
|
|
281
|
+
}
|
|
254
282
|
}
|
|
255
|
-
}
|
|
283
|
+
}
|
|
256
284
|
*/
|
|
257
285
|
return await UserController.get(req, res, options);
|
|
258
286
|
});
|
|
259
287
|
router.get(`/`, authMiddleware, async (req, res) => {
|
|
260
|
-
/*
|
|
288
|
+
/*
|
|
261
289
|
#swagger.ignore = true
|
|
262
290
|
*/
|
|
263
291
|
return await UserController.get(req, res, options);
|
|
264
292
|
});
|
|
265
293
|
router.put(`/recover/:id`, async (req, res) => {
|
|
266
|
-
/*
|
|
294
|
+
/*
|
|
267
295
|
#swagger.ignore = true
|
|
268
296
|
*/
|
|
269
297
|
return await UserController.put(req, res, options);
|
|
270
298
|
});
|
|
271
299
|
router.put(`/profile-image/:id`, authMiddleware, async (req, res) => {
|
|
272
|
-
/*
|
|
300
|
+
/*
|
|
273
301
|
#swagger.ignore = true
|
|
274
302
|
*/
|
|
275
303
|
return await UserController.put(req, res, options);
|
|
@@ -286,8 +314,8 @@ const UserRouter = (options) => {
|
|
|
286
314
|
#swagger.consumes = ['application/json']
|
|
287
315
|
#swagger.security = [{
|
|
288
316
|
'bearerAuth': []
|
|
289
|
-
}]
|
|
290
|
-
|
|
317
|
+
}]
|
|
318
|
+
|
|
291
319
|
#swagger.parameters['id'] = {
|
|
292
320
|
in: 'path',
|
|
293
321
|
description: 'User ID',
|
|
@@ -303,7 +331,7 @@ const UserRouter = (options) => {
|
|
|
303
331
|
'application/json': {
|
|
304
332
|
schema: {
|
|
305
333
|
$ref: '#/components/schemas/userRequest'
|
|
306
|
-
}
|
|
334
|
+
}
|
|
307
335
|
}
|
|
308
336
|
}
|
|
309
337
|
}
|
|
@@ -315,9 +343,9 @@ const UserRouter = (options) => {
|
|
|
315
343
|
schema: {
|
|
316
344
|
$ref: '#/components/schemas/userUpdateResponse'
|
|
317
345
|
}
|
|
318
|
-
}
|
|
346
|
+
}
|
|
319
347
|
}
|
|
320
|
-
}
|
|
348
|
+
}
|
|
321
349
|
|
|
322
350
|
#swagger.responses[400] = {
|
|
323
351
|
description: 'Bad request. Please check the input data',
|
|
@@ -326,14 +354,14 @@ const UserRouter = (options) => {
|
|
|
326
354
|
schema: {
|
|
327
355
|
$ref: '#/components/schemas/userBadRequestResponse'
|
|
328
356
|
}
|
|
329
|
-
}
|
|
357
|
+
}
|
|
330
358
|
}
|
|
331
|
-
}
|
|
359
|
+
}
|
|
332
360
|
*/
|
|
333
361
|
return await UserController.put(req, res, options);
|
|
334
362
|
});
|
|
335
363
|
router.put(`/`, authMiddleware, async (req, res) => {
|
|
336
|
-
/*
|
|
364
|
+
/*
|
|
337
365
|
#swagger.ignore = true
|
|
338
366
|
*/
|
|
339
367
|
return await UserController.put(req, res, options);
|
|
@@ -351,7 +379,7 @@ const UserRouter = (options) => {
|
|
|
351
379
|
#swagger.consumes = ['application/json']
|
|
352
380
|
#swagger.security = [{
|
|
353
381
|
'bearerAuth': []
|
|
354
|
-
}]
|
|
382
|
+
}]
|
|
355
383
|
|
|
356
384
|
#swagger.parameters['id'] = {
|
|
357
385
|
in: 'path',
|
|
@@ -367,9 +395,9 @@ const UserRouter = (options) => {
|
|
|
367
395
|
schema: {
|
|
368
396
|
$ref: '#/components/schemas/userGetResponse'
|
|
369
397
|
}
|
|
370
|
-
}
|
|
398
|
+
}
|
|
371
399
|
}
|
|
372
|
-
}
|
|
400
|
+
}
|
|
373
401
|
|
|
374
402
|
#swagger.responses[400] = {
|
|
375
403
|
description: 'Bad request. Please check the input data',
|
|
@@ -378,20 +406,25 @@ const UserRouter = (options) => {
|
|
|
378
406
|
schema: {
|
|
379
407
|
$ref: '#/components/schemas/userBadRequestResponse'
|
|
380
408
|
}
|
|
381
|
-
}
|
|
409
|
+
}
|
|
382
410
|
}
|
|
383
|
-
}
|
|
411
|
+
}
|
|
384
412
|
*/
|
|
385
413
|
return await UserController.delete(req, res, options);
|
|
386
414
|
});
|
|
387
415
|
|
|
388
416
|
router.delete(`/`, authMiddleware, async (req, res) => {
|
|
389
|
-
/*
|
|
417
|
+
/*
|
|
390
418
|
#swagger.ignore = true
|
|
391
419
|
*/
|
|
392
420
|
return await UserController.delete(req, res, options);
|
|
393
421
|
});
|
|
394
422
|
|
|
423
|
+
// Username public profile redirect
|
|
424
|
+
options.app.get(`${options.path === '/' ? '' : options.path}/u/:username`, async (req, res, next) =>
|
|
425
|
+
res.redirect(`${options.path === '/' ? '' : options.path}/u?cid=${req.params.username}`),
|
|
426
|
+
);
|
|
427
|
+
|
|
395
428
|
return router;
|
|
396
429
|
};
|
|
397
430
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loggerFactory } from '../../server/logger.js';
|
|
2
|
+
import { DataQuery } from '../../server/data-query.js';
|
|
2
3
|
import {
|
|
3
4
|
hashPassword,
|
|
4
5
|
verifyPassword,
|
|
@@ -17,9 +18,10 @@ import { CoreWsEmit } from '../../ws/core/core.ws.emit.js';
|
|
|
17
18
|
import { CoreWsMailerChannel } from '../../ws/core/channels/core.ws.mailer.js';
|
|
18
19
|
import validator from 'validator';
|
|
19
20
|
import { DataBaseProvider } from '../../db/DataBaseProvider.js';
|
|
20
|
-
import { FileFactory } from '../file/file.service.js';
|
|
21
|
+
import { FileFactory, FileCleanup } from '../file/file.service.js';
|
|
21
22
|
import { UserDto } from './user.model.js';
|
|
22
23
|
import { selectDtoFactory, ValkeyAPI } from '../../server/valkey.js';
|
|
24
|
+
import { timer } from '../../client/components/core/CommonJs.js';
|
|
23
25
|
|
|
24
26
|
const logger = loggerFactory(import.meta);
|
|
25
27
|
|
|
@@ -36,7 +38,11 @@ const UserService = {
|
|
|
36
38
|
email: req.body.email,
|
|
37
39
|
});
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
// Simulate success even if email doesn't exist to prevent email enumeration attacks
|
|
42
|
+
if (!user) {
|
|
43
|
+
await timer(3000);
|
|
44
|
+
return { message: 'email send successfully' };
|
|
45
|
+
}
|
|
40
46
|
|
|
41
47
|
const token = jwtSign({ email: req.body.email }, options, 15);
|
|
42
48
|
const payloadToken = jwtSign({ email: req.body.email }, options, 15);
|
|
@@ -136,19 +142,23 @@ const UserService = {
|
|
|
136
142
|
const { _id } = user;
|
|
137
143
|
const validPassword = await verifyPassword(req.body.password, user.password);
|
|
138
144
|
if (validPassword === true) {
|
|
139
|
-
if (!user.profileImageId)
|
|
140
|
-
await User.findByIdAndUpdate(
|
|
141
|
-
user._id,
|
|
142
|
-
{ profileImageId: await options.getDefaultProfileImageId(File) },
|
|
143
|
-
{
|
|
144
|
-
runValidators: true,
|
|
145
|
-
},
|
|
146
|
-
);
|
|
147
145
|
{
|
|
148
146
|
if (getMinutesRemaining() <= 0 || user.failedLoginAttempts >= 0) {
|
|
149
147
|
const user = await User.findOne({
|
|
150
148
|
_id,
|
|
151
149
|
}).select(UserDto.select.get());
|
|
150
|
+
|
|
151
|
+
// Check if profileImageId exists, if not set to null explicitly
|
|
152
|
+
if (!user.profileImageId) {
|
|
153
|
+
user.profileImageId = null;
|
|
154
|
+
} else {
|
|
155
|
+
const fileExists = await File.findById(user.profileImageId);
|
|
156
|
+
if (!fileExists) {
|
|
157
|
+
await User.findByIdAndUpdate(_id, { profileImageId: null }, { runValidators: true });
|
|
158
|
+
user.profileImageId = null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
152
162
|
await User.findByIdAndUpdate(
|
|
153
163
|
_id,
|
|
154
164
|
{ lastLoginDate: new Date(), failedLoginAttempts: 0 },
|
|
@@ -176,15 +186,18 @@ const UserService = {
|
|
|
176
186
|
runValidators: true,
|
|
177
187
|
},
|
|
178
188
|
);
|
|
179
|
-
setTimeout(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
189
|
+
setTimeout(
|
|
190
|
+
async () => {
|
|
191
|
+
await User.findByIdAndUpdate(
|
|
192
|
+
_id,
|
|
193
|
+
{ failedLoginAttempts: 0 },
|
|
194
|
+
{
|
|
195
|
+
runValidators: true,
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
},
|
|
199
|
+
60 * 1000 * 15,
|
|
200
|
+
);
|
|
188
201
|
throw new Error(`Account locked. Please try again in: 15 min.`);
|
|
189
202
|
} else if (user.failedLoginAttempts < 0 && getMinutesRemaining() > 0) {
|
|
190
203
|
throw new Error(accountLocketMessage());
|
|
@@ -225,9 +238,8 @@ const UserService = {
|
|
|
225
238
|
};
|
|
226
239
|
}
|
|
227
240
|
|
|
228
|
-
default:
|
|
229
|
-
return await createUserAndSession(req, res, User,
|
|
230
|
-
}
|
|
241
|
+
default:
|
|
242
|
+
return await createUserAndSession(req, res, User, options);
|
|
231
243
|
}
|
|
232
244
|
},
|
|
233
245
|
get: async (req, res, options) => {
|
|
@@ -237,6 +249,26 @@ const UserService = {
|
|
|
237
249
|
/** @type {import('../file/file.model.js').FileModel} */
|
|
238
250
|
const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
|
|
239
251
|
|
|
252
|
+
if (req.path.startsWith('/u/')) {
|
|
253
|
+
// First lookup user by username
|
|
254
|
+
const userByUsername = await User.findOne({
|
|
255
|
+
username: req.params.username,
|
|
256
|
+
});
|
|
257
|
+
if (!userByUsername) throw new Error('User not found');
|
|
258
|
+
if (!userByUsername.publicProfile) throw new Error('Public profile is private');
|
|
259
|
+
|
|
260
|
+
// Then fetch complete public data by ID
|
|
261
|
+
const user = await User.findOne({
|
|
262
|
+
_id: userByUsername._id,
|
|
263
|
+
}).select(UserDto.public.get());
|
|
264
|
+
return user;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (req.path.startsWith('/assets')) {
|
|
268
|
+
options.png.header(res);
|
|
269
|
+
return options.png.buffer[req.params.id];
|
|
270
|
+
}
|
|
271
|
+
|
|
240
272
|
if (req.path.startsWith('/email')) {
|
|
241
273
|
return await User.findOne({
|
|
242
274
|
email: req.params.email,
|
|
@@ -303,12 +335,16 @@ const UserService = {
|
|
|
303
335
|
switch (req.params.id) {
|
|
304
336
|
case 'all': {
|
|
305
337
|
if (req.auth.user.role === 'admin') {
|
|
306
|
-
|
|
307
|
-
const limit =
|
|
308
|
-
const skip = (page - 1) * limit;
|
|
338
|
+
// Use DataQuery.parse for filtering, sorting, and pagination
|
|
339
|
+
const { query, sort, skip, limit, page } = DataQuery.parse(req.query);
|
|
309
340
|
|
|
310
|
-
|
|
311
|
-
const
|
|
341
|
+
// Apply default sort if no sort was specified
|
|
342
|
+
const finalSort = Object.keys(sort).length > 0 ? sort : { updatedAt: -1 };
|
|
343
|
+
|
|
344
|
+
const [data, total] = await Promise.all([
|
|
345
|
+
User.find(query).select(UserDto.select.get()).sort(finalSort).skip(skip).limit(limit),
|
|
346
|
+
User.countDocuments(query),
|
|
347
|
+
]);
|
|
312
348
|
|
|
313
349
|
return {
|
|
314
350
|
data,
|
|
@@ -332,18 +368,6 @@ const UserService = {
|
|
|
332
368
|
|
|
333
369
|
if (!user) throw new Error('user not found');
|
|
334
370
|
|
|
335
|
-
const file = await File.findOne({ _id: user.profileImageId });
|
|
336
|
-
|
|
337
|
-
if (!file && !(await ValkeyAPI.getValkeyObject(options, req.auth.user.email))) {
|
|
338
|
-
await User.findByIdAndUpdate(
|
|
339
|
-
user._id,
|
|
340
|
-
{ profileImageId: await options.getDefaultProfileImageId(File) },
|
|
341
|
-
{
|
|
342
|
-
runValidators: true,
|
|
343
|
-
},
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
371
|
const guestUser = await ValkeyAPI.getValkeyObject(options, req.auth.user.email);
|
|
348
372
|
if (guestUser)
|
|
349
373
|
return {
|
|
@@ -426,8 +450,18 @@ const UserService = {
|
|
|
426
450
|
_id,
|
|
427
451
|
});
|
|
428
452
|
if (!user) throw new Error(`User not found`);
|
|
429
|
-
|
|
453
|
+
|
|
430
454
|
const [imageFile] = await FileFactory.upload(req, File);
|
|
455
|
+
|
|
456
|
+
// Clean up old profile image if being replaced
|
|
457
|
+
if (user.profileImageId && imageFile) {
|
|
458
|
+
await FileCleanup.cleanupReplacedFiles({
|
|
459
|
+
oldDoc: user,
|
|
460
|
+
newData: { profileImageId: imageFile._id.toString() },
|
|
461
|
+
fileFields: ['profileImageId'],
|
|
462
|
+
File,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
431
465
|
if (!imageFile) throw new Error('invalid file');
|
|
432
466
|
await User.findByIdAndUpdate(
|
|
433
467
|
_id,
|
|
@@ -479,14 +513,13 @@ const UserService = {
|
|
|
479
513
|
const _id = req.auth.user._id;
|
|
480
514
|
if (_id !== req.params.id) throw new Error(`Invalid token user id`);
|
|
481
515
|
const user = await User.findOne({ _id });
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
);
|
|
516
|
+
const updateData = {
|
|
517
|
+
email: req.body.email && !user.emailConfirmed ? req.body.email : user.email,
|
|
518
|
+
username: req.body.username,
|
|
519
|
+
};
|
|
520
|
+
if (req.body.publicProfile !== undefined) updateData.publicProfile = req.body.publicProfile;
|
|
521
|
+
if (req.body.briefDescription !== undefined) updateData.briefDescription = req.body.briefDescription;
|
|
522
|
+
await User.findByIdAndUpdate(_id, updateData, { runValidators: true });
|
|
490
523
|
return await User.findOne({
|
|
491
524
|
_id,
|
|
492
525
|
}).select(UserDto.select.get());
|