-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT] 신규 등록 매장 조회 api #106
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새로운 "신규 음식점" 조회 엔드포인트를 추가합니다. 사용자 위치 기반 거리 계산이 선택적으로 지원되며, 최근 14일 이내에 등록된 음식점을 반환합니다. 관련 DTO, 매퍼, 리포지토리 메서드 및 서비스 로직이 추가되었습니다. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ 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: 5
🧹 Nitpick comments (4)
src/main/java/konkuk/corkCharge/domain/restaurant/repository/RestaurantRepository.java (1)
51-70: 공간 쿼리가 올바르게 구현되었으나, 거리 필터링 및 페이지네이션 추가를 권장합니다.네이티브 쿼리가 잘 구현되었습니다:
ST_Distance_Sphere를 사용한 거리 계산이 정확합니다 (미터 단위 → km 변환)- SRID 4326 사용이 적절합니다
- 잘못된 좌표(0,0) 필터링이 좋습니다
- 파라미터 바인딩이 안전합니다
다음 개선사항을 고려해보세요:
- 거리 제한: 매우 먼 거리의 식당까지 반환될 수 있습니다.
HAVING distanceKm <= ?조건 추가를 권장합니다.- 페이지네이션: 결과 수가 많을 경우 성능 및 사용자 경험 문제가 발생할 수 있습니다.
Pageable파라미터 추가를 권장합니다.🔎 개선 제안
@Query(value = """ SELECT r.restaurant_id AS restaurantId, ROUND( ST_Distance_Sphere( r.location, ST_SRID(POINT(:lon, :lat), 4326) ) / 1000, 1 ) AS distanceKm FROM restaurant r WHERE r.created_at >= :from AND ST_X(r.location) != 0 AND ST_Y(r.location) != 0 HAVING distanceKm <= :maxDistanceKm ORDER BY r.created_at DESC LIMIT :limit OFFSET :offset """, nativeQuery = true) List<NewRestaurantDistanceProjection> findNewRestaurantsWithDistance( @Param("from") LocalDateTime from, @Param("lat") double lat, @Param("lon") double lon, @Param("maxDistanceKm") double maxDistanceKm, @Param("limit") int limit, @Param("offset") int offset );또는 Spring Data의
Pageable을 활용할 수 있습니다 (네이티브 쿼리에서는 제한적이지만 가능).src/main/java/konkuk/corkCharge/domain/restaurant/service/RestaurantSummaryService.java (1)
36-36: 캐시 키 표현식을 더 명시적으로 변경해주세요.현재 캐시 키가
#p0(위치 기반 참조)로 설정되어 있습니다. 메서드의 파라미터명이restaurantId이므로,#restaurantId를 사용하는 것이 더 명시적이고 가독성이 좋습니다. 위치 기반 참조는 향후 파라미터 순서가 변경될 경우 오류가 발생할 수 있으므로, 다음과 같이 수정하기를 권장합니다:@Cacheable(cacheNames = "restaurantSummary", key = "#restaurantId")src/main/java/konkuk/corkCharge/domain/restaurant/service/RestaurantService.java (2)
258-262: 불필요한 distanceMap 생성을 제거할 수 있습니다.Lines 258-262에서 distanceMap을 별도로 구성한 후 Lines 264-271에서 다시 rows를 순회하며 map에서 거리를 조회하고 있습니다. 이는 불필요한 중간 자료구조이며, row에서 직접 getDistanceKm()을 호출하는 것이 더 효율적입니다.
🔎 코드 간소화 제안
if (request.hasUserLocation()) { List<NewRestaurantDistanceProjection> rows = restaurantRepository.findNewRestaurantsWithDistance(from, request.lat(), request.lon()); - Map<Long, Double> distanceMap = rows.stream() - .collect(Collectors.toMap( - NewRestaurantDistanceProjection::getRestaurantId, - NewRestaurantDistanceProjection::getDistanceKm - )); - return rows.stream() .map(row -> { Long id = row.getRestaurantId(); RestaurantSummary summary = restaurantSummaryService.getSummary(id); - return newRestaurantResponseMapper.toResponse(summary, distanceMap.get(id)); + return newRestaurantResponseMapper.toResponse(summary, row.getDistanceKm()); }) .toList(); }
264-284: 중복된 매핑 로직을 헬퍼 메서드로 추출하는 것을 고려하세요.Lines 265-270과 Lines 278-282에서 거의 동일한 매핑 로직이 반복되고 있습니다. 헬퍼 메서드를 추출하면 코드 중복을 줄이고 가독성을 높일 수 있습니다.
🔎 리팩토링 제안
+private GetNewRestaurantResponse mapToNewRestaurantResponse(Long restaurantId, Double distanceKm) { + RestaurantSummary summary = restaurantSummaryService.getSummary(restaurantId); + return newRestaurantResponseMapper.toResponse(summary, distanceKm); +} + @Transactional(readOnly = true) public List<GetNewRestaurantResponse> getNewRestaurants(GetNewResaurantRequest request) { LocalDateTime from = LocalDateTime.now().minusDays(NEW_RESTAURANT_DAYS); // 사용자 좌표가 있는 경우 if (request.hasUserLocation()) { List<NewRestaurantDistanceProjection> rows = restaurantRepository.findNewRestaurantsWithDistance(from, request.lat(), request.lon()); return rows.stream() - .map(row -> { - Long id = row.getRestaurantId(); - RestaurantSummary summary = restaurantSummaryService.getSummary(id); - - return newRestaurantResponseMapper.toResponse(summary, row.getDistanceKm()); - }) + .map(row -> mapToNewRestaurantResponse(row.getRestaurantId(), row.getDistanceKm())) .toList(); } // 사용자 좌표가 없는 경우 List<Restaurant> restaurants = restaurantRepository.findByCreatedAtGreaterThanEqualOrderByCreatedAtDesc(from); return restaurants.stream() - .map(r -> { - Long id = r.getRestaurantId(); - RestaurantSummary summary = restaurantSummaryService.getSummary(id); - - return newRestaurantResponseMapper.toResponse(summary, null); - }) + .map(r -> mapToNewRestaurantResponse(r.getRestaurantId(), null)) .toList(); }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
src/main/java/konkuk/corkCharge/domain/restaurant/controller/RestaurantController.javasrc/main/java/konkuk/corkCharge/domain/restaurant/dto/mapper/NewRestaurantResponseMapper.javasrc/main/java/konkuk/corkCharge/domain/restaurant/dto/request/GetNewResaurantRequest.javasrc/main/java/konkuk/corkCharge/domain/restaurant/dto/response/GetNewRestaurantResponse.javasrc/main/java/konkuk/corkCharge/domain/restaurant/repository/NewRestaurantDistanceProjection.javasrc/main/java/konkuk/corkCharge/domain/restaurant/repository/RestaurantRepository.javasrc/main/java/konkuk/corkCharge/domain/restaurant/service/RestaurantService.javasrc/main/java/konkuk/corkCharge/domain/restaurant/service/RestaurantSummaryService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (4)
src/main/java/konkuk/corkCharge/domain/restaurant/repository/NewRestaurantDistanceProjection.java (1)
3-7: LGTM!프로젝션 인터페이스 설계가 명확하고 적절합니다. 네이티브 쿼리 결과를 타입 안전하게 매핑하기 위한 좋은 접근 방식입니다.
src/main/java/konkuk/corkCharge/domain/restaurant/dto/response/GetNewRestaurantResponse.java (1)
5-17: LGTM!응답 DTO 설계가 명확하고 적절합니다.
distance필드에 대한 주석(km 단위, 위치 정보 없을 시 null)이 유용하며, nullable Double 타입 사용이 적절합니다.src/main/java/konkuk/corkCharge/domain/restaurant/repository/RestaurantRepository.java (1)
48-48: LGTM!Spring Data JPA 파생 쿼리 메서드가 올바르게 정의되었습니다. 메서드명이 명확하고 정렬 순서도 적절합니다.
src/main/java/konkuk/corkCharge/domain/restaurant/dto/mapper/NewRestaurantResponseMapper.java (1)
16-34: 매핑 로직이 올바르게 구현되었습니다.RestaurantSummary를 GetNewRestaurantResponse로 변환하는 로직이 정확합니다. 특히 Line 27에서 reviewCount가 null일 경우 0으로 기본값 처리하는 방어 로직이 잘 적용되어 있습니다.
src/main/java/konkuk/corkCharge/domain/restaurant/controller/RestaurantController.java
Outdated
Show resolved
Hide resolved
| @GetMapping("/new") | ||
| public BaseResponse<List<GetNewRestaurantResponse>> getNewRestaurant( | ||
| @RequestBody GetNewResaurantRequest request | ||
| ) { | ||
| return BaseResponse.ok(restaurantService.getNewRestaurants(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.
GET 요청에서 @RequestBody 사용은 REST 모범 사례에 위배됩니다.
GET 요청은 멱등성(idempotent)을 가지며 요청 본문(body)을 포함하지 않는 것이 HTTP 표준입니다. 일부 HTTP 클라이언트, 프록시, 캐시 서버는 GET 요청의 본문을 무시하거나 거부할 수 있습니다. 위도/경도와 같은 선택적 파라미터는 @RequestParam을 사용하거나, POST 엔드포인트로 변경하는 것을 권장합니다.
🔎 REST API 디자인 개선 제안
방안 1 (권장): @RequestParam 사용
@GetMapping("/new")
public BaseResponse<List<GetNewRestaurantResponse>> getNewRestaurant(
- @RequestBody GetNewResaurantRequest request
+ @RequestParam(required = false) Double lat,
+ @RequestParam(required = false) Double lon
) {
- return BaseResponse.ok(restaurantService.getNewRestaurants(request));
+ return BaseResponse.ok(restaurantService.getNewRestaurants(lat, lon));
}방안 2: POST 엔드포인트로 변경
-@GetMapping("/new")
+@PostMapping("/new")
public BaseResponse<List<GetNewRestaurantResponse>> getNewRestaurant(
@RequestBody GetNewResaurantRequest request
) {
return BaseResponse.ok(restaurantService.getNewRestaurants(request));
}📝 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.
| @GetMapping("/new") | |
| public BaseResponse<List<GetNewRestaurantResponse>> getNewRestaurant( | |
| @RequestBody GetNewResaurantRequest request | |
| ) { | |
| return BaseResponse.ok(restaurantService.getNewRestaurants(request)); | |
| } | |
| @PostMapping("/new") | |
| public BaseResponse<List<GetNewRestaurantResponse>> getNewRestaurant( | |
| @RequestBody GetNewResaurantRequest request | |
| ) { | |
| return BaseResponse.ok(restaurantService.getNewRestaurants(request)); | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/konkuk/corkCharge/domain/restaurant/controller/RestaurantController.java
around lines 75–80, the controller currently uses @RequestBody on a GET
endpoint; change this to follow REST best practices by using @RequestParam for
the query parameters (preferred) or convert the endpoint to POST if a body is
required. Replace the single @RequestBody parameter with explicit method
parameters annotated with @RequestParam (e.g., latitude, longitude, page, size),
mark optional params required=false or provide defaultValue as needed, update
the @GetMapping signature accordingly, pass those values into
restaurantService.getNewRestaurants(...) and remove the @RequestBody import;
alternatively, if you decide to accept a complex request body, change
@GetMapping to @PostMapping and keep @RequestBody. Ensure imports are updated
and unit/integration tests adjusted for the new contract.
| private List<String> decodeOptions(Integer optionBits, String etcContent) { | ||
| if (optionBits == null || optionBits == 0) | ||
| return List.of(); | ||
|
|
||
| int bits = optionBits; | ||
|
|
||
| return Stream.of(OptionType.values()) | ||
| .filter(type -> (bits & (1 << type.ordinal())) != 0) | ||
| .map(type -> type == ETC ? etcContent : type.getLabel()) | ||
| .filter(opt -> opt != null && !opt.isBlank()) | ||
| .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 OptionType enum definition
fd -t f "OptionType.java" -o -name "*OptionType*"Repository: CorkCharge/CorkCharge-BE
Length of output: 298
🏁 Script executed:
# Search for OptionType enum in the codebase
rg -t java "enum OptionType" -A 20Repository: CorkCharge/CorkCharge-BE
Length of output: 1496
🏁 Script executed:
# Also check for any bitmask constants or ordinal-related patterns
rg -t java "OptionType\." | head -20Repository: CorkCharge/CorkCharge-BE
Length of output: 1272
🏁 Script executed:
# Search for where the bitmask is encoded/created
rg -t java "<<.*type.*ordinal|ordinal.*<<" -B 3 -A 3Repository: CorkCharge/CorkCharge-BE
Length of output: 4560
🏁 Script executed:
# Look for integer bit encoding related to OptionType
rg -t java "OptionType" -B 5 -A 5 | grep -A 10 -B 10 "1 <<\|bit"Repository: CorkCharge/CorkCharge-BE
Length of output: 14738
🏁 Script executed:
# Check RestaurantDetailResponseMapper for encoding logic
cat src/main/java/konkuk/corkCharge/domain/restaurant/dto/mapper/RestaurantDetailResponseMapper.javaRepository: CorkCharge/CorkCharge-BE
Length of output: 2924
🏁 Script executed:
# Check ClusterListResponseMapper for encoding logic
cat src/main/java/konkuk/corkCharge/domain/restaurant/dto/mapper/ClusterListResponseMapper.javaRepository: CorkCharge/CorkCharge-BE
Length of output: 3358
ordinal() 기반 비트마스크를 명시적 값으로 변경하세요.
OptionType enum이 암시적 ordinal 값(0-5)을 사용하여 비트마스킹을 수행하는데, optionBits 데이터가 데이터베이스에 영구 저장되므로 enum 순서 변경이나 새로운 값 추가 시 기존 데이터와의 호환성이 깨집니다.
현재 enum 순서: GLASS_PROVIDED(0), ICE_PROVIDED(1), ONE_BOTTLE_FREE(2), TWO_BOTTLE_FREE(3), WINE_GLASS_PROVIDED(4), ETC(5)
권장 해결책:
- enum에 명시적 비트 값을 정의:
GLASS_PROVIDED(1), ICE_PROVIDED(2), ONE_BOTTLE_FREE(4), ... addOptionBits(), 인코딩 로직, 디코딩 로직에서type.ordinal()대신 이 명시적 값 사용- 향후 enum 수정 시에도 기존 비트 값은 변경되지 않음
영향받는 파일: CorkageStore.addOptionBits(), CorkageStoreService 인코딩 로직, NewRestaurantResponseMapper, RestaurantDetailResponseMapper, ClusterListResponseMapper의 디코딩 로직
🤖 Prompt for AI Agents
In
src/main/java/konkuk/corkCharge/domain/restaurant/dto/mapper/NewRestaurantResponseMapper.java
around lines 36-47, the decoder uses OptionType.ordinal() to test bit flags
which breaks compatibility if enum order changes; update the code and related
places to use explicit bit values instead: add an explicit int bit value to each
OptionType constant (e.g. 1,2,4,8,...), provide a getter like getBitValue() on
the enum, then replace all uses of (1 << type.ordinal()) with the explicit bit
value (type.getBitValue()) in this mapper and the other decoders
(RestaurantDetailResponseMapper, ClusterListResponseMapper) and update encoding
logic in CorkageStore.addOptionBits() and CorkageStoreService to build masks
using the new enum bit values rather than ordinal shifts so stored optionBits
remain stable across enum changes.
| public record GetNewResaurantRequest( | ||
| Double lat, | ||
| Double lon | ||
| ) { | ||
| public boolean hasUserLocation() { | ||
| return lat != null && lon != null; | ||
| } | ||
| } |
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.
클래스명의 오타를 수정해야 합니다.
클래스명이 GetNewResaurantRequest로 되어 있습니다. "Resaurant" 대신 "Restaurant"가 올바른 철자입니다. 이는 파일명과 클래스명 모두에 영향을 미치며, 코드베이스 전체에서 일관성 문제를 야기합니다.
🔎 수정 제안
- 파일명을
GetNewRestaurantRequest.java로 변경 - 클래스명을 수정:
-public record GetNewResaurantRequest(
+public record GetNewRestaurantRequest(
Double lat,
Double lon
) {- 이 클래스를 사용하는 모든 곳에서 import와 참조를 업데이트해야 합니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public record GetNewResaurantRequest( | |
| Double lat, | |
| Double lon | |
| ) { | |
| public boolean hasUserLocation() { | |
| return lat != null && lon != null; | |
| } | |
| } | |
| public record GetNewRestaurantRequest( | |
| Double lat, | |
| Double lon | |
| ) { | |
| public boolean hasUserLocation() { | |
| return lat != null && lon != null; | |
| } | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/konkuk/corkCharge/domain/restaurant/dto/request/GetNewResaurantRequest.java
around lines 3 to 10, the class and filename contain a typo: "Resaurant" should
be "Restaurant". Rename the file to GetNewRestaurantRequest.java and rename the
record to GetNewRestaurantRequest (keeping the same fields and method), then
update all imports and references across the codebase (IDE refactor/rename is
recommended) so callers, tests, and package declarations reference the new class
name.
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.
이 부분 Restaurant로 오타 수정해서 다시 올려주시면 될 것 같습니다.
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.
@simchaehyeon 네, 맞습니다. GetNewResaurantRequest를 GetNewRestaurantRequest로 수정해야 합니다.
PR 작성자분께서 다음 사항을 수정해주셔야 합니다:
- 파일명:
GetNewResaurantRequest.java→GetNewRestaurantRequest.java - 클래스명과 모든 참조 업데이트
IDE의 리팩토링 기능(Rename)을 사용하시면 관련된 모든 import와 참조가 자동으로 업데이트됩니다.
src/main/java/konkuk/corkCharge/domain/restaurant/service/RestaurantService.java
Outdated
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: 0
♻️ Duplicate comments (1)
src/main/java/konkuk/corkCharge/domain/restaurant/controller/RestaurantController.java (1)
75-80: GET 요청에 @RequestBody 사용은 REST 표준에 위배됩니다.이전 리뷰에서도 지적된 사항입니다. GET 요청에 request body를 사용하는 것은 HTTP 표준을 위반하며, 일부 클라이언트, 프록시, 캐시 서버에서 body를 무시하거나 거부할 수 있습니다.
위도/경도와 같은 선택적 파라미터는
@RequestParam을 사용하거나, POST 엔드포인트로 변경하는 것을 권장합니다.🔎 수정 방안
방안 1 (권장): @RequestParam 사용
@GetMapping("/new") public BaseResponse<List<GetNewRestaurantResponse>> getNewRestaurant( - @RequestBody GetNewRestaurantRequest request + @RequestParam(required = false) Double lat, + @RequestParam(required = false) Double lon ) { - return BaseResponse.ok(restaurantService.getNewRestaurants(request)); + GetNewRestaurantRequest request = new GetNewRestaurantRequest(lat, lon); + return BaseResponse.ok(restaurantService.getNewRestaurants(request)); }방안 2: POST 엔드포인트로 변경
-@GetMapping("/new") +@PostMapping("/new") public BaseResponse<List<GetNewRestaurantResponse>> getNewRestaurant( @RequestBody GetNewRestaurantRequest request ) { return BaseResponse.ok(restaurantService.getNewRestaurants(request)); }
🧹 Nitpick comments (1)
src/main/java/konkuk/corkCharge/domain/restaurant/dto/request/GetNewRestaurantRequest.java (1)
3-9: 부분 좌표 입력에 대한 검증 추가를 고려하세요.현재
hasUserLocation()메서드는lat와lon이 모두 null이 아닐 때만 true를 반환합니다. 그러나 하나만 null인 경우(부분 좌표)에 대한 명시적인 검증이 없습니다. 클라이언트가 실수로 한 좌표만 전송하면 조용히 "위치 없음"으로 처리되어 예상치 못한 동작이 발생할 수 있습니다.부분 좌표 입력을 명시적으로 거부하려면 검증 로직 추가를 고려하세요.
🔎 검증 로직 추가 제안
public record GetNewRestaurantRequest( Double lat, Double lon ) { public boolean hasUserLocation() { return lat != null && lon != null; } + + public boolean hasPartialLocation() { + return (lat == null) != (lon == null); + } }컨트롤러나 서비스 레이어에서
hasPartialLocation()이 true일 때 적절한 에러를 반환할 수 있습니다.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/main/java/konkuk/corkCharge/domain/restaurant/controller/RestaurantController.javasrc/main/java/konkuk/corkCharge/domain/restaurant/dto/request/GetNewRestaurantRequest.javasrc/main/java/konkuk/corkCharge/domain/restaurant/service/RestaurantService.java
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/konkuk/corkCharge/domain/restaurant/service/RestaurantService.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
#️⃣연관된 이슈
📝작업 내용
새로 등록된 매장(2주 안에 생성된 매장)을 조회할 수 있는 기능을 구현했습니다.
사용자의 좌표(위도, 경도)값을 기준으로 매장과의 거리를 공간 인덱스로 계산하여 응답하도록 구현했습니다.
사용자의 좌표값을 받지 않은 경우(null)에는 거리 계산을 하지 않고 2주 안에 새로 등록된 매장에 관한 정보만 반환하도록 구현했습니다.
💬리뷰 요구사항(선택)
Summary by CodeRabbit
릴리스 노트
✏️ Tip: You can customize this high-level summary in your review settings.