Skip to content

[황채원] Sprint 10#16

Merged
Irelander merged 47 commits intocodeit-sprint-fullstack:node-황채원from
chaewon127:node-황채원-sprint10
Oct 29, 2025

Hidden character warning

The head ref may contain hidden characters: "node-\ud669\ucc44\uc6d0-sprint10"
Merged

[황채원] Sprint 10#16
Irelander merged 47 commits intocodeit-sprint-fullstack:node-황채원from
chaewon127:node-황채원-sprint10

Conversation

@chaewon127
Copy link
Collaborator

@chaewon127 chaewon127 commented Oct 26, 2025

요구사항

기본

공통

  • Github에 위클리 미션 PR을 만들어 주세요.
  • React.js 혹은 Next.js를 사용해 진행합니다.
  • RESTful를 설계하고 백엔드 코드를 변경하세요.
  • (풀스택) 설계한 백엔드 코드에 맞게 프론트엔드 코드를 변경해 주세요.
    https://panda-market-api.vercel.app의 API를 사용한 코드를 본인의 백엔드 API 코드로 변경하세요.
  • (백엔드) 프론트엔드 코드에 맞게 백엔드 코드를 변경해 주세요.
  • 백엔드 코드에 swagger를 추가해 API 명세 문서를 생성해 주세요.

백엔드 구현 요구사항

상품 등록

  • "상품 등록하기" 버튼 클릭 시, 상품 정보 등록 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 객체에 포함해 반환합니다.

상품 기능 인가

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

게시글 기능 인가

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

댓글 기능 인가

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

심화

인증

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

OAuth를 활용한 인증

  • 구글 OAuth를 사용하여 회원가입 및 로그인 기능을 구현합니다.

(생략 가능) 자유게시판 게시물 등록

  • 프론트엔드를 Next.js로 Migration 했을 경우에만 진행해 주세요.
  • 게시물 등록 시 이미지 등록 기능을 구현합니다. 파일을 선택해 이미지를 업로드하고, preview를 볼 수 있도록 구현합니다. 이미지는 최대 3개까지만 등록 가능하도록 구현해 주세요.
  • 게시물 등록 시 필요한 필드(제목, 내용 등)의 유효성 검증하는 미들웨어를 구현합니다.
  • multer 미들웨어를 사용하여 이미지 업로드 API를 구현해 주세요.
  • 업로드된 이미지는 서버에 저장하고, 해당 이미지의 경로를 response 객체에 포함해 반환합니다.

주요 변경사항

스크린샷

image

멘토에게

  • .env는 디코로 보내드리겠습니다.
  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.

@chaewon127 chaewon127 changed the title Node 황채원 sprint10 [황채원] sprint10 Oct 26, 2025
@chaewon127 chaewon127 changed the title [황채원] sprint10 [황채원] Sprint 10 Oct 26, 2025
@chaewon127 chaewon127 requested a review from Irelander October 26, 2025 14:09
@chaewon127 chaewon127 self-assigned this Oct 26, 2025
@chaewon127 chaewon127 marked this pull request as ready for review October 26, 2025 14:09
Copy link
Collaborator

@Irelander Irelander left a comment

Choose a reason for hiding this comment

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

검증 로직 중복 제거 (joi/zod 도입 검토)하고 에러 처리를 더 세밀하게 (토큰 만료 vs 변조 구분) 하는걸 신경쓰면 더 견고한 백엔드 코드가 될 것 같습니다 :)

이번 스프린트도 고생하셨어요!

Comment on lines +73 to +77
const isLiked = userId
? !!(await prisma.Like.findFirst({
where: { articleId: id, userId },
}))
: false;
Copy link
Collaborator

Choose a reason for hiding this comment

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

article 조회 후 별도로 Like 조회를 하게 되면서 쿼리 2번이 발생해요! ( N+1의 문제라고 합니다. )
include에서 한 번에 가져오면 쿼리 1번으로 처리 가능하니 개선하면 좋아요!

