aws 무료 기간이 끝난 김에 정리를,,,,,,해볼까,,,,,함~~~~
책 리뷰를 하는 사이트!!!!!!를 만들고 싶었고 책을 검색하면 국립중앙도서관에서 자동으로 책과 표지 이미지를 가지고 오는!!!!!!! 기능을 꼭 구현하고 싶었음,,
로그인 / 회원가입
회원가입/로그인 부분은 노드 책부분을 많이 참고해서 딱히 추가로 구현한 부분이 없음. 다만 aws로 배포해서 url이 바뀐 만큼 카카오 사이트 도메인과 redirect URL을 추가해줘야 작동함.
이렇게 로그인을 하면 프로필과 포스트 작성 페이지가 보이게 됨.
글 작성
책 데이터 가져오기
국립 중앙 도서관 사이트의 책들을 가져오기 위해 국립 중앙 도서관 사이트에 나와 있는 OPEN API를 활용했음.
https://www.nl.go.kr/NL/contents/N31101010000.do
국립중앙도서관
국립중앙도서관에 오신 것을 환영합니다
www.nl.go.kr
사이트에 너무 잘 나와있음!!!! 순서대로 인증키를 발급하면 JSON 데이터로 자유롭게 검색하고 갖다 쓸 수 있음. 사이트에 들어가서 OPEN API 활용 방법에 가이드 파일을 다운 받으면 요청 변수와 출력 결과 필드를 자세하게 볼 수 있음.
나는 여기서 title_info 제목, author_info 작가 변수와 이미지를 얻기 위한 control_no 변수, 장르 구분을 위한 kdc_code_1s 변수를 가져 오기로 함.
router.post('/search', isLogin, async (req, res, next)=>{
try {
const text= encodeURIComponent(req.body.search_text);
const url= "https://www.nl.go.kr/NL/search/openApi/search.do?key=" + process.env.libraryKey + "&apiType=json&pageNum=1&pageSize=5&category=%EB%8F%84%EC%84%9C&kwd=" + text;
//api type은 json타입으로, 한 쪽, 5개를 가져오고 카테고리는 도서, 검색어는 text를 입력 변수로 줌
const socket= req.app.get('io').of('/post').to(req.sessionID);
//새로고침 없이 데이터를 바로바로 띄우기 위해 소켓 사용함
request.get(url, async (err, res, body) =>{
if(err) {
logger.info(`err => ${err}`);
next(err);
}
else {
if(res.statusCode == 200) {
const jsondata= await body;
await socket.emit('search_books', jsondata);
}
}
});
} catch (err) {
next(err);
}
})
위처럼 가져와서 아래처럼 사용함.
const books_js= JSON.parse(books);
for(let i=0; i<books_js.result.length; i++) {
const item= books_js.result[i];
const title= item.titleInfo.replaceAll(/<[^>]*>/g, "");
const author= item.authorInfo;
const kdc= item.kdcCode1s;
const img_url= await promise_img_url(item.controlNo);
책 표지 이미지 불러오기
책의 표지 이미지를 정말!!!!!!꼭!!!!! 갖고 싶었는데 따로 api 출력 변수에는 찾을 수 없었음,, 국립 중앙 홈페이지에는 있는데 변수에는 없으니,,, 결국 사이트의 표지 이미지들 url을 왕창 보고 아무렇게나 유추를 해봤더니 출력 변수 중에 control_no 변수로 이 url을 만들 수 있을 것 같았음. 다만 정말 어거지로 끼워맞춰 만든 거라 이미지가 나올 때도 있고 안 나올 때도 있어서 이 url에 접속을 해봐서 접속이 되면 이 url을 사용하고 접속되지 않으면 디폴트 이미지 url을 돌려주도록 만들었음.
const return_book_img= async function (url) {
return new Promise(function (resolve, reject) {
request(url, function (error, res, body) {
if (!error && res.statusCode == 200) {
resolve(url);
} else {
resolve(null);
}
});
});
}
router.post('/search/image', isLogin, async (req, res, next)=>{
try {
const { controlno }= req.body;
const year= await controlno.replace( /[^0-9]/g, "").substr(0, 4);
const temp_img_url1= await return_book_img("http://cover.nl.go.kr/kolis/" + year + "/" + controlno + "_thumbnail.jpg");
//만든 url1,,,
const temp_img_url2= await return_book_img("http://cover.nl.go.kr/kolis/" + year + "/" + controlno + "01_thumbnail.jpg");
//만든 url2,,,,,
const imgurl= await (temp_img_url1 || temp_img_url2 || process.env.default_img_url);
//둘 다 null이면 default img url을 줌
return res.json({ code: 200, imgurl });
} catch (err) {
logger.info(err);
return res.json({ code: 500, message:'서버 에러'});
}
})
책 선택하기
책을 선택하면 책이 db에 등록되고 그 책을 가지고 글을 쓸 수 있도록 만들었음.
//db에 등록하는 코드
router.post('/register', isLogin, async (req, res, next) => {
try {
const { title, author, kdc, img }= req.body;
if(!title || !author || !kdc)
return res.json({ code: 404, message: `책의 제목이나 작가, kdc 코드 등 부족한 데이터가 있어서 등록할 수 없습니다`});
const book= await Book.findOrCreate({
where: { title, author },
defaults: { title, author, kdc_code: kdc, title_url: img }
});
return res.json({ code: 200, book: book[0], message: '책이 선택되었습니다' });
//findOrCreate는 Promise<[Book, boolean]>를 리턴하기에 book[0]으로 Book 모델만 줌
} catch(err) {
console.log(err);
return res.json({ code:404, message: '책이 제대로 등록되지 못했습니다'});
}
})
//등록된 책을 post에 나타내는 코드
function selectBook(title, author, kdc, img) {
axios.post(`/book/register`, { title, author, img, kdc }).then((res)=>{
alert(res.data.message);
if(res.data.code==200) {
const book= res.data.book;
document.getElementById('book_title').textContent= book.title;
document.getElementById('book_author').textContent= book.author;
document.getElementById('book_img').src= book.title_url;
document.getElementById('post_book_id').value= book.id;
}
}).catch((err)=>{
console.log(err);
});
}
글을 작성하고 저장하기
router.post('/register', isLogin, async (req, res, next)=>{
try {
const { title, free_text, bookid }= req.body;
if(!title || !free_text || !bookid)
return res.json({ code: 404, message: '선택한 책이 없거나 제목 또는 내용이 비어 있습니다'});
const post= await Post.create({
title,
free_text,
UserId: req.user.id,
BookId: bookid
});
return res.json({ code: 200, message:'글이 등록되었습니다'});
} catch (err) {
console.log(err);
return res.json({ code: 500, message:'등록 중 서버 에러가 발생했습니다'});
}
})
글 검색
책의 분야별 검색
분류는 보통 사용하는 kdc code, 도서관에서 흔히 볼 수 있는 분류기호를 사용했음. checkbox로 값을 체크하고 전달함.
router.post('/search/genre', async (req, res, next)=>{
try {
const { genre }= req.body;
const posts= await Post.findAll({
include: [{ model: Book, where: { kdc_code: [genre] }}, { model: User }],
//[Op.in] : [genre]와 같이 genre 배열에 값이 있으면 참임
order: [[ 'createdAt', 'DESC']]
});
return res.render('main', { posts });
} catch (err) {
console.log(err);
return res.json({ code: 500, message:'검색 중 서버 에러가 발생했습니다'});
}
})
이때 검색을 누르면 창이 뜨는데 뒤에 투명하게 블러된 배경이 뜨길 원했음!!!! 그리고 그 배경을 누르면 창이 닫히는!!!! 그런 배경을 너무 갖고 싶었는데 리액트와 같은 프론트엔드를 안배웠어서 이것도 어거지로,,아는 html css style들만 조합해서 만들어 봄.
document.querySelector('.post_genre_btn').addEventListener('click', function (e) {
const background= document.querySelector('.background_blur');
const genre= document.querySelector('.post_genre');
background.style.display="block";
genre.style.display= "block";
background.addEventListener('click', function (e) {
background.style.display="none";
genre.style.display="none";
})
})
//css 파일
.background_blur{
width: 100%;
height: 100%;
backdrop-filter: blur(4px);
z-index: 4;
position: fixed;
display: none;
top: 0px;
}
.post_genre {
display: none;
text-decoration: none;
height: 460px;
width: 450px;
text-align: left;
background-image: url("./search_final.png");
font-size: 17px;
color: #362706;
background-size: cover;
position: fixed;
left: 0px;
top: 10px;
font-family: 'HaenamFont';
padding: 20px;
z-index: 9;
}
책의 제목 / 작가 / 유저 닉네임 검색
const Op = require('sequelize').Op;
router.post('/search/text', async (req, res, next)=>{
try {
let { search_text }= req.body;
search_text= await search_text.trim().split(" ");
const posts= await Post.findAll({
include: [{ model: Book }, { model: User }],
where: {
[Op.or] : [
{ '$Book.title$': { [Op.substring]: [search_text] } },
{ '$Book.author$': { [Op.substring]: [search_text] } },
{ '$User.nick$': { [Op.substring]: [search_text] } }
]
},
order: [[ 'createdAt', 'DESC']]
});
return res.render('main', { posts });
} catch (err) {
console.log(err);
return res.json({ code: 500, message:'검색 중 서버 에러가 발생했습니다'});
}
})
[Op.or]로 셋 중 하나라도 참이라면 검색되도록 했고 각각 변수들이 Post가 가진 고유한 변수가 아니라 참조한 다른 모델의 변수였기 때문에 '$ $'를 사용해서 검색함. 전체 중 일부라도 동일하다면 검색되도록 Op.substring을 사용했고 작성된 시간 순으로 정렬해줌.
내 글만 보기
router.get('/search/mypost', isLogin, async (req, res, next)=>{
try {
const posts= await Post.findAll({ where: { UserId: req.user.id }, include: [{ model: Book }, { model: User }], order:[[ 'createdAt', 'DESC']]});
return res.render('main', { posts });
} catch (err) {
console.log(err);
return res.json({ code: 500, message:'검색 중 서버 에러가 발생했습니다'});
}
})
프로필
이름 / 비밀번호 수정
router.post('/update/nick', isLogin, async (req, res, next)=>{
try {
const user= await User.findOne({ where: { id: req.user.id }});
if(user) {
await User.update({ nick: req.body.nick }, { where: { id: req.user.id }});
return res.json({ code: 200, message:'잘 변경되었습니다'});
} else {
return res.json({ code: 500, message:'해당되는 유저를 찾지 못했습니다'});
}
} catch (err) {
console.log(err);
next(err);
}
});
router.post('/update/password', isLogin, async (req, res, next)=>{
try {
const match= await bcrypt.compare(req.body.pass_now, req.user.password);
if(match==false)
return res.json({ code: 500, message:'현재 비밀번호와 다릅니다'});
if(req.body.password1!==req.body.password2)
return res.json({ code: 500, message:'비밀번호를 다시 확인해주세요'});
const user= await User.findOne({ where: { id: req.user.id }});
const hash= await bcrypt.hash(req.body.password1, 12);
if(user) {
await User.update({ password: hash }, { where: { id: req.user.id }});
return res.json({ code: 200, message:'잘 변경되었습니다'});
} else {
return res.json({ code: 500, message:'해당되는 유저를 찾지 못했습니다'});
}
} catch (err) {
console.log(err);
next(err);
}
});
글 수정, 삭제
router.post('/update', isLogin, async (req, res, next)=>{
try {
const { title, free_text, id }= req.body;
await Post.update({ title, free_text }, { where: { id }});
return res.json({ code: 200, message:'잘 수정되었습니다' });
} catch (err) {
console.log(err);
return res.json({ code: 500, message:'수정 중 서버 에러가 발생했습니다'});
}
})
router.post('/remove', isLogin, async (req, res, next)=>{
try {
await Post.destroy({ where: { id: req.body.id }});
return res.json({ code: 200, message:'잘 삭제되었습니다' });
} catch (err) {
console.log(err);
return res.json({ code: 500, message:'삭제 중 서버 에러가 발생했습니다'});
}
})
혼자 만들어본 후기
혼자 처음 만든 거라 이상하게 짠 부분도 많고 해서 이건,,,,아무한테도,,,도움되지않아,,,, 인터넷에 데이터로 차지하기 아까워,,, 누가 보면 어떡해,,, 내 코드 막 다른 사람들이 보고 막 쓰러지고 화내면 어떡해,,,, 했다가 방문수 보고 마음 푹 놓고 정리 후닥닥 해서 올림,,,,,
작고 작은 내 플젝,,, 작은 내 블로그,,,,,
만들어보니 생각보다 정말 많은 시간과 삽질을 했음. 그리고 정말 정말 이상하고 길게 짜는 능력이 있다는 걸 새로 깨달음. 정말 볼 때마다 코드가 이상하고 줄일 부분이 보이는 게 아주 신기함. 저 위에 코드는 안 믿기겠지만 나름 정말 나름 줄이고 수정하고 고친 최최최종본 코드임. 그리고 뭔가 하기 전에 나는 백엔드다,,,, 백엔드에 뼈묻고 제물로 인생을 바친다 생각했는데 프론트엔드에도 미친듯이 시간을 쏟고 있는 자신을 발견할 수 있었음. 재미...? 있었음,, 그리고 코드를 봐줄 사람이 진자 필요하다,,,느꼈음 분명 코드가 똥같은데 어디가 똥같은지 나는 몰라,,,, 어디선가 분명 냄새가 나는데 어딘지 몰라,,,, 어떻게 처리하는 지도 몰라,,,,, 그래도 ?재미?있었음 음
댓글