Projects/Qwerty - 배달의민족 (team)

백오피스 프로젝트4. 회원가입 시 이메일인증, 회원가입 CRUD, 백엔드 API 기능구현

sangwoo_rhie 2023. 7. 24. 17:58

고객님 이메일 인증 회원가입 및 로그인 시연영상

 

 

사장님 프론트엔드-백엔드 연결 시연영상


고객님 파트 회원가입 이메일 인증 및 고객 CRUD 코드

 

// routes>users.routes.js

const express = require('express');
const bcrypt = require('bcrypt');
const nodemailer = require('nodemailer');
const router = express.Router();

// Middleware
const authMiddleware = require('../middlewares/cusAuthMiddleware.js');

// JWT
const jwt = require('jsonwebtoken');
// Model

const { Users, Menus, Stores } = require('../models/index.js');

const { Op } = require('sequelize');

// 회원가입 API (POST)
router.post('/user/signup', async (req, res) => {
  console.log('req.body =>', req.body);
  const { email, verifyNumberInput, password, passwordConfirm } = req.body;

  try {
    const existUserEmail = await Users.findOne({ where: { email } });
    const passwordCheck = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).*$/;
    const hashedPassword = await bcrypt.hash(password, 10);
    console.log('hashedPassword =>', hashedPassword);

    if (!email || !verifyNumberInput || !password || !passwordConfirm) {
      return res.status(400).json({ message: '입력값이 유효하지 않습니다.' });
    }
  
    // Session에서 verifyNumber 조회
    const verifyNumber = req.session.verifyNumber;
    console.log('verfifyNumber of Session =>', verifyNumber);
    if (verifyNumberInput != verifyNumber) {
      return res.status(412).json({ message: '인증번호가 일치하지 않습니다.' });
    }
    if (existUserEmail) {
      return res.status(412).json({ message: '중복된 email입니다.' });
    }
    if (!password || password.length < 4 || !passwordCheck.test(password)) {
      return res.status(412).json({ message: '비밀번호 형식이 올바르지 않습니다.' });
    }
    if (password !== passwordConfirm) {
      return res.status(412).json({ message: '비밀번호가 일치하지 않습니다.' });
    }

    await Users.create({ email: email, password: hashedPassword });
    // 사용한 verifyNumber 삭제
    delete req.session.verifyNumber;
    return res.status(201).json({ message: '회원 가입에 성공하였습니다.' });
  } catch {
    return res.status(400).json({ message: '사용자 계정 생성에 실패하였습니다.' });
  }
});

// e-mail 인증 API (POST)
router.post('/user/signup/email', async (req, res) => {
  console.log(req.body);
  const { email } = req.body;
  console.log(email);
  try {
    const transporter = nodemailer.createTransport({
      service: 'gmail',
      port: 587,
      host: 'smtp.gmail.com',
      secure: false,
      requireTLS: true,
      auth: {
        user: 'electruc0095@gmail.com',
        pass: 'yfjlkxwnfxkjxmed',
      },
    });

    const min = 100000;
    const max = 999999;
    const verifyNumber = Math.floor(Math.random() * (max - min + 1)) + min;

    // Session에 verifyNumber 저장
    req.session.verifyNumber = verifyNumber;

    transporter.sendMail({
      from: '쿼티의 민족',
      to: email,
      subject: '[쿼티의 민족] 반갑습니다! 인증번호를 보내드립니다.',
      text: `우측의 6자리 인증번호를 '인증번호 입력란'에 입력해주세요! => ${verifyNumber}`,
    });
    return res.status(200).json({ message: '전송 성공' });
  } catch {
    return res.status(400).json({ message: '전송 실패' });
  }
});

// log-in API (POST)
router.post('/login', async (req, res) => {
  const { email, password } = req.body;
  const existUser = await Users.findOne({ where: { email } });

  try {
    if (!req.body) {
      return res.status(404).json({ message: '입력값이 존재하지 않습니다.' });
    }

    if (!existUser) {
      //!passwordMatch
      return res.status(412).json({ message: 'email 또는 비밀번호를 확인해주세요.' });
    }

    // JWT 생성
    const token = jwt.sign({ userId: existUser.userId }, 'customized-secret-key');

    // Cookie 발급
    res.cookie('authorization', `Bearer ${token}`);
    return res.status(200).json({ token: token });
  } catch (error) {
    console.log(error);
    return res.status(400).json({ message: 'log-in에 실패하였습니다.' });
  }
});

// log-out API (POST)
router.post('/logout', authMiddleware, async (req, res) => {
  try {
    res.clearCookie('authorization');
    return res.status(200).json({ message: 'log-out 되었습니다.' });
  } catch {
    return res.status(400).json({ message: 'log-out에 실패하였습니다.' });
  }
});

