sails js model validation against database - sails.js

I am writing a custom validation rule to check if the "category_id" passed to my create function is valid or not.
types: {
isValidCategoryId: function(id){
return Category.findOne({id: id}).exec(function(err, user){
if(err || !user)
return false;
else{
return true;
}
});
}
},
attributes: {
category_id : { isValidCategoryId: true, required: true, type: 'string' },
}
I understand that my custom validation function should return true, but in an asynchronous context, this may not work, like checking the value in DB.
How should I write my custom validation function to make it behave correctly?

I tried particlebanana's solution. It didn't work but at least it pointed me in the right direction.
According to the docs:
Validation rules may be defined as simple values or functions (both sync and async) that return the value to test against.
So, one easy way to do this would be:
attributes: {
category_id : {
required: true,
type: 'string',
'true': function(cb) {
Category.findOne({id: this.category_id}).exec(function(err, category){
return cb(!err && category);
});
}
}
}
As you can see, I'm just using the "true" validator here, but you could of course write your own validator to work out some more advanced logic. The key here is that your custom validators aren't async, but your validation rules can be. Yes, the terminology is very confusing.
Here's another example with a custom validator:
attributes: {
author: {
required: true,
type: 'string',
canPost: function(cb) {
Author.findOne(this.author).exec(function(err, author) {
return cb(author);
});
}
}
},
types: {
canPost: function(id, author) {
return author && author.canPost();
}
}
Hopefully that makes sense. If not, See the docs.

You can pass in a callback and return the result. It's a bit weird because it doesn't look like it follows the (err, result) standard but instead just uses (result). Give this a try:
types: {
isValidCategoryId: function(id, cb){
return Category.findOne({id: id}).exec(function(err, user){
if(err || !user)
return cb(false);
else{
return cb(true);
}
});
}
},

Related

mongodb/mongoose: Save unique value if data is not null in from nestjs

I am trying to save data in MongoDB. I want to store unique data when data is not null. However, I want to allow multiple null values in the unique identifier.
My sample schema:
#Schema()
export class Contact extends Document {
#Prop({ unique: true, sparse: true, require: true })
email: string;
#Prop({ default: '+1' })
countryCode: string;
#Prop({ unique: true, sparse: true })
mobile: string;
}
In this case, a mobile number is not required. User can add their contact information with or without providing a mobile number. If the user sends their mobile number that should be unique. So, I need to allow multiple null values in the mobile field. However, that field should be unique when the user provides any mobile number.
Empty entries seem to get the value null so every entry without mobile crashes with the unique identifier.
Is there any way to solve this problem either from the database layer or the application layer?
I am using NestJS for developing my API.
A unique index still does not allow multiple docs with a field of null. You need to transform your data payload by dropping the null field before you save your docs in MongoDB. A transform pipe will help you to handle this issue. Here is a transform pipe that you can use for this purpose:
#Injectable()
export class NullValidationPipe implements PipeTransform {
private isObj(obj: any): boolean {
return typeof obj === 'object' && obj !== null;
}
private dropNull(values) {
Object.keys(values).forEach((key) => {
if (!(key === 'password' || key === '_id')) {
if (this.isObj(values[key])) {
values[key] = this.dropNull(values[key]);
} else if (Array.isArray(values[key]) && values[key].length > 0) {
values[key] = values[key].map((value) => {
if (this.isObj(value)) {
value = this.dropNull(value);
}
return value;
});
} else {
if (values[key] === null || values[key] === undefined) {
delete values[key];
}
}
}
});
return values;
}
transform(values: any, metadata: ArgumentMetadata) {
const { type } = metadata;
if (type === 'param' || type === 'custom') return values;
else if (this.isObj(values) && type === 'body') {
return this.dropNull(values);
}
throw new BadRequestException('Validation failed');
}
}
Use this pipe in the controller and this pipe will drop all incoming null fields which will come with the request payload.
You can also check nest pipe transform docs: https://docs.nestjs.com/techniques/validation

Validate DTO in controller when passing data to service

