I am implementing a post upvote system that limits by IP. So far, the route for upvoting a single post contains 4 total queries in order to complete these steps:
Look for an already existing upvote with same PostId and IP and fail if one exists
-otherwise-
Create an upvote
Find the post to associate the upvote with and associate them.
Lastly re-fetch the post to include the upvote that was just associated.
I feel like the last two steps could be combined, however if I just return the post after associating the upvote to it, it is not included which makes sense because when it was found it had no upvote associated. Here is what I currently have and I feel it's very inefficient for a single upvote.
router.get('/posts/:id/upvote', function(req, res) {
var id = req.params.id;
var query_options = {
where: {
id: id
},
include: common_includes
};
// Look for already existing upvote with same PostId and IP.
Upvote.findOne({ where: { ip: req.ip, PostId: id }}).then(function(upvote) {
if (upvote !== null) return res.fail('Already upvoted');
// No upvote exists, create one
Upvote.create({
ip: req.ip
}).then(function(upvote) {
// Find post to associate upvote with
Post.findOne({ where: { id: id }}).then(function(post) {
// Associate upvote to post
upvote.setPost(post).then(function() {
// Query again to get updated post to be returned
Post.findOne(query_options).then(function(post) {
return res.pass(formatPost(post));
}).error(function(err) {
console.log(err);
return res.fail('Server error');
});
}).error(function(err) {
console.log(err);
return res.fail('Server error');
});
}).error(function(err) {
console.log(err);
return res.fail('Server error');
});
}).error(function(err) {
console.log(err);
return res.fail('Server error');
});
});
});
May be helpful http://docs.sequelizejs.com/en/latest/docs/associations/#creating-with-associations .
But IMHO you can combine step 2 and 3 with:
Upvote.create({
ip: req.ip,
PostId: id
})
and then fetch the new post
Related
CODE:
server-side
/**
* List of Articles
*/
exports.list = function (req, res) {
Article.find({ 'user.displayName': 'GIGANTOR !' }).sort('-created').populate('user', 'displayName').exec(function (err, articles) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(articles);
}
});
};
SITUATION:
What I tried above does not work. I checked the mongoose docs: http://mongoosejs.com/docs/queries.html
but can't seem to get the query to work. Currently, the query just returns nothing.
QUESTION:
How to query all articles by a user with a specific displayName ?
TL;DR You can't query a document by a field that belongs to a populated object.
Since article simply has a ref to User, you'll have just get all articles, and then filter them in memory. Or, since the article.user field is an _id, you can find articles by the user ID (but your question is asking about finding them by user.displayName).
Mongoose populate does not do the populating in the MongoDB server itself; it populates on the application server. This means that multiple round-trips to the database are happening (see article Understanding Mongoose Population.) Therefore, you can't query by a field that exists as part of a populated object.
So, here's your 2 solutions:
Article.find({}).sort('-created').populate('user', 'displayName').exec(function (err, articles) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
let filteredArticles = articles
.filter(article => article.user.displayName === 'GIGANTOR !');
res.json(filteredArticles);
}
});
Or, if you can query by _id, you can do this:
Article.find({ user: 'somemongoobjectidofuser' }).sort('-created').populate('user', 'displayName').exec(function (err, articles) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(articles);
}
});
It gets to be a bit hairy and out of scope of the question, but another solution is the aggregation pipeline, which is only usually recommended for backend analytics. But, it'll provide you more flexibility in your query (especially if you user MongoDB's new $graphLookup).
Or, you can always store a copy of the user as a denormalized object inside the article document itself, but then you run into the much-discussed issue of maintaining denormalized documents in-sync.
Just putting the code I ended up using here for people who could need it:
/**
* List of Articles
*/
exports.list = function (req, res) {
Article.find({ user: req.user._id.toString() }).sort('-created').populate('user', 'displayName').exec(function (err, articles) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(articles);
}
});
};
In my node.js application I'm currently implementing a "Remember Me" functionality. Everything works quite well so far, but I have a problem with mongoose. What I want to do: I have a model named Token with this schema:
var TokenSchema = mongoose.Schema({
token: { type: String },
uid: { type: Schema.Types.ObjectId, ref: 'User' }
});
This is simply a little collection that maps cookie tokens to a UserId. Then I have this function here:
function consumeRememberMeToken(token, fn) {
Token
.findOne({ 'token': token }, (err, result) => {
return (result===null)?fn(null, null):fn(null, result.uid);
})
.remove();
}
What it should do, is this: find the uid for a given token string and return it (if there is a result). But this function should also delete the entry right after returning the uid.
At the moment, the uid from the found token result gets returned properly, but it (the result Token) does not get deleted from the collection with the above code. I don't understand how to remove it right after getting it and using the retrieved uid. I'm completely new to functional programming and I don't understand how and where to delete the token.
You can try db.collection.findOneAndDelete It deletes the document and returns the deleted data, quite the reverse of what you are saying but basically serves your purpose. here are the details.
Also here is the mongoose representation of the same.
Token.findOne({ 'token': token }, (err, result) => {
if(err || !result) return fn(err || "error", null);
else{
var uid = result.uid;
result.remove(function(){
return fn(null, uid);
});
}
})
I currently have the following code:
User.find({ featuredMerchant: true })
.lean()
.limit(2)
.exec(function(err, users) {
if (err) {
console.log(err);
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
_.forEach(users, function(user){
_.forEach(user.userListings, function(listing){
Listing.find({
user: user
}).populate('listings', 'displayName merchantName userImageName hasUploadedImage').exec(function(err, listings){
user.listings = listings;
});
});
});
res.jsonp(users);
}
});
As you can see I am trying to add the retrieved listings to each 'user' in the 'users' lean object that I have returned. If I do a console.log(user) inside the Listing.find exec method after adding 'user.listings = listings', the result is as I would expect; a user object with a listings property, with this listing property containing all the listings retrieved.
However, if I console.log the 'users' object, the listings for each user cannot be found.
I'm pretty sure I'm doing something stupid here, but I really cannot work out what. Any help would be greatly appreciated. Thank you!
you right about stupid thing !
No offense, I think this is a common mistake :)
_.forEach(users, function(user){
_.forEach(user.userListings, function(listing){
Listing.find({
user: user
})
.populate('listings', 'displayName merchantName userImageName hasUploadedImage')
.exec(function(err, listings){
user.listings = listings;
});
});
});
// Listing.find inside foreach hasn't finish yet
// I suppose it's always an asynchronous call
res.jsonp(users);
Maybe you can fix it using promises. This an example with q library.
var promises = [];
_.forEach(users, function(user){
_.forEach(user.userListings, function(listing){
var deferred = q.defer();
promises.push(deferred);
Listing.find({
user: user
})
.populate('listings', 'displayName merchantName userImageName hasUploadedImage')
.exec(function(err, listings){
user.listings = listings;
deferred.resolve(user);
});
});
});
q
.all(promises)
.done(function(allUsers){
// Do what you want here with your users
res.jsonp(allUsers);
});
Check this and don't hesitate to fix it because I can't test it.
Thank you both for your input. It's truly appreciated. I managed to solve this an easier way which now I come to think of it is pretty obvious - but hey we live and learn. Basically, my 'userListings' model field was an array of Object Id's and I wanted to add the physical listings from the listings model into the data returned. The following code did the trick for me.
exports.findFeaturedMerchants = function(req, res, next) {
User.find({ featuredMerchant: true })
.populate('userListings')
.limit(2)
.exec(function(err, data) {
_.forEach(data, function(user){
Listing.populate(user.userListings, '', function(err, user){
if (err) {
console.log(err);
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
}
});
});
console.log(data);
res.jsonp(data);
});
};
I simply had to populate each userListings object into the user, by using the populate function twice - once for the user and another time for each listing. I was finding it tricky to get my head around - probably because I didn't understand how the populate function worked exactly, but there we go :)
Jamie
I'm trying to push a message for when a user sends a message to a certain certain Id (within an User model) in a user to user chat messaging system. I can't get it to work, nor do I know how to get started. I have this in my Messaging controller:
sendAndReceiveMsgs: function(req, res) {
if (req.isSocket && req.param('message') && req.param('to')) {
var message = req.param('message'),
from = req.user.id,
to = req.param('to');
Messaging.create({ message: message, from: from, to: to })
.exec(function (err, created) {
console.log('sent');
});
} else if (req.isSocket) {
Messaging.find({ where: { to: req.user.id }, limit: 1, sort: 'id DESC' })
.exec(function(err, messages) {
if(messages.length > 0){
console.log(message.length + " new messages");
} else {
console.log("No new messages");
}
Messaging.subscribe(req.socket, message);
Messaging.watch(req);
Messaging.publishCreate({ id: message[0].id, message: message[0].message });
});
} else if (!req.isSocket) {
return res.notFound();
}
}
However, it doesn't push further new messages to the user (meant for him). Any clue? I really don't understand this, and don't know where to go from here. Thanks!
Please show the client side code too.
One thing I noticed is that you're using subscribe but it's used only to see messages emittted by .publishUpdate(), .publishDestroy(), .publishAdd() and .publishRemove(). Not sure if that helps
You need to use io.socket.on in your client. See the documentation here.
The eventIdentity if your using pub/sub methods are also the model name.
so i have this problem i am working on 'following' feature in my application. What's important, i have two models:
Follows and Notifications
When I hit follow button in front-end I run function from follow.client.controller.js which POSTs to API endpoint /api/follows which corresponds to follow.server.controller.js and then update action on Follows model is performed - easy. AFAIK thats how it works (and it works for me).
But in follows.server.controller.js I want also invoke post to API endpoint at /api/notifications which corresponds to notifications.server.controller.js but I can't find a proper way to do that. Any help will be appreciated.
I don't want another call from front-end to add notification because it should be automatic = if user starts following someone, information is saved in both models at once.
You can add middleware in your server route.
app.route('/api/follows')
.post(notification.firstFunction, follows.secondFunction);
And now add 2 methods in your contollers. First makes the call to db and add's some result's data to request object which will be forwarded to second method.
exports.firstFunction= function(req, res, next) {
Notification.doSometing({
}).exec(function(err, result) {
if (err) return next(err);
req.yourValueToPassForward = result
next(); // <-- important
});
};
exports.secondFunction= function(req, res) {
//...
};
Or you can make few database calls in one api method, joining this calls with promises. Example:
var promise = Meetups.find({ tags: 'javascript' }).select('_id').exec();
promise.then(function (meetups) {
var ids = meetups.map(function (m) {
return m._id;
});
return People.find({ meetups: { $in: ids }).exec();
}).then(function (people) {
if (people.length < 10000) {
throw new Error('Too few people!!!');
} else {
throw new Error('Still need more people!!!');
}
}).then(null, function (err) {
assert.ok(err instanceof Error);
});