-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/#37 리뷰 관련 기능 구현(ocr제외) #55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough리뷰 기능(엔티티·저장소·서비스·컨트롤러·매퍼·DTO·예외)과 커서 기반 페이징 및 인기 파트너십 조회가 추가되었고, PlaceService.findOrCreatePlace 시그니처가 변경되며 StudentCouncilPostRepository에 파트너십 추천 JPQL 메서드가 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant ReviewController
participant ReviewService
participant UserRepo as UserRepository
participant PlaceService
participant ReviewRepo as ReviewRepository
participant ReviewImageRepo as ReviewImageRepository
participant ReviewMapper
Client->>ReviewController: POST /reviews (ReviewRequest)
ReviewController->>ReviewService: writeReview(request, userId)
ReviewService->>UserRepo: findById(userId)
ReviewService->>PlaceService: findOrCreatePlace(SavedPlaceInfo)
ReviewService->>ReviewRepo: save(Review)
ReviewService->>ReviewImageRepo: save(ReviewImage) [n]
ReviewService->>ReviewMapper: toReviewCreateResponse(...)
ReviewService->>ReviewController: ReviewCreateResponse
ReviewController->>Client: CommonResponse<ReviewCreateResponse>
sequenceDiagram
participant Client
participant ReviewController
participant ReviewService
participant ReviewRepo as ReviewRepository
participant ReviewImageRepo as ReviewImageRepository
participant ReviewMapper
Client->>ReviewController: GET /reviews/list/{placeId}?cursor...
ReviewController->>ReviewService: getReviewList(placeId, cursorCreatedAt, cursorId, size)
ReviewService->>ReviewRepo: findByPlaceIdWithCursor(placeId, cursorCreatedAt, cursorId, pageable)
ReviewService->>ReviewImageRepo: findAllByReviewIdInOrderByIdAsc(reviewIds)
ReviewService->>ReviewMapper: toCursorReviewResponse(items, last, hasNext)
ReviewService->>ReviewController: CursorPageReviewResponse
ReviewController->>Client: CommonResponse<CursorPageReviewResponse>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 분 Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
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/user/presentation/AuthController.java (1)
26-32: 카카오 access token을query param으로 받는 방식은 유출 리스크가 큽니다.
token이 URL에 노출되어 서버/프록시 로그, 브라우저 히스토리, Referer 등에 남을 수 있어요. 가능하면 헤더(또는 바디)로 받는 형태로 바꾸는 걸 권장합니다.제안 변경안 (헤더로 수신)
import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; ... @PostMapping("/login/kakao") @Operation(summary = "카카오 로그인 (Native App 방식)") - public CommonResponse<OauthLoginResponse> kakaoLogin(@RequestParam("token") String kakaoAccessToken) { + public CommonResponse<OauthLoginResponse> kakaoLogin(@RequestHeader("X-Kakao-Access-Token") String kakaoAccessToken) { OauthLoginResponse response = kakaoOauthService.login(kakaoAccessToken); return CommonResponse.success(UserResponseCode.LOGIN_SUCCESS, response); }
🤖 Fix all issues with AI agents
In
@src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java:
- Around line 312-321: The JPQL in findRepresentativePosts references a
:writerId parameter (scp.writer.id = :writerId) but the method signature for
findRepresentativePosts lacks @Param("writerId") Long writerId, causing runtime
binding errors; fix by adding a method parameter annotated with
@Param("writerId") Long writerId to the findRepresentativePosts signature so the
query can bind, or if filtering by writer is not intended, remove the
scp.writer.id = :writerId clause from the @Query to match the existing parameter
list.
- Around line 282-310: The native query in findTop3RecommendedPartnershipPlaces
selects only StudentCouncilPost (SELECT scp.*), causing Place to be lazy-loaded
and N+1 queries; fix by eagerly loading Place: add an
@EntityGraph(attributePaths = {"place"}) on the
findTop3RecommendedPartnershipPlaces method and convert the native SQL to a JPQL
query (or remove nativeQuery=true) that joins the related entities (writer,
place, reviews) so JPA can apply the EntityGraph / fetch join and avoid separate
Place select calls. Ensure the method signature and parameter names remain the
same so the repository wiring still works.
In
@src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java:
- Around line 19-21: The field 'star' in ReviewRequest is declared as primitive
double with @NotNull which is ineffective; change its type to the wrapper Double
for null validation to work, keep or add @NotNull on the 'star' field, and add
range validation using @DecimalMin("0.0") and @DecimalMax("5.0") (import
jakarta.validation.constraints.DecimalMin and DecimalMax) to enforce the 0.0–5.0
bounds; update any constructors/builders/factory methods that set or accept star
to use Double accordingly.
In
@src/main/java/com/campus/campus/domain/review/application/exception/ErrorCode.java:
- Line 14: Update the ErrorCode enum entry REVIEW_NOT_FOUND to use the
domain-appropriate message by replacing "게시글을 찾을 수 없습니다." with "리뷰를 찾을 수 없습니다."
so the ErrorCode.REVIEW_NOT_FOUND constant reflects the review domain correctly.
In
@src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java:
- Around line 272-277: In cleanupUnusedImages replace the wrong catch type
PostOciImageDeleteFailedException with the actual exception thrown by
presignedUrlService.deleteImage(imageUrl): inspect
presignedUrlService.deleteImage's signature to find the declared/checked
exception (or, if it only throws unchecked exceptions, catch the appropriate
RuntimeException or remove the try/catch), then update the catch to that
exception type and keep the existing log.warn call using imageUrl and the caught
exception.
- Around line 59-66: The code in ReviewService.writeReview uses
UserNotFirstLoginException when userRepository.findById(userId) returns empty;
replace that with UserNotFoundException so missing-user cases use the correct
exception. Update the orElseThrow call on userRepository.findById in the
writeReview method to throw new UserNotFoundException (or use a method
reference) instead of UserNotFirstLoginException, keeping all other logic
unchanged.
- Around line 174-187: The getReviewList method declares a cursorStar parameter
but calls reviewRepository.findByPlaceIdWithCursor without a matching parameter;
fix by either removing cursorStar from getReviewList and all callers if it's
unused, or update ReviewRepository.findByPlaceIdWithCursor to accept cursorStar
and propagate it through the query implementation; locate getReviewList and the
repository method findByPlaceIdWithCursor and make their signatures and usages
consistent (either drop cursorStar everywhere or add cursorStar to the
repository interface and its query implementation).
- Around line 127-133: The catch block in ReviewService's image deletion loop is
using the wrong exception type; replace catching
PostOciImageDeleteFailedException with the actual exception thrown by
PresignedUrlService.deleteImage(), OciObjectDeleteFailException, and update
imports if necessary so the OciObjectDeleteFailException is caught and logged
(log.warn("OCI 파일 삭제 실패: {}", imageUrl, e)) to prevent the real exception from
propagating.
In @src/main/java/com/campus/campus/domain/review/domain/entity/Review.java:
- Around line 22-28: Replace the Lombok @Builder on the Review entity with
@SuperBuilder to match BaseEntity’s use of @SuperBuilder so inherited fields
(e.g., createdAt, updatedAt) are included in the builder; update the Review
class annotation from @Builder to @SuperBuilder (class: Review) and add the
lombok.experimental.SuperBuilder import.
In
@src/main/java/com/campus/campus/domain/review/domain/entity/ReviewImages.java:
- Around line 26-29: The field name ReviewImagesId in class ReviewImages
violates Java camelCase; rename the field to reviewImagesId and update all
references (constructors, getters/setters, equals/hashCode, any usages in
repository/DTOs, and JPA mappings) to use reviewImagesId while keeping the
@Column(name = "review_images_id") and @Id/@GeneratedValue annotations intact so
database mapping is unchanged.
- Around line 19-24: The ReviewImages entity extends BaseEntity which uses
Lombok's @SuperBuilder, so add @SuperBuilder to the ReviewImages class (above
the class declaration) and import lombok.experimental.SuperBuilder; also ensure
a public or appropriate access builder is available (keep existing
@AllArgsConstructor/@NoArgsConstructor access levels) so builder inheritance
works correctly for ReviewImages.
In
@src/main/java/com/campus/campus/domain/review/domain/repository/ReviewImagesRepository.java:
- Around line 1-8: The repository ReviewImagesRepository and entity ReviewImages
duplicate the existing ReviewImage/ReviewImageRepository pair; remove the
redundant ReviewImages entity and ReviewImagesRepository and update any usages
to point to ReviewImage and ReviewImageRepository instead. Search for references
to ReviewImagesRepository and ReviewImages (imports, service/adapter code,
tests) and replace them with ReviewImageRepository and ReviewImage, or if the
two were intentionally separate, add clear class-level Javadoc and rename to
reflect distinct roles; otherwise delete the duplicate files and run
compilation/tests to ensure only ReviewImage/ReviewImageRepository remain.
In
@src/main/java/com/campus/campus/domain/review/presentation/ReviewController.java:
- Around line 84-89: The readReview handler in ReviewController currently
returns the save success code; update readReview to return an appropriate
read/get response code by replacing ReviewResponseCode.REVIEW_SAVE_SUCCESS with
a GET-style constant (e.g., ReviewResponseCode.GET_REVIEW_SUCCESS or
ReviewResponseCode.GET_REVIEW_LIST_SUCCESS), or add a new enum value
GET_REVIEW_SUCCESS in ReviewResponseCode and use that in the
CommonResponse.success call; ensure the change is made in the readReview method
so the response code matches the operation.
- Around line 113-119: Remove the invalid @DateTimeFormat annotation from the
Long cursorId parameter in ReviewController (keep @DateTimeFormat only on
cursorCreatedAt: LocalDateTime), and update the call to
reviewService.getReviewList to match the service method signature exactly (pass
the correct parameters in the correct order, e.g., placeId, cursorCreatedAt,
cursorId, size or whatever the current ReviewService.getReviewList signature
expects) so parameter count and types align with ReviewService.getReviewList;
ensure the method returns and is assigned to
CursorPageReviewResponse<ReviewResponse> as before.
🧹 Nitpick comments (11)
src/main/java/com/campus/campus/domain/review/application/dto/response/RankingScope.java (1)
6-9: LGTM - RankingScope DTO 구조 적절scope와 rank 필드로 랭킹 범위 정보를 명확하게 표현하고 있습니다.
@Builder애노테이션을 통해 유연한 객체 생성이 가능합니다.선택적 개선: 주석 명확성 향상
Line 8의 주석을 더 명확하게 개선할 수 있습니다:
- long rank //n번째 리뷰 + long rank // 해당 scope 내에서의 리뷰 순위 (예: 학과 내 3번째 리뷰)src/main/java/com/campus/campus/domain/review/domain/entity/ReviewImage.java (1)
31-33:optional = false추가를 고려하세요.
NoticeImage엔티티에서는@ManyToOne(fetch = FetchType.LAZY, optional = false)를 사용하고 있습니다.ReviewImage도 리뷰 없이 존재할 수 없다면optional = false를 추가하여 데이터 무결성을 명시적으로 표현하는 것이 좋습니다.♻️ 수정 제안
- @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "review_id") private Review review;src/main/java/com/campus/campus/domain/review/application/dto/response/PlaceReviewRankResponse.java (1)
5-13: 주석 처리된 코드 정리 및 API 문서화를 권장합니다.
- Line 12의
// double distance주석은 필요하다면 TODO 주석으로 남기거나, 불필요하면 제거하세요.- 다른 Response DTO(예:
PartnershipResponse)처럼@Schema어노테이션을 추가하면 Swagger 문서화에 도움이 됩니다.♻️ 수정 제안
+import io.swagger.v3.oas.annotations.media.Schema; + @Builder public record PlaceReviewRankResponse( + @Schema(description = "장소 ID", example = "1") Long placeId, + @Schema(description = "장소 이름", example = "매머드익스프레스") String placeName, + @Schema(description = "카테고리", example = "카페") String category, + @Schema(description = "제휴 정보") String partnership, + @Schema(description = "썸네일 URL") String thumbnailUrl - // double distance ) { }src/main/java/com/campus/campus/domain/review/application/dto/response/ReviewCreateResult.java (1)
5-12: 주석 처리된 필드를 정리하세요.Line 9의
// int NumberOfUserStamp는 구현 예정이라면 TODO 주석으로 명확히 하거나, 불필요하면 제거하세요. 추후 추가 시 Java 네이밍 컨벤션에 따라numberOfUserStamp로 수정이 필요합니다.♻️ 수정 제안
@Builder public record ReviewCreateResult( boolean isFirstReviewOfPlace, int userReviewCountOfPlace, - // int NumberOfUserStamp, + // TODO: 스탬프 기능 구현 시 추가 - int numberOfUserStamp, String message ) { }또는 불필요하면 주석을 완전히 제거하세요.
src/main/java/com/campus/campus/domain/review/application/dto/response/CursorPageReviewResponse.java (1)
10-15: 제네릭 타입 파라미터가 실제 클래스 이름을 가립니다.
<ReviewResponse>타입 파라미터가 실제ReviewResponse클래스 이름과 동일하여 혼란을 야기합니다. 진정한 제네릭 클래스가 아니라면 제네릭을 제거하거나, 재사용 가능한 제네릭 클래스가 필요하다면<T>와 같은 관례적인 이름을 사용하세요.♻️ 옵션 1: 제네릭 제거 (ReviewResponse 전용인 경우)
-public class CursorPageReviewResponse<ReviewResponse> { - private List<ReviewResponse> items; +public class CursorPageReviewResponse { + private List<com.campus.campus.domain.review.application.dto.response.ReviewResponse> items;♻️ 옵션 2: 표준 제네릭 명명 사용 (재사용 가능한 경우)
-public class CursorPageReviewResponse<ReviewResponse> { - private List<ReviewResponse> items; +public class CursorPageResponse<T> { + private List<T> items;src/main/java/com/campus/campus/domain/review/domain/repository/ReviewImageRepository.java (1)
13-17: 중복 메서드 고려 필요
findAllByReviewOrderByIdAsc와findAllByReview는 정렬 여부만 다릅니다. 실제로 두 메서드가 모두 필요한지 검토해 주세요. 또한 Line 15의@Param("reviewIds")는 Spring Data JPA 파생 쿼리 메서드에서는 불필요합니다.♻️ 제안된 수정
- List<ReviewImage> findAllByReviewIdInOrderByIdAsc(@Param("reviewIds") List<Long> reviewIds); + List<ReviewImage> findAllByReviewIdInOrderByIdAsc(List<Long> reviewIds);src/main/java/com/campus/campus/domain/review/domain/repository/ReviewRepository.java (1)
33-41: 파라미터 타입 일관성 검토일부 메서드는
long(primitive)를 사용하고, 일부는Long(wrapper)를 사용합니다. null 값 처리를 고려하여 일관된 타입 사용을 권장합니다.♻️ 일관된 타입 사용 제안
- long countByPlace_PlaceId(long placeId); - long countByPlace_PlaceIdAndUser_Id(long placeId, long userId); - long countByPlace_PlaceIdAndUser_Major_MajorId(long placeId, long majorId); - long countByPlace_PlaceIdAndUser_College_CollegeId(Long placeId, long collegeId); - long countByPlace_PlaceIdAndUser_School_SchoolId(Long placeId, long schoolId); + long countByPlace_PlaceId(Long placeId); + long countByPlace_PlaceIdAndUser_Id(Long placeId, Long userId); + long countByPlace_PlaceIdAndUser_Major_MajorId(Long placeId, Long majorId); + long countByPlace_PlaceIdAndUser_College_CollegeId(Long placeId, Long collegeId); + long countByPlace_PlaceIdAndUser_School_SchoolId(Long placeId, Long schoolId);src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java (1)
244-244: 불필요한 stream().toList() 호출
partnerships는 이미List타입이므로.stream().toList()는 불필요합니다.♻️ 수정 제안
- log.info("찾은 결과:{}", partnerships.stream().toList()); + log.info("찾은 결과:{}", partnerships);src/main/java/com/campus/campus/domain/review/application/mapper/ReviewMapper.java (3)
66-74: 날짜 형식 일관성 고려
last.getCreatedAt().toString()은 JVM 기본 형식을 사용합니다. 클라이언트에서 파싱할 때 일관성을 위해 ISO 형식을 명시적으로 사용하는 것이 좋습니다.♻️ 수정 제안
public CursorPageReviewResponse<ReviewResponse> toCursorReviewResponse(List<ReviewResponse> items, Review last, boolean hasNext) { return CursorPageReviewResponse.<ReviewResponse>builder() .items(items) - .nextCursorCreatedAt(last.getCreatedAt().toString()) + .nextCursorCreatedAt(last.getCreatedAt().format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME)) .nextCursorId(last.getId()) .hasNext(hasNext) .build(); }
76-83: long에서 int로의 캐스팅 주의
userReviewCountOfPlace를long에서int로 캐스팅하고 있습니다. 실제로 오버플로가 발생할 가능성은 낮지만, DTO 필드 타입을long으로 변경하거나Math.toIntExact()를 사용하여 안전하게 처리하는 것이 좋습니다.
106-117: 주석 처리된 코드 제거Line 115의 주석 처리된 코드(
// .reviewCount(projection.getReviewCount()))는 제거하거나, 필요한 경우 TODO 주석과 함께 이슈로 추적하는 것이 좋습니다.♻️ 수정 제안
public PlaceReviewRankResponse toTopPartnershipResponse( StudentCouncilPost post ) { return PlaceReviewRankResponse.builder() .placeId(post.getPlace().getPlaceId()) .placeName(post.getPlace().getPlaceName()) .category(post.getPlace().getPlaceCategory()) .partnership(post.getTitle()) .thumbnailUrl(post.getThumbnailImageUrl()) - // .reviewCount(projection.getReviewCount()) .build(); }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (27)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.javasrc/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.javasrc/main/java/com/campus/campus/domain/place/application/dto/response/SavedPlaceInfo.javasrc/main/java/com/campus/campus/domain/place/application/service/PlaceService.javasrc/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.javasrc/main/java/com/campus/campus/domain/review/application/dto/response/CursorPageReviewResponse.javasrc/main/java/com/campus/campus/domain/review/application/dto/response/PlaceReviewRankResponse.javasrc/main/java/com/campus/campus/domain/review/application/dto/response/RankingScope.javasrc/main/java/com/campus/campus/domain/review/application/dto/response/ReviewCreateResponse.javasrc/main/java/com/campus/campus/domain/review/application/dto/response/ReviewCreateResult.javasrc/main/java/com/campus/campus/domain/review/application/dto/response/ReviewRankingResponse.javasrc/main/java/com/campus/campus/domain/review/application/dto/response/ReviewResponse.javasrc/main/java/com/campus/campus/domain/review/application/exception/ErrorCode.javasrc/main/java/com/campus/campus/domain/review/application/exception/NotUserWriterException.javasrc/main/java/com/campus/campus/domain/review/application/exception/ReviewNotFoundException.javasrc/main/java/com/campus/campus/domain/review/application/mapper/ReviewMapper.javasrc/main/java/com/campus/campus/domain/review/application/service/ReviewService.javasrc/main/java/com/campus/campus/domain/review/domain/entity/Review.javasrc/main/java/com/campus/campus/domain/review/domain/entity/ReviewImage.javasrc/main/java/com/campus/campus/domain/review/domain/entity/ReviewImages.javasrc/main/java/com/campus/campus/domain/review/domain/repository/ReviewImageRepository.javasrc/main/java/com/campus/campus/domain/review/domain/repository/ReviewImagesRepository.javasrc/main/java/com/campus/campus/domain/review/domain/repository/ReviewRepository.javasrc/main/java/com/campus/campus/domain/review/presentation/ReviewController.javasrc/main/java/com/campus/campus/domain/review/presentation/ReviewResponseCode.javasrc/main/java/com/campus/campus/domain/user/application/service/KakaoOauthService.javasrc/main/java/com/campus/campus/domain/user/presentation/AuthController.java
🧰 Additional context used
🧬 Code graph analysis (15)
src/main/java/com/campus/campus/domain/review/application/exception/ErrorCode.java (2)
src/main/java/com/campus/campus/global/common/exception/ErrorCodeInterface.java (1)
ErrorCodeInterface(5-11)src/main/java/com/campus/campus/global/common/exception/ApplicationException.java (1)
Getter(5-18)
src/main/java/com/campus/campus/domain/review/domain/entity/Review.java (4)
src/main/java/com/campus/campus/domain/review/application/dto/response/CursorPageReviewResponse.java (1)
Getter(8-16)src/main/java/com/campus/campus/domain/place/domain/entity/Place.java (1)
Entity(17-57)src/main/java/com/campus/campus/domain/user/domain/entity/User.java (1)
Entity(25-84)src/main/java/com/campus/campus/global/entity/BaseEntity.java (1)
Getter(16-29)
src/main/java/com/campus/campus/domain/review/application/dto/response/CursorPageReviewResponse.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/councilpost/application/dto/response/GetPostResponse.java (1)
Builder(13-35)
src/main/java/com/campus/campus/domain/review/domain/entity/ReviewImages.java (4)
src/main/java/com/campus/campus/domain/review/domain/entity/Review.java (1)
Entity(22-57)src/main/java/com/campus/campus/domain/place/domain/entity/PlaceImages.java (1)
Entity(18-40)src/main/java/com/campus/campus/domain/councilnotice/domain/entity/NoticeImage.java (1)
Entity(19-36)src/main/java/com/campus/campus/global/entity/BaseEntity.java (1)
Getter(16-29)
src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java (2)
src/main/java/com/campus/campus/domain/place/domain/repository/PlaceRepository.java (1)
PlaceRepository(10-15)src/main/java/com/campus/campus/domain/place/application/mapper/PlaceMapper.java (1)
createPlace(82-92)
src/main/java/com/campus/campus/domain/review/application/dto/response/RankingScope.java (6)
src/main/java/com/campus/campus/domain/user/application/dto/response/UserFirstProfileResponse.java (1)
Builder(6-17)src/main/java/com/campus/campus/domain/council/application/service/CouncilLoginService.java (1)
CouncilScope(261-266)src/main/java/com/campus/campus/domain/school/application/dto/response/CollegeFindResponse.java (1)
CollegeFindResponse(5-12)src/main/java/com/campus/campus/domain/user/application/dto/request/CampusNicknameUpdateRequest.java (1)
CampusNicknameUpdateRequest(6-11)src/main/java/com/campus/campus/domain/place/application/dto/response/SearchCandidateResponse.java (1)
SearchCandidateResponse(5-12)src/main/java/com/campus/campus/domain/councilnotice/application/dto/response/NoticeListItemResponse.java (1)
Builder(7-15)
src/main/java/com/campus/campus/domain/review/domain/entity/ReviewImage.java (3)
src/main/java/com/campus/campus/domain/councilpost/domain/entity/PostImage.java (1)
Entity(18-36)src/main/java/com/campus/campus/domain/councilnotice/domain/entity/NoticeImage.java (1)
Entity(19-36)src/main/java/com/campus/campus/domain/place/domain/entity/PlaceImages.java (1)
Entity(18-40)
src/main/java/com/campus/campus/domain/review/application/dto/response/PlaceReviewRankResponse.java (2)
src/main/java/com/campus/campus/domain/place/application/dto/response/partnership/PartnershipResponse.java (1)
PartnershipResponse(6-24)src/main/java/com/campus/campus/domain/place/domain/entity/Place.java (1)
Entity(17-57)
src/main/java/com/campus/campus/domain/review/application/dto/response/ReviewCreateResponse.java (9)
src/main/java/com/campus/campus/domain/councilnotice/application/dto/response/NoticeListItemResponse.java (1)
Builder(7-15)src/main/java/com/campus/campus/domain/council/application/dto/response/StudentCouncilLoginResponse.java (1)
Builder(8-40)src/main/java/com/campus/campus/domain/user/application/dto/response/UserFirstProfileResponse.java (1)
Builder(6-17)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/response/GetPostResponse.java (1)
Builder(13-35)src/main/java/com/campus/campus/domain/manager/application/dto/response/CertifyRequestCouncilResponse.java (1)
CertifyRequestCouncilResponse(5-15)src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostForUserResponse.java (1)
Builder(13-36)src/main/java/com/campus/campus/domain/place/application/dto/response/SearchCandidateResponse.java (1)
SearchCandidateResponse(5-12)src/main/java/com/campus/campus/domain/council/application/dto/response/StudentCouncilFindIdResponse.java (1)
Builder(6-11)
src/main/java/com/campus/campus/domain/review/application/dto/response/ReviewResponse.java (9)
src/main/java/com/campus/campus/domain/councilnotice/application/dto/response/NoticeListItemResponse.java (1)
Builder(7-15)src/main/java/com/campus/campus/domain/user/application/dto/response/UserFirstProfileResponse.java (1)
Builder(6-17)src/main/java/com/campus/campus/domain/council/application/dto/response/StudentCouncilLoginResponse.java (1)
Builder(8-40)src/main/java/com/campus/campus/domain/manager/application/dto/response/CouncilApproveOrDenyResponse.java (1)
CouncilApproveOrDenyResponse(5-12)src/main/java/com/campus/campus/domain/place/application/dto/response/partnership/PartnershipResponse.java (1)
PartnershipResponse(6-24)src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostResponse.java (1)
Builder(13-35)src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostForUserResponse.java (1)
Builder(13-36)src/main/java/com/campus/campus/domain/manager/application/dto/response/CertifyRequestCouncilResponse.java (1)
CertifyRequestCouncilResponse(5-15)src/main/java/com/campus/campus/domain/manager/application/dto/response/CertifyRequestCouncilListResponse.java (1)
CertifyRequestCouncilListResponse(7-17)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (3)
src/main/java/com/campus/campus/domain/place/application/mapper/PlaceMapper.java (2)
Component(21-114)createPlace(82-92)src/main/java/com/campus/campus/domain/place/domain/entity/Place.java (1)
Entity(17-57)src/main/java/com/campus/campus/domain/place/domain/repository/PlaceRepository.java (1)
PlaceRepository(10-15)
src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java (9)
src/main/java/com/campus/campus/domain/councilnotice/application/dto/request/NoticeRequest.java (1)
NoticeRequest(5-10)src/main/java/com/campus/campus/domain/council/application/dto/request/StudentCouncilChangePasswordRequest.java (1)
StudentCouncilChangePasswordRequest(9-24)src/main/java/com/campus/campus/domain/councilpost/application/dto/request/PostRequest.java (1)
PostRequest(16-63)src/main/java/com/campus/campus/domain/manager/application/dto/request/CouncilApproveOrDenyRequest.java (1)
CouncilApproveOrDenyRequest(6-11)src/main/java/com/campus/campus/domain/council/application/dto/request/StudentCouncilChangeEmailRequest.java (1)
StudentCouncilChangeEmailRequest(6-11)src/main/java/com/campus/campus/domain/mail/application/dto/request/EmailVerificationRequest.java (1)
EmailVerificationRequest(6-11)src/main/java/com/campus/campus/domain/user/application/dto/request/CampusNicknameUpdateRequest.java (1)
CampusNicknameUpdateRequest(6-11)src/main/java/com/campus/campus/domain/council/application/dto/request/StudentCouncilSignUpRequest.java (1)
StudentCouncilSignUpRequest(11-44)src/main/java/com/campus/campus/domain/user/application/dto/request/UserProfileRequest.java (1)
UserProfileRequest(5-12)
src/main/java/com/campus/campus/domain/review/application/exception/ReviewNotFoundException.java (8)
src/main/java/com/campus/campus/domain/school/application/exception/CollegeNotFoundException.java (2)
CollegeNotFoundException(5-9)CollegeNotFoundException(6-8)src/main/java/com/campus/campus/domain/councilnotice/application/exception/NoticeNotFoundException.java (1)
NoticeNotFoundException(5-9)src/main/java/com/campus/campus/domain/school/application/exception/SchoolNotFoundException.java (2)
SchoolNotFoundException(5-9)SchoolNotFoundException(6-8)src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)src/main/java/com/campus/campus/domain/mail/application/exception/EmailVerificationNotFoundException.java (1)
EmailVerificationNotFoundException(5-9)src/main/java/com/campus/campus/global/common/exception/ApplicationException.java (1)
Getter(5-18)src/main/java/com/campus/campus/domain/councilpost/application/exception/PostNotFoundException.java (1)
PostNotFoundException(5-9)src/main/java/com/campus/campus/domain/council/application/exception/SignupEmailNotFoundException.java (1)
SignupEmailNotFoundException(6-8)
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java (7)
src/main/java/com/campus/campus/domain/councilpost/application/exception/PostImageLimitExceededException.java (1)
PostImageLimitExceededException(5-9)src/main/java/com/campus/campus/domain/councilpost/application/exception/PostOciImageDeleteFailedException.java (1)
PostOciImageDeleteFailedException(5-9)src/main/java/com/campus/campus/domain/review/application/exception/NotUserWriterException.java (1)
NotUserWriterException(5-9)src/main/java/com/campus/campus/domain/review/application/exception/ReviewNotFoundException.java (1)
ReviewNotFoundException(5-9)src/main/java/com/campus/campus/domain/user/application/exception/UserNotFirstLoginException.java (1)
UserNotFirstLoginException(5-9)src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)src/main/java/com/campus/campus/global/oci/application/service/PresignedUrlService.java (1)
Slf4j(24-136)
src/main/java/com/campus/campus/domain/place/application/dto/response/SavedPlaceInfo.java (3)
src/main/java/com/campus/campus/domain/place/domain/entity/Place.java (1)
Entity(17-57)src/main/java/com/campus/campus/domain/place/application/mapper/PlaceMapper.java (2)
createPlace(82-92)toSavedPlaceInfo(29-41)src/main/java/com/campus/campus/domain/place/domain/entity/Coordinate.java (1)
Embeddable(5-16)
🪛 GitHub Actions: campus CI pipeline
src/main/java/com/campus/campus/domain/review/presentation/ReviewController.java
[error] 117-117: method getReviewList in class ReviewService cannot be applied to given types; found: Long,LocalDateTime,Long,int; required: Long,Double,LocalDateTime,Long,int
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
[error] 185-185: method findByPlaceIdWithCursor in interface ReviewRepository cannot be applied to given types; found: Long,Double,LocalDateTime,Long,Pageable; required: Long,LocalDateTime,Long,Pageable
🔇 Additional comments (11)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java (2)
73-73: PlaceService API 시그니처 변경 반영 완료
placeService.findOrCreatePlace(dto.place())로 변경하여SavedPlaceInfo를 직접 전달하도록 개선되었습니다. 이는 관심사의 분리 측면에서 더 명확한 API 설계입니다.
217-217: LGTM - update 메서드에서도 일관되게 적용create 메서드와 동일하게
dto.place()를 전달하여 API 변경사항이 일관되게 반영되었습니다.src/main/java/com/campus/campus/domain/review/application/dto/response/ReviewRankingResponse.java (1)
3-7: LGTM - 리뷰 랭킹 응답 DTO 구조 적절학과(major), 단과대(college), 학교(school) 단위의 랭킹 정보를 명확하게 구조화한 DTO입니다. 도메인 개념을 잘 반영하고 있습니다.
src/main/java/com/campus/campus/domain/review/application/exception/ReviewNotFoundException.java (1)
5-8: LGTM - 표준 예외 패턴 준수
ReviewNotFoundException이 프로젝트의 다른 예외 클래스들(UserNotFoundException,PostNotFoundException등)과 동일한 패턴을 따르고 있어 일관성이 유지됩니다.src/main/java/com/campus/campus/domain/place/application/dto/response/SavedPlaceInfo.java (1)
36-36: coordinate 필드의 @NotNull 검증은 적절합니다PlaceMapper.toCoordinate() 메서드는 항상 Coordinate.fromNaver()를 통해 새로운 Coordinate 인스턴스를 생성하므로 null을 반환하지 않습니다. 따라서 SavedPlaceInfo의 coordinate 필드에 대한 @NotNull 검증은 올바르게 적용되었으며 추가 확인이 불필요합니다.
src/main/java/com/campus/campus/domain/review/application/exception/NotUserWriterException.java (1)
1-9: LGTM!프로젝트의 예외 처리 패턴을 잘 따르고 있습니다. 리뷰 작성자 검증 실패 시 사용될 명확한 예외 클래스입니다.
src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java (1)
114-121: LGTM!
PostRequest대신SavedPlaceInfo를 직접 받도록 리팩토링하여 메서드의 책임이 명확해졌습니다. 필요한 데이터만 전달받는 좋은 API 설계입니다.src/main/java/com/campus/campus/domain/review/application/dto/response/ReviewCreateResponse.java (1)
5-12: LGTM!레코드 구조가 잘 정의되어 있으며,
@Builder어노테이션이 올바르게 적용되었습니다. 프로젝트의 다른 응답 DTO 패턴과 일관성이 있습니다.src/main/java/com/campus/campus/domain/review/application/dto/response/ReviewResponse.java (1)
8-19: LGTM!리뷰 응답 레코드가 적절한 필드와
@Builder어노테이션으로 잘 정의되었습니다. 프로젝트의 기존 응답 DTO 패턴과 일관성이 있습니다.src/main/java/com/campus/campus/domain/review/domain/entity/Review.java (1)
50-56: LGTM!
update()메서드가 간결하고 명확하게 구현되었습니다. 필드 수정이 집중된 형태로 잘 설계되었습니다.src/main/java/com/campus/campus/domain/review/presentation/ReviewResponseCode.java (1)
10-23: LGTM!응답 코드 enum이
ResponseCodeInterface를 올바르게 구현하고 있으며, Lombok 어노테이션을 적절히 사용하고 있습니다.
...ava/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
Outdated
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java
Outdated
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/application/exception/ErrorCode.java
Outdated
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
Show resolved
Hide resolved
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| @Column(name = "review_images_id") | ||
| private Long ReviewImagesId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
필드 이름이 Java 명명 규칙을 위반합니다.
ReviewImagesId는 대문자로 시작합니다. Java 필드명은 camelCase를 사용해야 합니다.
🐛 수정 제안
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "review_images_id")
- private Long ReviewImagesId;
+ private Long reviewImagesId;📝 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.
| @Id | |
| @GeneratedValue(strategy = GenerationType.IDENTITY) | |
| @Column(name = "review_images_id") | |
| private Long ReviewImagesId; | |
| @Id | |
| @GeneratedValue(strategy = GenerationType.IDENTITY) | |
| @Column(name = "review_images_id") | |
| private Long reviewImagesId; |
🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/review/domain/entity/ReviewImages.java
around lines 26 - 29, The field name ReviewImagesId in class ReviewImages
violates Java camelCase; rename the field to reviewImagesId and update all
references (constructors, getters/setters, equals/hashCode, any usages in
repository/DTOs, and JPA mappings) to use reviewImagesId while keeping the
@Column(name = "review_images_id") and @Id/@GeneratedValue annotations intact so
database mapping is unchanged.
src/main/java/com/campus/campus/domain/review/domain/repository/ReviewImagesRepository.java
Outdated
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/domain/repository/ReviewRepository.java
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/presentation/ReviewController.java
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/presentation/ReviewController.java
Show resolved
Hide resolved
There was a problem hiding this 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/domain/repository/StudentCouncilPostRepository.java:
- Around line 314-323: The method findRepresentativePosts declares a JPQL
parameter :writerId but the method signature only accepts placeIds, causing
runtime binding errors; update the repository method
StudentCouncilPostRepository.findRepresentativePosts to include a writerId
parameter (e.g., add @Param("writerId") Long writerId) alongside the existing
@Param("placeIds") List<Long> placeIds so the :writerId in the query is bound
correctly.
In
@src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java:
- Around line 60-66: In ReviewService.writeReview, replace the incorrect thrown
exception when a user is missing: change the
userRepository.findById(...).orElseThrow(UserNotFirstLoginException::new) to
throw UserNotFoundException instead; locate the orElseThrow usage in writeReview
and swap the exception constructor reference from UserNotFirstLoginException to
UserNotFoundException so the method throws the semantically correct exception
when a user is not found.
- Around line 292-320: getRankingResult accesses user.getMajor(), getCollege(),
getSchool() without null checks causing potential NPE; update getRankingResult
to null-safe behavior by checking each of user.getMajor(), user.getCollege(),
user.getSchool() before using them: if null, set the corresponding name to null
(or empty string) and the rank to 0 (skip repository count), otherwise retrieve
the id and call reviewRepository.countByPlace_PlaceIdAndUser_* with that id;
finally pass the computed names and ranks into
reviewMapper.toReviewRankingResponse so the method never dereferences null
domain objects.
- Around line 226-252: In readPopularPartnerships, avoid direct access to
nullable fields user.getMajor(), user.getCollege(), and user.getSchool(); add a
defensive check at the start of the method (similar to
StudentCouncilPostForUserService) like if (user.isProfileNotCompleted() ||
user.getCollege() == null || user.getMajor() == null || user.getSchool() ==
null) then return List.of() (or throw a dedicated UserProfileIncompleteException
if project convention prefers); ensure later code uses
user.getMajor().getMajorId(), user.getCollege().getCollegeId(),
user.getSchool().getSchoolId() only after this null-safety guard so no NPE
occurs.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.gitignoresrc/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.javasrc/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
✅ Files skipped from review due to trivial changes (1)
- .gitignore
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java (7)
src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java (1)
Service(46-252)src/main/java/com/campus/campus/domain/councilpost/application/exception/PostImageLimitExceededException.java (1)
PostImageLimitExceededException(5-9)src/main/java/com/campus/campus/domain/councilpost/application/exception/PostOciImageDeleteFailedException.java (1)
PostOciImageDeleteFailedException(5-9)src/main/java/com/campus/campus/domain/review/application/exception/NotUserWriterException.java (1)
NotUserWriterException(5-9)src/main/java/com/campus/campus/domain/review/application/exception/ReviewNotFoundException.java (1)
ReviewNotFoundException(5-9)src/main/java/com/campus/campus/domain/user/application/exception/UserNotFirstLoginException.java (1)
UserNotFirstLoginException(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 (7)
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (1)
284-312: 네이티브 쿼리 로직 확인 완료리뷰 개수 기준으로 상위 3개 제휴 장소를 조회하는 네이티브 쿼리 로직이 올바르게 구현되었습니다. 파라미터 매핑도 정확하고, LEFT JOIN을 사용하여 리뷰가 없는 장소도 포함할 수 있도록 처리되었습니다.
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java (6)
94-105: LGTM!리뷰 조회 로직이 명확하고 예외 처리도 적절합니다.
108-134: 리뷰 삭제 로직 확인 완료소유권 검증, 이미지 삭제, OCI 정리 순서가 올바르게 구현되었습니다. OCI 삭제 실패 시 warn 레벨 로그로 처리하여 전체 삭제 플로우가 중단되지 않도록 한 점이 좋습니다.
137-172: LGTM!리뷰 수정 로직이 올바르게 구현되었습니다. 기존 이미지 삭제 후 새 이미지 저장, 그리고 사용되지 않는 이미지 정리까지 순서가 적절합니다.
175-223: 우수한 페이지네이션 구현커서 기반 페이지네이션이 올바르게 구현되었으며, 196-208번 라인에서 이미지를 배치로 조회한 후 Map으로 그룹핑하여 N+1 쿼리 문제를 방지한 점이 훌륭합니다.
255-277: LGTM!사용하지 않는 이미지 정리 로직이 잘 구현되었습니다. 267-268번 라인의 null/blank 체크와 예외 처리가 적절합니다.
279-290: LGTM!리뷰 생성 결과를 계산하는 로직이 명확하고 정확합니다.
...ava/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
Outdated
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
Show resolved
Hide resolved
| public List<PlaceReviewRankResponse> readPopularPartnerships(Long userId) { | ||
| User user = userRepository.findById(userId) | ||
| .orElseThrow(UserNotFoundException::new); | ||
|
|
||
| LocalDateTime now = LocalDateTime.now(); | ||
| LocalDateTime from = now.minusMonths(1); | ||
|
|
||
| //리뷰가 가장 많은 top3 placeIds | ||
| List<StudentCouncilPost> partnerships = | ||
| studentCouncilPostRepository.findTop3RecommendedPartnershipPlaces( | ||
| user.getMajor().getMajorId(), | ||
| user.getCollege().getCollegeId(), | ||
| user.getSchool().getSchoolId(), | ||
| from, | ||
| now | ||
| ); | ||
|
|
||
| log.info("찾은 결과:{}", partnerships.stream().toList()); | ||
|
|
||
| if (partnerships.isEmpty()) { | ||
| return List.of(); | ||
| } | ||
|
|
||
| return partnerships.stream() | ||
| .map(reviewMapper::toTopPartnershipResponse) | ||
| .toList(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "*.java" -type f | grep -i user | head -20Repository: our-campUS/our-campUS-BE
Length of output: 1991
🏁 Script executed:
fd "User.java" -t fRepository: our-campUS/our-campUS-BE
Length of output: 135
🏁 Script executed:
find . -path "*/domain/*" -name "*.java" -type f | grep -i userRepository: our-campUS/our-campUS-BE
Length of output: 2412
🏁 Script executed:
cat -n src/main/java/com/campus/campus/domain/user/domain/entity/User.javaRepository: our-campUS/our-campUS-BE
Length of output: 2820
🏁 Script executed:
# Check for nullable annotations on these fields
grep -n "@NotNull\|@NonNull\|nullable = false" src/main/java/com/campus/campus/domain/user/domain/entity/User.javaRepository: our-campUS/our-campUS-BE
Length of output: 50
🏁 Script executed:
# Look at the method isProfileNotCompleted which suggests these fields CAN be null
grep -A 5 "isProfileNotCompleted" src/main/java/com/campus/campus/domain/user/domain/entity/User.javaRepository: our-campUS/our-campUS-BE
Length of output: 166
🏁 Script executed:
# Check other usages in ReviewService to see if null checks exist elsewhere
rg "getMajor\(\)|getCollege\(\)|getSchool\(\)" src/main/java/com/campus/campus/domain/review/ -A 2 -B 2Repository: our-campUS/our-campUS-BE
Length of output: 1755
🏁 Script executed:
# Check database schema or migration files to see if these columns are NOT NULL at DB level
find . -name "*.sql" -o -name "*migration*" -o -name "*schema*" | grep -i migrationRepository: our-campUS/our-campUS-BE
Length of output: 50
🏁 Script executed:
# Check the getRankingResult method more carefully to see if there's null checking
sed -n '200,270p' src/main/java/com/campus/campus/domain/review/application/service/ReviewService.javaRepository: our-campUS/our-campUS-BE
Length of output: 1944
🏁 Script executed:
# Check how the user's profile completion is enforced at the service level
rg "isProfileNotCompleted" src/main/java/com/campus/campus/domain/ -A 3 -B 3Repository: our-campUS/our-campUS-BE
Length of output: 5807
🏁 Script executed:
# Double-check the specific context in readPopularPartnerships method to see the exact line numbers
sed -n '226,252p' src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java | cat -nRepository: our-campUS/our-campUS-BE
Length of output: 996
Null 안전성 확보 필요
236-238번 라인에서 user.getMajor(), user.getCollege(), user.getSchool()을 null 체크 없이 직접 접근하고 있습니다. User 엔티티의 해당 필드들은 nullable이며, isProfileNotCompleted() 메서드에서 이들이 null일 수 있음을 명시적으로 확인하고 있습니다. 사용자 프로필이 미완성된 경우 NullPointerException이 발생할 것입니다.
다른 서비스(예: StudentCouncilPostForUserService)에서는 if (user.isProfileNotCompleted() || user.getCollege() == null) 형태로 방어적 null 체크를 수행하므로, 이 메서드에도 동일한 방식의 검증이 필요합니다.
🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
around lines 226 - 252, In readPopularPartnerships, avoid direct access to
nullable fields user.getMajor(), user.getCollege(), and user.getSchool(); add a
defensive check at the start of the method (similar to
StudentCouncilPostForUserService) like if (user.isProfileNotCompleted() ||
user.getCollege() == null || user.getMajor() == null || user.getSchool() ==
null) then return List.of() (or throw a dedicated UserProfileIncompleteException
if project convention prefers); ensure later code uses
user.getMajor().getMajorId(), user.getCollege().getCollegeId(),
user.getSchool().getSchoolId() only after this null-safety guard so no NPE
occurs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In
@src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java:
- Around line 284-306: The @EntityGraph(attributePaths = {"place"}) on the
nativeQuery in StudentCouncilPostRepository is ignored (causing N+1 when
accessing place); either 1) convert the native SQL in the @Query to a JPQL query
(remove nativeQuery = true) so @EntityGraph can eagerly fetch place, or 2) keep
the native SQL but include the necessary place columns in the SELECT and provide
a result mapping (use @SqlResultSetMapping or a projection interface/DTO) to
populate place fields and avoid lazy loads; update the repository method
signature accordingly to return the mapped DTO/entity.
In
@src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java:
- Around line 19-21: ReviewRequest.star lacks range validation allowing
negatives or >5 values; add bean validation annotations to enforce 0.0–5.0
(e.g., use javax.validation.constraints @DecimalMin("0.0") and
@DecimalMax("5.0") or @Min/@Max if using integral semantics) on the star field
alongside the existing @NotNull, and include an explicit message to clarify
invalid inputs.
In
@src/main/java/com/campus/campus/domain/review/application/exception/ErrorCode.java:
- Around line 14-19: The NOT_REVIEW_WRITER enum entry in ErrorCode currently
uses 2701 which conflicts with MANAGER_NOT_FOUND; update NOT_REVIEW_WRITER in
the ErrorCode enum to a unique unused value (e.g., 2702 or another number
>=2702) and update any references/usages across the codebase (tests, exception
constructors, mappings) to use the new constant to ensure no domain code
collision.
In
@src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java:
- Around line 210-217: The stream mapping can pass null images into
reviewMapper.toReviewResponse which may cause a NullPointerException; change the
call so it never passes null by replacing imageMap.get(review.getId()) with a
safe non-null value (e.g., imageMap.getOrDefault(review.getId(),
Collections.emptyList()) or
Optional.ofNullable(imageMap.get(review.getId())).orElse(Collections.emptyList()))
when invoking reviewMapper.toReviewResponse in ReviewService so toReviewResponse
always receives an empty list instead of null.
- Around line 127-133: cleanupUnusedImages is catching
PostOciImageDeleteFailException but PresignedUrlService.deleteImage() actually
throws OciObjectDeleteFailException, so update the exception handling in
cleanupUnusedImages to catch OciObjectDeleteFailException (or both exceptions if
you intend to handle both) to prevent the delete call from propagating; locate
the cleanupUnusedImages method and replace or add the catch for
OciObjectDeleteFailException (referencing PresignedUrlService.deleteImage(),
delete method, cleanupUnusedImages, OciObjectDeleteFailException, and
PostOciImageDeleteFailException).
- Around line 236-243: The code dereferences user.getMajor(), user.getCollege(),
and user.getSchool() without null checks causing NullPointerException for
incomplete profiles; update the calling code in ReviewService (the block that
calls the repository with user.getMajor().getMajorId(),
user.getCollege().getCollegeId(), user.getSchool().getSchoolId()) to first guard
with user.isProfileNotCompleted() or individually check for null and
handle/mapping missing ids (e.g., return early, provide Optional/nullable
parameters, or pass null-safe defaults) before accessing
getMajorId()/getCollegeId()/getSchoolId(); also replace log.info("찾은 결과:{}",
partnerships.stream().toList()) with log.debug("찾은 결과: {}", partnerships) to
avoid redundant stream conversion and lower log level for debug output.
🧹 Nitpick comments (5)
src/main/java/com/campus/campus/domain/review/presentation/ReviewResponseCode.java (1)
14-14: 리뷰 생성 시201 CREATED상태 코드 고려.REST API 관례상 리소스 생성 성공은
201 CREATED가 표준입니다. 프로젝트의 다른ResponseCode들이 동일한 패턴을 사용한다면 일관성을 위해 현재대로 유지해도 됩니다.💡 201 CREATED 적용 예시
-REVIEW_SAVE_SUCCESS(200, HttpStatus.OK, "리뷰 작성이 완료되었습니다."), +REVIEW_SAVE_SUCCESS(201, HttpStatus.CREATED, "리뷰 작성이 완료되었습니다."),src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java (1)
14-17: 리뷰 내용(content) 최대 길이 제한 추가 고려.
@Size(min = 20)만 지정되어 있어 매우 긴 리뷰가 입력될 수 있습니다. DB 컬럼 크기나 UI 표시를 고려하여 최대 길이 제한을 추가하는 것이 좋습니다.💡 최대 길이 추가 예시
@NotNull -@Size(min = 20, message = "리뷰 내용은 최소 20자 이상이어야 합니다.") +@Size(min = 20, max = 1000, message = "리뷰 내용은 20자 이상, 1000자 이하이어야 합니다.") @Schema(example = "아주 정말 맛있습니다. 저의 완전 짱 또간집. 꼭꼮꼬꼬꼭 가세요.") String content,src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java (3)
74-84: 이미지 저장 및 조회 최적화 제안저장 직후 다시 DB에서 이미지를 조회하는 것은 불필요한 오버헤드입니다. 이미
request.imageUrls()에 URL 목록이 있으므로 이를 직접 사용할 수 있습니다.또한
saveAll()을 사용하면 개별 save 호출보다 효율적입니다.♻️ 개선 제안
+ List<String> imageUrls = request.imageUrls() == null ? List.of() : request.imageUrls(); + if (request.imageUrls() != null) { - for (String imageUrl : request.imageUrls()) { - reviewImageRepository.save(reviewMapper.createReviewImage(review, imageUrl)); - } + List<ReviewImage> reviewImages = imageUrls.stream() + .map(url -> reviewMapper.createReviewImage(review, url)) + .toList(); + reviewImageRepository.saveAll(reviewImages); } - List<String> imageUrls = reviewImageRepository - .findAllByReviewOrderByIdAsc(review) - .stream() - .map(ReviewImage::getImageUrl) - .toList(); - ReviewResponse response = reviewMapper.toReviewResponse(review, imageUrls);
119-122: 스트림 코드 간소화
deleted리스트를 만드는 로직이 불필요하게 복잡합니다. 직접 collect로 수집할 수 있습니다.♻️ 간소화 제안
- List<String> deleted = new ArrayList<>(); - reviewImages.stream() - .map(ReviewImage::getImageUrl) - .forEach(deleted::add); + List<String> deleted = reviewImages.stream() + .map(ReviewImage::getImageUrl) + .toList();
165-169: writeReview와 동일한 최적화 적용 가능
writeReview메서드와 마찬가지로, 방금 저장한 이미지 URL을 다시 DB에서 조회하는 대신request.imageUrls()를 직접 사용할 수 있습니다.♻️ 개선 제안
- List<String> imageUrls = reviewImageRepository - .findAllByReview(review) - .stream() - .map(ReviewImage::getImageUrl) - .toList(); + List<String> imageUrls = request.imageUrls() == null ? List.of() : request.imageUrls();
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.javasrc/main/java/com/campus/campus/domain/place/application/dto/response/partnership/PartnershipResponse.javasrc/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.javasrc/main/java/com/campus/campus/domain/review/application/dto/response/ReviewResponse.javasrc/main/java/com/campus/campus/domain/review/application/exception/ErrorCode.javasrc/main/java/com/campus/campus/domain/review/application/service/ReviewService.javasrc/main/java/com/campus/campus/domain/review/domain/entity/Review.javasrc/main/java/com/campus/campus/domain/review/presentation/ReviewController.javasrc/main/java/com/campus/campus/domain/review/presentation/ReviewResponseCode.java
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/java/com/campus/campus/domain/review/domain/entity/Review.java
- src/main/java/com/campus/campus/domain/review/presentation/ReviewController.java
🧰 Additional context used
🧬 Code graph analysis (3)
src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java (1)
src/main/java/com/campus/campus/domain/place/application/dto/response/SavedPlaceInfo.java (1)
SavedPlaceInfo(10-41)
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java (7)
src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java (1)
Service(46-252)src/main/java/com/campus/campus/domain/councilpost/application/exception/PostImageLimitExceededException.java (1)
PostImageLimitExceededException(5-9)src/main/java/com/campus/campus/domain/councilpost/application/exception/PostOciImageDeleteFailedException.java (1)
PostOciImageDeleteFailedException(5-9)src/main/java/com/campus/campus/domain/review/application/exception/NotUserWriterException.java (1)
NotUserWriterException(5-9)src/main/java/com/campus/campus/domain/review/application/exception/ReviewNotFoundException.java (1)
ReviewNotFoundException(5-9)src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)src/main/java/com/campus/campus/global/oci/exception/OciObjectDeleteFailException.java (1)
OciObjectDeleteFailException(5-9)
src/main/java/com/campus/campus/domain/review/presentation/ReviewResponseCode.java (1)
src/main/java/com/campus/campus/domain/review/application/dto/response/CursorPageReviewResponse.java (1)
Getter(8-16)
⏰ 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 (7)
src/main/java/com/campus/campus/domain/review/application/exception/ErrorCode.java (1)
10-12: 구조가 잘 설계되었습니다.Lombok 어노테이션을 활용하여 boilerplate 코드를 줄이고,
ErrorCodeInterface를 구현하여 전역 예외 처리 패턴과 일관성을 유지하고 있습니다.src/main/java/com/campus/campus/domain/place/application/dto/response/partnership/PartnershipResponse.java (1)
16-16: LGTM!
star필드를Doublewrapper 타입으로 변경한 것은 적절합니다. 리뷰가 없는 장소의 경우 평점이null일 수 있으므로 nullable 지원이 필요합니다.src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (1)
295-301: Soft delete 조건 및 카테고리 필터 누락 확인 필요.다른 쿼리들은
w.deletedAt IS NULL조건과p.category = :category필터를 사용합니다. 이 native query에는 두 조건이 모두 없습니다:
sc.deleted_at IS NULL- 삭제된 student council의 게시물이 포함될 수 있음scp.category = 'PARTNERSHIP'- 제휴 게시물만 조회해야 하는지 확인 필요src/main/java/com/campus/campus/domain/review/application/dto/response/ReviewResponse.java (1)
8-19: LGTM!ReviewResponse record가 필요한 필드들을 적절히 포함하고 있습니다.
@Builder패턴 사용도 record와 잘 호환됩니다.src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java (3)
93-105: LGTM!읽기 전용 트랜잭션이 적절하게 사용되었고, 조회 로직이 명확합니다.
279-290: 변수명과 로직 명확성 확인 필요
isFirstReviewOfPlace가totalReviewCountOfPlace == 1로 판단됩니다. 이는 "해당 장소에 리뷰가 총 1개일 때" (즉, 방금 작성한 리뷰가 첫 리뷰)를 의미합니다.만약 의도가 "사용자가 해당 장소에 작성한 첫 번째 리뷰인지"라면
userReviewCountOfPlace == 1이어야 합니다. 현재 로직이 의도와 맞는지 확인해주세요.
292-320: LGTM!각 범위(전공/단과대/학교)별 랭킹 조회 로직이 명확하게 구현되어 있습니다.
...ava/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
Outdated
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/application/exception/ErrorCode.java
Outdated
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
Show resolved
Hide resolved
| user.getMajor().getMajorId(), | ||
| user.getCollege().getCollegeId(), | ||
| user.getSchool().getSchoolId(), | ||
| from, | ||
| now | ||
| ); | ||
|
|
||
| log.info("찾은 결과:{}", partnerships.stream().toList()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and examine the User entity definition
find . -type f -name "User.java" | head -5Repository: our-campUS/our-campUS-BE
Length of output: 137
🏁 Script executed:
#!/bin/bash
# Examine the ReviewService.java file, particularly around lines 225-252
fd -t f "ReviewService.java" --exec wc -l {} \; | head -1Repository: our-campUS/our-campUS-BE
Length of output: 158
🏁 Script executed:
#!/bin/bash
# Get the file path and read the relevant section
fd -t f "ReviewService.java" | head -1 | xargs cat -n | sed -n '220,255p'Repository: our-campUS/our-campUS-BE
Length of output: 1265
🏁 Script executed:
#!/bin/bash
# Search for User entity to check nullable properties
rg -t java "class User" -A 20 | head -50Repository: our-campUS/our-campUS-BE
Length of output: 4721
🏁 Script executed:
#!/bin/bash
# Check for null checks before line 236 in ReviewService
fd -t f "ReviewService.java" | head -1 | xargs cat -n | sed -n '225,243p'Repository: our-campUS/our-campUS-BE
Length of output: 818
🏁 Script executed:
#!/bin/bash
# Read the User entity file to check nullable annotations for major, college, school
cat -n ./src/main/java/com/campus/campus/domain/user/domain/entity/User.javaRepository: our-campUS/our-campUS-BE
Length of output: 2820
🏁 Script executed:
#!/bin/bash
# Check if there are any validation or null checks used elsewhere in the codebase for similar patterns
rg -t java "getMajor\(\)\.get" -B 2 -A 2 | head -30Repository: our-campUS/our-campUS-BE
Length of output: 3053
🏁 Script executed:
#!/bin/bash
# Check the repository method to understand what it returns
rg -t java "findTop3RecommendedPartnershipPlaces" -B 5 -A 10 | head -40Repository: our-campUS/our-campUS-BE
Length of output: 3679
사용자의 전공/단과대/학교 정보가 없을 경우 NullPointerException 발생
User 엔티티에서 major, college, school 필드는 모두 nullable 상태입니다(isProfileNotCompleted() 메서드가 이를 확인). 현재 코드는 이들 필드를 null 체크 없이 직접 접근하고 있어, 프로필이 완성되지 않은 사용자가 이 메서드를 호출하면 NullPointerException이 발생합니다.
// 현재 코드 (위험)
user.getMajor().getMajorId(),
user.getCollege().getCollegeId(),
user.getSchool().getSchoolId(),각 필드의 null 여부를 먼저 검증하거나, 프로필 완성 여부를 사전에 확인하는 방어적 코드가 필요합니다.
또한 line 243의 partnerships.stream().toList()는 이미 List 타입이므로 중복 변환이며, 디버그 목적이라면 log.debug() 사용을 고려해주세요.
🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
around lines 236 - 243, The code dereferences user.getMajor(),
user.getCollege(), and user.getSchool() without null checks causing
NullPointerException for incomplete profiles; update the calling code in
ReviewService (the block that calls the repository with
user.getMajor().getMajorId(), user.getCollege().getCollegeId(),
user.getSchool().getSchoolId()) to first guard with user.isProfileNotCompleted()
or individually check for null and handle/mapping missing ids (e.g., return
early, provide Optional/nullable parameters, or pass null-safe defaults) before
accessing getMajorId()/getCollegeId()/getSchoolId(); also replace log.info("찾은
결과:{}", partnerships.stream().toList()) with log.debug("찾은 결과: {}",
partnerships) to avoid redundant stream conversion and lower log level for debug
output.
1winhyun
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고하셨습니다!!
리뷰 남겨두었으니 확인 부탁드려요!!
| String telephone, | ||
|
|
||
| @Schema(description = "위도/경도") | ||
| @NotNull |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 부분은 지워주세요!! 해당 부분이 존재할 경우 게시글 생성 시 오류가 생길 수 있습니다!!
...ava/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
Outdated
Show resolved
Hide resolved
...ava/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
Outdated
Show resolved
Hide resolved
...ava/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
Outdated
Show resolved
Hide resolved
| List<String> imageUrls = reviewImageRepository | ||
| .findAllByReviewOrderByIdAsc(review) | ||
| .stream() | ||
| .map(ReviewImage::getImageUrl) | ||
| .toList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이런 식의 구현은 하나의 로직이 DB에 저장 이후 다시 DB에 접속해 정보를 가지고 오는 상당히 불필요한 DB 접속이 이루어집니다. 해당 부분은 DB에서 꺼내오는 식이 아닌 dto에 있는 imageUrls를 반환해도 충분히 가능합니다.
추가적으로 현재 피그마를 보면 리뷰 작성 완료 시 하나의 이미지만 반환하면 되는 것을 확인할 수 있을 것입니다. 따라서 ReviewResponse에서 List imageUrls를 String imageUrl로 바꾸고 해당 부분만 반환하면 될 것으로 보입니다.
하지만 현재 ReviewResponse는 생성, 업데이터, 조회 로직에서 전부 사용되기에 String imageUrl로 바꾸기 힘든 상황으로 보입니다. 이럴 때는 dto를 ReviewResponse는 생성 전용으로 놔둔 후 ReviwUpdateResponse, GetReviewResponse를 따로 구현한 후 ReviewResponse의 경우만 String imageUrl로 수정해도 될 것 같습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 수정하였습니다
| //해당 장소 리뷰 개수 | ||
| long totalReviewCountOfPlace = reviewRepository.countByPlace_PlaceId(place.getPlaceId()); | ||
|
|
||
| //해당 장소에서 유저가 쓴 리뷰가 몇번째인지 | ||
| long userReviewCountOfPlace = reviewRepository.countByPlace_PlaceIdAndUser_Id(place.getPlaceId(), | ||
| user.getId()); | ||
| boolean isFirstReviewOfPlace = totalReviewCountOfPlace == 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
근데 해당 장소의 리뷰 개수가 곧 해당 장소에서 유저 쓴 리뷰가 몇번쨰인지 아닌가요? 만약 내가 해당 매장에 1500번째 리뷰를 남겼다면 그것은 곳ㄷ 내가 1500번째 리뷰를 썼다느거 아닌가요...?
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @AllArgsConstructor(access = AccessLevel.PRIVATE) | ||
| public class ReviewImage { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 엔티티도 extends BaseEntity 추가 부탁드려요
src/main/java/com/campus/campus/domain/review/domain/repository/ReviewRepository.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수정이 완료되었다면 해당 부분은 다시 원래대로 복구 부탁드려요!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수정이 완료되었다면 해당 부분은 다시 원래대로 복구 부탁드려요!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In
@src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java:
- Around line 286-305: The JPQL in StudentCouncilPostRepository uses the wrong
CouncilType package and omits the deleted filter: replace references to
com.campus.campus.domain.studentcouncil.domain.CouncilType with the actual enum
package com.campus.campus.domain.council.domain.entity.CouncilType (as
used/imported in the file) and add the missing "AND sc.deletedAt IS NULL"
condition to the WHERE clause so deleted student councils are excluded; update
the query string around the sc.councilType checks and the WHERE block
accordingly.
In
@src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java:
- Around line 80-82: The current assignment of imageUrl in ReviewService uses
request.imageUrls().getFirst() which will throw NoSuchElementException if
imageUrls() returns a non-null but empty list; update the logic in ReviewService
so you first check that request.imageUrls() is not null AND not empty (e.g.,
!request.imageUrls().isEmpty()) before calling getFirst(), or use a safe
alternative (Optional or stream().findFirst()) to retrieve the first element and
fall back to null when no element exists; ensure you update the variable
imageUrl assignment accordingly to avoid calling getFirst() on an empty
collection.
- Around line 162-163: The current assignment to imageUrl in ReviewService uses
request.imageUrls().getFirst() which will throw NoSuchElementException for an
empty list (same issue as in writeReview); change the check to ensure the list
is non-null and non-empty before calling getFirst() (or replace with a safe
extraction like stream().findFirst().orElse(null)) so imageUrl becomes null when
no elements exist; update the code path where imageUrl is set (the imageUrl
variable in the ReviewService method) accordingly.
- Around line 228-236: The code calls user.getMajor(), user.getCollege(), and
user.getSchool() without null checks before passing their IDs to
studentCouncilPostRepository.findTop3RecommendedPartnershipPlaces; add a guard
in ReviewService (around where 'user' is used) that verifies user.getMajor(),
user.getCollege(), and user.getSchool() are not null and handle the missing
academic info by throwing a clear domain exception (e.g., BadRequestException or
a custom NoAcademicInfoException) or by using a fallback path, and then use the
validated objects' getMajorId()/getCollegeId()/getSchoolId() when invoking
findTop3RecommendedPartnershipPlaces.
- Around line 286-314: getRankingResult can throw NullPointerException because
it assumes user.getMajor(), user.getCollege(), and user.getSchool() are
non-null; add null checks at the start of getRankingResult and handle missing
relations by either returning sensible defaults or computing counts with
null-safe logic: check user.getMajor()/getCollege()/getSchool(), only call
major.getMajorId()/college.getCollegeId()/school.getSchoolId() and the
repository methods when the corresponding object is non-null, and supply
fallback names/counts (e.g., empty string and 0) when null so
reviewMapper.toReviewRankingResponse is called with safe values; update
getRankingResult to perform these null guards around Major/College/School and
adjust the variables majorId/collegeId/schoolId and
majorRank/collegeRank/schoolRank accordingly.
🧹 Nitpick comments (3)
src/main/java/com/campus/campus/domain/review/domain/entity/ReviewImage.java (1)
33-35:@ManyToOne에optional = false추가 검토 필요
NoticeImage.java의 패턴을 보면@ManyToOne(fetch = FetchType.LAZY, optional = false)로 설정되어 있습니다.ReviewImage는 항상Review에 속해야 하므로optional = false와@JoinColumn(name = "review_id", nullable = false)를 명시하는 것이 데이터 무결성에 더 적합합니다.♻️ 제안하는 수정
- @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "review_id") + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "review_id", nullable = false) private Review review;src/main/java/com/campus/campus/domain/review/domain/repository/ReviewRepository.java (1)
35-43: 파라미터 타입 일관성 개선 권장
placeId파라미터가long(Line 35, 39)과Long(Line 41, 43)으로 혼용되어 있습니다. 일관성을 위해 동일한 타입으로 통일하는 것이 좋습니다.♻️ 제안하는 수정
long countByPlace_PlaceId(long placeId); long countByPlaceAndUser(Place place, User user); long countByPlace_PlaceIdAndUser_Major_MajorId(long placeId, long majorId); - long countByPlace_PlaceIdAndUser_College_CollegeId(Long placeId, long collegeId); + long countByPlace_PlaceIdAndUser_College_CollegeId(long placeId, long collegeId); - long countByPlace_PlaceIdAndUser_School_SchoolId(Long placeId, long schoolId); + long countByPlace_PlaceIdAndUser_School_SchoolId(long placeId, long schoolId);src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java (1)
116-119: 불필요한 중간 리스트 생성스트림에서
ArrayList로 수집하는 대신 직접 매핑하거나delete루프에서 처리할 수 있습니다.♻️ 제안하는 수정
- List<String> deleted = new ArrayList<>(); - reviewImages.stream() - .map(ReviewImage::getImageUrl) - .forEach(deleted::add); - - reviewImageRepository.deleteAll(reviewImages); - reviewRepository.delete(review); - - for (String imageUrl : deleted) { + List<String> imageUrls = reviewImages.stream() + .map(ReviewImage::getImageUrl) + .toList(); + + reviewImageRepository.deleteAll(reviewImages); + reviewRepository.delete(review); + + for (String imageUrl : imageUrls) {
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.javasrc/main/java/com/campus/campus/domain/place/application/service/PlaceService.javasrc/main/java/com/campus/campus/domain/review/application/dto/response/ReviewCreateResponse.javasrc/main/java/com/campus/campus/domain/review/application/dto/response/ReviewCreateResult.javasrc/main/java/com/campus/campus/domain/review/application/dto/response/WriteReviewResponse.javasrc/main/java/com/campus/campus/domain/review/application/exception/ErrorCode.javasrc/main/java/com/campus/campus/domain/review/application/mapper/ReviewMapper.javasrc/main/java/com/campus/campus/domain/review/application/service/ReviewService.javasrc/main/java/com/campus/campus/domain/review/domain/entity/ReviewImage.javasrc/main/java/com/campus/campus/domain/review/domain/repository/ReviewRepository.java
🚧 Files skipped from review as they are similar to previous changes (3)
- src/main/java/com/campus/campus/domain/review/application/dto/response/ReviewCreateResult.java
- src/main/java/com/campus/campus/domain/review/application/dto/response/ReviewCreateResponse.java
- src/main/java/com/campus/campus/domain/review/application/mapper/ReviewMapper.java
🧰 Additional context used
🧬 Code graph analysis (4)
src/main/java/com/campus/campus/domain/review/application/dto/response/WriteReviewResponse.java (4)
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/GetPostResponse.java (1)
Builder(13-35)src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetPostForUserResponse.java (1)
Builder(13-36)src/main/java/com/campus/campus/domain/user/application/dto/response/UserFirstProfileResponse.java (1)
Builder(6-17)
src/main/java/com/campus/campus/domain/review/domain/entity/ReviewImage.java (4)
src/main/java/com/campus/campus/domain/review/domain/entity/Review.java (1)
Entity(22-57)src/main/java/com/campus/campus/domain/councilpost/domain/entity/PostImage.java (1)
Entity(18-36)src/main/java/com/campus/campus/domain/councilnotice/domain/entity/NoticeImage.java (1)
Entity(19-36)src/main/java/com/campus/campus/global/entity/BaseEntity.java (1)
Getter(16-29)
src/main/java/com/campus/campus/domain/review/application/exception/ErrorCode.java (9)
src/main/java/com/campus/campus/domain/user/application/exception/ErrorCode.java (1)
Getter(10-22)src/main/java/com/campus/campus/domain/mail/application/exception/ErrorCode.java (1)
Getter(10-23)src/main/java/com/campus/campus/domain/council/application/exception/ErrorCode.java (1)
Getter(10-28)src/main/java/com/campus/campus/domain/school/application/exception/ErrorCode.java (1)
Getter(10-22)src/main/java/com/campus/campus/domain/place/application/exception/ErrorCode.java (1)
Getter(10-24)src/main/java/com/campus/campus/domain/councilpost/application/exception/ErrorCode.java (1)
Getter(10-38)src/main/java/com/campus/campus/global/common/exception/ErrorCodeInterface.java (1)
ErrorCodeInterface(5-11)src/main/java/com/campus/campus/global/firebase/exception/ErrorCode.java (1)
Getter(10-20)src/main/java/com/campus/campus/global/common/exception/ApplicationException.java (1)
Getter(5-18)
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java (5)
src/main/java/com/campus/campus/domain/councilpost/application/exception/PostImageLimitExceededException.java (1)
PostImageLimitExceededException(5-9)src/main/java/com/campus/campus/domain/review/application/exception/NotUserWriterException.java (1)
NotUserWriterException(5-9)src/main/java/com/campus/campus/domain/review/application/exception/ReviewNotFoundException.java (1)
ReviewNotFoundException(5-9)src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)src/main/java/com/campus/campus/global/oci/exception/OciObjectDeleteFailException.java (1)
OciObjectDeleteFailException(5-9)
🔇 Additional comments (6)
src/main/java/com/campus/campus/domain/review/application/dto/response/WriteReviewResponse.java (1)
7-18: LGTM!레코드 DTO 구조가 기존 코드베이스 패턴(NoticeListItemResponse, GetPostResponse 등)과 일관성 있게 작성되었습니다.
src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java (1)
114-132: LGTM!
SavedPlaceInfo로의 시그니처 변경과@Transactional추가가 적절합니다. 동시 생성 시DataIntegrityViolationException처리도 올바르게 구현되어 있습니다.src/main/java/com/campus/campus/domain/review/application/exception/ErrorCode.java (1)
10-21: LGTM!에러 코드 범위(2800번대)가 기존 도메인들과 충돌하지 않으며,
ErrorCodeInterface구현 패턴이 일관성 있게 작성되었습니다.src/main/java/com/campus/campus/domain/review/domain/repository/ReviewRepository.java (1)
17-33: LGTM!커서 기반 페이지네이션 쿼리가 올바르게 구현되었습니다.
(createdAt < cursor) OR (createdAt = cursor AND id < cursorId)조건과ORDER BY createdAt DESC, id DESC정렬이 정확히 일치합니다.src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java (2)
249-272: LGTM!
cleanupUnusedImages메서드가 null/blank URL을 적절히 처리하고, OCI 삭제 실패 시 로깅만 하고 트랜잭션을 실패시키지 않는 것이 적절합니다.
204-211: null 처리가 이미 적절히 구현되어 있음
ReviewMapper.toReviewResponse메서드의 63번 라인에서imageUrls != null ? imageUrls : Collections.emptyList()로 명시적 null 처리가 있습니다. 이미지가 없는 리뷰의 경우imageMap.get(review.getId())가 null을 반환하더라도 안전하게 빈 리스트로 변환되므로 추가 조치가 필요하지 않습니다.Likely an incorrect or invalid review comment.
...ava/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
Outdated
Show resolved
Hide resolved
| private ReviewRankingResponse getRankingResult(Place place, User user) { | ||
| Long placeId = place.getPlaceId(); | ||
| Major major = user.getMajor(); | ||
| College college = user.getCollege(); | ||
| School school = user.getSchool(); | ||
|
|
||
| Long majorId = major.getMajorId(); | ||
| Long collegeId = college.getCollegeId(); | ||
| Long schoolId = school.getSchoolId(); | ||
|
|
||
| long majorRank = reviewRepository.countByPlace_PlaceIdAndUser_Major_MajorId( | ||
| placeId, | ||
| majorId | ||
| ); | ||
|
|
||
| long collegeRank = reviewRepository.countByPlace_PlaceIdAndUser_College_CollegeId( | ||
| placeId, | ||
| collegeId | ||
| ); | ||
|
|
||
| long schoolRank = reviewRepository.countByPlace_PlaceIdAndUser_School_SchoolId( | ||
| placeId, | ||
| schoolId | ||
| ); | ||
|
|
||
| return reviewMapper.toReviewRankingResponse(major.getMajorName(), majorRank, college.getCollegeName(), | ||
| collegeRank, | ||
| school.getSchoolName(), schoolRank); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사용자 학적 정보 null 체크 누락 (getRankingResult)
readPopularPartnerships와 동일하게, user.getMajor(), user.getCollege(), user.getSchool()이 null일 경우 NullPointerException이 발생합니다. writeReview에서 호출되므로 리뷰 작성 시에도 동일한 문제가 발생할 수 있습니다.
🐛 제안하는 수정
private ReviewRankingResponse getRankingResult(Place place, User user) {
Long placeId = place.getPlaceId();
Major major = user.getMajor();
College college = user.getCollege();
School school = user.getSchool();
+ if (major == null || college == null || school == null) {
+ return reviewMapper.toReviewRankingResponse(null, 0L, null, 0L, null, 0L);
+ }
+
Long majorId = major.getMajorId();📝 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.
| private ReviewRankingResponse getRankingResult(Place place, User user) { | |
| Long placeId = place.getPlaceId(); | |
| Major major = user.getMajor(); | |
| College college = user.getCollege(); | |
| School school = user.getSchool(); | |
| Long majorId = major.getMajorId(); | |
| Long collegeId = college.getCollegeId(); | |
| Long schoolId = school.getSchoolId(); | |
| long majorRank = reviewRepository.countByPlace_PlaceIdAndUser_Major_MajorId( | |
| placeId, | |
| majorId | |
| ); | |
| long collegeRank = reviewRepository.countByPlace_PlaceIdAndUser_College_CollegeId( | |
| placeId, | |
| collegeId | |
| ); | |
| long schoolRank = reviewRepository.countByPlace_PlaceIdAndUser_School_SchoolId( | |
| placeId, | |
| schoolId | |
| ); | |
| return reviewMapper.toReviewRankingResponse(major.getMajorName(), majorRank, college.getCollegeName(), | |
| collegeRank, | |
| school.getSchoolName(), schoolRank); | |
| } | |
| private ReviewRankingResponse getRankingResult(Place place, User user) { | |
| Long placeId = place.getPlaceId(); | |
| Major major = user.getMajor(); | |
| College college = user.getCollege(); | |
| School school = user.getSchool(); | |
| if (major == null || college == null || school == null) { | |
| return reviewMapper.toReviewRankingResponse(null, 0L, null, 0L, null, 0L); | |
| } | |
| Long majorId = major.getMajorId(); | |
| Long collegeId = college.getCollegeId(); | |
| Long schoolId = school.getSchoolId(); | |
| long majorRank = reviewRepository.countByPlace_PlaceIdAndUser_Major_MajorId( | |
| placeId, | |
| majorId | |
| ); | |
| long collegeRank = reviewRepository.countByPlace_PlaceIdAndUser_College_CollegeId( | |
| placeId, | |
| collegeId | |
| ); | |
| long schoolRank = reviewRepository.countByPlace_PlaceIdAndUser_School_SchoolId( | |
| placeId, | |
| schoolId | |
| ); | |
| return reviewMapper.toReviewRankingResponse(major.getMajorName(), majorRank, college.getCollegeName(), | |
| collegeRank, | |
| school.getSchoolName(), schoolRank); | |
| } |
🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
around lines 286 - 314, getRankingResult can throw NullPointerException because
it assumes user.getMajor(), user.getCollege(), and user.getSchool() are
non-null; add null checks at the start of getRankingResult and handle missing
relations by either returning sensible defaults or computing counts with
null-safe logic: check user.getMajor()/getCollege()/getSchool(), only call
major.getMajorId()/college.getCollegeId()/school.getSchoolId() and the
repository methods when the corresponding object is non-null, and supply
fallback names/counts (e.g., empty string and 0) when null so
reviewMapper.toReviewRankingResponse is called with safe values; update
getRankingResult to perform these null guards around Major/College/School and
adjust the variables majorId/collegeId/schoolId and
majorRank/collegeRank/schoolRank accordingly.
1winhyun
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고하셨습니다!! 현재 ci가 터지고 있는데 해당 원인은 ReviewController의 updateReview 때문인 것으로 확인됩니다. 해당 부분 확인 부탁드려요!!
...ava/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
Show resolved
Hide resolved
| return futures.stream().map(CompletableFuture::join).toList(); | ||
| } | ||
|
|
||
| @Transactional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이미 해당 메서드가 사용되는 상위 메서드들에 트랜잭션 어노테이션이 붙어있어요. 따라서 해당 메서드에 트랜잭션 어노테이션을 붙여 중복되게 할 필요는 없을 것 같아요!!
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
Show resolved
Hide resolved
| @PathVariable Long reviewId, | ||
| @RequestBody @Valid ReviewRequest request | ||
| ) { | ||
| ReviewResponse response = reviewService.update(userId, reviewId, request); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 부분은 update 메서드와 반환값이 다릅니다!! 확인하고 수정 부탁드려요!!
There was a problem hiding this 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/domain/repository/StudentCouncilPostRepository.java:
- Around line 286-306: The query in StudentCouncilPostRepository currently lacks
a category filter and can return non-PARTNERSHIP posts; add a category predicate
like "AND scp.category = :category" to the WHERE clause of the annotated query
and update the corresponding repository method signature to accept a category
parameter (e.g., CouncilPostCategory category), then ensure callers of the
method pass the intended CouncilPostCategory.PARTNERSHIP value (or make the
param fixed in the method if it should always be PARTNERSHIP).
In
@src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java:
- Around line 288-296: getRankingResult currently dereferences user.getMajor(),
user.getCollege(), and user.getSchool() and can throw NPE; add the same
null-checking logic used in readPopularPartnerships before accessing
major.getMajorId(), college.getCollegeId(), and school.getSchoolId().
Specifically, guard the retrieval of Major major, College college, School school
and only compute Long majorId, collegeId, schoolId when the corresponding
objects are non-null (or set those Longs to null/default and adapt downstream
calls in getRankingResult to handle nulls). Ensure any repository/service calls
inside getRankingResult that rely on these IDs mirror the null-safe behavior
used in readPopularPartnerships.
- Around line 230-238: NullPointerException risk: before calling
studentCouncilPostRepository.findTop3RecommendedPartnershipPlaces, null-check
user.getMajor(), user.getCollege(), and user.getSchool() and handle cases where
any is null. Retrieve ids via local variables using null-safe checks (e.g., Long
majorId = user.getMajor() != null ? user.getMajor().getMajorId() : null) and
then either 1) skip the repository call and set partnerships to an empty list,
2) call an overloaded repository method that accepts nullable params, or 3)
throw a clear domain exception; update the code around ReviewService's
partnerships assignment to use these null-safe ids or the chosen fallback.
🧹 Nitpick comments (4)
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (1)
304-306: GROUP BY 절 검토 권장
GROUP BY scp는 일부 데이터베이스의 strict SQL 모드에서 문제가 될 수 있습니다.GROUP BY scp.id로 명시하는 것이 더 안전합니다.♻️ GROUP BY 수정 제안
- GROUP BY scp + GROUP BY scp.idsrc/main/java/com/campus/campus/domain/review/application/service/ReviewService.java (2)
74-78: 이미지 저장 배치 처리 권장개별
save()호출 대신saveAll()을 사용하면 데이터베이스 라운드트립을 줄일 수 있습니다.♻️ 배치 저장으로 개선
if (request.imageUrls() != null) { - for (String imageUrl : request.imageUrls()) { - reviewImageRepository.save(reviewMapper.createReviewImage(review, imageUrl)); - } + List<ReviewImage> images = request.imageUrls().stream() + .map(url -> reviewMapper.createReviewImage(review, url)) + .toList(); + reviewImageRepository.saveAll(images); }
117-120: Set 구축 간소화 가능스트림을 사용해 Set을 직접 생성할 수 있습니다.
♻️ 간소화된 코드
- Set<String> deleted = new HashSet<>(); - reviewImages.stream() - .map(ReviewImage::getImageUrl) - .forEach(deleted::add); + Set<String> deleted = reviewImages.stream() + .map(ReviewImage::getImageUrl) + .collect(Collectors.toSet());src/main/java/com/campus/campus/domain/review/presentation/ReviewController.java (1)
110-121: size 파라미터 상한 검증 권장
size파라미터에 상한이 없어 악의적인 요청 시 대량 데이터를 조회할 수 있습니다.@Max어노테이션으로 제한하거나 서비스 레이어에서 검증하는 것이 좋습니다.♻️ size 제한 추가
+import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; + @GetMapping("/list/{placeId}") @Operation(summary = "리뷰 목록 조회 - 최신순 (더보기 이후)") public CommonResponse<CursorPageReviewResponse<ReviewResponse>> readAllReviews( @PathVariable Long placeId, @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime cursorCreatedAt, @RequestParam(required = false) Long cursorId, - @RequestParam(defaultValue = "10") int size + @RequestParam(defaultValue = "10") @Min(1) @Max(100) int size ) {
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.javasrc/main/java/com/campus/campus/domain/place/application/service/PlaceService.javasrc/main/java/com/campus/campus/domain/review/application/service/ReviewService.javasrc/main/java/com/campus/campus/domain/review/presentation/ReviewController.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java (4)
src/main/java/com/campus/campus/domain/councilpost/application/exception/PostImageLimitExceededException.java (1)
PostImageLimitExceededException(5-9)src/main/java/com/campus/campus/domain/review/application/exception/NotUserWriterException.java (1)
NotUserWriterException(5-9)src/main/java/com/campus/campus/domain/review/application/exception/ReviewNotFoundException.java (1)
ReviewNotFoundException(5-9)src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)
src/main/java/com/campus/campus/domain/review/presentation/ReviewController.java (1)
src/main/java/com/campus/campus/domain/user/presentation/AuthController.java (1)
RestController(20-42)
⏰ 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 (8)
src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java (4)
91-103: LGTM!읽기 전용 트랜잭션으로 리뷰와 이미지를 조회하는 로직이 명확합니다.
134-168: LGTM!리뷰 업데이트 로직이 올바르게 구현되었습니다. 이미지 교체 후 미사용 이미지를 정리하는 순서가 적절합니다.
206-213: 이미지가 없는 리뷰에 대한 null 처리 확인
imageMap.get(review.getId())는 이미지가 없는 리뷰에 대해null을 반환합니다.toReviewResponse가null이미지 목록을 올바르게 처리하는지 확인해 주세요.getOrDefault를 사용하면 더 안전합니다.♻️ null-safe 처리 제안
List<ReviewResponse> items = reviews.stream() .map(review -> reviewMapper.toReviewResponse( review, - imageMap.get(review.getId()) + imageMap.getOrDefault(review.getId(), List.of()) ) ) .toList();
251-274: LGTM!미사용 이미지 정리 로직이 올바르게 구현되었습니다. OCI 삭제 실패 시 로깅 후 계속 진행하는 것이 적절합니다.
src/main/java/com/campus/campus/domain/review/presentation/ReviewController.java (3)
38-83: LGTM!리뷰 작성 엔드포인트가 적절하게 구현되었습니다. Swagger 예제가 API 문서화에 도움이 됩니다.
85-108: LGTM!조회/삭제/수정 엔드포인트가 적절하게 구현되었습니다. 리뷰 상세 조회가 인증 없이 공개 접근 가능한 것이 의도된 것인지 확인해 주세요.
123-130: LGTM!제휴 매장 랭킹 조회 엔드포인트가 적절하게 구현되었습니다.
src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java (1)
91-108: 메서드 시그니처 변경이 적절합니다.
PostRequest에서SavedPlaceInfo로 파라미터 타입을 변경하여 리뷰 도메인에서도 재사용 가능하도록 개선되었습니다. ReviewService(writeReview)와 StudentCouncilPostService(create, update) 등 모든 호출부가 이미SavedPlaceInfo타입을 전달하고 있으므로 호환성에 문제가 없습니다.
| @Query(""" | ||
| SELECT scp | ||
| FROM StudentCouncilPost scp | ||
| JOIN scp.writer sc | ||
| LEFT JOIN Review r | ||
| ON r.place = scp.place | ||
| AND r.createdAt >= :from | ||
| WHERE scp.startDateTime <= :now | ||
| AND scp.endDateTime >= :now | ||
| AND ( | ||
| (sc.councilType = com.campus.campus.domain.council.domain.entity.CouncilType.MAJOR_COUNCIL | ||
| AND sc.major.majorId = :majorId) | ||
| OR (sc.councilType = com.campus.campus.domain.council.domain.entity.CouncilType.COLLEGE_COUNCIL | ||
| AND sc.college.collegeId = :collegeId) | ||
| OR (sc.councilType = com.campus.campus.domain.council.domain.entity.CouncilType.SCHOOL_COUNCIL | ||
| AND sc.school.schoolId = :schoolId) | ||
| ) | ||
| AND sc.deletedAt IS NULL | ||
| GROUP BY scp | ||
| ORDER BY COUNT(r.id) DESC | ||
| """) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
카테고리 필터 누락 가능성 확인 필요
이 쿼리는 PARTNERSHIP 카테고리로 필터링하지 않아 EVENT 게시물도 포함될 수 있습니다. findRandomActivePartnerships 등 유사한 메서드들은 p.category = :category 조건을 사용합니다. 의도된 동작인지 확인해 주세요.
🐛 카테고리 필터 추가 제안
WHERE scp.startDateTime <= :now
AND scp.endDateTime >= :now
+ AND scp.category = com.campus.campus.domain.councilpost.domain.entity.PostCategory.PARTNERSHIP
AND (
(sc.councilType = com.campus.campus.domain.council.domain.entity.CouncilType.MAJOR_COUNCIL🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
around lines 286 - 306, The query in StudentCouncilPostRepository currently
lacks a category filter and can return non-PARTNERSHIP posts; add a category
predicate like "AND scp.category = :category" to the WHERE clause of the
annotated query and update the corresponding repository method signature to
accept a category parameter (e.g., CouncilPostCategory category), then ensure
callers of the method pass the intended CouncilPostCategory.PARTNERSHIP value
(or make the param fixed in the method if it should always be PARTNERSHIP).
| List<StudentCouncilPost> partnerships = | ||
| studentCouncilPostRepository.findTop3RecommendedPartnershipPlaces( | ||
| user.getMajor().getMajorId(), | ||
| user.getCollege().getCollegeId(), | ||
| user.getSchool().getSchoolId(), | ||
| from, | ||
| now, | ||
| PageRequest.of(0, 3) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NPE 위험: 사용자의 major/college/school이 null일 수 있음
user.getMajor(), user.getCollege(), user.getSchool()이 null인 경우 getMajorId() 등 호출 시 NullPointerException이 발생합니다. 사용자 엔티티에서 이 필드들이 nullable인지 확인하고, 필요시 null 체크를 추가해 주세요.
🐛 null 체크 추가 제안
+ Major major = user.getMajor();
+ College college = user.getCollege();
+ School school = user.getSchool();
+
+ Long majorId = major != null ? major.getMajorId() : null;
+ Long collegeId = college != null ? college.getCollegeId() : null;
+ Long schoolId = school != null ? school.getSchoolId() : null;
+
List<StudentCouncilPost> partnerships =
studentCouncilPostRepository.findTop3RecommendedPartnershipPlaces(
- user.getMajor().getMajorId(),
- user.getCollege().getCollegeId(),
- user.getSchool().getSchoolId(),
+ majorId,
+ collegeId,
+ schoolId,
from,
now,
PageRequest.of(0, 3)
);📝 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.
| List<StudentCouncilPost> partnerships = | |
| studentCouncilPostRepository.findTop3RecommendedPartnershipPlaces( | |
| user.getMajor().getMajorId(), | |
| user.getCollege().getCollegeId(), | |
| user.getSchool().getSchoolId(), | |
| from, | |
| now, | |
| PageRequest.of(0, 3) | |
| ); | |
| Major major = user.getMajor(); | |
| College college = user.getCollege(); | |
| School school = user.getSchool(); | |
| Long majorId = major != null ? major.getMajorId() : null; | |
| Long collegeId = college != null ? college.getCollegeId() : null; | |
| Long schoolId = school != null ? school.getSchoolId() : null; | |
| List<StudentCouncilPost> partnerships = | |
| studentCouncilPostRepository.findTop3RecommendedPartnershipPlaces( | |
| majorId, | |
| collegeId, | |
| schoolId, | |
| from, | |
| now, | |
| PageRequest.of(0, 3) | |
| ); |
🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
around lines 230 - 238, NullPointerException risk: before calling
studentCouncilPostRepository.findTop3RecommendedPartnershipPlaces, null-check
user.getMajor(), user.getCollege(), and user.getSchool() and handle cases where
any is null. Retrieve ids via local variables using null-safe checks (e.g., Long
majorId = user.getMajor() != null ? user.getMajor().getMajorId() : null) and
then either 1) skip the repository call and set partnerships to an empty list,
2) call an overloaded repository method that accepts nullable params, or 3)
throw a clear domain exception; update the code around ReviewService's
partnerships assignment to use these null-safe ids or the chosen fallback.
| private ReviewRankingResponse getRankingResult(Place place, User user) { | ||
| Long placeId = place.getPlaceId(); | ||
| Major major = user.getMajor(); | ||
| College college = user.getCollege(); | ||
| School school = user.getSchool(); | ||
|
|
||
| Long majorId = major.getMajorId(); | ||
| Long collegeId = college.getCollegeId(); | ||
| Long schoolId = school.getSchoolId(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NPE 위험: getRankingResult에서도 동일한 null 체크 필요
readPopularPartnerships와 동일하게 user.getMajor(), user.getCollege(), user.getSchool()이 null일 경우 NPE가 발생합니다.
🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/review/application/service/ReviewService.java
around lines 288 - 296, getRankingResult currently dereferences user.getMajor(),
user.getCollege(), and user.getSchool() and can throw NPE; add the same
null-checking logic used in readPopularPartnerships before accessing
major.getMajorId(), college.getCollegeId(), and school.getSchoolId().
Specifically, guard the retrieval of Major major, College college, School school
and only compute Long majorId, collegeId, schoolId when the corresponding
objects are non-null (or set those Longs to null/default and adapt downstream
calls in getRankingResult to handle nulls). Ensure any repository/service calls
inside getRankingResult that rely on these IDs mirror the null-safe behavior
used in readPopularPartnerships.
There was a problem hiding this 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/review/application/dto/request/ReviewRequest.java:
- Around line 19-21: ReviewRequest's star field lacks range validation allowing
negative or >5 values; add Jakarta Bean Validation range annotations by
importing DecimalMin and DecimalMax and annotate the star field with
@DecimalMin(value = "0.0") and @DecimalMax(value = "5.0") (keep existing
@NotNull) so the Double star field enforces 0.0–5.0 inclusive.
- Around line 14-17: The @Size annotation on the ReviewRequest.content field
currently specifies min = 10 while the message says "최소 20자 이상이어야 합니다"; update
the validation message to match the configured minimum (10) or change the min to
20 if 20 is intended—specifically edit the ReviewRequest class's content field
annotations (the @Size on field named content) so the message and the min value
are consistent (e.g., set message to "리뷰 내용은 최소 10자 이상이어야 합니다." when keeping min
= 10).
- Around line 25-44: The @Schema example for the SavedPlaceInfo 'place' field in
ReviewRequest is missing the outer JSON braces; update the @Schema(example =
"""...""") value in class ReviewRequest (field: place) to wrap the shown content
with enclosing { ... } so the example becomes a valid JSON object, keeping the
existing multi-line string and description unchanged.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.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
src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java
Show resolved
Hide resolved
| @Schema(example = | ||
| """ | ||
| "placeName": "숙명여자대학교", | ||
| "placeKey": "string", | ||
| "address": "서울특별시 용산구 청파로47길 99", | ||
| "category": "교육,학문>대학교", | ||
| "link": "https://map.naver.com/v5/search/%EC%88%99%EB%AA%85%EC%97%AC%EC%9E%90%EB%8C%80%ED%95%99%EA%B5%90+%EC%A0%9C1%EC%BA%A0%ED%8D%BC%EC%8A%A4?c=37.545947,126.964578,15,0,0,0,dh", | ||
| "telephone": "010-1234-1234", | ||
| "coordinate": { | ||
| "latitude": 0.1, | ||
| "longitude": 0.1 | ||
| }, | ||
| "imgUrls": [ | ||
| "string" | ||
| ] | ||
| """, | ||
| description = "/search API에서 반환된 결과 중 하나를 선택") | ||
| @NotNull | ||
| @Valid | ||
| SavedPlaceInfo place |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Schema 예시 JSON 형식이 올바르지 않습니다.
place 필드의 @Schema 예시가 JSON 객체의 외부 중괄호 {}가 누락되어 있어 Swagger UI에서 잘못된 형식으로 표시될 수 있습니다.
📝 수정 제안
@Schema(example =
"""
- "placeName": "숙명여자대학교",
+ {
+ "placeName": "숙명여자대학교",
"placeKey": "string",
"address": "서울특별시 용산구 청파로47길 99",
"category": "교육,학문>대학교",
"link": "https://map.naver.com/v5/search/...",
"telephone": "010-1234-1234",
"coordinate": {
"latitude": 0.1,
"longitude": 0.1
},
"imgUrls": [
"string"
]
- """,
+ }
+ """,
description = "/search API에서 반환된 결과 중 하나를 선택")🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java
around lines 25 - 44, The @Schema example for the SavedPlaceInfo 'place' field
in ReviewRequest is missing the outer JSON braces; update the @Schema(example =
"""...""") value in class ReviewRequest (field: place) to wrap the shown content
with enclosing { ... } so the example becomes a valid JSON object, keeping the
existing multi-line string and description unchanged.
1winhyun
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수고하셨습니다!! 간단한 부분만 수정 부탁드려요!!
수정 완료되었다면 로그인 방식 관련한 부분만 기존 방식으로 수정 후 merge 부탁드립니다!!
src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java
Show resolved
Hide resolved
src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java
Show resolved
Hide resolved
There was a problem hiding this 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/review/application/dto/request/ReviewRequest.java:
- Around line 5-6: ReviewRequest가 응답 DTO인 SavedPlaceInfo에 의존해 검증 범위가 제한되어 있으니,
리뷰용으로 별도 PlaceRequest DTO를 생성하거나 SavedPlaceInfo의 모든 리뷰에 필요한 필수 필드에 명시적
검증(@NotBlank, @NotNull 등)을 추가하여 결합도를 낮추고 검증을 완전하게 만드세요; 구체적으로는 ReviewRequest에서
SavedPlaceInfo를 참조하는 부분을 찾아(예: ReviewRequest.place 필드/생성자) PlaceRequest로 교체하거나
SavedPlaceInfo의 link, telephone, coordinate, imgUrls 등에 적절한 제약을 추가하고
ReviewController/Service에서 @Valid가 적용되도록 보장하세요.
- Around line 29-45: The @Schema(example = """...""") JSON in ReviewRequest is
not a valid top-level JSON object; update the example inside the triple-quoted
string in the ReviewRequest class's @Schema annotation to be a valid JSON object
(wrap the existing fields with top-level { ... }, ensure all keys/values are
properly quoted and commas are correct) so Swagger can parse and render it;
locate the @Schema on the ReviewRequest DTO and replace the current invalid
example with a syntactically valid JSON example.
- Around line 21-25: The star field in ReviewRequest (Double star) only checks
range 0.0–5.0 and lacks enforcement of 0.5 steps; add a validation that ensures
star is a multiple of 0.5 either by creating a custom Bean Validation constraint
(e.g., @DecimalPlace or @Step(value = 0.5) with its corresponding
ConstraintValidator applied to the ReviewRequest.star) or by adding an explicit
check in the review creation service (e.g., in the method that consumes
ReviewRequest, such as ReviewService.createReview) that validates (star * 2) is
an integer and throws a proper BadRequest/ConstraintViolationException when it’s
not.
🧹 Nitpick comments (2)
src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java (2)
16-19:content는@NotBlank가 없어서 공백-only 입력을 허용합니다.현재는 길이만 10자 이상이면 통과해서
" "같은 값이 유효해질 수 있어요.@NotBlank추가 및max도 함께 두는 쪽을 권장합니다.Proposed diff
import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; public record ReviewRequest( @NotNull - @Size(min = 10, message = "리뷰 내용은 최소 10자 이상이어야 합니다.") + @NotBlank(message = "리뷰 내용은 비어 있을 수 없습니다.") + @Size(min = 10, max = 1000, message = "리뷰 내용은 최소 10자 이상, 최대 1000자 이하여야 합니다.") @Schema(example = "아주 정말 맛있습니다. 저의 완전 짱 또간집. 꼭꼮꼬꼬꼭 가세요.") String content,
27-27:imageUrls는 원소/개수/길이 제한이 없어 페이로드 과다·비정상 값 유입 여지가 큽니다.최소한 리스트 최대 개수와 각 원소
@NotBlank, (필요 시) URL 포맷/도메인 화이트리스트 등 정책을 DTO 레벨에서 걸어두는 편이 운영 리스크가 적습니다.Proposed diff (lightweight)
import java.util.List; +import jakarta.validation.constraints.Size; +import jakarta.validation.constraints.NotBlank; public record ReviewRequest( ... - List<String> imageUrls, + @Size(max = 10, message = "이미지는 최대 10개까지 첨부할 수 있습니다.") + List<@NotBlank(message = "이미지 URL은 비어 있을 수 없습니다.") String> imageUrls, ... ) { }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.gitignoresrc/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.javasrc/main/java/com/campus/campus/domain/user/application/service/KakaoOauthService.java
🚧 Files skipped from review as they are similar to previous changes (1)
- .gitignore
⏰ 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 (1)
src/main/java/com/campus/campus/domain/user/application/service/KakaoOauthService.java (1)
13-25: Import 정리/추가 자체는 무난합니다.추가된
UserSignupForbiddenException,LoginMapper,RedisTokenServiceimport들이 실제 사용과 일치하고, 미사용 import가 남지 않도록만 IDE/빌드에서 한번 확인하면 될 것 같습니다.
| import com.campus.campus.domain.place.application.dto.response.SavedPlaceInfo; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
fd -t f ReviewRequest.javaRepository: our-campUS/our-campUS-BE
Length of output: 192
🏁 Script executed:
find . -type f -name "SavedPlaceInfo.java"Repository: our-campUS/our-campUS-BE
Length of output: 159
🏁 Script executed:
find . -type f -name "PlaceRequest.java"Repository: our-campUS/our-campUS-BE
Length of output: 50
🏁 Script executed:
cat -n src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.javaRepository: our-campUS/our-campUS-BE
Length of output: 1899
🏁 Script executed:
cat -n src/main/java/com/campus/campus/domain/place/application/dto/response/SavedPlaceInfo.javaRepository: our-campUS/our-campUS-BE
Length of output: 1418
요청 DTO가 응답 DTO(SavedPlaceInfo)에 의존하는 구조는 결합도가 높고 검증 범위가 제한적입니다.
SavedPlaceInfo는 placeName, placeKey, address, category에만 @NotBlank 검증이 적용되어 있고, link, telephone, coordinate, imgUrls 필드는 검증 제약이 없습니다. 따라서 @Valid가 적용되더라도 모든 입력 필드를 검증하지 못합니다. 리뷰 작성용 별도의 PlaceRequest DTO를 분리하거나, SavedPlaceInfo의 모든 필수 필드에 명시적 검증 제약을 추가하는 것을 권장합니다.
🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java
around lines 5 - 6, ReviewRequest가 응답 DTO인 SavedPlaceInfo에 의존해 검증 범위가 제한되어 있으니,
리뷰용으로 별도 PlaceRequest DTO를 생성하거나 SavedPlaceInfo의 모든 리뷰에 필요한 필수 필드에 명시적
검증(@NotBlank, @NotNull 등)을 추가하여 결합도를 낮추고 검증을 완전하게 만드세요; 구체적으로는 ReviewRequest에서
SavedPlaceInfo를 참조하는 부분을 찾아(예: ReviewRequest.place 필드/생성자) PlaceRequest로 교체하거나
SavedPlaceInfo의 link, telephone, coordinate, imgUrls 등에 적절한 제약을 추가하고
ReviewController/Service에서 @Valid가 적용되도록 보장하세요.
| @NotNull | ||
| @DecimalMin(value = "0.0", inclusive = true) | ||
| @DecimalMax(value = "5.0", inclusive = true) | ||
| @Schema(example = "3.5") | ||
| Double star, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "ReviewRequest.java" | head -5Repository: our-campUS/our-campUS-BE
Length of output: 192
🏁 Script executed:
pwd && ls -la | head -20Repository: our-campUS/our-campUS-BE
Length of output: 906
🏁 Script executed:
find . -type f -name "ReviewRequest.java"Repository: our-campUS/our-campUS-BE
Length of output: 158
🏁 Script executed:
cat -n ./src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.javaRepository: our-campUS/our-campUS-BE
Length of output: 1899
🏁 Script executed:
find . -type f -name "*Validator*.java" | head -20Repository: our-campUS/our-campUS-BE
Length of output: 142
🏁 Script executed:
find . -type f -name "*ReviewService*.java"Repository: our-campUS/our-campUS-BE
Length of output: 154
🏁 Script executed:
cat -n ./src/main/java/com/campus/campus/domain/review/application/service/ReviewService.javaRepository: our-campUS/our-campUS-BE
Length of output: 12667
🏁 Script executed:
find . -type f -name "Review.java" -path "*/domain/entity/*"Repository: our-campUS/our-campUS-BE
Length of output: 141
🏁 Script executed:
cat -n ./src/main/java/com/campus/campus/domain/review/domain/entity/Review.javaRepository: our-campUS/our-campUS-BE
Length of output: 1942
🏁 Script executed:
find . -type f -name "*ReviewTest*.java" -o -name "*ReviewRequest*.java" | grep -i testRepository: our-campUS/our-campUS-BE
Length of output: 50
🏁 Script executed:
rg "0\.5|granularity|decimal|star" --type java -i | head -30Repository: our-campUS/our-campUS-BE
Length of output: 4196
🏁 Script executed:
cat -n ./src/main/java/com/campus/campus/domain/review/presentation/ReviewController.java | grep -A 20 -B 20 "star"Repository: our-campUS/our-campUS-BE
Length of output: 1631
🏁 Script executed:
find . -type f \( -name "*.md" -o -name "*.yaml" -o -name "*.yml" \) | xargs grep -l "star\|rating" 2>/dev/null | head -5Repository: our-campUS/our-campUS-BE
Length of output: 235
🏁 Script executed:
grep -A 5 -B 5 "star\|rating" ./src/main/resources/application-dev.yml ./src/main/resources/application-local.yml ./src/main/resources/application-prod.ymlRepository: our-campUS/our-campUS-BE
Length of output: 2156
🏁 Script executed:
find . -type f -name "*.java" -path "*review*" -exec grep -l "comment\|javadoc\|documentation\|granularity\|unit\|step" {} \;Repository: our-campUS/our-campUS-BE
Length of output: 50
🏁 Script executed:
find . -path "*/test/*" -name "*Review*Test*.java" -o -path "*/test/*" -name "*Test*Review*.java"Repository: our-campUS/our-campUS-BE
Length of output: 50
star 필드의 단위(예: 0.5점) 검증 로직이 구현되지 않았습니다.
현재 코드는 0.0~5.0 범위 검증만 수행하며, 0.5 단위 강제 같은 세부 정책은 Bean Validation만으로는 처리되지 않습니다. 컨트롤러 예제에 "3.5"가 명시되어 있지만 실제로는 3.14 같은 임의의 값도 저장될 수 있습니다. 커스텀 @DecimalPlace 제약조건이나 서비스 레벨 검증으로 보완이 필요합니다.
🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java
around lines 21 - 25, The star field in ReviewRequest (Double star) only checks
range 0.0–5.0 and lacks enforcement of 0.5 steps; add a validation that ensures
star is a multiple of 0.5 either by creating a custom Bean Validation constraint
(e.g., @DecimalPlace or @Step(value = 0.5) with its corresponding
ConstraintValidator applied to the ReviewRequest.star) or by adding an explicit
check in the review creation service (e.g., in the method that consumes
ReviewRequest, such as ReviewService.createReview) that validates (star * 2) is
an integer and throws a proper BadRequest/ConstraintViolationException when it’s
not.
| @Schema(example = | ||
| """ | ||
| "placeName": "숙명여자대학교", | ||
| "placeKey": "string", | ||
| "address": "서울특별시 용산구 청파로47길 99", | ||
| "category": "교육,학문>대학교", | ||
| "link": "https://map.naver.com/v5/search/%EC%88%99%EB%AA%85%EC%97%AC%EC%9E%90%EB%8C%80%ED%95%99%EA%B5%90+%EC%A0%9C1%EC%BA%A0%ED%8D%BC%EC%8A%A4?c=37.545947,126.964578,15,0,0,0,dh", | ||
| "telephone": "010-1234-1234", | ||
| "coordinate": { | ||
| "latitude": 0.1, | ||
| "longitude": 0.1 | ||
| }, | ||
| "imgUrls": [ | ||
| "string" | ||
| ] | ||
| """, | ||
| description = "/search API에서 반환된 결과 중 하나를 선택") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "ReviewRequest.java" | head -20Repository: our-campUS/our-campUS-BE
Length of output: 158
🏁 Script executed:
cat -n ./src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java | sed -n '20,50p'Repository: our-campUS/our-campUS-BE
Length of output: 1120
🏁 Script executed:
find . -type f -name "SavedPlaceInfo.java" | head -5Repository: our-campUS/our-campUS-BE
Length of output: 159
🏁 Script executed:
cat -n ./src/main/java/com/campus/campus/domain/place/application/dto/response/SavedPlaceInfo.javaRepository: our-campUS/our-campUS-BE
Length of output: 1418
@Schema(example = """ ... """)의 JSON 예시가 유효하지 않아 Swagger UI에서 오류를 야기할 수 있습니다.
최상위 {}를 포함하여 유효한 JSON 형식으로 수정이 필요합니다.
제안 수정
@Schema(example =
"""
- "placeName": "숙명여자대학교",
- "placeKey": "string",
- "address": "서울특별시 용산구 청파로47길 99",
- "category": "교육,학문>대학교",
- "link": "https://map.naver.com/v5/search/%EC%88%99%EB%AA%85%EC%97%AC%EC%9E%90%EB%8C%80%ED%95%99%EA%B5%90+%EC%A0%9C1%EC%BA%A0%ED%8D%BC%EC%8A%A4?c=37.545947,126.964578,15,0,0,0,dh",
- "telephone": "010-1234-1234",
- "coordinate": {
- "latitude": 0.1,
- "longitude": 0.1
- },
- "imgUrls": [
- "string"
- ]
+ {
+ "placeName": "숙명여자대학교",
+ "placeKey": "string",
+ "address": "서울특별시 용산구 청파로47길 99",
+ "category": "교육,학문>대학교",
+ "link": "https://map.naver.com/v5/search/%EC%88%99%EB%AA%85%EC%97%AC%EC%9E%90%EB%8C%80%ED%95%99%EA%B5%90+%EC%A0%9C1%EC%BA%A0%ED%8D%BC%EC%8A%A4?c=37.545947,126.964578,15,0,0,0,dh",
+ "telephone": "010-1234-1234",
+ "coordinate": { "latitude": 0.1, "longitude": 0.1 },
+ "imgUrls": ["string"]
+ }
""",
description = "/search API에서 반환된 결과 중 하나를 선택")🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/review/application/dto/request/ReviewRequest.java
around lines 29 - 45, The @Schema(example = """...""") JSON in ReviewRequest is
not a valid top-level JSON object; update the example inside the triple-quoted
string in the ReviewRequest class's @Schema annotation to be a valid JSON object
(wrap the existing fields with top-level { ... }, ensure all keys/values are
properly quoted and commas are correct) so Swagger can parse and render it;
locate the @Schema on the ReviewRequest DTO and replace the current invalid
example with a syntactically valid JSON example.
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.http.MediaType; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import org.springframework.util.LinkedMultiValueMap; | ||
| import org.springframework.util.MultiValueMap; | ||
| import org.springframework.web.client.RestClient; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RestClient 사용 전제(스프링 버전/타임아웃) 검증이 필요합니다.
org.springframework.web.client.RestClient는 프로젝트의 Spring Framework/Boot 버전에 따라 컴파일 자체가 깨질 수 있고, 외부 API 호출에 타임아웃이 명시적으로 없으면 요청 스레드가 장시간 블로킹될 수 있습니다. RestClient를 실제로 쓰는 경로에서 (1) 버전 호환, (2) connect/read 타임아웃, (3) 상태코드별 예외 매핑이 갖춰졌는지 확인해주세요.
#!/bin/bash
set -euo pipefail
# 1) RestClient 사용처 확인
rg -n --type=java '\bRestClient\b' -S
# 2) Spring(Boot) 버전/의존성 단서 확인(Gradle/Maven 모두 탐색)
fd -H 'build\.gradle(\.kts)?|settings\.gradle(\.kts)?|pom\.xml' . -x bash -lc 'echo "===== {} ====="; sed -n "1,200p" "{}"'
# 3) RestClient.Builder / 타임아웃 설정 코드 유무 탐색
rg -n -S 'RestClient\.builder\(|new RestClient|HttpClient|setConnectTimeout|responseTimeout|readTimeout|connectTimeout|clientHttpRequestFactory' .
🔀 변경 내용
✅ 작업 항목
📸 스크린샷 (선택)
📎 참고 이슈
관련 이슈 번호 #37
Summary by CodeRabbit
New Features
Bug Fixes
Chores
✏️ Tip: You can customize this high-level summary in your review settings.