node js mongoose delete a from an id in document array - node.js

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 })
}
})

Related

Express +Mongoose push object into array of objects and adding two more rows into the object while inserting

I want to add a user inside the array objects, and add two more rows while inserting.
These are the two mongoose models that are used.
module.exports = mongoose.model('Users', {
id:String, //the same id as user.id
nick:String, //the same nick as user.nick
});
module.exports = mongoose.model('Stores', {
id: String,
user: [{
id: String,
nick: String,
nr: Number,
earnings: Number
}],
total: Number
});
So let's say I want to insert a Users that is found by its id(not the auto-generated). (I have the removed the if (err) to make it readable).
This how i try to solve right now.
Users.findOne({id : req.body.userid }, function(err, user) {
//what user contains
user = { _id: 551fb0b688eacdf0e700000c,
id: '123abc',
nick: 'Foo',
__v: 0 }
//I want to add this into the user and push it into exsisting array of
objects that is 'users'
//But before that i want to add more info to the user,
//the desired format that I want to insert into 'users'
user = {id: '123abc',
nick: 'Foo',
nr: req.body.nr, //new
earnings: req.body.earnings} //new
Stores.update({id: req.params.id},
{$push: { 'users' : user }}, function(err, store) {
});
});
The current result is the following.
users: [
{
id: "123abc",
nick: "Foo"
}]
How can I solve this?
The schema design as is creates at least one problem. What if a user updates their nick? Rather than only updating the Users collection you would also need to update every document in Stores that matches the user. You could use a ref and then populate to negate this concern.
module.exports = mongoose.model('Users', {
id: String, //the same id as user.id
nick: String, //the same nick as user.nick
});
module.exports = mongoose.model('Stores', {
id: String,
users: [{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Users'
},
nr: Number,
earnings: Number
}],
total: Number
});
Now the query would be:
Users.findOne({
id: req.body.userid
}, function(err, user) {
Stores.update({
id: req.params.id
}, {
$push: {
'users': {
user: user,
nr: req.body.nr, //new
earnings: req.body.earnings
}
}
}, function(err, store) {});
});
Later when you need to query Stores:
Stores
.find(QUERY)
.populate('users')
.exec(function(err, stores) {...
});

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)

Updating mongoose schema fields concurrently

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.

Insert into embedded document

I have the following schema:
var UserSchema = new Schema({
username: { type: String, required: true },
password: { type: String, required: true },
userType: { type: String, default: 'user'},
quizzHistory: [{
quizzName: String,
quizzScore: Number
}]
});
my goal is to change document into embedded quizzHistory or insert new one if not exists document in embedded quizzeHistory
I try to set document into embedded quizzHistory :
User.findOneAndUpdate({ _id: req.session.user['_id'], 'quizzHistory.quizzName': testName},
{
'$set': {
'quizzHistory.$.quizzName': testName,
'quizzHistory.$.quizzScore': finalScore
}
}, {upsert: true},
function(err, upd) {
console.log("added");
})
code above works if there is document in quizzHistory with required _id and quizzHistory.quizzName,but don't pushed new one if there isn't any document.
Is there any way in Mongodb to change document into embedded collection or insert new one if not exists ?
the reason is because you are using "find and update" you are not handling the condition when the row hasn't been found and create a new document, being said that you need manage the in a different way like
User.update({ _id: req.session.user['_id'], 'quizzHistory.quizzName': testName},
{
'$push': {
'quizzHistory.quizzName': testName,
'quizzHistory.quizzScore': finalScore
}
}, {upsert: true},
function(err, upd) {
console.log("added");
})
this worked for me
User.update({ _id: req.session.user['_id'],'quizzHistory.quizzName':testName},
{
$set: {
"quizzHistory.$.quizzName":testName,
"quizzHistory.$.quizzScore":finalScore
}
},
function(err, upd) {
if(!upd){
User.update({ _id: req.session.user['_id']},
{ "$addToSet": { quizzHistory: newScoreData }},function(err,data){
});
}
});
If you want to benefit for all possible plugins and methods added to model and don't want to fight with actual Mongo queries you should first retrieve user object, then push new element to quizzHistory array and do save. Something like below (you need to align that code to your needs).
var entry = {
quizzName : 'abc',
quizzScore : 123
};
User.findOne({_id:id, function(err, object) {
if(err) {
return someErrorCallback();
}
var
object.quizzHistory.push(entry);
object.save(function(err) {
if(err) {
return someErrorCallback();
}
someSuccessCallback();
});
});
Updating directly may be efficient, but questions usage of mongoose.
Hope it makes sense.

Mongoose - Better solution with appending additional information

I have two Schemas:
var ProgramSchema = new Schema({
active: Boolean,
name: String,
...
});
var UserSchema = new Schema({
username: String,
email: { type: String, lowercase: true },
...
partnerships: [{
program: { type: Schema.Types.ObjectId, ref: 'Program' },
status: { type: Number, default: 0 },
log: [{
status: { type: Number },
time: { type: Date, default: Date.now() },
comment: { type: String },
user: { type: Schema.Types.ObjectId, ref: 'User' }
}]
}]
});
Now I want to get all Program docs, but also append 'status' to each doc, to return if the program is already in a partnership with the logged in user.
My solution looks like this:
Program.find({active: true}, 'name owner image user.payments', function (err, p) {
if(err) { return handleError(res, err); }
})
.sort({_id: -1})
.exec(function(err, programs){
if(err) { return handleError(res, err); }
programs = _.map(programs, function(program){
var partner = _.find(req.user.partnerships, { program: program._id });
var status = 0;
if(partner){
status = partner.status;
}
program['partnership'] = status;
return program;
});
res.json(200, programs);
});
The req.user object contains all information about the logged in user, including the partnerships array.
To get this solution to work, I have to append
partnership: Schema.Types.Mixed
to the ProgramSchema.
This looks a bit messy and thats why I am asking for help. What do you think?
When you want to freely modify the result of a Mongoose query, add lean() to the query chain so that the docs (programs in this case) are plain JavaScript objects instead of Mongoose doc instances.
Program.find({active: true}, 'name owner image user.payments')
.lean() // <= Here
.sort({_id: -1})
.exec(function(err, programs){ ...
Then you can remove partnership from your schema definition. Your query will also execute faster.

Resources