Joi validation react .custom() validation in react - javascript

Hello I'm trying to add custom validation to my form using the Joi library.Basically i want to reach to an api and either return error message or not based on the data.
here is my Joi schema
const schema = Joi.object({
email: Joi.string()
.email({ tlds: { allow: false } })
.required(),
firstName: Joi.string().required(),
lastName: Joi.string().required(),
description: Joi.string().min(10).max(250).required().custom(isSad).message({'description.invalid':`the value provided in the description field is sad, please redact it`}),
});
the isSad function passed in the custom() argument
const isSad = (value,helpers) => {
fetch('api url',{
method: "POST",
headers: {
"apikey": "key"
},
body:value
}).then(data => {
return data.json()
}).then(data => {
if(data.Sad > 0.49) {
return helpers.error('description.invalid');
}
}).catch(error => {
console.log('logging the error in catch', error)
})
}
As far as I understand I'm sending 'description.invalid' to the .message() function in the schema where I should use it in the passed object to display a custom message, but for some reason I'm not getting the error message displayed. The field seems to be validated as valid which it shouldn't be in my case if the value received is > 0.49
EDIT: Tried using schema.validateAsync with .external() like so
const isSad = (value,helpers) => {
console.log('logging value',value)
console.log('logging helpers',helpers)
fetch('api',{
method: "POST",
headers: {
"apikey": "apikey"
},
body:value
}).then(data => {
return data.json()
}).then(data => {
if(data.Sad > 0.49) {
throw new Error('Ur description is sad please edit it')
}
}).catch(error => {
console.log('logging the error in catch', error)
})
}
and to the schema i just attach .external(isSad) like so
const schema = Joi.object({
email: Joi.string()
.email({ tlds: { allow: false } })
.required(),
firstName: Joi.string().required(),
lastName: Joi.string().required(),
description: Joi.string().min(10).max(250).required().external(isSad)
});
I also had to convert the code where I use the schema.validateAsync since it now returns data as HTTP response.BUT it still doesn't work I get no response whatsoever from the .external() and the description field is validated ( It's like the .external() is not there at all ).

Found an issue, it says that custom is only for synchronous functions, for async you need to use external.
EDIT1
If I understand it right, and please correct me if not, the problem is that error is not thrown, when it should.
In that case I have done the following. Changed the request and the data.
The console says: logging the error in catch Error: Ur description is sad please edit it. Which looks to me as the expected behavior.
const isSad = (value) => {
console.log("value: ", value);
fetch("https://api.coindesk.com/v1/bpi/currentprice.json", {
method: "GET"
})
.then((data) => data.json())
.then((data) => {
console.log("request data: ", data);
if (value.startsWith(data.chartName)) {
throw new Error("Ur description is sad please edit it");
}
})
.catch((error) => {
console.log("logging the error in catch", error);
});
};
const schema = Joi.object({
email: Joi.string()
.email({ tlds: { allow: false } })
.required(),
firstName: Joi.string().required(),
lastName: Joi.string().required(),
description: Joi.string().min(10).max(250).required().external(isSad)
});
schema.validateAsync({
email: "asf#adf.asdf",
firstName: "adfsdafsdf",
lastName: "asdfasdf",
description: "Bitcoin111"
});

I ended up using .validate() not .validateAsync() and made my own custom function check after Joi has already validated the form.

Related

Strapi login/register and authentication

