Skip to content

Conversation

@play-ancora-gyungmin
Copy link
Collaborator

요구사항

백엔드 구현 요구사항

상품 등록

  • "상품 등록하기" 버튼 클릭 시, 상품 정보 등록 API 엔드포인트에 요청을 보내 상품을 등록합니다.
  • 상품 등록 시 필요한 필드(이름, 설명, 가격 등)의 유효성을 검증하는 미들웨어를 구현합니다.
  • multer 미들웨어를 사용하여 이미지 업로드 API를 구현해 주세요.
  • 업로드된 이미지는 서버에 저장하고, 해당 이미지의 경로를 response 객체에 포함해 반환합니다.

상품 상세

  • 상품을 조회할 때, 해당 상품에 대한 댓글 리스트, 사용자가 '좋아요'를 눌렀는지 여부를 확인할 수 있도록 응답 객체에 포함시켜 반환해 주세요.

좋아요 기능

  • '좋아요' API를 만들어 주세요.
  • 사용자는 상품 또는 게시글에 '좋아요'를 할 수 있습니다.
  • $transaction을 사용해 주세요.
  • '좋아요' 취소 API를 만들어 주세요.
  • 사용자는 상품 또는 게시글에 '좋아요'를 취소할 수 있습니다.
  • $transaction을 사용해 주세요.
  • 상품 또는 게시글을 조회할 때, 사용자가 '좋아요'를 누른 항목인지 확인할 수 있도록 isLiked 필드를 응답 객체에 포함시켜 반환해 주세요.

에러 처리

  • 모든 예외 상황을 처리할 수 있는 에러 핸들러 미들웨어를 구현합니다.
  • 서버 오류(500), 사용자 입력 오류(400 시리즈), 리소스 찾을 수 없음(404) 등 상황에 맞는 상태값을 반환합니다.
  • 라우트 중복 제거
  • 중복되는 라우트 경로(예: /users에 대한 get 및 post 요청)를 app.route()로 통합해 중복을 제거합니다.
  • express.Router()를 활용하여 중고마켓/자유게시판 관련 라우트를 별도의 모듈로 구분합니다.

인증

  • User 스키마를 작성해 주세요.
  • id, email, nickname, image, encryptedPassword, createdAt, updatedAt 필드를 가집니다.
  • 회원가입 API를 만들어 주세요.
  • email, nickname, password 를 입력하여 회원가입을 진행합니다.
  • password는 해싱해 저장합니다.
  • 로그인 API를 만들어 주세요.
  • 사용자의 신원을 확인하고, 성공적인 인증 후에는 액세스 토큰을 발급해 response 객체에 포함해 반환합니다.

상품 기능 인가

  • 로그인한 사용자만 상품을 등록할 수 있습니다.
  • 상품을 등록한 사용자만 해당 상품의 정보를 수정 및 삭제를 할 수 있습니다.
  • 로그인한 사용자만 상품에 '좋아요'를 추가하거나 삭제할 수 있습니다.

게시글 기능 인가

  • 로그인한 사용자만 게시글을 등록할 수 있습니다.
  • 게시글을 등록한 사용자만 해당 게시글 정보를 수정 및 삭제할 수 있습니다.
  • 로그인한 사용자만 게시글에 '좋아요'를 추가하거나 삭제할 수 있습니다.

댓글 기능 인가

  • 로그인한 사용자만 상품에 댓글을 등록할 수 있습니다.
  • 로그인한 사용자만 게시글에 댓글을 등록할 수 있습니다.
  • 댓글을 등록한 사용자만 댓글을 수정하거나 삭제할 수 있습니다.

심화 요구사항
상태코드 (웹 API 관련)

  • 프론트엔드에서는 서버 응답의 상태코드에 따라 적절한 사용자 피드백을 제공합니다.

