update products with the same id - node.js

I got products with the option to choose size,
So If I add 'X' product to the cart with sizes 4 and 5 it will add 2 items to the cart like this :
My goal is when you succeed buying that items from your cart the sizes that you just bought will be removed from main product page.
It works good only if I trying to buy 2 different items.
If I will try to buy 2 same items with different size only the first size will be filtered and I will get this error :
(node:21336) UnhandledPromiseRejectionWarning: VersionError: No matching document found for id "62bee4ce92e7c57a195686ae" version 0 modifiedPaths "size"
this is the success buy code :
createOrder: async (_, {}, context) => {
const userAuth = await auth(context);
const cart = await Cart.findOne({ userId: userAuth._id });
cart.cartProducts.forEach(async (p) => { // Here's the filtering product functionallity
const products = await Product.findById(p.productId);
if (products) {
products.size = products.size.filter((s) => s !== +p.size);
}
await products.save();
});
if (!cart) {
throw new UserInputError('No available order!');
}
const newOrder = new Order({
orderProducts: cart.cartProducts,
purchasedBy: userAuth._id,
datePurchased: new Date().toISOString(),
});
await newOrder.save();
return newOrder;
},
as I said it works only if you add 2 different items to your cart.
edited:
Now I am getting product.save is not a function
const userAuth = await auth(context);
const cart = await Cart.findOne({ userId: userAuth._id });
const products = await Product.find({
_id: cart.cartProducts.map((c) => c.productId),
});
cart.cartProducts.forEach(async (p) => {
if (products) {
products.map((product) => {
return (product.size = product.size.filter(
(size) => size !== +p.size
));
});
}
});
await products.save();

The issue is that you are using findById which will return the first matching document. So that is why when you add same product with different size in cart. It will always pick the first matching id of the product. You can try using find operator as it returns the list matching the condition.
Product.find({_id:p.productId});
It will return all of the products instead of matching only the first one.

Related

Why is my MongoDB estimatedDocumentCount return incorrectly?

I have a film controller, and inside of it has 2 functions, one to get a movie and one to get a series, both of it has the pagination that I created.
At first, I did the getMovies function, and everything seemed to be fine, working as I expected, but then I copy paste that same function and replaced the query inside of it, the reason I'm separating it into 2 because later on, I want movies and series to act independently (add more filter for both of it in the future).
The problem I'm having is the getSeries, I haven't created any "series" film type, so the count in my getSeries should be 0. But instead, it returns 10. That 10 number is the number I have right now in my MongoDB, but I only create a "movie" film type, not a "series" film type, I haven't created a "series" film type yet up to this point.
Here's my filmController:
const ITEMS_PER_PAGE = 4
const filmController = {
getMovies: async (req, res) => {
const page = req.query.page || 1
const query = { type: "movie" }
try {
const skip = (page - 1) * ITEMS_PER_PAGE
const count = await Films.estimatedDocumentCount(query)
const films = await Films.find(query).limit(ITEMS_PER_PAGE).skip(skip)
const pageCount = Math.ceil(count / ITEMS_PER_PAGE)
res.json({
films: films,
pagination: {
count,
pageCount,
},
})
} catch (err) {
return res.status(500).json({ msg: err.message })
}
},
getSeries: async (req, res) => {
const page = req.query.page || 1
const query = { type: "series" }
try {
const skip = (page - 1) * ITEMS_PER_PAGE
const count = await Films.estimatedDocumentCount(query)
const films = await Films.find(query).limit(ITEMS_PER_PAGE).skip(skip)
const pageCount = Math.ceil(count / ITEMS_PER_PAGE)
res.json({
films: films,
pagination: {
count,
pageCount,
},
})
} catch (err) {
return res.status(500).json({ msg: err.message })
}
},
}
The film model looks like this in MongoDB (I filter it using type: "movie" or "series"):
In the POSTMAN, although it doesn't return any items (because I haven't created any series type yet), it still counts that there are 10 items, so it makes the pageCount 3, which is 3 pages.
Where did I do wrong?
Need to use .countDocuments().
From the docs for the .estimatedDocumentCount() method:
db.collection.estimatedDocumentCount() does not take a query filter and instead uses metadata to return the count for a collection.
So your current code is ignoring the query predicate that you are providing, hence returning the full count of 10 documents in the collection each time.