Comment on lines +79 to +91
const data = {
id: article.id,
title: article.title,
content: article.content,
image: article.image.length ? article.image[0] : DEFAULT_ARTICLE_IMAGE,
createdAt: article.createdAt,
updatedAt: article.updatedAt,
userId: article.user.id,
nickname: article.user.nickname ?? "오류",
image: article.user.image || DEFAULT_PROFILE_IMAGE,
likeCount: article._count.Like,
isLiked,
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

엇 image 속성이 중복되어서 더 나중에 선언된 유저 이미지로 사용되어질꺼에요, 이부분 확인이 필요할꺼 같네요 !

Comment on lines +64 to +81
export const updateComment = asyncHandler(async (req, res) => {
const { id } = req.params;
const { content } = req.body;

const exist = await prisma.comment.findUnique({ where: { id } });
if (!exist)
return res.status(404).json({ message: "존재하지 않는 댓글입니다." });

const updated = await prisma.comment.update({
where: { id },
data: { content },
include: {
user: { select: { id: true, nickname: true, image: true } },
},
});

res.json({ message: "댓글 수정 완료", comment: updated });
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

권한 검증이 없어서 누구나 수정할수 있을꺼 같아요 ㅎㅎ

추가로 findUnique로 존재하는지 검사후 업데이트하는데, update 쿼리를 날렸을때 존재하지 않는다면 업데이트 행이 0개라 존재하지 않는지 확인 가능해요! 따로 존재하는지 확인해서 업데이트 할 필요가 없습니다 !

Comment on lines +9 to +20
await prisma.$transaction([
prisma.articleLike.create({
data: { articleId: id, userId },
}),
prisma.article.update({
where: { id },
data: { likeCount: { increment: 1 } },
}),
]);

res.status(201).json({ message: "게시글 좋아요 등록" });
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

schema.primsa에 Like모델로 정의도어 있는데 여긴 다른 모델로 처리되어 있어서 혼동이 있을꺼 같아요 ㅎㅎ 동일하게 사용되면 더 좋을꺼 같습니다.

Comment on lines +45 to +55
await prisma.$transaction([
prisma.favorite.create({
data: { productId: id, userId },
}),
prisma.product.update({
where: { id },
data: { favoriteCount: { increment: 1 } },
}),
]);

res.status(201).json({ message: "상품 좋아요 등록" });
Copy link
Collaborator

Choose a reason for hiding this comment

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

한 사용자가 여러번 좋아요를 누를수 있기때문에 중복 좋아요 방지를 하는 코드가 들어가면 더 좋을꺼 같습니다 ㅎㅎ

if (!authHeader)
return res.status(401).json({ message: "토큰이 필요합니다." });

const token = authHeader.split(" ")[1];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Bearer 토큰인지 검사하는건 중요합니다. ( 유형 ) Faketype token 이런 형태로 보내도 정상적으로 처리될 수 있으므로 검사해주는게 좋습니다 ㅎㅎ

Comment on lines +13 to +14
const decoded = jwt.verify(token, JWT_SECRET);
const user = await prisma.user.findUnique({ where: { id: decoded.id } });
Copy link
Collaborator

Choose a reason for hiding this comment

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

JWT의 장점중 하나가 JWT만 가지고 유저정보를 획득할수 있다는건데 DB에서 실제로 검사를 하는 단계가 들어가서 장점이 사라지게 됩니다 ! ㅎㅎ 이미 JWT에 유저정보가 있으니 불필요한 쿼리는 호출핮 ㅣ않도록 ㅎ ㅏ는게 좋을꺼 같습니다

Comment on lines +3 to +14
export const checkOwnership = (model) => {
return async (req, res, next) => {
const { id } = req.params;
const record = await prisma[model].findUnique({ where: { id } });

if (!record) return res.status(404).json({ message: "존재하지 않습니다." });
if (record.userId !== req.user.id)
return res.status(403).json({ message: "권한이 없습니다." });

next();
};
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

DB조회나 네트워크 요청과 같은곳엔 항상 try ~ catch를 해주는게 좋습니다

Copy link
Collaborator

Choose a reason for hiding this comment

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

title이 숫자나 객체면 .length가 undefined거나 에러 발생할 수 있어요.
추가로 JSON으로 들어오면 price는 string이에요. typeof price !== "number"로 체크하면 항상 실패할꺼 같습니다 ㅠ

@Irelander Irelander merged commit 0fb76bc into codeit-sprint-fullstack:node-황채원 Oct 29, 2025
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