Javascript variable scope when mongoose query - javascript

I'm working with node.js, mongoose and foursquare API.
foursquare.getVenues(params, function(err, venues) {
if(err) return res.json(JSON.stringify({status: 'error', returnData: err}));
// variable initialization
var rooms = [];
var vanueItem;
// iterate foursquare return list (venue item)
venues.response.venues.forEach(function(item) {
Room.aggregate(
[
{ "$group": {
"_id": '$mobileUser.genderType',
"genderTypeCount": { "$sum": 1 }
}}
],
function(err,result) {
if(err) return res.json(JSON.stringify({status: 'error', returnData: err}));
// build it to return after
vanueItem =
{
id: item.id,
name: item.name,
description: item.description,
contact: item.contact.formattedPhone,
lat: item.location.lat,
lng: item.location.lng,
distance: item.location.distance,
city: item.location.city
};
// insert it into venue array
rooms.push(vanueItem);
}
);
});
return res.json(JSON.stringify({ status: 'success', returnData: rooms }));
});
I'm having a problem with rooms array. When I remove the 'Room.aggregate' query, works fine (all rooms was ok), but when I use the aggregate, the return function gives me empty room.
I already tried remove var from 'var rooms = [];'

Room.aggregate is asynchronous function, if you want iterate over asynchronous function you can use async library, like this
var async = require('async');
foursquare.getVenues(params, function(err, venues) {
if (err) return res.json(JSON.stringify({
status: 'error',
returnData: err
}));
var rooms = [];
var vanueItem;
async.each(venues.response.venues, function (item, next) {
Room.aggregate(
[{
"$group": {
"_id": '$mobileUser.genderType',
"genderTypeCount": {
"$sum": 1
}
}
}],
function(err, result) {
if (err) {
return next(err);
}
// build it to return after
vanueItem = {
id: item.id,
name: item.name,
description: item.description,
contact: item.contact.formattedPhone,
lat: item.location.lat,
lng: item.location.lng,
distance: item.location.distance,
city: item.location.city
};
rooms.push(vanueItem);
next(null);
}
);
}, function (err) {
if (err) {
return res.json(JSON.stringify({
status: 'error',
returnData: err
}));
}
return res.json(JSON.stringify({
status: 'success',
returnData: rooms
}));
});
});

Related

How to increment a value inside an array object in MongoDB using nodejs?

Here's my MongoDB post model that I am using with node.js.I want to update the number of likes under each object in comments array i.e number of likes on each comment.How can I increment the number of likes value.
_postid:6045b7a3b0b0423790d6484b
photo:Object
likes:Array
text:"hey there"
comments:Array
0:Object
1:Object
2:Object
_id :6045c9251f99b81ee4dbc0f6
text:"tetstst"
postedBy:6045c36dd8df2f2f00b115d5
likes:0
created:2021-03-08T06:50:13.851+00:00
created:2021-03-08T05:35:31.524+00:00
postedBy:6045116e37280f0970cf63a5
here's what I am trying to do using FindoneandUpdate:
Post.findOneAndUpdate(
model,
{ $inc: { "comments.likes": 1 } },
{ new: true }
).exec((err, result) => {
if (err) {
return res.status(400).json({
error: err,
});
}
res.json(result);
});
};
Here's my post schema that I am using:
text: {
type: String,
required: "Name is required",
},
photo: {
data: Buffer,
contentType: String,
},
likes: [{ type: mongoose.Schema.ObjectId, ref: "User" }],
comments: [
{
text: String,
created: { type: Date, default: Date.now },
postedBy: { type: mongoose.Schema.ObjectId, ref: "User" },
likes: Number,
},
],
If you want to increment the likes for each comment by 1, (I assumed here model to be your query object.)
Post.findOneAndUpdate(
model,
{ $inc: { "comments.$[].likes" : 1 } },
{ new: true }
).exec((err, result) => {
if (err) {
return res.status(400).json({
error: err,
});
}
res.json(result);
});
};
If you want to increment only the first comment in the post,
Post.findOneAndUpdate(
model,
{ $inc: { "comments.0.likes" : 1 } },
{ new: true }
).exec((err, result) => {
if (err) {
return res.status(400).json({
error: err,
});
}
res.json(result);
});
};
If you want to increment likes for a comment posted By 6045c36dd8df2f2f00b115d5
Post.findOneAndUpdate(
{ ...model, { "comments.postedBy": 6045c36dd8df2f2f00b115d5 }},
{ $inc: { "comments.$.likes" : 1 } },
{ new: true }
).exec((err, result) => {
if (err) {
return res.status(400).json({
error: err,
});
}
res.json(result);
});
};
Also Ref: https://docs.mongodb.com/manual/reference/operator/update-array/

