Find documents that contain certain value for sub-object field Mongoose - node.js

Note: I asked this question here, however at that time I was working purely with MongoDB, now I am trying to implement this with Mongoose. I decided it was appropriate to ask a separate question as I believe the answer will be fundamentally different, however please let me know if I was incorrect in that decision.
I have a collection with the following format:
[
{
firstname: 'Joe',
lastname: 'Blow',
emails: [
{
email: 'test#example.com',
valid: false
},
{
email: 'test2#example.com',
valid: false
}
],
password: 'abc123',
_id: 57017e173915101e0ad5d94a
},
{
firstname: 'Johnny',
lastname: 'Doe',
emails: [
{
email: 'test3#example.com',
valid: false
}
],
password: 'abc123',
_id: 57017e173915101e0ad5d87b
},
]
I am trying to find a user based on the emails.email field. Here is what I have so far:
UserModel.find()
.where('emails')
.elemMatch(function (elem) {
elem.where('email').equals(userEmail);
})
.limit(1)
.exec(
(err, usersReturned) => {
console.log('test2#example.com');
});
What am I doing wrong? I am new to Mongoose and just can't seem to figure this out.

You could do something like this :
UserModel.find({"emails.email": userEmail}).limit(1).exec(function(err, user){
if(err) console.log("Error: " + JSON.stringify(err));
else if(user) console.log("User Returned is : " + JSON.stringify(user));
});

You can use Mongodb aggregate function .Use $unwind on "emails.email" field and it will separate the array make as independent documents.
UserModel.aggregate( [
{ $unwind : "$emails" },
{ $match: {$emails.email:"email you want to put"}}
],function(err,result){
//write code but you want to do
});

Related

How do I update a key in an array of objects in MongoDB?

