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
Related
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
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.
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)))
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
}
})
I have a very simple registration form only requiring a username, email, and password. I am trying to see why it takes 5-10sec to complete the registration after the user submits. I tried profiling on the server-end (see here), and have eliminated that as the problem.
It looks like my issue is the client-side validation. I am using the https://jqueryvalidation.org/ JS file plus another custom file that tells the user if they are trying to use a name or password that already exists:
$('.register-form').validate({
submitHandler: function(form){
$('.register-form').submit();
},
rules: {
password: {
required: true
},
tos: {
required: true
},
username: {
required: true,
remote: '/api/v1/users/username/'
},
email: {
required: true,
email: true,
remote: '/api/v1/users/email/'
},
},
messages: {
first_name: {
required: 'Please include your first name.'
},
last_name: {
required: 'Please include your last name.'
},
password: {
required: 'Please create a password'
},
tos: {
required: 'Please check that you agree to our TOS and Privacy Policy'
},
email: {
required: 'Please include your email.',
email: 'Please insert a valid email address.',
remote: 'This email is already in use.'
},
username: {
required: 'Please create a username.',
remote: 'This username is already in use.'
}
}
});
When I use Chrome's profiling (picture link), it looks like the problem is about 10sec of thousands of tiny tasks where register.js and the jquery.validator.js are calling each other. Specifically, its always submitHandler: function(form) line that is triggered on register.js. So I think I see the problem, but I am not clear on how to interpret it or fix it.
Any ideas? I am pretty new to using these validation plug-ins.
this line
$('.register-form').submit();
should read
form.submit();
so the function should look like this
$('.register-form').validate({
submitHandler: function(form) {
form.submit();
},
rules:...
});
other wise you keep recursively calling submit
from the documentation
Example: Use submitHandler to process something and then using the default submit. Note that "form" refers to a DOM element, this way the validation isn't triggered again.
https://jqueryvalidation.org/validate/