Skip to content

Conversation

@1winhyun
Copy link
Member

@1winhyun 1winhyun commented Jan 8, 2026

🔀 변경 내용

  • 학생회가 올린 게시글(제휴, 행사)에 좋아요를 눌러 관심 게시글로 등록하고 조회하는 기능을 구현했습니다.

✅ 작업 항목

  • 게시글 좋아요 등록 구현
  • 관심 게시글 조회(제휴, 행사 별 필터링) 기능 구현 (좋아요 누른 순서 내림차순)

📸 스크린샷 (선택)

좋아요 등록

  • 하나의 API를 토글 형식으로 구현해 좋아요와 좋아요 취소가 같이 동작하게 구현했습니다.
image image

관심 게시물 목록 조회

image

📎 참고 이슈

관련 이슈 번호 #42

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 게시글 좋아요 기능 추가 - 사용자가 게시글을 좋아요 표시하고 해제할 수 있음
    • 좋아요한 게시글 목록 조회 기능 추가 - 카테고리 필터 및 페이지네이션 지원
    • 게시글 응답에 좋아요 상태 표시 추가 - 사용자가 게시글의 좋아요 여부를 확인 가능

✏️ Tip: You can customize this high-level summary in your review settings.

@1winhyun 1winhyun requested review from 1224kang and jjaeroong January 8, 2026 08:34
@1winhyun 1winhyun self-assigned this Jan 8, 2026
@1winhyun 1winhyun added the ✨Feat 새로운 기능 개발 label Jan 8, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

Walkthrough

게시글 좋아요 기능을 추가합니다. 새로운 LikePost 엔티티와 LikePostRepository를 도입하고, 관련 DTO(GetLikedPostResponse, LikePostResponse, GetPostForUserResponse)를 정의하며, 서비스와 컨트롤러에 좋아요 토글, 좋아요한 게시글 조회 등의 기능을 구현합니다.

Changes

Cohort / File(s) 변경 사항
좋아요 관련 DTO
GetLikedPostResponse.java, LikePostResponse.java, GetPostForUserResponse.java
좋아요 기능을 위한 3개의 새로운 응답 DTO 추가. 좋아요 상태 및 게시글 세부정보 포함
LikePost 엔티티 및 저장소
LikePost.java, LikePostRepository.java
사용자와 게시글 간의 좋아요 관계를 나타내는 새로운 JPA 엔티티 추가. 좋아요 조회, 삭제, 존재 여부 확인 등의 쿼리메서드 포함
Mapper 레이어
StudentCouncilPostMapper.java
toPostListItemResponse 서명 변경 및 좋아요 관련 매핑 메서드 추가 (toLikePostResponse, toGetLikedPostResponse, createLikePost 등)
응답 타입 변경
GetPostResponse.java, PostListItemResponse.java
PostResponse → GetPostResponse 타입 변경 및 PostListItemResponse 필드 변경 (Boolean isWriter → boolean liked)
비즈니스 로직
StudentCouncilPostForUserService.java
좋아요 토글, 좋아요한 게시글 조회, 게시글 상세 조회 메서드 추가. like 상태를 포함한 페이지 매핑 로직 추가
응답 코드 및 컨트롤러
StudentCouncilPostResponseCode.java, StudentCouncilPostForUserController.java, StudentCouncilPostController.java, StudentCouncilPostService.java
POST_LIKE_SUCCESS 응답 코드 추가. 좋아요 토글 및 좋아요한 게시글 조회 엔드포인트 추가. GetPostResponse 반환 타입으로 통일

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant Controller as StudentCouncilPostForUserController
    participant Service as StudentCouncilPostForUserService
    participant Repository as LikePostRepository
    participant DB as 데이터베이스

    User->>Controller: POST /{postId}/like
    activate Controller
    Controller->>Service: toggleLikePost(userId, postId)
    deactivate Controller
    
    activate Service
    Service->>Repository: existsByUserIdAndPost_Id(userId, postId)
    activate Repository
    Repository->>DB: 좋아요 존재 여부 조회
    DB-->>Repository: 결과 반환
    deactivate Repository
    
    alt 좋아요 존재
        Service->>Repository: deleteByUserIdAndPostId(userId, postId)
        activate Repository
        Repository->>DB: 좋아요 삭제
        deactivate Repository
        Service->>Service: toLikePostResponse(user, post, false)
    else 좋아요 미존재
        Service->>Repository: save(LikePost)
        activate Repository
        Repository->>DB: 좋아요 저장
        DB-->>Repository: 저장 완료
        deactivate Repository
        Service->>Service: toLikePostResponse(user, post, true)
    end
    
    Service-->>Controller: LikePostResponse
    deactivate Service
    Controller-->>User: CommonResponse<LikePostResponse>
Loading
sequenceDiagram
    participant User as 사용자
    participant Controller as StudentCouncilPostForUserController
    participant Service as StudentCouncilPostForUserService
    participant Repository as LikePostRepository
    participant PostRepository as StudentCouncilPostRepository
    participant DB as 데이터베이스

    User->>Controller: GET /likes?category=...&page=...
    activate Controller
    Controller->>Service: findLikedPosts(category, page, size, userId)
    deactivate Controller
    
    activate Service
    Service->>Repository: findLikedPosts(userId, category, pageable)
    activate Repository
    Repository->>DB: 사용자의 좋아요한 게시글 조회
    DB-->>Repository: LikePost 페이지 반환
    deactivate Repository
    
    Service->>Service: mapPostsWithLikes(posts, userId)
    Service->>Repository: findLikedPostIds(userId, postIds)
    activate Repository
    Repository->>DB: 게시글 ID별 좋아요 상태 조회
    DB-->>Repository: 좋아요한 게시글 ID 목록
    deactivate Repository
    
    Service->>Service: toGetLikedPostResponse 매핑
    Service-->>Controller: Page<GetLikedPostResponse>
    deactivate Service
    
    Controller-->>User: CommonResponse<Page<GetLikedPostResponse>>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • 1224kang
  • jjaeroong

