Skip to content

Conversation

@1winhyun
Copy link
Member

@1winhyun 1winhyun commented Jan 13, 2026

🔀 변경 내용

  • 시간대별 랜덤 장소 추천 기능 구현했습니다.

✅ 작업 항목

  • 시간대별 랜덤 장소 추천 기능 구현. 시간대별로 로그인한 유저가 속한 학적정보 범위 내의 학생회가 올린 제휴가 등록된 장소 2개와, 현재 위치를 기반으로 한 장소 2개를 랜덤으로 조회합니다.
    오전 11시 30분 - 오후 2시: 밥집 추천
    오후 2시 - 오후 5시: 카페 추천
    오후 5시 - 오후 8시: 밥집 추천
    오후 8시 - 오후 11시: 술집 추천
    추가적으로 레디스 캐시를 적용해 랜덤 장소 검색 시 Naver/Google API 호출 수를 줄이고자 했습니다.
    좌표의 경우 소수점 3자리에 반올림을 진행하며 redis에 후보군 리스트를 고정합니다. 그 후 1시간 동안은 사용자가 새로고침할 때마다 고정된 해당 리스트 안에서 랜덤으로 2개를 뽑아서 보여주게 됩니다.
    이렇게 할 경우 가까운 위치에 있는 유저들이 해당 기능을 사용하게 되면 불필요한 외부 api의 호출을 줄일 수 있게 됩니다.

📸 스크린샷 (선택)

시간대별 랜덤 장소 추천 기능 호출 시 다음과 같이 제휴가 등록된 장소 2개와 현재 위치를 기반으로 한 장소 2개가 랜덤으로 검색되어 한번에 조회됩니다.

image image

📎 참고 이슈

관련 이슈 번호 #61

Summary by CodeRabbit

  • 새로운 기능

    • 시간대별 맞춤 장소 추천 추가: 점심/카페/저녁/바 시간대에 맞춘 제휴 게시물 및 주변 장소 통합 랜덤 추천 제공
    • 신규 추천 API 엔드포인트 추가로 위치 기반 시간대별 추천 조회 가능
  • 성능 개선

    • 검색결과당 이미지 표시 수 제한(기본 3장) 적용
    • Redis 기반 장소 캐싱 도입으로 응답 속도 및 리소스 효율 향상

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

@1winhyun 1winhyun self-assigned this Jan 13, 2026
@1winhyun 1winhyun added the ✨Feat 새로운 기능 개발 label Jan 13, 2026
@1winhyun 1winhyun linked an issue Jan 13, 2026 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

Walkthrough

시간대별 장소 추천 기능을 추가했습니다. Redis 캐시 서비스, 장소/파트너십 DTO 및 매퍼, PlaceService의 추천 흐름과 컨트롤러 엔드포인트, StudentCouncilPostRepository의 파트너십 조회 쿼리가 도입되었으며 일부 메서드 시그니처·포맷팅이 변경되었습니다.

Changes

