Why is mongoose changing variable - javascript

It seems like mongoose (5.6.11) is changing my value in a query. Is this normal? Is there a way to keep the string case the same?
console.log('>>',req.body.visitor)
Visitor.findOne({ email: req.body.visitor.email }).then((visitor)=>{
....
server console/log:
>> {
email: 'Maida_VonRueden#hotmail.com',//this is a fake email generated with faker
enteredValidationCode: '969621'
}
Mongoose: visitors.findOne({ email: 'maida_vonrueden#hotmail.com' }, { projection: {} })
// ^ Why does the email change here?
Do I have to use regex to query case insensitive (Visitor.findOne({email: {$regex: new RegExp(req.body.visitor.email, 'i')}}))?

Based on #AKX comment my schema required lowercase
email: {
type: String,
unique: true,
lowercase: true, // < HERE
required: [true, 'cant be blank'],
match: [/\S+#\S+\.\S+/, 'is invalid'],
index: true
}
Per doc
boolean, whether to always call .toLowerCase() on the value

Related

Adding a validator to an existing schema with mongoose

I have a mongoose schema that looks like this:
const userSchema = new Schema({
username: {
type: String,
required: true,
unique: true,
minlength: 4,
maxlength: 20,
validate: {
validator: username => !username.startsWith('banned_prefix')
msg: 'This username is invalid',
type: 'username-validation-1'
}
}
});
I want the schema to look like this:
const userSchema = new Schema({
username: {
type: String,
required: true,
unique: true,
minlength: 4,
maxlength: 20,
validate: [
{
validator: username => !username.startsWith('banned_prefix')
msg: 'This username is invalid',
type: 'username-validation-1'
},
{
validator: username => !username.startsWith('new_banned_prefix')
msg: 'This username is invalid',
type: 'username-validation-2'
}
]
}
});
How do I do this given that the database and schema already exist and I don't want to completely delete and reset the db?
I tried writing a migration using the native mongodb node driver based on https://docs.mongodb.com/manual/core/schema-validation/#existing-documents. However, it seems like mongoose doesn't actually add native mongodb validators for the validators specified in the schema. That is, when I printed out the validator data for the collection, I get an empty object:
// prints {}
console.log((await db.listCollections({ name: 'users' }).toArray())[0].options.validator);
I don't want to add this new validator in a way that makes it different from the existing validators I have on the schema.
Actually, it looks like this isn't an issue at all because, I presume, mongoose isn't using mongodb native validators so there doesn't need to be any change to the actual db. Mongoose will pick up a validator change like this automatically, no migration necessary.
This wasn't clear to me at first because I was trying to manually recreate the model with the mongoose.model function and was getting errors about overwriting an existing model.

How to make trim() function work in node.js code, even if not used in mongoose's model schema?

I've a mongodb collection which stores "Customers" data. I need to trim the mobile number of a customer if there are any trailing or leading white spaces available. I've used trim() function to remove them. But am getting an error, trim() is not a function.
Here's my model schema:
var schema = mongoose.Schema({
name : {type: String, required: true},
email : {type: String, required: true},
mobile : {type: String, required: true, index: false, unique: false});
module.exports = mongoose.model("customer", schema);
Here's the code where I've used trim function:
addNewCustomer: async function(payload){
var customer = new Customer({
name : payload.name,
email : payload.email,
mobile: payload.mobile.trim()
});
}
When I execute the above function, it is giving me the error.
But if I use trim() at schema level, it is working fine.
mobile : {type: String, required: true, index: false, unique: false, trim: true});
What is the difference between using trim() in SCHEMA level and FUNCTIONALITY level? Why it is not working when used in function?
Have seen functional alternative 'cursor' syntax ala:
db.collection.find({},{ "category": 1 }).forEach(function(doc) {
db.collection.update(
{ "_id": doc._id },
{ "$set": { "category": doc.category.trim() } }
);
})
Above example copied from: https://stackoverflow.com/a/46554968/2003321

Is it wrong to validate passwords using mongoose middlewares?

So right now I am using the validator package for email validation
const validator = require('validator');
email: {
type: String,
required: [true, 'User must have a email'],
unique: true,
lowercase: true, //transform to lowercase
validate: [validator.isEmail, 'Please provide a valid email']
}
So I found this function looking through Stack overflow:
var validateEmail = function(email) {
var re = /^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/;
return re.test(email)
};
var EmailSchema = new Schema({
email: {
type: String,
trim: true,
lowercase: true,
unique: true,
required: 'Email address is required',
validate: [validateEmail, 'Please fill a valid email address'],
match: [/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/, 'Please fill a valid email address']
}
});
So I was wondering, how can I use it or use something similar to run as a mongoose middleware? and, does it makes sense to use it as a middleware?
example:
userSchema.pre('save'....)
Basically I think using schema validation makes more sense and is easier to maintain for sure.
Using middleware for validation:
I have tried it before and I have to say it's pretty hard to get it right. If you define the middleware as a pre.('save', ...) (and this is the correct one to use) one as you mentioned, there are some cases when you are trying to update the doc, the middleware does not get executed, and you have to take care of those situations in separate tasks which are hard to maintain and you can't be sure that they did work or you didn't miss any cases. More on this topic

Mongoose model is not added as a new document unless I give it a unique constraint

I have a few models which I need to work with. However, the model won't be added as a document in a collection unless there is a unique attribute in the schema. This happens in my localhost mongo and in mongo atlas.
Every model with a property who has a unique constraint gets added the normal way. Every model without will not be added.
When the code is written as this everything works fine:
const UserSchema = new Schema ({
firstName: {
type: String,
required: [true, "firstName is required"]
},
lastName: {
type: String,
required: [true, "lastName is required"]
},
email: {
type: String,
required: [true, "email is required"],
index: { unique: true }
},
password: {
type: String,
required: [true, "password is required"]
},
appartments: [{
type: Schema.Types.ObjectId,
ref: "appartments"
}],
})
When the email index property gets commented out, the document will not appear:
email: {
type: String,
required: [true, "email is required"]
//index: { unique: true }
},
I want to add the model as a document without setting a unique constraint in every model.
So I'm back and figured it out!
Apparently, a model does not get added as a document until you create it from code. Because the user schema already has an index added from code it gets created. So to make your model visible as a collection you need to do something like this:
Apartment.create({title: "My Apartment"})
.then(apartment => {
console.log("The apartment model is now visible with entry: " + apartment);
}).catch((error) => next(new ApiError(error, 400)))

Mongoose saving empty array error "TypeError: Cannot read property '1' of null"

I have a schema that is defined like so:
const userSchema = new Schema({
...
surveys: [surveyKeySchema],
...
})
Where surveyKeySchema is actually a subdocument scheme defined like so:
const surveyKeySchema = new Schema({
slug: {
type: String,
required: 'Please supply a slug',
unique: true,
lowercase: true,
trim: true
},
name: {
type: String,
required: 'Please supply a name',
trim: true
},
responseCount: {
type: Number,
default: 0
}
})
Now whenever I try to modify anything on the user except for this array, everything goes fine. When instantiating the user, it is also totally fine. I can also call await user.save() in my code right before I empty the array.
It's also fine when I remove any subdocument from the survey as long as there is at least 1 element remaining.
However, when I try to remove the final subdocument using:
await user.surveys.id(sid).remove()
await user.save()
I get an error on the .save() which is just TypeError: Cannot read property '1' of null. I'm confused and can't find anything about this online, I assume it must be requiring at least one subdocument to be present? Is there any way to remove this, or if my assumption is wrong how would I go about resolving this?
Thanks in advance! And my apologies if I'm missing something obvious!
EDIT:
I found that mongoose's mongo error handler was actually throwing this in a regex it was using to parse the error message. Hacking this around to return the raw error message:
E11000 duplicate key error index: db.users.$surveys.slug_1 dup key: { : null }
As per this question I tried adding sparse: true but this didn't work.
For anyone else having this issue, here's what I did:
In node_modules/mongoose-mongodb-errors/lib/plugin.js on line 19, add a simple console.error(err.message) so you can actually get the output and not the regex handler error.
Because when you save an empty array of subdocuments in Mongoose it is equivalent to having the subschema set to values of null, this means that when Mongoose evaluates the indices of your subdocument collection it will evaluate it as having a value of null for each property. If you're indexing with a property (i.e. one of the properties in your subdocument schema has unique: true on it) then this is a violation as a null value cannot be unique, at least not in Mongo world. To get around this you can add sparse: true.
Any documents in the existing collection and the existing collection itself will create an issue with now having a changed index. You need to drop the index for this to work. I dropped the entire collection because I didn't need it anyways.
Here's my updated schema:
const surveyKeySchema = new Schema({
slug: {
type: String,
required: 'Please supply a slug',
unique: true,
lowercase: true,
sparse: true,
trim: true
},
name: {
type: String,
required: 'Please supply a name',
trim: true
},
responseCount: {
type: Number,
default: 0
}
})

Categories