@feardread/fear 1.1.5 → 1.1.6
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/FEARServer.js +2 -2
- package/controllers/address.js +9 -0
- package/controllers/auth/index.js +262 -91
- package/controllers/order.js +0 -1
- package/libs/db/index.js +5 -0
- package/libs/validator/index.js +2 -2
- package/models/address.js +37 -0
- package/models/order.js +24 -146
- package/models/payment.js +17 -76
- package/models/user.js +116 -51
- package/package.json +1 -1
- package/routes/address.js +15 -0
- package/routes/order.js +4 -2
- package/routes/payment.js +3 -3
package/FEARServer.js
CHANGED
|
@@ -71,9 +71,9 @@ const FearServer = (function () {
|
|
|
71
71
|
setupProcessHandlers() {
|
|
72
72
|
// Handle unhandled promise rejections
|
|
73
73
|
process.on("unhandledRejection", (reason, promise) => {
|
|
74
|
-
console.log('Unhandled Rejection at:', promise
|
|
74
|
+
console.log('Unhandled Rejection at:', promise);
|
|
75
75
|
this.fear.getLogger().error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
76
|
-
|
|
76
|
+
this.gracefulShutdown('unhandledRejection');
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
// Handle uncaught exceptions
|
|
@@ -18,15 +18,8 @@ const response = {
|
|
|
18
18
|
* @param {string} message - Success message
|
|
19
19
|
*/
|
|
20
20
|
success: (res, user, token, statusCode = 200, message = "Authentication successful") => {
|
|
21
|
-
//
|
|
22
|
-
const userResponse =
|
|
23
|
-
_id: user._id,
|
|
24
|
-
email: user.email,
|
|
25
|
-
name: user.name,
|
|
26
|
-
role: user.role,
|
|
27
|
-
createdAt: user.createdAt,
|
|
28
|
-
updatedAt: user.updatedAt
|
|
29
|
-
};
|
|
21
|
+
// Use the model's toJSON method which automatically removes sensitive data
|
|
22
|
+
const userResponse = user.toJSON();
|
|
30
23
|
|
|
31
24
|
return res
|
|
32
25
|
.status(statusCode)
|
|
@@ -64,34 +57,72 @@ const response = {
|
|
|
64
57
|
* @description Validates user credentials (email/password) and returns JWT token for authenticated user
|
|
65
58
|
* @tags authentication
|
|
66
59
|
*/
|
|
67
|
-
exports.login =
|
|
60
|
+
exports.login = (req, res) => {
|
|
68
61
|
const { email, password } = req.body;
|
|
69
62
|
|
|
70
63
|
// Validate input
|
|
71
64
|
const validation = validator.input.login({ email, password });
|
|
72
65
|
if (!validation.isValid) {
|
|
73
|
-
return response.
|
|
66
|
+
return response.error(res, 400, validation.message);
|
|
74
67
|
}
|
|
75
68
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
69
|
+
logger.info('Authentication attempt for:', email);
|
|
70
|
+
|
|
71
|
+
// Find user and include password field
|
|
72
|
+
User.findOne({
|
|
73
|
+
email: email.toLowerCase(),
|
|
74
|
+
status: { $ne: 'deleted' }
|
|
75
|
+
})
|
|
76
|
+
.select('+password')
|
|
77
|
+
.then(user => {
|
|
78
|
+
if (!user) {
|
|
79
|
+
return response.error(res, 401, "Invalid credentials");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check if account is locked
|
|
83
|
+
if (user.isLocked()) {
|
|
84
|
+
return response.error(
|
|
85
|
+
res,
|
|
86
|
+
423,
|
|
87
|
+
"Account temporarily locked due to too many failed login attempts. Please try again later."
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check if account is suspended or inactive
|
|
92
|
+
if (user.status === 'suspended') {
|
|
93
|
+
return response.error(res, 403, "Your account has been suspended. Please contact support.");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (user.status === 'inactive') {
|
|
97
|
+
return response.error(res, 403, "Your account is inactive. Please contact support to reactivate.");
|
|
98
|
+
}
|
|
80
99
|
|
|
81
100
|
// Verify password
|
|
82
|
-
user.
|
|
83
|
-
.then(
|
|
101
|
+
return user.comparePassword(password)
|
|
102
|
+
.then(isPasswordValid => {
|
|
84
103
|
if (!isPasswordValid) {
|
|
85
|
-
return
|
|
104
|
+
return user.incLoginAttempts()
|
|
105
|
+
.then(() => {
|
|
106
|
+
return response.error(res, 401, "Invalid credentials");
|
|
107
|
+
});
|
|
86
108
|
}
|
|
87
109
|
|
|
88
|
-
|
|
89
|
-
return
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
110
|
+
// Reset login attempts and update last login
|
|
111
|
+
return user.resetLoginAttempts()
|
|
112
|
+
.then(() => {
|
|
113
|
+
return user.updateOne({
|
|
114
|
+
lastLoginAt: new Date(),
|
|
115
|
+
lastLoginIP: req.ip || req.connection.remoteAddress
|
|
116
|
+
});
|
|
117
|
+
})
|
|
118
|
+
.then(() => {
|
|
119
|
+
// Generate token
|
|
120
|
+
const token = TokenService.generateToken(user);
|
|
121
|
+
return response.success(res, user, token, 200, "Login successful");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
93
124
|
})
|
|
94
|
-
.catch(
|
|
125
|
+
.catch(error => {
|
|
95
126
|
logger.error('Login error:', error);
|
|
96
127
|
return response.error(res, 500, "Authentication failed", error.message);
|
|
97
128
|
});
|
|
@@ -103,50 +134,77 @@ exports.login = async (req, res) => {
|
|
|
103
134
|
* @description Creates a new user account with provided information and returns JWT token
|
|
104
135
|
* @tags authentication
|
|
105
136
|
*/
|
|
106
|
-
exports.register =
|
|
107
|
-
const {
|
|
137
|
+
exports.register = (req, res) => {
|
|
138
|
+
const {
|
|
139
|
+
email,
|
|
140
|
+
password,
|
|
141
|
+
firstName,
|
|
142
|
+
lastName,
|
|
143
|
+
displayName,
|
|
144
|
+
phoneNumber,
|
|
145
|
+
dateOfBirth,
|
|
146
|
+
...otherFields
|
|
147
|
+
} = req.body;
|
|
108
148
|
|
|
109
149
|
// Validate input
|
|
110
150
|
const validation = validator.input.register({
|
|
111
|
-
email,
|
|
151
|
+
email,
|
|
152
|
+
password,
|
|
153
|
+
firstName,
|
|
154
|
+
lastName
|
|
112
155
|
});
|
|
113
156
|
|
|
114
157
|
if (!validation.isValid) {
|
|
115
158
|
return response.error(res, 400, validation.message);
|
|
116
159
|
}
|
|
117
160
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
161
|
+
// Check if user already exists
|
|
162
|
+
User.findOne({ email: email.toLowerCase() })
|
|
163
|
+
.then(existingUser => {
|
|
164
|
+
if (existingUser) {
|
|
165
|
+
return response.error(res, 409, "User with this email already exists");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Create new user with nested profile structure
|
|
169
|
+
const userData = {
|
|
170
|
+
email: email.toLowerCase(),
|
|
171
|
+
password,
|
|
172
|
+
firstName: firstName.trim(),
|
|
173
|
+
lastName: lastName.trim(),
|
|
174
|
+
displayName: displayName || `${firstName} ${lastName}`,
|
|
175
|
+
mobile: phoneNumber || undefined,
|
|
176
|
+
dateOfBirth: dateOfBirth || undefined,
|
|
177
|
+
role: otherFields.role || 'user',
|
|
178
|
+
status: 'active'
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
return User.create(userData);
|
|
182
|
+
})
|
|
183
|
+
.then(user => {
|
|
184
|
+
if (!user) return; // Already handled in previous then
|
|
124
185
|
|
|
125
|
-
|
|
126
|
-
const userData = {
|
|
127
|
-
...otherFields,
|
|
128
|
-
email: email.toLowerCase(),
|
|
129
|
-
name: validation.name,
|
|
130
|
-
password,
|
|
131
|
-
role: otherFields.role || 'user' // Default role
|
|
132
|
-
};
|
|
186
|
+
logger.info('New user registered:', user.email);
|
|
133
187
|
|
|
134
|
-
|
|
188
|
+
// Generate token and send response
|
|
189
|
+
const token = TokenService.generateToken(user);
|
|
190
|
+
return response.success(res, user, token, 201, "Registration successful");
|
|
191
|
+
})
|
|
192
|
+
.catch(error => {
|
|
193
|
+
logger.error('Registration error:', error);
|
|
135
194
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
195
|
+
// Handle duplicate key error (in case of race condition)
|
|
196
|
+
if (error.code === 11000) {
|
|
197
|
+
return response.error(res, 409, "User with this email already exists");
|
|
198
|
+
}
|
|
139
199
|
|
|
140
|
-
|
|
141
|
-
|
|
200
|
+
// Handle validation errors
|
|
201
|
+
if (error.name === 'ValidationError') {
|
|
202
|
+
const messages = Object.values(error.errors).map(err => err.message);
|
|
203
|
+
return response.error(res, 400, messages.join(', '));
|
|
204
|
+
}
|
|
142
205
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return response.error(res, 409, "User with this email already exists");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return response.error(res, 500, "Registration failed", error.message);
|
|
149
|
-
}
|
|
206
|
+
return response.error(res, 500, "Registration failed", error.message);
|
|
207
|
+
});
|
|
150
208
|
};
|
|
151
209
|
|
|
152
210
|
/**
|
|
@@ -175,18 +233,12 @@ exports.logout = async (req, res) => {
|
|
|
175
233
|
* @description Returns the current user's profile information
|
|
176
234
|
* @tags authentication
|
|
177
235
|
*/
|
|
178
|
-
exports.getCurrentUser =
|
|
236
|
+
exports.getCurrentUser = (req, res) => {
|
|
179
237
|
// req.user is set by isAuthorized middleware
|
|
180
238
|
const user = req.user;
|
|
181
239
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
email: user.email,
|
|
185
|
-
name: user.name,
|
|
186
|
-
role: user.role,
|
|
187
|
-
createdAt: user.createdAt,
|
|
188
|
-
updatedAt: user.updatedAt
|
|
189
|
-
};
|
|
240
|
+
// Use toJSON to automatically remove sensitive fields
|
|
241
|
+
const userResponse = user.toJSON();
|
|
190
242
|
|
|
191
243
|
return res.status(200).json({
|
|
192
244
|
success: true,
|
|
@@ -200,20 +252,112 @@ exports.getCurrentUser = async (req, res) => {
|
|
|
200
252
|
* @description Generates a new JWT token for authenticated user
|
|
201
253
|
* @tags authentication
|
|
202
254
|
*/
|
|
203
|
-
exports.refreshToken =
|
|
255
|
+
exports.refreshToken = (req, res) => {
|
|
204
256
|
const user = req.user; // Set by isAuthorized middleware
|
|
205
257
|
|
|
206
258
|
const newToken = TokenService.generateToken(user);
|
|
207
259
|
return response.success(res, user, newToken, 200, "Token refreshed successfully");
|
|
208
260
|
};
|
|
209
261
|
|
|
262
|
+
/**
|
|
263
|
+
* PUT /fear/api/auth/update-profile
|
|
264
|
+
* @summary Update user profile information
|
|
265
|
+
* @description Updates the authenticated user's profile data
|
|
266
|
+
* @tags authentication
|
|
267
|
+
*/
|
|
268
|
+
exports.updateProfile = (req, res) => {
|
|
269
|
+
const { firstName, lastName, displayName, phoneNumber, bio, dateOfBirth, avatar } = req.body;
|
|
270
|
+
|
|
271
|
+
User.findById(req.user._id)
|
|
272
|
+
.then(user => {
|
|
273
|
+
if (!user) {
|
|
274
|
+
return response.error(res, 404, "User not found");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Update profile fields if provided
|
|
278
|
+
if (firstName) user.firstName = firstName.trim();
|
|
279
|
+
if (lastName) user.lastName = lastName.trim();
|
|
280
|
+
if (displayName !== undefined) user.displayName = displayName.trim();
|
|
281
|
+
if (phoneNumber !== undefined) user.phoneNumber = phoneNumber;
|
|
282
|
+
if (bio !== undefined) user.bio = bio;
|
|
283
|
+
if (dateOfBirth !== undefined) user.dateOfBirth = dateOfBirth;
|
|
284
|
+
if (avatar !== undefined) user.avatar = avatar;
|
|
285
|
+
|
|
286
|
+
return user.save();
|
|
287
|
+
})
|
|
288
|
+
.then(user => {
|
|
289
|
+
if (!user) return; // Already handled
|
|
290
|
+
|
|
291
|
+
return res.status(200).json({
|
|
292
|
+
success: true,
|
|
293
|
+
message: "Profile updated successfully",
|
|
294
|
+
data: { user: user.toJSON() }
|
|
295
|
+
});
|
|
296
|
+
})
|
|
297
|
+
.catch(error => {
|
|
298
|
+
logger.error('Profile update error:', error);
|
|
299
|
+
|
|
300
|
+
if (error.name === 'ValidationError') {
|
|
301
|
+
const messages = Object.values(error.errors).map(err => err.message);
|
|
302
|
+
return response.error(res, 400, messages.join(', '));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return response.error(res, 500, "Profile update failed", error.message);
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* PUT /fear/api/auth/update-preferences
|
|
311
|
+
* @summary Update user preferences
|
|
312
|
+
* @description Updates the authenticated user's preferences (language, timezone, notifications, theme)
|
|
313
|
+
* @tags authentication
|
|
314
|
+
*/
|
|
315
|
+
exports.updatePreferences = (req, res) => {
|
|
316
|
+
const { language, timezone, notifications, theme } = req.body;
|
|
317
|
+
|
|
318
|
+
User.findById(req.user._id)
|
|
319
|
+
.then(user => {
|
|
320
|
+
if (!user) {
|
|
321
|
+
return response.error(res, 404, "User not found");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Update preferences
|
|
325
|
+
if (language) user.preferences.language = language;
|
|
326
|
+
if (timezone) user.preferences.timezone = timezone;
|
|
327
|
+
if (theme) user.preferences.theme = theme;
|
|
328
|
+
if (notifications) {
|
|
329
|
+
user.preferences.notifications = {
|
|
330
|
+
...user.preferences.notifications,
|
|
331
|
+
...notifications
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return user.save();
|
|
336
|
+
})
|
|
337
|
+
.then(user => {
|
|
338
|
+
if (!user) return; // Already handled
|
|
339
|
+
|
|
340
|
+
return res.status(200).json({
|
|
341
|
+
success: true,
|
|
342
|
+
message: "Preferences updated successfully",
|
|
343
|
+
data: {
|
|
344
|
+
preferences: user.preferences
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
})
|
|
348
|
+
.catch(error => {
|
|
349
|
+
logger.error('Preferences update error:', error);
|
|
350
|
+
return response.error(res, 500, "Preferences update failed", error.message);
|
|
351
|
+
});
|
|
352
|
+
};
|
|
353
|
+
|
|
210
354
|
/**
|
|
211
355
|
* Middleware: Verify JWT token and authenticate user
|
|
212
356
|
* @summary Checks if user has valid JWT token in cookies or Authorization header
|
|
213
357
|
* @description Validates JWT token and attaches user object to request
|
|
214
358
|
* @tags middleware, authentication
|
|
215
359
|
*/
|
|
216
|
-
exports.isAuthorized =
|
|
360
|
+
exports.isAuthorized = (req, res, next) => {
|
|
217
361
|
let token;
|
|
218
362
|
|
|
219
363
|
// Check for token in cookies first, then Authorization header
|
|
@@ -232,23 +376,37 @@ exports.isAuthorized = async (req, res, next) => {
|
|
|
232
376
|
const decodedToken = TokenService.verifyToken(token);
|
|
233
377
|
|
|
234
378
|
// Get user from database
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
379
|
+
User.findById(decodedToken.id)
|
|
380
|
+
.then(user => {
|
|
381
|
+
if (!user) {
|
|
382
|
+
return response.error(res, 401, "Invalid token. User not found.");
|
|
383
|
+
}
|
|
239
384
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
385
|
+
// Check user status
|
|
386
|
+
if (user.status === 'deleted') {
|
|
387
|
+
return response.error(res, 401, "Account has been deleted.");
|
|
388
|
+
}
|
|
244
389
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
390
|
+
if (user.status === 'suspended') {
|
|
391
|
+
return response.error(res, 403, "Account has been suspended.");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (user.status === 'inactive') {
|
|
395
|
+
return response.error(res, 403, "Account is inactive.");
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Attach user to request
|
|
399
|
+
req.user = user;
|
|
400
|
+
req.token = token;
|
|
401
|
+
next();
|
|
402
|
+
})
|
|
403
|
+
.catch(error => {
|
|
404
|
+
logger.error('Authorization error:', error);
|
|
405
|
+
return response.error(res, 500, "Authentication failed", error.message);
|
|
406
|
+
});
|
|
249
407
|
|
|
250
408
|
} catch (error) {
|
|
251
|
-
|
|
409
|
+
logger.error('Authorization error:', error);
|
|
252
410
|
|
|
253
411
|
if (error.name === 'JsonWebTokenError') {
|
|
254
412
|
return response.error(res, 401, "Invalid token.");
|
|
@@ -268,7 +426,7 @@ exports.isAuthorized = async (req, res, next) => {
|
|
|
268
426
|
* @description Checks if req.user.role equals 'admin'. Must be used after isAuthorized middleware
|
|
269
427
|
* @tags middleware, authorization
|
|
270
428
|
*/
|
|
271
|
-
exports.isAdmin =
|
|
429
|
+
exports.isAdmin = (req, res, next) => {
|
|
272
430
|
if (!req.user) {
|
|
273
431
|
return response.error(res, 401, "Authentication required");
|
|
274
432
|
}
|
|
@@ -289,7 +447,7 @@ exports.isAdmin = async (req, res, next) => {
|
|
|
289
447
|
* @returns {function} Express middleware function
|
|
290
448
|
*/
|
|
291
449
|
exports.authorizeRoles = (...roles) => {
|
|
292
|
-
return
|
|
450
|
+
return (req, res, next) => {
|
|
293
451
|
if (!req.user) {
|
|
294
452
|
return response.error(res, 401, "Authentication required");
|
|
295
453
|
}
|
|
@@ -303,7 +461,7 @@ exports.authorizeRoles = (...roles) => {
|
|
|
303
461
|
}
|
|
304
462
|
|
|
305
463
|
next();
|
|
306
|
-
}
|
|
464
|
+
};
|
|
307
465
|
};
|
|
308
466
|
|
|
309
467
|
/**
|
|
@@ -312,7 +470,7 @@ exports.authorizeRoles = (...roles) => {
|
|
|
312
470
|
* @description Useful for routes that behave differently for authenticated vs anonymous users
|
|
313
471
|
* @tags middleware, authentication
|
|
314
472
|
*/
|
|
315
|
-
exports.optionalAuth =
|
|
473
|
+
exports.optionalAuth = (req, res, next) => {
|
|
316
474
|
let token;
|
|
317
475
|
|
|
318
476
|
if (req.cookies?.jwt) {
|
|
@@ -327,19 +485,32 @@ exports.optionalAuth = async (req, res, next) => {
|
|
|
327
485
|
|
|
328
486
|
try {
|
|
329
487
|
const decodedToken = TokenService.verifyToken(token);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
488
|
+
|
|
489
|
+
User.findById(decodedToken.id)
|
|
490
|
+
.then(user => {
|
|
491
|
+
if (user && user.status === 'active') {
|
|
492
|
+
req.user = user;
|
|
493
|
+
req.token = token;
|
|
494
|
+
}
|
|
495
|
+
next();
|
|
496
|
+
})
|
|
497
|
+
.catch(error => {
|
|
498
|
+
// Silently fail for optional auth
|
|
499
|
+
logger.debug('Optional auth failed:', error.message);
|
|
500
|
+
next();
|
|
501
|
+
});
|
|
336
502
|
} catch (error) {
|
|
337
503
|
// Silently fail for optional auth
|
|
338
|
-
|
|
504
|
+
logger.debug('Optional auth failed:', error.message);
|
|
505
|
+
next();
|
|
339
506
|
}
|
|
340
|
-
|
|
341
|
-
next();
|
|
342
507
|
};
|
|
343
508
|
|
|
344
509
|
// Export TokenService and other utilities for use in other modules
|
|
345
510
|
exports.AuthResponse = response;
|
|
511
|
+
|
|
512
|
+
module.exports = {
|
|
513
|
+
login: exports.login,
|
|
514
|
+
register: exports.register,
|
|
515
|
+
logout: exports.logout
|
|
516
|
+
}
|
package/controllers/order.js
CHANGED
package/libs/db/index.js
CHANGED
package/libs/validator/index.js
CHANGED
|
@@ -52,7 +52,7 @@ exports.input = {
|
|
|
52
52
|
* @returns {object} Validation result
|
|
53
53
|
*/
|
|
54
54
|
register: (data) => {
|
|
55
|
-
const { email, password,
|
|
55
|
+
const { email, password, firstName, lastName, displayName } = data;
|
|
56
56
|
|
|
57
57
|
if (!email) return { isValid: false, message: "Email is required" };
|
|
58
58
|
if (!exports.input.email(email)) return { isValid: false, message: "Please provide a valid email address" };
|
|
@@ -60,7 +60,7 @@ exports.input = {
|
|
|
60
60
|
const passwordValidation = exports.input.password(password);
|
|
61
61
|
if (!passwordValidation.isValid) return passwordValidation;
|
|
62
62
|
|
|
63
|
-
const fullName =
|
|
63
|
+
const fullName = (displayName) ? displayName : `${firstName} ${lastName}`;
|
|
64
64
|
if (!fullName) return { isValid: false, message: "Name is required" };
|
|
65
65
|
|
|
66
66
|
return { isValid: true, name: fullName };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const mongoose = require('mongoose');
|
|
2
|
+
|
|
3
|
+
const addressSchema = new mongoose.Schema({
|
|
4
|
+
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true, index: true },
|
|
5
|
+
type: { type: String, enum: ['home', 'work', 'billing', 'shipping', 'other'], default: 'billing' },
|
|
6
|
+
isDefault: { type: Boolean, default: false },
|
|
7
|
+
name: { type: String, trim: true, maxlength: 50 },
|
|
8
|
+
line1: { type: String, required: true, trim: true },
|
|
9
|
+
line2: { type: String, trim: true },
|
|
10
|
+
city: { type: String, required: true, trim: true },
|
|
11
|
+
state: { type: String, required: true, trim: true },
|
|
12
|
+
postalCode: { type: String, required: true, trim: true },
|
|
13
|
+
country: { type: String, required: true, default: 'US', uppercase: true },
|
|
14
|
+
coordinates: { type: { type: String, enum: ['Point'], default: 'Point' },
|
|
15
|
+
coordinates: { type: [Number], default: [0, 0] }},
|
|
16
|
+
phoneNumber: { type: String, trim: true },
|
|
17
|
+
deliveryInstructions: { type: String, trim: true, maxlength: 500 }
|
|
18
|
+
}, {
|
|
19
|
+
timestamps: true
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Indexes
|
|
23
|
+
addressSchema.index({ userId: 1, isDefault: 1 });
|
|
24
|
+
addressSchema.index({ coordinates: '2dsphere' });
|
|
25
|
+
|
|
26
|
+
// Ensure only one default address per user
|
|
27
|
+
addressSchema.pre('save', async function(next) {
|
|
28
|
+
if (this.isDefault) {
|
|
29
|
+
await this.constructor.updateMany(
|
|
30
|
+
{ userId: this.userId, _id: { $ne: this._id } },
|
|
31
|
+
{ $set: { isDefault: false } }
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
next();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
module.exports = mongoose.model('Address', addressSchema);
|
package/models/order.js
CHANGED
|
@@ -1,93 +1,30 @@
|
|
|
1
1
|
const mongoose = require("mongoose");
|
|
2
2
|
|
|
3
3
|
const orderItemSchema = new mongoose.Schema({
|
|
4
|
-
productId: {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
},
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
required: true,
|
|
12
|
-
},
|
|
13
|
-
quantity: {
|
|
14
|
-
type: Number,
|
|
15
|
-
required: true,
|
|
16
|
-
min: 1,
|
|
17
|
-
},
|
|
18
|
-
price: {
|
|
19
|
-
type: Number,
|
|
20
|
-
required: true,
|
|
21
|
-
},
|
|
22
|
-
discount: {
|
|
23
|
-
type: Number,
|
|
24
|
-
default: 0,
|
|
25
|
-
},
|
|
26
|
-
tax: {
|
|
27
|
-
type: Number,
|
|
28
|
-
default: 0,
|
|
29
|
-
},
|
|
30
|
-
subtotal: {
|
|
31
|
-
type: Number,
|
|
32
|
-
required: true,
|
|
33
|
-
},
|
|
4
|
+
productId: { type: mongoose.Schema.Types.ObjectId, ref: "Product", required: true },
|
|
5
|
+
title: { type: String, required: true, },
|
|
6
|
+
quantity: { type: Number, required: true, min: 1, },
|
|
7
|
+
price: { type: Number, required: true, },
|
|
8
|
+
discount: { type: Number, default: 0, },
|
|
9
|
+
tax: { type: Number, default: 0, },
|
|
10
|
+
subtotal: { type: Number, required: true, },
|
|
34
11
|
image: String,
|
|
35
12
|
sku: String,
|
|
36
|
-
attributes: {
|
|
37
|
-
type: Map,
|
|
38
|
-
of: String, // For size, color, etc.
|
|
39
|
-
},
|
|
13
|
+
attributes: { type: Map, of: String, },
|
|
40
14
|
});
|
|
41
15
|
|
|
42
16
|
const orderSchema = new mongoose.Schema(
|
|
43
17
|
{
|
|
44
|
-
orderNumber: {
|
|
45
|
-
|
|
46
|
-
unique: true,
|
|
47
|
-
required: true,
|
|
48
|
-
},
|
|
49
|
-
userId: {
|
|
50
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
51
|
-
ref: "User",
|
|
52
|
-
required: true,
|
|
53
|
-
index: true,
|
|
54
|
-
},
|
|
55
|
-
// Order Items
|
|
18
|
+
orderNumber: { type: String, unique: true, required: true, },
|
|
19
|
+
userId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true, index: true, },
|
|
56
20
|
items: [orderItemSchema],
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
type: Number,
|
|
65
|
-
default: 0,
|
|
66
|
-
},
|
|
67
|
-
tax: {
|
|
68
|
-
type: Number,
|
|
69
|
-
default: 0,
|
|
70
|
-
},
|
|
71
|
-
shippingCost: {
|
|
72
|
-
type: Number,
|
|
73
|
-
default: 0,
|
|
74
|
-
},
|
|
75
|
-
total: {
|
|
76
|
-
type: Number,
|
|
77
|
-
required: true,
|
|
78
|
-
},
|
|
79
|
-
currency: {
|
|
80
|
-
type: String,
|
|
81
|
-
default: "INR",
|
|
82
|
-
enum: ["INR", "USD", "EUR", "GBP"],
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
// Payment Information
|
|
86
|
-
paymentMethod: {
|
|
87
|
-
type: String,
|
|
88
|
-
enum: ["razorpay", "paypal", "card", "upi", "netbanking", "cod"],
|
|
89
|
-
required: true,
|
|
90
|
-
},
|
|
21
|
+
subtotal: { type: Number, required: true },
|
|
22
|
+
discount: { type: Number, default: 0, },
|
|
23
|
+
tax: { type: Number, default: 0, },
|
|
24
|
+
shippingCost: { type: Number, default: 0, },
|
|
25
|
+
total: { type: Number, required: true, },
|
|
26
|
+
currency: { type: String, default: "INR", enum: ["INR", "USD", "EUR", "GBP"], },
|
|
27
|
+
paymentMethod: { type: String, enum: ["razorpay", "paypal", "card", "upi", "netbanking", "cod"], required: true, },
|
|
91
28
|
paymentStatus: {
|
|
92
29
|
type: String,
|
|
93
30
|
enum: ["pending", "processing", "completed", "failed", "refunded", "partially_refunded"],
|
|
@@ -112,8 +49,6 @@ const orderSchema = new mongoose.Schema(
|
|
|
112
49
|
},
|
|
113
50
|
paidAt: Date,
|
|
114
51
|
},
|
|
115
|
-
|
|
116
|
-
// Order Status
|
|
117
52
|
orderStatus: {
|
|
118
53
|
type: String,
|
|
119
54
|
enum: [
|
|
@@ -130,62 +65,12 @@ const orderSchema = new mongoose.Schema(
|
|
|
130
65
|
default: "pending",
|
|
131
66
|
index: true,
|
|
132
67
|
},
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
shippingAddress: {
|
|
136
|
-
name: {
|
|
137
|
-
type: String,
|
|
138
|
-
required: true,
|
|
139
|
-
},
|
|
140
|
-
phone: {
|
|
141
|
-
type: String,
|
|
142
|
-
required: true,
|
|
143
|
-
},
|
|
144
|
-
email: String,
|
|
145
|
-
line1: {
|
|
146
|
-
type: String,
|
|
147
|
-
required: true,
|
|
148
|
-
},
|
|
149
|
-
line2: String,
|
|
150
|
-
city: {
|
|
151
|
-
type: String,
|
|
152
|
-
required: true,
|
|
153
|
-
},
|
|
154
|
-
state: {
|
|
155
|
-
type: String,
|
|
156
|
-
required: true,
|
|
157
|
-
},
|
|
158
|
-
postalCode: {
|
|
159
|
-
type: String,
|
|
160
|
-
required: true,
|
|
161
|
-
},
|
|
162
|
-
country: {
|
|
163
|
-
type: String,
|
|
164
|
-
required: true,
|
|
165
|
-
default: "IN",
|
|
166
|
-
},
|
|
167
|
-
landmark: String,
|
|
168
|
-
},
|
|
169
|
-
|
|
170
|
-
billingAddress: {
|
|
171
|
-
name: String,
|
|
172
|
-
phone: String,
|
|
173
|
-
email: String,
|
|
174
|
-
line1: String,
|
|
175
|
-
line2: String,
|
|
176
|
-
city: String,
|
|
177
|
-
state: String,
|
|
178
|
-
postalCode: String,
|
|
179
|
-
country: String,
|
|
180
|
-
},
|
|
181
|
-
|
|
182
|
-
// Shipping Details
|
|
68
|
+
shippingAddress: { type: mongoose.Schema.Types.ObjectId, ref: "Address", required: true, },
|
|
69
|
+
billingAddress: { type: mongoose.Schema.Types.ObjectId, ref: "Address", required: true, },
|
|
183
70
|
shippingProvider: String,
|
|
184
71
|
trackingNumber: String,
|
|
185
72
|
estimatedDeliveryDate: Date,
|
|
186
73
|
actualDeliveryDate: Date,
|
|
187
|
-
|
|
188
|
-
// Status History
|
|
189
74
|
statusHistory: [
|
|
190
75
|
{
|
|
191
76
|
status: String,
|
|
@@ -200,30 +85,21 @@ const orderSchema = new mongoose.Schema(
|
|
|
200
85
|
},
|
|
201
86
|
},
|
|
202
87
|
],
|
|
203
|
-
|
|
204
|
-
// Discount/Coupon
|
|
205
88
|
couponCode: String,
|
|
206
89
|
couponDiscount: {
|
|
207
90
|
type: Number,
|
|
208
91
|
default: 0,
|
|
209
92
|
},
|
|
210
|
-
|
|
211
|
-
// Notes
|
|
212
93
|
customerNote: String,
|
|
213
94
|
internalNote: String,
|
|
214
|
-
|
|
215
|
-
// Cancellation/Return
|
|
216
95
|
cancellationReason: String,
|
|
217
96
|
cancelledAt: Date,
|
|
218
97
|
cancelledBy: {
|
|
219
98
|
type: mongoose.Schema.Types.ObjectId,
|
|
220
99
|
ref: "User",
|
|
221
100
|
},
|
|
222
|
-
|
|
223
101
|
returnReason: String,
|
|
224
102
|
returnedAt: Date,
|
|
225
|
-
|
|
226
|
-
// Refund Information
|
|
227
103
|
refundAmount: Number,
|
|
228
104
|
refundStatus: {
|
|
229
105
|
type: String,
|
|
@@ -232,8 +108,6 @@ const orderSchema = new mongoose.Schema(
|
|
|
232
108
|
},
|
|
233
109
|
refundedAt: Date,
|
|
234
110
|
refundTransactionId: String,
|
|
235
|
-
|
|
236
|
-
// Metadata
|
|
237
111
|
metadata: {
|
|
238
112
|
type: Map,
|
|
239
113
|
of: mongoose.Schema.Types.Mixed,
|
|
@@ -252,6 +126,8 @@ orderSchema.index({ paymentStatus: 1 });
|
|
|
252
126
|
orderSchema.index({ "paymentDetails.razorpayOrderId": 1 });
|
|
253
127
|
orderSchema.index({ "paymentDetails.paypalOrderId": 1 });
|
|
254
128
|
orderSchema.index({ trackingNumber: 1 });
|
|
129
|
+
orderSchema.index({ shippingAddress: 1 });
|
|
130
|
+
orderSchema.index({ billingAddress: 1 });
|
|
255
131
|
|
|
256
132
|
// Pre-save middleware to generate order number
|
|
257
133
|
orderSchema.pre("save", async function (next) {
|
|
@@ -288,7 +164,9 @@ orderSchema.statics.getUserOrders = async function (userId, options = {}) {
|
|
|
288
164
|
.limit(limit)
|
|
289
165
|
.skip(skip)
|
|
290
166
|
.populate("items.productId", "name images")
|
|
291
|
-
.populate("paymentDetails.paymentMethodId")
|
|
167
|
+
.populate("paymentDetails.paymentMethodId")
|
|
168
|
+
.populate("shippingAddress")
|
|
169
|
+
.populate("billingAddress");
|
|
292
170
|
};
|
|
293
171
|
|
|
294
172
|
// Static method to get order statistics
|
package/models/payment.js
CHANGED
|
@@ -3,87 +3,28 @@ const crypto = require("crypto");
|
|
|
3
3
|
|
|
4
4
|
const paymentMethodSchema = new mongoose.Schema(
|
|
5
5
|
{
|
|
6
|
-
userId: {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
},
|
|
12
|
-
paymentType: {
|
|
13
|
-
type: String,
|
|
14
|
-
enum: ["razorpay", "paypal", "card", "upi", "netbanking"],
|
|
15
|
-
required: true,
|
|
16
|
-
},
|
|
17
|
-
paymentToken: {
|
|
18
|
-
type: String,
|
|
19
|
-
required: true,
|
|
20
|
-
// This stores the tokenized payment method from the payment gateway
|
|
21
|
-
},
|
|
22
|
-
isDefault: {
|
|
23
|
-
type: Boolean,
|
|
24
|
-
default: false,
|
|
25
|
-
},
|
|
26
|
-
isActive: {
|
|
27
|
-
type: Boolean,
|
|
28
|
-
default: true,
|
|
29
|
-
},
|
|
30
|
-
// Card/Payment details (only non-sensitive info for display)
|
|
6
|
+
userId: { type: mongoose.Schema.Types.ObjectId. ref: "User", required: true, index: true, },
|
|
7
|
+
paymentType: { type: String, enum: ["razorpay", "paypal", "card", "upi", "netbanking", "stripe"], required: true, },
|
|
8
|
+
paymentToken: { type: String, required: true, },
|
|
9
|
+
isDefault: { type: Boolean, default: false, },
|
|
10
|
+
isActive: { type: Boolean, default: true, },
|
|
31
11
|
cardDetails: {
|
|
32
|
-
last4: {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
expiryMonth: {
|
|
40
|
-
type: String,
|
|
41
|
-
maxlength: 2,
|
|
42
|
-
},
|
|
43
|
-
expiryYear: {
|
|
44
|
-
type: String,
|
|
45
|
-
maxlength: 4,
|
|
46
|
-
},
|
|
47
|
-
cardholderName: {
|
|
48
|
-
type: String,
|
|
49
|
-
},
|
|
50
|
-
fingerprint: {
|
|
51
|
-
type: String, // Unique identifier for the card
|
|
52
|
-
},
|
|
12
|
+
last4: { type: String, maxlength: 4, },
|
|
13
|
+
brand: { type: String, },
|
|
14
|
+
expiryMonth: { type: String, maxlength: 2, },
|
|
15
|
+
expiryYear: { type: String, maxlength: 4, },
|
|
16
|
+
cardholderName: { type: String, },
|
|
17
|
+
fingerprint: { type: String,}
|
|
53
18
|
},
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
type: String,
|
|
58
|
-
},
|
|
59
|
-
payerId: {
|
|
60
|
-
type: String,
|
|
61
|
-
},
|
|
19
|
+
paypalDetails: {
|
|
20
|
+
email: { type: String, },
|
|
21
|
+
payerId: { type: String, },
|
|
62
22
|
},
|
|
63
|
-
// UPI details
|
|
64
23
|
upiDetails: {
|
|
65
|
-
vpa: {
|
|
66
|
-
type: String, // Virtual Payment Address (e.g., user@paytm)
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
// Billing address
|
|
70
|
-
billingAddress: {
|
|
71
|
-
name: String,
|
|
72
|
-
line1: String,
|
|
73
|
-
line2: String,
|
|
74
|
-
city: String,
|
|
75
|
-
state: String,
|
|
76
|
-
postalCode: String,
|
|
77
|
-
country: {
|
|
78
|
-
type: String,
|
|
79
|
-
default: "IN",
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
// Metadata for additional information
|
|
83
|
-
metadata: {
|
|
84
|
-
type: Map,
|
|
85
|
-
of: String,
|
|
24
|
+
vpa: { type: String, },
|
|
86
25
|
},
|
|
26
|
+
billingAddress: { type: mongoose.Schema.Types.ObjectId, ref: "Address", required: true, index: true, },
|
|
27
|
+
metadata: { type: Map, of: String, },
|
|
87
28
|
},
|
|
88
29
|
{
|
|
89
30
|
timestamps: true,
|
package/models/user.js
CHANGED
|
@@ -1,70 +1,135 @@
|
|
|
1
|
-
const mongoose = require(
|
|
2
|
-
const bcrypt = require(
|
|
3
|
-
const crypto = require("crypto");
|
|
1
|
+
const mongoose = require('mongoose');
|
|
2
|
+
const bcrypt = require('bcrypt');
|
|
4
3
|
|
|
5
4
|
const userSchema = new mongoose.Schema({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
avatar: { type: Object, required: false, default: {
|
|
5
|
+
email: { type: String, required: true, unique: true, lowercase: true, trim: true,
|
|
6
|
+
match: [/^\S+@\S+\.\S+$/, 'Please enter a valid email']
|
|
7
|
+
},
|
|
8
|
+
password: { type: String, required: true, minlength: 8, select: false },
|
|
9
|
+
firstName: { type: String, required: true, trim: true },
|
|
10
|
+
lastName: { type: String, required: true, trim: true },
|
|
11
|
+
displayName: { type: String, trim: true },
|
|
12
|
+
avatar: { type: Object, required: false, default: {
|
|
15
13
|
public_id: '',
|
|
16
|
-
secure_url: ''
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
secure_url: '',
|
|
15
|
+
}, trim: true
|
|
16
|
+
},
|
|
17
|
+
bio: { type: String, maxlength: 500 },
|
|
18
|
+
dateOfBirth: Date,
|
|
19
|
+
mobile: { type: String, trim: true, unique: false, required: false },
|
|
20
|
+
role: { type: String,
|
|
21
|
+
enum: ['user', 'admin', 'moderator'], default: 'user' },
|
|
22
|
+
status: { type: String,
|
|
23
|
+
enum: ['active', 'inactive', 'suspended', 'deleted'], default: 'active' },
|
|
24
|
+
emailVerified: { type: Boolean, default: false },
|
|
25
|
+
emailVerificationToken: String,
|
|
26
|
+
emailVerificationExpires: Date,
|
|
27
|
+
passwordResetToken: String,
|
|
28
|
+
passwordResetExpires: Date,
|
|
29
|
+
twoFactorSecret: { type: String, select: false},
|
|
30
|
+
twoFactorEnabled: { type: Boolean, default: false },
|
|
31
|
+
preferences: {
|
|
32
|
+
language: { type: String, default: 'en' },
|
|
33
|
+
timezone: { type: String, default: 'UTC' },
|
|
34
|
+
notifications: {
|
|
35
|
+
email: { type: Boolean, default: true },
|
|
36
|
+
push: { type: Boolean, default: true },
|
|
37
|
+
sms: { type: Boolean, default: false }
|
|
27
38
|
},
|
|
28
|
-
|
|
29
|
-
|
|
39
|
+
theme: { type: String, enum: ['light', 'dark', 'auto'], default: 'auto' }
|
|
40
|
+
},
|
|
41
|
+
socialAccounts: [{
|
|
42
|
+
provider: { type: String,
|
|
43
|
+
enum: ['google', 'facebook', 'github', 'apple']},
|
|
44
|
+
providerId: String,
|
|
45
|
+
email: String,
|
|
46
|
+
connectedAt: {
|
|
47
|
+
type: Date,
|
|
48
|
+
default: Date.now
|
|
49
|
+
}
|
|
50
|
+
}],
|
|
51
|
+
lastLoginAt: Date,
|
|
52
|
+
lastLoginIP: String,
|
|
53
|
+
loginAttempts: { type: Number, default: 0 },
|
|
54
|
+
lockUntil: Date
|
|
55
|
+
}, {
|
|
56
|
+
timestamps: true
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Indexes
|
|
60
|
+
userSchema.index({ email: 1 });
|
|
61
|
+
userSchema.index({ 'profile.firstName': 1, 'profile.lastName': 1 });
|
|
62
|
+
userSchema.index({ createdAt: 1 });
|
|
63
|
+
userSchema.index({ status: 1, role: 1 });
|
|
64
|
+
|
|
65
|
+
// Virtual for full name
|
|
66
|
+
userSchema.virtual('profile.fullName').get(function() {
|
|
67
|
+
return `${this.profile.firstName} ${this.profile.lastName}`;
|
|
68
|
+
});
|
|
30
69
|
|
|
31
|
-
//
|
|
32
|
-
userSchema.pre(
|
|
33
|
-
if (!this.isModified(
|
|
70
|
+
// Hash password before saving
|
|
71
|
+
userSchema.pre('save', async function(next) {
|
|
72
|
+
if (!this.isModified('password')) return next();
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const salt = await bcrypt.genSalt(12);
|
|
76
|
+
this.password = await bcrypt.hash(this.password, salt);
|
|
34
77
|
next();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
next(error);
|
|
35
80
|
}
|
|
36
|
-
const salt = await bcrypt.genSaltSync(10);
|
|
37
|
-
this.password = await bcrypt.hash(this.password, salt);
|
|
38
|
-
next();
|
|
39
81
|
});
|
|
40
82
|
|
|
41
|
-
//
|
|
42
|
-
userSchema.methods.
|
|
43
|
-
return await bcrypt.compare(
|
|
83
|
+
// Method to compare passwords
|
|
84
|
+
userSchema.methods.comparePassword = async function(candidatePassword) {
|
|
85
|
+
return await bcrypt.compare(candidatePassword, this.password);
|
|
44
86
|
};
|
|
45
87
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.passwordResetToken = crypto.createHash("sha256").update(resettoken).digest("hex");
|
|
50
|
-
this.passwordResetExpires = Date.now() + 30 * 60 * 1000;
|
|
51
|
-
|
|
52
|
-
return resettoken;
|
|
88
|
+
// Method to check if account is locked
|
|
89
|
+
userSchema.methods.isLocked = function() {
|
|
90
|
+
return !!(this.lockUntil && this.lockUntil > Date.now());
|
|
53
91
|
};
|
|
54
92
|
|
|
55
|
-
//
|
|
56
|
-
userSchema.
|
|
57
|
-
|
|
93
|
+
// Method to increment login attempts
|
|
94
|
+
userSchema.methods.incLoginAttempts = async function() {
|
|
95
|
+
if (this.lockUntil && this.lockUntil < Date.now()) {
|
|
96
|
+
return await this.updateOne({
|
|
97
|
+
$set: { loginAttempts: 1 },
|
|
98
|
+
$unset: { lockUntil: 1 }
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const updates = { $inc: { loginAttempts: 1 } };
|
|
103
|
+
const maxAttempts = 5;
|
|
104
|
+
const lockTime = 2 * 60 * 60 * 1000; // 2 hours
|
|
105
|
+
|
|
106
|
+
if (this.loginAttempts + 1 >= maxAttempts && !this.isLocked()) {
|
|
107
|
+
updates.$set = { lockUntil: Date.now() + lockTime };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return await this.updateOne(updates);
|
|
58
111
|
};
|
|
59
112
|
|
|
60
|
-
|
|
61
|
-
|
|
113
|
+
// Method to reset login attempts
|
|
114
|
+
userSchema.methods.resetLoginAttempts = async function() {
|
|
115
|
+
return await this.updateOne({
|
|
116
|
+
$set: { loginAttempts: 0 },
|
|
117
|
+
$unset: { lockUntil: 1 }
|
|
118
|
+
});
|
|
62
119
|
};
|
|
63
120
|
|
|
64
|
-
//
|
|
65
|
-
userSchema.
|
|
66
|
-
const
|
|
67
|
-
|
|
121
|
+
// Remove sensitive data from JSON output
|
|
122
|
+
userSchema.methods.toJSON = function() {
|
|
123
|
+
const obj = this.toObject();
|
|
124
|
+
delete obj.password;
|
|
125
|
+
delete obj.twoFactorSecret;
|
|
126
|
+
delete obj.emailVerificationToken;
|
|
127
|
+
delete obj.emailVerificationExpires;
|
|
128
|
+
delete obj.passwordResetToken;
|
|
129
|
+
delete obj.passwordResetExpires;
|
|
130
|
+
delete obj.loginAttempts;
|
|
131
|
+
delete obj.lockUntil;
|
|
132
|
+
return obj;
|
|
68
133
|
};
|
|
69
134
|
|
|
70
|
-
module.exports = mongoose.model(
|
|
135
|
+
module.exports = mongoose.model('User', userSchema);
|
package/package.json
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const Address = require('../controllers/address');
|
|
2
|
+
|
|
3
|
+
module.exports = ( fear ) => {
|
|
4
|
+
const router = fear.createRouter();
|
|
5
|
+
const handler = fear.getHandler();
|
|
6
|
+
|
|
7
|
+
router.get("/all", handler.async(Address.all));
|
|
8
|
+
router.post("/new", handler.async(Address.create));
|
|
9
|
+
router.route("/:id")
|
|
10
|
+
.get(handler.async(Address.read))
|
|
11
|
+
.put(handler.async(Address.update))
|
|
12
|
+
.delete(handler.async(Address.delete));
|
|
13
|
+
|
|
14
|
+
return router;
|
|
15
|
+
}
|
package/routes/order.js
CHANGED
|
@@ -5,8 +5,10 @@ module.exports = ( fear ) => {
|
|
|
5
5
|
const handler = fear.getHandler();
|
|
6
6
|
const validator = fear.getValidator();
|
|
7
7
|
|
|
8
|
-
router.get("/all", Order.list);
|
|
9
|
-
router.post("/new", Order.create);
|
|
8
|
+
router.get("/all", handler.async(Order.list));
|
|
9
|
+
router.post("/new", handler.async(Order.create));
|
|
10
|
+
router.post("/create", handler.async(Order.create));
|
|
11
|
+
|
|
10
12
|
router.route('/:id')
|
|
11
13
|
.get(Order.read)
|
|
12
14
|
.put(Order.update);
|
package/routes/payment.js
CHANGED
|
@@ -5,9 +5,9 @@ module.exports = (fear) => {
|
|
|
5
5
|
const handler = fear.getHandler();
|
|
6
6
|
const validator = fear.getValidator();
|
|
7
7
|
|
|
8
|
-
router.get("/all", Payment.list);
|
|
9
|
-
router.post("/new", Payment.create);
|
|
10
|
-
router.post("
|
|
8
|
+
router.get("/all", handler.async(Payment.list));
|
|
9
|
+
router.post("/new", handler.async(Payment.create));
|
|
10
|
+
router.post("/create", handler.async(Payment.create));
|
|
11
11
|
|
|
12
12
|
router.route('/:id')
|
|
13
13
|
.get(Payment.read)
|