-middlewares.js
const isHeroku = process.env.NODE_ENV === "production";
export const s3DeleteAvatar = (req, res, next) => {
if(!isHeroku){
return next();
}
const {session: {user: {avatarUrl}}, file} = req;
const avatar = file ? (isHeroku ? file.location : file.path) :avatarUrl;
if(avatarUrl === avatar){
return next();
}
s3.deleteObject({
Bucket:"wkitube",
Key: `images/${avatarUrl.split("/")[4]}`
},(err, data) => {
if(err){
throw err;
}
console.log("s3 deleteObject", data);
})
next();
};
-userRouter
userRouter.route("/edit").all(protectorMiddleware).get(getEdit).post(avatarUpload.single("avatar"), s3DeleteAvatar,postEdit);
-middlewares.js
import Video from "./models/Video";
const isHeroku = process.env.NODE_ENV === "production";
export const s3DeleteVideo = async (req, res, next) => {
if(!isHeroku){
return next();
}
const {session: {user: {_id}}, params:{id}} = req;
const video = await Video.findById(id);
if(!video){
return next();
}
if(String(video.owner) !== String(_id)){
return next();
}
s3.deleteObjects({
Bucket:"wkitube",
Delete: {
Objects: [{Key: `videos/${video.fileUrl.split("/")[4]}`},{Key: `videos/${video.thumbUrl.split("/")[4]}`}],
},
},(err, data) => {
if(err){
throw err;
}
console.log("s3 deleteObject", data);
})
next();
};
-videoRouter.js
videoRouter.route("/:id([0-9a-f]{24})/delete").all(protectorMiddleware).get(s3DeleteVideo,deleteVideo);
-> 비디오 watch템플릿에 좋아요 버튼을 만들고, 누르면 fake number를 이용하여 숫자가 오르도록 만들었다.
-> video model에 좋아요 누른 사용자의 id가 들어갈 수 있는 array를 만들고, user model에도 좋아요 누른 비디오의 id가 들어갈 수 있는 array를 만듦.
-> 좋아요를 누르면 fetch를 이용해 요청이 백엔드에 보내지고, 백엔드에서 video array에는 user의 id가, user의 array에는 video의 id가 들어가도록 설정해 줌. 그리고 만약 video의 array에 이미 user의 id가 있다면 아이디를 삭제하는 요청을 함(좋아요 취소 기능도 같이 넣은 것임). 이후 프론트엔드로 video의 좋아요 array를 반환해줌. 프론트엔드에선 이 받은 array의 length를 이용해 좋아요 개수를 표현하도록 해줌. 이 때, 새로고침을 하지 않으면 숫자가 바로 보이지 않기 때문에, fake number를 이용해 바로 보이도록 해줬음. fake num은 likeIcon의 classname에 따라 좋아요, 좋아요 취소가 발동되도록 함.
-watch.pug
if loggedIn
div.like__box
span.like Like
if video.meta.likes.find((id) => id !== String(loggedInUser._id))
i.fas.fa-thumbs-up
else
i.far.fa-thumbs-up
span.likeNum=video.meta.likes.length
else
span Like
i.far.fa-thumbs-up
span=video.meta.likes.length
-likeSection.js - frontend
import { async } from "regenerator-runtime";
const videoContainer = document.getElementById("videoContainer");
const like = document.querySelector(".like");
const likeIcon = like.querySelector("i");
const fakeLikeNum = (likes) => {
const likeNum = like.querySelector(".likeNum");
likeNum.remove();
likeIcon.className = "fas fa-thumbs-up"
const span = document.createElement("span");
span.className = "likeNum";
span.innerText = likes.length;
like.append(span);
};
const fakeDislikeNum = () => {
const likeNum = like.querySelector(".likeNum");
const currentLikeNum = likeNum.innerText;
likeNum.remove();
likeIcon.className = "far fa-thumbs-up"
const span = document.createElement("span");
span.className = "likeNum";
span.innerText = currentLikeNum - 1;
like.append(span);
};
const handleLikeClick = async () => {
console.log("like");
const { videoid } = videoContainer.dataset;
const response = await fetch(`/api/videos/${videoid}/like`, {
method:"POST",
});
if(response.status === 200){
const {likes}= await response.json();
return fakeLikeNum(likes);
};
if(response.status === 404){
return window.location.reload();
};
if(response.status === 204){
return fakeDislikeNum();
};
};
if(like){
like.addEventListener("click", handleLikeClick);
}
-apiRouter
apiRouter.post("/videos/:id([0-9a-f]{24})/like", videoLike);
-videoController - backend
export const videoLike = async (req, res) => {
const {params:{id}, session:{user:{_id}}} = req;
const user = await User.findById(_id);
const video = await Video.findById(id);
const found = video.meta.likes.find((element) => element !== _id);
if(!user || !video){
req.flash("error", "video or user is not exists");
return res.sendStatus(404);
}
if(found){
user.likes.splice(user.likes.indexOf(id),1);
video.meta.likes.splice(video.meta.likes.indexOf(_id),1);
user.save();
video.save();
return res.sendStatus(204);
}
user.likes.push(id);
user.save();
video.meta.likes.push(_id);
video.save();
return res.status(200).json({likes : video.meta.likes});
};
-> postㄱ는 같은 Router를 이용하지만, 백엔드에서 좋아요, 좋아요 취소 기능 둘 다 구현할 수 있도록 설정해줬음.
-> 버튼 구현 (social-login.pug)
a(href="/users/kakao/start").social__btn.social__btn--kakao
i.fas.fa-comment
| Continue with Kakao →
-> Router구현(userRouter)
userRouter.get("/kakao/start", publicOnlyMiddleware, startKakaoLogin);
userRouter.get("/kakao/callback", publicOnlyMiddleware, callbackKakaoLogin);
-> userController로 인가코드(code) 받기 -> 토큰 받기 -> 토큰으로 사용자 정보 받기
client_id & secret code는 env파일에 저장 -> heroku에서도 마찬가지
export const startKakaoLogin = (req, res) => {
const baseUrl = "https://kauth.kakao.com/oauth/authorize";
const host = req.host
let redirect_uri = ""
if(host === "localhost"){
redirect_uri = "http://localhost:4000/users/kakao/callback";
}
else {
redirect_uri = "https://itube-by-wk.herokuapp.com/users/kakao/callback";
}
const config = {
response_type:"code",
client_id:process.env.KAKAO_CLIENT,
redirect_uri:redirect_uri,
};
const params = new URLSearchParams(config).toString();
const finalUrl = `${baseUrl}?${params}`;
return res.redirect(finalUrl);
};
export const callbackKakaoLogin = async (req, res) => {
const baseUrl = "https://kauth.kakao.com/oauth/token";
const host = req.host
let redirect_uri = ""
if(host === "localhost"){
redirect_uri = "http://localhost:4000/users/kakao/callback";
}
else {
redirect_uri = "https://itube-by-wk.herokuapp.com/users/kakao/callback";
}
const config = {
grant_type:"authorization_code",
client_id:process.env.KAKAO_CLIENT,
client_secret: process.env.KAKAO_SECRET,
redirect_url:redirect_uri,
code: req.query.code,
};
const params = new URLSearchParams(config).toString();
const finalUrl = `${baseUrl}?${params}`;
const tokenRequest = await (
await fetch(finalUrl, {
method:"POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
}
})
).json();
if("access_token" in tokenRequest){
const {access_token} = tokenRequest;
const apiUrl = "https://kapi.kakao.com/v2/user/me";
const userDataAll = await (await fetch(`${apiUrl}`, {
headers: {
Authorization: `Bearer ${access_token}`,
"Content-type": "application/x-www-form-urlencoded;charset=utf-8",
},
secure_resource: true,
})).json();
const userData = userDataAll.kakao_account;
let userEmail = userData.email;
if(!userEmail){
await fetch("https://kapi.kakao.com/v1/user/unlink", {
headers: {
"Content-Type":"application/x-www-form-urlencoded",
Authorization: `Bearer ${access_token}`,
},
});
req.flash("error", "Please agree to collect your email");
return res.status(404).redirect("/login");
}
let user = await User.findOne({ email: userData.email });// github에서 찾은 이메일을 우리의 DB에서 찾는것임.
if(!user){
user = await User.create({
avatarUrl: userData.profile.profile_image_url,
name: userData.profile.nickname ? userData.profile.nickname : "Unknown",
username: userData.profile.nickname ? userData.profile.nickname : "Unknown",
email: userEmail ? userEmail : "Unknown",
password: "",
socialOnly: true,//socialOnly:true -> 소셜 계정으로 만들어진 계정이란 뜻임. 따라서 이 사람은 password가 없으므로 login form을 이용할 수 없음.
location: "Unknown",
});
}
req.session.loggedIn = true;
req.session.user = user;
return res.redirect("/");
} else {
return res.redirect("/login");
}
};
오류 발생 : cors 오류 발생. image의 origin이 cors의 origin에 등록되지 않아 발생한 오류. 고치는 법 모르겠음 ㅠㅠ
템플릿 작성 후, kakao javascript SDK 다운로드 후 client/js에 kakaoJsSDK파일을 만듦. 이 때 webpack에도 이 파일을 생성한다고 알려주는 코드 작성.
이후 watch 템플릿에 kakaoSDK파일을 추가해준다.
이후 client/js에서 videoShare파일 생성 후 webpack에 동일하게 생성되었다고 알리는 코드 작성. 이후 videoShare.js에
const shareBox = document.querySelector(".share__box");
//shareVideo
const handleShaerboxClick = () => {
const shareContainer = document.querySelector("#shareContainer");
shareContainer.classList.remove("hidden");
//link에 주소 넣기
const input = shareContainer.querySelector(".share__link input");
input.value = window.location.href;
//닫기 버튼
const exit = shareContainer.querySelector(".share__header i");
exit.addEventListener("click", () => {
shareContainer.classList.add("hidden");
})
// 공유하기
const kakaoShare = shareContainer.querySelector(".share__icons-kakao");
if(kakaoShare){
Kakao.init('a49bae0fc61fcf4a825e3df56b72ff6d');
Kakao.isInitialized();
kakaoShare.addEventListener("click", () => {
Kakao.Share.createCustomButton({
container: kakaoShare,
templateId: 82088,
templateArgs: {
'title': 'itube',
'description': '개발 공부중인 이원규가 NodeJS로 구현한 첫 사이트 itube'
}
});
})
}
}
if(shareBox){
shareBox.addEventListener("click", handleShaerboxClick);
}
코드 작성 후, watch 템플릿에 script추가 ㄱ ㄱ