Cohort / File(s) 변경 요약
DTOs 응답 모델
src/main/java/com/campus/campus/domain/place/application/dto/response/RecommendNearByPlaceResponse.java, src/main/java/com/campus/campus/domain/place/application/dto/response/RecommendPartnershipPlaceResponse.java, src/main/java/com/campus/campus/domain/place/application/dto/response/RecommendPlaceByTimeResponse.java
주변 장소, 파트너십 장소, 시간대별 종합 응답용 3개 레코드 DTO 추가
매퍼
src/main/java/com/campus/campus/domain/place/application/mapper/PlaceMapper.java
새 DTO들을 생성하는 3개의 매핑 메서드 추가 (파트너십/주변/종합)
서비스 — 장소 추천 & 검색 변경
src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java
searchByLocationAndKeyword/searchByKeyword에 imageLimit 파라미터 추가; findRecommendations(userId, lat, lng) 공개 메서드 추가 및 추천 흐름(시간대 판단, 파트너십 조회, 주변장소 결합, Redis 연동) 도입
캐시 서비스
src/main/java/com/campus/campus/domain/place/application/service/RedisPlaceCacheService.java
SavedPlaceInfo 리스트 캐싱/조회용 Redis 서비스 추가(키 생성, TTL 1시간, 직렬화/역직렬화)
저장소 쿼리
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
PARTNERSHIP 조회용 복합 필터 JPQL 메서드 findRandomPartnershipPlace(...) 추가 및 @EntityGraph 적용
외부 클라이언트 변경
src/main/java/com/campus/campus/domain/place/infrastructure/google/GooglePlaceClient.java
fetchImages 파라미터명 limitimageLimit로 변경 및 내부 limit 적용 수정
컨트롤러 및 응답 코드
src/main/java/com/campus/campus/domain/place/presentation/PlaceController.java, src/main/java/com/campus/campus/domain/place/presentation/PlaceResponseCode.java
GET /places/random 엔드포인트 추가(RecommendPlaceByTimeResponse 반환), PlaceController에서 기존 검색 호출에 imageLimit(3) 전달, 응답코드 GET_RANDOM_PLACE_SUCCESS 추가
포맷팅(무해한 변경)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java
findSchoolPosts/findCollegePosts/findMajorPosts 시그니처 줄바꿈/포맷팅 변경(기능적 변경 없음)
사소한(문서/개행)
src/main/java/com/campus/campus/domain/user/application/service/KakaoOauthService.java
파일 말미 개행 추가(내용 변경 없음)

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant PlaceController
    participant PlaceService
    participant RedisPlaceCacheService
    participant GooglePlaceClient
    participant StudentCouncilPostRepository
    participant PlaceMapper

    User->>PlaceController: GET /places/random (userId, lat, lng)
    PlaceController->>PlaceService: findRecommendations(userId, lat, lng)
    
    PlaceService->>RedisPlaceCacheService: getCachedPlaces(lat, lng, keyword)
    alt Cache Hit
        RedisPlaceCacheService-->>PlaceService: List<SavedPlaceInfo>
    else Cache Miss
        PlaceService->>GooglePlaceClient: searchByLocationAndKeyword(lat, lng, keyword, imageLimit=3)
        GooglePlaceClient-->>PlaceService: List<SavedPlaceInfo>
        PlaceService->>RedisPlaceCacheService: cachePlaces(keyword, lat, lng, places)
    end

    PlaceService->>StudentCouncilPostRepository: findRandomPartnershipPlace(schoolId,collegeId,majorId,icon,now,pageable)
    StudentCouncilPostRepository-->>PlaceService: List<StudentCouncilPost>

    PlaceService->>PlaceMapper: toRecommendPartnershipPlaceResponse/post, toRecommendNearByPlaceResponse/savedPlace
    PlaceMapper-->>PlaceService: RecommendPartnershipPlaceResponse / RecommendNearByPlaceResponse

    PlaceService->>PlaceMapper: toRecommendPlaceByTimeResponse(type, partnerships, nearbyPlaces)
    PlaceMapper-->>PlaceService: RecommendPlaceByTimeResponse

    PlaceService-->>PlaceController: RecommendPlaceByTimeResponse
    PlaceController-->>User: CommonResponse<RecommendPlaceByTimeResponse>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • 1224kang
  • jjaeroong

Poem

🐇 토끼가 말하네, 길 따라 냄새 맡고

캐시 속 보물 한 줌, 시간에 맞춰 꺼내요
점심엔 따끈, 카페엔 달콤, 밤엔 반짝임
지도를 펼치면 추천이 폴짝폴짝
함께 걷자, 맛과 시간이 만나는 곳으로 ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 변경 내용의 주요 목적을 명확하게 나타냅니다. 시간대별 랜덤 장소 추천 기능 구현이라는 핵심 변경사항을 정확히 요약하고 있습니다.

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

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
@src/main/java/com/campus/campus/domain/place/application/dto/response/RecommendPlaceByTimeResponse.java:
- Around line 7-16: The @Schema on the RecommendPlaceByTimeResponse record's
type field is inaccurate; update its description (and example if present) to
match the actual values emitted by PlaceService (e.g., "LUNCH", "CAFE",
"DINNER", "BAR", and "잠잘시간입니다.") or alternatively change PlaceService to emit
only the documented values—ensure the documented enum/list in
RecommendPlaceByTimeResponse.type matches PlaceService behavior so API docs and
runtime are consistent.

