Sequelize findOrCreate falling back to catch if data is found - node.js

FindOrCreate is suppose to either find or create based on the arguments you give it.
So I'm wondering why it's falling back to the catch promise when data (a user) is found, when it should just return the data that was found to the .spread function instead of trying to insert the data that already exists?!.
As you can see below if there is a user found or created it's going to run the same function regardless. All that function is doing is creating an auth token for the particular user. But for some reason it's going straight into the catch promise!?
User.findOrCreate({
where: {
userId: details.userId,
},
defaults: {
name: details.name,
email: details.email,
},
attributes: ['userId', 'name', 'email', 'profilePic'],
}).spread(function (new_user, created) {
console.log("\n\nNew user was created T or F: ", created);
// Create the token and then pass it back to our callback along with the user details
user.createTokenForUser(new_user.userId, function(token) {
cb(token, new_user);
});
}).catch(function (err) {
// For some reason I'm running...?!??!?!?!?!
console.log("\n\n", err);
});

Did you try removing the trailing comma in the where statement? Its just hitting an error and that looks like a culprit :)
where: {
userId: details.userId,
}
to
where: {
userId: details.userId
}

Related

Looking up a mongoDB database

so I have implemented a register page that sends the inputs into a mongoDB atlas. However, I am trying to implement a new page where users are able to login. I am having trouble trying to find the specific key: value pair in order to check my inputs.
My mondoDB atlas shows the following:
_id: 62da20a99df697486c4b12cc
username: "hello"
password: "hello"
__v: 0
My function for logging in is the below:
exports.login = async (req, res) => {
if (await User.find({ username: req.body.username })) {
console.log("do smth");
}}
For now, lets just ignore the console.log() as I just want to see whether its going through or not. It's always evaluating to true and I don't know why. I can type any input and it always goes through but I'm not sure why its always evaluating to true so I know for sure I'm not looking up at the database correctly.
The
User.find()
where
User
is the name of the variable I defined when requiring the file I defined my schema. Any help would be appreciated in terms of how I should look up a key which in this case is username and I want to look up the req.params.username to see if that actually exists in my database.
Updated CODE
exports.login = async (req, res) => {
if (
User.findOne({ username: req.body.username }, function (err, user) {
console.log(user);
})
) {
res.send("YES");
} else {
res.send("NO");
}
If the input is in the database, it will log the user which in this case is
{
_id: new ObjectId("62daeb740c2c6e2b61325151"),
username: '123',
password: '123',
__v: 0
}
However, if its not in the database, it will log null but the if statement still evaluates to true.
User.find returns an array, which may be empty or not, but is never false in the Javascript sense. By contrast User.findOne returns an object, or null if nothing is found, and null does not satisfy the if condition. Probably that's what you want.
If User.findOne returns a Query, you can try invoking it with a callback function:
User.findOne({username: req.body.username}, function(err, user) {
if (err)
console.error(err);
else if (user) {
console.log("do smth");
}
});

Mongoose model methods - create document if unique

I'm new to MongoDB and Mongoose. I'm using it with my Node project (with Express), and I'm trying to keep everything organized and separated. For example, I'm trying to keep all the database queries in each model file. This way all other files could simply use User.createNew({ fields }) and a new user will be created.
I need each user to be unique (based on their usernames), and I'm not sure exactly where to keep this functionality. I set unique: true in the Schema but upon reading Mongoose's documentation, they stated that unique is not real validation (or something about how validation should happen beforehand). So my main problem is how to create a static method to create a new user, and also validate this user doesn't exist beforehand. I could implement all of this in one static method:
userSchema.statics.createUser = function (username, ..., cb) {
this.findOne({ username }, function (err) {
if (err) {
return new this({
username,
...
}).save(cb);
} else {
return Promise.reject(new Error("User already exists!"));
}
});
};
I'm pretty confused with the whole cb function and what I'm supposed to pass to it.
After reading other posts about validation, I realized I could also do something like this:
const userSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
validate: function (val, fn) {
this.find({ username: val }, function (err) {
fn(err || true);
});
},
message: function (props) {
`Username '${props.value}' already exists.`;
},
},
...
Also here I'm confused about what fn accepts and what it even does (I found an answer similar to this with no explanation online).
In the end, I would like to use this model in a controller to create a new user, like this
User.createNew({ username: "example", ...})
.then(doc => console.log("User was created: " + doc))
.catch(err => console.error) // The error is something custom like "This user already exists"
Any help is appreciated!

