Why is validate.errors empty after this custom keyword validation? - javascript

I am using a pair of custom keywords to ease our validation needs. So far it has worked well everywhere... but now I'm not getting the errors to persist and be detected after validation.
ajv.addKeyword('batch', {
compile: function validator(batch) {
const { limit, items } = batch;
const arraySchema = {
type: 'array',
items,
minItems: 1,
maxItems: limit,
};
return ajv.compile({
oneOf: [
arraySchema,
items,
],
});
},
errors: false,
});
ajv.addKeyword('customValidator', {
type: 'string',
validate: function validate(schema, data) {
try {
if (data.length > 500) {
throw new Error('should be <= 500 characters');
}
const { type } = myCustomValidator.parse(data);
if (schema === true || schema.includes(type)) {
return true;
}
throw new TypeError(`${data} must be one of the following: ${schema}`);
} catch (error) {
if (!validate.errors) {
validate.errors = [];
}
validate.errors.push(error);
return false;
}
},
errors: true,
});
With then a schema like this:
{
type: 'object',
required: [
'requiredFieldName',
],
properties: {
requiredFieldName: {
batch: {
items: { customValidator: ['allowedType'] },
limit: 100,
},
},
optionalFields: { customValidator: ['allowedType1', 'allowedType2'] },
},
}
I then created a test that would fail causing myCustomValidator.parse to throw.
{
requiredFieldName: ['blah', 'blah'],
}
Using console.log I can see that it throwing and is being caught and being added to the validator.errors. I expect the validation to fail, however at the end of the day it says it passes. Any ideas of what I am doing wrong?
Note: If I change the definition of the items in batch of the schema to type: 'integer' it fails as expected.

Turns out it works if you specify type: 'string' in addition to the customValidator in the schema:
{
type: 'object',
required: [
'requiredFieldName',
],
properties: {
requiredFieldName: {
batch: {
items: { type: 'string', customValidator: ['allowedType'] },
limit: 100,
},
},
optionalFields: { customValidator: ['allowedType1', 'allowedType2'] },
},
}
Don't know why, but this is now working.

Related

mongoose .find ignores values returned by getters, despite toObject and toJSON getters = true in the Schema

I want to find all the documents whose getters return a particular value. Specifically, in my Position schema, I have a property called uncoveredQuantity, and I want to find all Position documents with uncoveredQuantity = 10.
When I do
const positions = await Position.find({uncoveredQuantity : 10});
it returns all documents with various different uncoveredQuantity values, not the ones that are 10! The interesting thing is that my getter function, calculateUncoveredQuantity, does get called when Position.find() gets called, and it prints the console.log statement.
function calculateUncoveredQuantity(this: PositionBaseDocument) {
console.log("getter called");
let quantityLeft = this.quantity;
const dest = this.direction === "in" ? this.closedBy : this.isCloseFor;
if (!dest) return quantityLeft;
for (const pos of dest) {
quantityLeft -= pos.amount;
}
return quantityLeft;
}
const PositionCloseSchema = {
position: {
type: mongoose.Schema.Types.ObjectId,
ref: "Position",
},
amount: {
type: Number,
min: 0,
},
};
const positionSchema = new mongoose.Schema(
{
direction: {
type: String,
enum: CONSTANTS.directions,
required: true,
},
quantity: {
type: Number,
required: true,
min: 0,
},
uncoveredQuantity: {
type: Number,
set: calculateUncoveredQuantity,
default: calculateUncoveredQuantity,
get: calculateUncoveredQuantity,
},
closedBy: {
type: [PositionCloseSchema],
required: function (this: PositionBaseDocument) {
return this.direction === "in";
},
},
isCloseFor: {
type: [PositionCloseSchema],
required: function (this: PositionBaseDocument) {
return this.direction === "out";
},
},
},
{
timestamps: true,
toJSON: {
getters: true,
},
toObject: {
getters: true
},
}
);
export const Position = mongoose.model("Position", positionSchema);

How to set validate rules in vuelidate to have value same as a field of an object?

