Mongoose validate field execute in both save and update time - node.js

I'm using validate in mongoose schema for unique name now face issue while someone updates that record It won't allow the user to update because already have an entry in a database.
I've schema & code like following.
Schema
let mongoose = require('mongoose'),
Schema = mongoose.Schema;
let pTSchema = mongoose.Schema({
type: {
type: String,
required: true,
validate: {
validator: function(v, cb) {
v = v.toLowerCase();
PT.aggregate([ // Query for validate type should be unique if already exist than through error.
{
$addFields:{
lowerCase: { $toLower: "$type" }
}
},
{
$match:{
lowerCase: v
}
}
], function(err,docs){
console.log(docs, v);
cb(docs.length == 0);
});
},
message: 'p already exists!'
}
}
});
module.exports = PT = mongoose.model('pt', pTSchema);
Insert New Record.
// Working as expected with validation
var newPT = new PT();
newPT.type = req.body.type;
newPT.save(function(err) {
if (err)
return res.status(400).send({ status: false, message: JSON.stringify(err) })
return req.res.status(200).send({status:true, data: newPT});
});
Update Records.
// While update single record it also executes schema validate and won't give permission to save.
// Please give me some suggestion on this.
PT.findOne({ _id : where }, function(err, responsePT) {
if (!PT) {
return res.status(500).send({ status: false, message: "ERROR" });
}
responsePT.type = req.body.type;
responsePT.save(function(err) {
if (err)
return res.status(400).send({ status: false, message: JSON.stringify(err) })
return req.res.status(200).send({status:true, data: responsePT});
});
});

At, Final I didn't get any solution so, I update my code with .pre('save', It's fixed my problem.
I ignore updated entry.

Related

Mongoose auto-increment fails because of a cast error

I am trying to increment a simple number field, but it is telling me it is failing to to a casting error.
CastError: Cast to Number failed for value "{ '$inc': 1 }" (type Object) at path "times_dealt"
Says it's an object?
This is my schema for Answer
const answerSchema = new mongoose.Schema({
body: {
type: String,
trim: true,
required: true,
},
times_dealt: {
type: Number,
required: true,
},
times_picked: {
type: Number,
required: true,
},
times_won: {
type: Number,
required: true,
},
}, {
timestamps: true,
});
module.exports = { answerSchema };
This is my route for me the admin to add new answers (it's a game so only I can add them, that why the auth. Figured I'll include the complete code.)
router.post("/answers", async(req, res) => {
try {
const isMatch = await bcrypt.compare(
req.body.password,
process.env.ADMIN_PASSWORD
);
if (isMatch) {
const answer = new Answer({
body: req.body.answer.trim(),
times_dealt: 0,
times_picked: 0,
times_won: 0,
});
await answer.save();
res.status(201).send(answer);
}
res.status(401).send();
} catch (e) {
console.log("failed to save", e);
res.status(400).send(e);
}
});
Then whenever a card is dealt, I want to increase the count for times_dealt, and this is when I get the error. This is how I do it:
async function getOneAnswerCard(room) {
if (room.unused_answer_cards.length !== 0) {
// We pick a random answer's ID from our array of unused answers
const randomAnswerID = getRandomElement(room.unused_answer_cards);
// We get that answer's full object from our DB
const newAnswer = await Answer.findById(randomAnswerID);
// const newAnswer = await Answer.findByIdAndUpdate(randomAnswerID, {
// times_dealt: { $inc: 1 },
// });
await Answer.findByIdAndUpdate(randomAnswerID, {
times_dealt: { $inc: 1 },
});
// We remove it from the unused cards array
room.unused_answer_cards = room.unused_answer_cards.filter(
(answerID) => answerID !== randomAnswerID
);
// We add it to the dealt cards array
room.dealt_answer_cards.push(randomAnswerID);
// We serialize the answer (we don't want the user to get info on our answer stats)
const serializedAnswer = { _id: newAnswer._id, body: newAnswer.body };
return serializedAnswer;
}
}
Just getting the answer by itself is no issue. Getting a random ID and fetching an answer object works just fine. It's only when I've added the increment functionality that it started crashing.
I think you're using $inc with a wrong syntax. Try this:
await Answer.findByIdAndUpdate(randomAnswerID, {
{ $inc: { times_dealt: 1 } },
});