I working on NodeJS backend API and trying to change a key in an array of objects from false to true in my MongoDB database. I am passing two conditions from the client: the email of the user and the email of the person that sent the user a message. I would like to change the boolean value of read to true.
Sample data:
{
_id: new ObjectId("6282163781acbcd969de3fc9"),
firstName: 'Amanda',
lastName: 'Nwadukwe',
role: 'Volunteer',
email: 'amandanwadukwe#gmail.com',
password: '$2a$10$YD5MQlMt0gqSULQOBNcEfOLr3vIK8eF4dqdLw3XctsIVgbnf54P32',
confirmPassword: '$2a$10$mnL0S1bDDkGVnKgqQP81mOew9aFdNTUCGOEs7LvWYRxzivN4hrtFS',
date: 2022-05-16T09:14:57.000Z,
messages: [
{
message: 'This is another message from Amanda',
sendersEmail: 'laju#gmail.com',
date: '2022-05-14T12:00:45.000Z',
read: false
},
{
sender: 'Amanda Nwadukwe',
message: 'This is another message from Amanda',
sendersEmail: 'amanda#gmail.com',
date: '2022-05-14T12:00:45.000Z',
read: false
}]
Desired Output:
{
_id: new ObjectId("6282163781acbcd969de3fc9"),
firstName: 'Amanda',
lastName: 'Nwadukwe',
role: 'Volunteer',
email: 'amandanwadukwe#gmail.com',
password: '$2a$10$YD5MQlMt0gqSULQOBNcEfOLr3vIK8eF4dqdLw3XctsIVgbnf54P32',
confirmPassword: '$2a$10$mnL0S1bDDkGVnKgqQP81mOew9aFdNTUCGOEs7LvWYRxzivN4hrtFS',
date: 2022-05-16T09:14:57.000Z,
messages: [
{
message: 'This is another message from Amanda',
sendersEmail: 'laju#gmail.com',
date: '2022-05-14T12:00:45.000Z',
read: true
},
{
sender: 'Amanda Nwadukwe',
message: 'This is another message from Amanda',
sendersEmail: 'amanda#gmail.com',
date: '2022-05-14T12:00:45.000Z',
read: false
}]
I am tried a lot of things with filtering but I have not been successful. Here is my code to change all the read to true. It is also not working.
app.post("/view_message", (req, res) => {
const email = req.body.email;
Users.findOneAndUpdate({ "email": email }, {$set:{"messages.$.read": true}}, (err, result) => {
console.log(result)
})
});
You missed to add a check to match the array element to be updated.
Playground
db.collection.update({
"email": "amandanwadukwe#gmail.com",
"messages.sendersEmail": "laju#gmail.com", //This did the trick
},
{
"$set": {
"messages.$.read": true
}
},
{
"multi": false,
"upsert": false
})
Just in case anyone needs it, to update all the read values for all objects in the array I used this:
User.findAndUpdateOne({
"email": "amandanwadukwe#gmail.com",
"messages.sendersEmail": "laju#gmail.com",
},
{
"$set": {
"messages.$[].read": true //Added square brackets
}
},
{
"multi": false,
"upsert": false
})

MongoDB Query Returns Empty Nested Object

I've got a 'conversations' collection in MongoDB which I'm querying from NodeJS to use the returned data to render the conversation's page.
The data has been stored in the database correctly as far as I can see, when I query it everything comes back as I'd expect, apart from a couple of nested objects - the two users that the conversation belongs to.
Here's what I get when I console.log a conversation (note the 'participants' field:
[ { _id: 57f96549cc4b1211abadf28e,
__v: 1,
messages: [ 57f96549cc4b1211abadf28d ],
participants: { user2: [Object], user1: [Object] } } ]
In Mongo shell the participants has the correct info - the id and username for both participants.
Here's the Schema:
var ConversationSchema = new mongoose.Schema({
participants: {
user1:
{
id: String,
username: String
},
user2:
{
id: String,
username: String
},
},
started: Number,
messages: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Message"
}
]
});
Here's the creation of the conversation document:
var conv = {
participants : {
"user1" : {
"id" : req.body.senderId,
"username" : req.body.senderName
},
"user2" : {
"id" : req.body.recipientId,
"username" : req.body.recipientName
}
},
created : Date.now(),
messages : [] // The message _id is pushed in later.
}
Conversation.create(conv, function(err, newConvo){
if(err){
console.log(err);
} else {
newConvo.messages.push(newMessage);
newConvo.save();
}
})
And lastly, in case it's useful, here's the query to Mongo:
// view all conversations a user belongs to
app.get('/messages', function(req, res){
Conversation.find({
$or : [
{"participants.user1.id" : req.user._id},
{"participants.user2.id" : req.user._id}
]
}, function(err, convos){
if(err){
console.log('Error getting Convos ' + err)
} else {
res.render('messages', {convos: convos, currentUser: req.user});
}
});
});
Thanks a lot for any help that!
It seems that everything is alright, the console.log just doesn't print nested objects by default. Try using:
console.log(JSON.stringify(conversation))
When logging a conversation in order to see the participants objects.
Fixed it!
Andresk's answer above was a big shove in the right direction. As he said, everything was OK, but I wasn't accessing the returned object in the correct way. It's obvious now, but I wasn't providing the index number for the 'convos' object.
I simply needed to do this, even though I was only getting one 'conversation' document back from MongoDB:
console.log(convos[0].participants.user1.username);

Cascade delete from array using Mongoose middleware remove hook