Let say I have a vue component with data like this:
data: () => ({
form: {
old_password: {
data: '',
type: 'password',
label: 'Old Password',
},
new_password: {
data: '',
type: 'password',
label: 'New Password',
},
repeat_password: {
data: '',
type: 'password',
label: 'New Password Confirmation',
},
},
}),
The data is formatted in this way as I am using another plug-in, ant-design, to build the form, and therefore formatting the data in another way is not an option. The data field will be storing the actual data.
Then, I have the following validation rules set for vuelidate.
validations: {
form: {
old_password: {
data: { required },
},
new_password: {
data: { required },
},
repeat_password: {
data: { sameAsPassword: sameAs('new_password') },
},
},
},
The required rules works, but the sameAsPassword rule is not working. It always return error, even I am sure I am inputting the same password. I guess it is not comparing to a correct field. How can I set the rule so that it is comparing to the correct field?
new_password is not a sibling of repeat_password.data. From the built in validator docs
Locator can be either a sibling property name or a function. When provided as a function, it receives the model under validation as
argument and this is bound to the component instance so you can access
all its properties and methods, even in the scope of a nested
validation.
So a function needs to be passed to sameAs:
validations: {
form: {
old_password: {
data: { required },
},
new_password: {
data: { required },
},
repeat_password: {
data: {
sameAsPassword: sameAs(function() {
return this.form.new_password.data;
})
},
},
},
},
At the same time, for the this to be working inside the function, the data needed to be changed from arrow function to return data. i.e.
data() {
return {
form: {
old_password: {
data: '',
type: 'password',
label: 'Old Password',
},
new_password: {
data: '',
type: 'password',
label: 'New Password',
},
repeat_password: {
data: '',
type: 'password',
label: 'New Password Confirmation',
},
},
}
},
First of all: it's not a good idea to use arrow function for data. You should use:
data() {
return {
form: {}
}
}
You could get a context issues..
And I don't know if you need that data inside validations...you could try this:
export default {
data() {
return {
form: {
nestedA: '',
nestedB: ''
}
}
},
validations: {
form: {
nestedA: {
required
},
nestedB: {
required
}
}
}
}

findOneAndUpdate the latest document

This is my schema
var BudgetSchema = mongoose.Schema({
user_email: { type: String, required: true },
user_budget: { type: Number, required: true },
platforms_budget: {
type: [{
platform_id: { type: String, required: true },
platform_name: { type: String, required: true },
platform_budget_percent: { type: Number, required: true },
platform_budget: { type: Number, required: true },
}]
},
created: { type: Date, default: Date.now() }
});
var BudgetSchemaExport = module.exports = mongoose.model('Budget', BudgetSchema);
And this is the function:
module.exports.updateBudgetPercent = async function (user_email, platform_name, p_budget) {
var query = {
"user_email": user_email,
"platforms_budget": {
$elemMatch: { "platform_name": platform_name }
}
};
//the location that need to be updated
var update = { "$set": { "platforms_budget.$[outer].platform_budget_percent": p_budget } };
//array's index
var options = { arrayFilters: [{ "outer.platform_name": platform_name }] };
var updated = await this.findOneAndUpdate(query, update, options).sort({ created: -1 })
if (updated) { //if the data was updated
return true;
} else {
return false;
}
}
The purpose of the function is to update a budget for specific user.
I want to update the latest document, but it updates the earliest/first one.
I tried this option: var updated = await this.findOneAndUpdate(query, update, options, {sort:{ "created": -1 }}), but I got an error:
events.js:167
throw er; // Unhandled 'error' event
^
TypeError: callback.apply is not a function
Any ideas? Thanks.
Use upsert: true with sort: { created: -1 }. Here is the query:
var updated = await this.findOneAndUpdate(
query,
update,
{
upsert: true,
sort: { created: -1 },
}
);
Hope this helps you.

Model methods are not working in sails js

