Mongoose findOneAndUpdate not working if I dont specify the field to update - mongodb

I currently have the following mongoose function in a hapi.js api call
server.route({
method: "PUT",
path:"/api/blockinfo/{hash}",
handler: async (request, h) => {
try{
var jsonPayload = JSON.parse(request.payload)
console.log(jsonPayload)
var result = await BlockModel.findOneAndUpdate(request.params.hash, {$set: { height : jsonPayload[Object.keys(jsonPayload)[0]]}}, {new: true});
return h.response(result);
}catch{
return h.response(error).code(500);
}
}
})
Its goal is basically to update a value using a PUT. In the case above, it will update the field height, and it will work just fine.
But what if I want to update an arbitrary field?
For example my object format is the following:
{"_id":"5cca9f15b1b535292eb4e468", "hash":"d6e0fdb404cb9779a34894b4809f492f1390216ef9d2dc0f2ec91f95cbfa89c9", "height":301651, "size":883, "time":1556782336, "__v":0}
In the case above I updated the height value using the $set, but what if I decide to input 2 random fields to update, for example, size and time.
This would be my put in postman:
{
"size": 300,
"time": 2
}
Well obviously it wont work in the code above because those fields are missing in the set.
SO how do i make that set to recognize automatically whatever it needs to update?
I tried to simplify it with the following code but it wont update anything
server.route({
method: "PUT",
path:"/api/blockinfo/{hash}",
handler: async (request, h) => {
try{
var result = await BlockModel.findOneAndUpdate(request.params.hash, request.payload, {new: true});
return h.response(result);
}catch{
return h.response(error).code(500);
}
}
})
Schema
const BlockModel = Mongoose.model("blocks", {
hash: String,
height: Number,
size: Number,
time: Number
});

Problem is with your hash key. First parameter/argument in findOneAndUpdate function should be the key value pair. And here you are directly putting the key.
So it should be
handler: async (request, h) => {
try {
const { hash } = request.params
var result = await BlockModel.findOneAndUpdate({ hash }, request.payload, { new: true })
return h.response(result)
} catch (err) {
return h.response(error).code(500)
}
}
Update:
You are defining mongoose model in incorrect way. Schema is not just an object. It should be mongoose object. Something like this
const schema = new Mongoose.Schema({
hash: String,
height: Number,
size: Number,
time: Number
})
export default Mongoose.model("blocks", schema)

handler: async (request, h) => {
try{
var result = await BlockModel.findOneAndUpdate(request.params.hash, JSON.parse(request.payload ), {new: true});
return h.response(result);
}catch{
return h.response(error).code(500);
}
}
SInce we are updating a JSON, the payload must be in JSON format

You have not added $set in your simplified code, adding that it should work.
Send payload as an object with the required fields.
server.route({
method: "PUT",
path:"/api/blockinfo/{hash}",
handler: async (request, h) => {
try{
var result = await BlockModel.findOneAndUpdate(request.params.hash, { $set: request.payload } , {new: true});
return h.response(result);
}catch{
return h.response(error).code(500);
}
}
})

Related

Mongoose Error stating that it cannot populate a path because its not in my schema - but im not populating anything?

I have two routes in my server. Both routes are identical minus one using $push and one using $pull. The $pull route works as it should but always responds with an error of
"MongooseError: Cannot populate path scenes because it is not in
your schema. Set the strictPopulate option to false to override."
while the $push route works perfect and responds accordingly. I am very confused as to why I am getting a populate error considering I am not populating? I do have a 'scenes' property but not in either Model being used in these routes.
router.put('/scene/:sceneId/addactor/:actorId', async (req, res) => {
const actorId = req.params.actorId;
const sceneId = req.params.sceneId;
try {
const actor = await Actor.findById(actorId);
await Scene.findByIdAndUpdate(sceneId, { $push: { actors: actor } }, { new: true }).then(dbSceneData => {
res.status(200).send(dbSceneData);
})
} catch (error) {
res.status(400).send(`An Error Ocurred: ${error}`);
}
});
router.put('/scene/:sceneId/removeactor/:actorId', async (req, res) => {
const actorId = req.params.actorId;
const sceneId = req.params.sceneId;
try {
const actor = await Actor.findById(actorId);
await Scene.findByIdAndUpdate(sceneId, { $pull: { actors: actor } }, { new: true }).then(dbSceneData => {
res.status(200).send(dbSceneData);
})
} catch (error) {
res.status(400).send(`An Error Ocurred: ${error}`);
}
});

