Mongodb addFields from an async/await function - javascript

I am trying to aggregate rooms data then add new fields into it. However, since the rooms itself is an await function, it will return the value of rooms before the forEach function takes place. How should I address such issue? console.log will return:
a, rooms
b, rooms
(notification_count value)
Below is the code that I am trying to use:
let rooms = await this.aggregate([
{
$match: {
userIds: { $all: [userId] },
},
},
{
$lookup: {
from: "users",
localField: "userIds",
foreignField: "_id",
as: "userProfiles",
},
},
]).sort({ updatedAt: -1 });
console.log("a", rooms);
rooms.forEach(async (room) => {
const notification_count =
await ChatMessageModel.getUnreadMessagesCountByRoomId(
room._id,
userId,
"is_read"
);
room["notification_count"] = notification_count;
console.log(notification_count);
});
console.log("b", rooms);
UPDATE
Using for loop instead of forEach worked based on my code workflow. I can use the code as of now. However if there is a way where I can insert the code directly in aggregate/addFields, that would be great. getUnreadMessagesCountByRoomId() only returns int.

Related

I could not query with different condition in mongoose

This is my Operator Models:
const operatorSchema = new Schema({
operatorName: {
type: String
},
users:[{
email:String,
payment:Number,
paymentsData: Date,
product: String,
}],
});
I need to filter by operatorName and email in users block. But when I try with this I get all users in related OperatorName how can I query correctly ?
Operators.find( { $and: [{operatorName: operatorName}, {'users.email': 'super#m.com'}]}, function (err, docs) {
) {
if (err) console.log(err)
else {
docs.forEach(function(data){
console.log(data)
})
// res.render('total_earns_operator_tables', { operators: docs });
}
});
EDIT
I also try with aggregate method like this but again, I get same result and I gel all bunch of user data, but I want only demouser#mail.com
Operators.aggregate([
{ $match: {$and: [{ operatorName: operatorName},{'users.email':
'demouser#mail.com' }]}},
]
,function (err, docs) {
// console.log(Operators)
// Operators.find( { $and: [{operatorName: operatorName}, {users: {$elemMatch: {email:['super#m.com']}}}]}, function (err, docs) {
// Operators.find( {operatorName: operatorName, "users.email": "demouser#mail.com"}, function (err, docs) {
if (err) console.log(err)
else {
docs.forEach(function(data){
console.log(data)
})
// res.render('total_earns_operator_tables', { operators: docs });
}
});
It is very basic but I couldnt find solution.
Your query is doing exactly what it is supposed to do. It is returning all documents that satisfy you two criteria: 1. having a specified operatorName and 2. users array having at least one user matching the specified email.
If you want to reashape your documents by filtering the user array to only include the user matching your condition, you'll have to use an aggregation.
EDIT
As per your edit: Your aggregation only have a $match stage, which is identical to your query above. To change the shape of a document, the aggregation framework provides you with the $project stage, see the example below:
Operators.aggregate([
{
$match: {
operatorName: operatorName,
"users.email": "demouser#mail.com"
}
},
{
$project: {
operatorName: '$operatorName',
users: {
$filter: {
input: "$users",
as: "user",
cond: {
$eq: [
"$$user.email",
"demouser#mail.com"
]
}
}
}
}
}
]
Here, we first filter the collection to get only the documents you want, using the $match stage, then we use the $filteroperator in $project stage, to return only the matching users within the array.
See the working playground

mongodb - get first X results and overall count of aggregate function

I have a aggregate query I'm using to get the 10 first results of a lookup between 2 collections.
I'm only getting the first 10, because if I use no limit and get 50 results the query gets slow (4-5 secs) (any suggestions on that will also be great)
So because Im doing some kind of scan I need to let the client know the number of total results, so it can query more when needed. currently im running the cursor twice and im sure that is not ideal.
const grades = database.collection('grades');
const match = { userId };
const aggregationPipeline = [
{ $match: match},
{ $addFields: { userIdObj: { $toObjectId: '$userId' } } },
{
$lookup: {
from: 'users',
localField: 'userIdObj',
foreignField: '_id',
as: 'userDetails',
},
},
];
const aggCursor = grades.aggregate(aggregationPipeline);
const aggCursorCount = grades.aggregate([...aggregationPipeline, {
$count: 'count',
}]);
const count = await aggCursorCount.toArray();
const allValues = await aggCursor.limit(10).toArray();
res.json({grades: allValues, count: count[0].count});
Im sure there is a more efficient way to get what I need. Still learning all mongodb stuff.
Thanks!

How to wait for a function to finish with all its inside expressions, in router in mongoose?

I just want to change the value of a key of all the objects inside an array
What I want actually -
The object which I queried from the database is -
{
_id: 61389277fa5c742caf959885,
title: 'What is GRE?',
forumTab: 'GRE',
askedAt: 2021-09-08T10:37:43.979Z,
askedBy: {
_id: 60f0a6a9b4259f7ef9c49cc8,
}
}
I want to add more key-value pairs in the askedBy key by again querying the database for the User with the given _id
Now, the user object which is queried is -
{
role: 'student',
_id: 60f0a6a9b4259f7ef9c49cc8,
firstName: 'Rishav',
lastName: 'Raj'
}
Finally I want to return the below object in response -
{
_id: 61389277fa5c742caf959885,
title: 'What is GRE?',
forumTab: 'GRE',
askedAt: 2021-09-08T10:37:43.979Z,
askedBy: {
_id: 60f0a6a9b4259f7ef9c49cc8,
role: 'student',
firstName: 'Rishav',
lastName: 'Raj'
}
}
I am creating a new array questionsToSend and pushing the object with updated key-value pairs which I am getting after querying the database for each elements in the questions array, I have created functions for respective query that I need to render in sequence, even after rendering the functions in proper sequence why the new array questionsToSend is not populating with the objects before returning the response?
router.get("/questions", async (req, res) => {
if (!req.query.forumTab) return res.status(400).send("something went wrong");
const page = parseInt(req.query.page) - 1;
const perPage = parseInt(req.query.perPage);
let questionsToSend = [];
const func0 = async (callback) => {
const questions = await Question.find({ forumTab: req.query.forumTab })
.sort({ askedAt: -1 })
.limit(perPage)
.skip(perPage * page);
console.log("xxxxxxx");
callback(questions);
};
const func1 = async (questions, callBack) => {
questions.forEach(async (question) => {
const askedUserData = await User.findById(question.askedBy._id);
if (!askedUserData) {
const index = questions.indexOf(question);
questions.splice(index, 1);
return;
}
questionsToSend.push({
..._.pick(question, [
"_id",
"title",
"askedAt",
"tags",
]),
askedUserData,
});
console.log(questionsToSend);
});
console.log("yyyyyyyy");
callBack();
};
func0(
(questions) =>
func1(questions, async () => {
console.log("zzzzzzzz");
res.status(200).send(questionsToSend);
})
);
});
We can use aggregation to achieve this
Question.aggregate([
{
$match: { forumTab: req.query.forumTab }
},
{
$lookup: {
from: 'users',
localField: 'askedBy._id',
foreignField: '_id',
as: "user"
}
},
{ $unwind: "$user"},
{ "$addFields": {
"askedBy": {
"$mergeObjects": ["$askedBy", "$user"]
}
}
},
{ $project: { "user" : 0} },
{ $sort: {"askedAt": -1}},
{ $skip: perPage * page},
{ $limit: perPage},
])
$match is used to apply filter
$lookup is used to do a join on a collection. I have assumed the collection name is users.
$lookup returns the matched result as an array. Converting it to object using $unwind since we get only one back.
$addFields with $mergeObjects is merging the existing askedBy field and newly user field
Removing the user field from the result set with $project.
And then sort, skip and limit.

Adding a created date field from _id to all mongo documents

I am trying to add a new field createdDate to all existing documents using _id field. I am doing this for easy readability of the documents. I tried following snippet but it is not working.
dbase.collection("cname").updateMany(
{},
{ $set: { createdDate: new Date(parseInt("$_id".substring(0, 8), 16) * 1000) } }
)
.then((resp)=>{ console.log('complete') })
This is the createdDate for all my documents now (after running the query): "1970-01-01T00:00:00.000+00:00"
What is wrong with my code ?
You need to use update with aggregation pipeline starting from MongoDB 4.2,
$toDate to cast type to date from _id
dbase.collection("cname").updateMany(
{},
[{
$set: { createdDate: { $toDate: "$_id" } }
}]
)
.then((resp) => { console.log('complete') });
Playground

get today's imported data from mongodb using timestamp node js

I add reports into a MongoDB database by current timestamp but I am not able to retrieve today's imported data because the current timestamp is different from the timestamp's value of inserted data. how can I see data by using timestamp?
add reprots
reportsController.addReports = function (req, resp) {
let battriesObjs = [];
let banksObjs = [];
let reports = req.body;
// console.log(batteries);
const siteReports = new SiteReports({
siteId: reports.siteId,
environmentParam1: reports.envp1,
environmentParam2: reports.envp2,
environmentParam3: reports.envp3,
reportDate: moment().valueOf()
});
let btr = reports['battries'];
btr.forEach(function (btry) {
const batteryReports = new BatteryReports({
batteryId: btry.bid,
batteryStatus: btry.status,
batteryVoltage: btry.voltage,
batteryTemperature: btry.temperature,
reportDate: moment().valueOf()
});
battriesObjs.push(batteryReports);
});
siteReports
.save()
.then(value => {
console.log("insert sampleCollection result ", value);
BatteryReports
.insertMany(battriesObjs)
.then(valueBatteries =>
resp.status(status.OK)
.json({'respond': 'the document is updated successfully'}))
.catch(err => {
console.log("bulk insert sampleCollection error ", err);
});
})
.catch(err => {
console.log("bulk insert sampleCollection error ", err);
});
};
get reprots
reportsController.getAllTodayBatteryReports = function (req, resp) {
console.log(req.query.batteryBankId, moment().valueOf());
BatteryReports.aggregate([
{
$match: {
"reportDate": moment().valueOf().toString()
}
}
,
{
$lookup:
{
from: "batteryschemas",
localField: "batteryId",
foreignField: "batteryId",
as: "batteryReports_with_their_info"
}
}
,
{
$match:
{"batteryReports_with_their_info.batteryBankId": new mongoose.Types.ObjectId(req.query.batteryBankId)}
}
,
{
$replaceRoot: {newRoot: {$mergeObjects: [{$arrayElemAt: ["$batteryReports_with_their_info", 0]}, "$$ROOT"]}}
}
,
{
$project: {
batteryReports_with_their_info: 0,
batteryBrand: 0,
batteryMaximumChargingCurrent: 0,
batteryCycleOfCharge: 0,
batteryModel: 0,
batteryProductDate: 0,
batteryInternalResistance: 0,
batteryCapacity: 0,
__v: 0
}
}
], function (err, result) {
console.log(result);
if (err) {
resp.status(status.NOT_FOUND).json({'respond': err + ''});
} else {
resp.status(status.OK).json(result);
}
// console.log(result);
});
};
As you see this part of my code get the current timestamp and put it to the MongoDB match method but although some data were added in today's timestamp, I cannot get these records by current timestamp value.
$match: {
"reportDate": moment().valueOf().toString()
}
samples
{
"_id": "5d3c9116ee51fe32b44160f6",
"batteryId": "1",
"batteryVoltage": "5.5",
"batteryVoltageMin": "34",
"batteryVoltageMax": "34",
"batteryMinTemperature": "443",
"batteryMaxTemperature": "43",
"batteryBankId": "5d29c1469e0e8b3afcf3a1e6",
"batteryStatus": "H",
"batteryTemperature": "38",
"reportDate": "1564250390757"
}
You can assign moment().valueOf() value to an object and use it against reportDate. Plus moment().valueOf() will give you timestamp in milliseconds then inserted time will be different to querying time, So you need to do a range query in order to get specific records/documents or else pass exact timestamp to get exact match.
let's say you've inserted few documents today, and wanted to run your query at a given timestamp on the same day(be mindful of timestamps are of same zone) then,
let currentTimeStamp = moment().valueOf().toString()
let startOfDayTimeStamp = moment().startOf('day').valueOf().toString()
then,
$match: {
"reportDate": { $gt: startOfDayTimeStamp, $lt: currentTimeStamp }
}
If you're dealing with collection which has frequent writes, then you're dealing with high amounts of data for read query like this, just keep in mind to do proper indexing.

Categories