How to use group by and sum in Mongoose?

I have a JSON array that contains objects like this one
{
"InvoiceNo": "FA 2019/1",
"Period": "01",
"DocumentTotals": {
"TaxPayable": "26.94",
"NetTotal": "117.16",
"GrossTotal": "144.10"
},
"WithholdingTax": {
"WithholdingTaxAmount": "0.00"
}
},
I want to sum the GrossTotal of the various objects and group it by Period.
I tried with the following code:
saftFileController.revenuePerMonth = function (req, res) {
Saft.find().exec(function (err, fileContent) {
if (err) {
console.log(err);
} else {
const JSONObject = JSON.parse(JSON.stringify(fileContent));
const sales = JSONObject[3].SalesInvoices.Invoice;
const revenuePerMonth = Saft.aggregate([
{
$group: {
Period: sales.Period,
revenue: {
$sum: "$GrossTotal",
},
},
},
]);
res.json({ revenue: revenuePerMonth });
}
});
};
But the output wasn´t the desired one. What I am doing wrong? This was the output:
{
"revenue": {
"_pipeline": [
{
"$group": {
"revenue": {
"$sum": "$GrossTotal"
}
}
}
],
"options": {}
}
}
Here are some printscreens of my Database
https://snipboard.io/QOfiYz.jpg
https://snipboard.io/72LSRC.jpg
Did this and now returns the Period but it is ignoring the sum because it is a string type.How can I convert?
saftFileController.revenuePerMonth = function (req, res) {
Saft.aggregate([
{
$group: {
_id: "$SalesInvoices.Invoice.Period",
revenue: {
$sum: "SalesInvoices.Invoice.DocumentTotals.GrossTotal",
},
},
},
]).exec(function (err, fileContent) {
if (err) {
console.log("Error: ", error);
} else {
res.json({ revenuePerMonth: fileContent });
}
});
};
You should pass the MongoDB expression what you want to group it by as _id in the $group stage (documentation). The $sum expression should contain the full path as well.
$group: {
_id: '$Period',
revenue: {
$sum: "$DocumentTotals.GrossTotal",
},
},
aggregate accepts a callback, or returns a Promise just like when you use Saft.find()
Saft.aggregate([/* ... */]).exec(function (err, result) {
/* use result here */
})

Organize promise code

