How do I update an object from an outer function? - javascript

Struggling with this mongoose function. I'm trying to add an embedded object inside another embedded object, but it is adding the new bid object to the wrong place - the new listings variable I created for my iterator.
My for loops are trying to find the exact Listing to update so I think the action of assigning them is messing them up. For instance if the Listing was users[2].listings[10].bids , how do I get to that object so I can update it?
function create(req, res) {
db.Listing.findById(req.params.listingId, function(err, foundListing) {
// console.log(foundListing._id );
if (err) {
console.log('Error', err);
}
// res.json(foundListing);
// get array of all users
db.User.find({}, function(err, users) {
// for loop iterates through all users' listings
for (let i = 0; i < users.length; i++) {
let listings = users[i].listings
// for loop iterates through all listings ids
for (let j = 0; j < listings.length; j++) {
// finds match
// comparing _id with _id returning false. Not sure why, will return later
if (listings[j].topic === foundListing.topic && listings[j].created === foundListing.created) {
console.log("Found match: " + foundListing.topic);
// get current user id to add to bid object
db.User.findById(req.user, function(err, user) {
if (err) {
console.log(err);
}
var newBid = new db.Bid(req.body); // add data validation later
newBid.uid = user._id
// pushes new bid object into embedded listing
listings[j].bids.push(newBid);
listings[j].save(function(err, savedBid) {
console.log('newBid created: ', newBid);
console.log(listings[j]);
res.json(newBid);
});
});
}
}
}
})
if (err) {
console.log(err);
}
});
};
EDIT - Got this far, but now it doesn't seem like my array is saving.
function create(req, res) {
db.Listing.findById(req.params.listingId, function(err, foundListing) {
if (err) {
console.log('Error:', err);
}
db.User.findOne({ 'listings._id': req.params.listingId }, function(err, foundUser) {
// console.log(foundUser.listings);
if (err) {
console.log('Error: ', err);
}
for (let i = 0; i < foundUser.listings.length; i++) {
// console.log(foundUser.listings[i]._id);
if (foundUser.listings[i]._id == req.params.listingId) {
console.log( 'found it! - ' + foundUser.listings[i].topic);
var newBid = new db.Bid(req.body);
// console.log(newBid)
foundUser.listings[i].bids.push(newBid);
console.log(foundUser.listings[i].bids)
foundUser.listings[i].save(function(err, savedListing) {
// console.log(foundUser.listings[i])
if (err) {
console.log('Error: ', err);
return;
}
console.log('newBid created: ', newBid);
console.log('savedListing', savedListing);
res.json(newBid);
})
}
}
})
});
};

