Updating mongoose schema fields concurrently - node.js

I'm trying to update a mongoose schema. Basically I have two api's '/follow/:user_id' and '/unfollow/:user_id'. What I'm trying to achieve is whenever user A follows user B , user B followers field in mongoose will increment as one.
As for now I managed to get only following fields increment by one but not the followers fields.
schema.js
var UserSchema = new Schema({
name: String,
username: { type: String, required: true, index: { unique: true }},
password: { type: String, required: true, select: false },
followers: [{ type: Schema.Types.ObjectId, ref: 'User'}],
following: [{ type: Schema.Types.ObjectId, ref: 'User'}],
followersCount: Number,
followingCount: Number
});
Updated version: I tried my solution, but whenever I post it, it just fetching the data ( I tried the api's on POSTMAN chrome app ).
api.js
// follow a user
apiRouter.post('/follow/:user_id', function(req, res) {
// find a current user that has logged in
User.update(
{
_id: req.decoded.id,
following: { $ne: req.params.user_id }
},
{
$push: { following: req.params.user_id},
$inc: { followingCount: 1}
},
function(err) {
if (err) {
res.send(err);
return;
}
User.update(
{
_id: req.params.user_id,
followers: { $ne: req.decoded.id }
},
{
$push: { followers: req.decoded.id },
$inc: { followersCount: 1}
}
), function(err) {
if(err) return res.send(err);
res.json({ message: "Successfully Followed!" });
}
});
});
These codes only manage to increment the user's following fields, and without duplication. How do I update logged in user's following fields and as well as other user's followers fields at the same time?
Updated version: it keeps fetching the data.

May be this is how you want to. Instead of using update, you can also use findOneAndUpdate from Mongoose queries.
apiRouter.post('/follow/:user_id', function(req, res) {
User.findOneAndUpdate(
{
_id: req.decoded.id
},
{
$push: {following: req.params.user_id},
$inc: {followingCount: 1}
},
function (err, user) {
if (err)
res.send(err);
User.findOneAndUpdate(
{
_id: req.params.user_id
},
{
$push: {followers: req.decoded.id},
$inc: {followersCount: 1}
},
function (err, anotherUser) {
if (err)
res.send(err);
res.json({message: "Successfully Followed!"})
});
});
}
If you are not sure about it is updated or not, you can just use console.log() for both user and anotherUser variables to see changes.

Related

Why $pull does not work when using mongodb on node js [duplicate]

i'm trying to do a pretty simple operation, pull an item from an array with Mongoose on a Mongo database like so:
User.update({ _id: fromUserId }, { $pull: { linkedUsers: [idToDelete] } });
fromUserId & idToDelete are both Objects Ids.
The schema for Users goes like this:
var UserSchema = new Schema({
groups: [],
linkedUsers: [],
name: { type: String, required: true, index: { unique: true } }
});
linkedUsers is an array that only receives Ids of other users.
I've tried this as well:
User.findOne({ _id: fromUserId }, function(err, user) {
user.linkedUsers.pull(idToDelete);
user.save();
});
But with no luck.
The second option seem to almost work when i console the lenghts of the array at different positions but after calling save and checking, the length is still at 36:
User.findOne({ _id: fromUserId }, function(err, user) {
console.log(user.linkedUsers.length); // returns 36
user.linkedUsers.pull(idToDelete);
console.log(user.linkedUsers.length); // returns 35
user.save();
});
So it looks like i'm close but still, no luck. Both Ids are sent via the frontend side of the app.
I'm running those versions:
"mongodb": "^2.2.29",
"mongoose": "^5.0.7",
Thanks in advance.
You need to explicitly define the types in your schema definition i.e.
groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }],
linkedUsers: [{ type: Schema.Types.ObjectId, ref: 'User' }]
and then use either
User.findOneAndUpdate(
{ _id: fromUserId },
{ $pullAll: { linkedUsers: [idToDelete] } },
{ new: true },
function(err, data) {}
);
or
User.findByIdAndUpdate(fromUserId,
{ $pullAll: { linkedUsers: [idToDelete] } },
{ new: true },
function(err, data) {}
);
I had a similar issue. I wanted to delete an object from an array, using the default _id from mongo, but my query was wrong:
const update = { $pull: { cities: cityId }};
It should be:
const update = { $pull: { cities: {_id: cityId} }};