Poem

🐰 좋아요 기능이 새로 생겼네, 토끼가 폴짝!
사용자가 클릭하면 마음을 담아,
게시글은 반짝반짝 빛이 나고,
저장소에 사랑이 쌓여가고,
좋아요한 글들을 모아보니, 정말 멋지다 냥냥! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 PR의 주요 변경사항을 명확하게 요약하고 있습니다. '학생회 게시글 좋아요 기능 구현'은 이 PR의 핵심 목표인 좋아요 기능 추가를 정확히 나타냅니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In
@src/main/java/com/campus/campus/domain/councilpost/application/dto/response/LikePostResponse.java:
- Around line 6-13: In LikePostResponse, replace the @Schema(name=...) usages on
the fields userId, postId, and liked with @Schema(description=...) and update
the liked field's example to a boolean literal (e.g., example = "true" or
"false"); specifically edit the annotations on the fields userId, postId, and
liked in the LikePostResponse class so they use description for human-readable
text and set liked's example to a proper boolean value.

In
@src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java:
- Around line 143-163: The toggleLikePost method in StudentCouncilPostService
has a race condition when two requests concurrently try to create a LikePost;
add a unique DB constraint on LikePost (annotate the LikePost entity @Table with
@UniqueConstraint(columnNames = {"user_id","post_id"}) to enforce uniqueness)
and modify toggleLikePost to catch DataIntegrityViolationException (thrown by
likePostRepository.save) and treat it as "already liked" instead of failing
(i.e., on catch, set isLike = true and continue to return the LikePostResponse);
keep existing delete path unchanged and reference methods/classes:
toggleLikePost, LikePost entity, likePostRepository, studentCouncilPostMapper,
StudentCouncilPostService.
- Around line 148-149: In toggleLikePost in StudentCouncilPostService replace
the use of postRepository.findById(postId) with
postRepository.findByIdWithFullInfo(postId) and keep throwing
PostNotFoundException if empty so the method uses the same full-info loading
path as findById/update/delete; update any variable names or expectations around
the loaded StudentCouncilPost accordingly, and ensure any callers still get the
same behavior (no changes to exception type).

In
@src/main/java/com/campus/campus/domain/councilpost/domain/entity/LikePost.java:
- Around line 23-41: Add a unique constraint on the (user_id, post_id) pair to
the LikePost entity to prevent duplicate likes under concurrent requests: modify
the @Table annotation on class LikePost to include a UniqueConstraint for
columns "user_id" and "post_id" and import jakarta.persistence.UniqueConstraint;
no other logic changes needed—this enforces uniqueness at the DB level for the
LikePost entity (fields: likePostId, user, post).
🧹 Nitpick comments (2)
src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostController.java (1)

189-201: 좋아요 게시글 목록 조회 엔드포인트가 잘 구현되어 있습니다.

기능 구현은 정상적이며, 페이지네이션과 필터링 로직이 적절합니다.

다만, Line 200에서 POST_LIST_READ_SUCCESS 응답 코드를 사용하고 있는데, 이는 일반 게시글 목록 조회와 동일한 메시지입니다. 더 명확한 사용자 경험을 위해 "관심 게시글 목록 조회에 성공했습니다." 같은 전용 응답 코드를 고려해볼 수 있습니다.

♻️ 선택적 개선사항: 전용 응답 코드 추가

StudentCouncilPostResponseCode에 새로운 상수를 추가:

POST_LIKED_LIST_READ_SUCCESS(200, HttpStatus.OK, "관심 게시글 목록 조회에 성공했습니다.")

그리고 컨트롤러에서 사용:

-	return CommonResponse.success(StudentCouncilPostResponseCode.POST_LIST_READ_SUCCESS, responses);
+	return CommonResponse.success(StudentCouncilPostResponseCode.POST_LIKED_LIST_READ_SUCCESS, responses);
src/main/java/com/campus/campus/domain/councilpost/domain/entity/LikePost.java (1)

3-10: Import 및 FetchType 사용 방식이 일관성이 없습니다.

라인 3에서 FetchType.*을 static import하고 있으나, 라인 10에서 다시 명시적으로 import하고 있습니다. 또한 라인 35에서는 FetchType.LAZY를, 라인 39에서는 LAZY를 사용하여 일관성이 없습니다.

♻️ 일관성 있는 사용 제안

static import를 제거하고 명시적 사용으로 통일하거나, 반대로 모두 static import 방식으로 통일하세요:

옵션 1: 명시적 사용으로 통일

-import static jakarta.persistence.FetchType.*;
-
 import com.campus.campus.domain.user.domain.entity.User;

그리고 라인 39를 FetchType.LAZY로 변경

옵션 2: static import로 통일

-import jakarta.persistence.FetchType;

그리고 라인 35를 LAZY로 변경

Also applies to: 35-41

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0985482 and cef4b68.

📒 Files selected for processing (8)
  • src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetLikedPostResponse.java
  • src/main/java/com/campus/campus/domain/councilpost/application/dto/response/LikePostResponse.java
  • src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java
  • src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java
  • src/main/java/com/campus/campus/domain/councilpost/domain/entity/LikePost.java
  • src/main/java/com/campus/campus/domain/councilpost/domain/repository/LikePostRepository.java
  • src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostController.java
  • src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostResponseCode.java
🧰 Additional context used
🧬 Code graph analysis (4)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetLikedPostResponse.java (7)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/PostListItemResponse.java (1)
  • PostListItemResponse (8-18)
src/main/java/com/campus/campus/domain/manager/application/dto/response/CouncilApproveOrDenyResponse.java (1)
  • CouncilApproveOrDenyResponse (5-12)
src/main/java/com/campus/campus/domain/manager/application/dto/response/CertifyRequestCouncilResponse.java (1)
  • CertifyRequestCouncilResponse (5-15)
