@feardread/fear 2.0.1 → 2.0.4

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.
@@ -12,6 +12,41 @@ const isValidObjectId = (id) => {
12
12
  return mongoose.Types.ObjectId.isValid(id) && /^[0-9a-fA-F]{24}$/.test(id);
13
13
  };
14
14
 
15
+ /**
16
+ * Sanitizes update data by removing undefined values
17
+ * @param {Object} data - Data object to sanitize
18
+ * @returns {Object} Sanitized data object
19
+ */
20
+ const sanitizeUpdateData = (data) => {
21
+ const sanitized = { ...data };
22
+ Object.keys(sanitized).forEach(key => {
23
+ if (sanitized[key] === undefined) {
24
+ delete sanitized[key];
25
+ }
26
+ });
27
+ return sanitized;
28
+ };
29
+
30
+ /**
31
+ * Processes image data for upload
32
+ * @param {string|Array} images - Images to process
33
+ * @returns {Promise<Array>} Promise resolving to uploaded image URLs
34
+ */
35
+ const processImages = (images) => {
36
+ if (!images) return Promise.resolve(null);
37
+
38
+ const imageArray = Array.isArray(images)
39
+ ? images
40
+ : images.split(',').map(item => item.trim());
41
+
42
+ return cloud.uploadImages(imageArray)
43
+ .then(imageLinks => imageLinks || null)
44
+ .catch(error => {
45
+ console.error('Error uploading images:', error);
46
+ return null;
47
+ });
48
+ };
49
+
15
50
  /**
16
51
  * Get all documents for requested Model
17
52
  * @param {mongoose.Model} Model - Mongoose model
@@ -19,34 +54,27 @@ const isValidObjectId = (id) => {
19
54
  * @param {Object} res - Express response object
20
55
  * @returns {Promise<void>}
21
56
  */