MongoDb bulkWrite is not working for me in node.js

This is my first time of using bulkWrite to carry out updates via mongoose. I am building a blog application and I am using it to learn MERN stack. I have a Post model. The Post model has object value which is an array. This is an example of it:
const PostSchema = new mongoose.Schema(
{
postLikes:{
type: Array,
default: []
}
}
)
The postLikes contain mongodb object ids of users who liked a post.
I have a logic for deleting selected users or all users by an admin. The like system does not come with a Like Model of it own. I simply used an array system inside the post model. After deleting a user, I would like to update all post models with likes of the selected users. Some users may have multiple likes across different posts.
In my node, I created a variable like this:
const {selectedIds} = req.body;
The selectedIds came from reactjs like this:
const [selectedUsers, setSelectedUsers] = useState([]);
const arrayOfSelectedUserId = (userId) =>{
setSelectedUsers(prevArray => [...prevArray, userId]);
);
}
For the request, I did it like this:
const response = await axiosPrivate.post(`/v1/users/deleteSelected`, selectedIds, { withCredentials: true,
headers:{authorization: `Bearer ${auth.token}`}})
In nodejs, the selectedUsers ids was passed to this variable:
const {selectedIds} = req.body;
I created the logic this way:
const findIntersection = (array1, array2) => {
return array1.filter((elem) => {
return array2.indexOf(elem) !== -1;
});
}
const filteredPost = posts.filter((singleFilter) => {
const intersection = findIntersection(selectedIds, singleFilter.postLikes);
return singleFilter.postLikes.length !== 0 && intersection.length !== 0;
});
const updatedPosts = filteredPost.map((obj)=>{
const intersection = findIntersection(selectedIds, obj.postLikes);
console.log(intersection )
return {
updateOne: {
filter: { _id: obj._id },
update: { $pull: { postLikes: { $in: intersection } } },
},
};
});
Post.bulkWrite(updatedPosts).then((res) => {
console.log("Documents Updated", res.modifiedCount)
})
The console.log shows the text Document updated and showed number of documents updated. However, if I check my database, the update won't reflect. This means that the selected users' ID is still in the array.
Is there a better method? What Am I doing wrong?

Sequelize - How to Insert data, and then update