// 음식점 조회 API (GET)
router.get('/user/stores', async (req, res) => {
  try {
    const stores = await Stores.findAll({
      attributes: ['storeId', 'storeImage', 'storeName', 'totalRating'],
      order: [['totalRating', 'DESC']],
    });
    return res.status(200).json(stores);
  } catch {
    return res.status(400).json({ message: '음식점 조회에 실패하였습니다.' });
  }
});

// 고객 메뉴조회 API (GET)
router.get('/user/:storeId/getMenuAll', async (req, res) => {
  const { storeId } = req.params;
  try {
    const menus = await Menus.findAll({
      attributes: ['menuId', 'menuName', 'menuImage', 'price'],
      order: [['menuId', 'DESC']],
      where: { storeId: storeId },
    });
    res.status(200).json(menus);
  } catch (error) {
    console.log(error);
    res.status(400).json({ message: '요청을 정상적으로 받아들이지 못했습니다.' });
  }
});

// e-mail 인증 API (POST)
router.post('/user/signup/email', async (req, res) => {
  console.log(req.body);
  const { email } = req.body;
  console.log(email);
  try {
    const transporter = nodemailer.createTransport({
      service: 'gmail',
      port: 587,
      host: 'smtp.gmail.com',
      secure: false,
      requireTLS: true,
      auth: {
        user: 'electruc0095@gmail.com',
        pass: 'yfjlkxwnfxkjxmed',
      },
    });

    const min = 100000;
    const max = 999999;
    const verifyNumber = Math.floor(Math.random() * (max - min + 1)) + min;

    // Session에 verifyNumber 저장
    req.session.verifyNumber = verifyNumber;

    transporter.sendMail({
      from: '쿼티의 민족',
      to: email,
      subject: '[쿼티의 민족] 반갑습니다! 인증번호를 보내드립니다.',
      text: `우측의 6자리 인증번호를 '인증번호 입력란'에 입력해주세요! => ${verifyNumber}`,
    });
    return res.status(200).json({ message: '전송 성공' });
  } catch {
    return res.status(400).json({ message: '전송 실패' });
  }
});

// 사용자 정보 조회 API (GET)
router.get('/users/:userId', authMiddleware, async (req, res) => {
  const { userId } = res.locals.user;

  try {
    const user = await Users.findOne({
      attributes: ['userId', 'email', 'point', 'createdAt', 'updatedAt'],
      where: { userId },
    });
    return res.status(200).json({ data: user });
  } catch {
    return res.status(400).json({ message: '사용자 정보 조회에 실패하였습니다.' });
  }
});

// 사용자 정보 수정 API (PUT)
router.put('/users/:userId', authMiddleware, async (req, res) => {
  const { userId } = res.locals.user;
  const { password, newPassword, newPasswordConfirm } = req.body;

  try {
    const existUser = await Users.findOne({ where: { userId } });
    const passwordCheck = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).*$/;
    const passwordMatch = await bcrypt.compare(password, existUser.password);
    const hashedNewPassword = await bcrypt.hash(newPassword, 10);

    if (!password) {
      return res.status(400).json({ message: '입력값이 유효하지 않습니다.' });
    }
    if (!passwordMatch) {
      return res.status(400).json({ message: '비밀번호가 일치하지 않습니다.' });
    }
    if (newPassword !== newPasswordConfirm) {
      return res.status(412).json({ message: '변경된 비밀번호가 일치하지 않습니다.' });
    }
    if (!newPassword || newPassword.length < 4 || !passwordCheck.test(newPassword)) {
      return res.status(412).json({ message: '변경된 비밀번호 형식이 올바르지 않습니다.' });
    }

    await Users.update({ password: hashedNewPassword }, { where: { userId } });
    return res.status(200).json({ message: '사용자 정보 수정에 성공하였습니다.' });
  } catch {
    return res.status(400).json({ message: '사용자 정보 수정에 실패하였습니다.' });
  }
});

// 회원탈퇴 API (DELETE)
router.delete('/users/:userId', authMiddleware, async (req, res) => {
  const { userId } = res.locals.user;
  const { email, password } = req.body;

  try {
    const existUser = await Users.findOne({ where: { userId } });
    const passwordMatch = await bcrypt.compare(password, existUser.password);

    if (!email || !password) {
      return res.status(400).json({ message: '입력값이 유효하지 않습니다.' });
    }
    if (email !== existUser.email || !passwordMatch) {
      return res.status(412).json({ message: 'email 또는 비밀번호를 확인해주세요.' });
    }

    await Users.destroy({
      where: { [Op.and]: [{ userId }, { email: existUser.email }] },
    });
    return res.status(200).json({ message: '사용자 정보 삭제에 성공하였습니다.' });
  } catch {
    return res.status(400).json({ message: '사용자 정보 조회에 실패하였습니다.' });
  }
});

module.exports = router;

 


