Data is inserting twice in mongodb - node.js

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)

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.

Mongoose: After finding document, iterate over a value in the document and run a new query on each

I have one schema which contains an array of references to another schema (among other fields):
const RecipeIngredient = new Schema({
ingredientId: { // store id ref so I can populate later
type: Schema.Types.ObjectId,
ref: 'ingredients',
required: true
},
// there are a couple other fields but not relevant here
});
const Recipe = new Schema({
ingredients: [RecipeIngredient]
});
I'm trying to write a route which will first find a recipe by _id, populate the ingredients array (already have this working), and finally iterate over each ingredient in that array.
router.get('/:recipeId/testing', async (req, res) => {
const { recipeId } = req.params
let recipe = await Recipe
.findById(recipeId)
.populate({
path: 'ingredients.ingredientId',
model: 'Ingredient',
select: '_id ......' //I'm selecting other fields too
})
.lean()
.exec();
if (recipe) {
const { ingredients } = recipe;
const newIngredients = [];
await ingredients.forEach(async (ingr) => {
// here I'd like to be able to run a new query
// and append the result to an array outside of the forEach
// I do need information about the ingr in order to run the new query
newIngredients.push(resultOfNewQuery);
});
return res.json(newIngredients)
};
return res.status(404).json({ noRecipeFound: 'No recipe found.'});
})
I've tried approaching this in a few different ways, and the closest I've gotten was executing the new query within each iteration, but because the query is async, I return the response before I've actually collected the documents from the inner query.
I also attempted to use .cursor() in the initial query, but that won't work for me because I do need to access the ingredients field on the recipe once it is resolved before I can iterate and run the new queries.
Any ideas would be appreciated! I'm definitely opening to restructuring this whole route if my approach is not ideal.
I was able to make this work by using a for loop:
const newIngredients = [];
for (let idx = 0; idx < ingredients.length; idx++) {
const { fieldsImInterestedIn } = ingredients[idx];
const matchingIngredients = await Ingredient
.find(fieldsImInterestedIn)
.lean()
.exec()
.catch(err => res.status(404).json({ noIngredientsFound: 'No ingredients found' }));
newIngredients.push(ingredientsToChooseFrom[randomIndex]);
};
return res.json(newIngredients);
still a little perplexed as to why this was able to work while forEach wasn't, but I'll happily move on...

update products with the same id

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.

Nested documents in mongodb implementation, just like Reddit comments

