프로젝트/풀스택 프로젝트

4-5. [React + Node.js Express] 회원 정보 수정 기능

찰리-누나 2022. 12. 6.

 

 

모든 사이트의 핵심 기능에는 회원가입, 로그인, 로그아웃이 있다. 그리고 회원가입이 가능한 웹사이트는 '아이디 찾기, 비밀번호 찾기, 회원정보 수정'이 가능해야 한다. 이번에는 세가지 중 회원정보 수정 기능을 만들어본다. 프로필 사진은 multer 등을 이용하여 여러가지 복잡한 과정을 거쳐야 하기 때문에, 틀만 만들어놓고 다음에 구현할 것이다.

 


 

Frontend

 

 

Topbar의 닉네임을 클릭하면 회원 정보를 누를 수 있는 메뉴가 뜬다. 

회원 정보

 

회원 정보는 /user/about 라우터로 구현했다. Redux에 저장된 회원 정보를 불러오는 식이다. 저번에는 localStorage에 redux의 내용을 모두 저장했는데, 혹시 모를 위험을 방지하기 위해 브라우저가 꺼지면 회원 정보가 브라우저에서 지워지도록 하기 위해 Redux에서 SessionStorage에 저장되도록 코드를 바꾸었다.

 

/src/store.js
const persistConfig = {
    key:'root',
    // 로컬 스토리지에 저장할 경우 storage, 세션에 저장할 경우 storageSession
    storage:storageSession,
    // whitelist : ['적용대상목록'] 
    // blacklist : ['미적용대상목록']
    whitelist: ['setUser'],
    blacklist: ['setToken']
}

 

About user.js

redux를 useSelector를 사용해 받아와, state의 기본값으로 할당해 주고 {변수} 로 html에서 값을 보여준다. onChage로 input에 사용자가 실시간으로 입력하는 값을 받아온 다음, '변경사항 저장'버튼을 누르면 post axios 요청으로 사용자가 입력한 데이터를 백엔드로 보내준다. 비밀번호 변경은 원래 비밀번호를 인증한 후, 별도의 페이지에서 변경하게 해 주는 것이 좋지만, 어차피 /login을 한번 더 불러오기만 하는 과정이라 빠르게 기능 개발을 해보기 위해 잠시 생략한다. 여기까지는 회원가입 및 로그인과 똑같은 기능이다.

