본 내용은 내일배움캠프에서 활동한 내용을 기록한 글입니다.
팔로우 기능이 구현된 후 사용자가 다른 사용자를 팔로우 하면 팔로우한 사용자들의 게시물들을 모아서 볼 수 있어야 함
그래서 총 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를 이용해서 이미지나 동영상을 클라이언트로부터 받아올 수 있음
하지만 기존에는 그저 사용하기만 했지만 에러에 대해서는 다뤄보지 못함
예를 들어, 용량이 너무 큰 파일을 업로드 할려고 하면 에러를 출력해줘야 함
그래서 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,
});
};
간만에 운동도 하고 느긋하게 휴식할 예정
시간 남을 때 AWS 리젼 바꾸기...
전부 다 구현한 줄 알았으나 마지막 하나가 남아 있었음
기존에 있던 게시물 목록 조회 API를 활용해서 만들면 금방이라고 생각했음
하지만 내가 팔로우한 게시물들을 보기 위해서는 req.user에 팔로우한 ID가 있어야 함
게시물 목록 조회 API는 보통의 경우에는 모두가 다 볼 수 있어야 하고, 로그인 한 유저가 볼 때는 모든 게시물 + 팔로우한 게시물을 선택해서 볼 수 있어야 함
그래서 찾아낸 방법이 Optional Middleware임
Optional Middleware를 통해 조건부를 걸어서 인증 절차를 거치는 것임