I am trying to insert a validation into PUT request(to update some data stored in MongoDB):
DTO:
export enum reportFields {
'startDate',
'targetDateOfCompletion',
'duration',
}
export class updateScheduleDto {
#IsOptional()
#IsString()
readonly frequency?: string;
#IsOptional()
#IsArray()
#IsEmail({}, { each: true })
#IsString({ each: true })
readonly emails?: string[];
#IsOptional()
#IsEnum(reportFields, { each: true })
#IsArray()
#IsString({ each: true })
readonly reportFields?: string[];
#IsOptional()
#Type(() => Number)
#IsNumber()
updatedAt?: number;
}
Controller:
#Put('reports/:id/schedule')
async updateScheduleData(
#Param('id') id: string,
#Body(new ValidationPipe()) updateData: updateScheduleDto,
) {
return this.reportService.updateScheduleData(id, updateData);
}
Service:
async updateScheduleData(id: string, updateData: updateScheduleDto) {
try {
updateData.updatedAt = this.utils.getCurrentTime();
const newData = await this.reportScheduleModel.findByIdAndUpdate(
id,
updateData,
{
new: true,
},
);
console.log(`Data has been updated to ${newData}`);
return newData;
} catch (error) {
throw new Error('>>>' + error);
}
}
But the validation not working over the keys. If I pass a non-valid key(like below) in the body object, even then the program executes without any error, how do I fix this? What am I missing?
{
"emaaaalls":["randomemail123#gmail.com"]
}
You need to pass the options { forbidUnknownValues: true } to the ValidationPipe. This will make class-validator throw an error when unknown values are passed in. You can read through the options here
You can make whitelist: true in ValidationPipe options.
When set to true, this will automatically remove non-whitelisted properties (those without any decorator in the validation class).
you can read more about this https://docs.nestjs.com/techniques/validation#stripping-properties

How to avoid unexpected side effect in computed properties - VueJS

