@feardread/fear 1.0.1
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 +459 -0
- package/FEARServer.js +280 -0
- package/controllers/agent.js +438 -0
- package/controllers/auth/index.js +345 -0
- package/controllers/auth/token.js +50 -0
- package/controllers/blog.js +105 -0
- package/controllers/brand.js +10 -0
- package/controllers/cart.js +425 -0
- package/controllers/category.js +9 -0
- package/controllers/coupon.js +63 -0
- package/controllers/crud/crud.js +508 -0
- package/controllers/crud/index.js +36 -0
- package/controllers/email.js +34 -0
- package/controllers/enquiry.js +65 -0
- package/controllers/events.js +9 -0
- package/controllers/order.js +125 -0
- package/controllers/payment.js +31 -0
- package/controllers/product.js +147 -0
- package/controllers/review.js +247 -0
- package/controllers/tag.js +10 -0
- package/controllers/task.js +10 -0
- package/controllers/upload.js +41 -0
- package/controllers/user.js +401 -0
- package/index.js +7 -0
- package/libs/agent/index.js +561 -0
- package/libs/agent/modules/ai/ai.js +285 -0
- package/libs/agent/modules/ai/chat.js +518 -0
- package/libs/agent/modules/ai/config.js +688 -0
- package/libs/agent/modules/ai/operations.js +787 -0
- package/libs/agent/modules/analyze/api.js +546 -0
- package/libs/agent/modules/analyze/dorks.js +395 -0
- package/libs/agent/modules/ccard/README.md +454 -0
- package/libs/agent/modules/ccard/audit.js +479 -0
- package/libs/agent/modules/ccard/checker.js +674 -0
- package/libs/agent/modules/ccard/payment-processors.json +16 -0
- package/libs/agent/modules/ccard/validator.js +629 -0
- package/libs/agent/modules/code/analyzer.js +303 -0
- package/libs/agent/modules/code/jquery.js +1093 -0
- package/libs/agent/modules/code/react.js +1536 -0
- package/libs/agent/modules/code/refactor.js +499 -0
- package/libs/agent/modules/crypto/exchange.js +564 -0
- package/libs/agent/modules/net/proxy.js +409 -0
- package/libs/agent/modules/security/cve.js +442 -0
- package/libs/agent/modules/security/monitor.js +360 -0
- package/libs/agent/modules/security/scanner.js +300 -0
- package/libs/agent/modules/security/vulnerability.js +506 -0
- package/libs/agent/modules/security/web.js +465 -0
- package/libs/agent/modules/utils/browser.js +492 -0
- package/libs/agent/modules/utils/colorizer.js +285 -0
- package/libs/agent/modules/utils/manager.js +478 -0
- package/libs/cloud/index.js +228 -0
- package/libs/config/db.js +21 -0
- package/libs/config/validator.js +82 -0
- package/libs/db/index.js +318 -0
- package/libs/emailer/imap.js +126 -0
- package/libs/emailer/info.js +41 -0
- package/libs/emailer/smtp.js +77 -0
- package/libs/handler/async.js +3 -0
- package/libs/handler/error.js +66 -0
- package/libs/handler/index.js +161 -0
- package/libs/logger/index.js +49 -0
- package/libs/logger/morgan.js +24 -0
- package/libs/passport/passport.js +109 -0
- package/libs/search/api.js +384 -0
- package/libs/search/features.js +219 -0
- package/libs/search/service.js +64 -0
- package/libs/swagger/config.js +18 -0
- package/libs/swagger/index.js +35 -0
- package/libs/validator/index.js +254 -0
- package/models/blog.js +31 -0
- package/models/brand.js +12 -0
- package/models/cart.js +14 -0
- package/models/category.js +11 -0
- package/models/coupon.js +9 -0
- package/models/customer.js +0 -0
- package/models/enquiry.js +29 -0
- package/models/events.js +13 -0
- package/models/order.js +94 -0
- package/models/product.js +32 -0
- package/models/review.js +14 -0
- package/models/tag.js +10 -0
- package/models/task.js +11 -0
- package/models/user.js +68 -0
- package/package.json +12 -0
- package/routes/agent.js +615 -0
- package/routes/auth.js +13 -0
- package/routes/blog.js +19 -0
- package/routes/brand.js +15 -0
- package/routes/cart.js +105 -0
- package/routes/category.js +16 -0
- package/routes/coupon.js +15 -0
- package/routes/enquiry.js +14 -0
- package/routes/events.js +16 -0
- package/routes/mail.js +170 -0
- package/routes/order.js +19 -0
- package/routes/product.js +22 -0
- package/routes/review.js +11 -0
- package/routes/task.js +12 -0
- package/routes/user.js +17 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const Order = require("../models/order");
|
|
2
|
+
const methods = require("./crud");
|
|
3
|
+
|
|
4
|
+
exports.getMyOrders = async (req, res) => {
|
|
5
|
+
const { _id } = req.user;
|
|
6
|
+
try {
|
|
7
|
+
const orders = await Order.find({ user: _id })
|
|
8
|
+
.populate("user")
|
|
9
|
+
.populate("orderItems.product")
|
|
10
|
+
.populate("orderItems.color");
|
|
11
|
+
res.json({
|
|
12
|
+
orders,
|
|
13
|
+
});
|
|
14
|
+
} catch (error) {
|
|
15
|
+
throw new Error(error);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
exports.updateOrderStatus = async (req, res) => {
|
|
20
|
+
const { id } = req.params;
|
|
21
|
+
try {
|
|
22
|
+
const orders = await Order.findById(id);
|
|
23
|
+
orders.orderStatus = req.body.status;
|
|
24
|
+
await orders.save();
|
|
25
|
+
res.json({
|
|
26
|
+
orders,
|
|
27
|
+
});
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new Error(error);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
exports.getMonthWiseOrderIncome = async (req, res) => {
|
|
34
|
+
let monthNames = [
|
|
35
|
+
"January",
|
|
36
|
+
"February",
|
|
37
|
+
"March",
|
|
38
|
+
"April",
|
|
39
|
+
"May",
|
|
40
|
+
"June",
|
|
41
|
+
"July",
|
|
42
|
+
"August",
|
|
43
|
+
"September",
|
|
44
|
+
"October",
|
|
45
|
+
"November",
|
|
46
|
+
"December",
|
|
47
|
+
];
|
|
48
|
+
let d = new Date();
|
|
49
|
+
let endDate = "";
|
|
50
|
+
d.setDate(1);
|
|
51
|
+
for (let index = 0; index < 11; index++) {
|
|
52
|
+
d.setMonth(d.getMonth() - 1);
|
|
53
|
+
endDate = monthNames[d.getMonth()] + " " + d.getFullYear();
|
|
54
|
+
}
|
|
55
|
+
const data = await Order.aggregate([
|
|
56
|
+
{
|
|
57
|
+
$match: {
|
|
58
|
+
createdAt: {
|
|
59
|
+
$lte: new Date(),
|
|
60
|
+
$gte: new Date(endDate),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
$group: {
|
|
66
|
+
_id: {
|
|
67
|
+
month: "$month",
|
|
68
|
+
},
|
|
69
|
+
amount: { $sum: "$totalPriceAfterDiscount" },
|
|
70
|
+
count: { $sum: 1 },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
]);
|
|
74
|
+
res.json(data);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
exports.getYearlyTotalOrder = async (req, res) => {
|
|
78
|
+
let monthNames = [
|
|
79
|
+
"January",
|
|
80
|
+
"February",
|
|
81
|
+
"March",
|
|
82
|
+
"April",
|
|
83
|
+
"May",
|
|
84
|
+
"June",
|
|
85
|
+
"July",
|
|
86
|
+
"August",
|
|
87
|
+
"September",
|
|
88
|
+
"October",
|
|
89
|
+
"November",
|
|
90
|
+
"December",
|
|
91
|
+
];
|
|
92
|
+
let d = new Date();
|
|
93
|
+
let endDate = "";
|
|
94
|
+
d.setDate(1);
|
|
95
|
+
for (let index = 0; index < 11; index++) {
|
|
96
|
+
d.setMonth(d.getMonth() - 1);
|
|
97
|
+
endDate = monthNames[d.getMonth()] + " " + d.getFullYear();
|
|
98
|
+
}
|
|
99
|
+
const data = await Order.aggregate([
|
|
100
|
+
{
|
|
101
|
+
$match: {
|
|
102
|
+
createdAt: {
|
|
103
|
+
$lte: new Date(),
|
|
104
|
+
$gte: new Date(endDate),
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
$group: {
|
|
110
|
+
_id: null,
|
|
111
|
+
amount: { $sum: 1 },
|
|
112
|
+
amount: { $sum: "$totalPriceAfterDiscount" },
|
|
113
|
+
count: { $sum: 1 },
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
]);
|
|
117
|
+
res.json(data);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const crud = methods.crudController( Order );
|
|
121
|
+
for(prop in crud) {
|
|
122
|
+
if(crud.hasOwnProperty(prop)) {
|
|
123
|
+
module.exports[prop] = crud[prop];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const Razorpay = require("razorpay");
|
|
2
|
+
const instance = new Razorpay({
|
|
3
|
+
key_id: "rzp_test_HSSeDI22muUrLR",
|
|
4
|
+
key_secret: "sRO0YkBxvgMg0PvWHJN16Uf7",
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
const checkout = async (req, res) => {
|
|
8
|
+
const { amount } = req.body;
|
|
9
|
+
const option = {
|
|
10
|
+
amount: amount * 100,
|
|
11
|
+
currency: "INR",
|
|
12
|
+
};
|
|
13
|
+
const order = await instance.orders.create(option);
|
|
14
|
+
res.json({
|
|
15
|
+
success: true,
|
|
16
|
+
order,
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const paymentVerification = async (req, res) => {
|
|
21
|
+
const { razorpayOrderId, razorpayPaymentId } = req.body;
|
|
22
|
+
res.json({
|
|
23
|
+
razorpayOrderId,
|
|
24
|
+
razorpayPaymentId,
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
checkout,
|
|
30
|
+
paymentVerification,
|
|
31
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const { tryCatch } = require("../libs/handler/error");
|
|
2
|
+
const { productSearch } = require("../libs/search/features");
|
|
3
|
+
const Product = require("../models/product");
|
|
4
|
+
const methods = require("./crud");
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get trending products (latest products with high ratings)
|
|
9
|
+
* @param {Object} req - Express request object
|
|
10
|
+
* @param {Object} res - Express response object
|
|
11
|
+
*/
|
|
12
|
+
exports.trending = tryCatch(async (req, res) => {
|
|
13
|
+
const { limit = 8, sortBy = 'latest' } = req.query;
|
|
14
|
+
|
|
15
|
+
let sortCriteria;
|
|
16
|
+
switch (sortBy) {
|
|
17
|
+
case 'rating':
|
|
18
|
+
sortCriteria = { rating: -1, createdAt: -1 };
|
|
19
|
+
break;
|
|
20
|
+
case 'popular':
|
|
21
|
+
sortCriteria = { totalReviews: -1, rating: -1 };
|
|
22
|
+
break;
|
|
23
|
+
case 'latest':
|
|
24
|
+
default:
|
|
25
|
+
sortCriteria = { createdAt: -1 };
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Build aggregation pipeline for better performance
|
|
30
|
+
const trendingProducts = await Product.aggregate([
|
|
31
|
+
{
|
|
32
|
+
$match: {
|
|
33
|
+
isActive: { $ne: false }, // Only active products
|
|
34
|
+
stock: { $gt: 0 } // Only in-stock products
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
$addFields: {
|
|
39
|
+
// Calculate trend score based on rating and recency
|
|
40
|
+
trendScore: {
|
|
41
|
+
$multiply: [
|
|
42
|
+
{ $ifNull: ["$rating", 0] },
|
|
43
|
+
{ $ifNull: ["$totalReviews", 0] }
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{ $sort: sortCriteria },
|
|
49
|
+
{ $limit: Number(limit) },
|
|
50
|
+
{
|
|
51
|
+
$project: {
|
|
52
|
+
name: 1,
|
|
53
|
+
price: 1,
|
|
54
|
+
discountPrice: 1,
|
|
55
|
+
rating: 1,
|
|
56
|
+
totalReviews: 1,
|
|
57
|
+
totalrating: 1,
|
|
58
|
+
totalRatings: 1,
|
|
59
|
+
images: 1,
|
|
60
|
+
category: 1,
|
|
61
|
+
brand: 1,
|
|
62
|
+
stock: 1,
|
|
63
|
+
createdAt: 1,
|
|
64
|
+
trendScore: 1
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
if (!trendingProducts || trendingProducts.length === 0) {
|
|
70
|
+
return res.status(200).json({
|
|
71
|
+
success: true,
|
|
72
|
+
message: "No trending products found",
|
|
73
|
+
result: [],
|
|
74
|
+
count: 0
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return res.status(200).json({
|
|
79
|
+
success: true,
|
|
80
|
+
message: `Top ${trendingProducts.length} trending products`,
|
|
81
|
+
result: trendingProducts,
|
|
82
|
+
count: trendingProducts.length,
|
|
83
|
+
sortBy
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
*
|
|
89
|
+
* Get product statistics
|
|
90
|
+
* @param {Object} req - Express request object
|
|
91
|
+
* @param {Object} res - Express response object
|
|
92
|
+
*/
|
|
93
|
+
exports.getProductStats = tryCatch(async (req, res) => {
|
|
94
|
+
const productId = req.params.id;
|
|
95
|
+
|
|
96
|
+
const product = await Product.findById(productId);
|
|
97
|
+
if (!product) {
|
|
98
|
+
return res.status(404).json({
|
|
99
|
+
success: false,
|
|
100
|
+
message: "Product not found"
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Calculate rating distribution
|
|
105
|
+
const ratingDistribution = {};
|
|
106
|
+
[1, 2, 3, 4, 5].forEach(star => {
|
|
107
|
+
ratingDistribution[`${star}star`] = product.reviews.filter(
|
|
108
|
+
review => review.rating === star
|
|
109
|
+
).length;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const stats = {
|
|
113
|
+
productId: product._id,
|
|
114
|
+
name: product.name,
|
|
115
|
+
averageRating: product.rating || 0,
|
|
116
|
+
totalReviews: product.reviews.length,
|
|
117
|
+
totalRatings: product.ratings ? product.ratings.length : 0,
|
|
118
|
+
ratingDistribution,
|
|
119
|
+
recentReviews: product.reviews
|
|
120
|
+
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
|
121
|
+
.slice(0, 5)
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return res.status(200).json({
|
|
125
|
+
success: true,
|
|
126
|
+
message: "Product statistics retrieved successfully",
|
|
127
|
+
result: stats
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
exports.productSearch = tryCatch(async (req, res) => {
|
|
132
|
+
return await productSearch(req.query, Product)
|
|
133
|
+
.then((result) => {
|
|
134
|
+
return res.status(200).json({ success: true, result });
|
|
135
|
+
})
|
|
136
|
+
.catch((error) => {
|
|
137
|
+
return res.status(500).json({ success: false, error });
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Extend with CRUD methods
|
|
142
|
+
const crud = methods.crudController(Product);
|
|
143
|
+
for (const prop in crud) {
|
|
144
|
+
if (crud.hasOwnProperty(prop)) {
|
|
145
|
+
module.exports[prop] = crud[prop];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
const { tryCatch } = require("../libs/handler/error");
|
|
2
|
+
const methods = require("./crud");
|
|
3
|
+
const Review = require("../models/review");
|
|
4
|
+
const Product = require("../models/product");
|
|
5
|
+
/**
|
|
6
|
+
* Add or update a product review
|
|
7
|
+
* @param {Object} req - Express request object
|
|
8
|
+
* @param {Object} res - Express response object
|
|
9
|
+
*/
|
|
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;
|
|
14
|
+
|
|
15
|
+
// Validate required fields
|
|
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
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!comment || comment.trim().length === 0) {
|
|
24
|
+
return res.status(400).json({
|
|
25
|
+
success: false,
|
|
26
|
+
message: "Comment is required"
|
|
27
|
+
});
|
|
28
|
+
}
|
|
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
|
+
|
|
44
|
+
const reviewData = {
|
|
45
|
+
user: userId,
|
|
46
|
+
product: productId,
|
|
47
|
+
rating: Number(rating),
|
|
48
|
+
comment: comment.trim(),
|
|
49
|
+
createdAt: new Date()
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (existingReviewIndex !== -1) {
|
|
53
|
+
// Update existing review
|
|
54
|
+
product.reviews[existingReviewIndex] = {
|
|
55
|
+
...product.reviews[existingReviewIndex],
|
|
56
|
+
...reviewData,
|
|
57
|
+
updatedAt: new Date()
|
|
58
|
+
};
|
|
59
|
+
} else {
|
|
60
|
+
// Add new review
|
|
61
|
+
product.reviews.push(reviewData);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Recalculate average rating
|
|
65
|
+
const totalRating = product.reviews.reduce((sum, review) => sum + review.rating, 0);
|
|
66
|
+
product.rating = Number((totalRating / product.reviews.length).toFixed(1));
|
|
67
|
+
product.totalReviews = product.reviews.length;
|
|
68
|
+
|
|
69
|
+
// Save product
|
|
70
|
+
const updatedProduct = await product.save();
|
|
71
|
+
|
|
72
|
+
// Also save to Review collection if it exists
|
|
73
|
+
try {
|
|
74
|
+
if (existingReviewIndex !== -1) {
|
|
75
|
+
// Update existing review in Review collection
|
|
76
|
+
await Review.findOneAndUpdate(
|
|
77
|
+
{ user: userId, product: productId },
|
|
78
|
+
reviewData,
|
|
79
|
+
{ upsert: true, new: true }
|
|
80
|
+
);
|
|
81
|
+
} else {
|
|
82
|
+
// Create new review in Review collection
|
|
83
|
+
await Review.create(reviewData);
|
|
84
|
+
}
|
|
85
|
+
} catch (reviewError) {
|
|
86
|
+
console.warn('Failed to sync review to Review collection:', reviewError.message);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return res.status(200).json({
|
|
90
|
+
success: true,
|
|
91
|
+
message: existingReviewIndex !== -1 ? "Review updated successfully" : "Review added successfully",
|
|
92
|
+
result: updatedProduct,
|
|
93
|
+
review: reviewData
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Add or update product rating (alternative rating system)
|
|
99
|
+
* @param {Object} req - Express request object
|
|
100
|
+
* @param {Object} res - Express response object
|
|
101
|
+
*/
|
|
102
|
+
exports.rating = tryCatch(async (req, res) => {
|
|
103
|
+
const { _id: userId } = req.user;
|
|
104
|
+
const { star, prodId, comment = "" } = req.body;
|
|
105
|
+
|
|
106
|
+
// Validate input
|
|
107
|
+
if (!star || star < 1 || star > 5) {
|
|
108
|
+
return res.status(400).json({
|
|
109
|
+
success: false,
|
|
110
|
+
message: "Star rating must be between 1 and 5"
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!prodId) {
|
|
115
|
+
return res.status(400).json({
|
|
116
|
+
success: false,
|
|
117
|
+
message: "Product ID is required"
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Find product
|
|
122
|
+
const product = await Product.findById(prodId);
|
|
123
|
+
if (!product) {
|
|
124
|
+
return res.status(404).json({
|
|
125
|
+
success: false,
|
|
126
|
+
message: "Product not found"
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Initialize ratings array if it doesn't exist
|
|
131
|
+
if (!product.ratings) {
|
|
132
|
+
product.ratings = [];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Check if user already rated this product
|
|
136
|
+
const existingRatingIndex = product.ratings.findIndex(
|
|
137
|
+
(rating) => rating.postedby.toString() === userId.toString()
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const ratingData = {
|
|
141
|
+
star: Number(star),
|
|
142
|
+
comment: comment.trim(),
|
|
143
|
+
postedby: userId,
|
|
144
|
+
createdAt: new Date()
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if (existingRatingIndex !== -1) {
|
|
148
|
+
// Update existing rating
|
|
149
|
+
product.ratings[existingRatingIndex] = {
|
|
150
|
+
...product.ratings[existingRatingIndex],
|
|
151
|
+
...ratingData,
|
|
152
|
+
updatedAt: new Date()
|
|
153
|
+
};
|
|
154
|
+
} else {
|
|
155
|
+
// Add new rating
|
|
156
|
+
product.ratings.push(ratingData);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Calculate average rating
|
|
160
|
+
const totalRatingSum = product.ratings.reduce((sum, rating) => sum + rating.star, 0);
|
|
161
|
+
const averageRating = totalRatingSum / product.ratings.length;
|
|
162
|
+
|
|
163
|
+
// Update product with new rating
|
|
164
|
+
product.totalrating = Number(averageRating.toFixed(1));
|
|
165
|
+
product.totalRatings = product.ratings.length;
|
|
166
|
+
|
|
167
|
+
// Save updated product
|
|
168
|
+
const updatedProduct = await product.save();
|
|
169
|
+
|
|
170
|
+
return res.status(200).json({
|
|
171
|
+
success: true,
|
|
172
|
+
message: existingRatingIndex !== -1 ? "Rating updated successfully" : "Rating added successfully",
|
|
173
|
+
result: updatedProduct,
|
|
174
|
+
rating: {
|
|
175
|
+
averageRating: product.totalrating,
|
|
176
|
+
totalRatings: product.totalRatings,
|
|
177
|
+
userRating: ratingData
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get product reviews with pagination
|
|
184
|
+
* @param {Object} req - Express request object
|
|
185
|
+
* @param {Object} res - Express response object
|
|
186
|
+
*/
|
|
187
|
+
exports.getProductReviews = tryCatch(async (req, res) => {
|
|
188
|
+
const productId = req.params.id;
|
|
189
|
+
const { page = 1, limit = 10, sortBy = 'newest' } = req.query;
|
|
190
|
+
|
|
191
|
+
// Validate product exists
|
|
192
|
+
const product = await Product.findById(productId);
|
|
193
|
+
if (!product) {
|
|
194
|
+
return res.status(404).json({
|
|
195
|
+
success: false,
|
|
196
|
+
message: "Product not found"
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let sortCriteria;
|
|
201
|
+
switch (sortBy) {
|
|
202
|
+
case 'oldest':
|
|
203
|
+
sortCriteria = { createdAt: 1 };
|
|
204
|
+
break;
|
|
205
|
+
case 'highest':
|
|
206
|
+
sortCriteria = { rating: -1, createdAt: -1 };
|
|
207
|
+
break;
|
|
208
|
+
case 'lowest':
|
|
209
|
+
sortCriteria = { rating: 1, createdAt: -1 };
|
|
210
|
+
break;
|
|
211
|
+
case 'newest':
|
|
212
|
+
default:
|
|
213
|
+
sortCriteria = { createdAt: -1 };
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const reviews = await Review.find({ product: productId })
|
|
218
|
+
.populate('user', 'name email avatar')
|
|
219
|
+
.sort(sortCriteria)
|
|
220
|
+
.limit(Number(limit))
|
|
221
|
+
.skip((Number(page) - 1) * Number(limit));
|
|
222
|
+
|
|
223
|
+
const totalReviews = await Review.countDocuments({ product: productId });
|
|
224
|
+
const totalPages = Math.ceil(totalReviews / Number(limit));
|
|
225
|
+
|
|
226
|
+
return res.status(200).json({
|
|
227
|
+
success: true,
|
|
228
|
+
message: "Product reviews retrieved successfully",
|
|
229
|
+
result: reviews,
|
|
230
|
+
pagination: {
|
|
231
|
+
currentPage: Number(page),
|
|
232
|
+
totalPages,
|
|
233
|
+
totalReviews,
|
|
234
|
+
reviewsPerPage: Number(limit),
|
|
235
|
+
hasNextPage: Number(page) < totalPages,
|
|
236
|
+
hasPrevPage: Number(page) > 1
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Extend with CRUD methods
|
|
242
|
+
const crud = methods.crudController(Review);
|
|
243
|
+
for (const prop in crud) {
|
|
244
|
+
if (crud.hasOwnProperty(prop)) {
|
|
245
|
+
module.exports[prop] = crud[prop];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const asyncHandler = require("../libs/handler/async");
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
upload,
|
|
6
|
+
remove,
|
|
7
|
+
} = require("../libs/cloud");
|
|
8
|
+
const uploadImages = asyncHandler(async (req, res) => {
|
|
9
|
+
try {
|
|
10
|
+
const uploader = (path) => upload(path, "images");
|
|
11
|
+
const urls = [];
|
|
12
|
+
const files = req.files;
|
|
13
|
+
for (const file of files) {
|
|
14
|
+
const { path } = file;
|
|
15
|
+
const newpath = await uploader(path);
|
|
16
|
+
console.log(newpath);
|
|
17
|
+
urls.push(newpath);
|
|
18
|
+
fs.unlinkSync(path);
|
|
19
|
+
}
|
|
20
|
+
const images = urls.map((file) => {
|
|
21
|
+
return file;
|
|
22
|
+
});
|
|
23
|
+
res.json(images);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw new Error(error);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
const deleteImages = asyncHandler(async (req, res) => {
|
|
29
|
+
const { id } = req.params;
|
|
30
|
+
try {
|
|
31
|
+
const deleted = remove(id, "images");
|
|
32
|
+
res.json({ message: "Deleted" });
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new Error(error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
uploadImages,
|
|
40
|
+
deleteImages,
|
|
41
|
+
};
|