이번 프로젝트를 진행하면서 총 8개의 API를 생성하였고 각 API에 대한 로직에 대해 설명을 하겠다.
const crypto = require("crypto");
// 솔트 생성
const createSalt = () => {
return new Promise((resolve, reject) => {
crypto.randomBytes(64, (err, buf) => {
if (err) reject(err);
resolve(buf.toString("base64"));
});
});
};
// 암호비번 생성
const createCryptoPassword = async (plainPassword) => {
console.log(">>>>>>>>>>", plainPassword);
const salt = await createSalt();
return new Promise((resolve, reject) => {
crypto.pbkdf2(plainPassword, salt, 10000, 64, "sha512", (err, key) => {
if (err) {
console.log("createCryptoPassword 에서 에러 발생");
reject(err);
}
resolve({ password: key.toString("base64"), salt });
});
});
};
// 비밀번호 검증
const getCryptoPassword = (plainPassword, salt) => {
return new Promise((resolve, reject) => {
crypto.pbkdf2(plainPassword, salt, 10000, 64, "sha512", (err, key) => {
if (err) {
console.log("getCryptoPassword 에서 에러 발생");
reject(err);
}
resolve({ password: key.toString("base64"), salt });
});
});
};
module.exports = {createSalt, createCryptoPassword, getCryptoPassword}
사용자가 전달한 암호는 createCryptoPassword
에서 createSalt
로 생성한 Salt값
을 활용하여 같이 sha-512 해쉬 알고리즘
으로 해쉬화
가 된다.
getCryptoPassword
는 사용자가 입력한 암호와 DB에 저장되어 있는 Salt값을 이용하여 위와 동일하게 해쉬화 한다.
1) /signup : 회원가입
exports.post_signup = async (req, res) => {
let cryptoPassword = await createCryptoPassword(req.body.password);
try{
const result = await models.USER.findOrCreate({
where : {
email: req.body.email,
},
defaults : {
name: req.body.name,
email: req.body.email,
password: cryptoPassword.password,
salt: cryptoPassword.salt,
gender: req.body.gender,
telephone: req.body.telephone,
}
})
if(result.at(-1) === false){
console.log('fail')
res.send('fail')
}
else{
console.log('suc')
res.send('success')
}
}
catch(error){
res.status(500).send("Error");
}
};
createCryptoPassword
로 해쉬화 되고 입력한 이메일이 DB에 존재할시
Front-End 측에 'fail'
을 send하게 되고 존재 하지 않으면 받은 정보와 해쉬화 된 암호 및 Salt
를 DB에 저장한다. 2) /signup/checkemail : 이메일 중복 체크
exports.checkEmail = async (req, res) => { let inputEmail = req.body.email try{ const result = await models.USER.findOne({ where : { email : inputEmail } }) if(result !== null){ // 중복 res.send(false) } else{ // 중복x res.send(true) } } catch{ res.status(500).send("Error"); } };
결과
findOne
의 결과값은 존재할시 값을 반환하고 없을시null
을 반환한다.
즉,result
의 값이null
이 아니면 중복되는 이메일이 존재한다.
Front-End 측에서 전달받은 사용자가 입력한 이메일이 DB에 존재하면중복
이라는 의미이기 때문에, Front-End측에 메시지를 send한다.
3) /login : 로그인
exports.post_login = async (req, res) => { try{ // 입력받은 아이디를 가진 사람을 찾아 salt와 입력한 비밀번호를 조합하며 저장된 비번과 같은지 확인 const result = await models.USER.findOne({ where:{ email: req.body.email, } }); const getCry = await getCryptoPassword(req.body.password, result.salt); if(getCry.password === result.password) { res.send({msg: 'success', userid : result.id, email : result.email}); // 성공메시지와 유저아디 반환 } else { // 로그인 틀림 res.send("fail"); } } catch{ res.status(500).send("Error"); } };
결과
- Front-End 측에서 전달받은 이메일과 같은 유저를 DB에서 찾는다.
그 다음 전달받은 암호 평문과 찾은 유저가 가진 Salt값을 이용하여getCryptoPassword
함수를 실행하여 반환값이 DB에 저장된 해쉬화된 암호와 일치할 시 옳바른 유저이기 때문에 Front-End측으로 성공을 send 한다.
4) /room/make/:userid : 방 생성
exports.room = async (req, res) => { const userId = req.params.userid // 가입한 사람 id나 kakaoId try{ const result = await models.USER.findOne({ // params랑 user의 id가 같은 사람 찾기 where : { id : userId } }) if(result !== null){ // params랑 user의 id가 같은 사람 발견 await models.ROOM.create({ id : userId, situation : req.body.situation, accent : req.body.accent, language : req.body.language, }) } else{ // 다르면 카카오 await models.ROOM.create({ kakaoId : userId, situation : req.body.situation, accent : req.body.accent, language : req.body.language, }) } res.send('success') } catch{ res.status(500).send("Error"); } };
결과
- Front-End에서 전달받은
params
를 먼저user 테이블
의ID
와 비교를 하고 일치하는 값이 있으면ROOM 테이블
의id
에 저장되고,user 테이블
에서ID
가 존재하지 않으면Kakao 테이블
에 저장된kakaoId
와 비교하여Room 테이블
의kakaoId
에 저장되며 Front-End 측에서 전달받은언어,상황,말투
로 방이 생성된다.
5) /kakao : OAuth로 받아온 kakao 유저 정보 저장
exports.kakao2 = async (req, res) => { try { const result = await models.KAKAO.findOrCreate({ where : { kakaoId : req.body.id.toString() }, defaults : { kakaoId : req.body.id.toString(), kakao_nickname : req.body.nickname } }) if(result.at(-1) === false){ // 중복되면 res.send('fail') } else{ // 중복 안되면 res.send('success') } } catch (error) { res.status(500).send("Error"); } };
id
를 KAKAO 테이블
에서 찾는다.findOrCreate
는 중복 시 생성에 실패하기 때문에 반환값의 끝값이 False가 된다. 따라서 .at()
메소드를 사용하여 끝값을 뽑아내어 중복여부를 체크하여 Front측에 send 해 주었다.6) /chat/:userid/:roomid : GPT와 대화
exports.runGPT35 = async (req, res) => { // MSG 정보 가져오기 const userid = req.params.userid; const roomid = req.params.roomid; const result = await models.MSG.findAll({ raw: true, where: { room_id: roomid, user_id: userid, }, }); if (result.length > 0) { // MSG 테이블이 비어있지 않다면 let newMsg = []; for (let i = 0; i < result.length; i++) { newMsg.push({ role: result[i].part_id, content: result[i].content }); } // 과거내역 불러오기 const response = await openai.createChatCompletion({ model: "gpt-3.5-turbo", messages: [...newMsg, { role: "user", content: req.body.msg }], }); await models.MSG.create({ part_id: "user", content: req.body.msg, room_id: roomid, user_id: userid, }); await models.MSG.create({ part_id: response.data.choices[0].message.role, content: response.data.choices[0].message.content, room_id: roomid, user_id: userid, }); res.send(response.data.choices[0].message.content); // 답변 반환 } else { // MSG 테이블이 비었다면 ROOM에 저장된 세팅 값으로 gpt 세팅 const settings = await models.ROOM.findOne({ raw: true, where: { room_id: roomid, }, }); const situation = settings.situation; const accent = settings.accent; const language = settings.language; const msg = `Let's play a role play. you can play any role in ${situation}. but you must use ${language} and please speak with ${accent} accent.`; const response = await openai.createChatCompletion({ model: "gpt-3.5-turbo", messages: [ { role: "system", content: msg }, { role: "user", content: req.body.msg }, ], }); // DB 추가 await models.MSG.create({ part_id: "system", content: msg, room_id: roomid, user_id: userid, }); await models.MSG.create({ part_id: "user", content: req.body.msg, room_id: roomid, user_id: userid, }); await models.MSG.create({ part_id: response.data.choices[0].message.role, content: response.data.choices[0].message.content, room_id: roomid, user_id: userid, }); res.send(response.data.choices[0].message.content); // 답변 반환 } };
결과
Front-End 측에서 전달받은 userId
와 roomID
로 MSG 테이블
을 조회한다.
MSG 테이블
에 해당하는 값 존재O: newMSG
라는 빈 배열에 해당하는 값들을 전부 넣어 GPT가 과거 무슨 대화를 나누었는지 알게 하고 전달받은 msg를 gpt에게 물어 답변과 msg
를 MSG 테이블
에 저장한다.
MSG 테이블
에 해당하는 값 존재X: Room 테이블
에서 해당하는 RoomID
를 찾아 Room
에 저장된 언어, 상황, 말투
를 반환받아 gpt의 기본 prompt를 구성한다.
그렇게 세팅된 초기값으로 전달받은 msg
를 gpt에게 전달하여 답변을 받고 MSG 테이블
에 저장한다.
1) /room/all/:userid : 한 유저가 가진 모든 방 번호 가져오기
exports.allRoom = async(req,res)=>{ const userId = req.params.userid try{ const result = await models.ROOM.findAll({ raw :true, where : { [Op.or] : [{id : userId},{kakaoId : userId}] } }) let roomIds = [] for(let i=0; i<result.length; i++){ roomIds.push(result[i].room_id) } res.send(roomIds) } catch{ res.status(500).send("Error"); } }
결과
params
로 ROOM 테이블
의 id와 kakaoId
를 비교하여 둘 중 하나와 일치하면 roomIds
라는 빈 배열에 해당 결과값을 담아 Front측으로 전달한다.2) /msg/:userid/:roomid : 한 유저가 가진 어떤 방에 대한 과거 대화내역 받아오기
exports.msg = async (req, res) => { const userid = req.params.userid; const roomid = req.params.roomid; const result = await models.MSG.findAll({ raw: true, where: { room_id: roomid, user_id: userid, }, }); let newMsg = []; for (let i = 0; i < result.length; i++) { const { msg_id, room_id, user_id, ...others } = result[i]; newMsg.push(others); } res.send(newMsg); };
결과
user_id
와 room_id
를 비교하여 둘 다 일치하는 해당 결과값을 result
로 받아 newMSG에 객체 분해 할당하여 필요한 msg(part_id, content)
인 ...others
를 받아 Front측에 전달한다.