Is there a way to get data from db before update for MongoDb - Mongoose

I want to do an update method for my project but i couldn't solve this issue. My model has a field call slug. If I need I add data to make this value unique. However, i am using findByIdAndUpdate method on my update function. I am wondering about that is there a way to get data before update this model? Do I have to make at least 2 different requests to my db to get the old data or does this method I use give me a chance to compare data? Because if the title field has changed I need to compare it and generate the new slug value.
Category model
const mongoose = require('mongoose')
const CategorySchema = mongoose.Schema({
title: {
type: String,
required: true,
trim: true,
minLength: 3,
maxLength: 70
},
description: {
type: String,
requried: true,
trim: true,
minLength: 30
},
coverImage: {
type: String,
trim: true
},
slug: {
type: String,
unique: true,
required: true
}
}, {collection: "categories", timestamps: true})
module.exports = mongoose.model('category', CategorySchema);
Update function
const update = async (req, res, next) => {
delete req.body.createdAt;
delete req.body.updatedAt;
try {
const data = req.body;
data.coverImage = req.file ? req.file.path.replace(/\\/g, "/") : undefined;
data.slug = data.title ? slugCreator(data.title, null): undefined;
const result = await CategoryModel.findByIdAndUpdate(req.params.categoryId, data, { new: true, runValidators: true });
if (result) {
return res.json({
message: "Category has been updated",
data: result
});
}else{
throw createError(404, "Category not found.")
}
} catch (error) {
next(createError(error));
}
};
You could solve your problems first by getting the documents and then do the update with the save method like the following example
const update = async (req, res, next) => {
delete req.body.createdAt;
delete req.body.updatedAt;
try {
const data = req.body;
//here you have the current category
const category = await CategoryModel.findById(req.params.categoryId);
if (!category) {
throw createError(404, 'Category not found.');
}
//do all you comparation and setting the data to the model...
category.slug = data.title ? slugCreator(data.title, null) : undefined;
category.coverImage = req.file
? req.file.path.replace(/\\/g, '/')
: undefined;
await category.save();
return res.json({
message: 'Category has been updated',
data: category,
});
} catch (error) {
next(createError(error));
}
};

How to update a text within a nested model - MERN stack