I am using sails v0.12 , I have different models in my MySql relational database but the area of concern lies in these 2 models which are
1. User
2. Appointment
I want to add apptCustomer and apptProvider data into appointment model
but the value is NULL in the database
My User model is :
module.exports = {
attributes: {
name: {
type: "string",
},
email: {
type: "email",
required: true,
unique: true
},
contact_no:{
type: "int",
maxLength: 15,
//required: true,
},
address: {
type:"longtext",
//required: true,
},
userId:{
type: "string",
primaryKey: true,
unique:true
},
gtoken:{
type: "string"
},
provider:{
type:"string"
},
cancel:{
type: "boolean",
// required: true
},
business_name:{
type:"string",
unique:true
},
business_category:{
type:"string"
},
roles:{ // Many to Many = User <-> User-role <-> Role
collection:'Role',
through:'userrole',
},
services:{
collection:'Service',
through:'serviceprovider', // Many to Many = User (provider) <-> Service-provider <-> Service
},
schedules:{ // One to One = User (provider) - Schedule
collection:'Schedule',
via:'owner',
},
providerAppointments:{ // One to Many = User(customer) - multiple Appointments
collection:'Appointment',
via:'appProvider',
},
customerAppointments:{
collection:'Appointment', // One to Many = User(provider) - multiple Appointments
via:'appCustomer'
}
}
};
And my Appointment Model is
module.exports = {
attributes: {
appointment_id:{
type:'int',
primaryKey: true,
autoIncrement:true
},
appointmentDate: {
type: 'datetime',
required: true,
},
start_time: {
type: 'string',
required: true,
},
end_time: {
type: 'string',
},
status: {
type: 'string',
enum: ['booked', 'canceled']
},
appProvider: {
model: 'user',
},
appCustomer:{
model: 'user',
},
serviceAppointment: {
model: 'service',
}
}
};
And my Model Methods are as follows
Appointment.findOrCreate({appointmentDate:req.body.values.appointmentDate, start_time:req.body.values.start_time, end_time:req.body.end_time, status: status},{appointmentDate:req.body.values.appointmentDate, start_time:req.body.values.start_time, end_time:req.body.end_time, status: status})
.exec(function apptCreated(err,appt){
if(err) { sails.log('err',err)}
Service.findOne({ service_id : req.body.values.selected_service})
.exec(function(err,service){
service.serviceAppointments.add(appt);
service.save(function(err,result){
if(err) { sails.log(err)}
})
}),
User.find({userId: req.body.businessId})
.populate('roles')
.exec(function(err,provider){
_.map( provider.roles, role => { // Problem lies here .. this method is not working
if(role.role_id==1){
provider.providerAppointments.add(appt);
provider.save(function(err, result){
if(err) { sails.log(err)}
sails.log('appointment added to provider')
})
}
})
}),
//Appointment adding to Customer
User.find({userId: req.body.customerId})
.populate('roles')
.exec(function(err,customer){
_.map( customer.roles, role => { // Problem lies here... this method is not working
if(role.role_id==2){
customer.customerAppointments.add(appt)
customer.save(function(err, result){
if(err) { sails.log(err)}
sails.log('appointment added to customer')
})
}
})
}),
// Adding contact to customer
User.update({userId: req.body.customerId},{ contact_no: req.body.values.contact_no}) // this method works fine
.exec(function userUpdated(err, user){
if(err) { return sails.log(err)}
sails.log('contact number updated',user);
})
})
As far as I can see, at the moment that you call provider.providerAppointments.add, provider.providerAppointments is still just a regular property - possibly an array of ids.
I think you need to add .populate('providerAppointments') to your User.find... if you do that, then provider.providerAppointments should have a .add method that works the way you expect.
Of course, if this is the source of error, I would have expected a pretty clear error message, like provider.providerAppointments.add is not a function or some such. But try adding the populate, see if it fixes your problem.

Mongoose. Update by document id throws [TypeError: Cannot read property '_id' of undefined]

There is my code:
var fileModel = context.models.File,
query = {
_id: context.models.ObjectId("532083358ab1654c0c8b4ced") // TODO: for debug, change after update fix
},
update = {
description: context.data.description,
userId: context.data.userId ?
context.models.ObjectId(context.data.userId) : undefined,
isAdded: true
};
fileModel.update(query, update, { multi: true }, function (err) {
if (err) {
console.log('update');
console.log(err);
context.sendJson({ success: false, err: err });
}
else {
context.sendJson({ success: true });
}
});
There is my Schema:
var fileSchema = new schema({
path: { type: String, required: true, validate: [validateName, 'a path is required'] },
isApproved: { type: Boolean, default: false },
isAdded: { type: Boolean, default: false },
name: { type: String, required: true, validate: [validateName, 'a name is required'] },
description: { type: String },
userId: { type: schema.Types.ObjectId },
updated: { type: Date, default: Date.now },
size: { type: Number }
}, { autoIndex: false });
When I try to update document by id I see this messages in console:
update
[TypeError: Cannot read property '_id' of undefined]
I think problem in
userId: context.data.userId ?
context.models.ObjectId(context.data.userId) : undefined,
But I don't understand how fix it.
I solve this by separate part of my code. But I can't understand what's wrong in my first solution. That's working code:
var fileModel = context.models.File,
query = {
_id: {
$in: context.data.files.map(function (el) {
return context.models.ObjectId(el);
})
}
},
update = {
description: context.data.description,
isAdded: true
};
if (context.data.userId){
update.userId = context.models.ObjectId(context.data.userId);
}
fileModel.update(query, update, { multi: true }, function (err) {
if (err) {
console.log('update');
console.log(err);
context.sendJson({ success: false, err: err });
}
else {
context.sendJson({ success: true });
}
});

Categories

Resources