[2024.06.04 TIL] 내일배움캠프 35일차 (팀프로젝트 구현, 게시물 좋아요, 좋아요 취소, 챌린지반, 서비스 분석)

My_Code·2024년 6월 4일
0

TIL

목록 보기
46/112
post-thumbnail

본 내용은 내일배움캠프에서 활동한 내용을 기록한 글입니다.


💻 TIL(Today I Learned)

📌 Today I Done

✏️ 상품 게시물 좋아요 기능 구현하기

  • 인증된(로그인된) 사용자만 사용 가능

  • 상품 게시물의 ID를 req.params를 통해서 가져옴

  • 사용자 테이블을 통해서 좋아요 테이블에 데이터가 생성됨

  • data 절에서 현재 테이블과 N:M 관계를 만들 테이블의 id를 connect를 통해 연결하면 됨

  • 오히려 사용자가 중복으로 좋아요 했을 때, 본인이 본인의 게시물에 좋아요를 누를 때와 같은 에러 처리 부분이 더 어려웠음

// 상품 게시글 좋아요 API
tradeRouter.post('/:tradeId/like', accessTokenValidator, async (req, res, next) => {
  try {
    // 상품 게시글 ID 가져오기
    const id = req.params.tradeId;

    // 상품 조회하기
    const trade = await prisma.trade.findFirst({ where: { id: +id }, include: { likedBy: true } });

    // 데이터베이스 상 해당 상품 ID에 대한 정보가 없는 경우
    if (!trade) {
      return res
        .status(HTTP_STATUS.NOT_FOUND)
        .json({ status: HTTP_STATUS.NOT_FOUND, message: MESSAGES.TRADE.COMMON.NOT_FOUND });
    }

    // 사용자 본인의 게시글에는 좋아요 누르지 못하도록 함
    if (trade.userId === req.user.id) {
      return res
        .status(HTTP_STATUS.BAD_REQUEST)
        .json({ status: HTTP_STATUS.BAD_REQUEST, message: MESSAGES.TRADE.LIKE.NO_PERMISSION });
    }

    // 이미 좋아요를 누른 경우
    const isDuplicatedLike = trade.likedBy.filter((user) => {
      return user.id === req.user.id;
    });
    if (isDuplicatedLike.length !== 0) {
      return res
        .status(HTTP_STATUS.BAD_REQUEST)
        .json({ status: HTTP_STATUS.BAD_REQUEST, message: MESSAGES.TRADE.LIKE.DUPLICATED });
    }

    // 사용자가 좋아요를 누르는 로직
    const user = await prisma.user.update({
      where: { id: req.user.id },
      omit: { password: true },
      data: {
        likedTrade: {
          connect: { id: trade.id },
        },
      },
    });

    return res.status(HTTP_STATUS.CREATED).json({
      status: HTTP_STATUS.CREATED,
      message: MESSAGES.TRADE.LIKE.SUCCEED,
      data: { tradeId: trade.id, userId: user.id },
    });
  } catch (err) {
    next(err);
  }
});

✏️ 상품 게시물 좋아요 취소 기능 구현하기

  • 인증된(로그인된) 사용자만 사용 가능

  • 상품 게시물의 ID를 req.params를 통해서 가져옴

  • 대부분의 코드가 좋아요 기능과 동일함

  • 다른 점은 connect로 연결했던 부분을 disconnect로 수정해주는 것임

// 상품 게시물 좋아요 취소 API
tradeRouter.post('/:tradeId/unlike', accessTokenValidator, async (req, res, next) => {
  try {
    // 상품 게시물 ID 가져오기
    const id = req.params.tradeId;

    // 상품 조회하기
    const trade = await prisma.trade.findFirst({ where: { id: +id }, include: { likedBy: true } });

    // 데이터베이스 상 해당 상품 ID에 대한 정보가 없는 경우
    if (!trade) {
      return res
        .status(HTTP_STATUS.NOT_FOUND)
        .json({ status: HTTP_STATUS.NOT_FOUND, message: MESSAGES.TRADE.COMMON.NOT_FOUND });
    }

    // 사용자 본인의 게시글에는 좋아요 취소 누르지 못하도록 함
    if (trade.userId === req.user.id) {
      return res
        .status(HTTP_STATUS.BAD_REQUEST)
        .json({ status: HTTP_STATUS.BAD_REQUEST, message: MESSAGES.TRADE.UNLIKE.NO_PERMISSION });
    }

    // 이미 좋아요 취소를 누른 경우
    const isDuplicatedUnlike = trade.likedBy.filter((user) => {
      return user.id === req.user.id;
    });
    if (isDuplicatedUnlike.length === 0) {
      return res
        .status(HTTP_STATUS.BAD_REQUEST)
        .json({ status: HTTP_STATUS.BAD_REQUEST, message: MESSAGES.TRADE.UNLIKE.NOT_LIKE });
    }

    // 사용자가 좋아요 취소를 누르는 로직
    const user = await prisma.user.update({
      where: { id: req.user.id },
      omit: { password: true },
      data: {
        likedTrade: {
          disconnect: { id: trade.id },
        },
      },
    });

    return res.status(HTTP_STATUS.CREATED).json({
      status: HTTP_STATUS.CREATED,
      message: MESSAGES.TRADE.UNLIKE.SUCCEED,
      data: { tradeId: trade.id, userId: user.id },
    });
  } catch (err) {
    next(err);
  }
});