Updating sub document using save() method in mongoose does not get saved in database and shows no error

I have a Mongoose model like this:
const centerSchema = mongoose.Schema({
centerName: {
type: String,
required: true,
},
candidates: [
{
candidateName: String,
voteReceived: {
type: Number,
default: 0,
},
candidateQR: {
type: String,
default: null,
},
},
],
totalVote: {
type: Number,
default: 0,
},
centerQR: String,
});
I have a Node.JS controller function like this:
exports.createCenter = async (req, res, next) => {
const newCenter = await Center.create(req.body);
newCenter.candidates.forEach(async (candidate, i) => {
const candidateQRGen = await promisify(qrCode.toDataURL)(
candidate._id.toString()
);
candidate.candidateQR = candidateQRGen;
// ** Tried these: **
// newCenter.markModified("candidates." + i);
// candidate.markModified("candidateQR");
});
// * Also tried this *
// newCenter.markModified("candidates");
const upDatedCenter = await newCenter.save();
res.status(201).json(upDatedCenter);
};
Simply, I want to modify the candidateQR field on the subdocument. The result should be like this:
{
"centerName": "Omuk Center",
"candidates": [
{
"candidateName": "A",
"voteReceived": 0,
"candidateQR": "some random qr code text",
"_id": "624433fc5bd40f70a4fda276"
},
{
"candidateName": "B",
"voteReceived": 0,
"candidateQR": "some random qr code text",
"_id": "624433fc5bd40f70a4fda277"
},
{
"candidateName": "C",
"voteReceived": 0,
"candidateQR": "some random qr code text",
"_id": "624433fc5bd40f70a4fda278"
}
],
"totalVote": 0,
"_id": "624433fc5bd40f70a4fda275",
"__v": 1,
}
But I am getting the candidateQR still as null in the Database. I tried markModified() method. But that didn't help (showed in the comment section in the code above). I didn't get any error message. In response I get the expected result. But that result is not being saved on the database. I just want candidateQR field to be changed. But couldn't figure out how.
forEach loop was the culprit here. After replacing the forEach with for...of it solved the issue. Basically, forEach takes a callback function which is marked as async in the codebase which returns a Promise initially and gets executed later.
As for...of doesn't take any callback function so the await inside of it falls under the controller function's scope and gets executed immediately. Thanks to Indraraj26 for pointing this out. So, the final working version of the controller would be like this:
exports.createCenter = async (req, res, next) => {
const newCenter = await Center.create(req.body);
for(const candidate of newCenter.candidates) {
const candidateQRGen = await promisify(qrCode.toDataURL)(
candidate._id.toString()
);
candidate.candidateQR = candidateQRGen;
};
newCenter.markModified("candidates");
const upDatedCenter = await newCenter.save();
res.status(201).json(upDatedCenter);
};
Also, shoutout to Moniruzzaman Dipto for showing a different approach to solve the issue using async.eachSeries() method.
You can use eachSeries instead of the forEach loop.
const async = require("async");
exports.createCenter = async (req, res, next) => {
const newCenter = await Center.create(req.body);
async.eachSeries(newCenter.candidates, async (candidate, done) => {
const candidateQRGen = await promisify(qrCode.toDataURL)(
candidate._id.toString(),
);
candidate.candidateQR = candidateQRGen;
newCenter.markModified("candidates");
await newCenter.save(done);
});
res.status(201).json(newCenter);
};
As far as I understand, you are just looping through the candidates array but you
are not storing the updated array. You need to store the updated data in a variable as well. Please give it a try with the solution below using map.
exports.createCenter = async (req, res, next) => {
const newCenter = await Center.create(req.body);
let candidates = newCenter.candidates;
candidates = candidates.map(candidate => {
const candidateQRGen = await promisify(qrCode.toDataURL)(
candidate._id.toString()
);
return {
...candidate,
candidateQR: candidateQRGen
}
});
newCenter.candidates = candidates;
const upDatedCenter = await newCenter.save();
res.status(201).json(upDatedCenter);
};
You can use this before save()
newCenter.markModified('candidates');

How to check if value already exists in the data received from api before inserting it into db