인증

  • 토큰 기반 방식을 사용할 경우, 만료된 액세스 토큰을 새로 발급하는 리프레시 토큰 발급 기능을 구현합니다.(jwt sliding session 적용)

Comment on lines +6 to +16
const { email, nickname, password } = req.body;

if (!email || !emailRegex.test(email)) {
throw new BadRequestException('유효한 이메일 형식이 아닙니다.');
}
if (!nickname) {
throw new BadRequestException('이름을 입력해주세요.');
}
if (!password || password.length < 6) {
throw new BadRequestException('비밀번호는 6자 이상이어야 합니다.');
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zod를 사용하지 않고 있었다면 그냥 넘겼을 것 같은 부분인데, 현재 프로젝트에서 이미 zod를 사용하고 있으니 검증 로직을 전부 zod 스키마에서 수행하게 만들면 좋을 것 같네요.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

데이터 검증은 zod로 처리하고 zod에서 발생하는 error를 캐치하는 핸들러를 하나 만들어서 express 미들웨어로 씌우거나 하는 방식으로 처리할 수도 있을 것 같습니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

빈 파일은 아직 구현이 안 된 부분인 걸까요?

Comment on lines 14 to 18
const page = parseInt(req.query.page, 10) || 1;
const pageSize = parseInt(req.query.pageSize, 10) || 10;
const pageSize =
parseInt(req.query.pageSize, 10) || Number.MAX_SAFE_INTEGER;
const keyword = req.query.keyword;
const orderBy = req.query.orderBy;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

req.query에서 데이터를 가져오는 부분도 zod로 처리하면 어떨까요?

Comment on lines +15 to +68
try {
const { authorization } = req.cookies;
if (!authorization) {
throw new UnauthorizedException('인증 정보가 없습니다.');
}
const [tokenType, token] = authorization.split(' ');
if (tokenType !== 'Bearer' || !token) {
throw new UnauthorizedException('지원하지 않는 인증 방식입니다.');
}

let decoded = verifyToken(token);

// Access Token이 만료된 경우
if (!decoded) {
const { refreshToken: refreshTokenWithBearer } = req.cookies;
if (!refreshTokenWithBearer) {
throw new UnauthorizedException('인증 정보가 만료되었습니다.');
}

const [refreshTokenType, refreshToken] =
refreshTokenWithBearer.split(' ');
if (refreshTokenType !== 'Bearer' || !refreshToken) {
throw new UnauthorizedException('지원하지 않는 인증 방식입니다.');
}

const decodedRefreshToken = verifyToken(refreshToken);
if (!decodedRefreshToken) {
throw new UnauthorizedException('인증 정보가 만료되었습니다.');
}

const user = await usersRepository.findUserById(
decodedRefreshToken.userId,
);
if (!user || user.refreshToken !== refreshToken) {
throw new UnauthorizedException('인증 정보가 유효하지 않습니다.');
}

// 새로운 Access Token 발급
const newAccessToken = jwt.sign({ userId: user.id }, config.JWT_SECRET, {
expiresIn: '6h',
});

res.cookie('authorization', `Bearer ${newAccessToken}`);
decoded = verifyToken(newAccessToken);
}

const user = await usersRepository.findUserById(decoded.userId);
if (!user) {
throw new UnauthorizedException(
'인증 정보와 일치하는 사용자가 없습니다.',
);
}
req.user = user;
next();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +79 to +81
function Grogu() {}

Grogu();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ 이 함수의 정체는 무엇인가요?

Comment on lines +74 to +75
// Refresh Token을 DB에 저장
await usersRepository.updateUserById(user.id, { refreshToken });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

권한 회수를 목적으로 refresh token을 db에 저장하는 경우 refresh token은 user 모델에 넣어두기보다는 다중 기기 사용 등의 시나리오에 대응하기 위해 별도의 모델(테이블)로 분리하는 것이 좋습니다.
e.g. PC와 모바일에서 동시에 로그인을 한다면?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants