Passing two query results into a response - node.js

I have a query that fetches the top 5 people for a leaderboard. In robomongo this query works fine.
When I do something like
var leaderboard = User.find({points: {$exists: true}}, {
"firstname": 1,
"lastname": 1,
"points": 1
}).sort({points : -1}).limit(5)
console.log('leaderboard');
I get a lot of meaningless json with [object] almost everywhere.
How would I execute this query for use with mongoose + express so I can pass through to the view as an array of
firstname, lastname, points
So I can loop it through in the view?
My complete code is
app.get('/dashboard', function(req, res){
if (req.user) {
// logged in
User.find({}, function(err, docs) {
// console.log(docs);
});
// Get total points after submit
var leaderboard = User.find({points: {$exists: true}}, {
"firstname": 1,
"lastname": 1,
"points": 1
}).sort({points : -1}).limit(5).toArray();
console.log(leaderboard);
User.find({
points: {
$exists: true
}
}, function(err, docs) {
if(err){
console.log(err);
//do error handling
}
//if no error, get the count and render it
var count = 0;
for (var i = 0; i < docs.length; i++) {
count += docs[i].points;
}
var totalpoints = count;
res.render('dashboard', {
title: 'Dashboard',
user: req.user,
totalpoints: totalpoints
});
});
} else {
// not logged in
return res.redirect('/login');
}
});

So you are really only returning a cursor here and not executing the query. You can of course always nest the queries, but you can be a bit nicer and use async.waterfall to avoid the indenting mess.
Also I would use .aggregate() rather than looping all the documents just to get a total. And mongoose automatically converts results to an array, so the .toArray() is not required here:
app.get('/dashboard', function(req, res){
if (req.user) {
// logged in
async.waterfall(
[
function(callback) {
User.find(
{ "points": { "$exists": true } },
{
"firstname": 1,
"lastname": 1,
"points": 1
}
).sort({points : -1}).limit(5).exec(callback);
},
function(leaderboard,callback) {
User.aggregate(
[
{ "$match": { "points": { "$exists": true } }},
{ "$group": {
"_id": null,
"totalpoints": { "$sum": "$points" }
}}
],
function(err,result) {
callback(err,result,leaderboard)
}
);
}
],
function(err,result,leaderboard) {
if (err) {
console.log(err);
//do error handling
} else {
res.render('dashboard', {
title: 'Dashboard',
user: req.user,
totalpoints: result[0].totalpoints,
leaderboard: leaderboard
});
}
}
);
} else {
// not logged in
return res.redirect('/login');
}
});
So you get your leaderboard result and just put it in the response, much as is done in the example here.
An alternate approach is using async.parallel, since you don't need the output of the first query within the second. In that case the results of both queries are sent to the callback at the end, much like above. Again you just use the results in your final response.
app.get('/dashboard', function(req, res){
if (req.user) {
// logged in
async.parallel(
{
"leaderboard": function(callback) {
User.find(
{ "points": { "$exists": true } },
{
"firstname": 1,
"lastname": 1,
"points": 1
}
).sort({points : -1}).limit(5).exec(callback);
},
"totalpoints":function(callback) {
User.aggregate(
[
{ "$match": { "points": { "$exists": true } }},
{ "$group": {
"_id": null,
"totalpoints": { "$sum": "$points" }
}}
],
function(err,result) {
callback(err,result[0].totalpoints)
}
);
}
},
function(err,results) {
if (err) {
console.log(err);
//do error handling
} else {
res.render('dashboard', {
title: 'Dashboard',
user: req.user,
totalpoints: results.totalpoints,
leaderboard: results.leaderboard
});
}
}
);
} else {
// not logged in
return res.redirect('/login');
}
});

Related

Mongoose updateMany by id using Node

is it possible to update array of object by id? ex.:
This is my array:
[
{
"_id": "5fdb614d686e671eb834a409",
"order": 1,
"title": "first"
},
{
"_id": "5fdb61c0686e671eb834a41e",
"order": 2,
"title": "second"
},
{
"_id": "5fdb61d6686e671eb834a424",
"order": 3,
"title": "last"
}
]
and I would like to change only the order of each by ID. I am using Node and I tried to do like that:
router.post("/edit-order", auth, async (req, res) => {
try {
const sections = await Section.updateMany(
req.body.map((item) => {
return { _id: item._id }, { $set: { order: item.order } };
})
);
res.json(sections);
} catch (e) {
res.status(500).json({ message: "Something went wrong in /edit-order" });
}
});
my request body is:
[
{
"_id": "5fdb614d686e671eb834a409",
"order": 2
},
{
"_id": "5fdb61c0686e671eb834a41e",
"order": 3
},
{
"_id": "5fdb61d6686e671eb834a424",
"order": 4
}
]
but as a result, I got:
[
{
"_id": "5fdb614d686e671eb834a409",
"order": 4,
"title": "first"
},
{
"_id": "5fdb61c0686e671eb834a41e",
"order": 4,
"title": "second"
},
{
"_id": "5fdb61d6686e671eb834a424",
"order": 4,
"title": "last"
}
]
so, it change every order by the last value of request array. Any ideas how could I manage that. If you know any other solution feel free to share, all what I need is to change order only by id.
Well, since you have a different value of order for each item, you'll need to do a bulkWrite.
router.post('/edit-order', auth, async (req, res) => {
try {
const writeOperations = req.body.map((item) => {
return {
updateOne: {
filter: { _id: item._id },
update: { order: item.order }
}
};
});
await Section.bulkWrite(writeOperations);
res.json(req.body);
} catch (e) {
res.status(500).json({ message: 'Something went wrong in /edit-order' });
}
});
If you had a single value of order to all the items, you could've used updateMany along with $in.
router.post('/edit-order', auth, async (req, res) => {
try {
const sectionsIds = req.body.map((item) => {
return item._id;
});
const sections = await Section.updateMany(
{ _id: { $in: sectionsIds } },
{ order: 'A single value for all sections in body' }
);
res.json(sections);
} catch (e) {
res.status(500).json({ message: 'Something went wrong in /edit-order' });
}
});

How to project a new boolean field in Mongoose if another property is listed in existing array?

Consider the query in Mongoose :
let StudentCodes = .... // getting this from somewhere
await Students.aggregate(
[
{
$project: {
StudentCODE: "$StudentCODE",
StudName: "$StudName",
StudProfileDesc: "$StudProfileDesc",
IsReviewed: {
$cond: [{ $eq: [StudentCodes, "$StudentCODE"] }, 1, 0]
}
}
}
],
function(err, results) {
if (err) {
console.log(err);
}
console.log(results);
return res.status(200).json(results);
}
);
How can We project IsReviewed as true or false if the property StudentCODE exists in the array StudentCodes ?
Try as below, you can use $in in $cond to do that :
let StudentCodes = .... // getting this from somewhere
await Students.aggregate(
[
{
$project: {
StudentCODE: "$StudentCODE",
StudName: "$StudName",
StudProfileDesc: "$StudProfileDesc",
IsReviewed: {
$cond: [{ $in: ["$StudentCODE", StudentCodes] }, true, false]
}
}
}
],
function (err, results) {
if (err) {
console.log(err);
}
console.log(results);
return res.status(200).json(results);
}
);

Update many documents in mongoDB with different values

