diff --git a/BACKEND/controllers/product.controller.js b/BACKEND/controllers/product.controller.js
index 187cb28..7862154 100644
--- a/BACKEND/controllers/product.controller.js
+++ b/BACKEND/controllers/product.controller.js
@@ -96,7 +96,6 @@ export const getProducts = async (req, res, next) => {
if (maxPrice) filter.price.$lte = Number(maxPrice);
}
if (brand) {
- // Case-insensitive brand search
filter.brand = { $regex: new RegExp(brand, 'i') };
}
if (minRating) {
@@ -158,6 +157,7 @@ export const createProduct = async (req, res, next) => {
}
let finalImageUrl = imageUrl || '';
+ let cloudinaryPublicId; // track uploaded image's public ID for potential cleanup
if (req.file) {
if (!cloudinaryConfigured()) {
@@ -166,7 +166,8 @@ export const createProduct = async (req, res, next) => {
try {
const result = await uploadToCloudinary(req.file.buffer);
finalImageUrl = result.secure_url;
- } catch (error) {
+ cloudinaryPublicId = result.public_id; // save public ID for cleanup if needed
+ } catch (_error) {
return next(new AppError("Image upload failed", 500));
}
}
@@ -194,7 +195,15 @@ export const createProduct = async (req, res, next) => {
await invalidateProductCache();
res.status(201).json({ success: true, data: newProduct });
} catch (error) {
- next(error);
+ // CLEANUP: delete uploaded Cloudinary image if save failed
+ if (cloudinaryPublicId) {
+ try {
+ await cloudinary.uploader.destroy(cloudinaryPublicId);
+ } catch (destroyError) {
+ console.error("Failed to delete Cloudinary image:", destroyError.message);
+ }
+ }
+ return next(error);
}
};
@@ -245,8 +254,7 @@ export const updateProduct = async (req, res, next) => {
try {
const result = await uploadToCloudinary(req.file.buffer);
updateData.image = result.secure_url;
-
- } catch (error) {
+ } catch (_error) {
return next(new AppError("Image upload failed", 500));
}
}
@@ -256,13 +264,13 @@ export const updateProduct = async (req, res, next) => {
if (!updatedProduct) {
return next(new AppError("Product not found", 404));
}
- if (req.file){
+ if (req.file) {
const oldPublicId = extractCloudinaryPublicId(existing.image);
- if (oldPublicId) {
- cloudinary.uploader.destroy(oldPublicId).catch((err) => {
- console.warn("Old image cleanup failed:", err.message);
- });
- }
+ if (oldPublicId) {
+ cloudinary.uploader.destroy(oldPublicId).catch((err) => {
+ console.warn("Old image cleanup failed:", err.message);
+ });
+ }
}
await indexProduct(updatedProduct);
@@ -305,20 +313,19 @@ export const deleteProduct = async (req, res, next) => {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id)) {
- return res.status(404).json({ success: false, message: "Invalid Product Id" });
+ return next(new AppError("Invalid Product Id format", 404));
}
try {
const product = await Product.findByIdAndUpdate(id, { isDeleted: true }, { new: true });
if (!product) {
- return res.status(404).json({ success: false, message: "Product not found" });
+ return next(new AppError("Product not found", 404));
}
await deleteProductFromIndex(id);
await invalidateProductCache();
res.status(200).json({ success: true, message: "Product deleted successfully" });
} catch (error) {
- console.log("error in deleting product:", error.message);
- res.status(500).json({ success: false, message: "Server Error" });
+ next(error);
}
};
@@ -371,7 +378,7 @@ export const getRelatedProducts = async (req, res) => {
const orConditions = [];
if (product.category) orConditions.push({ category: product.category });
if (product.brand) orConditions.push({ brand: product.brand });
- if (targetTagsSet.size > 0) orConditions.push({ tags: { $in: [ ...targetTagsSet ] } });
+ if (targetTagsSet.size > 0) orConditions.push({ tags: { $in: [...targetTagsSet] } });
const query = {
_id: { $ne: product._id },
@@ -396,7 +403,7 @@ export const getRelatedProducts = async (req, res) => {
if (c.tags && c.tags.length > 0) {
for (const tag of c.tags) {
- if (targetTags.has(tag.toLowerCase())) {
+ if (targetTagsSet.has(tag.toLowerCase())) {
score += 2;
}
}
@@ -441,11 +448,15 @@ export const getProductBundle = async (req, res) => {
.slice(0, 3);
const bundleTotal = [product, ...items.map(i => i.product)]
- .reduce((sum, p) => sum + p.price, 0);
+ .reduce((sum, p) => sum + (Number(p?.price) || 0), 0);
const bundleDiscount = 0.1;
- const bundlePrice = +(bundleTotal * (1 - bundleDiscount)).toFixed(2);
- const savings = +(bundleTotal * bundleDiscount).toFixed(2);
+ const bundlePrice = bundleTotal > 0
+ ? +(bundleTotal * (1 - bundleDiscount)).toFixed(2)
+ : 0;
+ const savings = bundleTotal > 0
+ ? +(bundleTotal * bundleDiscount).toFixed(2)
+ : 0;
res.status(200).json({
success: true,
@@ -476,13 +487,11 @@ export const searchProducts = async (req, res, next) => {
}
try {
- // Try Elasticsearch first
const esProducts = await searchProductsES(q);
if (esProducts) {
return res.status(200).json({ success: true, data: esProducts });
}
- // Fallback to MongoDB regex search
const safeQuery = escapeRegex(q);
const regex = new RegExp(safeQuery, 'i');
const products = await Product.find({ name: regex, isDeleted: { $ne: true } });
diff --git a/BACKEND/middleware/authMiddleware.js b/BACKEND/middleware/authMiddleware.js
index 2d19fdc..f878425 100644
--- a/BACKEND/middleware/authMiddleware.js
+++ b/BACKEND/middleware/authMiddleware.js
@@ -14,7 +14,6 @@ const authMiddleware = async (req, res, next) => {
const token = authHeader.split(" ")[1];
- // JWT specific errors alag handle honge
let decoded;
try {
decoded = jwt.verify(token, process.env.JWT_SECRET);
@@ -32,7 +31,6 @@ const authMiddleware = async (req, res, next) => {
});
}
- // Validate decoded payload before DB lookup
if (!decoded?.id) {
return res.status(401).json({
success: false,
@@ -40,7 +38,6 @@ const authMiddleware = async (req, res, next) => {
});
}
- // User existence verify karo DB se
const user = await User.findById(decoded.id).select("-password");
if (!user) {
@@ -59,7 +56,7 @@ const authMiddleware = async (req, res, next) => {
};
next();
- } catch (error) {
+ } catch (_error) {
return res.status(500).json({
success: false,
message: "Internal server error during authentication.",
@@ -68,4 +65,3 @@ const authMiddleware = async (req, res, next) => {
};
export default authMiddleware;
-
diff --git a/BACKEND/middleware/errorMiddleware.js b/BACKEND/middleware/errorMiddleware.js
index ad5c866..d71aa3f 100644
--- a/BACKEND/middleware/errorMiddleware.js
+++ b/BACKEND/middleware/errorMiddleware.js
@@ -1,4 +1,3 @@
-// Custom Error Class (for different error types)
export class AppError extends Error {
constructor(message, statusCode) {
super(message);
@@ -7,45 +6,42 @@ export class AppError extends Error {
}
}
-// ADD THIS - 404 Handler for unknown API routes
export const notFoundHandler = (req, res, next) => {
- // Check if it's an API route (starts with /api)
if (req.path.startsWith('/api')) {
return res.status(404).json({
success: false,
message: `API route not found: ${req.method} ${req.path}`
});
}
- // For non-API routes, pass to next middleware (like frontend)
+
next();
};
-// Global Error Handler Middleware
-export const errorHandler = (err, req, res, next) => {
- // Default values
+
+export const errorHandler = (err, req, res, _next) => {
let statusCode = err.statusCode || 500;
let message = err.message || "Internal Server Error";
- // Handle Mongoose Validation Errors (e.g., min:0, required, etc.)
+
if (err.name === 'ValidationError') {
statusCode = 400;
message = Object.values(err.errors).map(e => e.message).join(', ');
}
- // Handle Duplicate Key Error (MongoDB - when unique field repeats)
+
if (err.code === 11000) {
statusCode = 400;
const field = Object.keys(err.keyPattern)[0];
message = `Duplicate field value: ${field}. Please use another value.`;
}
- // Handle Cast Error (Invalid ObjectId format)
+
if (err.name === 'CastError') {
statusCode = 400;
message = `Invalid ${err.path}: ${err.value}`;
}
- // Send Response
+
res.status(statusCode).json({
success: false,
message: message,
diff --git a/FRONTEND/src/components/ui/ProductCard.jsx b/FRONTEND/src/components/ui/ProductCard.jsx
index 3000265..a3a5846 100644
--- a/FRONTEND/src/components/ui/ProductCard.jsx
+++ b/FRONTEND/src/components/ui/ProductCard.jsx
@@ -48,9 +48,9 @@ const ProductCard = ({ product }) => {
const [isInWishlist, setIsInWishlist] = useState(false);
const handleClose = () => {
- setUpdatedProduct(product);
- setImagePreview(product.image);
- onClose();
+ setUpdatedProduct(product);
+ setImagePreview(product.image);
+ onClose();
};
const fileInputRef = useRef(null);
@@ -73,7 +73,6 @@ const ProductCard = ({ product }) => {
const isInCompare = compareList.some((p) => p._id === product._id);
const { addToCart } = useCart();
- const { currency, rates } = useCurrencyStore();
const { addToWishlist, removeFromWishlist, checkInWishlist } = useWishlist();
const toast = useToast();
@@ -96,11 +95,8 @@ const ProductCard = ({ product }) => {
// Check wishlist status on mount
useEffect(() => {
- const checkWishlist = async () => {
- const inWishlist = await checkInWishlist(product._id);
- setIsInWishlist(inWishlist);
- };
- checkWishlist();
+ const inWishlist = checkInWishlist(product._id);
+ setIsInWishlist(inWishlist);
}, [product._id, checkInWishlist]);
// Revoke blob URLs to avoid memory leaks
@@ -266,7 +262,7 @@ const ProductCard = ({ product }) => {
{/* Price */}
- {formatPrice(product.price, currency, rates)}
+ ${product.price}
{/* Tags */}
@@ -363,7 +359,7 @@ const ProductCard = ({ product }) => {
- {/* ── Delete Confirmation Dialog ── */}
+ {/* Delete Confirmation Dialog */}
{
- {/* ── Edit / Update Modal ── */}
+ {/*Edit / Update Modal*/}
@@ -620,10 +616,7 @@ const ProductCard = ({ product }) => {
>
Update
-