I have the following code. But this looks similar to a callback hell. How do i reorganize this code to a more proper promise way
FacilityBooking.forge({
"booking_id": req.params.id
})
.fetch({
require: true
})
.then(function(collection) {
if(0) { //#todo check if admin
throw new NotAuthorised(CFG_MESSAGES.error[403]);
} else {
Bookshelf.transaction(function(t) {
collection
.save({
"is_valid": 0,
"updated_by": req.user.id
}, {transacting: t})
.tap(function(model) {
new FacilityBooking(model.toJSON())
.save({
"is_valid": 1,
"reason_for_reject": req.body.disapprovereason || '' ,
"status": approval_status[req.body.moderation_action]
}, {transacting: t})
.then(function(collection) {
res.json({
status: true,
message: CFG_MESSAGES.facility_booking.moderate.success
});
})
.catch(function(err) {
res.json({
status: false,
message: CFG_MESSAGES.facility_booking.moderate.error
});
});
});
});
}
})
.catch(function(err) {
ErrorHandler.handleError(res, err);
});
Well, you just can divide it into functions:
var onErrorProcessingBooking = function(err) {
ErrorHandler.handleError(res, err);
};
var tap = function(model) {
var onSuccessSave = function(collection) {
res.json({
status: true,
message: CFG_MESSAGES.facility_booking.moderate.success
});
};
var onErrorSave = function(err) {
res.json({
status: false,
message: CFG_MESSAGES.facility_booking.moderate.error
});
};
new FacilityBooking(model.toJSON())
.save({
"is_valid": 1,
"reason_for_reject": req.body.disapprovereason || '',
"status": approval_status[req.body.moderation_action]
}, {
transacting: t
})
.then(onSuccessSave)
.catch(onErrorSave);
}
var onSuccessProcessingBooking = function(collection) {
if (0) {
throw new NotAuthorised(CFG_MESSAGES.error[403]);
return;
}
Bookshelf.transaction(function(t) {
collection
.save({
"is_valid": 0,
"updated_by": req.user.id
}, {
transacting: t
})
.tap(tap);
});
}
FacilityBooking
.forge({"booking_id": req.params.id})
.fetch({require: true})
.then(onSuccessProcessBooking)
.catch(onErrorProcessingBooking);
Promises chain, then is an abstraction over flow control itself and you can return a promise from another promise. Better yet, you can use coroutines to take care of this with bluebird which is already included in bookshelf:
const forger = Promise.coroutine(function*() {
const collection = yield FacilityBooking.forge({ "booking_id": req.params.id })
.fetch({ require: true });
if(0) throw new NotAuthorised(CFG_MESSAGES.error[403]);
yield Bookshelf.transaction(Promise.coroutine(function *(t) {
yield collection.save({
"is_valid": 0,
"updated_by": req.user.id
}, {transacting: t});
const model = new FacilityBooking(model.toJSON());
yield model.save({
"is_valid": 1,
"reason_for_reject": req.body.disapprovereason || '' ,
"status": approval_status[req.body.moderation_action]
}, {transacting: t});
res.json({
status: true,
message: CFG_MESSAGES.facility_booking.moderate.success
});
}));
}).catch(function(err) {
// filter error here, and do the res.json with the failure here
if(isRelevantError) {
res.json({
status: false,
message: CFG_MESSAGES.facility_booking.moderate.error
});
}
ErrorHandler.handleError(res, err);
});
I tried to answer your question please check below code
Bookshelf.transaction(function(t) {
FacilityBooking.forge({
"booking_id": req.params.id
})
.fetch({
require: true
})
.then(function(collection) {
if(0) { //#todo check if admin
throw new NotAuthorised(CFG_MESSAGES.error[403]);
} else {
return collection
.save({
"is_valid": 0,
'updated_by': req.user.id
}, {
transaction: t,
patch: true
});
}
})
.then(function(model) {
var data = model.toJSON();
delete data.id;
return new FacilityBooking(data)
.save({
"is_valid": 1,
"reason_for_reject": req.body.disapprovereason || 'bbbbbbbbbbb' ,
"status": approval_status[req.body.moderation_action]
}, {
transaction: t,
method: 'insert'
})
})
.then(function(collection) {
res.json({
status: true,
message: CFG_MESSAGES.facility_booking.moderate.success
});
})
.catch(function(err) {
res.json({
status: false,
message: CFG_MESSAGES.facility_booking.moderate.error
});
})
});

Determine when async execution is finished

