Node js array push function is not working - javascript

I am trying to fetch data from two different collection category_types and categories. Each category has a parent category_type. So i want to push the category_type details when finding all the categories, in every index of category.
Here is my code
exports.findAllWithParentChild = (req, res) => {
let resData = [];
Models.Category.find()
.then(data => {
data.forEach(element => {
Models.CategoryType.findById(mongoose.Types.ObjectId(element.categorytype_id))
.then(catType => {
resData.push(element, {'category_type' : catType})
})
});
console.log(resData)
res.send({
response: true,
message: 'Categories fetched successfully.',
data : resData
});
}).catch(err => {
res.status(500).send({
response: false,
message: "Some error occurred while retrieving."
});
});
};
If i console resData within the loop then it prints data correctly, outside of loop it is empty and also send empty response.
I want the format should look like this
[{
_id: 5cb2f300ce34a53c9070ca9c,
title: 'edeededede',
description: 'dededededed',
slug: 'ededede',
categorytype_id: 5cb2f247db13d03360a3a3c5,
user_id: 'dedede',
created_at: 2019-04-14T08:44:48.516Z,
updated_at: 2019-04-14T08:44:48.516Z,
__v: 0 ,
category_type:
{
_id: 5cb2f247db13d03360a3a3c5,
title: 'trgtrgtrg',
description: 'trgtrgtrg',
slug: 'trgtrgtr',
user_id: 'gtrgtrgtr',
created_at: 2019-04-14T08:41:43.935Z,
updated_at: 2019-04-14T08:41:43.935Z,
__v: 0
}
}]
If there is any better way let me know.

The problem here is, that your console.log is executed before your async function (inside then statement). You should consider using async/await statements as shown below.
exports.findAllWithParentChild = (req, res) => {
let resData = [];
Models.Category.find()
.then(data => {
data.forEach(async element => {
let catType= await Models.CategoryType.findById(mongoose.Types.ObjectId(element.categorytype_id));
resData.push(element, {'category_type' : catType});
});
console.log(resData)
res.send({
response: true,
message: 'Categories fetched successfully.',
data : resData
});
}).catch(err => {
res.status(500).send({
response: false,
message: "Some error occurred while retrieving."
});
});
};

Related

Firebase: How to retrieve value from database then modify it and generate a new document with that value?

I am trying to retrieve the postId value of the last document created. I retrieved it but I could not find a way to bring that value out of the query. The problem with my code is that the variable that I am querying is trapped inside the promise and I am stuck on finding how to extract it.
exports.postOnePost = (req, res) => {
const first = db
.collection("posts")
.orderBy("createdAt", "desc")
.limit(1)
.get()
.then(function (documentSnapshots) {
const lastPostId = documentSnapshots.docs[
documentSnapshots.docs.length - 1
].get("postId");
console.log(lastPostId); // logs 10257
});
I wanted to take lastPostId and increment it by one when a new post is added. When I added console.log(lastVisible) outside the function, it is not defined.
const newPost = {
createdAt: new Date().toISOString(),
postId: lastPostId++, // This does not work
body: req.body.body,
};
db.collection("posts")
.add(newPost)
.then((doc) => {
const resPost = newPost;
resPost.docId = doc.id;
res.json(resPost);
})
.catch((err) => {
res.status(500).json({ error: "something went wrong" });
console.error(err);
});
};
My database documents look like this:
{
body: { stringValue: 'this is my body', valueType: 'stringValue' },
createdAt: {
stringValue: '2020-09-28T15:48:42.903Z',
valueType: 'stringValue'
},
postId: { integerValue: '10257', valueType: 'integerValue' },
}
exports.createPost = (req, res) => {
db.collection("posts")
.orderBy("createdAt", "desc")
.limit(1)
.get()
.then(function (documentSnapshots) {
let lastDocId = documentSnapshots.docs[
documentSnapshots.docs.length - 1
].get("postId");
if (req.body.body.trim() === "") {
return res.status(400).json({ body: "Body must not be empty" });
}
lastDocId++;
const newPost = {
createdAt: new Date().toISOString(),
postId: lastDocId,
body: req.body.body,
};
db.collection("posts")
.add(newPost)
.then((doc) => {
const resPost = newPost;
resPost.docId = doc.id;
res.json(resPost);
})
.catch((err) => {
res.status(500).json({ error: "something went wrong" });
console.error(err);
});
})
.catch((err) => {
res.status(500).json({ error: "something went wrong" });
console.error(err);
});
};