I'm using Sequalize ORM for node.js/maria db project.
What I'm trying to do is, generate new table for product data using raw query.
The sequence of my logic is written below.
Step 1. Destroy table to reset data.
Step 2. Insert product data.
Step 3. Update price data in product data.
Step 4. Update stock data in product data.
The problem is step 3, and 4. It is not working!
What I found is... 'Insert' took some time to finish. So, 'Update' could not fulfilled, because there's no product data yet.
Is there any idea to invoke step 3~4, soon after step 2 is finished?
Thanks in advance.
const generateProductList = () => {
return new Promise( async (resolve, reject) => {
try {
await ProductPresentation.destroy({ truncate: true })
const productSql = `INSERT INTO m_product_presentation (productId, sku, name, status, urlKey, category, shortDescription, imageSmall, imageThumbnail)
SELECT id, sku, name, status, urlKey, category, shortDescription, imageSmall, imageThumbnail FROM m_product;`
const priceSql = `UPDATE m_product_presentation INNER JOIN m_price
ON m_product_presentation.productId = m_price.productId
SET m_product_presentation.priceRrp = m_price.priceRrp, m_product_presentation.priceRegular = m_price.priceRegular, m_product_presentation.priceSpecial = m_price.priceSpecial;`
const stockSql = `UPDATE m_product_presentation INNER JOIN m_inventory
ON m_product_presentation.productId = m_inventory.productId
SET m_product_presentation.stockAvailability = m_inventory.stockAvailability, m_product_presentation.stockQty = m_inventory.stockQty;`
// What I want is, Create initial data first. And then, update price and stock info. But, It fail...
await ProductPresentation.sequelize.query(productSql, { type: QueryTypes.INSERT })
await ProductPresentation.sequelize.query(priceSql, { type: QueryTypes.UPDATE })
await ProductPresentation.sequelize.query(stockSql, { type: QueryTypes.UPDATE })
resolve()
} catch(err) {
reject(err)
logger.error(err)
}
})
}
What you can do is that before updating the values which is dependent on the same product, you can check whether the product is already entered or not.
const generateProductList = () => {
return new Promise(async (resolve, reject) => {
try {
await ProductPresentation.destroy({ truncate: true })
const productSql = `INSERT INTO m_product_presentation (productId, sku, name, status, urlKey, category, shortDescription, imageSmall, imageThumbnail)
SELECT id, sku, name, status, urlKey, category, shortDescription, imageSmall, imageThumbnail FROM m_product;`
const priceSql = `UPDATE m_product_presentation INNER JOIN m_price
ON m_product_presentation.productId = m_price.productId
SET m_product_presentation.priceRrp = m_price.priceRrp, m_product_presentation.priceRegular = m_price.priceRegular, m_product_presentation.priceSpecial = m_price.priceSpecial;`
const stockSql = `UPDATE m_product_presentation INNER JOIN m_inventory
ON m_product_presentation.productId = m_inventory.productId
SET m_product_presentation.stockAvailability = m_inventory.stockAvailability, m_product_presentation.stockQty = m_inventory.stockQty;`
// Inserting the product in the table
const product = await ProductPresentation.sequelize.query(productSql, { type: QueryTypes.INSERT })
// To find if the product is existing the table or not
const isProductEntered = await sequelize.query(`
select exists(select 1 from "m_product_presentation" where "productId"=$1)
`, {
bind: [`${product[0][0].productId}`],
type: QueryTypes.SELECT
});
if (isProductEntered[0].exists) {
await ProductPresentation.sequelize.query(priceSql, { type: QueryTypes.UPDATE })
await ProductPresentation.sequelize.query(stockSql, { type: QueryTypes.UPDATE })
}
resolve()
} catch (err) {
reject(err)
logger.error(err)
}
})
}
If the product will be entered only then the update queries will be executed.
I found that another source code (Some react code in frontend) cause this issue. I'll close this issue.

Handling concurrent request that finds and update the same resource in Node Js & Mongo DB?

I have a function in node that runs after a clicking the checkout button. It checks the availability of the items in cart and if the item is available it will deduct it from the inventory.
I'm currently testing with two users clicking the checkout button at the same time. Both users have the exact same content in their cart (10 apples each) which gives a total of 20 apples, but there are only 10 apples in inventory.
If there is no item in cart it should return an error to the user but both orders are going through.
NOTE: This works if there is a 1 second delay between the clicks.
What can i do to prevent this?
// Check if items in inventory
const availability = await checkInventory(store, cart, seller);
if (!availability.success) {
return res.status(400).json({
success: false,
type: 'unavailable',
errors: availability.errors,
});
}
// Deduct Inventory
const inventory = await deductInventory(store, seller, cart);
if (!inventory) {
return next(new ErrorResponse('Server Error', 500));
}
checkInventory
exports.checkInventory = asyncHandler(async (store, cart, seller) => {
let isAvailable = true;
const unavailableProducts = [];
const inventory = await Inventory.find({
$and: [
{
store: store,
user: seller,
},
],
});
const products = inventory[0].products;
cart.forEach((item) => {
const product = products.find(
(product) => product._id.toString() === item.productId
);
if (!item.hasvariation) {
if (product.stock < item.qty) {
isAvailable = false;
unavailableProducts.push(
`${item.title} is not available, only ${product.stock} left available`
);
}
}
if (item.hasvariation) {
const variation = product.variations.find(
(variation) => variation._id.toString() === item.variationId
);
const option = variation.options.find(
(option) => option._id.toString() === item.optionId
);
if (option.stock < item.qty) {
isAvailable = false;
unavailableProducts.push(
`${item.title} is not available, only ${product.stock} left available`
);
}
}
});
return {
success: isAvailable,
errors: unavailableProducts,
};
});
deductInventory
exports.deductInventory = asyncHandler(async (store, seller, cart) => {
const inventory = await Inventory.findOne({
$and: [
{
store: store,
user: seller,
},
],
});
const products = inventory.products;
cart.forEach((item) => {
const product = products.find(
(product) => product._id.toString() === item.productId
);
if (!item.hasvariation) {
product.stock = product.stock - item.qty;
}
if (item.hasvariation) {
const variation = product.variations.find(
(variation) => variation._id.toString() === item.variationId
);
const option = variation.options.find(
(option) => option._id.toString() === item.optionId
);
option.stock = option.stock - item.qty;
}
});
const saveInventory = await Inventory.findOneAndUpdate(
{
$and: [
{
store: store,
user: seller,
},
],
},
{
$set: { products: products },
},
{ new: true, runValidator: true }
);
if (!saveInventory) {
return {
success: false,
errors: ['Server Error'],
};
}
return {
success: true,
};
});
The problem is that the 2 checkout calls run at (almost) the same time and your routine is not thread-safe. Both calls read a copy of the inventory data in memory. So both calls get a products.stock=10 and based on that local info you check and set the products counter by calculating the new amount in your function (stock-qty) and use an update query to set it as a fixed value (so both calls update the products.stock to 0). Resulting in your concurrency issues.
What you should do is let mongodb handle the concurrency for you.
There are several ways to handle concurrency but you could for example use the $inc to decrease the stock amount directly in mongo. That way the stock amount in the db can never be wrong.
result = await update({stock: {$ge: 10}}, {$inc: {stock : -10}})
As I added a filter to the query the order amount can not be lower than 0 plus you can now check the result of the update call to see if the update modified any documents. If it did not (result.nModified==0) you know the inventory was too low and you can report that back to the user.
https://docs.mongodb.com/manual/reference/operator/update/inc/
https://docs.mongodb.com/manual/reference/method/db.collection.update/#std-label-writeresults-update