I am building a Node.js express RESTfull API using Mongodb and mongoose.
This is my schema:
var UserSchema = new mongo.Schema({
username: { type: String },
password: { type: String, min: 8 },
display_name: { type: String, min: 1 },
friends: { type: [String] }
});
UserSchema.post('remove', function(next){
console.log({ friends: this._id }); // to test if this gets reached (it does)
UserSchema.remove({ friends: this._id });
});
And this is the function that removes a User:
.delete(function(req, res) {
User.findById(req.params.user_id, function(err, user) {
if (err) {
res.status(500);
res.send(err);
} else {
if (user != null) {
user.remove();
res.json({ message: 'User successfully deleted' });
} else {
res.status(403);
res.json({ message: 'Could not find user.' });
res.send();
}
}
});
});
What I need to do is when a user is removed, his or her _id (String) should also be removed from all the other users' friends array. Hence the remove hook in the schema.
Right now the user gets deleted and the hook gets triggered, but the user _id is not removed from the friends array (tested with Postman):
[
{
"_id": "563155447e982194d02a4890",
"username": "admin",
"__v": 25,
"password": "adminpass",
"display_name": "admin",
"friends": [
"5633d1c02a8cd82f5c7c55d4"
]
},
{
"_id": "5633d1c02a8cd82f5c7c55d4",
"display_name": "Johnybruh",
"password": "donttouchjohnsstuff",
"username": "John stuff n things",
"__v": 0,
"friends": []
}
]
To this:
[
{
"_id": "563155447e982194d02a4890",
"username": "admin",
"__v": 25,
"password": "adminpass",
"display_name": "admin",
"friends": [
"5633d1c02a8cd82f5c7c55d4"
]
}
]
To try and figure it out I have looked at the Mongoosejs Documentation, but the mongoose doc example doesn't cover the remove hook. Also this stackoverflow qestion but this question seems to be about removing from other schemas.
I think i'm doing the remove in the hook wrong, but I can't seem to find the problem.
Thanks in advance!
EDIT:
I could not get the first suggestion by cmlndz to work, so I ended up fetching all the documents with arrays that contained the to-be-deleted users' id and pulling it from them one-by-one:
The delete function now contains this bit of code that does the magic:
// retrieve all documents that have this users' id in their friends lists
User.find({ friends: user._id }, function(err, friends) {
if (err) {
res.json({ warning: 'References not removed' });
} else {
// pull each reference to the deleted user one-by-one
friends.forEach(function(friend){
friend.friends.pull(user._id);
friend.save(function(err) {
if (err) {
res.json({ warning: 'Not all references removed' });
}
});
});
}
});
You could use $pull to find all documents that contain the "ID" in the "friends" array -or- find any matching document and popping the "ID" out of the array one by one.

I want a specific document and only the latest item in a subdocument (Mongodb / Node.js)

I have a model like this:
User:
{
name: "test",
phone: "1234567890",
password: "test",
email: "test#tester.com",
tasks: {
{ task: "do this", timestamp: "2015-08-01" },
{ task: "then this", timestamp: "2015-08-05" },
{ task: "finally this", timestamp: "2015-08-07" }
}
},
... (more users) ...
How can I get a specific user's details like name, email, and only 1 task the latest one?
User.find({phone: '1234567890', password: 'test'}, '_id name email tasks', function(err, user) {
if (err)
res.json({result: false});
res.json({result: !(user == null), object: user});
});
This returns all the tasks.
You could use $slice (projection)
User.find({
phone: '1234567890',
password: 'test'
}, {
name: 1,
email: 1,
tasks: {$slice: -1}
}, function (err, user) {
if (err) {
res.json({result: false});
return;
}
return res.json({result: !(user == null), object: user});
});
Note that tasks should be Array but not Object.
if you are inserting new task by push method of array. then you can get last record by getting length of tasks array and access last record by length - 1.
or you can refer this :
return document with latest subdocument only in mongodb aggregate

Populating nested properties with Mongoose

In the API I'm trying to write with Node and Mongoose, the following query:
User.findOne({username: req.params.username}, "-_id -__v")
.populate({path: "songs", select: "-_id -__v"})
.populate({path: "following", select: "-_id username email"})
.exec(function(err, user) {
res.send(user);
});
returns the following JSON:
{
"email": "alice#alice.org",
"username": "alice",
"following": [
{
"username": "john",
"email": "john#john.org"
}
],
"songs": [
{
"slug": "dvorak-cello-concerto",
"artist": "5403e825cc9c45e9c55c4e7d",
"title": "Cello Concerto"
}
]
}
In the songs schema, I've setup artist as the following:
artist: {type: Schema.Types.ObjectId, ref: 'User'}
What is the best way to also populate the 'artist' property in every song that's populated in the initial query, rather than just its _id which references the user that song belongs to?
I figured out a way to do this, but please correct me if there's a better/cleaner way of doing it.
User.findOne({username: req.params.username}, "-_id -__v")
.populate({path: "songs", select: "-_id -__v"})
.exec(function(err, user) {
Songs.populate(user, {
path: 'songs.artist',
select: '-_id username',
model: 'User'
}, function (err, user) {
res.send(user);
});
});
Also, I'm only returning the 'username' value for the user which is all I need, but 'artist' still ends up being an object with that one property. If anybody knows how to return that value just as a string.
I'd love to know how.

Resources