[2024.06.07 TIL] 내일배움캠프 37일차 (팀프로젝트 완성, 발표, Optional Middleware, Multer Error Handling)

My_Code·2024년 6월 7일
0

TIL

목록 보기
48/113
post-thumbnail

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


💻 TIL(Today I Learned)

📌 Today I Done

✏️ 팔로우한 사용자의 게시물 필터링 구현

  • 팔로우 기능이 구현된 후 사용자가 다른 사용자를 팔로우 하면 팔로우한 사용자들의 게시물들을 모아서 볼 수 있어야 함

  • 그래서 총 2가지 방법을 생각함

  • 첫 번째는 그냥 팔로우한 사용자들의 게시물만 볼 수 있는 라우터를 따로 만드는 방법임

  • 이 방법을 사용하면 기존의 게시물 목록 조회 API의 코드를 거의 따라하면 됨

  • 하지만 코드가 너무 중복되기에 일단 보류함

  • 두 번째는 기존의 게시물 목록 조회 API를 활용해서 조건문을 통해 분기하는 방법임

  • 이 방법을 사용하면 확실히 중복된 코드도 줄어들고 중복으로 사용되는 코드들을 재활용 할 수 있다는 장점이 있음

  • 다만 문제가 있다면, 현재 사용자가 팔로우한 사용자의 정보를 알려면 Access Token을 받아와야 함

  • 즉, 로그인이 되어야만 알 수 있다는 것임

  • 하지만 기존의 게시물 목록 조회 API는 인가 절차 없이 모든 사용자의 게시물을 볼 수 있어야 하기에 Access Token의 인증 미들웨어를 사용하지 않았음

  • 그래서 인터넷에 찾은 방법이 Optional Middleware라는 방법임

  • Optional Middleware는 미들웨어를 어떤 경우에는 동작시키고 어떤 경우에는 그냥 next()하는 미들웨어를 의미함

  • 나는 이 방법을 통해서 평상시에는 모든 사용자가 게시물 목록 조회 API를 사용할 수 있게 하고, 로그인 후에는 모든 게시물 목록 조회 + 팔로우한 사용자의 게시물 조회를 가능하게 함

  • 먼저 미들웨어로는 기존에 사용한 Access Token 인가 미들웨어를 고쳐서 사용하기로 함

import jwt from 'jsonwebtoken';
import 'dotenv/config';
import { prisma } from '../utils/prisma.util.js';
import { HTTP_STATUS } from '../constants/http-status.constant.js';
import { MESSAGES } from '../constants/message.constant.js';

// accessToken 인증 미들웨어

export const optionalAccessTokenValidator = async (req, res, next) => {
  try {
    // 헤더에서 토큰 정보 받아오기
    const { authorization } = req.headers;

    const [tokenType, accessToken] = authorization?.split(' ');

    let decodedToken;
    // jwt.verify 에러를 컨트롤 하는 try, catch문
    try {
      decodedToken = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET_KEY);
    } catch (err) {
      // 유효기간 만료 시 에러처리
      if (err.name === 'TokenExpiredError') {
        return res
          .status(HTTP_STATUS.UNAUTHORIZED)
          .json({ status: HTTP_STATUS.UNAUTHORIZED, message: MESSAGES.AUTH.COMMON.JWT.EXPIRED });
      }
      // 그 밖의 검증 실패(JsonWebTokenError, NotBeforeError)
      else {
        return res
          .status(HTTP_STATUS.UNAUTHORIZED)
          .json({ status: HTTP_STATUS.UNAUTHORIZED, message: MESSAGES.AUTH.COMMON.JWT.INVALID });
      }
    }

    // decodedToken에 담긴 사용자 id와 db의 유저 비교 검증
    const user = await prisma.user.findUnique({
      include: { follower: true, following: true },
      where: { id: decodedToken.id },
      omit: { password: true },
    });
    // 일치하는 사용자가 없을 때
    if (!user) {
      return res.status(HTTP_STATUS.UNAUTHORIZED).json({
        status: HTTP_STATUS.UNAUTHORIZED,
        message: MESSAGES.AUTH.COMMON.JWT.NO_USER,
      });
    }
    // 인증 통과 시 유저 정보 req.user로 넘기기(password 제외)
    req.user = user;
    next();
  } catch (err) {
    next();
  }
};
  • 원래는 authorization 변수에 값이 없으면 에러처리를 하도록 했지만 이번에는 Optional하게 사용하기 위해서 JWT 검증을 위한 에러처리를 제외하고 삭제함

  • 특히 catch 문에서 원래는 next(err)로 에러를 에러처리 미들웨어로 넘겨줬지만 여기서는 Access Token이 없으면 try문 안에 있는 jwt.verify()검증에 실패해서 catch문으로 넘어가서 그냥 아무 동작없는 미들웨어가 됨

  • Access Token이 있으면 사용자 인가처리 후 req.user를 생성함

  • 이를 통해 미들웨어를 Optional하게 사용하는 것임

  • 아래 코드는 이러한 Optional Middleware를 적용한 게시글 목록 조회 API

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

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

  let follow = req.query.follow;
  let trades;

  // 인가된 사용자만 사용하는 목록 조회 (팔로우한 사용자의 게시물만 조회)
  if (follow && req.user) {
    const followingIds = req.user.following.map((following) => following.followingId);
    trades = await prisma.trade.findMany({
      where: { userId: { in: followingIds } },
      include: { tradePicture: true, user: true, likedBy: true },
      orderBy: type,
      omit: { content: true },
    });
  } else {
    // 모든 사용자가 사용하는 목록 조회
    // trade 테이블의 데이터 모두를 조회
    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,
      status: trade.status,
      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 } });
});