✏️ 챌린지반 특강 - 서비스 분석하기

  • Q. 슬랙의 모든 기능들을 나열하기

    • 닫기, 최대창, 최소창, 이전 크기로 복원
    • 로그인, 로그아웃, 프로필 설정 기능,페이지 히스토리,다운로드 히스토리,도움말
    • 1:1채팅, 1:N 채팅, 이모지 추가, 허들(보이스 채팅), 녹화 기능 (클립), 책갈피 기능
    • 채널 기능 (CRUD), 스레드 기능 (CRUD), 공개/비공개 설정 기능, 테스크 관리 기능
    • 파일 전송/다운로드 기능, 검색 기능 (사용자, 채널, DM, 스레드 등등), 알람 기능 (on/off), 외부 어플 가져오는 기능(Extention)
  • Q. 워크스페이스 회원 관리의 경우의 수를 여러분들이 구현해야 된다고 하면 어떻게 구현하시겠습니까? 구현 로직을 상세히 설명해주세요.

    • 회원 등급 조정은 OWNER만 위 기능 사용 가능하게 조건문 사용
    • USER 테이블에 ROLE 컬럼을 만들고 DEFAULT로 MEMBER 역할을 설정
    • BLACKLIST는 ADMIN도 설정 및 해제가 가능
    • ROLE은 ENUM의 데이터를 사용
    • BLACKLIST는 ISBLACKLIST 라는 플래그 컬럼을 사용


📌 Tomorrow's Goal

✏️ 팀프로젝트 진행

  • 명예의 전당 과제 도전하기

  • 상수 객체 구조 변경으로 인한 에러 수정하기

  • API 명세서 수정하기

  • 배포 테스트하기



📌 Today's Goal I Done

✔️ 팀프로젝트 기능 구현하기

  • 오늘은 게시물 좋아요 기능 구현을 통해 N:M 관계를 구현하는 방법에 대해서 알게 됨

  • 그래도 다행히 초반에 N:M 관계에 대한 스키마를 잘 정의해서 구현하기 용이했음

  • N:M 관계에서 생성된 암시적 테이블은 connect, disconnect 쿼리를 통해서 관계를 연결하고 끊는 작업을 함

  • 그저 connect 쿼리로 관계를 연결해주기만 하면 자동으로 암시적 테이블에 데이터가 들어감

  • 그리고 게시물 목록, 상세 조회에서는 좋아요의 수가 보여야 하기 때문에 계속을 방법을 찾았음

  • 찾아보니 생각보다 간단하게 구현됨

  • _count, _sum, _avg 등 실제 Raw 쿼리에서 사용하던 쿼리가 존재했음

  • 이번에는 _count 쿼리를 이용해서 생각보다 손 쉽게 N:M 관계의 요소의 개수를 구함



⚠️ 구현 시 발생한 문제

✔️ 좋아요 순으로 정렬하는 조건을 어디에 위치시켜야 할지 모르겠음

  • 기존에 단순히 임의의 like 값으로 정렬을 함

  • 하지만 좋아요 기능이 구현되어 좋아요를 카운트 하는 방법을 찾고 있었음

  • include 절을 통해서 likedBy라는 관계 변수도 포함해서 상품 목록을 가져왔음

  • 하지만 정렬의 컨디션을 정하기 위한 조건문은 상품 목록을 찾는 쿼리보다 먼저 실행되어야 했음

  • 그래서 조건을 어디에 어떻게 작성해야 할지 도저히 감도 잡히지 않았음

  • 계속 찾다보니 Prisma에서 _count 라는 쿼리를 지원한다는 것을 찾음

// 상품 게시물 목록 조회 API (뉴스피드)
tradeRouter.get('/', async (req, res) => {
  // 정렬 조건 쿼리 가져오기
  let sortDate = req.query.sort?.toLowerCase();
  let sortLike = req.query.like?.toLowerCase();
  let type; // 쿼리 orderBy 조건을 담을 변수

  // like 정렬 쿼리가 있으면 좋아요 순으로 정렬
  if (sortLike) {
    // 시간 순 정렬 기본 값 설정
    if (sortLike !== SORT.desc && sortLike !== SORT.asc) {
      sortLike = SORT.desc;
    }
    // 같은 좋아요가 있는 경우 최신순으로 정렬
    type = [{ likedBy: { _count: sortLike } }, { createdAt: SORT.desc }];
  } else {
    // 좋아요 순 정렬 기본 값 설정 (상세한 내용은 회의가 필요)
    if (sortDate !== SORT.desc && sortDate !== SORT.asc) {
      sortDate = SORT.desc;
    }
    type = { createdAt: sortDate };
  }

  // trade 테이블의 데이터 모두를 조회
  let trades = await prisma.trade.findMany({
    include: { tradePicture: true, user: true, likedBy: true },
    orderBy: type,
    omit: { content: true },
  });

  trades = trades.map((trade) => {
    return {
      id: trade.id,
      userId: trade.user.id,
      title: trade.title,
      price: trade.price,
      region: trade.region,
      like: trade.likedBy.length,
      createdAt: trade.createdAt,
      updatedAt: trade.updatedAt,
      tradePicture: trade.tradePicture.map((img) => img.imgUrl),
    };
  });

  return res
    .status(HTTP_STATUS.OK)
    .json({ status: HTTP_STATUS.OK, message: MESSAGES.TRADE.READ.SUCCEED, data: { trades } });
});
profile
조금씩 정리하자!!!

0개의 댓글