22
- exports.all = tryCatch(async (Model, req, res) => {
57
+ exports.all = tryCatch((Model, req, res) => {
23
58
  const { sort = 'category', order = 'asc', populate = 'true' } = req.query;
24
-
25
59
  const sortOrder = order === 'desc' ? -1 : 1;
60
+
26
61
  let query = Model.find().sort({ [sort]: sortOrder });
27
62
 
28
- // Apply population if requested
29
- if (populate !== 'false') {
30
- query = query.populate();
31
- }
63
+ if (populate !== 'false') query = query.populate();
32
64
 
33
- const result = await query.exec();
65
+ return query
66
+ .exec()
67
+ .then(result => {
68
+ if (!result || result.length === 0) {
69
+ return res.status(200).json({result: [],success: true,message: "No documents found",count: 0});
70
+ }
34
71
 
35
- if (!result || result.length === 0) {
36
- return res.status(200).json({
37
- result: [],
38
- success: true,
39
- message: "No documents found",
40
- count: 0
72
+ return res.status(200).json({result,success: true,message: `Found ${result.length} documents`,count: result.length});
73
+ })
74
+ .catch(error => {
75
+ console.error('Error fetching documents:', error);
76
+ return res.status(500).json({result: null,success: false});
41
77
  });
42
- }
43
-
44
- return res.status(200).json({
45
- result,
46
- success: true,
47
- message: `Found ${result.length} documents`,
48
- count: result.length
49
- });
50
78
  });
51
79
 
52
80
  /**
@@ -56,31 +84,43 @@ exports.all = tryCatch(async (Model, req, res) => {
56
84
  * @param {Object} res - Express response object
57
85
  * @returns {Promise<void>}
58
86
  */
59
- exports.read = tryCatch(async (Model, req, res) => {
87
+ exports.read = tryCatch((Model, req, res) => {
60
88
  const { id } = req.params;
61
89
 
62
90
  if (!id || !isValidObjectId(id)) {
63
- return res.status(400).json({
64
- result: null,
65
- success: false,
66
- message: "Invalid or Missing Document ID"
67
- });
91
+ return Promise.resolve(
92
+ res.status(400).json({
93
+ result: null,
94
+ success: false,
95
+ message: "Invalid or missing document ID"
96
+ })
97
+ );
68
98
  }
69
- const result = await Model.findById(id).exec();
70
99
 
71
- if (!result) {
72
- return res.status(404).json({
73
- result: null,
74
- success: false,
75
- message: `Document with ID ${id} not found`
100
+ return Model.findById(id).exec()
101
+ .then(result => {
102
+ if (!result) {
103
+ return res.status(404).json({
104
+ result: null,
105
+ success: false,
106
+ message: `Document with ID ${id} not found`
107
+ });
108
+ }
109
+
110
+ return res.status(200).json({
111
+ result,
112
+ success: true,
113
+ message: "Document found successfully"
114
+ });
115
+ })
116
+ .catch(error => {
117
+ console.error('Error reading document:', error);
118
+ return res.status(500).json({
119
+ result: null,
120
+ success: false,
121
+ message: "Error reading document"
122
+ });
76
123
  });
77
- }
78
-
79
- return res.status(200).json({
80
- result,
81
- success: true,
82
- message: "Document found successfully"
83
- });
84
124
  });
85
125
 
86
126
  /**
@@ -90,27 +130,36 @@ exports.read = tryCatch(async (Model, req, res) => {
90
130
  * @param {Object} res - Express response object
91
131
  * @returns {Promise<void>}
92
132
  */
93
- exports.create = tryCatch(async (Model, req, res) => {
133
+ exports.create = tryCatch((Model, req, res) => {
94
134
  const documentData = { ...req.body };
95
- // Handle image uploads if present
96
- if (documentData.images) {
97
-
98
- documentData.images.split(',').map(item => item.trim());
99
-
100
- const imageLinks = await cloud.uploadImages(documentData.images);
101
-
102
- if ( imageLinks ) documentData.images = imageLinks;
103
- }
104
-
105
- console.log('Creating document:', documentData);
106
135
 
107
- const document = new Model(documentData);
108
- await document.save()
109
- .then((result) => {
110
- return res.status(201).json({ result, success: true,
111
- message: `Document created successfully in ${Model.modelName} collection`});
112
- })
113
- .catch((error) => {console.log('Error Saving document :: ', error);});
136
+ return processImages(documentData.images)
137
+ .then(imageLinks => {
138
+ if (imageLinks) {
139
+ documentData.images = imageLinks;
140
+ }
141
+
142
+ console.log('Creating document:', documentData);
143
+ const document = new Model(documentData);
144
+
145
+ return document.save();
146
+ })
147
+ .then(result => {
148
+ return res.status(201).json({
149
+ result,
150
+ success: true,
151
+ message: `Document created successfully in ${Model.modelName} collection`
152
+ });
153
+ })
154
+ .catch(error => {
155
+ console.error('Error creating document:', error);
156
+ return res.status(500).json({
157
+ result: null,
158
+ success: false,
159
+ message: "Error creating document",
160
+ error: error.message
161
+ });
162
+ });
114
163
  });
115
164
 
116
165
  /**
@@ -120,60 +169,71 @@ exports.create = tryCatch(async (Model, req, res) => {
120
169
  * @param {Object} res - Express response object
121
170
  * @returns {Promise<void>}
122
171
  */
123
- exports.update = tryCatch(async (Model, req, res) => {
172
+ exports.update = tryCatch((Model, req, res) => {
124
173
  const { id } = req.params;
125
174
 
126
175
  if (!id) {
127
- return res.status(400).json({
128
- result: null,
129
- success: false,
130
- message: "Document ID is required"
131
- });
176
+ return Promise.resolve(
177
+ res.status(400).json({
178
+ result: null,
179
+ success: false,
180
+ message: "Document ID is required"
181
+ })
182
+ );
132
183
  }
133
184
 
134
185
  if (!isValidObjectId(id)) {
135
- return res.status(400).json({
136
- result: null,
137
- success: false,
138
- message: "Invalid document ID format"
139
- });
140
- }
141
-
142
- const updateData = { ...req.body };
143
-
144
- // Remove undefined values
145
- Object.keys(updateData).forEach(key => {
146
- if (updateData[key] === undefined) {
147
- delete updateData[key];
148
- }
149
- });
150
-
151
- // Handle image uploads if present
152
- if (updateData.images && Array.isArray(updateData.images) && updateData.images.length > 0) {
153
- const uploadedImages = await cloud.uploadImages(updateData.images);
154
- updateData.images = uploadedImages;
186
+ return Promise.resolve(
187
+ res.status(400).json({
188
+ result: null,
189
+ success: false,
190
+ message: "Invalid document ID format"
191
+ })
192
+ );
155
193
  }
156
194
 
157
- // Update document
158
- const result = await Model.findByIdAndUpdate(
159
- id,
160
- updateData,
161
- { new: true, runValidators: true }
162
- ).exec();
163
-
164
- if (!result) {
165
- return res.status(404).json({
166
- result: null,
167
- success: false,
168
- message: `Document with ID ${id} not found`
195
+ const updateData = sanitizeUpdateData(req.body);
196
+
197
+ const imagePromise = (updateData.images && Array.isArray(updateData.images) && updateData.images.length > 0)
198
+ ? processImages(updateData.images)
199
+ : Promise.resolve(null);
200
+
201
+ return imagePromise
202
+ .then(uploadedImages => {
203
+ if (uploadedImages) {
204
+ updateData.images = uploadedImages;
205
+ }
206
+
207
+ return Model.findByIdAndUpdate(
208
+ id,
209
+ updateData,
210
+ { new: true, runValidators: true }
211
+ ).exec();
212
+ })
213
+ .then(result => {
214
+ if (!result) {
215
+ return res.status(404).json({
216
+ result: null,
217
+ success: false,
218
+ message: `Document with ID ${id} not found`
219
+ });
220
+ }
221
+
222
+ return res.status(200).json({
223
+ result,
224
+ success: true,
225
+ message: `Document updated successfully with ID: ${id}`
226
+ });
227
+ })
228
+ .catch(error => {
229
+ console.error('Error updating document:', error);
230
+ return res.status(500).json({
231
+ result: null,
232
+ success: false,
233
+ message: "Error updating document",
234
+ error: error.message
235
+ });
169
236
  });
170
- }
171
-
172
- return res.status(200).json({
173
- result,
174
- success: true,
175
- message: `Document updated successfully with ID: ${id}`
176
- });
177
237
  });
178
238
 
179
239
  /**
@@ -183,47 +243,60 @@ exports.update = tryCatch(async (Model, req, res) => {
183
243
  * @param {Object} res - Express response object
184
244
  * @returns {Promise<void>}
185
245
  */
186
- exports.delete = tryCatch(async (Model, req, res) => {
246
+ exports.delete = tryCatch((Model, req, res) => {
187
247
  const { id } = req.params;
188
248
 
189
249
  if (!id) {
190
- return res.status(400).json({
191
- result: null,
192
- success: false,
193
- message: "Document ID is required"
194
- });
250
+ return Promise.resolve(
251
+ res.status(400).json({
252
+ result: null,
253
+ success: false,
254
+ message: "Document ID is required"
255
+ })
256
+ );
195
257
  }
196
258
 
197
259
  if (!isValidObjectId(id)) {
198
- return res.status(400).json({
199
- result: null,
200
- success: false,
201
- message: "Invalid document ID format"
202
- });
260
+ return Promise.resolve(
261
+ res.status(400).json({
262
+ result: null,
263
+ success: false,
264
+ message: "Invalid document ID format"
265
+ })
266
+ );
203
267
  }
204
268
 
205
- const result = await Model.findByIdAndDelete(id).exec();
206
-
207
- if (!result) {
208
- return res.status(404).json({
209
- result: null,
210
- success: false,
211
- message: `Document with ID ${id} not found`
269
+ return Model.findByIdAndDelete(id)
270
+ .exec()
271
+ .then(result => {
272
+ if (!result) {
273
+ return res.status(404).json({ result: null, success: false });
274
+ }
275
+
276
+ // Clean up associated images if they exist
277
+ if (result.images && Array.isArray(result.images) && result.images.length > 0) {
278
+ console.log('Document had images that should be cleaned up:', result.images);
279
+ // Uncomment when cloud.deleteImages is available:
280
+ // return cloud.deleteImages(result.images)
281
+ // .then(() => result)
282
+ // .catch(err => {
283
+ // console.error('Error deleting images:', err);
284
+ // return result;
285
+ // });
286
+ }
287
+
288
+ return result;
289
+ })
290
+ .then(result => {
291
+ return res.status(200)
292
+ .json({ result, success: true,
293
+ message: `Document deleted successfully with ID: ${id}`
294
+ });
295
+ })
296
+ .catch(error => {
297
+ console.error('Error deleting document:', error);
298
+ return res.status(500).json({ result: error, success: false });
212
299
  });
213
- }
214
-
215
- // TODO: Clean up associated images if they exist
216
- if (result.images && Array.isArray(result.images) && result.images.length > 0) {
217
- console.log('Document had images that should be cleaned up:', result.images);
218
- // Uncomment when cloud.deleteImages is available:
219
- // await cloud.deleteImages(result.images);
220
- }
221
-
222
- return res.status(200).json({
223
- result,
224
- success: true,
225
- message: `Document deleted successfully with ID: ${id}`
226
- });
227
300
  });
228
301
 
229
302
  /**
@@ -233,7 +306,7 @@ exports.delete = tryCatch(async (Model, req, res) => {
233
306
  * @param {Object} res - Express response object
234
307
  * @returns {Promise<void>}
235
308
  */
236
- exports.list = tryCatch(async (Model, req, res) => {
309
+ exports.list = tryCatch((Model, req, res) => {
237
310
  const page = Math.max(1, parseInt(req.query.page) || 1);
238
311
  const limit = Math.min(100, Math.max(1, parseInt(req.query.items) || 10));
239
312
  const skip = (page - 1) * limit;
@@ -241,35 +314,44 @@ exports.list = tryCatch(async (Model, req, res) => {
241
314
 
242
315
  const sortOrder = order === 'asc' ? 1 : -1;
243
316
 
244
- // Execute count and find queries in parallel
245
- const [result, count] = await Promise.all([
246
- Model.find()
247
- .skip(skip)
248
- .limit(limit)
249
- .sort({ [sort]: sortOrder })
250
- .populate(populate !== 'false' ? undefined : null)
251
- .exec(),
252
- Model.countDocuments().exec()
253
- ]);
254
-
255
- const totalPages = Math.ceil(count / limit);
256
- const pagination = {
257
- currentPage: page,
258
- totalPages,
259
- totalItems: count,
260
- itemsPerPage: limit,
261
- hasNextPage: page < totalPages,
262
- hasPrevPage: page > 1
263
- };
264
-
265
- return res.status(200).json({
266
- result,
267
- success: true,
268
- pagination,
269
- message: count > 0
270
- ? `Found ${result.length} of ${count} documents`
271
- : "Collection is empty"
272
- });
317
+ const findQuery = Model.find()
318
+ .skip(skip)
319
+ .limit(limit)
320
+ .sort({ [sort]: sortOrder })
321
+ .populate(populate !== 'false' ? undefined : null)
322
+ .exec();
323
+
324
+ const countQuery = Model.countDocuments().exec();
325
+
326
+ return Promise.all([findQuery, countQuery])
327
+ .then(([result, count]) => {
328
+ const totalPages = Math.ceil(count / limit);
329
+ const pagination = {
330
+ currentPage: page,
331
+ totalPages,
332
+ totalItems: count,
333
+ itemsPerPage: limit,
334
+ hasNextPage: page < totalPages,
335
+ hasPrevPage: page > 1
336
+ };
337
+
338
+ return res.status(200).json({
339
+ result,
340
+ success: true,
341
+ pagination,
342
+ message: count > 0
343
+ ? `Found ${result.length} of ${count} documents`
344
+ : "Collection is empty"
345
+ });
346
+ })
347
+ .catch(error => {
348
+ console.error('Error listing documents:', error);
349
+ return res.status(500).json({
350
+ result: null,
351
+ success: false,
352
+ message: "Error listing documents"
353
+ });
354
+ });
273
355
  });
274
356
 
275
357
  /**
@@ -279,7 +361,7 @@ exports.list = tryCatch(async (Model, req, res) => {
279
361
  * @param {Object} res - Express response object
280
362
  * @returns {Promise<void>}
281
363
  */
282
- exports.search = tryCatch(async (Model, req, res) => {
364
+ exports.search = tryCatch((Model, req, res) => {
283
365
  console.log('CRUD search query:', req.query);
284
366
 
285
367
  const {
@@ -294,7 +376,6 @@ exports.search = tryCatch(async (Model, req, res) => {
294
376
  limit = 24
295
377
  } = req.query;
296
378
 
297
- // Build query object
298
379
  const queryObj = {
299
380
  page: parseInt(page),
300
381
  limit: parseInt(limit)
@@ -325,19 +406,28 @@ exports.search = tryCatch(async (Model, req, res) => {
325
406
  defaultSort: sortOptions[sortBy] || sortOptions.popularity
326
407
  });
327
408
 
328
- const result = await searchApi
409
+ return searchApi
329
410
  .search(['_id', 'id', 'price', 'name', 'description', 'brand', 'tags'])
330
411
  .filter()
331
412
  .sort()
332
413
  .selectFields('-__v,-updatedAt')
333
414
  .populate('reviews')
334
415
  .paginate(parseInt(limit), 50)
335
- .execute();
336
-
337
- return res.status(200).json({
338
- success: true,
339
- ...result
340
- });
416
+ .execute()
417
+ .then(result => {
418
+ return res.status(200).json({
419
+ success: true,
420
+ ...result
421
+ });
422
+ })
423
+ .catch(error => {
424
+ console.error('Error searching documents:', error);
425
+ return res.status(500).json({
426
+ success: false,
427
+ message: "Error searching documents",
428
+ error: error.message
429
+ });
430
+ });
341
431
  });
342
432
 
343
433
  /**
@@ -8,9 +8,9 @@ const Product = require("../models/product");
8
8
  * @param {Object} res - Express response object
9
9
  */
10
10
  exports.review = tryCatch(async (req, res) => {
11
- const { rating, comment } = req.body;
12
- const productId = req.params.id;
13
- const userId = req.user._id;
11
+ const { username, email, rating, comment } = req.body;
12
+ const productId = req.params.productId || req.body.productId;
13
+ //const userId = req.user._id;
14
14
 
15
15
  // Validate required fields
16
16
  if (!rating || rating < 1 || rating > 5) {
@@ -42,8 +42,9 @@ exports.review = tryCatch(async (req, res) => {
42
42
  );
43
43
 
44
44
  const reviewData = {
45
- user: userId,
46
- product: productId,
45
+ productId: productId,
46
+ username,
47
+ email,
47
48
  rating: Number(rating),
48
49
  comment: comment.trim(),
49
50
  createdAt: new Date()
@@ -74,7 +75,7 @@ exports.review = tryCatch(async (req, res) => {
74
75
  if (existingReviewIndex !== -1) {
75
76
  // Update existing review in Review collection
76
77
  await Review.findOneAndUpdate(
77
- { user: userId, product: productId },
78
+ { username, productId },
78
79
  reviewData,
79
80
  { upsert: true, new: true }
80
81
  );
package/models/review.js CHANGED
@@ -1,8 +1,11 @@
1
1
  const mongoose = require("mongoose");
2
2
 
3
3
  const reviewSchema = new mongoose.Schema({
4
- user: { type: mongoose.Schema.Types.ObjectId, ref:"User", required:true },
5
- product: { type: mongoose.Schema.Types.ObjectId, ref:"Product", required:true },
4
+ productId: { type: mongoose.Schema.Types.ObjectId, ref:"Product", required:true },
5
+ username: { type: String, required: true },
6
+ email: { type: String, required: false, unique: true, lowercase: true, trim: true,
7
+ match: [/^\S+@\S+\.\S+$/, 'Please enter a valid email']
8
+ },
6
9
  rating: { type:Number, required:true, min:1, max:5 },
7
10
  comment: { type:String, required:true },
8
11
  createdAt: { type:Date, default:Date.now }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feardread/fear",
3
- "version": "2.0.1",
3
+ "version": "2.0.4",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/routes/review.js CHANGED
@@ -5,7 +5,7 @@ module.exports = (fear) => {
5
5
 
6
6
  router.post("/new", Review.review)
7
7
  .get("/rating", Review.rating)
8
- .get("/product/:id", Review.getProductReviews);
9
-
8
+ .get("/by-product/:productId", Review.getProductReviews);
9
+ router.get("/by-product", Review.getProductReviews);
10
10
  return router;
11
11
  };