Joi requiring fields when it shoudnt be - javascript

So I have some posts validation using #hapi/joi 17.1.1 and in there I have two fields: textfield and picture. Im not requring any of the fields yet still it is saying that picture is required.
posts validation
module.exports.postsValidation = (data) => {
const schema = Joi.object({
textfield: Joi.string().max(280),
picture: Joi.string(),
});
return schema.validate(data);
};
posts.js (where im using validation)
router.post("/create", authenticateToken, async (req, res) => {
try {
if ((req.body.textfield == "") & (req.body.picture == "")) {
return res.status(400).json("Fill one of the fields");
}
const { error } = postsValidation(req.body);
if (error) return res.status(400).json(error.details[0].message);
// Getting info for new post
const newPost = new Post({
textfield: req.body.textfield,
picture: req.body.picture,
ownerId: req.user._id,
});
// Saving new post
await newPost.save();
res.json(newPost);
} catch (error) {
res.sendStatus(500);
}
});
when I log out the error it says this
[Error [ValidationError]: "picture" is not allowed to be empty] {
_original: { textfield: 'sssss', picture: '' },
details: [
{
message: '"picture" is not allowed to be empty',
path: [Array],
type: 'string.empty',
context: [Object]
}
]
}
Can anyone please tell whats going on?

This is because you are sending the prop picture from the FE side and it's an empty string ''. You should add an .allow('') your validation picture: Joi.string().allow('') if you want to save an empty string inside the DB or change the FE side to not send the picture prop at all if the string is empty.

Just in case the value is null too
module.exports.postsValidation = (data) => {
const schema = Joi.object({
textfield: Joi.string().max(280),
picture: Joi.string().allow("", null),
});
return schema.validate(data);
};

Related

CastError: Cast to ObjectId failed for value "XXXXXXXXXX" (type string) at path "_id" for model "XXXX"

I'm a beginner.
when i'm testing route with wrong (short) ID, for example instead of "6261220286e8d5e7ee6f221e" i'm putting "6261220286e8d5e7ee6f221" node app crashes with this error:
here is the route code:
router.put('/:id', async (req, res) => {
//Validate with Joi
const { error } = validateGenre(req.body);
//If invalid, return 400 - Bad Request.
if (error) return res.status(400).send(error.details[0].message);
//Look up the genre and Update.
const genre = await Genre.findByIdAndUpdate(req.params.id, { name: req.body.name }, { new: true })
//If not exists, return 404.
if (!genre) return res.status(404).send('The genre with the given ID was not found');
//Return ubdated genre.
res.send(genre);
});
function validateGenre(genre) {
const schema = Joi.object({ name: Joi.string().min(3).max(50).required() });
return schema.validate(genre);
}
here also Genre schema:
const genreSchema = new mongoose.Schema({
name: {
type: String,
required: true,
minlength: 5,
maxlength: 50,
}
});
const Genre = new mongoose.model('Genre', genreSchema);
My question is: How to handle this type of error, or how to validate requested ID length to return proper message to the client.
Sorry again, I'm really a beginner and thanks in advance.
Have you tried using try catch ??, in catch section you can do response with status 500 and an error message thrown by Genre

Mongoose Throw Error When Non Existing Key is Present

I have a code where I am updating my schema object with request body. I have applied validation rules on the schema. The problem is, I want the schema to throw an error when there's a non existing field in the request body. Non existing key doesn't save to the database as I want but I want to throw some error instead of saving the object. Schema:
const peopleSchema = new mongoose.Schema(
{
fullname: {
type: String,
required: [true, "fullname is required"],
validate: [(value) => isAlpha(value, "en-US", {ignore: " "}), "name should be alphabetic only"],
},
phone: {
type: String,
validate: [isPhone, "please enter a valid phone number"],
},
address: String,
},
{ timestamps: true }
);
Code to update person:
router.put("/:id", checkUser, async (req, res, next) => {
try {
const { id } = req.params;
const user = req.currentUser;
const person = user.people.id(id);
person.set(req.body);
const response = await user.save();
res.json({ response });
} catch (err) {
next(new BadRequestError(err));
}
});
for validation there are two way based on callback and async approache ,
because your code is based on async/await you must to use validateSync() like the following code:
let errors = user.validateSync()//check validation
if(errors){
console.log(errors)
throw errors;//handle your error
}
const response = await user.save()
in callback method :
user.save(function(err,response){
if (err){
console.log(err);
//handle error
}
else{
console.log(response)
res.json({ response });
}
})