Mongoose Object returns undefined when populating

I'm struggling with a little kinda of problem. What I wanna do is populating users in comments.
User schema:
const userSchema = mongoose.Schema({
username: {
type: String,
required: true,
},
password: {
type: String,
required: true
}
});
Comment schema:
const commentSchema = mongoose.Schema({
comment:{
type: String
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
});
I had already created user and comment. Everything is fine when I'm trying to find both objects.
Comment:
Comment.find({}).exec((err, comments) => {
console.log(comments);
});
Output:
[
{
_id: 5e62472d5f593f3c642ee1e5,
comment: 'something',
user: 5e624522366d8c4150278a64,
__v: 0
}
]
User:
User.find({}).exec((err, users) => {
console.log(users);
});
Output:
[
{
_id: 5e624522366d8c4150278a64,
username: "SomeBodY",
password: "$2a$10$nm5BJ7zeI1tet3UEzcakf.8xoTgV/Yti5l1EKNg5inxiehevUlGRm"
}
]
The problem is when I'm using .populate('user') to Comment model it returns the comment as undefined in the console. I've tried different methods and even dropping the database but without success.
Here's the route when this happens
// Route To Single Project
router.get('/:projectId', (req, res) => {
const requestedProjectId = req.params.projectId;
Project.findById({_id: requestedProjectId}).populate('image_file').exec((err, project) => {
Rating.find({projectId: requestedProjectId}, (err, ratings) => {
Rating.countDocuments({projectId: requestedProjectId}, (err, count) => {
Comment.find({projectId: requestedProjectId}).populate('user').exec((err, comments) => {
console.log(comments)
if(err) return next(err);
res.render('project', { ... });
});
});
});
});
});
Actually your populate code is true.
The reason to get empty comments is because this Comment.find({projectId: requestedProjectId}) seems to return empty. So just check your request param.
Also to get rid of callback hell, you can rewrite your route using async/await like this.
router.get("/:projectId", async (req, res) => {
const requestedProjectId = req.params.projectId;
try {
const project = await Project.findById({ _id: requestedProjectId }).populate("image_file");
if (!project) {
return res.status(400).send("Project not found, check your projectId");
}
const comments = await Comment.find({ projectId: requestedProjectId }).populate("user");
console.log(comments);
const ratings = await Rating.find({ projectId: requestedProjectId });
const count = await Rating.countDocuments({ projectId: requestedProjectId });
res.render("project", {
project,
comments,
ratings,
count
});
} catch (err) {
console.log("Error: ", err);
res.status(500).send("Something went wrong");
}
});

Part of the API is not working in NodeJS. How do I fix it?

I'm trying to build a REST API in NodeJS for an online-store. My code for the POST-request looks like this:
router.post('/', (req, res, next) => {
const order = new Order({
_id: new mongoose.Types.ObjectId(),
customer_name: req.body.order.customer_name,
total_price: req.body.order.total_price,
products: req.body.order.products,
});
order
.save()
.then(result => {
req.body.order.products.forEach(value => {
let availiableQuantity = value.available_quantity - value.ordered_quantity;
Product.findOneAndUpdate({ id: value.id }, { available_quantity: availiableQuantity
})
})
res.status(201).json({
message: "Successfully created product",
createdProduct: {
customer_name: result.customer_name,
products: result.products,
}
});
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
});
});
});
The issue that I'm having is that the code in .then block is not working when I'm trying to send this POST-request, even if I try to console.log something, it doesn't do anything, everything works except the code in .then block:
.then(result => {
console.log('test')
req.body.order.products.forEach(value => {
let availiableQuantity = value.available_quantity - value.ordered_quantity;
Product.findOneAndUpdate({ id: value.id }, { available_quantity: availiableQuantity
})
})
Am I missing something out?
You can instead use async await instead of processing the next step with the then block.
router.post('/',async (req, res) => {
try{
const order = new Order({
_id: new mongoose.Types.ObjectId(),
customer_name: req.body.order.customer_name,
total_price: req.body.order.total_price,
products: req.body.order.products,
});
var result = await order.save();
req.body.order.products.forEach(value => {
let availiableQuantity = value.available_quantity -
value.ordered_quantity;
Product.findOneAndUpdate({ id: value.id }, {
available_quantity: availiableQuantity
})
res.status(201).json({
message: "Successfully created product",
createdProduct: {
customer_name: result.customer_name,
products: result.products,
}
});
} catch(e) {
res.status(500).json({
message: "Bad request",
error: e
});
}
})

NodeJS - controller - multiple queries from collections + forEach

Getting messy with promises and async & await. Would like to get your advice, what is the best practice for below controller case? Now it wont get user details before passing data to client.
I am doing a simply API endpoint to server side with NodeJS. Controller is making two queries to complete simply results as json object for client. Yes there is this useless run[] array but it is used when building whole event results :)
exports.runresults3 = function(req, res) {
// Route is passing ID
console.log('id', req.params.id);
// Test results object to fill
const resultsParams = {
run: [{
runid: {},
endtimeid: [],
userid: [],
endtime: [],
username: [],
}]
}
// Run objectid
resultsParams.run[0].runid = req.params.id;
// Get the endtimes by location/stage
Endtime.find({stage: req.params.id})
.then(data => {
// First loop
data.forEach(value => {
resultsParams.run[0].endtimeid.push(value._id);
resultsParams.run[0].userid.push(value.user);
resultsParams.run[0].endtime.push(value.endtime);
})
})
.then(() => {
// Second loop to get user details for results object
resultsParams.run[0].userid.forEach((userId, i) => {
TempUser.findById(userId)
.then(userdetails => {
console.log('userdetails.name', userdetails.name);
resultsParams.run[0].username.push(userdetails.name);
});
})
})
.then(() => {
res.json(resultsParams);
});
}
//////////// client side will get json as follows
{
"run": [
{
"runid": "5ae850d51717862590dc30d4",
"endtimeid": [
"5aec482d98555332145eccd3",
"5aec48c098555332145eccd6",
"5aec4a2c98555332145eccda",
"5aec4ab398555332145eccdd",
"5aec4bb998555332145ecce1",
"5aec4e42c3bcbb196c8474fc",
"5aec4e44c3bcbb196c8474fe",
"5aec4e45c3bcbb196c847500"
],
"userid": [
"5aec13b098555332145eccbe",
"5ae869c797e54a37f498c98f",
"5aec4a1298555332145eccd7",
"5aec4a1298555332145eccd7",
"5aec4ba698555332145eccde",
"5aec13a598555332145eccbc",
"5ae869c797e54a37f498c98f",
"5aec13b098555332145eccbe"
],
"endtime": [
24424,
3280,
11858,
38874,
5738,
40384,
50906,
36717
],
"username": []
}
]
}
exports.runresults3 = function(req, res) {
// Route is passing ID
console.log('id', req.params.id);
// Test results object to fill
const resultsParams = {
run: [{
runid: {},
endtimeid: [],
userid: [],
endtime: [],
username: [],
}]
}
// Run objectid
resultsParams.run[0].runid = req.params.id;
Endtime.find({stage: req.params.id})
.then(data => {
return Promise.all(data.map(record => {
TempUser.findById(record.user)
.then(userdetails => {
return {
endtimeid: record._id,
userid: record.user,
endtime: record.endtime,
username: userdetails.name
};
})
}))
.then(detailRecords => {
return detailRecords.reduce((acc, curr) => {
acc.endtimeid.push(curr.endtimeid);
acc.userid.push(curr.userid);
acc.endtime.push(curr.endtime);
acc.username.push(curr.username);
return acc;
},resultsParams.run[0]);
})
.then(() => {
res.json(resultsParams);
});
});
}
If you have a requirement to accumulate things into the "associative array" style of things in this result object, this is probably the way I would do it.
With that said, I would most likely try to return things as an array of run results - something more like the following...
{
"run": [
{
"runid": "5ae850d51717862590dc30d4",
"results": [
{
"endtimeid": "5aec482d98555332145eccd3",
"userid": "5aec13b098555332145eccbe",
"endtime": 24424,
"username": "User123"
},
{
"endtimeid": "5aec48c098555332145eccd6",
"userid": "5ae869c797e54a37f498c98f",
"endtime": 3280,
"username": "User234"
}
]
}
]
}
I believe the following code should do that.
exports.runresults3 = function(req, res) {
// Route is passing ID
console.log('id', req.params.id);
// Test results object to fill
const resultsParams = {
run: [{
runid: {},
results: []
}]
}
// Run objectid
resultsParams.run[0].runid = req.params.id;
Endtime.find({stage: req.params.id})
.then(data => {
return Promise.all(data.map(record => {
TempUser.findById(record.user)
.then(userdetails => {
return {
endtimeid: record._id,
userid: record.user,
endtime: record.endtime,
username: userdetails.name
};
})
}))
})
.then(results => resultsParams.run[0].results = reuslts)
.then(() => res.json(resultsParams));
}
There could certainly be more readability refactors to be done, and the fact that the last step of populating the result doesn't use the result of the previous flow troubles me a bit - but I try to avoid side effects whenever possible, so that may just be me.
Here is the edited versions as adviced :) But still missing the user details, it looks like promise.all isnt waiting "TempUser.findById(record.user)" query.
1 I created a fresh event, run, 3 users and 3 results.
2 Then I set some logs along db calls
3 Then I did a get request with postman
Log from localhost look like this after Postman GET request:
$ node app
Server started on 5000
Mongodb connected
id 5aeebd8a1b5ddf1424c25194
Get endtimes from database
[ { created: 2018-05-06T08:32:46.359Z,
_id: 5aeebdae1b5ddf1424c25199,
endtime: 23204,
user: 5aeebd751b5ddf1424c25191,
stage: 5aeebd8a1b5ddf1424c25194,
__v: 0 },
{ created: 2018-05-06T08:32:49.414Z,
_id: 5aeebdb11b5ddf1424c2519b,
endtime: 17149,
user: 5aeebd7b1b5ddf1424c25192,
stage: 5aeebd8a1b5ddf1424c25194,
__v: 0 },
{ created: 2018-05-06T08:32:51.769Z,
_id: 5aeebdb31b5ddf1424c2519d,
endtime: 10840,
user: 5aeebd7f1b5ddf1424c25193,
stage: 5aeebd8a1b5ddf1424c25194,
__v: 0 } ]
Set results to object
[ undefined, undefined, undefined ]
This should be the last one at chain?
Get user details from database
{ date: 2018-05-06T08:31:49.673Z,
_id: 5aeebd751b5ddf1424c25191,
name: 'Firstame Lastname1',
__v: 0 }
2. Get user details from database { date: 2018-05-06T08:31:55.562Z,
_id: 5aeebd7b1b5ddf1424c25192,
name: 'Firstame Lastname2',
__v: 0 }
2. Get user details from database { date: 2018-05-06T08:31:59.906Z,
_id: 5aeebd7f1b5ddf1424c25193,
name: 'Firstame Lastname3',
__v: 0 }
That is made by following code:
exports.runresults3 = function(req, res) {
// Route is passing ID
console.log('id', req.params.id);
// Test results object to fill
const resultsParams = {
run: [{
runid: {},
results: []
}]
}
// Each run objectid
resultsParams.run[0].runid = req.params.id;
Endtime.find({stage: req.params.id})
.then(data => {
console.log('1. Get endtimes from database', data);
return Promise.all(data.map(record => {
TempUser.findById(record.user)
.then(userdetails => {
console.log('2. Get user details from database', userdetails);
return {
endtimeid: record._id,
userid: record.user,
endtime: record.endtime,
username: userdetails.name
};
})
}))
})
.then(results => {
console.log('3. Set results to object', results);
console.log('This should be the last one at chain?');
resultsParams.run[0].results = results;
})
.then(() => res.json(resultsParams));
}

Bluebird with mongoose using Promise.Each

I'm stuck in a function I'm working with ( I can be doing this all wrong ). So a quick explanation, I want to add bulk data in a collection, the collection is called "Sites" the format of the CSV is site,country,type. I'm trying to use promises for this (Bluebird). So consider the code:
Promise.each(sites, sites => new Promise((resolve, reject) => {
//console.log(sites);
let name = tools.extractDomain(req, res, sites[0]);
let country = sites[1];
let group = sites[2];
if (name != "" && country != "" && group != "") {
Site.findOne({ name: name }, "_id", function(err, duplicate) {
if (false) {
console.log("Duplicate site: " + duplicate);
} else {
//console.log("Adding " + name)
let site = new Site()
site.name = name
site.meta = {}
site.group = group
site.country = country
site.geomix = []
site.addedBy = req.user._id
site.addedAt = Date.now()
site.saveAsync().then(function(response){
tools.saveHistory(req, res, response._id, response.name, "Website Meta fetched.");
tools.saveHistory(req, res, response._id, response.name, "Link added for the first time."); //Save in history
resolve(site);
}).catch(function (e){
console.log(name);
reject();
});
}
});
}else{
console.log('Wrong Format');
}
}).then((data) => {
console.log('All websites processed!');
addedSites.push(data);
}).catch(err => {
//console.error('Failed');
}));
res.send({ status: 'ok', message: ''});
I'm making ajax calls so I return a res.send({ status: 'ok', message: ''}), I know that its in the incorrect place and I want to send some data along the res.send. Currently it sends the headers before the code actually finishes. I want to send the headers after all the data is added in Mongo but for every each in this case he resolve() so if I send the headers inside the ".then" of the ".each" I will get headers already sent error.
This might be a bit confusing. I feel I'm not doing this right. I'm going a bit crazy as well as I can't find a proper example that I can understand and implement.
But in the end my main question is: using an Ajax call what's the proper way to add let's say 1000 records in a collection using promises and actually control properly those who fail to add and those who don't?
Right now my code actually works but the logic is wrong for sure.
Thanks.
You can use bulkWrite on your model.
Ref: http://mongoosejs.com/docs/api.html#model_Model.bulkWrite
EDIT:
Sorry I misunderstood you. You need to move res.send({ status: 'ok', message: ''}); to then() and catch() blocks, so you will get something like this:
Promise.each(sites, sites => new Promise((resolve, reject) => {
// stuff you did before
}).then((data) => {
console.log('All websites processed!');
addedSites.push(data);
res.send({ status: 'ok', message: ''});
}).catch(err => {
res.send({ status: 'failed', message: err.message});
}));
This is what I came too, if someone can tell me if this is a good arch.
exports.addBulkSite = function(req, res, next) {
let siteArray = csv.parse((req.body.sites).trim()),
addedSites = [],
failedSites = [],
duplicated = [],
sites = siteArray,
size = sites.length,
processed = 0,
meta;
Promise.each(sites, sites => new Promise((resolve, reject) => {
let name = tools.extractDomain(req, res, sites[0]),
country = sites[1],
group = sites[2];
if (name != "" && country != "" && group != "") {
Site.findOneAsync({ name: name }, "_id").then(function(duplicate) {
duplicated.push(duplicate);
reject({name:name, message: 'Duplicated', critical:false});
}).catch(function(notDuplicated){
let site = new Site()
site = {
name: name,
meta: {},
group: group,
country: country, geomix:{},
addedBy: req.user._id,
addedAt:Date.now()
}
site.saveAsync().then(function(response){
tools.saveHistory(req, res, response._id, response.name, "Website Meta fetched.");
tools.saveHistory(req, res, response._id, response.name, "Link added for the first time."); //Save in history
resolve(site);
}).catch(function (e){
console.log(e);
reject({name:name, message: 'Error saving in the database. Please contact the administrator.', critical: true});
});
});
}else{
reject({name:name, message: 'Paramaters are missing', critical:false});
}
}).then((data) => {
processed++;
addedSites.push(data);
if(processed==size){
console.log('out');
res.send({ status: 'ok', addedSites: addedSites, failedSites: failedSites, duplicated: duplicated});
}
}).catch((err) => {
processed++;
console.log(err);
failedSites.push(err);
if(processed==size){
console.log('out');
res.send({ status: 'ok', addedSites: addedSites, failedSites: failedSites, duplicated: duplicated});
}
}));
}

Categories