@feardread/fear 2.0.2 → 2.0.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.
@@ -2,45 +2,32 @@ const { tryCatch } = require("../libs/handler/error");
2
2
  const methods = require("./crud");
3
3
  const Review = require("../models/review");
4
4
  const Product = require("../models/product");
5
+
5
6
  /**
6
7
  * Add or update a product review
7
8
  * @param {Object} req - Express request object
8
9
  * @param {Object} res - Express response object
9
10
  */
10
- exports.review = tryCatch(async (req, res) => {
11
- const { productId, username, email, rating, comment } = req.body;
12
- //const productId = req.params.id;
13
- //const userId = req.user._id;
14
-
11
+ exports.add = async (req, res) => {
12
+ const { username, email, rating, comment } = req.body;
13
+ const productId = req.params.id || req.body.productId;
14
+
15
15
  // Validate required fields
16
16
  if (!rating || rating < 1 || rating > 5) {
17
- return res.status(400).json({
18
- success: false,
19
- message: "Rating must be between 1 and 5"
20
- });
17
+ return Promise.resolve(
18
+ res.status(400).json({
19
+ success: false,
20
+ message: "Rating must be between 1 and 5"
21
+ }));
21
22
  }
22
-
23
23
  if (!comment || comment.trim().length === 0) {
24
- return res.status(400).json({
25
- success: false,
26
- message: "Comment is required"
27
- });
24
+ return Promise.resolve(
25
+ res.status(400).json({
26
+ success: false,
27
+ message: "Comment is required"
28
+ }));
28
29
  }
29
-
30
- // Find product and check if it exists
31
- const product = await Product.findById(productId);
32
- if (!product) {
33
- return res.status(404).json({
34
- success: false,
35
- message: "Product not found"
36
- });
37
- }
38
-
39
- // Check if user already reviewed this product
40
- const existingReviewIndex = product.reviews.findIndex(
41
- (review) => review.user.toString() === userId.toString()
42
- );
43
-
30
+
44
31
  const reviewData = {
45
32
  productId: productId,
46
33
  username,
@@ -50,57 +37,50 @@ exports.review = tryCatch(async (req, res) => {
50
37
  createdAt: new Date()
51
38
  };
52
39
 
53
- if (existingReviewIndex !== -1) {
54
- // Update existing review
55
- product.reviews[existingReviewIndex] = {
56
- ...product.reviews[existingReviewIndex],
57
- ...reviewData,
58
- updatedAt: new Date()
59
- };
60
- } else {
61
- // Add new review
62
- product.reviews.push(reviewData);
63
- }
64
-
65
- // Recalculate average rating
66
- const totalRating = product.reviews.reduce((sum, review) => sum + review.rating, 0);
67
- product.rating = Number((totalRating / product.reviews.length).toFixed(1));
68
- product.totalReviews = product.reviews.length;
69
-
70
- // Save product
71
- const updatedProduct = await product.save();
72
-
73
- // Also save to Review collection if it exists
74
- try {
75
- if (existingReviewIndex !== -1) {
76
- // Update existing review in Review collection
77
- await Review.findOneAndUpdate(
78
- { username, productId },
79
- reviewData,
80
- { upsert: true, new: true }
81
- );
82
- } else {
83
- // Create new review in Review collection
84
- await Review.create(reviewData);
85
- }
86
- } catch (reviewError) {
87
- console.warn('Failed to sync review to Review collection:', reviewError.message);
88
- }
89
-
90
- return res.status(200).json({
91
- success: true,
92
- message: existingReviewIndex !== -1 ? "Review updated successfully" : "Review added successfully",
93
- result: updatedProduct,
94
- review: reviewData
95
- });
96
- });
40
+ // Find product and check if it exists
41
+ return Product.findById(productId)
42
+ .then(product => {
43
+ if (!product) {
44
+ res.status(400).json({
45
+ success: false,
46
+ message: "Product not found"
47
+ });
48
+ return Promise.reject(new Error("Product not found"));
49
+ }
50
+
51
+ product.reviews.push(reviewData);
52
+
53
+ // Recalculate average rating
54
+ const totalRating = product.reviews.reduce((sum, review) => sum + review.rating, 0);
55
+ product.rating = Number((totalRating / product.reviews.length).toFixed(1));
56
+ product.totalReviews = product.reviews.length;
57
+
58
+ return product.save();
59
+ })
60
+ .then(() => {
61
+
62
+ return Review.create(reviewData);
63
+ })
64
+ .then(review => {
65
+ return res.status(200).json({
66
+ success: true,
67
+ message: "Review added successfully",
68
+ result: review,
69
+ });
70
+ //return Promise.resolve(review);
71
+ })
72
+ .catch(error => {
73
+ console.error('Error in review process:', error);
74
+ return Promise.reject(error);
75
+ });
76
+ };
97
77
 
98
78
  /**
99
79
  * Add or update product rating (alternative rating system)
100
80
  * @param {Object} req - Express request object
101
81
  * @param {Object} res - Express response object
102
82
  */
103
- exports.rating = tryCatch(async (req, res) => {
83
+ exports.rating = tryCatch((req, res) => {
104
84
  const { _id: userId } = req.user;
105
85
  const { star, prodId, comment = "" } = req.body;
106
86
 
@@ -120,64 +100,79 @@ exports.rating = tryCatch(async (req, res) => {
120
100
  }
121
101
 
122
102
  // Find product
123
- const product = await Product.findById(prodId);
124
- if (!product) {
125
- return res.status(404).json({
126
- success: false,
127
- message: "Product not found"
128
- });
129
- }
130
-
131
- // Initialize ratings array if it doesn't exist
132
- if (!product.ratings) {
133
- product.ratings = [];
134
- }
135
-
136
- // Check if user already rated this product
137
- const existingRatingIndex = product.ratings.findIndex(
138
- (rating) => rating.postedby.toString() === userId.toString()
139
- );
140
-
141
- const ratingData = {
142
- star: Number(star),
143
- comment: comment.trim(),
144
- postedby: userId,
145
- createdAt: new Date()
146
- };
147
-
148
- if (existingRatingIndex !== -1) {
149
- // Update existing rating
150
- product.ratings[existingRatingIndex] = {
151
- ...product.ratings[existingRatingIndex],
152
- ...ratingData,
153
- updatedAt: new Date()
154
- };
155
- } else {
156
- // Add new rating
157
- product.ratings.push(ratingData);
158
- }
103
+ Product.findById(prodId)
104
+ .then(product => {
105
+ if (!product) {
106
+ return res.status(404).json({
107
+ success: false,
108
+ message: "Product not found"
109
+ });
110
+ }
111
+
112
+ // Initialize ratings array if it doesn't exist
113
+ if (!product.ratings) {
114
+ product.ratings = [];
115
+ }
116
+
117
+ // Check if user already rated this product
118
+ const existingRatingIndex = product.ratings.findIndex(
119
+ (rating) => rating.postedby.toString() === userId.toString()
120
+ );
159
121
 
160
- // Calculate average rating
161
- const totalRatingSum = product.ratings.reduce((sum, rating) => sum + rating.star, 0);
162
- const averageRating = totalRatingSum / product.ratings.length;
163
-
164
- // Update product with new rating
165
- product.totalrating = Number(averageRating.toFixed(1));
166
- product.totalRatings = product.ratings.length;
167
-
168
- // Save updated product
169
- const updatedProduct = await product.save();
170
-
171
- return res.status(200).json({
172
- success: true,
173
- message: existingRatingIndex !== -1 ? "Rating updated successfully" : "Rating added successfully",
174
- result: updatedProduct,
175
- rating: {
176
- averageRating: product.totalrating,
177
- totalRatings: product.totalRatings,
178
- userRating: ratingData
179
- }
180
- });
122
+ const ratingData = {
123
+ star: Number(star),
124
+ comment: comment.trim(),
125
+ postedby: userId,
126
+ createdAt: new Date()
127
+ };
128
+
129
+ if (existingRatingIndex !== -1) {
130
+ // Update existing rating
131
+ product.ratings[existingRatingIndex] = {
132
+ ...product.ratings[existingRatingIndex],
133
+ ...ratingData,
134
+ updatedAt: new Date()
135
+ };
136
+ } else {
137
+ // Add new rating
138
+ product.ratings.push(ratingData);
139
+ }
140
+
141
+ // Calculate average rating
142
+ const totalRatingSum = product.ratings.reduce((sum, rating) => sum + rating.star, 0);
143
+ const averageRating = totalRatingSum / product.ratings.length;
144
+
145
+ // Update product with new rating
146
+ product.totalrating = Number(averageRating.toFixed(1));
147
+ product.totalRatings = product.ratings.length;
148
+
149
+ // Save updated product and return the promise
150
+ return product.save().then(updatedProduct => ({
151
+ updatedProduct,
152
+ existingRatingIndex,
153
+ ratingData
154
+ }));
155
+ })
156
+ .then(({ updatedProduct, existingRatingIndex, ratingData }) => {
157
+ return res.status(200).json({
158
+ success: true,
159
+ message: existingRatingIndex !== -1 ? "Rating updated successfully" : "Rating added successfully",
160
+ result: updatedProduct,
161
+ rating: {
162
+ averageRating: updatedProduct.totalrating,
163
+ totalRatings: updatedProduct.totalRatings,
164
+ userRating: ratingData
165
+ }
166
+ });
167
+ })
168
+ .catch(error => {
169
+ console.error('Error in rating process:', error);
170
+ return res.status(500).json({
171
+ success: false,
172
+ message: "Failed to process rating",
173
+ error: error.message
174
+ });
175
+ });
181
176
  });
182
177
 
183
178
  /**
@@ -185,19 +180,10 @@ exports.rating = tryCatch(async (req, res) => {
185
180
  * @param {Object} req - Express request object
186
181
  * @param {Object} res - Express response object
187
182
  */
188
- exports.getProductReviews = tryCatch(async (req, res) => {
189
- const productId = req.params.id;
183
+ exports.getProductReviews = tryCatch((req, res) => {
184
+ const productId = req.params.id || req.body.productId;
190
185
  const { page = 1, limit = 10, sortBy = 'newest' } = req.query;
191
186
 
192
- // Validate product exists
193
- const product = await Product.findById(productId);
194
- if (!product) {
195
- return res.status(404).json({
196
- success: false,
197
- message: "Product not found"
198
- });
199
- }
200
-
201
187
  let sortCriteria;
202
188
  switch (sortBy) {
203
189
  case 'oldest':
@@ -215,28 +201,39 @@ exports.getProductReviews = tryCatch(async (req, res) => {
215
201
  break;
216
202
  }
217
203
 
218
- const reviews = await Review.find({ product: productId })
219
- .populate('user', 'name email avatar')
220
- .sort(sortCriteria)
221
- .limit(Number(limit))
222
- .skip((Number(page) - 1) * Number(limit));
223
-
224
- const totalReviews = await Review.countDocuments({ product: productId });
225
- const totalPages = Math.ceil(totalReviews / Number(limit));
226
-
227
- return res.status(200).json({
228
- success: true,
229
- message: "Product reviews retrieved successfully",
230
- result: reviews,
231
- pagination: {
232
- currentPage: Number(page),
233
- totalPages,
234
- totalReviews,
235
- reviewsPerPage: Number(limit),
236
- hasNextPage: Number(page) < totalPages,
237
- hasPrevPage: Number(page) > 1
238
- }
239
- });
204
+ // Validate product exists
205
+ return Product.findById(productId)
206
+ .then(product => {
207
+ if (!product) {
208
+ return res.status(404).json({
209
+ success: false,
210
+ message: "Product not found"
211
+ });
212
+ }
213
+
214
+ const totalReviews = product.reviews.length;
215
+ const totalPages = Math.ceil(totalReviews / Number(limit));
216
+
217
+ return res.status(200).json({
218
+ success: true,
219
+ message: "Product reviews retrieved successfully",
220
+ result: product.reviews,
221
+ pagination: {
222
+ currentPage: Number(page),
223
+ totalPages,
224
+ totalReviews,
225
+ limit: Number(limit)
226
+ }
227
+ });
228
+ })
229
+ .catch(error => {
230
+ console.error('Error fetching reviews:', error);
231
+ return res.status(500).json({
232
+ success: false,
233
+ message: "Failed to fetch reviews",
234
+ error: error.message
235
+ });
236
+ });
240
237
  });
241
238
 
242
239
  // Extend with CRUD methods
@@ -245,4 +242,4 @@ for (const prop in crud) {
245
242
  if (crud.hasOwnProperty(prop)) {
246
243
  module.exports[prop] = crud[prop];
247
244
  }
248
- }
245
+ }
@@ -21,7 +21,8 @@ module.exports = function (fear) {
21
21
  const { apiKey, domain, region } = _this.mailConfig.mailgun;
22
22
 
23
23
  if (!apiKey || !domain) {
24
- throw new Error('Mailgun requires apiKey and domain in configuration.');
24
+ logger.error('Mailgun requires apiKey and domain in configuration.')
25
+ return;
25
26
  }
26
27
 
27
28
  const mailgun = new Mailgun(_this.mailConfig.mailgun);
package/models/review.js CHANGED
@@ -3,12 +3,13 @@ const mongoose = require("mongoose");
3
3
  const reviewSchema = new mongoose.Schema({
4
4
  productId: { type: mongoose.Schema.Types.ObjectId, ref:"Product", required:true },
5
5
  username: { type: String, required: true },
6
- email: { type: String, required: false, unique: true, lowercase: true, trim: true,
6
+ email: { type: String, required: false, lowercase: true, trim: true,
7
7
  match: [/^\S+@\S+\.\S+$/, 'Please enter a valid email']
8
8
  },
9
- rating: { type:Number, required:true, min:1, max:5 },
10
- comment: { type:String, required:true },
11
- createdAt: { type:Date, default:Date.now }
9
+ rating: { type: Number, required:true, min:1, max:5 },
10
+ comment: { type: String, required:true },
11
+ createdAt: { type: Date, default:Date.now },
12
+ verified: { type: Boolean, default: false, index: true }
12
13
  },
13
14
  { timestamps: true, versionKey:false}
14
15
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feardread/fear",
3
- "version": "2.0.2",
3
+ "version": "2.0.5",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/routes/review.js CHANGED
@@ -2,10 +2,14 @@ const Review = require("../controllers/review");
2
2
 
3
3
  module.exports = (fear) => {
4
4
  const router = fear.createRouter();
5
-
6
- router.post("/new", Review.review)
7
- .get("/rating", Review.rating)
8
- .get("/by-product/:id", Review.getProductReviews);
9
-
5
+ const handler = fear.getHandler();
6
+
7
+ router.get("/all", handler.async(Review.all));
8
+ router.post("/new", handler.async(Review.add));
9
+ router.route("/:id")
10
+ .put(Review.update)
11
+ .delete(Review.delete)
12
+ .get(Review.getProductReviews);
13
+
10
14
  return router;
11
15
  };