I am trying to prefill a form with data from a vuex store.In the code provided is the expected result, I need but I know that this is not the way to do it. I am fairly new to Vue/Vuex. The inputs use a v-model thats why i cant use :value="formInformation.parentGroup" to prefill.
data() {
return {
groupName: { text: '', state: null },
parentName: { text: '', state: null },
};
},
computed: {
formInformation() {
const groups = this.$store.getters.groups;
const activeForm = this.$store.getters.activeForm;
if (activeForm.groupIndex) {
const formInfo = groups[0][activeForm.groupIndex][activeForm.formIndex]
this.groupName.text = formInfo.name // Is there a way to not use this unexpected side effect ?
return formInfo;
} else {
return 'No Form Selected';
}
},
},
I searched for an answere for so long now that i just needed to ask it. Maybe i am just googling for something wrong, but maybe someone here can help me.
You are doing all right, just a little refactoring and separation is needed - separate all the logic to computed properties (you can also use mapGetters):
mounted() {
if (this.formInformation) {
this.$set(this.groupName.text, this.formInformation.name);
}
},
computed: {
groups() {
return this.$store.getters.groups;
},
activeForm() {
return this.$store.getters.activeForm;
},
formInformation() {
if (this.activeForm.groupIndex) {
return this.groups[0][this.activeForm.groupIndex][
this.activeForm.formIndex
];
}
}
}
You could either make groupName a computed property:
computed: {
groupName() {
let groupName = { text: '', state: null };
if (formInformation.name) {
return groupName.text = formInfo.name;
}
return groupName;
}
Or you could set a watcher on formInformation:
watch: {
formInformation: function (newFormInformation, oldFormInformation) {
this.groupName.text = formInfo.name;
}
},
Avoid mutating data property in computed.
Computed are meant to do some operation (eg. reduce, filter etc) on data properties & simply return the result.
Instead, you can try this:
computed: {
formInformation() {
const groups = this.$store.getters.groups;
const activeForm = this.$store.getters.activeForm;
if (activeForm.groupIndex) {
const formInfo = groups[0][activeForm.groupIndex][activeForm.formIndex]
// this.groupName.text = formInfo.name // <-- [1] simply, remove this
return formInfo;
} else {
return 'No Form Selected';
}
}
},
// [2] add this, so on change `formInformation` the handler will get called
watch: {
formInformation: {
handler (old_value, new_value) {
if (new_value !== 'No Form Selected') { // [3] to check if some form is selected
this.groupName.text = new_value.name // [4] update the data property with the form info
},
deep: true, // [5] in case your object is deeply nested
}
}
}

.find() returning nothing even when data exists

I've a mongo database with 3 collections for 3 different kind of users as User,Partner,Admin. Whenever a new user of any type signup I'm searching all three collections to check if username and email exist already. I'm trying to achieve this by calling a function as:
function checkAttribute(attr,val,callback){
User.find({attr: val},function(err,user){
if(err){
console.log(err);
}else{
if(user.length === 0){
Partner.find({attr: val},function(err,partner){
if(err){
console.log(err);
}else{
if(partner.length === 0){
Admin.find({attr: val},function(err,admin){
if(err){
console.log(err);
}else{
if(admin.length === 0){
return callback(null,true);
}else{
return callback(null,false);
}
}
});
}else{
return callback(null,false);
}
}
});
}else{
return callback(null,false);
}
}
});
};
Calling function line:
checkAttribute("username",newUser.username,function(error,response){
.......
});
But this is not working as it returns true always even when users with passed username/email exists already. I am unable to find the problem. Any one knows why this is happening?
Thanks in advance.
Since you are passing in the attribute as a variable in the function parameters, the query document
{ attr: val } is an object with the key "attr", not the dynamic attribute you pass in.
To fix this, you need to use computed property names in your query object as
{ [attr]: val }
Also, the function can use async/await pattern to be more readable and for the purpose of finding if a document exist findOne does the job so
well as it returns a document if it exists and null otherwise.
So your function can be refactored as
async function checkAttribute(attr, val, callback) {
try {
const query = { [attr]: val }
const user = await User.findOne(query).exec()
const partner = await Partner.findOne(query).exec()
const admin = await Admin.findOne(query).exec()
const found = (user || partner || admin) ? true: false
return callback(null, found)
} catch (err) {
console.error(err)
return callback(err, null)
}
};
attr: in your queries will search for a db field called attr. If you want to use the function parameter attr, use [attr]: as the key.
Example:
attr = 'username'
User.find({ [attr]: val }, function (err, user) {
if (err) {
console.log(err);
}
})
This is a feature available since ES6 so should work fine. See the docs here for more info

Sails inconsistent record creation

I use the following piece of code to create some records. If I provide incorrect values, say(password and passwordConfirmation does not match), then sometimes an institute record is created without a rollback and sometimes, rollback happens properly.
I would appreciate any help. Is there a better way to do this?
create: function (req, res) {
User.query("BEGIN TRANSACTION", function(result){
if(result) {
sails.log.info(result);
return res.serverError(result);
} else {
Institute.create({
name: req.param('name'),
shortName: req.param('shortName'),
phoneNumber: req.param('phoneNumber'),
subdomain: req.param('subdomain'),
managerEmail: req.param('email')
}, function(error, institute){
if(error) {
sails.log.info(error);
Institute.query("ROLLBACK", function(result) {
sails.log.info(result);
return res.badRequest(error);
});
} else {
User.create({
email: req.param('email'),
password: req.param('password'),
passwordConfirmation: req.param('passwordConfirmation'),
account: institute.id
}, function(error, user) {
if(error) {
sails.log.info(error);
Institute.query("ROLLBACK", function(result) {
sails.log.info(result);
return res.badRequest(error);
});
} else {
User.query("COMMIT", function(result){
sails.log.info(result);
return res.created(user);
});
}
});
}
});
}
});
}
You have a few of options, in no particular order.
1. Write a function that makes all the possible security checks before creation occurs, or use the beforeCreate life cycle call for your models.
For example, you could write a function verifyParams(params) that makes checks such as password comparison (and any other checks you want) for your user creation parameters before you create the institution, or you could just include these checks in your institution creation's beforeCreate method.
2. Delete if there is an error during your user creation
Delete theInstitute model instance in your error case of user creation:
...
User.create(..., function (error, user) {
if (error) {
Institute.destroy(institute.id, function instDestroyed(err) {
...
});
} else {
...
}
});
3. Create a user in your institute model's beforeCreate method.
module.exports = {
attributes: { ... },
beforeCreate: function(values, next) {
User.create(..., function (err, user) {
if (err) { return next(err) }
return next();
});
}
}
Personally, I use method #2 in my own apps.