@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/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
- title: { type: String, required: true, unique: true, index: true },
5
- logo: { type: Object, required: false, default:
6
- { public_id: '', secure_url: ''}},
7
- isActive: { type: Boolean, default: true }
8
- },
9
- { timestamps: true }
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
- module.exports = mongoose.model("Brand", brandSchema);
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);