Query db without certain elements inside an array - mongodb

I set up a small database using a model and 2 schemas.
The model goes as follows:
const userSchema = new mongoose.Schema({
friendsRequests: [friendRequestSchema],
//other credentials that are not important//
});
And the friendRequestSchema:
const friendRequestSchema = new mongoose.Schema({
from: { type: Schema.Types.ObjectId, ref: "User" },
to: { type: Schema.Types.ObjectId, ref: "User" },
});
Basically friendsRequests is an array consisting of who requested to add the user to the friends list (which is the from property) and whom the user wants to add to their friends list (which is the to property).
For the query, I am trying to sort out how to send a response without containing the users that are inside the user's friendsRequests array.
If i do this :
const recFriends = await User.findOne({ _id: req.user }).select(
"friendsRequests"
);
i will get back the array with objects containing either sent or received requests. Now i want to query again the User model and have it not return elements from this array. How would i go about doing that?

Related

Removing reference from a child collection in MongooseJS

I have a schema that includes an array of child references:
const schemaSet = {
userSchema: new Schema({
name: {
type: String,
required: true
}
}),
groupSchema: new Schema({
name: {
type: String,
required: true
},
members: [ {
type: Schema.Types.ObjectId
ref: 'User'
}]
})
}
This is working fine in terms of being able to create groups and add users to them, but I find I can't remove a user from the group.
The closest I have got so far is this:
async removeUser(group, userId) {
console.log("Before: group has "+group.members.length+" members");
await group.members.pull({ _id : userId });
console.log("After: group has "+group.members.length+" members");
await group.save();
}
This logs out the correct size before and after the call and runs with no errors, but the next time I retrieve that group, the member is still there. I have tried using remove as well with much the same outcome.
I only want to remove the reference from the members collection, the User needs to persist. How do I persist the removal of the reference to a document?
It took me a while to discover this. If you have a group model (created using const Group = mongoose.model("Group", groupSchema)), and the id of the user you want to remove from the group, you can use the following syntax: const updatedGroup = await Group.findOneAndUpdate({ members: userId }, { $pull: { members: userId }}, {new: true, useFindAndModify: false});
The third parameter in that function indicates that the updated document should be returned, and ensures that you don't get a warning. Actually, that syntax will only work if the group to user relationship is one to many (i.e. any user can only be in one group). If the relationships is many to many, I guess you would have to use the following:
await Group.where({ members: userId}).update({ $pull: { members: userId }});

MongoDB joining user and likes collection based on reference ids

I am trying to create a little social network using ExpressJS and MongoDB. I have a little problem relating to likes and posts collection. I know you can embed a likes inside a posts collection, but I have decided to separate both of the collection and use reference ids so I can join them later on. The main problem I have currently is this, how do I include the likes reference on the posts collection?
Let's say my posts schema looks something like this:
const PostSchema = new Schema({
content: { type: String, required: true },
isLiked: false,
}, { timestamps: true });
and my likes schema looks something like this:
const LikeSchema = new Schema(
{
// The user who is liking the post.
user: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
// The post that is being liked.
question: {
type: Schema.Types.ObjectId,
ref: 'Question',
required: true
},
},
{ timestamps: true }
);
I wanna make it so that whenever I try to query the posts collection, I can also get the likes embedded in it by referencing the collection and not modifying the schema to have embedded likes in it.
An example response:
{
_id: ObjectId("test"),
content: 'A post',
isLiked: false,
likes: ["A user object here based on the `likes collection`"]
}
You have to obtain them before sending the response:
Find all the likes of that post, something similar to Like.find({ question: <postId> })
Then you can resolve the users of that likes, in the command above you can concatenate .populate('user') with the mongoose populate feature
If you are interested only to the user object and not the entire like object, you can extract resolved user: const users = likes.map(x => x.user)
Then you can add the users array to the post object and sending the final object as response

how to multi ref in mongoose

