Rewrite mongoose code to avoid Versioning error - node.js

I think I'm coming up against this problem https://github.com/Automattic/mongoose/issues/1844 .
I can see how that would happen - one request comes in, and tests is being updated, and at the same time another request comes, causing another update for tests.
I have schema that look like this
const User = new mongoose.Schema({
_id: { type: String, default: uuid.v1 },
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
tests: [Test],
});
const Test = new mongoose.Schema(
{
_id: { type: String, default: uuid.v1 },
responses: [Response]
},
{
timestamps: true
}
);
const Response = new mongoose.Schema({
_id: { type: String, default: uuid.v1 },
answer: {
type: String,
enum: [
"StronglyAgree",
"Agree",
"SomewhatAgree",
"Neutral",
"SomewhatDisagree",
"Disagree",
"StronglyDisagree"
]
},
question: { type: String, ref: "Question" }
});
const Question = new mongoose.Schema({
_id: { type: String, default: uuid.v1 },
description: String,
});
I have a class, UserModel that uses mongoose's models.
It does this
async createTest(userId) {
try {
const test = await this.testModel.create();
try {
const user = await this.model.findOne({ userId });
if (user) {
user.tests.push(test);
return await user.save();
} else {
throw new Error("Non existent UserId");
}
} catch (e) {
throw e;
}
} catch (e) {
throw e;
}
}
and this is what create looks like.
async create() {
if (!this._model) {
await this._getModel();
}
try {
const questions = await this.questionModel.getAllQuestions();
const test = new this.model();
questions.forEach(question => {
const response = this.responseModel.create(question.id);
test.responses.push(response);
});
await this.model.populate(test, {
path: "responses.question",
model: "Question"
});
return test;
} catch (e) {
throw e;
}
}
I'm not sure how to re-write this to avoid the versioning problem (and I'd rather not skip versioning). The schema also makes sense to me as I don't want to carry duplicate descriptions of Questions (I might have to change the descriptions in future).
How can I do this?

The only way is that you disable the versioning by putting
{
"versionKey":false
}
at end of your schema.

Related

Mongodb find specific comment within array of objects in post collection

i store comments in post collection like this:
...
const postSchema = new mongoose.Schema(
{
body: {
type: String,
},
userId: {
type: String,
required: true,
},
likes: {
type: Array,
default: [],
},
img: {
type: String,
default: null,
},
comments: [
{
body: {
type: String,
required: true,
},
userId: {
type: String,
required: true,
},
postId: {
type: String,
required: true,
},
},
{ timestamps: true },
],
},
{ timestamps: true }
);
...
i created this put route to update comment:
app.put("/update-comment", (req, res) => {
posts.updateComment(req, res);
});
updateComment function:
const updateComment = async (req, res) => {
try {
const post = await Post.findById(req.body.postId);
const comment = await post.comments.map((commentObj) => {
return commentObj.find({ _id: req.body.commentId });
});
await comment.updateOne({ $set: req.body });
res.status(200).json(comment);
} catch (err) {
res.status(500).json(err);
}
};
it finds the post by postId and simply loop the comments array to find the comment object with the comment id provided, i tried this route with postman providing the following json body:
{
"postId":"6242a4c75bce78154824fc8f",
"commentId":"6242ac32a61fd275ed13846b",
"body":"my first comment updated"
}
but it doesn't work, it returns 500 internal error, if i replaced the code with this:
try {
const post = await Post.findById(req.body.postId);
const comment = post.comments;
res.status(200).json(comment);
} catch (err) {
res.status(500).json(err);
}
it will indeed return the comments within that post as an array of objects, i don't know what's wrong, i made sure the postId and commentId provided by the json body is correct, what's the problem?

In Mongoose findOneAndUpdate, how can I make my post request work?

Hi all so I am trying to make a post request that increments a value if it already exists and if not it should create a new item.
router.post('/', auth, async (req, res) => {
try {
const { name, price, image } = req.body;
var query = { name },
update = { $inc: { count: 1 } },
options = { upsert: true, new: true,};
await CartItem.findOneAndUpdate(query, update, options, function (
err,
data
) {
if (err) {
const newItem = new CartItem({
user: req.user.id,
name: name,
price: price,
image: image,
});
const item = newItem.save();
res.json(item);
} else {
res.json(data);
}
});
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CartItemSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
name: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
},
count: {
type: Number,
},
image: {
type: String,
required: true,
},
});
module.exports = CartItem = mongoose.model('cartItem', CartItemSchema);
So there are two problems here that I cannot wrap my head around(Pretty new with MongoDb, did do my research).
I can get the count to increment, but it increments with 2 or even more instead of 1. (I know other users also experienced this)
If the item is already in the cart(name matches) I want a new item to be added which does happen, but it only adds the name, count and Id. I want it to add the user, name, price and image.
Would appreciate some assistance.
you should create your document with a default value equals to 0.
define count at your schema like the following:
count: {
type: Number,
default: 0
}
then use { $inc: { <field1>: <amount1>, <field2>: <amount2>, ... } }.
link to docs: https://docs.mongodb.com/manual/reference/operator/update/inc/

Is there a way i could keep track of the Time and the entity that was changed in a model