node js mongoose delete a from an id in document array

I've two collection of reference both collection together. One of the collection is user and the other collection is project.
So, a user can add project to the projects collection, then one of the user type called supervisor can add staff to the project and the project id saved to the user collection which referred to staff document on the project collection.
So actually i need to do when admin deletes a supervisor from the user collection it deletes all the projects created by supervisor users's id that equal to addedBy documents which deleted from the users collection.
So my problems is when i do this process i need to delete all the project id is equal to the users collection projectId. it's an array and I tried to do this to many times but i couldn't find a solution. I'll provide all of the source code. That i created for this project.
Users collection
const userSchema = new Schema({
firstName: {
type: String
},
lastName: {
type: String
},
email: {
type: String
},
username: {
type: String
},
password: {
type: String
},
usertype: {
type: Schema.ObjectId,
ref: 'usertypes'
},
projectId: [{
type: Schema.ObjectId,
ref: 'projects'
}]
});
Project collection
const proSchema = new Schema({
projectName: {
type: String
},
description: {
type: String
},
addedBy: {
type: Schema.ObjectId,
ref: 'users'
},
staff: [{
type: Schema.ObjectId,
ref: 'users'
}]
});
Here is the query that i tried to do the process that i mentioned in above
Users
.findByIdAndRemove({
_id: req.params.id
})
.then(function(){
Projects
.remove({
userId: req.params.id
})
.then(function(err, project){
if(err) throw err;
console.log(project.id)
Users
.update({}, {
$pull: {
projectId: project.id
}
}, function(){
res.json({
success: true,
message: "Deleted"
});
});
});
});
I think the problems are
(1) Model.findByIdAndRemove only expects the ID (not the condition) i.e. Users.findByIdAndRemove(req.params.id) instead of Users.findByIdAndRemove({ _id: req.params.id })
(2) Model.remove's callback does not have a second argument in Projects.remove({ userId: req.params.id }).then(function (err, project) {. As well, you don't have a userId field in your ProjectSchema.
I would do
// delete user
Users.findByIdAndRemove(req.params.id, function (err, user) {
console.log('deleting user', user);
if (err)
throw err;
// delete user's projects
Projects.remove({ addedBy: user._id }, function (err) {
console.log('deleting projects');
if (err)
throw err;
// delete project references
Users.update({}, { $pull: { projectId: { $in: user.projectId }}}, function (err) {
console.log('deleting project references');
if (err)
throw err;
res.json({ success: true, message: "Deleted" });
});
});
});
(3) user.projectId is an array of ObjectIDs, so you need to use $in (see first example).
Aside: projects is a better name than projectId. The latter is ambiguous because a user has multiple projects not projectIds.
User.findByIdAndUpdate(req.params.idRec,
{ $pull: { comments: { _id: comm._id } } },
function (err, doc) {
if (!err) {
res.status(200).send()
} else {
res.render('error', { error: err })
}
})

How to trigger a function whenever a mongoose document is updated