Query data is received but can not be accessed from GraphQL API

I built the API with apollo server and everything works fine in graphiql. I make requests to the api from front-end react app with apollo client.
const [getUserPosts, { loading, error, data }] = useLazyQuery(GET_USER_POSTS);
useEffect(() => {
getUserProfile();
getUserPosts({ variables: { email: userEmail } });
}, [userEmail]);
SO getUserProfile fetches the user email from the express back end (I have an express serving react and a separate graphql api), then I query the posts of that user on the api. Below is the query itself
export const GET_USER_POSTS = gql`
query User($email: String) {
user(email: $email) {
email
posts {
content
}
}
}
`;
This is the typedefs and resolver on the api server
const typeDefs = gql`
type User {
email: String
posts: [Post]
}
type Post {
id: ID!
email: String
content: String
}
type Query {
users: [User]
posts: [Post]
user(email: String): [User]
post(id: String): [Post]
}
type Mutation {
addPost(email: String, content: String): [Post]
deletePost(id: String): [Post]
}
`;
const resolvers = {
Query: {
users: () => User.find(),
posts: () => Post.find(),
user: (parent, args) => User.find({ email: args.email }),
post: (parent, args) => Post.find({ _id: args.id }),
},
User: {
posts: async user => {
try {
const postsByUser = Post.find({ email: user.email });
return postsByUser;
} catch (error) {
console.log(err);
}
},
},
Mutation: {
addPost: async (parent, args) => {
const newPost = new Post({
email: args.email,
content: args.content,
});
try {
newPost.save();
} catch (err) {
console.log(err);
}
},
deletePost: async (parent, args) => {
try {
const deletedPost = await Post.deleteOne({ _id: args.id });
} catch (err) {
console.log(err);
}
},
},
};
then I try to console.log the data here
if (loading) {
console.log(loading);
}
if (error) {
console.log(error);
}
if (data) {
console.log(loading);
let test = data.user[0];
//I can see the data logged in the console as an object {email: "abc", posts: [array of posts]}
console.log(test);
}
BUT if I try to console.log(test.posts) react results with can not read property "posts" of undefined
UPDATE-1 !!
So when react results with the above error, I try to refresh the page again and it now can logs the "posts" array. But it sometimes take 2 or 3 refresh to make it work and sometimes when I refresh again it does not work anymore. Why is this happening ????
UPDATE-2 !!
So I try to troubleshoot with this line:
{data ? console.log(data.user[0].posts) : console.log("nothing")}
and interestingly it actually does log "nothing" a few times in the console before logging the data. But this is weird because I explicitly write that if only "data" is "true" then log it in the console. But somehow "data" is somtimes null itself. This data is provided by apollo client and it should be always true after loading is false, how is data still null after loading is false already ???
So I found the problem. Turns out it actually comes from within this block:
useEffect(() => {
getUserProfile();
getUserPosts({ variables: { email: userEmail } });
}, [userEmail]);
After observing in the network tab, it seems that my app try to send request to graphQL api before getUserProfile was done pulling user email, so it sent an empty request and hence received nothing. I was naive to think getUserProfile and getUserPosts will be executed synchronously. So I wrap getUserPosts with
if (userEmail) {
getUserPosts({ variables: { email: userEmail } });
}
So now only after I received the uerEmail then getUserPosts will be executed.

How to add custom validator function in Joi?