src/main/java/com/campus/campus/domain/school/application/dto/response/CollegeFindResponse.java (1)
  • CollegeFindResponse (5-12)
src/main/java/com/campus/campus/domain/manager/application/dto/response/CertifyRequestCouncilListResponse.java (1)
  • CertifyRequestCouncilListResponse (7-17)
src/main/java/com/campus/campus/domain/place/application/dto/response/LikeResponse.java (1)
  • LikeResponse (3-7)
src/main/java/com/campus/campus/domain/councilnotice/application/dto/response/NoticeListItemResponse.java (1)
  • Builder (7-15)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/LikePostResponse.java (7)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/PostListItemResponse.java (1)
  • PostListItemResponse (8-18)
src/main/java/com/campus/campus/domain/place/application/dto/response/LikeResponse.java (1)
  • LikeResponse (3-7)
src/main/java/com/campus/campus/domain/manager/application/dto/response/CouncilApproveOrDenyResponse.java (1)
  • CouncilApproveOrDenyResponse (5-12)
src/main/java/com/campus/campus/domain/councilpost/application/dto/request/PostRequest.java (1)
  • PostRequest (14-42)
src/main/java/com/campus/campus/domain/school/application/dto/response/CollegeFindResponse.java (1)
  • CollegeFindResponse (5-12)
src/main/java/com/campus/campus/domain/manager/application/dto/response/CertifyRequestCouncilListResponse.java (1)
  • CertifyRequestCouncilListResponse (7-17)
src/main/java/com/campus/campus/domain/councilnotice/application/dto/response/NoticeListItemResponse.java (1)
  • Builder (7-15)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (3)
src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
  • UserNotFoundException (5-9)
src/main/java/com/campus/campus/domain/councilpost/application/exception/PostNotFoundException.java (1)
  • PostNotFoundException (5-9)
src/main/java/com/campus/campus/domain/councilpost/domain/entity/StudentCouncilPost.java (1)
  • Entity (25-98)
src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java (1)
src/main/java/com/campus/campus/domain/councilpost/domain/entity/StudentCouncilPost.java (1)
  • Entity (25-98)
🔇 Additional comments (7)
src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostResponseCode.java (1)

18-19: LGTM!

새로운 응답 코드가 기존 패턴을 잘 따르고 있으며, 좋아요 기능에 적합한 메시지입니다.

src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetLikedPostResponse.java (1)

7-22: LGTM!

DTO 정의가 명확하며, Swagger 문서화도 적절합니다.

src/main/java/com/campus/campus/domain/councilpost/domain/repository/LikePostRepository.java (1)

15-27: LGTM!

리포지토리 구현이 잘 되어 있습니다:

  • @EntityGraph를 통해 N+1 쿼리 문제를 방지하고 있습니다.
  • 카테고리 필터링이 선택적으로 동작하도록 IS NULL 조건을 잘 활용했습니다.
  • 생성 시간 기준 내림차순 정렬이 적절합니다.
src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostController.java (1)

180-187: LGTM!

좋아요 토글 엔드포인트가 잘 구현되어 있습니다:

  • USER 권한 검증이 적절합니다.
  • @CurrentUserId를 통한 사용자 식별이 명확합니다.
  • 응답 코드가 적절합니다.
src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java (2)

66-82: LGTM! 새로운 매퍼 메서드들이 기존 패턴을 잘 따르고 있습니다.

toLikePostResponsetoGetLikedPostResponse 메서드가 깔끔하게 구현되어 있습니다. 특히 toGetLikedPostResponseisEvent() 삼항 연산자 로직이 toPostListItemResponse (라인 32-34)와 일관성 있게 작성되었습니다.


106-111: LGTM! createLikePost 메서드가 적절하게 구현되었습니다.

Builder 패턴을 사용하여 LikePost 엔티티를 생성하며, 기존 createPostImage 메서드와 동일한 패턴을 따르고 있습니다.

src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (1)

165-177: 정렬이 이미 Repository 쿼리에서 처리되고 있습니다.

LikePostRepository.findLikedPosts() 메서드의 @query 주석에 ORDER BY lp.createdAt DESC가 명시되어 있어 "좋아요 누른 순서 내림차순" 요구사항이 이미 충족되고 있습니다. 서비스 레이어에서 추가 정렬 지정은 필요하지 않습니다.

Likely an incorrect or invalid review comment.