백엔드에서 구현한 기능들 (고객님 파트)

 

No. / 기능명/ API URL / Method / request / response 

/// userRoute.js

1.
회원 가입 API
API URL: '/user/signup'
Method: post
Request: {"email":"", "verifyNumberInput":"", "password":"", "passwordConfirm":""}
Response: {"message":'회원 가입에 성공하였습니다.}

2. 
email 인증키 발송 API
API URL: '/user/signup/email'
Method: post
Request: {"email":""} 
Response: {"message':"전송 성공"}

3.
고객 로그인 API
API URL: '/login'
Method: post
Request: {"email":"", "password":""}
Response: {"message':"log-in 되었습니다."}

4. 
고객 로그아웃 API
API URL: '/logout'
Method: post
Request: (request 없음)
Response: {"message":"log-out 되었습니다."}

5.
음식점 조회 APi
API URL: "/user/stores"
Method: get
Request: (request 없음)
Response: {"storeId":"",'storeImage':"", 'storeName':"", 'totalRating':""}

6.
고객 메뉴 조회 API
API URL: '/user/:storeId/getMenuAll'
Method: get
Request: (request 없음)
Response: {'menuId':"", 'menuName':"", 'menuImage':"", 'price':""}

7. 
사용자 정보 조회 API
API URL: '/users/:userId'
Method: get
Request: (request 없음)
Response: {'userId':"", 'email':"", 'point':"", 'createdAt':"", 'updatedAt':""}

8. 
사용자 정보 수정 APi
API URL: '/users/:userId'
Method: put
Request: { "password":"", "newPassword":"", "newPasswordConfirm":"" }
Response: {"message':"사용자 정보 수정에 성공하였습니다."}

9.
회원탈퇴 API
API URL: '/users/:userId'
Method: delete
Request: { "email":"", "password":"" }
Response: {"message':"사용자 정보 삭제에 성공하였습니다."}

// userReviewRoute.js
1.
리뷰 작성 APi
API URL: '/user/store/:storeId/review'
Method: post
Request: {"rating":"","content":""}
Response: {"message":"리뷰 작성에 성공하였습니다."}
=> 고객님 로그인 후 사용가능

2.
리뷰 목록 조회 API
API URL: '/user/store/:storeId/review'
Method: post
Request: (request 없음)
Response: {'reviewId':'', 'storeId':'', 'userId':'', 'rating':'', 'content':'', 'createdAt', 'updatedAt':''}

3. 
리뷰 수정 API
API URL: '/user/store/:storeId/review/:reviewId'
Method: put
Request: {"reating":"","content":""}
Response: {"data":'리뷰 수정에 성공하였습니다.}

4. 
리뷰 삭제 API
API URL: '/user/store/:storeId/review/:reviewId'
Method: delete
Request: (request 없음)
Response: {"data":'리뷰 삭제에 성공하였습니다.}



// ordersRoute.js
1.
고객 주문메뉴 생성 API
API URL: '/user/store/:storeId/order/menu/:menuId'
Method: POST
Request: {"quantity"}
Response: {"message":'성공'} // 실패시 {"message":'실패'}
=> 고객님 로그인 후 사용가능

2
고객 주문 생성 API
API URL : '/user/store/:storeId/order'
Method: POST
Request: {"address"}
Response: {"message":'주문 완료!'}
=> 고객님 로그인 후, 고객 주문메뉴 생성 이후 사용가능

3.
고객이 배달을 받았는지 여부 API
API URL: '/user/order/:orderId/delivery'
Method: POST
Request 없음
Response: { message: '배달 완료 처리되었습니다!'}
=> 고객님 로그인 후, 고객 주문메뉴 생성 이후 사용가능

4. 
고객 주문조회 API
API URL: '/user/order/:orderId'
Method: GET
Request 없음
Response: { data: order} 
=> 고객님 로그인 후, 고객 주문메뉴 생성 이후 사용가능

//여기부터는 사장님 파트입니다.

5. 
사장님 주문 조회 API
API URL: '/ceo/:storeId/order'
Method: GET
Request 없음
Response: { data: order} 
=> 사장님 로그인 후, 고객이 주문 생성했을때 조회 가능. (해당 storeId의 주문 목록조회)

6.
사장님 주문 상세 조회
API URL: '/ceo/:storeId/order/:orderId'
Method: GET
Request 없음
Response: { data: orderMenus} 
=> 사장님 로그인 후, 고객이 주문 생성했을때 조회 가능. (해당 orderId의 주문 상세조회)

7.
사장님 주문 취소 API 
API URL: '/ceo/:storeId/order/:orderId'
Method: DELETE
Request 없음
Response: { data: '사장님 주문 취소 완료'} 
=> 사장님 로그인 후, 고객이 주문 생성했을때 삭제 가능. (해당 orderId에 대한 삭제제)