I have a user model schema in mongoose which contains a list of friends and groups and stats info like so...
var user = new Schema({
email: { type: String, required: true, unique: true },
password: { type: String, required: true, select: false },
roles: [{ type: String, required: true }],
friends: [{ type: Schema.Types.ObjectId, ref: 'User' }],
groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }],
stats : {
nbrFriends: { type: Number, required: false },
nbrGroups: { type: Number, required: false }
}
}, {
timestamps: true
});
I need to update the users stats whenever a change is made to the friends or groups fields to contain the new number of friends or groups etc. For example, when the following function is called on a user:
var addGroup = function(user, group, cb) {
user.groups.push(group);
User.findOneAndUpdate({ _id: user._id }, { $set: { groups: user.groups }}, { new: true }, function(err, savedResult) {
if(err) {
return cb(err);
}
console.log('updated user: ' + JSON.stringify(savedResult));
return cb(null, savedResult);
});
};
How could I make sure the stats is automatically updated to contain the new number of groups the user has? It seems like a middleware function would be the best approach here. I tried the following but this never seems to get called...
user.pre('save', function(next) {
var newStats = {
nbrGroups: this.groups.length,
nbrPatients: this.friends.length
};
this.stats = newStats;
this.save(function(err, result) {
if(err) {
console.log('error saving: ' + err);
} else {
console.log('saved');
}
next();
});
});
You need to use the middleware a.k.a. hooks:
Middleware (also called pre and post hooks) are functions which are passed control during execution of asynchronous functions.
See the docs:
http://mongoosejs.com/docs/middleware.html
From version 3.6, you can use change streams.
Like:
const Users = require('./models/users.js')
var filter = [{
$match: {
$and: [{
$or:[
{ "updateDescription.updatedFields.friends": { $exists: true } },
{ "updateDescription.updatedFields.groups": { $exists: true } },
]
{ operationType: "update" }]
}
}];
var options = { fullDocument: 'updateLookup' };
let userStream = Users.watch(filter,options)
userStream.on('change',next=>{
//Something useful!
})
You should update with vanilla JS and then save the document updated to trigger the pre-save hooks.
See Mongoose docs
If you have many keys to update you could loop through the keys in the body and update one by one.
const user = await User.findById(id);
Object.keys(req.body).forEach(key => {
user[key] = req.body[key];
}
const saved = await user.save();

Issues With Mongoose $push

I really just need a second set of eyes here. I am using the Mongoose npm to create a new entry in my MongoDB. Then I am using that new entry in a few functions in the Async npm.
The issue that I am having is that I am getting the first three console logs, "hitter", "create", and "req.body.campaign_id" but nothing past that. I think it has to do with my $push in the first findByIdAndUpdate. Please see my code and schema below.
Code! See async parallel "campaign" function
Bid.create(req.body, function(err, bid){
console.log('create')
async.parallel({
campaign: function(done) {
console.log(req.body.campaign_id)
Camapaign.findByIdAndUpdate(req.body.campaign_id, {
$push: { bids: bid._id }
}, {
safe: true,
upsert: true
}, function(err, campaign){
console.log('camp', 2)
if(err) {
console.log(err)
done(err)
} else {
done(null, campaign)
}
});
},
user: function(done) {
console.log('user', 1)
User.findByIdAndUpdate(req.body.user_id, {
$push: {'bids': bid._id }
}, {
safe: true,
upsert: true
}, function(err, bid){
console.log('user', 2)
if(err) {
done(err)
} else {
done(null, bid)
}
});
}
}, function(err, response){
console.log('response')
if(err) {
console.log(err)
} else {
res.status(200).send(response);
}
});
})
Campaign Schema
var campaignSchema = new mongoose.Schema({
title:String,
imgUrl:[String],
shortDesc: { type: String, set: shortenDesc },
longDesc:String,
duration: Number,
price: Number,
desired_price: Number,
bids: [{ type: mongoose.Schema.Types.ObjectId, ref: 'bidSchema' }],
owner_id: { type: mongoose.Schema.Types.ObjectId, ref: 'userSchema' }
});
User Schema
var schema = new mongoose.Schema({
name: String,
email: {
type: String
},
password: {
type: String
},
salt: {
type: String
},
twitter: {
id: String,
username: String,
token: String,
tokenSecret: String
},
facebook: {
id: String
},
google: {
id: String
},
campaigns: [campaignSchema],
bids: [{type: mongoose.Schema.Types.ObjectId, ref: 'bidSchema'}]
});
Please let me know if you need to see anything else. All help is appreciated.
Thanks!
You are doing Camapaign.findByIdAndUpdate are you sure Camapaign isn't mispelled there? Shouldn't it be Campaign?

Updating 2 mongoose schemas in an api call

Currently I'm trying to update Two different User Schema's in an api call.
The first schema is logged in user schema, we give it a name = Tom
The second schema is other users who signup for the app, we give it a name = John
The schema code
schema.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt-nodejs');
var UserSchema = new Schema({
name: String,
username: { type: String, required: true, index: { unique: true }},
password: { type: String, required: true, select: false },
followers: [{ type: Schema.Types.ObjectId, ref: 'User'}],
following: [{ type: Schema.Types.ObjectId, ref: 'User'}],
followersCount: Number,
followingCount: Number
});
module.exports = mongoose.model('User', UserSchema);
The api name is '/follow/:user_id', what I want to achieve is . Whenever user Tom follows other user's like John, Tom's following field will be updated as well as John's follower field.
My current attempt (req.decoded.id is the logged in user)
api.js
// The first way
apiRouter.post('/follow/:user_id', function(req, res) {
User.findOneAndUpdate(
{
_id: req.decoded.id,
following: { $ne: req.params.user_id }
},
{
$push: { following: req.params.user_id},
$inc: { followingCount: 1}
},
function(err, currentUser) {
if (err) {
res.send(err);
return;
}
console.log(currentUser);
});
User.findOneAndUpdate(
{
_id: req.params.user_id,
followers: { $ne: req.decoded.id }
},
{
$push: { followers: req.decoded.id },
$inc: { followersCount: 1}
}, function(err, user) {
if(err) {
res.send(err);
return;
}
res.json({
message: "Successfully followed"
});
}
)
});
//Second way
apiRouter.post('/follow/:user_id', function(req, res) {
// find a current user that has logged in
User.update(
{
_id: req.decoded.id,
following: { $ne: req.params.user_id }
},
{
$push: { following: req.params.user_id},
$inc: { followingCount: 1}
},
function(err) {
if (err) {
res.send(err);
return;
}
User.update(
{
_id: req.params.user_id,
followers: { $ne: req.decoded.id }
},
{
$push: { followers: req.decoded.id },
$inc: { followersCount: 1}
}
), function(err) {
if(err) return res.send(err);
res.json({ message: "Successfully Followed!" });
}
});
});
Both have problems,
The first way: The problem is, 'Can't set headers that already sent', because of the two separate mongoose query in one api call, it response twice that's why I get that error.
The second way: The problem is, the following field of logged in user(Tom) gets updated while the other user's followers field (John) return null. I console log both value and as well test it with POSTMAN chrome app.
Lend me your thoughts fellas!
The first route you took seems to be fine.
However, as #cdbajorin mentioned, the error "can't send headers that already sent" has nothing to do with mongoose but the fact that you're trying to set the header after sending a response to the client already. (see this lovely answer)
My suggestion would be to make sure that both database calls are successful before you send a response.
You may also want to look into a two phase commit in this situation, as MongoDB does not support traditional DB transactions and you're updating two documents, one at a time. If for some reason either database call fails, a procedure to recover to a stable state should be taken.
The first way can be improved in two ways. One is updating followers field inside the callback of updating following field. The other way is using async-waterfall. I suggest to go with async-waterfall(npm async-waterfall).
The second way it is correct (could be improved running both of them in parallel) I guess the problem is in another place. I don't know which framework you are using but i guess the field _id is from mongoDB and is an ObjectId and looks like that the decoded.id can be an objectId while the one that comes from the request is of course just a string. So I guess it is empty because it does not find any user with that string.
Try do make it an objectId out of that string ( reffering to req.params.user_id in the second query)

Resources