포스트와 코멘트 투표 완성 UI는 아래와 같다
포스트 내용 페이지 내 포스트, 코멘트 투표 UI를 추가해야 하므로
reddit-clone-app\client\src\pages\r\[sub]\[identifier]\[slug].tsx
파일에 아래 내용을 추가한다.
...생략
{post && (
<>
<div className="flex">
{/* 투표 가능 부분 */}
<div className="flex-shrink-0 w-10 py-2 text-center rounded-l">
{/* 좋아요 부분 */}
<div
className="w-6 mx-auto text-gray-400 rounded cursor-pointer hover:bg-gray-300 hover:text-red-500"
// onClick={() => vote(1)}
>
<i
className={classNames("fas fa-arrow-up", {
"text-red-500": post.userVote === 1
})}
>
</i>
</div>
<p className="text-xs font-bold">{post.voteScore}</p>
{/* 싫어요 부분 */}
<div
className="w-6 mx-auto text-gray-400 rounded cursor-pointer hover:bg-gray-300 hover:text-blue-500"
// onClick={() => vote(-1)}
>
<i
className={classNames("fas fa-arrow-down", {
"text-blue-500": post.userVote === -1
})}
>
</i>
</div>
</div>
...생략
...생략
{comments?.map((comment) => (
<div className="flex" key={comment.identifier}>
{/* 투표 기능 부분 */}
<div className="flex-shrink-0 w-10 py-2 text-center rounded-l">
{/* 좋아요 부분 */}
<div
className="w-6 mx-auto text-gray-400 rounded cursor-pointer hover:bg-gray-300 hover:text-red-500"
// onClick={() => vote(1, comment)}
>
<i
className={classNames("fas fa-arrow-up", {
"text-red-500": comment.userVote === 1
})}
>
</i>
</div>
<p className="text-xs font-bold">{comment.voteScore}</p>
{/* 싫어요 부분 */}
<div
className="w-6 mx-auto text-gray-400 rounded cursor-pointer hover:bg-gray-300 hover:text-blue-500"
// onClick={() => vote(-1, comment)}
>
<i
className={classNames("fas fa-arrow-down", {
"text-blue-500": comment.userVote === -1
})}
>
</i>
</div>
</div>
...생략
투표 UI에서 좋아요, 싫어요 투표 시 호출하는 vote
함수를 추가한다.
const vote = async (value: number, comment?:Comment) => {
if(!authenticated) router.push("/login");
// 이미 클릭한 vote 버튼을 눌렀을 시에는 자신이 한 투표를 reset하기 위해 value=0
if(
(!comment && value === post?.userVote) ||
(comment && comment.userVote === value)
) {
value = 0;
}
try {
await axios.post("/votes", {
identifier,
slug,
commentIdentifier: comment?.identifier,
value
})
} catch (error) {
console.log(error);
}
}
reddit-clone-app\server\src\routes\votes.ts
라우트 파일을 생성하고 아래와 같이 내용을 추가한다.
import { Router } from "express";
import userMiddleware from "../middlewares/user";
import authMiddleware from "../middlewares/auth";
const vote = () => {
}
const router = Router();
router.post("/", userMiddleware, authMiddleware, vote);
export default router;
그리고 서버가 votes 요청을 받을 수 있도록 reddit-clone-app\server\src\server.ts
엔트리 파일에 라우트를 연결한다.
아래 내용을 추가한다.
import voteRoutes from "./routes/votes";
...생략
// "/api/votes/..." 요청 시 해당 라우터로 연결
app.use("/api/votes", voteRoutes)
...생략
POST /api/votes
요청 시 실행되는 vote 함수를 아래와 같이 정의한다.
...생략
const vote = async (req: Request, res: Response) => {
const { identifier, slug, commentIdentifier, value } = req.body;
// -1, 0, 1의 value만 오는지 체크
if(![-1, 0 ,1].includes(value)) {
return res.status(400).json({ value: "-1, 0, 1의 value만 올 수 있습니다." });
}
try {
// userMiddleware 에서 가져온 user 정보 초기화
const user: User = res.locals.user;
// 현재 포스트 정보를 통해 Post 데이터 가져오기
let post: Post = await Post.findOneByOrFail({ identifier, slug });
// vote 초기화
let vote: Vote | undefined;
// comment 초기화
let comment: Comment;
// vote 객체 찾기
if(commentIdentifier) {
// 댓글 식별자가 있는 경우 댓글로 vote 찾기
comment = await Comment.findOneByOrFail({ identifier: commentIdentifier });
vote = await Vote.findOneBy({ username: user.username, commentId: comment.id });
} else {
// 댓글 식별자가 없으면 포스트로 vote 찾기
vote = await Vote.findOneBy({ username: user.username, postId: post.id });
}
if(!vote && value === 0) {
// vote이 없고 value가 0인 경우 오류 반환
// value = 0 : 좋아요 또는 싫어요를 중복으로 누른 경우 = vote 데이터가 존재
return res.status(400).json({ error: "Vote을 찾을 수 없습니다." });
} else if(!vote) {
// 첫 투표인 경우
vote = new Vote();
vote.user = user;
vote.value = value;
// 게시물에 속한 vote or 댓글에 속한 vote
if(comment) vote.comment = comment
else vote.post = post;
await vote.save();
} else if (vote.value !== value) {
// 조회된 vote 값과 요청 value가 다르면
// => 첫 투표와 다른 투표 값 입력
vote.value = value;
await vote.save();
}
// 포스트 데이터 관련 테이블 조인하여 조회
post = await Post.findOneOrFail({
where: {
identifier, slug
},
relations: ["comments", "comments.votes", "sub", "votes"]
})
// 현 사용자의 포스트 투표 반영
post.setUserVote(user);
// 현 사용자의 코멘트 투표 반영 (댓글마다 loop)
post.comments.forEach(c => c.setUserVote(user));
return res.json(post);
} catch (error) {
console.log(error);
return res.status(500).json({ error: "문제가 발생했습니다." });
}
}
...생략