내가 맡았던 부분 중 기록하고 싶은 것들을 기록해놓으려 한다.
내일배움캠프 Node.js 심화주차 팀프로젝트입니다.
😺 https://github.com/choisooyoung-dev/this-one-please
팀명 | 2거주세요 |
---|---|
팀장 | 최수영 |
팀원 | 강다형, 고병옥, 신정선, 최연식 |
나는 처음으로 Redis, refreshtoken 부분을 구현해보았다.
로그인 전
로그인 후
// 레디스 연결 redis.util.js
import redis from 'redis';
import dotenv from 'dotenv';
dotenv.config();
const redisClient = redis.createClient({
host: process.env.REDIS_HOST, // 여기에 원하는 호스트를 설정하세요
port: process.env.REDIS_PORT,
password: process.env.REDIS_PW,
username: default
});
redisClient.on('connect', () => console.log('Connected to Redis!'));
redisClient.on('error', (err) => console.log('Redis Client Error', err));
redisClient.connect();
// auth controller
import redisClient from '../../../auth-utils/redis.util.js';
import { AuthService } from './auth.service.js';
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';
import { loginSchemaValidation } from '../../middlewares/validation.middleware.js';
dotenv.config();
export class AuthController {
authService = new AuthService();
login = async (req, res, next) => {
try {
const { email, password } = await loginSchemaValidation.validateAsync(req.body);
const user = await this.authService.login(email, password);
if (!user) {
return res.status(400).json({ success: false, error: '가입되어있지 않은 계정입니다. 회원가입 해주세요.' });
}
const user_id = user.id;
if (email !== user.email) {
return res.status(400).json({ success: false, error: '가입되어있지 않은 계정입니다. 회원가입 해주세요.' });
}
if (password !== user.password) {
return res.status(401).json({ success: false, error: '비밀번호가 일치하지 않습니다.' });
}
const accessToken = jwt.sign({ user_id }, process.env.ACC_TOKEN_KEY, {
expiresIn: process.env.ACCESS_EXP_IN,
});
const refreshToken = jwt.sign({ user_id }, process.env.REF_TOKEN_KEY, {
expiresIn: process.env.REFRESH_EXP_IN,
});
// Accesstoken 쿠키 저장
res.cookie('accessToken', accessToken);
// Refreshtoken redis 저장 (key, value)
redisClient.set(refreshToken, user_id);
res.cookie('refreshToken', refreshToken);
return res.status(200).json({
success: true,
message: '로그인 되었습니다.',
data: { accessToken, refreshToken },
});
} catch (error) {
console.log(error);
next(error);
}
};
logout = async (req, res, next) => {
try {
// AccessToken 및 RefreshToken 변수 선언
const accessToken = req.cookies.accessToken;
const refreshToken = req.cookies.refreshToken;
res.clearCookie('accessToken');
res.clearCookie('refreshToken');
// Redis에서 키 삭제
redisClient.del(refreshToken);
redisClient.del(accessToken);
return res.status(200).json({
success: true,
message: '로그아웃 성공',
});
} catch (err) {
next(err);
}
};
}
이메일 인증 기능
빈값 체크
프론트에서 값을 넘겨줘야 하기 때문에 fetch를 썼다.
각 버튼마다 라우터로 가서 해야할 일을 하고 해당 페이지로 돌아오게끔 설계하였다.
...
// 메일 보내주는 로직
const sendEmail = async () => {
const email = document.getElementById('email').value;
try {
const response = await fetch('/api/users/signup/send-mail', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});
const data = await response.json();
// console.log(data);
if (data.error) {
alert(data.error);
window.location.href = '/login';
}
if (data.message) {
alert(data.message);
}
} catch (error) {
console.log('Error!', error);
}
};
// 메일 인증 로직
const authEmail = async () => {
const authEmailNum = document.getElementById('email-verification').value;
try {
const response = await fetch('/api/users/signup/verify-mail', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ authEmailNum }),
});
const data = await response.json();
console.log(data);
if (data.error) {
alert(data.error);
} else if (data.message) {
signupBtn.removeAttribute('disabled');
signupBtn.classList.add('hover:bg-blue-700');
emailBtn.setAttribute('disabled', '');
authMailBtn.setAttribute('disabled', '');
// console.log('signupBtn: ', signupBtn);
alert(data.message);
} else {
alert('인증 번호 확인에 실패했습니다.');
}
} catch (error) {
console.log('에러 발생', error);
}
};
// 회원가입 로직
const signup = async () => {
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('password-confirm').value;
const name = document.getElementById('nickname').value;
const userType = document.querySelector('input[name="userType"]:checked').value;
const address = document.getElementById('address').value;
let type = 0;
try {
if (userType === 'business') {
type = 1;
}
const response = await fetch('/api/users/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
password,
confirmPassword,
name,
type,
address,
}),
});
const data = await response.json();
console.log(data);
if (data.error) {
alert(data.error);
} else if (data.message) {
alert(data.message);
window.location.href = '/';
}
} catch (error) {
console.log('에러 발생', error);
}
};
export class UsersController {
usersService = new UsersService();
// 회원 가입 시 인증 이메일 보내기
sendEmail = async (req, res, next) => {
const { email } = req.body;
const userEmail = await this.usersService.getUserEmail(email);
console.log('userEmail: ', userEmail);
if (userEmail) {
return res.status(400).json({
success: false,
error: '이미 등록되어있는 이메일 입니다. 로그인 해주세요',
});
}
try {
const transporter = nodemailer.createTransport({
service: 'gmail', // gmail 사용
auth: {
user: process.env.MAILS_EMAIL, // env 파일 내 보내는 사람의 메일 주소
pass: process.env.MAILS_PWD, // env 파일 내 생성된 앱 비밀번호 16자리
},
});
// 랜덤 인증번호 생성 함수
const randomStrFunc = (num) => {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
let result = '';
const randomMaxLength = characters.length;
for (let i = 0; i < num; i++) {
result += characters.charAt(Math.floor(Math.random() * randomMaxLength));
}
return result;
};
let randomStr = randomStrFunc(10);
redisClient.set(randomStr, randomStr);
if (!email) {
return res.status(400).json({ success: false, error: '이메일을 입력해주세요.' });
}
async function main() {
await transporter.sendMail({
from: process.env.MAILS_EMAIL, // env 파일 내 보내는 사람의 메일 주소
to: email, // 받는 사람
subject: '👋 2거주세요 가입 인증번호입니다.', // 제목
text: `인증번호는 ${randomStr} 입니다. 3분 내로 입력해주세요.`, // 메일 내용
// html: "<b>Hello world?</b>", // html 보내줄 수도 있음
});
await redisClient.expire(randomStr, 180);
}
await main();
res.status(200).json({
success: true,
message: '입력해주신 이메일 주소로 전송되었습니다. 확인해주세요.',
});
res.redirect('/');
} catch (error) {
console.log(error);
next(error);
}
};
// 회원 가입 시 인증 번호 검증
verifyEmail = async (req, res, next) => {
try {
const { authEmailNum } = req.body;
const redisAuthEmailNum = await redisClient.get(authEmailNum);
if (!redisAuthEmailNum) {
res.status(400).json({
success: false,
error: '일치하지 않은 인증번호 입니다. 다시 입력해주세요.',
});
} else {
redisClient.del(redisAuthEmailNum);
res.status(200).json({
success: true,
message: '인증되었습니다. 가입을 진행해주세요.',
});
}
res.redirect('/');
} catch (error) {
console.log(error);
next(error);
}
};
// 회원 가입
signup = async (req, res, next) => {
try {
const { email, password, name, type, address, confirmPassword } = req.body;
// 회원가입 입력 폼에서 받아 온 데이터들로 변경
// const { email, name, type, address, password, confirmPassword } =
// res.data;
if (password !== confirmPassword) {
return res.status(400).json({
success: false,
error: '비밀번호와 확인 비밀번호가 일치하지 않습니다. 다시 적어주세요.',
});
} else {
await this.usersService.signup(email, password, name, type, address);
res.status(201).json({
success: true,
data: { email, password, name, type, address },
message: '회원가입이 되었습니다. 로그인 해주세요.',
});
}
} catch (error) {
next(error);
}
};
...
const container = document.getElementById(`store-container`);
// url 파라미터를 가져와준다.
const urlParams = new URLSearchParams(window.location.search);
const searchInputValue = urlParams.get('searchInputValue');
document.addEventListener('DOMContentLoaded', (e) => {
e.preventDefault();
searchStore();
});
const searchStore = async () => {
try {
const response = await fetch('/api/stores', {
method: 'GET',
});
const data = await response.json();
const stores = data.data;
const findStoresNameArr = [];
const matchingStores = stores.filter((store) => store.name.includes(searchInputValue));
if (matchingStores.length > 0) {
// Display the matching stores
findStoresNameArr.push(...matchingStores);
}
findStoresNameArr.forEach((store) => {
const newItem = document.createElement('a');
newItem.href = `/store/${store.id}`;
container.appendChild(newItem);
...
const image = document.createElement('img');
image.src = `${store.image_url}`;
image.className = 'w-full h-full object-cover';
cdev4.appendChild(image);
...
const storeName = document.createElement('label');
storeName.className = 'block text-sm font-medium text-gray-700';
storeName.innerText = `${store.name}`;
cdev6.appendChild(storeName);
const storeAddress = document.createElement('label');
storeAddress.className = 'mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm';
storeAddress.innerText = `${store.address}`;
cdev6.appendChild(storeAddress);
});
} catch (error) {
console.log('Error!', error);
}
};
보고있나 헤뻐치 ㅋ
이번 프로젝트 23년 12월 12일 ~ 23년 12월 18일 간 진행하는거였는데 정말 촉박했지만, 피드백은 좋았던 편(팀원들을 정말 잘 만났다..)
팀장을 맡긴 했지만 1인분을 못한 느낌이니 다음번엔 2인분 하도록 노력해야지 냅다 달려~
같이 노력해준 팀원분들에게 감사하다 ㅠㅠ
너무잘봤어요!
코드는 제가 이해를 못하겠지만 언젠가 공부하다가
문득 이글을 다시 보러왔을때 이해할 수 있는 제가됐으면 좋겠어요
함께해서 너무 고맙고 감사했습니다