I have Joi schema and want to add a custom validator for validating data which isn't possible with default Joi validators.
Currently, I'm using the version 16.1.7 of Joi
const method = (value, helpers) => {
// for example if the username value is (something) then it will throw an error with flowing message but it throws an error inside (value) object without error message. It should throw error inside the (error) object with a proper error message
if (value === "something") {
return new Error("something is not allowed as username");
}
// Return the value unchanged
return value;
};
const createProfileSchema = Joi.object().keys({
username: Joi.string()
.required()
.trim()
.empty()
.min(5)
.max(20)
.lowercase()
.custom(method, "custom validation")
});
const { error,value } = createProfileSchema.validate({ username: "something" });
console.log(value); // returns {username: Error}
console.log(error); // returns undefined
But I couldn't implement it the right way. I read Joi documents but it seems a little bit confusing to me. Can anyone help me to figure it out?
const Joi = require('#hapi/joi');
Joi.object({
password: Joi
.string()
.custom((value, helper) => {
if (value.length < 8) {
return helper.message("Password must be at least 8 characters long")
} else {
return true
}
})
}).validate({
password: '1234'
});
Your custom method must be like this:
const method = (value, helpers) => {
// for example if the username value is (something) then it will throw an error with flowing message but it throws an error inside (value) object without error message. It should throw error inside the (error) object with a proper error message
if (value === "something") {
return helpers.error("any.invalid");
}
// Return the value unchanged
return value;
};
Docs:
https://github.com/hapijs/joi/blob/master/API.md#anycustommethod-description
Output for value :
{ username: 'something' }
Output for error:
[Error [ValidationError]: "username" contains an invalid value] {
_original: { username: 'something' },
details: [
{
message: '"username" contains an invalid value',
path: [Array],
type: 'any.invalid',
context: [Object]
}
]
}
This is how I validated my code, have a look at it and try to format yours
const busInput = (req) => {
const schema = Joi.object().keys({
routId: Joi.number().integer().required().min(1)
.max(150),
bus_plate: Joi.string().required().min(5),
currentLocation: Joi.string().required().custom((value, helper) => {
const coordinates = req.body.currentLocation.split(',');
const lat = coordinates[0].trim();
const long = coordinates[1].trim();
const valRegex = /-?\d/;
if (!valRegex.test(lat)) {
return helper.message('Laltitude must be numbers');
}
if (!valRegex.test(long)) {
return helper.message('Longitude must be numbers');
}
}),
bus_status: Joi.string().required().valid('active', 'inactive'),
});
return schema.validate(req.body);
};

How can I make sure that this function handles errors properly?

I have a function that checks user input in an express application. I don't want to use any library to validate those inputs so I declared an array where errors are pushed into.
I have embedded the middleware function as a static method in a class...
static postAdchecker(req, res, next) {
let { state, price, manufacturer, model, bodytype } = req.body;
console.log('req', req.body);
const errors = [];
// If state is empty or undefined throw this error
if (!state) {
console.log('state', state);
const error = {
message: 'Please specify the state of the car'
};
errors.push(error);
}
// If state is supplied, convert it to lowercase, trim and check if value is new/used
if (state.toLowerCase().trim() !== 'new' && state.toLowerCase().trim() !== 'used') {
const error = {
message: 'State can only be new or used'
};
errors.push(error);
}
// Same goes for the others.
if (!price) {
const error = {
message: 'You will need to specify a sale price'
};
errors.push(error);
}
if (!manufacturer) {
const error = {
message: 'Specify a manufacturer'
};
errors.push(error);
}
if (!model) {
const error = {
message: 'Specify a model'
};
errors.push(error);
}
if (!bodytype) {
const error = {
message: 'You will need to specify a bodytype'
};
errors.push(error);
}
return res.status(400).json({
status: 400,
errors: {
body: errors.map(err => err.message)
}
});
console.log('errors', errors);
req.body.state = state.toLowerCase().trim();
req.body.price = price.toLowerCase().trim();
req.body.manufacturer = manufacturer.toLowerCase().trim();
req.body.model = model.toLowerCase().trim();
req.body.bodytype = bodytype.toLowerCase().trim();
// req.authData;
return next();
}
How can I achieve the following?
Convert the values in the input field to lowercase and trim when supplied.
When there are errors, return all the errors.
When there are no errors, transfer operation to the next function instead of returning an empty array.
You are just missing one condition:
if(errors.length) { // <<<
return res.status(400).json({
status: 400,
errors: {
body: errors.map(err => err.message)
}
});
}

Categories

Resources