Basically I'm trying to get the time and the entity changed in a particular model when ever the update method is called.
This is my model I want to keep track of:
const mongoose = require("mongoose");
const modelSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
name: {
type: String,
required: true,
},
note1: String,
note2: String,
note3: String,
images: {
type: Array,
required: true
},
status: {
enum: ['draft', 'pending_quote', 'pendong_payment', 'in_production', 'in_repair', 'pemding_my_review', 'fulfilled'],
type: String,
default: "draft"
},
price: {
type: mongoose.Schema.Types.ObjectId,
ref: "Price",
}
}, {
timestamps: true,
})
module.exports = mongoose.model("Model", modelSchema)
And this is the method I call to update the status:
exports.updateModel = async (req, res) => {
try {
let id = req.params.id;
let response = await Model.findByIdAndUpdate(id, req.body, {
new: true
})
res.status(200).json({
status: "Success",
data: response
})
} catch (err) {
res.status(500).json({
error: err,
msg: "Something Went Wrong"
})
}
}
you can add a new field in your schema like:
logs:[{
entity: String,
timeStamp: Date
}]
Then updating it basing on your current code:
let id = req.params.id;
// I don't know whats in the req.body but assuming that it
// has the correct structure when passed from the front end
let response = await Model.findByIdAndUpdate(id,
{
$set:req.body,
$push:{logs:{entity:'your entity name here',timeStamp:new Date()}}
}, {
new: true
})

how to save document and update another which are dependent on each other with mongoose

Here is my bid model.
const BidSchema = new Schema({
auctionKey: {
type: mongoose.Types.ObjectId,
ref: "Auction",
required: true
},
amount: { type: String, required: true },
userName: { type: String, required: true },
});
And, here is my Auction Model (Notice the relationships between these two models).
const AuctionSchema = new Schema({
title: { type: String, required: true },
startDate: { type: Date, required: true },
closeDate: { type: Date, required: true },
initialBidAmount: { type: Number, required: true },
bidIncrementAmount: { type: Number, required: true },
bids: [
{
type: mongoose.Types.ObjectId,
ref: 'Bid'
}
]
});
When user bids for any auction, I'm saving bid in bids collection and updating auctions collection using mongoose findOneAndUpdate.
const postBid = async (req, res, next) => {
const { auctionKey } = req.body;
const bid = new BidModel(req.body);
bid.save(error => {
if (error) {
res.status(500).json({ message: "Could not post bid." });
}
});
const aucById = await AuctionModel.findOneAndUpdate(
{ _id: auctionKey },
{ $push: { bids: bid } }
).exec((error: any, auction: IAuction) => {
if (error) {
res.status(500).json({ message: "Could not post bid." });
} else {
res.status(201).json({ bid });
}
});
};
For any reason if any of these two (save bid and findOneAndUpdate) throws any error I want nothing to be saved into database. I mean to say either they should save and update or nothing should be done on database.
I have tried using mongoose session and transaction but got this error.
MongoError: This MongoDB deployment does not support retryable writes. Please add retryWrites=false to your connection string.
Is there any way to work out in this scenario?
If I understand your problem right, you can just delete created document in:
.exec((error: any, auction: IAuction) => {
if (error) {
// here, by using .deleteOne()
res.status(500).json({ message: "Could not post bid." });
}
Or just change structure of your code, so only when two are successfully created, they will be saved and response will be sent.

MongooseError [ParallelSaveError]: Can't save() the same doc multiple times in parallel

I am having an issue with mongoose and nodejs. May be i am writing wrong code or any other problem please help. Here is my controller file. alldata.save gives [ParallelSaveError]
let createData = async function(req,res,next) {
let body = req.body;
let alldata = new League(body);
let start_time = new Date().getTime();
try {
await Leaguecategories.find({})
.then(async function(categories) {
categories.forEach(async function(category) {
//here i am assigning foreign key
alldata.league_category_id = category._id;
await alldata.save(function(err, book){
if(err){
console.log(err);
}else{
res.send({status: 0, statusCode:"success", message: "Successfully inserted."})
}
});
})
})
}
catch (error){
return res.send({status : 1 , statusCode : "error" , message : error.message})
}
}
Here is my Leaguecategories model
var mongoose = require('mongoose');
const league_categories = new mongoose.Schema({
name: {
type: String,
required: true
},
active: {
type: String,
required: true
},
create_date: {
type: Date,
required: true,
default: Date.now
},
league_type_id: {
type: String,
required: 'league_type',
required:true
}
})
module.exports = mongoose.model('Leaguecategories', league_categories)
Here is my League model
var mongoose = require('mongoose');
const league = new mongoose.Schema({
title: {
type: String,
required: true
},
pool_price: {
type: Number,
required: true
},
entry_fee: {
type: Number,
required: true
},
total_spots: {
type: Number,
required: true
},
start_time: {
type: Date,
required: true
},
end_time: {
type: Date,
required: true
},
create_date: {
type: Date,
required: true,
default: Date.now
},
active: {
type: String,
required: true
},
league_category_id: {
type: String,
ref: 'Leaguecategories',
required:true
}
})
module.exports = mongoose.model('League', league)
You have to create new instance of League each time. Like this:
categories.forEach(async function(category) {
//here i am assigning foreign key
let alldata = new League(body);
alldata.league_category_id = category._id;
...
});
Suggestion:
Why are you using both async/await and .then()? You should use only one of them. Also, there are some other problems.
await won't work inside forEach
You are calling res.send() every time you call .save(). This might end up throwing an error as well.
You can refactor the code like this.
try {
const categories = await Leaguecategories.find({});
const promises = categories.map(function (category) {
//here i am assigning foreign key
let alldata = new League(body);
alldata.league_category_id = category._id;
return alldata.save();
});
await Promise.all(promises);
res.send({ status: 0, statusCode: "success", message: "Successfully inserted." })
} catch (error) {
return res.send({ status: 1, statusCode: "error", message: error.message });
}

Resources