I have a peculiar problem here guys. While working with strapi (first timer), I'm attempting to set up login and register, but I keep running into the same error, until I used the exact code example from the documentation, even then it only works on a condition.
When I do this, it works. that is when I hardcode the identifier and password. it works and I get the jwt.
function fetchLogin(){
await axios
.post(url, {
identifier: "user",
password: "123456"
})
.then((response) => {
// Handle success.
console.log('Well done!');
console.log('User profile', response.data.user);
setUser(response.data.user)
console.log('User token', response.data.jwt);
setUserToken(response.data.jwt)
})
.catch((error) => {
// Handle error.
console.log('An error occurred:', error.response);
setLoginError(error.response)
});
}
But I used the input fields from the client, and I collect the correct data, I get an error. Here.
function fetchLogin(data){
console.log(data)
await axios
.post(url, {
data
})
.then((response) => {
// Handle success.
console.log('Well done!');
console.log('User profile', response.data.user);
setUser(response.data.user)
console.log('User token', response.data.jwt);
setUserToken(response.data.jwt)
})
.catch((error) => {
// Handle error.
console.log('An error occurred:', error.response);
setLoginError(error.response)
});
}
//Heres the function that collects the input
const LoginSubmit = (e) =>{
e.preventDefault()
const data = {
identifier: login.email,
password: login.password
}
const details = JSON.stringify(data)
fetchLogin(details)
}
// useState
const [login, setLogin] = useState({
email: '',
password: ""
})
// input fields
<div className="input-group input-group-sm mb-3">
<span className="input-group-text" id="inputGroup-sizing-sm">Password</span>
<input type="password" className="form-control"
aria-label="Sizing example input"
value={login.password}
onChange={(e)=> setLogin({...login, password: e.target.value})}
aria-describedby="inputGroup-sizing-sm"/>
</div>
Everything checks out when I log data to console, but I get a validation error
POST http://localhost:1337/api/auth/local 400 (Bad Request)
An error occurred: {data: {…}, status: 400, statusText: 'Bad Request', headers: {…}, config: {…}, …}
//the error message
errors: Array(2)
0: {path: Array(1), message: 'identifier is a required field', name: 'ValidationError'}
1: {path: Array(1), message: 'password is a required field', name: 'ValidationError'}
What I'm I doing wrong?
should remove Json.Stringfy in LoginSubmit when call fetchLogin and use spread operator. Body of request must be {id:"", password:""}, not like {data: "{id:"",password:""}"}
so should use spread operator {...}, it do unpack elements of an object/array
ex:
const data = {name:"foo"}
const newData = {
...data,
color: 'black'
};
//newData is {name: "foo",color:'black'};
solution:
//data is {identifier: "user",password: "123456"}
function fetchLogin(data){
await axios.post(url, { ...data })
equal:
function fetchLogin(data){
await axios.post(url, {identifier: "user",password: "123456"})

Separating Mongoose code from Express Router

So basically, I'm trying to separate my code that handles data (mongoose) from my express Router code, since I might want to use it elsewhere too.
The first thing I did was, I got rid of the res.json() calls, since I don't want the code to only work returning a http response. I want it to return data, so I can then return that data from my router as a http response, but still use it as regular data elsewhere.
Here is a function I wrote to get data from mongoose.
module.exports.user_login = data => {
console.log(data);
ModelUser.findOne({email: data.email}).then(user => {
if(!user){
console.log({email: 'E-mail address not found'});
return {
status: response_code.HTTP_404,
response: {email: 'E-mail address not found'}
}
}
bcrypt.compare(data.password, user.password).then(isMatch => {
if(!isMatch){
console.log({password: 'Invalid password'});
return {
status: response_code.HTTP_400,
response: {password: 'Invalid password'}
}
}
const payload = {
id: user.id,
email: user.email
};
jwt.sign(
payload,
config.PASSPORT_SECRET,
{
expiresIn: "1h"
},
(err, token) => {
console.log({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
return {
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
}
}
);
});
});
};
When this code gets executed in my route like so:
router.post("/login", (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
console.log("ret", dm_user.user_login(req.body));
});
The log says the return value of user_login() is undefined, even though right before the return statement in user_login() I am logging the exact same values and they are getting logged.
Before I changed it to a log, I tried to store the return value in a variable, but obviously that remained undefined as well, and I got the error: 'Cannot read propery 'status' of undefined' when trying to use the value.
I am definitely missing something..
Well you have an small callback hell here. It might be a good idea to go with async / await and splitting up your code into smaller chunks instead of putting everyhing in 1 file.
I rewrote your user_login function:
const { generateToken } = require("./token.js");
module.exports.user_login = async data => {
let user = await ModelUser.findOne({ email: data.email });
if (!user) {
console.log({ email: "E-mail address not found" });
return {
status: response_code.HTTP_404,
response: { email: "E-mail address not found" }
};
}
let isMatch = await bcrypt.compare(data.password, user.password);
if (!isMatch) {
console.log({ password: "Invalid password" });
return {
status: response_code.HTTP_400,
response: { password: "Invalid password" }
};
}
const payload = {
id: user.id,
email: user.email
};
let response = await generateToken(
payload,
config.PASSPORT_SECRET,
response_code
);
return response;
};
I have moved your token signing method into another file and promisfied it:
module.exports.generateToken = (payload, secret, response_code) => {
return new Promise((res, rej) => {
jwt.sign(
payload,
secret,
{
expiresIn: "1h"
},
(err, token) => {
if (err) {
rej(err);
}
res({
status: response_code.HTTP_200,
response: {
success: true,
token: token
}
});
}
);
});
};
Now you need to change your router function into an async:
router.post("/login", async (req, res) => {
const { errors, isValid } = validateLogin(req.body);
if(!isValid) return res.status(400).json(errors);
let result = await dm_user.user_login(req.body);
console.log(result);
});
In addition: You get undefined because you return your value to an callback function
I also would seperate your routes from your controllers instead of writing your code inside an anonymous function
Please notice that whenever you are trying to return any value you are always present in the callback function and that is definitely not going to return any value to its intended place.
There are a couple of things you can improve about your code :
1.Donot use jwt inside your code where you are making database calls, instead move it where your routes are defined or make a separate file.
2.If you are intending to re-use the code, I would suggest you either use async-await as shown in the answer above by Ifaruki or you can use something like async.js. But the above shown approach is better.
Also always use 'error' field when you are making db calls like this:
ModelUser.findOne({email: data.email}).then((error,user) => {

How to check if there are no more documents to update using findOneAndUpdate

So I am learning CRUD for a school project and I followed a tutorial that was really useful. However, when I completed it I noticed that when there are no more quotes to update, it still updates quotes. How can I change this so that it will stop updating quotes that arent even there?
app.put('/quotes', (req, res) => {
quoteCollection.findOneAndUpdate(
{ name: 'Yoda' },
{
$set: {
name: req.body.name,
quote: req.body.quote
}
},
{upsert: true}
)
.then(result => {
//The if block that i am trying
if (result.deletedCount === 0) {
return res.json('No quote to delete')
}
})
.catch(error => console.error(error))
})
Why are you passing {name: "Yoda}? This route is supposed to only update the quote with "Yoda" as its name? If not, then you need to grab from the request object the quote that should be updated.
I tried to create a different version, based on the assumption that the quote that should be updated will come from the req.body:
app.put("/quotes", async (req, res) => {
//Grab the name/id/identifier for the quote you want to update from the body
const query = req.body.name;
// Try to update the document on the database
try {
const result = await quoteCollection.findOneAndUpdate(
query,
{
name: req.body.name,
quote: req.body.quote,
},
{
upsert: true,
new: true,
}
);
// If it worked, it will return the updated quote
res.status(200).json({
status: 200,
data: {
result,
},
});
} catch (err) {
res.status(400).json({
status: 400,
message: "Something went wrong",
});
}
});

UnhandledPromiseRejectionWarning: Error: Property 'password' doesn't exist. pg-promise

I am using pg-promise.
I am having an issue trying to insert the following Javascript array:
[ { email: 'test1#gmail.com', password: 'test2' },
{ email: 'tes2t#gmx.com', password: 'test'3 },
{ email: 'test4#gmail.com', password: 'test4' },
{ email: 'test4#yahoo.com.ar', password: 'test5' }]
Using the following:
async function insertDB(data){
const cs = new pgp.helpers.ColumnSet(['email', 'password'], {table: 'users'});
console.log(data)
const query = pgp.helpers.insert(data, cs);
db.none(query)
.then(data => {
logger.info(" Query success: ", data);
})
.catch(error => {
logger.warn(" Query error: ", error);
});
}
and I get
UnhandledPromiseRejectionWarning: Error: Property 'password' doesn't exist.
**data.password = undefined**
**data[0] = { email: 'test1#gmail.com', password: 'test2' }**
How can I insert this data into my postgresdb?
// should create columnsets only once:
const cs = new pgp.helpers.ColumnSet(['email', 'password'], {table: 'users'});
function insertDB(data) {
// wrapping it into a function is safer, for error-reporting via query methods:
const query = ()=> pgp.helpers.insert(data, cs);
db.none(query)
.then(data => {
// data = null always here, no point displaying it
logger.info('Query success:', data);
})
.catch(error => {
logger.warn('Query error:', error);
});
}
And your function doesn't need async in this case.
UnhandledPromiseRejectionWarning: Error: Property 'password' doesn't exist.
You are confusing the JavaScript compiler, declaring the function as async, and then throwing an error synchronously while generating the insert, due to missing property password.
And if you want to insert some records without password, with null, for example, define your columnset like this:
const cs = new pgp.helpers.ColumnSet([
'email',
{name: 'password', def: null}
], {table: 'users'});
Other than that, type ColumnSet is ultimately flexible, see the documentation for each contained Column.
EXTRA
And if you want to use server-side DEFAULT value for missing passwords, you can provide it, with the help of Custom Type Formatting:
const DEFAULT = {rawType: true, toPostgres: ()=> 'DEFAULT'};
And then your password column can be defined like this:
{name: 'password', def: DEFAULT}
And there are many alternatives, with properties init and mod supported by Column.

Sequelize call structure

I'm working with sequelize and want to query my database using the following code:
models.user.findOne({ where: {email: req.body.email} }, (err, existingUser) => {
.... More code
}
But the code block wont run so i read the docs and the only example they give uses the following structure:
models.user.findOne({ where: {title: 'aProject'} }).then(project => {
.... Code
})
Did i do something wrong with the first example or am i just unable to use that structure?
Sequelize.js uses promises. When you use findOne, as an example, a promise is returned. So try the following:
models.user.findOne({
where: {
email: req.body.email
}).then(existingUser => {
//Do something
}).catch(err => {
//Do something with error
});
Sequelize>=1.7 query return a promise , to resolve it you need to use "then"
nb: you can use the model directly from the declaration instead of "models.user" like this:
const Userdb = sequelize.define('userdb', {
id : {
primaryKey: true,
autoIncrement:true,
allowNull:false,
type: Sequelize.INTEGER
},
email: {
type: Sequelize.STRING,
allowNull:false,
validation:{
isEmail:true
},
username: {
type: Sequelize.STRING,
allowNull:false,
min:6,
max:25,
notEmpty:true,
}
}
and to get a user by it's email you can do this :
Userdb.findOne({
where: {
email: req.body.email
}
}).then((result)=>{
if(results) // there is one user
{
// do something with resutls
}else{
// no user with that mail
}
})

Categories