대댓글 기능 (무한 댓글) - nextjs + prisma

Pyotato·2024년 1월 24일
1
post-thumbnail

https://www.youtube.com/watch?v=bhnDSyiPvaY&t=186s

  • db 설계를 위해 위의 영상을 참고했다.

☀️ 주목할 컬럼들

  • ref: 댓글들의 그룹, 댓글이 없다면 1부터 시작, 다음 댓글은 ref는 (max ref)+1 대댓글은 ref
  • refOrder : 같은 ref(그룹) 내에서의 순서
  • step: 들여쓰기 수준, 대댓글의 depths
  • answerNum: 현재 ref의 컬럼이 지닌 자식 글의 개수
  • parentCommentId: 부모의 고유 id

🌟 정렬은 ref desc, refOrder asc 로 정렬

  • ref는 댓글들의 그룹이므로 최신으로 정렬 (desc: 내림차순): 새로운 댓글을 달 때마다 ref+1을 해주기 때문
  • refOrder는 (asc: 오름차순): 대댓글은 부모댓글의 refOrder+1으로 추가되므로, 최신의 대댓글이 항상 적은 수기 때문

prisma 테이블

  • 테이블을 댓글, 대댓글을 하나로 했다.

model Comment {
  id              String   @id @default(cuid())
  createdAt       DateTime
  content         String
  postId          String
  parentCommentId String?
  post            Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
  commenterId     String
  image           String?
  name            String?
  ref             Int      @default(1)
  step            Int      @default(0)
  refOrder        Int      @default(0)
  answerNum       Int      @default(0)
  commenter       User?    @relation(fields: [commenterId, image, name], references: [id, image, name], onDelete: Cascade)
}

로직

  • 댓글은 최신을 가장 위로 오게 보는게 좋다고 생각한다.

  • 따라서 댓글이든 대댓글이든 가장 위로 오게 해둔다.

  • 경우 1: 댓글 달기
    - 댓글일 경우에는 대댓글이 아니므로 부모댓글이 없다. (자식은 부모가 있어야 존대한다.)
    - parentCommentId (부모댓글 없음) 현재 그룹(ref)+1을 해서 다음 그룹의 댓글의 ref를 지정.

    • refOrder는 현재 그룹 내의 대댓글 & 댓글의 순서다.
      • 댓글은 부모이므로 순서상 가장 처음이므로 0으로 지정.
  • 경우 2: 대댓글 달기
    - parent :부모 댓글의 모든 정보

    • updateChildRefOrder : 같은 ref그룹 내에서 순서를 조정해줘야 할 대댓글들
    • hasChildren: answerNum (같은 그룹 내의 자식들 개수)의 합, 즉 각각의 댓글/대댓글들이 가진 자식의 합
      • 경우 2-1 : 마지막 맨 마지막 부모댓글에 대댓글을 다는 경우
        - 대댓글생성 + 부모의 자식 개수(updateChildRefOrder) +1
      • 경우 2-2 : 같은 그룹 내(ref)에서 하나씩 그룹 순서를 +1 해줘야하는 대댓글들이 있음 (refOrder가 현재 작성하는 대댓글보다 refOrder가 큰 경우)
        - 모든 대댓글을 업데이트 후, 2-1을 해주면 된다.

ts 코드

import { NextApiRequest, NextApiResponse } from "next";
import prisma from "../../../lib/prisma";
// POST /api/comment
// Required fields in body: content, parentCommentId, email, postId, createdAt
export default async function handle(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === "POST") {
    const { content, parentCommentId, email, postId, createdAt } = req.body;
    /**
     * 댓글이 저장될 때마다 ref의 최댓값을 찾아서 ref+1 저장
     */
    const { _max } = await prisma.comment.aggregate({
      where: { post: { id: postId } },
      _max: { ref: true },
    });

    //  댓글
    if (!parentCommentId) {
      const comment = await prisma.comment.create({
        data: {
          createdAt: createdAt,
          content: content,
          commenter: { connect: { email: email } },
          post: {
            connect: { id: postId },
          },
          ref: _max.ref + 1,
          refOrder: 0,
        },
      });
      res.json(comment);
    } else {
      // 대댓글
      const parent = await prisma.comment.findFirst({
        where: { post: { id: postId }, id: parentCommentId },
      });

      const { _count } = await prisma.comment.aggregate({
        where: { post: { id: postId }, ref: parent.ref },
        _count: { answerNum: true },
      });
      const updateChildRefOrder = [];

      const hasChildren = _count.answerNum;

      if (hasChildren > 0) {
        // 업데이트할 자식들이 있음
        const children = await prisma.comment.findMany({
          where: { post: { id: postId }, ref: parent.ref },
          select: { id: true, refOrder: true, content: true },
        });
        let filt = children.filter((v) => v.refOrder >= parent.refOrder + 1);
        filt.forEach(async (v) => {
          let res = await prisma.comment.update({
            where: { post: { id: postId }, id: v.id },
            data: { refOrder: v.refOrder + 1 },
          });
          updateChildRefOrder.push(res);
        });
      }
      const comment = await prisma.comment.create({
        data: {
          createdAt: createdAt,
          content: content,
          commenter: { connect: { email: email } },
          post: {
            connect: { id: postId },
          },
          ref: parent.ref,
          step: parent.step + 1,
          parentCommentId: parent.id,
          refOrder: parent.refOrder + 1,
        },
      });
      const updateParentChildCount = await prisma.comment.update({
        where: { post: { id: postId }, id: parentCommentId },
        data: { answerNum: parent.answerNum + 1 },
      });

      res.json([comment, updateParentChildCount]);
    }
  } else {
    console.log("req method : ", req.method);
  }
}

테스트해보기~

(다음에는 prisma+postgre+nextjs+nextOAuth(카카오+깃헙)예제한 거 정리해야할듯)

profile
https://pyotato-dev.tistory.com/ 로 이사중 🚚💨🚛💨🚚💨

0개의 댓글

관련 채용 정보