In
@src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java:
- Around line 324-338: Boundary checks for meal/time windows are inconsistent
causing overlaps (e.g., 17:00 true for both isCafeTime and isDinnerTime); change
all end-bound checks to use half-open interval semantics [start, end) by
replacing "!now.isAfter(…)" with "now.isBefore(…)" in isCafeTime, isDinnerTime,
and isBarTime so they match isLunchTime’s logic (which already uses isBefore for
LUNCH_END) and avoid inclusive end overlaps.

In
@src/main/java/com/campus/campus/domain/place/application/service/RedisPlaceCacheService.java:
- Around line 24-25: GRID_PRECISION is set to 1000.0 which rounds coordinates to
3 decimal places but the PR requires rounding to 4 decimal places; update the
constant GRID_PRECISION in RedisPlaceCacheService to 10000.0 so methods that use
GRID_PRECISION (e.g., any coordinate rounding logic in RedisPlaceCacheService)
will round to four decimal places as documented.
🧹 Nitpick comments (3)
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (1)

286-304: @EntityGraphJOIN FETCH 중복 사용 및 하드코딩된 문자열 리터럴 검토 필요

  1. 중복 페칭: @EntityGraph(attributePaths = {"writer", ..., "place"})JOIN FETCH p.writer w, JOIN FETCH p.place pl이 동시에 사용되고 있습니다. @EntityGraph만으로 충분하므로 JOIN FETCH를 일반 JOIN으로 변경하는 것을 권장합니다.

  2. 하드코딩된 리터럴: 'PARTNERSHIP', 'SCHOOL_COUNCIL' 등이 문자열로 하드코딩되어 있습니다. 이 파일의 다른 메서드들(findRandomActivePartnerships 등)처럼 enum 파라미터를 사용하는 것이 유지보수성과 일관성 면에서 더 좋습니다.

♻️ 개선 제안
 	@EntityGraph(attributePaths = {"writer", "writer.school", "writer.college", "writer.major", "place"})
 	@Query("""
         SELECT p FROM StudentCouncilPost p
-        JOIN FETCH p.writer w
-        JOIN FETCH p.place pl
+        JOIN p.writer w
+        JOIN p.place pl
         LEFT JOIN w.school s
         LEFT JOIN w.college c
         LEFT JOIN w.major m
-        WHERE p.category = 'PARTNERSHIP'
+        WHERE p.category = :category
           AND p.thumbnailIcon = :icon
           AND :now BETWEEN p.startDateTime AND p.endDateTime
           AND w.deletedAt IS NULL
           AND (
-               (w.councilType = 'SCHOOL_COUNCIL' AND s.schoolId = :schoolId)
-            OR (w.councilType = 'COLLEGE_COUNCIL' AND c.collegeId = :collegeId)
-            OR (w.councilType = 'MAJOR_COUNCIL' AND m.majorId = :majorId)
+               (w.councilType = :schoolType AND s.schoolId = :schoolId)
+            OR (w.councilType = :collegeType AND c.collegeId = :collegeId)
+            OR (w.councilType = :majorType AND m.majorId = :majorId)
           )
         ORDER BY function('RAND')
         """)
 	List<StudentCouncilPost> findRandomPartnershipPlace(
 		@Param("schoolId") Long schoolId,
 		@Param("collegeId") Long collegeId,
 		@Param("majorId") Long majorId,
 		@Param("icon") ThumbnailIcon icon,
+		@Param("category") PostCategory category,
+		@Param("schoolType") CouncilType schoolType,
+		@Param("collegeType") CouncilType collegeType,
+		@Param("majorType") CouncilType majorType,
 		@Param("now") LocalDateTime now,
 		Pageable pageable
 	);
src/main/java/com/campus/campus/domain/place/presentation/PlaceController.java (1)

196-206: @parameter 어노테이션 누락으로 API 문서 불완전

다른 엔드포인트들(/search, /partnership 등)과 달리 lat, lng 파라미터에 @Parameter 어노테이션이 없어 Swagger 문서의 일관성이 떨어집니다.