I am trying to update two documents in mongoDB, with two different values. I made it with two different callbacks, but is it possible to do it with only one request?
My solution:
mongo.financeCollection.update(
{ 'reference': 10 },
{ $push:
{ history: history1 }
}, function (err){
if (err){
callback (err);
}
else {
mongo.financeCollection.update(
{ 'reference': 20 },
{ $push:
{ history: history2 }
}, function (err){
if (err){
callback(err);
}
else {
callback(null);
}
});
}
});
Sorry if it is a stupid question but I just want to optimize my code!
Best to do this update using the bulkWrite API. Consider the following example for the above two documents:
var bulkUpdateOps = [
{
"updateOne": {
"filter": { "reference": 10 },
"update": { "$push": { "history": history1 } }
}
},
{
"updateOne": {
"filter": { "reference": 20 },
"update": { "$push": { "history": history2 } }
}
}
];
mongo.financeCollection.bulkWrite(bulkUpdateOps,
{"ordered": true, "w": 1}, function(err, result) {
// do something with result
callback(err);
}
The {"ordered": true, "w": 1} ensures that the documents will be updated on the server serially, in the order provided and thus if an error occurs all remaining updates are aborted. The {"w": 1} option determines the write concern with 1 being a request acknowledgement that the write operation has propagated to the standalone mongod or the primary in a replica set.
For MongoDB >= 2.6 and <= 3.0, use the Bulk Opeartions API as follows:
var bulkUpdateOps = mongo.financeCollection.initializeOrderedBulkOp();
bulkUpdateOps
.find({ "reference": 10 })
.updateOne({
"$push": { "history": history1 }
});
bulkUpdateOps
.find({ "reference": 20 })
.updateOne({
"$push": { "history": history2 }
});
bulk.execute(function(err, result){
bulkUpdateOps = mongo.financeCollection.initializeOrderedBulkOp();
// do something with result
callback(err);
});

Mongodb node.js sum of key values of all the document in the collection

this is my cart collection which is having a price key. In my node.js code I want to view sum of price of both the documents. I tried using aggregate but didnt work
cart collection
[
{
"_id": "57244d0a05dcf1d7151ede7f",
"art_id": "57244c9505dcf1d7151ede7c",
"artist_id": "5721a528c9d28cd51f014038",
"user_id": "5721a528c9d28cd51f014038",
"price": "90"
},
{
"_id": "57244d1f05dcf1d7151ede80",
"art_id": "57244c6105dcf1d7151ede7b",
"artist_id": "5721a528c9d28cd51f014038",
"user_id": "5721a528c9d28cd51f014038",
"price": "150"
}
]
node.js code
function test(req, res, next) {
db.users.findOne({
_id: mongoskin.helper.toObjectID(req.session.user._id)
}, function(err, user) {
if (!user) {
return res.status(400).send({
status: '404 user not found'
});
}
db.cart.find({
user_id: req.session.user._id
}).toArray(function(err, result) {
if (err) return next(err);
res.send(result)
});
});
}
to add all price can use aggregate like:
db.cart.aggregate(
[
{
$group : {
_id : null,
totalPrice: { $sum: price }
}
}
]
).exec(function(error, result) {
if (err) return next(err);
res.send(result)
});

What am I doing wrong trying to make this run with meteor?

I am trying to adapt the full text search made here to work with meteor. I exported the mongodb url to one running 2.6.1. to make full text search compatible but I am getting these errors server/search.js:2:15: Unexpected token .andserver/search.js:42:7: Unexpected token ). What am I missing?
server.js
Meteor.methods({
Meteor.ensureIndex("Posts", {
smaintext: "text"
}, function(err, indexname) {
assert.equal(null, err);
});
)
};
Meteor.methods({
feedupdate: function(req) {
Posts.find({
"$text": {
"$search": req
}
}, {
smaintext: 1,
submitted: 1,
_id: 1,
Posts: {
$meta: "Posts"
}
}, {
sort: {
textScore: {
$meta: "posts"
}
}
}).toArray(function(err, items) {
for (e=0;e<101;e++) {
Meteor.users.update({
"_id": this.userId
}, {
"$addToSet": {
"profile.search": item[e]._id
}
});
}
})
}
)
};
This is wrong definition of method
Meteor.methods({
Meteor.ensureIndex("Posts", {
smaintext: "text"
}, function(err, indexname) {
assert.equal(null, err);
});
)
};
you must specify a method name ( http://docs.meteor.com/#/basic/Meteor-methods )
So it will be something like this
Meteor.methods({
myMethodName : function() { Meteor.ensureIndex("Posts", {
smaintext: "text"
}, function(err, indexname) {
assert.equal(null, err);
});
}
});
in second method there is a semicron and parenthise problem.
Correct version is
Meteor.methods({
feedupdate: function(req) {
Posts.find({
"$text": {
"$search": req
}
}, {
smaintext: 1,
submitted: 1,
_id: 1,
Posts: {
$meta: "Posts"
}
}, {
sort: {
textScore: {
$meta: "posts"
}
}
}).toArray(function(err, items) {
for (e=0;e<101;e++) {
Meteor.users.update({
"_id": this.userId
}, {
"$addToSet": {
"profile.search": item[e]._id
}
});
}
});
}
});

Resources