@feardread/fear 1.2.0 → 2.0.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/FEAR.js +76 -63
- package/FEARServer.js +290 -20
- package/controllers/address.js +9 -0
- package/controllers/auth/index.js +498 -92
- package/controllers/auth/password.js +237 -0
- package/controllers/order.js +0 -1
- package/controllers/payment.js +5 -142
- package/libs/db/index.js +5 -0
- package/libs/emailer/info.js +22 -34
- package/libs/emailer/smtp.js +511 -65
- package/libs/passport/index.js +137 -0
- package/libs/passport.js +22 -0
- package/libs/paypal/index.js +82 -0
- package/libs/stripe/index.js +306 -0
- package/libs/validator/index.js +2 -2
- package/models/address.js +37 -0
- package/models/blog.js +947 -17
- package/models/brand.js +205 -8
- package/models/category.js +498 -7
- package/models/events.js +1 -0
- package/models/order.js +29 -154
- package/models/payment.js +18 -79
- package/models/user.js +116 -49
- package/package.json +1 -1
- package/routes/address.js +16 -0
- package/routes/auth.js +9 -3
- package/routes/mail.js +10 -165
- package/routes/order.js +7 -4
- package/routes/password.js +17 -0
- package/routes/payment.js +4 -7
- package/routes/paypal.js +12 -0
- package/routes/stripe.js +27 -0
- package/libs/passport/passport.js +0 -109
- /package/routes/{events.js → event.js} +0 -0
package/models/brand.js
CHANGED
|
@@ -1,12 +1,209 @@
|
|
|
1
1
|
const mongoose = require("mongoose");
|
|
2
|
+
const slugify = require("slugify");
|
|
2
3
|
|
|
3
|
-
const brandSchema = new mongoose.Schema(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
const brandSchema = new mongoose.Schema(
|
|
5
|
+
{
|
|
6
|
+
name: { type: String, unique: true, trim: true, index: true,
|
|
7
|
+
required: [true, "Brand name is required"],
|
|
8
|
+
maxlength: [100, "Brand name cannot exceed 100 characters"]
|
|
9
|
+
},
|
|
10
|
+
slug: { type: String, unique: true, lowercase: true, index: true },
|
|
11
|
+
website: {type: String, unique: true, trim: true },
|
|
12
|
+
logo: { public_id: { type: String, default: "" }, url: { type: String, default: "" }, secure_url: { type: String, default: "" }},
|
|
13
|
+
isActive: { type: Boolean, default: true, index: true },
|
|
14
|
+
isFeatured: { type: Boolean, default: false, index: true },
|
|
15
|
+
status: { type: String, enum: ["active", "inactive", "pending", "archived"], default: "active", index: true },
|
|
16
|
+
categories: [{ type: mongoose.Schema.Types.ObjectId, ref: "Category" }],
|
|
17
|
+
products: [{ type: mongoose.Schema.Types.ObjectId, ref: "Product" }],
|
|
18
|
+
tags: [{ type: String, trim: true, lowercase: true }],
|
|
19
|
+
createdBy: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
|
|
20
|
+
notes: [{ content: { type: String },
|
|
21
|
+
createdBy: {
|
|
22
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
23
|
+
ref: "User"
|
|
24
|
+
},
|
|
25
|
+
createdAt: { type: Date, default: Date.now }
|
|
26
|
+
}],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
timestamps: true,
|
|
30
|
+
toJSON: { virtuals: true },
|
|
31
|
+
toObject: { virtuals: true }
|
|
32
|
+
}
|
|
10
33
|
);
|
|
11
34
|
|
|
12
|
-
|
|
35
|
+
brandSchema.index({ name: 1, isActive: 1 });
|
|
36
|
+
brandSchema.index({ featured: 1, isActive: 1 });
|
|
37
|
+
brandSchema.index({ slug: 1, isActive: 1 });
|
|
38
|
+
brandSchema.index({ status: 1, featured: 1 });
|
|
39
|
+
brandSchema.index({ tags: 1 });
|
|
40
|
+
brandSchema.index({ categories: 1 });
|
|
41
|
+
brandSchema.index({ createdAt: -1 });
|
|
42
|
+
|
|
43
|
+
// Text index for search
|
|
44
|
+
brandSchema.index({
|
|
45
|
+
name: "text",
|
|
46
|
+
title: "text",
|
|
47
|
+
tags: "text"
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Virtual for full URL
|
|
51
|
+
brandSchema.virtual("url").get(function() {
|
|
52
|
+
return `/brands/${this.slug}`;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Virtual for product count (if not using stats)
|
|
56
|
+
brandSchema.virtual("productCount", {
|
|
57
|
+
ref: "Product",
|
|
58
|
+
localField: "_id",
|
|
59
|
+
foreignField: "brand",
|
|
60
|
+
count: true
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Pre-save middleware to generate slug
|
|
64
|
+
brandSchema.pre("save", async function(next) {
|
|
65
|
+
if (this.isModified("name")) {
|
|
66
|
+
this.slug = slugify(this.name, {
|
|
67
|
+
lower: true,
|
|
68
|
+
strict: true,
|
|
69
|
+
remove: /[*+~.()'"!:@]/g
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Ensure unique slug
|
|
73
|
+
const slugRegEx = new RegExp(`^${this.slug}(-[0-9]*)?$`, "i");
|
|
74
|
+
const brandsWithSlug = await this.constructor.find({ slug: slugRegEx });
|
|
75
|
+
|
|
76
|
+
if (brandsWithSlug.length > 0) {
|
|
77
|
+
this.slug = `${this.slug}-${brandsWithSlug.length}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Sync isActive with active field
|
|
82
|
+
if (this.isModified("isActive")) {
|
|
83
|
+
this.active = this.isActive;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Set title to name if not provided
|
|
87
|
+
if (!this.title) {
|
|
88
|
+
this.title = this.name;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
next();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Pre-update middleware
|
|
95
|
+
brandSchema.pre("findOneAndUpdate", function(next) {
|
|
96
|
+
const update = this.getUpdate();
|
|
97
|
+
|
|
98
|
+
if (update.name) {
|
|
99
|
+
update.slug = slugify(update.name, {
|
|
100
|
+
lower: true,
|
|
101
|
+
strict: true,
|
|
102
|
+
remove: /[*+~.()'"!:@]/g
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (update.isActive !== undefined) {
|
|
107
|
+
update.active = update.isActive;
|
|
108
|
+
}
|
|
109
|
+
next();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Instance methods
|
|
113
|
+
brandSchema.methods = {
|
|
114
|
+
// Increment view count
|
|
115
|
+
incrementViews: function() {
|
|
116
|
+
this.stats.viewCount += 1;
|
|
117
|
+
return this.save();
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
// Increment click count
|
|
121
|
+
incrementClicks: function() {
|
|
122
|
+
this.stats.clickCount += 1;
|
|
123
|
+
return this.save();
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// Update product count
|
|
127
|
+
updateProductCount: async function() {
|
|
128
|
+
const Product = mongoose.model("Product");
|
|
129
|
+
const count = await Product.countDocuments({ brand: this._id, isActive: true });
|
|
130
|
+
this.stats.productCount = count;
|
|
131
|
+
return this.save();
|
|
132
|
+
},
|
|
133
|
+
// Soft delete
|
|
134
|
+
softDelete: function(userId) {
|
|
135
|
+
this.isDeleted = true;
|
|
136
|
+
this.deletedAt = new Date();
|
|
137
|
+
this.deletedBy = userId;
|
|
138
|
+
this.isActive = false;
|
|
139
|
+
this.active = false;
|
|
140
|
+
return this.save();
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// Restore from soft delete
|
|
144
|
+
restore: function() {
|
|
145
|
+
this.isDeleted = false;
|
|
146
|
+
this.deletedAt = null;
|
|
147
|
+
this.deletedBy = null;
|
|
148
|
+
this.isActive = true;
|
|
149
|
+
this.active = true;
|
|
150
|
+
return this.save();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Static methods
|
|
155
|
+
brandSchema.statics = {
|
|
156
|
+
// Get featured brands
|
|
157
|
+
getFeatured: function(limit = 10) {
|
|
158
|
+
return this.find({
|
|
159
|
+
featured: true,
|
|
160
|
+
isActive: true,
|
|
161
|
+
isDeleted: false
|
|
162
|
+
})
|
|
163
|
+
.sort({ priority: -1, displayOrder: 1 })
|
|
164
|
+
.limit(limit)
|
|
165
|
+
.exec();
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// Get popular brands
|
|
169
|
+
getPopular: function(limit = 10) {
|
|
170
|
+
return this.find({
|
|
171
|
+
isActive: true,
|
|
172
|
+
isDeleted: false
|
|
173
|
+
})
|
|
174
|
+
.sort({ "stats.productCount": -1, "stats.totalSales": -1 })
|
|
175
|
+
.limit(limit)
|
|
176
|
+
.exec();
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
// Search brands
|
|
180
|
+
searchBrands: function(query, options = {}) {
|
|
181
|
+
const searchQuery = {
|
|
182
|
+
$text: { $search: query },
|
|
183
|
+
isActive: true,
|
|
184
|
+
isDeleted: false
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
return this.find(searchQuery, { score: { $meta: "textScore" } })
|
|
188
|
+
.sort({ score: { $meta: "textScore" } })
|
|
189
|
+
.limit(options.limit || 20)
|
|
190
|
+
.exec();
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// Query helpers
|
|
195
|
+
brandSchema.query = {
|
|
196
|
+
active: function() {
|
|
197
|
+
return this.where({ isActive: true, isDeleted: false });
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
featured: function() {
|
|
201
|
+
return this.where({ featured: true, isActive: true, isDeleted: false });
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
byCategory: function(categoryId) {
|
|
205
|
+
return this.where({ categories: categoryId, isActive: true, isDeleted: false });
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
module.exports = mongoose.model("Brand", brandSchema);
|