안녕하세요

게시물 좋아요 기능 구현 방법 본문

Next13 블로그 프로젝트

게시물 좋아요 기능 구현 방법

sakuraop 2023. 10. 2. 23:16

컴포넌트화한 버튼으로 카드와 게시물에서 재사용
좋아요 누른 시점에 다른 사람이 좋아요를 누른 경우


게시물 좋아요 구현 방법

첫번째, 게시물에 좋아요를 클릭한 유저들을 저장한다.
두번째, 좋아요 collection을 만들어 유저들은 게시물별로 저장한다.

ex) likes: ["유저1", "유저2", "유저3"]
좋아요 누를 사람이 많아지면 그때 나누는게 나을것 같다.

mongodb free tier는 콜렉션 수에 제한이 있어서 많이 못 만들기 때문이다.


1) 좋아요를 누른 유저 목록에 현재 로그인한 유저가 포함되어 있는지 확인

ex) likes: ["유저1", "유저2", "유저3"] 

포함되어있다 ? "하트" : "빈 하트"

  const [isLiked, setIsLiked] = useState<boolean>(userEmail ? likes.includes(userEmail) : true); // 로그인 여부에 따라 heart 모양 변경
     return <> {isLiked ? "❤" : "🤍"} {likeCount} <>

 

2) 게시물 좋아요 버튼을 누르면 좋아요 요청

좋아요 버튼은 컴포넌트화하여 게시물 카드에서도 동작하도록 할 수 있다.

  const handleClickLikePostButton = async () => {
    try {
      // 게시물 좋아요 api
      const res: number = await postLikeCountData(postId, likeCount);
      handleSuccess(res); // 요청 성공 시 실행
    } catch (err) {
      console.error(err);
      window.alert("서버에 문제가 발생하였습니다. 잠시 후에 시도해주세요.");
    }
  };

3. 좋아요 권한 검사

좋아요를 누르는 유저를 email 로그인 한 유저로 한정하고 싶다.

token을 검사하여 인증되지 않은 유저는 동작하지 않도록 선리턴 한다.

// 게시물 좋아요 버튼 API 입니다.
export const PATCH = async (req: NextRequest, { params }: Params) => {
  const token: JWT | null = await getToken({ req }); // 유저 정보
  const { postId } = params;

  // 로그인 유저를 검사합니다.
  if (!token) {
    return NextResponse.json({ message: "게시물 좋아요: 로그인 된 유저가 아닙니다." }, { status: 400 });
  }

  // 이메일 인증된 유저가 아니면 에러 반환
  if (!token.email) {
    return NextResponse.json(
      { message: "게시물 좋아요: 이메일이 인증 된 유저가 아닙니다." },
      { status: 400 }
    );
  }
  
  //...
};

로그인 된 유저인가?
>no. 로그인 된 유저가 아닐 경우에는 "로그인 유저만 가능합니다." 라는 경고를 띄운다.
>yes. 다음으로

4. 서버에서 이미 좋아요를 누른 유저인지 확인

좋아요를 누른 게시물이 서버 상에서는 이미 삭제되어 있을 수 있다.

이 경우에는 DB에서 에러가 발생하므로 다음으로 넘어가지 않고,

좋아요를 누르려는 게시물을 찾을 수 없다는 경고를 띄운다.

    // 좋아요 업데이트
    const db = (await connectDB).db("blog");
    const postsCollection = db.collection<Post>("posts");
    const foundPostResult = await postsCollection.findOne({ id: Number(postId) });

    // 게시물이 존재하지 않으면 에러 반환
    if (!foundPostResult) {
      return NextResponse.json(
        { message: "게시물 좋아요: 게시물을 찾을 수 없습니다." },
        { status: 404 }
      );
    }

 

 

다른 유저가 좋아요를 눌렀을 수도 있기 때문에 좋아요를 누른 시점에서는 실제 게시물의 좋아요 수와 다를 수 있다. 

따라서 좋아요를 서버 상의 데이터로 반영하기 위해서는 현재 숫자에서 +1, -1를 하는 것이 아니라

DB에서 가져온 데이터에 +1, -1 한 결과를 반영해주어야 한다.

 

    // 좋아요 누른 시점의 숫자 결과 반환
    const postLikeUsers = foundPostResult?.likes;
    let count: number = postLikeUsers.length;

    // 좋아요를 이미 눌렀다면 배열에서 email 제외
    if (postLikeUsers?.includes(token.email)) {
      await postsCollection.updateOne({ id: Number(postId) }, { $pull: { likes: token.email } });
      count--;
    } else {
      // 그렇지 않다면 배열에 email추가
      await postsCollection.updateOne({ id: Number(postId) }, { $push: { likes: token.email } });
      count++;
    }
    return NextResponse.json({ count }, { status: 200 }); // 응답에 게시물 id를 포함하여 redirect할 수 있도록 합니다.

이미 좋아요를 누른 유저라면 좋아요 배열에 유저 정보가 포함되어 있기 때문에 pull로 제거하고,

그렇지 않으면 push로 추가한다.

 

    if (postLikes?.includes(token.email)) { // 이미 좋아요를 눌렀는가?

>no. likes 배열에서 유저 정보를 제거한다.
 await postsCollection.updateOne({ id: Number(postId) }, { $push: { likes: token.email } });

>yes. likes 배열에 유저 정보를 삽입한다. 
 await postsCollection.updateOne({ id: Number(postId) }, { $pull: { likes: token.email } });

5. 클라이언트 컴포넌트에서 좋아요 숫자를 반영한다.

  const handleClickLikePostButton = async () => {
      const res: number = await postLikeCountData(postId, likeCount);
      handleSuccess(res); // 요청 성공 시 실행
  };

  // 좋아요 요청 성공 시 이벤트
  const handleSuccess = (likeCount: number) => {
    // like 숫자 변경
    setPostLike(likeCount);

    // card 페이지에서는 로그인 여부와 관계 없이 heart 표시를 합니다.
    if (userEmail) {
      setIsLiked(!isLiked);
    }
  };

요청 성공 시 서버에서 받아 온 좋아요를 클라이언트 컴포넌트에 반영해준다.