mongoose-unique-validator detects all inputs as already existed

I am trying to create a unique email value in MongoDB. I used mongoose-unique-validator to implement that but resulted in error claiming that my unique emails that I just inputted are already existed.
This is the error message I received from trying to input unique email.
"message": "User validation failed: email: Error, expected email to be
unique., _id: Error, expected _id to be unique."
They said the Email and _id are not unique. But.., _id is an auto-generated value to be unique in MongoDB meaning that it should not be detected as a duplicated value.
I am not sure if that was caused by my own implementation so I would look forward to see any assumption or ways to debug to the root cause of this too. I tried restarting from fresh Mongo DB and manually inputting uniquely via Postman but still no hope.
These are a part of the codes that might be related to the data creation on MongoDB
UserModel.js
var uniqueValidator = require('mongoose-unique-validator');
const { Schema } = mongoose;
const UsersSchema = new Schema({
email: { type: String, unique: true, required: true},
hashedPassword: String,
salt: String,
});
UsersSchema.plugin(uniqueValidator, { message: 'Error, expected {PATH} to be unique.' });
UsersSchema.set('autoIndex', false);
Server.js ---- /api/users/register
const finalUser = new Users(user);
finalUser.setPassword(user.password);
finalUser.save((err, data) => {
if (err) {
return res.status(400).json(err)
}
return res.json({ user: finalUser.toAuthJSON() })
})
Additional Information
I tried this solution from Sritam to detect another email with the same value but it still claims that the inputted email is already existed.
UsersSchema.path('email').validate(async (value) => {
const emailCount = await mongoose.models.User.countDocuments({email: value });
return !emailCount;
}, 'Email already exists');
"message": "User validation failed: email: Email already exists"
You can use validateModifiedOnly option in Document.save(). _id and email fields will never be validated unless they are modified. The code should look like this:
finalUser.save({ validateModifiedOnly: true }, (err, data) => {
if (err) {
return res.status(400).json(err)
}
return res.json({ user: finalUser.toAuthJSON() })
})
Found the issue and solution!
They are acting weird like in the question because the model was not initialized.
I must perform Schema.init() before performing any model validation.
The solution is to add UsersSchema.init().then(() => {...your operation})
Now Server.js ---- /api/users/register should look like this.
Users.init().then(() => { // where Users is my UsersModel
finalUser.save((err, data) => {
if (err) {
return res.status(400).json(err)
}
return res.json({ user: finalUser.toAuthJSON() })
})
})
Hope this helps other developers who experience similarly odd error!

I want to check another field When I get dublicate key error for email (unique) in mongodb findoneandupdate

