user.email become undefined in Node.js mongo - node.js

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

Related

How do I update a key in an array of objects in MongoDB?

I working on NodeJS backend API and trying to change a key in an array of objects from false to true in my MongoDB database. I am passing two conditions from the client: the email of the user and the email of the person that sent the user a message. I would like to change the boolean value of read to true.
Sample data:
{
_id: new ObjectId("6282163781acbcd969de3fc9"),
firstName: 'Amanda',
lastName: 'Nwadukwe',
role: 'Volunteer',
email: 'amandanwadukwe#gmail.com',
password: '$2a$10$YD5MQlMt0gqSULQOBNcEfOLr3vIK8eF4dqdLw3XctsIVgbnf54P32',
confirmPassword: '$2a$10$mnL0S1bDDkGVnKgqQP81mOew9aFdNTUCGOEs7LvWYRxzivN4hrtFS',
date: 2022-05-16T09:14:57.000Z,
messages: [
{
message: 'This is another message from Amanda',
sendersEmail: 'laju#gmail.com',
date: '2022-05-14T12:00:45.000Z',
read: false
},
{
sender: 'Amanda Nwadukwe',
message: 'This is another message from Amanda',
sendersEmail: 'amanda#gmail.com',
date: '2022-05-14T12:00:45.000Z',
read: false
}]
Desired Output:
{
_id: new ObjectId("6282163781acbcd969de3fc9"),
firstName: 'Amanda',
lastName: 'Nwadukwe',
role: 'Volunteer',
email: 'amandanwadukwe#gmail.com',
password: '$2a$10$YD5MQlMt0gqSULQOBNcEfOLr3vIK8eF4dqdLw3XctsIVgbnf54P32',
confirmPassword: '$2a$10$mnL0S1bDDkGVnKgqQP81mOew9aFdNTUCGOEs7LvWYRxzivN4hrtFS',
date: 2022-05-16T09:14:57.000Z,
messages: [
{
message: 'This is another message from Amanda',
sendersEmail: 'laju#gmail.com',
date: '2022-05-14T12:00:45.000Z',
read: true
},
{
sender: 'Amanda Nwadukwe',
message: 'This is another message from Amanda',
sendersEmail: 'amanda#gmail.com',
date: '2022-05-14T12:00:45.000Z',
read: false
}]
I am tried a lot of things with filtering but I have not been successful. Here is my code to change all the read to true. It is also not working.
app.post("/view_message", (req, res) => {
const email = req.body.email;
Users.findOneAndUpdate({ "email": email }, {$set:{"messages.$.read": true}}, (err, result) => {
console.log(result)
})
});
You missed to add a check to match the array element to be updated.
Playground
db.collection.update({
"email": "amandanwadukwe#gmail.com",
"messages.sendersEmail": "laju#gmail.com", //This did the trick
},
{
"$set": {
"messages.$.read": true
}
},
{
"multi": false,
"upsert": false
})
Just in case anyone needs it, to update all the read values for all objects in the array I used this:
User.findAndUpdateOne({
"email": "amandanwadukwe#gmail.com",
"messages.sendersEmail": "laju#gmail.com",
},
{
"$set": {
"messages.$[].read": true //Added square brackets
}
},
{
"multi": false,
"upsert": false
})

Problem with ottoman not resolving the references

I have two models in my ottoman 1.0.5 setup. One holds contact info which includes an emails array of docs and then the email doc. I can insert new contacts fine as well as emails in docs and the corresponding link in the contact doc for the new email.
Here is my model
const ottoman = require("ottoman")
ottoman.bucket = require("../app").bucket
var ContactModel = ottoman.model("Contact",{
timestamp: {
type: "Date",
default: function() {return new Date()}
},
first_name : "string",
last_name : "string",
emails: [
{
ref:"Email"
}
]} )
var EmailModel = ottoman.model("Email",{
timestamp: {
type: "Date",
default: function() {return new Date()}
},
type : "string",
address : "string",
name: "string"
} )
module.exports = {
ContactModel : ContactModel,
EmailModel : EmailModel
}
Now to get an contact and all its emails i use this function
app.get("/contacts/:id", function(req, res){
model.ContactModel.getById(req.params.id,{load: ["emails"]}, function(error, contact){
if(error) {
res.status(400).json({ Success: false , Error: error, Message: ""})
}
res.status(200).json({ Success: true , Error: "", Message: "", Data : contact})
})
})
Which returns me this
{
"Success": true,
"Error": "",
"Message": "",
"Data": {
"timestamp": "2019-01-30T23:59:59.188Z",
"emails": [
{
"$ref": "Email",
"$id": "3ec07ba0-aaec-4fd4-a207-c4272cef8d66"
}
],
"_id": "0112f774-4b5d-4b73-b784-60fa9fa2f9ff",
"first_name": "Test",
"last_name": "User"
}
}
if i go and log the contact to my console i get this
OttomanModel(`Contact`, loaded, key:Contact|0112f774-4b5d-4b73-b784-60fa9fa2f9ff, {
timestamp: 2019-01-30T23:59:59.188Z,
emails: [ OttomanModel(`Email`, loaded, key:Email|3ec07ba0-aaec-4fd4-a207-c4272cef8d66, {
timestamp: 2019-01-31T00:36:01.264Z,
_id: '3ec07ba0-aaec-4fd4-a207-c4272cef8d66',
type: 'work',
address: 'test#outlook.com',
name: 'Test Outlook',
}),
OttomanModel(`Email`, loaded, key:Email|93848b71-7696-4ef5-979d-05c19be9d593, {
timestamp: 2019-01-31T04:12:40.603Z,
_id: '93848b71-7696-4ef5-979d-05c19be9d593',
type: 'work',
address: 'newTest#outlook.com',
name: 'Test2 Outlook',
}) ],
_id: '0112f774-4b5d-4b73-b784-60fa9fa2f9ff',
first_name: 'Test',
last_name: 'User',
})
This shows that emails was resolved but why does it not show up in the returned json. On the other hand if i return contact.emails i get the resolved emails just fine. So i hope someone can shed some light on what i am missing here
I asked a similar question on the couchbase forum, and I also found out the solution:
(a slight difference that the result of my search is an array not an object like in your case)
forum.couchbase.com
app.get("/assets", (req, res) => {
AssetModel.find({}, { load: ["assetModelId", "assetGroupId", "assetTypeId"] }, (err, results) => {
if (err) return res.status(400).send("no asset found");
const assets = [];
results.map(asset => {
assets.push({...asset});
});
res.status(200).send(assets)
});
});

How to create item if not exists and return an error if exists

I'm writing alexa skill and would like to check if user exists in MongoDB. My code works but I don't know how to define situation if user is already in a database :(
Everytime when I execute code I get:
"Hello Anna you are new here"
My user Anna is saved in MongoDB
But I would like to distinguish when my user is already in a database and react for that.
Does anybody smart has a solution for my problem?
var myName = "Anan1";
var userID = this.event.session.user.userId;
console.log(userID);
self = this;
User.findOneAndUpdate(
{userId: userID},
{$set:{name:myName}},
{upsert: true, new: false, runValidators: true},
function(err, doc){
if(err){
console.log("eeoror");
}
console.log(doc);
if (doc==null){
self.emit(':ask',
"Hello "+ myName +"you are new here")
}else {
self.emit(':ask',
"Hello "+ myName +"you are not new here")
}
});
It sounds like what you really want is a unique key constraint and not an upsert.
The unique key can be set in [mongoose] with either the schema field options:
const s = new Schema({ name: { type: String, unique: true }});
or by the index method:
Schema.path('name').index({ unique: true });
If an attempt is made to create a document that already has an entry for that key then an error will be thrown:
NOTE: violating the constraint returns an E11000 error from MongoDB when saving, not a Mongoose validation error.
As noted in comment earlier, you have two basic approaches to work out whether something was "created" or not. These are either to:
Return the rawResult in the response and check the updatedExisting property which tells you if it's an "upsert" or not
Set new: false so that "no document" is actually returned in result when it's actually an "upsert"
As a listing to demonstrate:
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/thereornot';
mongoose.set('debug', true);
mongoose.Promise = global.Promise;
const userSchema = new Schema({
username: { type: String, unique: true }, // Just to prove a point really
password: String
});
const User = mongoose.model('User', userSchema);
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
// Shows updatedExisting as false - Therefore "created"
let bill1 = await User.findOneAndUpdate(
{ username: 'Bill' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: true, rawResult: true }
);
log(bill1);
// Shows updatedExisting as true - Therefore "existing"
let bill2 = await User.findOneAndUpdate(
{ username: 'Bill' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: true, rawResult: true }
);
log(bill2);
// Test with something like:
// if ( bill2.lastErrorObject.updatedExisting ) throw new Error("already there");
// Return will be null on "created"
let ted1 = await User.findOneAndUpdate(
{ username: 'Ted' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: false }
);
log(ted1);
// Return will be an object where "existing" and found
let ted2 = await User.findOneAndUpdate(
{ username: 'Ted' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: false }
);
log(ted2);
// Test with something like:
// if (ted2 !== null) throw new Error("already there");
// Demonstrating "why" we reserve the "Duplicate" error
let fred1 = await User.findOneAndUpdate(
{ username: 'Fred', password: 'password' },
{ $setOnInsert: { } },
{ upsert: true, new: false }
);
log(fred1); // null - so okay
let fred2 = await User.findOneAndUpdate(
{ username: 'Fred', password: 'badpassword' }, // <-- dup key for wrong password
{ $setOnInsert: { } },
{ upsert: true, new: false }
);
mongoose.disconnect();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
And the output:
Mongoose: users.remove({}, {})
Mongoose: users.findAndModify({ username: 'Bill' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: true, rawResult: true, remove: false, fields: {} })
{
"lastErrorObject": {
"n": 1,
"updatedExisting": false,
"upserted": "5adfc8696878cfc4992e7634"
},
"value": {
"_id": "5adfc8696878cfc4992e7634",
"username": "Bill",
"__v": 0,
"password": "password"
},
"ok": 1,
"operationTime": "6548172736517111811",
"$clusterTime": {
"clusterTime": "6548172736517111811",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Mongoose: users.findAndModify({ username: 'Bill' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: true, rawResult: true, remove: false, fields: {} })
{
"lastErrorObject": {
"n": 1,
"updatedExisting": true
},
"value": {
"_id": "5adfc8696878cfc4992e7634",
"username": "Bill",
"__v": 0,
"password": "password"
},
"ok": 1,
"operationTime": "6548172736517111811",
"$clusterTime": {
"clusterTime": "6548172736517111811",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
{
"_id": "5adfc8696878cfc4992e7639",
"username": "Ted",
"__v": 0,
"password": "password"
}
So the first case actually considers this code:
User.findOneAndUpdate(
{ username: 'Bill' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: true, rawResult: true }
)
Most options are standard here as "all" "upsert" actions will result in the field content being used to "match" ( i.e the username ) is "always" created in the new document, so you don't need to $set that field. In order to not actually "modify" other fields on subsequent requests you can use $setOnInsert, which only adds these properties during an "upsert" action where no match is found.
Here the standard new: true is used to return the "modified" document from the action, but the difference is in the rawResult as is shown in the returned response:
{
"lastErrorObject": {
"n": 1,
"updatedExisting": false,
"upserted": "5adfc8696878cfc4992e7634"
},
"value": {
"_id": "5adfc8696878cfc4992e7634",
"username": "Bill",
"__v": 0,
"password": "password"
},
"ok": 1,
"operationTime": "6548172736517111811",
"$clusterTime": {
"clusterTime": "6548172736517111811",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Instead of a "mongoose document" you get the actual "raw" response from the driver. The actual document content is under the "value" property, but it's the "lastErrorObject" we are interested in.
Here we see the property updatedExisting: false. This indicates that "no match" was actually found, thus a new document was "created". So you can use this to determine that creation actually happened.
When you issue the same query options again, the result will be different:
{
"lastErrorObject": {
"n": 1,
"updatedExisting": true // <--- Now I'm true
},
"value": {
"_id": "5adfc8696878cfc4992e7634",
"username": "Bill",
"__v": 0,
"password": "password"
},
"ok": 1,
"operationTime": "6548172736517111811",
"$clusterTime": {
"clusterTime": "6548172736517111811",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
The updatedExisting value is now true, and this is because there already was a document that matched the username: 'Bill' in the query statement. This tells you the document was already there, so you can then branch your logic to return an "Error" or whatever response you want.
In the other case, it may be desirable to "not" return the "raw" response and use a returned "mongoose document" instead. In this case we vary the value to be new: false without the rawResult option.
User.findOneAndUpdate(
{ username: 'Ted' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: false }
)
Most of the same things apply except that now the action is the original state of the document is returned as opposed to the "modified" state of the document "after" the action. Therefore when there is no document that actually matches the "query" statement, the returned result is null:
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null // <-- Got null in response :(
This tells you the document was "created", and it's arguable that you already know what the content of the document should be since you sent that data with the statement ( ideally in the $setOnInsert ). Point being, you already know what to return "should" you require to actually return the document content.
By contrast, a "found" document returns the "original state" showing the document "before" it was modified:
{
"_id": "5adfc8696878cfc4992e7639",
"username": "Ted",
"__v": 0,
"password": "password"
}
Therefore any response which is "not null" is therefore an indication that the document was already present, and again you can branch your logic depending on what was actually received in response.
So those are the two basic approaches to what you are asking, and they most certainly "do work"! And just as is demonstrated and reproducible with the same statements here.
Addendum - Reserve Duplicate Key for bad passwords
There is one more valid approach that is hinted at in the full listing as well, which is essentially to simply .insert() ( or .create() from mongoose models ) new data and have a "duplicate key" error throw where the "unique" property by index is actually encountered. It's a valid approach but there is one particular use case in "user validation" which is a handy piece of logic handling, and that is "validating passwords".
So it's a pretty common pattern to retrieve user information by the username and password combination. In the case of an "upsert" this combination justifies as "unique" and therefore an "insert" is attempted if no match is found. This is exactly what makes matching the password a useful implementation here.
Consider the following:
// Demonstrating "why" we reserve the "Duplicate" error
let fred1 = await User.findOneAndUpdate(
{ username: 'Fred', password: 'password' },
{ $setOnInsert: { } },
{ upsert: true, new: false }
);
log(fred1); // null - so okay
let fred2 = await User.findOneAndUpdate(
{ username: 'Fred', password: 'badpassword' }, // <-- dup key for wrong password
{ $setOnInsert: { } },
{ upsert: true, new: false }
);
On the first attempt we don't actually have a username for "Fred", so the "upsert" would occur and all the other things as already described above happen to identify whether it was a creation or a found document.
The statement that follows uses the same username value but provides a different password to what is recorded. Here MongoDB attempts to "create" the new document since it did not match on the combination, but because the username is expected to be "unique" you receive a "Duplicate key error":
{ MongoError: E11000 duplicate key error collection: thereornot.users index: username_1 dup key: { : "Fred" }
So what you should realize is you now get three conditions to evaluate for "free". Being:
The "upsert" was recorded by either the updatedExisting: false or null result depending on the method.
You know the document ( by combination ) "exists" via either the updatedExisting: true or where the document returns was "not null".
If the password provided was not a match for what already existed for the username, then you would get the "duplicate key error" which you can trap and respond accordingly, advising the user in response that the "password is incorrect".
All of that from one request.
That's the main reasoning for using "upserts" as opposed to simply throwing inserts at a collection, as you can get different branching of the logic without making additional requests to the database to determine "which" of those conditions should be the actual response.

Mongoose findOne query returning old data, is there some caching happening?

I have a user model that gets created with some properties that later get deleted after user activation. I noticed that these properties were re-appearing after the user initiates a forgotPassword request.
When I step through the code (it's an express app), I have a User.findOne request in the forgotPassword controller that is returning a user doc that still has the deleted properties, so they get saved back to the database, when I save the password reset token.
function forgotPassword(req, res, next) {
const username = req.body.username;
// Generate token
const token = uuid.v4();
// Find the user
User.findOne({ username: username }, function(err, user) {
if (err) {
return next(err);
}
// At this stage user also has the properties that
// were previously deleted when I inspect it
user.resetToken = token;
user.save(function(err, savedUser, numAffected) {
// So resetToken gets saved but also the previously deleted properties
...
});
I verified in the database that they get deleted on user activation, then re-appear after forgotPassword request.
Is there some sort of caching that Mongoose does on findOne requests?
I looked through Mongoose github issues and didn't find anything. Mongoose findOne uses mquery under the covers but I can't see a caching layer.
How do I get Mongoose to use the live data in the database?
Updated with minimal example:
[Update: I didn't add any of the express routes in the minimal example, but the sequence of events is essentially the same, though in the real app the db calls are spread out over a few different express routes]
package.json:
{
"name": "minimal-mongoose-findone-old-data-issue",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"chance": "^1.0.11",
"mongoose": "^4.12.4",
"uuid": "^3.1.0"
}
}
index.js:
const mongoose = require('mongoose');
const uuid = require('uuid');
const chance = new(require('chance'));
const dbPort = process.env.DBPORT;
const dbName = 'minimal-mongoose-findone-old-data-issue';
const mongoUrl = `mongodb://localhost:${dbPort}/${dbName}`;
mongoose.connect(mongoUrl, { user: '', pass: '' });
mongoose.set("debug",true);
const UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true
},
activationToken: {
type: String,
default: uuid.v4()
},
active: {
type: Boolean,
default: false
},
passwordToken: {
type: String
}
}, { timestamps: true });
const User = mongoose.model('User', UserSchema);
// Create user
const username = chance.first();
const user = new User();
user.username = username;
user.save(function(err, savedUser) {
if (err) { throw err };
console.log(`STEP 1: user created: ${JSON.stringify(savedUser, 0, 2)}`);
// Activate user
User.findOne({
activationToken: savedUser.activationToken
}, function(err, foundUser) {
if (err) { throw err };
console.log(`STEP 2: user found: ${JSON.stringify(foundUser, 0, 2)}`);
foundUser.active = true;
foundUser.activationToken = undefined;
foundUser.save(function(err, activatedUser, numAffected) {
if (err) { throw err };
console.log(`STEP 3: user activated: ${JSON.stringify(activatedUser, 0, 2)}`);
// Reset password
User.findOne({
username: username,
}, function(err, resetPasswordUser) {
if (err) { throw err };
console.log(`STEP 4: user found for password reset: ${JSON.stringify(resetPasswordUser, 0, 2)}`);
// Password reset logic here etc...
// The problem is that since the second findOne call returns old data,
// the activationToken gets saved back to the database when I save resetPasswordUser
process.exit();
});
});
});
});
In STEP 3 the console.log output has no activationToken
In STEP 4 the activationToken is back in the console.log output
Output of running node index.js: [Updated with mongo debug output]
Mongoose: users.insert({ updatedAt: new Date("Wed, 25 Oct 2017 08:02:48 GMT"), createdAt: new Date("Wed, 25 Oct 2017 08:02:48 GMT"), username: 'Hannah', _id: ObjectId("59f045287a2de871d0fbce14"), active: false, activationToken: 'c9048f36-02c7-4a0f-8e2c-abf5cb9120fe', __v: 0 })
STEP 1: user created: {
"__v": 0,
"updatedAt": "2017-10-25T08:02:48.107Z",
"createdAt": "2017-10-25T08:02:48.107Z",
"username": "Hannah",
"_id": "59f045287a2de871d0fbce14",
"active": false,
"activationToken": "c9048f36-02c7-4a0f-8e2c-abf5cb9120fe"
}
Mongoose: users.findOne({ activationToken: 'c9048f36-02c7-4a0f-8e2c-abf5cb9120fe' }, { fields: {} })
STEP 2: user found: {
"_id": "59f045287a2de871d0fbce14",
"updatedAt": "2017-10-25T08:02:48.107Z",
"createdAt": "2017-10-25T08:02:48.107Z",
"username": "Hannah",
"__v": 0,
"active": false,
"activationToken": "c9048f36-02c7-4a0f-8e2c-abf5cb9120fe"
}
Mongoose: users.update({ _id: ObjectId("59f045287a2de871d0fbce14") }, { '$unset': { activationToken: 1 }, '$set': { active: true, updatedAt: new Date("Wed, 25 Oct 2017 08:02:48 GMT") } })
STEP 3: user activated: {
"_id": "59f045287a2de871d0fbce14",
"updatedAt": "2017-10-25T08:02:48.153Z",
"createdAt": "2017-10-25T08:02:48.107Z",
"username": "Hannah",
"__v": 0,
"active": true
}
Mongoose: users.findOne({ username: 'Hannah' }, { fields: {} })
STEP 4: user found for password reset: {
"_id": "59f045287a2de871d0fbce14",
"updatedAt": "2017-10-25T08:02:48.153Z",
"createdAt": "2017-10-25T08:02:48.107Z",
"username": "Hannah",
"__v": 0,
"active": true,
"activationToken": "c9048f36-02c7-4a0f-8e2c-abf5cb9120fe"
}
In mongo shell after running index.js:
MongoDB shell version: 3.2.4
connecting to: test
> use minimal-mongoose-findone-old-data-issue
switched to db minimal-mongoose-findone-old-data-issue
> db.users.find({ username: 'Hannah'}).pretty()
{
"_id" : ObjectId("59f045287a2de871d0fbce14"),
"updatedAt" : ISODate("2017-10-25T08:02:48.153Z"),
"createdAt" : ISODate("2017-10-25T08:02:48.107Z"),
"username" : "Hannah",
"active" : true,
"__v" : 0
}

Cascade delete from array using Mongoose middleware remove hook

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.

Resources