Cascade delete from array using Mongoose middleware remove hook - node.js

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.

Related

user.email become undefined in Node.js mongo

Our backend was deployed on Heroku and running without error until today but 2-3 hours ago our servers started to give crash, and the reason is our function below can't read property 'email' of user and prints undefined. No idea why this is happening suddenly. First, I thought it was related to the server but I'm getting the same result in localhost too. As i said before code was working literally 2-3 hours ago.
findMe function
exports.findMe = (req, res) => {
User.findOne(
{ _id: req.userData.userId },
{ email: 0, password: 0 },
(err, user) => {
if (err) {
return res.status(500).json({
...err,
});
}
console.log(user.email);
const expires = "10y";
const token = jwt.sign(
{
email: user.email,
userId: user._id,
username: user.username,
committeeId: user.committeeId,
role: user.role,
},
process.env.JWT_KEY,
{
expiresIn: expires,
}
);
return res.status(200).json({
user: user,
token: token,
});
}
);
};
Output of code above
{
"user": {
"education": {
"university": "YILDIZ TEKNİK ÜNİVERSİTESİ",
"department": "MATEMATİK MÜHENDİSLİĞİ",
"year": 3
},
"oldCommittees": {
"committeeId": null,
"title": null,
"year": null,
"date": "2021-08-28T21:53:48.992Z"
},
"isVerified": true,
"bio": null,
"photo": null,
"photoSm": null,
"photoXs": null,
"phoneNo": null,
"committeeId": "5d9360e99b572100172cc581",
"title": "CS Başkanı",
"role": 0,
"blockedUsers": [],
"_id": "5d9362ec9b572100172cc648",
"name": "Emir",
"surname": "Kutlugün",
"username": "emir-kutlugun2",
"date": "2019-10-01T14:30:04.884Z",
"__v": 0
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZDkzNjJlYzliNTcyMTAwMTcyY2M2NDgiLCJ1c2VybmFtZSI6ImVtaXIta3V0bHVndW4yIiwiY29tbWl0dGVlSWQiOiI1ZDkzNjBlOTliNTcyMTAwMTcyY2M1ODEiLCJyb2xlIjowLCJpYXQiOjE2MzAxODc2MzEsImV4cCI6MTk0NTc2MzYzMX0.Jjl_-mXN2Ozn96yJpqNPYPFl3mngnZ4N_I8KYBNYCoo"
}
console.log(user.email) logs undefined
This code:
User.findOne(
{ _id: req.userData.userId },
{ email: 0, password: 0 },
Uses mongoose's Model.findOne() method. The first parameter, { _id: req.userData.userId } is a document containing the conditions you want the document returned to match.
The second parameter, { email: 0, password: 0 } is the projection. This tells MongoDB which fields should be included in the result. The projection in your code is an example of returning all fields but excluded fields. The document you get back from this operation will have every field except email and password.
If you want to include email in the result for later use, you will need to remove it from this projection. You could change the projection to { password: 0 }, so the email field is included and the password field is still excluded.
Put together, it may look like this:
User.findOne(
{ _id: req.userData.userId },
{ password: 0 },

How to insert Multiple collections using .forEach() and check if it exist or not

I want to insert multiple user details using .forEach() into my Mongodb Database.
Before insert records I want to check whether user record is exist or not. If user is not exist then insert new User record otherwise update the existing user record.
Below is
var dataArray=[
{"id":"1","name":"abc","email":"abc#gmail.com"},
{"id":"2","name":"xyz","email":"xyz#gmail.com"},
{"id":"1","name":"abc","email":"abc#gmail.com"},
];
dataArray.forEach(function(dataVar){
//check record exist or not
User.findOne({id:dataVar.id},function(err,user){
if(!user){// Insert If user not exist
var userSchema=new User({
id:dataVar.id,
name:dataVar.name,
email:dataVar.email
});
userSchema.save(function(err,result){
console.log('New Record Inserted');
})
}else{ // Update records if user exist
User.update({id:dateVar.id},{email:dataVar.email},function(err,result){
console.log('Record Updated');;
});
}
})
});
When run this code snippet, its checking only first object from array and insert in my DB. But next time when 3rd object going to execute then its not checking and inserting like a new record.
I am not getting whats going on.
Please let me know how to solve it.
Thanks.
You should do your loop async
See: https://caolan.github.io/async/docs.html#eachOfSeries
Example code
var dataArray = [{
"id": "1",
"name": "abc",
"email": "abc#gmail.com"
}, {
"id": "2",
"name": "xyz",
"email": "xyz#gmail.com"
}, {
"id": "1",
"name": "abc",
"email": "abc#gmail.com"
}, ];
async.eachOfSeries(dataArray, function(dataVar, key, callback) {
User.findOne({
id: dataVar.id
}, function(err, user) {
if (!user) { // Insert If user not exist
var userSchema = new User({
id: dataVar.id,
name: dataVar.name,
email: dataVar.email
});
userSchema.save(function(err, result) {
console.log('New Record Inserted');
callback();
})
} else { // Update records if user exist
User.update({
id: dateVar.id
}, {
email: dataVar.email
}, function(err, result) {
console.log('Record Updated');;
callback();
});
}
})
}, function(err) {
if (err) console.error(err.message);
// configs is now a map of JSON data
console.log("All done")
});

Update array in mongoDB through my loopback application

I am developing API using loopback + mongoDB. In that i have to update an array in a document in mongoDB:
My model:
{
"userId": "546fgdfgd445",
"rrrr": "7",
"bbbbb": [
{}
],
"id": "57a437789521b58b12124016"
}
I need to update the array with new element with the data sent form client:
app.models.vvvvv.update({ userId: '546fgdfgd445' },{ $push: { "transactions": transactionData }}, function(err, res){
if(err){
console.log(err);
} else {
console.log(res);
}
});
But i am receiving error:
{ name: 'MongoError',
message: 'The dollar ($) prefixed field \'$push\' in \'$push\' is not valid for storage.',
driver: true,
index: 0,
code: 52,
errmsg: 'The dollar ($) prefixed field \'$push\' in \'$push\' is not valid for storage.' }
I am using mongoDb version 3.2.3.
Please share your ideas. Thanks in advance.
in model.json you need to enable extended operations like this :
"options": {
"mongodb": {
"collection": "model_collection",
"allowExtendedOperators": true
}
},
or
app.models.vvvvv.update({ userId: '546fgdfgd445' },
{ $push: { "transactions": transactionData }}, { allowExtendedOperators: true }, function(err, res){
if(err){
console.log(err);
} else {
console.log(res);
}
});

Trying to find record using $in in mongoose

I'm trying to find record using $in in mongoose. But it's not working for me. I have same query in mongo shell its working but in mongoose it is not workinh
my schema
{
"_id": "574f1f979f44e7531786c80f",
"name": "mySchool2",
"branch": "karachi",
"location": "Clifton ",
"block": false,
"created_at": 1464803223441,
"updated_at": 1464803223441,
"__v": 0,
"classes": [
"574f216afd487958cd69772a"
],
"admin": [
"574f20509f44e7531786c811",
"57508a2a3a0a919c16ace3c0"
],
"teacher": [
"574f20f39f44e7531786c812",
"575002b48188a3f821c2a66e",
"57500bbaea09bc400d047bf6"
],
"student": [
"574f2d56590529c01a2a473b",
"574f2e5842c5885b1b1729ab",
"574f2ed542c5885b1b1729ae",
"574f2f57555210991bf66e07",
"574f2fcd087809b11bd8d5e4",
"574f301d1f5025d61b7392b6",
"574f30481d02afff1bb00c71",
"574f30b01d02afff1bb00c74",
"574f310038136b3d1cf31b96"
]
}
My mongose query
app.services._chkAdminSchool = function(payload){
var deferred = q.defer();
School
//.find({ teacher:{$in:['574f20f39f44e7531786c812']}})
.find({_id : "574f1f979f44e7531786c80f",admin:{"$in":[Object("57508a2a3a0a919c16ace3c0")]}})
//.select(filers)
.exec(function(error, record) {
if (error) {
deferred.reject({
status:404,
message: 'Error in API',
error: error
});
} else {
if(record.length === 0){
deferred.reject({
message: 'Admin and school doesnot match',
data: record
});
}else{
deferred.resolve({
message: 'Successfully get Admin',
data: record
});
}
}
});
return deferred.promise;
}
records are return empty array.
Thanks in advance for help
The query you use works fine for me and returns object, make sure you define admin field in Schema like this:
"admin": [{type: mongoose.Schema.ObjectId }]
to avoid admin array object being string. If you compare Object("57508a2a3a0a919c16ace3c0") to "57508a2a3a0a919c16ace3c0" it will return empty array, check and replay to me, thanks.
I am not sure about how you define your School Schema, but if you are saving id in admin array as String, you don't need to change to Object in your mongoose query.
School.find({
_id: "574f1f979f44e7531786c80f",
admin: {
$in : ['57508a2a3a0a919c16ace3c0']
}
}, functin (err, record) {
...
})

Why is MongoDB ignoring some of my updates?

I've been building an application in Node.JS using the native MongoDB driver - it includes contacts, and when a user accepts a contact, it should remove from "pending" and "sent" contacts, then add to "contacts".
Example code and documents:
/*
=============================
User "john"
=============================
{
username: "john",
contacts: ["jim"],
pending_contacts: ["bob"]
}
=============================
User "bob"
=============================
{
username: "bob",
contacts: ["dave"],
sent_contacts: ["john"]
}
=============================
What SHOULD happen
=============================
{
username: "bob",
contacts: ["dave", "john"],
sent_contacts: []
},
{
username: "john",
contacts: ["jim", "bob"],
pending_contacts: []
}
=============================
What ACTUALLY happens
=============================
{
username: "john",
contacts: ["jim", "bob"],
pending_contacts: ["bob"]
},
{
username: "bob",
contacts: ["dave", "john"],
sent_contacts: ["john"]
}
*/
var col = this.db.collection('users');
var contact = "bob", username = "john";
var who = [contact, username];
var finishCount = 0;
// finish will run 3 times before callback
function finish(name) {
console.log(name, ' has finished');
finishCount++;
if(finishCount<3) return;
callback(false, null);
}
// run if there's an error
function failed(err) {
callback(err, null)
}
console.log('removing %s and %s from pending and sent', username, contact)
col.update(
{username: { $in: who }},
{
$pullAll: {
sent_contacts: who,
pending_contacts: who
}
}, {multi: 1},
function(err,data) {
if(err) return failed(err);
finish('REMOVE_CONTACTS');
}
);
col.update(
{username: username}, {$addToSet: {contacts: contact}},
function(err,res) {
if(err) return failed(err);
console.log('added 1');
finish('ADD_TO_USER');
}
);
col.update(
{username: contact}, {$addToSet: {contacts: username}},
function(err,res) {
if(err) return failed(err);
console.log('added 2');
finish('ADD_TO_CONTACT');
}
);
The first update removes the contact and the owner from each-others pending/sent list, the second and third update add the owner to the contact's contact list and vice versa.
The issue is, the final result appears to be as if the removal never happened, though the removal query works perfectly fine by itself. I don't know if this is a problem with MongoDB itself (or if it's intended), or if it's an issue with the driver, so I hope someone can at least clarify this for me.
NOTE: Yes I know they run asynchronously. Running them one after the other by putting each update in the previous callback does NOT make a difference. Before anyone complains about how awful this code looks, I previously had it set up within Async.JS but I removed it from this code sample to ensure that Asyn.cJS was not responsible for the issues.
Using the node native driver this works for me every time:
var mongodb = require('mongodb'),
async = require('async'),
MongoClient = mongodb.MongoClient;
var user = "john",
contact = "bob";
var contactsList = [
{
"username": "john",
"contacts": [
"jim"
],
"pending_contacts": [
"bob"
]
},
{
"username": "bob",
"contacts": [
"dave"
],
"sent_contacts": [
"john"
]
}
];
MongoClient.connect('mongodb://localhost/test',function(err,db) {
var coll = db.collection("contacts");
async.series(
[
// Wipe clean
function(callback) {
coll.remove({},callback)
},
// Init collection
function(callback) {
async.each(contactsList,function(contact,callback) {
coll.insert(contact,callback);
},callback);
},
// Do updates
function(callback) {
// Init batch
var bulk = coll.initializeOrderedBulkOp();
// Add to user and pull from pending
bulk.find({
"username": user,
"contacts": { "$ne": contact },
}).updateOne({
"$push": { "contacts": contact },
"$pull": { "pending_contacts": contact }
});
// Add to contact and pull from sent
bulk.find({
"username": contact,
"contacts": { "$ne": user },
"sent_contacts": user
}).updateOne({
"$push": { "contacts": user },
"$pull": { "sent_contacts": user }
});
// Execute
bulk.execute(function(err,response) {
console.log( response.toJSON() );
callback(err);
});
},
// List collection
function(callback) {
coll.find({}).toArray(function(err,results) {
console.log(results);
callback(err);
});
}
],
function(err) {
if (err) throw err;
db.close();
}
);
});
And the output:
{ ok: 1,
writeErrors: [],
writeConcernErrors: [],
insertedIds: [],
nInserted: 0,
nUpserted: 0,
nMatched: 2,
nModified: 2,
nRemoved: 0,
upserted: [] }
[ { _id: 55b0c16934fadce812cdcf9d,
username: 'john',
contacts: [ 'jim', 'bob' ],
pending_contacts: [] },
{ _id: 55b0c16934fadce812cdcf9e,
username: 'bob',
contacts: [ 'dave', 'john' ],
sent_contacts: [] } ]
Improvements here are basically to use the Bulk Operations API and send all updates at once to the server and get a single response. Also note the use of operators in the updates and the query selection as well.
Simply put, you already know the "user" as well as the "contact" they are accepting. The contact to be accepted is "pending" and the contact themselves have the user in "sent".
These are really just simple $push and $pull operations on either array as is appropriate. Rather than using $addToSet here, the query conditions make sure that the expected values are present when performing the update. This also preserves "order" which $addToSet can basically not guarantee, because it's a "set", which is un-ordered.
One send to the server and one callback response, leaving both users updated correctly. Makes more sense then sending multiple updates and waiting for the callback response from each.
Anyhow, this is a complete self contained listing with only the two named dependencies, so you can easily run it yourself and confirm the results.
When I say "Complete and self contained" it means start a new project and simply run the code. Here's the complete instruction:
mkdir sample
cd sample
npm init
npm install mongodb --save
npm install async --save
Then create a file with the code listing in that folder, say test.js and then run:
node test.js

Resources