I am trying to ref two documents in one property, i have been checking the oficial documentation but i didn't get the solution...
At the moment i am trying this...
items: [{
type: mongoose.Schema.Types.ObjectId,
ref: ['items','users']
}],
In the documentation they mention refPath... but i could not populate both models... any solution for this?
// LINK TO DOCUMENTATION
https://mongoosejs.com/docs/populate.html#dynamic-ref
You don't need to pass refs in arrays. Here is the simple solution:
Mongoose Model (Report.js):
You can clearly see that I did not pass any ref to my Model but still, you can use multiple refs in post/get APIs. I will show you next.
const mongoose = require('mongoose');
const reportSchema = new mongoose.Schema({
reportFrom : {
type: mongoose.Schema.Types.ObjectId,
require: true,
},
reportTo: {
type: mongoose.Schema.Types.ObjectId,
require: true,
},
}
);
module.exports = mongoose.model("report", reportSchema);
Above "reportTo" means the Id of someone post whom the user is going to report or the id of user profile whom the user is going to report. Means "reportTo" may be an ID of User Profile or Post. So, if "reportTo" contains user Id then I have to refer to users collection but if "reportTo" contains post Id then I have to refer to posts collection. So, how I can use two refs. I will simply pass type query from postman to tell which ref to go either posts or users. See below my API request:
APIs file (reports.js)
const reports = req.query.type === "Post" ? await Report.find({reportTo: req.params.id}).populate({
path: 'reportFrom', // attribute name of Model
model: "User", // name of model from where you want to populate
select: "name profilePicture", // get only user name & profilePicture
}).populate({
path: 'reportTo', // attribute name of Model
model: "Post",
}).sort({ _id: -1 })
: req.query.type === "Profile" ? await Report.find({reportTo: req.params.id}).populate({
path: 'reportFrom', // attribute name of Model
model: "User",
select: "name profilePicture",
}).populate({
path: 'reportTo', // attribute name of Model
model: "User",
select: "name profilePicture",
})
.sort({ _id: -1 })
: null
return res.status(200).json(reports);
See the line 7 & 15, you can clearly see how I use two different refs for same attribute. In first case, reportTo is refered to Post Model & in second case reportTo is refered to User Model.

Cannot set the reference to the user document in courses mongoose

I am currently working on a RESTful API, and I am trying to reference the users schema in the courses document such that, when a POST request gets sent to the route of the course, a course is created in the DB and has as one of its fields a reference to the user that created it. However, for the life of me, I cannot figure out why the "user" field is not appearing when I post. There seem to be quite a few of these questions here on Stack so I may just be adding to the pile, but I tried their solutions and they did not work for me
var mongoose = require('mongoose')
var Schema = mongoose.Schema
var userSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
emailAddress: {
type: String,
required: true
},
password: {
type: String,
required: true
}
});
var CourseSchema = new Schema({
user: {type: Schema.Types.ObjectId, ref: 'User'}, //FOR some reason this is not showing up on any courses created using the
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
estimatedTime: {
type: String
},
materialsNeeded: {
type: String
}
});
var User = mongoose.model('User', userSchema);
var Course = mongoose.model('Course', CourseSchema);
module.exports = {Course, User};
Do you see anything in here that would preclude the user field from appearing when a new course is created?
I have attached some screenshots to further explain.
This first image is a screen of the currently authenticated user credentials (fake data obviously). This is the user that is sending the POST request for the new course. I would expect his information to be attached to the course (see screenshot 3)
This image shows the body of the request that is sent. You can see that the key-value pairs match what is in the CourseSchema. I would expect that the "user" field would be created once the POST request is sent.
This last image is some dummy data that is the expected result.
Thanks all for taking a look at this!
User field will not be automatically added to the course document. You have to manually set the user field in the request body itself or while creating a course.
Example of the course body to be sent:-
{
user: "userId",
title: "test",
description: "test",
estimatedTime: "test",
materialsNeeded: 1
}
Also, the result of this will not include the whole user document as you have mentioned in the expected result. It will only return the userId. However, while accessing the course you can populate the user field to get the whole user document. Example for the same
Course.find({...query}).populate("user")

how to instert populated documents without: casting it in ObjectId then populate

I would like to avoid this (see comments below):
var UserSchema = Schema({
name: String
});
var UserGroupSchema = Schema({
users: [ { type: Schema.Types.ObjectId, ref: 'user' } ]
});
var user = new User({ name: 'John' });
var userGroup = new userGroup();
userGroup.users.push(user); // auto cast user in its objectId ! How to avoid that ?
UserGroup.populate(userGroup, { path: 'users', model: 'User' }); // Get back the user object (this step should be obsolete)
I do not want to declare this:
var UserGroupSchema = Schema({
users: [ User ]
});
Because i want to be able to find my users without looking into UserGroups.
I really hope there is something to avoid that because i don't see how to write clean code this way.
Because i want to be able to find my users without looking into UserGroups.
Specifically that means that you can only use references, which means saving new users separately before pushing a reference onto (and saving) a UserGroup instance.
I assume that a user can belong to more than one UserGroup, which means using subdocs (the users : [ User ] variant) can't really be used anyway.