The code you have is pretty hard to grasp. I think you might be using the wrong tools to solve the problem. MongoDb and Mongoose seem to have pretty advanced querying, so that you can specify what exact user, listing etc. you're interested in.
I think it would be worth your time to look a bit at the documentation for Mongoose queries and maybe MongoDb queries as well. The . used for listings.id in db.User.findOne({ 'listings.id': req.params.listingId } is described here.
I can't really test that this code compiles or that it would work, because I don't know enough about your database models and so on, but something along these lines should be possible:
function create(req, res) {
db.Listing.findById(req.params.listingId, function(err, foundListing) {
// use the power of mongo db and mongoose.
// you can specify more exactly what you're looking for
// in this case we're interested in ONE user that
// has a listing with a specific id
// (if you still problems with comparing identical IDs, then that's a serious issue..
// maybe try logging req.params.listingId and check if it really exists
// in your database.)
db.User.findOne({ 'listings.id': req.params.listingId }, function(err, foundUser) {
var newBid = new db.Bid(req.body); // add data validation later
newBid.uid = foundUser._id
// Old code: foundListing.bids.push(newBid)
// Change according to first question/answer at http://mongoosejs.com/docs/faq.html
// which was linked in the thread you linked to in comment.
// The essence is that Mongoose doesn't get
// notified about changes to arrays by default
// To get around this you can update using "set"
// but then you need both a value and an index:
var oldBidsLength = foundListing.bids.length;
foundListing.bids.set(oldBidsLength, newBid);
foundListing.save(function(err, savedBid) {
// savedBid = saved LISTING?
console.log('newBid created: ', newBid);
console.log('savedBid', savedBid);
res.json(savedBid);
}
}
})
});
};
Things that may be off with this code example is things like if db.User.findOne({ 'listings.id': req.params.listingId } should be used with listings._id. I don't know enough about your models.

Related

Best way to turn JSON change into event

I'm creating a YouTube upload notification bot for a Discord Server I am in using the YouTube RSS Feed and am having problems with it. I have issues with the bot sending the same video twice even though I've tried everything to fix it. The bot cycles through different users in a for loop and checks the user's latest video's ID with one stored in a JSON file. If they do not match, it sends a message and updates the JSON. Here is my current code:
function update(videoId, n) {
var u = JSON.parse(fs.readFileSync("./jsons/uploads.json"))
u[n].id = videoId
fs.writeFile("./jsons/uploads.json", JSON.stringify(u, null, 2), (err) => {
if (err) throw err;
// client.channels.cache.get("776895633033396284").send()
console.log('Hey, Listen! ' + n + ' just released a new video! Go watch it: https://youtu.be/' + videoId + "\n\n")
});
}
async function uploadHandler() {
try {
var u = require('./jsons/uploads.json');
var users = require('./jsons/users.json');
for (i = 0; i < Object.keys(users).length; i++) {
// sleep(1000)
setTimeout(function(i) {
var username = Object.keys(users)[i]
let xml = f("https://www.youtube.com/feeds/videos.xml?channel_id=" + users[username]).text()
parseString(xml, function(err, result) {
if (err) {} else {
let videoId = result.feed.entry[0]["yt:videoId"][0]
let isMatch = u[username].id == videoId ? true : false
if (isMatch) {} else {
if (!isMatch) {
u[username] = videoId
update(videoId, username)
}
}
}
});
}, i * 1000, i)
}
} catch (e) {
console.log(e)
}
}
My code is rather simple but I've had the same issue with other codes that use this method; therefore what would be the best way to accomplish this? Any advice is appreciated
There are a few issues with your code that I would call out right off the bat:
Empty blocks. You use this especially with your if statements, e.g. if (condition) {} else { // Do the thing }. Instead, you should negate the condition, e.g. if (!condition) { // Do the thing }.
You declare the function uploadHandler as async, but you never declare that you're doing anything asynchronously. I'm suspecting that f is your asynchronous Promise that you're trying to handle.
You've linked the duration of the timeout to your incrementing variable, so in the first run of your for block, the timeout will wait zero seconds (i is 0, times 1000), then one second, then two seconds, then three...
Here's a swag at a refactor with some notes that I hope are helpful in there:
// Only require these values once
const u = require('./jsons/uploads.json');
const users = require('./jsons/users.json');
// This just makes the code a little more readable, I think
const URL_BASE = 'https://www.youtube.com/feeds/videos.xml?channel_id=';
function uploadHandler() {
Object.keys(users).forEach(username => {
// We will run this code once for each username that we find in users
// I am assuming `f` is a Promise. When it resolves, we'll have xml available to us in the .then method
f(`${URL_BASE}${username}`).then(xml => {
parseString(xml, (err, result) => {
if (!err) {
const [videoId] = result.feed.entry[0]['yt:videoId']; // We can use destructuring to get element 0 from this nested value
if (videoId !== u[username].id) {
// Update the in-memory value for this user's most recent video
u[username].id = videoId;
// Console.log the update
console.log(`Hey listen! ${username} just released a new video! Go watch it: https://youtu.be/${videoId}\n\n`);
// Attempt to update the json file; this won't affect the u object in memory, but will keep your app up to date
// when you restart it in the future.
fs.writeFile('./jsons/uploads.json', JSON.stringify(u, null, 2), err => {
if (err) {
console.err(`There was a problem updating uploads.json with the new videoId ${videoId} for user ${username}`);
}
});
}
}
});
})
// This .catch method will run if the call made by `f` fails for any reason
.catch(err => console.error(err));
});
}
// I am assuming that what you want is to check for updates once every second.
setInterval(uploadHandler, 1000);

Node.js calling a MySQL stored procedure multiple times in a loop

I'm writing an application that lets you add visits (visit date, visit type, notes) to a case, via an Ajax POST call from a form. The visit creation functionality lets you add the same visit type and notes on several dates. So I end up with a visit object that has an array of dates in it, but the same notes and visit type. Because SQL isn't where I should be doing any looping, I want to do it in Node as I'll be able to handle any failures in the array or results returned from the individual SQL calls.
I tried setting up the procedure call so that it took an array of arrays in an array as a parameter as per here, but I couldn't get it to work, so am falling back to looping through.
The issue I'm having is with callbacks completing before I've got any results. Obviously it's because I don't understand callbacks enough and no amount of reading is making it any clearer, so I've ended up here to ask for help.
Below is the code that is executed. The visit object that is the parameter of the insertVisit function is the class as mentioned above with the array of dates.
this.insertVisit = function (req, res, visit)
{
var insertVisit = new Visit();
insertVisit = visit;
var success = 0;
var visitId = 0;
//Split the visits into an array of individual dates
var allVisits = insertVisit.visitDates.split(',');
//Attemp to call insertVisits using a callback
insertVisits(0, function(err){
if( err ) {
console.log('yeah, that insert didnt work: '+ err)
}
});
console.log('finished');
function insertVisits(v)
{
//Loop through all of the visits
if (v < allVisits.length )
{
//Attempt to call the next function
singleDate(allVisits[v], function(err)
{
if(err)
{
console.log(err);
}
else
{
//if everything is successful, insert the next individual date
allVisits[v + 1];
}
})
}
}
function singleDate(singleVisitDate)
{
var query = 'CALL aau.sp_InsertVisit (?,?,?,?,?,?,?,#visitId,#success); SELECT #visitId, #success;';
var parts = singleVisitDate.split('-');
var formattedDate = new Date(parts[2], parts[1] - 1, parts[0]);
connection.init();
//Everything runs fine up to here, but as soon as we go to the next line, the program
//continues back at the end of the loop in the insertVisits function an exits the function.
//At this point the below code executes asynchronously and inserts one of the dates before returning
//and doesn't call any further dates.
connection.acquire(function (err, con)
{
con.query(query,
[
insertVisit.caseId,
formattedDate,
parseInt(insertVisit.visitTypeId),
parseInt(insertVisit.visitStatusId),
insertVisit.adminNotes,
insertVisit.operatorNotes,
insertVisit.isDeleted,
visitId,
success
]
, function (err, result)
{
if(err)
{
console.log(err);
}
else
{
con.release();
res.write(JSON.stringify(result));
}
})
})
}
So I'm trying to loop through each of the dates and call the stored procedure for each date and add the results to response using res.write.
This is a brand new project, so happy to rewrite it with promises or asynch/await. But any examples would be greatly appreciated of looping through multiple procedure calls
Ok,
So I looked at using async.eachSeries and managed to get it to work when I put the callback at the bottom of the 'tree'.
Hopefully this can be helpful to anyone else trying to run the same proc multiple times.
this.insertVisit = function (req, res, visit)
{
var insertVisit = new Visit();
insertVisit = visit;
var success = 0;
var visitId = 0;
var allVisits = insertVisit.visitDates.split(',');
async.eachSeries(allVisits, function(singleVisitDate, callback)
{
var query = 'CALL aau.sp_InsertVisit (?,?,?,?,?,?,?,#visitId,#success); SELECT #visitId, #success;';
var parts = singleVisitDate.split('-');
var formattedDate = new Date(parts[2], parts[1] - 1, parts[0]);
connection.init();
connection.acquire(function (err, con)
{
con.query(query,
[
insertVisit.caseId,
formattedDate,
parseInt(insertVisit.visitTypeId),
parseInt(insertVisit.visitStatusId),
insertVisit.adminNotes,
insertVisit.operatorNotes,
insertVisit.isDeleted,
visitId,
success
]
, function (err, result)
{
if(err)
{
console.log(err);
}
else
{
con.release();
res.write(JSON.stringify(result));
callback();
}
})
})
},
function(err)
{
if(err)
{
console.log(err);
}
else
{
res.end();
}
});

Firebase using JS - Struggling to update value if exists or add if it doesnt

Bit confused why this won't work.
const person = {};
person[this.name] = 0;
const personQuery = `person/${this.name}`;
firebase.child(personQuery).on('value', (snapshot) => {
if (snapshot.exists()) {
// Update
console.log('exists');
} else {
console.log('creating');
firebase.child('person').set(person, (err) => {
if (err) {
throw err;
}
});
}
this.name = '';
});
My data looks like this if I get rid of the updating logic and just have the setting logic:
Bill: 0,
John: 0,
Beth: 0
etc
If the record exists it doesn't have a problem (since I'm not even doing the update yet) but if it doesn't it just goes into a crazy loop and I end up with a single value in my table at the end.
I think I'm misinterpreting how to structure my data.
I basically want to make a super simple app with a key value pair of people, each person has 1 single number and if the person already exists then increment the value by one.
Can someone help me out with understanding how to create the structure and query for it to make the updates?
You're looking for once(). Since you only want to check if the person node currently exists and don't want to keep synchronizing it. So:
firebase.child(personQuery).once('value', (snapshot) => {
if (snapshot.exists()) {
// Update
console.log('exists');
} else {
console.log('creating');
firebase.child('person').set(person, (err) => {
if (err) {
throw err;
}
});
}
this.name = '';
});
But in general this sort of scenario calls for use of a transaction. With a transaction you get the current value and specify the new value atomically. This removes a race condition that exists in your current code.

Make multiple request in Mongoose

I'm trying to reach different select's from another select with MongoDB database with mongoose to redirect to Emberjs front-end.
If the text above it's not clear, look at the schema of the database:
// I already get the exam content given an id
Exam:{
...
collections:[{
question: Object(Id)
}]
...
}
and in the question schema it's:
// I want to get content of this giving an id
question:{
...
questions: String, // I want to get this given an Id exam
value: Number // and this
...
}
I tried to get it getting the objects id of the collections and then make a for to extract each question, and save the returned values into a json variable like this:
Test.findById(id, 'collections', function(err, collections){
if(err)
{
res.send(err);
}
var result ={}; //json variable for the response
// the for it's with the number of objectId's that had been found
for(var i = 0; i < collections.collections.length; i++)
{
// Send the request's to the database
QuestionGroup.findById(collections.collections[i].id, 'questions').exec(function(err, questiongroup)
{
if(err)
{
res.send(err);
}
// save the results in the json
result.questiongroup = questiongroup;
// prints questions
console.log(result);
});
// it return's {}
console.log(result);
}
// also return {}
console.log(result);
res.json({result: result});
});
It's there a way to save the requests into a variable and return it like a json to the front end?
since the query within the loop executes in async manner you'll have send response once everything finished execution.
For eg.
Test.findById(id, 'collections', function(err, collections) {
if (err) {
res.send(err);
}
var result = []; //json variable for the response
function done(result) {
res.json({
result
});
}
for (var i = 0, l = collections.collections.length; i < l; i++) {
// i need needs to be in private scope
(function(i) {
QuestionGroup.findById(collections.collections[i].id, 'questions').exec(function(err, questiongroup) {
if (err) {
res.send(err);
}
// save the results in the json
result.push(questiongroup);
if (l === i + 1) {
done(result);
}
});
})(i);
}
});
NOTE: untested, and you might have to handle errors appropriately

How to use 64-bit long auto-increment counters in MongoDB

I'm implementing a logger database using MongoDB. The capped collection will contain log messages collected from several sources across the network. Since I want to do $lte/$gte queries on _id afterwards I need to have an _id that grows as a monotonic function.
To achieve that I've implemented the auto-incremented counter described in this article http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/
My code looks like that:
var mongo = require("mongodb");
var Promise = require('es6-promise').Promise;
function connectToDB(mongo, uri) {
return new Promise(function(resolve, reject) {
mongo.MongoClient.connect(uri, function (err, db) {
if(err) reject(err);
else resolve(db);
});
});
}
function getNextSequenceNumber(db, counterName) {
return new Promise(function(resolve, reject) {
db.collection("counters", function (err, collection) {
if (err) reject(err);
else {
var criteria = { _id: counterName };
var sort = {$natural: 1};
var update = { $inc: { seq: 1 } };
var options = {remove: false, new: true, upsert: true};
collection.findAndModify(criteria, sort, update, options, function(err, res) {
if(err) reject(err);
else resolve(res.seq);
});
}
});
});
}
It works perfectly fine, but I've read that by default the number fields used in MongoDB are actually floats. The problem is that my database is a capped collection of log entries and it is going to have lots of entries. Moreover, since this is a capped collection the old entries will be overwritten but the counter will keep growing. Having counter as a float I cannot guarantee the system will keep on working after a few years.
My question is how can I force MongoDB to use 64 bit counter in this particular case.
Please provide some code examples.
MongoDB (or rather BSON) has the NumberLong type, which is a 64-bit signed integer.
From Node.js you can use it in your update statement to create the seq property of that type:
var update = { $inc : { seq : mongo.Long(1) } };
This also seems to convert the seq property of existing documents to NumberLong.

Categories

Resources