Mongoose middleware when adding multiple elements to a mongo array - node.js

Basically I have two mongodb collections, users and projects.
I want to keep track on which projects a user is working on,
and also which users that are working on a given problem.
const User = mongoose.model('User', new Schema({
name: {
type: String,
},
_projects: [{
type: Schema.ObjectId,
ref: 'Project'
}]
})
and
const Project = mongoose.model('Project', new Schema({
title: {
type: String,
},
_usersWorkingOn: [{
type: Schema.ObjectId,
ref: 'User',
}]
})
Now, let's say i want to add multiple existing user-id to an existing project.
What would best practice be?
Right now I'm using findOneAndUpdate to get the project document to push the user-id to the array. And then a mongoose middleware were I take the last added entry and get the user-id from there:
Schema.post('findOneAndUpdate', addProjectToUser)
function addProjectToUser(updatedProject, next) {
User.findByIdAndUpdate(
updatedProject._usersWorkingOn[updatedProject._usersWorkingOn.length - 1],
{ $push: { '_projects': updatedProject._id}},
{ 'new': true }
).then(() => {
next()
})
}
This works when I add one user to a project. But if a want to add multiple users in one save, addProjectToUser have no way of knowing how many entries were added to the array.
Am I missing something here, or thinking about it all wrong?

Related

Using "deleteMany" post middleware in mongoose

In my Nodejs and Express app, I have a mongoose User schema, a Post schema and a Comment schema as follows:
const UserSchema = new Schema({
username: {
type: String,
required: true,
unique: true
},
password: String,
posts : [
{
type : mongoose.Schema.Types.ObjectId,
ref : 'Post'
}
]
});
const PostSchema = new Schema({
author : {
type : mongoose.Schema.Types.ObjectId,
ref : 'User'
},
createdAt: { type: Date, default: Date.now },
text: String,
comments : [
{
type : mongoose.Schema.Types.ObjectId,
ref : 'Comment'
}
],
});
const CommentSchema = new Schema({
author : {
type : mongoose.Schema.Types.ObjectId,
ref : 'User'
},
createdAt: { type: Date, default: Date.now },
text: String
});
I have coded the general CRUD operations for my User. When deleting my user, I can easily delete all posts associated with that user using deleteMany:
Post.deleteMany ({ _id: {$in : user.posts}});
To delete all the comments for all the deleted posts, I can probably loop through posts and delete all the comments, but I looked at mongoose documentation here and it seems that deleteMany function triggers the deleteMany middleware. So In my Post schema, I went ahead and added the following after defining schema and before exporting the model.
PostSchema.post('deleteMany', async (doc) => {
if (doc) {
await Comment.deleteMany({
_id: {
$in: doc.comments
}
})
}
})
When deleting user, this middleware is triggered, but the comments don't get deleted. I got the value of doc using console.log(doc) and I don't think it includes what I need for what I intend to do. Can someone tell me how to use the deleteMany middleware properly or if this is not the correct path, what is the most efficient way for me to delete all the associated comments when I delete the user and their posts?
deleteMany will not give you access to the affected document because it's a query middleware rather than a document middleware (see https://mongoosejs.com/docs/middleware.html#types-of-middleware). Instead it returns the "Receipt-like" object where it tells it successfully deleted n objects and such.
In order for your hook to work as expected, you'll need to use something other than deleteMany, such as getting all of the documents (or their IDs), and loop through each one, using deleteOne.

Is there an opposite to mongoose populate() method? (Mongoose, node.js, express)

I have two mongoose schemes one for users and one for posts.
One user can like many posts.
At the end I would like to present in the client side all the posts the user liked in one section which I can do using the populate() method, and in another section the posts the user didn’t like without creating duplicates of the liked posts.
Is there unpopulate() method I can use to get only the unliked posts? If not, what is the best way to approach this?
userScheme =
{
// some other fields…
post: {
type: Schema.Types.ObjectId,
ref: 'Post'
}
}
postScheme =
{
// some other fields…
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
likes: [
{
type: Schema.Types.ObjectId,
ref: 'User'
}
],
}
You can query for all the posts that doesn't have the particular user in their likes array i.e. is not liked the user yet. Reference
Post.find({ likes: { $nin: [user._id] }})

Mongoose: Modeling and updating many to many

I'm very new to mongodb and mongoose and I'd like to know how to handle many to many relations. I know mongodb is a non-relational database, but how would I model something like Users and Groups.
A User can join multiple groups while a group can have multiple users.
My Schemas look like this:
User
const UserSchema: Schema = new Schema({
username: {
type: String,
required: true,
unique: true,
},
groups: [
{
type: Schema.Types.ObjectId,
ref: 'Group',
},
],
});
Group
const GroupSchema: Schema = new Schema({
name: {
type: String,
required: true
},
users: [
{
type: Schema.Types.ObjectId,
ref: 'User',
},
],
});
So, with an endpoint like 'users/:id/assigngroup/:groupid I would like to add an User_id to User.groups and the groupid to Group.users.
For my endpoint I do something like this:
User.findOneAndUpdate({ _id }, { $addToSet: { groups: groupId } }, { new: true }).populate('groups').exec((err, user) => {
Group.findOneAndUpdate({ _id: groupId }, { $addToSet: { users: _id } }, { new: true }).exec((err, group) => {
res.send(user);
});
})
This works tho, but the problems I encountered are:
I could still add a groupId to User.users even if the Group with this id doesn't exist. I could check this with an additional Group.findOne() before the User.findOneAndUpate but this doesn't feel like it's the best/quickest way in mongoose.
How should I handle a rollback if, for example one of the findOneAndUpdate, fails?
What I need is a clean way to check if the Group with the groupid exists, then check if the user with the _id exists and after that I can add the groupid to User.groups and the _id to Group.users (and not being able to add the same id multiple times of course). What would be the best way to do this? Using findOne/findOneUpdate mutiple times for each check doesn't feel like the right way.
Please let me know if my approach to model this "relation" is completely wrong.
Thanks! :)
Edit: I don't think it's the smart way to save a reference in both models. I removed groups from the User model. So to assign a User I just use
Group.findOneAndUpdate({ _id: groupId }, { $addToSet: { users: _id } }, { new: true }).populate('users', '_id username').exec().then(group => { });
And the easy way to get all Groups of a User would be
Group.find({
"users": new Types.ObjectId(id)
}).exec(callback);
So I don't need to update a reference twice if I assign/remove a User.

Add Data to MongoDB Models At Different Times

I have a pretty good understanding of mongdoDB with mongoose, but this one aspect of it has been confusing me for a while now. I have a user.js model with a username, password, etc (all the basic user stuff). This data is added when a user registers for an account. But each user also has more data linked to it that IS NOT created or added at the time of registering.
This is my model:
// User Schema
const UserSchema = new Schema({
// PERSONAL USER INFO
username: {
type: String,
index: true
},
email: {
type: String
},
password: {
type: String
},
// INSTAGRAM ACCOUNT INFORMATION
ig_username: {
type: String
},
ig_password: {
type: String
},
story_price: {
type: Number
},
fullpost_price: {
type: Number
},
halfpost_price: {
type: Number
},
leads: [{
title: { type: String }
}]
});
// EXPORTS
const User = module.exports = mongoose.model('user', UserSchema);
All the field except "leads" are created at the time of registering. But I want to fill the Leads field using another form. I've tried the .update(), .save(), $set, $push, and all kinds of methods, but I cannot get it to work.
Most solutions that I have found use var user = new User({...}) to create a new user and then use .save() after adding the additional data. But this seems wrong since the user has already been created and I am just trying to add data to an additional field.
I think I'm just glossing over something basic, but if there is a way to do this I would be glad to hear it. Thanks!
I would create a sub-schema for leads
// Create a sub-schema for leads
const leadsSubSchema = new Schema({
title: {
type: String,
},
});
// Create a schema for user
const UserSchema = new Schema({
username: {
type: String,
index: true
},
// ...
leads: [leadsSubSchema]
});
// EXPORTS
const User = module.exports = mongoose.model('user', UserSchema);
Then for the update
User.update({
_id: user_id,
}, {
$push: {
leads: lead_to_add,
},
});

Mongoose: how to structure a schema with a SET of subdocuments (one unique field)?

I'm trying to make the following schema to work:
var FormSchema = new mongoose.Schema({
form_code: { type: String, unique: true },
...
});
var UserSchema = new mongoose.Schema({
...
submissions: [{
form_code: { type: String, unique: true },
last_update: Date,
questions: [{
question_code: String,
answers: [Number]
}]
}],
});
The rationale here is that a user can have many unique forms submitted, but only the last submission of each unique form should be saved. So, ideally, by pushing a submission subdocument when updating a user, the schema would either add the submission object to the set, or update the subdocument containing that form_code.
The following code doesn't work as desired (it pushes the new subdocument even if the form_code is already present):
User.findOneAndUpdate(
{ _id: user.id },
{ $addToSet: { submissions: submission_object } },
function (err, user) {
// will eventually have duplicates of form_code at user.submissions
}
);
The above schema clearly doesn't work, what must be changed to achieve that "upsertToSet"?

Resources