I am having hard times trying to write data received from a api to db.
I successfully got data and then have to write it to db. The point is to check whether the quote is already exists in my collection.
The problem I am dealing with is that every value gets inserted in my collection, not regarding if it exists or not.
const { MongoClient } = require('mongodb')
const mongoUrl = 'mongodb://localhost/kanye_quotes'
async function connectToDb() {
const client = new MongoClient(mongoUrl, { useNewUrlParser: true })
await client.connect()
db = client.db()
}
async function addQuote(data) {
await connectToDb()
try {
const collection = db.collection('quotes')
let quotes = [];
quotes = await collection.find({}).toArray()
if (quotes = []) { // I added this piece of code because if not check for [], no values will be inserted
collection.insertOne(data, (err, result) => {
if (err) {
return
}
console.log(result.insertedId);
return
})
}
quotes.forEach(quote => {
if (quote.quote !== data.quote) { // I compare received data with data in collection, it actually works fine(the comparison works as it supposed to)
collection.insertOne(data, (err, result) => {
if (err) {
return
}
console.log(result.insertedId);
})
} else console.log('repeated value found'); // repeated value gets inserted. Why?
})
}
catch (err) {
console.log(err)
}
}
Hi it's probably better to set unique: true indexing on your schema. That way you won't have duplicated values.

How to convert from get.JSON to fetch

I have this working fine with get.JSON but when I try and use the fetch API instead, it gives me the error "Required parameter: part".
export const fetchYoutube = () => {
return dispatch => {
fetchAsync()
.then(data => console.log(data))
.catch(reason => console.log(reason.message))
dispatch({
type: INCREMENT
})
}
}
async function fetchAsync () {
var query = {
part: 'snippet',
key: 'AIzaSyA3IHL73MF00WFjgxdwzg57nI1CwW4dybQ',
maxResults: 6,
type: 'video',
q: 'music'
}
let response = await fetch('https://www.googleapis.com/youtube/v3/search', {
data : query,
method: 'GET'
});
let data = await response.json();
return data;
}
How do I pass the query object using the fetch API?
Try attaching the query as params:
replace:
let response = await fetch('https://www.googleapis.com/youtube/v3/search', {
data : query,
method: 'GET'
});
with:
var url = new URL("https://www.googleapis.com/youtube/v3/search"),
query = {
part: 'snippet',
key: '#####################################',
maxResults: 6,
type: 'video',
q: 'music'
}
Object.keys(query).forEach(key => url.searchParams.append(key, query[key]))
let response = await fetch(url)
Setting query string using Fetch GET request

Using $inc to increment a document property with Mongoose

I would like to increment the views count by 1 each time my document is accessed. So far, my code is:
Document
.find({})
.sort('date', -1)
.limit(limit)
.exec();
Where does $inc fit in here?
Never used mongoose but quickly looking over the docs here it seems like this will work for you:
# create query conditions and update variables
var conditions = { },
update = { $inc: { views: 1 }};
# update documents matching condition
Model.update(conditions, update).limit(limit).sort('date', -1).exec();
Cheers and good luck!
I ran into another problem, which is kind of related to $inc.. So I'll post it here as it might help somebody else. I have the following code:
var Schema = require('models/schema.js');
var exports = module.exports = {};
exports.increase = function(id, key, amount, callback){
Schema.findByIdAndUpdate(id, { $inc: { key: amount }}, function(err, data){
//error handling
}
}
from a different module I would call something like
var saver = require('./saver.js');
saver.increase('555f49f1f9e81ecaf14f4748', 'counter', 1, function(err,data){
//error handling
}
However, this would not increase the desired counter. Apparently it is not allowed to directly pass the key into the update object. This has something to do with the syntax for string literals in object field names. The solution was to define the update object like this:
exports.increase = function(id, key, amount, callback){
var update = {};
update['$inc'] = {};
update['$inc'][key] = amount;
Schema.findByIdAndUpdate(id, update, function(err, data){
//error handling
}
}
Works for me (mongoose 5.7)
blogRouter.put("/:id", async (request, response) => {
try {
const updatedBlog = await Blog.findByIdAndUpdate(
request.params.id,
{
$inc: { likes: 1 }
},
{ new: true } //to return the new document
);
response.json(updatedBlog);
} catch (error) {
response.status(400).end();
}
});