In my project, I would like to implement a comment section which consists of list of comments.
const myschema= mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
//other fields
comments : [comment]
},{collection : 'TABLE_NAME'} );
const comment= mongoose.Schema({
_id : mongoose.Schema.Types.ObjectId,
commentid: String, // id consists of uuid1()
senderid: Number, //user's id
body: String, // useful information
parent: String,// parent id consists of uuid1()
children: [] // I couldn't add children = [comment], it gives me an error,
//instead, I make it empty and will fill it when a comment comes
})
In the request tab, I will receive a JSON in the following format:
{
"userid": "NUMBERID HERE",
"comment": "COMMENT BODY",
"parent" : "Comment's parent id"
}
I would like to add a comment which can be a child of another comment. How can I search and find the appropriate position?
If there are no parent in JSON body, I'm doing this:
// import comment somewhere at the beginning
.then(doc =>{
var newc= new comment();
newc.cid = uuidv1();
newc.sender = req.body.userid;
newc.body = req.body.comment;
newc.parent = "";
newc.children = "";
doc.comments.push(newc);
// save to DB
doc
.save()
.then(docres =>{
res.status(200).json(docres);
})
.catch(err => {
res.status(500).json({error: err});
})
}
I have no idea how to find a comment that resides in a deep level
You cannot search for an array element or object property given an unspecified, arbitrarily-nested depth. It just isn't possible. You would need to instead handle this in the application layer. Denormalizing your data is fine in many cases, but arbitrary nesting depths isn't a recommended use case for data denormalization, especially since you can't index efficiently!
If you want a pure MongoDB solution, then you'll need a different document structure. I would recommend taking a look at the documentation, particularly the section concerning an array of ancestors, in order to properly model your data.
I have found a solution.
The manually traversing the comments and inserting in the right place works. However, it only works up to some level. In my case, I can insert a comment below comment and save it. I can also insert a 3rd deep level comment and see the JSON dump of the object that I have inserted or even wrap it in an HTTP response object and send to the client. But the database does not save this update.
What I did is, after inserting the comment in the correct place I added this code before saving to the database.
doc.markModified('comments'); // this is added
doc.save().then(/*prepare response*/);
Somehow the MongoDB or mongoose or javascript interpreter knows the document has been changed and save the updated version. If the markmodified part is not specified, probably the compiler thinks that the object has not been modified and skips it.
Hopefully, this helps people who encounter this issue.
This is my implementation
// find the document which comment is going to be inserted
MyDOC.findOne({'id' : req.params.id})
.exec()
.then(doc =>{
// if the comment has a parent, it should be inserted in nested
if(req.body.parent != null && req.body.parent != undefined)
{
// find correct position and insert
findComment(doc.comments, req.body);
doc.markModified('comments');
doc
.save()
.then(docres =>{
res.status(200).json(docres);
})
.catch(err => {
console.log(err);
res.status(500).json({error: err});
})
}
else
{
//Add comment to the root level
var comment= new Comment();
comment.cid = uuidv1();
comment.body = req.body.comment;
comment.parent = "";
doc.comments.push(comment);
doc.markModified('comments');
// save to DB
doc
.save()
.then(docres =>{
res.status(200).json(docres);
})
.catch(err => {
console.log(err);
res.status(500).json({error: err});
})
}
})
function findComment(comment, body)
{
comment.forEach(element => {
if(element.children.length != 0)
{
if(element.cid === body.parent)
{
// found, insert
var comment= new Comment();
comment.cid = uuidv1();
comment.body = body.comment;
comment.parent = body.parent;
element.children.push(comment);
return true;
}
if(findComment(element.children, body, usr))
return true;
}
else
{
// this comment does not have children. If this comments id and the body's parent is equal, add it
if(element.cid === body.parent)
{
// found, insert
var comment= new Comment();
comment.cid = uuidv1();
comment.body = body.comment;
comment.parent = body.parent;
element.children.push(comment);
return true;
}
return false;
}
});
}

How to add object to nested array in mongoose?

In essence what I am trying to do is something along the lines of FindByIdAndCreate, a method which does not exist in mongoose.
I have a schema as so:
const WordSchema = new Schema ({
TargetWord: String,
Translation: String,
ExampleSentences: [{
Number: Number, //increment somehow each time
Sentence: String,
}],
});
I have a form where the user can add example sentences of this target word, the route for which looks like this:
router.put("/word/:id/add", async(req, res) => {
//get the new sentence from the field
var NewSentence = req.body.Sentence;
Now once I have this new sentence saved to the variable NewSentence I want to create a new object within the WordSchema.ExampleSentences array which contains the new sentences itself, and the number which should automatically increment.
I have fiddled around with FindByIdAndUpdate to no avail,this syntax does not work because it throws an error at the use of .
WordSchema.findByIdAndUpdate(req.params.id, {ExampleSentences.Sentence: NewSentence}, ...
The only solution to increment your counter is to retrieve every document with a good old 'find' and create your new entry accordingly, since 'update' has no way to self reference a document during the process.
router.put("/word/:id/add", async(req, res) => {
WordSchema.find({_id: req.body.id}, function(results) {
if (results.length === 0) return res.json();
const word = result[0];
const NewExampleSentence = {
Number: word.ExampleSentences.length, // if your counter start from 1, then add 1
Sentence: req.body.Sentence
};
word.ExampleSentences.push(NewExampleSentence);
word.save(function(err) {
if (err) {
// handle error
}
return res.json();
})
}
})

Resources