Data is inserting twice in mongodb

I am reading the json file and check to product document is brand is already there in my document, If for brand suppose multiple products found, Then I am looping through products and check for category in category document. If category does not exist, then I create new document in categories document. But if product found 3 times then new category creating 3 times and respectively. Can anyone help me with this?
Below is my code
jsonData.products.forEach((element, index) => {
//get data from database which have same product
db.Product
.find({ brand: element.brand })
.then(product => {
if (product.length > 0) {
product.forEach((item, productIndex) => {
//Check product title greater than 70 %
// var similarity = stringSimilarity.compareTwoStrings(element.name, item.title);
var similarity = stringSimilarity.compareTwoStrings("surat rawal", "surat rawal prem");
// console.log(element.name, item.title);
if (similarity > 0.7) {
//if categoris found in json file
let newCategory = {};
if (element.categories) {
element.categories.forEach((category, categoryIndex) => {
db.Category
.findOne({
name: category
})
.then(categoryData => {
if (categoryData) {
const categoryObj = item.categories.find(categoryEle => {
return categoryEle.name === categoryData.name;
});
//If category not found on product then add category
if (!categoryObj) {
newCategory.id = categoryData._id;
newCategory.name = categoryData.name;
item.categories.push(newCategory);
}
} else {
//If category is not already there then add new one
const categorySchema = new db.Category({ name: category });
categorySchema
.save()
.then(insertedCategory => {
newCategory.id = insertedCategory._id;
newCategory.name = insertedCategory.name;
item.categories.push(newCategory);
})
.catch(err => {
throw err
})
}
})
.catch(err => console.log(err))
})
}
}
});
}
})
});
A number of observations here:
The section where you call
element.categories.forEach((category, categoryIndex) => {
db.Category.findOne({ name: category })
This is independent from item. You are looking for the categories of element in DB and saving them if they don't exist. So this same piece of code will be executed for each item you found.
Also, remember this:
db.Category.findOne({ name: category })
will be async.
Meaning even if you loop on items, and execute this piece of code, the affect of categorySchema.save() will not reflect. (I assume that's what you were going for here.)
I think the better way to do this would be, just insert all the missing categories without looping over items. After those operations are complete, you can do comparisons with items and make neccesary changes to item.categories (I assume you are going to update back each of the items)

Resources