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);
    });
};