I have just started learning the MERN stack and I am having trouble updating a text within a model with Express/Node. I tried to look for help and accessed Update a model within a model
How to Nest Models within a Model but they weren't quite what I was looking for.
I am working with 2 models, with the comments model embedded within the cats models like so. This is the comment model
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const commentSchema = new Schema(
{
user_id: { type: String, required: true },
cat_id: { type: String, required: true },
text: {
type: String,
min: [3, "Comment cannot be too short"],
},
email: { type: String, required: true },
},
{ timestamps: true }
);
const Comment = mongoose.model("Comment", commentSchema);
module.exports = Comment;
And this comment model is within the cat models
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const Comment = require("./comments.js");
const catSchema = new Schema(
{
name: {
type: String,
required: true,
unique: true,
min: [2, "Cat name minimum of 2 characters."],
},
description: { type: String, required: true },
image: { type: String },
gender: { type: String, required: true },
cage: { type: String, required: true },
adoptable: { type: String, required: true },
comments: [Comment.schema],
},
{ timestamps: true }
);
const Cat = mongoose.model("Cat", catSchema);
module.exports = Cat;
In my controller, when I update a comment, I need to update the respective comment inside the cat model too, but I'm not able to do so. I tried targeting the particular cat, foundCat, and I can't access the comment with foundCat.comments.id(req.params.id)
Strangely enough, when I console log "foundCat.comments.id", it tells me that this is a function? So I don't know why I can't access and update that text...
Here is my code for updating the comment: Note! The part with issue is located at the end, look for "Cat.findOne"
// For updating comment
const updateComment = async (req, res) => {
// if there is no req.body, return error
if (!req.body) {
return res.status(400).json({
success: false,
error: "You must provide a body to update",
});
}
// req.body exists, so find the comment by id and then update
Comment.findOne({ _id: req.params.id }, (err, comment) => {
if (err) {
return res.status(404).json({
err,
message: "Comment not found!",
});
}
console.log(req.body);
// update the comment details
comment.text = req.body.text;
// save the updated comment
comment
.save()
.then(() => {
// return json response if successful
return res.status(200).json({
success: true,
id: comment._id,
message: "Comment updated!",
});
})
.catch((error) => {
return res.status(404).json({
error,
message: "Comment not updated!",
});
});
// now update the comment entry for the cat too
Cat.findOne({ _id: comment.cat_id }, (err, foundCat) => {
console.log("This doesnt work", foundCat.comments.id(req.params.id));
foundCat.save((err, updatedCat) => {
console.log(updatedCat);
});
});
});
};
Example of the comments within a cat:
You should update the cat instance once the comment has been fetched.
Try to change your code like this (using async wait):
const updateComment = async (req, res) => {
// if there is no req.body, return error
if (!req.body) {
return res.status(400).json({
success: false,
error: 'You must provide a body to update',
});
}
try {
// req.body exists, so find the comment by id and then update
const comment = await Comment.findById(req.params.id);
if (!comment) {
return res.status(404).json({
err,
message: 'Comment not found!',
});
}
// update the comment details
comment.text = req.body.text;
// save the updated comment
await comment.save();
// now update the comment entry for the cat too
const cat = await Cat.findById(comment.cat_id);
const otherCatComments = cat.comments.filter((c) => c._id !== comment._id);
cat.comments = [...otherCatComments, comment];
await cat.save();
res.status(200).json({
success: true,
id: comment._id,
message: 'Comment updated!',
});
} catch (err) {
res.status(404).json({
error,
message: 'Comment not updated!',
});
}
};
Luca, thank you! That was extremely helpful and I can see the appended comment added to the cats comment array. Now the only thing is the cats.comment.filter isn’t quite working as intended, as the otherCatsComments still includes all the comments. I had to do a little digging in the code and I managed to console log the id, which returns “_id: new ObjectId("617d57719e815e39f6049452"),”
I tried changing it to
const otherCatComments = cat.comments.filter((c) => c._id !== `new ObjectId("${comment._id}")`);
const otherCatComments = cat.comments.filter((c) => c._id !== ` new ObjectId("${comment._id}")`);
const otherCatComments = cat.comments.filter((c) => c._id !== `ObjectId("${comment._id}")`);
But they all don’t seem to work, so I had to do a deep de-bugging and turns out my code is off for some things! I’ll just add them here in case anyone bumps into this issue in the future.
First off, my comment id was different from the comment id within my cats model. For reference, here is my create comment model (I modified it to use the async/await + try/catch block as recommended by Luca:
const createComment = async (req, res) => {
// if there is no req.body, return error
if (!req.body) {
return res.status(400).json({
success: false,
error: "You must provide a comment",
});
}
try {
// req.body exists, so make a new comment
const comment = new Comment(req.body);
await comment.save();
// now add comment to cat
Cat.findById(req.params.id, (err, foundCat) => {
// Append the comment to the cat
foundCat.comments.push(comment);
foundCat.save();
});
// somehow, if the new comment doesn't exist, return error
if (!comment) {
return res.status(400).json({ success: false, error: err });
}
// success!
res.status(201).json({
success: true,
id: comment._id,
message: "Comment created!",
});
} catch (err) {
return res.status(400).json({
err,
message: "Comment not created!",
});
}
};
Note the part where I add the comments in the cat:
At first it was
foundCat.comments.push(req.body);
but this would generate a comment id in the cat that would be different from the comment id in the comment. so req.body is changed to comment.
Once that was fixed, I tried the original code by Luca, but it still didn’t work. My workaround was to not use the filter, and just delete the old comment and then add in the new comment.
Code here:
const updateComment = async (req, res) => {
// if there is no req.body, return error
if (!req.body) {
return res.status(400).json({
success: false,
error: "You must provide a body to update",
});
}
try {
// req.body exists, so find the comment by id and then update
const comment = await Comment.findById(req.params.id);
if (!comment) {
return res.status(404).json({
err,
message: "Comment not found!",
});
}
// update the comment details
comment.text = req.body.text;
// save the updated comment
await comment.save();
// now update the comment entry for the cat too
const cat = await Cat.findById(comment.cat_id);
// remove the old, non-updated comment first
cat.comments.id(comment._id).remove();
// now add in the updated comment
cat.comments.push(comment);
await cat.save();
res.status(200).json({
success: true,
id: comment._id,
message: "Comment updated!",
});
} catch (err) {
res.status(404).json({
error,
message: "Comment not updated!",
});
}
};

Mongoose: Create() doesn't create document

Hello this is the first time I post a question.
So basically i'm build an API to manage car(Im doing this as a training for better building API). My connection to the D is made using a singleton which is called once by server and the mongo promises are global(and it works fine for now)
I have my model:
import mongoose, {Schema} from "mongoose";
import mongooseUniqueValidator from "mongoose-unique-validator";
class Voiture{
initSchema(){
const schema = new Schema({
Marque:{
type: String,
require:true,
},
Modele:{
type: String,
require:true,
},
Année:{
type: Number,
require:true,
},
Energie:{
type: String,
require:true,
},
Boite_De_Vitesse:{
type: String,
require:true,
},
couleur_exterieure:{
type: String,
require:true,
},
couleur_intérieur:{
type: String,
},
nombre_De_Portes:{
type: Number,
},
nombre_De_Places:{
type: Number,
},
Prix:{
type: Number,
},
Etat:{
type: String,
require: true,
},
Kilométrage:{
type: Number,
},
prix_location:{
type: Number,
require:true,
}
},{timestamp: true});
schema.plugin(mongooseUniqueValidator);
mongoose.model("voitures", schema);
}
getInstance() {
this.initSchema();
return mongoose.model("voitures");
}
}
export default Voiture;
and I also have services and controllers attached to them
I can get all the documents in the mongoDB Database but I can't create Documents
Here is my service
import voiture from "../models/Voiture"
import mongoose from "mongoose"
class VoitureService{
constructor(){
this.model = new voiture().getInstance();
this.getAll = this.getAll.bind(this);
this.insert = this.insert.bind(this);
}
/**
* GET All voiture
*/
async getAll(query){
let {skip, limit} = query;
skip = skip ? Number : 0;
limit = limit ? Number : 10;
delete query.skip;
delete query.limit;
if(query._id){
try {
query._id = new mongoose.mongo.ObjetId(uery._id);
} catch (error) {
console.log("not able to generate mongoose id with content", query._id);
}
}
try {
let items = await this.model
.find(query)
.skip(skip)
.limit(limit)
let total = await this.model.countDocuments();
return {
error: false,
statusCode: 200,
data: items,
total
};
} catch (errors) {
return {
error: true,
statusCode: 500,
errors
};
}
}
/**
* Insert a car in the Database
*/
async insert(data){
console.log("line 60");
try {
console.log("line 62");
let item = await this.model.create(data, function (err) {
console.log("line 64");
if (err) return handleError(err);
// saved!
console.log("line 67");
});
if (item){
console.log("line 70");
return {
error: false,
item
};
}
} catch (error) {
console.log("error", error);
return {
error: true,
statusCode: 500,
message: error.message || "Not able to create item",
errors: error.errors
};
}
}
}
And this my controller
async addCar(req, res){
let response = await this.service.insert(req.body)
if (res.error) return res.status(res.statusCode).send(response);
return res.status(201).send(response);
}
I tried to log the Item but it gives me undefined
Thanks to all of you !
Salayna
You can Follow this code
// At Frist, You import **Voiture** Model Then use the model
async insert(data){
console.log("line 60");
try {
console.log("line 62");
let item = await Voiture.create(data, function (err) {
console.log("line 64");
if (err) return handleError(err);
// saved!
console.log("line 67");
});
if (item){
console.log("line 70");
return {
error: false,
item
};
}
} catch (error) {
console.log("error", error);
return {
error: true,
statusCode: 500,
message: error.message || "Not able to create item",
errors: error.errors
};
}
}

Query not giving updated result after database update in mongoDB

I have a server setup in node.js that uses mongoose. The problem i am having is that when i update a database as follows
Questions.update({id:questionID}, {$push: {answers: {answer:answer,answeredBy:username}}}, callback);
I see that the data has been added in MongoCompass but when i query it using
Questions.find({tags:interests},callback);
the updated data is not returned. But if i restart the server and again query, i receive the updated result.
And if i just save the data and query by using .save method and query, the database is updated.
So the problem has to be with my .update method right? I really cant find what i am doing wrong
my database is as follows
let mongoose =new require('mongoose');
const uuidv4 = require('uuid/v4');
//Define a schema
let Schema = mongoose.Schema;
let QuestionsSchema = new Schema({
id: {
type: String,
unique: true
},
title: String,
details: String,
askedDate: {type: Date, default: Date.now},
tags: [{
type: String
}],
askedBy: String,
answers: [{
answeredBy: String,
answer: String,
answeredDate: {type: Date, default: Date.now}
}]
});
let Questions = module.exports = mongoose.model('Questions', QuestionsSchema);
module.exports.addQuestion = function (newQuestion, callback) {
//generating a new random id for each question
newQuestion.id = uuidv4();
newQuestion.save(callback);
};
module.exports.addAnswer = function (username,answer, questionID, callback) {
Questions.update({id:questionID}, {$push: {answers: {answer:answer,answeredBy:username}}}, callback);
};
module.exports.getQuestions = function (interests, callback) {
Questions.find({tags:interests},callback);
};
The methods are called as follows
Questions.getQuestions(user.tags[i], function (err, questions) {
if (err) {
let output = {
error: {
status: "fail",
name: err.name,
message: err.message,
text: err.toString()
}
};
let statusCode = err.status || 500;
res.json(output);
res.send();
}
else {
questions.map((data) => {
if (!sentTags.includes(data.id)) {
jsonResponse.push(data);
sentTags.push(data.id);
}
});
if (i === user.tags.length - 1) {
res.send(JSON.parse(JSON.stringify(jsonResponse)));
}
}
});
and
Questions.addAnswer(user.username,req.body.answer, req.body.id, function (err) {
if (err) {
let output = {
error: {
status: "fail",
name: err.name,
message: err.message,
text: err.toString()
}
};
let statusCode = err.status || 500;
res.status(statusCode).json(output);
res.send();
}
else {
res.json({status: "success", message: "Answer Added"});
res.send();
}
});
Since MongoDB is eventually consistent, if the Question is saved and then immediately updated, it could be the case that the changes haven't been fully propagated yet.
In Mongoose, try specifying the read concern to 'primary' with this:
Questions.find({tags:interests}).read('p').exec(callback)
How to specify read concerns in Mongoose
MongoDB Read Concern documentation
Depending on the use-case, you may also want to explore the write concerns:
MongoDB Write Concerns

Resources