I have to process an array of entries that requires to perform to async tasks for each file entry: getFile and uploadResult both are async task. My question is how can I know when the array doc.entries is being processed using an async library like asyncjs. The code below is just an illustration of what I am trying to accomplish.
var doc = {
version: '1.7',
entries: [{
date: '11/11/10',
files: [{
name: 100,
executable: false
},
{
name: 101,
executable: false
}]
},
{
date: '11/12/10',
files: [{
name: 200,
executable: false
},
{
name: 201,
executable: false
}]
},
{
date: '11/13/10',
files: [{
name: 300,
executable: false
}]
},
{
date: '11/14/10',
files: [{
name: 400,
executable: false
}]
}]
};
doc.entries.map(function(entry){
entry.files.map(function(file){
getFile(file, function(err, result){
if(err){
throw Error(err)
}
uploadResult(result, function(err, status){
WriteOnDb(file.name, status, function(err, result){ ... });
});
})
});
});
How can I know when the last file is being store on the db and then do something else?
Thanks.
The easiest way is to use promises, or better observables, but you do it with callbacks too - for example you can count how many tasks are in total and how many was finished:
var total = doc.entries
.map(function (entry) {
return entry.files.length;
})
.reduce(function (x, acc) {
return acc + x
}, 0);
var finished = 0;
function finishCallback(err) {
if (err === null) {
/// all async tasks are finished;
}
}
doc.entries.map(function (entry) {
entry.files.map(function (file) {
getFile(file, function (err, result) {
if (err) {
finishCallback(err);
} else {
uploadResult(result, function (err, status) {
WriteOnDb(file.name, status, function (err, result) {
if (err) {
finishCallback(err);
} else {
finished += 1;
if (finished === total) finishCallback(null);
}
});
});
}
})
});
});

How to access data on a `through` table with Bookshelf

I am using [BookshelfJS][bookshelfjs] for my ORM and am wondering how to access data on a though table.
I have 3 Models, Recipe, Ingredient and RecipeIngredient which joins the two.
var Recipe = BaseModel.extend({
tableName: 'recipe',
defaults: { name: null },
ingredients: function () {
return this
.belongsToMany('Ingredient')
.through('RecipeIngredient')
.withPivot(['measurement']);
}
}));
var Ingredient = BaseModel.extend({
tableName: 'ingredients',
defaults: { name: null },
recipes: function () {
return this
.belongsToMany('Recipe')
.through('RecipeIngredient');
}
}));
var RecipeIngredient = BaseModel.extend({
tableName: 'recipe_ingredients',
defaults: { measurement: null },
recipe: function () {
return this.belongsToMany('Recipe');
},
ingredient: function () {
return this.belongsToMany('Ingredient');
}
}));
I then attempt to retrieve a Recipe along with all the Ingredients however cannot work out how to access measurement on the RecipeIngredient.
Recipe
.forge({
id: 1
})
.fetch({
withRelated: ['ingredients']
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.error(err);
});
Return:
{
"id": 1,
"name": "Delicious Recipe",
"ingredients": [
{
"id": 1,
"name": "Tasty foodstuff",
"_pivot_id": 1,
"_pivot_recipe_id": 1,
"_pivot_ingredient_id": 1
}
]
}
With no measurement value.
I had thought that the .withPivot(['measurement']) method would have grabbed the value but it does not return any additional data.
Have I missed something or misunderstood how this works?
I'm not exactly sure why you want to use through. If it's just a basic many-to-many mapping, you can achieve this by doing the following:
var Recipe = BaseModel.extend({
tableName: 'recipe',
defaults: { name: null },
ingredients: function () {
return this
.belongsToMany('Ingredient').withPivot(['measurement']);
}
}));
var Ingredient = BaseModel.extend({
tableName: 'ingredients',
defaults: { name: null },
recipes: function () {
return this
.belongsToMany('Recipe').withPivot(['measurement']);;
}
}));
You don't need an additional model for junction table. Just be sure to define a junction table in your database as ingredients_recipe (alphabetically joining the name of tables!). Or , you can provide your own custom name to belongsToMany function for what the junction table should be named. Be sure to have ingredients_id and recipe_id in ingredients_recipe
This is pretty much it. Then you can do
Recipe
.forge({
id: 1
})
.fetch({
withRelated: ['ingredients']
})
.then(function (model) {
console.log(model.toJSON());
})
.catch(function (err) {
console.error(err);
});

Categories

Resources