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에 대한 삭제제)