Project/첫번째 프로젝트 쇼핑몰 웹
[ node js - express 쇼핑몰 웹 ] 4. input data validation 추가
무니웜테일패드풋프롱스
2020. 7. 19. 12:26
📌2020 07 19
- log in, sign up, add product, edit product에 input data에 대한 validation을 추가했음
- express validator third party package 사용
- 500 error page 구현
- 데이터베이스 문제 혹은 permission 문제에 대해 stuck 되지 않고 500 error 페이지로 보내줌
✨ Login Validation
- 원래는 user databse를 탐색하여 user id 일치여부, password 일치 여부를 확인해주는 custom validation도 라우터에서 함께 구현하였으나 , 어차피 컨트롤러에서 로그인된 user 를 session에 저장하는 과정에서 Userdatabse. findOne('user') 연산이 들어가서 해당 validation은 컨트롤러로 옮겨줌.
auth.js ( routes)
const { body } = require('express-validator/check');
router.post('/login',
[
body('email').isEmail().withMessage('email을 입력해주세요') // email 형식 검사
body('password').isLength({min:6, max:12}).withMessage('비밀번호를 잘못 입력하셨습니다.') // 비밀번호 형식 검사
],authController.postLogin);
auth.js (controller)
const { validationResult } = require('express-validator/check');
exports.postLogin=(req,res,next)=>{
const email = req.body.email;
const password=req.body.password;
const error = validationResult(req);
if(!error.isEmpty()){ // error 가 넘어온 경우
return res.status(422).render('auth/login',{ //다시 login 페이지로 렌더링해줌
path:'/login',
pageTitle:'login',
ErrorMessage : error.array()[0].msg, // 에러메시지
oldInput : {email: email, password: password}, // form에 old Input을 value로 띄워주기 위해서
validationError: error.array() // input data중 어느곳에 error가 있는지 알려줘서, error가 있는 곳에 css class 적용하여 붉은색 border color 설정
});
}
User.findOne({email:email})
.then(user=>{
if(!user){ // 이메일 불일치
return res.status(422).render('auth/login', {
path:'/login',
pageTitle:'login',
ErrorMessage : '입력하신 email은 존재하지 않습니다.',
oldInput : {email: email, password: password},
validationError:[{param:'email'}]
})
}
bcrypt.compare(password,user.password)
.then(doMatch=>{
if(!doMatch){ // 비밀번호 불일치
res.status(422).render('auth/login', {
path:'/login',
pageTitle:'login',
ErrorMessage : '비밀번호가 일치하지 않습니다.',
oldInput : {email: email, password: password},
validationError:[{param:'password'}]
});
}
else{ // 이메일과 비밀번호 일치 ==> 로그인 성공!
req.session.isLoggedIn= true; // 로그인 되었음
req.session.user = user; // 유저 정보 저장
req.session.save(err=>{ // 세션 저장
console.log(err);
res.redirect('/'); // password 체크하는 flow 추가해줘야함
})
}
})
})
.catch(err=>{
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
});
}
✨ Sign up Validation
- sign up validation에서는 로그인과 같이 email, password형식을 검사한다.
- email custom validator를 추가하여 User database를 탐색하여 이메일 중복 여부를 검사한다.
- confirmPassword custom validator 를 추가하여 2차 비밀번호 일치 여부를 검사한다.
auth.js (routes)
router.post('/signUp', [
body('email').isEmail().withMessage('올바른 email을 입력해주세요') // 이메일 형식 검사
.custom((value, {req})=>{ // custom validator : 해당 이메일이 이미 존재하는지 여부
return User.findOne({email:value})
.then(user=>{
if(user){
return Promise.reject('이미 존재하는 이메일 입니다.');
};
}).catch(err=>{
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
});
}).normalizeEmail(), // 소문자로 변경
// 비밀번호 형식 검사
body('password').isAlphanumeric().withMessage('비밀번호는 숫자와 문자로만 생성가능합니다.')
.isLength({min:6, max:12}).withMessage('비밀번호는 최소 6자 최대 12자 입니다.').trim(),
body('confirmPassword').custom((value,{req})=>{ // custom validator : 2차 비밀번호 일치 여부
if(value !== req.body.password){
throw new Error('비밀번호가 일치하지 않습니다.');
}
else return true;
}).trim()
],authController.postSignUp);
auth.js (controller)
exports.postSignUp=(req,res,next)=>{
const email = req.body.email;
const password = req.body.password;
const error = validationResult(req);
if(!error.isEmpty()){ // error가 넘어온 경우
return res.status(422).render('auth/signUp',{
path:'/signUp',
pageTitle:'sign up',
ErrorMessage : error.array()[0].msg,
oldInput : {email: email, password: password},
validationError: error.array()
});
}
// routes에서 모든 검사를 마쳤으니 바로 db에 저장해준다
bcrypt.hash(password,12)
.then(hashedPassword=>{ // hashed password로 변경
const user = new User({
email:email,
password:hashedPassword,
cart:{items:[]}
}); // 새로운 User 데이터 저장
user.save()
.then(result=>{
res.redirect('/login');
sendMail(email,'welcome to Amadoo!',
welcomeMessage);
})
}).catch(err=>{
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
});
✨ Add product && Edit Product Validation
- add와 edit 모두 제목, 이미지 url, 가격, 설명에 대한 형식 검사 구현
- rendering 시 isError 데이터를 넘겨주어서 isError == true 이거나 editing == true 일 때 input value를 띄워주도록 함
admin.js (routes )
router.post('/add-product', AuthRouting,[
body('title').isString().withMessage('제목을 올바르게 입력해주세요.') // 제목 형식 검사
.isLength({min:2,max:15}).withMessage('제목은 2글자 이상, 15글자 이하여야 합니다.').trim(),
body('imageUrl').isURL().withMessage('올바른 형식의 이미지 주소를 입력해주세요.').trim(), // 이미지 url 형식 검사
body('price').isNumeric().withMessage('숫자만 입력해주세요'), // 가격 형식 검사
body('description').isLength({min:5, max:100}).withMessage('상품 설명은 최소 5글자여야합니다.') // 설명 형식 검사
] ,
AdminController.postAddProduct);
admin.js (controller)
exports.postAddProduct=(req,res,next)=>{
const title = req.body.title;
const imageUrl =req.body.imageUrl;
const price = req.body.price;
const description=req.body.description;
const error = validationResult(req);
if(!error.isEmpty()){ // error가 넘어온 경우
return res.status(422).render('admin/add-product',{
path:'/add-product',
pageTitle:'add product',
ErrorMessage : error.array()[0].msg,
validationError: error.array(),
editing:false,
isError:true, // 에러 여부 ==> isError || editing 일 경우 value 띄워주도록 함
product:{title:title,imageUrl:imageUrl,price:price,description:description}
// value에 띄울 값 product 객체에 담아주기
})
}
const product = new Product({
title:title,
price:price,
description:description,
imageUrl:imageUrl,
userId:req.user._id
});
product.save()
.then(result=>{
res.redirect('/');
}).catch(err=>{
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
});
};