Conversation
페이지 조회를 통해 좋아요 개수도 확인 가능 좋아요 테스트, 좋아요 취소 테스트 작성
조회하는 유저가 좋아요를 눌렀다면 isLiked 속성을 true로 해서 프론트에서 다르게 처리 하도록 함
Walkthrough게시글 좋아요 기능이 백엔드와 프론트엔드에 새롭게 추가되었습니다. 백엔드는 좋아요 엔티티, 컨트롤러, 서비스, 예외, 저장소, DTO, 그리고 좋아요 수/상태를 포함하는 게시글 조회 기능을 구현했습니다. 프론트엔드는 게시글 목록에서 좋아요 버튼 및 카운트 표시와 토글 기능을 추가했습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Frontend as "PostListPage.jsx"
participant Backend as "LikeController"
participant Service as "LikeService"
participant Repo as "LikeRepository"
participant DB as "Database"
User->>Frontend: 게시글 목록 조회
Frontend->>Backend: GET /api/posts?page=n (JWT 포함)
Backend->>Service: getPosts(userId, page)
Service->>Repo: findAllWithLikeCount(userId, pageable)
Repo->>DB: 게시글+좋아요 수/상태 쿼리
DB-->>Repo: 결과 반환
Repo-->>Service: Projection 반환
Service-->>Backend: Projection 반환
Backend-->>Frontend: 게시글+좋아요 수/상태 반환
Frontend-->>User: 목록+좋아요 표시
User->>Frontend: 좋아요 버튼 클릭
Frontend->>Backend: POST /api/posts/likes/{postId} (JWT 포함)
Backend->>Service: likePost(userId, postId)
Service->>Repo: findByUserIdAndPostId(userId, postId)
alt 좋아요 없음
Service->>Repo: save(Like)
else 이미 좋아요
Service->>Repo: deleteById(likeId)
end
Service-->>Backend: LikeResponse 반환
Backend-->>Frontend: LikeResponse 반환
Frontend-->>User: UI 업데이트(하트/카운트)
Suggested labels
Poem
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (8)
backend/src/main/java/org/juniortown/backend/post/dto/response/PostWithLikeCount.java (1)
30-31: 생성자 주석 개선주석이 Korean과 English이 혼재되어 있습니다. 일관성을 위해 Korean으로 통일하는 것이 좋겠습니다.
- // 생성자 기반 DTO 매핑 + // 프로젝션 기반 DTO 매핑을 위한 생성자backend/src/main/java/org/juniortown/backend/like/controller/LikeController.java (2)
9-9: 불필요한 import 제거DeleteMapping import가 사용되지 않습니다.
-import org.springframework.web.bind.annotation.DeleteMapping;
22-27: API 설계 검토 제안현재 POST 엔드포인트만 제공하고 있는데, REST API 설계 관점에서 좋아요 추가/제거를 별도 엔드포인트로 분리하는 것을 고려해보세요.
예를 들어:
- POST
/api/posts/{postId}/likes- 좋아요 추가- DELETE
/api/posts/{postId}/likes- 좋아요 제거또는 현재와 같은 토글 방식을 유지한다면 PUT 메소드가 더 적절할 수 있습니다.
backend/src/main/java/org/juniortown/backend/like/entity/Like.java (1)
12-12: 불필요한 import 제거 필요
OneToOne어노테이션이 import되었지만 사용되지 않습니다.-import jakarta.persistence.OneToOne;frontend/src/pages/posts/PostListPage.jsx (1)
149-164: 접근성 개선 제안좋아요 버튼의 접근성을 개선할 수 있습니다.
- {post.isLiked ? '❤️' : '🤍'} + {post.isLiked ? '♥' : '♡'}이모지 대신 유니코드 하트 문자를 사용하면 스크린 리더에서 더 일관된 경험을 제공할 수 있습니다.
backend/src/main/java/org/juniortown/backend/post/controller/PostController.java (1)
5-5: 불필요한 import 제거
PostWithLikeCount클래스가 import되었지만 사용되지 않습니다.-import org.juniortown.backend.post.dto.response.PostWithLikeCount;backend/src/test/java/org/juniortown/backend/like/service/LikeServiceTest.java (2)
9-9: 불필요한 import 제거 필요사용하지 않는 import가 있습니다:
- 9번째 줄:
org.assertj.core.api.Assertions(3번째 줄에서 static import로 이미 가져옴)- 24번째 줄:
org.springframework.security.core.parameters.P(코드에서 사용되지 않음)-import org.assertj.core.api.Assertions;-import org.springframework.security.core.parameters.P;Also applies to: 24-24
84-101: 예외 테스트 개선 제안예외 메시지가 한국어와 영어가 혼재되어 있어 일관성이 떨어집니다. 또한 실제 서비스에서 발생할 수 있는 예외 상황을 더 현실적으로 시뮬레이션할 수 있습니다.
- when(likeRepository.save(any(Like.class))) - .thenThrow((new RuntimeException("게시글 삭제됐는데? DB 예외 펑!"))); + when(likeRepository.save(any(Like.class))) + .thenThrow(new RuntimeException("Database constraint violation"));
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
backend/src/main/java/org/juniortown/backend/controller/ExceptionController.java(1 hunks)backend/src/main/java/org/juniortown/backend/like/controller/LikeController.java(1 hunks)backend/src/main/java/org/juniortown/backend/like/dto/response/LikeResponse.java(1 hunks)backend/src/main/java/org/juniortown/backend/like/entity/Like.java(1 hunks)backend/src/main/java/org/juniortown/backend/like/exception/LikeFailureException.java(1 hunks)backend/src/main/java/org/juniortown/backend/like/repository/LikeRepository.java(1 hunks)backend/src/main/java/org/juniortown/backend/like/service/LikeService.java(1 hunks)backend/src/main/java/org/juniortown/backend/post/controller/PostController.java(2 hunks)backend/src/main/java/org/juniortown/backend/post/dto/response/PostWithLikeCount.java(1 hunks)backend/src/main/java/org/juniortown/backend/post/dto/response/PostWithLikeCountProjection.java(1 hunks)backend/src/main/java/org/juniortown/backend/post/repository/PostRepository.java(1 hunks)backend/src/main/java/org/juniortown/backend/post/service/PostService.java(2 hunks)backend/src/test/java/org/juniortown/backend/controller/PostControllerPagingTest.java(9 hunks)backend/src/test/java/org/juniortown/backend/like/controller/LikeControllerTest.java(1 hunks)backend/src/test/java/org/juniortown/backend/like/service/LikeServiceTest.java(1 hunks)backend/src/test/java/org/juniortown/backend/post/entity/PostTest.java(1 hunks)backend/src/test/java/org/juniortown/backend/post/service/PostServiceTest.java(4 hunks)frontend/src/pages/posts/PostListPage.jsx(4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/src/test/java/org/juniortown/backend/like/service/LikeServiceTest.java (1)
backend/src/main/java/org/juniortown/backend/like/exception/LikeFailureException.java (1)
LikeFailureException(8-17)
🔇 Additional comments (21)
backend/src/main/java/org/juniortown/backend/like/repository/LikeRepository.java (1)
1-12: 잘 구현된 리포지토리 인터페이스입니다.Spring Data JPA 규칙을 잘 따르고 있으며,
Optional<Like>반환 타입 사용이 적절합니다.참고사항:
@Repository애너테이션은 Spring Data JPA 인터페이스에서 불필요하지만 명시적으로 표현하는 것도 좋은 방법입니다.backend/src/main/java/org/juniortown/backend/post/dto/response/PostWithLikeCountProjection.java (1)
5-14: 잘 설계된 프로젝션 인터페이스입니다.Spring Data JPA 프로젝션 규칙을 잘 따르고 있으며, 좋아요 기능에 필요한 모든 필드를 포함하고 있습니다.
Boolean래퍼 타입 사용도 적절합니다.backend/src/main/java/org/juniortown/backend/like/exception/LikeFailureException.java (1)
8-17: 잘 구현된 커스텀 예외 클래스입니다.
CustomException을 적절히 확장하고 있으며, 정적 메시지와 원인 예외를 포함한 생성자 구현이 좋습니다. HTTP 500 상태 코드도 적절합니다.backend/src/main/java/org/juniortown/backend/post/service/PostService.java (1)
75-75: getPosts 호출 검증 완료서비스 메소드 시그니처 변경에 맞춰 아래 모든 호출부가
userId인자를 포함하도록 업데이트되어 있습니다.
- backend/src/main/java/org/juniortown/backend/post/controller/PostController.java:62
- backend/src/test/java/org/juniortown/backend/post/service/PostServiceTest.java:214, 244
backend/src/test/java/org/juniortown/backend/post/service/PostServiceTest.java (2)
202-217: 테스트 코드 업데이트 잘 반영됨서비스 레이어의 변경사항이 테스트 코드에 정확히 반영되었습니다. PostWithLikeCountProjection 사용과 새로운 repository 메소드 호출이 적절히 모킹되었습니다.
227-230: 테스트 검증 로직 적절함새로운 projection 타입을 사용한 테스트 검증 로직이 적절합니다.
backend/src/main/java/org/juniortown/backend/like/dto/response/LikeResponse.java (1)
7-19: 잘 구현된 DTO 클래스Lombok 어노테이션을 적절히 사용하고 필드 구성이 명확합니다. 좋아요 응답 데이터를 담기에 적절한 구조입니다.
backend/src/test/java/org/juniortown/backend/like/service/LikeServiceTest.java (3)
27-43: 테스트 클래스 설정이 적절합니다MockitoExtension을 사용한 모킹 설정과 @activeprofiles("test") 설정이 잘 구성되어 있습니다.
45-63: 좋아요 성공 테스트 로직 검증테스트 로직이 올바르게 구현되었습니다. 모킹 설정과 검증 단계가 적절하게 구성되어 있습니다.
65-82: 좋아요 취소 테스트 로직 검증기존 좋아요가 있는 상태에서 취소하는 시나리오를 잘 테스트하고 있습니다.
backend/src/test/java/org/juniortown/backend/like/controller/LikeControllerTest.java (6)
34-39: 테스트 클래스 설정이 적절합니다SpringBootTest를 사용한 통합 테스트 설정이 올바르게 구성되어 있습니다. @transactional 어노테이션으로 테스트 간 격리도 보장됩니다.
56-64: 테스트 데이터 초기화 로직 검증@beforeeach에서 좋아요 데이터를 초기화하고 테스트 게시글을 생성하는 로직이 적절합니다.
65-91: JWT 인증 설정 검증사용자 생성, 로그인, JWT 토큰 발급 과정이 올바르게 구현되어 있습니다.
93-107: 좋아요 성공 테스트 검증HTTP 요청과 응답 검증이 적절하게 구현되어 있습니다. 상태 코드와 응답 본문의 각 필드를 모두 검증하고 있습니다.
109-128: 좋아요 취소 테스트 검증사전에 좋아요를 생성하고 취소하는 시나리오를 잘 테스트하고 있습니다.
130-143: 예외 처리 테스트 검증존재하지 않는 게시글에 대한 좋아요 시도 시 예외 처리를 잘 테스트하고 있습니다.
backend/src/test/java/org/juniortown/backend/controller/PostControllerPagingTest.java (5)
12-14: 좋아요 기능 통합을 위한 의존성 추가좋아요 기능 테스트를 위해 필요한 의존성들이 적절히 추가되었습니다.
Also applies to: 55-61
65-67: 테스트 데이터 개선두 명의 테스트 사용자 생성과 POST_COUNT 상수 사용으로 테스트 데이터 관리가 개선되었습니다.
Also applies to: 69-69
85-108: 사용자 설정 로직 개선두 명의 테스트 사용자를 생성하여 더 포괄적인 좋아요 테스트가 가능하도록 설정이 개선되었습니다.
209-249: 좋아요 기능 통합 테스트 검증게시글 목록 조회 시 좋아요 수가 올바르게 집계되고 반환되는지 검증하는 테스트가 잘 구현되어 있습니다.
251-295: 좋아요 취소 기능 통합 테스트 검증LikeService를 통해 좋아요를 취소하고 목록에서 좋아요 수가 올바르게 업데이트되는지 검증하는 테스트가 적절하게 구현되어 있습니다.
| /*assertEquals(LocalDateTime.ofInstant( | ||
| Instant.parse("2025-06-20T10:15:30Z"), ZoneOffset.UTC), | ||
| post.getUpdatedAt()); | ||
| post.getUpdatedAt());*/ |
There was a problem hiding this comment.
🛠️ Refactor suggestion
주석 처리된 테스트 어설션을 수정하거나 제거하세요.
updatedAt 필드에 대한 어설션이 주석 처리되어 있습니다. 이는 테스트가 불안정하거나 예상대로 동작하지 않음을 의미할 수 있습니다. 다음 중 하나를 선택하세요:
- 어설션을 완전히 제거하고 테스트 목적을 명확히 하기
- 더 안정적인 방식으로
updatedAt검증 로직 구현하기
- /*assertEquals(LocalDateTime.ofInstant(
- Instant.parse("2025-06-20T10:15:30Z"), ZoneOffset.UTC),
- post.getUpdatedAt());*/
+ assertEquals(LocalDateTime.ofInstant(
+ Instant.parse("2025-06-20T10:15:30Z"), ZoneOffset.UTC),
+ post.getUpdatedAt());또는 어설션이 불필요하다면 완전히 제거하세요.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /*assertEquals(LocalDateTime.ofInstant( | |
| Instant.parse("2025-06-20T10:15:30Z"), ZoneOffset.UTC), | |
| post.getUpdatedAt()); | |
| post.getUpdatedAt());*/ | |
| assertEquals(LocalDateTime.ofInstant( | |
| Instant.parse("2025-06-20T10:15:30Z"), ZoneOffset.UTC), | |
| post.getUpdatedAt()); |
🤖 Prompt for AI Agents
In backend/src/test/java/org/juniortown/backend/post/entity/PostTest.java around
lines 54 to 56, the assertion for the updatedAt field is commented out,
indicating instability or incorrect behavior. Either remove this assertion
entirely if it is unnecessary for the test's purpose, or rewrite it using a more
reliable approach to verify updatedAt, such as comparing with a mocked or fixed
timestamp or allowing a time range to account for execution delays.
backend/src/main/java/org/juniortown/backend/controller/ExceptionController.java
Outdated
Show resolved
Hide resolved
| public PostWithLikeCount(Post post) { | ||
| this.id = post.getId(); | ||
| this.title = post.getTitle(); | ||
| // 이거 null 체크 어지럽네 | ||
| this.username = post.getUser().getName(); | ||
| this.userId = post.getUser().getId(); | ||
| this.createdAt = post.getCreatedAt(); | ||
| this.updatedAt = post.getUpdatedAt(); | ||
| this.deletedAt = post.getDeletedAt(); | ||
| } |
There was a problem hiding this comment.
likeCount 필드 초기화 누락
Post 객체를 받는 생성자에서 likeCount 필드가 초기화되지 않아 null 값이 될 수 있습니다. 이는 NullPointerException을 발생시킬 수 있습니다.
다음 수정을 제안합니다:
public PostWithLikeCount(Post post) {
this.id = post.getId();
this.title = post.getTitle();
this.username = post.getUser().getName();
this.userId = post.getUser().getId();
+ this.likeCount = 0L; // 기본값 설정
this.createdAt = post.getCreatedAt();
this.updatedAt = post.getUpdatedAt();
this.deletedAt = post.getDeletedAt();
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public PostWithLikeCount(Post post) { | |
| this.id = post.getId(); | |
| this.title = post.getTitle(); | |
| // 이거 null 체크 어지럽네 | |
| this.username = post.getUser().getName(); | |
| this.userId = post.getUser().getId(); | |
| this.createdAt = post.getCreatedAt(); | |
| this.updatedAt = post.getUpdatedAt(); | |
| this.deletedAt = post.getDeletedAt(); | |
| } | |
| public PostWithLikeCount(Post post) { | |
| this.id = post.getId(); | |
| this.title = post.getTitle(); | |
| this.username = post.getUser().getName(); | |
| this.userId = post.getUser().getId(); | |
| this.likeCount = 0L; // 기본값 설정 | |
| this.createdAt = post.getCreatedAt(); | |
| this.updatedAt = post.getUpdatedAt(); | |
| this.deletedAt = post.getDeletedAt(); | |
| } |
🤖 Prompt for AI Agents
In
backend/src/main/java/org/juniortown/backend/post/dto/response/PostWithLikeCount.java
around lines 20 to 28, the constructor that takes a Post object does not
initialize the likeCount field, which can lead to null values and potential
NullPointerExceptions. To fix this, retrieve the like count from the Post object
(e.g., post.getLikeCount()) and assign it to the likeCount field within the
constructor.
There was a problem hiding this comment.
이 코드는 사실상 사용을 하지 않음. JPA가 인터페이스 기반 프로젝션을 통해 구현체를 직접 주입해주기 때문에 삭제해도 무방할듯
There was a problem hiding this comment.
🧩 Analysis chain
@Doncham 맞습니다! 인터페이스 기반 프로젝션 방식이 훨씬 깔끔하네요.
확인해보겠습니다:
인터페이스 기반 프로젝션을 사용하면 JPA가 런타임에 구현체를 생성해주므로 별도의 DTO 클래스가 필요 없죠. 더 간결하고 효율적인 접근 방식입니다.
🏁 Script executed:
#!/bin/bash
# PostWithLikeCount 클래스의 사용처 확인
echo "=== PostWithLikeCount 클래스 사용처 검색 ==="
rg "PostWithLikeCount" --type java -A 2 -B 2
echo -e "\n=== PostWithLikeCountProjection 인터페이스 사용처 검색 ==="
rg "PostWithLikeCountProjection" --type java -A 2 -B 2
Length of output: 22763
@Doncham
확인 결과 PostWithLikeCount 클래스는 실제 로직에서 참조되지 않고, PostService·PostController에서 import만 남아 있으며 구현은 전부 PostWithLikeCountProjection을 사용하고 있습니다. 안전하게 삭제해도 무방하겠습니다. (불필요한 import·주석도 함께 정리 권장) 👍🏻
✏️ Learnings added
Learnt from: Doncham
PR: Doncham/JuniorTown#10
File: backend/src/main/java/org/juniortown/backend/post/dto/response/PostWithLikeCount.java:20-28
Timestamp: 2025-07-08T14:49:27.329Z
Learning: 이 프로젝트에서는 JPA 인터페이스 기반 프로젝션(PostWithLikeCountProjection)을 사용하여 데이터를 조회하므로, 클래스 기반 DTO(PostWithLikeCount)는 실제로 사용되지 않는다.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
backend/src/main/java/org/juniortown/backend/post/service/PostService.java
Outdated
Show resolved
Hide resolved
backend/src/main/java/org/juniortown/backend/like/controller/LikeController.java
Show resolved
Hide resolved
| const handleLike = async (postId, currentIsLiked, currentLikeCount) => { | ||
| try { | ||
| const token = localStorage.getItem('jwt'); | ||
| const response = await axios.post(`/api/posts/likes/${postId}`, null, { | ||
| headers: { 'Authorization': `${token}` }, | ||
| }); | ||
| const { isLiked } = response.data; | ||
|
|
||
| // posts 배열에서 해당 post의 isLiked, likeCount 업데이트 | ||
| setPosts((prevPosts) => | ||
| prevPosts.map((post) => | ||
| post.id === postId | ||
| ? { | ||
| ...post, | ||
| isLiked: isLiked, | ||
| likeCount: isLiked | ||
| ? currentLikeCount + 1 | ||
| : Math.max(0, currentLikeCount - 1), | ||
| } | ||
| : post | ||
| ) | ||
| ); | ||
| } catch (err) { | ||
| alert('좋아요 처리에 실패했습니다.'); | ||
| } | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
좋아요 기능 구현이 잘 되었지만 개선사항 제안
기본적인 좋아요 기능은 잘 구현되었습니다. 다음과 같은 개선사항을 제안합니다:
- 토큰 존재 여부 검증 필요
- 좋아요 처리 중 로딩 상태 표시
- 더 구체적인 에러 처리
const handleLike = async (postId, currentIsLiked, currentLikeCount) => {
+ const token = localStorage.getItem('jwt');
+ if (!token) {
+ alert('로그인이 필요합니다.');
+ return;
+ }
+
+ // 로딩 상태 관리를 위한 상태 추가 필요
try {
- const token = localStorage.getItem('jwt');
const response = await axios.post(`/api/posts/likes/${postId}`, null, {
headers: { 'Authorization': `${token}` },
});
// ... 나머지 코드
} catch (err) {
- alert('좋아요 처리에 실패했습니다.');
+ if (err.response?.status === 401) {
+ alert('로그인이 필요합니다.');
+ } else {
+ alert('좋아요 처리에 실패했습니다.');
+ }
}
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleLike = async (postId, currentIsLiked, currentLikeCount) => { | |
| try { | |
| const token = localStorage.getItem('jwt'); | |
| const response = await axios.post(`/api/posts/likes/${postId}`, null, { | |
| headers: { 'Authorization': `${token}` }, | |
| }); | |
| const { isLiked } = response.data; | |
| // posts 배열에서 해당 post의 isLiked, likeCount 업데이트 | |
| setPosts((prevPosts) => | |
| prevPosts.map((post) => | |
| post.id === postId | |
| ? { | |
| ...post, | |
| isLiked: isLiked, | |
| likeCount: isLiked | |
| ? currentLikeCount + 1 | |
| : Math.max(0, currentLikeCount - 1), | |
| } | |
| : post | |
| ) | |
| ); | |
| } catch (err) { | |
| alert('좋아요 처리에 실패했습니다.'); | |
| } | |
| }; | |
| const handleLike = async (postId, currentIsLiked, currentLikeCount) => { | |
| const token = localStorage.getItem('jwt'); | |
| if (!token) { | |
| alert('로그인이 필요합니다.'); | |
| return; | |
| } | |
| // 로딩 상태 관리를 위한 상태 추가 필요 | |
| try { | |
| const response = await axios.post(`/api/posts/likes/${postId}`, null, { | |
| headers: { 'Authorization': `${token}` }, | |
| }); | |
| const { isLiked } = response.data; | |
| // posts 배열에서 해당 post의 isLiked, likeCount 업데이트 | |
| setPosts((prevPosts) => | |
| prevPosts.map((post) => | |
| post.id === postId | |
| ? { | |
| ...post, | |
| isLiked: isLiked, | |
| likeCount: isLiked | |
| ? currentLikeCount + 1 | |
| : Math.max(0, currentLikeCount - 1), | |
| } | |
| : post | |
| ) | |
| ); | |
| } catch (err) { | |
| if (err.response?.status === 401) { | |
| alert('로그인이 필요합니다.'); | |
| } else { | |
| alert('좋아요 처리에 실패했습니다.'); | |
| } | |
| } | |
| }; |
🤖 Prompt for AI Agents
In frontend/src/pages/posts/PostListPage.jsx around lines 63 to 88, the
handleLike function lacks token existence validation, loading state management,
and detailed error handling. First, check if the token exists before making the
API call and handle the case when it is missing. Second, introduce a loading
state to indicate the like action is in progress and disable repeated clicks.
Third, improve error handling by capturing and displaying specific error
messages instead of a generic alert.
| public ResponseEntity<PageResponse<PostWithLikeCountProjection>> getPosts(@AuthenticationPrincipal CustomUserDetails customUserDetails, @PathVariable int page) { | ||
| Long userId = customUserDetails.getUserId(); | ||
| // null체크 | ||
|
|
||
| Page<PostWithLikeCountProjection> posts = postService.getPosts(userId, page); | ||
| PageResponse<PostWithLikeCountProjection> response = new PageResponse<>(posts); | ||
| return ResponseEntity.ok(response); | ||
| } |
There was a problem hiding this comment.
인증 사용자 null 체크 구현 필요
주석에서 언급된 null 체크를 실제로 구현해야 합니다.
public ResponseEntity<PageResponse<PostWithLikeCountProjection>> getPosts(@AuthenticationPrincipal CustomUserDetails customUserDetails, @PathVariable int page) {
+ if (customUserDetails == null) {
+ throw new IllegalArgumentException("인증된 사용자 정보가 필요합니다.");
+ }
Long userId = customUserDetails.getUserId();
- // null체크
+ if (userId == null) {
+ throw new IllegalArgumentException("사용자 ID가 없습니다.");
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public ResponseEntity<PageResponse<PostWithLikeCountProjection>> getPosts(@AuthenticationPrincipal CustomUserDetails customUserDetails, @PathVariable int page) { | |
| Long userId = customUserDetails.getUserId(); | |
| // null체크 | |
| Page<PostWithLikeCountProjection> posts = postService.getPosts(userId, page); | |
| PageResponse<PostWithLikeCountProjection> response = new PageResponse<>(posts); | |
| return ResponseEntity.ok(response); | |
| } | |
| public ResponseEntity<PageResponse<PostWithLikeCountProjection>> getPosts( | |
| @AuthenticationPrincipal CustomUserDetails customUserDetails, | |
| @PathVariable int page | |
| ) { | |
| if (customUserDetails == null) { | |
| throw new IllegalArgumentException("인증된 사용자 정보가 필요합니다."); | |
| } | |
| Long userId = customUserDetails.getUserId(); | |
| if (userId == null) { | |
| throw new IllegalArgumentException("사용자 ID가 없습니다."); | |
| } | |
| Page<PostWithLikeCountProjection> posts = postService.getPosts(userId, page); | |
| PageResponse<PostWithLikeCountProjection> response = new PageResponse<>(posts); | |
| return ResponseEntity.ok(response); | |
| } |
🤖 Prompt for AI Agents
In
backend/src/main/java/org/juniortown/backend/post/controller/PostController.java
around lines 58 to 65, the code has a comment about null checking the
authenticated user but lacks the actual implementation. Add a null check for the
customUserDetails parameter at the start of the method, and if it is null,
return an appropriate error response such as
ResponseEntity.status(HttpStatus.UNAUTHORIZED).build() to handle unauthenticated
access properly.
There was a problem hiding this comment.
@Doncham 네, 맞습니다! Spring Security의 인증 처리는 별도로 공부하시는 것이 좋겠네요.
주요 학습 포인트들을 정리해드리면:
- @AuthenticationPrincipal 어노테이션의 동작 원리
- Spring Security의 인증 필터 체인
- JWT 토큰 검증과 사용자 인증
- 예외 처리와 에러 응답 설계
당장은 주석으로 남겨두시고, 나중에 Spring Security를 제대로 학습하신 후에 적용하시면 될 것 같습니다.
🐰 계속 배우려는 자세가 훌륭하네요! 차근차근 공부하시면서 진행하세요.
✏️ Learnings added
Learnt from: Doncham
PR: Doncham/JuniorTown#10
File: backend/src/main/java/org/juniortown/backend/post/controller/PostController.java:58-65
Timestamp: 2025-07-08T15:09:35.094Z
Learning: 사용자 Doncham은 Spring Security와 인증 처리에 대해 별도로 공부하는 것을 선호하며, 당장 구현보다는 학습을 통해 제대로 이해한 후 적용하는 접근 방식을 취한다.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: Doncham
PR: Doncham/JuniorTown#10
File: backend/src/main/java/org/juniortown/backend/post/dto/response/PostWithLikeCount.java:20-28
Timestamp: 2025-07-08T14:49:27.329Z
Learning: 이 프로젝트에서는 JPA 인터페이스 기반 프로젝션(PostWithLikeCountProjection)을 사용하여 데이터를 조회하므로, 클래스 기반 DTO(PostWithLikeCount)는 실제로 사용되지 않는다.
| User user = userRepository.getReferenceById(userId); | ||
| Post post = postRepository.getReferenceById(postId); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
엔티티 존재 여부 검증 필요
getReferenceById는 지연 로딩 프록시를 반환하므로 실제 엔티티가 존재하지 않을 수 있습니다. 엔티티 존재 여부를 검증하는 것이 좋습니다.
-User user = userRepository.getReferenceById(userId);
-Post post = postRepository.getReferenceById(postId);
+User user = userRepository.findById(userId)
+ .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));
+Post post = postRepository.findById(postId)
+ .orElseThrow(() -> new IllegalArgumentException("게시글을 찾을 수 없습니다."));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| User user = userRepository.getReferenceById(userId); | |
| Post post = postRepository.getReferenceById(postId); | |
| User user = userRepository.findById(userId) | |
| .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); | |
| Post post = postRepository.findById(postId) | |
| .orElseThrow(() -> new IllegalArgumentException("게시글을 찾을 수 없습니다.")); |
🤖 Prompt for AI Agents
In backend/src/main/java/org/juniortown/backend/like/service/LikeService.java
around lines 26 to 27, replace getReferenceById calls with a method that fetches
the entity and verifies its existence, such as findById. Check if the returned
Optional is present; if not, handle the absence appropriately (e.g., throw an
exception) to ensure the user and post entities actually exist before
proceeding.
There was a problem hiding this comment.
만약 post나 user가 존재하지 않으면 likeRepository.save() 할 때 외래키 제약 조건에 의해서(너가 추천해줘서 그제야 넣긴했지만) 예외를 터뜨리기 때문에 굳이 매번 좋아요 할 때마다 user와 post를 DB에서 직접 조회할 필요는 없다고 생각함.
There was a problem hiding this comment.
@Doncham 맞습니다! 외래키 제약 조건이 있다면 getReferenceById를 사용하는 접근 방식이 더 효율적입니다.
실제로 존재하지 않는 엔티티에 대해 getReferenceById로 프록시를 생성하고, likeRepository.save() 시점에 외래키 제약 조건 위반으로 예외가 발생하는 것이 맞고, 이는 이미 기존의 try-catch 블록에서 LikeFailureException으로 처리되고 있네요.
매번 좋아요 요청마다 사용자와 게시글 존재 여부를 확인하기 위해 추가 DB 조회를 하는 것보다, 데이터베이스 레벨에서 참조 무결성을 보장하는 방식이 성능상 더 유리합니다.
🐰 (nodding thoughtfully)
(\ /)
( ._.)
o_(")(")
✏️ Learnings added
Learnt from: Doncham
PR: Doncham/JuniorTown#10
File: backend/src/main/java/org/juniortown/backend/like/service/LikeService.java:26-27
Timestamp: 2025-07-08T15:21:25.357Z
Learning: LikeService에서 getReferenceById를 사용하는 이유: 외래키 제약 조건이 있으면 실제 엔티티 존재 여부를 사전에 확인할 필요가 없고, save() 시점에 제약 조건 위반으로 예외가 발생하므로 프록시 객체를 사용하는 것이 성능상 더 효율적이다.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: Doncham
PR: Doncham/JuniorTown#10
File: backend/src/main/java/org/juniortown/backend/post/dto/response/PostWithLikeCount.java:20-28
Timestamp: 2025-07-08T14:49:27.329Z
Learning: 이 프로젝트에서는 JPA 인터페이스 기반 프로젝션(PostWithLikeCountProjection)을 사용하여 데이터를 조회하므로, 클래스 기반 DTO(PostWithLikeCount)는 실제로 사용되지 않는다.
| else{ | ||
| likeRepository.deleteById(like.get().getId()); | ||
|
|
||
| return LikeResponse.builder() | ||
| .userId(userId) | ||
| .postId(postId) | ||
| .isLiked(false) | ||
| .build(); | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
좋아요 취소 시 예외 처리 추가
좋아요 취소 시에도 예외 처리가 필요합니다.
else{
- likeRepository.deleteById(like.get().getId());
+ try {
+ likeRepository.deleteById(like.get().getId());
+ } catch (Exception e) {
+ throw new LikeFailureException("좋아요 취소에 실패했습니다.", e);
+ }
return LikeResponse.builder()
.userId(userId)
.postId(postId)
.isLiked(false)
.build();
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| else{ | |
| likeRepository.deleteById(like.get().getId()); | |
| return LikeResponse.builder() | |
| .userId(userId) | |
| .postId(postId) | |
| .isLiked(false) | |
| .build(); | |
| } | |
| else{ | |
| try { | |
| likeRepository.deleteById(like.get().getId()); | |
| } catch (Exception e) { | |
| throw new LikeFailureException("좋아요 취소에 실패했습니다.", e); | |
| } | |
| return LikeResponse.builder() | |
| .userId(userId) | |
| .postId(postId) | |
| .isLiked(false) | |
| .build(); | |
| } |
🤖 Prompt for AI Agents
In backend/src/main/java/org/juniortown/backend/like/service/LikeService.java
around lines 47 to 56, the code deletes a like without handling potential
exceptions. Add exception handling around the likeRepository.deleteById call to
catch and properly handle any exceptions that may occur during the deletion
process, ensuring the service remains robust and errors are managed gracefully.
| if(like.isEmpty()){ | ||
| try{ | ||
| likeRepository.save(Like.builder() | ||
| .user(user) | ||
| // 저장하는 시점에 post가 삭제될 수 있으니까 try-catch로 잡음. | ||
| .post(post) | ||
| .build() | ||
| ); | ||
| return LikeResponse.builder() | ||
| .userId(userId) | ||
| .postId(postId) | ||
| .isLiked(true) | ||
| .build(); | ||
| }catch (Exception e) { | ||
| throw new LikeFailureException(e); | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
예외 처리 개선 필요
현재 구현에서는 다음과 같은 문제가 있습니다:
- 너무 광범위한 예외 처리
- 동시성 문제 (race condition) 가능성
if(like.isEmpty()){
try{
- likeRepository.save(Like.builder()
- .user(user)
- .post(post)
- .build()
- );
+ Like newLike = Like.builder()
+ .user(user)
+ .post(post)
+ .build();
+ likeRepository.save(newLike);
return LikeResponse.builder()
.userId(userId)
.postId(postId)
.isLiked(true)
.build();
- }catch (Exception e) {
+ } catch (DataIntegrityViolationException e) {
+ // 동시성으로 인한 중복 처리
+ throw new LikeFailureException("이미 좋아요가 처리되었습니다.");
+ } catch (Exception e) {
throw new LikeFailureException(e);
}필요한 import 추가:
+import org.springframework.dao.DataIntegrityViolationException;🤖 Prompt for AI Agents
In backend/src/main/java/org/juniortown/backend/like/service/LikeService.java
between lines 30 and 46, the current exception handling is too broad and may
mask specific issues, and there is a potential race condition when saving likes
concurrently. Refine the exception handling by catching more specific exceptions
related to database constraints or entity state, and implement a mechanism to
handle or prevent race conditions, such as checking existence again before
saving or using database constraints with proper exception handling. Also, add
any necessary imports for the specific exceptions you handle.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
backend/src/main/java/org/juniortown/backend/like/entity/Like.java (1)
21-21: 이전 리뷰 제안사항 구현 완료복합 유니크 제약 조건이 올바르게 구현되었습니다. 이제 동일한 사용자가 같은 게시글에 중복으로 좋아요를 할 수 없도록 방지됩니다.
🧹 Nitpick comments (2)
backend/src/main/java/org/juniortown/backend/like/entity/Like.java (2)
12-12: 사용되지 않는 import 제거
@OneToOne어노테이션이 임포트되었지만 실제로는 사용되지 않습니다. 코드 정리를 위해 제거하는 것이 좋습니다.-import jakarta.persistence.OneToOne;
37-41: Builder 패턴 구현 적절Builder 패턴을 사용한 생성자가 올바르게 구현되었습니다. 간단하고 명확한 구조입니다.
필요에 따라 생성자에서 null 검증을 추가하는 것을 고려해볼 수 있습니다:
@Builder public Like(User user, Post post) { + if (user == null || post == null) { + throw new IllegalArgumentException("User and Post cannot be null"); + } this.user = user; this.post = post; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
backend/src/main/java/org/juniortown/backend/like/controller/LikeController.java(1 hunks)backend/src/main/java/org/juniortown/backend/like/entity/Like.java(1 hunks)backend/src/main/java/org/juniortown/backend/like/exception/LikeFailureException.java(1 hunks)backend/src/main/java/org/juniortown/backend/like/service/LikeService.java(1 hunks)backend/src/main/java/org/juniortown/backend/post/service/PostService.java(2 hunks)backend/src/test/java/org/juniortown/backend/post/entity/PostTest.java(0 hunks)frontend/src/pages/posts/PostListPage.jsx(5 hunks)
💤 Files with no reviewable changes (1)
- backend/src/test/java/org/juniortown/backend/post/entity/PostTest.java
🚧 Files skipped from review as they are similar to previous changes (5)
- backend/src/main/java/org/juniortown/backend/like/exception/LikeFailureException.java
- backend/src/main/java/org/juniortown/backend/like/controller/LikeController.java
- backend/src/main/java/org/juniortown/backend/post/service/PostService.java
- frontend/src/pages/posts/PostListPage.jsx
- backend/src/main/java/org/juniortown/backend/like/service/LikeService.java
🧰 Additional context used
🧠 Learnings (1)
backend/src/main/java/org/juniortown/backend/like/entity/Like.java (1)
Learnt from: Doncham
PR: Doncham/JuniorTown#10
File: backend/src/main/java/org/juniortown/backend/post/dto/response/PostWithLikeCount.java:20-28
Timestamp: 2025-07-08T14:49:27.329Z
Learning: 이 프로젝트에서는 JPA 인터페이스 기반 프로젝션(PostWithLikeCountProjection)을 사용하여 데이터를 조회하므로, 클래스 기반 DTO(PostWithLikeCount)는 실제로 사용되지 않는다.
🔇 Additional comments (3)
backend/src/main/java/org/juniortown/backend/like/entity/Like.java (3)
25-27: ID 필드 구현 적절기본 키 필드가 JPA 표준에 맞게 올바르게 구현되었습니다. IDENTITY 전략을 사용한 자동 생성 방식이 적절합니다.
29-31: User 관계 설정 적절User와의 다대일 관계가 올바르게 구성되었습니다. nullable = false 설정으로 데이터 무결성이 보장됩니다.
33-35: Post 관계 설정 적절Post와의 다대일 관계가 올바르게 구성되었습니다. nullable = false 설정으로 데이터 무결성이 보장됩니다.
Summary by CodeRabbit
신규 기능
버그 수정
테스트
문서화