@feardread/fear 1.1.4 → 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/controllers/payment.js +180 -20
- package/libs/db/index.js +5 -0
- package/libs/validator/index.js +2 -2
- package/models/address.js +37 -0
- package/models/order.js +249 -72
- package/models/payment.js +139 -0
- package/models/user.js +116 -49
- package/package.json +1 -1
- package/routes/address.js +15 -0
- package/routes/order.js +4 -2
- package/routes/payment.js +18 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const mongoose = require("mongoose");
|
|
2
|
+
const crypto = require("crypto");
|
|
3
|
+
|
|
4
|
+
const paymentMethodSchema = new mongoose.Schema(
|
|
5
|
+
{
|
|
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, },
|
|
11
|
+
cardDetails: {
|
|
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,}
|
|
18
|
+
},
|
|
19
|
+
paypalDetails: {
|
|
20
|
+
email: { type: String, },
|
|
21
|
+
payerId: { type: String, },
|
|
22
|
+
},
|
|
23
|
+
upiDetails: {
|
|
24
|
+
vpa: { type: String, },
|
|
25
|
+
},
|
|
26
|
+
billingAddress: { type: mongoose.Schema.Types.ObjectId, ref: "Address", required: true, index: true, },
|
|
27
|
+
metadata: { type: Map, of: String, },
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
timestamps: true,
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Indexes for better query performance
|
|
35
|
+
paymentMethodSchema.index({ userId: 1, isDefault: 1 });
|
|
36
|
+
paymentMethodSchema.index({ userId: 1, isActive: 1 });
|
|
37
|
+
paymentMethodSchema.index({ createdAt: -1 });
|
|
38
|
+
|
|
39
|
+
// Middleware to ensure only one default payment method per user
|
|
40
|
+
paymentMethodSchema.pre("save", async function (next) {
|
|
41
|
+
if (this.isDefault && this.isModified("isDefault")) {
|
|
42
|
+
// Remove default flag from other payment methods for this user
|
|
43
|
+
await mongoose.model("PaymentMethod").updateMany(
|
|
44
|
+
{
|
|
45
|
+
userId: this.userId,
|
|
46
|
+
_id: { $ne: this._id },
|
|
47
|
+
isDefault: true,
|
|
48
|
+
},
|
|
49
|
+
{ isDefault: false }
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
next();
|
|
53
|
+
});
|
|
54
|
+
paymentMethodSchema.statics.getDefaultPaymentMethod = async function (userId) {
|
|
55
|
+
return await this.findOne({
|
|
56
|
+
userId,
|
|
57
|
+
isDefault: true,
|
|
58
|
+
isActive: true,
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
paymentMethodSchema.statics.getUserPaymentMethods = async function (userId) {
|
|
62
|
+
return await this.find({
|
|
63
|
+
userId,
|
|
64
|
+
isActive: true,
|
|
65
|
+
}).sort({ isDefault: -1, createdAt: -1 });
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
paymentMethodSchema.methods.setAsDefault = async function () {
|
|
69
|
+
await mongoose.model("PaymentMethod").updateMany(
|
|
70
|
+
{
|
|
71
|
+
userId: this.userId,
|
|
72
|
+
_id: { $ne: this._id },
|
|
73
|
+
},
|
|
74
|
+
{ isDefault: false }
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
this.isDefault = true;
|
|
78
|
+
return await this.save();
|
|
79
|
+
};
|
|
80
|
+
paymentMethodSchema.methods.softDelete = async function () {
|
|
81
|
+
this.isActive = false;
|
|
82
|
+
if (this.isDefault) {
|
|
83
|
+
this.isDefault = false;
|
|
84
|
+
// Optionally set another method as default
|
|
85
|
+
const otherMethod = await mongoose.model("PaymentMethod").findOne({
|
|
86
|
+
userId: this.userId,
|
|
87
|
+
_id: { $ne: this._id },
|
|
88
|
+
isActive: true,
|
|
89
|
+
});
|
|
90
|
+
if (otherMethod) {
|
|
91
|
+
otherMethod.isDefault = true;
|
|
92
|
+
await otherMethod.save();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return await this.save();
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Virtual for masked card number display
|
|
99
|
+
paymentMethodSchema.virtual("displayNumber").get(function () {
|
|
100
|
+
if (this.cardDetails?.last4) {
|
|
101
|
+
return `•••• •••• •••• ${this.cardDetails.last4}`;
|
|
102
|
+
}
|
|
103
|
+
if (this.paypalDetails?.email) {
|
|
104
|
+
return this.paypalDetails.email;
|
|
105
|
+
}
|
|
106
|
+
if (this.upiDetails?.vpa) {
|
|
107
|
+
return this.upiDetails.vpa;
|
|
108
|
+
}
|
|
109
|
+
return "Payment Method";
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Virtual for expiry display
|
|
113
|
+
paymentMethodSchema.virtual("expiryDisplay").get(function () {
|
|
114
|
+
if (this.cardDetails?.expiryMonth && this.cardDetails?.expiryYear) {
|
|
115
|
+
return `${this.cardDetails.expiryMonth}/${this.cardDetails.expiryYear}`;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Virtual for checking if card is expired
|
|
121
|
+
paymentMethodSchema.virtual("isExpired").get(function () {
|
|
122
|
+
if (this.cardDetails?.expiryMonth && this.cardDetails?.expiryYear) {
|
|
123
|
+
const now = new Date();
|
|
124
|
+
const expiryDate = new Date(
|
|
125
|
+
parseInt(this.cardDetails.expiryYear),
|
|
126
|
+
parseInt(this.cardDetails.expiryMonth) - 1
|
|
127
|
+
);
|
|
128
|
+
return now > expiryDate;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Ensure virtuals are included in JSON
|
|
134
|
+
paymentMethodSchema.set("toJSON", { virtuals: true });
|
|
135
|
+
paymentMethodSchema.set("toObject", { virtuals: true });
|
|
136
|
+
|
|
137
|
+
const PaymentMethod = mongoose.model("PaymentMethod", paymentMethodSchema);
|
|
138
|
+
|
|
139
|
+
module.exports = PaymentMethod;
|
package/models/user.js
CHANGED
|
@@ -1,68 +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
|
-
|
|
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: {
|
|
13
13
|
public_id: '',
|
|
14
|
-
secure_url: ''
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 }
|
|
25
38
|
},
|
|
26
|
-
|
|
27
|
-
|
|
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
|
+
});
|
|
28
69
|
|
|
29
|
-
//
|
|
30
|
-
userSchema.pre(
|
|
31
|
-
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);
|
|
32
77
|
next();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
next(error);
|
|
33
80
|
}
|
|
34
|
-
const salt = await bcrypt.genSaltSync(10);
|
|
35
|
-
this.password = await bcrypt.hash(this.password, salt);
|
|
36
|
-
next();
|
|
37
81
|
});
|
|
38
82
|
|
|
39
|
-
//
|
|
40
|
-
userSchema.methods.
|
|
41
|
-
return await bcrypt.compare(
|
|
83
|
+
// Method to compare passwords
|
|
84
|
+
userSchema.methods.comparePassword = async function(candidatePassword) {
|
|
85
|
+
return await bcrypt.compare(candidatePassword, this.password);
|
|
42
86
|
};
|
|
43
87
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.passwordResetToken = crypto.createHash("sha256").update(resettoken).digest("hex");
|
|
48
|
-
this.passwordResetExpires = Date.now() + 30 * 60 * 1000;
|
|
49
|
-
|
|
50
|
-
return resettoken;
|
|
88
|
+
// Method to check if account is locked
|
|
89
|
+
userSchema.methods.isLocked = function() {
|
|
90
|
+
return !!(this.lockUntil && this.lockUntil > Date.now());
|
|
51
91
|
};
|
|
52
92
|
|
|
53
|
-
//
|
|
54
|
-
userSchema.
|
|
55
|
-
|
|
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);
|
|
56
111
|
};
|
|
57
112
|
|
|
58
|
-
|
|
59
|
-
|
|
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
|
+
});
|
|
60
119
|
};
|
|
61
120
|
|
|
62
|
-
//
|
|
63
|
-
userSchema.
|
|
64
|
-
const
|
|
65
|
-
|
|
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;
|
|
66
133
|
};
|
|
67
134
|
|
|
68
|
-
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);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const Payment = require('../controllers/payment');
|
|
2
|
+
|
|
3
|
+
module.exports = (fear) => {
|
|
4
|
+
const router = fear.createRouter();
|
|
5
|
+
const handler = fear.getHandler();
|
|
6
|
+
const validator = fear.getValidator();
|
|
7
|
+
|
|
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
|
+
|
|
12
|
+
router.route('/:id')
|
|
13
|
+
.get(Payment.read)
|
|
14
|
+
.put(Payment.update);
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
return router;
|
|
18
|
+
}
|