Comment on lines 143 to 163
@Transactional
public LikePostResponse toggleLikePost(Long userId, Long postId) {
User user = userRepository.findByIdAndDeletedAtIsNull(userId)
.orElseThrow(UserNotFoundException::new);

StudentCouncilPost post = postRepository.findById(postId)
.orElseThrow(PostNotFoundException::new);

Optional<LikePost> likePost = likePostRepository.findByUserIdAndPost_Id(userId, postId);
boolean isLike;
if (likePost.isPresent()) {
likePostRepository.delete(likePost.get());
isLike = false;
} else {
LikePost newLikePost = studentCouncilPostMapper.createLikePost(user, post);
likePostRepository.save(newLikePost);
isLike = true;
}

return studentCouncilPostMapper.toLikePostResponse(user, post, isLike);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -t f "LikePostRepository.java"

Repository: our-campUS/our-campUS-BE

Length of output: 160


🏁 Script executed:

fd -t f "LikePost.java"

Repository: our-campUS/our-campUS-BE

Length of output: 146


🏁 Script executed:

fd -t f "StudentCouncilPostService.java" --exec wc -l {} \;

Repository: our-campUS/our-campUS-BE

Length of output: 175


🏁 Script executed:

cat -n src/main/java/com/campus/campus/domain/councilpost/domain/repository/LikePostRepository.java

Repository: our-campUS/our-campUS-BE

Length of output: 1331


🏁 Script executed:

cat -n src/main/java/com/campus/campus/domain/councilpost/domain/entity/LikePost.java

Repository: our-campUS/our-campUS-BE

Length of output: 1550


🏁 Script executed:

sed -n '140,170p' src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java

Repository: our-campUS/our-campUS-BE

Length of output: 1118


🏁 Script executed:

fd -t f "\.sql" -o -t f "*migration*" -o -t f "*schema*" | head -20

Repository: our-campUS/our-campUS-BE

Length of output: 184


🏁 Script executed:

rg "UniqueConstraint" src/main/java/com/campus/campus/domain/councilpost/domain/entity/

Repository: our-campUS/our-campUS-BE

Length of output: 50


🏁 Script executed:

sed -n '1,50p' src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java

Repository: our-campUS/our-campUS-BE

Length of output: 2931


🏁 Script executed:

find . -name "*.sql" -type f 2>/dev/null | head -20

Repository: our-campUS/our-campUS-BE

Length of output: 50


🏁 Script executed:

find . -path "*/db/migration*" -type f 2>/dev/null | head -20

Repository: our-campUS/our-campUS-BE

Length of output: 50


🏁 Script executed:

rg "like_post" -i --type sql | head -30

Repository: our-campUS/our-campUS-BE

Length of output: 216


🏁 Script executed:

find . -name "*Flyway*" -o -name "*flyway*" -o -name "*liquibase*" 2>/dev/null | head -10

Repository: our-campUS/our-campUS-BE

Length of output: 50


🏁 Script executed:

rg "DataIntegrityViolationException|UniqueConstraint" src/main/java/ -A 2 -B 2

Repository: our-campUS/our-campUS-BE

Length of output: 3306


🏁 Script executed:

rg "toggleLikePost|LikePost" src/main/java/com/campus/campus/domain/councilpost/application/controller/ -A 5 -B 2

Repository: our-campUS/our-campUS-BE

Length of output: 182


🏁 Script executed:

find src/main/java -path "*councilpost*controller*" -type f

Repository: our-campUS/our-campUS-BE

Length of output: 50


🏁 Script executed:

find src/main/java -path "*council*controller*" -type f

Repository: our-campUS/our-campUS-BE

Length of output: 50


🏁 Script executed:

rg "toggleLikePost" src/main/java/ -A 5 -B 2

Repository: our-campUS/our-campUS-BE

Length of output: 2318


toggleLikePost 메서드의 동시성 문제를 해결하세요.

현재 구현은 race condition이 발생할 수 있습니다. 두 요청이 동시에 좋아요 존재 여부를 조회한 후, 둘 다 새로운 좋아요를 생성하려 할 수 있습니다.

이를 해결하려면:

  1. LikePost 엔티티의 @TableUniqueConstraint를 추가하세요 (예: @UniqueConstraint(columnNames = {"user_id", "post_id"}))
  2. 중복 생성 시 발생하는 DataIntegrityViolationExceptiontoggleLikePost 메서드에서 처리하세요

참고로 LikedPlace 엔티티와 PlaceService에서 이와 동일한 패턴을 사용하고 있습니다.

🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java
around lines 143 - 163, The toggleLikePost method in StudentCouncilPostService
has a race condition when two requests concurrently try to create a LikePost;
add a unique DB constraint on LikePost (annotate the LikePost entity @Table with
@UniqueConstraint(columnNames = {"user_id","post_id"}) to enforce uniqueness)
and modify toggleLikePost to catch DataIntegrityViolationException (thrown by
likePostRepository.save) and treat it as "already liked" instead of failing
(i.e., on catch, set isLike = true and continue to return the LikePostResponse);
keep existing delete path unchanged and reference methods/classes:
toggleLikePost, LikePost entity, likePostRepository, studentCouncilPostMapper,
StudentCouncilPostService.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/main/java/com/campus/campus/domain/councilpost/domain/entity/LikePost.java (1)

3-3: FetchType 사용 스타일 통일을 권장합니다.

Line 3에서 LAZY를 static import하고 있지만, Line 38에서는 FetchType.LAZY를, Line 42에서는 LAZY를 사용하여 일관성이 없습니다. 다음 중 하나의 스타일로 통일하는 것을 권장합니다:

  • 옵션 1: static import를 제거하고 모든 곳에서 FetchType.LAZY 사용
  • 옵션 2: 모든 곳에서 static import된 LAZY 사용
♻️ 옵션 2 (static import 사용) 적용 예시
 	@ManyToOne(fetch = FetchType.LAZY)
 	@JoinColumn(name = "user_id")
 	private User user;

위 코드를 아래와 같이 변경:

-	@ManyToOne(fetch = FetchType.LAZY)
+	@ManyToOne(fetch = LAZY)
 	@JoinColumn(name = "user_id")
 	private User user;

Also applies to: 10-10, 38-38, 42-42

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cef4b68 and c73b0d0.

📒 Files selected for processing (2)
  • src/main/java/com/campus/campus/domain/councilpost/application/dto/response/LikePostResponse.java
  • src/main/java/com/campus/campus/domain/councilpost/domain/entity/LikePost.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/campus/campus/domain/councilpost/application/dto/response/LikePostResponse.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
src/main/java/com/campus/campus/domain/councilpost/domain/entity/LikePost.java (2)

25-27: 유니크 제약 조건이 올바르게 적용되었습니다.

(user_id, post_id) 조합에 대한 유니크 제약 조건이 적절히 설정되어 있어, 동일한 사용자가 같은 게시글에 중복으로 좋아요를 등록하는 것을 데이터베이스 레벨에서 방지합니다. 토글 방식의 좋아요 기능 구현에 필수적인 제약 조건입니다.


24-45: 엔티티 구조가 잘 설계되었습니다.

전체적인 엔티티 구조가 JPA 및 도메인 모델 설계 모범 사례를 따르고 있습니다:

  • Builder 패턴과 적절한 접근 제어자 사용
  • 양방향 관계에서 LAZY 페치 전략으로 N+1 쿼리 방지
  • BaseEntity 상속으로 감사(audit) 필드 관리
  • IDENTITY 전략은 like_posts 테이블에 적합한 선택

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
@src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java:
- Around line 29-41: The mapper's parameter name councilId is misleading because
callers pass a userId, causing post.isWrittenByCouncil(councilId) to compare the
wrong id; rename the parameter in
StudentCouncilPostMapper.toPostListItemResponse from councilId to a neutral name
like entityId (or authorId/writerId) and update all callers (e.g.,
StudentCouncilPostForUserService.mapPostsWithLikes) to pass the correct id
semantics or add an explicit boolean/overload for "writtenByCouncil" so the
method either accepts a councilId when checking post.isWrittenByCouncil(...) or
is clearly labeled for other id types.

In
@src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostForUserController.java:
- Line 39: Remove the redundant method-level @PreAuthorize("hasRole('USER')")
annotations inside the StudentCouncilPostForUserController class since the class
itself already declares @PreAuthorize("hasRole('USER')"); locate any methods in
that controller that repeat the same @PreAuthorize annotation (the ones flagged
in the review) and delete those method-level annotations, leaving the
class-level security annotation intact.
🧹 Nitpick comments (3)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/PostListItemResponse.java (1)

16-17: 타입 일관성을 검토해주세요.

isWriterBoolean(nullable)인 반면 likedboolean(primitive)입니다. liked가 항상 값을 가져야 한다면 현재 구현이 적절하지만, DTO 내 일관성을 위해 의도적인 선택인지 확인하시기 바랍니다.

♻️ 일관성을 위한 제안

만약 두 필드 모두 항상 값이 있다면:

-	Boolean isWriter,
-	boolean liked
+	boolean isWriter,
+	boolean liked

또는 nullable이 필요하다면:

-	Boolean isWriter,
-	boolean liked
+	Boolean isWriter,
+	Boolean liked
src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostForUserController.java (1)

38-60: 메서드 위치를 필드 선언 이후로 이동하세요.

Java 코딩 컨벤션에 따르면 필드 선언(Line 61)은 메서드보다 앞에 위치해야 합니다. 새로 추가된 메서드들을 필드 선언 다음으로 이동하는 것이 좋습니다.

♻️ 권장하는 구조
 @Slf4j
 public class StudentCouncilPostForUserController {
 
+	private final StudentCouncilPostForUserService postService;
+
 	@PostMapping("/{postId}/like")
 	@Operation(summary = "학생회 게시글 좋아요 토글")
 	public CommonResponse<LikePostResponse> togglePostLike(@PathVariable Long postId, @CurrentUserId Long userId) {
 		...
 	}
 
 	@GetMapping("/likes")
 	@Operation(summary = "관심 학생회 게시글 목록 조회")
 	public CommonResponse<Page<GetLikedPostResponse>> getLikedPosts(...) {
 		...
 	}
-
-	private final StudentCouncilPostForUserService postService;
 
 	@GetMapping("/school")
 	@Operation(...)
src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java (1)

100-124: 사용되지 않는 파라미터

currentUserId 파라미터가 선언되었지만 메서드 본문에서 사용되지 않습니다. 필요하지 않다면 제거하거나, 향후 사용 계획이 있다면 주석으로 의도를 명시하세요.

♻️ 수정 제안
-	public GetPostResponseForUser toGetPostResponseForUser(StudentCouncilPost post, List<String> images,
-		Long currentUserId, boolean isLiked) {
+	public GetPostResponseForUser toGetPostResponseForUser(StudentCouncilPost post, List<String> images,
+		boolean isLiked) {

호출부도 함께 수정이 필요합니다:

-		return studentCouncilPostMapper.toGetPostResponseForUser(post, imageUrls, userId, isLiked);
+		return studentCouncilPostMapper.toGetPostResponseForUser(post, imageUrls, isLiked);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 682f910 and 49de9e8.

📒 Files selected for processing (10)
  • src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostResponse.java
  • src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostResponseForUser.java
  • src/main/java/com/campus/campus/domain/councilpost/application/dto/response/PostListItemResponse.java
  • src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java
  • src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java
  • src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java
  • src/main/java/com/campus/campus/domain/councilpost/domain/repository/LikePostRepository.java
  • src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostController.java
  • src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostForUserController.java
  • src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostResponseCode.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostController.java
🧰 Additional context used
🧬 Code graph analysis (3)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostResponseForUser.java (2)
src/main/java/com/campus/campus/domain/councilpost/application/dto/request/PostRequest.java (1)
  • PostRequest (14-42)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/PostResponse.java (1)
  • Builder (13-35)
src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostForUserController.java (1)
src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostController.java (1)
  • RestController (32-167)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java (4)
src/main/java/com/campus/campus/domain/councilpost/application/exception/CollegeNotSetException.java (1)
  • CollegeNotSetException (5-9)
src/main/java/com/campus/campus/domain/councilpost/application/exception/MajorNotSetException.java (1)
  • MajorNotSetException (5-9)
src/main/java/com/campus/campus/domain/councilpost/application/exception/PostNotFoundException.java (1)
  • PostNotFoundException (5-9)
src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
  • UserNotFoundException (5-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (16)
src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostResponseCode.java (1)

21-22: 토글(좋아요/취소) 동작과 POST_LIKE_SUCCESS 의미가 정확히 맞는지 확인 필요

현재 메시지가 “좋아요 처리”로 되어 있어 토글(좋아요/취소) 모두를 포괄하는 건 자연스럽습니다. 다만 클라이언트가 “최종 상태(좋아요됨/취소됨)”를 응답코드로 구분해야 하는 요구사항이 있다면, (1) 응답 바디에 최종 상태를 명시하거나 (2) 코드 상수를 like/unlike로 분리하는 편이 더 명확합니다.

src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostResponseForUser.java (1)

13-36: 새로운 사용자 전용 응답 DTO가 적절하게 구현되었습니다.

GetPostResponseForUser는 사용자별 좋아요 상태(isLiked)를 포함하여 기존 PostResponse와 명확하게 분리되어 있습니다. 구조와 어노테이션 사용이 적절합니다.

src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostResponse.java (1)

15-15: 네이밍 변경이 적절합니다.

PostResponse에서 GetPostResponse로의 리네이밍은 응답 DTO의 용도를 더 명확하게 표현합니다.

src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (1)

18-18: 반환 타입 변경이 일관되게 적용되었습니다.

PostResponse에서 GetPostResponse로의 변경이 모든 메서드(create, findById, update)와 매퍼 호출에 일관되게 적용되었습니다.

Also applies to: 53-53, 86-86, 90-90, 100-100, 181-181, 233-233

src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostForUserController.java (3)

38-45: 좋아요 토글 엔드포인트가 적절하게 구현되었습니다.

토글 방식으로 좋아요/좋아요 취소를 하나의 엔드포인트로 처리하는 것은 RESTful API 설계에서 일반적인 패턴입니다.


47-59: 관심 게시글 조회 엔드포인트가 적절하게 구현되었습니다.

카테고리 필터링과 페이지네이션을 지원하는 구조가 잘 설계되어 있습니다.


113-119: 사용자 전용 응답 타입으로의 변경이 적절합니다.

GetPostResponseForUser를 사용하여 사용자별 좋아요 상태를 포함한 게시글 상세 정보를 제공하는 것은 적절한 분리입니다.

src/main/java/com/campus/campus/domain/councilpost/domain/repository/LikePostRepository.java (2)

21-30: LGTM! EntityGraph와 JPQL 쿼리가 적절하게 구성되었습니다.

N+1 문제를 방지하기 위해 postpost.writer를 함께 로딩하는 EntityGraph 설정이 잘 되어 있습니다. 카테고리 필터링을 위한 nullable 파라미터 처리도 적절합니다.


32-37: 빈 리스트는 이미 적절히 처리되고 있습니다

postIds가 빈 리스트일 경우의 잠재적 이슈는 이미 서비스 레이어에서 명시적으로 처리되고 있습니다. mapPostsWithLikes 메서드의 267-269줄에서 if (postIds.isEmpty()) 가드 절이 빈 리스트일 때 findLikedPostIds 호출을 방지하므로, 현재 구현은 안전하고 추가 수정은 불필요합니다.

src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java (4)

79-90: LGTM!

사용자 존재 여부 검증 후 관심 게시글을 조회하는 로직이 명확합니다. 페이지네이션 처리도 적절합니다.


92-109: LGTM!

게시글 상세 조회 로직이 잘 구현되었습니다. 권한 검증, 이미지 조회, 좋아요 상태 확인이 적절하게 처리됩니다.


262-276: 잘 설계된 헬퍼 메서드입니다.

N+1 문제를 방지하기 위해 배치 쿼리로 좋아요 상태를 조회하고, HashSet으로 변환하여 O(1) 조회 성능을 확보한 점이 좋습니다. 빈 리스트에 대한 early return 처리도 적절합니다.


119-119: 일관된 리팩토링 패턴

모든 게시글 목록 조회 메서드에서 mapPostsWithLikes를 사용하여 좋아요 상태를 일관되게 처리하는 패턴이 잘 적용되었습니다.

src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java (3)

75-98: LGTM!

빌더 패턴 사용과 null 처리가 적절합니다. 이벤트/제휴 유형에 따른 날짜 필드 분기 처리도 일관성 있게 구현되었습니다.


126-142: LGTM!

toLikePostResponsetoGetLikedPostResponse 매핑 메서드가 간결하고 명확하게 구현되었습니다.


166-171: LGTM!

LikePost 엔티티 생성 로직이 적절합니다. createdAt 필드는 엔티티의 @CreatedDate 어노테이션으로 자동 설정될 것으로 예상됩니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java (1)

99-123: 게시글 작성자 null 체크 필요 (사용자용 응답)

toGetPostResponse와 동일하게, Line 101에서 post.getWriter()의 결과를 null 체크 없이 Lines 104-105에서 사용하고 있습니다. NPE 위험이 있으므로 null 체크 로직을 추가하거나 엔티티 레벨에서 제약을 보장해야 합니다.

🛡️ NPE 방지를 위한 수정 제안
 public GetPostForUserResponse toGetPostForUserResponse(StudentCouncilPost post, List<String> images,
 	Long currentUserId, boolean isLiked) {
 	var writer = post.getWriter();
+	if (writer == null) {
+		throw new IllegalStateException("게시글에 작성자 정보가 없습니다.");
+	}
 	var builder = GetPostForUserResponse.builder()
🤖 Fix all issues with AI agents
In
@src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java:
- Around line 74-97: The toGetPostResponse method uses post.getWriter() without
null-check, which can NPE on writer.getId() / writer.getCouncilName(); update
toGetPostResponse to safely handle a null writer by fetching var writer =
post.getWriter(); then check if writer == null and set builder.writerId(null)
and builder.writerName(null) or a sensible default (e.g., "Unknown") before
calling writer.getId() / writer.getCouncilName(); ensure
isWriter(post.isWrittenByCouncil(currentCouncilId)) still works when writer is
null (guard or compute using post.isWrittenByCouncil which should tolerate
null), and return builder.build().

In
@src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java:
- Around line 58-77: The toggleLikePost method in
StudentCouncilPostForUserService uses a delete-then-insert pattern and currently
treats any DataIntegrityViolationException as a unique-constraint conflict;
update the exception handling to explicitly detect a unique-constraint violation
before assuming the like already exists (e.g., inspect SQLState, vendor-specific
error code, or constraint name from the DataIntegrityViolationException/cause),
rethrow or wrap other constraint violations (foreign key, not-null, etc.), and
keep the existing delete-then-insert flow otherwise; ensure changes are applied
around the likePostRepository.saveAndFlush call and in the catch for
DataIntegrityViolationException so only true unique key violations return the
"true" response while other DB errors propagate as errors.
🧹 Nitpick comments (1)
src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostForUserController.java (1)

38-44: 경로 변수에 대한 유효성 검증 추가를 권장합니다.

좋아요 토글 엔드포인트가 잘 구현되었습니다. 다만 @PathVariable@Positive 같은 검증 어노테이션을 추가하면 잘못된 ID 값에 대한 처리가 더 명확해집니다.

♻️ 제안하는 개선 사항
 @PostMapping("/{postId}/like")
 @Operation(summary = "학생회 게시글 좋아요 토글")
-public CommonResponse<LikePostResponse> togglePostLike(@PathVariable Long postId, @CurrentUserId Long userId) {
+public CommonResponse<LikePostResponse> togglePostLike(@PathVariable @Positive Long postId, @CurrentUserId Long userId) {
 	LikePostResponse response = postService.toggleLikePost(userId, postId);
 
 	return CommonResponse.success(StudentCouncilPostResponseCode.POST_LIKE_SUCCESS, response);
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 49de9e8 and a5f80d0.

📒 Files selected for processing (6)
  • src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostForUserResponse.java
  • src/main/java/com/campus/campus/domain/councilpost/application/dto/response/PostListItemResponse.java
  • src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java
  • src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java
  • src/main/java/com/campus/campus/domain/councilpost/domain/repository/LikePostRepository.java
  • src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostForUserController.java
🧰 Additional context used
🧬 Code graph analysis (5)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/PostListItemResponse.java (2)
src/main/java/com/campus/campus/domain/councilnotice/application/dto/response/NoticeListItemResponse.java (1)
  • Builder (7-15)
src/main/java/com/campus/campus/domain/place/application/dto/response/LikeResponse.java (1)
  • LikeResponse (3-7)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostForUserResponse.java (4)
src/main/java/com/campus/campus/domain/councilpost/application/dto/request/PostRequest.java (1)
  • PostRequest (14-42)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/PostResponse.java (1)
  • Builder (13-35)
src/main/java/com/campus/campus/domain/councilpost/domain/entity/StudentCouncilPost.java (1)
  • Entity (25-98)
src/main/java/com/campus/campus/domain/councilpost/domain/entity/PostImage.java (1)
  • Entity (18-36)
src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostForUserController.java (1)
src/main/java/com/campus/campus/domain/place/presentation/PlaceController.java (1)
  • PostMapping (36-42)
src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java (7)
src/main/java/com/campus/campus/domain/council/application/util/CouncilNameGenerator.java (1)
  • Component (8-34)
src/main/java/com/campus/campus/domain/councilnotice/application/mapper/StudentCouncilNoticeMapper.java (1)
  • Component (16-58)
src/main/java/com/campus/campus/global/oci/mapper/PresignedUrlMapper.java (1)
  • Component (14-45)
src/main/java/com/campus/campus/domain/councilpost/domain/entity/StudentCouncilPost.java (1)
  • Entity (25-98)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (3)
  • post (110-111)
  • Slf4j (37-255)
  • post (128-129)
src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostController.java (2)
  • PatchMapping (113-124)
  • PostMapping (40-111)
src/main/java/com/campus/campus/domain/councilpost/application/service/UserPostService.java (1)
  • post (84-84)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java (4)
src/main/java/com/campus/campus/domain/councilpost/application/exception/CollegeNotSetException.java (1)
  • CollegeNotSetException (5-9)
src/main/java/com/campus/campus/domain/councilpost/application/exception/MajorNotSetException.java (1)
  • MajorNotSetException (5-9)
src/main/java/com/campus/campus/domain/councilpost/application/exception/PostNotFoundException.java (1)
  • PostNotFoundException (5-9)
src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
  • UserNotFoundException (5-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (16)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/PostListItemResponse.java (1)

8-17: LGTM! 좋아요 상태를 나타내는 필드로 적절하게 변경되었습니다.

isWriter에서 liked로 변경하여 좋아요 기능을 지원하도록 DTO를 업데이트했습니다. primitive boolean 타입 사용도 적절합니다.

src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostForUserResponse.java (1)

13-36: LGTM! 사용자 전용 게시글 응답 DTO가 잘 설계되었습니다.

좋아요 상태(isLiked)와 이미지 목록을 포함하여 사용자 관점의 게시글 정보를 적절히 표현하고 있습니다. @JsonInclude(NON_NULL) 사용도 적절합니다.

src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostForUserController.java (2)

46-57: LGTM! 관심 게시글 조회 엔드포인트가 잘 구현되었습니다.

카테고리 필터링과 페이지네이션이 적절히 구현되어 있습니다. size 기본값이 3으로 다른 엔드포인트(20)보다 작은데, 관심 게시글 목록 특성상 의도적인 것으로 보입니다.


109-118: LGTM! 게시글 상세 조회가 좋아요 상태를 포함하도록 업데이트되었습니다.

GetPostForUserResponse로 응답 타입을 변경하여 사용자별 좋아요 상태를 함께 반환할 수 있게 되었습니다.

src/main/java/com/campus/campus/domain/councilpost/domain/repository/LikePostRepository.java (4)

18-23: LGTM! 삭제 쿼리가 올바르게 구현되었습니다.

@Modifying(clearAutomatically = true)를 사용하여 삭제 후 영속성 컨텍스트를 적절히 관리하고 있으며, 반환 타입 int를 통해 삭제 여부를 확인할 수 있습니다.


25-25: LGTM! 존재 여부 확인 메서드가 적절합니다.

Spring Data JPA의 derived query 규칙에 따라 올바르게 작성되었으며, 좋아요 상태 확인에 효율적입니다.


38-43: LGTM! 배치 조회 쿼리가 효율적으로 구현되었습니다.

여러 게시글의 좋아요 상태를 한 번의 쿼리로 확인할 수 있어 N+1 문제를 방지합니다. 서비스의 mapPostsWithLikes에서 효과적으로 활용되고 있습니다.


27-36: @entitygraph에서 post.writer 제거를 권장합니다.

GetLikedPostResponse는 게시글의 id, title, place, 시간, thumbnailImageUrl만 포함하며 작성자 정보를 사용하지 않습니다. 따라서 현재 post.writer를 로드하는 것은 불필요한 N+1 쿼리를 발생시킵니다. @EntityGraph(attributePaths = {"post"})로 단순화하거나, 작성자 정보가 실제로 필요 없다면 @entitygraph 자체를 제거하고 레이지 로딩에 의존하는 것이 좋습니다.

Likely an incorrect or invalid review comment.

src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java (4)

79-90: LGTM! 관심 게시글 조회가 깔끔하게 구현되었습니다.

사용자 검증, 페이지네이션 처리, 응답 매핑이 모두 적절합니다. Math.max(page - 1, 0)로 1-based 페이지 번호를 안전하게 처리하고 있습니다.


92-109: LGTM! 게시글 상세 조회가 좋아요 상태를 포함하도록 개선되었습니다.

권한 검증, 이미지 로딩, 좋아요 상태 확인이 모두 적절히 구현되어 있습니다. 단건 조회이므로 isLiked 체크를 위한 추가 쿼리의 오버헤드는 허용 가능한 수준입니다.


262-276: LGTM! 좋아요 상태를 효율적으로 배치 처리하고 있습니다.

게시글 목록 조회 시 N+1 문제를 방지하기 위해 좋아요한 게시글 ID를 배치로 조회하고, HashSet을 사용하여 O(1) 시간 복잡도로 조회하는 최적화가 훌륭합니다.


119-119: LGTM! 모든 목록 조회 메서드에 좋아요 상태가 일관되게 적용되었습니다.

mapPostsWithLikes 헬퍼 메서드를 활용하여 학교/단과대/전공 게시글 및 임박한 행사 조회 모두에 좋아요 상태를 일관되게 제공하고 있습니다.

Also applies to: 134-134, 145-145, 165-165, 193-193, 221-221

src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java (4)

125-131: LGTM!

좋아요 응답 매핑 로직이 명확하고 올바르게 구현되었습니다.


133-141: LGTM!

좋아요한 게시글 응답 매핑이 올바르게 구현되었습니다. Line 138의 날짜 선택 로직(행사는 startDateTime, 제휴는 endDateTime)도 다른 메서드들과 일관성 있게 작성되었습니다.


165-170: LGTM!

LikePost 엔티티 생성 로직이 빌더 패턴을 사용하여 명확하게 구현되었습니다.


29-40: 메서드 시그니처 변경이 모든 호출부에서 올바르게 적용되었습니다. StudentCouncilPostForUserService의 두 호출 지점 모두 boolean isLiked 파라미터를 올바르게 전달하고 있으며, 호환성 문제는 없습니다.

Comment on lines +74 to +97
public GetPostResponse toGetPostResponse(StudentCouncilPost post, List<String> images, Long currentCouncilId) {
var writer = post.getWriter();
var builder = GetPostResponse.builder()
.id(post.getId())
.writerId(writer.getId())
.writerName(writer.getCouncilName())
.isWriter(post.isWrittenByCouncil(currentCouncilId))
.category(post.getCategory())
.title(post.getTitle())
.content(post.getContent())
.place(post.getPlace())
.thumbnailImageUrl(post.getThumbnailImageUrl())
.thumbnailIcon(post.getThumbnailIcon())
.images(images != null ? images : Collections.emptyList());

if (post.isEvent()) {
builder.startDateTime(post.getStartDateTime());
} else {
builder.startDate(post.getDisplayStartDate());
builder.endDate(post.getDisplayEndDate());
}

return builder.build();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

게시글 작성자 null 체크 필요

Line 75에서 post.getWriter()의 결과를 null 체크 없이 사용하고 있습니다. StudentCouncilPost 엔티티의 writer 필드는 @ManyToOne으로 정의되어 있으나 @NotNull 제약이 없어 null일 가능성이 있습니다. Lines 78-79에서 writer.getId()writer.getCouncilName() 호출 시 NPE가 발생할 수 있습니다.

🛡️ NPE 방지를 위한 수정 제안
 public GetPostResponse toGetPostResponse(StudentCouncilPost post, List<String> images, Long currentCouncilId) {
 	var writer = post.getWriter();
+	if (writer == null) {
+		throw new IllegalStateException("게시글에 작성자 정보가 없습니다.");
+	}
 	var builder = GetPostResponse.builder()
 		.id(post.getId())
 		.writerId(writer.getId())

또는 서비스 계층에서 writer가 항상 존재함을 보장하는 경우, 엔티티에 @NotNull 제약을 추가하는 것을 권장합니다.

🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java
around lines 74 - 97, The toGetPostResponse method uses post.getWriter() without
null-check, which can NPE on writer.getId() / writer.getCouncilName(); update
toGetPostResponse to safely handle a null writer by fetching var writer =
post.getWriter(); then check if writer == null and set builder.writerId(null)
and builder.writerName(null) or a sensible default (e.g., "Unknown") before
calling writer.getId() / writer.getCouncilName(); ensure
isWriter(post.isWrittenByCouncil(currentCouncilId)) still works when writer is
null (guard or compute using post.isWrittenByCouncil which should tolerate
null), and return builder.build().

@our-campUS our-campUS deleted a comment from coderabbitai bot Jan 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨Feat 새로운 기능 개발 ♻️Refactor 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 일반 유저 관심 제휴 혹은 행사 좋아요 누르기 기능 구현

2 participants