function AboutUser(){


   let navigate = useNavigate()
   
 // < ---------------------------- get Redux ---------------------------->
    
    let redux = useSelector((state)=>{return state})
    const dispatch = useDispatch() 
    
     // < ---------------------------- set Values ---------------------------->

    const [values, setValues] = useState({
        email:redux.setUser.u_email,
        name:redux.setUser.u_name,
        nikname:redux.setUser.u_nik,
        password: '',
        ckpassword:'',
        showPassword: false,
        showPasswordTwo:false
      });
      
       // < ---------------------------- onChange Values ---------------------------->

      const handleChange = (prop) => (event) => {
        setValues({ ...values, [prop]: event.target.value });
      };

      
       // < ---------------------------- CALL API ---------------------------->

     function CallUpdate() {

      let body = {
        email:values.email,
        name:values.name,
        nikname:values.nikname,
        password:values.ckpassword
      }

         axios.post('/api/user/update', body)
          .then(response=>{console.log('회원정보 수정 완료',response.data) 
          dispatch(GET_USER({email:values.email, name:values.name, nikname:values.nikname, role:redux.setUser.u_role}))
          navigate('/')
          // eslint-disable-next-line no-restricted-globals
         location.reload()
      })
     }

      
      
      ...
      
      return (
      
      ...
      
       <FormControl style={{marginTop:'40px'}} variant="standard">
                      <InputLabel htmlFor="component-helper">이메일</InputLabel>
                      <Input
                      id="component-helper"
                      value={values.email}
                      onChange={handleChange('email')}
                      aria-describedby="component-helper-text"
                      readOnly
                      endAdornment={
                        <InputAdornment position="end">
                          <span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
                        </InputAdornment>  }/>              
            </FormControl>          
            
            ...
        )

 

 

 

 

Backend

 

 

회원정보 수정과 같은 DB 수정에 있어 가장 중요한 기능은 DB가 제공하는 함수에 있다. MySql을 사용하려면 SQL을 잘해야 하듯이, 몽고db와 몽구스를 사용하려면 몽구스의 함수를 잘 이용해야 한다. 처음 회원가입 기능을 만들 때 몽구스의 save에 대해 설명했었다. (https://make-somthing.tistory.com/11)

 

몽구스의 save() 쿼리는 저장을 위해 들어온 데이터의 _id값이 이미 존재하는 경우, 해당 _id 문서에서 수정된 값만 반영하여 데이터를 업데이트해준다. 만일 들어온 데이터의 _id값이 존재하지 않으면 DocumentNotFoundError를 반환한다. 

 

https://mongoosejs.com/docs/documents.html#updating-using-save

 

Mongoose v6.8.0: Documents

Mongoose documents represent a one-to-one mapping to documents as stored in MongoDB. Each document is an instance of its Model. Document and Model are distinct classes in Mongoose. The Model class is a subclass of the Document class. When you use the Model

mongoosejs.com

 

이 기능을 활용하여 DB에서 찾아낸 유저에게 수정할 값을 전달해 줄 것이다. 프론트를 구현할 때 post 요청에 변수를 담아 name, nikname, password를 보내주었다. 이때 회원가입 로직을 만들 때 사용한 userSchema.pre('save', function( next ) 를 잊지 않도록 한다. 해당 메소드는 save 함수를 실행하기 전 선행하여 실행되는, 비밀번호를 암호화해주는 메소드이다. 따라서 password가 null일 경우에도 작동될 우려가 있기 때문에 먼저 유저가 '비밀번호를 수정하였는지, 그렇지 않고 나머지만 수정하였는지'를 검사하여 업데이트 과정을 나눈다. (비밀번호 변경 창을 따로 빼면 이렇게 하지 않아도 되지만, 나는 한 화면에 작성하였기 때문에 이 과정을 거쳐야 한다..)

 

비밀번호를 변경하지 않는 경우, 비밀번호를 수정하기 위해 pre와 save를 거쳐야 할 필요가 없으므로 DB로 email을 전달하여 findOneAndUpdate로 바로 업데이트해주면 된다. 유저를 찾아냄과 동시에 데이터를 수정해줄 수 있다.


router.post('/update', function(req,res){

    // 비밀번호를 변경하지 않는 경우
    if(req.body.password === '') {
        User.findOneAndUpdate({ email:req.body.email }, {nikname:req.body.nikname, name:req.body.name}, (err, doc) => {
            if (err) return res.json({ success: false, err });
            return res.status(200).send({
                success: true, noPasswordChange:true
            });
        });
    }

 

 

비밀번호를 변경하는 경우, 먼저 자신의 정보를 수정하고자 하는 유저가 누구인지를 db에서 찾아내는 것이 먼저이다. 업데이트는 그 후에 할 수 있다.

 

else {
    // 비밀번호를 변경하는 경우
        User.findOne({ email: req.body.email }, (err, user) => {
        if (!user) {
            return res.json({
                loginSuccess: false,
                message: "정보를 찾지 못했습니다."
            });}

 

만일 유저가 존재한다면 프론트에서 보내준 정보를 findOne이 반환한 user에 셋팅해준다. 

 

else if (user) {

                // client에서 받아온 정보로 변경할 정보 set
                user.name=req.body.name
                user.nikname=req.body.nikname
                user.password=req.body.password

 

이제 해당 정보로 save()를 불러주면 되는데, save()는 몽구스 스키마를 필요로 하기 때문에 new User를 사용하여 새로운 몽구스 모델에 우리가 설정한 user정보를 전달해 만든 뒤 save()를 불러준다.

 

                // save 호출 위해 User set
                const userUpdate = new User(user)

                // Update 위해 save 호출
                // ========= User.js => userSchema.pre('save', function( next ) { bcrypt Password } =========

                userUpdate.save((err, userInfo) => {
                if(err) return res.json({success:false, err})
                return res.status(200).json({
                    success:true, save:'저장에 성공하였습니다.',userInfo:userInfo
                })
            })} 
        })
    }
})

 

 

즉 /update api의 전체 코드는 아래와 같아진다.

 

router.post('/update', function(req,res){

    // 비밀번호를 변경하지 않는 경우
    if(req.body.password === '') {
        User.findOneAndUpdate({ email:req.body.email }, {nikname:req.body.nikname, name:req.body.name}, (err, doc) => {
            if (err) return res.json({ success: false, err });
            return res.status(200).send({
                success: true, noPasswordChange:true
            });
        });
    } else {
    // 비밀번호를 변경하는 경우
        User.findOne({ email: req.body.email }, (err, user) => {
        if (!user) {
            return res.json({
                loginSuccess: false,
                message: "정보를 찾지 못했습니다."
            });} else if (user) {

                // client에서 받아온 정보로 변경할 정보 set
                user.name=req.body.name
                user.nikname=req.body.nikname
                user.password=req.body.password
                
                // save 호출 위해 User set
                const userUpdate = new User(user)

                // Update 위해 save 호출
                // ========= User.js => userSchema.pre('save', function( next ) { bcrypt Password } =========

                userUpdate.save((err, userInfo) => {
                if(err) return res.json({success:false, err})
                return res.status(200).json({
                    success:true, save:'저장에 성공하였습니다.',userInfo:userInfo
                })
            })} 
        })
    }
})

 

 

이름 란에는 수정된이름, 닉네임 란에는 수정된찰리, 비밀번호에는 1234를 넣어 이름과 닉네임을 수정해보았다.

 

변경 셋팅

 

유저 정보 변경 저장이 실행된 콘솔창

 

결과

 

유저 정보가 잘 수정되었고, redux의 정보도 제대로 세팅되었다. 끝~ 

댓글