📝 제안 수정안
 @GetMapping("/random")
 @Operation(summary = "시간대별 랜덤 장소 추천 (제휴 장소 2, 랜덤 장소 2) (홈화면)")
 public CommonResponse<RecommendPlaceByTimeResponse> getRandomPlaceByTime(
 	@CurrentUserId Long userId,
+	@Parameter(description = "현재 위치의 위도", example = "37.50415")
 	@RequestParam double lat,
+	@Parameter(description = "현재 위치의 경도", example = "126.9570")
 	@RequestParam double lng
 ) {
src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java (1)

196-198: API 응답 type 값의 일관성 문제

추천 시간대가 아닐 때 "잠잘시간입니다."라는 한글 문장을 type 필드에 반환하고 있습니다. 다른 값들(LUNCH, CAFE, DINNER, BAR)이 enum 스타일의 영문 상수인 것과 비교하면 일관성이 없어 API 소비자가 파싱하기 어렵습니다.

📝 제안 수정안
 } else {
-	return placeMapper.toRecommendPlaceByTimeResponse("잠잘시간입니다.", List.of(), List.of());
+	return placeMapper.toRecommendPlaceByTimeResponse("NONE", List.of(), List.of());
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5859b74 and 87b9a2f.

📒 Files selected for processing (11)
  • src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java
  • src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
  • src/main/java/com/campus/campus/domain/place/application/dto/response/RecommendNearByPlaceResponse.java
  • src/main/java/com/campus/campus/domain/place/application/dto/response/RecommendPartnershipPlaceResponse.java
  • src/main/java/com/campus/campus/domain/place/application/dto/response/RecommendPlaceByTimeResponse.java
  • src/main/java/com/campus/campus/domain/place/application/mapper/PlaceMapper.java
  • src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java
  • src/main/java/com/campus/campus/domain/place/application/service/RedisPlaceCacheService.java
  • src/main/java/com/campus/campus/domain/place/infrastructure/google/GooglePlaceClient.java
  • src/main/java/com/campus/campus/domain/place/presentation/PlaceController.java
  • src/main/java/com/campus/campus/domain/place/presentation/PlaceResponseCode.java
🧰 Additional context used
🧬 Code graph analysis (5)
src/main/java/com/campus/campus/domain/place/application/dto/response/RecommendPartnershipPlaceResponse.java (5)
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/application/dto/response/PartnershipPinResponse.java (1)
  • PartnershipPinResponse (5-22)
src/main/java/com/campus/campus/domain/councilpost/application/dto/response/GetActivePartnershipListForUserResponse.java (1)
  • GetActivePartnershipListForUserResponse (5-18)
src/main/java/com/campus/campus/domain/place/application/dto/response/SavedPlaceInfo.java (1)
  • SavedPlaceInfo (10-41)
src/main/java/com/campus/campus/domain/place/application/dto/response/google/GooglePlaceDetailResponse.java (1)
  • GooglePlaceDetailResponse (5-12)
src/main/java/com/campus/campus/domain/place/presentation/PlaceResponseCode.java (6)
src/main/java/com/campus/campus/domain/councilnotice/presentation/NoticeResponseCode.java (1)
  • Getter (10-23)
src/main/java/com/campus/campus/domain/place/application/exception/ErrorCode.java (1)
  • Getter (10-24)
src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostResponseCode.java (1)
  • Getter (10-27)
src/main/java/com/campus/campus/domain/manager/presentation/ManagerResponseCode.java (1)
  • Getter (10-21)
src/main/java/com/campus/campus/domain/council/presentation/StudentCouncilResponseCode.java (1)
  • Getter (10-26)
src/main/java/com/campus/campus/domain/user/presentation/UserResponseCode.java (1)
  • Getter (10-22)
src/main/java/com/campus/campus/domain/place/application/mapper/PlaceMapper.java (2)
src/main/java/com/campus/campus/domain/place/application/service/PartnershipPlaceService.java (1)
  • entry (112-128)
src/main/java/com/campus/campus/domain/councilpost/application/mapper/StudentCouncilPostMapper.java (2)
  • toGetActivePartnershipListForUserResponse (72-79)
  • toGetLikedPostResponse (169-178)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java (3)
src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
  • UserNotFoundException (5-9)
src/main/java/com/campus/campus/domain/councilpost/domain/entity/PostCategory.java (2)
  • Override (14-23)
  • Override (27-36)
src/main/java/com/campus/campus/domain/councilpost/presentation/StudentCouncilPostForUserController.java (1)
  • GetMapping (89-118)
src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java (3)
src/main/java/com/campus/campus/domain/place/application/service/RedisPlaceCacheService.java (1)
  • Service (17-68)
src/main/java/com/campus/campus/domain/councilpost/application/exception/AcademicInfoNotSetException.java (1)
  • AcademicInfoNotSetException (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 (10)
src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostForUserService.java (1)

110-141: LGTM!

메서드 시그니처와 호출부의 줄바꿈 포맷팅만 변경되었으며, 기능적 변경은 없습니다.

src/main/java/com/campus/campus/domain/place/presentation/PlaceResponseCode.java (1)

17-18: LGTM!

새로운 GET_RANDOM_PLACE_SUCCESS 응답 코드가 기존 패턴에 맞게 추가되었습니다. 시간대별 랜덤 장소 추천 기능의 성공 응답에 적합합니다.

src/main/java/com/campus/campus/domain/place/application/dto/response/RecommendPartnershipPlaceResponse.java (1)

5-23: LGTM!

제휴 장소 추천 응답을 위한 DTO가 적절하게 정의되었습니다. Swagger 문서화를 위한 @Schema 어노테이션도 잘 적용되어 있습니다.

src/main/java/com/campus/campus/domain/place/infrastructure/google/GooglePlaceClient.java (1)

54-76: LGTM!

imageLimit 파라미터 도입으로 이미지 개수 제한을 호출자가 제어할 수 있게 되었습니다. 기존 하드코딩된 값에서 유연한 구조로 개선되었습니다.

src/main/java/com/campus/campus/domain/place/application/service/RedisPlaceCacheService.java (1)

27-67: 캐시 서비스 구현 적절

Redis 캐시 로직이 잘 구현되어 있습니다:

  • null/빈 리스트 가드 처리
  • JSON 직렬화/역직렬화 예외 처리
  • 적절한 로깅 레벨 사용 (성공 시 info, 실패 시 warn)
src/main/java/com/campus/campus/domain/place/application/dto/response/RecommendNearByPlaceResponse.java (1)

10-38: LGTM!

주변 장소 추천 응답 DTO가 적절히 정의되었습니다. SavedPlaceInfo와 필드 구조가 유사한데, 응답 DTO를 별도로 분리한 것은 API 계층 분리 관점에서 좋은 접근입니다.

src/main/java/com/campus/campus/domain/place/application/mapper/PlaceMapper.java (2)

92-104: LGTM!

SavedPlaceInfoRecommendNearByPlaceResponse로 매핑하는 로직이 명확하고, 기존 매핑 패턴과 일관성이 있습니다.


81-90: The code is safe from NullPointerException. The findRandomPartnershipPlace query explicitly uses JOIN FETCH p.writer and JOIN FETCH p.place, which are INNER JOINs that guarantee both the writer and place are present. FETCH ensures eager loading, preventing any initialization issues. No defensive null checks are needed.

Likely an incorrect or invalid review comment.

src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java (2)

363-394: LGTM! Redis 캐싱과 랜덤 선택 로직

Redis 캐시를 활용한 외부 API 호출 최소화 전략이 잘 구현되어 있습니다. ArrayList로 복사 후 shuffle하여 불변 리스트 문제도 방지했습니다.


349-361: Partnership 쿼리에서 랜덤 정렬 확인 완료

쿼리가 ORDER BY function('RAND')를 통해 실제로 랜덤 정렬을 수행하고 있습니다. PageRequest.of(0, 2)는 랜덤 정렬 후 결과를 2개로 제한하는 것이므로 올바르게 구현되어 있습니다.

Comment on lines +7 to +16
public record RecommendPlaceByTimeResponse(
@Schema(description = "추천 타입 (LUNCH: 점심, CAFE: 카페, NONE: 해당 시간 아님)", example = "LUNCH")
String type,

@Schema(description = "추천 제휴 게시글 (최대 2개)")
List<RecommendPartnershipPlaceResponse> partnershipPosts,

@Schema(description = "추천 주변 장소 (최대 2개)")
List<RecommendNearByPlaceResponse> nearbyPlaces
) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

@Schema 설명과 실제 사용 값 불일치

@Schema description에 "LUNCH: 점심, CAFE: 카페, NONE: 해당 시간 아님"으로 명시되어 있으나, PlaceService에서 실제로 사용하는 값은 "LUNCH", "CAFE", "DINNER", "BAR", "잠잘시간입니다."입니다. API 문서와 실제 동작이 일치하도록 수정이 필요합니다.

📝 제안 수정안
 public record RecommendPlaceByTimeResponse(
-	@Schema(description = "추천 타입 (LUNCH: 점심, CAFE: 카페, NONE: 해당 시간 아님)", example = "LUNCH")
+	@Schema(description = "추천 타입 (LUNCH: 점심, CAFE: 카페, DINNER: 저녁, BAR: 술집, 또는 추천 시간대 아님)", example = "LUNCH")
 	String type,
📝 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.

Suggested change
public record RecommendPlaceByTimeResponse(
@Schema(description = "추천 타입 (LUNCH: 점심, CAFE: 카페, NONE: 해당 시간 아님)", example = "LUNCH")
String type,
@Schema(description = "추천 제휴 게시글 (최대 2개)")
List<RecommendPartnershipPlaceResponse> partnershipPosts,
@Schema(description = "추천 주변 장소 (최대 2개)")
List<RecommendNearByPlaceResponse> nearbyPlaces
) {
public record RecommendPlaceByTimeResponse(
@Schema(description = "추천 타입 (LUNCH: 점심, CAFE: 카페, DINNER: 저녁, BAR: 술집, 또는 추천 시간대 아님)", example = "LUNCH")
String type,
@Schema(description = "추천 제휴 게시글 (최대 2개)")
List<RecommendPartnershipPlaceResponse> partnershipPosts,
@Schema(description = "추천 주변 장소 (최대 2개)")
List<RecommendNearByPlaceResponse> nearbyPlaces
) {
🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/place/application/dto/response/RecommendPlaceByTimeResponse.java
around lines 7 - 16, The @Schema on the RecommendPlaceByTimeResponse record's
type field is inaccurate; update its description (and example if present) to
match the actual values emitted by PlaceService (e.g., "LUNCH", "CAFE",
"DINNER", "BAR", and "잠잘시간입니다.") or alternatively change PlaceService to emit
only the documented values—ensure the documented enum/list in
RecommendPlaceByTimeResponse.type matches PlaceService behavior so API docs and
runtime are consistent.

Copy link
Contributor

@jjaeroong jjaeroong left a comment

Choose a reason for hiding this comment

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

고생하셨습니다!

@our-campUS our-campUS deleted a comment from coderabbitai bot Jan 13, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
@src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java:
- Around line 64-72: The BAR_END constant in PlaceService is set to 23:30 but
should be 23:00 per PR requirements; update the BAR_END declaration (static
final ZoneId KST remains) to LocalTime.of(23, 0) so the bar recommendation
window becomes 20:00–23:00, and run related tests or usages that rely on
BAR_START/BAR_END to verify behavior.
🧹 Nitpick comments (3)
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (2)

305-312: 메서드 이름이 실제 동작과 일치하지 않습니다.

findRandomPartnershipPlace라는 이름이지만 쿼리에서는 ORDER BY p.id DESC로 정렬만 수행하고 랜덤 선택은 하지 않습니다. PR 설명에 따르면 랜덤 선택이 서비스 레이어로 이동된 것으로 보이므로, 메서드 이름을 findPartnershipPlacesByIcon 또는 findLatestPartnershipPlaces와 같이 실제 동작을 반영하도록 변경하는 것이 좋습니다.


294-302: Enum 값 처리 방식이 기존 메서드와 일관성이 없습니다.

이 파일의 다른 메서드들은 enum 비교 시 파라미터를 사용합니다:

  • findRandomActivePartnerships: :category, :councilType 파라미터 사용
  • findByUserScopeWithCursor: :majorType, :collegeType, :schoolType 파라미터 사용

하지만 findRandomPartnershipPlace 메서드는 'PARTNERSHIP', 'SCHOOL_COUNCIL' 등의 문자열 리터럴을 직접 사용합니다. 비록 @Enumerated(EnumType.STRING) 설정으로 인해 기술적으로는 동작하지만, 이 방식은 enum 값 이름이 변경되면 컴파일 타임에 오류를 감지할 수 없으며 코드베이스 전체의 패턴과도 맞지 않습니다.

파라미터 기반 접근 방식으로 통일하여 유지보수성과 타입 안정성을 높여 주세요.

♻️ 파라미터 기반 접근 방식 제안
 @EntityGraph(attributePaths = {"writer", "writer.school", "writer.college", "writer.major", "place"})
 @Query("""
     SELECT p FROM StudentCouncilPost p
     JOIN p.writer w
     JOIN p.place pl
     LEFT JOIN w.school s
     LEFT JOIN w.college c
     LEFT JOIN w.major m
-    WHERE p.category = 'PARTNERSHIP'
+    WHERE p.category = :category
       AND p.thumbnailIcon = :icon
       AND :now BETWEEN p.startDateTime AND p.endDateTime
       AND w.deletedAt IS NULL
       AND (
-           (w.councilType = 'SCHOOL_COUNCIL' AND s.schoolId = :schoolId)
-        OR (w.councilType = 'COLLEGE_COUNCIL' AND c.collegeId = :collegeId)
-        OR (w.councilType = 'MAJOR_COUNCIL' AND m.majorId = :majorId)
+           (w.councilType = :schoolType AND s.schoolId = :schoolId)
+        OR (w.councilType = :collegeType AND c.collegeId = :collegeId)
+        OR (w.councilType = :majorType AND m.majorId = :majorId)
       )
     ORDER BY p.id DESC
     """)
 List<StudentCouncilPost> findRandomPartnershipPlace(
     @Param("schoolId") Long schoolId,
     @Param("collegeId") Long collegeId,
     @Param("majorId") Long majorId,
+    @Param("category") PostCategory category,
+    @Param("schoolType") CouncilType schoolType,
+    @Param("collegeType") CouncilType collegeType,
+    @Param("majorType") CouncilType majorType,
     @Param("icon") ThumbnailIcon icon,
     @Param("now") LocalDateTime now,
     Pageable pageable
 );
src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java (1)

372-384: Redis 캐시 메서드의 파라미터 순서가 일관성이 없습니다.

getCachedPlaces(lat, lng, keyword)cachePlaces(keyword, lat, lng, places)의 파라미터 순서가 다릅니다. 내부적으로 generateKey가 동일한 순서를 사용하므로 동작에는 문제가 없지만, API 일관성을 위해 RedisPlaceCacheService의 메서드 시그니처를 통일하는 것이 좋습니다.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87b9a2f and 02332bb.

📒 Files selected for processing (2)
  • src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
  • src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java (3)
src/main/java/com/campus/campus/domain/place/application/service/RedisPlaceCacheService.java (1)
  • Service (17-68)
src/main/java/com/campus/campus/domain/councilpost/application/exception/AcademicInfoNotSetException.java (1)
  • AcademicInfoNotSetException (5-9)
src/main/java/com/campus/campus/domain/user/application/exception/UserNotFoundException.java (1)
  • UserNotFoundException (5-9)
🔇 Additional comments (7)
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (1)

17-17: LGTM!

새로운 메서드 파라미터로 사용되는 ThumbnailIcon import가 올바르게 추가되었습니다.

src/main/java/com/campus/campus/domain/place/application/service/PlaceService.java (6)

178-199: 시간대별 추천 로직이 잘 구현되어 있습니다.

타임존 처리와 사용자 검증 로직이 적절합니다. 다만, 23:00(또는 23:30)11:30 사이의 기본 메시지 "잠잘시간입니다"가 이른 아침 시간(예: 611시)에 사용자에게 어색하게 느껴질 수 있습니다. 추후 UX 개선 시 고려해 보세요.


324-338: 시간대 헬퍼 메서드가 올바르게 구현되었습니다.

경계값 처리 (START <= now < END)가 일관되게 적용되어 있어 시간대 전환 시점에서의 동작이 명확합니다.


340-347: LGTM!

협력업체 장소와 주변 장소를 조합하여 응답을 생성하는 로직이 깔끔합니다.


201-243: 이미지 제한 파라미터 전파와 비동기 처리가 적절합니다.

imageLimit 파라미터가 메서드 체인을 통해 올바르게 전달되고 있으며, CompletableFuture의 타임아웃 및 폴백 처리가 잘 구현되어 있습니다.


390-402: LGTM!

셔플을 위한 mutable 리스트 복사와 이미지 URL null 처리가 안전하게 구현되어 있습니다.


349-361: 학교 정보 null 체크는 불필요합니다.

findRecommendations() 메서드의 184-186줄에서 user.isProfileNotCompleted() 검증이 수행됩니다. isProfileNotCompleted()school == null || major == null을 반환하므로, 이 검증을 통과한 후 generateResponse()getRandomPartnerships()가 호출될 때 school은 반드시 null이 아닙니다. 따라서 NPE 위험은 없으며, 현재 코드는 안전합니다.

Likely an incorrect or invalid review comment.

@our-campUS our-campUS deleted a comment from coderabbitai bot Jan 13, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java (2)

317-344: 메서드 이름이 실제 동작과 불일치합니다.

findRandomPartnershipPlace라는 이름은 랜덤 결과를 반환할 것처럼 보이지만, 실제 쿼리는 ORDER BY p.id DESC로 최신순 정렬만 수행합니다. PR 설명에 따르면 랜덤 선택은 서비스 레이어에서 처리되므로, 메서드 이름을 실제 동작에 맞게 수정하는 것이 좋습니다.

♻️ 메서드 이름 변경 제안
-	List<StudentCouncilPost> findRandomPartnershipPlace(
+	List<StudentCouncilPost> findLatestPartnershipPlacesByIcon(
		@Param("schoolId") Long schoolId,
		@Param("collegeId") Long collegeId,
		@Param("majorId") Long majorId,
		@Param("icon") ThumbnailIcon icon,
		@Param("now") LocalDateTime now,
		Pageable pageable
	);

326-334: JPQL에서 문자열 리터럴 대신 enum 참조 또는 파라미터 사용을 권장합니다.

동일 파일 내 다른 메서드들(lines 57-60, 297-302)은 fully qualified enum 참조(com.campus.campus.domain.councilpost.domain.entity.PostCategory.PARTNERSHIP 등)를 사용하거나 파라미터(:category)를 사용합니다. 문자열 리터럴은 오타 발생 시 컴파일 타임에 감지되지 않고 런타임 오류로 이어질 수 있습니다.

♻️ enum 참조 또는 파라미터 사용 제안
        WHERE p.category = 'PARTNERSHIP'
          AND p.thumbnailIcon = :icon
          AND :now BETWEEN p.startDateTime AND p.endDateTime
          AND w.deletedAt IS NULL
          AND (
-               (w.councilType = 'SCHOOL_COUNCIL' AND s.schoolId = :schoolId)
-            OR (w.councilType = 'COLLEGE_COUNCIL' AND c.collegeId = :collegeId)
-            OR (w.councilType = 'MAJOR_COUNCIL' AND m.majorId = :majorId)
+               (w.councilType = com.campus.campus.domain.council.domain.entity.CouncilType.SCHOOL_COUNCIL AND s.schoolId = :schoolId)
+            OR (w.councilType = com.campus.campus.domain.council.domain.entity.CouncilType.COLLEGE_COUNCIL AND c.collegeId = :collegeId)
+            OR (w.councilType = com.campus.campus.domain.council.domain.entity.CouncilType.MAJOR_COUNCIL AND m.majorId = :majorId)
          )

또는 기존 findByUserScopeWithCursor 메서드(lines 237-248)처럼 파라미터로 전달:

List<StudentCouncilPost> findLatestPartnershipPlacesByIcon(
    @Param("schoolId") Long schoolId,
    @Param("collegeId") Long collegeId,
    @Param("majorId") Long majorId,
    @Param("icon") ThumbnailIcon icon,
    @Param("now") LocalDateTime now,
    @Param("schoolType") CouncilType schoolType,
    @Param("collegeType") CouncilType collegeType,
    @Param("majorType") CouncilType majorType,
    Pageable pageable
);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02332bb and 303327f.

📒 Files selected for processing (2)
  • src/main/java/com/campus/campus/domain/councilpost/domain/repository/StudentCouncilPostRepository.java
  • src/main/java/com/campus/campus/domain/user/application/service/KakaoOauthService.java
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/com/campus/campus/domain/user/application/service/KakaoOauthService.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

@1winhyun 1winhyun merged commit 71079fe into dev Jan 13, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨Feat 새로운 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 시간대별 장소 추천 (홈화면) 기능 구현

3 participants