✏️ Multer Error Handing

  • Multer를 이용해서 이미지나 동영상을 클라이언트로부터 받아올 수 있음

  • 하지만 기존에는 그저 사용하기만 했지만 에러에 대해서는 다뤄보지 못함

  • 예를 들어, 용량이 너무 큰 파일을 업로드 할려고 하면 에러를 출력해줘야 함

  • 그래서 Multer와 관련된 에러 처리를 찾아보니 공식 문서에서 다음과 같은 주소를 알려줌

  • https://github.com/expressjs/multer/blob/master/lib/multer-error.js

  • 살펴보니 Multer에서 나올 수 있는 에러 코드의 종류들이었음

  • 이를 통해서 에러 처리 미들웨어에서 동시에 처리해 줌

  • 먼저 관련 메세지를 개체화해서 관리함

export const MESSAGES = {
  ERROR_HANDLER: {
    MULTER: {
      PART_COUNT: '필드와 파일 수의 총합이 너무 많습니다.',
      FILE_SIZE: '파일 용량이 너무 큽니다.',
      FILE_COUNT: '파일 수가 너무 많습니다.',
      FIELD_KEY: '필드의 이름이 너무 깁니다.',
      FIELD_VALUE: '필드의 값이 너무 깁니다.',
      FIELD_COUNT: '필드가 너무 많습니다.',
      UNEXPECTED_FILE: '지원하지 않은 파일입니다.',
      FIELD_NAME: '필드의 이름을 읽어버렸습니다.',
    },
    ETC: '예상치 못한 에러가 발생했습니다. 관리자에게 문의해 주세요.',
  },
}
  • 이후 에러 코드들과 같은 이름을 가진 객체를 생성
import { MESSAGES } from './message.constant.js';

export const multerErrorMessages = {
  LIMIT_PART_COUNT: MESSAGES.ERROR_HANDLER.MULTER.PART_COUNT,
  LIMIT_FILE_SIZE: MESSAGES.ERROR_HANDLER.MULTER.FILE_SIZE,
  LIMIT_FILE_COUNT: MESSAGES.ERROR_HANDLER.MULTER.FILE_COUNT,
  LIMIT_FIELD_KEY: MESSAGES.ERROR_HANDLER.MULTER.FIELD_KEY,
  LIMIT_FIELD_VALUE: MESSAGES.ERROR_HANDLER.MULTER.FIELD_VALUE,
  LIMIT_FIELD_COUNT: MESSAGES.ERROR_HANDLER.MULTER.FIELD_COUNT,
  LIMIT_UNEXPECTED_FILE: MESSAGES.ERROR_HANDLER.MULTER.UNEXPECTED_FILE,
  MISSING_FIELD_NAME: MESSAGES.ERROR_HANDLER.MULTER.FIELD_NAME,
};
  • 기존에 있던 에러처리 미들웨어에서 조건문을 통해 걸러줌
import { HTTP_STATUS } from '../constants/http-status.constant.js';
import { MESSAGES } from '../constants/message.constant.js';
import { multerErrorMessages } from '../constants/multer-error.constant.js';

export default (err, req, res, next) => {
  console.error(err);

  // multer에서 발생한 에러 처리
  if (err.name === 'MulterError') {
    return res
      .status(HTTP_STATUS.BAD_REQUEST)
      .json({ status: HTTP_STATUS.BAD_REQUEST, message: multerErrorMessages[err.code] });
  }

....

  // 그 밖의 예상치 못한 에러 처리
  return res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({
    status: HTTP_STATUS.INTERNAL_SERVER_ERROR,
    message: MESSAGES.ERROR_HANDLER.ETC,
  });
};


📌 Tomorrow's Goal

✏️ 개인 일정으로 인한 휴식

  • 간만에 운동도 하고 느긋하게 휴식할 예정

  • 시간 남을 때 AWS 리젼 바꾸기...



📌 Today's Goal I Done

✔️ 팔로우한 사용자의 게시물 필터링 구현

  • 전부 다 구현한 줄 알았으나 마지막 하나가 남아 있었음

  • 기존에 있던 게시물 목록 조회 API를 활용해서 만들면 금방이라고 생각했음

  • 하지만 내가 팔로우한 게시물들을 보기 위해서는 req.user에 팔로우한 ID가 있어야 함

  • 게시물 목록 조회 API는 보통의 경우에는 모두가 다 볼 수 있어야 하고, 로그인 한 유저가 볼 때는 모든 게시물 + 팔로우한 게시물을 선택해서 볼 수 있어야 함

  • 그래서 찾아낸 방법이 Optional Middleware임

  • Optional Middleware를 통해 조건부를 걸어서 인증 절차를 거치는 것임



⚠️ 구현 시 발생한 문제

✔️ 팔로우한 사용자의 게시물 필터링 구현의 어려움

  • 위에서 자세하게 설명했기에 여기는 생략
profile
조금씩 정리하자!!!

0개의 댓글