User.findOneAndUpdate({ _id: userFindByid }, {
$set: {
"email": req.body.email,
"username": req.body.username,
"phone_number": req.body.phone_number,
"address": req.body.address,
"isBenefactor": req.body.isBenefactor,
"location": req.body.location
}
}, { runValidators: true, context: 'query' }, (err, doc) => {
if (err) {
// if request email has already exist in db I want to check that emails isDeleted field in here . if isDeleted is true I want to update .
return res.status(500).json({ message: err.message });
}
else {
return res.status(200).json({ message: 'Your account was updated' });
}
})
//
Let me explain scenario clearly,
I registered with an email address(first#gmail.com) then I deleted my account =>(first#gmail.com)=>isDeleted=true
After that I again registered with another email address(second#gmail.com)=>isDeleted=false
Now I want to update my second email address with first one I will get an unique key error because (first#gmail.com) is in mydb ,but I have to da update process because (first#gmail.com)=>IsDelete=true
If I use { 'email': req.body.email, 'isDeleted': true} I can not update (second#gmail.com)=>isDeleted=false
I can fix the problem by using too much if statements , but I dont want to use if statements too much. I am looking for best practice for that problem.
I hope I could explain
Here is my code block , can someone help me ?
THIS ANSWER ASSUMES YOU ARE USING MONGOOSE!
One way you can do is instead of using findOneAndUpdate you can use .save this way you can issue a hook on mongoose.
For example, you would do User.save(...) then you go to your schema code and you add the following (assuming your schema name is UserSchema)
UserSchema.post('save', function(error, doc, next) {
// Error code 11000 means this is a duplicate
if (error.name === 'MongoError' && error.code === 11000) {
// So instead of throwing an error you would do anything you want
// Such as look for the other record and delete it, update its isDelete
// field, remove email, etc... really is up to you
}
next()
});
EDIT: Of course before User.save(...) you need to find the user!
For example,
User.findOne({_id:1}, function(err, doc){
// Update doc values
// Finally do doc.save(...)
})
You can make it look much better by using the async library and using async.waterfall
EDIT2: Okay so now that I understand your requirement better, here is your best solution in my opinion.
Find the user you want to update
Change email
Save
On the other side, you need to have a hook (unfortunately its poorly documented, you have to do your own digging but here is a link to mongoose documentation)
Here is how this will work
1. Hook a pre save (before actually saving execute specific block of code)
2. The block of code will only execute when the email is modified (we dont want to execute it everytime, its just a waste of resources)
3. The block of code will use deleteOne and delete the user matching that email
NOTE: For best performance make sure to index the email and make it unique!
I have created the full (similar to what you want) project with the code on here
But if you wish here are also some snippet
// This will run before saving the object
UserSchema.pre('save', function (next) {
let user = this
// Make sure to run only when email is modified
if(user.isModified('email')) {
// IF the email was modified, then attempt to delete a record with this email (if there is one, then it will be deleted otherwise it will just continue)
this.constructor.deleteOne({email:this.email, isDeleted:true}, (err) => {
err ? next(err) : next()
})
} else {
next()
}
})
// Code to save/update the user
User.findOne({_id:"1"}, function(err, user) {
if (err) {
throw err
} else {
if (user) {
user.email = "test2#test.com"
user.save(err => {
err ? console.log(err) : console.log("Success!")
})
} else {
console.log("User was not found!")
}
}
})
Good luck!

Mongoose unique validation error type

I'm using this schema with mongoose 3.0.3 from npm:
var schema = new Schema({
_id: Schema.ObjectId,
email: {type: String, required: true, unique: true}
});
If I try to save a email that is already in db, I expect to get a ValidationError like if a required field is omitted. However this is not the case, I get a MongoError: E11000 duplicate key error index.
Which is not a validation error (happens even if I remove the unique:true).
Any idea why?
I prefer putting it in path validation mechanisms, like
UserSchema.path('email').validate(function(value, done) {
this.model('User').count({ email: value }, function(err, count) {
if (err) {
return done(err);
}
// If `count` is greater than zero, "invalidate"
done(!count);
});
}, 'Email already exists');
Then it'll just get wrapped into ValidationError and will return as first argument when you call validate or save .
I had some issues with the approved answer. Namely:
this.model('User') didn't work for me.
the callback done wasn't working properly.
I resolved those issues by:
UserSchema.path('email').validate(async (value) => {
const emailCount = await mongoose.models.User.countDocuments({email: value });
return !emailCount;
}, 'Email already exists');
I use async/await which is a personal preference because it is much neater: https://javascript.info/async-await.
Let me know if I got something wrong.
This is expected behavior
The unique: true is equivalent to setting an index in mongodb like this:
db.myCollection.ensureIndex( { "email": 1 }, { unique: true } )
To do this type of validation using Mongoose (Mongoose calls this complex validation- ie- you are not just asserting the value is a number for example), you will need to wire in to the pre-save event:
mySchema.pre("save",function(next, done) {
var self = this;
mongoose.models["User"].findOne({email : self.email},function(err, results) {
if(err) {
done(err);
} else if(results) { //there was a result found, so the email address exists
self.invalidate("email","email must be unique");
done(new Error("email must be unique"));
} else {
done();
}
});
next();
});
Simply response to json
try {
let end_study_year = new EndStudyYear(req.body);
await end_study_year.save();
res.json({
status: true,
message: 'បានរក្សាទុក!'
})
}catch (e) {
res.json({
status: false,
message: e.message.toString().includes('duplicate') ? 'ទិន្នន័យមានរួចហើយ' : e.message.split(':')[0] // check if duplicate message exist
})
}
Sorry for answering an old question. After testing I feel good to have find these answers, so I will give my experience. Both top answers are great and right, just remember that:
if your document is new, you can just validate if count is higher than 0, thats the common situation;
if your document is NOT new and has modified the unique field, you need to validate with 0 too;
if your document is NOT new and has NOT being modified, just go ahead;
Here is what I made in my code:
UserSchema.path('email').validate(async function validateDuplicatedEmail(value) {
if (!this.isNew && !this.isModified('email')) return true;
try {
const User = mongoose.model("User");
const count = await User.countDocuments({ email: value });
if (count > 0) return false;
return true;
}
catch (error) {
return false;
}
}, "Email already exists");

Resources